diff --git a/.gitignore b/.gitignore
index fee89f1c6ed84af38adbf97c5b2c935b33f5bfd2..b8e09d45518dac401f70d0ae138d530997fe4dc6 100644
--- a/.gitignore
+++ b/.gitignore
@@ -26,6 +26,7 @@ install_manifest.txt
 tarantool-*.tar.gz
 doc/tnt.ent
 doc/user/tarantool_user_guide.html
+doc/developer/tarantool_developer_guide.html
 third_party/luajit/src/luajit
 third_party/luajit/lib/vmdef.lua
 third_party/luajit/src/buildvm
diff --git a/CMakeLists.txt b/CMakeLists.txt
index 7adcf4e1195b2ac95a1b0a12b14c29698c631929..6904f17f5f14f269c0a1b6c650b8bd68605b850e 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -125,7 +125,7 @@ execute_process (COMMAND ${GIT} describe HEAD
 #
 set (CPACK_PACKAGE_VERSION_MAJOR "1")
 set (CPACK_PACKAGE_VERSION_MINOR "4")
-set (CPACK_PACKAGE_VERSION_PATCH "3")
+set (CPACK_PACKAGE_VERSION_PATCH "4")
 if (TARANTOOL_VERSION STREQUAL "")
     set (TARANTOOL_VERSION
         "${CPACK_PACKAGE_VERSION_MAJOR}.${CPACK_PACKAGE_VERSION_MINOR}.${CPACK_PACKAGE_VERSION_PATCH}")
@@ -171,6 +171,16 @@ include_directories("${PROJECT_SOURCE_DIR}/connector/c/tnt/include")
 include_directories("${PROJECT_SOURCE_DIR}/connector/c/tntsql/include")
 include_directories("${PROJECT_SOURCE_DIR}/connector/c/tntnet/include")
 
+#
+# Specify prefixes
+#
+if (NOT DEFINED CMAKE_SYSCONF_DIR)
+    set (CMAKE_SYSCONF_DIR "${CMAKE_INSTALL_PREFIX}/etc")
+endif()
+if (NOT DEFINED CMAKE_LOCALSTATE_DIR)
+    set (CMAKE_LOCALSTATE_DIR "${CMAKE_INSTALL_PREFIX}/var")
+endif()
+
 #
 # Now handle all configuration options.
 #
@@ -246,7 +256,7 @@ add_subdirectory(doc EXCLUDE_FROM_ALL)
 add_subdirectory(extra)
 
 install (FILES README LICENSE doc/box-protocol.txt
-         DESTINATION doc)
+         DESTINATION share/doc/tarantool)
 
 include (cmake/tarantool_cpack.cmake)
 #
diff --git a/cfg/tarantool_box_cfg.c b/cfg/tarantool_box_cfg.c
index 71301db96394f372497b2b45ad237f86af3a9cf1..b0d6e2747e52fb186fd95f22d0dc86b9d3cf25d0 100644
--- a/cfg/tarantool_box_cfg.c
+++ b/cfg/tarantool_box_cfg.c
@@ -1687,7 +1687,7 @@ tarantool_cfg_iterator_next(tarantool_cfg_iterator_t* i, tarantool_cfg *c, char
 							out_warning(CNF_NOMEMORY, "No memory to output value");
 							return NULL;
 						}
-						sprintf(*v, "%s", c->space[i->idx_name__space]->enabled ? "true" : "false");
+						sprintf(*v, "%s", c->space[i->idx_name__space]->enabled == -1 ? "false" : c->space[i->idx_name__space]->enabled ? "true" : "false");
 						snprintf(buf, PRINTBUFLEN-1, "space[%d].enabled", i->idx_name__space);
 						i->state = S_name__space__cardinality;
 						return buf;
@@ -1740,7 +1740,7 @@ tarantool_cfg_iterator_next(tarantool_cfg_iterator_t* i, tarantool_cfg *c, char
 										out_warning(CNF_NOMEMORY, "No memory to output value");
 										return NULL;
 									}
-									sprintf(*v, "%s", c->space[i->idx_name__space]->index[i->idx_name__space__index]->unique ? "true" : "false");
+									sprintf(*v, "%s", c->space[i->idx_name__space]->index[i->idx_name__space__index]->unique == -1 ? "false" : c->space[i->idx_name__space]->index[i->idx_name__space__index]->unique ? "true" : "false");
 									snprintf(buf, PRINTBUFLEN-1, "space[%d].index[%d].unique", i->idx_name__space, i->idx_name__space__index);
 									i->state = S_name__space__index__key_field;
 									return buf;
diff --git a/client/tarantool/CMakeLists.txt b/client/tarantool/CMakeLists.txt
index 14254f5d11b8667949a5d618403e1f666ee9ea76..cdaf531fd4304ddcc02e994d060ef04f66b53392 100644
--- a/client/tarantool/CMakeLists.txt
+++ b/client/tarantool/CMakeLists.txt
@@ -13,6 +13,7 @@ endif()
 set (cli_deps readline history)
 
 # trying to resolve readline dependencies
+#
 find_library(WITH_NCURSES_LIB NAMES "ncurses")
 if (WITH_NCURSES_LIB)
 	set(cli_deps ${cli_deps} ncurses)
@@ -31,4 +32,4 @@ add_executable(${cli} ${cli_sources} ${CMAKE_SOURCE_DIR}/core/errcode.c)
 set_target_properties(${cli} PROPERTIES COMPILE_FLAGS "${core_cflags}")
 target_link_libraries (${cli} ${cli_libs})
 
-install (PROGRAMS ${cli} DESTINATION bin)
+install (TARGETS ${cli} DESTINATION bin)
diff --git a/client/tarantool/tnt.c b/client/tarantool/tnt.c
index 563571feb7c7579a411e7c053075b56e92902594..1c80da4d5ba3eedb2f594659d8d3affda11f225b 100644
--- a/client/tarantool/tnt.c
+++ b/client/tarantool/tnt.c
@@ -36,6 +36,7 @@
 #include <readline/readline.h>
 #include <readline/history.h>
 
+#include <errcode.h>
 #include <third_party/gopt/gopt.h>
 #include <connector/c/tnt/include/tnt.h>
 #include <connector/c/tntsql/include/tnt_sql.h>
@@ -73,14 +74,11 @@ static int query_reply_handle(struct tnt_stream *t, struct tnt_reply *r) {
 		break;
 	}
 	if (tnt_error(t) != TNT_EOK) {
-		printf("FAIL, %s (op: %d, reqid: %d, code: %d, count: %d)\n",
-			tnt_strerror(t),
-			r->op,
-			r->reqid,
-			r->code,
-			r->count);
-		if (r->error)
-			printf("error: %s\n", r->error);
+		printf("ERROR, %s\n", tnt_strerror(t));
+		return -1;
+	} else if (r->code != 0) {
+		printf("ERROR, %s (%s)\n",
+		       ((r->error) ? r->error : ""), tnt_errcode_str(r->code >> 8));
 		return -1;
 	}
 	printf("OK, %d rows affected\n", r->count);
@@ -137,15 +135,15 @@ static int
 query(struct tnt_stream *t, char *q)
 {
 	char *e = NULL;
-	int ops = tnt_query(t, q, strlen(q), &e);
-	if (ops == -1) {
+	int rc = tnt_query(t, q, strlen(q), &e);
+	if (rc == -1) {
 		if (e) {
 			printf("error: %s", e);
 			free(e);
 		}
 		return -1;
 	}
-	int rc = tnt_flush(t);
+	rc = tnt_flush(t);
 	if (rc < 0) {
 		printf("error: %s\n", tnt_strerror(t));
 		return -1;
diff --git a/connector/c/tnt/CMakeLists.txt b/connector/c/tnt/CMakeLists.txt
index 4cb0291af8bfeb9bd54e1dea96ee48aa371eda9f..0aa3c6de65c096b4cac03b36f197a58d82216a69 100644
--- a/connector/c/tnt/CMakeLists.txt
+++ b/connector/c/tnt/CMakeLists.txt
@@ -58,7 +58,7 @@ set_target_properties(tnt PROPERTIES COMPILE_FLAGS "${tnt_cflags}")
 # Shared library
 #
 
-project(tnt)
+project(tnt_shared)
 add_library(tnt_shared SHARED ${tnt_sources})
 set_target_properties(tnt_shared PROPERTIES OUTPUT_NAME tnt)
 set_target_properties(tnt_shared PROPERTIES COMPILE_FLAGS "${tnt_cflags}")
diff --git a/connector/c/tnt/include/tnt.h b/connector/c/tnt/include/tnt.h
index be0d1ca0ae5c116173e8d6d82e7c9e2e8d7eaeb7..cf50e685ca9a6a74ce3f47f7e3340d015132d645 100644
--- a/connector/c/tnt/include/tnt.h
+++ b/connector/c/tnt/include/tnt.h
@@ -26,6 +26,10 @@
  * SUCH DAMAGE.
  */
 
+#ifdef __cplusplus
+extern "C" {
+#endif
+
 #include <stdint.h>
 #include <stdarg.h>
 
@@ -44,4 +48,8 @@
 #include <tnt_call.h>
 #include <tnt_select.h>
 
+#ifdef __cplusplus
+} /* extern "C" */
+#endif
+
 #endif /* TNT_H_INCLUDED */
diff --git a/connector/c/tnt/tnt_call.c b/connector/c/tnt/tnt_call.c
index 13e0f89fc74ed9b1eceb404c05c918a753f16611..23bf002c717053ec229d7ff303c1ebf0219d5b75 100644
--- a/connector/c/tnt/tnt_call.c
+++ b/connector/c/tnt/tnt_call.c
@@ -65,6 +65,8 @@ tnt_call(struct tnt_stream *s, uint32_t flags, char *proc,
 	hdr.type = TNT_OP_CALL;
 	hdr.len = sizeof(struct tnt_header_call) +
 		  proc_enc_size + proc_len + args->size;
+	if (args->size == 0)
+		hdr.len += 4;
 	hdr.reqid = s->reqid;
 	/* filling call header */
 	struct tnt_header_call hdr_call;
@@ -79,7 +81,13 @@ tnt_call(struct tnt_stream *s, uint32_t flags, char *proc,
 	v[2].iov_len  = proc_enc_size;
 	v[3].iov_base = proc;
 	v[3].iov_len  = proc_len;
-	v[4].iov_base = args->data;
-	v[4].iov_len  = args->size;
+	if (args->size == 0) {
+		uint32_t argc = 0;
+		v[4].iov_base = &argc;
+		v[4].iov_len  = 4;
+	} else {
+		v[4].iov_base = args->data;
+		v[4].iov_len  = args->size;
+	}
 	return s->writev(s, v, 5);
 }
diff --git a/connector/c/tnt/tnt_iter.c b/connector/c/tnt/tnt_iter.c
index eb95cfbd0dbc9cc63ec015cf9c4e3c991be64ec7..0075ef929499c073dcd78a0a2d3643981bdc218a 100644
--- a/connector/c/tnt/tnt_iter.c
+++ b/connector/c/tnt/tnt_iter.c
@@ -196,7 +196,7 @@ struct tnt_iter *tnt_iter_stream(struct tnt_iter *i, struct tnt_stream *s) {
 	i = tnt_iter_tryalloc(i);
 	if (i == NULL)
 		return NULL;
-	i->type = TNT_ITER_LIST;
+	i->type = TNT_ITER_STREAM;
 	i->next = tnt_iter_stream_next;
 	i->rewind = NULL;
 	i->free = tnt_iter_stream_free;
diff --git a/connector/c/tnt/tnt_reply.c b/connector/c/tnt/tnt_reply.c
index 3f58b1210b715b9c2257f3cfe61cd16b6ba7fc32..f8313e1e2fb545b85af71a80ec6c0a8fb278a3b3 100644
--- a/connector/c/tnt/tnt_reply.c
+++ b/connector/c/tnt/tnt_reply.c
@@ -179,8 +179,10 @@ int tnt_reply_from(struct tnt_reply *r, tnt_replyf_t rcv, void *ptr) {
  *
  * returns zero on fully read reply, or NULL on error.
 */
-static ssize_t tnt_reply_cb(size_t *off, char *buf, ssize_t size) {
-	memcpy(buf, buf + *off, size);
+static ssize_t tnt_reply_cb(void *ptr[2], char *buf, ssize_t size) {
+	char *src = ptr[0];
+	ssize_t *off = ptr[1];
+	memcpy(buf, src + *off, size);
 	*off += size;
 	return size;
 }
@@ -195,13 +197,14 @@ int tnt_reply(struct tnt_reply *r, char *buf, size_t size, size_t *off) {
 		return 1;
 	}
 	struct tnt_header *hdr = (struct tnt_header*)buf;
-	if (size < hdr->len) {
+	if (size < sizeof(struct tnt_header) + hdr->len) {
 		if (off)
-			*off = hdr->len - size;
+			*off = (sizeof(struct tnt_header) + hdr->len) - size;
 		return 1;
 	}
 	size_t offv = 0;
-	int rc = tnt_reply_from(r, (tnt_replyf_t)tnt_reply_cb, &offv);
+	void *ptr[2] = { buf, &offv };
+	int rc = tnt_reply_from(r, (tnt_replyf_t)tnt_reply_cb, ptr);
 	if (off)
 		*off = offv;
 	return rc;
diff --git a/connector/c/tntnet/CMakeLists.txt b/connector/c/tntnet/CMakeLists.txt
index 9fb73cf3202cc9dab6bbd2158363c8ae7ae4c386..2df1f3a72febc49db51171bda9452de3eb72be00 100644
--- a/connector/c/tntnet/CMakeLists.txt
+++ b/connector/c/tntnet/CMakeLists.txt
@@ -49,7 +49,7 @@ set_target_properties(tntnet PROPERTIES COMPILE_FLAGS "${tntnet_cflags}")
 # Shared library
 #
 
-project(tntnet)
+project(tntnet_shared)
 add_library(tntnet_shared SHARED ${tntnet_sources})
 set_target_properties(tntnet_shared PROPERTIES OUTPUT_NAME tntnet)
 set_target_properties(tntnet_shared PROPERTIES COMPILE_FLAGS "${tntnet_cflags}")
diff --git a/connector/c/tntnet/tnt_io.c b/connector/c/tntnet/tnt_io.c
index ea04533311d5a1725022f5f0b1eb7fd3e5dff658..abee2ec67bf984e057745be8e79a6bfa745cb632 100644
--- a/connector/c/tntnet/tnt_io.c
+++ b/connector/c/tntnet/tnt_io.c
@@ -251,10 +251,10 @@ tnt_io_send_raw(struct tnt_stream_net *s, char *buf, size_t size, int all)
 	do {
 		ssize_t r;
 		if (s->sbuf.tx) {
-			r = s->sbuf.tx(s->sbuf.buf, buf, size);
+			r = s->sbuf.tx(s->sbuf.buf, buf + off, size - off);
 		} else {
 			do {
-				r = send(s->fd, buf, size, 0);
+				r = send(s->fd, buf + off, size - off, 0);
 			} while (r == -1 && (errno == EINTR));
 		}
 		if (r <= 0) {
@@ -368,10 +368,10 @@ tnt_io_recv_raw(struct tnt_stream_net *s, char *buf, size_t size, int all)
 	do {
 		ssize_t r;
 		if (s->rbuf.tx) {
-			r = s->rbuf.tx(s->rbuf.buf, buf, size);
+			r = s->rbuf.tx(s->rbuf.buf, buf + off, size - off);
 		} else {
 			do {
-				r = recv(s->fd, buf, size, 0);
+				r = recv(s->fd, buf + off, size - off, 0);
 			} while (r == -1 && (errno == EINTR));
 		}
 		if (r <= 0) {
diff --git a/connector/c/tntsql/CMakeLists.txt b/connector/c/tntsql/CMakeLists.txt
index 9b011663aaea88320dce611bf08dac0d295d7510..49ac24c258eaa5d493e2a193f099d9afdcabcf90 100644
--- a/connector/c/tntsql/CMakeLists.txt
+++ b/connector/c/tntsql/CMakeLists.txt
@@ -48,7 +48,7 @@ set_target_properties(tntsql PROPERTIES COMPILE_FLAGS "${tntsql_cflags}")
 # Shared library
 #
 
-project(tntsql)
+project(tntsql_shared)
 add_library(tntsql_shared SHARED ${tntsql_sources})
 set_target_properties(tntsql_shared PROPERTIES OUTPUT_NAME tntsql)
 set_target_properties(tntsql_shared PROPERTIES COMPILE_FLAGS "${tntsql_cflags}")
diff --git a/connector/c/tntsql/include/tnt_lex.h b/connector/c/tntsql/include/tnt_lex.h
index aca782458dc679b56b475ad2865b043cafce2fd1..921d01a32c6f70fd6f5464ae382f75e769828336 100644
--- a/connector/c/tntsql/include/tnt_lex.h
+++ b/connector/c/tntsql/include/tnt_lex.h
@@ -46,9 +46,11 @@ enum {
 	TNT_TK_DELETE,
 	TNT_TK_FROM,
 	TNT_TK_INSERT,
+	TNT_TK_REPLACE,
 	TNT_TK_INTO,
 	TNT_TK_VALUES,
 	TNT_TK_SELECT,
+	TNT_TK_LIMIT,
 	TNT_TK_CALL,
 	TNT_TK_OR
 };
diff --git a/connector/c/tntsql/tnt_lex.c b/connector/c/tntsql/tnt_lex.c
index 3f1d1e70be9b86cd9798edfeabe30b1fcb33bb28..96ddc5715bea034406f34921df2cb14b7c8210a9 100644
--- a/connector/c/tntsql/tnt_lex.c
+++ b/connector/c/tntsql/tnt_lex.c
@@ -136,20 +136,22 @@ static struct {
 	int tk;
 } tnt_keywords[] =
 {
-	{  "PING",   4, TNT_TK_PING },
-	{  "UPDATE", 6, TNT_TK_UPDATE },
-	{  "SET",    3, TNT_TK_SET },
-	{  "WHERE",  5, TNT_TK_WHERE },
-	{  "SPLICE", 6, TNT_TK_SPLICE },
-	{  "DELETE", 6, TNT_TK_DELETE },
-	{  "FROM",   4, TNT_TK_FROM },
-	{  "INSERT", 6, TNT_TK_INSERT },
-	{  "INTO",   4, TNT_TK_INTO },
-	{  "VALUES", 6, TNT_TK_VALUES },
-	{  "SELECT", 6, TNT_TK_SELECT },
-	{  "OR",     2, TNT_TK_OR },
-	{  "CALL",   4, TNT_TK_CALL },
-	{  NULL,     0, TNT_TK_NONE }
+	{  "PING",    4, TNT_TK_PING },
+	{  "UPDATE",  6, TNT_TK_UPDATE },
+	{  "SET",     3, TNT_TK_SET },
+	{  "WHERE",   5, TNT_TK_WHERE },
+	{  "SPLICE",  6, TNT_TK_SPLICE },
+	{  "DELETE",  6, TNT_TK_DELETE },
+	{  "FROM",    4, TNT_TK_FROM },
+	{  "INSERT",  6, TNT_TK_INSERT },
+	{  "REPLACE", 7, TNT_TK_REPLACE },
+	{  "INTO",    4, TNT_TK_INTO },
+	{  "VALUES",  6, TNT_TK_VALUES },
+	{  "SELECT",  6, TNT_TK_SELECT },
+	{  "OR",      2, TNT_TK_OR },
+	{  "LIMIT",   5, TNT_TK_LIMIT },
+	{  "CALL",    4, TNT_TK_CALL },
+	{  NULL,      0, TNT_TK_NONE }
 };
 
 char*
@@ -157,7 +159,7 @@ tnt_lex_nameof(int tk)
 {
 	/* system tokens */
 	switch (tk) {
-	case TNT_TK_EOF: return "EOF";
+	case TNT_TK_EOF: return "End-Of-Statement";
 	case TNT_TK_ERROR: return "ERROR";
 	case TNT_TK_NUM: return "NUM";
 	case TNT_TK_STRING: return "STRING";
diff --git a/connector/c/tntsql/tnt_sql.c b/connector/c/tntsql/tnt_sql.c
index 0c3b5943f4c068e7dc3205524d6e0684175148a1..c8e9fc154e269dffa7650a969c52ac4c2793921c 100644
--- a/connector/c/tntsql/tnt_sql.c
+++ b/connector/c/tntsql/tnt_sql.c
@@ -41,7 +41,6 @@
 struct tnt_sql {
 	struct tnt_stream *s;
 	struct tnt_lex *l;
-	int ops;
 	char *error;
 };
 
@@ -115,12 +114,14 @@ tnt_sqltryv(struct tnt_sql *sql, int tk, struct tnt_tk **tkp) {
 /* key-value parsing for tuple operation. */
 
 static bool
-tnt_sql_kv(struct tnt_sql *sql, struct tnt_tuple *tu, bool key)
+tnt_sql_keyval(struct tnt_sql *sql, struct tnt_tuple *tu, bool key, struct tnt_tk **kt)
 {
 	/* key */
 	struct tnt_tk *k = NULL;
 	if (key && (!tnt_sqltkv(sql, TNT_TK_KEY, &k) || !tnt_sqltk(sql, '=')))
 		return false;
+	if (kt)
+		*kt = k;
 	/* value */
 	struct tnt_tk *v = NULL;
 	if (tnt_lex(sql->l, &v) == TNT_TK_ERROR)
@@ -135,6 +136,27 @@ tnt_sql_kv(struct tnt_sql *sql, struct tnt_tuple *tu, bool key)
 	return true;
 }
 
+static bool
+tnt_sql_kv(struct tnt_sql *sql, struct tnt_tuple *tu, bool key) {
+	return tnt_sql_keyval(sql, tu, key, NULL);
+}
+
+static bool
+tnt_sql_kv_select(struct tnt_sql *sql, struct tnt_tuple *tu, int32_t *index)
+{
+	struct tnt_tk *key = NULL;
+	bool rc = tnt_sql_keyval(sql, tu, true, &key);
+	if (rc == false)
+		return false;
+	if (*index == -1)
+		*index = TNT_TK_I(key);
+	else
+	if (*index != TNT_TK_I(key))
+		return tnt_sql_error(sql, key,
+				     "select key values must refer to the same index");
+	return true;
+}
+
 #define tnt_expect(a) \
 	do { if (!(a)) goto error; } while (0)
 
@@ -223,6 +245,7 @@ tnt_sql_stmt_update(struct tnt_sql *sql, struct tnt_tuple *tu, struct tnt_stream
 	tnt_expect(tnt_sqltk(sql, TNT_TK_WHERE));
 	/* predicate */
 	tnt_expect(tnt_sql_kv(sql, tu, true));
+	tnt_expect(tnt_sqltk(sql, TNT_TK_EOF));
 	if (tnt_update(sql->s, TNT_TK_I(tn), 0, tu, u) == -1) {
 		tnt_sql_error(sql, tn, "update failed");
 		goto error;
@@ -244,11 +267,13 @@ tnt_sql_stmt(struct tnt_sql *sql)
 	tnt_list_init(&tuples);
 	tnt_buf(&update);
 
+	int flags = 0;
 	struct tnt_tk *tk = NULL, *tn = NULL;
 	bool rc = false;
 	switch (tnt_lex(sql->l, &tk)) {
-	/* INSERT [INTO] TABLE VALUES ( list ) */
+	/* <INSERT|REPLACE> [INTO] TABLE VALUES ( list ) */
 	case TNT_TK_INSERT:
+	case TNT_TK_REPLACE:
 		tnt_sqltry(sql, TNT_TK_INTO);
 		if (sql->error)
 			goto error;
@@ -263,18 +288,20 @@ tnt_sql_stmt(struct tnt_sql *sql)
 				goto error;
 			break;
 		}
+		flags = TNT_FLAG_ADD;
+		if (tk->tk == TNT_TK_REPLACE)
+			flags = TNT_FLAG_REPLACE;
 		tnt_expect(tnt_sqltk(sql, ')'));
-		if (tnt_insert(sql->s, TNT_TK_I(tn), 0, &tu) == -1) {
+		tnt_expect(tnt_sqltk(sql, TNT_TK_EOF));
+		if (tnt_insert(sql->s, TNT_TK_I(tn), flags, &tu) == -1) {
 			tnt_sql_error(sql, tk, "insert failed");
 			goto error;
 		}
-		sql->ops++;
 		break;
 	/* UPDATE TABLE SET operations WHERE predicate */
 	case TNT_TK_UPDATE:
 		if (!tnt_sql_stmt_update(sql, &tu, &update))
 			goto error;
-		sql->ops++;
 		break;
 	/* DELETE FROM TABLE WHERE predicate */
 	case TNT_TK_DELETE:
@@ -283,33 +310,43 @@ tnt_sql_stmt(struct tnt_sql *sql)
 		tnt_expect(tnt_sqltk(sql, TNT_TK_WHERE));
 		/* predicate */
 		tnt_expect(tnt_sql_kv(sql, &tu, true));
+		tnt_expect(tnt_sqltk(sql, TNT_TK_EOF));
 		if (tnt_delete(sql->s, TNT_TK_I(tn), 0, &tu) == -1) {
 			tnt_sql_error(sql, tk, "delete failed"); 
 			goto error;
 		}
-		sql->ops++;
 		break;
-	/* SELECT * FROM TABLE WHERE predicate OR predicate... */
-	case TNT_TK_SELECT:
+	/* SELECT * FROM TABLE WHERE predicate OR predicate... LIMIT NUM */
+	case TNT_TK_SELECT: {
 		tnt_expect(tnt_sqltk(sql, '*'));
 		tnt_expect(tnt_sqltk(sql, TNT_TK_FROM));
 		tnt_expect(tnt_sqltkv(sql, TNT_TK_TABLE, &tn));
 		tnt_expect(tnt_sqltk(sql, TNT_TK_WHERE));
+		int32_t index = -1;
 		while (1) {
 			struct tnt_tuple *tup = tnt_list_at(&tuples, NULL);
-			tnt_expect(tnt_sql_kv(sql, tup, true));
+			tnt_expect(tnt_sql_kv_select(sql, tup, &index));
 			if (tnt_sqltry(sql, TNT_TK_OR))
 				continue;
 			if (sql->error)
 				goto error;
 			break;
 		}
-		if (tnt_select(sql->s, TNT_TK_I(tn), 0, 0, 1000, &tuples) == -1) {
+		uint32_t limit = UINT32_MAX;
+		if (tnt_sqltry(sql, TNT_TK_LIMIT)) {
+			struct tnt_tk *ltk;
+			tnt_expect(tnt_sqltkv(sql, TNT_TK_NUM, &ltk));
+			limit = TNT_TK_I(ltk);
+		} else
+		if (sql->error)
+			goto error;
+		tnt_expect(tnt_sqltk(sql, TNT_TK_EOF));
+		if (tnt_select(sql->s, TNT_TK_I(tn), index, 0, limit, &tuples) == -1) {
 			tnt_sql_error(sql, tk, "select failed");
 			goto error;
 		}
-		sql->ops++;
 		break;
+	}
 	/* CALL NAME[{.NAME}+](STRING [{,STRING}+]) */
 	case TNT_TK_CALL: {
 		char proc[512];
@@ -342,26 +379,28 @@ tnt_sql_stmt(struct tnt_sql *sql)
 		}
 		tnt_expect(tnt_sqltk(sql, ')'));
 noargs:
+		tnt_expect(tnt_sqltk(sql, TNT_TK_EOF));
 		if (tnt_call(sql->s, 0, proc, &tu) == -1) {
 			tnt_sql_error(sql, tk, "call failed"); 
 			goto error;
 		}
-		sql->ops++;
 		break;
 	}
 	/* PING */
 	case TNT_TK_PING:
+		tnt_expect(tnt_sqltk(sql, TNT_TK_EOF));
 		if (tnt_ping(sql->s) == -1) {
 			tnt_sql_error(sql, tk, "ping failed"); 
 			goto error;
 		}
-		sql->ops++;
+		break;
+	case TNT_TK_EOF:
 		break;
 	case TNT_TK_ERROR:
 		return tnt_sql_error(sql, tk, "%s", sql->l->error);
 	default:
 		return tnt_sql_error(sql, tk,
-			"insert, update, delete, select, call, ping are expected");
+			"insert, replace, update, delete, select, call, ping are expected");
 	}
 	rc = true;
 error:
@@ -376,24 +415,7 @@ tnt_sql_stmt(struct tnt_sql *sql)
 /* primary sql grammar parsing function. */
 
 static bool tnt_sql(struct tnt_sql *sql) {
-	struct tnt_tk *tk;
-	while (1) {
-		switch (tnt_lex(sql->l, &tk)) {
-		case TNT_TK_ERROR:
-			return tnt_sql_error(sql, NULL, "%s", sql->l->error);
-		case TNT_TK_EOF:
-			return true;
-		default:
-			tnt_lex_push(sql->l, tk);
-			if (!tnt_sql_stmt(sql))
-				return false;
-			if (tnt_sqltry(sql, ';'))
-				continue;
-			if (sql->error)
-				return false;
-			break;
-		}
-	}
+	return tnt_sql_stmt(sql);
 }
 
 /*
@@ -406,7 +428,7 @@ static bool tnt_sql(struct tnt_sql *sql) {
  * qsize - query size
  * e     - error description string
  * 
- * returns number of operations processed on succes, or -1 on error
+ * returns 0 on success, or -1 on error
  * and string description returned (must be freed after use).
 */
 int
@@ -415,7 +437,7 @@ tnt_query(struct tnt_stream *s, char *q, size_t qsize, char **e)
 	struct tnt_lex l;
 	if (!tnt_lex_init(&l, (unsigned char*)q, qsize))
 		return -1;
-	struct tnt_sql sql = { s, &l, 0, NULL};
+	struct tnt_sql sql = { s, &l, NULL };
 	bool ret = tnt_sql(&sql);
 	if (e) {
 		*e = sql.error;
@@ -424,7 +446,7 @@ tnt_query(struct tnt_stream *s, char *q, size_t qsize, char **e)
 			tnt_mem_free(sql.error);
 	}
 	tnt_lex_free(&l);
-	return (ret) ? sql.ops : -1;
+	return (ret) ? 0 : -1;
 }
 
 /*
@@ -450,11 +472,12 @@ tnt_query_is(char *q, size_t qsize)
 	case TNT_TK_EOF:
 		break;
 	default:
-		if (tk->tk == TNT_TK_PING ||
-		    tk->tk == TNT_TK_INSERT ||
-		    tk->tk == TNT_TK_UPDATE ||
-		    tk->tk == TNT_TK_SELECT ||
-		    tk->tk == TNT_TK_DELETE ||
+		if (tk->tk == TNT_TK_PING    ||
+		    tk->tk == TNT_TK_INSERT  ||
+		    tk->tk == TNT_TK_REPLACE ||
+		    tk->tk == TNT_TK_UPDATE  ||
+		    tk->tk == TNT_TK_SELECT  ||
+		    tk->tk == TNT_TK_DELETE  ||
 		    tk->tk == TNT_TK_CALL)
 			rc = 1;
 		break;
diff --git a/connector/perl/MANIFEST b/connector/perl/MANIFEST
index a6c5f9954c52a8f15db2066861100b238c79a9e3..391d3da256473205118072f9920d131ebfed47a7 100644
--- a/connector/perl/MANIFEST
+++ b/connector/perl/MANIFEST
@@ -1,5 +1,15 @@
 Makefile.PL
 MANIFEST
 lib/MR/IProto.pm
+lib/MR/IProto/Cluster.pm
+lib/MR/IProto/Cluster/Server.pm
+lib/MR/IProto/Connection.pm
+lib/MR/IProto/Connection/Async.pm
+lib/MR/IProto/Connection/Sync.pm
+lib/MR/IProto/Error.pm
+lib/MR/IProto/Message.pm
+lib/MR/IProto/Request.pm
+lib/MR/IProto/Response.pm
 lib/MR/SilverBox.pm
-lib/MR/Storage/Const.pm
+lib/MR/Tarantool/Box.pm
+lib/MR/Tarantool/Box/Singleton.pm
diff --git a/connector/perl/Makefile.PL b/connector/perl/Makefile.PL
index 08d6dabb058446d6641780f0d15dba3c88ece0ae..362a6cdb8a593079e02e451eb25e434fd51e2def 100644
--- a/connector/perl/Makefile.PL
+++ b/connector/perl/Makefile.PL
@@ -1,16 +1,19 @@
 use ExtUtils::MakeMaker;
 
 WriteMakefile(
-    NAME          => "MR::SilverBox",
-    VERSION_FROM  => "lib/MR/SilverBox.pm",
+    NAME          => "MR::Tarantool",
+    VERSION_FROM  => "lib/MR/Tarantool/Box.pm",
     MAKEFILE      => 'Makefile',
     PREREQ_PM     => {
         'Scalar::Util'      => 0,
+        'AnyEvent'          => 0,
         'List::Util'        => 0,
         'List::MoreUtils'   => 0,
-        'Time::HiRes'       => 0,
+        'Mouse'             => 0,
+        'MRO::Compat'       => 0,
         'String::CRC32'     => 0,
+        'Time::HiRes'       => 0,
         'Exporter'          => 0,
-        'Fcntl'             => 0,
+        'Class::Singleton'  => 0,
     },
 );
diff --git a/connector/perl/lib/MR/IProto.pm b/connector/perl/lib/MR/IProto.pm
index f32ee14ac14824d4cc52e27b9e7599be19df10da..152b226620637bd80f53aadeb40cb7fd311f84e0 100644
--- a/connector/perl/lib/MR/IProto.pm
+++ b/connector/perl/lib/MR/IProto.pm
@@ -1,727 +1,661 @@
 package MR::IProto;
 
-use strict;
+=head1 NAME
 
-use Socket qw(PF_INET SOCK_STREAM SOL_SOCKET SO_SNDTIMEO SO_RCVTIMEO SO_KEEPALIVE TCP_NODELAY);
-use String::CRC32 qw(crc32);
-use Time::HiRes qw/time sleep/;
-use Fcntl;
+MR::IProto - iproto network protocol client
 
-use vars qw($VERSION $PROTO_TCP %sockets);
-$VERSION = 0;
+=head1 SYNOPSIS
 
-use overload '""' => sub { "$_[0]->{name}\[$_[0]->{_last_server}]" };
+IProto client can be created with full control of
+its behaviour:
 
-BEGIN {
-    if (eval {require 5.8.3}) {
-        require bytes;
-        import bytes;
-        require warnings;
-        import warnings;
-    }
-}
+    my $client = MR::IProto->new(
+        cluster => MR::IProto::Cluster->new(
+            servers => [
+                MR::IProto::Cluster::Server->new(
+                    host => 'xxx.xxx.xxx.xxx',
+                    port => xxxx,
+                ),
+                ...
+            ],
+        ),
+    );
 
-$PROTO_TCP = 0;
-%sockets = ();
+Or without it:
 
-sub DEFAULT_RETRY_DELAY         () { 0 }
-sub DEFAULT_MAX_REQUEST_RETRIES () { 2 }
-sub DEFAULT_TIMEOUT             () { 2 }
+    my $client = MR::IProto->new(
+        servers => 'xxx.xxx.xxx.xxx:xxxx,xxx.xxx.xxx.xxx:xxxx',
+    );
 
-sub RR                          () { 1 }
-sub HASH                        () { 2 }
-sub KETAMA                      () { 3 }
+Messages can be prepared and processed using objects (requires some more CPU):
 
-sub confess { die @_ };
+    my $request = MyProject::Message::MyOperation::Request->new(
+        arg1 => 1,
+        arg2 => 2,
+    );
+    my $response = $client->send($request);
+    # $response isa My::Project::Message::MyOperation::Response.
+    # Of course, both message classes (request and reply) must
+    # be implemented by user.
+
+Or without them:
+
+    my $response = $client->send({
+        msg    => x,
+        data   => [...],
+        pack   => 'xxx',
+        unpack => sub {
+            my ($data) = @_;
+            return (...);
+        },
+    });
 
-sub DisconnectAll {
-    close $_ foreach (values %sockets);
-    %sockets = ();
-}
+Messages can be sent synchronously:
 
-sub new {
-    my ($class, $args) = @_;
-    my $self = {};
-    bless $self, $class;
-
-    $self->{debug}                = $args->{debug} || 0;
-    $self->{balance}              = $args->{balance} && $args->{balance} eq 'hash-crc32' ? HASH() : RR();
-    $self->{balance}              = KETAMA() if $args->{balance} && $args->{balance} eq 'ketama';
-    $self->{rotateservers}        = 1 unless $args->{norotateservers};
-    $self->{max_request_retries}  = $args->{max_request_retries} || DEFAULT_MAX_REQUEST_RETRIES();
-    $self->{retry_delay}          = $args->{retry_delay} || DEFAULT_RETRY_DELAY();
-    $self->{dump_no_ints}         = 1 if $args->{dump_no_ints};
-    $self->{tcp_nodelay}          = 1 if $args->{tcp_nodelay};
-    $self->{tcp_keepalive}        = $args->{tcp_keepalive} || 0;
-    $self->{param}                = $args->{param};
-    $self->{_last_server}         = '';
-
-    $self->{name} = $args->{name} or ($self->{name}) = caller;
-
-    my $servers = $args->{servers} || confess("${class}->new: no servers given");
-    _parse_servers $self 'servers', $servers;
-
-    if ($self->{balance} == RR()) {
-        $self->{aviable_servers} = [@{$self->{servers}}]; #make copy
-        $servers = $args->{broadcast_servers} || ''; # confess("${class}->new: no broadcast servers given");
-        _parse_servers $self 'broadcast_servers', $servers;
-    } elsif ($self->{balance} == KETAMA()) {
-        $self->{'ketama'} = [];
-        for my $s (@{$self->{'servers'}}) {
-            $s->{'ok'} = 1;
-            for my $i (0..10) {
-               push @{$self->{'ketama'}}, [crc32($s->{'repr'}.$i), $s];
-            }
-        }
-        @{$self->{'ketama'}} = sort {$a->[0] cmp $b->[0]} @{$self->{'ketama'}};
-    }
+    my $response = $client->send($response);
+    # exception is raised if error is occured
+    # besides $@ you can check $! to identify reason of error
 
-    my $timeout = exists $args->{timeout} ? $args->{timeout} : DEFAULT_TIMEOUT();
-    SetTimeout $self $timeout;
+Or asynchronously:
 
-    confess "${class}: no servers given" unless @{$self->{servers}};
-    return $self;
-}
+    use AnyEvent;
+    my $callback = sub {
+        my ($reply, $error) = @_;
+        # on error $error is defined and $! can be set
+        return;
+    };
+    $client->send($request, $callback);
+    # callback is called when reply is received or error is occured
 
-sub GetParam {
-    return $_[0]->{param};
-}
+=head1 DESCRIPTION
 
-sub Chat {
-    my ($self, %message) = @_;
+This client is used to communicate with cluster of balanced servers using
+iproto network protocol.
 
-    confess "wrong msg id" unless exists($message{msg});
-    confess "wrong msg body" unless exists($message{payload}) or exists($message{data});
-    confess "wrong msg reply" unless $message{no_reply} or exists($message{unpack});
+To use it nicely you should to implement two subclasses of
+L<MR::IProto::Message> for each message type, one for request message
+and another for reply.
+This classes must be named as C<prefix::*::suffix>, where I<prefix>
+must be passed to constructor of L<MR::IProto> as value of L</prefix>
+attribute and I<suffix> is either C<Request> or C<Response>.
+This classes must be loaded before first message through client object
+will be sent.
 
-    my $retries = $self->{max_request_retries};
+To send messages asyncronously you should to implement event loop by self.
+L<AnyEvent> is recomended.
 
-    for (my $try = 1; $try <= $retries; $try++) {
-        sleep $self->{retry_delay} if $try > 1 && $self->{retry_delay};
-        $self->{debug} >= 2 && _debug $self "chat msg=$message{msg} $try of $retries total";
-        my $ret = $self->Chat1(%message);
+=cut
 
-        if ($ret->{ok}) {
-            return (wantarray ? @{$ret->{ok}} : $ret->{ok}->[0]);
-        }
-    }
-    return undef;
-}
+use Mouse;
+use Errno;
+use MRO::Compat;
+use Scalar::Util qw(weaken);
+use Time::HiRes;
+use MR::IProto::Cluster;
+use MR::IProto::Error;
 
-sub Chat1 {
-    my ($self, %message) = @_;
+with 'MR::IProto::Role::Debuggable';
 
-    confess "wrong msg id" unless exists($message{msg});
-    confess "wrong msg body" unless exists($message{payload}) or exists($message{data});
-    confess "wrong msg reply" unless $message{no_reply} or exists($message{unpack});
+=head1 ATTRIBUTES
 
-    my $server = _select_server $self $message{key};
-    unless($server) {
-        $self->{_error} ||= 'Could not find a valid server';
-        return;
-    }
+=over
 
-    my ($ret) = _chat $self $server, \%message;
+=item prefix
 
-    if ($ret->{fail} && $self->{rotateservers}) {
-        $self->{debug} >= 2 && _debug $self "chat: failed";
-    	_mark_server_bad $self;
-    }
+Prefix of the class name in which hierarchy subclasses of L<MR::IProto::Message>
+are located. Used to find reply message classes.
 
-    return $ret;
-}
+=cut
 
-# private methods
-# order of method declaration is important, see perlobj
-
-sub _parse_servers {
-    my ($self, $key, $line) = @_;
-    my @servers;
-    my $weighted = $self->{balance} == HASH();
-    foreach my $server (split(/,/, $line)) {
-        if ($weighted) {
-            my ($host, $port, $weight) = split /:/, $server;
-            $weight ||= 1;
-            push @servers, { host => $host, port => $port, ok => 1, repr => "$host:$port" } for (1..$weight);
-        } else {
-            my ($host, $port) = $server =~ /(.+):(\d+)/;
-            push @servers, { host => $host, port => $port, repr => "$host:$port" };
-        }
-    }
-    $self->{$key} = \@servers;
-}
+has prefix => (
+    is  => 'ro',
+    isa => 'Str',
+    default => sub { ref shift },
+);
 
-sub _chat {
-    my ($self, $server, $message) = @_;
-    _a_init $self $server, $message
-        and _a_send $self
-        and !$message->{no_reply}
-        and _a_recv $self;
-    return _a_close $self;
-}
+=item cluster
 
-sub _a_init {
-    my ($self, $server, $message, $async) = @_;
-
-    _a_clear $self;
-    $self->{_server} = $server;
-    $self->{_message} = $message;
-    $self->{_async} = !!$async;
-
-    my $payload = $message->{payload} || do { no warnings 'uninitialized'; pack($message->{pack} || 'L*', @{$message->{data}}) };
-    $self->{_sync} = exists $message->{sync} ? $message->{sync} : int(rand 0xffffffff);
-    my $header = pack('LLL', $message->{msg}, length($payload), $self->{_sync});
-    $self->{_write_buf} = $header . $payload;
-    $self->{debug} >= 5 && _debug_dump $self '_pack_request: header ', $header;
-    $self->{debug} >= 5 && _debug_dump $self '_pack_request: payload ', $payload;
-
-    $self->{_start_time} = time;
-    $self->{_connecting} = 1;
-    $self->{sock} = $sockets{$server->{repr}} || _connect $self $server, \$self->{_error}, $async;
-    _set_blocking($self->{sock},!$async) if $self->{sock};
-    return $self->{sock};
-}
+Instance of L<MR::IProto::Cluster>. Contains all servers between which
+requests can be balanced.
+Also can be specified in I<servers> parameter of constructor as a list of
+C<host:port> pairs separated by comma.
 
-sub _a_send {
-    my ($self) = @_;
+=cut
 
-    $self->{_connecting} = 0;
-    $self->{_timedout} = 0;
-    while(length $self->{_write_buf}) {
-        local $! = 0;
-        my $res = syswrite($self->{sock}, $self->{_write_buf});
-        if(defined $res && $res == 0) {
-            $self->{debug} <= 0 || _debug $self "$!" and next if $!{EINTR};
-            $self->{_error} .= "Server unexpectedly closed connection (${\length $self->{_write_buf}} bytes unwritten)";
-            return;
-        }
-        if(!defined $res) {
-            $self->{debug} <= 0 || _debug $self "$!" and next if $!{EINTR};
-            $self->{_timedout} = !!$!{EAGAIN};
-            $self->{_error} .= $! unless $self->{_async} && $!{EAGAIN};
-            return;
-        }
-        substr $self->{_write_buf}, 0, $res, '';
-    }
+has cluster => (
+    is  => 'ro',
+    isa => 'MR::IProto::Cluster',
+    required => 1,
+    coerce   => 1,
+    handles  => [qw( timeout )],
+);
 
-    die "We should never get here" if length $self->{_write_buf};
+=item max_parallel
 
-    delete $self->{_write_buf};
-    $self->{_read_header} = 1;
-    $self->{_to_read} = 12;
+Max amount of simultaneous request to all servers.
 
-    return 1;
-}
+=cut
 
-sub _a_recv {
-    my ($self) = @_;
-    $self->{_connecting} = 0;
-    $self->{_timedout} = 0;
-    while(1) {
-        local $! = 0;
-        my $res;
-        while($self->{_to_read} and $res = sysread($self->{sock}, my $buffer, $self->{_to_read} ) ) {
-            $self->{_to_read} -= $res;
-            $self->{_buf} .= $buffer;
-            $self->{debug} >= 6 && _debug_dump $self '_a_recv: ', $buffer;
-        }
-        if(defined $res && $res == 0 && $self->{_to_read}) {
-            $self->{debug} <= 0 || _debug $self "$!" and next if $!{EINTR};
-            $self->{_error} .= "Server unexpectedly closed connection ($self->{_to_read} bytes unread)";
-            return;
-        }
-        if(!defined $res) {
-            $self->{debug} <= 0 || _debug $self "$!" and next if $!{EINTR};
-            $self->{_timedout} = !!$!{EAGAIN};
-            $self->{_error} .= $! unless $self->{_async} && $!{EAGAIN};
-            return;
-        }
+has max_parallel => (
+    is  => 'ro',
+    isa => 'Int',
+    default => 1000,
+);
 
-        last unless $self->{_read_header};
-        if($self->{_read_header} && length $self->{_buf} >= 12) {
-            my ($response_msg, $to_read, $response_sync) = unpack('L3', substr($self->{_buf},0,12,''));
-            unless ($response_msg == $self->{_message}->{msg} and $response_sync == $self->{_sync}) {
-                $self->{_error} .= "unexpected reply msg($response_msg != $self->{_message}->{msg}) or sync($response_sync != $self->{_sync})";
-                return;
-            }
-            $self->{_read_header} = 0;
-            $self->{_to_read} = $to_read - length $self->{_buf};
-        }
-    }
+=item max_request_retries
 
-    die "We should never get here" if $self->{_to_read};
-    return 1;
-}
+Max amount of request retries which must be sent to different servers
+before error is returned.
 
-sub _a_close {
-    my ($self, $error) = @_;
-    $error = '' unless defined $error;
-    $self->{_timedout} ||= $error eq 'timeout';
-    $self->{_error} ||= $error;
-    my $ret;
-    if ($self->{_error} || $self->{_timedout}) {
-        _close_sock $self $self->{_server}; # something went wrong, close socket just in case
-        $self->{_error} = "Timeout ($self->{_error})" if $self->{_timedout};
-        $self->{debug} >= 1 && _debug $self "failed with: $self->{_error}";
-        $ret = { fail => $self->{_error}, timeout => $self->{_timedout} };
-    } else {
-        $self->{debug} >= 5 && _debug_dump $self '_unpack_body: ', $self->{_buf};
-        $ret = { ok => [ $self->{_message}->{no_reply} ? (0) : $self->{_message}->{unpack}->($self->{_buf}) ] };
-    }
-    _a_clear $self;
-    return $ret;
-}
+=cut
 
-sub _a_is_reading {
-    my ($self) = @_;
-    return $self->{_to_read} && $self->{sock};
-}
+has max_request_retries => (
+    is  => 'ro',
+    isa => 'Int',
+    default => 2,
+);
 
-sub _a_is_writing {
-    my ($self) = @_;
-    return exists $self->{_write_buf} && $self->{sock};
-}
+=item retry_delay
 
-sub _a_is_connecting {
-    my ($self) = @_;
-    return $self->{_connecting} && $self->{sock};
-}
+Delay between request retries.
 
-sub _a_clear {
-    my ($self) = @_;
-    $self->{_buf} = $self->{_error} = '';
-    $self->{_to_read} = undef;
-    $self->{_data} = undef;
-    $self->{_server} = undef;
-    $self->{_message} = undef;
-    $self->{_sync} = undef;
-    $self->{_read_header} = undef;
-    $self->{_to_read} = undef;
-    $self->{_timedout} = undef;
-    $self->{_connecting} = undef;
-    delete $self->{_write_buf};
-}
+=cut
 
-sub _n_servers {
-    return scalar @{$_[0]->{servers}};
-}
+has retry_delay => (
+    is  => 'ro',
+    isa => 'Num',
+    default => 0,
+);
 
-sub _select_server {
-    my ($self, $key) = @_;
-    my $n = @{$self->{servers}};
-    while($n--) {
-        if ($self->{balance} == RR()) {
-            $self->{current_server} ||= $self->_balance_rr;
-        } elsif ($self->{balance} == KETAMA()) {
-            $self->{current_server} = $self->_balance_ketama($key);
-        } else {
-            $self->{current_server} = $self->_balance_hash($key);
-        }
-        last unless $self->{current_server};
-        last if $self->{current_server};
-    }
-    if($self->{current_server}) {
-        $self->{_last_server} = $self->{current_server}->{repr};
-    }
-    return $self->{current_server};
-}
+=back
 
-sub _mark_server_bad {
-    my ($self, $remove_last_server) = @_;
-    if ($self->{balance} == HASH()) {
-        delete($self->{current_server}->{ok});
-    }
-    delete($self->{current_server});
-    $self->{_last_server} = '' if $remove_last_server;
-}
+=cut
 
-sub _balance_rr {
-    my ($self) = @_;
-    if (scalar(@{$self->{servers}}) == 1) {
-        return $self->{servers}->[0];
-    } else {
-        $self->{aviable_servers} = [@{$self->{servers}}] if (scalar(@{$self->{aviable_servers}}) == 0);
-        return splice(@{$self->{aviable_servers}}, int(rand(@{$self->{aviable_servers}})), 1);
-    }
-}
+has _reply_class => (
+    is  => 'ro',
+    isa => 'HashRef[ClassName]',
+    lazy_build => 1,
+);
 
-sub _balance_hash {
-    my ($self, $key) = @_;
-    my ($hash_, $hash, $server);
+has _queue => (
+    is  => 'ro',
+    isa => 'ArrayRef',
+    lazy_build => 1,
+);
 
-    $hash = $hash_ = crc32($key) >> 16;
-    for (0..19) {
-        $server = $self->{servers}->[$hash % @{$self->{servers}}];
-        return $server if $server->{ok};
-        $hash += crc32($_, $hash_) >> 16;
-    }
+has _in_progress => (
+    is  => 'rw',
+    isa => 'Int',
+    default => 0,
+);
 
-    return $self->{servers}->[rand @{$self->{servers}}]; #last resort
-}
+=head1 PUBLIC METHODS
 
-sub _balance_ketama {
-    my ($self, $key) = @_;
+=over
 
-    my $idx = crc32($key);
+=item new( [ %args | \%args ] )
 
-    for my $a (@{$self->{'ketama'}}) {
-       next unless ($a);
-       return $a->[1] if ($a->[0] >= $idx);
-    }
+Constructor.
+See L</ATTRIBUTES> and L</BUILDARGS> for more information about allowed arguments.
 
-    return $self->{'ketama'}->[0]->[1];
-}
+=item send( [ $message | \%args ], $callback? )
 
-sub _set_sock_timeout ($$) { # not a class method!
-    my ($sock, $tv) = @_;
-    return (
-        setsockopt $sock, SOL_SOCKET, SO_SNDTIMEO, $tv
-         and setsockopt $sock, SOL_SOCKET, SO_RCVTIMEO, $tv
-    );
-}
+Send C<$message> to server and receive reply.
 
-sub _set_blocking ($$) {
-    my ($sock, $blocking) = @_;
-    my $flags = 0;
-    fcntl($sock, F_GETFL, $flags) or die $!;
-    if($blocking) {
-        $flags &= ~O_NONBLOCK;
-    } else {
-        $flags |=  O_NONBLOCK;
-    }
-    fcntl($sock, F_SETFL, $flags) or die $!;
-}
+If C<$callback> is passed then request is done asyncronously and reply is passed
+to callback as first argument.
+Method B<must> be called in void context to prevent possible errors.
+Only client errors can be raised in async mode. All communication errors are
+passed to callback as second argument. Additional information can be extracted
+from C<$!> variable.
 
-sub _connect {
-    my ($self, $server, $err, $async) = @_;
+In sync mode (when C<$callback> argument is skipped) all errors are raised
+and C<$!> is also set. Response is returned from method, so method B<must>
+be called in scalar context.
 
-    $self->{_timedout} = 0;
-    $self->{debug} >= 4 && _debug $self "connecting";
+Request C<$message> can be instance of L<MR::IProto::Message> subclass.
+In this case reply will be also subclass of L<MR::IProto::Message>.
+Or it can be passed as C<\%args> hash reference with keys described
+in L</_send>.
 
-    my $sock = "$server->{host}:$server->{port}";
-    my $proto = $PROTO_TCP ||= getprotobyname('tcp');
-    do {
-        no strict 'refs';
-        socket($sock, PF_INET, SOCK_STREAM, $proto);
-    };
+=cut
 
-    if ($async) {
-        _set_blocking($sock,0);
-    } else {
-        _set_sock_timeout $sock, $self->{timeout_timeval};
+sub send {
+    my ($self, $message, $callback) = @_;
+    if($callback) {
+        die "Method must be called in void context if you want to use async" if defined wantarray;
+        $self->_send($message, $callback);
+        return;
+    }
+    else {
+        die "Method must be called in scalar context if you want to use sync" unless defined wantarray;
+        my $olddie = ref $SIG{__DIE__} eq 'CODE' ? $SIG{__DIE__} : ref $SIG{__DIE__} eq 'GLOB' ? *{$SIG{__DIE__}}{CODE} : undef;
+        local $SIG{__DIE__} = sub { local $! = 0; $olddie->(@_); } if $olddie;
+        my %servers;
+        my ($data, $error, $errno);
+        $self->_send_now($message, sub {
+            ($data, $error) = @_;
+            $errno = $!;
+            return;
+        }, \%servers);
+        $self->_recv_now(\%servers);
+        $! = $errno;
+        die $error if $error;
+        return $data;
     }
+}
 
-    my $sin = Socket::sockaddr_in($server->{'port'}, Socket::inet_aton($server->{'host'}));
-    while(1) {
-        local $! = 0;
-        unless(connect($sock, $sin)) {
-            $self->{debug} <= 0 || _debug $self "$!" and next if $!{EINTR};
-            $self->{_timedout} = !!$!{EINPROGRESS};
-            if (!$async || !$!{EINPROGRESS}) {
-                $$err .= "cannot connect: $!";
-                close $sock;
-                return undef;
-            }
+=item send_bulk( \@messages, $callback? )
+
+Send all of messages in C<\@messages> and return result (sync-mode) or
+call callback (async-mode) after all replies was received.
+Result is returned as array reference, which values can be instances of
+L<MR::IProto::Response> or L<MR::IProto::Error> if request was passed
+as object, or hash with keys C<data> and C<error> if message was passed
+as C<\%args>.
+Replies in result can be returned in order different then order of requests.
+
+See L</_send> for more information about message data. Either
+C<$message> or C<\%args> allowed as content of C<\@messages>.
+
+=cut
+
+sub send_bulk {
+    my ($self, $messages, $callback) = @_;
+    my @result;
+    if($callback) {
+        die "Method must be called in void context if you want to use async" if defined wantarray;
+        my $cv = AnyEvent->condvar();
+        $cv->begin( sub { $callback->(\@result) } );
+        foreach my $message ( @$messages ) {
+            $cv->begin();
+            $self->_send($message, sub {
+                my ($data, $error) = @_;
+                push @result, blessed($data) ? $data
+                    : { data => $data, error => $error };
+                $cv->end();
+                return;
+            });
         }
-        last;
+        $cv->end();
+        return;
     }
-
-    if($self->{tcp_nodelay}) {
-        setsockopt($sock, $PROTO_TCP, TCP_NODELAY, 1);
+    else {
+        die "Method must be called in scalar context if you want to use sync" unless defined wantarray;
+        my $olddie = ref $SIG{__DIE__} eq 'CODE' ? $SIG{__DIE__} : ref $SIG{__DIE__} eq 'GLOB' ? *{$SIG{__DIE__}}{CODE} : undef;
+        local $SIG{__DIE__} = sub { local $! = 0; $olddie->(@_); } if $olddie;
+        my %servers;
+        foreach my $message ( @$messages ) {
+            $self->_send_now($message, sub {
+                my ($data, $error) = @_;
+                push @result, blessed($data) ? $data
+                    : { data => $data, error => $error };
+                return;
+            }, \%servers);
+        }
+        $self->_recv_now(\%servers);
+        return \@result;
     }
+}
 
-    if($self->{tcp_keepalive}) {
-        setsockopt($sock, SOL_SOCKET, SO_KEEPALIVE, 1);
-    }
+sub Chat {
+    my $self = shift;
+    my $message = @_ == 1 ? shift : { @_ };
+    $message->{retry} = 1 if ref $message eq 'HASH';
+    my $data;
+    eval { $data = $self->send($message); 1 } or return;
+    return wantarray ? @$data : $data->[0];
+}
 
-    $self->{debug} >= 2 && _debug $self "connected";
-    return $sockets{$server->{repr}} = $sock;
+sub Chat1 {
+    my $self = shift;
+    my $message = @_ == 1 ? shift : { @_ };
+    my $data;
+    return eval { $data = $self->send($message); 1 } ? { ok => $data }
+        : { fail => $@ =~ /^(.*?) at \S+ line \d+/s ? $1 : $@, timeout => $! == Errno::ETIMEDOUT };
 }
 
 sub SetTimeout {
     my ($self, $timeout) = @_;
-    $self->{timeout} = $timeout;
+    $self->timeout($timeout);
+    return;
+}
 
-    my $sec  = int $timeout; # seconds
-    my $usec = int( ($timeout - $sec) * 1_000_000 ); # micro-seconds
-    $self->{timeout_timeval} = pack "L!L!", $sec, $usec; # struct timeval;
+=back
 
-    _set_sock_timeout $sockets{$_}, $self->{timeout_timeval}
-        for
-            grep {exists $sockets{$_}}
-                map {$_->{repr}}
-                    @{ $self->{servers} };
-}
+=head1 PROTECTED METHODS
 
-sub _close_sock {
-    my ($self, $server) = @_;
+=over
 
-    if ($sockets{$server->{repr}}) {
-        $self->{debug} >= 2 && _debug $self 'closing socket';
-        close $sockets{$server->{repr}};
-        delete $sockets{$server->{repr}};
-    }
-}
+=item BUILDARGS( [ %args | \%args ] )
 
-sub _debug {
-    my ($self, $msg)= @_;
-    my $server = $self->{current_server} && $self->{current_server}->{repr} || $self->{_last_server} || '';
-    my $sock = $self->{sock} || 'none';
-    $server &&= "$server($sock) ";
+For compatibility with previous version of client and simplicity
+some additional arguments to constructor is allowed:
 
-    warn("$self->{name}: $server$msg\n");
-    1;
-}
+=over
+
+=item servers
+
+C<host:port> pairs separated by comma used to create
+L<MR::IProto::Cluster::Server> objects.
+
+=item timeout, tcp_nodelay, tcp_keepalive, dump_no_ints
+
+Are passed directly to constructor of L<MR::IProto::Cluster::Server>.
+
+=item balance
 
-sub _debug_dump {
-    my ($self, $msg, $datum) = @_;
-    my $server = $self->{current_server} && $self->{current_server}->{repr};
-    my $sock = $self->{sock} || 'none';
-    $server &&= "$server($sock) ";
+Is passed directly to constructor of L<MR::IProto::Cluster>.
 
-    unless($self->{dump_no_ints}) {
-        $msg .= join(' ', unpack('L*', $datum));
-        $msg .= ' > ';
+=back
+
+See L<Mouse::Manual::Construction/BUILDARGS> for more information.
+
+=cut
+
+my %servers;
+around BUILDARGS => sub {
+    my $orig = shift;
+    my $class = shift;
+    my %args = @_ == 1 ? %{shift()} : @_;
+    $args{prefix} = $args{name} if exists $args{name};
+    if( $args{servers} ) {
+        my $cluster_class = $args{cluster_class} || 'MR::IProto::Cluster';
+        my $server_class = $args{server_class} || 'MR::IProto::Cluster::Server';
+        my %srvargs;
+        $srvargs{debug} = $args{debug} if exists $args{debug};
+        $srvargs{timeout} = delete $args{timeout} if exists $args{timeout};
+        $srvargs{tcp_nodelay} = delete $args{tcp_nodelay} if exists $args{tcp_nodelay};
+        $srvargs{tcp_keepalive} = delete $args{tcp_keepalive} if exists $args{tcp_keepalive};
+        $srvargs{dump_no_ints} = delete $args{dump_no_ints} if exists $args{dump_no_ints};
+        my %clusterargs;
+        $clusterargs{balance} = delete $args{balance} if exists $args{balance};
+        $clusterargs{servers} = [
+            map {
+                my ($host, $port, $weight) = split /:/, $_;
+                $args{no_pool} ? my $server : $servers{"$host:$port"} ||= $server_class->new(
+                    %srvargs,
+                    host => $host,
+                    port => $port,
+                    defined $weight ? ( weight => $weight ) : (),
+                );
+            } split /,/, delete $args{servers}
+        ];
+        $args{cluster} = $cluster_class->new(%clusterargs);
     }
-    $msg .= join(' ', map { sprintf "%02x", $_ } unpack("C*", $datum));
-    warn("$self->{name}: $server$msg\n");
-}
+    return $class->$orig(%args);
+};
 
-package MR::IProto::Async;
-use Time::HiRes qw/time/;
-use List::Util qw/min shuffle/;
-
-sub DEFAULT_TIMEOUT()           { 10 }
-sub DEFAULT_TIMEOUT_SINGLE()    {  4 }
-sub DEFAULT_TIMEOUT_CONNECT()   {  1 }
-sub DEFAULT_RETRY()             {  3 }
-sub IPROTO_CLASS()              { 'MR::IProto' }
-
-use overload '""' => sub { $_[0]->{name}.'[async]' };
-
-BEGIN { *confess = \&MR::IProto::confess }
-
-sub new {
-    my ($class, %opt) = @_;
-    ($opt{name}) = caller unless $opt{name};
-    my $self = bless {
-        name                        => $opt{name},
-        iproto_class                => $opt{iproto_class}         || $class->IPROTO_CLASS,
-        timeout                     => $opt{timeout}              || $class->DEFAULT_TIMEOUT(),          # over-all-requests timeout
-        timeout_single              => $opt{timeout_single}       || $class->DEFAULT_TIMEOUT_SINGLE(),   # per-request timeout
-        timeout_connect             => $opt{timeout_connect}      || $class->DEFAULT_TIMEOUT_CONNECT(),  # timeout to do connect()
-        max_request_retries         => $opt{max_request_retries}  || $class->DEFAULT_RETRY(),
-        debug                       => $opt{debug}                || 0,
-        servers                     => [ ],
-        requests                    => [ @{$opt{requests}||[]} ],
-        nreqs                       => $opt{requests} ? scalar(@{$opt{requests}}) : 0,
-        servers_used                => {},
-        nserver                     => 0,
-        working                     => {},
-    }, $class;
-
-    _parse_servers $self 'servers', $opt{servers};
-    confess "no servers given" unless @{$self->{servers}};
-    @{$self->{servers}} = shuffle @{$self->{servers}};
-
-    return $self;
+sub _build_debug_cb {
+    my ($self) = @_;
+    my $prefix = $self->prefix;
+    return sub {
+        my ($msg) = @_;
+        chomp $msg;
+        warn sprintf "%s: %s\n", $prefix, $msg;
+        return;
+    };
 }
 
-sub Request { # MR::IProto::Chat()-compatible
-    my ($self, %message) = @_;
-
-    confess "wrong msg id" unless exists($message{msg});
-    confess "wrong msg body" unless exists($message{payload}) or exists($message{data});
-    confess "wrong msg reply" unless $message{no_reply} or exists($message{unpack});
+sub _build__callbacks {
+    my ($self) = @_;
+    return {};
+}
 
-    push @{$self->{requests}}, \%message;
+sub _build__reply_class {
+    my ($self) = @_;
+    my $re = sprintf '^%s::', $self->prefix;
+    my %reply = map { $_->msg => $_ }
+        grep $_->can('msg'),
+        grep /$re/,
+        # MR::IProto::Response->meta->subclasses();
+        @{ mro::get_isarev('MR::IProto::Response') };
+    return \%reply;
 }
 
-sub Results {
+sub _build__queue {
     my ($self) = @_;
+    return [];
+}
 
-    my (@done);
-    my $timeout_single = $self->{timeout_single};
-    my $timeout_connect = $self->{timeout_connect};
-    my $nreqs = @{$self->{requests}};
+=item _send( [ $message | \%args ], $callback? )
 
-    unless ($nreqs) {
-        warn "No requests; return nothing;\n";
-        return;
-    }
+Pure asyncronious internal implementation of send.
 
-    my $t0 = time;
+C<$message> is an instance of L<MR::IProto::Message>.
+If C<\%args> hash reference is passed instead of C<$message> then it can
+contain following keys:
 
-    my $err = 'timeout';
-    my $working = $self->{working};
-    confess "we have something what we should not" if %$working;
+=over
 
-    while(scalar@{$self->{requests}}) {
-        int _init_req $self $self->{requests}->[0], $working
-            or last;
-        shift @{$self->{requests}};
-    }
+=item msg
 
-    unless (%$working) {
-        warn "Connection error; return nothing;\n";
-        return;
-    }
+Message code.
 
-    my $deadline = $self->{timeout} + time;
-
-    MAINLOOP:
-    while((my $timeout = $deadline - time) > 0 && %$working) {
-        my ($r,$w) = ('','');
-        vec($r, $_, 1) = 1 for grep {  $working->{$_}->{_conn}->_a_is_reading } keys %$working;
-        vec($w, $_, 1) = 1 for grep { !$working->{$_}->{_conn}->_a_is_reading } keys %$working;
-        my $t1 = time;
-        $timeout = min $timeout, map { ($_->{_conn}->_a_is_connecting() ? $timeout_connect : $timeout_single) - ($t1 - $_->{_conn}->{_start_time}) } values %$working;
-        my $found = select($r, $w, undef, $timeout);
-
-        my $time = time;
-        for my $fd (keys %$working) {
-            my $req = $working->{$fd};
-            my $conn = $req->{_conn};
-
-            my $restart = 0;
-            if(vec($w, $fd, 1) && ($conn->_a_is_writing || $conn->_a_is_connecting)) {
-                $restart = 1 if !$conn->_a_send && !$conn->{_timedout};
-            } elsif(vec($r, $fd, 1) && $conn->_a_is_reading) {
-                if($conn->_a_recv) {
-                    delete $working->{$fd};
-                    _server_free $self $req->{_server};
-                    push @done, $req;
-                    if(@{$self->{requests}}) {
-                        _init_req $self shift@{$self->{requests}}, $working or _die $self "shit1";
-                    }
-                } elsif(!$conn->{_timedout}) {
-                    $restart = 2;
-                }
-            } else {
-                if($conn->{_start_time} + ($conn->_a_is_connecting() ? $timeout_connect : $timeout_single) < $time) {
-                    $restart = -1;
-                }
-            }
+=item key
 
-            next unless $restart;
-
-            delete $working->{$fd};
-            $conn->_a_close($restart < 0 ? 'timeout' : 'shit happened!');
-            my $oldserver = $req->{_server};
-            my $res = _init_req $self $req, $working;
-            if( !$res ) {
-                $err = 'one of requests has failed, aborting';
-                last MAINLOOP;
-            } elsif (! int $res ) {
-                push @{$self->{requests}}, $req;
-            }
-            _server_free $self $oldserver;
-        }
-    }
+Depending on this value balancing between servers is implemented.
 
-    my $t9 = time;
-    warn sprintf "$self->{name}: fetched %d requests for %.4f sec (%d/%d done)\n", $self->{nreqs}, $t9-$t0, scalar@done, $nreqs;
+=item data
 
-    if(%$working) {
-        warn "$self->{name}: ${\scalar values %$working} requests not fetched\n";
-        $_->{_conn}->_a_close($err) for values %$working;
-        # warn "return nothing\n";
-        # return;
-    }
+Message data. Already packed or unpacked. Unpacked data must be passed as
+array reference and additional parameter I<pack> must be passed.
 
-    for my $req (@done) {
-        my $result = $req->{_conn}->_a_close;
-        # warn "ret nothing\n" and return unless $result->{ok};
-        $req = $result->{ok};
-    }
+=item pack
 
-    return grep { $_ } @done;
-}
+First argument of L<pack|perlfunc/pack> function.
 
-sub _init_req {
-    my ($self, $req) = @_;
+=item unpack
 
-    $req->{_try} ||= 0;
-    return if $req->{_try} >= $self->{max_request_retries};
+Code reference which is used to unpack reply.
 
-    $req->{_conn} = undef;
-    $req->{_server} = undef;
+=item no_reply
 
-    my $server = _select_server $self or return '0E0';
-    my $conn = $self->{iproto_class}->new({
-        servers => $server->{repr},
-        debug   => $self->{debug},
-    });
-    $conn->_select_server; # fiction
+Message have no reply.
 
-    my $sock = $conn->_a_init($server, $req, 'async') or do{ $conn->_a_close; warn "can't init connection: $server->{repr}"; return '0E0'; };
-    my $fd = fileno $sock or do{ $conn->_a_close; _die $self "connection has no FD", $server->{repr}; };
+=item retry
 
-    my $working = $self->{working};
-    _die $self "FD $fd already exists in workers", $server->{repr} if exists $working->{$fd};
-    $working->{ $fd } = $req;
-    _server_use $self $server;
+Is retry is allowed. Values of attributes L</max_request_retries> and
+L</retry_delay> is used if retry is allowed.
 
-    $req->{_conn} = $conn;
-    $req->{_server} = $server;
-    ++$req->{_try};
-    ++$self->{nreqs};
+=item is_retry
 
-    return $conn && 1;
-}
+Callback used to determine if server asks for retry. Unpacked data is passed
+to it as a first argument.
+
+=back
+
+=cut
 
-sub _select_server {
-    my ($self, $disable_servers) = @_;
-    $disable_servers ||= {};
-
-    my $servers = $self->{servers};
-    my $servers_used = $self->{servers_used};
-    my $nserver = $self->{nserver};
-
-    my $i = 1;
-    while($i <= @$servers) {
-        my $repr = $servers->[($i+$nserver)%@$servers]->{repr};
-        next if $servers_used->{$repr};
-        next if $disable_servers->{$repr};
-        last;
-    } continue {
-        ++$i;
+sub _send {
+    my ($self, $message, $callback) = @_;
+    if( $self->_in_progress < $self->max_parallel ) {
+        $self->_in_progress( $self->_in_progress + 1 );
+        eval { $self->_send_now($message, $callback); 1 }
+            or $self->_report_error($message, $callback, $@);
     }
+    else {
+        push @{$self->_queue}, [ $message, $callback ];
+    }
+    return;
+}
 
-    return if $i > @$servers;
+sub _finish_and_start {
+    my ($self) = @_;
+    if( my $task = shift @{$self->_queue} ) {
+        eval { $self->_send_now(@$task); 1 }
+            or $self->_report_error(@$task, $@);
+    }
+    else {
+        $self->_in_progress( $self->_in_progress - 1 );
+    }
+    return;
+}
+
+sub _send_now {
+    my ($self, $message, $callback, $sync) = @_;
+    my $args;
+    # MR::IProto::Message OO-API
+    if( ref $message ne 'HASH' ) {
+        my $msg = $message->msg;
+        my $response_class = $self->_reply_class->{$msg};
+        die sprintf "Cannot find response class for message code %d\n", $msg unless $response_class;
+        $args = {
+            request        => $message,
+            msg            => $msg,
+            key            => $message->key,
+            body           => $message->data,
+            response_class => $response_class,
+            no_reply       => $response_class->isa('MR::IProto::NoResponse'),
+        };
+    }
+    # Old-style compatible API
+    else {
+        die "unpack or no_reply must be specified" unless $message->{unpack} || $message->{no_reply};
+        $args = $message;
+        $args->{body} = exists $args->{payload} ? delete $args->{payload}
+            : ref $message->{data} ? pack delete $message->{pack} || 'L*', @{ delete $message->{data} }
+            : delete $message->{data};
+    }
 
-    $self->{nserver} = $nserver = ($i+$nserver)%@$servers;
-    return $servers->[$nserver];
+    my $try = 1;
+    weaken($self);
+    my $handler;
+    $handler = sub {
+        $self->_server_callback(
+            [\$handler, $args, $callback, $sync, \$try],
+            [@_],
+        );
+        return;
+    };
+    $self->_send_try($sync, $args, $handler, $try);
+    return;
+}
+
+sub _send_try {
+    my ($self, $sync, $args, $handler, $try) = @_;
+    my $xsync = $sync ? 'sync' : 'async';
+    $self->_debug(sprintf "send msg=%d try %d of %d total", $args->{msg}, $try, $self->max_request_retries ) if $self->debug >= 2;
+    my $server = $self->cluster->server( $args->{key} );
+    my $connection = $server->$xsync();
+    $connection->send($args->{msg}, $args->{body}, $handler, $args->{no_reply}, $args->{sync});
+    $sync->{$connection} ||= $connection if $sync;
+    return;
+}
+
+sub _send_retry {
+    my ($self, @in) = @_;
+    my ($sync) = @in;
+    if( $sync ) {
+        Time::HiRes::sleep($self->retry_delay);
+        $self->_send_try(@in);
+    }
+    else {
+        my $timer;
+        $timer = AnyEvent->timer(
+            after => $self->retry_delay,
+            cb    => sub {
+                undef $timer;
+                $self->_send_try(@in);
+                return;
+            },
+        );
+    }
+    return;
+}
+
+sub _server_callback {
+    my ($self, $req_args, $resp_args) = @_;
+    my ($handler, $args, $callback, $sync, $try) = @$req_args;
+    my ($resp_msg, $data, $error, $errno) = @$resp_args;
+    eval {
+        if ($error) {
+            $! = $errno;
+            $@ = $error;
+            my $retry = defined $args->{request} ? $args->{request}->retry()
+                : ref $args->{retry} eq 'CODE' ? $args->{retry}->()
+                : $args->{retry};
+            $self->_debug("send: failed[@{[$retry, $$try+1, $self->max_request_retries]}]") if $self->debug >= 2;
+            if( $retry && $$try++ < $self->max_request_retries ) {
+                $self->_send_retry($sync, $args, $$handler, $$try);
+            }
+            else {
+                undef $$handler;
+                $self->_report_error($args->{request}, $callback, $error, $sync, $errno);
+            }
+        }
+        else {
+            my $ok = eval {
+                die "Request and reply message code is different: $resp_msg != $args->{msg}\n"
+                    unless $args->{no_reply} || $resp_msg == $args->{msg};
+                if( defined $args->{request} ) {
+                    $data = $args->{response_class}->new( data => $data, request => $args->{request} );
+                }
+                else {
+                    $data = $args->{no_reply} ? [ 0 ] : [ ref $args->{unpack} eq 'CODE' ? $args->{unpack}->($data) : unpack $args->{unpack}, $data ];
+                }
+                1;
+            };
+            if($ok) {
+                if( defined $args->{request} && $data->retry && $$try++ < $self->max_request_retries ) {
+                    $self->_send_retry($sync, $args, $$handler, $$try);
+                }
+                elsif( defined $args->{is_retry} && $args->{is_retry}->($data) && $$try++ < $self->max_request_retries ) {
+                    $self->_send_retry($sync, $args, $$handler, $$try);
+                }
+                else {
+                    undef $$handler;
+                    $self->_finish_and_start() unless $sync;
+                    $callback->($data);
+                }
+            }
+            else {
+                undef $$handler;
+                $self->_report_error($args->{request}, $callback, $@, $sync);
+            }
+        }
+        1;
+    } or do {
+        undef $$handler;
+        $self->_debug("unhandled fatal error: $@");
+    };
+    return;
 }
 
-sub _server_use {
-    my ($self,$server) = @_;
-    _die $self "want to use used server:$server->{repr}:" if $self->{servers_used}->{$server->{repr}};
-    ++ $self->{servers_used}->{$server->{repr}};
+sub _recv_now {
+    my ($self, $servers) = @_;
+    while(my @servers = values %$servers) {
+        %$servers = ();
+        $_->recv_all() foreach @servers;
+    }
+    return;
 }
 
-sub _server_free {
-    my ($self,$server) = @_;
-    _die $self "want to free free server:$server->{repr}:" unless $self->{servers_used}->{$server->{repr}};
-    $self->{servers_used}->{$server->{repr}} = 0;
+sub _report_error {
+    my ($self, $request, $callback, $error, $sync, $errno) = @_;
+    my $errobj = defined $request && ref $request ne 'HASH'
+        ? MR::IProto::Error->new(
+            request => $request,
+            error   => $error,
+            errno   => defined $errno ? 0 + $errno : 0,
+        )
+        : undef;
+    $self->_finish_and_start() unless $sync;
+    $! = $errno;
+    $@ = $error;
+    $callback->($errobj, $error, $errno);
+    return;
 }
 
-sub _parse_servers {
-    my ($self, $key, $line) = @_;
+=back
 
-    my @servers;
-    foreach my $server (split(/,/, $line)) {
-        my ($host, $port) = $server =~ /(.+):(\d+)/ or confess "bad server: $server!";
-        push @servers, { host => $host, port => $port, repr => "$host:$port" }
-    }
+=head1 SEE ALSO
 
-    $self->{$key} = \@servers;
-}
+L<MR::IProto::Cluster>, L<MR::IProto::Cluster::Server>, L<MR::IProto::Message>.
 
-sub _die {
-    my ($self, @e) = @_;
-    die "$self->{name}: ".join('; ', @e);
-}
+=cut
 
-1;
+no Mouse;
+__PACKAGE__->meta->make_immutable();
 
+1;
diff --git a/connector/perl/lib/MR/IProto/Cluster.pm b/connector/perl/lib/MR/IProto/Cluster.pm
new file mode 100644
index 0000000000000000000000000000000000000000..d5f46b8bdeffe20e559bfb387157597e234762c9
--- /dev/null
+++ b/connector/perl/lib/MR/IProto/Cluster.pm
@@ -0,0 +1,253 @@
+package MR::IProto::Cluster;
+
+=head1 NAME
+
+MR::IProto::Cluster - cluster of servers
+
+=head1 DESCRIPTION
+
+This class is used to implement balancing between several servers.
+
+=cut
+
+use Mouse;
+use Mouse::Util::TypeConstraints;
+use MR::IProto::Cluster::Server;
+use String::CRC32 qw(crc32);
+
+=head1 EXPORTED CONSTANTS
+
+=over
+
+=item RR
+
+Round robin algorithm
+
+=item HASH
+
+Hashing algorithm using CRC32
+
+=item KETAMA
+
+Ketama algorithm
+
+=back
+
+=cut
+
+use Exporter 'import';
+our @EXPORT_OK = qw( RR HASH KETAMA );
+
+coerce 'MR::IProto::Cluster'
+    => from 'Str'
+    => via { __PACKAGE__->new( servers => $_ ) };
+
+subtype 'MR::IProto::Cluster::Servers'
+    => as 'ArrayRef[MR::IProto::Cluster::Server]'
+    => where { scalar @_ };
+coerce 'MR::IProto::Cluster::Servers'
+    => from 'Str'
+    => via {
+        my $type = find_type_constraint('MR::IProto::Cluster::Server');
+        [ map $type->coerce($_), split /,/, $_ ];
+    };
+
+use constant {
+    RR     => 1,
+    HASH   => 2,
+    KETAMA => 3,
+};
+
+enum 'MR::IProto::Balance' => (
+    RR,
+    HASH,
+    KETAMA,
+);
+coerce 'MR::IProto::Balance'
+    => from 'Str',
+    => via {
+        $_ eq 'hash-crc32' ? HASH
+            : $_ eq 'ketama' ? KETAMA
+            : RR;
+    };
+
+=head1 ATTRIBUTES
+
+=over
+
+=item balance
+
+Balancing algorithms.
+Possible values are constants: RR, HASH, KETAMA.
+Or their string analogs: 'round-robin', 'hash-crc32', 'ketama'.
+
+=cut
+
+has balance => (
+    is  => 'ro',
+    isa => 'MR::IProto::Balance',
+    default => RR,
+    coerce  => 1,
+);
+
+=item servers
+
+ArrayRef of L<MR::IProto::Cluster::Server>.
+
+=cut
+
+has servers => (
+    is  => 'ro',
+    isa => 'MR::IProto::Cluster::Servers',
+    required => 1,
+    coerce   => 1,
+);
+
+=back
+
+=cut
+
+has _one => (
+    is  => 'ro',
+    isa => 'Maybe[MR::IProto::Cluster::Server]',
+    lazy_build => 1,
+);
+
+has _ketama => (
+    is  => 'ro',
+    isa => 'ArrayRef[ArrayRef]',
+    lazy_build => 1,
+);
+
+has _rr_servers => (
+    is  => 'rw',
+    isa => 'MR::IProto::Cluster::Servers',
+    lazy_build => 1,
+);
+
+has _hash_servers => (
+    is  => 'rw',
+    isa => 'MR::IProto::Cluster::Servers',
+    lazy_build => 1,
+);
+
+=head1 PUBLIC METHODS
+
+=over
+
+=item server( $key? )
+
+Get server from balancing using C<$key>.
+
+=cut
+
+sub server {
+    my ($self, $key) = @_;
+    my $one = $self->_one;
+    return $one if defined $one;
+    my $method = $self->balance == RR ? '_balance_rr'
+        : $self->balance == KETAMA ? '_balance_ketama'
+        : '_balance_hash';
+    return $self->$method($key);
+}
+
+=item timeout( $new? )
+
+Used to set C<$new> timeout value to all servers.
+If argument is skipped and timeout is equal for all servers then returns
+it value, if timeout is different then returns undef.
+
+=cut
+
+sub timeout {
+    my $self = shift;
+    if(@_) {
+        my $timeout = shift;
+        $_->timeout($timeout) foreach @{$self->servers};
+        return $timeout;
+    }
+    else {
+        my $timeout;
+        foreach my $t ( map $_->timeout, @{$self->servers} ) {
+            return if defined $timeout && $timeout != $t;
+            $timeout = $t unless defined $timeout;
+        }
+        return $timeout;
+    }
+}
+
+=back
+
+=cut
+
+sub _build__one {
+    my ($self) = @_;
+    return @{$self->servers} == 1 ? $self->servers->[0] : undef;
+}
+
+sub _build__ketama {
+    my $self = shift;
+    my @ketama;
+    foreach my $server (@{$self->servers}) {
+        for my $i (0..10) {
+           push @ketama, [crc32($server->host.$server->port.$i), $server];
+        }
+    }
+    return [ sort { $a->[0] cmp $b->[0] } @ketama ];
+}
+
+sub _build__rr_servers {
+    my ($self) = @_;
+    return [ @{ $self->servers } ];
+}
+
+sub _build__hash_servers {
+    my ($self) = @_;
+    return [ map { my @s; my $s = $_; push @s, $s for ( 1 .. $s->weight ); @s } @{ $self->servers } ];
+}
+
+sub _balance_rr {
+    my ($self) = @_;
+    $self->_clear_rr_servers() if @{$self->_rr_servers} == 0;
+    return splice(@{$self->_rr_servers}, int(rand(@{$self->_rr_servers})), 1);
+}
+
+sub _balance_hash {
+    my ($self, $key) = @_;
+    my ($hash_, $hash, $server);
+
+    die "Cannot balance hash without key" unless defined $key;
+    $hash = $hash_ = crc32($key) >> 16;
+    for (0..19) {
+        $server = $self->_hash_servers->[$hash % @{$self->_hash_servers}];
+        return $server if $server->active;
+        $hash += crc32($_, $hash_) >> 16;
+    }
+
+    return $self->_hash_servers->[rand @{$self->_hash_servers}]; #last resort
+}
+
+sub _balance_ketama {
+    my ($self, $key) = @_;
+
+    die "Cannot balance ketama without key" unless defined $key;
+    my $idx = crc32($key);
+
+    foreach (@{$self->_ketama}) {
+       next unless ($_);
+       return $_->[1] if ($_->[0] >= $idx);
+    }
+
+    return $self->_ketama->[0]->[1];
+}
+
+=head1 SEE ALSO
+
+L<MR::IProto>, L<MR::IProto::Cluster::Server>.
+
+=cut
+
+no Mouse;
+__PACKAGE__->meta->make_immutable();
+
+1;
diff --git a/connector/perl/lib/MR/IProto/Cluster/Server.pm b/connector/perl/lib/MR/IProto/Cluster/Server.pm
new file mode 100644
index 0000000000000000000000000000000000000000..e22de5e00ef054e60043bb36881fe2f33573b582
--- /dev/null
+++ b/connector/perl/lib/MR/IProto/Cluster/Server.pm
@@ -0,0 +1,232 @@
+package MR::IProto::Cluster::Server;
+
+=head1 NAME
+
+MR::IProto::Cluster::Server - server
+
+=head1 DESCRIPTION
+
+This class is used to implement all communication with one server.
+
+=cut
+
+use Mouse;
+use Mouse::Util::TypeConstraints;
+use MR::IProto::Connection::Async;
+use MR::IProto::Connection::Sync;
+use MR::IProto::Message;
+
+with 'MR::IProto::Role::Debuggable';
+
+coerce 'MR::IProto::Cluster::Server'
+    => from 'Str'
+    => via {
+        my ($host, $port, $weight) = split /:/, $_;
+        __PACKAGE__->new(
+            host => $host,
+            port => $port,
+            defined $weight ? ( weight => $weight ) : (),
+        );
+    };
+
+=head1 ATTRIBUTES
+
+=over
+
+=item host
+
+Host name or IP address.
+
+=cut
+
+has host => (
+    is  => 'ro',
+    isa => 'Str',
+    required => 1,
+);
+
+=item port
+
+Port number.
+
+=cut
+
+has port => (
+    is  => 'ro',
+    isa => 'Int',
+    required => 1,
+);
+
+=item weight
+
+Server weight.
+
+=cut
+
+has weight => (
+    is  => 'ro',
+    isa => 'Int',
+    default => 1,
+);
+
+=item connect_timeout
+
+Timeout of connect operation.
+
+=cut
+
+has connect_timeout => (
+    is  => 'rw',
+    isa => 'Num',
+    default => 2,
+);
+
+=item timeout
+
+Timeout of read and write operations.
+
+=cut
+
+has timeout => (
+    is  => 'rw',
+    isa => 'Num',
+    default => 2,
+    trigger => sub {
+        my ($self, $new) = @_;
+        $self->async->set_timeout($new) if $self->has_async();
+        $self->sync->set_timeout($new) if $self->has_sync();
+        return;
+    },
+);
+
+=item tcp_nodelay
+
+Enable TCP_NODELAY.
+
+=cut
+
+has tcp_nodelay => (
+    is  => 'ro',
+    isa => 'Int',
+    default => 1,
+);
+
+=item tcp_keepalive
+
+Enable SO_KEEPALIVE.
+
+=cut
+
+has tcp_keepalive => (
+    is  => 'ro',
+    isa => 'Int',
+    default => 0,
+);
+
+=item max_parallel
+
+Max amount of simultaneous request.
+
+=cut
+
+has max_parallel => (
+    is  => 'ro',
+    isa => 'Int',
+    default => 10,
+);
+
+=item active
+
+Is server used in balancing.
+
+=cut
+
+has active => (
+    is  => 'rw',
+    isa => 'Bool',
+    default => 1,
+);
+
+has on_close => (
+    is  => 'rw',
+    isa => 'CodeRef',
+);
+
+has async => (
+    is  => 'ro',
+    isa => 'MR::IProto::Connection::Async',
+    lazy_build => 1,
+);
+
+has sync => (
+    is  => 'ro',
+    isa => 'MR::IProto::Connection::Sync',
+    lazy_build => 1,
+);
+
+=back
+
+=head1 PROTECTED METHODS
+
+=over
+
+=cut
+
+sub _build_async {
+    my ($self) = @_;
+    return MR::IProto::Connection::Async->new( server => $self );
+}
+
+sub _build_sync {
+    my ($self) = @_;
+    return MR::IProto::Connection::Sync->new( server => $self );
+}
+
+sub _build_debug_cb {
+    my ($self) = @_;
+    return sub {
+        my ($msg) = @_;
+        chomp $msg;
+        warn "MR::IProto: $msg\n";
+        return;
+    };
+}
+
+=item _send_started( $sync, $message, $data )
+
+This method is called when message is started to send.
+
+=cut
+
+sub _send_started {
+    return;
+}
+
+=item _recv_finished( $sync, $message, $data, $error )
+
+This method is called when message is received.
+
+=cut
+
+sub _recv_finished {
+    return;
+}
+
+sub _debug {
+    my ($self, $msg) = @_;
+    $self->debug_cb->( sprintf "%s:%d: %s", $self->host, $self->port, $msg );
+    return;
+}
+
+=back
+
+=head1 SEE ALSO
+
+L<MR::IProto>, L<MR::IProto::Cluster>.
+
+=cut
+
+no Mouse;
+__PACKAGE__->meta->make_immutable();
+
+1;
diff --git a/connector/perl/lib/MR/IProto/Connection.pm b/connector/perl/lib/MR/IProto/Connection.pm
new file mode 100644
index 0000000000000000000000000000000000000000..4cc0d66a2c2d8c89ee69075d083627ebd71a6191
--- /dev/null
+++ b/connector/perl/lib/MR/IProto/Connection.pm
@@ -0,0 +1,67 @@
+package MR::IProto::Connection;
+
+=head1 NAME
+
+MR::IProto::Connection - base communication class
+
+=head1 DESCRIPTION
+
+Base class for sync and async connections.
+
+=cut
+
+use Mouse;
+
+=head1 server
+
+=over
+
+=item server
+
+Instanse of L<MR::IProto::Cluster::Server>.
+
+=cut
+
+has server => (
+    is  => 'ro',
+    isa => 'MR::IProto::Cluster::Server',
+    required => 1,
+    weak_ref => 1,
+    handles  => [qw(
+        host
+        port
+        connect_timeout
+        timeout
+        tcp_nodelay
+        tcp_keepalive
+        max_parallel
+        _send_started
+        _recv_finished
+        _debug
+        _debug_dump
+    )],
+);
+
+=back
+
+=cut
+
+sub _pack_header {
+    my ($self, $msg, $length, $sync) = @_;
+    return pack 'L3', $msg, $length, $sync;
+}
+
+sub _unpack_header {
+    my ($self, $header) = @_;
+    return unpack 'L3', $header;
+}
+
+sub _choose_sync {
+    my ($self) = @_;
+    return int(rand 0xffffffff);
+}
+
+no Mouse;
+__PACKAGE__->meta->make_immutable();
+
+1;
diff --git a/connector/perl/lib/MR/IProto/Connection/Async.pm b/connector/perl/lib/MR/IProto/Connection/Async.pm
new file mode 100644
index 0000000000000000000000000000000000000000..d78b9180ff7eafcf10b7fac169b513c90bef57d4
--- /dev/null
+++ b/connector/perl/lib/MR/IProto/Connection/Async.pm
@@ -0,0 +1,280 @@
+package MR::IProto::Connection::Async;
+
+=head1 NAME
+
+MR::IProto::Connection::Async - async communication
+
+=head1 DESCRIPTION
+
+Used to perform asynchronous communication.
+
+=cut
+
+use Mouse;
+extends 'MR::IProto::Connection';
+
+use AnyEvent::Handle;
+use Scalar::Util qw(weaken);
+
+has _handle => (
+    is  => 'ro',
+    isa => 'AnyEvent::Handle',
+    lazy_build => 1,
+);
+
+has _queue => (
+    is  => 'ro',
+    isa => 'ArrayRef',
+    lazy_build => 1,
+);
+
+has _in_progress => (
+    is  => 'rw',
+    isa => 'Int',
+    default => 0,
+);
+
+has _callbacks => (
+    is  => 'ro',
+    isa => 'HashRef',
+    lazy_build => 1,
+);
+
+has _read_reply => (
+    is  => 'ro',
+    isa => 'CodeRef',
+    lazy_build => 1,
+);
+
+has _no_reply => (
+    is  => 'ro',
+    isa => 'ArrayRef',
+    lazy_build => 1,
+);
+
+has _on_drain => (
+    is  => 'ro',
+    isa => 'CodeRef',
+    lazy_build => 1,
+);
+
+=head1 PUBLIC METHODS
+
+=over
+
+=item send
+
+Enqueue message send.
+For list of arguments see L</_send>.
+
+=cut
+
+sub send {
+    my $self = shift;
+    if( $self->_in_progress < $self->max_parallel ) {
+        $self->_in_progress( $self->_in_progress + 1 );
+        $self->_send(@_);
+    }
+    else {
+        push @{$self->_queue}, [@_];
+    }
+    return;
+}
+
+=item set_timeout( $timeout )
+
+Set timeout value for existing connection.
+
+=cut
+
+sub set_timeout {
+    my ($self, $timeout) = @_;
+    $self->_handle->timeout($timeout) if $self->_has_handle();
+    return;
+}
+
+=back
+
+=head1 PROTECTED METHODS
+
+=over
+
+=item _send( $msg, $payload, $callback, $no_reply )
+
+Send message to server.
+
+=cut
+
+sub _send {
+    my ($self, $msg, undef, $callback, $no_reply, $sync) = @_;
+    $sync = $self->_choose_sync() unless defined $sync;
+    my $header = $self->_pack_header($msg, length $_[2], $sync);
+    my $server = $self->server;
+    $self->_callbacks->{$sync} = $callback;
+    $server->_send_started($sync, $msg, $_[2]);
+    my $handle = $self->_handle;
+    if( $server->debug >= 5 ) {
+        $server->_debug_dump('send header: ', $header);
+        $server->_debug_dump('send payload: ', $_[2]);
+    }
+    $handle->push_write($header);
+    $handle->push_write($_[2]);
+    if( $no_reply ) {
+        push @{$self->_no_reply}, $sync;
+        $handle->on_drain( $self->_on_drain ) unless defined $handle->{on_drain};
+    }
+    else {
+        $handle->push_read( chunk => 12, $self->_read_reply );
+    }
+    return;
+}
+
+sub _build__read_reply {
+    my ($self) = @_;
+    my $server = $self->server;
+    weaken($self);
+    weaken($server);
+    return sub {
+        my ($handle, $data) = @_;
+        my $dump_resp = $server->debug >= 6;
+        $server->_debug_dump('recv header: ', $data) if $dump_resp;
+        my ($msg, $payload_length, $sync) = $self->_unpack_header($data);
+        $handle->unshift_read( chunk => $payload_length, sub {
+            my ($handle, $data) = @_;
+            $server->_debug_dump('recv payload: ', $data) if $dump_resp;
+            $server->_recv_finished($sync, $msg, $data);
+            $self->_finish_and_start();
+            delete($self->_callbacks->{$sync})->($msg, $data);
+            return;
+        });
+        return;
+    };
+}
+
+sub _try_to_send {
+    my ($self) = @_;
+    while( $self->_in_progress < $self->max_parallel && (my $task = shift @{ $self->_queue }) ) {
+        $self->_in_progress( $self->_in_progress + 1 );
+        $self->_send(@$task);
+    }
+    return;
+}
+
+sub _finish_and_start {
+    my ($self) = @_;
+    if( my $task = shift @{$self->_queue} ) {
+        $self->_send(@$task);
+    }
+    else {
+        $self->_in_progress( $self->_in_progress - 1 );
+    }
+    return;
+}
+
+sub _build__handle {
+    my ($self) = @_;
+    my $server = $self->server;
+    $server->_debug("connecting") if $server->debug >= 4;
+    weaken($self);
+    weaken($server);
+    return AnyEvent::Handle->new(
+        connect    => [ $self->host, $self->port ],
+        no_delay   => $self->tcp_nodelay,
+        keepalive  => $self->tcp_keepalive,
+        timeout    => $self->timeout,
+        on_prepare => sub {
+            return $self->connect_timeout;
+        },
+        on_connect => sub {
+            my ($handle) = @_;
+            $server->_debug("connected") if $server->debug >= 1;
+            return;
+        },
+        on_error   => sub {
+            my ($handle, $fatal, $message) = @_;
+            my $errno = $!;
+            $server->_debug(($fatal ? 'fatal ' : '') . 'error: ' . $message);
+            my @callbacks;
+            foreach my $sync ( keys %{$self->_callbacks} ) {
+                $server->_recv_finished($sync, undef, undef, $message, $errno);
+                $self->_in_progress( $self->_in_progress - 1 );
+                push @callbacks, $self->_callbacks->{$sync};
+            }
+            $server->active(0);
+            $self->_clear_handle();
+            $self->_clear_callbacks();
+            $self->_clear_no_reply();
+            $server->_debug('closing socket') if $server->debug >= 1;
+            $handle->destroy();
+            $self->_try_to_send();
+            $_->(undef, undef, $message, $errno) foreach @callbacks;
+            return;
+        },
+        on_timeout => sub {
+            my ($handle) = @_;
+            return unless keys %{$self->_callbacks};
+            $handle->_error( Errno::ETIMEDOUT ) if keys %{$self->_callbacks};
+            return;
+        },
+    );
+}
+
+sub _build__on_drain {
+    my ($self) = @_;
+    my $server = $self->server;
+    weaken($self);
+    weaken($server);
+    return sub {
+        my ($handle) = @_;
+        if( $self->_has_no_reply() ) {
+            foreach my $sync ( @{$self->_no_reply} ) {
+                $server->_recv_finished($sync, undef, undef);
+                $self->_in_progress( $self->_in_progress - 1 );
+                delete($self->_callbacks->{$sync})->(undef, undef);
+            }
+            $self->_clear_no_reply();
+            $self->_try_to_send();
+            $handle->on_drain(undef);
+        }
+        return;
+    };
+}
+
+sub _build__queue {
+    my ($self) = @_;
+    return [];
+}
+
+sub _build__callbacks {
+    my ($self) = @_;
+    return {};
+}
+
+sub _build__no_reply {
+    my ($self) = @_;
+    return [];
+}
+
+around _choose_sync => sub {
+    my ($orig, $self) = @_;
+    my $sync;
+    my $callbacks = $self->_callbacks;
+    for( 1 .. 50 ) {
+        $sync = $self->$orig();
+        return $sync unless exists $callbacks->{$sync};
+    }
+    die "Can't choose sync value after 50 iterations";
+};
+
+=back
+
+=head1 SEE ALSO
+
+L<MR::IProto::Connection>, L<MR::IProto::Cluster::Server>.
+
+=cut
+
+no Mouse;
+__PACKAGE__->meta->make_immutable();
+
+1;
diff --git a/connector/perl/lib/MR/IProto/Connection/Sync.pm b/connector/perl/lib/MR/IProto/Connection/Sync.pm
new file mode 100644
index 0000000000000000000000000000000000000000..7c3da90c46e690d3eaca9e31e2dc6d6b2e1b3d89
--- /dev/null
+++ b/connector/perl/lib/MR/IProto/Connection/Sync.pm
@@ -0,0 +1,243 @@
+package MR::IProto::Connection::Sync;
+
+=head1 NAME
+
+MR::IProto::Connection::Sync - sync communication
+
+=head1 DESCRIPTION
+
+Used to perform synchronous communication.
+
+=cut
+
+use Mouse;
+extends 'MR::IProto::Connection';
+
+use Errno;
+use IO::Socket::INET;
+use Socket qw( TCP_NODELAY SO_KEEPALIVE SO_SNDTIMEO SO_RCVTIMEO );
+
+has _socket => (
+    is  => 'ro',
+    isa => 'IO::Socket::INET',
+    lazy_build => 1,
+);
+
+has _sent => (
+    is  => 'ro',
+    default => sub { [] },
+);
+
+=head1 PUBLIC METHODS
+
+=over
+
+=item send
+
+See L<MR::IProto::Connection/send> for more information.
+
+=cut
+
+sub send {
+    my ($self, $msg, $payload, $callback, $no_reply, $sync) = @_;
+    my $server = $self->server;
+    my $sent = $self->_sent;
+    my $ok = eval {
+        $sync = $self->_choose_sync() unless defined $sync;
+        $server->_send_started($sync, $msg, $payload);
+
+        my $socket = $self->_socket;
+        unless (@$sent) {
+            vec((my $rin = ''), fileno($socket), 1) = 1;
+            if (select((my $rout = $rin), undef, undef, 0) > 0) {
+                if (sysread($socket, my $buf, 1)) {
+                    die "More then 0 bytes was received when nothing was waited for";
+                } else {
+                    # Connection was closed by other side, socket is in CLOSE_WAIT state, reconnecting
+                    $self->_clear_socket();
+                    $socket = $self->_socket;
+                }
+            }
+        }
+
+        my $header = $self->_pack_header($msg, length $payload, $sync);
+        if( $server->debug >= 5 ) {
+            $server->_debug_dump('send header: ', $header);
+            $server->_debug_dump('send payload: ', $payload);
+        }
+
+        my $write = $header . $payload;
+        while( length $write ) {
+            my $written = syswrite($socket, $write);
+            if (!defined $written) {
+                if ($! != Errno::EINTR) {
+                    $! = Errno::ETIMEDOUT if $! == Errno::EAGAIN; # Hack over SO_SNDTIMEO behaviour
+                    die "send: $!";
+                }
+            } else {
+                substr $write, 0, $written, '';
+            }
+        }
+        1;
+    };
+    if($ok) {
+        if ($no_reply) {
+            $callback->(undef, undef);
+            $server->_recv_finished($sync, undef, undef);
+        } else {
+            push @$sent, [$sync, $callback];
+        }
+    }
+    else {
+        $self->_handle_error($sync, $callback, $@);
+    }
+    return;
+}
+
+sub recv_all {
+    my ($self) = @_;
+    my $server = $self->server;
+    my $sent = $self->_sent;
+    my $dump_resp = $server->debug >= 6;
+    while (my $args = shift @$sent) {
+        my ($sync, $callback) = @$args;
+        my ($resp_msg, $resp_payload);
+        my $ok = eval {
+            my $socket = $self->_socket;
+            my $resp_header;
+            my $to_read = 12;
+            while( $to_read ) {
+                my $read = sysread($socket, my $buf, $to_read);
+                if (!defined $read) {
+                    if ($! != Errno::EINTR) {
+                        $! = Errno::ETIMEDOUT if $! == Errno::EAGAIN; # Hack over SO_RCVTIMEO behaviour
+                        die "recv: $!";
+                    }
+                } elsif ($read == 0) {
+                    die "recv: Unexpected end-of-file";
+                } else {
+                    $resp_header .= $buf;
+                    $to_read -= $read;
+                }
+            }
+            $server->_debug_dump('recv header: ', $resp_header) if $dump_resp;
+            ($resp_msg, my $resp_length, my $resp_sync) = $self->_unpack_header($resp_header);
+            die "Request and reply sync is different: $resp_sync != $sync" unless $resp_sync == $sync;
+
+            $to_read = $resp_length;
+            while( $to_read ) {
+                my $read = sysread($socket, my $buf, $to_read);
+                if (!defined $read) {
+                    if ($! != Errno::EINTR) {
+                        $! = Errno::ETIMEDOUT if $! == Errno::EAGAIN; # Hack over SO_RCVTIMEO behaviour
+                        die "recv: $!";
+                    }
+                } elsif ($read == 0) {
+                    die "recv: Unexpected end-of-file";
+                } else {
+                    $resp_payload .= $buf;
+                    $to_read -= $read;
+                }
+            }
+            $server->_debug_dump('recv payload: ', $resp_payload) if $dump_resp;
+            1;
+        };
+        if($ok) {
+            $server->_recv_finished($sync, $resp_msg, $resp_payload);
+            $callback->($resp_msg, $resp_payload);
+        }
+        else {
+            $self->_handle_error($sync, $callback, $@);
+        }
+    }
+    return;
+}
+
+=item set_timeout( $timeout )
+
+Set timeout value for existing connection.
+
+=cut
+
+sub set_timeout {
+    my ($self, $timeout) = @_;
+    $self->_set_timeout($self->_socket, $timeout) if $self->_has_socket();
+    return;
+}
+
+=back
+
+=cut
+
+sub _build__socket {
+    my ($self) = @_;
+    my $server = $self->server;
+    $server->_debug("connecting") if $server->debug >= 4;
+    my $socket = IO::Socket::INET->new(
+        PeerHost => $self->host,
+        PeerPort => $self->port,
+        Proto    => 'tcp',
+        Timeout  => $self->connect_timeout,
+    ) or do {
+        $@ =~ s/^IO::Socket::INET: (?:connect: )?//;
+        if ($@ eq 'timeout') {
+            # Hack over IO::Socket behaviour
+            $! = Errno::ETIMEDOUT;
+            $@ = "$!";
+        }
+        die "connect: $@";
+    };
+    $socket->sockopt(SO_KEEPALIVE, 1) if $self->tcp_keepalive;
+    $socket->setsockopt((getprotobyname('tcp'))[2], TCP_NODELAY, 1) if $self->tcp_nodelay;
+    $self->_set_timeout($socket, $self->timeout) if $self->timeout;
+    $server->_debug("connected") if $server->debug >= 4;
+    return $socket;
+}
+
+sub _set_timeout {
+    my ($self, $socket, $timeout) = @_;
+    my $sec  = int $timeout; # seconds
+    my $usec = int( ($timeout - $sec) * 1_000_000 ); # micro-seconds
+    my $timeval = pack "L!L!", $sec, $usec; # struct timeval;
+    $socket->sockopt(SO_SNDTIMEO, $timeval);
+    $socket->sockopt(SO_RCVTIMEO, $timeval);
+    return;
+}
+
+sub _handle_error {
+    my ($self, $sync, $callback, $error) = @_;
+    my $errno = $!;
+    if (!$error) {
+        $error = 'Unknown error';
+    } elsif ($error =~ /^(.+?) at \S+ line \d+/s) {
+        $error = $1;
+    }
+    my $server = $self->server;
+    $server->_debug("error: $error");
+    if($self->_has_socket()) {
+        close($self->_socket);
+        $self->_clear_socket();
+    }
+    $server->active(0);
+    my $sent = $self->_sent;
+    my @sent = splice @$sent, 0, scalar @$sent;
+    $server->_recv_finished($sync, undef, undef, $error, $errno);
+    $callback->(undef, undef, $error, $errno);
+    foreach my $args (@sent) {
+        my ($sync, $callback) = @$args;
+        $server->_recv_finished($sync, undef, undef, $error, $errno);
+        $callback->(undef, undef, $error, $errno);
+    }
+    return
+}
+
+=head1 SEE ALSO
+
+L<MR::IProto::Connection>, L<MR::IProto::Cluster::Server>.
+
+=cut
+
+no Mouse;
+__PACKAGE__->meta->make_immutable();
+
+1;
diff --git a/connector/perl/lib/MR/IProto/Error.pm b/connector/perl/lib/MR/IProto/Error.pm
new file mode 100644
index 0000000000000000000000000000000000000000..93eda1724bce7bcd5db2b9282c21ba653034dfd0
--- /dev/null
+++ b/connector/perl/lib/MR/IProto/Error.pm
@@ -0,0 +1,89 @@
+package MR::IProto::Error;
+
+=head1 NAME
+
+MR::IProto::Error - iproto error
+
+=head1 DESCRIPTION
+
+Instance of this class is returned instead of L<MR::IProto::Response>
+if error was occured.
+
+=cut
+
+use Mouse;
+
+=head1 PUBLIC ATTRIBUTES
+
+=over
+
+=item error
+
+Error string.
+
+=cut
+
+has error => (
+    is  => 'ro',
+    isa => 'Str',
+    required => 1,
+);
+
+=item errno
+
+Integer value of C<$!>.
+
+=cut
+
+has errno => (
+    is  => 'ro',
+    isa => 'Int',
+);
+
+=item request
+
+Instance of L<MR::IProto::Request>.
+
+=cut
+
+has request => (
+    is  => 'ro',
+    isa => 'MR::IProto::Request',
+    required => 1,
+);
+
+=back
+
+=head1 PUBLIC METHODS
+
+=over
+
+=item is_error
+
+Always returns true.
+
+=cut
+
+sub is_error {
+    return 1;
+}
+
+=item error_message
+
+Error message text.
+
+=cut
+
+sub error_message {
+    my ($self) = @_;
+    return $self->error;
+}
+
+=back
+
+=cut
+
+no Mouse;
+__PACKAGE__->meta->make_immutable();
+
+1;
diff --git a/connector/perl/lib/MR/IProto/Message.pm b/connector/perl/lib/MR/IProto/Message.pm
new file mode 100644
index 0000000000000000000000000000000000000000..2282378871687493a24ea0545d4dc9b6b342c626
--- /dev/null
+++ b/connector/perl/lib/MR/IProto/Message.pm
@@ -0,0 +1,41 @@
+package MR::IProto::Message;
+
+=head1 NAME
+
+MR::IProto::Message - iproto message
+
+=head1 DESCRIPTION
+
+Base class for all messages sent throught iproto protocol.
+
+=cut
+
+use Mouse;
+
+=head1 ATTRIBUTES
+
+=over
+
+=item data
+
+Binary representation of message data.
+
+=cut
+
+has data => (
+    is  => 'ro',
+    isa => 'Value',
+);
+
+=back
+
+=head1 SEE ALSO
+
+L<MR::IProto>.
+
+=cut
+
+no Mouse;
+__PACKAGE__->meta->make_immutable();
+
+1;
diff --git a/connector/perl/lib/MR/IProto/NoResponse.pm b/connector/perl/lib/MR/IProto/NoResponse.pm
new file mode 100644
index 0000000000000000000000000000000000000000..e3a5be524f922826cb1d6279264184c05a55ef00
--- /dev/null
+++ b/connector/perl/lib/MR/IProto/NoResponse.pm
@@ -0,0 +1,23 @@
+package MR::IProto::NoResponse;
+
+=head1 NAME
+
+MR::IProto::NoResponse - no response
+
+=head1 DESCRIPTION
+
+Base class used to mark messages with no response.
+
+=cut
+
+use Mouse;
+extends 'MR::IProto::Response';
+
+has '+data' => (
+    isa => 'Undef',
+);
+
+no Mouse;
+__PACKAGE__->meta->make_immutable();
+
+1;
diff --git a/connector/perl/lib/MR/IProto/Request.pm b/connector/perl/lib/MR/IProto/Request.pm
new file mode 100644
index 0000000000000000000000000000000000000000..5c86e5842ff1999afe0eb99b36fdbf4e72d890c6
--- /dev/null
+++ b/connector/perl/lib/MR/IProto/Request.pm
@@ -0,0 +1,73 @@
+package MR::IProto::Request;
+
+=head1 NAME
+
+MR::IProto::Request - request message
+
+=head1 DESCRIPTION
+
+Base class for all request messages.
+
+=cut
+
+use Mouse;
+extends 'MR::IProto::Message';
+
+has '+data' => (
+    lazy_build => 1,
+);
+
+=head1 PUBLIC METHODS
+
+=over
+
+=item key
+
+Returns key value.
+You must reimplement this method in your subclass to use this feature.
+
+=cut
+
+sub key {
+    return undef;
+}
+
+=item retry
+
+If request retry is allowed.
+
+=cut
+
+sub retry {
+    return 0;
+}
+
+=back
+
+=head1 PROTECTED METHODS
+
+=over
+
+=item key_attr
+
+Class method which must return name of key attribute.
+
+=item _build_data
+
+You B<must> implement this method in you subclass to pack your object.
+Returns packed data.
+
+=cut
+
+sub _build_data {
+    return '';
+}
+
+=back
+
+=cut
+
+no Mouse;
+__PACKAGE__->meta->make_immutable();
+
+1;
diff --git a/connector/perl/lib/MR/IProto/Response.pm b/connector/perl/lib/MR/IProto/Response.pm
new file mode 100644
index 0000000000000000000000000000000000000000..49088530cd4b2b2e0829522c25ca54c080387f8a
--- /dev/null
+++ b/connector/perl/lib/MR/IProto/Response.pm
@@ -0,0 +1,94 @@
+package MR::IProto::Response;
+
+=head1 NAME
+
+MR::IProto::Response - response message
+
+=head1 DESCRIPTION
+
+Base class for all response messages.
+
+=cut
+
+use Mouse;
+extends 'MR::IProto::Message';
+
+=head1 PUBLIC ATTRIBUTES
+
+=over
+
+=item request
+
+Instance of L<MR::IProto::Request>.
+
+=cut
+
+has request => (
+    is  => 'ro',
+    isa => 'MR::IProto::Request',
+    required => 1,
+);
+
+=back
+
+=head1 PUBLIC METHODS
+
+=over
+
+=item retry
+
+Is request retry must be done.
+
+=cut
+
+sub retry {
+    return 0;
+}
+
+=back
+
+=head1 PROTECTED METHODS
+
+=over
+
+=item BUILDARGS( %args | \%args | $data )
+
+If C<$data> is passed as argument then it unpacked and object is
+created based on information contained in it.
+
+See L<Mouse::Manual::Construction/BUILDARGS> for more information.
+
+=cut
+
+around BUILDARGS => sub {
+    my ($orig, $class, %args) = @_;
+    if( exists $args{data} ) {
+        my $parsed_args = $class->_parse_data($args{data});
+        my $tail = delete $parsed_args->{data};
+        warn "Not all data was parsed" if defined $tail && length $tail;
+        return $class->$orig(%$parsed_args, %args);
+    }
+    else {
+        return $class->$orig(%args);
+    }
+};
+
+=item _parse_data( $data )
+
+You B<must> implement this method in you subclass to unpack your object.
+Returns hashref of attributes which will be passed to constructor.
+
+=cut
+
+sub _parse_data {
+    return { data => $_[1] };
+}
+
+=back
+
+=cut
+
+no Mouse;
+__PACKAGE__->meta->make_immutable();
+
+1;
diff --git a/connector/perl/lib/MR/IProto/Role/Debuggable.pm b/connector/perl/lib/MR/IProto/Role/Debuggable.pm
new file mode 100644
index 0000000000000000000000000000000000000000..34a670ed2899b5b8f98dec42053b62df9d99c71a
--- /dev/null
+++ b/connector/perl/lib/MR/IProto/Role/Debuggable.pm
@@ -0,0 +1,74 @@
+package MR::IProto::Role::Debuggable;
+
+=head1 NAME
+
+=head1 DESCRIPTION
+
+=cut
+
+use Mouse::Role;
+
+=item debug
+
+Deug level.
+
+=cut
+
+has debug => (
+    is  => 'rw',
+    isa => 'Int',
+    default => 0,
+);
+
+=item debug_cb
+
+Callback which is called when debug message is written.
+
+=cut
+
+has debug_cb => (
+    is  => 'rw',
+    isa => 'CodeRef',
+    lazy_build => 1,
+);
+
+=item dump_no_ints
+
+Skip print of integers in dump.
+
+=cut
+
+has dump_no_ints => (
+    is  => 'ro',
+    isa => 'Bool',
+);
+
+sub _build_debug_cb {
+    my ($self) = @_;
+    my $prefix = ref $self;
+    return sub {
+        my ($msg) = @_;
+        chomp $msg;
+        warn sprintf "$prefix: $msg\n";
+        return;
+    };
+}
+
+sub _debug {
+    $_[0]->debug_cb->($_[1]);
+}
+
+sub _debug_dump {
+    my ($self, $msg, $datum) = @_;
+    unless($self->dump_no_ints) {
+        $msg .= join(' ', unpack('L*', $datum));
+        $msg .= ' > ';
+    }
+    $msg .= join(' ', map { sprintf "%02x", $_ } unpack("C*", $datum));
+    $self->_debug($msg);
+    return;
+}
+
+no Mouse::Role;
+
+1;
diff --git a/connector/perl/lib/MR/IProto/Server.pm b/connector/perl/lib/MR/IProto/Server.pm
new file mode 100644
index 0000000000000000000000000000000000000000..4c8b121142dc5103d762801db4ef7f4985a785e8
--- /dev/null
+++ b/connector/perl/lib/MR/IProto/Server.pm
@@ -0,0 +1,120 @@
+package MR::IProto::Server;
+
+=head1 NAME
+
+=head1 DESCRIPTION
+
+=cut
+
+use Mouse;
+use AnyEvent::Handle;
+use AnyEvent::Socket;
+use Scalar::Util qw/weaken/;
+use MR::IProto::Server::Connection;
+
+with 'MR::IProto::Role::Debuggable';
+
+has prefix => (
+    is  => 'ro',
+    isa => 'Str',
+    default => sub { ref shift },
+);
+
+has host => (
+    is  => 'ro',
+    isa => 'Str',
+    default => '0.0.0.0',
+);
+
+has port => (
+    is  => 'ro',
+    isa => 'Int',
+    required => 1,
+);
+
+has handler => (
+    is  => 'ro',
+    isa => 'CodeRef',
+    required => 1,
+);
+
+has on_accept => (
+    is  => 'ro',
+    isa => 'CodeRef',
+);
+
+has on_close => (
+    is  => 'ro',
+    isa => 'CodeRef',
+);
+
+has on_error => (
+    is  => 'ro',
+    isa => 'CodeRef',
+);
+
+has _guard => (
+    is  => 'ro',
+    lazy_build => 1,
+);
+
+has _connections => (
+    is  => 'ro',
+    isa => 'HashRef',
+    default => sub { {} },
+);
+
+has _recv_payload => (
+    is  => 'ro',
+    isa => 'CodeRef',
+    lazy_build => 1,
+);
+
+sub run {
+    my ($self) = @_;
+    $self->_guard;
+    return;
+}
+
+sub _build_debug_cb {
+    my ($self) = @_;
+    my $prefix = $self->prefix;
+    return sub {
+        my ($msg) = @_;
+        chomp $msg;
+        warn sprintf "%s: %s\n", $prefix, $msg;
+        return;
+    };
+}
+
+sub _build__guard {
+    my ($self) = @_;
+    weaken($self);
+    return tcp_server $self->host, $self->port, sub {
+        my ($fh, $host, $port) = @_;
+        my $connection = MR::IProto::Server::Connection->new(
+            fh        => $fh,
+            host      => $host,
+            port      => $port,
+            handler   => $self->handler,
+            on_accept => $self->on_accept,
+            on_close  => sub {
+                my ($connection) = @_;
+                my $key = sprintf "%s:%d", $connection->host, $connection->port;
+                delete $self->_connections->{$key};
+                $self->on_close->($connection) if $self->on_close;
+                return;
+            },
+            on_error  => $self->on_error,
+            debug     => $self->debug,
+            debug_cb  => $self->debug_cb,
+        );
+        $self->_connections->{"$host:$port"} = $connection;
+        return;
+    };
+}
+
+no Mouse;
+__PACKAGE__->meta->make_immutable();
+
+1;
diff --git a/connector/perl/lib/MR/IProto/Server/Connection.pm b/connector/perl/lib/MR/IProto/Server/Connection.pm
new file mode 100644
index 0000000000000000000000000000000000000000..eb8260fe4b47411c8848e5e479c21fb4d407ba80
--- /dev/null
+++ b/connector/perl/lib/MR/IProto/Server/Connection.pm
@@ -0,0 +1,186 @@
+package MR::IProto::Server::Connection;
+
+=head1 NAME
+
+=head1 DESCRIPTION
+
+=cut
+
+use Mouse;
+use AnyEvent::DNS;
+use Scalar::Util qw/weaken/;
+
+with 'MR::IProto::Role::Debuggable';
+
+has handler => (
+    is  => 'ro',
+    isa => 'CodeRef',
+    required => 1,
+);
+
+has on_accept => (
+    is  => 'ro',
+    isa => 'CodeRef',
+);
+
+has on_close => (
+    is  => 'ro',
+    isa => 'CodeRef',
+);
+
+has on_error => (
+    is  => 'ro',
+    isa => 'CodeRef',
+);
+
+has fh => (
+    is  => 'ro',
+    isa => 'FileHandle',
+    required => 1,
+);
+
+has host => (
+    is  => 'ro',
+    isa => 'Str',
+    required => 1,
+);
+
+has port => (
+    is  => 'ro',
+    isa => 'Int',
+    required => 1,
+);
+
+has hostname => (
+    is  => 'ro',
+    isa => 'Str',
+    writer => '_hostname',
+    lazy_build => 1,
+);
+
+has _handle => (
+    is  => 'ro',
+    isa => 'AnyEvent::Handle',
+    lazy_build => 1,
+);
+
+has _recv_header => (
+    is  => 'ro',
+    isa => 'CodeRef',
+    lazy_build => 1,
+);
+
+sub BUILD {
+    my ($self) = @_;
+    $self->_debug(sprintf "Connection accepted") if $self->debug >= 1;
+    weaken($self);
+    AnyEvent::DNS::reverse_verify $self->host, sub {
+        my ($hostname) = @_;
+        if ($hostname) {
+            $self->_hostname($hostname);
+            $self->_debug(sprintf "%s resolved as %s", $self->host, $hostname) if $self->debug >= 4;
+        } else {
+            $self->_hostname($self->host);
+            $self->_debug(sprintf "Can't resolve %s", $self->host);
+        }
+        $self->_handle;
+        $self->on_accept->($self) if $self->on_accept;
+        return;
+    };
+    return;
+}
+
+sub DEMOLISH {
+    my ($self) = @_;
+    $self->_debug(sprintf "Object for connection was destroyed\n") if $self->debug >= 1;
+    return;
+}
+
+sub close {
+    my ($self) = @_;
+    $self->on_close->($self) if $self->on_close;
+    return;
+}
+
+sub _build_hostname {
+    my ($self) = @_;
+    return $self->host;
+}
+
+sub _build__handle {
+    my ($self) = @_;
+    weaken($self);
+    my $peername = join ':', $self->host, $self->port;
+    return AnyEvent::Handle->new(
+        fh       => $self->fh,
+        peername => $peername,
+        on_read  => sub {
+            my ($handle) = @_;
+            $handle->unshift_read( chunk => 12, $self->_recv_header );
+            return;
+        },
+        on_eof   => sub {
+            my ($handle) = @_;
+            $self->_debug("Connection closed by foreign host\n") if $self->debug >= 1;
+            $handle->destroy();
+            $self->on_close->($self) if $self->on_close;
+            return;
+        },
+        on_error => sub {
+            my ($handle, $fatal, $message) = @_;
+            $handle->destroy();
+            if ($self->on_error) {
+                $self->_debug("error: $message\n") if $self->debug >= 1;
+                $self->on_error->($self, $message);
+            } else {
+                $self->_debug("error: $message\n");
+            }
+            $self->on_close->($self) if $self->on_close;
+            return;
+        }
+    );
+    return;
+}
+
+sub _build__recv_header {
+    my ($self) = @_;
+    weaken($self);
+    my $handler = $self->handler;
+    return sub {
+        my ($handle, $data) = @_;
+        $self->_debug_dump('recv header: ', $data) if $self->debug >= 5;
+        my ($cmd, $length, $sync) = unpack 'L3', $data;
+        $handle->unshift_read(
+            chunk => $length,
+            sub {
+                my ($handle, $data) = @_;
+                $self->_debug_dump('recv payload: ', $data) if $self->debug >= 5;
+                my $result;
+                if (eval { $result = $handler->($self, $cmd, $data); 1 }) {
+                    my $header = pack 'L3', $cmd, length $result, $sync;
+                    if ($self->debug >= 6) {
+                        $self->_debug_dump('send header: ', $header);
+                        $self->_debug_dump('send payload: ', $result);
+                    }
+                    $handle->push_write($header . $result);
+                } else {
+                    warn $@;
+                    $self->_debug("Failed to handle cmd=$cmd\n");
+                }
+                return;
+            }
+        );
+        return;
+    };
+}
+
+sub _debug {
+    my ($self, $msg) = @_;
+    $self->debug_cb->( sprintf "%s(%s:%d): %s", $self->hostname, $self->host, $self->port, $msg );
+    return;
+}
+
+no Mouse;
+__PACKAGE__->meta->make_immutable();
+
+1;
diff --git a/connector/perl/lib/MR/SilverBox.pm b/connector/perl/lib/MR/SilverBox.pm
index cc8befc2d339714b612cdad081cb81e91551e107..8c0bfaf32a7930ab203ea5179447d50332ef49aa 100644
--- a/connector/perl/lib/MR/SilverBox.pm
+++ b/connector/perl/lib/MR/SilverBox.pm
@@ -1,646 +1,19 @@
 package MR::SilverBox;
+use MR::Tarantool::Box;
+use base qw/MR::Tarantool::Box/;
 
-use strict;
-use warnings;
-use Scalar::Util qw/looks_like_number/;
-use List::MoreUtils qw/each_arrayref/;
+=pod
 
-use MR::IProto ();
-use MR::Storage::Const ();
+=head1 NAME
 
-use constant {
-    WANT_RESULT       => 1,
-    INSERT_ADD        => 2,
-    INSERT_REPLACE    => 4,
-};
+MR::SilverBox
 
+A backward-compatiblity module. Is it obsolete and is unsupported. Do not use.
 
-sub IPROTOCLASS () { 'MR::IProto' }
-sub ERRSTRCLASS () { 'MR::Storage::Const::Errors::SilverBox' }
+=head1 SEE ALSO
 
-use vars qw/$VERSION/;
-$VERSION = 0;
+L<MR::Tarantool::Box>
 
-BEGIN { *confess = \&MR::IProto::confess }
-
-sub new {
-    my ($class, $arg) = @_;
-    my $self;
-
-    $arg = { %$arg };
-    $self->{name}            = $arg->{name}      || ref$class || $class;
-    $self->{timeout}         = $arg->{timeout}   || 23;
-    $self->{retry    }       = $arg->{retry}     || 1;
-    $self->{select_retry}    = $arg->{select_retry} || 3;
-    $self->{softretry}       = $arg->{softretry} || 3;
-    $self->{debug}           = $arg->{'debug'}   || 0;
-    $self->{ipdebug}         = $arg->{'ipdebug'} || 0;
-    $self->{raise}           = 1;
-    $self->{raise}           = $arg->{raise} if exists $arg->{raise};
-    $self->{hashify}         = $arg->{'hashify'} if exists $arg->{'hashify'};
-    $self->{default_raw}     = exists $arg->{default_raw} ? $arg->{default_raw} : !$self->{hashify};
-    $self->{select_timeout}  = $arg->{select_timeout} || $self->{timeout};
-    $self->{iprotoclass}     = $arg->{iprotoclass} || $class->IPROTOCLASS;
-    $self->{errstrclass}     = $arg->{errstrclass} || $class->ERRSTRCLASS;
-    $self->{_last_error}     = 0;
-
-    $arg->{namespaces} = [@{ $arg->{namespaces} }];
-    my %namespaces;
-    for my $ns (@{$arg->{namespaces}}) {
-        $ns = { %$ns };
-        my $namespace = $ns->{namespace};
-        confess "ns[?] `namespace' not set" unless defined $namespace;
-        confess "ns[$namespace] already defined" if $namespaces{$namespace} || $ns->{name}&&$namespaces{$ns->{name}};
-        confess "ns[$namespace] no indexes defined" unless $ns->{indexes} && @{$ns->{indexes}};
-        $namespaces{$namespace} = $ns;
-        $namespaces{$ns->{name}} = $ns if $ns->{name};
-        confess "ns[$namespace] bad format `$ns->{format}'" if $ns->{format} =~ m/[^&lLsScCqQ ]/;
-        $ns->{format} =~ s/\s+//g;
-        my @f = split //, $ns->{format};
-        $ns->{byfield_unpack_format} = [ map { /&/ ? 'w/a*' : "x$_" } @f ];
-        $ns->{field_format}  = [         map { /&/ ? 'a*'   : $_    } @f ];
-        $ns->{unpack_format}  = join('', @{$ns->{byfield_unpack_format}});
-        $ns->{append_for_unpack} = '' unless defined $ns->{append_for_unpack};
-        $ns->{check_keys} = {};
-        $ns->{string_keys} = { map { $_ => 1 } grep { $f[$_] eq '&' } 0..$#f };
-        my $inames = $ns->{index_names} = {};
-        my $i = -1;
-        for my $index (@{$ns->{indexes}}) {
-            ++$i;
-            confess "ns[$namespace]index[($i)] no name given" unless length $index->{index_name};
-            my $index_name = $index->{index_name};
-            confess "ns[$namespace]index[$index_name($i)] no indexes defined" unless $index->{keys} && @{$index->{keys}};
-            confess "ns[$namespace]index[$index_name($i)] already defined" if $inames->{$index_name} || $inames->{$i};
-            $index->{id} = $i unless defined $index->{id};
-            $inames->{$i} = $inames->{$index_name} = $index;
-            int $_ == $_ and $_ >= 0 and $_ < @f or confess "ns[$namespace]index[$index_name] bad key `$_'" for @{$ns->{keys}};
-            $ns->{check_keys}->{$_} = int !! $ns->{string_keys}->{$_} for @{$index->{keys}};
-            $index->{string_keys} ||= $ns->{string_keys};
-        }
-        if( @{$ns->{indexes}} > 1 ) {
-            confess "ns[$namespace] default_index not given" unless defined $ns->{default_index};
-            confess "ns[$namespace] default_index $ns->{default_index} does not exist" unless $inames->{$ns->{default_index}};
-        } else {
-            $ns->{default_index} ||= 0;
-        }
-    }
-    $self->{namespaces} = \%namespaces;
-    if (values %namespaces > 1) {
-        confess "default_namespace not given" unless defined $arg->{default_namespace};
-        confess "default_namespace $arg->{default_namespace} does not exist" unless $namespaces{$arg->{default_namespace}};
-        $self->{default_namespace} = $arg->{default_namespace};
-    } else {
-        $self->{default_namespace} = $arg->{default_namespace} || $arg->{namespaces}->[0]->{namespace};
-    }
-    bless $self, $class;
-    $self->_connect($arg->{'servers'});
-    return $self;
-}
-
-sub _debug {
-    if($_[0]->{warn}) {
-        &{$_[0]->{warn}};
-    } else {
-        warn "@_[1..$#_]\n";
-    }
-}
-
-sub _connect {
-    my ($self, $servers) = @_;
-    $self->{server} = $self->{iprotoclass}->new({
-        servers       => $servers,
-        name          => $self->{name},
-        debug         => $self->{'ipdebug'},
-        dump_no_ints  => 1,
-    });
-}
-
-sub ErrorStr {
-    my ($self, $code) = @_;
-    return $self->{_last_error_msg} if $self->{_last_error} eq 'fail';
-    return $self->{errstrclass}->ErrorStr($code || $self->{_last_error});
-}
-
-sub Error {
-    return $_[0]->{_last_error};
-}
-
-sub _chat {
-    my ($self, %param) = @_;
-    my $orig_unpack = delete $param{unpack};
-
-    $param{unpack} = sub {
-        my $data = $_[0];
-        confess __LINE__."$self->{name}: [common]: Bad response" if length $data < 4;
-        my ($full_code, @err_code) = unpack('LX[L]CCCC', substr($data, 0, 4, ''));
-        # $err_code[0] = severity: 0 -> ok, 1 -> transient, 2 -> permanent;
-        # $err_code[1] = description;
-        # $err_code[3] = da box project;
-        return (\@err_code, \$data, $full_code);
-    };
-
-    my $timeout = $param{timeout} || $self->{timeout};
-    my $retry = $param{retry} || $self->{retry};
-    my $soft_retry = $self->{softretry};
-    my $retry_count = 0;
-
-    while ($retry > 0) {
-        $retry_count++;
-
-        $self->{_last_error} = 0x77777777;
-        $self->{server}->SetTimeout($timeout);
-        my $ret = $self->{server}->Chat1(%param);
-        my $message;
-
-        if (exists $ret->{ok}) {
-            my ($ret_code, $data, $full_code) = @{$ret->{ok}};
-            $self->{_last_error} = $full_code;
-            if ($ret_code->[0] == 0) {
-                my $ret = $orig_unpack->($$data,$ret_code->[3]);
-                confess __LINE__."$self->{name}: [common]: Bad response (more data left)" if length $$data > 0;
-                return $ret;
-            }
-
-            $message = $self->{errstrclass}->ErrorStr($full_code);
-            $self->_debug("$self->{name}: $message") if $self->{debug} >= 1;
-            if ($ret_code->[0] == 2) { #fatal error
-                $self->_raise($message) if $self->{raise};
-                return 0;
-            }
-
-            # retry if error is soft even in case of update e.g. ROW_LOCK
-            if ($ret_code->[0] == 1 and --$soft_retry > 0) {
-                --$retry if $retry > 1;
-                sleep 1;
-                next;
-            }
-        } else { # timeout has caused the failure if $ret->{timeout}
-            $self->{_last_error} = 'fail';
-            $message ||= $self->{_last_error_msg} = $ret->{fail};
-            $self->_debug("$self->{name}: $message") if $self->{debug} >= 1;
-        }
-
-        last unless --$retry;
-
-        sleep 1;
-    };
-
-    $self->_raise("no success after $retry_count tries\n") if $self->{raise};
-}
-
-sub _raise {
-    my ($self, $msg) = @_;
-    die "$self->{name}: $msg";
-}
-
-sub _validate_param {
-    my ($self, $args, @pnames) = @_;
-    my $param = ref $args->[-1] eq 'HASH' ? {%{pop @$args}}: {};
-
-    foreach my $pname (keys %$param) {
-        confess "$self->{name}: unknown param $pname\n" if 0 == grep { $_ eq $pname } @pnames;
-    }
-
-    $param->{namespace} = $self->{default_namespace} unless defined $param->{namespace};
-    confess "$self->{name}: bad namespace `$param->{namespace}'" unless exists $self->{namespaces}->{$param->{namespace}};
-    my $ns = $self->{namespaces}->{$param->{namespace}};
-    $param->{use_index} = $ns->{default_index} unless defined $param->{use_index};
-    confess "$self->{name}: bad index `$param->{use_index}'" unless exists $ns->{index_names}->{$param->{use_index}};
-    $param->{index} = $ns->{index_names}->{$param->{use_index}};
-    return ($param, map { /namespace/ ? $self->{namespaces}->{$param->{namespace}} : $param->{$_} } @pnames);
-}
-
-sub Add { # store tuple if tuple identified by primary key _does_not_ exist
-    my $param = @_ && ref $_[-1] eq 'HASH' ? pop : {};
-    $param->{action} = 'add';
-    $_[0]->Insert(@_[1..$#_], $param);
-}
-
-sub Set { # store tuple _anyway_
-    my $param = @_ && ref $_[-1] eq 'HASH' ? pop : {};
-    $param->{action} = 'set';
-    $_[0]->Insert(@_[1..$#_], $param);
-}
-
-sub Replace { # store tuple if tuple identified by primary key _does_ exist
-    my $param = @_ && ref $_[-1] eq 'HASH' ? pop : {};
-    $param->{action} = 'replace';
-    $_[0]->Insert(@_[1..$#_], $param);
-}
-
-sub Insert {
-    my ($param, $namespace) = $_[0]->_validate_param(\@_, qw/namespace _flags action/);
-    my ($self, @tuple) = @_;
-
-    $self->_debug("$self->{name}: INSERT(@{[map {qq{`$_'}} @tuple]})") if $self->{debug} >= 3;
-
-    my $flags = $param->{_flags} || 0;
-    $param->{action} ||= 'set';
-    if ($param->{action} eq 'add') {
-        $flags |= INSERT_ADD;
-    } elsif ($param->{action} eq 'replace') {
-        $flags |= INSERT_REPLACE;
-    } elsif ($param->{action} ne 'set') {
-        confess "$self->{name}: Bad insert action `$param->{action}'";
-    }
-    my $chkkey = $namespace->{check_keys};
-    my $fmt = $namespace->{field_format};
-    for (0..$#tuple) {
-        confess "$self->{name}: ref in tuple $_=`$tuple[$_]'" if ref $tuple[$_];
-        no warnings 'uninitialized';
-        if(exists $chkkey->{$_}) {
-            if($chkkey->{$_}) {
-                confess "$self->{name}: undefined key $_" unless defined $tuple[$_];
-            } else {
-                confess "$self->{name}: not numeric key $_=`$tuple[$_]'" unless looks_like_number($tuple[$_]) && int($tuple[$_]) == $tuple[$_];
-            }
-        }
-        $tuple[$_] = pack($fmt->[$_], $tuple[$_]);
-    }
-
-    $self->_debug("$self->{name}: INSERT[${\join '   ', map {join' ',unpack'(H2)*',$_} @tuple}]") if $self->{debug} >= 4;
-
-    $self->_chat (
-        msg => 13,
-        payload => pack("LLL (w/a*)*", $namespace->{namespace}, $flags, scalar(@tuple), @tuple),
-        unpack => sub { $self->_unpack_affected($flags, $namespace, @_) }
-    );
-}
-
-sub _unpack_select {
-    my ($self, $ns, $debug_prefix) = @_;
-    local *@;
-    $debug_prefix ||= "SELECT";
-    confess __LINE__."$self->{name}: [$debug_prefix]: Bad response" if length $_[3] < 4;
-    my $result_count = unpack('L', substr($_[3], 0, 4, ''));
-    $self->_debug("$self->{name}: [$debug_prefix]: COUNT=[$result_count];") if $self->{debug} >= 3;
-    my (@res);
-    my $appe = $ns->{append_for_unpack};
-    my $fmt  = $ns->{unpack_format};
-    for(my $i = 0; $i < $result_count; ++$i) {
-        confess __LINE__."$self->{name}: [$debug_prefix]: Bad response" if length $_[3] < 8;
-        my ($len, $cardinality) = unpack('LL', substr($_[3], 0, 8, ''));
-        $self->_debug("$self->{name}: [$debug_prefix]: ROW[$i]: LEN=[$len]; NFIELD=[$cardinality];") if $self->{debug} >= 4;
-        confess __LINE__."$self->{name}: [$debug_prefix]: Bad response" if length $_[3] < $len;
-        my $packed_tuple = substr($_[3], 0, $len, '');
-        $self->_debug("$self->{name}: [$debug_prefix]: ROW[$i]: DATA=[@{[unpack '(H2)*', $packed_tuple]}];") if $self->{debug} >= 6;
-        $packed_tuple .= $appe;
-        my @tuple = eval { unpack($fmt, $packed_tuple) };
-        confess "$self->{name}: [$debug_prefix]: ROW[$i]: can't unpack tuple [@{[unpack('(H2)*', $packed_tuple)]}]" if !@tuple || $@;
-        $self->_debug("$self->{name}: [$debug_prefix]: ROW[$i]: FIELDS=[@{[map { qq{`$_'} } @tuple]}];") if $self->{debug} >= 5;
-        push @res, \@tuple;
-    }
-    return \@res;
-}
-
-sub _unpack_select_multi {
-    my ($self, $nss, $debug_prefix) = @_;
-    $debug_prefix ||= "SMULTI";
-    my (@rsets);
-    my $i = 0;
-    for my $ns (@$nss) {
-        push @rsets, $self->_unpack_select($ns, "${debug_prefix}[$i]", $_[3]);
-        ++$i;
-    }
-    return \@rsets;
-}
-
-
-sub _unpack_affected {
-    my ($self, $flags, $ns) = @_;
-
-    ($flags & WANT_RESULT) ? $self->_unpack_select($ns, "AFFECTED", $_[3])->[0] : unpack('L', substr($_[3],0,4,''));
-}
-
-sub NPRM () { 3 }
-sub _pack_keys {
-    my ($self, $ns, $idx) = @_;
-
-    my $keys   = $idx->{keys};
-    my $strkey = $ns->{string_keys};
-    my $fmt    = $ns->{field_format};
-
-    if (@$keys == 1) {
-        $fmt    = $fmt->[$keys->[0]];
-        $strkey = $strkey->{$keys->[0]};
-        foreach (@_[NPRM..$#_]) {
-            ($_) = @$_ if ref $_ eq 'ARRAY';
-            unless ($strkey) {
-                confess "$self->{name}: not numeric key [$_]" unless looks_like_number($_) && int($_) == $_;
-                $_ = pack($fmt, $_);
-            }
-            $_ = pack('L(w/a*)*', 1, $_);
-        }
-    } else {
-        foreach my $k (@_[NPRM..$#_]) {
-            confess "bad key [@$keys][$k][@{[ref $k eq 'ARRAY' ? (@$k) : ()]}]" unless ref $k eq 'ARRAY' and @$k and @$k <= @$keys;
-            for my $i (0..$#$k) {
-                unless ($strkey->{$keys->[$i]}) {
-                    confess "$self->{name}: not numeric key [$i][$k->[$i]]" unless looks_like_number($k->[$i]) && int($k->[$i]) == $k->[$i];
-                }
-                $k->[$i] = pack($fmt->[$keys->[$i]], $k->[$i]);
-            }
-            $k = pack('L(w/a*)*', scalar(@$k), @$k);
-        }
-    }
-}
-
-sub _PackSelect {
-    my ($self, $param, $namespace, @keys) = @_;
-    return '' unless @keys;
-    $self->_pack_keys($namespace, $param->{index}, @keys);
-    my $format = "";
-    if ($param->{format}) {
-        my $f = $namespace->{byfield_unpack_format};
-        $param->{unpack_format} = join '', map { $f->[$_->{field}] } @{$param->{format}};
-        $format = pack 'l*', scalar @{$param->{format}}, map {
-            $_ = { %$_ };
-            if($_->{full}) {
-                $_->{offset} = 0;
-                $_->{length} = 'max';
-            }
-            $_->{length} = 0x7FFFFFFF if $_->{length} eq 'max';
-            @$_{qw/field offset length/}
-        } @{$param->{format}};
-    }
-    return pack("LLLL a* La*", $namespace->{namespace}, $param->{index}->{id}, $param->{offset} || 0, $param->{limit} || scalar(@keys), $format, scalar(@keys), join('',@keys));
-}
-
-sub _PostSelect {
-    my ($self, $r, $param) = @_;
-    if(!$param->{raw} && ref $param->{hashify} eq 'CODE') {
-        $param->{hashify}->($param->{namespace}->{namespace}, $r);
-    }
-}
-
-my @select_param_ok = qw/namespace use_index raw want next_rows limit offset raise hashify timeout format hash_by/;
-sub Select {
-    confess q/Select isnt callable in void context/ unless defined wantarray;
-    my ($param, $namespace) = $_[0]->_validate_param(\@_, @select_param_ok);
-    my ($self, @keys) = @_;
-    local $self->{raise} = $param->{raise} if defined $param->{raise};
-    @keys = @{$keys[0]} if ref $keys[0] eq 'ARRAY' and 1 == @{$param->{index}->{keys}} || ref $keys[0]->[0] eq 'ARRAY';
-
-    $self->_debug("$self->{name}: SELECT($namespace->{namespace}/$param->{use_index})[@{[map{ref$_?qq{[@$_]}:$_}@keys]}]") if $self->{debug} >= 3;
-
-    my ($msg,$payload);
-    if(exists $param->{next_rows}) {
-        confess "$self->{name}: One and only one key can be used to get N>0 rows after it" if @keys != 1 || !$param->{next_rows};
-        $msg = 15;
-        $self->_pack_keys($namespace, $param->{index}, @keys);
-        $payload = pack("LL a*", $namespace->{namespace}, $param->{next_rows}, join('',@keys)),
-    } else {
-        $payload = $self->_PackSelect($param, $namespace, @keys);
-        $msg = $param->{format} ? 21 : 17;
-    }
-
-    local $namespace->{unpack_format} = $param->{unpack_format} if $param->{unpack_format};
-
-    my $r = [];
-    if (@keys && $payload) {
-        $r = $self->_chat(
-            msg      => $msg,
-            payload  => $payload,
-            unpack   => sub { $self->_unpack_select($namespace, "SELECT", @_) },
-            retry    => $self->{select_retry},
-            timeout  => $param->{timeout} || $self->{select_timeout},
-        ) or return;
-    }
-
-    $param->{raw} = $self->{default_raw} unless exists $param->{raw};
-    $param->{want} ||= !1;
-
-    $self->_PostSelect($r, { hashify => $param->{hashify}||$namespace->{hashify}||$self->{hashify}, %$param, namespace => $namespace });
-
-    if(defined(my $p = $param->{hash_by})) {
-        my %h;
-        if(@$r) {
-            if (ref $r->[0] eq 'HASH') {
-                confess "Bad hash_by `$p' for HASH" unless exists $r->[0]->{$p};
-                $h{$_->{$p}} = $_ for @$r;
-            } elsif(ref $r->[0] eq 'ARRAY') {
-                confess "Bad hash_by `$p' for ARRAY" unless $p =~ m/^\d+$/ && $p >= 0 && $p < @{$r->[0]};
-                $h{$_->[$p]} = $_ for @$r;
-            } else {
-                confess "i dont know how to hash_by ".ref($r->[0]);
-            }
-        }
-        return \%h;
-    }
-
-    return $r if $param->{want} eq 'arrayref';
-
-    if (wantarray) {
-        return @{$r};
-    } else {
-        confess "$self->{name}: too many keys in scalar context" if @keys > 1;
-        return $r->[0];
-    }
-}
-
-sub SelectUnion {
-    confess "not supported yet";
-    my ($param) = $_[0]->_validate_param(\@_, qw/raw raise/);
-    my ($self, @reqs) = @_;
-    return [] unless @reqs;
-    local $self->{raise} = $param->{raise} if defined $param->{raise};
-    confess "bad param" if grep { ref $_ ne 'ARRAY' } @reqs;
-    $param->{raw} = $self->{default_raw} unless exists $param->{raw};
-    $param->{want} ||= 0;
-    for my $req (@reqs) {
-        my ($param, $namespace) = $self->_validate_param($req, @select_param_ok);
-        $req = {
-            payload   => $self->_PackSelect($param, $namespace, $req),
-            param     => $param,
-            namespace => $namespace,
-        };
-    }
-    my $r = $self->_chat(
-        msg      => 18,
-        payload  => pack("L (a*)*", scalar(@reqs), map { $_->{payload} } @reqs),
-        unpack   => sub { $self->_unpack_select_multi([map { $_->{namespace} } @reqs], "SMULTI", @_) },
-        retry    => $self->{select_retry},
-        timeout  => $param->{timeout} || $self->{select_timeout},
-    ) or return;
-    confess __LINE__."$self->{name}: something wrong" if @$r != @reqs;
-    my $ea = each_arrayref $r, \@reqs;
-    while(my ($res, $req) = $ea->()) {
-        $self->_PostSelect($res, { hashify => $req->{namespace}->{hashify}||$self->{hashify}, %$param, %{$req->{param}}, namespace => $req->{namespace} });
-    }
-    return $r;
-}
-
-sub Delete {
-    my ($param, $namespace) = $_[0]->_validate_param(\@_, qw/namespace/);
-    my ($self, $key) = @_;
-
-    $self->_debug("$self->{name}: DELETE($key)") if $self->{debug} >= 3;
-
-    confess "$self->{name}\->Delete: for now key cardinality of 1 is only allowed" unless 1 == @{$param->{index}->{keys}};
-    $self->_pack_keys($namespace, $param->{index}, $key);
-
-    $self->_chat (
-        msg => 20,
-        payload => pack("L a*", $namespace->{namespace}, $key),
-        unpack  => sub { $self->_unpack_affected(0, $namespace, @_) }
-    );
-}
-
-sub OP_SET          () { 0 }
-sub OP_ADD          () { 1 }
-sub OP_AND          () { 2 }
-sub OP_XOR          () { 3 }
-sub OP_OR           () { 4 }
-sub OP_SPLICE       () { 5 }
-
-my %update_ops = (
-    set         => OP_SET,
-    add         => OP_ADD,
-    and         => OP_AND,
-    xor         => OP_XOR,
-    or          => OP_OR,
-    splice      => sub {
-        confess "value for operation splice must be an ARRAYREF of <int[, int[, string]]>" if ref $_[0] ne 'ARRAY' || @{$_[0]} < 1;
-        $_[0]->[0] = 0x7FFFFFFF unless defined $_[0]->[0];
-        $_[0]->[0] = pack 'l', $_[0]->[0];
-        $_[0]->[1] = defined $_[0]->[1] ? pack 'l', $_[0]->[1] : '';
-        $_[0]->[2] = '' unless defined $_[0]->[2];
-        return (OP_SPLICE, [ pack '(w/a*)*', @{$_[0]} ]);
-    },
-    append      => sub { splice => [undef,  0,     $_[0]] },
-    prepend     => sub { splice => [0,      0,     $_[0]] },
-    cutbeg      => sub { splice => [0,      $_[0], ''   ] },
-    cutend      => sub { splice => [-$_[0], $_[0], ''   ] },
-    substr      => 'splice',
-);
-
-!ref $_ && m/^\D/ and $_ = $update_ops{$_} || die "bad link" for values %update_ops;
-
-my %update_arg_fmt = (
-    (map { $_ => 'l' } OP_ADD),
-    (map { $_ => 'L' } OP_AND, OP_XOR, OP_OR),
-);
-
-my %ops_type = (
-    (map { $_ => 'any'    } OP_SET),
-    (map { $_ => 'number' } OP_ADD, OP_AND, OP_XOR, OP_OR),
-    (map { $_ => 'string' } OP_SPLICE),
-);
-
-BEGIN {
-    for my $op (qw/Append Prepend Cutbeg Cutend Substr/) {
-        eval q/
-            sub /.$op.q/ {
-                my $param = ref $_[-1] eq 'HASH' ? pop : {};
-                my ($self, $key, $field_num, $val) = @_;
-                $self->UpdateMulti($key, [ $field_num => /.lc($op).q/ => $val ], $param);
-            }
-            1;
-        / or die $@;
-    }
-}
-
-sub UpdateMulti {
-    my ($param, $namespace) = $_[0]->_validate_param(\@_, qw/namespace want_result _flags raw/);
-    my ($self, $key, @op) = @_;
-
-    $self->_debug("$self->{name}: UPDATEMULTI($namespace->{namespace}=$key)[@{[map{qq{[@$_]}}@op]}]") if $self->{debug} >= 3;
-
-    confess "$self->{name}\->UpdateMulti: for now key cardinality of 1 is only allowed" unless 1 == @{$param->{index}->{keys}};
-    confess "$self->{name}: too many op" if scalar @op > 128;
-
-    my $flags = $param->{_flags} || 0;
-    $flags |= WANT_RESULT if $param->{want_result};
-
-    my $fmt = $namespace->{field_format};
-
-    foreach (@op) {
-        confess "$self->{name}: bad op <$_>" if ref ne 'ARRAY' or @$_ != 3;
-        my ($field_num, $op, $value) = @$_;
-        my $field_type = $namespace->{string_keys}->{$field_num} ? 'string' : 'number';
-
-        my $is_array = 0;
-        if ($op eq 'bit_set') {
-            $op = OP_OR;
-        } elsif ($op eq 'bit_clear') {
-            $op = OP_AND;
-            $value = ~$value;
-        } elsif ($op =~ /^num_(add|sub)$/) {
-            $value = -$value if $1 eq 'sub';
-            $op = OP_ADD;
-        } else {
-            confess "$self->{name}: bad op <$op>" unless exists $update_ops{$op};
-            $op = $update_ops{$op};
-        }
-
-        while(ref $op eq 'CODE') {
-            ($op, $value) = &$op($value);
-            $op = $update_ops{$op} if exists $update_ops{$op};
-        }
-
-        confess "Are you sure you want to apply `$ops_type{$op}' operation to $field_type field?" if $ops_type{$op} ne $field_type && $ops_type{$op} ne 'any';
-
-        $value = [ $value ] unless ref $value;
-        confess "dunno what to do with ref `$value'" if ref $value ne 'ARRAY';
-
-        confess "bad fieldnum: $field_num" if $field_num >= @$fmt;
-        $value = pack($update_arg_fmt{$op} || $fmt->[$field_num], @$value);
-        $_ = pack('LCw/a*', $field_num, $op, $value);
-    }
-
-    $self->_pack_keys($namespace, $param->{index}, $key);
-
-    $self->_chat(
-        msg      => 19,
-        payload  => pack("LL a* L (a*)*" , $namespace->{namespace}, $flags, $key, scalar(@op), @op),
-        unpack   => sub { $self->_unpack_affected($flags, $namespace, @_) }
-    );
-}
-
-sub Update {
-    my $param = ref $_[-1] eq 'HASH' ? pop : {};
-    my ($self, $key, $field_num, $value) = @_;
-    $self->UpdateMulti($key, [$field_num => set => $value ], $param);
-}
-
-sub AndXorAdd {
-    my $param = ref $_[-1] eq 'HASH' ? pop : {};
-    my ($self, $key, $field_num, $and, $xor, $add) = @_;
-    my @upd;
-    push @upd, [$field_num => and => $and] if defined $and;
-    push @upd, [$field_num => xor => $xor] if defined $xor;
-    push @upd, [$field_num => add => $add] if defined $add;
-    $self->UpdateMulti($key, @upd, $param);
-}
-
-sub Bit {
-    my $param = ref $_[-1] eq 'HASH' ? pop : {};
-    my ($self, $key, $field_num, %arg) = @_;
-    confess "$self->{name}: unknown op '@{[keys %arg]}'"  if grep { not /^(bit_clear|bit_set|set)$/ } keys(%arg);
-
-    $arg{bit_clear} ||= 0;
-    $arg{bit_set}   ||= 0;
-    my @op;
-    push @op, [$field_num => set       => $arg{set}]        if exists $arg{set};
-    push @op, [$field_num => bit_clear => $arg{bit_clear}]  if $arg{bit_clear};
-    push @op, [$field_num => bit_set   => $arg{bit_set}]    if $arg{bit_set};
-
-    $self->UpdateMulti($key, @op, $param);
-}
-
-sub Num {
-    my $param = ref $_[-1] eq 'HASH' ? pop : {};
-    my ($self, $key, $field_num, %arg) = @_;
-    confess "$self->{name}: unknown op '@{[keys %arg]}'"  if grep { not /^(num_add|num_sub|set)$/ } keys(%arg);
-
-    $arg{num_add} ||= 0;
-    $arg{num_sub} ||= 0;
-
-    $arg{num_add} -= $arg{num_sub};
-    my @op;
-    push @op, [$field_num => set     => $arg{set}]     if exists $arg{set};
-    push @op, [$field_num => num_add => $arg{num_add}]; # if $arg{num_add};
-    $self->UpdateMulti($key, @op, $param);
-}
+=cut
 
 1;
diff --git a/connector/perl/lib/MR/Storage/Const.pm b/connector/perl/lib/MR/Storage/Const.pm
deleted file mode 100644
index 64f1deee76fb7439be5de49f449bac4d0fd03df0..0000000000000000000000000000000000000000
--- a/connector/perl/lib/MR/Storage/Const.pm
+++ /dev/null
@@ -1,109 +0,0 @@
-use strict;
-use Exporter;
-
-package MR::Storage::Const;
-use base qw/Exporter/;
-
-use vars qw/$VERSION @EXPORT_OK %EXPORT_TAGS %ERRORS/;
-$VERSION = 0;
-
-my (@ERRORS);
-$EXPORT_TAGS{all}    = \@EXPORT_OK;
-$EXPORT_TAGS{errors} = \@ERRORS;
-
-BEGIN {
-    sub constantiate($$;@) {
-        my ($const, @arrs) = @_;
-        my $pkg = (caller)[0];
-        my @a;
-        eval qq{ sub $pkg\::$_ () { $const->{$_} }; push \@a, q{$_}; 'TRUE'.$_; } or die $@
-        for keys %$const;
-        push @$_, @a for @arrs;
-    }
-
-    # Errors. Numbers >=0 are returned by server, <0 are internals for the module.
-    constantiate {
-        SE_OK                                       =>    0,
-        SE_UNKNOWN                                  =>   -1,
-        SE_COMM                                     =>   -2,
-    }, \@ERRORS, \@EXPORT_OK;
-}
-
-%ERRORS = (
-    0x00000000  => q{OK},
-    0x00000100  => q{Non master connection, but it should be},
-    0x00000200  => q{Illegal parametrs},
-    0x00000300  => q{Uid not from this storage range},
-    0x00000400  => q{Node is marked as read-only},
-    0x00000500  => q{Node isn't locked},
-    0x00000600  => q{Node is locked},
-    0x00000700  => q{Some memory issues},
-    0x00000800  => q{Bad integrity},
-    0x00000a00  => q{Unsupported command},
-
-    0x00000b00  => q{Can't do select},
-    0x00000c00  => q{Silverspoon for first uid is not found},
-    0x00000d00  => q{Silverspoon for second uid is not found},
-    0x00000e00  => q{Silverspoons for both uids are not found},
-    0x00000f00  => q{Can't do 2pc prepare on first sp},
-    0x00001000  => q{Can't do 2pc prepare on second sp},
-    0x00001100  => q{Can't do 2pc prepare on both sps},
-    0x00001200  => q{Can't do 2pc commit on first sp},
-    0x00001300  => q{Can't do 2pc commit on second sp},
-    0x00001400  => q{Can't do 2pc commit on both sps},
-    0x00001500  => q{Can't do 2pc abort on first sp},
-    0x00001600  => q{Can't do 2pc abort on second sp},
-    0x00001700  => q{Can't do 2pc abort on both sps},
-
-    0x00001800  => q{Can't register new user},
-    0x00001a00  => q{Can't generate alert id},
-    0x00001b00  => q{Can't del node},
-
-    0x00001c00  => q{User isn't registered},
-    0x00001d00  => q{Syntax error in query},
-    0x00001e00  => q{Unknown field},
-    0x00001f00  => q{Number value is out of range},
-    0x00002000  => q{Insert already existing object},
-    0x00002200  => q{Can not order result},
-    0x00002300  => q{Multiple update/delete forbidden},
-    0x00002400  => q{Nothing affected},
-    0x00002500  => q{Primary key update forbidden},
-    0x00002600  => q{Incorrect protocol version},
-    0x00002700  => q{Bad record ID},
-    0x00002800  => q{WAL failed},
-    0x00002900  => q{Bad event ID},
-    0x00002a00  => q{Read-only mode},
-    0x00002b00  => q{Event is locked},
-    0x00002c00  => q{Event is already liked by this user},
-    0x00002d00  => q{Event is not liked by this user},
-    0x00002e00  => q{The message has been already deleted by this user},
-    0x00002f00  => q{User can't delete message},
-    0x00003100  => q{Node not found},
-    0x00003200  => q{User isn't member of the dialogue},
-    0x00003300  => q{Dialogue not found},
-    0x00003400  => q{Error during iconv},
-    0x00003500  => q{Event isn't contained in this node},
-    0x00003600  => q{Proxy reply: destination node timed out},
-    0x00003700  => q{Node found},
-    0x00003800  => q{Index violation},
-);
-
-sub ErrorText {
-    my $e = $_[1] & 0xFFFFFF00;
-    return $ERRORS{$e} if exists $ERRORS{$e};
-    return 'Unknown error';
-}
-
-sub ErrorStr {
-    my $e = $_[1] ? ($_[1] % 0xFF) ? 'Error' : 'Warning' : '';
-    $e &&= sprintf "$e %08X: ", $_[1];
-    return sprintf "%s%s", $e, $_[0]->ErrorText($_[1]);
-}
-
-package MR::Storage::Const::Errors;
-use base qw/MR::Storage::Const/;
-
-package MR::Storage::Const::Errors::SilverBox;
-use base qw/MR::Storage::Const::Errors/;
-
-1;
diff --git a/connector/perl/lib/MR/Tarantool/Box.pm b/connector/perl/lib/MR/Tarantool/Box.pm
new file mode 100644
index 0000000000000000000000000000000000000000..0e8279f4f1c7f40512ffc001b75553017e8a8a67
--- /dev/null
+++ b/connector/perl/lib/MR/Tarantool/Box.pm
@@ -0,0 +1,1280 @@
+package MR::Tarantool::Box;
+
+=pod
+
+=head1 NAME
+
+MR::Tarantool::Box - A driver for an efficient Tarantool/Box NoSQL in-memory storage.
+
+=head1 SYNOPSIS
+
+    my $box = MR::Tarantool::Box->new({
+        servers => "127.0.0.1:33013",
+        name    => "My Box",              # mostly used for debug purposes
+        spaces => [ {
+            indexes => [ {
+                index_name   => 'idx1',
+                keys         => [0],
+            }, {
+                index_name   => 'idx2',
+                keys         => [1,2],
+            }, ],
+            space         => 1,           # space id, as set in Tarantool/Box config
+            name          => "primary",   # self-descriptive space-id
+            format        => "QqLlSsCc&", # pack()-compatible, Qq must be supported by perl itself, & stands for byte-string.
+            default_index => 'idx1',
+            fields        => [qw/ id f2 field3 f4 f5 f6 f7 f8 misc_string /], # turn each tuple into hash, field names according to format
+        }, {
+            #...
+        } ],
+        default_space => "primary",
+
+        timeout   => 1.0,                 # seconds
+        retry     => 3,
+        debug     => 9,                   # output to STDERR some debugging info
+        raise     => 0,                   # dont raise an exception in case of error
+    });
+
+    my $bool  = $box->Insert(1, 2,3, 4,5,6,7,8,"asdf")                            or die $box->ErrorStr;
+    my $bool  = $box->Insert(2, 2,4, 4,5,6,7,8,"asdf",{space => "primary"})       or die $box->ErrorStr;
+    my $tuple = $box->Insert(3, 3,3, 4,5,6,7,8,"asdf",{want_inserted_tuple => 1}) or die $box->ErrorStr;
+
+    # Select by single-field key
+    my $tuple  = $box->Select(1);                                                 # scalar context - scalar result: $tuple
+    my @tuples = $box->Select(1,2,3);                                             # list   context - list   result: ($tuple, $tuple, ...)
+    my $tuples = $box->Select([1,2,3],{space => "primary", use_index => "idx1"}); #                arrayref result: [$tuple, $tuple, ...]
+
+    # Select by multi-field key
+    my $tuples = $box->Select([[2,3]],{use_index => "idx2"}); # by full key
+    my $tuples = $box->Select([[2]]  ,{use_index => "idx2"}); # by partial key
+
+    my $bool  = $box->UpdateMulti(1,[ f4 => add => 3 ]);
+    my $bool  = $box->UpdateMulti(2,[ f4 => add => 3 ],{space => "primary"});
+    my $tuple = $box->UpdateMulti(3,[ f4 => add => 3 ],{want_updated_tuple => 1});
+
+    my $bool  = $box->Delete(1);
+    my $tuple = $box->Delete(2, {want_deleted_tuple => 1});
+
+=head1 DESCRIPTION
+
+=head2 METHODS
+
+=cut
+
+use strict;
+use warnings;
+use Scalar::Util qw/looks_like_number/;
+use List::MoreUtils qw/each_arrayref zip/;
+use Time::HiRes qw/sleep/;
+
+use MR::IProto ();
+
+use constant {
+    WANT_RESULT       => 1,
+    INSERT_ADD        => 2,
+    INSERT_REPLACE    => 4,
+};
+
+
+sub IPROTOCLASS () { 'MR::IProto' }
+
+use vars qw/$VERSION %ERRORS/;
+$VERSION = 0.0.11;
+
+BEGIN { *confess = \&MR::IProto::confess }
+
+%ERRORS = (
+    0x00000000  => q{OK},
+    0x00000100  => q{Non master connection, but it should be},
+    0x00000200  => q{Illegal parametrs},
+    0x00000300  => q{Uid not from this storage range},
+    0x00000400  => q{Tuple is marked as read-only},
+    0x00000500  => q{Tuple isn't locked},
+    0x00000600  => q{Tuple is locked},
+    0x00000700  => q{Failed to allocate memory},
+    0x00000800  => q{Bad integrity},
+    0x00000a00  => q{Unsupported command},
+
+    0x00000b00  => q{Can't do select},
+
+    0x00001800  => q{Can't register new user},
+    0x00001a00  => q{Can't generate alert id},
+    0x00001b00  => q{Can't del node},
+
+    0x00001c00  => q{User isn't registered},
+    0x00001d00  => q{Syntax error in query},
+    0x00001e00  => q{Unknown field},
+    0x00001f00  => q{Number value is out of range},
+    0x00002000  => q{Insert already existing object},
+    0x00002200  => q{Can not order result},
+    0x00002300  => q{Multiple update/delete forbidden},
+    0x00002400  => q{Nothing affected},
+    0x00002500  => q{Primary key update forbidden},
+    0x00002600  => q{Incorrect protocol version},
+    0x00002700  => q{WAL failed},
+    0x00003000  => q{Procedure return type is not supported in the binary protocol},
+    0x00003100  => q{Tuple doesn't exist},
+    0x00003200  => q{Procedure is not defined},
+    0x00003300  => q{Lua error},
+    0x00003400  => q{Space is disabled},
+    0x00003500  => q{No such index in space},
+    0x00003600  => q{Field was not found in the tuple},
+    0x00003700  => q{Tuple already exists},
+    0x00003800  => q{Duplicate key exists in a unique index},
+    0x00003900  => q{Space does not exists},
+);
+
+
+
+=pod
+
+=head3 new
+
+    my $box = $class->new(\%args);
+
+%args:
+
+=over
+
+=item B<spaces> => [ \%space, ... ]
+
+%space:
+
+=over
+
+=item B<space> => $space_id_uint32
+
+Space id as set in Tarantool/Box config.
+
+=item B<name> => $space_name_string
+
+Self-descriptive space id, which will be mapped into C<space>.
+
+=item B<format> => $format_string
+
+C<pack()>-compatible tuple format string, allowed formats: C<QqLlSsCc&>,
+where C<&> stands for bytestring. C<Qq> usable only if perl supports
+int64 itself. Tuples' fields are packed/unpacked according to this C<format>.
+
+=item B<hashify> => B<$coderef>
+
+Specify a callback to turn each tuple into a good-looking hash.
+It receives C<space> id and resultset as arguments. No return value needed.
+
+    $coderef = sub {
+        my ($space_id, $resultset) = @_;
+        $_ = { FieldName1 => $_->[0], FieldName2 => $_->[1], ... } for @$resultset;
+    };
+
+=item B<fields> => B<$arrayref>
+
+Specify an arrayref of fields names according to C<format> to turn each
+tuple into a good-looking hash. Names must begin with C<< [A-Za-z] >>.
+
+=item B<indexes> => [ \%index, ... ]
+
+%index:
+
+=over
+
+=item B<id> => $index_id_uint32
+
+Index id as set in Tarantool/Box config within current C<space>.
+If not set, order position in C<indexes> is theated as C<id>.
+
+=item B<name> => $index_name_string
+
+Self-descriptive index id, which will be mapped into C<index_id>.
+
+=item B<keys> => [ $field_no_uint32, ... ]
+
+Properly ordered arrayref of fields' numbers which are indexed.
+
+=back
+
+=item B<default_index> => $default_index_name_string_or_id_uint32
+
+Index C<id> or C<name> to be used by default for the current C<space>.
+Must be set if there are more than one C<\%index>es.
+
+=back
+
+=item B<default_space> => $default_space_name_string_or_id_uint32
+
+Space C<space> or C<name> to be used by default. Must be set if there are
+more than one C<\%space>s.
+
+=item B<timeout> => $timeout_fractional_seconds_float || 23
+
+A common timeout for network operations.
+
+=item B<select_timeout> => $select_timeout_fractional_seconds_float || 2
+
+Select queries timeout for network operations. See L</select_retry>.
+
+=item B<retry> => $retry_int || 1
+
+A common retries number for network operations.
+
+=item B<select_retry> => $select_retry_int || 3
+
+Select queries retries number for network operations.
+
+Sometimes we need short timeout for select's and long timeout for B<critical> update's,
+because in case of timeout we B<don't know if the update has succeeded>. For the same
+reason we B<can't retry> update operation.
+
+So increasing C<timeout> and setting C<< retry => 1 >> for updates lowers possibility of
+such situations (but, of course, does not exclude them at all), and guarantees that
+we dont do the same more then once.
+
+=item B<soft_retry> => $soft_retry_int || 3
+
+A common retries number for Tarantool/Box B<temporary errors> (these marked by 1 in the
+lowest byte of C<error_code>). In that case we B<know for sure> that the B<request was
+declined> by Tarantool/Box for some reason (a tuple was locked for another update, for
+example), and we B<can> try it again.
+
+This is also limited by C<retry>/C<select_retry>
+(depending on query type).
+
+=item B<retry_delay> => $retry_delay_fractional_seconds_float || 1
+
+Specify a delay between retries for network operations.
+
+=item B<raise> => $raise_bool || 1
+
+Should we raise an exceptions? If so, exceptions are raised when no more retries left and
+all tries failed (with timeout, fatal, or temporary error).
+
+=item B<debug> => $debug_level_int || 0
+
+Debug level, 0 - print nothing, 9 - print everything
+
+=item B<name> => $name
+
+A string used for self-description. Mainly used for debugging purposes.
+
+=back
+
+=cut
+
+sub new {
+    my ($class, $arg) = @_;
+    my $self;
+
+    $arg = { %$arg };
+    $self->{name}            = $arg->{name}      || ref$class || $class;
+    $self->{timeout}         = $arg->{timeout}   || 23;
+    $self->{retry}           = $arg->{retry}     || 1;
+    $self->{retry_delay}     = $arg->{retry_delay} || 1;
+    $self->{select_retry}    = $arg->{select_retry} || 3;
+    $self->{softretry}       = $arg->{soft_retry} || $arg->{softretry} || 3;
+    $self->{debug}           = $arg->{'debug'}   || 0;
+    $self->{ipdebug}         = $arg->{'ipdebug'} || 0;
+    $self->{raise}           = 1;
+    $self->{raise}           = $arg->{raise} if exists $arg->{raise};
+    $self->{select_timeout}  = $arg->{select_timeout} || $self->{timeout};
+    $self->{iprotoclass}     = $arg->{iprotoclass} || $class->IPROTOCLASS;
+    $self->{_last_error}     = 0;
+
+    $self->{hashify}         = $arg->{'hashify'} if exists $arg->{'hashify'};
+    $self->{default_raw}     = $arg->{default_raw};
+
+    $arg->{spaces} = $arg->{namespaces} = [@{ $arg->{spaces} ||= $arg->{namespaces} || confess "no spaces given" }];
+    confess "no spaces given" unless @{$arg->{spaces}};
+    my %namespaces;
+    for my $ns (@{$arg->{spaces}}) {
+        $ns = { %$ns };
+        my $namespace = defined $ns->{space} ? $ns->{space} : $ns->{namespace};
+        $ns->{space} = $ns->{namespace} = $namespace;
+        confess "space[?] `space' not set" unless defined $namespace;
+        confess "space[$namespace] already defined" if $namespaces{$namespace} or $ns->{name}&&$namespaces{$ns->{name}};
+        confess "space[$namespace] no indexes defined" unless $ns->{indexes} && @{$ns->{indexes}};
+        $namespaces{$namespace} = $ns;
+        $namespaces{$ns->{name}} = $ns if $ns->{name};
+        confess "space[$namespace] bad format `$ns->{format}'" if $ns->{format} =~ m/[^&lLsScCqQ ]/;
+        $ns->{format} =~ s/\s+//g;
+        my @f = split //, $ns->{format};
+        $ns->{byfield_unpack_format} = [ map { /&/ ? 'w/a*' : "x$_" } @f ];
+        $ns->{field_format}  = [         map { /&/ ? 'a*'   : $_    } @f ];
+        $ns->{unpack_format}  = join('', @{$ns->{byfield_unpack_format}});
+        $ns->{append_for_unpack} = '' unless defined $ns->{append_for_unpack};
+        $ns->{check_keys} = {};
+        $ns->{string_keys} = { map { $_ => 1 } grep { $f[$_] eq '&' } 0..$#f };
+        my $inames = $ns->{index_names} = {};
+        my $i = -1;
+        for my $index (@{$ns->{indexes}}) {
+            ++$i;
+            confess "space[$namespace]index[($i)] no name given" unless length $index->{index_name};
+            my $index_name = $index->{index_name};
+            confess "space[$namespace]index[$index_name($i)] no indexes defined" unless $index->{keys} && @{$index->{keys}};
+            confess "space[$namespace]index[$index_name($i)] already defined" if $inames->{$index_name} || $inames->{$i};
+            $index->{id} = $i unless defined $index->{id};
+            $inames->{$i} = $inames->{$index_name} = $index;
+            int $_ == $_ and $_ >= 0 and $_ < @f or confess "space[$namespace]index[$index_name] bad key `$_'" for @{$ns->{keys}};
+            $ns->{check_keys}->{$_} = int !! $ns->{string_keys}->{$_} for @{$index->{keys}};
+            $index->{string_keys} ||= $ns->{string_keys};
+        }
+        if( @{$ns->{indexes}} > 1 ) {
+            confess "space[$namespace] default_index not given" unless defined $ns->{default_index};
+            confess "space[$namespace] default_index $ns->{default_index} does not exist" unless $inames->{$ns->{default_index}};
+        } else {
+            $ns->{default_index} ||= 0;
+        }
+        if($ns->{fields}) {
+            confess "space[$namespace] fields must be ARRAYREF" unless ref $ns->{fields} eq 'ARRAY';
+            confess "space[$namespace] fields number must match format" if @{$ns->{fields}} != @f;
+            m/^[A-Za-z]/ or confess "space[$namespace] fields names must begin with [A-Za-z]: bad name $_" for @{$ns->{fields}};
+            $ns->{fields_hash} = { map { $ns->{fields}->[$_] => $_ } 0..$#{$ns->{fields}} };
+        }
+    }
+    $self->{namespaces} = \%namespaces;
+    if (@{$arg->{spaces}} > 1) {
+        $arg->{default_namespace} = $arg->{default_space} if defined $arg->{default_space};
+        confess "default_space not given" unless defined $arg->{default_namespace};
+        confess "default_space $arg->{default_namespace} does not exist" unless $namespaces{$arg->{default_namespace}};
+        $self->{default_namespace} = $arg->{default_namespace};
+    } else {
+        $self->{default_namespace} = $arg->{default_space} || $arg->{default_namespace} || $arg->{spaces}->[0]->{space};
+        confess "default_space $self->{default_namespace} does not exist" unless $namespaces{$self->{default_namespace}};
+    }
+    bless $self, $class;
+    $self->_connect($arg->{'servers'});
+    return $self;
+}
+
+sub _debug {
+    if($_[0]->{warn}) {
+        &{$_[0]->{warn}};
+    } else {
+        warn "@_[1..$#_]\n";
+    }
+}
+
+sub _connect {
+    my ($self, $servers) = @_;
+    $self->{server} = $self->{iprotoclass}->new({
+        servers       => $servers,
+        name          => $self->{name},
+        debug         => $self->{'ipdebug'},
+        dump_no_ints  => 1,
+    });
+}
+
+=pod
+
+=head3 Error
+
+Last error code, or 'fail' for some network reason, oftenly a timeout.
+
+    $box->Insert(@tuple) or die sprintf "Error %X", $box->Error; # die "Error 202"
+
+=head3 ErrorStr
+
+Last error code and description in a single string.
+
+    $box->Insert(@tuple) or die $box->ErrorStr;                  # die "Error 00000202: Illegal Parameters"
+
+=cut
+
+sub ErrorStr {
+    return $_[0]->{_last_error_msg};
+}
+
+sub Error {
+    return $_[0]->{_last_error};
+}
+
+sub _chat {
+    my ($self, %param) = @_;
+    my $orig_unpack = delete $param{unpack};
+
+    $param{unpack} = sub {
+        my $data = $_[0];
+        confess __LINE__."$self->{name}: [common]: Bad response" if length $data < 4;
+        my ($full_code, @err_code) = unpack('LX[L]CSC', substr($data, 0, 4, ''));
+        # $err_code[0] = severity: 0 -> ok, 1 -> transient, 2 -> permanent;
+        # $err_code[1] = description;
+        # $err_code[2] = da box project;
+        return (\@err_code, \$data, $full_code);
+    };
+
+    my $timeout = $param{timeout} || $self->{timeout};
+    my $retry = $param{retry} || $self->{retry};
+    my $soft_retry = $self->{softretry};
+    my $retry_count = 0;
+
+    while ($retry > 0) {
+        $retry_count++;
+
+        $self->{_last_error} = 0x77777777;
+        $self->{server}->SetTimeout($timeout);
+        my $ret = $self->{server}->Chat1(%param);
+        my $message;
+
+        if (exists $ret->{ok}) {
+            my ($ret_code, $data, $full_code) = @{$ret->{ok}};
+            $self->{_last_error} = $full_code;
+            if ($ret_code->[0] == 0) {
+                my $ret = $orig_unpack->($$data,$ret_code->[2]);
+                confess __LINE__."$self->{name}: [common]: Bad response (more data left)" if length $$data > 0;
+                return $ret;
+            }
+
+            $self->{_last_error_msg} = $message = $ret_code->[0] == 0 ? "ok" : sprintf "Error %08X: %s", $full_code, $$data || $ERRORS{$full_code & 0xFFFFFF00} || 'Unknown error';
+            $self->_debug("$self->{name}: $message") if $self->{debug} >= 1;
+            if ($ret_code->[0] == 2) { #fatal error
+                $self->_raise($message) if $self->{raise};
+                return 0;
+            }
+
+            # retry if error is soft even in case of update e.g. ROW_LOCK
+            if ($ret_code->[0] == 1 and --$soft_retry > 0) {
+                --$retry if $retry > 1;
+                sleep $self->{retry_delay};
+                next;
+            }
+        } else { # timeout has caused the failure if $ret->{timeout}
+            $self->{_last_error} = 'fail';
+            $message ||= $self->{_last_error_msg} = $ret->{fail};
+            $self->_debug("$self->{name}: $message") if $self->{debug} >= 1;
+        }
+
+        last unless --$retry;
+
+        sleep $self->{retry_delay};
+    };
+
+    $self->_raise("no success after $retry_count tries\n") if $self->{raise};
+}
+
+sub _raise {
+    my ($self, $msg) = @_;
+    die "$self->{name}: $msg\n";
+}
+
+sub _validate_param {
+    my ($self, $args, @pnames) = @_;
+    my $param = $args && @$args && ref $args->[-1] eq 'HASH' ? {%{pop @$args}} : {};
+
+    my %pnames = map { $_ => 1 } @pnames;
+    $pnames{space} = 1;
+    $pnames{namespace} = 1;
+    foreach my $pname (keys %$param) {
+        confess "$self->{name}: unknown param $pname\n" unless exists $pnames{$pname};
+    }
+
+    $param->{namespace} = $param->{space} if exists $param->{space} and defined $param->{space};
+    $param->{namespace} = $self->{default_namespace} unless defined $param->{namespace};
+    confess "$self->{name}: bad space `$param->{namespace}'" unless exists $self->{namespaces}->{$param->{namespace}};
+
+    my $ns = $self->{namespaces}->{$param->{namespace}};
+    $param->{use_index} = $ns->{default_index} unless defined $param->{use_index};
+    confess "$self->{name}: bad index `$param->{use_index}'" unless exists $ns->{index_names}->{$param->{use_index}};
+    $param->{index} = $ns->{index_names}->{$param->{use_index}};
+
+    if(exists $pnames{raw}) {
+        $param->{raw} = $ns->{default_raw}   unless defined $param->{raw};
+        $param->{raw} = $self->{default_raw} unless defined $param->{raw};
+    }
+
+    return ($param, $ns, map { $param->{$_} } @pnames);
+}
+
+=pod
+
+=head3 Call
+
+Call a stored procedure. Returns an arrayref of the result tuple(s) upon success.
+
+    my $results = $box->Call('stored_procedure_name', \@procedure_params, \%options) or die $box->ErrorStr; # Call failed
+    my $result_tuple = @$results && $results->[0] or warn "Call succeeded, but returned nothing";
+
+=over
+
+=item B<@procedure_params>
+
+An array of bytestrings to be passed as is to the procecedure.
+
+=item B<%options>
+
+=over
+
+=item B<unpack_format>
+
+Format to unpack the result tuple, the same as C<format> option for C<new()>
+
+=back
+
+=back
+
+=cut
+
+sub Call {
+    my ($param, $namespace) = $_[0]->_validate_param(\@_, qw/flags raise unpack unpack_format/);
+    my ($self, $sp_name, $tuple) = @_;
+
+    my $flags = $param->{flags} || 0;
+    local $self->{raise} = $param->{raise} if defined $param->{raise};
+
+    $self->_debug("$self->{name}: CALL($sp_name)[${\join '   ', map {join' ',unpack'(H2)*',$_} @$tuple}]") if $self->{debug} >= 4;
+    confess "All fields must be defined" if grep { !defined } @$tuple;
+
+    confess "Bad `unpack_format` option" if exists $param->{unpack_format} and ref $param->{unpack_format} ne 'ARRAY';
+    my $unpack_format = join '', map { /&/ ? 'w/a*' : "x$_" } @{$param->{unpack_format}};
+
+    local $namespace->{unpack_format} = $unpack_format if $unpack_format; # XXX
+    local $namespace->{append_for_unpack} = ''         if $unpack_format; # shit...
+
+    $self->_chat (
+        msg      => 22,
+        payload  => pack("L w/a* L(w/a*)*", $flags, $sp_name, scalar(@$tuple), @$tuple),
+        unpack   => $param->{unpack} || sub { $self->_unpack_select($namespace, "CALL", @_) },
+        callback => $param->{callback},
+    );
+}
+
+=pod
+
+=head3 Add, Insert, Replace
+
+    $box->Add(@tuple) or die $box->ErrorStr;         # only store a new tuple
+    $box->Replace(@tuple, { space => "secondary" }); # only store an existing tuple
+    $box->Insert(@tuple, { space => "main" });       # store anyway
+
+Insert a C<< @tuple >> into the storage into C<$options{space}> or C<default_space> space.
+All of them return C<true> upon success.
+
+All of them have the same parameters:
+
+=over
+
+=item B<@tuple>
+
+A tuple to insert. All fields must be defined. All fields will be C<pack()>ed according to C<format> (see L</new>)
+
+=item B<%options>
+
+=over
+
+=item B<space> => $space_id_uint32_or_name_string
+
+Specify storage space to work on.
+
+=back
+
+=back
+
+The difference between them is the behaviour concerning tuple with the same primary key:
+
+=over
+
+=item *
+
+B<Add> will succeed if and only if duplicate-key tuple B<does not exist> 
+
+=item *
+
+B<Replace> will succeed if and only if a duplicate-key tuple B<exists> 
+
+=item *
+
+B<Insert> will succeed B<anyway>. Duplicate-key tuple will be B<overwritten>
+
+=back
+
+=cut
+
+sub Add { # store tuple if tuple identified by primary key _does_not_ exist
+    my $param = @_ && ref $_[-1] eq 'HASH' ? pop : {};
+    $param->{action} = 'add';
+    $_[0]->Insert(@_[1..$#_], $param);
+}
+
+sub Set { # store tuple _anyway_
+    my $param = @_ && ref $_[-1] eq 'HASH' ? pop : {};
+    $param->{action} = 'set';
+    $_[0]->Insert(@_[1..$#_], $param);
+}
+
+sub Replace { # store tuple if tuple identified by primary key _does_ exist
+    my $param = @_ && ref $_[-1] eq 'HASH' ? pop : {};
+    $param->{action} = 'replace';
+    $_[0]->Insert(@_[1..$#_], $param);
+}
+
+sub Insert {
+    my ($param, $namespace) = $_[0]->_validate_param(\@_, qw/want_result want_inserted_tuple _flags action raw/);
+    my ($self, @tuple) = @_;
+
+    $self->_debug("$self->{name}: INSERT(NS:$namespace->{namespace},TUPLE:[@{[map {qq{`$_'}} @tuple]}])") if $self->{debug} >= 3;
+
+    $param->{want_result} = $param->{want_inserted_tuple} if !defined $param->{want_result};
+
+
+    my $flags = $param->{_flags} || 0;
+    $flags |= WANT_RESULT if $param->{want_result};
+
+    $param->{action} ||= 'set';
+    if ($param->{action} eq 'add') {
+        $flags |= INSERT_ADD;
+    } elsif ($param->{action} eq 'replace') {
+        $flags |= INSERT_REPLACE;
+    } elsif ($param->{action} ne 'set') {
+        confess "$self->{name}: Bad insert action `$param->{action}'";
+    }
+    my $chkkey = $namespace->{check_keys};
+    my $fmt = $namespace->{field_format};
+    confess "Wrong fields number in tuple" if @tuple != @$fmt;
+    for (0..$#tuple) {
+        confess "$self->{name}: ref in tuple $_=`$tuple[$_]'" if ref $tuple[$_];
+        no warnings 'uninitialized';
+        if(exists $chkkey->{$_}) {
+            if($chkkey->{$_}) {
+                confess "$self->{name}: undefined key $_" unless defined $tuple[$_];
+            } else {
+                confess "$self->{name}: not numeric key $_=`$tuple[$_]'" unless looks_like_number($tuple[$_]) && int($tuple[$_]) == $tuple[$_];
+            }
+        }
+        $tuple[$_] = pack($fmt->[$_], $tuple[$_]);
+    }
+
+    $self->_debug("$self->{name}: INSERT[${\join '   ', map {join' ',unpack'(H2)*',$_} @tuple}]") if $self->{debug} >= 4;
+
+    my $r = $self->_chat (
+        msg      => 13,
+        payload  => pack("LLL (w/a*)*", $namespace->{namespace}, $flags, scalar(@tuple), @tuple),
+        unpack   => sub { $self->_unpack_affected($flags, $namespace, @_) },
+        callback => $param->{callback},
+    ) or return;
+
+    return $r unless $param->{want_result};
+
+    $self->_PostSelect($r, $param, $namespace);
+    return $r->[0];
+}
+
+sub _unpack_select {
+    my ($self, $ns, $debug_prefix) = @_;
+    $debug_prefix ||= "SELECT";
+    confess __LINE__."$self->{name}: [$debug_prefix]: Bad response" if length $_[3] < 4;
+    my $result_count = unpack('L', substr($_[3], 0, 4, ''));
+    $self->_debug("$self->{name}: [$debug_prefix]: COUNT=[$result_count];") if $self->{debug} >= 3;
+    my (@res);
+    my $appe = $ns->{append_for_unpack};
+    my $fmt  = $ns->{unpack_format};
+    for(my $i = 0; $i < $result_count; ++$i) {
+        confess __LINE__."$self->{name}: [$debug_prefix]: Bad response" if length $_[3] < 8;
+        my ($len, $cardinality) = unpack('LL', substr($_[3], 0, 8, ''));
+        $self->_debug("$self->{name}: [$debug_prefix]: ROW[$i]: LEN=[$len]; NFIELD=[$cardinality];") if $self->{debug} >= 4;
+        confess __LINE__."$self->{name}: [$debug_prefix]: Bad response" if length $_[3] < $len;
+        my $packed_tuple = substr($_[3], 0, $len, '');
+        $self->_debug("$self->{name}: [$debug_prefix]: ROW[$i]: DATA=[@{[unpack '(H2)*', $packed_tuple]}];") if $self->{debug} >= 6;
+        $packed_tuple .= $appe;
+        my @tuple = eval { unpack($fmt, $packed_tuple) };
+        confess "$self->{name}: [$debug_prefix]: ROW[$i]: can't unpack tuple [@{[unpack('(H2)*', $packed_tuple)]}]: $@" if !@tuple || $@;
+        $self->_debug("$self->{name}: [$debug_prefix]: ROW[$i]: FIELDS=[@{[map { qq{`$_'} } @tuple]}];") if $self->{debug} >= 5;
+        push @res, \@tuple;
+    }
+    return \@res;
+}
+
+sub _unpack_select_multi {
+    my ($self, $nss, $debug_prefix) = @_;
+    $debug_prefix ||= "SMULTI";
+    my (@rsets);
+    my $i = 0;
+    for my $ns (@$nss) {
+        push @rsets, $self->_unpack_select($ns, "${debug_prefix}[$i]", $_[3]);
+        ++$i;
+    }
+    return \@rsets;
+}
+
+
+sub _unpack_affected {
+    my ($self, $flags, $ns) = @_;
+    ($flags & WANT_RESULT) ? $self->_unpack_select($ns, "AFFECTED", $_[3]) : unpack('L', substr($_[3],0,4,''))||'0E0';
+}
+
+sub NPRM () { 3 }
+sub _pack_keys {
+    my ($self, $ns, $idx) = @_;
+
+    my $keys   = $idx->{keys};
+    my $strkey = $ns->{string_keys};
+    my $fmt    = $ns->{field_format};
+
+    if (@$keys == 1) {
+        $fmt    = $fmt->[$keys->[0]];
+        $strkey = $strkey->{$keys->[0]};
+        foreach (@_[NPRM..$#_]) {
+            ($_) = @$_ if ref $_ eq 'ARRAY';
+            unless ($strkey) {
+                confess "$self->{name}: not numeric key [$_]" unless looks_like_number($_) && int($_) == $_;
+                $_ = pack($fmt, $_);
+            }
+            $_ = pack('L(w/a*)*', 1, $_);
+        }
+    } else {
+        foreach my $k (@_[NPRM..$#_]) {
+            confess "bad key [@$keys][$k][@{[ref $k eq 'ARRAY' ? (@$k) : ()]}]" unless ref $k eq 'ARRAY' and @$k and @$k <= @$keys;
+            for my $i (0..$#$k) {
+                unless ($strkey->{$keys->[$i]}) {
+                    confess "$self->{name}: not numeric key [$i][$k->[$i]]" unless looks_like_number($k->[$i]) && int($k->[$i]) == $k->[$i];
+                }
+                $k->[$i] = pack($fmt->[$keys->[$i]], $k->[$i]);
+            }
+            $k = pack('L(w/a*)*', scalar(@$k), @$k);
+        }
+    }
+}
+
+sub _PackSelect {
+    my ($self, $param, $namespace, @keys) = @_;
+    return '' unless @keys;
+    $self->_pack_keys($namespace, $param->{index}, @keys);
+    my $format = "";
+    if ($param->{format}) {
+        my $f = $namespace->{byfield_unpack_format};
+        $param->{unpack_format} = join '', map { $f->[$_->{field}] } @{$param->{format}};
+        $format = pack 'l*', scalar @{$param->{format}}, map {
+            $_ = { %$_ };
+            if($_->{full}) {
+                $_->{offset} = 0;
+                $_->{length} = 'max';
+            }
+            $_->{length} = 0x7FFFFFFF if $_->{length} eq 'max';
+            @$_{qw/field offset length/}
+        } @{$param->{format}};
+    }
+    return pack("LLLL a* La*", $namespace->{namespace}, $param->{index}->{id}, $param->{offset} || 0, $param->{limit} || scalar(@keys), $format, scalar(@keys), join('',@keys));
+}
+
+sub _PostSelect {
+    my ($self, $r, $param, $namespace) = @_;
+    if(!$param->{raw}) {
+        my $hashify = $param->{hashify} || $namespace->{hashify} || $self->{hashify};
+        if($hashify) {
+            $hashify->($namespace->{namespace}, $r);
+        } elsif( $namespace->{fields} ) {
+            $_ = { zip @{$namespace->{fields}}, @$_ } for @$r;
+        }
+    }
+}
+
+=pod
+
+=head3 Select
+
+Select tuple(s) from storage
+
+    my $key = $id;
+    my $key = [ $firstname, $lastname ];
+    my @keys = ($key, ...);
+    
+    my $tuple  = $box->Select($key)              or $box->Error && die $box->ErrorStr;
+    my $tuple  = $box->Select($key, \%options)   or $box->Error && die $box->ErrorStr;
+    
+    my @tuples = $box->Select(@keys)             or $box->Error && die $box->ErrorStr;
+    my @tuples = $box->Select(@keys, \%options)  or $box->Error && die $box->ErrorStr;
+    
+    my $tuples = $box->Select(\@keys)            or die $box->ErrorStr;
+    my $tuples = $box->Select(\@keys, \%options) or die $box->ErrorStr;
+
+=over
+
+=item B<$key>, B<@keys>, B<\@keys>
+
+Specify keys to select. All keys must be defined.
+
+Contextual behaviour:
+
+=over
+
+=item *
+
+In scalar context, you can select one C<$key>, and the resulting tuple will be returned.
+Check C<< $box->Error >> to see if there was an error or there is just no such key
+in the storage
+
+=item *
+
+In list context, you can select several C<@keys>, and the resulting tuples will be returned.
+Check C<< $box->Error >> to see if there was an error or there is just no such keys
+in the storage
+
+=item *
+
+If you select C<< \@keys >> then C<< \@tuples >> will be returned upon success. C<< @tuples >> will
+be empty if there are no such keys, and false will be returned in case of error.
+
+=back
+
+Other notes:
+
+=over
+
+=item *
+
+If you select using index on multiple fields each C<< $key >> should be given as a key-tuple C<< $key = [ $key_field1, $key_field2, ... ] >>.
+
+=back
+
+=item B<%options>
+
+=over
+
+=item B<space> => $space_id_uint32_or_name_string
+
+Specify storage (by id or name) space to select from.
+
+=item B<use_index> => $index_id_uint32_or_name_string
+
+Specify index (by id or name) to use.
+
+=item B<raw> => $bool
+
+Don't C<hashify> (see L</new>).
+
+=item B<hash_by> => $by
+
+Return a hashref of the resultset. If you C<hashify> the result set,
+then C<$by> must be a field name of the hash you return,
+otherwise it must be a number of field of the tuple.
+C<False> will be returned in case of error.
+
+=back
+
+=back
+
+=cut
+
+my @select_param_ok = qw/use_index raw want next_rows limit offset raise hashify timeout format hash_by/;
+sub Select {
+    confess q/Select isnt callable in void context/ unless defined wantarray;
+    my ($param, $namespace) = $_[0]->_validate_param(\@_, @select_param_ok);
+    my ($self, @keys) = @_;
+    local $self->{raise} = $param->{raise} if defined $param->{raise};
+    @keys = @{$keys[0]} if @keys && ref $keys[0] eq 'ARRAY' and 1 == @{$param->{index}->{keys}} || (@keys && ref $keys[0]->[0] eq 'ARRAY');
+
+    $self->_debug("$self->{name}: SELECT(NS:$namespace->{namespace},IDX:$param->{use_index})[@{[map{ref$_?qq{[@$_]}:$_}@keys]}]") if $self->{debug} >= 3;
+
+    my ($msg,$payload);
+    if(exists $param->{next_rows}) {
+        confess "$self->{name}: One and only one key can be used to get N>0 rows after it" if @keys != 1 || !$param->{next_rows};
+        $msg = 15;
+        $self->_pack_keys($namespace, $param->{index}, @keys);
+        $payload = pack("LL a*", $namespace->{namespace}, $param->{next_rows}, join('',@keys)),
+    } else {
+        $payload = $self->_PackSelect($param, $namespace, @keys);
+        $msg = $param->{format} ? 21 : 17;
+    }
+
+    local $namespace->{unpack_format} = $param->{unpack_format} if $param->{unpack_format};
+
+    my $r = [];
+    if (@keys && $payload) {
+        $r = $self->_chat(
+            msg      => $msg,
+            payload  => $payload,
+            unpack   => sub { $self->_unpack_select($namespace, "SELECT", @_) },
+            retry    => $self->{select_retry},
+            timeout  => $param->{timeout} || $self->{select_timeout},
+            callback => $param->{callback},
+        ) or return;
+    }
+
+    $param->{want} ||= !1;
+
+    $self->_PostSelect($r, $param, $namespace);
+
+    if(defined(my $p = $param->{hash_by})) {
+        my %h;
+        if(@$r) {
+            if (ref $r->[0] eq 'HASH') {
+                confess "Bad hash_by `$p' for HASH" unless exists $r->[0]->{$p};
+                $h{$_->{$p}} = $_ for @$r;
+            } elsif(ref $r->[0] eq 'ARRAY') {
+                confess "Bad hash_by `$p' for ARRAY" unless $p =~ m/^\d+$/ && $p >= 0 && $p < @{$r->[0]};
+                $h{$_->[$p]} = $_ for @$r;
+            } else {
+                confess "i dont know how to hash_by ".ref($r->[0]);
+            }
+        }
+        return \%h;
+    }
+
+    return $r if $param->{want} eq 'arrayref';
+
+    if (wantarray) {
+        return @{$r};
+    } else {
+        confess "$self->{name}: too many keys in scalar context" if @keys > 1;
+        return $r->[0];
+    }
+}
+
+sub SelectUnion {
+    confess "not supported yet";
+    my ($param) = $_[0]->_validate_param(\@_, qw/raw raise/);
+    my ($self, @reqs) = @_;
+    return [] unless @reqs;
+    local $self->{raise} = $param->{raise} if defined $param->{raise};
+    confess "bad param" if grep { ref $_ ne 'ARRAY' } @reqs;
+    $param->{want} ||= 0;
+    for my $req (@reqs) {
+        my ($param, $namespace) = $self->_validate_param($req, @select_param_ok);
+        $req = {
+            payload   => $self->_PackSelect($param, $namespace, $req),
+            param     => $param,
+            namespace => $namespace,
+        };
+    }
+    my $r = $self->_chat(
+        msg      => 18,
+        payload  => pack("L (a*)*", scalar(@reqs), map { $_->{payload} } @reqs),
+        unpack   => sub { $self->_unpack_select_multi([map { $_->{namespace} } @reqs], "SMULTI", @_) },
+        retry    => $self->{select_retry},
+        timeout  => $param->{select_timeout} || $self->{timeout},
+        callback => $param->{callback},
+    ) or return;
+    confess __LINE__."$self->{name}: something wrong" if @$r != @reqs;
+    my $ea = each_arrayref $r, \@reqs;
+    while(my ($res, $req) = $ea->()) {
+        $self->_PostSelect($res, { %$param, %{$req->{param}} }, $req->{namespace});
+    }
+    return $r;
+}
+
+=pod
+
+=head3 Delete
+
+Delete tuple from storage. Return false upon error.
+
+    my $n_deleted = $box->Delete($key) or die $box->ErrorStr;
+    my $n_deleted = $box->Delete($key, \%options) or die $box->ErrorStr;
+    warn "Nothing was deleted" unless int $n_deleted;
+    
+    my $deleted_tuple_set = $box->Delete($key, { want_deleted_tuples => 1 }) or die $box->ErrorStr;
+    warn "Nothing was deleted" unless @$deleted_tuple_set;
+
+=over
+
+=item B<%options>
+
+=over
+
+=item B<space> => $space_id_uint32_or_name_string
+
+Specify storage space (by id or name) to work on.
+
+=item B<want_deleted_tuple> => $bool
+
+if C<$bool> then return deleted tuple.
+
+=back
+
+=back
+
+=cut
+
+sub Delete {
+    my ($param, $namespace) = $_[0]->_validate_param(\@_, qw/want_deleted_tuple want_result raw/);
+    my ($self, $key) = @_;
+
+    $param->{want_result} = $param->{want_deleted_tuple} if !defined $param->{want_result};
+
+    my $flags = 0;
+    $flags |= WANT_RESULT if $param->{want_result};
+
+    $self->_debug("$self->{name}: DELETE(NS:$namespace->{namespace},KEY:$key,F:$flags)") if $self->{debug} >= 3;
+
+    confess "$self->{name}\->Delete: for now key cardinality of 1 is only allowed" unless 1 == @{$param->{index}->{keys}};
+    $self->_pack_keys($namespace, $param->{index}, $key);
+
+    my $r = $self->_chat(
+        msg      => $flags ? 21 : 20,
+        payload  => $flags ? pack("L L a*", $namespace->{namespace}, $flags, $key) : pack("L a*", $namespace->{namespace}, $key),
+        unpack   => sub { $self->_unpack_affected($flags, $namespace, @_) },
+        callback => $param->{callback},
+    ) or return;
+
+    return $r unless $param->{want_result};
+
+    $self->_PostSelect($r, $param, $namespace);
+    return $r->[0];
+}
+
+sub OP_SET          () { 0 }
+sub OP_ADD          () { 1 }
+sub OP_AND          () { 2 }
+sub OP_XOR          () { 3 }
+sub OP_OR           () { 4 }
+sub OP_SPLICE       () { 5 }
+
+my %update_ops = (
+    set         => OP_SET,
+    add         => OP_ADD,
+    and         => OP_AND,
+    xor         => OP_XOR,
+    or          => OP_OR,
+    splice      => sub {
+        confess "value for operation splice must be an ARRAYREF of <int[, int[, string]]>" if ref $_[0] ne 'ARRAY' || @{$_[0]} < 1;
+        $_[0]->[0] = 0x7FFFFFFF unless defined $_[0]->[0];
+        $_[0]->[0] = pack 'l', $_[0]->[0];
+        $_[0]->[1] = defined $_[0]->[1] ? pack 'l', $_[0]->[1] : '';
+        $_[0]->[2] = '' unless defined $_[0]->[2];
+        return (OP_SPLICE, [ pack '(w/a*)*', @{$_[0]} ]);
+    },
+    append      => sub { splice => [undef,  0,     $_[0]] },
+    prepend     => sub { splice => [0,      0,     $_[0]] },
+    cutbeg      => sub { splice => [0,      $_[0], ''   ] },
+    cutend      => sub { splice => [-$_[0], $_[0], ''   ] },
+    substr      => 'splice',
+);
+
+!ref $_ && m/^\D/ and $_ = $update_ops{$_} || die "bad link" for values %update_ops;
+
+my %update_arg_fmt = (
+    (map { $_ => 'l' } OP_ADD),
+    (map { $_ => 'L' } OP_AND, OP_XOR, OP_OR),
+);
+
+my %ops_type = (
+    (map { $_ => 'any'    } OP_SET),
+    (map { $_ => 'number' } OP_ADD, OP_AND, OP_XOR, OP_OR),
+    (map { $_ => 'string' } OP_SPLICE),
+);
+
+BEGIN {
+    for my $op (qw/Append Prepend Cutbeg Cutend Substr/) {
+        eval q/
+            sub /.$op.q/ {
+                my $param = ref $_[-1] eq 'HASH' ? pop : {};
+                my ($self, $key, $field_num, $val) = @_;
+                $self->UpdateMulti($key, [ $field_num => /.lc($op).q/ => $val ], $param);
+            }
+            1;
+        / or die $@;
+    }
+}
+
+=pod
+
+=head3 UpdateMulti
+
+Apply several update operations to a tuple.
+
+    my @op = ([ f1 => add => 10 ], [ f1 => and => 0xFF], [ f2 => set => time() ], [ misc_string => cutend => 3 ]);
+    
+    my $n_updated = $box->UpdateMulti($key, @op) or die $box->ErrorStr;
+    my $n_updated = $box->UpdateMulti($key, @op, \%options) or die $box->ErrorStr;
+    warn "Nothing was updated" unless int $n_updated;
+    
+    my $updated_tuple_set = $box->UpdateMulti($key, @op, { want_result => 1 }) or die $box->ErrorStr;
+    warn "Nothing was updated" unless @$updated_tuple_set;
+
+Different fields can be updated at one shot.
+The same field can be updated more than once.
+All update operations are done atomically.
+Returns false upon error.
+
+=over
+
+=item B<@op> = ([ $field => $op => $value ], ...)
+
+=over
+
+=item B<$field>
+
+Field-to-update number or name (see L</fields>).
+
+=item B<$op>
+
+=over
+
+=item B<set>
+
+Set C<< $field >> to C<< $value >>
+
+=item B<add>, B<and>, B<xor>, B<or>
+
+Apply an arithmetic operation to C<< $field >> with argument C<< $value >>
+Currently arithmetic operations are supported only for int32 (4-byte length) fields (and C<$value>s too)
+
+=item B<splice>, B<substr>
+
+Apply a perl-like L<splice|perlfunc/splice> operation to C<< $field >>. B<$value> = [$OFFSET, $LENGTH, $REPLACE_WITH].
+substr is just an alias.
+
+=item B<append>, B<prepend>
+
+Append or prepend C<< $field >> with C<$value> string.
+
+=item B<cutbeg>, B<cutend>
+
+Cut C<< $value >> bytes from beginning or end of C<< $field >>.
+
+=back 
+
+=back
+
+=item B<%options>
+
+=over
+
+=item B<space> => $space_id_uint32_or_name_string
+
+Specify storage space (by id or name) to work on.
+
+=item B<want_updated_tuple> => $bool
+
+if C<$bool> then return updated tuple.
+
+=back
+
+=cut
+
+sub UpdateMulti {
+    my ($param, $namespace) = $_[0]->_validate_param(\@_, qw/want_updated_tuple want_result _flags raw/);
+    my ($self, $key, @op) = @_;
+
+    $self->_debug("$self->{name}: UPDATEMULTI(NS:$namespace->{namespace},KEY:$key)[@{[map{qq{[@$_]}}@op]}]") if $self->{debug} >= 3;
+
+    confess "$self->{name}\->UpdateMulti: for now key cardinality of 1 is only allowed" unless 1 == @{$param->{index}->{keys}};
+    confess "$self->{name}: too many op" if scalar @op > 128;
+
+    $param->{want_result} = $param->{want_updated_tuple} if !defined $param->{want_result};
+
+    my $flags = $param->{_flags} || 0;
+    $flags |= WANT_RESULT if $param->{want_result};
+
+    my $fmt = $namespace->{field_format};
+    my $fields_hash = $namespace->{fields_hash};
+
+    foreach (@op) {
+        confess "$self->{name}: bad op <$_>" if ref ne 'ARRAY' or @$_ != 3;
+        my ($field_num, $op, $value) = @$_;
+
+        if($field_num =~ m/^[A-Za-z]/) {
+            confess "no such field $field_num in space $namespace->{name}($namespace->{space})" unless exists $fields_hash->{$field_num};
+            $field_num = $fields_hash->{$field_num};
+        }
+
+        my $field_type = $namespace->{string_keys}->{$field_num} ? 'string' : 'number';
+
+        my $is_array = 0;
+        if ($op eq 'bit_set') {
+            $op = OP_OR;
+        } elsif ($op eq 'bit_clear') {
+            $op = OP_AND;
+            $value = ~$value;
+        } elsif ($op =~ /^num_(add|sub)$/) {
+            $value = -$value if $1 eq 'sub';
+            $op = OP_ADD;
+        } else {
+            confess "$self->{name}: bad op <$op>" unless exists $update_ops{$op};
+            $op = $update_ops{$op};
+        }
+
+        while(ref $op eq 'CODE') {
+            ($op, $value) = &$op($value);
+            $op = $update_ops{$op} if exists $update_ops{$op};
+        }
+
+        confess "Are you sure you want to apply `$ops_type{$op}' operation to $field_type field?" if $ops_type{$op} ne $field_type && $ops_type{$op} ne 'any';
+
+        $value = [ $value ] unless ref $value;
+        confess "dunno what to do with ref `$value'" if ref $value ne 'ARRAY';
+
+        confess "bad fieldnum: $field_num" if $field_num >= @$fmt;
+        $value = pack($update_arg_fmt{$op} || $fmt->[$field_num], @$value);
+        $_ = pack('LCw/a*', $field_num, $op, $value);
+    }
+
+    $self->_pack_keys($namespace, $param->{index}, $key);
+
+    my $r = $self->_chat(
+        msg      => 19,
+        payload  => pack("LL a* L (a*)*" , $namespace->{namespace}, $flags, $key, scalar(@op), @op),
+        unpack   => sub { $self->_unpack_affected($flags, $namespace, @_) },
+        callback => $param->{callback},
+    ) or return;
+
+    return $r unless $param->{want_result};
+
+    $self->_PostSelect($r, $param, $namespace);
+    return $r->[0];
+}
+
+sub Update {
+    my $param = ref $_[-1] eq 'HASH' ? pop : {};
+    my ($self, $key, $field_num, $value) = @_;
+    $self->UpdateMulti($key, [$field_num => set => $value ], $param);
+}
+
+sub AndXorAdd {
+    my $param = ref $_[-1] eq 'HASH' ? pop : {};
+    my ($self, $key, $field_num, $and, $xor, $add) = @_;
+    my @upd;
+    push @upd, [$field_num => and => $and] if defined $and;
+    push @upd, [$field_num => xor => $xor] if defined $xor;
+    push @upd, [$field_num => add => $add] if defined $add;
+    $self->UpdateMulti($key, @upd, $param);
+}
+
+sub Bit {
+    my $param = ref $_[-1] eq 'HASH' ? pop : {};
+    my ($self, $key, $field_num, %arg) = @_;
+    confess "$self->{name}: unknown op '@{[keys %arg]}'"  if grep { not /^(bit_clear|bit_set|set)$/ } keys(%arg);
+
+    $arg{bit_clear} ||= 0;
+    $arg{bit_set}   ||= 0;
+    my @op;
+    push @op, [$field_num => set       => $arg{set}]        if exists $arg{set};
+    push @op, [$field_num => bit_clear => $arg{bit_clear}]  if $arg{bit_clear};
+    push @op, [$field_num => bit_set   => $arg{bit_set}]    if $arg{bit_set};
+
+    $self->UpdateMulti($key, @op, $param);
+}
+
+sub Num {
+    my $param = ref $_[-1] eq 'HASH' ? pop : {};
+    my ($self, $key, $field_num, %arg) = @_;
+    confess "$self->{name}: unknown op '@{[keys %arg]}'"  if grep { not /^(num_add|num_sub|set)$/ } keys(%arg);
+
+    $arg{num_add} ||= 0;
+    $arg{num_sub} ||= 0;
+
+    $arg{num_add} -= $arg{num_sub};
+    my @op;
+    push @op, [$field_num => set     => $arg{set}]     if exists $arg{set};
+    push @op, [$field_num => num_add => $arg{num_add}]; # if $arg{num_add};
+    $self->UpdateMulti($key, @op, $param);
+}
+
+=head1 LICENCE AND COPYRIGHT
+
+This is free software; you can redistribute it and/or modify it under the same terms as the Perl 5 programming language system itself.
+
+=head1 SEE ALSO
+
+=over
+
+=item *
+
+L<http://tarantool.org>
+
+=item *
+
+L<MR::Tarantool::Box::Singleton>
+
+=back
+
+=cut
+
+
+1;
diff --git a/connector/perl/lib/MR/Tarantool/Box/Singleton.pm b/connector/perl/lib/MR/Tarantool/Box/Singleton.pm
new file mode 100644
index 0000000000000000000000000000000000000000..3d106a1f800efd2c6ea7a805c9b071638d32ad72
--- /dev/null
+++ b/connector/perl/lib/MR/Tarantool/Box/Singleton.pm
@@ -0,0 +1,484 @@
+package MR::Tarantool::Box::Singleton;
+
+=pod
+
+=head1 NAME
+
+MR::Tarantool::Box::Singleton - A singleton wrapper for L<MR::Tarantool::Box>.
+
+Provides connection-persistence and replica fallback.
+Please read L<"MR::Tarantool::Box manual"|MR::Tarantool::Box> first.
+
+=head1 SYNOPSIS
+
+    package Some::Tarantool::Box::Singleton;
+    use MR::Tarantool::Box::Singleton;
+    use base 'MR::Tarantool::Box::Singleton';
+
+    BEGIN { # generates "TUPLE_$field_name" constants, and methods: FIELDS, FIELDS_HASH
+        __PACKAGE__->mkfields(qw/ id f1 f2 f3 field4 f5 f6 f7 misc_string /); # applicable for DEFAULT_SPACE only
+    }
+
+    sub SERVER   { Some::Config->GetBoxServer()   }
+    sub REPLICAS { Some::Config->GetBoxReplicas() }
+
+    sub DEFAULT_SPACE { 0 }
+
+    sub SPACES   {[{
+        space         => 0,
+        indexes => [ {
+            index_name   => 'primary_id',
+            keys         => [TUPLE_id],
+        }, {
+            index_name   => 'secondary_f1f2',
+            keys         => [TUPLE_f1, TUPLE_f2],
+        }, ],
+        format        => 'QqLlSsCc&',
+        default_index => 'primary_id',
+    }, {
+        space         => 1,
+        indexes => [ {
+            index_name   => 'primary_id',
+            keys         => [0],
+        }, ],
+        format        => '&&&&',
+        fields        => [qw/ string1 str2 s3 s4 /],
+    }]}
+
+=head1 DESCRIPTION
+
+=head2 METHODS
+
+=cut
+
+use strict;
+use warnings;
+
+use MR::Tarantool::Box;
+use Class::Singleton;
+use Carp qw/confess cluck/;
+use List::Util qw/shuffle/;
+
+use base qw/Class::Singleton/;
+
+=pod
+
+=head3 mkfields
+
+    BEGIN {
+        $CLASS->mkfields(@names);
+    }
+
+=over
+
+=item *
+
+Generates constants "TUPLE_$fieldname" => $fieldposition in C<$CLASS>.
+Just Like if you say C<< use constant TUPLE_id => 0, TUPLE_f1 => 1, ...; >>
+
+=item *
+
+Generates C<$CLASS> variable C<< @fields >> containing field names,
+and a C<$CLASS> method C<FIELDS> returning C<< @fields >>.
+
+=item *
+
+Generates C<$CLASS> variable C<< %fields >> containing field names mapping to positions,
+and a C<$CLASS> method C<FIELDS_HASH> returning C<< \%fields >>.
+
+=item *
+
+These C<< @fields >> are applied to the C<< DEFAULT_SPACE >>,
+if I<< fields >> were not set explicitly for that space.
+
+=back
+
+=cut
+
+sub mkfields {
+    my($class, @fields) = @_;
+    no strict 'refs';
+    confess "Fields are already defined for $class" if @{"${class}::fields"};
+    @{"${class}::fields"} = @fields;
+    %{"${class}::fields"} = map { $fields[$_] => $_ } 0..$#fields;
+    eval qq{ sub ${class}::TUPLE_$fields[$_] () { $_ } } for 0..$#fields;
+    eval qq{ sub ${class}::FIELDS      () {   \@${class}::fields } };
+    eval qq{ sub ${class}::FIELDS_HASH () { \\\%${class}::fields } };
+}
+
+=pod
+
+=head3 declare_stored_procedure
+
+    $CLASS->declare_stored_procedure(%args);
+    
+    $CLASS->declare_stored_procedure(
+        name             => "box.do.something",                        # internal procedure name, in da box
+        method_name      => "CallMyTestingStoredProcedure",            # will generate method named
+        options          => { default => options },                    # MR::Tarantool::Box->Call \%options
+        params           => [ qw{ P1 P2 P3 Param4 }],                  # names
+    
+        unpack_format    => [qw/ & L S C /],
+    
+        params_format    => [qw{ C S L a* }],
+        params_default   => [ 1, 2, undef, 'the_default' ],            # undef's are mandatory params
+    );
+    
+    ...
+    
+    my $data = $CLASS->CallMyTestingStoredProcedure(
+        P1 => $val1,
+        P2 => $val2,
+        P3 => $val3,
+        Param4 => $val3,
+        { option => $value }, # optional
+    ) or warn $CLASS->ErrorStr;
+
+Declare a stored procedure. This generates C<$CLASS> method C<< $args{method_name} >> which
+calls Tarantool/Box procedure C<< $args{name} >>, using C<< $args{options} >> as default
+C<< \%options >> for C<< MR::Tarantool::Box->Call >> call. The generated method has the following
+prototype:
+
+    $CLASS->CallMyTestingStoredProcedure( %sp_params, \%optional_options );
+
+Parameters description:
+
+=over
+
+=item B<%args>:
+
+=over
+
+=item B<name> => $tarantool_box_sp_name
+
+The name of procedure in Tarantool/Box to call.
+
+=item B<method_name> => $class_method_name
+
+Class method name to generate.
+
+=item B<options> => \%options
+
+Options to pass to L<MR::Taranatool::Box->Call|MR::Taranatool::Box/Call> method.
+
+=item B<params> => \@names
+
+Procedure input parameters' names
+
+=item B<params_default> => \@defaults
+
+Procedure input parameters default values. Undefined or absent value makes
+its parameter mandatory.
+
+=item B<params_format> => \@format
+
+C<< pack() >>-compatible format to pack input parameters. Must match C<params>.
+
+=item B<unpack_format> => \@format
+
+C<< pack() >>-compatible format to unpack procedure output.
+
+=back
+
+=item B<%sp_params>:
+
+C<< Name => $value >> pairs.
+
+=item B<%optional_options>:
+
+Options to pass to L<MR::Taranatool::Box->Call|MR::Taranatool::Box/Call> method.
+This overrides C<< %options >> values key-by-key.
+
+=back
+
+=cut
+
+sub declare_stored_procedure {
+    my($class, %opts) = @_;
+    my $name = delete $opts{name} or confess "No `name` given";
+    my $options = $opts{options} || {};
+
+    confess "no `params` given; it must be an arrayref" if !exists $opts{params} or ref $opts{params} ne 'ARRAY';
+    my @params = @{$opts{params}};
+
+    my $pack;
+    if(my $fn = $opts{pack}) {
+        confess "`params_format` and `params_default` are not applicable while `pack` is in use" if exists $opts{params_format} or exists $opts{params_default};
+        if(ref $fn) {
+            confess "`pack` can be code ref or a method name, nothing else" unless ref $fn eq 'CODE';
+            $pack = $fn;
+        } else {
+            confess "`pack` method $fn is not provided by class ${class}" unless $class->can($fn);
+            $pack = sub { $class->$fn(@_) };
+        }
+    } else {
+        confess "no `pack` nor `params_format` given; it must be an arrayref with number of elements exactly as in `params`" if !exists $opts{params_format} or ref $opts{params_format} ne 'ARRAY' or @{$opts{params_format}} != @params;
+        confess "`params_default` is given but it must be an arrayref with number of elements no more then in `params`" if exists $opts{params_format} and (ref $opts{params_format} ne 'ARRAY' or @{$opts{params_format}} > @params);
+        my @fmt = @{$opts{params_format}};
+        my @def = @{$opts{params_default}||[]};
+        $pack = sub {
+            my $p = $_[0];
+            for my $i (0..$#params) {
+                $p->[$i] = $def[$i] if !defined$p->[$i] and $i < @def;
+                confess "All params must be defined" unless defined $p->[$i];
+                $p->[$i] = pack $fmt[$i], $p->[$i];
+            }
+            return $p;
+        };
+    }
+
+    my $unpack;
+    if(my $fn = $opts{unpack}) {
+        if(ref $fn) {
+            confess "`unpack` can be code ref or a method name, nothing else" unless ref $fn eq 'CODE';
+            $unpack = $fn;
+        } else {
+            confess "`unpack` method $fn is not provided by class ${class}" unless $class->can($fn);
+            $unpack = sub { $class->$fn(@_) };
+        }
+        $options->{unpack_format} = [ "a*" ];
+    } else {
+        confess "no `unpack` nor `unpack_format` given; it must be an arrayref" if !exists $opts{unpack_format} or ref $opts{unpack_format} ne 'ARRAY';
+        my $f = $opts{unpack_format};
+        $options->{unpack_format} = $f;
+    }
+
+    my $method = $opts{method_name} or confess "`method_name` not given";
+    confess "bad `method_name` $method" unless $method =~ m/^[a-zA-Z]\w*$/;
+    my $fn = "${class}::${method}";
+    confess "Method $method id already defined in class $class" if defined &{$fn};
+    do {
+        no strict 'refs';
+        *$fn = sub {
+            my $p0 = @_ && ref $_[-1] eq 'HASH' ? pop : {};
+            my $param = { %$options, %$p0 };
+            my ($class, %params) = @_;
+            my $res = $class->Call($name, $pack->([@params{@params}]), $param) or return;
+            return $res unless $unpack;
+            return $unpack->($res);
+        }
+    };
+    return $method;
+}
+
+sub Param {
+    confess "bad Param call" unless $_[2];
+    return $_[2] && @{$_[2]} && ref $_[2]->[-1] eq 'HASH' && pop @{$_[2]} || {};
+}
+
+=pod
+
+=head3 Configuration methods
+
+=over
+
+=item B<SERVER>
+
+Must return a string of ip:port of I<master> server.
+
+=item B<REPLICAS>
+
+Must return a comma separated string of ip:port pairs of I<replica> servers (see L</is_replica>).
+Server is chosen from the list randomly.
+
+=item B<MR_TARANTOOL_BOX_CLASS>
+
+Must return name of the class implementing L<MR::Tarantool::Box> interface, or it's descendant.
+
+=item B<SPACES>, B<RAISE>, B<TIMEOUT>, B<SELECT_TIMEOUT>, B<RETRY>, B<SELECT_RETRY>, B<SOFT_RETRY>, B<DEBUG>
+
+See corresponding arguments of L<MR::Tarantool::Box->new|MR::Tarantool::Box/new> method.
+
+=back
+
+=cut
+
+sub DEBUG           () { 0 }
+sub IPDEBUG         () { 0 }
+
+sub TIMEOUT         () { 23 }
+sub SELECT_TIMEOUT  () {  2 }
+
+sub RAISE           () { 1 }
+
+sub RETRY           () { 1 }
+sub SELECT_RETRY    () { 3 }
+sub SOFT_RETRY      () { 3 }
+sub RETRY_DELAY     () { 1 }
+
+sub SERVER          () { die }
+sub REPLICAS        () { []  }
+
+sub MR_TARANTOOL_BOX_CLASS () { 'MR::Tarantool::Box' }
+
+sub SPACES          () { die }
+sub DEFAULT_SPACE   () { undef }
+
+sub _new_instance {
+    my ($class) = @_;
+    my ($config) = $class->can('_config') ? $class->_config : {};
+    $config->{param} ||= {};
+
+    $config->{servers}                     ||= $class->SERVER;
+
+    $config->{param}->{name}               ||= $class;
+
+    $config->{param}->{spaces}             ||= my $sp = $class->SPACES;
+    $config->{param}->{default_space}      ||= my $defsp = @$sp == 1 ? 0 : $class->DEFAULT_SPACE;
+
+    $sp->[$defsp]->{fields} ||= [ $class->FIELDS ] if $class->can('FIELDS');
+
+    $config->{param}->{raise}                = $class->RAISE unless defined $config->{param}->{raise};
+
+    $config->{param}->{timeout}            ||= $class->TIMEOUT;
+    $config->{param}->{select_timeout}     ||= $class->SELECT_TIMEOUT;
+
+    $config->{param}->{debug}              ||= $class->DEBUG;
+    $config->{param}->{ipdebug}            ||= $class->IPDEBUG;
+
+    $config->{param}->{retry}              ||= $class->RETRY;
+    $config->{param}->{select_retry}       ||= $class->SELECT_RETRY;
+    $config->{param}->{softretry}          ||= $class->SOFT_RETRY;
+    $config->{param}->{retry_delay}        ||= $class->RETRY_DELAY;
+
+    $config->{param}->{fields}             ||= [ $class->FIELDS ];
+
+    my $replicas = delete $config->{replicas} || $class->REPLICAS || [];
+    $replicas = [ split /,/, $replicas ] unless ref $replicas eq 'ARRAY';
+
+    return bless {
+        box      => $class->MR_TARANTOOL_BOX_CLASS->new({ servers => $config->{servers}, %{$config->{param}} }),
+        replicas => [ map { $class->MR_TARANTOOL_BOX_CLASS->new({ servers => $_, %{$config->{param}} }) } shuffle @$replicas ],
+    }, $class;
+}
+
+=pod
+
+=head3 Add, Insert, Replace, UpdateMulti, Delete
+
+These methods operate on C<< SERVER >> only.
+See corresponding methods of L<MR::Tarantool::Box> class.
+
+=head3 Select, Call
+
+These methods operate on C<< SERVER >> at first, and then B<may>
+try to query C<< REPLICAS >>.
+
+See corresponding methods of L<MR::Tarantool::Box> class.
+
+These methods have additional C<< %options >> params:
+
+=over
+
+=item B<is_replica> => \$is_result_from_replica
+
+If this option is set, then if the query to C<< SERVER >> fails,
+C<< REPLICAS >> will be queried one-by-one until query succeeds or
+the list ends, and C<< $is_result_from_replica >> will be set to
+C<< true >>, no matter whether any query succeeds or not.
+
+=back
+
+=cut
+
+BEGIN {
+
+    foreach my $method (qw/Insert UpdateMulti Delete Add Set Replace Bit Num AndXorAdd Update/) {
+        no strict 'refs';
+        *$method = sub {
+            use strict;
+            my ($class, @args) = @_;
+            my $param = $class->Param($method, \@args);
+            my $self = $class->instance;
+            $self->{_last_box} = $self->{box};
+            $self->{box}->$method(@args, $param);
+        };
+    }
+
+    foreach my $method (qw/Select SelectUnion Call/) {
+        no strict 'refs';
+        *$method = sub {
+            use strict;
+            my ($class, @args) = @_;
+            my $param = $class->Param($method, \@args);
+
+            if ($param->{format}) {
+                my @F;
+                my $F = $class->FIELDS_HASH;
+                my @format = ref $param->{format} eq 'ARRAY' ? @{$param->{format}} : %{$param->{format}};
+                confess "Odd number of elements in format" if @format % 2;
+                $param->{format} = [];
+                while( my ($field, $fmt) = splice(@format, 0, 2) ) {
+                    confess "Bad format for field `$field'" unless $fmt;
+                    confess "Unknown field `$field'" unless exists $F->{$field};
+                    push @F, $field;
+                    push @{$param->{format}}, {
+                        field  => $F->{$field},
+                        $fmt eq 'full' ? (
+                            full => 1,
+                        ) : (
+                            offset => $fmt->{offset} || 0,
+                            length => (exists $fmt->{length} ? $fmt->{length}||0 : 'max'),
+                        ),
+                    };
+                }
+                $param->{hashify} = sub { $class->_hashify(\@F, @_) };
+            }
+
+            die "${class}\->${method}: is_replica must be a SCALARREF" if exists $param->{is_replica} && ref $param->{is_replica} ne 'SCALAR';
+            my $is_rep = delete $param->{is_replica};
+            $$is_rep = 0 if $is_rep;
+            my $self = $class->instance;
+            my @rep = $is_rep ? @{ $self->{replicas} } : ();
+            my ($ret,@ret);
+            for(my $box = $self->{box}; $box; $box = shift @rep) {
+                $self->{_last_box} = $box;
+                if(wantarray) {
+                    @ret = $box->$method(@args, $param);
+                } elsif(defined wantarray) {
+                    $ret = $box->$method(@args, $param);
+                } else {
+                    $box->$method(@args, $param);
+                }
+                last if !$box->Error or !$is_rep or !@rep;
+                ++$$is_rep;
+            }
+            return wantarray ? @ret : $ret;
+        };
+    }
+}
+
+=pod
+
+=head3 B<Error>, B<ErrorStr>
+
+Return error code or description (see <MR::Tarantool::Box|MR::Tarantool::Box/Error>).
+
+=cut
+
+sub Error {
+    my ($class, @args) = @_;
+    $class->instance->{_last_box}->Error(@args);
+}
+
+sub ErrorStr {
+    my ($class, @args) = @_;
+    $class->instance->{_last_box}->ErrorStr(@args);
+}
+
+=pod
+
+=head1 LICENCE AND COPYRIGHT
+
+This is free software; you can redistribute it and/or modify it under the same terms as the Perl 5 programming language system itself.
+
+=head1 SEE ALSO
+
+L<http://tarantool.org>
+
+L<MR::Tarantool::Box>
+
+=cut
+
+
+1;
diff --git a/connector/perl/t/00require.t b/connector/perl/t/00require.t
new file mode 100644
index 0000000000000000000000000000000000000000..0b8fe3288e8a524e025b6dae1a4f1c47be343e3a
--- /dev/null
+++ b/connector/perl/t/00require.t
@@ -0,0 +1,23 @@
+#!/usr/bin/perl 
+use warnings;
+use strict;
+
+use Test::More tests => 16; 
+
+require_ok 'MR::IProto'; 
+require_ok 'MR::IProto::Cluster'; 
+require_ok 'MR::IProto::Cluster::Server'; 
+require_ok 'MR::IProto::Connection'; 
+require_ok 'MR::IProto::Connection::Async'; 
+require_ok 'MR::IProto::Connection::Sync'; 
+require_ok 'MR::IProto::Error'; 
+require_ok 'MR::IProto::Message'; 
+require_ok 'MR::IProto::NoResponse'; 
+require_ok 'MR::IProto::Request'; 
+require_ok 'MR::IProto::Response'; 
+require_ok 'MR::IProto::Role::Debuggable'; 
+require_ok 'MR::IProto::Server'; 
+require_ok 'MR::IProto::Server::Connection'; 
+require_ok 'MR::Tarantool::Box'; 
+require_ok 'MR::Tarantool::Box::Singleton'; 
+
diff --git a/connector/perl/t/TBox.pm b/connector/perl/t/TBox.pm
deleted file mode 100644
index 6bd7cdfcd4953afc4fbec2b52e743216761a72f5..0000000000000000000000000000000000000000
--- a/connector/perl/t/TBox.pm
+++ /dev/null
@@ -1,15 +0,0 @@
-use FindBin qw($Bin);
-use lib "$Bin/../client/perl/lib";
-
-use MR::SilverBox;
-
-$main::box = MR::SilverBox->new({ servers => $ARGV[1] || q/localhost:15013/,
-                                  namespaces => [ {
-                                      indexes => [ {
-                                          index_name   => 'primary_id',
-                                          keys         => [0],
-                                      } ],
-                                      namespace     => 1,
-                                      format        => 'l& SSLL',
-                                      default_index => 'primary_id',
-                                  } ], });
diff --git a/connector/perl/t/box.pl b/connector/perl/t/box.pl
index 810a919f14b0a17cf48e17fdbdc0fa088adfa960..b9e4f854def6c1200f94e21f27cd2b734d874193 100644
--- a/connector/perl/t/box.pl
+++ b/connector/perl/t/box.pl
@@ -1,3 +1,7 @@
+#!/usr/bin/perl
+
+# Tarantool/Box config below
+
 use strict;
 use warnings;
 BEGIN {
@@ -7,21 +11,22 @@ BEGIN {
 }
 use FindBin qw($Bin);
 use lib "$Bin";
-use TBox ();
 use Carp qw/confess/;
 
-use Test::More tests => 218;
+use Test::More tests => 233;
 use Test::Exception;
 
+use List::MoreUtils qw/zip/;
+
 local $SIG{__DIE__} = \&confess;
 
 our $CLASS;
-BEGIN { $CLASS = $ENV{BOXCLASS} || 'MR::SilverBox'; eval "require $CLASS" or die $@; }
+BEGIN { $CLASS = $ENV{BOXCLASS} || 'MR::Tarantool::Box'; eval "require $CLASS" or die $@; }
 
-use constant ILL_PARAM         => qr/$CLASS: Error 00000202/;
-use constant TUPLE_NOT_EXISTS  => qr/$CLASS: Error 00003102/;
-use constant TUPLE_EXISTS      => qr/$CLASS: Error 00003702/;
-use constant INDEX_VIOLATION   => qr/$CLASS: Error 00003802/;
+use constant ILL_PARAM         => qr/Error 00000202/;
+use constant TUPLE_NOT_EXISTS  => qr/Error 00003102/;
+use constant TUPLE_EXISTS      => qr/Error 00003702/;
+use constant INDEX_VIOLATION   => qr/Error 00003802/;
 
 use constant TOO_BIG_FIELD => qr/too big field/;
 
@@ -30,6 +35,7 @@ my $server = (shift || $ENV{BOX}) or die;
 
 sub cleanup ($) {
     my ($id) = @_;
+    die unless defined $id;
     ok defined $box->Delete($id), 'delete of possible existing record';
     ok $box->Delete($id) == 0, 'delete of non existing record';
 }
@@ -49,17 +55,20 @@ sub def_param  {
                  namespace     => 0,
                  format        => $format,
                  default_index => 'primary_id',
-             } ]}
+                 name          => 'main',
+             } ],
+             default_space => "main",
+         }
 }
 
 $box = $CLASS->new(def_param('l&SSLL&'));
 ok $box->isa($CLASS), 'connect';
 cleanup 13;
 
-ok $box->Insert(13, q/some_email@test.mail.ru/, 1, 2, 3, 4, '777'), 'insert';
+ok $box->Insert(13, q/some_email@test.mail.ru/, 1, 2, 3, 4, '777',{space => 'main'}), 'insert';
 is_deeply scalar $box->Select(13), [13, 'some_email@test.mail.ru', 1, 2, 3, 4, '777'], 'select/insert';
 
-ok $box->Insert(13, q/some_email@test.mail.ru/, 2, 2, 3, 4, '666'), 'replace';
+ok $box->Insert(13, q/some_email@test.mail.ru/, 2, 2, 3, 4, '666',{namespace => 'main'}), 'replace';
 is_deeply scalar $box->Select(13), [13, 'some_email@test.mail.ru', 2, 2, 3, 4, '666'], 'select/replace';
 
 ok $box->Update(13, 3 => 1) == 1, 'update of some field';
@@ -88,7 +97,7 @@ cleanup 13;
 ok $box->Insert(13, q/some_email@test.mail.ru/, 1, 2, 3, 4, '123456789'), 'insert';
 is_deeply scalar $box->Select(13), [13, 'some_email@test.mail.ru', 1, 2, 3, 4, '123456789'], 'select/insert';
 
-throws_ok sub { $box->UpdateMulti(13, [6 => splice => [-10]]) }, qr/Illegal parametrs/, "splice/bad_params_1";
+throws_ok sub { $box->UpdateMulti(13, [6 => splice => [-10]]) }, ILL_PARAM, "splice/bad_params_1";
 
 ok $box->UpdateMulti(13, [6 => splice => [100]]), "splice/big_offset";
 is_deeply scalar $box->Select(13), [13, 'some_email@test.mail.ru', 1, 2, 3, 4, '123456789'], 'select';
@@ -409,8 +418,8 @@ sub def_param1 {
              } ]}
 }
 
-$box = MR::SilverBox->new(def_param1);
-ok $box->isa('MR::SilverBox'), 'connect';
+$box = $CLASS->new(def_param1);
+ok $box->isa($CLASS), 'connect';
 
 my @tuple1 = (13, 'mail.ru', 123);
 cleanup $tuple1[0];
@@ -435,7 +444,7 @@ is_deeply [$box->Select([[$tuple2[1], $tuple2[2]]], { use_index => 'secondary_co
 sub def_param_bad {
     my $format = 'l&&';
     return { servers => $server,
-             namespaces => [ {
+             spaces => [ {
                  indexes => [ {
                      index_name   => 'primary_num1',
                      keys         => [0],
@@ -452,12 +461,12 @@ sub def_param_bad {
              } ]}
 }
 
-$box = MR::SilverBox->new(def_param_bad);
-ok $box->isa('MR::SilverBox'), 'connect';
+$box = $CLASS->new(def_param_bad);
+ok $box->isa($CLASS), 'connect';
 
 my @tuple_bad = (13, 'mail.ru', '123');
 cleanup $tuple_bad[0];
-throws_ok sub { $box->Insert(@tuple_bad) }, qr/Illegal parametrs/, "index_constains/bad_field_type";
+throws_ok sub { $box->Insert(@tuple_bad) }, ILL_PARAM, "index_constains/bad_field_type";
 
 
 ## Check unique tree index
@@ -466,9 +475,9 @@ sub def_param_unique {
     return { servers => $server,
              namespaces => [ {
                  indexes => [ {
-		     index_name   => 'id',
-		     keys         => [0],
-		 }, {
+                     index_name   => 'id',
+                     keys         => [0],
+                 }, {
                      index_name   => 'email',
                      keys         => [1],
                  }, {
@@ -478,17 +487,17 @@ sub def_param_unique {
                      index_name   => 'lastname',
                      keys         => [3],
                  } , {
-		     index_name   => 'fullname',
-		     keys         => [2, 3]
-		 } ],
-                 namespace     => 27,
+                     index_name   => 'fullname',
+                     keys         => [2, 3]
+                 } ],
+                 space     => 27,
                  format        => $format,
                  default_index => 'id',
              } ]}
 }
 
-$box = MR::SilverBox->new(def_param_unique);
-ok $box->isa('MR::SilverBox'), 'connect';
+$box = $CLASS->new(def_param_unique);
+ok $box->isa($CLASS), 'connect';
 
 my $tuples = [ [1, 'rtokarev@corp.mail.ru', 'Roman', 'Tokarev'],
 	       [2, 'vostrikov@corp.mail.ru', 'Yuri', 'Vostrikov'],
@@ -502,7 +511,7 @@ foreach my $tuple (@$tuples) {
 
 foreach my $tuple (@$tuples) {
 	if ($tuple == $tuples->[-1] || $tuple == $tuples->[-2]) {
-		throws_ok sub { $box->Insert(@$tuple) }, qr/Index violation/, "unique_tree_index/insert \'$tuple->[0]\'";
+		throws_ok sub { $box->Insert(@$tuple) }, INDEX_VIOLATION, "unique_tree_index/insert \'$tuple->[0]\'";
 	} else {
 		ok $box->Insert(@$tuple), "unique_tree_index/insert \'$tuple->[0]\'";
 	}
@@ -513,6 +522,54 @@ foreach my $r (@res) {
 	ok sub { return $r != $tuples->[-1] && $r != $tuples->[-2] };
 }
 
+my $flds;
+BEGIN{ $flds = [qw/ f1 f2 f3 f4 /] }
+    {
+        package TestBox;
+        use MR::Tarantool::Box::Singleton;
+        use base 'MR::Tarantool::Box::Singleton';
+
+        BEGIN {
+            __PACKAGE__->mkfields(@$flds);
+        }
+
+        sub SERVER   { $server }
+        sub REPLICAS { '' }
+
+        sub SPACES   {[{
+            space         => 27,
+            indexes       => [ {
+                index_name   => 'primary_id',
+                keys         => [TUPLE_f1],
+            } ],
+            format        => 'l&&&',
+            default_index => 'primary_id',
+        }]}
+
+    }
+
+$box = 'TestBox';
+#$box = $CLASS->new(def_param_flds);
+#ok $box->isa($CLASS), 'connect';
+
+do {
+    my $tuples = [ @$tuples[0..2] ];
+    foreach my $tuple (@$tuples) {
+        cleanup $tuple->[0];
+    }
+
+    foreach my $tuple (@$tuples) {
+        is_deeply [$box->Insert(@$tuple, {want_inserted_tuple => 1})], [{zip @$flds, @$tuple}], "flds/insert \'$tuple->[0]\'";
+    }
+
+    is_deeply [$box->Select([[$tuples->[0]->[0]]])], [{zip @$flds, @{$tuples->[0]}}], 'select by primary_num1 index';
+    is_deeply [$box->UpdateMulti($tuples->[0]->[0],[ $flds->[3] => set => $tuples->[0]->[3] ],{want_updated_tuple => 1})], [{zip @$flds, @{$tuples->[0]}}], 'update1';
+    ok         $box->UpdateMulti($tuples->[0]->[0],[ $flds->[3] => set => $tuples->[0]->[3] ]), 'update2';
+    is_deeply [$box->UpdateMulti($tuples->[0]->[0],[ 3          => set => $tuples->[0]->[3] ],{want_updated_tuple => 1})], [{zip @$flds, @{$tuples->[0]}}], 'update3';
+    ok         $box->UpdateMulti($tuples->[0]->[0],[ 3          => set => $tuples->[0]->[3] ]), 'update4';
+
+    is_deeply [$box->Delete($tuples->[0]->[0],{want_deleted_tuple => 1})], [{zip @$flds, @{$tuples->[0]}}], 'update3';
+};
 
 
 
@@ -521,19 +578,21 @@ foreach my $r (@res) {
 sub def_param_u64 {
     my $format = '&&&&';
     return { servers => $server,
-             namespaces => [ {
+             spaces => [ {
                  indexes => [ {
-		     index_name   => 'id',
-		     keys         => [0],
-		 } ],
-                 namespace     => 20,
+                     index_name   => 'id',
+                     keys         => [0],
+                 } ],
+                 space         => 20,
                  format        => $format,
                  default_index => 'id',
-             } ]}
+             } ],
+             debug => 0,
+         }
 }
 
-$box = MR::SilverBox->new(def_param_u64);
-ok $box->isa('MR::SilverBox'), 'connect';
+$box = $CLASS->new(def_param_u64);
+ok $box->isa($CLASS), 'connect';
 
 $_->[0] = pack('ll', $_->[0], 0) foreach @$tuples;
 
@@ -546,3 +605,68 @@ foreach my $tuple (@$tuples) {
 }
 
 is_deeply($tuples, [$box->Select([map $_->[0], @$tuples])]);
+
+
+__END__
+
+space[0].enabled = 1
+space[0].index[0].type = "HASH"
+space[0].index[0].unique = 1
+space[0].index[0].key_field[0].fieldno = 0
+space[0].index[0].key_field[0].type = "NUM"
+space[0].index[1].type = "HASH"
+space[0].index[1].unique = 1
+space[0].index[1].key_field[0].fieldno = 1
+space[0].index[1].key_field[0].type = "STR"
+
+space[20].enabled = 1
+space[20].index[0].type = "HASH"
+space[20].index[0].unique = 1
+space[20].index[0].key_field[0].fieldno = 0
+space[20].index[0].key_field[0].type = "NUM64"
+
+
+space[26].enabled = 1
+space[26].index[0].type = "HASH"
+space[26].index[0].unique = 1
+space[26].index[0].key_field[0].fieldno = 0
+space[26].index[0].key_field[0].type = "NUM"
+space[26].index[1].type = "TREE"
+space[26].index[1].unique = 0
+space[26].index[1].key_field[0].fieldno = 1
+space[26].index[1].key_field[0].type = "STR"
+space[26].index[2].type = "TREE"
+space[26].index[2].unique = 0
+space[26].index[2].key_field[0].fieldno = 1
+space[26].index[2].key_field[0].type = "STR"
+space[26].index[2].key_field[1].fieldno = 2
+space[26].index[2].key_field[1].type = "NUM"
+
+
+
+space[27].enabled = 1
+space[27].index[0].type = "HASH"
+space[27].index[0].unique = 1
+space[27].index[0].key_field[0].fieldno = 0
+space[27].index[0].key_field[0].type = "NUM"
+space[27].index[1].type = "HASH"
+space[27].index[1].unique = 1
+space[27].index[1].key_field[0].fieldno = 1
+space[27].index[1].key_field[0].type = "STR"
+
+space[27].index[2].type = "TREE"
+space[27].index[2].unique = 1
+space[27].index[2].key_field[0].fieldno = 2
+space[27].index[2].key_field[0].type = "STR"
+
+space[27].index[2].type = "TREE"
+space[27].index[2].unique = 1
+space[27].index[2].key_field[0].fieldno = 3
+space[27].index[2].key_field[0].type = "STR"
+
+space[27].index[3].type = "TREE"
+space[27].index[3].unique = 1
+space[27].index[3].key_field[0].fieldno = 2
+space[27].index[3].key_field[0].type = "STR"
+space[27].index[3].key_field[1].fieldno = 3
+space[27].index[3].key_field[1].type = "STR"
diff --git a/connector/perl/t/box_tree.pl b/connector/perl/t/box_tree.pl
deleted file mode 100644
index dfd567403ca310f97e06b2ac2c4d4e3686128fb9..0000000000000000000000000000000000000000
--- a/connector/perl/t/box_tree.pl
+++ /dev/null
@@ -1,52 +0,0 @@
-use strict;
-use warnings;
-use Data::Dumper;
-BEGIN {
-    sub mPOP::Config::GetValue ($) {
-        die;
-    }
-}
-use My::SilverBox ();
-
-use Test::More qw/no_plan/;
-use Test::Exception;
-
-use constant ILL_PARAM => qr/Error 00000202: Illegal parametrs/;
-
-my $box;
-my $server = q/alei7:13013/;
-
-sub cleanup ($) {
-    my ($id) = @_;
-    ok defined $box->Delete($id), 'delete of possible existing record';
-    ok $box->Delete($id) == 0, 'delete of non existing record';
-}
-
-
-$box = My::SilverBox->new({servers => $server, tuple_format_0 => q/l& SSLL/, tuple_format_1 => q/l& SSLL/});
-ok $box->isa('My::SilverBox'), 'connect';
-
-for (my $from = 100; $from < 200; ++$from) {
-	ok $box->Insert($from, "$from\@test.mail.ru", 1, 2, 3, 4), 'insert';
-}
-
-my $from = 100;
-while (1) {
-	my @ans;
-	my $num = int(rand 20) + 1;
-
-	if ($from != 200) {
-		ok @ans = $box->Select($from, {next_rows => $num}), 'select';
-	} else {
-		ok not (defined $box->Select($from, {next_rows => $num})), 'select';
-	}
-
-	last if scalar @ans == 0;
-
-	my @tuples = ();
-	for (my $i = 0; $i < scalar @ans; ++$i) {
-		push @tuples, [$from, "$from\@test.mail.ru", 1, 2, 3, 4];
-		$from++;
-	}
-	is_deeply \@ans, \@tuples, 'select deeply'
-}
diff --git a/connector/php/exampleCall.php b/connector/php/exampleCall.php
deleted file mode 100644
index d02a7dda7264c23c580d4fd3a70318bc7393ba37..0000000000000000000000000000000000000000
--- a/connector/php/exampleCall.php
+++ /dev/null
@@ -1,37 +0,0 @@
-<?php
-
-define("NMSPACE", 0);
-
-if(!extension_loaded('tarantool')) {
-	dl('tarantool.so');
-}
-
-/**
- *  Tarantool::constructor
- * 
- *  @param optional string host default 127.0.0.1
- *  @param optional number port default 33013
- */
-$tnt = new Tarantool('localhost');
-
-/**
- *  Tarantool::call
- * 
- *  @param procname procedure namei
- *  @param array    procedure argumetns
- * 
- *  @return count of tuples
- */
-$tuple = array('test', 'x', 'abd', 1023);
-
-$res = $tnt->insert(NMSPACE, $tuple);
-
-$tuple = array(NMSPACE, 0, 'test');
-
-$res = $tnt->call("box.select", $tuple);
-
-var_dump($res);
-
-while(($res = $tnt->getTuple()) != false) {
-    var_dump($res);
-}
diff --git a/connector/php/exampleInsert.php b/connector/php/exampleInsert.php
deleted file mode 100644
index 2766fd65ed8261278a288819351ff2ab340b289a..0000000000000000000000000000000000000000
--- a/connector/php/exampleInsert.php
+++ /dev/null
@@ -1,66 +0,0 @@
-<?php
-if(!extension_loaded('tarantool')) {
-	dl('tarantool.so');
-}
-
-define("NMSPACE", 0);
-
-$tnt = new Tarantool('tfn24');
-$res=0;
-
-/**
- *  Tarantool::insert
- * 
- *  @param namespace
- *  @param array of tuple
- * 
- *  @return result
- */
-
-
-$tuple = array(1, 'x','abd', 1023 );
-$res += $tnt->insert(NMSPACE, $tuple);
-
-$tuple = array(2, 'z','abd', 1023 );
-$res += $tnt->insert(NMSPACE,$tuple);
-
-$tuple = array(3, 'z','abc', 1025 );
-$res += $tnt->insert(NMSPACE,$tuple);
-
-$tuple = array(4, 'y','abd', 1025 );
-$res += $tnt->insert(NMSPACE,$tuple);
-
-$tuple = array(5, 'y','abc', 1025 );
-$res += $tnt->insert(NMSPACE,$tuple);
-
-$tuple = array(6, 'x','abd', 1025 );
-$res += $tnt->insert(NMSPACE,$tuple);
-
-$tuple = array(7, 'x','abc', 1025 );
-$res += $tnt->insert(NMSPACE,$tuple);
-
-$tuple = array(8, 'x','abd', 1025 );
-$res += $tnt->insert(NMSPACE,$tuple);
-
-$tuple = array(9, 'x','abc', 1024 );
-$res += $tnt->insert(NMSPACE,$tuple);
-
-$tuple = array(9, 'x','abc', 1024 );
-$res += $tnt->insert(NMSPACE,$tuple);
-echo "inserted $res tuples\n";
-
-$tuple = array('9', 66,65, 1024 );
-$res = $tnt->insert(NMSPACE,$tuple);
-if (!$res)
-    echo $tnt->getError();
-else    
-    echo "inserted $res tuples\n";
-    
-$tuple = array(10, 66,65, 'xdf' );
-$res = $tnt->insert(NMSPACE,$tuple);
-
-if (!$res)
-    echo $tnt->getError();
-else    
-    echo "inserted $res tuples\n";
-    
\ No newline at end of file
diff --git a/connector/php/exampleSelect.php b/connector/php/exampleSelect.php
deleted file mode 100644
index f3b3c1c58f748442826052f54b4e517b2519bfc2..0000000000000000000000000000000000000000
--- a/connector/php/exampleSelect.php
+++ /dev/null
@@ -1,100 +0,0 @@
-<?php
-
-define("NMSPACE", 0);
-define("PRIMARYINDEX", 0);
-define("INDEX_1", 1);
-define("INDEX_2", 2);
-define("INDEX_3", 3);
-
-
-if(!extension_loaded('tarantool')) {
-	dl('tarantool.so');
-}
-
-$res = true;
-
-/**
- *  Tarantool::constructor
- * 
- *  @param optional string host default 127.0.0.1
- *  @param optional number port default 33013
- */
-$tnt = new Tarantool('tfn24');
-
-echo 'single tuple select by primary key (key=1)', PHP_EOL;
-
-/**
- *  Tarantool::select
- * 
- *  @param number namespace
- *  @param number index No 
- *  @param mixed key 
- *  @param optional limit default all
- *  @param optional offset default 0
- * 
- *  @return count of tuples
- */
-$res = $tnt->select(NMSPACE,PRIMARYINDEX,1); // namespace,index, key
-var_dump($res);
-$res = $tnt->getTuple();
-var_dump($res);
-
-
-echo "some tuple select by fielNo[1] = 'x'\n";
-$res = $tnt->select(NMSPACE,INDEX_1,'x'); // namespace,index, key
-var_dump($res);
-
-while( ($res = $tnt->getTuple()) != false){
-    var_dump($res);
-}
-
-echo "some tuple select by index = 2  (fielNo[1] = 'x' and fielNo[2] = 'abc')\n";
-$res = $tnt->select(NMSPACE,INDEX_2,array('x', 'abc')); // namespace,index, key
-var_dump($res);
-
-while( ($res = $tnt->getTuple()) != false){
-    var_dump($res);
-}
-
-echo "some tuple select by index = 3  (fielNo[3] = 1025)\n";
-$res = $tnt->select(NMSPACE,INDEX_3,1025); // namespace,index, key
-var_dump($res);
-
-while( ($res = $tnt->getTuple()) != false){
-    var_dump($res);
-}
-
-echo "some tuple select by index = 3  (fielNo[3] = 1025) LIMIT=3\n";
-$res = $tnt->select(NMSPACE,INDEX_3,1025,3); // namespace,index, key, limit
-var_dump($res);
-
-while( ($res = $tnt->getTuple()) != false){
-    var_dump($res);
-}
-
-echo "some tuple select by index = 3  (fielNo[3] = 1025) NEXT 3 (offset=3, limit=3)\n";
-$res = $tnt->select(NMSPACE,INDEX_3,1025,3,3); // namespace,index, key, limit, offset
-var_dump($res);
-
-while( ($res = $tnt->getTuple()) != false){
-    var_dump($res);
-}
-
-// multi select
-/// SELECT * FROM t0 WHERE k0 IN (1,2) 
-$count = $tnt->mselect(NMSPACE,PRIMARYINDEX, array(1,2)); // ns, idx , keys, [limit, offset]  
-print "count=$count\n";
-while ( false != ($res = $tnt->getTuple())) {    
-    var_dump($res);  
-}
-
-/// SELECT * FROM t0 WHERE k1 IN ('x','z') 
-$count = $tnt->mselect(NMSPACE,INDEX_1, array('x','z')); // ns, idx , keys, [limit, offset]
-print "count=$count\n";
-while ( false != ($res = $tnt->getTuple())) {    
-    var_dump($res);  
-}
-
-
-
-
diff --git a/connector/php/exampleUpdate.php b/connector/php/exampleUpdate.php
deleted file mode 100644
index 0d07f62091f8e44f63f318473142ff4751038163..0000000000000000000000000000000000000000
--- a/connector/php/exampleUpdate.php
+++ /dev/null
@@ -1,98 +0,0 @@
-<?php
-
-define("NMSPACE", 0);
-define("PRIMARYINDEX", 0);
-
-define("FIELD_1", 1);
-define("FIELD_2", 2);
-define("FIELD_3", 3);
-
-if(!extension_loaded('tarantool')) {
-	dl('tarantool.so');
-}
-
-$res = true;
-
-// constructor(host,port);
-$tnt = new Tarantool('tfn24');
-
-$tnt->select(NMSPACE,PRIMARYINDEX,1);
-$tuple = $tnt->getTuple();
-var_dump($tuple);
-
-/**
- *  Tarantool::inc
- * 
- *  @param namespace
- *  @param primary key
- *  @param field No for increment
- *  @param optional incremental data default +1
- * 
- *  @return bool result
- */
-// increment
-$tnt->inc(NMSPACE,1,FIELD_3);
-
-$tnt->select(NMSPACE,PRIMARYINDEX,1);
-$tuple = $tnt->getTuple();
-var_dump($tuple);
-
-
-// duble increment (+2)
-$tnt->inc(NMSPACE,1,FIELD_3,2);
-
-$tnt->select(NMSPACE,PRIMARYINDEX,1);
-$tuple = $tnt->getTuple();
-var_dump($tuple);
-
-
-// decrement
-$res = $tnt->inc(NMSPACE,1,FIELD_3,-1);
-echo "increment tuple res=$res\n";
-
-$tnt->select(NMSPACE,PRIMARYINDEX,1);
-$tuple = $tnt->getTuple();
-var_dump($tuple);
-
-// error, type inxex must be NUM 
-$res = $tnt->inc(NMSPACE,1,FIELD_2);
-echo "increment tuple res=$res\n";
-if (!$res)
-    echo $tnt->getError();
-$tnt->select(NMSPACE,PRIMARYINDEX,1);
-$tuple = $tnt->getTuple();
-var_dump($tuple);
-
-/**
- *  Tarantool::update
- * 
- *  @param namespace
- *  @param primary key
- *  @param array of new data:
- *          array( fieldNo => newValue, ... )
- * 
- *  @return bool result
- */
-$res = $tnt->update(NMSPACE,1,array(2=>'y'));
-echo "update tuple res=$res\n";
-
-$tnt->select(NMSPACE,0,1);
-$tuple = $tnt->getTuple();
-var_dump($tuple);
-
-
-$res = $tnt->update(NMSPACE,1,array(1 => 'z', 2=>'abc'));
-echo "update tuple res=$res\n";
-
-$tnt->select(NMSPACE,0,1);
-$tuple = $tnt->getTuple();
-var_dump($tuple);
-
-
-// Error  type inxex 0 must be NUM 
-$res = $tnt->update(NMSPACE,1,array(0 => 'z', 2=>'abc'));
-echo "update tuple res=$res\n";
-if (!$res)
-    echo $tnt->getError();
-
-
diff --git a/connector/php/tarantool.c b/connector/php/tarantool.c
index b9b917c95975ec752a0a3d0420288d2610478fca..b783ed37344582eb42daf86e0e2558cfdea99038 100644
--- a/connector/php/tarantool.c
+++ b/connector/php/tarantool.c
@@ -16,197 +16,156 @@
   | Copyright (c) 2011                                                   |
   +----------------------------------------------------------------------+
 */
-
-/* $Id: header 252479 2008-02-07 19:39:50Z iliaa $ */
-
 #ifdef HAVE_CONFIG_H
 #include "config.h"
 #endif
 
+#include <stdint.h>
+#include <stdbool.h>
 #include <netinet/in.h>
 #include <netdb.h>
+#include <inttypes.h>
 
-#include "php.h"
-#include "php_ini.h"
-#include "ext/standard/info.h"
-#include "zend_exceptions.h"
+#include <php.h>
+#include <php_ini.h>
+#include <ext/standard/info.h>
+#include <zend_exceptions.h>
 
 #include "tarantool.h"
 
-/* If you declare any globals in php_tarantool.h uncomment this:
-ZEND_DECLARE_MODULE_GLOBALS(tarantool)
-*/
-
-/* True global resources - no need for thread safety here */
-static int le_tarantool;
-
-zend_class_entry *tarantool_class_entry;
-
-typedef  struct {
-		uint32_t type;
-		uint32_t len;
-		uint32_t request_id;
-} Header;
-
-typedef struct _tarantool_object {
-	zend_object  zo;
-	char * host;		//	tarantool host
-	int port;			//  tarantool port
-	int admin_port;		//  tarantool admin port
-	int bodyLen;		// body len from tuples header
-	int countTuples;	// count tuples
-	int readedTuples;	//
-	int readed;			// readed byte
-	uint32_t errorcode;		// error code
-	php_stream * stream;
-	php_stream * admin_stream;
-} tarantool_object;
-
-#define HEADER_SIZE sizeof(Header)
-
-typedef union {
-	u_char b;
-	uint32_t  i;
-} b2i;
-
-typedef	struct {
-	uint32_t count;
-	u_char data[];
-} Tuple;
-
-typedef	struct {
-	uint32_t fieldNo;
-	int8_t code;
-	u_char arg[];
-} Operation;
-
-
-// sizeof(spaceNo) + sizeof(flag) + sizeof(tuple.count)
-#define INSERT_REQUEST_SIZE 12
-typedef	struct {
-	uint32_t spaceNo;
-	uint32_t flag;
-	Tuple tuple;
-} InsertRequest;
-
-typedef	struct {
-	uint32_t spaceNo;
-	Tuple tuple;
-} DeleteRequest;
-
-
-// sizeof(count) + sizeof(operation.fieldNo) + sizeof(operation.code) +1 // 10
-#define UPDATE_REQUEST_SIZE 10
-typedef	struct {
-	uint32_t count;
-	Operation operation;
-} UpdateRequest;
-
-
 
-#define SELECT_REQUEST_SIZE 20
-typedef	struct {
-	uint32_t spaceNo;
-	uint32_t indexNo;
-	uint32_t offset;
-	uint32_t limit;
-	uint32_t count;
-	char	 tuples[];
-} SelectRequest;
-
-typedef	struct {
-	uint32_t len;
-	Tuple tuple;
-} FTuple;
-
-
-typedef	struct {
-	uint32_t code;			// request error code
-	uint32_t tuples_count;	// count of tuples
-} SelectResponseBody;
-
-
-#define SELECT_RESPONSE_SIZE sizeof(SelectResponseTuple)
-typedef	struct {
-	uint32_t size;			// tuple size in bytes
-	uint32_t count;			// count elements in tuple
-} SelectResponseTuple;
-
-typedef	struct {
-	uint32_t code;
-	uint32_t count;
-	char data[];
-} Response;
-
-//typedef	struct {
-//	uint32_t code;
-//	uint32_t count;
-//	char data[];
-//} UpdateResponse;
-
-
-static void printLine( u_char *p );
-static void printLine3( u_char *p );
-
-static void
-leb128_write(char * buf, unsigned long value);
-
-static int
-leb128_size(unsigned long value);
-
-static int
-leb128_read(char * buf, int size, unsigned long * value);
-
-/* {{{ tarantool_functions[]
- *
- * Every user visible function must have an entry in tarantool_functions[].
- */
-const zend_function_entry tarantool_functions[] = {
-	 PHP_ME(tarantool_class, __construct, NULL, ZEND_ACC_PUBLIC)
-	{NULL, NULL, NULL}
+/*============================================================================*
+ * Tarantool extension structures defintion
+ *============================================================================*/
+
+/* I/O buffer */
+struct io_buf {
+	/* buffer's size */
+	size_t size;
+    /* buffer's capacity */
+	size_t capacity;
+	/* read position in the I/O buffer */
+	size_t readed;
+	/* buffer value */
+	uint8_t *value;
 };
 
-zend_function_entry tarantool_class_functions[] = {
-	 PHP_ME(tarantool_class, __construct,	NULL, ZEND_ACC_PUBLIC)
-
-	 PHP_ME(tarantool_class, insert,		NULL, ZEND_ACC_PUBLIC)
-	 PHP_ME(tarantool_class, select,		NULL, ZEND_ACC_PUBLIC)
-	 PHP_ME(tarantool_class, mselect,		NULL, ZEND_ACC_PUBLIC)
-	 PHP_ME(tarantool_class, call,			NULL, ZEND_ACC_PUBLIC)
-	 PHP_ME(tarantool_class, getTuple,		NULL, ZEND_ACC_PUBLIC)
-	 PHP_ME(tarantool_class, delete,		NULL, ZEND_ACC_PUBLIC)
-	 PHP_ME(tarantool_class, update,		NULL, ZEND_ACC_PUBLIC)
-	 PHP_ME(tarantool_class, inc,		NULL, ZEND_ACC_PUBLIC)
-	 PHP_ME(tarantool_class, getError,		NULL, ZEND_ACC_PUBLIC)
-	 PHP_ME(tarantool_class, getInfo,		NULL, ZEND_ACC_PUBLIC)
-	 PHP_ME(tarantool_class, getStat,		NULL, ZEND_ACC_PUBLIC)
-	 PHP_ME(tarantool_class, getConf,		NULL, ZEND_ACC_PUBLIC)
+/* tarantool object */
+typedef struct tarantool_object {
+	zend_object zo;
+	/* host name */
+	char *host;
+	/* tarantool primary port */
+	int port;
+	/* tarantool admin port */
+	int admin_port;
+	/* tarantool primary connection */
+	php_stream *stream;
+	/* tarantool admin connecion */
+	php_stream *admin_stream;
+	/* I/O buffer */
+	struct io_buf *io_buf;
+	/* additional buffer for splice args */
+	struct io_buf *splice_field;
+} tarantool_object;
+
+/* iproto header */
+struct iproto_header {
+	/* command code */
+	uint32_t type;
+	/* command length */
+	uint32_t length;
+	/* request id */
+	uint32_t request_id;
+} __attribute__((packed));
+
+/* tarantool select command request */
+struct tnt_select_request {
+	/* space number */
+	int32_t space_no;
+	/* index number */
+	int32_t index_no;
+	/* select offset from begining */
+	int32_t offset;
+	/* maximail number tuples in responce */
+	int32_t limit;
+} __attribute__((packed));
+
+/* tarantool insert command request */
+struct tnt_insert_request {
+	/* space number */
+	int32_t space_no;
+	/* flags */
+	int32_t flags;
+} __attribute__((packed));
+
+/* tarantool update fields command request */
+struct tnt_update_fields_request {
+	/* space number */
+	int32_t space_no;
+	/* flags */
+	int32_t flags;
+} __attribute__((packed));
+
+/* tarantool delete command request */
+struct tnt_delete_request {
+	/* space number */
+	int32_t space_no;
+	/* flags */
+	int32_t flags;
+} __attribute__((packed));
+
+/* tarantool call command request */
+struct tnt_call_request {
+	/* flags */
+	int32_t flags;
+} __attribute__((packed));
+
+/* tarantool command response */
+struct tnt_response {
+	/* return code */
+	int32_t return_code;
+	union {
+		/* count */
+		int32_t count;
+		/* error message */
+		char return_msg[0];
+	};
+} __attribute__((packed));
+
+
+/*============================================================================*
+ * Global variables definition
+ *============================================================================*/
+
+
+/*----------------------------------------------------------------------------*
+ * Tarantool module variables
+ *----------------------------------------------------------------------------*/
+
+/* module functions list */
+zend_function_entry tarantool_module_functions[] = {
+	PHP_ME(tarantool_class, __construct, NULL, ZEND_ACC_PUBLIC)
 	{NULL, NULL, NULL}
 };
 
-
-/* }}} */
-
-/* {{{ tarantool_module_entry
- */
+/* tarantool module struct */
 zend_module_entry tarantool_module_entry = {
 #if ZEND_MODULE_API_NO >= 20010901
 	STANDARD_MODULE_HEADER,
 #endif
 	"tarantool",
-	tarantool_functions,
+	tarantool_module_functions,
 	PHP_MINIT(tarantool),
 	PHP_MSHUTDOWN(tarantool),
 	NULL,
 	NULL,
 	PHP_MINFO(tarantool),
 #if ZEND_MODULE_API_NO >= 20010901
-	"0.1", /* Replace with version number for your extension */
+	"1.0",
 #endif
 	STANDARD_MODULE_PROPERTIES
 };
-/* }}} */
-
 
 
 #ifdef COMPILE_DL_TARANTOOL
@@ -214,1875 +173,1889 @@ ZEND_GET_MODULE(tarantool)
 #endif
 
 
-static int php_tnt_connect( tarantool_object *ctx TSRMLS_DC) {
-			// open stream
-		struct timeval tv;
-		tv.tv_sec = TARANTOOL_TIMEOUT;
-		tv.tv_usec = 0;
+/*----------------------------------------------------------------------------*
+ * Tarantool class variables
+ *----------------------------------------------------------------------------*/
 
-		char * errstr = NULL, *hostname = NULL;
-		int   err = 0, hostname_len;
+/* tarantool class methods */
+const zend_function_entry tarantool_class_methods[] = {
+	PHP_ME(tarantool_class, __construct, NULL, ZEND_ACC_PUBLIC)
+	PHP_ME(tarantool_class, select, NULL, ZEND_ACC_PUBLIC)
+	PHP_ME(tarantool_class, insert, NULL, ZEND_ACC_PUBLIC)
+	PHP_ME(tarantool_class, update_fields, NULL, ZEND_ACC_PUBLIC)
+	PHP_ME(tarantool_class, delete, NULL, ZEND_ACC_PUBLIC)
+	PHP_ME(tarantool_class, call, NULL, ZEND_ACC_PUBLIC)
+	PHP_ME(tarantool_class, admin, NULL, ZEND_ACC_PUBLIC)
+	{NULL, NULL, NULL}
+};
 
-		if (ctx->port) {
-			hostname_len = spprintf(&hostname, 0, "tcp://%s:%d", ctx->host, ctx->port);
-		} else {
-			php_printf("port undefined\n");
-		}
+/* tarantool class */
+zend_class_entry *tarantool_class_ptr;
 
 
-		ctx->stream = php_stream_xport_create( hostname, hostname_len,
-											   ENFORCE_SAFE_MODE | REPORT_ERRORS,
-											   STREAM_XPORT_CLIENT | STREAM_XPORT_CONNECT,
-											   NULL, &tv, NULL, &errstr, &err);
+/*============================================================================*
+ * local functions declaration
+ *============================================================================*/
 
-		efree(hostname);
 
-		if (err && errstr) {
-			php_printf("stream error: %s\n", errstr);
-			ctx->stream = NULL;
-			efree(errstr);
-		}
+/*----------------------------------------------------------------------------*
+ * I/O buffer interface
+ *----------------------------------------------------------------------------*/
 
-		if (!ctx->stream) {
-			return 1;
-		}
+/* create I/O buffer instance */
+static struct io_buf *
+io_buf_create();
 
-		return 0;
-}
+/* destroy I/O buffer */
+static void
+io_buf_destroy(struct io_buf *buf);
 
-static int php_tnt_admin_connect( tarantool_object *ctx TSRMLS_DC) {
-			// open stream
-		struct timeval tv;
-		tv.tv_sec = TARANTOOL_TIMEOUT;
-		tv.tv_usec = 0;
+/* reserv I/O buffer space */
+inline static bool
+io_buf_reserve(struct io_buf *buf, size_t n);
 
-		char * errstr = NULL, *hostname = NULL;
-		int   err = 0, hostname_len;
+/* resize I/O buffer */
+inline static bool
+io_buf_resize(struct io_buf *buf, size_t n);
 
-		if (ctx->admin_port) {
-			hostname_len = spprintf(&hostname, 0, "tcp://%s:%d", ctx->host, ctx->admin_port);
-		} else {
-			php_printf("admin port undefined\n");
-		}
+/* calculate next capacity for I/O buffer */
+inline static size_t
+io_buf_next_capacity(size_t n);
 
-		ctx->admin_stream = php_stream_xport_create( hostname, hostname_len,
-											   ENFORCE_SAFE_MODE | REPORT_ERRORS,
-											   STREAM_XPORT_CLIENT | STREAM_XPORT_CONNECT,
-											   NULL, &tv, NULL, &errstr, &err);
+/* clean I/O buffer */
+static void
+io_buf_clean(struct io_buf *buf);
 
-		efree(hostname);
+/* read struct from buffer */
+static bool
+io_buf_read_struct(struct io_buf *buf, void **ptr, size_t n);
 
-		if (err && errstr) {
-			php_printf("stream error: %s\n", errstr);
-			ctx->admin_stream = NULL;
-			efree(errstr);
-		}
+/* read 32-bit integer from buffer */
+static bool
+io_buf_read_int32(struct io_buf *buf, int32_t *val);
 
-		if (!ctx->admin_stream) {
-			return 1;
-		}
+/* read 64-bit integer from buffer */
+static bool
+io_buf_read_int64(struct io_buf *buf, int64_t *val);
 
-		return 0;
-}
+/* read var integer from buffer */
+static bool
+io_buf_read_varint(struct io_buf *buf, int32_t *val);
 
-/* {{{ proto tarantool::__construct( string string host=localhost, int port=PORT, int admin_port=ADMIN_PORT)
-   tarantool constructor */
-PHP_METHOD(tarantool_class, __construct)
-{
-	zval *id;
-	tarantool_object *ctx;
+/* read string from buffer */
+static bool
+io_buf_read_str(struct io_buf *buf, char **str, size_t len);
 
+/* read fied from buffer */
+static bool
+io_buf_read_field(struct io_buf *buf, zval *tuple);
 
-	zval** zdata; //??????
+/* read tuple from buffer */
+static bool
+io_buf_read_tuple(struct io_buf *buf, zval **tuple);
 
-	char * host = NULL;
-	int host_len = 0;
-	long port=0, admin_port=0;
+/*
+ * Write to I/O buffer functions
+ */
 
-	if (zend_parse_method_parameters(ZEND_NUM_ARGS() TSRMLS_CC, getThis(), "O|sll",
-			&id, tarantool_class_entry, &host, &host_len, &port, &admin_port) == FAILURE) {
-		return;
-	}
+/* write struct to I/O buffer */
+static void *
+io_buf_write_struct(struct io_buf *buf, size_t n);
 
-	ctx = (tarantool_object *)zend_object_store_get_object(id TSRMLS_CC);
-	ctx->host = NULL;
-	ctx->port = 0;
-	ctx->admin_port = 0;
-	ctx->stream = NULL;
-	ctx->bodyLen = 0;
-	ctx->errorcode = 0;
+/* write byte to I/O buffer */
+static bool
+io_buf_write_byte(struct io_buf *buf, int8_t value);
 
-	if (host_len > 0)
-		ctx->host = estrdup(host);
-	else
-		ctx->host = estrdup(TARANTOOL_DEF_HOST);
+/* write 32-bit integer to I/O buffer */
+static bool
+io_buf_write_int32(struct io_buf *buf, int32_t value);
 
-	if (port)
-		ctx->port = port;
-	else
-		ctx->port = TARANTOOL_DEF_PORT;
+/* write 64-bit integer to I/O buffer */
+static bool
+io_buf_write_int64(struct io_buf *buf, int64_t value);
 
-	if (admin_port)
-		ctx->admin_port = admin_port;
-	else
-		ctx->admin_port = TARANTOOL_ADMIN_PORT;
+/* write varint to I/O buffer */
+static bool
+io_buf_write_varint(struct io_buf *buf, int32_t value);
 
-}
-/* }}} */
+/* write string to I/O buffer */
+static bool
+io_buf_write_str(struct io_buf *buf, uint8_t *str, size_t len);
 
+/* write 32-bit integer as tuple's field to I/O buffer */
+static bool
+io_buf_write_field_int32(struct io_buf *buf, uint32_t value);
 
+/* write 64-bit integer as tuple's field to I/O buffer */
+static bool
+io_buf_write_field_int64(struct io_buf *buf, uint64_t value);
 
-/* {{{ proto int tarantool::insert(int space, int index, zval tuple);
-insert tuple
-*/
-PHP_METHOD(tarantool_class, insert )
-{
-	zval *id;
-	tarantool_object *ctx;
-	long space;
-	zval * tuple;
-	HashTable *pht;
-	HashPosition pos;
-	zval **curr;
-
-	if (zend_parse_method_parameters(ZEND_NUM_ARGS() TSRMLS_CC, getThis(), "Ola", &id,
-		tarantool_class_entry, &space, &tuple) == FAILURE) {
-		return;
-	}
+/* write string tuple's field to I/O buffer */
+static bool
+io_buf_write_field_str(struct io_buf *buf, uint8_t *val, size_t len);
 
-	ctx = (tarantool_object *)zend_object_store_get_object(id TSRMLS_CC);
+/* write tuple to I/O buffer */
+static bool
+io_buf_write_tuple_int(struct io_buf *buf, zval *tuple);
 
-	if (!ctx->stream) {
-		if (php_tnt_connect(ctx TSRMLS_CC)) {
-			zend_throw_exception(zend_exception_get_default(TSRMLS_C),
-				"the can't open remote host " ,0 TSRMLS_DC);
-			return;
-		}
-	}
+/* write tuple (string) to I/O buffer */
+static bool
+io_buf_write_tuple_str(struct io_buf *buf, zval *tuple);
+
+/* write tuple (array) to I/O buffer */
+static bool
+io_buf_write_tuple_array(struct io_buf *buf, zval *tuple);
 
-	pht = Z_ARRVAL_P(tuple);
-	int num = zend_hash_num_elements(pht);
+/* write tuple to I/O buffer */
+static bool
+io_buf_write_tuple(struct io_buf *buf, zval *tuple);
 
-	char * out_buf = emalloc(TARANTOOL_BUFSIZE);
-	bzero(out_buf, TARANTOOL_BUFSIZE);
+/* write array of tuples to I/O buffer */
+static bool
+io_buf_write_tuples_list_array(struct io_buf *buf, zval *tuples_list);
 
-	Header * header = (Header *) out_buf;
-	InsertRequest * insert = (InsertRequest *) (out_buf + HEADER_SIZE);
+/* write tuples list to I/O buffer */
+static bool
+io_buf_write_tuples_list(struct io_buf *buf, zval *tuples_list);
 
-	header->type		= TARANTOOL_INSERT;
-	header->request_id	= TARANTOOL_REQUEST_ID;
+/*
+ * I/O buffer send/recv 
+ */
 
-	insert->spaceNo = space;
-	insert->tuple.count = num; // count Tuples
+/* send administation command request */
+static bool
+io_buf_send_yaml(php_stream *stream, struct io_buf *buf);
 
-	u_char * p = (u_char *) insert->tuple.data;
+/* receive administration command response */
+static bool
+io_buf_recv_yaml(php_stream *stream, struct io_buf *buf);
 
-    for(zend_hash_internal_pointer_reset_ex(pht, &pos);
-          zend_hash_get_current_data_ex(pht, (void **) &curr, &pos) == SUCCESS;
-          zend_hash_move_forward_ex(pht, &pos)) {
+/* send request by iproto */
+static bool
+io_buf_send_iproto(php_stream *stream, int32_t type, int32_t request_id, struct io_buf *buf);
 
-		if (Z_TYPE_PP(curr) == IS_STRING)  {
-			char * strval = Z_STRVAL_PP(curr);
-			int str_len = Z_STRLEN_PP(curr);
+/* receive response by iproto */
+static bool
+io_buf_recv_iproto(php_stream *stream, struct io_buf *buf);
 
-			u_char str_shortlen = (u_char)str_len;
 
-			*(p++) = str_shortlen;
-			memcpy(p, strval, str_len);
-			p += str_len;
-		}
-		if (Z_TYPE_PP(curr) == IS_LONG)  {
-		   unsigned long val = Z_LVAL_PP(curr);
-
-//		   u_char leb_size = (u_char)leb128_size( val);
-//		   *(p++) = leb_size;
-//		   leb128_write( (char *)p, val);
-//		   p += leb_size;
-			u_char leb_size = 4;
-			*(p++) = leb_size;
-			b2i * pval = (b2i*) p;
-			pval->i = (int) val;
-			p += leb_size;
-		}
-    }
+/*----------------------------------------------------------------------------*
+ * support local functions
+ *----------------------------------------------------------------------------*/
 
-	header->len = INSERT_REQUEST_SIZE + (p-insert->tuple.data); // 12 = 3 * sizeof(int32) :ns, flag & cardinality
+/* tarantool class instance allocator */
+static zend_object_value
+alloc_tarantool_object(zend_class_entry *entry TSRMLS_DC);
 
-	// write header
-	int len = php_stream_write(ctx->stream, out_buf , sizeof(Header)); // 12
+/* free tarantool class instance */
+static void
+free_tarantool_object(tarantool_object *tnt TSRMLS_DC);
 
-	// write tuple
-	p = (u_char*)insert;
-	len = php_stream_write(ctx->stream, (char*)p , header->len );
+/* establic connection */
+static php_stream *
+establish_connection(char *host, int port);
 
-	bzero(out_buf, header->len + INSERT_REQUEST_SIZE);
+/* find long by key in the hash table */
+static bool
+hash_fing_long(HashTable *hash, char *key, long *value);
 
-	len = php_stream_read(ctx->stream, out_buf, TARANTOOL_BUFSIZE);
+/* find string by key in the hash table */
+static bool
+hash_fing_str(HashTable *hash, char *key, char **value, int *value_length);
 
-	if ( *(out_buf+HEADER_SIZE) == '\0') {
-		efree(out_buf);
-		RETURN_TRUE;
-	}
+/* find scalar by key in the hash table */
+static bool
+hash_fing_scalar(HashTable *hash, char *key, zval ***value);
+
+
+/*============================================================================*
+ * Interface definition
+ *============================================================================*/
 
 
-	b2i* bb = (b2i*)(out_buf+HEADER_SIZE);
-	ctx->errorcode = bb->i;
+/*----------------------------------------------------------------------------*
+ * Tarantool main module interface
+ *----------------------------------------------------------------------------*/
+
+/* initialize module function */
+PHP_MINIT_FUNCTION(tarantool)
+{
+	/* register constants */
+
+	/* register tarantool flags */
+	REGISTER_LONG_CONSTANT("TARANTOOL_FLAGS_RETURN_TUPLE",
+						   TARANTOOL_FLAGS_RETURN_TUPLE,
+						   CONST_CS | CONST_PERSISTENT);
+	REGISTER_LONG_CONSTANT("TARANTOOL_FLAGS_ADD",
+						   TARANTOOL_FLAGS_ADD,
+						   CONST_CS | CONST_PERSISTENT);
+	REGISTER_LONG_CONSTANT("TARANTOOL_FLAGS_REPLACE",
+						   TARANTOOL_FLAGS_REPLACE,
+						   CONST_CS | CONST_PERSISTENT);
+	REGISTER_LONG_CONSTANT("TARANTOOL_FLAGS_NOT_STORE",
+						   TARANTOOL_FLAGS_NOT_STORE,
+						   CONST_CS | CONST_PERSISTENT);
+
+	/* register tarantool update fields operations */
+	REGISTER_LONG_CONSTANT("TARANTOOL_OP_ASSIGN",
+						   TARANTOOL_OP_ASSIGN,
+						   CONST_CS | CONST_PERSISTENT);
+	REGISTER_LONG_CONSTANT("TARANTOOL_OP_ADD",
+						   TARANTOOL_OP_ADD,
+						   CONST_CS | CONST_PERSISTENT);
+	REGISTER_LONG_CONSTANT("TARANTOOL_OP_AND",
+						   TARANTOOL_OP_AND,
+						   CONST_CS | CONST_PERSISTENT);
+	REGISTER_LONG_CONSTANT("TARANTOOL_OP_XOR",
+						   TARANTOOL_OP_XOR,
+						   CONST_CS | CONST_PERSISTENT);
+	REGISTER_LONG_CONSTANT("TARANTOOL_OP_OR",
+						   TARANTOOL_OP_OR,
+						   CONST_CS | CONST_PERSISTENT);
+	REGISTER_LONG_CONSTANT("TARANTOOL_OP_SPLICE",
+						   TARANTOOL_OP_SPLICE,
+						   CONST_CS | CONST_PERSISTENT);
+
+	/* register classes */
+
+	/* register tarantool class */
+	zend_class_entry tarantool_class;
+	INIT_CLASS_ENTRY(tarantool_class, "Tarantool", tarantool_class_methods);
+	tarantool_class.create_object = alloc_tarantool_object;
+	tarantool_class_ptr = zend_register_internal_class(&tarantool_class TSRMLS_CC);
+
+	return SUCCESS;
+}
 
-	efree(out_buf);
+/* shutdown module function */
+PHP_MSHUTDOWN_FUNCTION(tarantool)
+{
+	return SUCCESS;
+}
 
-	RETURN_FALSE;
+/* show information about this module */
+PHP_MINFO_FUNCTION(tarantool)
+{
+	php_info_print_table_start();
+	php_info_print_table_header(2, "Tarantool/Box support", "enabled");
+	php_info_print_table_row(2, "Extension version", TARANTOOL_EXTENSION_VERSION);
+	php_info_print_table_end();
 }
-/* }}} */
 
 
+/*----------------------------------------------------------------------------*
+ * Tarantool class interface
+ *----------------------------------------------------------------------------*/
 
-/* {{{ proto int tarantool::select(space, index, tuple [, limit=all, offset=0]);
-	select tuple use php_streams
-*/
-PHP_METHOD(tarantool_class, select )
+PHP_METHOD(tarantool_class, __construct)
 {
-	zval *id, * tuple;
-	tarantool_object *ctx;
-	long ns		= 0;
-	long idx	= 0;
-	long limit	= 0xffffffff;
-	long offset	= 0;
-
-	if (zend_parse_method_parameters(ZEND_NUM_ARGS() TSRMLS_CC, getThis(), "Ollz|ll", &id,
-		tarantool_class_entry, &ns, &idx, &tuple, &limit, &offset) == FAILURE) {
+	/*
+	 * parse method's parameters
+	 */
+	zval *id;
+	char *host = NULL;
+	int host_len = 0;
+	long port = 0;
+	long admin_port = 0;
+	if (zend_parse_method_parameters(ZEND_NUM_ARGS() TSRMLS_CC,
+									 getThis(),
+									 "Osl|l",
+									 &id,
+									 tarantool_class_ptr,
+									 &host,
+									 &host_len,
+									 &port,
+									 &admin_port) == FAILURE) {
+		return;
+	}
+	
+	/*
+	 * validate parameters
+	 */
+
+	/* check host name */
+	if (host == NULL || host_len == 0) {
+		zend_throw_exception_ex(zend_exception_get_default(TSRMLS_C), 0 TSRMLS_DC,
+								"invalid tarantool's hostname");
 		return;
 	}
 
-	ctx = (tarantool_object *)zend_object_store_get_object(id TSRMLS_CC);
-	if (!ctx)
-		zend_throw_exception(zend_exception_get_default(TSRMLS_C),
-			"the context is null" ,0 TSRMLS_CC);
+	/* validate port value */
+	if (port <= 0 || port >= 65536) {
+		zend_throw_exception_ex(zend_exception_get_default(TSRMLS_C), 0 TSRMLS_DC,
+								"invalid primary port value: %li", port);
+		return;
+	}
 
-	if (!ctx->stream) {
-		if (php_tnt_connect(ctx TSRMLS_CC)) {
-			zend_throw_exception(zend_exception_get_default(TSRMLS_C),
-				"the can't open remote host " ,0 TSRMLS_DC);
+	/* check admin port */
+	if (admin_port) {
+		/* validate port value */
+		if (admin_port < 0 || admin_port >= 65536) {
+			zend_throw_exception_ex(zend_exception_get_default(TSRMLS_C), 0 TSRMLS_DC,
+									"invalid admin port value: %li", admin_port);
 			return;
 		}
-
 	}
 
-	ctx->bodyLen = 0;
-	ctx->countTuples = 0;
-	ctx->readedTuples = 0;
-	ctx->readed	= 0;
-	ctx->errorcode	= 0;
-
-	char * out_buf = emalloc(TARANTOOL_BUFSIZE);
-	bzero(out_buf, TARANTOOL_BUFSIZE);
+	/* initialzie object structure */
+	tarantool_object *object = (tarantool_object *) zend_object_store_get_object(
+		id TSRMLS_CC);
+	object->host = estrdup(host);
+	object->port = port;
+	object->admin_port = admin_port;
+	object->stream = NULL;
+	object->admin_stream = NULL;
+	object->io_buf = io_buf_create();
+	if (!object->io_buf) {
+		return;
+	}
+	object->splice_field = io_buf_create();
+	if (!object->splice_field) {
+		return;
+	}
 
-	Header * header = (Header *) out_buf;
-	SelectRequest * select = (SelectRequest *) out_buf + HEADER_SIZE;
+	return;
+}
 
-	header->type		= TARANTOOL_SELECT;
-	header->request_id	= TARANTOOL_REQUEST_ID;
+PHP_METHOD(tarantool_class, select)
+{
+	/* 
+	 * parse methods parameters 
+	 */
+	zval *id;
+	long space_no = 0;
+	long index_no = 0;
+	zval *keys_list = NULL;
+	long limit = -1;
+	long offset = 0;
+	if (zend_parse_method_parameters(ZEND_NUM_ARGS() TSRMLS_CC,
+									 getThis(),
+									 "Ollz|ll",
+									 &id,
+									 tarantool_class_ptr,
+									 &space_no,
+									 &index_no,
+									 &keys_list,
+									 &limit,
+									 &offset) == FAILURE) {
+		return;
+	}
 
-	select->spaceNo = ns;
-	select->indexNo		= idx;
-	select->limit		= limit;
-	select->offset		= offset;
-	select->count		= 1;			// âðåìåííî îòëàæèâàåì - îäèí êîðòåæ
+	tarantool_object *tnt = (tarantool_object *) zend_object_store_get_object(
+		id TSRMLS_CC);
 
-	u_char * p = (u_char *)select->tuples ;
+	/* check connection */
+	if (!tnt->stream) {
+		/* establis connection */
+		tnt->stream = establish_connection(tnt->host, tnt->port);
+		if (!tnt->stream)
+			return;
+	}
 
-	int * count_elements = (int *)p;
+	/*
+	 * send request
+	 */
 
-	switch (Z_TYPE_P(tuple)) {
-		case IS_STRING: {
-			*count_elements  = 1;	//!!!! <------- êîë-âî ýëåìåíòîâ â  êîðòåæå
-			p += sizeof(uint32_t);
+	/* clean-up buffer */
+	io_buf_clean(tnt->io_buf);
 
-				char * strval = Z_STRVAL_P(tuple);
-				int str_len = Z_STRLEN_P(tuple);
-				u_char str_shortlen = (u_char)str_len;
+	/* fill select command */
+	/* fill command header */
+	struct tnt_select_request *request = (struct tnt_select_request *) io_buf_write_struct(
+		tnt->io_buf, sizeof(struct tnt_select_request));
+	if (request == NULL)
+		return;
+	request->space_no = space_no;
+	request->index_no = index_no;
+	request->offset = offset;
+	request->limit = limit;
+	/* fill keys */
+	if (!io_buf_write_tuples_list(tnt->io_buf, keys_list))
+		return;
 
-				*(p++) = str_shortlen;
-				memcpy(p, strval, str_len);
-				p += str_len;
+	/* send iproto request */
+	if (!io_buf_send_iproto(tnt->stream, TARANTOOL_COMMAND_SELECT, 0, tnt->io_buf))
+	  return;
 
-//				printf("tuple: len=%d [%s]\n", str_len, strval );
-			}
-			break;
+	/*
+	 * receive response
+	 */
 
-		case IS_LONG: {
-			*count_elements  = 1;	//!!!! <------- êîë-âî ýëåìåíòîâ â  êîðòåæå
-			p += sizeof(uint32_t);
+	/* clean-up buffer */
+	io_buf_clean(tnt->io_buf);
 
-			unsigned long val = Z_LVAL_P(tuple);
-//		    u_char leb_size = (u_char)leb128_size( val);
-//		    *(p++) = leb_size;
-//		    leb128_write( (char *)p, val);
+	/* receive */
+	if (!io_buf_recv_iproto(tnt->stream, tnt->io_buf))
+		return;
 
-			u_char leb_size = 4;
-			*(p++) = leb_size;
-			b2i * pval = (b2i*) p;
-			pval->i = (int) val;
-			p += leb_size;
+	/* read response */
+	struct tnt_response *response;
+	if (!io_buf_read_struct(tnt->io_buf,
+						  (void **) &response,
+						  sizeof(struct tnt_response))) {
+		zend_throw_exception_ex(zend_exception_get_default(TSRMLS_C), 0 TSRMLS_DC,
+								"select failed: invalid response was received");
+		return;
+	}
 
-//			printf("tuple: int %d\n", val );
-			}
-			break;
+	/* check return code */
+	if (response->return_code) {
+		/* error happen, throw exceprion */
+		zend_throw_exception_ex(zend_exception_get_default(TSRMLS_C), 0 TSRMLS_DC,
+								"select failed: %"PRIi32"(0x%08"PRIx32"): %s",
+								response->return_code,
+								response->return_code,
+								response->return_msg);
+		return;
+	}
 
+	if (array_init(return_value) != SUCCESS) {
+		zend_throw_exception_ex(zend_exception_get_default(TSRMLS_C), 0 TSRMLS_DC,
+								"select failed: create array failed");
+		return;
+	}
 
-		case IS_ARRAY: {
-			HashTable *pht;
-			HashPosition pos;
-			zval **curr;
-
-			*count_elements  = zend_hash_num_elements(Z_ARRVAL_P(tuple));
-			p += sizeof(uint32_t);
-
-			pht = Z_ARRVAL_P(tuple);
-			for(zend_hash_internal_pointer_reset_ex(pht, &pos);
-				  zend_hash_get_current_data_ex(pht, (void **)&curr, &pos) == SUCCESS;
-				  zend_hash_move_forward_ex(pht, &pos)) {
-
-				if (Z_TYPE_PP(curr) == IS_STRING)  {
-					char * strval = Z_STRVAL_PP(curr);
-					int str_len = Z_STRLEN_PP(curr);
-		//			printf("tuple: len=%d [%s]", str_len, strval );
-					u_char str_shortlen = (u_char)str_len;
-
-					*(p++) = str_shortlen;
-					memcpy(p, strval, str_len);
-					p += str_len;
-				}
-				if (Z_TYPE_PP(curr) == IS_LONG)  {
-					unsigned long val = Z_LVAL_PP(curr);
-
-//					u_char leb_size = (u_char)leb128_size( val);
-//					*(p++) = leb_size;
-//					leb128_write( (char *)p, val);
-					u_char leb_size = 4;
-					*(p++) = leb_size;
-					b2i * pval = (b2i*) p;
-					pval->i = (int) val;
-
-					p += leb_size;
-				}
-			}
+	/* put count to result array */
+	add_assoc_long(return_value, "count", response->count);
 
-		}
-		break;
-		default :
-			zend_throw_exception(zend_exception_get_default(TSRMLS_C),
-			"tuple: unsuport tuple type" ,0 TSRMLS_CC);
-			return;
+	/* put tuple list to result array */
+	zval *tuples_list;
+	MAKE_STD_ZVAL(tuples_list);
+	if (array_init(tuples_list) == FAILURE) {
+		zend_throw_exception_ex(zend_exception_get_default(TSRMLS_C), 0 TSRMLS_DC,
+								"select failed: create array failed");
+		return;
 	}
 
-	u_char * p_end = (u_char *)select->tuples ;
-	header->len = (p - p_end)  +  SELECT_REQUEST_SIZE; //+ HEADER_SIZE ; // 12 = 3 * sizeof(int32) :ns, flag & cardinality
-
-	// write header
-	int len = php_stream_write(ctx->stream, out_buf, HEADER_SIZE); // 12
-	if (len != HEADER_SIZE) {
-			zend_throw_exception(zend_exception_get_default(TSRMLS_C),
-			"write header error" ,0 TSRMLS_CC);
-			efree(out_buf);
+	/* read tuples for responce */
+	int i;
+	for (i = 0; i < response->count; ++i) {
+		zval *tuple;
+		if (!io_buf_read_tuple(tnt->io_buf, &tuple)) {
+			zend_throw_exception_ex(zend_exception_get_default(TSRMLS_C), 0 TSRMLS_DC,
+									"select failed: invalid response was received");
 			return;
+		}
+		add_next_index_zval(tuples_list, tuple);
 	}
 
-	len = php_stream_write(ctx->stream, (void*)select , header->len );
-	if (len != header->len) {
-			zend_throw_exception(zend_exception_get_default(TSRMLS_C),
-			"write request error" ,0 TSRMLS_CC);
-			efree(out_buf);
-			return;
+	add_assoc_zval(return_value, "tuples_list", tuples_list);
+}
+
+PHP_METHOD(tarantool_class, insert)
+{
+	/* 
+	 * parse methods parameters 
+	 */
+	zval *id;
+	long space_no = 0;
+	long flags = 0;
+	zval *tuple = NULL;
+	if (zend_parse_method_parameters(ZEND_NUM_ARGS() TSRMLS_CC,
+									 getThis(),
+									 "Ola|l",
+									 &id,
+									 tarantool_class_ptr,
+									 &space_no,
+									 &tuple,
+									 &flags) == FAILURE) {
+		return;
 	}
 
-	Header responseHeader;
-	bzero(&responseHeader, HEADER_SIZE);
+	tarantool_object *tnt = (tarantool_object *) zend_object_store_get_object(
+		id TSRMLS_CC);
 
-	len = php_stream_read(ctx->stream, (void *)&responseHeader, HEADER_SIZE);
-	if (len != HEADER_SIZE) {
-			zend_throw_exception(zend_exception_get_default(TSRMLS_C),
-			"read header error" ,0 TSRMLS_CC);
-			efree(out_buf);
+	/* check connection */
+	if (!tnt->stream) {
+		/* establis connection */
+		tnt->stream = establish_connection(tnt->host, tnt->port);
+		if (!tnt->stream)
 			return;
 	}
 
-	ctx->bodyLen = responseHeader.len;
+	/*
+	 * send request
+	 */
 
-	uint32_t code;
+	/* clean-up buffer */
+	io_buf_clean(tnt->io_buf);
 
-	len = php_stream_read(ctx->stream, (void *) &code,sizeof(uint32_t));
-	ctx->readed += len;
-	if (code > 0 || len != sizeof(uint32_t)) {
-		ctx->errorcode = code;					///  need test
-		efree(out_buf);
-		RETURN_FALSE;
-	}
-//
-	uint32_t tuples_count=0;
-	len = php_stream_read(ctx->stream, (void *) &tuples_count,sizeof(uint32_t));
-	ctx->readed += len;
-	if (len != sizeof(uint32_t)) {
-			zend_throw_exception_ex(zend_exception_get_default(TSRMLS_C),
-			0 TSRMLS_CC,"read body error");
-		efree(out_buf);
+	/* fill insert command */
+	struct tnt_insert_request *request = (struct tnt_insert_request *) io_buf_write_struct(
+		tnt->io_buf, sizeof(struct tnt_insert_request));
+	if (request == NULL)
 		return;
-	}
 
-	ctx->errorcode	= code;
-	ctx->countTuples = tuples_count;
-	efree(out_buf);
-
-	RETURN_LONG(ctx->countTuples);
+	/* space number */
+	request->space_no = space_no;
+	/* flags */
+	request->flags = flags;
+	/* tuple */
+	if (!io_buf_write_tuple(tnt->io_buf, tuple))
+		return;
 
-}
-/* }}} */
+	/* send iproto request */
+	if (!io_buf_send_iproto(tnt->stream, TARANTOOL_COMMAND_INSERT, 0, tnt->io_buf))
+	  return;
 
-/* {{{ proto int tarantool::mselect(space, index, tuples [, limit=all, offset=0]);
-	select some tuples tuples = array(key1,key2,key3);
-	use only simple key
-*/
-PHP_METHOD(tarantool_class, mselect )
-{
-	zval *id, * tuple;
-	tarantool_object *ctx;
-	long ns		= 0;
-	long idx	= 0;
-	long limit	= 0xffffffff;
-	long offset	= 0;
-
-	if (zend_parse_method_parameters(ZEND_NUM_ARGS() TSRMLS_CC, getThis(), "Olla|ll", &id,
-		tarantool_class_entry, &ns, &idx, &tuple, &limit, &offset) == FAILURE) {
-		return;
-	}
+	/*
+	 * receive response
+	 */
 
-	ctx = (tarantool_object *)zend_object_store_get_object(id TSRMLS_CC);
-	if (!ctx)
-		zend_throw_exception(zend_exception_get_default(TSRMLS_C),
-			"the context is null" ,0 TSRMLS_CC);
+	/* clean-up buffer */
+	io_buf_clean(tnt->io_buf);
 
-	if (!ctx->stream) {
-		if (php_tnt_connect(ctx TSRMLS_CC)) {
-			zend_throw_exception(zend_exception_get_default(TSRMLS_C),
-				"the can't open remote host " ,0 TSRMLS_DC);
-			return;
-		}
+	/* receive */
+	if (!io_buf_recv_iproto(tnt->stream, tnt->io_buf))
+		return;
 
+	/* read response */
+	struct tnt_response *response;
+	if (!io_buf_read_struct(tnt->io_buf,
+						  (void **) &response,
+						  sizeof(struct tnt_response))) {
+		zend_throw_exception_ex(zend_exception_get_default(TSRMLS_C), 0 TSRMLS_DC,
+								"insert failed: invalid response was received");
+		return;
 	}
 
-	ctx->bodyLen = 0;
-	ctx->countTuples = 0;
-	ctx->readedTuples = 0;
-	ctx->readed	= 0;
-	ctx->errorcode	= 0;
+	/* check return code */
+	if (response->return_code) {
+		/* error happen, throw exceprion */
+		zend_throw_exception_ex(zend_exception_get_default(TSRMLS_C), 0 TSRMLS_DC,
+								"insert failed: %"PRIi32"(0x%08"PRIx32"): %s",
+								response->return_code,
+								response->return_code,
+								response->return_msg);
+		return;
+	}
+	
+	/*
+	 * fill return value
+	 */
 
-	char * out_buf = emalloc(TARANTOOL_BUFSIZE);
-	bzero(out_buf, TARANTOOL_BUFSIZE);
+	if (array_init(return_value) != SUCCESS) {
+		zend_throw_exception_ex(zend_exception_get_default(TSRMLS_C), 0 TSRMLS_DC,
+								"insert failed: create array failed");
+		return;
+	}
 
-	Header * header = (Header *) out_buf;
-	SelectRequest * select = (SelectRequest *) out_buf + HEADER_SIZE;
+	/* put count to result array */
+	add_assoc_long(return_value, "count", response->count);	
 
-	header->type		= TARANTOOL_SELECT;
-	header->request_id	= TARANTOOL_REQUEST_ID;
+	/* check "return tuple" flag */
+	if (flags & TARANTOOL_FLAGS_RETURN_TUPLE) {
+		/* ok, the responce should contain inserted tuple */
+		if (!io_buf_read_tuple(tnt->io_buf, &tuple)) {
+			zend_throw_exception_ex(zend_exception_get_default(TSRMLS_C), 0 TSRMLS_DC,
+									"insert failed: invalid response was received");
+			return;
+		}
 
-	select->spaceNo = ns;
-	select->indexNo		= idx;
-	select->limit		= limit;
-	select->offset		= offset;
+		/* put returned tuple to result array */
+		add_assoc_zval(return_value, "tuple", tuple);
+	}
+}
 
+PHP_METHOD(tarantool_class, update_fields)
+{
+	/* 
+	 * parse methods parameters 
+	 */
+	zval *id;
+	long space_no = 0;
+	long flags = 0;
+	zval *tuple = NULL;
+	zval *op_list = NULL;
+	if (zend_parse_method_parameters(ZEND_NUM_ARGS() TSRMLS_CC,
+									 getThis(),
+									 "Olza|l",
+									 &id,
+									 tarantool_class_ptr,
+									 &space_no,
+									 &tuple,
+									 &op_list,
+									 &flags) == FAILURE) {
+		return;
+	}
 
-	u_char * p = (u_char *)select->tuples ;
-	select->count = zend_hash_num_elements(Z_ARRVAL_P(tuple));
+	tarantool_object *tnt = (tarantool_object *) zend_object_store_get_object(
+		id TSRMLS_CC);
 
-	// ôîðìèðóåì òàê êàê íàäî!
+	/* check connection */
+	if (!tnt->stream) {
+		/* establis connection */
+		tnt->stream = establish_connection(tnt->host, tnt->port);
+		if (!tnt->stream)
+			return;
+	}
 
-	HashTable *pht;
-	HashPosition pos;
-	zval **curr;
+	/*
+	 * send request
+	 */
 
+	/* clean-up buffer */
+	io_buf_clean(tnt->io_buf);
 
-	pht = Z_ARRVAL_P(tuple);
-	for(zend_hash_internal_pointer_reset_ex(pht, &pos);
-		  zend_hash_get_current_data_ex(pht, (void **)&curr, &pos) == SUCCESS;
-		  zend_hash_move_forward_ex(pht, &pos)) {
+	/* fill insert command */
+	struct tnt_update_fields_request *request = (struct tnt_update_fields_request *) io_buf_write_struct(
+		tnt->io_buf, sizeof(struct tnt_update_fields_request));
+	if (request == NULL)
+		return;
 
-			int32_t * count = (int32_t *)p;
-			*count = 1;
-			p += sizeof(int32_t);
+	/* space number */
+	request->space_no = space_no;
+	/* flags */
+	request->flags = flags;
+	/* tuple */
+	if (!io_buf_write_tuple(tnt->io_buf, tuple))
+		return;
 
+	HashTable *op_list_array = Z_ARRVAL_P(op_list);
+	int op_count = zend_hash_num_elements(op_list_array);
 
-		if (Z_TYPE_PP(curr) == IS_STRING)  {
-			char * strval = Z_STRVAL_PP(curr);
-			int str_len = Z_STRLEN_PP(curr);
-//			printf("tuple: len=%d [%s]", str_len, strval );
-			u_char str_shortlen = (u_char)str_len;
+	/* write number of update fields operaion */
+	if (!io_buf_write_int32(tnt->io_buf, op_count))
+		return;
 
-			*(p++) = str_shortlen;
-			memcpy(p, strval, str_len);
-			p += str_len;
+	HashPosition itr;
+	zval **op;
+	for (zend_hash_internal_pointer_reset_ex(op_list_array, &itr);
+		 zend_hash_get_current_data_ex(op_list_array, (void **) &op, &itr) == SUCCESS;
+		 zend_hash_move_forward_ex(op_list_array, &itr)) {
+		/* check operation type */
+		if (Z_TYPE_PP(op) != IS_ARRAY) {
+			zend_throw_exception_ex(zend_exception_get_default(TSRMLS_C), 0 TSRMLS_DC,
+									"invalid operations list");
+			return;
 		}
-		if (Z_TYPE_PP(curr) == IS_LONG)  {
-			unsigned long val = Z_LVAL_PP(curr);
 
-			u_char leb_size = 4;
-			*(p++) = leb_size;
-			b2i * pval = (b2i*) p;
-			pval->i = (int) val;
+		HashTable *op_array = Z_ARRVAL_PP(op);
+		long field_no;
+		long opcode;
 
-			p += leb_size;
+		if (!hash_fing_long(op_array, "field", &field_no)) {
+			zend_throw_exception_ex(zend_exception_get_default(TSRMLS_C), 0 TSRMLS_DC,
+									"can't find 'field' in the update field operation");
+			return;
 		}
-	} // end array
 
+		if (!hash_fing_long(op_array, "op", &opcode)) {
+			zend_throw_exception_ex(zend_exception_get_default(TSRMLS_C), 0 TSRMLS_DC,
+									"can't find 'op' in the update field operation");
+			return;
+		}
 
+		/* write field number */
+		if (!io_buf_write_int32(tnt->io_buf, field_no))
+			return;
 
-	u_char * p_end = (u_char *)select->tuples ;
-	header->len = (p - p_end)  +  SELECT_REQUEST_SIZE; //+ HEADER_SIZE ; // 12 = 3 * sizeof(int32) :ns, flag & cardinality
+		/* write operation code */
+		if (!io_buf_write_byte(tnt->io_buf, opcode))
+			return;
 
+		zval **assing_arg = NULL;
+		long arith_arg;
+		long splice_offset;
+		long splice_length;
+		char *splice_list;
+		int splice_list_len;
+		switch (opcode) {
+		case TARANTOOL_OP_ASSIGN:
+			if (!hash_fing_scalar(op_array, "arg", &assing_arg)) {
+				zend_throw_exception_ex(zend_exception_get_default(TSRMLS_C), 0 TSRMLS_DC,
+										"can't find 'arg' in the update field operation");
+				return;
+			}
+			if (Z_TYPE_PP(assing_arg) == IS_LONG) {
+				/* write as interger */
+				if (!io_buf_write_field_str(tnt->io_buf, (uint8_t *) &Z_LVAL_PP(assing_arg), sizeof(int32_t)))
+					return;
+			} else {
+				/* write as string */
+				if (!io_buf_write_field_str(tnt->io_buf, (uint8_t *) Z_STRVAL_PP(assing_arg), Z_STRLEN_PP(assing_arg)))
+					return;
+			}
+			break;
+		case TARANTOOL_OP_ADD:
+		case TARANTOOL_OP_AND:
+		case TARANTOOL_OP_XOR:
+		case TARANTOOL_OP_OR:
+			if (!hash_fing_long(op_array, "arg", &arith_arg)) {
+				zend_throw_exception_ex(zend_exception_get_default(TSRMLS_C), 0 TSRMLS_DC,
+										"can't find 'arg' in the update field operation");
+				return;
+			}
+			/* write arith arg */
+			if (!io_buf_write_field_str(tnt->io_buf, (uint8_t *) &arith_arg, sizeof(int32_t)))
+				return;
+			break;
+		case TARANTOOL_OP_SPLICE:
+			/*
+			 * read splice args
+			 */
+
+			/* read offset */
+			if (!hash_fing_long(op_array, "offset", &splice_offset)) {
+				zend_throw_exception_ex(zend_exception_get_default(TSRMLS_C), 0 TSRMLS_DC,
+										"can't find 'offset' in the update field operation");
+				return;
+			}
+			/* read length */
+			if (!hash_fing_long(op_array, "length", &splice_length)) {
+				zend_throw_exception_ex(zend_exception_get_default(TSRMLS_C), 0 TSRMLS_DC,
+										"can't find 'length' in the update field operation");
+				return;
+			}
+			/* read list */
+			if (!hash_fing_str(op_array, "list", &splice_list, &splice_list_len)) {
+				zend_throw_exception_ex(zend_exception_get_default(TSRMLS_C), 0 TSRMLS_DC,
+										"can't find 'list' in the update field operation");
+				return;
+			}
 
-//	printf("body len %d\n", header->len);
-//
-//			p = (u_char *)select;
-//			printLine3(p);
-//			printLine3(p+12);
-//			printLine3(p+24);
-//			printLine3(p+36);
-//
-//		efree(out_buf);
-//		RETURN_FALSE;
+			/*
+			 * write splice args
+			 */
+			io_buf_clean(tnt->splice_field);
+
+			/* write offset to separate buffer */
+			if (!io_buf_write_field_str(tnt->splice_field, (uint8_t *) &splice_offset, sizeof(int32_t)))
+				return;
+			/* write length to separate buffer */
+			if (!io_buf_write_field_str(tnt->splice_field, (uint8_t *) &splice_length, sizeof(int32_t)))
+				return;
+			/* write list to separate buffer */
+			if (!io_buf_write_field_str(tnt->splice_field, (uint8_t *) splice_list, splice_list_len))
+				return;
+
+			/* write splice args as alone field */
+			if (!io_buf_write_field_str(tnt->io_buf, tnt->splice_field->value, tnt->splice_field->size))
+				return;
 
-	// write header
-	int len = php_stream_write(ctx->stream, out_buf, HEADER_SIZE); // 12
-	if (len != HEADER_SIZE) {
-			zend_throw_exception(zend_exception_get_default(TSRMLS_C),
-			"write header error" ,0 TSRMLS_CC);
-			efree(out_buf);
+			break;
+		default:
+			zend_throw_exception_ex(zend_exception_get_default(TSRMLS_C), 0 TSRMLS_DC,
+									"invalid operaion code %i", opcode);
 			return;
+		}
 	}
 
-	len = php_stream_write(ctx->stream, (void*)select , header->len );
-	if (len != header->len) {
-			zend_throw_exception(zend_exception_get_default(TSRMLS_C),
-			"write request error" ,0 TSRMLS_CC);
-			efree(out_buf);
-			return;
-	}
+	/* send iproto request */
+	if (!io_buf_send_iproto(tnt->stream, TARANTOOL_COMMAND_UPDATE, 0, tnt->io_buf))
+	  return;
 
-	Header responseHeader;
-	bzero(&responseHeader, HEADER_SIZE);
+	/*
+	 * receive response
+	 */
 
-	len = php_stream_read(ctx->stream, (void *)&responseHeader, HEADER_SIZE);
-	if (len != HEADER_SIZE) {
-			zend_throw_exception(zend_exception_get_default(TSRMLS_C),
-			"read header error" ,0 TSRMLS_CC);
-			efree(out_buf);
-			return;
-	}
+	/* clean-up buffer */
+	io_buf_clean(tnt->io_buf);
 
-	ctx->bodyLen = responseHeader.len;
+	/* receive */
+	if (!io_buf_recv_iproto(tnt->stream, tnt->io_buf))
+		return;
 
-	uint32_t code;
+	/* read response */
+	struct tnt_response *response;
+	if (!io_buf_read_struct(tnt->io_buf,
+						  (void **) &response,
+						  sizeof(struct tnt_response))) {
+		zend_throw_exception_ex(zend_exception_get_default(TSRMLS_C), 0 TSRMLS_DC,
+								"update fields failed: invalid response was received");
+		return;
+	}
 
-	len = php_stream_read(ctx->stream, (void *) &code,sizeof(uint32_t));
-	ctx->readed += len;
-	if (code > 0 || len != sizeof(uint32_t)) {
-		ctx->errorcode = code;					///  need test
-		efree(out_buf);
-		RETURN_FALSE;
+	/* check return code */
+	if (response->return_code) {
+		/* error happen, throw exceprion */
+		zend_throw_exception_ex(zend_exception_get_default(TSRMLS_C), 0 TSRMLS_DC,
+								"update fields failed: %"PRIi32"(0x%08"PRIx32"): %s",
+								response->return_code,
+								response->return_code,
+								response->return_msg);
+		return;
 	}
-//
-	uint32_t tuples_count=0;
-	len = php_stream_read(ctx->stream, (void *) &tuples_count,sizeof(uint32_t));
-	ctx->readed += len;
-	if (len != sizeof(uint32_t)) {
-			zend_throw_exception_ex(zend_exception_get_default(TSRMLS_C),
-			0 TSRMLS_CC,"read body error");
-		efree(out_buf);
+	
+	/*
+	 * fill return value
+	 */
+
+	if (array_init(return_value) != SUCCESS) {
+		zend_throw_exception_ex(zend_exception_get_default(TSRMLS_C), 0 TSRMLS_DC,
+								"update fields failed: create array failed");
 		return;
 	}
 
-	ctx->errorcode	= code;
-	ctx->countTuples = tuples_count;
-	efree(out_buf);
+	/* put count to result array */
+	add_assoc_long(return_value, "count", response->count);	
 
-	RETURN_LONG(ctx->countTuples);
+	/* check "return tuple" flag */
+	if ((response->count > 0) && (flags & TARANTOOL_FLAGS_RETURN_TUPLE)) {
+		/* ok, the responce should contain inserted tuple */
+		if (!io_buf_read_tuple(tnt->io_buf, &tuple)) {
+			zend_throw_exception_ex(zend_exception_get_default(TSRMLS_C), 0 TSRMLS_DC,
+									"update fields failed: invalid response was received");
+			return;
+		}
 
+		/* put returned tuple to result array */
+		add_assoc_zval(return_value, "tuple", tuple);
+	}
 }
-/* }}} */
 
-/* {{{ proto int tarantool::call(procname, tuple);
-	call a stored procedure by  name
-*/
-PHP_METHOD(tarantool_class, call)
+PHP_METHOD(tarantool_class, delete)
 {
-	zval *id, * tuple;
-	tarantool_object *ctx;
-	const char *procname = NULL;
-	long procname_len = 0;
-
-	if (zend_parse_method_parameters(ZEND_NUM_ARGS() TSRMLS_CC, getThis(), "Osz", &id,
-		tarantool_class_entry, &procname, &procname_len, &tuple) == FAILURE) {
+	/* 
+	 * parse methods parameters 
+	 */
+	zval *id;
+	long space_no = 0;
+	long flags = 0;
+	zval *tuple = NULL;
+	if (zend_parse_method_parameters(ZEND_NUM_ARGS() TSRMLS_CC,
+									 getThis(),
+									 "Olz|l",
+									 &id,
+									 tarantool_class_ptr,
+									 &space_no,
+									 &tuple,
+									 &flags) == FAILURE) {
 		return;
 	}
 
-	ctx = (tarantool_object *)zend_object_store_get_object(id TSRMLS_CC);
-	if (!ctx)
-		zend_throw_exception(zend_exception_get_default(TSRMLS_C),
-			"the context is null" ,0 TSRMLS_CC);
+	tarantool_object *tnt = (tarantool_object *) zend_object_store_get_object(
+		id TSRMLS_CC);
 
-	if (!ctx->stream) {
-		if (php_tnt_connect(ctx TSRMLS_CC)) {
-			zend_throw_exception(zend_exception_get_default(TSRMLS_C),
-				"can't connect to remote host " ,0 TSRMLS_DC);
+	/* check connection */
+	if (!tnt->stream) {
+		/* establis connection */
+		tnt->stream = establish_connection(tnt->host, tnt->port);
+		if (!tnt->stream)
 			return;
-		}
 	}
 
-	ctx->bodyLen = 0;
-	ctx->countTuples = 0;
-	ctx->readedTuples = 0;
-	ctx->readed	= 0;
-	ctx->errorcode	= 0;
-
-	char *out_buf = emalloc(TARANTOOL_BUFSIZE);
-	bzero(out_buf, TARANTOOL_BUFSIZE);
-
-	Header *header = (Header *) out_buf;
-
-	header->type		= TARANTOOL_CALL;
-	header->request_id	= TARANTOOL_REQUEST_ID;
-
-	u_char *call = (char *) out_buf + HEADER_SIZE;
-	u_char *p = call;
+	/*
+	 * send request
+	 */
 
-	p+= 4; /* flags */
-	*p++ = (u_char) procname_len;
+	/* clean-up buffer */
+	io_buf_clean(tnt->io_buf);
 
-	memcpy(p, procname, procname_len);
-	p += procname_len;
-
-	int * count_elements = (int *)p;
-
-	switch (Z_TYPE_P(tuple)) {
-		case IS_STRING: {
-			*count_elements  = 1;
-			p += sizeof(uint32_t);
-
-			char * strval = Z_STRVAL_P(tuple);
-			int str_len = Z_STRLEN_P(tuple);
-			u_char str_shortlen = (u_char)str_len;
+	/* fill delete command */
+	struct tnt_delete_request *request = (struct tnt_delete_request *) io_buf_write_struct(
+		tnt->io_buf, sizeof(struct tnt_delete_request));
+	if (request == NULL)
+		return;
 
-			*(p++) = str_shortlen;
-			memcpy(p, strval, str_len);
-			p += str_len;
-		}
-		break;
+	/* space number */
+	request->space_no = space_no;
+	/* flags */
+	request->flags = flags;
+	/* tuple */
+	if (!io_buf_write_tuple(tnt->io_buf, tuple))
+		return;
 
-		case IS_LONG: {
-			*count_elements  = 1;
-			p += sizeof(uint32_t);
+	/* send iproto request */
+	if (!io_buf_send_iproto(tnt->stream, TARANTOOL_COMMAND_DELETE, 0, tnt->io_buf))
+		return;
 
-			unsigned long val = Z_LVAL_P(tuple);
+	/*
+	 * receive response
+	 */
 
-			u_char leb_size = 4;
-			*(p++) = leb_size;
-			b2i * pval = (b2i*) p;
-			pval->i = (int) val;
-			p += leb_size;
+	/* clean-up buffer */
+	io_buf_clean(tnt->io_buf);
 
-			}
-			break;
+	/* receive */
+	if (!io_buf_recv_iproto(tnt->stream, tnt->io_buf))
+		return;
 
+	/* read response */
+	struct tnt_response *response;
+	if (!io_buf_read_struct(tnt->io_buf,
+						  (void **) &response,
+						  sizeof(struct tnt_response))) {
+		zend_throw_exception_ex(zend_exception_get_default(TSRMLS_C), 0 TSRMLS_DC,
+								"delete failed: invalid response was received");
+		return;
+	}
 
-		case IS_ARRAY: {
-			HashTable *pht;
-			HashPosition pos;
-			zval **curr;
-
-			*count_elements  = zend_hash_num_elements(Z_ARRVAL_P(tuple));
-			p += sizeof(uint32_t);
-
-			pht = Z_ARRVAL_P(tuple);
-			for(zend_hash_internal_pointer_reset_ex(pht, &pos);
-				  zend_hash_get_current_data_ex(pht, (void **)&curr, &pos) == SUCCESS;
-				  zend_hash_move_forward_ex(pht, &pos)) {
-
-				if (Z_TYPE_PP(curr) == IS_STRING)  {
-					char * strval = Z_STRVAL_PP(curr);
-					int str_len = Z_STRLEN_PP(curr);
-		//			printf("tuple: len=%d [%s]", str_len, strval );
-					u_char str_shortlen = (u_char)str_len;
-
-					*(p++) = str_shortlen;
-					memcpy(p, strval, str_len);
-					p += str_len;
-				}
-				if (Z_TYPE_PP(curr) == IS_LONG)  {
-					unsigned long val = Z_LVAL_PP(curr);
-
-//					u_char leb_size = (u_char)leb128_size( val);
-//					*(p++) = leb_size;
-//					leb128_write( (char *)p, val);
-					u_char leb_size = 4;
-					*(p++) = leb_size;
-					b2i * pval = (b2i*) p;
-					pval->i = (int) val;
-
-					p += leb_size;
-				}
-			}
+	/* check return code */
+	if (response->return_code) {
+		/* error happen, throw exceprion */
+		zend_throw_exception_ex(zend_exception_get_default(TSRMLS_C), 0 TSRMLS_DC,
+								"delete failed: %"PRIi32"(0x%08"PRIx32"): %s",
+								response->return_code,
+								response->return_code,
+								response->return_msg);
+		return;
+	}
+	
+	/*
+	 * fill return value
+	 */
 
-		}
-		break;
-		default :
-			zend_throw_exception(zend_exception_get_default(TSRMLS_C),
-			"tuple: unsuport tuple type" ,0 TSRMLS_CC);
-			return;
+	if (array_init(return_value) != SUCCESS) {
+		zend_throw_exception_ex(zend_exception_get_default(TSRMLS_C), 0 TSRMLS_DC,
+								"delete failed: create array failed");
+		return;
 	}
 
-	header->len = p - call;
+	/* put count to result array */
+	add_assoc_long(return_value, "count", response->count);
 
-	// write header
-	int len = php_stream_write(ctx->stream, out_buf, HEADER_SIZE); // 12
-	if (len != HEADER_SIZE) {
-			zend_throw_exception(zend_exception_get_default(TSRMLS_C),
-			"write header error" ,0 TSRMLS_CC);
-			efree(out_buf);
+	/* check "return tuple" flag */
+	if ((response->count) > 0 && (flags & TARANTOOL_FLAGS_RETURN_TUPLE)) {
+		/* ok, the responce should contain inserted tuple */
+		if (!io_buf_read_tuple(tnt->io_buf, &tuple)) {
+			zend_throw_exception_ex(zend_exception_get_default(TSRMLS_C), 0 TSRMLS_DC,
+									"delete failed: invalid response was received");
 			return;
+		}
+
+		/* put returned tuple to result array */
+		add_assoc_zval(return_value, "tuple", tuple);
 	}
+}
 
-	len = php_stream_write(ctx->stream, call, header->len );
-	if (len != header->len) {
-			zend_throw_exception(zend_exception_get_default(TSRMLS_C),
-			"write request error" ,0 TSRMLS_CC);
-			efree(out_buf);
-			return;
+PHP_METHOD(tarantool_class, call)
+{
+	/* 
+	 * parse methods parameters 
+	 */
+	zval *id;
+	char *proc_name = NULL;
+	int proc_name_len = 0;
+	zval *tuple = NULL;
+	long flags = 0;
+	if (zend_parse_method_parameters(ZEND_NUM_ARGS() TSRMLS_CC,
+									 getThis(),
+									 "Osz|l",
+									 &id,
+									 tarantool_class_ptr,
+									 &proc_name, &proc_name_len,
+									 &tuple,
+									 &flags) == FAILURE) {
+		return;
 	}
 
-	Header responseHeader;
-	bzero(&responseHeader, HEADER_SIZE);
+	tarantool_object *tnt = (tarantool_object *) zend_object_store_get_object(
+		id TSRMLS_CC);
 
-	len = php_stream_read(ctx->stream, (void *)&responseHeader, HEADER_SIZE);
-	if (len != HEADER_SIZE) {
-			zend_throw_exception(zend_exception_get_default(TSRMLS_C),
-			"read header error" ,0 TSRMLS_CC);
-			efree(out_buf);
+	/* check connection */
+	if (!tnt->stream) {
+		/* establis connection */
+		tnt->stream = establish_connection(tnt->host, tnt->port);
+		if (!tnt->stream)
 			return;
 	}
 
-	ctx->bodyLen = responseHeader.len;
+	/*
+	 * send request
+	 */
 
-	uint32_t code;
+	/* clean-up buffer */
+	io_buf_clean(tnt->io_buf);
 
-	len = php_stream_read(ctx->stream, (void *) &code,sizeof(uint32_t));
-	ctx->readed += len;
-	if (code > 0 || len != sizeof(uint32_t)) {
-		ctx->errorcode = code;					///  need test
-		efree(out_buf);
-		RETURN_FALSE;
-	}
-//
-	uint32_t tuples_count=0;
-	len = php_stream_read(ctx->stream, (void *) &tuples_count,sizeof(uint32_t));
-	ctx->readed += len;
-	if (len != sizeof(uint32_t)) {
-			zend_throw_exception_ex(zend_exception_get_default(TSRMLS_C),
-			0 TSRMLS_CC,"read body error");
-		efree(out_buf);
+	/* fill insert command */
+	struct tnt_call_request *request = (struct tnt_call_request *) io_buf_write_struct(
+		tnt->io_buf, sizeof(struct tnt_call_request));
+	if (request == NULL)
 		return;
-	}
 
-	ctx->errorcode	= code;
-	ctx->countTuples = tuples_count;
-	efree(out_buf);
+	/* flags */
+	request->flags = flags;
+	/* proc name */
+	if (!io_buf_write_field_str(tnt->io_buf, proc_name, proc_name_len))
+		return;
+	/* tuple */
+	if (!io_buf_write_tuple(tnt->io_buf, tuple))
+		return;
 
-	RETURN_LONG(ctx->countTuples);
+	/* send iproto request */
+	if (!io_buf_send_iproto(tnt->stream, TARANTOOL_COMMAND_CALL, 0, tnt->io_buf))
+	  return;
 
-}
-/* }}} */
 
+	/*
+	 * receive response
+	 */
 
-/* {{{ proto array tarantool::getTuple()
-   return one tuple */
-PHP_METHOD(tarantool_class, getTuple )
-{
-	zval *id;
-	tarantool_object *ctx;
+	/* clean-up buffer */
+	io_buf_clean(tnt->io_buf);
 
-	int len;
+	/* receive */
+	if (!io_buf_recv_iproto(tnt->stream, tnt->io_buf))
+		return;
 
-	if (zend_parse_method_parameters(ZEND_NUM_ARGS() TSRMLS_CC, getThis(), "O", &id,
-		tarantool_class_entry) == FAILURE) {
+	/* read response */
+	struct tnt_response *response;
+	if (!io_buf_read_struct(tnt->io_buf,
+						  (void **) &response,
+						  sizeof(struct tnt_response))) {
+		zend_throw_exception_ex(zend_exception_get_default(TSRMLS_C), 0 TSRMLS_DC,
+								"call failed: invalid response was received");
 		return;
 	}
 
-	ctx = (tarantool_object *)zend_object_store_get_object(id TSRMLS_CC);
-	if (!ctx) {
-		zend_throw_exception(zend_exception_get_default(TSRMLS_C),
-			"the context is null" ,0 TSRMLS_CC);
+	/* check return code */
+	if (response->return_code) {
+		/* error happen, throw exceprion */
+		zend_throw_exception_ex(zend_exception_get_default(TSRMLS_C), 0 TSRMLS_DC,
+								"call failed: %"PRIi32"(0x%08"PRIx32"): %s",
+								response->return_code,
+								response->return_code,
+								response->return_msg);
 		return;
 	}
 
-	if (!ctx->bodyLen) {
-		zend_throw_exception(zend_exception_get_default(TSRMLS_C),
-			"the response body is null" ,0 TSRMLS_CC);
+	if (array_init(return_value) != SUCCESS) {
+		zend_throw_exception_ex(zend_exception_get_default(TSRMLS_C), 0 TSRMLS_DC,
+								"call failed: create array failed");
 		return;
 	}
 
-	if (++ctx->readedTuples > ctx->countTuples) {
-		RETURN_FALSE;
-	}
+	/* put count to result array */
+	add_assoc_long(return_value, "count", response->count);
 
-	if (ctx->readed >= ctx->bodyLen) {
-		RETURN_FALSE;
+	/* put tuple list to result array */
+	zval *tuples_list;
+	MAKE_STD_ZVAL(tuples_list);
+	if (array_init(tuples_list) == FAILURE) {
+		zend_throw_exception_ex(zend_exception_get_default(TSRMLS_C), 0 TSRMLS_DC,
+								"call failed: create array failed");
+		return;
 	}
 
+	/* read tuples for responce */
+	int i;
+	for (i = 0; i < response->count; ++i) {
+		zval *tuple;
+		if (!io_buf_read_tuple(tnt->io_buf, &tuple)) {
+			zend_throw_exception_ex(zend_exception_get_default(TSRMLS_C), 0 TSRMLS_DC,
+									"call failed: invalid response was received");
+			return;
+		}
+		add_next_index_zval(tuples_list, tuple);
+	}
 
-	SelectResponseTuple responseBody;
+	add_assoc_zval(return_value, "tuples_list", tuples_list);
+}
 
-	if (php_stream_read(ctx->stream, (void*)&responseBody, sizeof(SelectResponseTuple)) !=sizeof(SelectResponseTuple)) {
-		zend_throw_exception(zend_exception_get_default(TSRMLS_C),
-			"the read body error" ,0 TSRMLS_CC);
+PHP_METHOD(tarantool_class, admin)
+{
+	/* parse methods parameters */
+	zval *id;
+	char *cmd = NULL;
+	int cmd_len = 0;
+	if (zend_parse_method_parameters(ZEND_NUM_ARGS() TSRMLS_CC,
+									 getThis(),
+									 "Os",
+									 &id,
+									 tarantool_class_ptr,
+									 &cmd, &cmd_len) == FAILURE) {
 		return;
 	}
 
-	ctx->readed += sizeof(SelectResponseTuple);
+	tarantool_object *tnt = (tarantool_object *) zend_object_store_get_object(
+		id TSRMLS_CC);
 
-	u_char * buf = emalloc(responseBody.size);
-
-	if (php_stream_read(ctx->stream, (char*) buf, responseBody.size) != responseBody.size) {
-		efree(buf);
-		zend_throw_exception(zend_exception_get_default(TSRMLS_C),
-			"the read tuple error" ,0 TSRMLS_CC);
+	/* check admin port */
+	if (!tnt->admin_port) {
+		zend_throw_exception_ex(zend_exception_get_default(TSRMLS_C), 0 TSRMLS_DC,
+								"admin command not allowed for this commection");
 		return;
 	}
 
-	ctx->readed += responseBody.size;
-
-	array_init(return_value);
-
-	char bb2[16];
-	int i;
-	unsigned long value;
-	b2i b;
-	b2i * pb;
-	u_char* p = buf;
-	int is_string = 0;
-
-	for(i=0; i < responseBody.count; i++) { //
-		b.i=0;
-		b.b = *p;
-		bzero(bb2,16);
-		len = b.i;
-		memcpy(bb2, p+1, len);
-
-		switch (len) {
-			case 1 : {
-				if ( isprint(*bb2) ) {
-//					printf("tuple element '%s' len=%d\n",bb2, len);
-					is_string = 1;
-				} else {
-					is_string = 0;
-					leb128_read(bb2, len, &value);
-//					printf("tuple element(int) %d len=%d\n",value, len);
-				}
-				break;
-			}
-			case 2 : {
-				if ( isprint(*bb2) && isprint(*(bb2+1))) {
-//					printf("tuple element '%s' len=%d\n",bb2, len);
-					is_string = 1;
-				} else {
-					is_string = 0;
-					leb128_read(bb2, len, &value);
-//					printf("tuple element(int) %d len=%d\n",value, len);
-				}
-				break;
-			}
-
-			case 3 : {
-				if ( isprint(*bb2) && isprint(*(bb2+1)) && isprint(*(bb2+2)) ) {
-//					printf("tuple element '%s' len=%d\n",bb2, len);
-					is_string = 1;
-				} else {
-					is_string = 0;
-					leb128_read(bb2, len, &value);
-//					printf("tuple element(int) %d len=%d\n",value, len);
-				}
-				break;
-			}
-
-			case 4 : {
-				if ( isprint(*bb2) && isprint(*(bb2+1)) && isprint(*(bb2+2)) && isprint(*(bb2+3)) ) {
-					is_string = 1;
-				} else {
-
-					pb = (b2i*) bb2;
-					value = (unsigned long) pb->i;
-//				php_printf("tuple element(int) %d len=%d\n",value, len);
-					is_string = 0;
-				}
-				break;
-			}
-			default :  {
-				is_string = 0;
-//				printf("tuple element %s len=%d\n",bb2, len);
-			}
-		} // end switch
-
-
-		if (is_string) {
-//			php_printf("tuple element '%s' len=%d\n",bb2, len);
-			add_next_index_stringl( return_value, bb2 , len , 1);
-		} else
-//			php_printf("tuple element %d\n",bb2, value);
-			add_next_index_long( return_value, value);
+	/* check connection */
+	if (!tnt->admin_stream) {
+		/* establis connection */
+		tnt->admin_stream = establish_connection(tnt->host, tnt->admin_port);
+		if (!tnt->admin_stream)
+			return;
 
-		p += len+1;
+		/* set string eol */
+		php_stream_locate_eol(tnt->admin_stream,
+							  ADMIN_SEPARATOR,
+							  strlen(ADMIN_SEPARATOR) TSRMLS_DC);
 	}
 
-	efree(buf);
-}
-/* }}} */
-
-/* {{{ proto int tarantool::delete(key)
-   tarantool delete tuple */
-PHP_METHOD(tarantool_class, delete)
-{
-	zval *id;
-	tarantool_object *ctx;
-	long space;
-
-	zval* data;
-
-	if (zend_parse_method_parameters(ZEND_NUM_ARGS() TSRMLS_CC, getThis(), "Olz",
-			&id, tarantool_class_entry, &space, &data) == FAILURE) {
+	/* send request */
+	io_buf_clean(tnt->io_buf);
+	if (!io_buf_write_str(tnt->io_buf, cmd, cmd_len))
 		return;
-	}
-
-	ctx = (tarantool_object *)zend_object_store_get_object(id TSRMLS_CC);
-	if (!ctx) {
-		zend_throw_exception(zend_exception_get_default(TSRMLS_C),
-			"the context is null" ,0 TSRMLS_CC);
+	if (!io_buf_write_str(tnt->io_buf, ADMIN_SEPARATOR, strlen(ADMIN_SEPARATOR)))
+		return;
+	if (!io_buf_send_yaml(tnt->admin_stream, tnt->io_buf))
 		return;
-	}
-
-	ctx->bodyLen = 0;
-	ctx->countTuples = 0;
-	ctx->readedTuples = 0;
-	ctx->readed	= 0;
-
-	if (!ctx->stream) {
-		if (php_tnt_connect(ctx TSRMLS_CC)) {
-			zend_throw_exception(zend_exception_get_default(TSRMLS_C),
-				"the can't open remote host " ,0 TSRMLS_DC);
-			return;
-		}
-	}
-
-	char * buf = emalloc(TARANTOOL_BUFSIZE);
-	bzero(buf, TARANTOOL_BUFSIZE);
-
-	Header * header = (Header *) buf;
-	DeleteRequest * delRequest = (DeleteRequest *) buf + HEADER_SIZE;
-
-	header->type		= TARANTOOL_DELETE;
-	header->request_id	= TARANTOOL_REQUEST_ID;
-
-	delRequest->spaceNo = space;
-	delRequest->tuple.count = 1; // count in the Tuples
-
-	u_char * p = (u_char *) delRequest->tuple.data;
-
-	int * count_elements = (int *)p;
 
-	switch (Z_TYPE_P(data)) {
-		case IS_STRING: {
+	/* recv response */
+	io_buf_clean(tnt->io_buf);
+	if (!io_buf_recv_yaml(tnt->admin_stream, tnt->io_buf))
+		return;
 
-			char * strval = Z_STRVAL_P(data);
-			int str_len = Z_STRLEN_P(data);
-			u_char str_shortlen = (u_char)str_len;
+	char *response = estrndup(tnt->io_buf->value, tnt->io_buf->size);
+	RETURN_STRING(response, 0);
+}
 
-			*(p++) = str_shortlen;
-			memcpy(p, strval, str_len);
-			p += str_len;
-		}
-			break;
 
-		case IS_LONG: {
+/*============================================================================*
+ * local functions definition
+ *============================================================================*/
 
-			unsigned long val = Z_LVAL_P(data);
-		    u_char leb_size = (u_char)leb128_size( val);
-		    *(p++) = leb_size;
-		    leb128_write( (char *)p, val);
-		    p += leb_size;
 
-//			printf("tuple: int %d\n", val );
-			}
-			break;
+/*----------------------------------------------------------------------------*
+ * Buffer interface
+ *----------------------------------------------------------------------------*/
 
-		case IS_ARRAY: {
-			HashTable *pht;
-			HashPosition pos;
-			zval **curr;
-
-			delRequest->tuple.count = zend_hash_num_elements(Z_ARRVAL_P(data));
-			pht = Z_ARRVAL_P(data);
-			for(zend_hash_internal_pointer_reset_ex(pht, &pos);
-				  zend_hash_get_current_data_ex(pht, (void **)&curr, &pos) == SUCCESS;
-				  zend_hash_move_forward_ex(pht, &pos)) {
-
-				if (Z_TYPE_PP(curr) == IS_STRING)  {
-					char * strval = Z_STRVAL_PP(curr);
-					int str_len = Z_STRLEN_PP(curr);
-					u_char str_shortlen = (u_char)str_len;
-
-					*(p++) = str_shortlen;
-					memcpy(p, strval, str_len);
-					p += str_len;
-				}
-				if (Z_TYPE_PP(curr) == IS_LONG)  {
-				   unsigned long val = Z_LVAL_PP(curr);
-
-				   u_char leb_size = (u_char)leb128_size( val);
-				   *(p++) = leb_size;
-				   leb128_write( (char *)p, val);
-				   p += leb_size;
-				}
-			}
-
-		}
-		break;
-		default :
-			efree(buf);
-			zend_throw_exception(zend_exception_get_default(TSRMLS_C),
-			"tuple: unsuport tuple type" ,0 TSRMLS_CC);
-			return;
+static struct io_buf *
+io_buf_create()
+{
+	struct io_buf *buf = (struct io_buf *) emalloc(sizeof(struct io_buf));
+	if (!buf) {
+		zend_throw_exception_ex(zend_exception_get_default(TSRMLS_C), 0 TSRMLS_DC,
+								"allocation memory fail: %s (%i)", strerror(errno), errno);
+		goto failure;
 	}
 
-	header->len = (p-delRequest->tuple.data) + sizeof(uint32_t) *2 ; // sizeof(int) + sizeof(int)
-
-	// write header
-	int len = php_stream_write(ctx->stream, buf , HEADER_SIZE); // 12
-
-	// write tuple
-	p = (u_char*)delRequest;
-	len = php_stream_write(ctx->stream, (char*)p , header->len );
-
-	if (header->len + HEADER_SIZE > TARANTOOL_BUFSIZE) {
-			efree(buf);
-			zend_throw_exception(zend_exception_get_default(TSRMLS_C),
-			"out of memory: the tuple is very big" ,0 TSRMLS_CC);
-			return;
+	buf->size = 0;
+	buf->capacity = io_buf_next_capacity(buf->size);
+	buf->readed = 0;
+	buf->value = (uint8_t *) emalloc(buf->capacity);
+	if (!buf->value) {
+		zend_throw_exception_ex(zend_exception_get_default(TSRMLS_C), 0 TSRMLS_DC,
+								"allocation memory fail: %s (%i)", strerror(errno), errno);
+		goto failure;
 	}
 
-	bzero(buf, header->len + HEADER_SIZE);
+	return buf;
 
-	len = php_stream_read(ctx->stream, buf, TARANTOOL_BUFSIZE);
+failure:
+	if (buf) {
+		if (buf->value)
+			efree(buf->value);
 
-	if ( *(buf+HEADER_SIZE) == '\0') {
-		int deleted = *(buf+HEADER_SIZE + sizeof(uint32_t));
 		efree(buf);
-		RETURN_LONG(deleted);
 	}
 
-
-	b2i* bb =(b2i*)(buf+HEADER_SIZE);
-	ctx->errorcode = bb->i;
-
-	efree(buf);
-	RETURN_FALSE;
-
+	return NULL;
 }
-/* }}} */
 
-/* {{{ proto int tarantool::update(int space, mixed key, array data);
-   tarantool update tuple */
-PHP_METHOD(tarantool_class, update)
+static void
+io_buf_destroy(struct io_buf *buf)
 {
-	zval *id;
-	tarantool_object *ctx;
-	long space;
-
-	zval* key;
-	zval* data;
-
-	if (zend_parse_method_parameters(ZEND_NUM_ARGS() TSRMLS_CC, getThis(), "Olza",
-			&id, tarantool_class_entry, &space, &key, &data) == FAILURE) {
+	if (!buf)
 		return;
-	}
-
-	ctx = (tarantool_object *)zend_object_store_get_object(id TSRMLS_CC);
-	if (!ctx) {
-		zend_throw_exception(zend_exception_get_default(TSRMLS_C),
-			"the context is null" ,0 TSRMLS_CC);
-		return;
-	}
-
-	ctx->bodyLen = 0;
-	ctx->countTuples = 0;
-	ctx->readedTuples = 0;
-	ctx->readed	= 0;
-	ctx->errorcode = 0;
-
-	if (!ctx->stream) {
-		if (php_tnt_connect(ctx TSRMLS_CC)) {
-			zend_throw_exception(zend_exception_get_default(TSRMLS_C),
-				"the can't open remote host " ,0 TSRMLS_DC);
-			return;
-		}
-	}
-
-// <insert_request_body> ::= <space_no><flags><tuple>
-// <update_request_body> ::= <space_no><flags><tuple><count><operation>+
-// <operation> ::= <field_no><op_code><op_arg>
-
-	char * out_buf = emalloc(TARANTOOL_BUFSIZE);
-	bzero(out_buf, TARANTOOL_BUFSIZE);
-
-	Header * header = (Header *) out_buf;
-
-	header->type		= TARANTOOL_UPDATE;
-	header->request_id	= TARANTOOL_REQUEST_ID;
-
-	InsertRequest * insert = (InsertRequest *) (out_buf + HEADER_SIZE);
-
-	insert->spaceNo = space;
-	insert->tuple.count = 1;
-
-	u_char * p = (u_char *) insert->tuple.data;
-
-	switch (Z_TYPE_P(key)) {
-		case IS_STRING: {
-//			*count_elements  = 1;	//!!!! <------- êîë-âî ýëåìåíòîâ â  êîðòåæå
-//			p += sizeof(uint32_t);
-
-				char * strval = Z_STRVAL_P(key);
-				int str_len = Z_STRLEN_P(key);
-				u_char str_shortlen = (u_char)str_len;
-
-				*(p++) = str_shortlen;
-				memcpy(p, strval, str_len);
-				p += str_len;
-
-//				printf("tuple: len=%d [%s]\n", str_len, strval );
-			}
-			break;
-
-		case IS_LONG: {
-//			*count_elements  = 1;	//!!!! <------- êîë-âî ýëåìåíòîâ â  êîðòåæå
-//			p += sizeof(uint32_t);
-
-			unsigned long val = Z_LVAL_P(key);
-
-		    u_char leb_size = 4; //(u_char)leb128_size( val);
-		    *(p++) = leb_size;
 
-			b2i * pb = (b2i*) p;
-			pb->i = (uint32_t) val;
-//		    leb128_write( (char *)p, val);
-		    p += leb_size;
+	if (buf->value)
+		efree(buf->value);
 
-//			printf("tuple: int %d\n", val );
-			}
-			break;
-
-		default :
-			zend_throw_exception(zend_exception_get_default(TSRMLS_C),
-			"unsupport key type" ,0 TSRMLS_CC);
-			return;
-	}
-
-	const int insertLen = p-insert->tuple.data;
-
-
-	HashTable *pht = Z_ARRVAL_P(data);
-
-	UpdateRequest  * updateRequest = (UpdateRequest*) p;
-	updateRequest->count = zend_hash_num_elements(pht);;
-
-	HashPosition pos;
-	zval** curr;
-
-	char *ht_key;
-	ulong index;
-	uint ht_key_len;
-
-	Operation * operation = &(updateRequest->operation);
-
-    for(zend_hash_internal_pointer_reset_ex(pht, &pos);
-          zend_hash_get_current_data_ex(pht, (void **) &curr, &pos) == SUCCESS;
-          zend_hash_move_forward_ex(pht,&pos)) {
-
-		if (HASH_KEY_IS_LONG != zend_hash_get_current_key_ex(pht, &ht_key, &ht_key_len, &index, 0, &pos)) {
-			efree(out_buf);
-			zend_throw_exception(zend_exception_get_default(TSRMLS_C),
-			"key type error" ,0 TSRMLS_CC);
-			return;
-		}
-
-		operation->code = TARANTOOL_OP_ASSIGN;
-		operation->fieldNo = index;
-		p = operation->arg;
-
-		if (Z_TYPE_PP(curr) == IS_STRING)  {
-			char * strval = Z_STRVAL_PP(curr);
-			int str_len = Z_STRLEN_PP(curr);
-
-			u_char str_shortlen = (u_char)str_len;
-
-			*(p++) = str_shortlen;
-			memcpy(p, strval, str_len);
-			p += str_len;
-		}
-		if (Z_TYPE_PP(curr) == IS_LONG)  {
-		   unsigned long val = Z_LVAL_PP(curr);
-
-		    u_char leb_size = 4;
-		    *(p++) = leb_size;
-			b2i * pb = (b2i*) p;
-			pb->i = (uint32_t) val;
-		    p += leb_size;
-		}
-
-		operation = (Operation*)p;
-    }
-
-	u_char * p2 = (u_char *) insert;
-	header->len = (uint32_t) (p-p2);
+	efree(buf);
+}
 
-	// write header
-	int len = php_stream_write(ctx->stream, out_buf , HEADER_SIZE); // 12
-	if (len!=HEADER_SIZE) {
-		zend_throw_exception(zend_exception_get_default(TSRMLS_C),
-			"error write header" ,0 TSRMLS_CC);
-		return;
-	}
+inline static bool
+io_buf_reserve(struct io_buf *buf, size_t n)
+{
+	if (buf->capacity > n)
+		return true;
 
-//	write tuple
-	p = (u_char*) out_buf+HEADER_SIZE;
-	len = php_stream_write(ctx->stream, (char*)p , header->len ); //
-	if (len != header->len) {
-		zend_throw_exception(zend_exception_get_default(TSRMLS_C),
-			"error write body" ,0 TSRMLS_CC);
-		return;
+	size_t new_capacity = io_buf_next_capacity(n);
+	uint8_t *new_value = (uint8_t *) erealloc(buf->value, new_capacity);
+	if (!new_value) {
+		zend_throw_exception_ex(zend_exception_get_default(TSRMLS_C), 0 TSRMLS_DC,
+								"allocation memory fail: %s (%i)", strerror(errno), errno);
+		return false;
 	}
 
-	bzero(out_buf, header->len + HEADER_SIZE);
-//
-	len = php_stream_read(ctx->stream, out_buf, TARANTOOL_BUFSIZE);
+	buf->capacity = new_capacity;
+	buf->value = new_value;
+	return true;
+}
 
-	if ( *(out_buf+HEADER_SIZE) == '\0') {
-		efree(out_buf);
-		RETURN_TRUE;
-	}
+inline static bool
+io_buf_resize(struct io_buf *buf, size_t n)
+{
+	io_buf_reserve(buf, n);
+	buf->size = n;
+	return true;
+}
 
-	b2i* bb = (b2i*) out_buf+HEADER_SIZE;
-	ctx->errorcode = bb->i;
+inline static size_t
+io_buf_next_capacity(size_t n)
+{
+	size_t capacity = IO_BUF_CAPACITY_MIN;
+	while (capacity < n)
+		capacity *= IO_BUF_CAPACITY_FACTOR;
+	return capacity;
+}
 
-	efree(out_buf);
-	RETURN_FALSE;
+static void
+io_buf_clean(struct io_buf *buf)
+{
+	buf->size = 0;
+	buf->readed = 0;
+}
 
+static bool
+io_buf_read_struct(struct io_buf *buf, void **ptr, size_t n)
+{
+	size_t last = buf->size - buf->readed;
+	if (last < n)
+		return false;
+	*ptr = buf->value + buf->readed;
+	buf->readed += n;
+	return true;
 }
-/* }}} */
 
+static bool
+io_buf_read_int32(struct io_buf *buf, int32_t *val)
+{
+	size_t last = buf->size - buf->readed;
+	if (last < sizeof(int32_t))
+		return false;
+	*val = *(int32_t *)(buf->value + buf->readed);
+	buf->readed += sizeof(int32_t);
+	return true;
+}
 
-/* {{{ proto int tarantool::inc(int space, mixed key, int fieldNo, [data = 1, flag=0]);
-		$tnt->inc($NS,$key, $fieldNo , $inc=1;  );
-   tarantool incremental tuple */
-PHP_METHOD(tarantool_class, inc)
+static bool
+io_buf_read_int64(struct io_buf *buf, int64_t *val)
 {
-	zval *id;
-	tarantool_object *ctx;
-	long space, fieldNo ;
-	zval* key;
-	long data = 1;
-	long flag = 0;
-
-	if (zend_parse_method_parameters(ZEND_NUM_ARGS() TSRMLS_CC, getThis(), "Olzl|lb",
-			&id, tarantool_class_entry, &space, &key, &fieldNo, &data, &flag) == FAILURE) {
-		return;
-	}
+	size_t last = buf->size - buf->readed;
+	if (last < sizeof(int64_t))
+		return false;
+	*val = *(int64_t *)(buf->value + buf->readed);
+	buf->readed += sizeof(int64_t);
+	return true;
+}
 
-	ctx = (tarantool_object *)zend_object_store_get_object(id TSRMLS_CC);
-	if (!ctx) {
-		zend_throw_exception(zend_exception_get_default(TSRMLS_C),
-			"the context is null" ,0 TSRMLS_CC);
-		return;
-	}
+static bool
+io_buf_read_varint(struct io_buf *buf, int32_t *val)
+{
+	uint8_t *b = buf->value + buf->readed;
+	size_t size = buf->size - buf->readed;
 
-	ctx->bodyLen = 0;
-	ctx->countTuples = 0;
-	ctx->readedTuples = 0;
-	ctx->readed	= 0;
-	ctx->errorcode = 0;
+	if (size < 1)
+		return false;
 
-	if (!ctx->stream) {
-		if (php_tnt_connect(ctx TSRMLS_CC)) {
-			zend_throw_exception(zend_exception_get_default(TSRMLS_C),
-				"the can't open remote host " ,0 TSRMLS_DC);
-			return;
-		}
+	if (!(b[0] & 0x80)) {
+		buf->readed += 1;
+		*val = (b[0] & 0x7f);
+		return true;
 	}
 
-// <insert_request_body> ::= <space_no><flags><tuple>
-// <update_request_body> ::= <space_no><flags><tuple><count><operation>+
-
-// <operation> ::= <field_no><op_code><op_arg>
-
-	char * out_buf = emalloc(TARANTOOL_BUFSIZE);
-	bzero(out_buf, TARANTOOL_BUFSIZE);
-
-
-	Header * header = (Header *) out_buf;
-
-
-	header->type		= TARANTOOL_UPDATE;
-	header->request_id	= TARANTOOL_REQUEST_ID;
-
-	InsertRequest * insert = (InsertRequest *) (out_buf + HEADER_SIZE);
-
-	insert->spaceNo = space;
-	insert->tuple.count = 1;
-	insert->flag = flag;
-
-	u_char * p = (u_char *) insert->tuple.data;
-
-	switch (Z_TYPE_P(key)) {
-		case IS_STRING: {
-//			*count_elements  = 1;	//!!!! <------- êîë-âî ýëåìåíòîâ â  êîðòåæå
-//			p += sizeof(uint32_t);
-
-				char * strval = Z_STRVAL_P(key);
-				int str_len = Z_STRLEN_P(key);
-				u_char str_shortlen = (u_char)str_len;
-
-				*(p++) = str_shortlen;
-				memcpy(p, strval, str_len);
-				p += str_len;
-
-//				printf("tuple: len=%d [%s]\n", str_len, strval );
-			}
-			break;
-
-		case IS_LONG: {
-//			*count_elements  = 1;	//!!!! <------- êîë-âî ýëåìåíòîâ â  êîðòåæå
-//			p += sizeof(uint32_t);
-
-			unsigned long val = Z_LVAL_P(key);
-
-		    u_char leb_size = 4; //(u_char)leb128_size( val);
-		    *(p++) = leb_size;
-
-			b2i * pb = (b2i*) p;
-			pb->i = (uint32_t) val;
-//		    leb128_write( (char *)p, val);
-		    p += leb_size;
-
-//			printf("tuple: int %d\n", val );
-			}
-			break;
+	if (size < 2)
+		return false;
 
-		default :
-			zend_throw_exception(zend_exception_get_default(TSRMLS_C),
-			"unsupport key type" ,0 TSRMLS_CC);
-			return;
+	if (!(b[1] & 0x80)) {
+		buf->readed += 2;
+		*val = (b[0] & 0x7f) << 7 | (b[1] & 0x7f);
+		return true;
 	}
 
-	const int insertLen = p-insert->tuple.data;
-
-	UpdateRequest  * incRequest = (UpdateRequest*) p;
-	incRequest->count = 1;
-	incRequest->operation.code = TARANTOOL_OP_ADD;
-	incRequest->operation.fieldNo = fieldNo;
-
-	u_char leb_size = '\4';
-	if (data == 1) {
-		incRequest->operation.arg[0] = leb_size;
-		incRequest->operation.arg[1] = '\1';
-		incRequest->operation.arg[2] = '\0';
-		incRequest->operation.arg[3] = '\0';
-		incRequest->operation.arg[4] = '\0';
+	if (size < 3)
+		return false;
 
-	} else {
-		p = incRequest->operation.arg;
-		*(p++) = leb_size;
-		b2i * pb = (b2i*) p;
-		pb->i = (int) data;
-		p += leb_size;
+	if (!(b[2] & 0x80)) {
+		buf->readed += 3;
+		*val = (b[0] & 0x7f) << 14 | (b[1] & 0x7f) << 7 | (b[2] & 0x7f);
+		return true;
 	}
 
-	header->len = INSERT_REQUEST_SIZE + insertLen + UPDATE_REQUEST_SIZE + (int)leb_size;
+	if (size < 4)
+		return false;
 
-	// write header
-	int len = php_stream_write(ctx->stream, out_buf , HEADER_SIZE); // 12
-	if (len!=HEADER_SIZE) {
-		efree(out_buf);
-		zend_throw_exception(zend_exception_get_default(TSRMLS_C),
-			"error write header" ,0 TSRMLS_CC);
-		return;
+	if (!(b[3] & 0x80)) {
+		buf->readed += 4; 
+		*val = (b[0] & 0x7f) << 21 | (b[1] & 0x7f) << 14 |
+			(b[2] & 0x7f) << 7 | (b[3] & 0x7f);
+		return true;
 	}
 
-//	write tuple
-	p = (u_char*) out_buf+HEADER_SIZE;
-	len = php_stream_write(ctx->stream, (char*)p , header->len ); //
-	if (len != header->len) {
-		efree(out_buf);
-		zend_throw_exception(zend_exception_get_default(TSRMLS_C),
-			"error write body" ,0 TSRMLS_CC);
-		return;
-	}
-
-
-	bzero(out_buf, header->len + HEADER_SIZE);
-//
-	len = php_stream_read(ctx->stream, out_buf, TARANTOOL_BUFSIZE);
+	if (size < 5)
+		return false;
 
-	if ( *(out_buf+HEADER_SIZE) != '\0') {
-		b2i* bb = (b2i*)(out_buf+HEADER_SIZE);
-		ctx->errorcode = bb->i;
-		efree(out_buf);
-		RETURN_FALSE;
+	if (!(b[4] & 0x80)) {
+		buf->readed += 5;
+		*val = (b[0] & 0x7f) << 28 | (b[1] & 0x7f) << 21 |
+			(b[2] & 0x7f) << 14 | (b[3] & 0x7f) << 7 | (b[4] & 0x7f);
+		return true;
 	}
 
-	if( !flag)
-		RETURN_TRUE;
+	return false;
+}
 
-	Response * responseBody = (Response*) (out_buf+HEADER_SIZE);
-	SelectResponseTuple * responseTuple = (SelectResponseTuple*) responseBody->data;
+static bool
+io_buf_read_str(struct io_buf *buf, char **str, size_t len)
+{
+	size_t last = buf->size - buf->readed;
+	if (last < len)
+		return false;
+	*str = (char *)(buf->value + buf->readed);
+	buf->readed += len;
+	return true;
+}
 
-	p = responseBody->data + SELECT_RESPONSE_SIZE;
+static bool
+io_buf_read_field(struct io_buf *buf, zval *tuple)
+{
+	int32_t field_length;
+
+	if (!io_buf_read_varint(buf, &field_length))
+		return false;
+
+	int32_t i32_val;
+	int64_t i64_val;
+	char *str_val;
+	switch (field_length) {
+	case sizeof(int32_t):
+		if (!io_buf_read_int32(buf, &i32_val))
+			return false;
+		add_next_index_long(tuple, i32_val);
+		break;
+	case sizeof(int64_t):
+		if (!io_buf_read_int64(buf, &i64_val))
+			return false;
+		add_next_index_long(tuple, i32_val);
+		break;		
+	default:
+		if (!io_buf_read_str(buf, &str_val, field_length))
+			return false;
+		add_next_index_stringl(tuple, str_val, field_length, true);
+	}
+
+	return true;
+}
 
-	int i=0;
-	int8_t * size;
-	while (i < fieldNo) {
-		size = (int8_t*) p;
-		p += *size+1;
-		i++;
+static bool
+io_buf_read_tuple(struct io_buf *buf, zval **tuple)
+{
+	MAKE_STD_ZVAL(*tuple);
+	if (array_init(*tuple) == FAILURE) {
+		return false;
 	}
 
-	b2i* bb = (b2i*)(p+1);
+	int32_t size;
+	if (!io_buf_read_int32(buf, &size))
+		return false;
 
-	efree(out_buf);
-	RETURN_LONG(bb->i);
+	int32_t cardinality;
+	if (!io_buf_read_int32(buf, &cardinality))
+		return false;
 
+	while (cardinality > 0) {
+		if (!io_buf_read_field(buf, *tuple))
+			return false;
+		cardinality -= 1;
+	}
 
+	return true;
 }
-/* }}} */
 
+static void *
+io_buf_write_struct(struct io_buf *buf, size_t n)
+{
+	if (!io_buf_reserve(buf, buf->size + n))
+		return NULL;
+	void *ptr = buf->value + buf->size;
+	buf->size += n;
+	return ptr;
+}
 
-/* {{{ proto string tarantool::getError();
-   returb tarantool error string */
+static bool
+io_buf_write_byte(struct io_buf *buf, int8_t value)
+{
+	if (!io_buf_reserve(buf, buf->size + sizeof(int8_t)))
+		return false;
+	*(int8_t *)(buf->value + buf->size) = value;
+	buf->size += sizeof(uint8_t);
+	return true;
+}
 
-PHP_METHOD(tarantool_class, getError)
+static bool
+io_buf_write_int32(struct io_buf *buf, int32_t value)
 {
-	zval *id;
-	tarantool_object *ctx;
+	if (!io_buf_reserve(buf, buf->size + sizeof(int32_t)))
+		return false;
+	*(int32_t *)(buf->value + buf->size) = value;
+	buf->size += sizeof(int32_t);
+	return true;
+}
 
-	if (zend_parse_method_parameters(ZEND_NUM_ARGS() TSRMLS_CC, getThis(), "O", &id,
-		tarantool_class_entry) == FAILURE) {
-		return;
-	}
+static bool
+io_buf_write_int64(struct io_buf *buf, int64_t value)
+{
+	if (!io_buf_reserve(buf, buf->size + sizeof(int64_t)))
+		return false;
+	*(int64_t *)(buf->value + buf->size) = value;
+	buf->size += sizeof(int64_t);
+	return true;
+}
 
-	ctx = (tarantool_object *)zend_object_store_get_object(id TSRMLS_CC);
-	if (!ctx) {
-		zend_throw_exception(zend_exception_get_default(TSRMLS_C),
-			"the context is null" ,0 TSRMLS_CC);
-		return;
-	}
+static bool
+io_buf_write_varint(struct io_buf *buf, int32_t value)
+{
+	if (!io_buf_reserve(buf, buf->size + 5))
+		/* reseve maximal varint size (5 bytes) */
+		return false;
 
-	switch(ctx->errorcode) {
-		case 0: { RETURN_STRING("Result Ok",1); break; }
-		case 258: {RETURN_STRING("Non master connection, but it should be",1); break; }
-		case 514: {RETURN_STRING("Illegal parametrs",1); break; }
-		case 770: {RETURN_STRING("Uid not from this storage range",1); break; }
-		case 1025: {RETURN_STRING("Node is marked as read-only",1); break; }
-		case 1281: {RETURN_STRING("Node isn't locked",1); break; }
-		case 1537: {RETURN_STRING("Node is locked",1); break; }
-		case 1793: {RETURN_STRING("Some memory issues",1); break; }
-		case 0x00000802: {RETURN_STRING("Bad graph integrity",1); break; }
-		case 0x00000a02: {RETURN_STRING("Unsupported command",1); break; }
-		case 0x00001801: {RETURN_STRING("Can't register new user",1); break; }
-		case 0x00001a01: {RETURN_STRING("Can't generate alert id",1); break; }
-		case 0x00001b02: {RETURN_STRING("Can't del node",1); break; }
-		case 0x00001c02: {RETURN_STRING("User isn't registered",1); break; }
-		case 0x00001d02: {RETURN_STRING("Syntax error in query",1); break; }
-		case 0x00001e02: {RETURN_STRING("Unknown field",1); break; }
-		case 0x00001f02: {RETURN_STRING("Number value is out of range",1); break; }
-		case 0x00002002: {RETURN_STRING("Insert already existing object",1); break; }
-		case 0x00002202: {RETURN_STRING("Can not order result",1); break; }
-		case 0x00002302: {RETURN_STRING("Multiple to update/delete",1); break; }
-		case 0x00002400: {RETURN_STRING("nothing to do (not an error)",1); break; }
-		case 0x00002502: {RETURN_STRING("id's update",1); break; }
-		case 0x00002602: {RETURN_STRING("unsupported version of protocol",1); break; }
-
-		case 0x00002702: {RETURN_STRING("Unknow error",1); break; }
-		case 0x00003102: {RETURN_STRING("Node not found",1); break; }
-
-		case 0x00003702: {RETURN_STRING("Node found",1); break; }
-		case 0x00003802: {RETURN_STRING("INDEX violation",1); break; }
-		case 0x00003902: {RETURN_STRING("No such space",1); break; }
-
-		default : {
-				char * err_string;
-				int len = spprintf(&err_string, 0, "Unknow error code : %X\n", ctx->errorcode);
-				RETVAL_STRINGL(err_string, len,1);
-				efree(err_string);
+	if (value >= (1 << 7)) {
+		if (value >= (1 << 14)) {
+			if (value >= (1 << 21)) {
+				if (value >= (1 << 28))
+					io_buf_write_byte(buf, (int8_t)(value >> 28) | 0x80);
+				io_buf_write_byte(buf, (int8_t)(value >> 21) | 0x80);
 			}
+			io_buf_write_byte(buf, (int8_t)((value >> 14) | 0x80));
+		}
+		io_buf_write_byte(buf, (int8_t)((value >> 7) | 0x80));
 	}
+	io_buf_write_byte(buf, (int8_t)((value) & 0x7F));
 
+	return true;
 }
-/* }}}*/
-
 
-/* {{{ proto string tarantool::getInfo();
-   return tarantool info string */
-PHP_METHOD(tarantool_class, getInfo)
+static bool
+io_buf_write_str(struct io_buf *buf, uint8_t *str, size_t len)
 {
-	zval *id;
-	tarantool_object *ctx;
-	size_t response_len=0;
+	if (!io_buf_reserve(buf, buf->size + len))
+		return false;
 
-	if (zend_parse_method_parameters(ZEND_NUM_ARGS() TSRMLS_CC, getThis(), "O", &id,
-		tarantool_class_entry) == FAILURE) {
-		return;
-	}
-
-	ctx = (tarantool_object *)zend_object_store_get_object(id TSRMLS_CC);
-	if (!ctx) {
-		zend_throw_exception(zend_exception_get_default(TSRMLS_C),
-			"the context is null" ,0 TSRMLS_CC);
-		return;
-	}
-
-	if (!ctx->admin_stream) {
-		if (php_tnt_admin_connect(ctx TSRMLS_CC)) {
-			zend_throw_exception(zend_exception_get_default(TSRMLS_C),
-				"the can't open remote host " ,0 TSRMLS_DC);
-			return;
-		}
-	}
+	memcpy(buf->value + buf->size, str, len);
+	buf->size += len;
+	return true;
+}
 
-	if (php_stream_write(ctx->admin_stream, TARANTOOL_SHOW_INFO, TARANTOOL_SHOW_INFO_SIZE) != TARANTOOL_SHOW_INFO_SIZE) {
-		zend_throw_exception(zend_exception_get_default(TSRMLS_C),
-			"Failed sending command" ,0 TSRMLS_DC);
-		return;
-	}
+static bool
+io_buf_write_field_int32(struct io_buf *buf, uint32_t value)
+{
+	/* write field length (4 bytes) */
+	if (!io_buf_write_varint(buf, sizeof(int32_t)))
+		return false;
+	/* write field value */
+	if (!io_buf_write_int32(buf, value))
+		return false;
+	return true;
+}
 
-	char * buf=emalloc(TARANTOOL_SMALL_BUFSIZE);
-	bzero(buf,TARANTOOL_SMALL_BUFSIZE);
+static bool
+io_buf_write_field_int64(struct io_buf *buf, uint64_t value)
+{
+	/* write field length (8 bytes) */
+	if (!io_buf_write_varint(buf, sizeof(int64_t)))
+		return false;
+	/* write field value */
+	if (!io_buf_write_int64(buf, value))
+		return false;
+	return true;
+}
 
-	response_len = php_stream_read(ctx->admin_stream, buf, TARANTOOL_SMALL_BUFSIZE);
+static bool
+io_buf_write_field_str(struct io_buf *buf, uint8_t *field_value, size_t field_length)
+{
+	/* write field length (string length) */
+	if (!io_buf_write_varint(buf, (int32_t)field_length))
+		return false;
+	/* write field value (string) */
+	if (!io_buf_write_str(buf, field_value, field_length))
+		return false;
+	return true;
+}
 
-	if (response_len) {
-		RETVAL_STRINGL(buf,response_len,1);
-		efree(buf);
-		return;
+static bool
+io_buf_write_tuple_int(struct io_buf *buf, zval *tuple)
+{
+	/* single field tuple: (int) */
+	long long_value = Z_LVAL_P(tuple);
+	/* write tuple cardinality */
+	if (!io_buf_write_int32(buf, 1))
+		return false;
+	/* write field */
+	if ((unsigned long)long_value <= 0xffffffffllu) {
+		if (!io_buf_write_field_int32(buf, (uint32_t)long_value))
+			return false;
+	} else {
+		if (!io_buf_write_field_int64(buf, (uint64_t)long_value))
+			return false;
 	}
 
-	efree(buf);
-	RETURN_FALSE;
-
+	return true;
 }
-/* }}}*/
 
-/* {{{ proto string tarantool::getConf();
-   returb tarantool info string */
-PHP_METHOD(tarantool_class, getConf)
+static bool
+io_buf_write_tuple_str(struct io_buf *buf, zval *tuple)
 {
-	zval *id;
-	tarantool_object *ctx;
-	size_t response_len=0;
-
-	if (zend_parse_method_parameters(ZEND_NUM_ARGS() TSRMLS_CC, getThis(), "O", &id,
-		tarantool_class_entry) == FAILURE) {
-		return;
-	}
-
-	ctx = (tarantool_object *)zend_object_store_get_object(id TSRMLS_CC);
-	if (!ctx) {
-		zend_throw_exception(zend_exception_get_default(TSRMLS_C),
-			"the context is null" ,0 TSRMLS_CC);
-		return;
-	}
+	/* single field tuple: (string) */
+	char *str_value = Z_STRVAL_P(tuple);
+	size_t str_length = Z_STRLEN_P(tuple);
+	/* write tuple cardinality */
+	if (!io_buf_write_int32(buf, 1))
+		return false;
+	/* write field */
+	if (!io_buf_write_field_str(buf, str_value, str_length))
+		return false;
+
+	return true;
+}
 
-	if (!ctx->admin_stream) {
-		if (php_tnt_admin_connect(ctx TSRMLS_CC)) {
-			zend_throw_exception(zend_exception_get_default(TSRMLS_C),
-				"the can't open remote host " ,0 TSRMLS_DC);
-			return;
+static bool
+io_buf_write_tuple_array(struct io_buf *buf, zval *tuple)
+{
+	/* multyply tuple array */
+	HashTable *hash = Z_ARRVAL_P(tuple);
+	HashPosition itr;
+	zval **field;
+	/* put tuple cardinality */
+	io_buf_write_int32(buf, zend_hash_num_elements(hash));
+	for (zend_hash_internal_pointer_reset_ex(hash, &itr);
+		 zend_hash_get_current_data_ex(hash, (void **) &field, &itr) == SUCCESS;
+		 zend_hash_move_forward_ex(hash, &itr)) {
+		char *str_value;
+		size_t str_length;
+		long long_value;
+
+		switch (Z_TYPE_PP(field)) {
+		case IS_STRING:
+			/* string field */
+			str_value = Z_STRVAL_PP(field);
+			str_length = Z_STRLEN_PP(field);
+			io_buf_write_field_str(buf, str_value, str_length);
+			break;
+		case IS_LONG:
+			/* integer field */
+			long_value = Z_LVAL_PP(field);
+			io_buf_write_field_str(buf, (uint8_t *)&long_value, sizeof(int32_t));
+			break;
+		default:
+			zend_throw_exception_ex(zend_exception_get_default(TSRMLS_C), 0 TSRMLS_DC,
+									"unsupported field type");
+			return false;
 		}
 	}
 
-	if (php_stream_write(ctx->admin_stream, TARANTOOL_SHOW_CONF, TARANTOOL_SHOW_CONF_SIZE) != TARANTOOL_SHOW_CONF_SIZE) {
-		zend_throw_exception(zend_exception_get_default(TSRMLS_C),
-			"Failed sending command" ,0 TSRMLS_DC);
-		return;
-	}
-
-	char * buf = emalloc(TARANTOOL_BUFSIZE);
-	response_len = php_stream_read(ctx->admin_stream, buf, TARANTOOL_BUFSIZE);
-
-	if (response_len) {
-		RETVAL_STRINGL(buf,response_len,1);
-		efree(buf);
-		return;
-	}
-
-	efree(buf);
-	RETURN_FALSE;
-
+	return true;
 }
-/* }}}*/
 
-/* {{{ proto string tarantool::getStat();
-   returb tarantool statistic string */
-PHP_METHOD(tarantool_class, getStat)
+static bool
+io_buf_write_tuple(struct io_buf *buf, zval *tuple)
 {
-	zval *id;
-	tarantool_object *ctx;
-	size_t response_len=0;
-
-	if (zend_parse_method_parameters(ZEND_NUM_ARGS() TSRMLS_CC, getThis(), "O", &id,
-		tarantool_class_entry) == FAILURE) {
-		return;
-	}
-
-	ctx = (tarantool_object *)zend_object_store_get_object(id TSRMLS_CC);
-	if (!ctx) {
-		zend_throw_exception(zend_exception_get_default(TSRMLS_C),
-			"the context is null" ,0 TSRMLS_CC);
-		return;
-	}
+	/* write tuple by type */
+	switch (Z_TYPE_P(tuple)) {
+	case IS_LONG:
+		/* write integer as tuple */
+		return io_buf_write_tuple_int(buf, tuple);
+	case IS_STRING:
+		/* write string as tuple */
+		return io_buf_write_tuple_str(buf, tuple);
+	case IS_ARRAY:
+		/* write array as tuple */
+		return io_buf_write_tuple_array(buf, tuple);
+	default:
+		zend_throw_exception_ex(zend_exception_get_default(TSRMLS_C), 0 TSRMLS_DC,
+								"unsupported tuple type");
+		return false;		
+	}
+
+	return true;
+}
 
-	if (!ctx->admin_stream) {
-		if (php_tnt_admin_connect(ctx TSRMLS_CC)) {
-			zend_throw_exception(zend_exception_get_default(TSRMLS_C),
-				"the can't open remote host " ,0 TSRMLS_DC);
-			return;
+static bool
+io_buf_write_tuples_list_array(struct io_buf *buf, zval *tuples_list)
+{
+	HashTable *hash = Z_ARRVAL_P(tuples_list);
+	HashPosition itr;
+	zval **tuple;
+
+	/* write number of tuples */
+	if (!io_buf_write_int32(buf, zend_hash_num_elements(hash)))
+		return false;
+
+	/* write tuples */
+	for (zend_hash_internal_pointer_reset_ex(hash, &itr);
+		 zend_hash_get_current_data_ex(hash, (void **) &tuple, &itr) == SUCCESS;
+		 zend_hash_move_forward_ex(hash, &itr)) {
+		if (Z_TYPE_PP(tuple) != IS_ARRAY) {
+			zend_throw_exception_ex(zend_exception_get_default(TSRMLS_C), 0 TSRMLS_DC,
+									"invalid tuples list: expected array of array");
+			return false;
 		}
-	}
 
-	if (php_stream_write(ctx->admin_stream, TARANTOOL_SHOW_STAT, TARANTOOL_SHOW_STAT_SIZE) != TARANTOOL_SHOW_STAT_SIZE) {
-		zend_throw_exception(zend_exception_get_default(TSRMLS_C),
-			"Failed sending command" ,0 TSRMLS_DC);
-		return;
+		if (!io_buf_write_tuple_array(buf, *tuple))
+			return false;
 	}
 
-	char * buf = emalloc(TARANTOOL_SMALL_BUFSIZE);
-	bzero(buf,TARANTOOL_SMALL_BUFSIZE);
-
-	response_len = php_stream_read(ctx->admin_stream, buf, TARANTOOL_SMALL_BUFSIZE);
+	return true;
+}
 
-	if (response_len) {
-		RETVAL_STRINGL(buf,response_len,1);
-		efree(buf);
-		return;
-	}
 
-	efree(buf);
-	RETURN_FALSE;
+static bool
+io_buf_write_tuples_list(struct io_buf *buf, zval *tuples_list)
+{
+	HashTable *hash;
+	HashPosition itr;
+	zval **tuple;
+
+	switch (Z_TYPE_P(tuples_list)) {
+	case IS_LONG:
+		/* single tuple: long */
+		/* write number of tuples */
+		if (!io_buf_write_int32(buf, 1))
+			return false;
+		/* write tuple */
+		if (!io_buf_write_tuple_int(buf, tuples_list))
+			return false;
+		break;
+	case IS_STRING:
+		/* single tuple: string */
+		/* write number of tuples */
+		if (!io_buf_write_int32(buf, 1))
+			return false;		
+		/* write tuple */
+		if (!io_buf_write_tuple_str(buf, tuples_list))
+			return false;
+		break;
+	case IS_ARRAY:
+		/* array: migth be single or multi tuples array */
+		hash = Z_ARRVAL_P(tuples_list);
+		zend_hash_internal_pointer_reset_ex(hash, &itr);		
+		if (zend_hash_get_current_data_ex(hash, (void **) &tuple, &itr) != SUCCESS) {
+			zend_throw_exception_ex(zend_exception_get_default(TSRMLS_C), 0 TSRMLS_DC,
+									"invalid tuples list: empty array");
+			return false;
+		}
 
-}
-/* }}}*/
+		/* check type of the first element */
+		switch (Z_TYPE_PP(tuple)) {
+		case IS_STRING:
+		case IS_LONG:
+			/* single tuple: array */
+			/* write tuples count */
+			if (!io_buf_write_int32(buf, 1))
+				return false;
+			/* write tuple */
+			if (!io_buf_write_tuple_array(buf, tuples_list))
+				return false;
+			break;
+		case IS_ARRAY:
+			/* multi tuples list */
+			if (!io_buf_write_tuples_list_array(buf, tuples_list))
+				return false;
+			break;
+		default:
+			/* invalid element type */
+			zend_throw_exception_ex(zend_exception_get_default(TSRMLS_C), 0 TSRMLS_DC,
+									"unsupported tuple type");
+			return false;
+		}
 
-static void printLine( u_char *p ) {
-	u_char b[4];
-	memcpy(b, p, 4);
-	php_printf("%x %x %x %x\t\t", b[0], b[1], b[2], b[3]);
-}
+		break;
+	default:
+		zend_throw_exception_ex(zend_exception_get_default(TSRMLS_C), 0 TSRMLS_DC,
+								"unsupported tuple type");
+		return false;		
+	}
 
-static void printLine3( u_char *p ) {
-	u_char b[12];
-	memcpy(b, p, 12);
-	php_printf("%x %x %x %x\t\t", b[0], b[1], b[2], b[3]);
-	php_printf("%x %x %x %x\t\t", b[4], b[5], b[6], b[7]);
-	php_printf("%x %x %x %x\n", b[8], b[9], b[10], b[11]);
+	return true;
 }
 
+/*
+ * I/O buffer send/recv 
+ */
 
-static void
-leb128_write(char * buf, unsigned long value)
+static bool
+io_buf_send_yaml(php_stream *stream, struct io_buf *buf)
 {
-	if (value >= (1 << 7)) {
-		if (value >= (1 << 14)) {
-			if (value >= (1 << 21)) {
-				if (value >= (1 << 28))
-					*(buf++) = (value >> 28) | 0x80;
-				*(buf++) = (value >> 21) | 0x80;
-			}
-			*(buf++) = ((value >> 14) | 0x80);
-		}
-		*(buf++) = ((value >> 7) | 0x80);
+	if (php_stream_write(stream,
+						 buf->value,
+						 buf->size) != buf->size) {
+		zend_throw_exception_ex(zend_exception_get_default(TSRMLS_C), 0 TSRMLS_DC,
+								"send message fail");
+		return false;
 	}
-	*(buf++) = ((value) & 0x7F);
 
+	return true;
 }
 
-static int
-leb128_read(char * buf, int size, unsigned long * value)
+static bool
+io_buf_recv_yaml(php_stream *stream, struct io_buf *buf)
 {
-	*value = 0;
-
-	if (size < 1)
-		return -1;
-
-	if (!(buf[0] & 0x80)) {
-
-		*value = buf[0] & 0x7f;
-		return 1;
+	char *line = php_stream_get_line(stream, NULL, 0, NULL);
+	while (strcmp(line, ADMIN_TOKEN_BEGIN) != 0) {
+		line = php_stream_get_line(stream, NULL, 0, NULL);
 	}
 
-	if (size < 2)
-		return -1;
-
-	if (!(buf[1] & 0x80)) {
-
-		*value = (buf[0] & 0x7f) << 7 |
-		         (buf[1] & 0x7f);
-		return 2;
+	line = php_stream_get_line(stream, NULL, 0, NULL);
+	while (strcmp(line, ADMIN_TOKEN_END) != 0) {
+		io_buf_write_str(buf, line, strlen(line));
+		line = php_stream_get_line(stream, NULL, 0, NULL);
 	}
 
-	if (size < 3)
-		return -1;
+	return true;
+}
 
-	if (!(buf[2] & 0x80)) {
+static bool
+io_buf_send_iproto(php_stream *stream, int32_t type, int32_t request_id, struct io_buf *buf)
+{
+	/* send iproto header */
+	struct iproto_header header;
+	header.type = type;
+	header.length = buf->size;
+	header.request_id = request_id;
 
-		*value = (buf[0] & 0x7f) << 14 |
-				 (buf[1] & 0x7f) << 7  |
-				 (buf[2] & 0x7f);
-		return 3;
+	size_t length = sizeof(struct iproto_header);
+	if (php_stream_write(stream, (char *) &header, length) != length) {
+		zend_throw_exception_ex(zend_exception_get_default(TSRMLS_C), 0 TSRMLS_DC,
+								"send requset failed");
+		return false;
 	}
 
-	if (size < 4)
-		return -1;
-
-	if (!(buf[3] & 0x80)) {
-
-		*value = (buf[0] & 0x7f) << 21 |
-				 (buf[1] & 0x7f) << 14 |
-				 (buf[2] & 0x7f) << 7  |
-				 (buf[3] & 0x7f);
-		return 4;
+	/* send requets */
+	if (php_stream_write(stream, buf->value, buf->size) != buf->size) {
+		zend_throw_exception_ex(zend_exception_get_default(TSRMLS_C), 0 TSRMLS_DC,
+								"send requset failed");
+		return false;
 	}
 
-	if (size < 5)
-		return -1;
+	return true;
+}
 
-	if (!(buf[4] & 0x80)) {
+static bool
+io_buf_recv_iproto(php_stream *stream, struct io_buf *buf)
+{
+	/* receiving header */
+	struct iproto_header header;
+	size_t length = sizeof(struct iproto_header);
+	if (php_stream_read(stream, (char *) &header, length) != length) {
+		zend_throw_exception_ex(zend_exception_get_default(TSRMLS_C), 0 TSRMLS_DC,
+								"receive response failed");
+		return false;
+	}
 
-		*value = (buf[0] & 0x7f) << 28 |
-		         (buf[1] & 0x7f) << 21 |
-				 (buf[2] & 0x7f) << 14 |
-				 (buf[3] & 0x7f) << 7  |
-				 (buf[4] & 0x7f);
-		return 5;
+	/* receiving body */
+	if (!io_buf_resize(buf, header.length))
+		return false;
+	if (php_stream_read(stream, buf->value, buf->size) != buf->size) {
+		zend_throw_exception_ex(zend_exception_get_default(TSRMLS_C), 0 TSRMLS_DC,
+								"receive response failed");
+		return false;
 	}
 
-	return -1;
+	return true;
 }
 
 
-static int
-leb128_size(unsigned long value)
-{
-	if (value < (1 << 7))
-		return 1;
-
-	if (value < (1 << 14))
-		return 2;
+/*----------------------------------------------------------------------------*
+ * support local functions
+ *----------------------------------------------------------------------------*/
 
-	if (value < (1 << 21))
-		return 3;
-
-	if (value < (1 << 28))
-		return 4;
+static zend_object_value
+alloc_tarantool_object(zend_class_entry *entry TSRMLS_DC)
+{
+	zend_object_value new_value;
 
-	return 5;
+	/* allocate and clean-up instance */
+	tarantool_object *tnt = (tarantool_object *) emalloc(sizeof(tarantool_object));
+	/* TODO: emalloc result must be checked */
+	memset(tnt, 0, sizeof(tarantool_object));
+
+	/* initialize class instance */
+	zend_object_std_init(&tnt->zo, entry TSRMLS_CC);
+	new_value.handle = zend_objects_store_put(
+		tnt,
+		(zend_objects_store_dtor_t) zend_objects_destroy_object, 
+		(zend_objects_free_object_storage_t) free_tarantool_object,
+		NULL TSRMLS_CC);
+ 	new_value.handlers = zend_get_std_object_handlers();
+	return new_value;
 }
 
 static void
-tarantool_dtor(void *object TSRMLS_DC)
+free_tarantool_object(tarantool_object *tnt TSRMLS_DC)
 {
-	tarantool_object *ctx = (tarantool_object*)object;
-
-	if (ctx) {
-		if (ctx->stream) {
-			php_stream_close(ctx->stream);
-		}
-
-		if (ctx->admin_stream) {
-			php_stream_close(ctx->admin_stream);
-		}
-
-		if (ctx->host) {
-			efree(ctx->host);
-		}
-
-	}
+	if (tnt == NULL)
+		return;
 
-	zend_object_std_dtor(&ctx->zo TSRMLS_CC);
+	if (tnt->stream)
+		php_stream_close(tnt->stream);
 
-	efree(object);
+	if (tnt->admin_stream)
+		php_stream_close(tnt->admin_stream);
 
+	io_buf_destroy(tnt->io_buf);
+	io_buf_destroy(tnt->splice_field);
+	efree(tnt);
 }
 
-static zend_object_value tarantool_ctor(zend_class_entry *ce TSRMLS_DC)
+static php_stream *
+establish_connection(char *host, int port)
 {
-	zend_object_value new_value;
-	tarantool_object* obj = (tarantool_object*)emalloc(sizeof(tarantool_object));
-	memset(obj, 0, sizeof(tarantool_object));
-
-	zend_object_std_init(&obj->zo, ce TSRMLS_CC);
-
-	new_value.handle = zend_objects_store_put(obj, (zend_objects_store_dtor_t)zend_objects_destroy_object,
-		(zend_objects_free_object_storage_t)tarantool_dtor, NULL TSRMLS_CC);
-	new_value.handlers = zend_get_std_object_handlers();
-
-	return new_value;
+	char *msg = NULL;
+	/* initialize connection parameters */
+	char *dest_addr = NULL;
+	size_t dest_addr_len = spprintf(&dest_addr, 0, "tcp://%s:%d", host, port);
+	int options = ENFORCE_SAFE_MODE | REPORT_ERRORS;
+	int flags = STREAM_XPORT_CLIENT | STREAM_XPORT_CONNECT;
+	struct timeval timeout = {
+		.tv_sec = TARANTOOL_TIMEOUT_SEC,
+		.tv_usec = TARANTOOL_TIMEOUT_USEC,
+	};
+	char *error_msg = NULL;
+	int error_code = 0;
+
+	/* establish connection */
+	php_stream *stream = php_stream_xport_create(dest_addr, dest_addr_len,
+												 options, flags,
+												 NULL, &timeout, NULL,
+												 &error_msg, &error_code);
+	efree(dest_addr);
+
+	/* check result */
+	if (error_code && error_msg) {
+		zend_throw_exception_ex(zend_exception_get_default(TSRMLS_C), 0 TSRMLS_DC,
+								"establist connection fail: %s", error_msg);
+		efree(error_msg);
+		return NULL;
+	}
+
+	return stream;
 }
 
-/* {{{ PHP_MINIT_FUNCTION
- */
-PHP_MINIT_FUNCTION(tarantool)
+static bool
+hash_fing_long(HashTable *hash, char *key, long *value)
 {
-	zend_class_entry ce;
-
-	INIT_CLASS_ENTRY(ce, "Tarantool", tarantool_class_functions);
-	ce.create_object = tarantool_ctor;
-	tarantool_class_entry = zend_register_internal_class(&ce TSRMLS_CC);
-
-	return SUCCESS;
+	zval **zvalue = NULL;
+	if (zend_hash_find(hash, key, strlen(key) + 1, (void **)&zvalue) != SUCCESS)
+		return false;
+	if (Z_TYPE_PP(zvalue) != IS_LONG)
+		return false;
+	*value = Z_LVAL_PP(zvalue);
+	return true;
 }
-/* }}} */
 
-/* {{{ PHP_MSHUTDOWN_FUNCTION
- */
-PHP_MSHUTDOWN_FUNCTION(tarantool)
+static bool
+hash_fing_str(HashTable *hash, char *key, char **value, int *value_length)
 {
-	/* uncomment this line if you have INI entries
-	UNREGISTER_INI_ENTRIES();
-	*/
-	return SUCCESS;
+	zval **zvalue = NULL;
+	if (zend_hash_find(hash, key, strlen(key) + 1, (void **)&zvalue) != SUCCESS)
+		return false;
+	if (Z_TYPE_PP(zvalue) != IS_STRING)
+		return false;
+	*value = Z_STRVAL_PP(zvalue);
+	*value_length = Z_STRLEN_PP(zvalue);
+	return true;
 }
-/* }}} */
 
-
-/* {{{ PHP_MINFO_FUNCTION
- */
-PHP_MINFO_FUNCTION(tarantool)
+static bool
+hash_fing_scalar(HashTable *hash, char *key, zval ***value)
 {
-	php_info_print_table_start();
-	php_info_print_table_header(2, "tarantool_box support", "enabled");
-	php_info_print_table_row(2,		"default host", TARANTOOL_DEF_HOST);
-	php_info_print_table_row(2,		"default port", TARANTOOL_DEF_PORT);
-	php_info_print_table_row(2,		"admin port",	TARANTOOL_ADMIN_PORT);
-	php_info_print_table_row(2,		"timeout in sec",TARANTOOL_TIMEOUT);
-	php_info_print_table_end();
-
-	/* Remove comments if you have entries in php.ini
-	DISPLAY_INI_ENTRIES();
-	*/
+	if (zend_hash_find(hash, key, strlen(key) + 1, (void **)value) != SUCCESS)
+		return false;
+	if (Z_TYPE_PP(*value) != IS_STRING && Z_TYPE_PP(*value) != IS_LONG)
+		return false;
+	return true;
 }
-/* }}} */
+
 
 /*
  * Local variables:
diff --git a/connector/php/tarantool.h b/connector/php/tarantool.h
index a05dfeacfd5f7549c81e1e98b28f41c6b3049a7b..74a5635185dd2db3dbf7abe6098f799cd31d74e1 100644
--- a/connector/php/tarantool.h
+++ b/connector/php/tarantool.h
@@ -16,79 +16,165 @@
   | Copyright (c) 2011                                                   |
   +----------------------------------------------------------------------+
 */
-
-/* $Id: header 252479 2008-02-07 19:39:50Z iliaa $ */
-
 #ifndef PHP_TARANTOOL_H
 #define PHP_TARANTOOL_H
 
-extern zend_module_entry tarantool_module_entry;
-#define phpext_tarantool_ptr &tarantool_module_entry
-
-#ifdef PHP_WIN32
-#	define PHP_TARANTOOL_API __declspec(dllexport)
-#elif defined(__GNUC__) && __GNUC__ >= 4
-#	define PHP_TARANTOOL_API __attribute__ ((visibility("default")))
-#else
-#	define PHP_TARANTOOL_API
+#ifdef ZTS
+#include "TSRM.h"
 #endif
 
-#define TARANTOOL_TIMEOUT  5 // sec
-#define TARANTOOL_DEF_PORT 33013
-#define TARANTOOL_ADMIN_PORT 33015
 
-#define TARANTOOL_DEF_HOST "localhost"
-#define TARANTOOL_BUFSIZE  2048 
-#define TARANTOOL_SMALL_BUFSIZE  256 
+/*============================================================================*
+ * Constants
+ *============================================================================*/
+
+#define TARANTOOL_EXTENSION_VERSION "1.0"
+
+
+/*----------------------------------------------------------------------------*
+ * tbuf constants
+ *----------------------------------------------------------------------------*/
+
+enum {
+	/* tbuf minimal capacity */
+	IO_BUF_CAPACITY_MIN = 128,
+	/* tbuf factor */
+	IO_BUF_CAPACITY_FACTOR = 2,
+};
+
+
+/*----------------------------------------------------------------------------*
+ * Connections constants
+ *----------------------------------------------------------------------------*/
+
+enum {
+	/* timeout: seconds */
+	TARANTOOL_TIMEOUT_SEC = 5,
+	/* timeout: microseconds */
+	TARANTOOL_TIMEOUT_USEC = 0,
+	/* tarantool default primary port */
+	TARANTOOL_DEFAULT_PORT = 33013,
+	/* tarantool default readonly port */
+	TARANTOOL_DEFAULT_RO_PORT = 33014,
+	/* tarantool default adnim port */
+	TARANTOOL_DEFAULT_ADMIN_PORT = 33015,
+};
+
+#define TARANTOOL_DEFAULT_HOST "localhost"
+
+
+/*----------------------------------------------------------------------------*
+ * Commands constants
+ *----------------------------------------------------------------------------*/
+
+/* tarantool/box flags */
+enum {
+	/* return resulting tuples */
+	TARANTOOL_FLAGS_RETURN_TUPLE = 0x01,
+	/* insert is add operation: errro will be raised if tuple exists */
+	TARANTOOL_FLAGS_ADD = 0x02,
+	/* insert is replace operation: errro will be raised if tuple doesn't exist */
+	TARANTOOL_FLAGS_REPLACE = 0x04,
+	/* doesn't write command to WAL */
+	TARANTOOL_FLAGS_NOT_STORE = 0x10,
+};
+
+/* tarantool command codes */
+enum {
+	/* insert/replace command code */
+	TARANTOOL_COMMAND_INSERT = 13,
+	/* select command code */
+	TARANTOOL_COMMAND_SELECT = 17,
+	/* update fields command code */
+	TARANTOOL_COMMAND_UPDATE = 19,
+	/* delete command code */
+	TARANTOOL_COMMAND_DELETE = 21,
+	/* call lua function command code */
+	TARANTOOL_COMMAND_CALL = 22,
+	/* pid command code */
+	TARANTOOL_COMMAND_PING = 65280,
+};
+
+/* update fields operation codes */
+enum {
+	/* update fields: assing field value operation code */
+	TARANTOOL_OP_ASSIGN = 0,
+	/* update fields: add operation code */
+	TARANTOOL_OP_ADD = 1,
+	/* update fields: and operation code */
+	TARANTOOL_OP_AND = 2,
+	/* update fields: xor operation code */
+	TARANTOOL_OP_XOR = 3,
+	/* update fields: or operation code */
+	TARANTOOL_OP_OR	= 4,
+	/* update fields: splice operation code */
+	TARANTOOL_OP_SPLICE = 5,
+};
+
+
+/*----------------------------------------------------------------------------*
+ * Amdin commands
+ *----------------------------------------------------------------------------*/
+
+/* admin protocol separator */
+#define ADMIN_SEPARATOR "\r\n"
+/* admin command begin token */
+#define ADMIN_TOKEN_BEGIN "---"ADMIN_SEPARATOR
+/* admin command end token */
+#define ADMIN_TOKEN_END "..."ADMIN_SEPARATOR
+
+/* show information admin command */
+#define ADMIN_COMMAND_SHOW_INFO "show info"
+/* show statistic admin command */
+#define ADMIN_COMMAND_SHOW_STAT "show stat"
+/* show configuration admin command */
+#define ADMIN_COMMAND_SHOW_CONF "show configuration"
+
+
+/*============================================================================*
+ * Interaface decalaration
+ *============================================================================*/
+
+
+/*----------------------------------------------------------------------------*
+ * Tarantool module interface
+ *----------------------------------------------------------------------------*/
+
+/* initialize module function */
+PHP_MINIT_FUNCTION(tarantool);
+
+/* shutdown module function */
+PHP_MSHUTDOWN_FUNCTION(tarantool);
 
-#define TARANTOOL_INSERT  13 
-#define TARANTOOL_SELECT  17 
-#define TARANTOOL_UPDATE  19 
-#define TARANTOOL_DELETE  20 
-#define TARANTOOL_CALL    22
-#define TARANTOOL_PING	  65280
+/* show information about this module */
+PHP_MINFO_FUNCTION(tarantool);
 
-#define TARANTOOL_REQUEST_ID  8 
 
-#define TARANTOOL_OP_ASSIGN 0 
-#define TARANTOOL_OP_ADD	1 
-#define TARANTOOL_OP_AND	2
-#define TARANTOOL_OP_XOR	3
-#define TARANTOOL_OP_OR		4
+/*----------------------------------------------------------------------------*
+ * Tarantool class interface
+ *----------------------------------------------------------------------------*/
 
-#define TARANTOOL_SHOW_INFO "show info\r\n"
-#define TARANTOOL_SHOW_INFO_SIZE sizeof(TARANTOOL_SHOW_INFO) 
+/* class constructor */
+PHP_METHOD(tarantool_class, __construct);
 
-#define TARANTOOL_SHOW_STAT "show stat\r\n"
-#define TARANTOOL_SHOW_STAT_SIZE sizeof(TARANTOOL_SHOW_STAT) 
+/* do select operation */
+PHP_METHOD(tarantool_class, select);
 
-#define TARANTOOL_SHOW_CONF "show configuration\n"
-#define TARANTOOL_SHOW_CONF_SIZE sizeof(TARANTOOL_SHOW_CONF) 
+/* do insert operation */
+PHP_METHOD(tarantool_class, insert);
 
+/* do update fields operation */
+PHP_METHOD(tarantool_class, update_fields);
 
-#ifdef ZTS
-#include "TSRM.h"
-#endif
+/* do delete operation */
+PHP_METHOD(tarantool_class, delete);
 
-PHP_MINIT_FUNCTION(tarantool);
-PHP_MSHUTDOWN_FUNCTION(tarantool);
+/* call lua funtion operation */
+PHP_METHOD(tarantool_class, call);
 
-PHP_MINFO_FUNCTION(tarantool);
+/* do admin command */
+PHP_METHOD(tarantool_class, admin);
 
-PHP_METHOD( tarantool_class, __construct);
-
-PHP_METHOD( tarantool_class, insert);
-PHP_METHOD( tarantool_class, select);
-PHP_METHOD( tarantool_class, mselect);
-PHP_METHOD( tarantool_class, call);
-PHP_METHOD( tarantool_class, getTuple);
-PHP_METHOD( tarantool_class, delete);
-PHP_METHOD( tarantool_class, update);
-PHP_METHOD( tarantool_class, inc);
-PHP_METHOD( tarantool_class, getError);
-PHP_METHOD( tarantool_class, getInfo);
-PHP_METHOD( tarantool_class, getStat);
-PHP_METHOD( tarantool_class, getConf);
 
 #ifdef ZTS
 #define TARANTOOL_G(v) TSRMG(tarantool_globals_id, zend_tarantool_globals *, v)
@@ -96,8 +182,7 @@ PHP_METHOD( tarantool_class, getConf);
 #define TARANTOOL_G(v) (tarantool_globals.v)
 #endif
 
-#endif	
-/* PHP_TARANTOOL_H */
+#endif /* PHP_TARANTOOL_H */
 
 /*
  * Local variables:
diff --git a/connector/php/test/admin.phpt b/connector/php/test/admin.phpt
new file mode 100644
index 0000000000000000000000000000000000000000..3880b11722b7b5733d216f001fd6a6955830893c
--- /dev/null
+++ b/connector/php/test/admin.phpt
@@ -0,0 +1,92 @@
+--TEST--
+Tarantool/box administation commands test
+--FILE--
+<?php
+include "lib/php/tarantool_utest.php";
+
+$tarantool = new Tarantool("localhost", 33013, 33015);
+
+echo "---------- test begin ----------\n";
+echo "help\n";
+echo $tarantool->admin("help");
+echo "----------- test end -----------\n\n";
+
+echo "---------- test begin ----------\n";
+echo "insert\n";
+for ($i = 0; $i < 10; ++$i)
+    echo $tarantool->admin("lua box.insert(0, $i, 'test_id1', 'test field #$i')");
+echo "----------- test end -----------\n\n";
+
+echo "---------- test begin ----------\n";
+echo "select\n";
+echo $tarantool->admin("lua box.select(0, 1, 'test_id1')");
+echo "----------- test end -----------\n\n";
+
+echo "---------- test begin ----------\n";
+echo "delete\n";
+for ($i = 0; $i < 10; ++$i)
+    echo $tarantool->admin("lua box.delete(0, $i)");
+echo "----------- test end -----------\n\n";
+?>
+===DONE===
+--EXPECT--
+---------- test begin ----------
+help
+available commands:
+ - help
+ - exit
+ - show info
+ - show fiber
+ - show configuration
+ - show slab
+ - show palloc
+ - show stat
+ - save coredump
+ - save snapshot
+ - lua command
+ - reload configuration
+----------- test end -----------
+
+---------- test begin ----------
+insert
+ - 0: {'test_id1', 'test field #0'}
+ - 1: {'test_id1', 'test field #1'}
+ - 2: {'test_id1', 'test field #2'}
+ - 3: {'test_id1', 'test field #3'}
+ - 4: {'test_id1', 'test field #4'}
+ - 5: {'test_id1', 'test field #5'}
+ - 6: {'test_id1', 'test field #6'}
+ - 7: {'test_id1', 'test field #7'}
+ - 8: {'test_id1', 'test field #8'}
+ - 9: {'test_id1', 'test field #9'}
+----------- test end -----------
+
+---------- test begin ----------
+select
+ - 0: {'test_id1', 'test field #0'}
+ - 1: {'test_id1', 'test field #1'}
+ - 2: {'test_id1', 'test field #2'}
+ - 3: {'test_id1', 'test field #3'}
+ - 4: {'test_id1', 'test field #4'}
+ - 5: {'test_id1', 'test field #5'}
+ - 6: {'test_id1', 'test field #6'}
+ - 7: {'test_id1', 'test field #7'}
+ - 8: {'test_id1', 'test field #8'}
+ - 9: {'test_id1', 'test field #9'}
+----------- test end -----------
+
+---------- test begin ----------
+delete
+ - 0: {'test_id1', 'test field #0'}
+ - 1: {'test_id1', 'test field #1'}
+ - 2: {'test_id1', 'test field #2'}
+ - 3: {'test_id1', 'test field #3'}
+ - 4: {'test_id1', 'test field #4'}
+ - 5: {'test_id1', 'test field #5'}
+ - 6: {'test_id1', 'test field #6'}
+ - 7: {'test_id1', 'test field #7'}
+ - 8: {'test_id1', 'test field #8'}
+ - 9: {'test_id1', 'test field #9'}
+----------- test end -----------
+
+===DONE===
\ No newline at end of file
diff --git a/connector/php/test/bin/run-test.bash b/connector/php/test/bin/run-test.bash
new file mode 100755
index 0000000000000000000000000000000000000000..59b262d82b1c1da7f376fd9e0d16083e50e4e83a
--- /dev/null
+++ b/connector/php/test/bin/run-test.bash
@@ -0,0 +1,206 @@
+#!/bin/bash
+#==============================================================================#
+# PHP tarantool test suite runner
+#==============================================================================#
+
+#------------------------------------------------------------------------------#
+# Constants
+#------------------------------------------------------------------------------#
+
+# Success constant
+SUCCESS=0
+# Failure constant
+FAILURE=1
+
+# test runner etc directory
+TEST_RUNNER_ETC_DIR="etc"
+# test runner var directory
+TEST_RUNNER_VAR_DIR="var"
+# test runner var directory
+TEST_RUNNER_LIB_DIR="lib"
+
+# php.ini file
+PHP_INI="$TEST_RUNNER_ETC_DIR/php.ini"
+
+# Tarantool/box binary file name
+TARANTOOL_BOX_BIN="tarantool_box"
+# Tarantool/box configuration file
+TARANTOOL_BOX_CFG="$TEST_RUNNER_ETC_DIR/tarantool_box.cfg"
+# Tarantool/box pid file
+TARANTOOL_BOX_PID="$TEST_RUNNER_VAR_DIR/tarantool_box.pid"
+
+
+#------------------------------------------------------------------------------#
+# Suite runner functions
+#------------------------------------------------------------------------------#
+
+# initialize suite
+function init_suite()
+{
+	mkdir -p $TEST_RUNNER_VAR_DIR
+
+	# check TARANTOOL_HOME variable
+	if [ ! -z $TARANTOOL_HOME ]; then
+		# Use user-defined path
+		tarantool_bin="$TARANTOOL_HOME/$TARANTOOL_BOX_BIN"
+    else
+		# try to find by standard paths
+		tarantool_bin="$TARANTOOL_BOX_BIN"
+	fi
+
+	# check binary
+	if ! which $tarantool_bin > /dev/null 2>&1; then
+		echo "can't found Tarantool/Box binary file"
+		exit $FAILURE
+	fi
+
+	# check pear
+	if ! which pear > /dev/null 2>&1; then
+		echo "can't found pear"
+		exit $FAILURE
+	fi
+
+	# check tarantool module library
+	if [ ! -f ../modules/tarantool.so ]; then
+		echo "can't found tarantool module library"
+		exit $FAILURE
+	fi
+	if [ -f $TEST_RUNNER_LIB_DIR/tarantool.so ]; then
+		rm $TEST_RUNNER_LIB_DIR/tarantool.so
+	fi
+	ln -s ../../modules/tarantool.so $TEST_RUNNER_LIB_DIR
+
+	if [ -f $TEST_RUNNER_VAR_DIR/init.lua ]; then
+		rm $TEST_RUNNER_VAR_DIR/init.lua
+	fi
+	ln -s ../$TEST_RUNNER_LIB_DIR/lua/sorted_array.lua $TEST_RUNNER_VAR_DIR/init.lua
+
+	return $SUCCESS
+}
+
+# initialize tarantool's storage
+function tarantool_init_storage()
+{
+	$tarantool_bin --init-storage -c $TARANTOOL_BOX_CFG 1> /dev/null 2>&1
+	return $SUCCESS
+}
+
+# start tarantool
+function tarantool_start()
+{
+	if [ -f $TARANTOOL_BOX_PID ]; then
+		tarantool_stop
+		tarantool_cleanup
+	fi
+
+	# run tarantool to background
+	$tarantool_bin -c $TARANTOOL_BOX_CFG &
+	# wait pid file
+	for i in {1..500}; do
+		if [ -f $TARANTOOL_BOX_PID ]; then
+			break
+		fi
+		sleep 0.01
+	done
+
+	if [ ! -f $TARANTOOL_BOX_PID ]; then
+		echo "error: can't start tarantool"
+		tarantool_cleanup
+		exit $FAILURE
+	fi
+
+	return $SUCCESS
+}
+
+# stop tarantool
+function tarantool_stop()
+{
+	if [ ! -f $TARANTOOL_BOX_PID ]; then
+		return $SUCCESS
+	fi
+
+	# get tarantool pid form pid file
+	pid=`cat $TARANTOOL_BOX_PID`
+	# kill process via SIGTERM
+	kill -TERM $pid 1> /dev/null 2>&1
+
+	for i in {1..500}; do
+		if [ ! -f $TARANTOOL_BOX_PID ]; then
+			# tarantool successfully stopped
+			return $SUCCESS
+		fi
+		sleep 0.01
+	done
+
+	if [ -f $TARANTOOL_BOX_PID ]; then
+		kill -KILL $pid 1> /dev/null 2>&1
+	fi
+
+	return $SUCCESS
+}
+
+# clean-up tarantool
+function tarantool_cleanup()
+{
+	# delete pid
+	rm -f $TEST_RUNNER_VAR_DIR/*.pid
+	# delete xlogs
+	rm -f $TEST_RUNNER_VAR_DIR/*.xlog
+	# delete snaps
+	rm -f $TEST_RUNNER_VAR_DIR/*.snap
+}
+
+
+#------------------------------------------------------------------------------#
+# run test scrip body
+#------------------------------------------------------------------------------#
+
+#
+# initialize
+#
+
+printf "initialize tarantool ... "
+# initializing suite
+init_suite
+# initializing storage
+tarantool_init_storage
+printf "done\n"
+
+printf "starting tarantool ... "
+# start tarantool
+tarantool_start
+printf "done\n"
+
+
+#
+# run
+#
+
+printf "\n"
+printf "================================= PHP test ===============================\n"
+
+# running pear's regression test scrips
+PHPRC=$PHP_INI pear run-tests $1
+
+printf "==========================================================================\n"
+printf "\n"
+
+#
+# stop & clean-up
+#
+
+printf "stopping tarantool ... "
+# stop tarantool
+tarantool_stop
+# clean-up tarantool
+tarantool_cleanup
+printf "done\n"
+
+#
+# Local variables:
+# tab-width: 4
+# c-basic-offset: 4
+# End:
+# vim600: noet sw=4 ts=4 fdm=marker
+# vim<600: noet sw=4 ts=4
+#
diff --git a/connector/php/test/call.phpt b/connector/php/test/call.phpt
new file mode 100644
index 0000000000000000000000000000000000000000..08640f9e1fe500ad97f13d2a14bfb9ff800128f7
--- /dev/null
+++ b/connector/php/test/call.phpt
@@ -0,0 +1,857 @@
+--TEST--
+Tarantool/box call commands test
+--FILE--
+<?php
+include "lib/php/tarantool_utest.php";
+
+$tarantool = new Tarantool("localhost", 33013, 33015);
+test_init($tarantool, 0);
+
+echo "---------- test begin ----------\n";
+echo "test call: myselect by primary index\n";
+test_call($tarantool, "box.select", array(0, 0, 2), 0);
+echo "----------- test end -----------\n\n";
+
+echo "---------- test begin ----------\n";
+echo "test call: call undefined function (expected error exception)\n";
+test_call($tarantool, "fafagaga", array("fafa-gaga", "foo", "bar"), 0);
+echo "----------- test end -----------\n\n";
+
+echo "---------- test begin ----------\n";
+echo "test call: sa_insert to key_1\n";
+echo "sa_insert('1', 'key_1', '10')\n";
+test_call($tarantool, "box.sa_insert", array("1", "key_1", "10"), 0);
+echo "sa_insert('1', 'key_1', '11')\n";
+test_call($tarantool, "box.sa_insert", array("1", "key_1", "11"), 0);
+echo "sa_insert('1', 'key_1', '15')\n";
+test_call($tarantool, "box.sa_insert", array("1", "key_1", "15"), 0);
+echo "sa_insert('1', 'key_1', '101')\n";
+test_call($tarantool, "box.sa_insert", array("1", "key_1", "101"), 0);
+echo "sa_insert('1', 'key_1', '511')\n";
+test_call($tarantool, "box.sa_insert", array("1", "key_1", "511"), 0);
+echo "sa_insert('1', 'key_1', '16')\n";
+test_call($tarantool, "box.sa_insert", array("1", "key_1", "16"), 0);
+echo "sa_insert('1', 'key_1', '42')\n";
+test_call($tarantool, "box.sa_insert", array("1", "key_1", "42"), 0);
+echo "----------- test end -----------\n\n";
+
+echo "---------- test begin ----------\n";
+echo "test call: sa_select from key_1\n";
+echo "sa_select('1', 'key_1', '101', '3')\n";
+test_call($tarantool, "box.sa_select", array("1", "key_1", "101", "3"), 0);
+echo "sa_select('1', 'key_1', '101', '2')\n";
+test_call($tarantool, "box.sa_select", array("1", "key_1", "101", "2"), 0);
+echo "sa_select('1', 'key_1', '511', '4')\n";
+test_call($tarantool, "box.sa_select", array("1", "key_1", "511", "4"), 0);
+echo "----------- test end -----------\n\n";
+
+echo "---------- test begin ----------\n";
+echo "test call: sa_delete from key_1\n";
+echo "sa_delete('1', 'key_1', '11', '101', '511')\n";
+test_call($tarantool, "box.sa_delete", array("1", "key_1", "11", "101", "511"), 0);
+echo "----------- test end -----------\n\n";
+
+echo "---------- test begin ----------\n";
+echo "test call: sa_select from key_1\n";
+echo "sa_select('1', 'key_1', '101', '3')\n";
+test_call($tarantool, "box.sa_select", array("1", "key_1", "101", "3"), 0);
+echo "sa_select('1', 'key_1', '101', '2')\n";
+test_call($tarantool, "box.sa_select", array("1", "key_1", "101", "2"), 0);
+echo "sa_select('1', 'key_1', '511', '4')\n";
+test_call($tarantool, "box.sa_select", array("1", "key_1", "511", "4"), 0);
+echo "----------- test end -----------\n\n";
+
+echo "---------- test begin ----------\n";
+echo "test call: sa_insert to key_2\n";
+echo "sa_insert('1', 'key_2', '10')\n";
+test_call($tarantool, "box.sa_insert", array("1", "key_2", "10"), 0);
+echo "sa_insert('1', 'key_2', '8')\n";
+test_call($tarantool, "box.sa_insert", array("1", "key_2", "8"), 0);
+echo "sa_insert('1', 'key_2', '500')\n";
+test_call($tarantool, "box.sa_insert", array("1", "key_2", "500"), 0);
+echo "sa_insert('1', 'key_2', '166')\n";
+test_call($tarantool, "box.sa_insert", array("1", "key_2", "166"), 0);
+echo "sa_insert('1', 'key_2', '233')\n";
+test_call($tarantool, "box.sa_insert", array("1", "key_2", "233"), 0);
+echo "sa_insert('1', 'key_2', '357')\n";
+test_call($tarantool, "box.sa_insert", array("1", "key_2", "357"), 0);
+echo "----------- test end -----------\n\n";
+
+echo "---------- test begin ----------\n";
+echo "test call: sa_select from key_2\n";
+echo "sa_select('1', 'key_2', '500', '100')\n";
+test_call($tarantool, "box.sa_select", array("1", "key_2", "500", "100"), 0);
+echo "sa_select('1', 'key_2', '18', '15')\n";
+test_call($tarantool, "box.sa_select", array("1", "key_2", "18", "15"), 0);
+echo "sa_select('1', 'key_2', '18', '1')\n";
+test_call($tarantool, "box.sa_select", array("1", "key_2", "18", "1"), 0);
+echo "----------- test end -----------\n\n";
+
+echo "---------- test begin ----------\n";
+echo "test call: sa_merge key_1 and key_2\n";
+echo "sa_merge('1', 'key_1', 'key_2')\n";
+test_call($tarantool, "box.sa_merge", array("1", "key_1", "key_2"), 0);
+echo "----------- test end -----------\n\n";
+
+echo "---------- test begin ----------\n";
+echo "test call: sa_delete from key_1\n";
+echo "sa_delete('1', 'key_1', '42')\n";
+test_call($tarantool, "box.sa_delete", array("1", "key_1", "42"), 0);
+echo "sa_delete('1', 'key_1', '16')\n";
+test_call($tarantool, "box.sa_delete", array("1", "key_1", "16"), 0);
+echo "sa_delete('1', 'key_1', '10')\n";
+test_call($tarantool, "box.sa_delete", array("1", "key_1", "10"), 0);
+echo "sa_delete('1', 'key_1', '15')\n";
+test_call($tarantool, "box.sa_delete", array("1", "key_1", "15"), 0);
+echo "----------- test end -----------\n\n";
+
+
+test_clean($tarantool, 0);
+?>
+===DONE===
+--EXPECT--
+---------- test begin ----------
+test call: myselect by primary index
+result:
+array(2) {
+  ["count"]=>
+  int(1)
+  ["tuples_list"]=>
+  array(1) {
+    [0]=>
+    array(6) {
+      [0]=>
+      int(2)
+      [1]=>
+      string(9) "Star Wars"
+      [2]=>
+      int(1983)
+      [3]=>
+      string(18) "Return of the Jedi"
+      [4]=>
+      string(460) "Luke Skywalker has returned
+to his home planet of
+Tatooine in an attempt
+to rescue his friend
+Han Solo from the
+clutches of the vile
+gangster Jabba the Hutt.
+
+Little does Luke know
+that the GALACTIC EMPIRE
+has secretly begun construction
+on a new armored space station
+even more powerful than the
+first dreaded Death Star.
+
+When completed, this ultimate
+weapon will spell certain
+doom for the small band of
+rebels struggling to restore
+freedom to the galaxy..."
+      [5]=>
+      int(-1091633149)
+    }
+  }
+}
+----------- test end -----------
+
+---------- test begin ----------
+test call: call undefined function (expected error exception)
+catched exception: call failed: 12802(0x00003202): Procedure 'fafagaga' is not defined
+----------- test end -----------
+
+---------- test begin ----------
+test call: sa_insert to key_1
+sa_insert('1', 'key_1', '10')
+result:
+array(2) {
+  ["count"]=>
+  int(2)
+  ["tuples_list"]=>
+  array(2) {
+    [0]=>
+    array(1) {
+      [0]=>
+      string(5) "key_1"
+    }
+    [1]=>
+    array(1) {
+      [0]=>
+      string(2) "10"
+    }
+  }
+}
+sa_insert('1', 'key_1', '11')
+result:
+array(2) {
+  ["count"]=>
+  int(2)
+  ["tuples_list"]=>
+  array(2) {
+    [0]=>
+    array(1) {
+      [0]=>
+      string(5) "key_1"
+    }
+    [1]=>
+    array(2) {
+      [0]=>
+      string(2) "11"
+      [1]=>
+      string(2) "10"
+    }
+  }
+}
+sa_insert('1', 'key_1', '15')
+result:
+array(2) {
+  ["count"]=>
+  int(2)
+  ["tuples_list"]=>
+  array(2) {
+    [0]=>
+    array(1) {
+      [0]=>
+      string(5) "key_1"
+    }
+    [1]=>
+    array(3) {
+      [0]=>
+      string(2) "15"
+      [1]=>
+      string(2) "11"
+      [2]=>
+      string(2) "10"
+    }
+  }
+}
+sa_insert('1', 'key_1', '101')
+result:
+array(2) {
+  ["count"]=>
+  int(2)
+  ["tuples_list"]=>
+  array(2) {
+    [0]=>
+    array(1) {
+      [0]=>
+      string(5) "key_1"
+    }
+    [1]=>
+    array(4) {
+      [0]=>
+      string(3) "101"
+      [1]=>
+      string(2) "15"
+      [2]=>
+      string(2) "11"
+      [3]=>
+      string(2) "10"
+    }
+  }
+}
+sa_insert('1', 'key_1', '511')
+result:
+array(2) {
+  ["count"]=>
+  int(2)
+  ["tuples_list"]=>
+  array(2) {
+    [0]=>
+    array(1) {
+      [0]=>
+      string(5) "key_1"
+    }
+    [1]=>
+    array(5) {
+      [0]=>
+      string(3) "511"
+      [1]=>
+      string(3) "101"
+      [2]=>
+      string(2) "15"
+      [3]=>
+      string(2) "11"
+      [4]=>
+      string(2) "10"
+    }
+  }
+}
+sa_insert('1', 'key_1', '16')
+result:
+array(2) {
+  ["count"]=>
+  int(2)
+  ["tuples_list"]=>
+  array(2) {
+    [0]=>
+    array(1) {
+      [0]=>
+      string(5) "key_1"
+    }
+    [1]=>
+    array(6) {
+      [0]=>
+      string(3) "511"
+      [1]=>
+      string(3) "101"
+      [2]=>
+      string(2) "16"
+      [3]=>
+      string(2) "15"
+      [4]=>
+      string(2) "11"
+      [5]=>
+      string(2) "10"
+    }
+  }
+}
+sa_insert('1', 'key_1', '42')
+result:
+array(2) {
+  ["count"]=>
+  int(2)
+  ["tuples_list"]=>
+  array(2) {
+    [0]=>
+    array(1) {
+      [0]=>
+      string(5) "key_1"
+    }
+    [1]=>
+    array(7) {
+      [0]=>
+      string(3) "511"
+      [1]=>
+      string(3) "101"
+      [2]=>
+      string(2) "42"
+      [3]=>
+      string(2) "16"
+      [4]=>
+      string(2) "15"
+      [5]=>
+      string(2) "11"
+      [6]=>
+      string(2) "10"
+    }
+  }
+}
+----------- test end -----------
+
+---------- test begin ----------
+test call: sa_select from key_1
+sa_select('1', 'key_1', '101', '3')
+result:
+array(2) {
+  ["count"]=>
+  int(2)
+  ["tuples_list"]=>
+  array(2) {
+    [0]=>
+    array(1) {
+      [0]=>
+      string(5) "key_1"
+    }
+    [1]=>
+    array(3) {
+      [0]=>
+      string(2) "42"
+      [1]=>
+      string(2) "16"
+      [2]=>
+      string(2) "15"
+    }
+  }
+}
+sa_select('1', 'key_1', '101', '2')
+result:
+array(2) {
+  ["count"]=>
+  int(2)
+  ["tuples_list"]=>
+  array(2) {
+    [0]=>
+    array(1) {
+      [0]=>
+      string(5) "key_1"
+    }
+    [1]=>
+    array(2) {
+      [0]=>
+      string(2) "42"
+      [1]=>
+      string(2) "16"
+    }
+  }
+}
+sa_select('1', 'key_1', '511', '4')
+result:
+array(2) {
+  ["count"]=>
+  int(2)
+  ["tuples_list"]=>
+  array(2) {
+    [0]=>
+    array(1) {
+      [0]=>
+      string(5) "key_1"
+    }
+    [1]=>
+    array(4) {
+      [0]=>
+      string(3) "101"
+      [1]=>
+      string(2) "42"
+      [2]=>
+      string(2) "16"
+      [3]=>
+      string(2) "15"
+    }
+  }
+}
+----------- test end -----------
+
+---------- test begin ----------
+test call: sa_delete from key_1
+sa_delete('1', 'key_1', '11', '101', '511')
+result:
+array(2) {
+  ["count"]=>
+  int(2)
+  ["tuples_list"]=>
+  array(2) {
+    [0]=>
+    array(1) {
+      [0]=>
+      string(5) "key_1"
+    }
+    [1]=>
+    array(4) {
+      [0]=>
+      string(2) "42"
+      [1]=>
+      string(2) "16"
+      [2]=>
+      string(2) "15"
+      [3]=>
+      string(2) "10"
+    }
+  }
+}
+----------- test end -----------
+
+---------- test begin ----------
+test call: sa_select from key_1
+sa_select('1', 'key_1', '101', '3')
+result:
+array(2) {
+  ["count"]=>
+  int(2)
+  ["tuples_list"]=>
+  array(2) {
+    [0]=>
+    array(1) {
+      [0]=>
+      string(5) "key_1"
+    }
+    [1]=>
+    array(3) {
+      [0]=>
+      string(2) "42"
+      [1]=>
+      string(2) "16"
+      [2]=>
+      string(2) "15"
+    }
+  }
+}
+sa_select('1', 'key_1', '101', '2')
+result:
+array(2) {
+  ["count"]=>
+  int(2)
+  ["tuples_list"]=>
+  array(2) {
+    [0]=>
+    array(1) {
+      [0]=>
+      string(5) "key_1"
+    }
+    [1]=>
+    array(2) {
+      [0]=>
+      string(2) "42"
+      [1]=>
+      string(2) "16"
+    }
+  }
+}
+sa_select('1', 'key_1', '511', '4')
+result:
+array(2) {
+  ["count"]=>
+  int(2)
+  ["tuples_list"]=>
+  array(2) {
+    [0]=>
+    array(1) {
+      [0]=>
+      string(5) "key_1"
+    }
+    [1]=>
+    array(4) {
+      [0]=>
+      string(2) "42"
+      [1]=>
+      string(2) "16"
+      [2]=>
+      string(2) "15"
+      [3]=>
+      string(2) "10"
+    }
+  }
+}
+----------- test end -----------
+
+---------- test begin ----------
+test call: sa_insert to key_2
+sa_insert('1', 'key_2', '10')
+result:
+array(2) {
+  ["count"]=>
+  int(2)
+  ["tuples_list"]=>
+  array(2) {
+    [0]=>
+    array(1) {
+      [0]=>
+      string(5) "key_2"
+    }
+    [1]=>
+    array(1) {
+      [0]=>
+      string(2) "10"
+    }
+  }
+}
+sa_insert('1', 'key_2', '8')
+result:
+array(2) {
+  ["count"]=>
+  int(2)
+  ["tuples_list"]=>
+  array(2) {
+    [0]=>
+    array(1) {
+      [0]=>
+      string(5) "key_2"
+    }
+    [1]=>
+    array(2) {
+      [0]=>
+      string(2) "10"
+      [1]=>
+      string(1) "8"
+    }
+  }
+}
+sa_insert('1', 'key_2', '500')
+result:
+array(2) {
+  ["count"]=>
+  int(2)
+  ["tuples_list"]=>
+  array(2) {
+    [0]=>
+    array(1) {
+      [0]=>
+      string(5) "key_2"
+    }
+    [1]=>
+    array(3) {
+      [0]=>
+      string(3) "500"
+      [1]=>
+      string(2) "10"
+      [2]=>
+      string(1) "8"
+    }
+  }
+}
+sa_insert('1', 'key_2', '166')
+result:
+array(2) {
+  ["count"]=>
+  int(2)
+  ["tuples_list"]=>
+  array(2) {
+    [0]=>
+    array(1) {
+      [0]=>
+      string(5) "key_2"
+    }
+    [1]=>
+    array(4) {
+      [0]=>
+      string(3) "500"
+      [1]=>
+      string(3) "166"
+      [2]=>
+      string(2) "10"
+      [3]=>
+      string(1) "8"
+    }
+  }
+}
+sa_insert('1', 'key_2', '233')
+result:
+array(2) {
+  ["count"]=>
+  int(2)
+  ["tuples_list"]=>
+  array(2) {
+    [0]=>
+    array(1) {
+      [0]=>
+      string(5) "key_2"
+    }
+    [1]=>
+    array(5) {
+      [0]=>
+      string(3) "500"
+      [1]=>
+      string(3) "233"
+      [2]=>
+      string(3) "166"
+      [3]=>
+      string(2) "10"
+      [4]=>
+      string(1) "8"
+    }
+  }
+}
+sa_insert('1', 'key_2', '357')
+result:
+array(2) {
+  ["count"]=>
+  int(2)
+  ["tuples_list"]=>
+  array(2) {
+    [0]=>
+    array(1) {
+      [0]=>
+      string(5) "key_2"
+    }
+    [1]=>
+    array(6) {
+      [0]=>
+      string(3) "500"
+      [1]=>
+      string(3) "357"
+      [2]=>
+      string(3) "233"
+      [3]=>
+      string(3) "166"
+      [4]=>
+      string(2) "10"
+      [5]=>
+      string(1) "8"
+    }
+  }
+}
+----------- test end -----------
+
+---------- test begin ----------
+test call: sa_select from key_2
+sa_select('1', 'key_2', '500', '100')
+result:
+array(2) {
+  ["count"]=>
+  int(2)
+  ["tuples_list"]=>
+  array(2) {
+    [0]=>
+    array(1) {
+      [0]=>
+      string(5) "key_2"
+    }
+    [1]=>
+    array(5) {
+      [0]=>
+      string(3) "357"
+      [1]=>
+      string(3) "233"
+      [2]=>
+      string(3) "166"
+      [3]=>
+      string(2) "10"
+      [4]=>
+      string(1) "8"
+    }
+  }
+}
+sa_select('1', 'key_2', '18', '15')
+result:
+array(2) {
+  ["count"]=>
+  int(2)
+  ["tuples_list"]=>
+  array(2) {
+    [0]=>
+    array(1) {
+      [0]=>
+      string(5) "key_2"
+    }
+    [1]=>
+    array(2) {
+      [0]=>
+      string(2) "10"
+      [1]=>
+      string(1) "8"
+    }
+  }
+}
+sa_select('1', 'key_2', '18', '1')
+result:
+array(2) {
+  ["count"]=>
+  int(2)
+  ["tuples_list"]=>
+  array(2) {
+    [0]=>
+    array(1) {
+      [0]=>
+      string(5) "key_2"
+    }
+    [1]=>
+    array(1) {
+      [0]=>
+      string(2) "10"
+    }
+  }
+}
+----------- test end -----------
+
+---------- test begin ----------
+test call: sa_merge key_1 and key_2
+sa_merge('1', 'key_1', 'key_2')
+result:
+array(2) {
+  ["count"]=>
+  int(1)
+  ["tuples_list"]=>
+  array(1) {
+    [0]=>
+    array(10) {
+      [0]=>
+      string(3) "500"
+      [1]=>
+      string(3) "357"
+      [2]=>
+      string(3) "233"
+      [3]=>
+      string(3) "166"
+      [4]=>
+      string(2) "42"
+      [5]=>
+      string(2) "16"
+      [6]=>
+      string(2) "15"
+      [7]=>
+      string(2) "10"
+      [8]=>
+      string(2) "10"
+      [9]=>
+      string(1) "8"
+    }
+  }
+}
+----------- test end -----------
+
+---------- test begin ----------
+test call: sa_delete from key_1
+sa_delete('1', 'key_1', '42')
+result:
+array(2) {
+  ["count"]=>
+  int(2)
+  ["tuples_list"]=>
+  array(2) {
+    [0]=>
+    array(1) {
+      [0]=>
+      string(5) "key_1"
+    }
+    [1]=>
+    array(3) {
+      [0]=>
+      string(2) "16"
+      [1]=>
+      string(2) "15"
+      [2]=>
+      string(2) "10"
+    }
+  }
+}
+sa_delete('1', 'key_1', '16')
+result:
+array(2) {
+  ["count"]=>
+  int(2)
+  ["tuples_list"]=>
+  array(2) {
+    [0]=>
+    array(1) {
+      [0]=>
+      string(5) "key_1"
+    }
+    [1]=>
+    array(2) {
+      [0]=>
+      string(2) "15"
+      [1]=>
+      string(2) "10"
+    }
+  }
+}
+sa_delete('1', 'key_1', '10')
+result:
+array(2) {
+  ["count"]=>
+  int(2)
+  ["tuples_list"]=>
+  array(2) {
+    [0]=>
+    array(1) {
+      [0]=>
+      string(5) "key_1"
+    }
+    [1]=>
+    array(1) {
+      [0]=>
+      string(2) "15"
+    }
+  }
+}
+sa_delete('1', 'key_1', '15')
+result:
+array(2) {
+  ["count"]=>
+  int(2)
+  ["tuples_list"]=>
+  array(2) {
+    [0]=>
+    array(1) {
+      [0]=>
+      string(5) "key_1"
+    }
+    [1]=>
+    array(0) {
+    }
+  }
+}
+----------- test end -----------
+
+===DONE===
\ No newline at end of file
diff --git a/connector/php/test/delete.phpt b/connector/php/test/delete.phpt
new file mode 100644
index 0000000000000000000000000000000000000000..e9b68a235a6bf839e45b5b874fd9d04c98e0f4a6
--- /dev/null
+++ b/connector/php/test/delete.phpt
@@ -0,0 +1,138 @@
+--TEST--
+Tarantool/box delete commands test
+--FILE--
+<?php
+include "lib/php/tarantool_utest.php";
+
+$tarantool = new Tarantool("localhost", 33013, 33015);
+
+test_init($tarantool, 0);
+
+echo "---------- test begin ----------\n";
+echo "test delete: invalid key (expected error exception)\n";
+test_delete($tarantool, 0, $tarantool, 0);
+echo "----------- test end -----------\n\n";
+
+echo "---------- test begin ----------\n";
+echo "test delete: invalid key (expected error exception)\n";
+test_delete($tarantool, 0, array($tarantool), 0);
+echo "----------- test end -----------\n\n";
+
+echo "---------- test begin ----------\n";
+echo "test delete: invalid key (expected error exception)\n";
+test_delete($tarantool, 0, array(1, 2), 0);
+echo "----------- test end -----------\n\n";
+
+echo "---------- test begin ----------\n";
+echo "test delete: delete key as interger\n";
+test_delete($tarantool, 0, 0, TARANTOOL_FLAGS_RETURN_TUPLE);
+echo "----------- test end -----------\n\n";
+
+echo "---------- test begin ----------\n";
+echo "test delete: delete key as array\n";
+test_delete($tarantool, 0, array(1), TARANTOOL_FLAGS_RETURN_TUPLE);
+echo "----------- test end -----------\n\n";
+
+echo "---------- test begin ----------\n";
+echo "test delete: delete key (tuple doesn't return)\n";
+test_delete($tarantool, 0, 2, 0);
+echo "----------- test end -----------\n\n";
+
+echo "---------- test begin ----------\n";
+echo "test delete: delete not existing tuple w/ return tuple flag\n";
+test_delete($tarantool, 0, 5, TARANTOOL_FLAGS_RETURN_TUPLE);
+echo "----------- test end -----------\n\n";
+
+test_clean($tarantool, 0);
+?>
+===DONE===
+--EXPECT--
+---------- test begin ----------
+test delete: invalid key (expected error exception)
+catched exception: unsupported tuple type
+----------- test end -----------
+
+---------- test begin ----------
+test delete: invalid key (expected error exception)
+catched exception: unsupported field type
+----------- test end -----------
+
+---------- test begin ----------
+test delete: invalid key (expected error exception)
+catched exception: delete failed: 514(0x00000202): Illegal parameters, key must be single valued
+----------- test end -----------
+
+---------- test begin ----------
+test delete: delete key as interger
+result:
+count = 1
+tuple:
+  id     = 0
+  series = Star Wars
+  year   = 1977
+  name   = A New Hope
+  crawl  = A long time ago, in a galaxy far, far away...
+It is a period of civil war. Rebel
+spaceships, striking from a hidden
+base, have won their first victory
+against the evil Galactic Empire.
+
+During the battle, Rebel spies managed
+to steal secret plans to the Empire's
+ultimate weapon, the Death Star, an
+armored space station with enough
+power to destroy an entire planet.
+
+Pursued by the Empire's sinister agents,
+Princess Leia races home aboard her
+starship, custodian of the stolen plans
+that can save her people and restore
+freedom to the galaxy....
+  uuid   = -1091633151
+----------- test end -----------
+
+---------- test begin ----------
+test delete: delete key as array
+result:
+count = 1
+tuple:
+  id     = 1
+  series = Star Wars
+  year   = 1980
+  name   = The Empire Strikes Back
+  crawl  = It is a dark time for the
+Rebellion. Although the Death
+Star has been destroyed.
+Imperial troops have driven the
+Rebel forces from their hidden
+base and pursued them across
+the galaxy.
+
+Evading the dreaded Imperial
+Starfleet, a group of freedom
+fighters led by Luke Skywalker
+have established a new secret base
+on the remote ice world
+of Hoth.
+
+The evil lord Darth Vader,
+obsessed with finding young
+Skywalker, has dispatched
+thousands of remote probes
+into the far reaches of space....
+  uuid   = -1091633150
+----------- test end -----------
+
+---------- test begin ----------
+test delete: delete key (tuple doesn't return)
+result:
+count = 1
+----------- test end -----------
+
+---------- test begin ----------
+test delete: delete not existing tuple w/ return tuple flag
+result:
+count = 0
+----------- test end -----------
+
+===DONE===
\ No newline at end of file
diff --git a/connector/php/test/errors.phpt b/connector/php/test/errors.phpt
new file mode 100644
index 0000000000000000000000000000000000000000..2450b3efb6e53af297665d252a057e04888ab89d
--- /dev/null
+++ b/connector/php/test/errors.phpt
@@ -0,0 +1,26 @@
+--TEST--
+Tarantool/box error commands test
+--FILE--
+<?php
+
+try
+{
+    $tarantool = new Tarantool("localhost", -33013, 0);
+    echo "error: the exception didn't raise\n";
+} catch (Exception $e) {
+    echo "catched exception: ", $e->getMessage(), "\n";
+}
+
+try {
+    $tarantool = new Tarantool("localhost", 33013, 65537);
+    echo "error: the exception didn't raise\n";
+} catch (Exception $e) {
+    echo "catched exception: ", $e->getMessage(), "\n";
+}
+
+?>
+===DONE===
+--EXPECT--
+catched exception: invalid primary port value: -33013
+catched exception: invalid admin port value: 65537
+===DONE===
diff --git a/connector/php/test/etc/php.ini b/connector/php/test/etc/php.ini
new file mode 100644
index 0000000000000000000000000000000000000000..ae7b2ee849ad7c3d954530eedf871c5192769f1b
--- /dev/null
+++ b/connector/php/test/etc/php.ini
@@ -0,0 +1,1851 @@
+[PHP]
+
+;;;;;;;;;;;;;;;;;;;
+; About php.ini   ;
+;;;;;;;;;;;;;;;;;;;
+; PHP's initialization file, generally called php.ini, is responsible for
+; configuring many of the aspects of PHP's behavior.
+
+; PHP attempts to find and load this configuration from a number of locations.
+; The following is a summary of its search order:
+; 1. SAPI module specific location.
+; 2. The PHPRC environment variable. (As of PHP 5.2.0)
+; 3. A number of predefined registry keys on Windows (As of PHP 5.2.0)
+; 4. Current working directory (except CLI)
+; 5. The web server's directory (for SAPI modules), or directory of PHP
+; (otherwise in Windows)
+; 6. The directory from the --with-config-file-path compile time option, or the
+; Windows directory (C:\windows or C:\winnt)
+; See the PHP docs for more specific information.
+; http://php.net/configuration.file
+
+; The syntax of the file is extremely simple.  Whitespace and Lines
+; beginning with a semicolon are silently ignored (as you probably guessed).
+; Section headers (e.g. [Foo]) are also silently ignored, even though
+; they might mean something in the future.
+
+; Directives following the section heading [PATH=/www/mysite] only
+; apply to PHP files in the /www/mysite directory.  Directives
+; following the section heading [HOST=www.example.com] only apply to
+; PHP files served from www.example.com.  Directives set in these
+; special sections cannot be overridden by user-defined INI files or
+; at runtime. Currently, [PATH=] and [HOST=] sections only work under
+; CGI/FastCGI.
+; http://php.net/ini.sections
+
+; Directives are specified using the following syntax:
+; directive = value
+; Directive names are *case sensitive* - foo=bar is different from FOO=bar.
+; Directives are variables used to configure PHP or PHP extensions.
+; There is no name validation.  If PHP can't find an expected
+; directive because it is not set or is mistyped, a default value will be used.
+
+; The value can be a string, a number, a PHP constant (e.g. E_ALL or M_PI), one
+; of the INI constants (On, Off, True, False, Yes, No and None) or an expression
+; (e.g. E_ALL & ~E_NOTICE), a quoted string ("bar"), or a reference to a
+; previously set variable or directive (e.g. ${foo})
+
+; Expressions in the INI file are limited to bitwise operators and parentheses:
+; |  bitwise OR
+; ^  bitwise XOR
+; &  bitwise AND
+; ~  bitwise NOT
+; !  boolean NOT
+
+; Boolean flags can be turned on using the values 1, On, True or Yes.
+; They can be turned off using the values 0, Off, False or No.
+
+; An empty string can be denoted by simply not writing anything after the equal
+; sign, or by using the None keyword:
+
+;  foo =         ; sets foo to an empty string
+;  foo = None    ; sets foo to an empty string
+;  foo = "None"  ; sets foo to the string 'None'
+
+; If you use constants in your value, and these constants belong to a
+; dynamically loaded extension (either a PHP extension or a Zend extension),
+; you may only use these constants *after* the line that loads the extension.
+
+;;;;;;;;;;;;;;;;;;;
+; About this file ;
+;;;;;;;;;;;;;;;;;;;
+; PHP comes packaged with two INI files. One that is recommended to be used
+; in production environments and one that is recommended to be used in
+; development environments.
+
+; php.ini-production contains settings which hold security, performance and
+; best practices at its core. But please be aware, these settings may break
+; compatibility with older or less security conscience applications. We
+; recommending using the production ini in production and testing environments.
+
+; php.ini-development is very similar to its production variant, except it's
+; much more verbose when it comes to errors. We recommending using the
+; development version only in development environments as errors shown to
+; application users can inadvertently leak otherwise secure information.
+
+;;;;;;;;;;;;;;;;;;;
+; Quick Reference ;
+;;;;;;;;;;;;;;;;;;;
+; The following are all the settings which are different in either the production
+; or development versions of the INIs with respect to PHP's default behavior.
+; Please see the actual settings later in the document for more details as to why
+; we recommend these changes in PHP's behavior.
+
+; allow_call_time_pass_reference
+;   Default Value: On
+;   Development Value: Off
+;   Production Value: Off
+
+; display_errors
+;   Default Value: On
+;   Development Value: On
+;   Production Value: Off
+
+; display_startup_errors
+;   Default Value: Off
+;   Development Value: On
+;   Production Value: Off
+
+; error_reporting
+;   Default Value: E_ALL & ~E_NOTICE
+;   Development Value: E_ALL | E_STRICT
+;   Production Value: E_ALL & ~E_DEPRECATED
+
+; html_errors
+;   Default Value: On
+;   Development Value: On
+;   Production value: Off
+
+; log_errors
+;   Default Value: Off
+;   Development Value: On
+;   Production Value: On
+
+; magic_quotes_gpc
+;   Default Value: On
+;   Development Value: Off
+;   Production Value: Off
+
+; max_input_time
+;   Default Value: -1 (Unlimited)
+;   Development Value: 60 (60 seconds)
+;   Production Value: 60 (60 seconds)
+
+; output_buffering
+;   Default Value: Off
+;   Development Value: 4096
+;   Production Value: 4096
+
+; register_argc_argv
+;   Default Value: On
+;   Development Value: Off
+;   Production Value: Off
+
+; register_long_arrays
+;   Default Value: On
+;   Development Value: Off
+;   Production Value: Off
+
+; request_order
+;   Default Value: None
+;   Development Value: "GP"
+;   Production Value: "GP"
+
+; session.bug_compat_42
+;   Default Value: On
+;   Development Value: On
+;   Production Value: Off
+
+; session.bug_compat_warn
+;   Default Value: On
+;   Development Value: On
+;   Production Value: Off
+
+; session.gc_divisor
+;   Default Value: 100
+;   Development Value: 1000
+;   Production Value: 1000
+
+; session.hash_bits_per_character
+;   Default Value: 4
+;   Development Value: 5
+;   Production Value: 5
+
+; short_open_tag
+;   Default Value: On
+;   Development Value: Off
+;   Production Value: Off
+
+; track_errors
+;   Default Value: Off
+;   Development Value: On
+;   Production Value: Off
+
+; url_rewriter.tags
+;   Default Value: "a=href,area=href,frame=src,form=,fieldset="
+;   Development Value: "a=href,area=href,frame=src,input=src,form=fakeentry"
+;   Production Value: "a=href,area=href,frame=src,input=src,form=fakeentry"
+
+; variables_order
+;   Default Value: "EGPCS"
+;   Development Value: "GPCS"
+;   Production Value: "GPCS"
+
+;;;;;;;;;;;;;;;;;;;;
+; php.ini Options  ;
+;;;;;;;;;;;;;;;;;;;;
+; Name for user-defined php.ini (.htaccess) files. Default is ".user.ini"
+;user_ini.filename = ".user.ini"
+
+; To disable this feature set this option to empty value
+;user_ini.filename =
+
+; TTL for user-defined php.ini files (time-to-live) in seconds. Default is 300 seconds (5 minutes)
+;user_ini.cache_ttl = 300
+
+;;;;;;;;;;;;;;;;;;;;
+; Language Options ;
+;;;;;;;;;;;;;;;;;;;;
+
+; Enable the PHP scripting language engine under Apache.
+; http://php.net/engine
+engine = On
+
+; This directive determines whether or not PHP will recognize code between
+; <? and ?> tags as PHP source which should be processed as such. It's been
+; recommended for several years that you not use the short tag "short cut" and
+; instead to use the full <?php and ?> tag combination. With the wide spread use
+; of XML and use of these tags by other languages, the server can become easily
+; confused and end up parsing the wrong code in the wrong context. But because
+; this short cut has been a feature for such a long time, it's currently still
+; supported for backwards compatibility, but we recommend you don't use them.
+; Default Value: On
+; Development Value: Off
+; Production Value: Off
+; http://php.net/short-open-tag
+short_open_tag = On
+
+; Allow ASP-style <% %> tags.
+; http://php.net/asp-tags
+asp_tags = Off
+
+; The number of significant digits displayed in floating point numbers.
+; http://php.net/precision
+precision = 14
+
+; Enforce year 2000 compliance (will cause problems with non-compliant browsers)
+; http://php.net/y2k-compliance
+y2k_compliance = On
+
+; Output buffering is a mechanism for controlling how much output data
+; (excluding headers and cookies) PHP should keep internally before pushing that
+; data to the client. If your application's output exceeds this setting, PHP
+; will send that data in chunks of roughly the size you specify.
+; Turning on this setting and managing its maximum buffer size can yield some
+; interesting side-effects depending on your application and web server.
+; You may be able to send headers and cookies after you've already sent output
+; through print or echo. You also may see performance benefits if your server is
+; emitting less packets due to buffered output versus PHP streaming the output
+; as it gets it. On production servers, 4096 bytes is a good setting for performance
+; reasons.
+; Note: Output buffering can also be controlled via Output Buffering Control
+;   functions.
+; Possible Values:
+;   On = Enabled and buffer is unlimited. (Use with caution)
+;   Off = Disabled
+;   Integer = Enables the buffer and sets its maximum size in bytes.
+; Note: This directive is hardcoded to Off for the CLI SAPI
+; Default Value: Off
+; Development Value: 4096
+; Production Value: 4096
+; http://php.net/output-buffering
+output_buffering = 4096
+
+; You can redirect all of the output of your scripts to a function.  For
+; example, if you set output_handler to "mb_output_handler", character
+; encoding will be transparently converted to the specified encoding.
+; Setting any output handler automatically turns on output buffering.
+; Note: People who wrote portable scripts should not depend on this ini
+;   directive. Instead, explicitly set the output handler using ob_start().
+;   Using this ini directive may cause problems unless you know what script
+;   is doing.
+; Note: You cannot use both "mb_output_handler" with "ob_iconv_handler"
+;   and you cannot use both "ob_gzhandler" and "zlib.output_compression".
+; Note: output_handler must be empty if this is set 'On' !!!!
+;   Instead you must use zlib.output_handler.
+; http://php.net/output-handler
+;output_handler =
+
+; Transparent output compression using the zlib library
+; Valid values for this option are 'off', 'on', or a specific buffer size
+; to be used for compression (default is 4KB)
+; Note: Resulting chunk size may vary due to nature of compression. PHP
+;   outputs chunks that are few hundreds bytes each as a result of
+;   compression. If you prefer a larger chunk size for better
+;   performance, enable output_buffering in addition.
+; Note: You need to use zlib.output_handler instead of the standard
+;   output_handler, or otherwise the output will be corrupted.
+; http://php.net/zlib.output-compression
+zlib.output_compression = Off
+
+; http://php.net/zlib.output-compression-level
+;zlib.output_compression_level = -1
+
+; You cannot specify additional output handlers if zlib.output_compression
+; is activated here. This setting does the same as output_handler but in
+; a different order.
+; http://php.net/zlib.output-handler
+;zlib.output_handler =
+
+; Implicit flush tells PHP to tell the output layer to flush itself
+; automatically after every output block.  This is equivalent to calling the
+; PHP function flush() after each and every call to print() or echo() and each
+; and every HTML block.  Turning this option on has serious performance
+; implications and is generally recommended for debugging purposes only.
+; http://php.net/implicit-flush
+; Note: This directive is hardcoded to On for the CLI SAPI
+implicit_flush = Off
+
+; The unserialize callback function will be called (with the undefined class'
+; name as parameter), if the unserializer finds an undefined class
+; which should be instantiated. A warning appears if the specified function is
+; not defined, or if the function doesn't include/implement the missing class.
+; So only set this entry, if you really want to implement such a
+; callback-function.
+unserialize_callback_func =
+
+; When floats & doubles are serialized store serialize_precision significant
+; digits after the floating point. The default value ensures that when floats
+; are decoded with unserialize, the data will remain the same.
+serialize_precision = 17
+
+; This directive allows you to enable and disable warnings which PHP will issue
+; if you pass a value by reference at function call time. Passing values by
+; reference at function call time is a deprecated feature which will be removed
+; from PHP at some point in the near future. The acceptable method for passing a
+; value by reference to a function is by declaring the reference in the functions
+; definition, not at call time. This directive does not disable this feature, it
+; only determines whether PHP will warn you about it or not. These warnings
+; should enabled in development environments only.
+; Default Value: On (Suppress warnings)
+; Development Value: Off (Issue warnings)
+; Production Value: Off (Issue warnings)
+; http://php.net/allow-call-time-pass-reference
+allow_call_time_pass_reference = Off
+
+; Safe Mode
+; http://php.net/safe-mode
+safe_mode = Off
+
+; By default, Safe Mode does a UID compare check when
+; opening files. If you want to relax this to a GID compare,
+; then turn on safe_mode_gid.
+; http://php.net/safe-mode-gid
+safe_mode_gid = Off
+
+; When safe_mode is on, UID/GID checks are bypassed when
+; including files from this directory and its subdirectories.
+; (directory must also be in include_path or full path must
+; be used when including)
+; http://php.net/safe-mode-include-dir
+safe_mode_include_dir =
+
+; When safe_mode is on, only executables located in the safe_mode_exec_dir
+; will be allowed to be executed via the exec family of functions.
+; http://php.net/safe-mode-exec-dir
+safe_mode_exec_dir =
+
+; Setting certain environment variables may be a potential security breach.
+; This directive contains a comma-delimited list of prefixes.  In Safe Mode,
+; the user may only alter environment variables whose names begin with the
+; prefixes supplied here.  By default, users will only be able to set
+; environment variables that begin with PHP_ (e.g. PHP_FOO=BAR).
+; Note:  If this directive is empty, PHP will let the user modify ANY
+;   environment variable!
+; http://php.net/safe-mode-allowed-env-vars
+safe_mode_allowed_env_vars = PHP_
+
+; This directive contains a comma-delimited list of environment variables that
+; the end user won't be able to change using putenv().  These variables will be
+; protected even if safe_mode_allowed_env_vars is set to allow to change them.
+; http://php.net/safe-mode-protected-env-vars
+safe_mode_protected_env_vars = LD_LIBRARY_PATH
+
+; open_basedir, if set, limits all file operations to the defined directory
+; and below.  This directive makes most sense if used in a per-directory
+; or per-virtualhost web server configuration file. This directive is
+; *NOT* affected by whether Safe Mode is turned On or Off.
+; http://php.net/open-basedir
+;open_basedir =
+
+; This directive allows you to disable certain functions for security reasons.
+; It receives a comma-delimited list of function names. This directive is
+; *NOT* affected by whether Safe Mode is turned On or Off.
+; http://php.net/disable-functions
+disable_functions =
+
+; This directive allows you to disable certain classes for security reasons.
+; It receives a comma-delimited list of class names. This directive is
+; *NOT* affected by whether Safe Mode is turned On or Off.
+; http://php.net/disable-classes
+disable_classes =
+
+; Colors for Syntax Highlighting mode.  Anything that's acceptable in
+; <span style="color: ???????"> would work.
+; http://php.net/syntax-highlighting
+;highlight.string  = #DD0000
+;highlight.comment = #FF9900
+;highlight.keyword = #007700
+;highlight.bg      = #FFFFFF
+;highlight.default = #0000BB
+;highlight.html    = #000000
+
+; If enabled, the request will be allowed to complete even if the user aborts
+; the request. Consider enabling it if executing long requests, which may end up
+; being interrupted by the user or a browser timing out. PHP's default behavior
+; is to disable this feature.
+; http://php.net/ignore-user-abort
+;ignore_user_abort = On
+
+; Determines the size of the realpath cache to be used by PHP. This value should
+; be increased on systems where PHP opens many files to reflect the quantity of
+; the file operations performed.
+; http://php.net/realpath-cache-size
+;realpath_cache_size = 16k
+
+; Duration of time, in seconds for which to cache realpath information for a given
+; file or directory. For systems with rarely changing files, consider increasing this
+; value.
+; http://php.net/realpath-cache-ttl
+;realpath_cache_ttl = 120
+
+;;;;;;;;;;;;;;;;;
+; Miscellaneous ;
+;;;;;;;;;;;;;;;;;
+
+; Decides whether PHP may expose the fact that it is installed on the server
+; (e.g. by adding its signature to the Web server header).  It is no security
+; threat in any way, but it makes it possible to determine whether you use PHP
+; on your server or not.
+; http://php.net/expose-php
+expose_php = On
+
+;;;;;;;;;;;;;;;;;;;
+; Resource Limits ;
+;;;;;;;;;;;;;;;;;;;
+
+; Maximum execution time of each script, in seconds
+; http://php.net/max-execution-time
+; Note: This directive is hardcoded to 0 for the CLI SAPI
+max_execution_time = 30
+
+; Maximum amount of time each script may spend parsing request data. It's a good
+; idea to limit this time on productions servers in order to eliminate unexpectedly
+; long running scripts.
+; Note: This directive is hardcoded to -1 for the CLI SAPI
+; Default Value: -1 (Unlimited)
+; Development Value: 60 (60 seconds)
+; Production Value: 60 (60 seconds)
+; http://php.net/max-input-time
+max_input_time = 60
+
+; Maximum input variable nesting level
+; http://php.net/max-input-nesting-level
+;max_input_nesting_level = 64
+
+; Maximum amount of memory a script may consume (128MB)
+; http://php.net/memory-limit
+memory_limit = -1
+
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+; Error handling and logging ;
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+
+; This directive informs PHP of which errors, warnings and notices you would like
+; it to take action for. The recommended way of setting values for this
+; directive is through the use of the error level constants and bitwise
+; operators. The error level constants are below here for convenience as well as
+; some common settings and their meanings.
+; By default, PHP is set to take action on all errors, notices and warnings EXCEPT
+; those related to E_NOTICE and E_STRICT, which together cover best practices and
+; recommended coding standards in PHP. For performance reasons, this is the
+; recommend error reporting setting. Your production server shouldn't be wasting
+; resources complaining about best practices and coding standards. That's what
+; development servers and development settings are for.
+; Note: The php.ini-development file has this setting as E_ALL | E_STRICT. This
+; means it pretty much reports everything which is exactly what you want during
+; development and early testing.
+;
+; Error Level Constants:
+; E_ALL             - All errors and warnings (includes E_STRICT as of PHP 6.0.0)
+; E_ERROR           - fatal run-time errors
+; E_RECOVERABLE_ERROR  - almost fatal run-time errors
+; E_WARNING         - run-time warnings (non-fatal errors)
+; E_PARSE           - compile-time parse errors
+; E_NOTICE          - run-time notices (these are warnings which often result
+;                     from a bug in your code, but it's possible that it was
+;                     intentional (e.g., using an uninitialized variable and
+;                     relying on the fact it's automatically initialized to an
+;                     empty string)
+; E_STRICT          - run-time notices, enable to have PHP suggest changes
+;                     to your code which will ensure the best interoperability
+;                     and forward compatibility of your code
+; E_CORE_ERROR      - fatal errors that occur during PHP's initial startup
+; E_CORE_WARNING    - warnings (non-fatal errors) that occur during PHP's
+;                     initial startup
+; E_COMPILE_ERROR   - fatal compile-time errors
+; E_COMPILE_WARNING - compile-time warnings (non-fatal errors)
+; E_USER_ERROR      - user-generated error message
+; E_USER_WARNING    - user-generated warning message
+; E_USER_NOTICE     - user-generated notice message
+; E_DEPRECATED      - warn about code that will not work in future versions
+;                     of PHP
+; E_USER_DEPRECATED - user-generated deprecation warnings
+;
+; Common Values:
+;   E_ALL & ~E_NOTICE  (Show all errors, except for notices and coding standards warnings.)
+;   E_ALL & ~E_NOTICE | E_STRICT  (Show all errors, except for notices)
+;   E_COMPILE_ERROR|E_RECOVERABLE_ERROR|E_ERROR|E_CORE_ERROR  (Show only errors)
+;   E_ALL | E_STRICT  (Show all errors, warnings and notices including coding standards.)
+; Default Value: E_ALL & ~E_NOTICE
+; Development Value: E_ALL | E_STRICT
+; Production Value: E_ALL & ~E_DEPRECATED
+; http://php.net/error-reporting
+error_reporting = E_ALL & ~E_DEPRECATED
+
+; This directive controls whether or not and where PHP will output errors,
+; notices and warnings too. Error output is very useful during development, but
+; it could be very dangerous in production environments. Depending on the code
+; which is triggering the error, sensitive information could potentially leak
+; out of your application such as database usernames and passwords or worse.
+; It's recommended that errors be logged on production servers rather than
+; having the errors sent to STDOUT.
+; Possible Values:
+;   Off = Do not display any errors
+;   stderr = Display errors to STDERR (affects only CGI/CLI binaries!)
+;   On or stdout = Display errors to STDOUT
+; Default Value: On
+; Development Value: On
+; Production Value: Off
+; http://php.net/display-errors
+display_errors = Off
+
+; The display of errors which occur during PHP's startup sequence are handled
+; separately from display_errors. PHP's default behavior is to suppress those
+; errors from clients. Turning the display of startup errors on can be useful in
+; debugging configuration problems. But, it's strongly recommended that you
+; leave this setting off on production servers.
+; Default Value: Off
+; Development Value: On
+; Production Value: Off
+; http://php.net/display-startup-errors
+display_startup_errors = Off
+
+; Besides displaying errors, PHP can also log errors to locations such as a
+; server-specific log, STDERR, or a location specified by the error_log
+; directive found below. While errors should not be displayed on productions
+; servers they should still be monitored and logging is a great way to do that.
+; Default Value: Off
+; Development Value: On
+; Production Value: On
+; http://php.net/log-errors
+log_errors = On
+
+; Set maximum length of log_errors. In error_log information about the source is
+; added. The default is 1024 and 0 allows to not apply any maximum length at all.
+; http://php.net/log-errors-max-len
+log_errors_max_len = 1024
+
+; Do not log repeated messages. Repeated errors must occur in same file on same
+; line unless ignore_repeated_source is set true.
+; http://php.net/ignore-repeated-errors
+ignore_repeated_errors = Off
+
+; Ignore source of message when ignoring repeated messages. When this setting
+; is On you will not log errors with repeated messages from different files or
+; source lines.
+; http://php.net/ignore-repeated-source
+ignore_repeated_source = Off
+
+; If this parameter is set to Off, then memory leaks will not be shown (on
+; stdout or in the log). This has only effect in a debug compile, and if
+; error reporting includes E_WARNING in the allowed list
+; http://php.net/report-memleaks
+report_memleaks = On
+
+; This setting is on by default.
+;report_zend_debug = 0
+
+; Store the last error/warning message in $php_errormsg (boolean). Setting this value
+; to On can assist in debugging and is appropriate for development servers. It should
+; however be disabled on production servers.
+; Default Value: Off
+; Development Value: On
+; Production Value: Off
+; http://php.net/track-errors
+track_errors = Off
+
+; Turn off normal error reporting and emit XML-RPC error XML
+; http://php.net/xmlrpc-errors
+;xmlrpc_errors = 0
+
+; An XML-RPC faultCode
+;xmlrpc_error_number = 0
+
+; When PHP displays or logs an error, it has the capability of inserting html
+; links to documentation related to that error. This directive controls whether
+; those HTML links appear in error messages or not. For performance and security
+; reasons, it's recommended you disable this on production servers.
+; Note: This directive is hardcoded to Off for the CLI SAPI
+; Default Value: On
+; Development Value: On
+; Production value: Off
+; http://php.net/html-errors
+html_errors = Off
+
+; If html_errors is set On PHP produces clickable error messages that direct
+; to a page describing the error or function causing the error in detail.
+; You can download a copy of the PHP manual from http://php.net/docs
+; and change docref_root to the base URL of your local copy including the
+; leading '/'. You must also specify the file extension being used including
+; the dot. PHP's default behavior is to leave these settings empty.
+; Note: Never use this feature for production boxes.
+; http://php.net/docref-root
+; Examples
+;docref_root = "/phpmanual/"
+
+; http://php.net/docref-ext
+;docref_ext = .html
+
+; String to output before an error message. PHP's default behavior is to leave
+; this setting blank.
+; http://php.net/error-prepend-string
+; Example:
+;error_prepend_string = "<span style='color: #ff0000'>"
+
+; String to output after an error message. PHP's default behavior is to leave
+; this setting blank.
+; http://php.net/error-append-string
+; Example:
+;error_append_string = "</span>"
+
+; Log errors to specified file. PHP's default behavior is to leave this value
+; empty.
+; http://php.net/error-log
+; Example:
+;error_log = php_errors.log
+; Log errors to syslog (Event Log on NT, not valid in Windows 95).
+;error_log = syslog
+
+;;;;;;;;;;;;;;;;;
+; Data Handling ;
+;;;;;;;;;;;;;;;;;
+
+; The separator used in PHP generated URLs to separate arguments.
+; PHP's default setting is "&".
+; http://php.net/arg-separator.output
+; Example:
+;arg_separator.output = "&amp;"
+
+; List of separator(s) used by PHP to parse input URLs into variables.
+; PHP's default setting is "&".
+; NOTE: Every character in this directive is considered as separator!
+; http://php.net/arg-separator.input
+; Example:
+;arg_separator.input = ";&"
+
+; This directive determines which super global arrays are registered when PHP
+; starts up. If the register_globals directive is enabled, it also determines
+; what order variables are populated into the global space. G,P,C,E & S are
+; abbreviations for the following respective super globals: GET, POST, COOKIE,
+; ENV and SERVER. There is a performance penalty paid for the registration of
+; these arrays and because ENV is not as commonly used as the others, ENV is
+; is not recommended on productions servers. You can still get access to
+; the environment variables through getenv() should you need to.
+; Default Value: "EGPCS"
+; Development Value: "GPCS"
+; Production Value: "GPCS";
+; http://php.net/variables-order
+variables_order = "GPCS"
+
+; This directive determines which super global data (G,P,C,E & S) should
+; be registered into the super global array REQUEST. If so, it also determines
+; the order in which that data is registered. The values for this directive are
+; specified in the same manner as the variables_order directive, EXCEPT one.
+; Leaving this value empty will cause PHP to use the value set in the
+; variables_order directive. It does not mean it will leave the super globals
+; array REQUEST empty.
+; Default Value: None
+; Development Value: "GP"
+; Production Value: "GP"
+; http://php.net/request-order
+request_order = "GP"
+
+; Whether or not to register the EGPCS variables as global variables.  You may
+; want to turn this off if you don't want to clutter your scripts' global scope
+; with user data.
+; You should do your best to write your scripts so that they do not require
+; register_globals to be on;  Using form variables as globals can easily lead
+; to possible security problems, if the code is not very well thought of.
+; http://php.net/register-globals
+register_globals = Off
+
+; Determines whether the deprecated long $HTTP_*_VARS type predefined variables
+; are registered by PHP or not. As they are deprecated, we obviously don't
+; recommend you use them. They are on by default for compatibility reasons but
+; they are not recommended on production servers.
+; Default Value: On
+; Development Value: Off
+; Production Value: Off
+; http://php.net/register-long-arrays
+register_long_arrays = Off
+
+; This directive determines whether PHP registers $argv & $argc each time it
+; runs. $argv contains an array of all the arguments passed to PHP when a script
+; is invoked. $argc contains an integer representing the number of arguments
+; that were passed when the script was invoked. These arrays are extremely
+; useful when running scripts from the command line. When this directive is
+; enabled, registering these variables consumes CPU cycles and memory each time
+; a script is executed. For performance reasons, this feature should be disabled
+; on production servers.
+; Note: This directive is hardcoded to On for the CLI SAPI
+; Default Value: On
+; Development Value: Off
+; Production Value: Off
+; http://php.net/register-argc-argv
+register_argc_argv = Off
+
+; When enabled, the SERVER and ENV variables are created when they're first
+; used (Just In Time) instead of when the script starts. If these variables
+; are not used within a script, having this directive on will result in a
+; performance gain. The PHP directives register_globals, register_long_arrays,
+; and register_argc_argv must be disabled for this directive to have any affect.
+; http://php.net/auto-globals-jit
+auto_globals_jit = On
+
+; Maximum size of POST data that PHP will accept.
+; http://php.net/post-max-size
+post_max_size = 8M
+
+; Magic quotes are a preprocessing feature of PHP where PHP will attempt to
+; escape any character sequences in GET, POST, COOKIE and ENV data which might
+; otherwise corrupt data being placed in resources such as databases before
+; making that data available to you. Because of character encoding issues and
+; non-standard SQL implementations across many databases, it's not currently
+; possible for this feature to be 100% accurate. PHP's default behavior is to
+; enable the feature. We strongly recommend you use the escaping mechanisms
+; designed specifically for the database your using instead of relying on this
+; feature. Also note, this feature has been deprecated as of PHP 5.3.0 and is
+; scheduled for removal in PHP 6.
+; Default Value: On
+; Development Value: Off
+; Production Value: Off
+; http://php.net/magic-quotes-gpc
+magic_quotes_gpc = Off
+
+; Magic quotes for runtime-generated data, e.g. data from SQL, from exec(), etc.
+; http://php.net/magic-quotes-runtime
+magic_quotes_runtime = Off
+
+; Use Sybase-style magic quotes (escape ' with '' instead of \').
+; http://php.net/magic-quotes-sybase
+magic_quotes_sybase = Off
+
+; Automatically add files before PHP document.
+; http://php.net/auto-prepend-file
+auto_prepend_file =
+
+; Automatically add files after PHP document.
+; http://php.net/auto-append-file
+auto_append_file =
+
+; By default, PHP will output a character encoding using
+; the Content-type: header.  To disable sending of the charset, simply
+; set it to be empty.
+;
+; PHP's built-in default is text/html
+; http://php.net/default-mimetype
+default_mimetype = "text/html"
+
+; PHP's default character set is set to empty.
+; http://php.net/default-charset
+;default_charset = "iso-8859-1"
+
+; Always populate the $HTTP_RAW_POST_DATA variable. PHP's default behavior is
+; to disable this feature.
+; http://php.net/always-populate-raw-post-data
+;always_populate_raw_post_data = On
+
+;;;;;;;;;;;;;;;;;;;;;;;;;
+; Paths and Directories ;
+;;;;;;;;;;;;;;;;;;;;;;;;;
+
+; UNIX: "/path1:/path2"
+;include_path = ".:/usr/share/php"
+;
+; Windows: "\path1;\path2"
+;include_path = ".;c:\php\includes"
+;
+; PHP's default setting for include_path is ".;/path/to/php/pear"
+; http://php.net/include-path
+
+; The root of the PHP pages, used only if nonempty.
+; if PHP was not compiled with FORCE_REDIRECT, you SHOULD set doc_root
+; if you are running php as a CGI under any web server (other than IIS)
+; see documentation for security issues.  The alternate is to use the
+; cgi.force_redirect configuration below
+; http://php.net/doc-root
+doc_root =
+
+; The directory under which PHP opens the script using /~username used only
+; if nonempty.
+; http://php.net/user-dir
+user_dir =
+
+; Directory in which the loadable extensions (modules) reside.
+; http://php.net/extension-dir
+; extension_dir = "lib/"
+
+; Whether or not to enable the dl() function.  The dl() function does NOT work
+; properly in multithreaded servers, such as IIS or Zeus, and is automatically
+; disabled on them.
+; http://php.net/enable-dl
+enable_dl = Off
+
+; cgi.force_redirect is necessary to provide security running PHP as a CGI under
+; most web servers.  Left undefined, PHP turns this on by default.  You can
+; turn it off here AT YOUR OWN RISK
+; **You CAN safely turn this off for IIS, in fact, you MUST.**
+; http://php.net/cgi.force-redirect
+;cgi.force_redirect = 1
+
+; if cgi.nph is enabled it will force cgi to always sent Status: 200 with
+; every request. PHP's default behavior is to disable this feature.
+;cgi.nph = 1
+
+; if cgi.force_redirect is turned on, and you are not running under Apache or Netscape
+; (iPlanet) web servers, you MAY need to set an environment variable name that PHP
+; will look for to know it is OK to continue execution.  Setting this variable MAY
+; cause security issues, KNOW WHAT YOU ARE DOING FIRST.
+; http://php.net/cgi.redirect-status-env
+;cgi.redirect_status_env = ;
+
+; cgi.fix_pathinfo provides *real* PATH_INFO/PATH_TRANSLATED support for CGI.  PHP's
+; previous behaviour was to set PATH_TRANSLATED to SCRIPT_FILENAME, and to not grok
+; what PATH_INFO is.  For more information on PATH_INFO, see the cgi specs.  Setting
+; this to 1 will cause PHP CGI to fix its paths to conform to the spec.  A setting
+; of zero causes PHP to behave as before.  Default is 1.  You should fix your scripts
+; to use SCRIPT_FILENAME rather than PATH_TRANSLATED.
+; http://php.net/cgi.fix-pathinfo
+;cgi.fix_pathinfo=1
+
+; FastCGI under IIS (on WINNT based OS) supports the ability to impersonate
+; security tokens of the calling client.  This allows IIS to define the
+; security context that the request runs under.  mod_fastcgi under Apache
+; does not currently support this feature (03/17/2002)
+; Set to 1 if running under IIS.  Default is zero.
+; http://php.net/fastcgi.impersonate
+;fastcgi.impersonate = 1;
+
+; Disable logging through FastCGI connection. PHP's default behavior is to enable
+; this feature.
+;fastcgi.logging = 0
+
+; cgi.rfc2616_headers configuration option tells PHP what type of headers to
+; use when sending HTTP response code. If it's set 0 PHP sends Status: header that
+; is supported by Apache. When this option is set to 1 PHP will send
+; RFC2616 compliant header.
+; Default is zero.
+; http://php.net/cgi.rfc2616-headers
+;cgi.rfc2616_headers = 0
+
+;;;;;;;;;;;;;;;;
+; File Uploads ;
+;;;;;;;;;;;;;;;;
+
+; Whether to allow HTTP file uploads.
+; http://php.net/file-uploads
+file_uploads = On
+
+; Temporary directory for HTTP uploaded files (will use system default if not
+; specified).
+; http://php.net/upload-tmp-dir
+;upload_tmp_dir =
+
+; Maximum allowed size for uploaded files.
+; http://php.net/upload-max-filesize
+upload_max_filesize = 2M
+
+; Maximum number of files that can be uploaded via a single request
+max_file_uploads = 20
+
+;;;;;;;;;;;;;;;;;;
+; Fopen wrappers ;
+;;;;;;;;;;;;;;;;;;
+
+; Whether to allow the treatment of URLs (like http:// or ftp://) as files.
+; http://php.net/allow-url-fopen
+allow_url_fopen = On
+
+; Whether to allow include/require to open URLs (like http:// or ftp://) as files.
+; http://php.net/allow-url-include
+allow_url_include = Off
+
+; Define the anonymous ftp password (your email address). PHP's default setting
+; for this is empty.
+; http://php.net/from
+;from="john@doe.com"
+
+; Define the User-Agent string. PHP's default setting for this is empty.
+; http://php.net/user-agent
+;user_agent="PHP"
+
+; Default timeout for socket based streams (seconds)
+; http://php.net/default-socket-timeout
+default_socket_timeout = 60
+
+; If your scripts have to deal with files from Macintosh systems,
+; or you are running on a Mac and need to deal with files from
+; unix or win32 systems, setting this flag will cause PHP to
+; automatically detect the EOL character in those files so that
+; fgets() and file() will work regardless of the source of the file.
+; http://php.net/auto-detect-line-endings
+;auto_detect_line_endings = Off
+
+;;;;;;;;;;;;;;;;;;;;;;
+; Dynamic Extensions ;
+;;;;;;;;;;;;;;;;;;;;;;
+
+extension=./lib/tarantool.so
+; If you wish to have an extension loaded automatically, use the following
+; syntax:
+;
+;   extension=modulename.extension
+;
+; For example, on Windows:
+;
+;   extension=msql.dll
+;
+; ... or under UNIX:
+;
+;   extension=msql.so
+;
+; ... or with a path:
+;
+;   extension=/path/to/extension/msql.so
+;
+; If you only provide the name of the extension, PHP will look for it in its
+; default extension directory.
+
+;;;;;;;;;;;;;;;;;;;
+; Module Settings ;
+;;;;;;;;;;;;;;;;;;;
+
+[Date]
+; Defines the default timezone used by the date functions
+; http://php.net/date.timezone
+;date.timezone =
+
+; http://php.net/date.default-latitude
+;date.default_latitude = 31.7667
+
+; http://php.net/date.default-longitude
+;date.default_longitude = 35.2333
+
+; http://php.net/date.sunrise-zenith
+;date.sunrise_zenith = 90.583333
+
+; http://php.net/date.sunset-zenith
+;date.sunset_zenith = 90.583333
+
+[filter]
+; http://php.net/filter.default
+;filter.default = unsafe_raw
+
+; http://php.net/filter.default-flags
+;filter.default_flags =
+
+[iconv]
+;iconv.input_encoding = ISO-8859-1
+;iconv.internal_encoding = ISO-8859-1
+;iconv.output_encoding = ISO-8859-1
+
+[intl]
+;intl.default_locale =
+; This directive allows you to produce PHP errors when some error
+; happens within intl functions. The value is the level of the error produced.
+; Default is 0, which does not produce any errors.
+;intl.error_level = E_WARNING
+
+[sqlite]
+; http://php.net/sqlite.assoc-case
+;sqlite.assoc_case = 0
+
+[sqlite3]
+;sqlite3.extension_dir =
+
+[Pcre]
+;PCRE library backtracking limit.
+; http://php.net/pcre.backtrack-limit
+;pcre.backtrack_limit=100000
+
+;PCRE library recursion limit.
+;Please note that if you set this value to a high number you may consume all
+;the available process stack and eventually crash PHP (due to reaching the
+;stack size limit imposed by the Operating System).
+; http://php.net/pcre.recursion-limit
+;pcre.recursion_limit=100000
+
+[Pdo]
+; Whether to pool ODBC connections. Can be one of "strict", "relaxed" or "off"
+; http://php.net/pdo-odbc.connection-pooling
+;pdo_odbc.connection_pooling=strict
+
+;pdo_odbc.db2_instance_name
+
+[Pdo_mysql]
+; If mysqlnd is used: Number of cache slots for the internal result set cache
+; http://php.net/pdo_mysql.cache_size
+pdo_mysql.cache_size = 2000
+
+; Default socket name for local MySQL connects.  If empty, uses the built-in
+; MySQL defaults.
+; http://php.net/pdo_mysql.default-socket
+pdo_mysql.default_socket=
+
+[Phar]
+; http://php.net/phar.readonly
+;phar.readonly = On
+
+; http://php.net/phar.require-hash
+;phar.require_hash = On
+
+;phar.cache_list =
+
+[Syslog]
+; Whether or not to define the various syslog variables (e.g. $LOG_PID,
+; $LOG_CRON, etc.).  Turning it off is a good idea performance-wise.  In
+; runtime, you can define these variables by calling define_syslog_variables().
+; http://php.net/define-syslog-variables
+define_syslog_variables  = Off
+
+[mail function]
+; For Win32 only.
+; http://php.net/smtp
+SMTP = localhost
+; http://php.net/smtp-port
+smtp_port = 25
+
+; For Win32 only.
+; http://php.net/sendmail-from
+;sendmail_from = me@example.com
+
+; For Unix only.  You may supply arguments as well (default: "sendmail -t -i").
+; http://php.net/sendmail-path
+;sendmail_path =
+
+; Force the addition of the specified parameters to be passed as extra parameters
+; to the sendmail binary. These parameters will always replace the value of
+; the 5th parameter to mail(), even in safe mode.
+;mail.force_extra_parameters =
+
+; Add X-PHP-Originating-Script: that will include uid of the script followed by the filename
+mail.add_x_header = On
+
+; The path to a log file that will log all mail() calls. Log entries include
+; the full path of the script, line number, To address and headers.
+;mail.log =
+
+[SQL]
+; http://php.net/sql.safe-mode
+sql.safe_mode = Off
+
+[ODBC]
+; http://php.net/odbc.default-db
+;odbc.default_db    =  Not yet implemented
+
+; http://php.net/odbc.default-user
+;odbc.default_user  =  Not yet implemented
+
+; http://php.net/odbc.default-pw
+;odbc.default_pw    =  Not yet implemented
+
+; Controls the ODBC cursor model.
+; Default: SQL_CURSOR_STATIC (default).
+;odbc.default_cursortype
+
+; Allow or prevent persistent links.
+; http://php.net/odbc.allow-persistent
+odbc.allow_persistent = On
+
+; Check that a connection is still valid before reuse.
+; http://php.net/odbc.check-persistent
+odbc.check_persistent = On
+
+; Maximum number of persistent links.  -1 means no limit.
+; http://php.net/odbc.max-persistent
+odbc.max_persistent = -1
+
+; Maximum number of links (persistent + non-persistent).  -1 means no limit.
+; http://php.net/odbc.max-links
+odbc.max_links = -1
+
+; Handling of LONG fields.  Returns number of bytes to variables.  0 means
+; passthru.
+; http://php.net/odbc.defaultlrl
+odbc.defaultlrl = 4096
+
+; Handling of binary data.  0 means passthru, 1 return as is, 2 convert to char.
+; See the documentation on odbc_binmode and odbc_longreadlen for an explanation
+; of odbc.defaultlrl and odbc.defaultbinmode
+; http://php.net/odbc.defaultbinmode
+odbc.defaultbinmode = 1
+
+;birdstep.max_links = -1
+
+[Interbase]
+; Allow or prevent persistent links.
+ibase.allow_persistent = 1
+
+; Maximum number of persistent links.  -1 means no limit.
+ibase.max_persistent = -1
+
+; Maximum number of links (persistent + non-persistent).  -1 means no limit.
+ibase.max_links = -1
+
+; Default database name for ibase_connect().
+;ibase.default_db =
+
+; Default username for ibase_connect().
+;ibase.default_user =
+
+; Default password for ibase_connect().
+;ibase.default_password =
+
+; Default charset for ibase_connect().
+;ibase.default_charset =
+
+; Default timestamp format.
+ibase.timestampformat = "%Y-%m-%d %H:%M:%S"
+
+; Default date format.
+ibase.dateformat = "%Y-%m-%d"
+
+; Default time format.
+ibase.timeformat = "%H:%M:%S"
+
+[MySQL]
+; Allow accessing, from PHP's perspective, local files with LOAD DATA statements
+; http://php.net/mysql.allow_local_infile
+mysql.allow_local_infile = On
+
+; Allow or prevent persistent links.
+; http://php.net/mysql.allow-persistent
+mysql.allow_persistent = On
+
+; If mysqlnd is used: Number of cache slots for the internal result set cache
+; http://php.net/mysql.cache_size
+mysql.cache_size = 2000
+
+; Maximum number of persistent links.  -1 means no limit.
+; http://php.net/mysql.max-persistent
+mysql.max_persistent = -1
+
+; Maximum number of links (persistent + non-persistent).  -1 means no limit.
+; http://php.net/mysql.max-links
+mysql.max_links = -1
+
+; Default port number for mysql_connect().  If unset, mysql_connect() will use
+; the $MYSQL_TCP_PORT or the mysql-tcp entry in /etc/services or the
+; compile-time value defined MYSQL_PORT (in that order).  Win32 will only look
+; at MYSQL_PORT.
+; http://php.net/mysql.default-port
+mysql.default_port =
+
+; Default socket name for local MySQL connects.  If empty, uses the built-in
+; MySQL defaults.
+; http://php.net/mysql.default-socket
+mysql.default_socket =
+
+; Default host for mysql_connect() (doesn't apply in safe mode).
+; http://php.net/mysql.default-host
+mysql.default_host =
+
+; Default user for mysql_connect() (doesn't apply in safe mode).
+; http://php.net/mysql.default-user
+mysql.default_user =
+
+; Default password for mysql_connect() (doesn't apply in safe mode).
+; Note that this is generally a *bad* idea to store passwords in this file.
+; *Any* user with PHP access can run 'echo get_cfg_var("mysql.default_password")
+; and reveal this password!  And of course, any users with read access to this
+; file will be able to reveal the password as well.
+; http://php.net/mysql.default-password
+mysql.default_password =
+
+; Maximum time (in seconds) for connect timeout. -1 means no limit
+; http://php.net/mysql.connect-timeout
+mysql.connect_timeout = 60
+
+; Trace mode. When trace_mode is active (=On), warnings for table/index scans and
+; SQL-Errors will be displayed.
+; http://php.net/mysql.trace-mode
+mysql.trace_mode = Off
+
+[MySQLi]
+
+; Maximum number of persistent links.  -1 means no limit.
+; http://php.net/mysqli.max-persistent
+mysqli.max_persistent = -1
+
+; Allow accessing, from PHP's perspective, local files with LOAD DATA statements
+; http://php.net/mysqli.allow_local_infile
+;mysqli.allow_local_infile = On
+
+; Allow or prevent persistent links.
+; http://php.net/mysqli.allow-persistent
+mysqli.allow_persistent = On
+
+; Maximum number of links.  -1 means no limit.
+; http://php.net/mysqli.max-links
+mysqli.max_links = -1
+
+; If mysqlnd is used: Number of cache slots for the internal result set cache
+; http://php.net/mysqli.cache_size
+mysqli.cache_size = 2000
+
+; Default port number for mysqli_connect().  If unset, mysqli_connect() will use
+; the $MYSQL_TCP_PORT or the mysql-tcp entry in /etc/services or the
+; compile-time value defined MYSQL_PORT (in that order).  Win32 will only look
+; at MYSQL_PORT.
+; http://php.net/mysqli.default-port
+mysqli.default_port = 3306
+
+; Default socket name for local MySQL connects.  If empty, uses the built-in
+; MySQL defaults.
+; http://php.net/mysqli.default-socket
+mysqli.default_socket =
+
+; Default host for mysql_connect() (doesn't apply in safe mode).
+; http://php.net/mysqli.default-host
+mysqli.default_host =
+
+; Default user for mysql_connect() (doesn't apply in safe mode).
+; http://php.net/mysqli.default-user
+mysqli.default_user =
+
+; Default password for mysqli_connect() (doesn't apply in safe mode).
+; Note that this is generally a *bad* idea to store passwords in this file.
+; *Any* user with PHP access can run 'echo get_cfg_var("mysqli.default_pw")
+; and reveal this password!  And of course, any users with read access to this
+; file will be able to reveal the password as well.
+; http://php.net/mysqli.default-pw
+mysqli.default_pw =
+
+; Allow or prevent reconnect
+mysqli.reconnect = Off
+
+[mysqlnd]
+; Enable / Disable collection of general statstics by mysqlnd which can be
+; used to tune and monitor MySQL operations.
+; http://php.net/mysqlnd.collect_statistics
+mysqlnd.collect_statistics = On
+
+; Enable / Disable collection of memory usage statstics by mysqlnd which can be
+; used to tune and monitor MySQL operations.
+; http://php.net/mysqlnd.collect_memory_statistics
+mysqlnd.collect_memory_statistics = Off
+
+; Size of a pre-allocated buffer used when sending commands to MySQL in bytes.
+; http://php.net/mysqlnd.net_cmd_buffer_size
+;mysqlnd.net_cmd_buffer_size = 2048
+
+; Size of a pre-allocated buffer used for reading data sent by the server in
+; bytes.
+; http://php.net/mysqlnd.net_read_buffer_size
+;mysqlnd.net_read_buffer_size = 32768
+
+[OCI8]
+
+; Connection: Enables privileged connections using external
+; credentials (OCI_SYSOPER, OCI_SYSDBA)
+; http://php.net/oci8.privileged-connect
+;oci8.privileged_connect = Off
+
+; Connection: The maximum number of persistent OCI8 connections per
+; process. Using -1 means no limit.
+; http://php.net/oci8.max-persistent
+;oci8.max_persistent = -1
+
+; Connection: The maximum number of seconds a process is allowed to
+; maintain an idle persistent connection. Using -1 means idle
+; persistent connections will be maintained forever.
+; http://php.net/oci8.persistent-timeout
+;oci8.persistent_timeout = -1
+
+; Connection: The number of seconds that must pass before issuing a
+; ping during oci_pconnect() to check the connection validity. When
+; set to 0, each oci_pconnect() will cause a ping. Using -1 disables
+; pings completely.
+; http://php.net/oci8.ping-interval
+;oci8.ping_interval = 60
+
+; Connection: Set this to a user chosen connection class to be used
+; for all pooled server requests with Oracle 11g Database Resident
+; Connection Pooling (DRCP).  To use DRCP, this value should be set to
+; the same string for all web servers running the same application,
+; the database pool must be configured, and the connection string must
+; specify to use a pooled server.
+;oci8.connection_class =
+
+; High Availability: Using On lets PHP receive Fast Application
+; Notification (FAN) events generated when a database node fails. The
+; database must also be configured to post FAN events.
+;oci8.events = Off
+
+; Tuning: This option enables statement caching, and specifies how
+; many statements to cache. Using 0 disables statement caching.
+; http://php.net/oci8.statement-cache-size
+;oci8.statement_cache_size = 20
+
+; Tuning: Enables statement prefetching and sets the default number of
+; rows that will be fetched automatically after statement execution.
+; http://php.net/oci8.default-prefetch
+;oci8.default_prefetch = 100
+
+; Compatibility. Using On means oci_close() will not close
+; oci_connect() and oci_new_connect() connections.
+; http://php.net/oci8.old-oci-close-semantics
+;oci8.old_oci_close_semantics = Off
+
+[PostgresSQL]
+; Allow or prevent persistent links.
+; http://php.net/pgsql.allow-persistent
+pgsql.allow_persistent = On
+
+; Detect broken persistent links always with pg_pconnect().
+; Auto reset feature requires a little overheads.
+; http://php.net/pgsql.auto-reset-persistent
+pgsql.auto_reset_persistent = Off
+
+; Maximum number of persistent links.  -1 means no limit.
+; http://php.net/pgsql.max-persistent
+pgsql.max_persistent = -1
+
+; Maximum number of links (persistent+non persistent).  -1 means no limit.
+; http://php.net/pgsql.max-links
+pgsql.max_links = -1
+
+; Ignore PostgreSQL backends Notice message or not.
+; Notice message logging require a little overheads.
+; http://php.net/pgsql.ignore-notice
+pgsql.ignore_notice = 0
+
+; Log PostgreSQL backends Notice message or not.
+; Unless pgsql.ignore_notice=0, module cannot log notice message.
+; http://php.net/pgsql.log-notice
+pgsql.log_notice = 0
+
+[Sybase-CT]
+; Allow or prevent persistent links.
+; http://php.net/sybct.allow-persistent
+sybct.allow_persistent = On
+
+; Maximum number of persistent links.  -1 means no limit.
+; http://php.net/sybct.max-persistent
+sybct.max_persistent = -1
+
+; Maximum number of links (persistent + non-persistent).  -1 means no limit.
+; http://php.net/sybct.max-links
+sybct.max_links = -1
+
+; Minimum server message severity to display.
+; http://php.net/sybct.min-server-severity
+sybct.min_server_severity = 10
+
+; Minimum client message severity to display.
+; http://php.net/sybct.min-client-severity
+sybct.min_client_severity = 10
+
+; Set per-context timeout
+; http://php.net/sybct.timeout
+;sybct.timeout=
+
+;sybct.packet_size
+
+; The maximum time in seconds to wait for a connection attempt to succeed before returning failure.
+; Default: one minute
+;sybct.login_timeout=
+
+; The name of the host you claim to be connecting from, for display by sp_who.
+; Default: none
+;sybct.hostname=
+
+; Allows you to define how often deadlocks are to be retried. -1 means "forever".
+; Default: 0
+;sybct.deadlock_retry_count=
+
+[bcmath]
+; Number of decimal digits for all bcmath functions.
+; http://php.net/bcmath.scale
+bcmath.scale = 0
+
+[browscap]
+; http://php.net/browscap
+;browscap = extra/browscap.ini
+
+[Session]
+; Handler used to store/retrieve data.
+; http://php.net/session.save-handler
+session.save_handler = files
+
+; Argument passed to save_handler.  In the case of files, this is the path
+; where data files are stored. Note: Windows users have to change this
+; variable in order to use PHP's session functions.
+;
+; The path can be defined as:
+;
+;     session.save_path = "N;/path"
+;
+; where N is an integer.  Instead of storing all the session files in
+; /path, what this will do is use subdirectories N-levels deep, and
+; store the session data in those directories.  This is useful if you
+; or your OS have problems with lots of files in one directory, and is
+; a more efficient layout for servers that handle lots of sessions.
+;
+; NOTE 1: PHP will not create this directory structure automatically.
+;         You can use the script in the ext/session dir for that purpose.
+; NOTE 2: See the section on garbage collection below if you choose to
+;         use subdirectories for session storage
+;
+; The file storage module creates files using mode 600 by default.
+; You can change that by using
+;
+;     session.save_path = "N;MODE;/path"
+;
+; where MODE is the octal representation of the mode. Note that this
+; does not overwrite the process's umask.
+; http://php.net/session.save-path
+;session.save_path = "/tmp"
+
+; Whether to use cookies.
+; http://php.net/session.use-cookies
+session.use_cookies = 1
+
+; http://php.net/session.cookie-secure
+;session.cookie_secure =
+
+; This option forces PHP to fetch and use a cookie for storing and maintaining
+; the session id. We encourage this operation as it's very helpful in combatting
+; session hijacking when not specifying and managing your own session id. It is
+; not the end all be all of session hijacking defense, but it's a good start.
+; http://php.net/session.use-only-cookies
+session.use_only_cookies = 1
+
+; Name of the session (used as cookie name).
+; http://php.net/session.name
+session.name = PHPSESSID
+
+; Initialize session on request startup.
+; http://php.net/session.auto-start
+session.auto_start = 0
+
+; Lifetime in seconds of cookie or, if 0, until browser is restarted.
+; http://php.net/session.cookie-lifetime
+session.cookie_lifetime = 0
+
+; The path for which the cookie is valid.
+; http://php.net/session.cookie-path
+session.cookie_path = /
+
+; The domain for which the cookie is valid.
+; http://php.net/session.cookie-domain
+session.cookie_domain =
+
+; Whether or not to add the httpOnly flag to the cookie, which makes it inaccessible to browser scripting languages such as JavaScript.
+; http://php.net/session.cookie-httponly
+session.cookie_httponly =
+
+; Handler used to serialize data.  php is the standard serializer of PHP.
+; http://php.net/session.serialize-handler
+session.serialize_handler = php
+
+; Defines the probability that the 'garbage collection' process is started
+; on every session initialization. The probability is calculated by using
+; gc_probability/gc_divisor. Where session.gc_probability is the numerator
+; and gc_divisor is the denominator in the equation. Setting this value to 1
+; when the session.gc_divisor value is 100 will give you approximately a 1% chance
+; the gc will run on any give request.
+; Default Value: 1
+; Development Value: 1
+; Production Value: 1
+; http://php.net/session.gc-probability
+session.gc_probability = 0
+
+; Defines the probability that the 'garbage collection' process is started on every
+; session initialization. The probability is calculated by using the following equation:
+; gc_probability/gc_divisor. Where session.gc_probability is the numerator and
+; session.gc_divisor is the denominator in the equation. Setting this value to 1
+; when the session.gc_divisor value is 100 will give you approximately a 1% chance
+; the gc will run on any give request. Increasing this value to 1000 will give you
+; a 0.1% chance the gc will run on any give request. For high volume production servers,
+; this is a more efficient approach.
+; Default Value: 100
+; Development Value: 1000
+; Production Value: 1000
+; http://php.net/session.gc-divisor
+session.gc_divisor = 1000
+
+; After this number of seconds, stored data will be seen as 'garbage' and
+; cleaned up by the garbage collection process.
+; http://php.net/session.gc-maxlifetime
+session.gc_maxlifetime = 1440
+
+; NOTE: If you are using the subdirectory option for storing session files
+;       (see session.save_path above), then garbage collection does *not*
+;       happen automatically.  You will need to do your own garbage
+;       collection through a shell script, cron entry, or some other method.
+;       For example, the following script would is the equivalent of
+;       setting session.gc_maxlifetime to 1440 (1440 seconds = 24 minutes):
+;          find /path/to/sessions -cmin +24 | xargs rm
+
+; PHP 4.2 and less have an undocumented feature/bug that allows you to
+; to initialize a session variable in the global scope, even when register_globals
+; is disabled.  PHP 4.3 and later will warn you, if this feature is used.
+; You can disable the feature and the warning separately. At this time,
+; the warning is only displayed, if bug_compat_42 is enabled. This feature
+; introduces some serious security problems if not handled correctly. It's
+; recommended that you do not use this feature on production servers. But you
+; should enable this on development servers and enable the warning as well. If you
+; do not enable the feature on development servers, you won't be warned when it's
+; used and debugging errors caused by this can be difficult to track down.
+; Default Value: On
+; Development Value: On
+; Production Value: Off
+; http://php.net/session.bug-compat-42
+session.bug_compat_42 = Off
+
+; This setting controls whether or not you are warned by PHP when initializing a
+; session value into the global space. session.bug_compat_42 must be enabled before
+; these warnings can be issued by PHP. See the directive above for more information.
+; Default Value: On
+; Development Value: On
+; Production Value: Off
+; http://php.net/session.bug-compat-warn
+session.bug_compat_warn = Off
+
+; Check HTTP Referer to invalidate externally stored URLs containing ids.
+; HTTP_REFERER has to contain this substring for the session to be
+; considered as valid.
+; http://php.net/session.referer-check
+session.referer_check =
+
+; How many bytes to read from the file.
+; http://php.net/session.entropy-length
+session.entropy_length = 0
+
+; Specified here to create the session id.
+; http://php.net/session.entropy-file
+; On systems that don't have /dev/urandom /dev/arandom can be used
+; On windows, setting the entropy_length setting will activate the 
+; Windows random source (using the CryptoAPI)
+;session.entropy_file = /dev/urandom
+
+; Set to {nocache,private,public,} to determine HTTP caching aspects
+; or leave this empty to avoid sending anti-caching headers.
+; http://php.net/session.cache-limiter
+session.cache_limiter = nocache
+
+; Document expires after n minutes.
+; http://php.net/session.cache-expire
+session.cache_expire = 180
+
+; trans sid support is disabled by default.
+; Use of trans sid may risk your users security.
+; Use this option with caution.
+; - User may send URL contains active session ID
+;   to other person via. email/irc/etc.
+; - URL that contains active session ID may be stored
+;   in publically accessible computer.
+; - User may access your site with the same session ID
+;   always using URL stored in browser's history or bookmarks.
+; http://php.net/session.use-trans-sid
+session.use_trans_sid = 0
+
+; Select a hash function for use in generating session ids.
+; Possible Values
+;   0  (MD5 128 bits)
+;   1  (SHA-1 160 bits)
+; This option may also be set to the name of any hash function supported by
+; the hash extension. A list of available hashes is returned by the hash_algos()
+; function.
+; http://php.net/session.hash-function
+session.hash_function = 0
+
+; Define how many bits are stored in each character when converting
+; the binary hash data to something readable.
+; Possible values:
+;   4  (4 bits: 0-9, a-f)
+;   5  (5 bits: 0-9, a-v)
+;   6  (6 bits: 0-9, a-z, A-Z, "-", ",")
+; Default Value: 4
+; Development Value: 5
+; Production Value: 5
+; http://php.net/session.hash-bits-per-character
+session.hash_bits_per_character = 5
+
+; The URL rewriter will look for URLs in a defined set of HTML tags.
+; form/fieldset are special; if you include them here, the rewriter will
+; add a hidden <input> field with the info which is otherwise appended
+; to URLs.  If you want XHTML conformity, remove the form entry.
+; Note that all valid entries require a "=", even if no value follows.
+; Default Value: "a=href,area=href,frame=src,form=,fieldset="
+; Development Value: "a=href,area=href,frame=src,input=src,form=fakeentry"
+; Production Value: "a=href,area=href,frame=src,input=src,form=fakeentry"
+; http://php.net/url-rewriter.tags
+url_rewriter.tags = "a=href,area=href,frame=src,input=src,form=fakeentry"
+
+[MSSQL]
+; Allow or prevent persistent links.
+mssql.allow_persistent = On
+
+; Maximum number of persistent links.  -1 means no limit.
+mssql.max_persistent = -1
+
+; Maximum number of links (persistent+non persistent).  -1 means no limit.
+mssql.max_links = -1
+
+; Minimum error severity to display.
+mssql.min_error_severity = 10
+
+; Minimum message severity to display.
+mssql.min_message_severity = 10
+
+; Compatibility mode with old versions of PHP 3.0.
+mssql.compatability_mode = Off
+
+; Connect timeout
+;mssql.connect_timeout = 5
+
+; Query timeout
+;mssql.timeout = 60
+
+; Valid range 0 - 2147483647.  Default = 4096.
+;mssql.textlimit = 4096
+
+; Valid range 0 - 2147483647.  Default = 4096.
+;mssql.textsize = 4096
+
+; Limits the number of records in each batch.  0 = all records in one batch.
+;mssql.batchsize = 0
+
+; Specify how datetime and datetim4 columns are returned
+; On => Returns data converted to SQL server settings
+; Off => Returns values as YYYY-MM-DD hh:mm:ss
+;mssql.datetimeconvert = On
+
+; Use NT authentication when connecting to the server
+mssql.secure_connection = Off
+
+; Specify max number of processes. -1 = library default
+; msdlib defaults to 25
+; FreeTDS defaults to 4096
+;mssql.max_procs = -1
+
+; Specify client character set.
+; If empty or not set the client charset from freetds.comf is used
+; This is only used when compiled with FreeTDS
+;mssql.charset = "ISO-8859-1"
+
+[Assertion]
+; Assert(expr); active by default.
+; http://php.net/assert.active
+;assert.active = On
+
+; Issue a PHP warning for each failed assertion.
+; http://php.net/assert.warning
+;assert.warning = On
+
+; Don't bail out by default.
+; http://php.net/assert.bail
+;assert.bail = Off
+
+; User-function to be called if an assertion fails.
+; http://php.net/assert.callback
+;assert.callback = 0
+
+; Eval the expression with current error_reporting().  Set to true if you want
+; error_reporting(0) around the eval().
+; http://php.net/assert.quiet-eval
+;assert.quiet_eval = 0
+
+[COM]
+; path to a file containing GUIDs, IIDs or filenames of files with TypeLibs
+; http://php.net/com.typelib-file
+;com.typelib_file =
+
+; allow Distributed-COM calls
+; http://php.net/com.allow-dcom
+;com.allow_dcom = true
+
+; autoregister constants of a components typlib on com_load()
+; http://php.net/com.autoregister-typelib
+;com.autoregister_typelib = true
+
+; register constants casesensitive
+; http://php.net/com.autoregister-casesensitive
+;com.autoregister_casesensitive = false
+
+; show warnings on duplicate constant registrations
+; http://php.net/com.autoregister-verbose
+;com.autoregister_verbose = true
+
+; The default character set code-page to use when passing strings to and from COM objects.
+; Default: system ANSI code page
+;com.code_page=
+
+[mbstring]
+; language for internal character representation.
+; http://php.net/mbstring.language
+;mbstring.language = Japanese
+
+; internal/script encoding.
+; Some encoding cannot work as internal encoding.
+; (e.g. SJIS, BIG5, ISO-2022-*)
+; http://php.net/mbstring.internal-encoding
+;mbstring.internal_encoding = EUC-JP
+
+; http input encoding.
+; http://php.net/mbstring.http-input
+;mbstring.http_input = auto
+
+; http output encoding. mb_output_handler must be
+; registered as output buffer to function
+; http://php.net/mbstring.http-output
+;mbstring.http_output = SJIS
+
+; enable automatic encoding translation according to
+; mbstring.internal_encoding setting. Input chars are
+; converted to internal encoding by setting this to On.
+; Note: Do _not_ use automatic encoding translation for
+;       portable libs/applications.
+; http://php.net/mbstring.encoding-translation
+;mbstring.encoding_translation = Off
+
+; automatic encoding detection order.
+; auto means
+; http://php.net/mbstring.detect-order
+;mbstring.detect_order = auto
+
+; substitute_character used when character cannot be converted
+; one from another
+; http://php.net/mbstring.substitute-character
+;mbstring.substitute_character = none;
+
+; overload(replace) single byte functions by mbstring functions.
+; mail(), ereg(), etc are overloaded by mb_send_mail(), mb_ereg(),
+; etc. Possible values are 0,1,2,4 or combination of them.
+; For example, 7 for overload everything.
+; 0: No overload
+; 1: Overload mail() function
+; 2: Overload str*() functions
+; 4: Overload ereg*() functions
+; http://php.net/mbstring.func-overload
+;mbstring.func_overload = 0
+
+; enable strict encoding detection.
+;mbstring.strict_detection = Off
+
+; This directive specifies the regex pattern of content types for which mb_output_handler()
+; is activated.
+; Default: mbstring.http_output_conv_mimetype=^(text/|application/xhtml\+xml)
+;mbstring.http_output_conv_mimetype=
+
+; Allows to set script encoding. Only affects if PHP is compiled with --enable-zend-multibyte
+; Default: ""
+;mbstring.script_encoding=
+
+[gd]
+; Tell the jpeg decode to ignore warnings and try to create
+; a gd image. The warning will then be displayed as notices
+; disabled by default
+; http://php.net/gd.jpeg-ignore-warning
+;gd.jpeg_ignore_warning = 0
+
+[exif]
+; Exif UNICODE user comments are handled as UCS-2BE/UCS-2LE and JIS as JIS.
+; With mbstring support this will automatically be converted into the encoding
+; given by corresponding encode setting. When empty mbstring.internal_encoding
+; is used. For the decode settings you can distinguish between motorola and
+; intel byte order. A decode setting cannot be empty.
+; http://php.net/exif.encode-unicode
+;exif.encode_unicode = ISO-8859-15
+
+; http://php.net/exif.decode-unicode-motorola
+;exif.decode_unicode_motorola = UCS-2BE
+
+; http://php.net/exif.decode-unicode-intel
+;exif.decode_unicode_intel    = UCS-2LE
+
+; http://php.net/exif.encode-jis
+;exif.encode_jis =
+
+; http://php.net/exif.decode-jis-motorola
+;exif.decode_jis_motorola = JIS
+
+; http://php.net/exif.decode-jis-intel
+;exif.decode_jis_intel    = JIS
+
+[Tidy]
+; The path to a default tidy configuration file to use when using tidy
+; http://php.net/tidy.default-config
+;tidy.default_config = /usr/local/lib/php/default.tcfg
+
+; Should tidy clean and repair output automatically?
+; WARNING: Do not use this option if you are generating non-html content
+; such as dynamic images
+; http://php.net/tidy.clean-output
+tidy.clean_output = Off
+
+[soap]
+; Enables or disables WSDL caching feature.
+; http://php.net/soap.wsdl-cache-enabled
+soap.wsdl_cache_enabled=1
+
+; Sets the directory name where SOAP extension will put cache files.
+; http://php.net/soap.wsdl-cache-dir
+soap.wsdl_cache_dir="/tmp"
+
+; (time to live) Sets the number of second while cached file will be used
+; instead of original one.
+; http://php.net/soap.wsdl-cache-ttl
+soap.wsdl_cache_ttl=86400
+
+; Sets the size of the cache limit. (Max. number of WSDL files to cache)
+soap.wsdl_cache_limit = 5
+
+[sysvshm]
+; A default size of the shared memory segment
+;sysvshm.init_mem = 10000
+
+[ldap]
+; Sets the maximum number of open links or -1 for unlimited.
+ldap.max_links = -1
+
+[mcrypt]
+; For more information about mcrypt settings see http://php.net/mcrypt-module-open
+
+; Directory where to load mcrypt algorithms
+; Default: Compiled in into libmcrypt (usually /usr/local/lib/libmcrypt)
+;mcrypt.algorithms_dir=
+
+; Directory where to load mcrypt modes
+; Default: Compiled in into libmcrypt (usually /usr/local/lib/libmcrypt)
+;mcrypt.modes_dir=
+
+[dba]
+;dba.default_handler=
+
+; Local Variables:
+; tab-width: 4
+; End:
diff --git a/connector/php/test/etc/tarantool_box.cfg b/connector/php/test/etc/tarantool_box.cfg
new file mode 100644
index 0000000000000000000000000000000000000000..39be5f4f3a58c00e1b25b548a66f5cd40cd39165
--- /dev/null
+++ b/connector/php/test/etc/tarantool_box.cfg
@@ -0,0 +1,61 @@
+# Limit of memory used to store tuples to 100MB
+# (0.1 GB)
+# This effectively limits the memory, used by
+# Tarantool. However, index and connection memory
+# is stored outside the slab allocator, hence
+# the effective memory usage can be higher (sometimes
+# twice as high).
+slab_alloc_arena = 0.1
+
+# Tarantool executable directory
+work_dir = "var"
+
+# Store the pid in this file. Relative to startup dir.
+pid_file = "tarantool_box.pid"
+
+# Snapshot directory (where snapshots get saved/read)
+snap_dir="."
+
+# WAL directory (where WALs get saved/read)
+wal_dir="."
+
+# Pipe the logs into the following process.
+logger="cat - >> tarantool_box.log"
+
+# Read only and read-write port.
+primary_port = 33013
+
+# Read-only port.
+secondary_port = 33014
+
+# The port for administrative commands.
+admin_port = 33015
+
+# Log level
+log_level = 4
+
+# Each write ahead log contains this many rows.
+# When the limit is reached, Tarantool closes
+# the WAL and starts a new one.
+rows_per_wal = 5000000
+
+wal_fsync_delay = 0.1
+wal_writer_inbox_size=1024
+
+# Define a simple space with 1 HASH-based
+# primary key.
+space[0].enabled = 1
+space[0].index[0].type = "HASH"
+space[0].index[0].unique = 1
+space[0].index[0].key_field[0].fieldno = 0
+space[0].index[0].key_field[0].type = "NUM"
+space[0].index[1].type = "TREE"
+space[0].index[1].unique = 0
+space[0].index[1].key_field[0].fieldno = 1
+space[0].index[1].key_field[0].type = "STR"
+
+space[1].enabled = 1
+space[1].index[0].type = "HASH"
+space[1].index[0].unique = 1
+space[1].index[0].key_field[0].fieldno = 0
+space[1].index[0].key_field[0].type = "STR"
diff --git a/connector/php/test/insert.phpt b/connector/php/test/insert.phpt
new file mode 100644
index 0000000000000000000000000000000000000000..6338ab1a10ce539f98e7d4a844810bee33ec58a7
--- /dev/null
+++ b/connector/php/test/insert.phpt
@@ -0,0 +1,176 @@
+--TEST--
+Tarantool/box insert command test
+--FILE--
+<?php
+include "lib/php/tarantool_utest.php";
+
+$tarantool = new Tarantool("localhost", 33013, 33015);
+
+echo "---------- test begin ----------\n";
+echo "test insert: invalid tuple (expected error exception)\n";
+test_insert($tarantool, 0, array(0, array(1, 2, 3), "str"), 0);
+echo "----------- test end -----------\n\n";
+
+echo "---------- test begin ----------\n";
+echo "test insert: invalid tuple (expected error exception)\n";
+test_insert($tarantool, 0, array(0, $tarantool), 0);
+echo "----------- test end -----------\n\n";
+
+echo "---------- test begin ----------\n";
+echo "test insert: assign tuple";
+test_insert($tarantool, 0, $sw4,
+            TARANTOOL_FLAGS_RETURN_TUPLE);
+echo "----------- test end -----------\n\n";
+
+echo "---------- test begin ----------\n";
+echo "test insert: assign tuple (tuple doesn't return)\n";
+test_insert($tarantool, 0, $sw6, 0);
+echo "----------- test end -----------\n\n";
+
+echo "---------- test begin ----------\n";
+echo "test insert: add existed tuple (expected error exception)\n";
+test_insert($tarantool, 0, $sw4,
+            TARANTOOL_FLAGS_RETURN_TUPLE | TARANTOOL_FLAGS_ADD);
+echo "----------- test end -----------\n\n";
+
+echo "---------- test begin ----------\n";
+echo "test insert: replace not existed tuple (expected error exception)\n";
+test_insert($tarantool, 0, $sw5,
+            TARANTOOL_FLAGS_RETURN_TUPLE | TARANTOOL_FLAGS_REPLACE);
+echo "----------- test end -----------\n\n";
+
+echo "---------- test begin ----------\n";
+echo "test insert: add not existed tuple\n";
+test_insert($tarantool, 0, $sw5,
+            TARANTOOL_FLAGS_RETURN_TUPLE | TARANTOOL_FLAGS_ADD);
+echo "----------- test end -----------\n\n";
+
+echo "---------- test begin ----------\n";
+echo "test insert: replace existed tuple\n";
+test_insert($tarantool, 0, $sw5,
+            TARANTOOL_FLAGS_RETURN_TUPLE | TARANTOOL_FLAGS_REPLACE);
+echo "----------- test end -----------\n\n";
+
+test_clean($tarantool, 0);
+?>
+===DONE===
+--EXPECT--
+---------- test begin ----------
+test insert: invalid tuple (expected error exception)
+catched exception: unsupported field type
+----------- test end -----------
+
+---------- test begin ----------
+test insert: invalid tuple (expected error exception)
+catched exception: unsupported field type
+----------- test end -----------
+
+---------- test begin ----------
+test insert: assign tupleresult:
+count = 1
+tuple:
+  id     = 0
+  series = Star Wars
+  year   = 1977
+  name   = A New Hope
+  crawl  = A long time ago, in a galaxy far, far away...
+It is a period of civil war. Rebel
+spaceships, striking from a hidden
+base, have won their first victory
+against the evil Galactic Empire.
+
+During the battle, Rebel spies managed
+to steal secret plans to the Empire's
+ultimate weapon, the Death Star, an
+armored space station with enough
+power to destroy an entire planet.
+
+Pursued by the Empire's sinister agents,
+Princess Leia races home aboard her
+starship, custodian of the stolen plans
+that can save her people and restore
+freedom to the galaxy....
+  uuid   = -1091633151
+----------- test end -----------
+
+---------- test begin ----------
+test insert: assign tuple (tuple doesn't return)
+result:
+count = 1
+----------- test end -----------
+
+---------- test begin ----------
+test insert: add existed tuple (expected error exception)
+catched exception: insert failed: 14082(0x00003702): Tuple already exists
+----------- test end -----------
+
+---------- test begin ----------
+test insert: replace not existed tuple (expected error exception)
+catched exception: insert failed: 12546(0x00003102): Tuple doesn't exist
+----------- test end -----------
+
+---------- test begin ----------
+test insert: add not existed tuple
+result:
+count = 1
+tuple:
+  id     = 1
+  series = Star Wars
+  year   = 1980
+  name   = The Empire Strikes Back
+  crawl  = It is a dark time for the
+Rebellion. Although the Death
+Star has been destroyed.
+Imperial troops have driven the
+Rebel forces from their hidden
+base and pursued them across
+the galaxy.
+
+Evading the dreaded Imperial
+Starfleet, a group of freedom
+fighters led by Luke Skywalker
+have established a new secret base
+on the remote ice world
+of Hoth.
+
+The evil lord Darth Vader,
+obsessed with finding young
+Skywalker, has dispatched
+thousands of remote probes
+into the far reaches of space....
+  uuid   = -1091633150
+----------- test end -----------
+
+---------- test begin ----------
+test insert: replace existed tuple
+result:
+count = 1
+tuple:
+  id     = 1
+  series = Star Wars
+  year   = 1980
+  name   = The Empire Strikes Back
+  crawl  = It is a dark time for the
+Rebellion. Although the Death
+Star has been destroyed.
+Imperial troops have driven the
+Rebel forces from their hidden
+base and pursued them across
+the galaxy.
+
+Evading the dreaded Imperial
+Starfleet, a group of freedom
+fighters led by Luke Skywalker
+have established a new secret base
+on the remote ice world
+of Hoth.
+
+The evil lord Darth Vader,
+obsessed with finding young
+Skywalker, has dispatched
+thousands of remote probes
+into the far reaches of space....
+  uuid   = -1091633150
+----------- test end -----------
+
+===DONE===
\ No newline at end of file
diff --git a/connector/php/test/lib/lua/sorted_array.lua b/connector/php/test/lib/lua/sorted_array.lua
new file mode 100644
index 0000000000000000000000000000000000000000..1d8c3fb597495727166ceebecc99df537fb48d9b
--- /dev/null
+++ b/connector/php/test/lib/lua/sorted_array.lua
@@ -0,0 +1,173 @@
+local bit = require("bit")
+local table = require("table")
+local string = require("string")
+local floor = require("math").floor
+
+local function s_to_a(data)
+    local a = {}
+    local bytes = {string.byte(data, i, #data)}
+    for i = 1, #data / 8 do
+	a[i] = 0
+	for c = 0, 7 do
+	    a[i] = bit.lshift(a[i], 8)
+	    a[i] = a[i] + bytes[i * 8 - c]
+	end
+    end
+    return a
+end
+
+local function a_to_s(a)
+    local bytes = {}
+    local shift = {}
+    for i = 1, #a do
+	val = a[i]
+	for c = 0, 7 do
+	    table.insert(bytes, bit.band(val, 0xff))
+	    val = bit.rshift(val, 8)
+	end
+    end
+    return string.char(unpack(bytes))
+end
+
+local max_limit = 500
+local function amerge(a, b)
+    local r = {}
+    local n = max_limit
+    while #a > 0 and #b > 0 and n > 0 do
+	if a[1] > b[1] then
+	    table.insert(r, table.remove(a, 1))
+	else
+	    table.insert(r, table.remove(b, 1))
+	end
+	n = n - 1
+    end
+    while #a > 0 and n > 0 do
+	table.insert(r, table.remove(a, 1))
+                n = n - 1
+    end
+    while #b > 0 and n > 0 do
+	table.insert(r, table.remove(b, 1))
+	n = n - 1
+    end
+    return r
+end
+
+local function afind_ge(a, x)
+    if #a == 0 then
+	return 1
+    elseif a[1] < x then
+	return 1
+    elseif a[#a] > x then
+	return #a + 1
+    end
+
+    local first, last = 1, #a + 1
+    local mid
+    while first < last do
+	mid = floor(first + (last - first) / 2)
+	if x >= a[mid] then
+	    last = mid
+	else
+	    first = mid + 1
+	end
+    end
+
+    return last
+end
+
+local function ains(a, key)
+    key = tonumber(key)
+    
+    local i = afind_ge(a, key)
+    if a[i] and a[i] >= key then
+	table.insert(a, i + 1, key) -- next to equal or greater
+    else
+	table.insert(a, i, key)
+    end
+    
+    while #a > max_limit do
+	table.remove(a)
+    end
+end
+
+local function adel(a, key)
+    key = tonumber(key)
+    local i = afind_ge(a, key)
+    print("key = ", key)
+    print("i   = ", i)
+    if a[i] == key then
+	table.remove(a, i)
+    end
+end
+
+local function get(space, key)
+    local tuple = box.select(space, 0, key)
+    if tuple then
+	return s_to_a(tuple[1])
+    else
+	return {}
+    end
+end
+
+local function store(space, key, a)
+    box.replace(space, key, a_to_s(a))
+    return key, a
+end
+
+function box.sa_insert(space, key, value)
+    local a = get(space, key)
+    ains(a, value)
+    print(unpack(a))
+    return store(space, key, a)
+end
+
+function box.sa_delete(space, key, ...)
+    local a = get(space, key)
+    for i = 1, #a do
+	print("a[", i, "] = ", a[i])
+    end
+    for i, d in pairs({...}) do
+	adel(a, d)
+    end
+    return store(space, key, a)
+end
+
+function box.sa_select(space, key, from, limit)
+    local a = get(space, key)
+
+    if from ~= nil then
+	from = tonumber(from)
+	index = afind_ge(a, from)
+	if a[index] == from then
+	    index = index + 1
+	end
+    else
+	index = 1
+    end
+    
+    if limit ~= nil then
+	limit = tonumber(limit)
+    else
+	limit = max_limit
+    end
+
+    local r = {}
+    for i = index, #a do
+	if a[i] == nil then
+	    break
+	end
+	table.insert(r, a[i])
+	limit = limit - 1
+	if limit == 0 then
+	    break
+	end
+    end
+    return key, r
+end
+
+function box.sa_merge(space, key_a, key_b)
+    local a = get(space, key_a)
+    local b = get(space, key_b)
+    local r = amerge(a, b)
+    return r
+end
diff --git a/connector/php/test/lib/php/tarantool_utest.php b/connector/php/test/lib/php/tarantool_utest.php
new file mode 100644
index 0000000000000000000000000000000000000000..011d375843af8c61d0902f4748bbd859cd3b5372
--- /dev/null
+++ b/connector/php/test/lib/php/tarantool_utest.php
@@ -0,0 +1,180 @@
+<?php
+
+$sw4 = array(0, "Star Wars", 1977, "A New Hope",
+             "A long time ago, in a galaxy far, far away...\n" .
+             "It is a period of civil war. Rebel\n" .
+             "spaceships, striking from a hidden\n" .
+             "base, have won their first victory\n" .
+             "against the evil Galactic Empire.\n" .
+             "\n" .
+             "During the battle, Rebel spies managed\n" .
+             "to steal secret plans to the Empire's\n" .
+             "ultimate weapon, the Death Star, an\n" . 
+             "armored space station with enough\n". 
+             "power to destroy an entire planet.\n" .
+             "\n" .
+             "Pursued by the Empire's sinister agents,\n" .
+             "Princess Leia races home aboard her\n" .
+             "starship, custodian of the stolen plans\n" .
+             "that can save her people and restore\n" .
+             "freedom to the galaxy....",
+             0xf10dbeef0001);
+
+$sw5 = array(1, "Star Wars", 1980, "The Empire Strikes Back",
+             "It is a dark time for the\n" . 
+             "Rebellion. Although the Death\n" . 
+             "Star has been destroyed.\n" .
+             "Imperial troops have driven the\n" . 
+             "Rebel forces from their hidden\n" .
+             "base and pursued them across\n" . 
+             "the galaxy.\n" .
+             "\n" .
+             "Evading the dreaded Imperial\n" .
+             "Starfleet, a group of freedom\n" .
+             "fighters led by Luke Skywalker\n" .
+             "have established a new secret base\n" .
+             "on the remote ice world\n" .
+             "of Hoth.\n" .
+             "\n" .
+             "The evil lord Darth Vader,\n" .
+             "obsessed with finding young\n" .
+             "Skywalker, has dispatched\n" .
+             "thousands of remote probes\n" .
+             "into the far reaches of space....",
+             0xf10dbeef0002);
+
+$sw6 = array(2, "Star Wars", 1983, "Return of the Jedi",
+             "Luke Skywalker has returned\n" .
+             "to his home planet of\n" .
+             "Tatooine in an attempt\n" .
+             "to rescue his friend\n" .
+             "Han Solo from the\n" .
+             "clutches of the vile\n" .
+             "gangster Jabba the Hutt.\n" .
+             "\n" .
+             "Little does Luke know\n" .
+             "that the GALACTIC EMPIRE\n" .
+             "has secretly begun construction\n" .
+             "on a new armored space station\n" .
+             "even more powerful than the\n" .
+             "first dreaded Death Star.\n" .
+             "\n" .
+             "When completed, this ultimate\n" .
+             "weapon will spell certain\n" .
+             "doom for the small band of\n" .
+             "rebels struggling to restore\n" .
+             "freedom to the galaxy...",
+             0xf10dbeef0003);
+
+function test_init($tarantool, $space_no) {
+    try {
+        global $sw4, $sw5, $sw6;
+        $tarantool->insert($space_no, $sw4);
+        $tarantool->insert($space_no, $sw5);
+        $tarantool->insert($space_no, $sw6);
+    } catch (Exception $e) {
+        echo "init failed: ", $e->getMessage(), "\n";
+        throw $e;
+    }
+}
+
+function test_clean($tarantool, $space_no) {
+    try {
+        $tarantool->delete($space_no, 0);
+        $tarantool->delete($space_no, 1);
+        $tarantool->delete($space_no, 2);
+    } catch (Exception $e) {
+        echo "clean-up failed: ", $e->getMessage(), "\n";
+        throw $e;
+    }
+}
+
+function test_select($tarantool, $space_no, $index_no, $key) {
+    try {
+        $result = $tarantool->select($space_no, $index_no, $key);
+        echo "result:\n";
+        echo "count = ", $result["count"], "\n";
+        $tuples_list = $result["tuples_list"];
+        sort($tuples_list);
+        for ($i = 0; $i < $result["count"]; $i++) {
+            echo "tuple[", $i, "]:", "\n";
+            echo "  id     = ", $tuples_list[$i][0], "\n";
+            echo "  series = ", $tuples_list[$i][1], "\n";
+            echo "  year   = ", $tuples_list[$i][2], "\n";
+            echo "  name   = ", $tuples_list[$i][3], "\n";
+            echo "  crawl  = ", $tuples_list[$i][4], "\n";
+            echo "  uuid   = ", $tuples_list[$i][5], "\n";
+        }
+    } catch (Exception $e) {
+        echo "catched exception: ", $e->getMessage(), "\n";
+    }
+}
+
+function test_insert($tarantool, $space_no, $tuple, $flags) {
+    try {
+        $result = $tarantool->insert($space_no, $tuple, $flags);
+        echo "result:\n";
+        echo "count = ", $result["count"], "\n";
+        if (array_key_exists("tuple", $result)) {
+            echo "tuple:", "\n";
+            echo "  id     = ", $result["tuple"][0], "\n";
+            echo "  series = ", $result["tuple"][1], "\n";
+            echo "  year   = ", $result["tuple"][2], "\n";
+            echo "  name   = ", $result["tuple"][3], "\n";
+            echo "  crawl  = ", $result["tuple"][4], "\n";
+            echo "  uuid   = ", $result["tuple"][5], "\n";
+        }
+    } catch (Exception $e) {
+        echo "catched exception: ", $e->getMessage(), "\n";
+    }
+}
+
+function test_update_fields($tarantool, $space_no, $key, $ops, $flags) {
+    try {
+        $result = $tarantool->update_fields($space_no, $key, $ops, $flags);
+        echo "result:\n";
+        echo "count = ", $result["count"], "\n";
+        if (array_key_exists("tuple", $result)) {
+            echo "tuple:", "\n";
+            echo "  id     = ", $result["tuple"][0], "\n";
+            echo "  series = ", $result["tuple"][1], "\n";
+            echo "  year   = ", $result["tuple"][2], "\n";
+            echo "  name   = ", $result["tuple"][3], "\n";
+            echo "  crawl  = ", $result["tuple"][4], "\n";
+            echo "  uuid   = ", $result["tuple"][5], "\n";
+        }
+    } catch (Exception $e) {
+        echo "catched exception: ", $e->getMessage(), "\n";
+    }
+}
+
+function test_delete($tarantool, $space_no, $key, $flags) {
+    try {
+        $result = $tarantool->delete($space_no, $key, $flags);
+        echo "result:\n";
+        echo "count = ", $result["count"], "\n";
+        if (array_key_exists("tuple", $result)) {
+            echo "tuple:", "\n";
+            echo "  id     = ", $result["tuple"][0], "\n";
+            echo "  series = ", $result["tuple"][1], "\n";
+            echo "  year   = ", $result["tuple"][2], "\n";
+            echo "  name   = ", $result["tuple"][3], "\n";
+            echo "  crawl  = ", $result["tuple"][4], "\n";
+            echo "  uuid   = ", $result["tuple"][5], "\n";
+        }
+    } catch (Exception $e) {
+        echo "catched exception: ", $e->getMessage(), "\n";
+    }
+}
+
+function test_call($tarantool, $proc, $tuple_args, $flags) {
+    try {
+        $result = $tarantool->call($proc, $tuple_args, $flags);
+        echo "result:\n";
+        var_dump($result);
+    } catch (Exception $e) {
+        echo "catched exception: ", $e->getMessage(), "\n";
+    }
+}
+
+?>
diff --git a/connector/php/test/run-test b/connector/php/test/run-test
new file mode 120000
index 0000000000000000000000000000000000000000..1993bbd700ce69abe07bca7b482359f16c41bfbd
--- /dev/null
+++ b/connector/php/test/run-test
@@ -0,0 +1 @@
+bin/run-test.bash
\ No newline at end of file
diff --git a/connector/php/test/select.phpt b/connector/php/test/select.phpt
new file mode 100644
index 0000000000000000000000000000000000000000..668306928079dcc2c4e8d74383310405ebdb15af
--- /dev/null
+++ b/connector/php/test/select.phpt
@@ -0,0 +1,356 @@
+--TEST--
+Tarantool/box select commands test
+--FILE--
+<?php
+include "lib/php/tarantool_utest.php";
+
+$tarantool = new Tarantool("localhost", 33013, 33015);
+test_init($tarantool, 0);
+
+echo "---------- test begin ----------\n";
+echo "test select: key is integer\n";
+test_select($tarantool, 0, 0, 0);
+echo "----------- test end -----------\n\n";
+
+echo "---------- test begin ----------\n";
+echo "test select: key is string \n";
+test_select($tarantool, 0, 1, "Star Wars");
+echo "----------- test end -----------\n\n";
+
+echo "---------- test begin ----------\n";
+echo "test select: key is object (expected error exception)\n";
+test_select($tarantool, 0, 0, $tarantool);
+echo "----------- test end -----------\n\n";
+
+echo "---------- test begin ----------\n";
+echo "test select: key is array\n";
+test_select($tarantool, 0, 1, array("Star Wars"));
+echo "----------- test end -----------\n\n";
+
+echo "---------- test begin ----------\n";
+echo "test select: key is empty array (expected error exception)\n";
+test_select($tarantool, 0, 0, array());
+echo "----------- test end -----------\n\n";
+
+echo "---------- test begin ----------\n";
+echo "test select: key is array of objects (expected error exception)\n";
+test_select($tarantool, 0, 0, array($tarantool, 1, 2));
+echo "----------- test end -----------\n\n";
+
+echo "---------- test begin ----------\n";
+echo "test select: multi keys select\n";
+test_select($tarantool, 0, 0, array(array(0), array(1), array(2)));
+echo "----------- test end -----------\n\n";
+
+echo "---------- test begin ----------\n";
+echo "test select: key is array of array of objects (expected error exception)\n";
+test_select($tarantool, 0, 0, array(array(1, $tarantool)));
+echo "----------- test end -----------\n\n";
+
+echo "---------- test begin ----------\n";
+echo "test select: key is array of diffirent elements (expected error exception)\n";
+test_select($tarantool, 0, 0, array(array(1, 2), 2));
+echo "----------- test end -----------\n\n";
+
+test_clean($tarantool, 0);
+?>
+===DONE===
+--EXPECT--
+---------- test begin ----------
+test select: key is integer
+result:
+count = 1
+tuple[0]:
+  id     = 0
+  series = Star Wars
+  year   = 1977
+  name   = A New Hope
+  crawl  = A long time ago, in a galaxy far, far away...
+It is a period of civil war. Rebel
+spaceships, striking from a hidden
+base, have won their first victory
+against the evil Galactic Empire.
+
+During the battle, Rebel spies managed
+to steal secret plans to the Empire's
+ultimate weapon, the Death Star, an
+armored space station with enough
+power to destroy an entire planet.
+
+Pursued by the Empire's sinister agents,
+Princess Leia races home aboard her
+starship, custodian of the stolen plans
+that can save her people and restore
+freedom to the galaxy....
+  uuid   = -1091633151
+----------- test end -----------
+
+---------- test begin ----------
+test select: key is string 
+result:
+count = 3
+tuple[0]:
+  id     = 0
+  series = Star Wars
+  year   = 1977
+  name   = A New Hope
+  crawl  = A long time ago, in a galaxy far, far away...
+It is a period of civil war. Rebel
+spaceships, striking from a hidden
+base, have won their first victory
+against the evil Galactic Empire.
+
+During the battle, Rebel spies managed
+to steal secret plans to the Empire's
+ultimate weapon, the Death Star, an
+armored space station with enough
+power to destroy an entire planet.
+
+Pursued by the Empire's sinister agents,
+Princess Leia races home aboard her
+starship, custodian of the stolen plans
+that can save her people and restore
+freedom to the galaxy....
+  uuid   = -1091633151
+tuple[1]:
+  id     = 1
+  series = Star Wars
+  year   = 1980
+  name   = The Empire Strikes Back
+  crawl  = It is a dark time for the
+Rebellion. Although the Death
+Star has been destroyed.
+Imperial troops have driven the
+Rebel forces from their hidden
+base and pursued them across
+the galaxy.
+
+Evading the dreaded Imperial
+Starfleet, a group of freedom
+fighters led by Luke Skywalker
+have established a new secret base
+on the remote ice world
+of Hoth.
+
+The evil lord Darth Vader,
+obsessed with finding young
+Skywalker, has dispatched
+thousands of remote probes
+into the far reaches of space....
+  uuid   = -1091633150
+tuple[2]:
+  id     = 2
+  series = Star Wars
+  year   = 1983
+  name   = Return of the Jedi
+  crawl  = Luke Skywalker has returned
+to his home planet of
+Tatooine in an attempt
+to rescue his friend
+Han Solo from the
+clutches of the vile
+gangster Jabba the Hutt.
+
+Little does Luke know
+that the GALACTIC EMPIRE
+has secretly begun construction
+on a new armored space station
+even more powerful than the
+first dreaded Death Star.
+
+When completed, this ultimate
+weapon will spell certain
+doom for the small band of
+rebels struggling to restore
+freedom to the galaxy...
+  uuid   = -1091633149
+----------- test end -----------
+
+---------- test begin ----------
+test select: key is object (expected error exception)
+catched exception: unsupported tuple type
+----------- test end -----------
+
+---------- test begin ----------
+test select: key is array
+result:
+count = 3
+tuple[0]:
+  id     = 0
+  series = Star Wars
+  year   = 1977
+  name   = A New Hope
+  crawl  = A long time ago, in a galaxy far, far away...
+It is a period of civil war. Rebel
+spaceships, striking from a hidden
+base, have won their first victory
+against the evil Galactic Empire.
+
+During the battle, Rebel spies managed
+to steal secret plans to the Empire's
+ultimate weapon, the Death Star, an
+armored space station with enough
+power to destroy an entire planet.
+
+Pursued by the Empire's sinister agents,
+Princess Leia races home aboard her
+starship, custodian of the stolen plans
+that can save her people and restore
+freedom to the galaxy....
+  uuid   = -1091633151
+tuple[1]:
+  id     = 1
+  series = Star Wars
+  year   = 1980
+  name   = The Empire Strikes Back
+  crawl  = It is a dark time for the
+Rebellion. Although the Death
+Star has been destroyed.
+Imperial troops have driven the
+Rebel forces from their hidden
+base and pursued them across
+the galaxy.
+
+Evading the dreaded Imperial
+Starfleet, a group of freedom
+fighters led by Luke Skywalker
+have established a new secret base
+on the remote ice world
+of Hoth.
+
+The evil lord Darth Vader,
+obsessed with finding young
+Skywalker, has dispatched
+thousands of remote probes
+into the far reaches of space....
+  uuid   = -1091633150
+tuple[2]:
+  id     = 2
+  series = Star Wars
+  year   = 1983
+  name   = Return of the Jedi
+  crawl  = Luke Skywalker has returned
+to his home planet of
+Tatooine in an attempt
+to rescue his friend
+Han Solo from the
+clutches of the vile
+gangster Jabba the Hutt.
+
+Little does Luke know
+that the GALACTIC EMPIRE
+has secretly begun construction
+on a new armored space station
+even more powerful than the
+first dreaded Death Star.
+
+When completed, this ultimate
+weapon will spell certain
+doom for the small band of
+rebels struggling to restore
+freedom to the galaxy...
+  uuid   = -1091633149
+----------- test end -----------
+
+---------- test begin ----------
+test select: key is empty array (expected error exception)
+catched exception: invalid tuples list: empty array
+----------- test end -----------
+
+---------- test begin ----------
+test select: key is array of objects (expected error exception)
+catched exception: unsupported tuple type
+----------- test end -----------
+
+---------- test begin ----------
+test select: multi keys select
+result:
+count = 3
+tuple[0]:
+  id     = 0
+  series = Star Wars
+  year   = 1977
+  name   = A New Hope
+  crawl  = A long time ago, in a galaxy far, far away...
+It is a period of civil war. Rebel
+spaceships, striking from a hidden
+base, have won their first victory
+against the evil Galactic Empire.
+
+During the battle, Rebel spies managed
+to steal secret plans to the Empire's
+ultimate weapon, the Death Star, an
+armored space station with enough
+power to destroy an entire planet.
+
+Pursued by the Empire's sinister agents,
+Princess Leia races home aboard her
+starship, custodian of the stolen plans
+that can save her people and restore
+freedom to the galaxy....
+  uuid   = -1091633151
+tuple[1]:
+  id     = 1
+  series = Star Wars
+  year   = 1980
+  name   = The Empire Strikes Back
+  crawl  = It is a dark time for the
+Rebellion. Although the Death
+Star has been destroyed.
+Imperial troops have driven the
+Rebel forces from their hidden
+base and pursued them across
+the galaxy.
+
+Evading the dreaded Imperial
+Starfleet, a group of freedom
+fighters led by Luke Skywalker
+have established a new secret base
+on the remote ice world
+of Hoth.
+
+The evil lord Darth Vader,
+obsessed with finding young
+Skywalker, has dispatched
+thousands of remote probes
+into the far reaches of space....
+  uuid   = -1091633150
+tuple[2]:
+  id     = 2
+  series = Star Wars
+  year   = 1983
+  name   = Return of the Jedi
+  crawl  = Luke Skywalker has returned
+to his home planet of
+Tatooine in an attempt
+to rescue his friend
+Han Solo from the
+clutches of the vile
+gangster Jabba the Hutt.
+
+Little does Luke know
+that the GALACTIC EMPIRE
+has secretly begun construction
+on a new armored space station
+even more powerful than the
+first dreaded Death Star.
+
+When completed, this ultimate
+weapon will spell certain
+doom for the small band of
+rebels struggling to restore
+freedom to the galaxy...
+  uuid   = -1091633149
+----------- test end -----------
+
+---------- test begin ----------
+test select: key is array of array of objects (expected error exception)
+catched exception: unsupported field type
+----------- test end -----------
+
+---------- test begin ----------
+test select: key is array of diffirent elements (expected error exception)
+catched exception: invalid tuples list: expected array of array
+----------- test end -----------
+
+===DONE===
\ No newline at end of file
diff --git a/connector/php/test/update_fields.phpt b/connector/php/test/update_fields.phpt
new file mode 100644
index 0000000000000000000000000000000000000000..91287232df5c4c6010c5c08c4c505363000f5f05
--- /dev/null
+++ b/connector/php/test/update_fields.phpt
@@ -0,0 +1,115 @@
+--TEST--
+Tarantool/box update fields commands test
+--FILE--
+<?php
+include "lib/php/tarantool_utest.php";
+
+$tarantool = new Tarantool("localhost", 33013, 33015);
+test_init($tarantool, 0);
+
+echo "---------- test begin ----------\n";
+echo "test update fields: do update w/o operations (expected error exception)\n";
+test_update_fields($tarantool, 0, 0, array(), TARANTOOL_FLAGS_RETURN_TUPLE);
+echo "----------- test end -----------\n\n";
+
+echo "---------- test begin ----------\n";
+echo "test update fields: invalid operation list (expected error exception)\n";
+test_update_fields($tarantool, 0, 0, array($tarantool), TARANTOOL_FLAGS_RETURN_TUPLE);
+echo "----------- test end -----------\n\n";
+
+echo "---------- test begin ----------\n";
+echo "test update fields: do update arith operation\n";
+test_update_fields($tarantool, 0, 0,
+                   array(
+                       array(
+                           "field" => 2,
+                           "op" => TARANTOOL_OP_ADD,
+                           "arg" => 30,
+                           ),
+                       array(
+                           "field" => 0,
+                           "op" => TARANTOOL_OP_ASSIGN,
+                           "arg" => 5,
+                           ),
+                       array(
+                           "field" => 1,
+                           "op" => TARANTOOL_OP_ASSIGN,
+                           "arg" => "",
+                           ),
+                       array(
+                           "field" => 3,
+                           "op" => TARANTOOL_OP_ASSIGN,
+                           "arg" => "return",
+                           ),
+                       array(
+                           "field" => 4,
+                           "op" => TARANTOOL_OP_SPLICE,
+                           "offset" => 1,
+                           "length" => 64,
+                           "list" => " <<splice string>> ",
+                           ),
+                       ), TARANTOOL_FLAGS_RETURN_TUPLE);
+echo "----------- test end -----------\n\n";
+
+echo "---------- test begin ----------\n";
+echo "test update fields: delete not existing tuple w/ return tuple flag\n";
+test_update_fields($tarantool, 0, 0,
+                   array(
+                       array(
+                           "field" => 2,
+                           "op" => TARANTOOL_OP_ADD,
+                           "arg" => 30,
+                           ),
+                       ),
+                   TARANTOOL_FLAGS_RETURN_TUPLE);
+echo "----------- test end -----------\n\n";
+
+test_clean($tarantool, 0);
+?>
+===DONE===
+--EXPECT--
+---------- test begin ----------
+test update fields: do update w/o operations (expected error exception)
+catched exception: update fields failed: 514(0x00000202): Illegal parameters, no operations for update
+----------- test end -----------
+
+---------- test begin ----------
+test update fields: invalid operation list (expected error exception)
+catched exception: invalid operations list
+----------- test end -----------
+
+---------- test begin ----------
+test update fields: do update arith operation
+result:
+count = 1
+tuple:
+  id     = 5
+  series = 
+  year   = 2007
+  name   = return
+  crawl  = A <<splice string>> ivil war. Rebel
+spaceships, striking from a hidden
+base, have won their first victory
+against the evil Galactic Empire.
+
+During the battle, Rebel spies managed
+to steal secret plans to the Empire's
+ultimate weapon, the Death Star, an
+armored space station with enough
+power to destroy an entire planet.
+
+Pursued by the Empire's sinister agents,
+Princess Leia races home aboard her
+starship, custodian of the stolen plans
+that can save her people and restore
+freedom to the galaxy....
+  uuid   = -1091633151
+----------- test end -----------
+
+---------- test begin ----------
+test update fields: delete not existing tuple w/ return tuple flag
+result:
+count = 0
+----------- test end -----------
+
+===DONE===
\ No newline at end of file
diff --git a/connector/python/README.rst b/connector/python/README.rst
index 622d0565aef509b73f836d441a002457af62960c..5671b4ee8b829283bd4370ab1b2fda795114abb7 100644
--- a/connector/python/README.rst
+++ b/connector/python/README.rst
@@ -4,7 +4,10 @@
 
 Python interface to the `Tarantool database <https://github.com/mailru/tarantool>`_.
 
-Tarantool is a damn fast key/value data store originally designed by `Mail.Ru <http://mail.ru>`_
+The latest version of this package is found at
+`<https://github.com/mailru/tarnatool-python`_.
+
+Tarantool is a fast key/value data store originally designed by `Mail.Ru <http://mail.ru>`_
 and released under the terms of `BSD license <http://www.gnu.org/licenses/license-list.html#ModifiedBSD>`_.
 Tarantool is production-ready and actively used at `Mail.Ru <http://mail.ru>`_,
 one of the leading Russian web content providers.
diff --git a/connector/python/setup.py b/connector/python/setup.py
deleted file mode 100644
index 95090dc2eb84efdc0a129e2e36f20cf710a73754..0000000000000000000000000000000000000000
--- a/connector/python/setup.py
+++ /dev/null
@@ -1,15 +0,0 @@
-# -*- coding: utf-8 -*-
-from distutils.core import setup
-import os.path
-
-setup(
-    name = "tarantool-python",
-    packages = ["tarantool"],
-    package_dir = {"tarantool": os.path.join("src", "tarantool")},
-    version = "0.1.1-dev",
-    platforms = ["all"],
-    author = "Konstantin Cherkasoff",
-    author_email = "k.cherkasoff@gmail.com",
-    url = "https://github.com/coxx/tarantool-python",
-    description = "Python client library for Tarantool Database",
-)
diff --git a/connector/python/src/tarantool/__init__.py b/connector/python/src/tarantool/__init__.py
deleted file mode 100644
index 836a633575a9fcde65dbc651281bc18628688025..0000000000000000000000000000000000000000
--- a/connector/python/src/tarantool/__init__.py
+++ /dev/null
@@ -1,13 +0,0 @@
-# -*- coding: utf-8 -*-
-# pylint: disable=C0301,W0105,W0401,W0614
-
-from tarantool.connection import Connection
-from tarantool.const import *
-from tarantool.error import *
-
-def connect(host="localhost", port=33013):
-    return Connection(host, port,
-                      socket_timeout=SOCKET_TIMEOUT,
-                      reconnect_max_attempts=RECONNECT_MAX_ATTEMPTS,
-                      reconnect_delay=RECONNECT_DELAY,
-                      connect_now=True)
diff --git a/connector/python/src/tarantool/connection.py b/connector/python/src/tarantool/connection.py
deleted file mode 100644
index 06e713b0d85865b0d4c7bd3e17412eb9d04feb82..0000000000000000000000000000000000000000
--- a/connector/python/src/tarantool/connection.py
+++ /dev/null
@@ -1,337 +0,0 @@
-# -*- coding: utf-8 -*-
-# pylint: disable=C0301,W0105,W0401,W0614
-'''
-This module provides low-level API for Tarantool
-'''
-
-import socket
-import sys
-import time
-
-from tarantool.response import Response
-from tarantool.request import (
-                    Request,
-                    RequestCall,
-                    RequestDelete,
-                    RequestInsert,
-                    RequestSelect,
-                    RequestUpdate)
-from tarantool.space import Space
-from tarantool.const import *
-from tarantool.error import *
-
-
-
-class Connection(object):
-    '''\
-    Represents low-level interface to the Tarantool server.
-    This class can be used directly or using object-oriented wrappers.
-    '''
-
-    def __init__(self, host, port,
-                 socket_timeout=SOCKET_TIMEOUT,
-                 reconnect_max_attempts=RECONNECT_MAX_ATTEMPTS,
-                 reconnect_delay=RECONNECT_DELAY,
-                 connect_now=True):
-        '''\
-        Initialize an connection to the server.
-
-        :param str host: Server hostname or IP-address
-        :param int port: Server port
-        :param bool connect_now: if True (default) than __init__() actually creates network connection.
-                             if False than you have to call connect() manualy.
-        '''
-        self.host = host
-        self.port = port
-        self.socket_timeout = socket_timeout
-        self.reconnect_delay = reconnect_delay
-        self.reconnect_max_attempts = reconnect_max_attempts
-        self._socket = None
-        if connect_now:
-            self.connect()
-
-
-    def connect(self):
-        '''\
-        Create connection to the host and port specified in __init__().
-        Usually there is no need to call this method directly,
-        since it is called when you create an `Connection` instance.
-
-        :raise: `NetworkError`
-        '''
-
-        try:
-            # If old socket already exists - close it and re-create
-            if self._socket:
-                self._socket.close()
-            self._socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
-            self._socket.setsockopt(socket.SOL_TCP, socket.TCP_NODELAY, 1)
-            self._socket.connect((self.host, self.port))
-            # It is important to set socket timeout *after* connection.
-            # Otherwise the timeout exception will rised, even if the server does not listen
-            self._socket.settimeout(self.socket_timeout)
-        except socket.error as e:
-            raise NetworkError(e)
-
-
-    def _send_request_wo_reconnect(self, request, field_types=None):
-        '''\
-        :rtype: `Response` instance
-
-        :raise: NetworkError
-        '''
-        assert isinstance(request, Request)
-
-        # Repeat request in a loop if the server returns completion_status == 1 (try again)
-        for attempt in xrange(RETRY_MAX_ATTEMPTS):    # pylint: disable=W0612
-            try:
-                self._socket.sendall(bytes(request))
-                response = Response(self._socket, field_types)
-            except socket.error as e:
-                raise NetworkError(e)
-
-            if response.completion_status != 1:
-                return response
-            warn(response.return_message, RetryWarning)
-
-        # Raise an error if the maximum number of attempts have been made
-        raise DatabaseError(response.return_code, response.return_message)
-
-
-    def _send_request(self, request, field_types=None):
-        '''\
-        Send the request to the server through the socket.
-        Return an instance of `Response` class.
-
-        :param request: object representing a request
-        :type request: `Request` instance
-
-        :rtype: `Response` instance
-        '''
-        assert isinstance(request, Request)
-
-        connected = True
-        attempt = 1
-        while True:
-            try:
-                if not connected:
-                    time.sleep(self.reconnect_delay)
-                    self.connect()
-                    connected = True
-                    warn("Successfully reconnected", NetworkWarning)
-                response = self._send_request_wo_reconnect(request, field_types)
-                break
-            except NetworkError as e:
-                if attempt > self.reconnect_max_attempts:
-                    raise
-                warn("%s : Reconnect attempt %d of %d"%(e.message, attempt, self.reconnect_max_attempts), NetworkWarning)
-                attempt += 1
-                connected = False
-
-        return response
-
-    def call(self, func_name, *args, **kwargs):
-        '''\
-        Execute CALL request. Call stored Lua function.
-
-        :param func_name: stored Lua function name
-        :type func_name: str
-        :param args: list of function arguments
-        :type args: list or tuple
-        :param return_tuple: True indicates that it is required to return the inserted tuple back
-        :type return_tuple: bool
-
-        :rtype: `Response` instance
-        '''
-        assert isinstance(func_name, str)
-        assert len(args) != 0
-
-        # This allows to use a tuple or list as an argument
-        if isinstance(args[0], (list, tuple)):
-            args = args[0]
-
-        # Check if 'field_types' keyword argument is passed
-        field_types = kwargs.get("field_types", None)
-
-        request = RequestCall(func_name, args, return_tuple=True)
-        response = self._send_request(request, field_types=field_types)
-        return response
-
-
-    def insert(self, space_no, values, return_tuple=False, field_types=None):
-        '''\
-        Execute INSERT request.
-        Insert single record into a space `space_no`.
-
-        :param int space_no: space id to insert a record
-        :type space_no: int
-        :param values: record to be inserted. The tuple must contain only scalar (integer or strings) values
-        :type values: tuple
-        :param return_tuple: True indicates that it is required to return the inserted tuple back
-        :type return_tuple: bool
-
-        :rtype: `Response` instance
-        '''
-        assert isinstance(values, tuple)
-
-        request = RequestInsert(space_no, values, return_tuple)
-        return self._send_request(request, field_types=field_types)
-
-
-    def delete(self, space_no, key, return_tuple=False, field_types=None):
-        '''\
-        Execute DELETE request.
-        Delete single record identified by `key` (using primary index).
-
-        :param space_no: space id to delete a record
-        :type space_no: int
-        :param key: key that identifies a record
-        :type key: int or str
-        :param return_tuple: indicates that it is required to return the deleted tuple back
-        :type return_tuple: bool
-
-        :rtype: `Response` instance
-        '''
-        assert isinstance(key, (int, basestring))
-
-        request = RequestDelete(space_no, key, return_tuple)
-        return self._send_request(request, field_types=field_types)
-
-
-    def update(self, space_no, key, op_list, return_tuple=False, field_types=None):
-        '''\
-        Execute UPDATE request.
-        Update single record identified by `key` (using primary index).
-
-        List of operations allows to update individual fields.
-
-        :param space_no: space id to update a record
-        :type space_no: int
-        :param key: key that identifies a record
-        :type key: int or str
-        :param op_list: list of operations. Each operation is tuple of three values
-        :type op_list: a list of the form [(field_1, symbol_1, arg_1), (field_2, symbol_2, arg_2),...]
-        :param return_tuple: indicates that it is required to return the updated tuple back
-        :type return_tuple: bool
-
-        :rtype: `Response` instance
-        '''
-        assert isinstance(key, (int, basestring))
-
-        request = RequestUpdate(space_no, key, op_list, return_tuple)
-        return self._send_request(request, field_types=field_types)
-
-
-    def ping(self):
-        '''\
-        Execute PING request.
-        Send empty request and receive empty response from server.
-
-        :return: response time in seconds
-        :rtype: float
-        '''
-        t0 = time.time()
-        self._socket.sendall(struct_LLL.pack(0xff00, 0, 0))
-        request_type, body_length, request_id = struct_LLL.unpack(self._socket.recv(12)) # pylint: disable=W0612
-        t1 = time.time()
-        assert request_type == 0xff00
-        assert body_length == 0
-        return t1 - t0
-
-
-    def _select(self, space_no, index_no, values, offset=0, limit=0xffffffff, field_types=None):
-        '''\
-        Low level version of select() method.
-
-        :param space_no: space id to select data
-        :type space_no: int
-        :param index_no: index id to use
-        :type index_no: int
-        :param values: list of values to search over the index
-        :type values: list of tuples
-        :param offset: offset in the resulting tuple set
-        :type offset: int
-        :param limit: limits the total number of returned tuples
-        :type limit: int
-
-        :rtype: `Response` instance
-        '''
-
-        # 'values' argument must be a list of tuples
-        assert isinstance(values, (list, tuple))
-        assert len(values) != 0
-        assert isinstance(values[0], (list, tuple))
-
-        request = RequestSelect(space_no, index_no, values, offset, limit)
-        response = self._send_request(request, field_types=field_types)
-        return response
-
-
-    def select(self, space_no, index_no, values, offset=0, limit=0xffffffff, field_types=None):
-        '''\
-        Execute SELECT request.
-        Select and retrieve data from the database.
-
-        :param space_no: specifies which space to query
-        :type space_no: int
-        :param index_no: specifies which index to use
-        :type index_no: int
-        :param values: list of values to search over the index
-        :type values: list of tuples
-        :param offset: offset in the resulting tuple set
-        :type offset: int
-        :param limit: limits the total number of returned tuples
-        :type limit: int
-
-        :rtype: `Response` instance
-
-        Select one single record (from space=0 and using index=0)
-        >>> select(0, 0, 1)
-
-        Select several records using single-valued index
-        >>> select(0, 0, [1, 2, 3])
-        >>> select(0, 0, [(1,), (2,), (3,)]) # the same as above
-
-        Select serveral records using composite index
-        >>> select(0, 1, [(1,'2'), (2,'3'), (3,'4')])
-
-        Select single record using composite index
-        >>> select(0, 1, [(1,'2')])
-        This is incorrect
-        >>> select(0, 1, (1,'2'))
-        '''
-
-        # Perform smart type cheching (scalar / list of scalars / list of tuples)
-        if isinstance(values, (int, basestring)): # scalar
-            # This request is looking for one single record
-            values = [(values, )]
-        elif isinstance(values, (list, tuple, set, frozenset)):
-            assert len(values) > 0
-            if isinstance(values[0], (int, basestring)): # list of scalars
-                # This request is looking for several records using single-valued index
-                # Ex: select(space_no, index_no, [1, 2, 3])
-                # Transform a list of scalar values to a list of tuples
-                values = [(v, ) for v in values]
-            elif isinstance(values[0], (list, tuple)): # list of tuples
-                # This request is looking for serveral records using composite index
-                pass
-            else:
-                raise ValueError("Invalid value type, expected one of scalar (int or str) / list of scalars / list of tuples ")
-
-        return self._select(space_no, index_no, values, offset, limit, field_types=field_types)
-
-
-    def space(self, space_no, field_types=None):
-        '''\
-        Create `Space` instance for particular space
-
-        `Space` instance encapsulates the identifier of the space and provides more convenient syntax
-        for accessing the database space.
-
-        :param space_no: identifier of the space
-        :type space_no: int
-
-        :rtype: `Space` instance
-        '''
-        return Space(self, space_no, field_types)
-
diff --git a/connector/python/src/tarantool/const.py b/connector/python/src/tarantool/const.py
deleted file mode 100644
index 60cad84b8cc278a86f7591d33191deccdba1d6fc..0000000000000000000000000000000000000000
--- a/connector/python/src/tarantool/const.py
+++ /dev/null
@@ -1,34 +0,0 @@
-# -*- coding: utf-8 -*-
-# pylint: disable=C0301,W0105,W0401,W0614
-
-import struct
-
-
-# pylint: disable=C0103
-struct_BL = struct.Struct("<BL")
-struct_LB = struct.Struct("<LB")
-struct_L = struct.Struct("<L")
-struct_LL = struct.Struct("<LL")
-struct_LLL = struct.Struct("<LLL")
-struct_LLLL = struct.Struct("<LLLL")
-struct_LLLLL = struct.Struct("<LLLLL")
-struct_Q = struct.Struct("<Q")
-
-
-REQUEST_TYPE_CALL = 22
-REQUEST_TYPE_DELETE = 21
-REQUEST_TYPE_INSERT = 13
-REQUEST_TYPE_SELECT = 17
-REQUEST_TYPE_UPDATE = 19
-
-
-UPDATE_OPERATION_CODE = {'=': 0, '+': 1, '&': 2, '^': 3, '|': 4, 'splice': 5}
-
-# Default value for socket timeout (seconds)
-SOCKET_TIMEOUT = 1
-# Default maximum number of attempts to reconnect
-RECONNECT_MAX_ATTEMPTS = 10
-# Default delay between attempts to reconnect (seconds)
-RECONNECT_DELAY = 0.1
-# Number of reattempts in case of server return completion_status == 1 (try again)
-RETRY_MAX_ATTEMPTS = 10
diff --git a/connector/python/src/tarantool/error.py b/connector/python/src/tarantool/error.py
deleted file mode 100644
index 75f7899a6646741ce0384d9b13ced86bb42e76a5..0000000000000000000000000000000000000000
--- a/connector/python/src/tarantool/error.py
+++ /dev/null
@@ -1,143 +0,0 @@
-# -*- coding: utf-8 -*-
-# pylint: disable=C0301,W0105,W0401,W0614
-'''
-Python DB API compatible exceptions
-http://www.python.org/dev/peps/pep-0249/
-
-The PEP-249 says that database related exceptions must be inherited as follows:
-
-    StandardError
-    |__Warning
-    |__Error
-       |__InterfaceError
-       |__DatabaseError
-          |__DataError
-          |__OperationalError
-          |__IntegrityError
-          |__InternalError
-          |__ProgrammingError
-          |__NotSupportedError
-'''
-
-import os
-import socket
-import sys
-import warnings
-
-
-class Error(StandardError):
-    '''Base class for error exceptions'''
-
-
-class DatabaseError(Error):
-    '''Error related to the database engine'''
-
-
-class InterfaceError(Error):
-    '''Error related to the database interface rather than the database itself'''
-
-
-
-# Monkey patch os.strerror for win32
-if sys.platform == "win32":
-    # Windows Sockets Error Codes (not all, but related on network errors)
-    # http://msdn.microsoft.com/en-us/library/windows/desktop/ms740668(v=vs.85).aspx
-    _code2str = {
-        10004: "Interrupted system call",
-        10009: "Bad file descriptor",
-        10013: "Permission denied",
-        10014: "Bad address",
-        10022: "Invalid argument",
-        10024: "Too many open files",
-        10035: "Resource temporarily unavailable",
-        10036: "Operation now in progress",
-        10037: "Operation already in progress",
-        10038: "Socket operation on nonsocket",
-        10039: "Destination address required",
-        10040: "Message too long",
-        10041: "Protocol wrong type for socket",
-        10042: "Bad protocol option",
-        10043: "Protocol not supported",
-        10044: "Socket type not supported",
-        10045: "Operation not supported",
-        10046: "Protocol family not supported",
-        10047: "Address family not supported by protocol family",
-        10048: "Address already in use",
-        10049: "Cannot assign requested address",
-        10050: "Network is down",
-        10051: "Network is unreachable",
-        10052: "Network dropped connection on reset",
-        10053: "Software caused connection abort",
-        10054: "Connection reset by peer",
-        10055: "No buffer space available",
-        10056: "Socket is already connected",
-        10057: "Socket is not connected",
-        10058: "Cannot send after transport endpoint shutdown",
-        10060: "Connection timed out",
-        10061: "Connection refused",
-        10062: "Cannot translate name",
-        10063: "File name too long",
-        10064: "Host is down",
-        10065: "No route to host",
-        11001: "Host not found",
-        11004: "Name or service not known"
-    }
-
-
-    os_strerror_orig = os.strerror
-
-    def os_strerror_patched(code):
-        '''\
-        Return cross-platform message about socket-related errors
-
-        This function exists because under Windows os.strerror returns 'Unknown error' on all socket-related errors.
-        And socket-related exception contain broken non-ascii encoded messages.
-        '''
-        message = os_strerror_orig(code)
-        if not message.startswith("Unknown"):
-            return message
-        else:
-            return _code2str.get(code, "Unknown error %s"%code)
-
-    os.strerror = os_strerror_patched
-
-
-class NetworkError(DatabaseError):
-    '''Error related to network'''
-    def __init__(self, orig_exception=None, *args):
-        if orig_exception:
-            if isinstance(orig_exception, socket.timeout):
-                self.message = "Socket timeout"
-                super(NetworkError, self).__init__(0, self.message)
-            elif isinstance(orig_exception, socket.error):
-                self.message = os.strerror(orig_exception.errno)
-                super(NetworkError, self).__init__(orig_exception.errno, self.message)
-            else:
-                super(NetworkError, self).__init__(orig_exception, *args)
-
-
-class NetworkWarning(UserWarning):
-    '''Warning related to network'''
-    pass
-
-
-class RetryWarning(UserWarning):
-    '''Warning is emited in case of server return completion_status == 1 (try again)'''
-    pass
-
-
-# always print this warnings
-warnings.filterwarnings("always", category=NetworkWarning)
-warnings.filterwarnings("always", category=RetryWarning)
-
-
-def warn(message, warning_class):
-    '''\
-    Emit warinig message.
-    Just like standard warnings.warn() but don't output full filename.
-    '''
-    frame = sys._getframe(2) # pylint: disable=W0212
-    module_name = frame.f_globals.get("__name__")
-    line_no = frame.f_lineno
-    warnings.warn_explicit(message, warning_class, module_name, line_no)
-
diff --git a/connector/python/src/tarantool/request.py b/connector/python/src/tarantool/request.py
deleted file mode 100644
index 6cd4e81d60f7dbc16f7b98b7f5bf176194b1ea7b..0000000000000000000000000000000000000000
--- a/connector/python/src/tarantool/request.py
+++ /dev/null
@@ -1,277 +0,0 @@
-# -*- coding: utf-8 -*-
-# pylint: disable=C0301,W0105,W0401,W0614
-'''
-Request types definitions
-'''
-
-import struct
-
-from tarantool.const import *
-
-
-class Request(object):
-    '''\
-    Represents a single request to the server in compliance with the Tarantool protocol.
-    Responsible for data encapsulation and builds binary packet to be sent to the server.
-
-    This is the abstract base class. Specific request types are implemented by the inherited classes.
-    '''
-    request_type = None
-
-    def __init__(self):
-        self._bytes = None
-        raise NotImplementedError("Abstract method must be overridden")
-
-
-    def __bytes__(self):
-        return self._bytes
-    __str__ = __bytes__
-
-
-    @classmethod
-    def header(cls, body_length):
-        return struct_LLL.pack(cls.request_type, body_length, 0)
-
-
-    @staticmethod
-    def pack_int(value):
-        '''\
-        Pack integer field
-        <field> ::= <int32_varint><data>
-
-        :param value: integer value to be packed
-        :type value: int
-
-        :return: packed value
-        :rtype: bytes
-        '''
-        if __debug__:
-            if not isinstance(value, int):
-                raise TypeError("Invalid argument type '%s', 'int' expected"%type(value).__name__)
-        return struct_BL.pack(4, value)
-
-
-    @staticmethod
-    def pack_int_base128(value):
-        """Implement Perl pack's 'w' option, aka base 128 encoding."""
-        res = ''
-        if value >= 1 << 7:
-            if value >= 1 << 14:
-                if value >= 1 << 21:
-                    if value >= 1 << 28:
-                        res += chr(value >> 28 & 0xff | 0x80)
-                    res += chr(value >> 21 & 0xff | 0x80)
-                res += chr(value >> 14 & 0xff | 0x80)
-            res += chr(value >> 7 & 0xff | 0x80)
-        res += chr(value & 0x7F)
-        return res
-
-
-    @classmethod
-    def pack_str(cls, value):
-        '''\
-        Pack string field
-        <field> ::= <int32_varint><data>
-
-        :param value: string to be packed
-        :type value: bytes or str
-
-        :return: packed value
-        :rtype: bytes
-        '''
-        if __debug__:
-            if not isinstance(value, basestring):
-                raise TypeError("Invalid argument type '%s', 'str' expected"%type(value).__name__)
-        value_len_packed = cls.pack_int_base128(len(value))
-        return struct.pack("<%ds%ds"%(len(value_len_packed), len(value)), value_len_packed,  value)
-
-
-    @classmethod
-    def pack_field(cls, value):
-        '''\
-        Pack single field (string or integer value)
-        <field> ::= <int32_varint><data>
-
-        :param value: value to be packed
-        :type value: bytes, str or int
-
-        :return: packed value
-        :rtype: bytes
-        '''
-        if isinstance(value, basestring):
-            return cls.pack_str(value)
-        elif isinstance(value, (int, long)):
-            return cls.pack_int(value)
-        else:
-            raise TypeError("Invalid argument type '%s', 'str' or 'int' expected"%type(value).__name__)
-
-
-    @classmethod
-    def pack_tuple(cls, values):
-        '''\
-        Pack tuple of values
-        <tuple> ::= <cardinality><field>+
-
-        :param value: tuple to be packed
-        :type value: tuple of scalar values (bytes, str or int)
-
-        :return: packed tuple
-        :rtype: bytes
-        '''
-        assert isinstance(values, (tuple, list))
-        cardinality = struct_L.pack(len(values))
-        packed_items = [cls.pack_field(v) for v in values]
-        packed_items.insert(0, cardinality)
-        return b"".join(packed_items)
-
-
-
-class RequestInsert(Request):
-    '''\
-    Represents INSERT request
-
-    <insert_request_body> ::= <space_no><flags><tuple>
-    |--------------- header ----------------|--------- body ---------|
-     <request_type><body_length><request_id> <space_no><flags><tuple>
-                                                               |
-                          items to add (multiple values)  -----+
-    '''
-    request_type = REQUEST_TYPE_INSERT
-
-    def __init__(self, space_no, values, return_tuple):    # pylint: disable=W0231
-        '''\
-        '''
-        assert isinstance(values, (tuple, list))
-        flags = 1 if return_tuple else 0
-
-        request_body = \
-            struct_LL.pack(space_no, flags) + \
-            self.pack_tuple(values)
-
-        self._bytes = self.header(len(request_body)) + request_body
-
-
-
-class RequestDelete(Request):
-    '''
-    Represents DELETE request
-
-    <delete_request_body> ::= <space_no><flags><tuple>
-    |--------------- header ----------------|--------- body ---------|
-     <request_type><body_length><request_id> <space_no><flags><tuple>
-                                                               |
-                          key to search in primary index  -----+
-                          (tuple with single value)
-    '''
-    request_type = REQUEST_TYPE_DELETE
-
-    def __init__(self, space_no, key, return_tuple):    # pylint: disable=W0231
-        '''
-        '''
-        flags = 1 if return_tuple else 0
-
-        request_body = \
-            struct_LL.pack(space_no, flags) + \
-            self.pack_tuple((key,))
-
-        self._bytes = self.header(len(request_body)) + request_body
-
-
-
-class RequestSelect(Request):
-    '''\
-    Represents SELECT request
-
-    <select_request_body> ::= <space_no><index_no><offset><limit><count><tuple>+
-
-    |--------------- header ----------------|---------------request_body ---------------------...|
-     <request_type><body_length><request_id> <space_no><index_no><offset><limit><count><tuple>+
-                                                        ^^^^^^^^                 ^^^^^^^^^^^^
-                                                            |                          |
-                                           Index to use ----+                          |
-                                                                                       |
-                            List of tuples to search in the index ---------------------+
-                            (tuple cardinality can be > 1 when using composite indexes)
-    '''
-    request_type = REQUEST_TYPE_SELECT
-
-    def __init__(self, space_no, index_no, tuple_list, offset, limit):    # pylint: disable=W0231
-
-        assert isinstance(tuple_list, (list, tuple))
-
-        request_body = \
-            struct_LLLLL.pack(space_no, index_no, offset, limit, len(tuple_list)) + \
-            b"".join([self.pack_tuple(t) for t in tuple_list])
-
-        self._bytes = self.header(len(request_body)) + request_body
-
-
-
-class RequestUpdate(Request):
-    '''
-    <update_request_body> ::= <space_no><flags><tuple><count><operation>+
-    <operation> ::= <field_no><op_code><op_arg>
-
-    |--------------- header ----------------|---------------request_body --------------...|
-     <request_type><body_length><request_id> <space_no><flags><tuple><count><operation>+
-                                                               |      |      |
-                           Key to search in primary index -----+      |      +-- list of operations
-                           (tuple with cardinality=1)                 +-- number of operations
-    '''
-
-    request_type = REQUEST_TYPE_UPDATE
-
-    def __init__(self, space_no, key, op_list, return_tuple):    # pylint: disable=W0231
-        flags = 1 if return_tuple else 0
-        assert isinstance(key, (int, basestring))
-
-        request_body = \
-            struct_LL.pack(space_no, flags) + \
-            self.pack_tuple((key,)) + \
-            struct_L.pack(len(op_list)) +\
-            self.pack_operations(op_list)
-
-        self._bytes = self.header(len(request_body)) + request_body
-
-
-    @classmethod
-    def pack_operations(cls, op_list):
-        result = []
-        for op in op_list:
-            try:
-                field_no, op_symbol, op_arg = op
-            except ValueError:
-                raise ValueError("Operation must be a tuple of 3 elements (field_id, op, value)")
-            try:
-                op_code = UPDATE_OPERATION_CODE[op_symbol]
-            except KeyError:
-                raise ValueError("Invalid operaction symbol '%s', expected one of %s"\
-                                %(op_symbol, ', '.join(["'%s'"%c for c in sorted(UPDATE_OPERATION_CODE.keys())])))
-            data = b"".join([struct_LB.pack(field_no, op_code), cls.pack_field(op_arg)])
-            result.append(data)
-        return b"".join(result)
-
-
-
-class RequestCall(Request):
-    '''
-    <call_request_body> ::= <flags><proc_name><tuple>
-    <proc_name> ::= <field>
-
-    |--------------- header ----------------|-----request_body -------|
-     <request_type><body_length><request_id> <flags><proc_name><tuple>
-                                                                |
-                                    Lua function arguments -----+
-    '''
-    request_type = REQUEST_TYPE_CALL
-
-    def __init__(self, proc_name, args, return_tuple):    # pylint: disable=W0231
-        flags = 1 if return_tuple else 0
-        assert isinstance(args, (list, tuple))
-
-        request_body = \
-            struct_L.pack(flags) + \
-            self.pack_field(proc_name) +\
-            self.pack_tuple(args)
-
-        self._bytes = self.header(len(request_body)) + request_body
diff --git a/connector/python/src/tarantool/response.py b/connector/python/src/tarantool/response.py
deleted file mode 100644
index 047133e3206ebd355c7aa470802a64ca11e36f26..0000000000000000000000000000000000000000
--- a/connector/python/src/tarantool/response.py
+++ /dev/null
@@ -1,296 +0,0 @@
-# -*- coding: utf-8 -*-
-# pylint: disable=C0301,W0105,W0401,W0614
-
-import ctypes
-import socket
-import struct
-import sys
-
-from tarantool.const import *
-from tarantool.error import *
-
-
-
-if sys.version_info < (2, 6):
-    bytes = str    # pylint: disable=W0622
-
-class field(bytes):
-    '''\
-    Represents a single element of the Tarantool's tuple
-    '''
-    def __new__(cls, value):
-        '''\
-        Create new instance of Tarantool field (single tuple element)
-        '''
-        # Since parent class is immutable, we should override __new__, not __init__
-
-        if isinstance(value, unicode):
-            return super(field, cls).__new__(cls, value.encode("utf-8", "replace"))
-        if sys.version_info.major < 3 and isinstance(value, str):
-            return super(field, cls).__new__(cls, value)
-        if isinstance(value, (bytearray, bytes)):
-            return super(field, cls).__new__(cls, value)
-        if isinstance(value, int):
-            if value <= 0xFFFFFFFF:
-                # 32 bit integer
-                return super(field, cls).__new__(cls, struct_L.pack(value))
-            else:
-                # 64 bit integer
-                return super(field, cls).__new__(cls, struct_Q.pack(value))
-        # NOTE: It is posible to implement float
-        raise TypeError("Unsupported argument type '%s'"%(type(value).__name__))
-
-
-    def __int__(self):
-        '''\
-        Cast filed to int
-        '''
-        if len(self) == 4:
-            return struct_L.unpack(self)[0]
-        elif len(self) == 8:
-            return struct_Q.unpack(self)[0]
-        else:
-            raise ValueError("Unable to cast field to int: length must be 4 or 8 bytes, field length is %d"%len(self))
-
-
-    if sys.version_info.major > 2:
-        def __str__(self):
-            '''\
-            Cast filed to str
-            '''
-            return self.decode("utf-8", "replace")
-    else:
-        def __unicode__(self):
-            '''\
-            Cast filed to unicode
-            '''
-            return self.decode("utf-8", "replace")
-
-
-
-class Response(list):
-    '''\
-    Represents a single response from the server in compliance with the Tarantool protocol.
-    Responsible for data encapsulation (i.e. received list of tuples) and parses binary
-    packet received from the server.
-    '''
-
-    def __init__(self, _socket, field_types=None):
-        '''\
-        Create an instance of `Response` using data received from the server.
-
-        __init__() itself reads data from the socket, parses response body and
-        sets appropriate instance attributes.
-
-        :params _socket: socket connected to the server
-        :type _socket: instance of socket.socket class (from stdlib)
-        '''
-        # This is not necessary, because underlying list data structures are created in the __new__(). But let it be.
-        super(Response, self).__init__()
-
-        self._body_length = None
-        self._request_id = None
-        self._request_type = None
-        self._completion_status = None
-        self._return_code = None
-        self._return_message = None
-        self._rowcount = None
-        self.field_types = field_types
-
-        # Read response header
-        buff = ctypes.create_string_buffer(16)
-        nbytes = _socket.recv_into(buff, 16, )
-
-        # Immediately raises an exception if the data cannot be read
-        if nbytes != 16:
-            raise socket.error(socket.errno.ECONNABORTED, "Software caused connection abort")
-
-        # Unpack header (including <return_code> attribute)
-        self._request_type, self._body_length, self._request_id, self._return_code = struct_LLLL.unpack(buff)
-
-        # Separate return_code and completion_code
-        self._completion_status = self._return_code & 0x00ff
-        self._return_code = self._return_code >> 8
-
-        # Unpack body if there is one (i.e. not PING)
-        if self._body_length != 0:
-
-            # In the protocol description <body_length> includes 4 bytes of <return_code>
-            self._body_length -= 4
-
-            # Read response body
-            buff = ctypes.create_string_buffer(self._body_length)
-            nbytes = _socket.recv_into(buff)
-
-            # Immediately raises an exception if the data cannot be read
-            if nbytes != self._body_length:
-                raise socket.error(socket.errno.ECONNABORTED, "Software caused connection abort")
-
-            if self._return_code == 0:
-                # If no errors, unpack response body
-                self._unpack_body(buff)
-            else:
-                # In case of error unpack body as error message
-                self._unpack_message(buff)
-                if self._completion_status == 2:
-                    raise DatabaseError(self._return_code, self._return_message)
-
-
-    def _unpack_message(self, buff):
-        '''\
-        Extract error message from response body
-        Called when return_code! = 0.
-
-        :param buff: buffer containing request body
-        :type byff: ctypes buffer
-        :return: error message
-        :rtype:  str
-        '''
-
-        self._return_message = unicode(buff.value, "utf8", "replace")
-
-
-    @staticmethod
-    def _unpack_int_base128(varint, offset):
-        """Implement Perl unpack's 'w' option, aka base 128 decoding."""
-        res = ord(varint[offset])
-        if ord(varint[offset]) >= 0x80:
-            offset += 1
-            res = ((res - 0x80) << 7) + ord(varint[offset])
-            if ord(varint[offset]) >= 0x80:
-                offset += 1
-                res = ((res - 0x80) << 7) + ord(varint[offset])
-                if ord(varint[offset]) >= 0x80:
-                    offset += 1
-                    res = ((res - 0x80) << 7) + ord(varint[offset])
-                    if ord(varint[offset]) >= 0x80:
-                        offset += 1
-                        res = ((res - 0x80) << 7) + ord(varint[offset])
-        return res, offset + 1
-
-
-    def _unpack_tuple(self, buff):
-        '''\
-        Unpacks the tuple from byte buffer
-        <tuple> ::= <cardinality><field>+
-
-        :param buff: byte array of the form <cardinality><field>+
-        :type buff: ctypes buffer or bytes
-
-        :return: tuple of unpacked values
-        :rtype: tuple
-        '''
-
-        cardinality = struct_L.unpack_from(buff)[0]
-        _tuple = ['']*cardinality
-        offset = 4    # The first 4 bytes in the response body is the <count> we have already read
-        for i in xrange(cardinality):
-            field_size, offset = self._unpack_int_base128(buff, offset)
-            field_data = struct.unpack_from("<%ds"%field_size, buff, offset)[0]
-            _tuple[i] = field(field_data)
-            offset += field_size
-
-        return tuple(_tuple)
-
-
-    def _unpack_body(self, buff):
-        '''\
-        Parse the response body.
-        After body unpacking its data available as python list of tuples
-
-        For each request type the response body has the same format:
-        <insert_response_body> ::= <count> | <count><fq_tuple>
-        <update_response_body> ::= <count> | <count><fq_tuple>
-        <delete_response_body> ::= <count> | <count><fq_tuple>
-        <select_response_body> ::= <count><fq_tuple>*
-        <call_response_body>   ::= <count><fq_tuple>
-
-        :param buff: buffer containing request body
-        :type byff: ctypes buffer
-        '''
-
-        # Unpack <count> (first 4 bytes) - how many records returned
-        self._rowcount = struct_L.unpack_from(buff)[0]
-
-        # If the response body contains only <count> - there is no tuples to unpack
-        if self._body_length == 4:
-            return
-
-        # Parse response tuples (<fq_tuple>)
-        if self._rowcount > 0:
-            offset = 4    # The first 4 bytes in the response body is the <count> we have already read
-            while offset < self._body_length:
-                '''
-                # In resonse tuples have the form <size><tuple> (<fq_tuple> ::= <size><tuple>).
-                # Attribute <size> takes into account only size of tuple's <field> payload,
-                # but does not include 4-byte of <cardinality> field.
-                # Therefore the actual size of the <tuple> is greater to 4 bytes.
-                '''
-                tuple_size = struct.unpack_from("<L", buff, offset)[0] + 4
-                tuple_data = struct.unpack_from("<%ds"%(tuple_size), buff, offset+4)[0]
-                tuple_value = self._unpack_tuple(tuple_data)
-                if self.field_types:
-                    self.append(self._cast_tuple(tuple_value))
-                else:
-                    self.append(tuple_value)
-
-                offset = offset + tuple_size + 4    # This '4' is a size of <size> attribute
-
-
-    @property
-    def completion_status(self):
-        return self._completion_status
-
-    @property
-    def rowcount(self):
-        return self._rowcount
-
-    @property
-    def return_code(self):
-        return self._return_code
-
-    @property
-    def return_message(self):
-        return self._return_message
-
-
-    @staticmethod
-    def _cast_field(cast_to, value):
-        '''\
-        Convert field type from raw bytes to native python type
-
-        :param cast_to: native python type to cast to
-        :type cast_to: a type object (one of bytes, int, unicode (str for py3k))
-        :param value: raw value from the database
-        :type value: bytes
-
-        :return: converted value
-        :rtype: value of native python type (one of bytes, int, unicode (str for py3k))
-        '''
-
-        if cast_to in (int, unicode):
-            return cast_to(value)
-        elif cast_to in (any, bytes):
-            return value
-        else:
-            raise TypeError("Invalid field type %s"%(cast_to))
-
-
-    def _cast_tuple(self, values):
-        '''\
-        Convert values of the tuple from raw bytes to native python types
-
-        :param values: tuple of the raw database values
-        :type value: tuple of bytes
-
-        :return: converted tuple value
-        :rtype: value of native python types (bytes, int, unicode (or str for py3k))
-        '''
-        result = []
-        for i in xrange(len(values)):
-            if i < len(self.field_types):
-                result.append(self._cast_field(self.field_types[i], values[i]))
-            else:
-                result.append(self._cast_field(self.field_types[-1], values[i]))
-
-        return tuple(result)
diff --git a/connector/python/src/tarantool/space.py b/connector/python/src/tarantool/space.py
deleted file mode 100644
index 2010f5436b8bc8fc575444bb61743c6f3d084d25..0000000000000000000000000000000000000000
--- a/connector/python/src/tarantool/space.py
+++ /dev/null
@@ -1,36 +0,0 @@
-# -*- coding: utf-8 -*-
-### pylint: disable=C0301,W0105,W0401,W0614
-'''
-This module provides object-oriented wrapper for accessing a particular Tarantool space
-'''
-import sys
-
-
-
-class Space(object):
-    '''\
-    Object-oriented wrapper for accessing a particular space.
-    Encapsulates the identifier of the space and provides more convenient syntax
-    for database operations.
-    '''
-    def __init__(self, connection, space_no, field_types=None):
-        if __debug__:
-            if field_types and not all([(t is bytes) or (t is int) or (t is unicode) for t in field_types]):
-                raise TypeError("Argument field_types can contain only bytes, int or %s"\
-                                %('str' if sys.version_info.major > 2 else 'unicode'))
-
-        self.connection = connection
-        self.space_no = space_no
-        self.field_types = field_types
-
-    def insert(self, values, return_tuple=False):
-        return self.connection.insert(self.space_no, values, return_tuple, self.field_types)
-
-    def delete(self, key, return_tuple=False):
-        return self.connection.delete(self.space_no, key, return_tuple, self.field_types)
-
-    def update(self, key, op_list, return_tuple=False):
-        return self.connection.update(self.space_no, key, op_list, return_tuple, self.field_types)
-
-    def select(self, index_no, values, offset=0, limit=0xffffffff):
-        return self.connection.select(self.space_no, index_no, values, offset, limit, self.field_types)
diff --git a/connector/ruby/README b/connector/ruby/README
index 826e0c9b6a37d9569997b84e41452bd2e5817556..651b8f1d4a2e2b15ae7912d8ce8107d0ae634b1a 100644
--- a/connector/ruby/README
+++ b/connector/ruby/README
@@ -1 +1,2 @@
-A toy implementation of ruby client library for IProto/Box binary protocol
+Tarantool Ruby driver can be found at
+https://github.com/mailru/tarantool-ruby
diff --git a/core/CMakeLists.txt b/core/CMakeLists.txt
index d39ef1a804857b25545df8f8de5d90fbaf169d5d..861e9e11b50625991fc72c715cceddf83b09b631 100644
--- a/core/CMakeLists.txt
+++ b/core/CMakeLists.txt
@@ -61,7 +61,7 @@ set (recompiled_core_sources
      ${CMAKE_SOURCE_DIR}/core/fiber.m PARENT_SCOPE)
 
 set (common_sources tbuf.m palloc.m util.m
-    salloc.m pickle.m coro.m stat.m log_io.m
+    salloc.m pickle.m coro.m stat.m log_io.m cpu_feature.m
     log_io_remote.m iproto.m exception.m errcode.c latch.m)
 
 if (ENABLE_TRACE)
diff --git a/core/cpu_feature.m b/core/cpu_feature.m
new file mode 100644
index 0000000000000000000000000000000000000000..01eb71b490f804e1c5eaec1c3c3337a3b3edf748
--- /dev/null
+++ b/core/cpu_feature.m
@@ -0,0 +1,205 @@
+/*
+ * Copyright (C) 2010 Mail.RU
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED.  IN NO EVENT SHALL AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#include <sys/types.h>
+#include <errno.h>
+
+#include "cpu_feature.h"
+
+enum { eAX=0, eBX, eCX, eDX };
+
+static const struct cpuid_feature {
+	unsigned int 	ri;
+	u_int32_t 		mask;
+} cpu_ftr[] = {
+	{eDX, (1 << 28)},	/* HT 		*/
+	{eCX, (1 << 19)},	/* SSE 4.1 	*/
+	{eCX, (1 << 20)},	/* SSE 4.2 	*/
+	{eCX, (1 << 31)}	/* HYPERV	*/
+};
+static const size_t LEN_cpu_ftr = sizeof(cpu_ftr) / sizeof (cpu_ftr[0]);
+
+#define SCALE_F		sizeof(unsigned long)
+
+#if defined (__x86_64__)
+	#define REX_PRE "0x48, "
+#elif defined (__i386__)
+	#define REX_PRE
+#else
+	#error "Only x86 and x86_64 architectures supported"
+#endif
+
+
+/* hw-calculate CRC32 per byte (for the unaligned portion of data buffer)
+ */
+static u_int32_t
+crc32c_hw_byte(u_int32_t crc, unsigned char const *data, size_t length)
+{
+	while (length--) {
+		__asm__ __volatile__(
+			".byte 0xf2, 0xf, 0x38, 0xf0, 0xf1"
+			:"=S"(crc)
+			:"0"(crc), "c"(*data)
+		);
+		data++;
+	}
+
+	return crc;
+}
+
+
+/* hw-calculate CRC32 for the given data buffer
+ */
+u_int32_t
+crc32c_hw(u_int32_t crc, unsigned char const *p, size_t len)
+{
+	unsigned int iquotient = len / SCALE_F;
+	unsigned int iremainder = len % SCALE_F;
+	unsigned long *ptmp = (unsigned long *)p;
+
+	while (iquotient--) {
+		__asm__ __volatile__(
+			".byte 0xf2, " REX_PRE "0xf, 0x38, 0xf1, 0xf1;"
+			:"=S"(crc)
+			:"0"(crc), "c"(*ptmp)
+		);
+		ptmp++;
+	}
+
+	if (iremainder) {
+		crc = crc32c_hw_byte(crc, (unsigned char *)ptmp,
+				 			iremainder);
+	}
+
+	return crc;
+}
+
+
+/* toggle x86 flag-register bits, as per mask
+   return flags both in original and toggled state
+ */
+static void
+toggle_x86_flags (long mask, long* orig, long* toggled)
+{
+	long forig = 0, fres = 0;
+
+#if defined (__i386__)
+	asm (
+		"pushfl; popl %%eax; movl %%eax, %0; xorl %2, %%eax; "
+		"pushl %%eax; popfl; pushfl; popl %%eax; pushl %0; popfl "
+		: "=r" (forig), "=a" (fres)
+		: "m" (mask)
+	);
+#elif __x86_64__
+	asm (
+		"pushfq; popq %%rax; movq %%rax, %0; xorq %2, %%rax; "
+		"pushq %%rax; popfq; pushfq; popq %%rax; pushq %0; popfq "
+		: "=r" (forig), "=a" (fres)
+		: "m" (mask)
+	);
+#endif
+
+	if (orig) 		*orig = forig;
+	if (toggled) 	*toggled = fres;
+	return;
+}
+
+
+/* is CPUID instruction available ? */
+static int
+can_cpuid ()
+{
+	long of = -1, tf = -1;
+
+	/* x86 flag register masks */
+	enum {
+		cpuf_AC = (1 << 18), 	/* bit 18 */
+		cpuf_ID = (1 << 21)		/* bit 21 */
+	};
+
+
+	/* check if AC (alignment) flag could be toggled:
+		if not - it's i386, thus no CPUID
+	*/
+	toggle_x86_flags (cpuf_AC, &of, &tf);
+	if ((of & cpuf_AC) == (tf & cpuf_AC)) {
+		return 0;
+	}
+
+	/* next try toggling CPUID (ID) flag */
+	toggle_x86_flags (cpuf_ID, &of, &tf);
+	if ((of & cpuf_ID) == (tf & cpuf_ID)) {
+		return 0;
+	}
+
+	return 1;
+}
+
+
+/* retrieve CPUID data using info as the EAX key */
+static void
+get_cpuid (long info, long* eax, long* ebx, long* ecx, long *edx)
+{
+	*eax = info;
+
+#if defined (__i386__)
+	asm __volatile__ (
+		"movl %%ebx, %%edi; " 	/* must save ebx for 32-bit PIC code */
+		"cpuid; "
+		"movl %%ebx, %%esi; "
+		"movl %%edi, %%ebx; "
+		: "+a" (*eax), "=S" (*ebx), "=c" (*ecx), "=d" (*edx)
+		:
+		: "%edi"
+	);
+#elif defined (__x86_64__)
+	asm __volatile__ (
+		"cpuid; "
+		: "+a" (*eax), "=b" (*ebx), "=c" (*ecx), "=d" (*edx)
+	);
+#endif
+}
+
+
+/* return 1=feature is available, 0=unavailable, 0>(errno) = error */
+int
+cpu_has (unsigned int feature)
+{
+	long info = 1, reg[4] = {0,0,0,0};
+
+	if (!can_cpuid ())
+		return -EINVAL;
+
+	if (feature > LEN_cpu_ftr)
+		return -ERANGE;
+
+	get_cpuid (info, &reg[eAX], &reg[eBX], &reg[eCX], &reg[eDX]);
+
+	return (reg[cpu_ftr[feature].ri] & cpu_ftr[feature].mask) ? 1 : 0;
+}
+
+
+/* __EOF__ */
+
diff --git a/core/fiber.m b/core/fiber.m
index ed8091aebd04ec09a837fb48312c81ce84ac51e2..32f36d7be157bb2afe3e3b52b58e69782e4e2626 100644
--- a/core/fiber.m
+++ b/core/fiber.m
@@ -689,7 +689,7 @@ read_inbox(void)
 ssize_t
 fiber_bread(struct tbuf *buf, size_t at_least)
 {
-	ssize_t r;
+	ssize_t r = 0;
 	tbuf_ensure(buf, MAX(cfg.readahead, at_least));
 	size_t stop_at = buf->size + at_least;
 
diff --git a/core/log_io_remote.m b/core/log_io_remote.m
index 4666796049a987faf17bf3f8c4d6aad90c0eaa5f..4b14427f11e57bf39662f34e5bc3209ffaabbe24 100644
--- a/core/log_io_remote.m
+++ b/core/log_io_remote.m
@@ -190,6 +190,7 @@ recovery_follow_remote(struct recovery_state *r, const char *remote)
 
 	rc = sscanf(remote, "%31[^:]:%i", ip_addr, &port);
 	assert(rc == 2);
+	(void)rc;
 
 	if (inet_aton(ip_addr, &server) < 0) {
 		say_syserror("inet_aton: %s", ip_addr);
diff --git a/core/palloc.m b/core/palloc.m
index 3b4bd325bd5366541848bd1dd7915b6007ad3ef8..3dfe17b2ddbb51060c96fee0597f8d4976b0afbd 100644
--- a/core/palloc.m
+++ b/core/palloc.m
@@ -366,19 +366,7 @@ palloc_stat(struct tbuf *buf)
 	int chunks[class_count];
 
 	tbuf_printf(buf, "palloc statistic:" CRLF);
-	tbuf_printf(buf, "  classes:" CRLF);
-	TAILQ_FOREACH(class, &classes, link) {
-		int free_chunks = 0;
-		SLIST_FOREACH(chunk, &class->chunks, free_link)
-		    free_chunks++;
-
-		tbuf_printf(buf,
-			    "    - { size: %"PRIu32
-			    ", free_chunks: %- 6i, busy_chunks: %- 6i }" CRLF, class->allocated_size,
-			    free_chunks, class->chunks_count - free_chunks);
-	}
 	tbuf_printf(buf, "  pools:" CRLF);
-
 	SLIST_FOREACH(pool, &pools, link) {
 		for (int i = 0; i < class_count; i++)
 			chunks[i] = 0;
@@ -392,18 +380,39 @@ palloc_stat(struct tbuf *buf)
 			SLIST_FOREACH(chunk, &pool->chunks, busy_link)
 			    chunks[chunk->class->i]++;
 
-			int indent = 0;
 			TAILQ_FOREACH(class, &classes, link) {
 				if (chunks[class->i] == 0)
 					continue;
 				tbuf_printf(buf, "        - { size: %"PRIu32", used: %i }" CRLF,
 					    class->allocated_size, chunks[class->i]);
-
-				if (indent == 0)
-					indent = 19;
 			}
 		}
 	}
+	tbuf_printf(buf, "  classes:" CRLF);
+	TAILQ_FOREACH(class, &classes, link) {
+		int free_chunks = 0;
+		SLIST_FOREACH(chunk, &class->chunks, free_link)
+			free_chunks++;
+
+		tbuf_printf(buf,
+			    "    - { size: %"PRIu32
+			    ", free_chunks: %- 6i, busy_chunks: %- 6i }" CRLF, class->allocated_size,
+			    free_chunks, class->chunks_count - free_chunks);
+	}
+	u64 palloc_total = 0;
+	u64 palloc_used = 0;
+	SLIST_FOREACH(pool, &pools, link) {
+		SLIST_FOREACH(chunk, &pool->chunks, busy_link) {
+			palloc_total += chunk->size;
+			palloc_used += chunk->size - chunk->free;
+		}
+		SLIST_FOREACH(chunk, &pool->chunks, free_link)
+			palloc_total += chunk->size;
+	}
+	tbuf_printf(buf, "  total:" CRLF);
+	tbuf_printf(buf, "    - { occupied: %"PRIu64", used: %"PRIu64" }"CRLF,
+		    palloc_total, palloc_used);
+
 }
 
 void
diff --git a/core/tarantool.m b/core/tarantool.m
index c59e0da8f4843df5ed411607d9931f9bde98d38c..90ef0082a2ac22f005c22b395039728a3b1c9367 100644
--- a/core/tarantool.m
+++ b/core/tarantool.m
@@ -60,7 +60,7 @@
 
 static pid_t master_pid;
 #define DEFAULT_CFG_FILENAME "tarantool.cfg"
-#define DEFAULT_CFG INSTALL_PREFIX "/etc/" DEFAULT_CFG_FILENAME
+#define DEFAULT_CFG SYSCONF_DIR "/" DEFAULT_CFG_FILENAME
 const char *cfg_filename = NULL;
 char *cfg_filename_fullpath = NULL;
 char *binary_filename;
@@ -374,6 +374,8 @@ tarantool_free(void)
 #ifdef HAVE_BFD
 	symbols_free();
 #endif
+	if (tarantool_L)
+		tarantool_lua_close(tarantool_L);
 }
 
 static void
@@ -464,6 +466,15 @@ main(int argc, char **argv)
 		return 0;
 	}
 
+	if (gopt_arg(opt, 'C', &cat_filename)) {
+		initialize_minimal();
+		if (access(cat_filename, R_OK) == -1) {
+			panic("access(\"%s\"): %s", cat_filename, strerror(errno));
+			exit(EX_OSFILE);
+		}
+		return mod_cat(cat_filename);
+	}
+
 	gopt_arg(opt, 'c', &cfg_filename);
 	/* if config is not specified trying ./tarantool.cfg then /etc/tarantool.cfg */
 	if (cfg_filename == NULL) {
@@ -568,15 +579,6 @@ main(int argc, char **argv)
 #endif
 	}
 
-	if (gopt_arg(opt, 'C', &cat_filename)) {
-		initialize_minimal();
-		if (access(cat_filename, R_OK) == -1) {
-			say_syserror("access(\"%s\")", cat_filename);
-			exit(EX_OSFILE);
-		}
-		return mod_cat(cat_filename);
-	}
-
 	if (gopt(opt, 'I')) {
 		init_storage = true;
 		initialize_minimal();
diff --git a/core/tarantool_lua.m b/core/tarantool_lua.m
index 52a78a1fe5b20696fd7b5fcf23a2900330a9f8d6..04f6dcb3de185da917a825c5aafdcf5b2db48716 100644
--- a/core/tarantool_lua.m
+++ b/core/tarantool_lua.m
@@ -36,6 +36,7 @@
 
 #include "pickle.h"
 #include "fiber.h"
+#include <ctype.h>
 #include TARANTOOL_CONFIG
 
 struct lua_State *tarantool_L;
@@ -434,7 +435,7 @@ box_lua_fiber_run(void *arg __attribute__((unused)))
 		 * cancel() is synchronous.
 		 */
 		lua_settop(L, 0); /* pop any possible garbage */
-		lua_pushboolean(L, 0); /* completion status */
+		lua_pushboolean(L, false); /* completion status */
 		lua_pushstring(L, e->errmsg); /* error message */
 	} @finally {
 		/*
@@ -698,6 +699,37 @@ lbox_print(struct lua_State *L)
 	return 0;
 }
 
+/**
+ * Redefine lua 'pcall' built-in to correctly handle exceptions,
+ * produced by 'box' C functions.
+ *
+ * See Lua documentation on 'pcall' for additional information.
+ */
+
+static int
+lbox_pcall(struct lua_State *L)
+{
+	/*
+	 * Lua pcall() returns true/false for completion status
+	 * plus whatever the called function returns.
+	 */
+	@try {
+		lua_call(L, lua_gettop(L) - 1, LUA_MULTRET);
+		lua_pushboolean(L, true); /* push completion status */
+		lua_insert(L, 1); /* move 'true' to stack start */
+	} @catch (ClientError *e) {
+		/*
+		 * Note: FiberCancelException passes through this
+		 * catch and thus leaves garbage on coroutine
+		 * stack.
+		 */
+		lua_settop(L, 0); /* pop any possible garbage */
+		lua_pushboolean(L, false); /* completion status */
+		lua_pushstring(L, e->errmsg); /* error message */
+	}
+	return lua_gettop(L);
+}
+
 /** A helper to register a single type metatable. */
 void
 tarantool_lua_register_type(struct lua_State *L, const char *type_name,
@@ -730,11 +762,18 @@ tarantool_lua_init()
 	lua_pop(L, 1);
 	tarantool_lua_register_type(L, fiberlib_name, lbox_fiber_meta);
 	lua_register(L, "print", lbox_print);
+	lua_register(L, "pcall", lbox_pcall);
 	L = mod_lua_init(L);
 	lua_settop(L, 0); /* clear possible left-overs of init */
 	return L;
 }
 
+void
+tarantool_lua_close(struct lua_State *L)
+{
+	lua_close(L); /* collects garbage, invoking userdata gc */
+}
+
 /**
  * Attempt to append 'return ' before the chunk: if the chunk is
  * an expression, this pushes results of the expression onto the
@@ -790,6 +829,8 @@ is_string(const char *str)
 {
 	if (strcmp(str, "true") == 0 || strcmp(str, "false") == 0)
 	    return false;
+	if (! isdigit(*str))
+	    return true;
 	char *endptr;
 	(void) strtod(str, &endptr);
 	return *endptr != '\0';
diff --git a/core/util.m b/core/util.m
index 83299dbb18f718fa9def70049968bc97b7a8c577..b78b43061ea3a9ad74ad3012bd5513474643bce1 100644
--- a/core/util.m
+++ b/core/util.m
@@ -219,7 +219,7 @@ assert_fail(const char *assertion, const char *file, unsigned int line, const ch
 
 #ifdef HAVE_BFD
 static struct symbol *symbols;
-static size_t symbol_count;
+static ssize_t symbol_count;
 
 int
 compare_symbol(const void *_a, const void *_b)
@@ -321,29 +321,31 @@ void symbols_free()
 	free(symbols);
 }
 
+/**
+ * Sic: this assumes stack direction is from lowest to
+ * highest.
+ */
 struct symbol *
 addr2symbol(void *addr)
 {
-	int low = 0, high = symbol_count, middle = -1;
-	struct symbol *ret, key = {.addr = addr};
+	struct symbol key = {.addr = addr, .name = NULL, .end = NULL};
+	struct symbol *low = symbols;
+	struct symbol *high = symbols + symbol_count;
 
-	while(low < high) {
-		middle = low + ((high - low) >> 1);
-		int diff = compare_symbol(symbols + middle, &key);
+	while (low + 1 < high) { /* there are at least two to choose from. */
+		struct symbol *middle = low + ((high - low) >> 1);
 
-		if (diff < 0) {
-			low = middle + 1;
-		} else if (diff > 0) {
+		int diff = compare_symbol(&key, middle);
+		if (diff < 0) { /* key < middle. */
 			high = middle;
-		} else {
-			ret = symbols + middle;
-			goto out;
+		} else {/* key >= middle */
+			low = middle;
+			if (diff == 0)
+				break;
 		}
 	}
-	ret = symbols + high - 1;
-out:
-	if (middle != -1 && ret->addr <= key.addr && key.addr <= ret->end)
-		return ret;
+	if (low->addr <= key.addr && low->end >= key.addr)
+		return low;
 	return NULL;
 }
 
diff --git a/doc/box-protocol.txt b/doc/box-protocol.txt
index d42c078fe931efce3e9ae9b5dac15403564cc0e0..c5e090da7b3fefd89bbb1a5fdd437ddacd1e393c 100644
--- a/doc/box-protocol.txt
+++ b/doc/box-protocol.txt
@@ -1,4 +1,4 @@
-; Mail.RU IPROTO protocol, Tarantool/Box subset.
+Tarantool/Box IPROTO protocol.
 ;
 ; The latest version of this document can be found in
 ; tarantool source tree, doc/box-protocol.txt
@@ -13,7 +13,7 @@
 ;
 ; int8 - a single 8-bit byte (i.e. an octet)
 ;
-; int32 - a 32-bit integer in big-endian format (Intel x86)
+; int32 - a 32-bit integer in little-endian format (Intel x86)
 ;
 ; int32_varint - a 1 to 5 byte representation of an integer
 ;
@@ -21,8 +21,8 @@
 ; integer (BER stands for Basic Encoding Rules, but in reality it
 ; has little to do with ASN.1 standard). 
 ; See http://en.wikipedia.org/wiki/LEB128
-; for encoding description, or core/pickle.c for implementation
-; in tarantool.
+; for encoding description, or core/pickle.m for implementation
+; in Tarantool.
 
 ; All requests and responses utilize the same basic structure:
 
@@ -193,6 +193,7 @@
 <int32_varint> ::= <int8>+
 ;
 ; SELECT may return zero, one or several tuples.
+; CALL response is identical to one for SELECT.
 ; <select_response_body> starts with the number of found
 ; tuples:
 ;
@@ -214,6 +215,7 @@
 
 <fq_tuple> ::= <size><tuple>
 
+; length of the variable part of the tuple (all tuple fields)
 <size> ::= <int32>
 
 ;
@@ -274,7 +276,7 @@
 ; on strings:
 ; 5 - implementation of Perl 'splice' command
 
-<op_code> ::= <int8> # 0 | 1 | 2 | 3
+<op_code> ::= <int8> # 0 | 1 | 2 | 3 | 4 | 5
 
 ;
 ; It's an error to specify an argument of a type that
@@ -340,7 +342,7 @@
 ; <return_code>. If it's not 0, it is followed by an
 ; error message. Otherwise, the response, just like in case of
 ; SELECT, is a sequence of <fq_tuple>s.
-<call_response_body> ::= <count><fq_tuple>
+<call_response_body> ::= <select_response_body>
 
 ;
 ; The server response, in addition to response header and body,
@@ -351,7 +353,7 @@
 
 <return_code> ::= <int32>
 
-; Currently, the completion status is complementary:
+; The completion status is complementary:
 ; it can be deduced from the error code. There are only
 ; 3 completion status codes in use:
 ; 0  - success; The only possible error code with this status is
@@ -406,8 +408,8 @@
 ;  0x00002702 -- ER_WAL_IO
 ;                WAL I/O error
 ;
-; Convenience that define hexadecimal constants for <int32>
-; return codes (completion status + code) can be found in
-; include/iproto.h.
+; Convenience macros which define hexadecimal constants for
+; <int32> return codes (completion status + code) can be found
+; in include/iproto.h.
 ;
 ; vim: syntax=bnf
diff --git a/doc/coding-style-shell.txt b/doc/coding-style-shell.txt
new file mode 100644
index 0000000000000000000000000000000000000000..4ed2bc6776873d9352dc0893c495874f69682366
--- /dev/null
+++ b/doc/coding-style-shell.txt
@@ -0,0 +1,259 @@
+
+Bourne Shell Coding Conventions
+--------------------------------
+
+Original version by Mike Shapiro and OpenSolaris Shell Project.
+This document describes the shell coding style used for all the ON shell
+script changes integrated into Solaris.
+
+All new shell code should conform to this coding standard, which is intended
+to match our existing C coding standard.
+
+When in doubt, think "what would be the C-Style equivalent?"
+
+Basic Format
+------------
+
+Similar to cstyle, the basic format is that all lines are indented by TABs,
+and continuation lines (which in the shell end with "\") are indented by
+an equivalent number of TABs and then an additional four spaces, e.g.
+
+cp foo bar
+cp some_realllllllllllllllly_realllllllllllllly_long_path \
+   to_another_really_long_path
+
+If, For, and While
+------------------
+
+To match cstyle, the sh token equivalent to the C "{" should appear on
+the same line, separated by a ";", as in:
+
+if [ $x = hello ]; then
+ echo $x
+fi
+
+for i in 1 2 3; do
+  echo $i
+done
+
+while [ $# -gt 0 ]; do
+  echo $1
+ shift
+done
+
+Test Built-in
+-------------
+
+DO NOT use the test built-in. Sorry, executive decision. In our Bourne shell,
+the test built-in is the same as the "[" built-in (if you don't believe me,
+try "type test" or refer to usr/src/cmd/sh/msg.c). So please do not write:
+
+if test $# -gt 0; then
+
+instead use:
+
+if [ $# -gt 0 ]; then
+
+Single-line if-statements
+-------------------------
+
+It is permissible to use && and || to construct shorthand for an "if"
+statement in the case where the if statement has a single consequent line:
+
+[ $# -eq 0 ] && exit 0
+
+instead of the longer:
+
+if [ $# -eq 0 ]; then
+  exit 0
+fi
+
+DO NOT combine && with { }, as in:
+
+[ $# -eq 0 ] && {
+ do something
+ do something else
+}
+
+Use a complete "if-then-fi" construct for this instead.
+
+Infinite Loops
+--------------
+
+The proper way to write an infinite loop in the Bourne shell is to use
+the ":" built-in, which evaluates to true (exit status 0).
+This is better than using "true", because that is *not* a built-in in the
+Bourne shell and thus runs /bin/true.
+
+while :; do
+ echo infinite loop
+done
+
+Exit Status and If/While Statements
+-----------------------------------
+
+Recall that "if" and "while" operate on the exit status of the statement
+to be executed. In the shell, zero (0) means true and non-zero means false.
+The exit status of the last command which was executed is available in
+the $? variable. When using "if" and "while", it is typically not necessary
+to use $? explicitly, as in:
+
+grep foo /etc/passwd >/dev/null 2>&1
+if [ $? -eq 0 ]; then
+  echo found
+fi
+
+Instead, you can more concisely write:
+
+if grep foo /etc/passwd >/dev/null 2>&1; then
+  echo found
+fi
+
+Or, when appropriate:
+grep foo /etc/passwd >/dev/null 2>&1 && echo found
+
+DO NOT attempt to make pseudo-booleans by setting variables to "true"
+and "false" and then running the variable as a command instead of using a
+comparison test. This is non-idiomatic and confusing to many long-time
+shell programmers.
+
+Use:
+
+good=true
+if [[ $good = "true" ]] ; then
+
+Not:
+
+good=false
+if $good ; then
+
+Variable References
+-------------------
+
+Variable references begin with $ and *may* have their name enclosed in {}'s.
+We prefer to only see the {}'s when required.
+Do not spuriously enclose all your variable names in braces, like this:
+foo=${bar}
+
+This is kind of like writing all your C variable assignments like this:
+foo = (bar);
+
+It compiles, but it looks stupid.
+
+Braces are required around variable names in two specific cases:
+
+(1) when you are forming the string concatenation of your variable with
+another string:
+
+[ $install = yes ] && root="/a/" || root="/"
+hosts=${root}etc/inet/hosts
+
+and (2) when you are using one of the various substitution/assignment operators:
+
+echo ${BASEDIR:-/a}
+
+Variable Naming
+---------------
+
+We prefer that you adopt a shell variable naming scheme where capitalization
+provides additional meaning (as in our C style): use CAPITAL letters for
+variables that are exported into the environment, or are equivalent to C
+constants or #defines. Use lowercase letters for other variable names:
+BASEDIR=/a; export BASEDIR
+argc=$#
+
+This helps your reader immediately understand the implication of modifying a
+given variable (i.e. whether it will be inherited by child processes).
+
+Quoting
+-------
+
+Quick review of the quoting basics:
+
+Single quotes ('') mean quote but do not expand variable or backquote
+substitutions.  
+Double quotes ("") mean quote but allow expansion.  
+Backquotes (``) mean execute the command and substitute its standard output
+(note: stderr is unchanged and may "leak" through unless properly redirected)
+
+Use whatever quotes are appropriate for your situation, but please do not
+unnecessarily quote everything (also see 7 above).
+
+For example, references to variables controlled by your script do not have to
+be quoted unless you are expecting your variable to expand to multiple tokens,
+or to the empty string.
+
+However, any variable which contains values from outside the script, such as
+user input or filenames, should be quoted to avoid errors from special
+characters, including whitespace
+
+Testing for (Non-)Empty Strings
+-------------------------------
+
+DO NOT test for (non-)/empty strings by comparing to "" or ''. ALWAYS use the
+test operators -n (non-zero-length string) and -z (zero-length string):
+
+if [ -z "$foo" ]; then
+   echo 'you forgot to set $foo'
+fi
+
+if [ -n "$BASEDIR" ]; then
+   echo "\$BASEDIR is set to $BASEDIR"
+fi
+
+Commenting
+----------
+
+Shell comments are preceded by the '#' character. Place single-line comments
+in the right-hand margin. Use an extra '#' above and below the comment in the
+case of multi-line comments:
+cp foo bar # Copy foo to bar
+
+#
+# Modify the permissions on bar.  We need to set them to root/sys
+# in order to match the package prototype.
+#
+chown root bar
+chgrp sys bar
+
+Pathnames
+---------
+
+It is always a good idea to be careful about $PATH settings and pathnames when
+writing shell scripts. This allows them to function correctly even when the
+user invoking your script has some strange $PATH set in their environment.
+
+There are two acceptable ways to do this:
+
+(1) make *all* command references in your script use explicit pathnames:
+/usr/bin/chown root bar
+/usr/bin/chgrp sys bar
+
+or (2) explicitly reset $PATH in your script:
+PATH=/usr/bin; export PATH
+
+chown root bar
+chgrp sys bar
+
+DO NOT use a mixture of (1) and (2) in the same script.
+Pick one method and use it consistently.
+
+Command arguments
+-----------------
+
+When passing user input to commands, if the first operand of a command is a
+variable, use -- for any command that accepts this to flag the end of
+arguments to avoid problems if the variable expands to a value startingwith -. 
+
+Interpreter Magic
+-----------------
+
+The proper interpreter magic for a shell script should be simply #!/bin/sh.
+
+End of file
+-----------
+
+Following 2 lines should be placed at the end of file:
+
+# __EOF__
+<empty line>
diff --git a/doc/developer/CMakeLists.txt b/doc/developer/CMakeLists.txt
index 691e16c82e2c17018469853a29576e56238a4f06..d4cd5d356bea6d223fb13811f1965042579a862d 100644
--- a/doc/developer/CMakeLists.txt
+++ b/doc/developer/CMakeLists.txt
@@ -5,4 +5,7 @@ add_custom_target(dev-html ALL
         ${CMAKE_SOURCE_DIR}/doc/user/tnt-html.xsl
         developer.xml)
 
+add_custom_target(dev-check ALL
+	COMMAND jing http://docbook.org/xml/5.0/rng/docbookxi.rng developer.xml
+    )
 
diff --git a/doc/developer/developer.xml b/doc/developer/developer.xml
index 2b54754e3f2a9c49c69124e22f27001fec3b03f2..b6eea619c39e799225f888b190e3d6e75d8f320c 100644
--- a/doc/developer/developer.xml
+++ b/doc/developer/developer.xml
@@ -7,10 +7,24 @@
       xmlns:xlink="http://www.w3.org/1999/xlink"
       xmlns:xi="http://www.w3.org/2001/XInclude" version="5.0">
 <title>Tarantool/Box Developer Guide</title>
+<preface>
+  <title>What documentation there is</title>
+  <para>
+    Tarantool documentation consists of:
+    <itemizedlist>
+    <listitem><para>a user guide</para></listitem>
+    <listitem><para>a developer guide (you're reading it)</para></listitem>
+    <listitem><para>protocol description box-protocol.txt</para></listitem>
+    <listitem><para>coding style guides for Lua, Objective C, C, Python (for
+    other connectors, we use conventions of the connector programming
+    language community)</para></listitem>
+  </itemizedlist>
+  </para>
+</preface>
 <chapter>
 <title>Compiling</title>
 <section>
-<title>How to fix the compile time error about missing confetti</title>
+<title>How to fix a compile time error about missing confetti</title>
 <para>
   An error about missing confetti:
 <programlisting>
@@ -34,58 +48,81 @@ The other alternative, if you have actually modified
   
   </para>
 </section>
-</chapter>
-
-To build documentation, you'll need:
-
-xsltproc
-docbook5-xml
-docbook-xsl-ns
-libsaxon-java- for saxon processing
-libxml-commons-resolver1.1-java
-libxerces2-java
-libxslthl-java
-<!--
-     TOC:
-
-     Introduction
-       What it can basically do; 
-
-
-     Installation and Running
-       Command line options reference
- 
-     Data console and administrative console
-       Data operations
-       Administrative console operations
-         Format of response
 
-     Setting up replication
-       Master-slave replication
-
-     Support of memcached protocol
-
-     Configuration reference
-
-     Log file messages
-
-     Connectors
-       C
-       Ruby 
-         Example
-       Perl
+<section>
+<title>How to build the XML manual</title>
+<para>
+To build XML manual, you'll need:
+<itemizedlist>
 
-     Frequently Asked Questions
-       Q. What's on your roadmap?
-       Q. I found a bug. What to do?
+<listitem><para>xsltproc</para></listitem>
+<listitem><para>docbook5-xml</para></listitem>
+<listitem><para>docbook-xsl-ns</para></listitem>
+<listitem><para>libsaxon-java- for saxon processing</para></listitem>
+<listitem><para>libxml-commons-resolver1.1-java</para></listitem>
+<listitem><para>libxerces2-java</para></listitem>
+<listitem><para>libxslthl-java</para></listitem>
+</itemizedlist>
+Once all pre-requisites are met, <computeroutput>make html</computeroutput>
+to build the user guide.
+</para>
+</section>
+</chapter>
+<chapter>
+<title>Release management</title>
+  <section><title>How to make a minor release</title>
+  <para>
+  <programlisting>git tag -a 1.4.4 -m "Next minor in 1.4 series"
+vim CMakeLists.txt # edit CPACK_PACKAGE_VERSION_PATCH
+git push --tags
+</programlisting>
+Update the Web site in doc/www-data.
+</para>
+<para>
+Go to launchpad and move all "Fix committed" bugs to "Fix released".
+</para>
+<para>
+Update all blueprints, upload the ChangeLog, based on <prompt>git log</prompt>output.
+The ChangeLog must only include items which are mentioned as bugs
+or blueprints on Launchpad. If anything significant is there,
+which is not mentioned, something went wrong in release planning
+and the release should be held up until this is cleared.
+  </para>
+<para>
+Click 'Release milestone'. Create a milestone for the next minor 
+release. Alert the driver to target bugs and blueprints 
+to the new milestone.
+</para>
+<para>
+Upload the milestone from http://tarantool.org/dist to Launchpad. Use the .src.tar.gz build.
+</para>
+<para>
+</para>
+  </section>
+</chapter>
+<chapter>
+<title>Developer guidelines</title>
+<section>
+<title>How to work on a bug</title>
+<para>Any defect, even minor, if it changes the user-visible
+server behavior, needs a bug report. Report a bug at
+bugs.launchpad.net/tarantool.
 
+When reporting a bug, try to come up with a test case right away. 
+Set the current maintenance milestone for the bug fix, and specify the series.
+Assign the bug to yourself. Put the status to 'In progress'
+Once the patch is ready, put the bug the bug to 'In review' and solicit a review for the fix.
 
+Once there is a positive code review, push the patch 
+and set the status to 'Fix committed'
 
-     Appendixes:
-       Configuration parameters index
-       Server error codes
-       Glossary of terms
--->
+Patches for bugs should contain a reference to the respective
+Launchpad bug page or at least bug id. Each patch should have a
+test, unless coming up with one is difficult in the current
+framework, in which case QA should be alerted.
+</para>
+</section>
+</chapter>
 </book>
 
 <!--
diff --git a/doc/sql.txt b/doc/sql.txt
index 9cbbd9a7de20e3aab1203d77a8356fb59a1169f8..ccf121755c3c5f9a74fb2ef4de5e119dca0b6317 100644
--- a/doc/sql.txt
+++ b/doc/sql.txt
@@ -9,10 +9,12 @@
 ; case-insensitive, so this convention is present only to imporve
 ; legibility of the BNF.
 
-<sql> ::= <insert> | <update> | <delete> | <select>
+<sql> ::= <insert> | <replace> | <update> | <delete> | <select>
 
 <insert> ::= INSERT [INTO] <ident> VALUES <value_list>
 
+<insert> ::= REPLACE [INTO] <ident> VALUES <value_list>
+
 <update> ::= UPDATE <ident> SET <update_list> <simple_where>
 
 <delete> ::= DELETE FROM <ident> <simple_where>
diff --git a/doc/user/CMakeLists.txt b/doc/user/CMakeLists.txt
index 5421db1da1d7f97e0f7c52cb9c77299d452d92cf..1750c45970e16b55eefaa03388d8764543f1cc79 100644
--- a/doc/user/CMakeLists.txt
+++ b/doc/user/CMakeLists.txt
@@ -21,7 +21,7 @@ add_custom_target(relink
         user.xml)
 
 add_custom_target(html-saxon
-    COMMAND java -cp "/usr/share/java/saxon.jar:/usr/share/java/xml-resolver.jar:/usr/share/java/docbook-xsl-saxon.jar:/usr/share/java/xercesImpl.jar:/etc/xml/resolver:/usr/share/java/xslthl.jar"
+    COMMAND java -cp "/usr/share/java/saxon.jar:/usr/share/java/xml-resolver.jar:/usr/share/java/docbook-xsl-saxon.jar:/usr/share/java/xercesImpl.jar:/etc/xml/resolver:/usr/share/java/xslthl.jar:/usr/share/java/xml-commons-resolver-1.1.jar"
         -Djavax.xml.parsers.DocumentBuilderFactory=org.apache.xerces.jaxp.DocumentBuilderFactoryImpl
         -Djavax.xml.parsers.SAXParserFactory=org.apache.xerces.jaxp.SAXParserFactoryImpl
         -Dorg.apache.xerces.xni.parser.XMLParserConfiguration=org.apache.xerces.parsers.XIncludeParserConfiguration
diff --git a/doc/user/configuration-reference.xml b/doc/user/configuration-reference.xml
index 843d9bb6d3ac4fb960551015eb185d7305aea958..311ab5cf7be3ccf9d19d79560226862762deff09 100644
--- a/doc/user/configuration-reference.xml
+++ b/doc/user/configuration-reference.xml
@@ -501,7 +501,7 @@ tarantool_box: primary@sessions pri:33013 sec:33014 adm:33015</programlisting>
         </row>
 
         <row>
-          <entry>rows_per_wal</entry>
+          <entry xml:id="rows_per_wal" xreflabel="rows_per_wal">rows_per_wal</entry>
           <entry>integer</entry>
           <entry>500000</entry>
           <entry>no</entry>
@@ -530,7 +530,7 @@ tarantool_box: primary@sessions pri:33013 sec:33014 adm:33015</programlisting>
         </row>
 
         <row>
-          <entry>wal_writer_inbox_size</entry>
+          <entry xml:id="wal_writer_inbox_size" xreflabel="wal_writer_inbox_size">wal_writer_inbox_size</entry>
           <entry>integer</entry>
           <entry>128</entry>
           <entry>no</entry>
diff --git a/doc/user/connectors.xml b/doc/user/connectors.xml
index b97c6eaec6a949785bb200ca86033474292d6041..fb56717dc01f20aedb4c0862faa2105ee42abcfc 100644
--- a/doc/user/connectors.xml
+++ b/doc/user/connectors.xml
@@ -12,7 +12,7 @@
   C, Perl, Ruby, PHP and Python.
 </para></blockquote>
 
-<para>All connectors are located in srcdir/connector directory. Apart from the native Tarantool client driver, you can always use a <emphasis role="strong">Memcached</emphasis> driver of your choice, after enabling Memcached protocol in the configuration file.</para>
+<para>Apart from the native Tarantool client driver, you can always use a <emphasis role="strong">Memcached</emphasis> driver of your choice, after enabling Memcached protocol in the configuration file.</para>
 
   <section>
     <title>C</title>
@@ -25,129 +25,23 @@
   <section>
     <title>Perl</title>
     <para>
-       The perl client is located in <link
-       xlink:href="https://github.com/mailru/tarantool/blob/master/connector/perl/lib/"><filename>connector/perl/lib/</filename></link>.
-    <orderedlist>
-
-    <listitem>
-      <simpara><emphasis role="strong">new</emphasis></simpara>
-<programlisting language="perl"><![CDATA[
-     my $box = MR::SilverBox->new({
-            spaces => [ {
-                indexes => [ {
-                    index_name   => 'primary_id',
-                    keys         => [0],
-                }, {
-                    index_name   => 'primary_email',
-                    keys         => [1],
-                }, ],
-                space     => 0,
-                format        => 'l& SSLL',
-                default_index => 'primary_id',
-            } ],
-        },
-        servers      => $server})
-]]></programlisting>
-    </listitem>
-
-    <listitem>
-      <simpara><emphasis role="strong">Insert</emphasis></simpara>
-<programlisting language="perl">
-    $box->Insert(@tuple);
-</programlisting>
-    </listitem>
-
-    <listitem>
-      <simpara><emphasis role="strong">Select</emphasis></simpara>
-<programlisting language="perl"><![CDATA[
-    [\%tuple1, \%tuple2, ...] = $box->Select(@id);
-    \%tuple = $box->Select($id);
-    [\@tuple1, \@tuple2, ...] = $box->Select(@id, {raw => 1});
-    \@tuple = $box->Select($id, {raw => 1});
-    $box->Select($email, {use_index => 1});
-]]></programlisting>
-    </listitem>
-
-    <listitem>
-      <simpara><emphasis role="strong">Update</emphasis>
-      accepts parameters in the last argument just like Select:</simpara>
-<programlisting language="perl"><![CDATA[
-    $key = 1; # key, ID of user by default
-    $field_num = 2; # posititon in tuple, starts from 0, must be >= 1
-    $value = pack('L', 123);  # integer values must be packed
-    $box->Update($key, $field_num, $value);
-]]></programlisting>
-    </listitem>
-
-    <listitem>
-      <simpara><emphasis role="strong">Delete</emphasis></simpara>
-<programlisting language="perl"><![CDATA[
-    $box->Delete($key);
-]]></programlisting>
-    </listitem>
-
-    <listitem>
-      <simpara><emphasis role="strong">AndXorAdd</emphasis>
-      transforms the tuple field at position <quote>$field_num</quote>
-      according to formula <quote>field= ((field &amp; $and_mask) ^ $xor_mask) + $add_value</quote>.
-      </simpara>
-<programlisting language="perl"><![CDATA[
-    $box->AndXorAdd($key, $field_num, $and_mask, $xor_mask, $add_value);
-]]></programlisting>
-    </listitem>
-
-    <listitem>
-      <simpara><emphasis role="strong">Bit</emphasis>
-      performs a bitwise operation on field at position
-      <quote>$field_num</quote>. Unused arguments can be omitted.
-      Note: <quote>set</quote> has a higher precedence than
-      <quote>bit_set</quote> and <quote>bit_clear</quote>.
-      </simpara>
-<programlisting language="perl"><![CDATA[
-     $box->Bit($key, $field_num, bit_clear => $clear_mask, bit_set => $set_mask, set => $set_value);
-     $box->Bit($key, $field_num, bit_set => $set_mask);
-]]></programlisting>
-    </listitem>
-
-    <listitem>
-      <simpara><emphasis role="strong">Num</emphasis>
-      performs a numeric update operation on field at position
-      <quote>$field_num</quote>. Unused arguments can be omitted.
-      Note: again, <quote>set</quote> has a higher precedence than
-      <quote>num_add</quote> and <quote>num_sub</quote>.
-      </simpara>
-<programlisting language="perl"><![CDATA[
-     $box->Num($key, $field_num, set => $set_value, num_add => $add_value, num_sub => $sub_value);
-]]></programlisting>
-    </listitem>
-
-    <listitem>
-      <simpara><emphasis role="strong">Flag</emphasis>
-      sets or clears flags on a tuple, the calling convention 
-      is the same as for <quote>Bit</quote> and <quote>Num</quote>
-      operations.
-      </simpara>
-<programlisting language="perl"><![CDATA[
-     $box->Flags(bit_set => $set_mask, bit_clear => $clear_mask);
-]]></programlisting>
-    </listitem>
-
-    </orderedlist>
-
+       Please refer to CPAN module <link xlink:href='http://search.cpan.org/search?query=Tarantool&amp;mode=all'>MR::Tarantool::Box</link>.
     </para>
   </section>
 
   <section>
     <title>PHP</title>
     <para>
-       @tba
+       Please see <link
+       xlink:href="https://github.com/mailru/tarantool/blob/master/connector/php"><filename>connector/php</filename></link> in the source tree.
     </para>
   </section>
 
   <section>
     <title>Python</title>
     <para>
-       @tba
+       Please see <link
+       xlink:href="https://github.com/mailru/tarantool-python"><filename>http://github.com/mailru/tarantool-python</filename></link>.
     </para>
   </section>
 
@@ -156,105 +50,8 @@
     <para>
        You need <emphasis role="strong">Ruby 1.9</emphasis> or later
        to use this connector. Connector sources are located in <link
-       xlink:href="https://github.com/mailru/tarantool/blob/master/connector/ruby/box.rb"><filename>connector/ruby/box.rb</filename></link>.
+       xlink:href="https://github.com/mailru/tarantool-ruby"><filename>http://github.com/mailru/tarantool-ruby</filename></link>.
     </para>
-    <para>
-       Assume, for the sake of example, that Tarantool has the
-       following space configuration:
-<programlisting language="c">
-primary_port = 33013 
-admin_port = 33015 
-log_level = 3
-slab_alloc_arena = 0.1
-
-space[0].enabled = 1
-space[0].index[0].type = "NUM"
-space[0].index[0].key_fields[0].fieldno = 0
-
-space[0].index[1].type = "STR"
-space[0].index[1].key_fields[0].fieldno = 1
-</programlisting>
-        The only defined space will be used to store user
-        account information, such as id, name, email, and other
-        properties. User ID is used for the primary key, but
-        it's also possible to find a user by name.
-   </para>
-   <para>In Ruby, a helper class is defined to present
-     Tarantool to the rest of the application as a typical object
-     container. 
-<example>
-<title>userbox.rb</title>
-<programlisting language="ruby"><![CDATA[require 'box'
-
-class UserBox < Box
-  def initialize(host)
-    super(host, :space => 0)
-  end
-
-  def insert(user)
-    case user
-    when Hash then super [user[:uid], user[:email], user[:name], user[:apple_count]]
-    when Array then super user
-    else fail "don't know what to do with #{user.class}"
-    end
-  end
-
-  def update_fields(key, *ops)
-    mapping = {:uid => 0, :email => 1, :name => 2, :apple_count => 3}
-    ops.map do |op|
-      op[0] = mapping[op[0]] if op.is_a? Array
-    end
-
-    super key, *ops
-  end
-
-  def unpack_tuple!(data)
-    tuple = super data
-    { :uid => tuple[0].unpack(?L)[0],
-      :email => tuple[1],
-      :name => tuple[2],
-      :apple_count => tuple[3].unpack(?L)[0]
-    }
-  end
-end]]></programlisting></example>
-     Here's how this helper class can be used:
-<programlisting><prompt>kostja@shmita:~$ </prompt><command>irb</command>
-<![CDATA[>> # Connect to the server
->> require 'userbox'
-=> true
->> b = UserBox.new 'localhost:33013'
-=> #<UserBox:0x870fd48 @space=1, @end_point=["localhost", 33013], @sock=#<TCPSocket:0x870f85c>
->> # Insert a few users
->> b.insert :uid => 1, :email => 'pupkin@mail.ru', :name => 'Vasya', :apple_count => 1
-=> 1
->> b.insert :uid => 2, :email => 'masha@mail.ru', :name => 'Masha', :apple_count => 0
-=> 1
->> b.insert :uid => 3, :email => 'petya@mail.ru', :name => 'Petya', :apple_count => 3
-=> 1
->> # Perform selects
->> b.select 1
-=> [{:uid=>1, :email=>"pupkin@mail.ru", :name=>"Vasya", :apple_count=>1}]
->> b.select 1,2,3
-=> [{:uid=>1, :email=>"pupkin@mail.ru", :name=>"Vasya", :apple_count=>1}, {:uid=>2, :email=>"masha@mail.ru", :name=>"Masha", :apple_count=>0}, {:uid=>3, :email=>"petya@mail.ru", :name=>"Petya", :apple_count=>3}]
->> # It's possible to retrieve records by email using second index
->> b.select 'pupkin@mail.ru', 'petya@mail.ru', :index => 1
-=> [{:uid=>1, :email=>"pupkin@mail.ru", :name=>"Vasya", :apple_count=>1}, {:uid=>3, :email=>"petya@mail.ru", :name=>"Petya", :apple_count=>3}]
-Delete
->> b.delete 2
-=> 1
->> # Update values
->> b.update_fields 1, [:apple_count, :add, 2 ]
-=> 1
->> b.select 1
-=> [{:uid=>1, :email=>"pupkin@mail.ru", :name=>"Vasya", :apple_count=>3}]
->> # It's possible to do several updates in a single atomic command
->> b.update_fields 3, [:apple_count, :add, 10], [:name, :set, "foobar"]
-=> 1
->> b.select 3
-=> [{:uid=>3, :email=>"petya@mail.ru", :name=>"foobar", :apple_count=>13}]
-]]>
-</programlisting>
-   </para>
   </section>
 
 </chapter>
diff --git a/doc/user/data-and-persistence.xml b/doc/user/data-and-persistence.xml
new file mode 100644
index 0000000000000000000000000000000000000000..b2a61856ad8d3ce9ff50d5d99a1732bedfa84816
--- /dev/null
+++ b/doc/user/data-and-persistence.xml
@@ -0,0 +1,22 @@
+<!DOCTYPE chapter [
+<!ENTITY % tnt SYSTEM "../tnt.ent">
+%tnt;
+]>
+<chapter xmlns="http://docbook.org/ns/docbook" version="5.0"
+         xmlns:xlink="http://www.w3.org/1999/xlink"
+         xmlns:xi="http://www.w3.org/2001/XInclude"
+         xml:id="data-and-persistence">
+<title>Data model and data persitence</title>
+<blockquote><para>
+  This chapter describes how Tarantool stores 
+  values and what operations with data it supports.
+</para></blockquote>
+
+<xi:include href="data-model.xml"/>
+<xi:include href="persistence-architecture.xml"/>
+
+</chapter>
+<!--
+vim: tw=66 syntax=docbk
+vim: spell spelllang=en_us
+-->
diff --git a/doc/user/data-model.xml b/doc/user/data-model.xml
index 17268baff21ee31b1d8e7059d0f412621e389981..d8d193e5e95bde5677451fb343d0624a45903398 100644
--- a/doc/user/data-model.xml
+++ b/doc/user/data-model.xml
@@ -1,15 +1,10 @@
-<!DOCTYPE chapter [
+<!DOCTYPE section [
 <!ENTITY % tnt SYSTEM "../tnt.ent">
 %tnt;
 ]>
-<chapter xmlns="http://docbook.org/ns/docbook" version="5.0"
+<section xmlns="http://docbook.org/ns/docbook" version="5.0"
          xmlns:xlink="http://www.w3.org/1999/xlink">
 <title>Dynamic data model</title>
-<blockquote><para>
-  This chapter describes how Tarantool stores 
-  values and what operations with data it supports.
-</para></blockquote>
-
 <para>
   Tarantool data is organized in <emphasis>tuples</emphasis>. Tuple
   length is varying: a tuple can contain any number
@@ -62,8 +57,8 @@
   [1]
   localhost> insert into t0 values ('hello')
   An error occurred: ER_ILLEGAL_PARAMS, 'Illegal parameters'
-  localhost> insert into t0 values (1, 'hello')
-  Insert OK, 1 row affected
+  localhost> replace into t0 values (1, 'hello')
+  Replace OK, 1 row affected
   localhost> select * from t0 where k0=1 
   Found 1 tuple:
   [1, 'hello']
@@ -93,9 +88,9 @@
        is, therefore, mandatory.
     </para></listitem>
     <listitem><para>
-       INSERT replaces data when a tuple with given
-       primary key already exists. Such replace can insert
-       a tuple of different cardinality.
+       REPLACE replaces data when a
+       tuple with given primary key already exists. Such replace
+       can insert a tuple of different cardinality.
     </para></listitem>
   </itemizedlist>
 </para>
@@ -115,7 +110,7 @@
   xlink:href="https://github.com/mailru/tarantool/blob/master/doc/box-protocol.txt" xlink:title="A complete BNF of Tarantool client/server protocol">doc/protocol.txt</filename>.
 </para>
 
-</chapter>
+</section>
 <!--
 vim: tw=66 syntax=docbk
 vim: spell spelllang=en_us
diff --git a/doc/user/language-reference.xml b/doc/user/language-reference.xml
index 519fa3346164cb677161882b7db163718fc47730..8a8bcb7b8a18fdb62ee09abe69790ae3983d9194 100644
--- a/doc/user/language-reference.xml
+++ b/doc/user/language-reference.xml
@@ -164,8 +164,26 @@
         updates, there are going to be page splits, and therefore
         you need to have some extra free memory to run this
         command. 15%-30% of <olink targetptr="slab_alloc_arena"/>
-        is, on average, sufficient.
-      </para></listitem>
+        is, on average, sufficient. This statement waits until a
+        snapshot is taken and returns operation result. For
+        example:
+<programlisting>localhost> show info
+---
+info:
+  version: "1.4.4"
+  lsn: 843301
+...
+localhost> save snapshot
+---
+ok
+...
+localhost> save snapshot
+---
+fail: can't save snapshot, errno 17 (File exists)
+...
+</programlisting>
+      </para>
+      </listitem>
     </varlistentry>
 
 
@@ -202,7 +220,7 @@
 localhost> show info
 ---
 info:
-  version: "1.4.0-30-ge500b95"
+  version: "1.4.4"
   uptime: 441524
   pid: 16180
   wal_writer_pid: 16182
@@ -224,11 +242,12 @@ info:
       <para>
         <emphasis role="strong">recovery_last_update</emphasis> is
         the wall clock time of the last change recorded in the
-        write ahead log.
+        write ahead log. To convert it to human-readable time,
+        you can use <command>date -d@<replaceable>1306964594.980</replaceable></command>.
       </para>
       <para>
         <emphasis role="strong">status</emphasis> is
-        either "primary" or "hot_standby/&lt;hostname&gt;".
+        either "primary" or "replica/&lt;hostname&gt;".
       </para>
 
       </listitem>
diff --git a/doc/user/persistence-architecture.xml b/doc/user/persistence-architecture.xml
new file mode 100644
index 0000000000000000000000000000000000000000..2147c536581e3efa0937e4c862bc6f48bd524e38
--- /dev/null
+++ b/doc/user/persistence-architecture.xml
@@ -0,0 +1,85 @@
+<!DOCTYPE section [
+<!ENTITY % tnt SYSTEM "../tnt.ent">
+%tnt;
+]>
+<section xmlns="http://docbook.org/ns/docbook" version="5.0"
+         xmlns:xlink="http://www.w3.org/1999/xlink">
+<title>Data persistence</title>
+<para>
+To maintain data persistence, Tarantool writes each data change
+request (INSERT, UPDATE, DELETE) into a write ahead log. WAL
+files have extension <filename>.xlog</filename> and are stored in <olink
+targetptr="wal_dir"/>. A new WAL file is created for every <olink
+targetptr="rows_per_wal"/> records.  Each INSERT, UPDATE or DELETE
+gets assigned a continuously growing 64-bit log sequence number. The name of the log file is based on the log sequence
+number of the first record this file contains.
+</para>
+
+<para>Apart from a log sequence number and the data change request
+(its format is the same as in the binary protocol and is described
+in <link
+xlink:href="https://github.com/mailru/tarantool/blob/master/doc/box-protocol.txt"><filename>doc/box-protocol.txt</filename></link>),
+each WAL record contains a checksum and a UNIX time stamp.
+</para>
+
+<para>
+Tarantool proceeds requests atomically: a change is either
+accepted and recorded in the WAL, or is rejected wholesale.
+Let's clarify how this happens, using REPLACE command as an
+example:
+<orderedlist>
+    <listitem><para>
+    The server tries to locate an old tuple identified by
+    the same primary key. If found, the tuple is remembered for
+    later.
+    </para></listitem>
+    <listitem><para>
+    The new tuple is <emphasis>validated</emphasis>. If it
+    violates any of the unique key constraints, misses
+    any of indexed fields, or an indexed field type does not
+    match index type, the change is aborted.
+    </para></listitem>
+    <listitem><para>
+    The new tuple is marked 'invisible' and is added to 
+    the primary and secondary indexes.
+    </para></listitem>
+    <listitem><para>
+    A message is sent to a separate <quote>wal_writer</quote>
+    process requesting that the change is recorded in the WAL.
+    The fiber associate with the current connection is scheduled
+    off CPU until an acknowledgment is received from the WAL
+    writer.
+    </para></listitem>
+    <listitem><para>
+    Upon success, 'invisible' flag is cleared
+    and the old tuple is deleted. A response is sent to the
+    client. Upon failure, the new tuple is removed and <olink
+    targetptr="ER_WAL_IO"/> error is sent to the client.
+    </para></listitem>
+</orderedlist>
+</para>
+
+<para>
+Communication between master and WAL writer processes is asynchronous.
+It is implemented using 'inbox' paradigm, similar to 
+process inboxes in Erlang, from which it was derived.
+Asynchronous but reliable message passing between processes
+allows Tarantool to continue handling requests regardless of disk
+throughput. SELECT performance, provided SELECTs are run in their
+own connections, is unaffected by disk load.
+</para>
+
+<para>
+The size of each process' inbox is fixed. In particular,
+the WAL writer inbox can hold only <olink targetptr="wal_writer_inbox_size"/>
+messages. This can pose a practical problem when thousands of
+connections perform updates &mdash; the WAL writer inbox can become full.
+Once this happens, the server aborts any update for which
+sending a WAL message has failed with <olink targetptr="ER_WAL_IO"/>
+error.
+</para>
+</section>
+<!--
+vim: tw=66 syntax=docbk
+vim: spell spelllang=en_us
+-->
diff --git a/doc/user/preface.xml b/doc/user/preface.xml
index b0d37a00c86ffe8d4b5eaf58f18213ffb7e30fff..29b9f3f26102dd380f63add747911fa2bddc0e7e 100644
--- a/doc/user/preface.xml
+++ b/doc/user/preface.xml
@@ -67,6 +67,14 @@
     under 10%.
   </para>
 
+  <para>
+    The key feature of Tarantool is <emphasis role="strong">support
+    for stored procedures</emphasis>, which
+    can access and modify data atomically. With stored procedures,
+    it's possible to turn Tarantool into a highly
+    flexible and extremely fast social web application server.
+  </para>
+
   <para>
     <emphasis role="strong">The software is production-ready</emphasis>.
     Tarantool has been developed and is actively used at
@@ -96,7 +104,7 @@
     This manual is written in <citetitle
     xlink:href="http://www.docbook.org/tdg5/en/html/docbook.html">DocBook
     5</citetitle> XML markup language and is using the standard <citetitle
-    xlink:href="http://docbook.sourceforge.net/release/xsl/current/doc/">DocbBook
+    xlink:href="http://docbook.sourceforge.net/release/xsl/current/doc/">DocBook
     XSL</citetitle> formatting conventions:</para>
   <para>
     UNIX shell command input is prefixed with '$ ' and is 
diff --git a/doc/user/stored-programs.xml b/doc/user/stored-programs.xml
index 6f1710f500c90212148ed0f01e13e760eb089698..d6a10205d034a75d9d68945014485fa5f18a77d7 100644
--- a/doc/user/stored-programs.xml
+++ b/doc/user/stored-programs.xml
@@ -9,7 +9,8 @@
   <title>Writing stored procedures in Lua</title>
 <blockquote>
     <para>
-        Lua is a light-weight, multi-paradigm embeddable language.
+        <link xlink:href="http://www.lua.org">Lua</link>
+        is a light-weight, multi-paradigm, embeddable language.
         Stored procedures in Lua can be used to implement
         data manipulation patterns or data structures. A
         server-side procedure written in Lua can select and modify
@@ -18,13 +19,10 @@
         alter and drop Lua procedures.
     </para>
 </blockquote>
-  
 <para>
     Procedures can be invoked both from the administrative
     console and using the binary protocol, for example:
-<programlisting>
-<computeroutput>
-localhost> lua function f1() return 'hello' end
+<programlisting><computeroutput>localhost> lua function f1() return 'hello' end
 ---
 ...
 localhost> call f1()
@@ -32,9 +30,9 @@ Found 1 tuple:
 ['hello']
 </computeroutput>
 </programlisting>
-    In the language of the administrative console 
+    In the language of the administrative console
     <olink targetptr="lua-command" /> evaluates an arbitrary
-    Lua chunk. "CALL" is the SQL standard statement used
+    Lua chunk. CALL is the SQL standard statement used
     to represent CALL command of the binary
     protocol.
     In the example above, a Lua procedure is first defined
@@ -45,9 +43,7 @@ Found 1 tuple:
     administrative console, the newly created <code
     language="Pascal">function f1()</code>
     can be called there too:
-<programlisting>
-<computeroutput>
-localhost> lua f1()
+<programlisting><computeroutput>localhost> lua f1()
 ---
  - hello
 ...
@@ -59,38 +55,110 @@ localhost> lua "hello".." world"
 ---
  - hello world
 ...
-</computeroutput>
-</programlisting>
+</computeroutput></programlisting>
+  </para>
+  <para>
+    There is a single global instance of Lua interpreter, which is
+    shared across all connections. Anything prefixed with
+    <code>lua </code> on the administrative console is sent
+    directly to this interpreter. Any change of the interpreter
+    state is immediately available to all client connections.
+  </para>
+  <para>
+    Each connection, however, is using its own Lua
+    <emphasis>coroutine</emphasis> &mdash; a mechanism, akin to
+    Tarantool <emphasis>fibers</emphasis>. A coroutine has an
+    own execution stack and a Lua <emphasis>closure</emphasis>
+    &mdash; set of local variables and definitions.
   </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 <emphasis>thread</emphasis> &mdash; a mechanism, akin to
-    Tarantool <emphasis>fibers</emphasis>.
-    Anything prefixed with <code>lua </code> 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 are chosen as the transport media 
-    between the server and the interpreter.
+    In the binary protocol, it's only possible to <emphasis
+    role="strong">invoke</emphasis> existing
+    procedures, but not <emphasis role="strong">define</emphasis>
+    or <emphasis role="strong">alter</emphasis> them.
+    CALL request packet contains CALL command code (22), the name
+    of a procedure to be called, and a tuple for procedure
+    arguments. Currently, Tarantool tuples are type-agnostic,
+    thus each field of the tuple is passed into the procedure
+    as an argument of type <quote>string</quote>. For example:
+<programlisting><computeroutput>kostja@atlas:~$ cat arg.lua
+function f1(a)
+    local s = a
+    if type(a) == 'string' then
+        s = ''
+        for i=1, #a, 1 do
+            s = s..string.format('0x%x ', string.byte(a, i))
+        end
+    end
+    return type(a), s
+end
+kostja@atlas:~$ tarantool
+localhost> lua dofile('arg.lua')
+---
+...
+localhost> lua f1('1234')
+---
+ - string
+ - 0x31 0x32 0x33 0x34
+...
+localhost> call f1('1234')
+Call OK, 2 rows affected
+['string']
+['0x31 0x32 0x33 0x34 ']
+localhost> lua f1(1234)
+---
+ - number
+ - 1234
+...
+localhost> call f1(1234)
+Call OK, 2 rows affected
+['string']
+['0xd2 0x4 0x0 0x0 ']</computeroutput></programlisting>
+    In the above example, the way the procedure receives its
+    argument is identical in two protocols, when the argument is a
+    string. A numeric field, however, when submitted via the
+    binary protocol, is seen by the procedure as 
+    a 4-byte blob, not as a Lua <quote>number</quote> type.
+    </para>
+    <para>In addition to conventional method invocation,
+    Lua provides object-oriented syntax. Access to the latter is
+    available on the administrative console only:
+<programlisting><computeroutput>localhost> lua box.space[0]:truncate()
+---
+...
+localhost> call box.space[0]:truncate()
+error: 1:15 expected '('
+</computeroutput></programlisting>
   </para>
   <para>
     Every value, returned from a stored function by means of
-    <code>return</code> clause, is converted to Tarantool/Box tuple
-    and sent back to the client in binary form.
+    <code>return</code> clause, is converted to a Tarantool/Box tuple.
+    Tuples are returned as such, in binary form; a Lua scalar, such as
+    a string or an integer, is converted to a tuple with only
+    one field. When the returned value is a <emphasis>Lua
+    table</emphasis>, the resulting tuple contains only table
+    values, but not keys.
   </para>
   <para>
-    When a function in Lua terminates with an error, it is 
-    returned to the client as <olink targetptr="ER_PROC_LUA" />
+    When a function in Lua terminates with an error, the error
+    is sent to the client as <olink targetptr="ER_PROC_LUA" />
     return code, with the original error message preserved.
     Similarly, an error occurred inside Tarantool (observed on the
     client as an error code), when happens during execution of a
-    Lua procedure, produces a genuine Lua exception.
+    Lua procedure, produces a genuine Lua error:
+<programlisting><computeroutput>localhost> lua function f1() error("oops") end
+---
+...
+localhost> call f1()
+Call ERROR, Lua error: [string "function f1() error("oops") end"]:1: oops (ER_PROC_LUA)
+localhost> call box.insert('99', 1, 'test')
+Call ERROR, Space 99 is disabled (ER_SPACE_DISABLED)
+localhost> lua pcall(box.insert, 99, 1, 'test')
+---
+ - false
+ - Space 99 is disabled
+...
+</computeroutput></programlisting>
   </para>
   <para>
     It's possible not only to invoke trivial Lua code, but call
@@ -126,54 +194,57 @@ pack: function
         <listitem><para>
             libraries, such as <code>cfg, space, fiber, index, tuple</code>,
             to access server configuration, create, resume and
-            interrupt fibers, inspect content of spaces, indexes
+            interrupt fibers, inspect contents of spaces, indexes
             and tuples.
         </para></listitem>
     </itemizedlist>
   </para>
 
 <variablelist>
-    <title>Package <code>box</code> function index</title>
-
+    <title>Package <code xml:id="box" xreflabel="box">box</code> function index</title>
     <varlistentry>
         <term>
             <emphasis role="lua">box.process(op, request)</emphasis>
         </term>
         <listitem>
             <para>
-                The main extension provided to Lua by
-                Tarantool/Box &mdash; ability to call
-                INSERT/UPDATE/SELECT/DELETE from within a Lua
-                procedure.
+                Process a request passed in as a binary string.
+                This is an entry point into the server request
+                processor. It allows to insert, update,
+                select and delete tuples from within a Lua procedure.
             </para>
             <para>
                 This is a low-level API, and it expects
                 all arguments to be packed in accordance
-                with the binary protocol format (iproto
-                header excluded). Normally there is no need
+                with the binary protocol (iproto
+                header excluded). Normally, there is no need
                 to use <code>box.process()</code> directly:
-                <code>box.select(), box.update(), ...</code>
+                <code>box.select(), box.update()</code>
                 and other convenience wrappers
                 invoke <code>box.process()</code> with
                 correctly packed arguments.
                 <bridgehead renderas="sect4">Parameters</bridgehead>
                 <simplelist>
-                    <member><code>op</code> &mdash; number, Tarantool/Box command code, see
+                    <member><code>op</code> &mdash; number, any
+                    Tarantool/Box command code, except 22 (CALL). See
                     <link xlink:href="https://github.com/mailru/tarantool/blob/master/doc/box-protocol.txt">
-                    <filename>doc/box-protocol.txt</filename></link>,
+                    <filename>doc/box-protocol.txt</filename></link>.
                     </member>
-                    <member><code>request</code> &mdash; a request packed in binary format</member>
+                    <member><code>request</code> &mdash; command
+                    arguments packed in binary format.</member>
                 </simplelist>
                 <bridgehead renderas="sect4">Returns</bridgehead>
                 This function returns zero or more tuples. In Lua, a
-                tuple is represented by
+                tuple is represented by a
                 <emphasis>userdata</emphasis> object of type
-                <code>box.tuple</code>. If a Lua procedure
-                is called from the administrative console, tuples
-                are converted to YAML. When called from the binary
+                <code xlink:href="#box.tuple">box.tuple</code>. If
+                a Lua procedure is called from the administrative
+                console, returned tuples are printed out in YAML
+                format.  When called from the binary
                 protocol, the binary format is used.
                 <bridgehead renderas="sect4">Errors</bridgehead>
-                Any server error produced by the executed command.
+                Any server error produced by the executed
+                command.
             </para>
         </listitem>
     </varlistentry>
@@ -184,19 +255,23 @@ pack: function
         </term>
         <listitem>
             <para>
-                Select a tuple in the given namespace by key. A
+                Select a tuple in the given space. A
                 wrapper around <code>box.process()</code>.
                 <bridgehead renderas="sect4">Parameters</bridgehead>
                 <simplelist>
-                    <member><code>space_no</code> &mdash; namespace id,
+                    <member><code>space_no</code> &mdash; space id,
                     </member>
                     <member><code>index_no</code> &mdash; index number in the
-                        namespace,</member>
-                    <member><code>...</code> &dash; possibly compound key.
+                        space,</member>
+                    <member><code>...</code>&mdash; index key,
+                    possibly compound.
                     </member>
                 </simplelist>
                 <bridgehead renderas="sect4">Returns</bridgehead>
                 Returns zero or more tuples.
+                <bridgehead renderas="sect4">Errors</bridgehead>
+                Same as in <code>box.process()</code>. Any error
+                results in a Lua exception.
                 <bridgehead renderas="sect4">Example</bridgehead>
 <programlisting>
 localhost> call box.insert(0, 'test', 'my first tuple')
@@ -224,6 +299,7 @@ localhost> lua box.select(5, 1, 'firstname', 'lastname')
         </term>
         <listitem><simpara></simpara></listitem>
     </varlistentry>
+
     <varlistentry>
         <term>
             <emphasis role="lua">box.replace(space_no, ...)</emphasis>
@@ -235,7 +311,7 @@ localhost> lua box.select(5, 1, 'firstname', 'lastname')
                 the same primary key already exists,
                 <code>box.insert()</code> returns an error, while
                 <code>box.replace()</code> replaces the existing
-                tuple with the new one. These functions are
+                tuple with a new one. These functions are
                 wrappers around <code>box.process()</code>
                 <bridgehead renderas="sect4">Returns</bridgehead>
                 Returns the inserted tuple.
@@ -243,56 +319,217 @@ localhost> lua box.select(5, 1, 'firstname', 'lastname')
         </listitem>
     </varlistentry>
 
+    <varlistentry>
+        <term>
+            <emphasis role="lua">box.update(space_no, key, format, ...)</emphasis>
+        </term>
+        <listitem>
+            <para>
+                Update a tuple identified by a primary
+                <code>key</code>. Update arguments follow,
+                described by <code>format</code>.
+                The format and arguments are passed to
+                <code>box.pack()</code> and the result is sent
+                to <code>box.process()</code>.
+                A correct <code>format</code> is a sequence of
+                pairs: update operation, operation arguments. A
+                single character of format describes either an
+                operation which needs to take place or operation
+                argument. A format specifier also works as a
+                placeholder for the number of field, which needs
+                to be updated, or argument value.
+                For example: 
+                <simplelist>
+                    <member><code>+p=p</code> &mdash; add a value
+                    to one field and assign another,
+                    </member>
+                    <member><code>:p</code> &mdash; splice a
+                    field: start at offset, cut length bytes, and add a
+                    string.</member>
+                </simplelist>
+                Possible format specifiers are: <quote>+</quote>
+                for addition, <quote>-</quote> for subtraction,
+                <quote>&amp;</quote> for bitwise AND,
+                <quote>|</quote> for bitwise OR, <quote>^</quote>
+                for bitwise exclusive OR (XOR), <quote>:</quote>
+                for string splice and <quote>p</quote> for
+                operation argument.
+                <bridgehead renderas="sect4">Returns</bridgehead>
+                Returns the updated tuple.
+                <bridgehead renderas="sect4">Example</bridgehead>
+<programlisting>
+localhost> lua box.insert(0, 0, 'hello world')
+---
+ - 0: {'hello world'}
+...
+localhost> lua box.update(0, 0, '+p', 1, 1) -- add value 1 to field #1
+---
+error: 'Illegal parameters, numeric operation on a field with length != 4'
+...
+localhost> lua box.update(0, 0, '=p', 1, 1) -- assign field #1 to value 1
+---
+ - 0: {1}
+...
+localhost> lua box.update(0, 0, '+p', 1, 1)
+---
+ - 0: {2}
+...
+</programlisting>
+            </para>
+        </listitem>
+    </varlistentry>
+
     <varlistentry>
         <term>
             <emphasis role="lua">box.delete(space_no, key)</emphasis>
         </term>
         <listitem><para>
-            Delete a tuple, identified by a primary key.
+            Delete a tuple identified by a primary key.
             <bridgehead renderas="sect4">Returns</bridgehead>
             Returns the deleted tuple.
+                <bridgehead renderas="sect4">Example</bridgehead>
+<programlisting>
+localhost> call box.delete(0, 'test')
+Call OK, 1 rows affected
+['test', 'my first tuple']
+localhost> call box.delete(0, 'test')
+Call OK, 0 rows affected
+localhost> call box.delete(0, 'tes')
+Call ERROR, Illegal parameters, key is not u32 (ER_ILLEGAL_PARAMS)
+</programlisting>
         </para></listitem>
     </varlistentry>
     <varlistentry>
         <term>
-            <emphasis role="lua">box.select_range(space_no, index_no limit, ...)</emphasis>
+            <emphasis role="lua">box.select_range(space_no, index_no, limit, key, ...)</emphasis>
         </term>
         <listitem><para>
             Select a range of tuples, starting from offset
-            specified by the key.
+            specified by <code>key</code>. The key can be
+            multipart.
             Limit selection with at most <code>limit</code>
             tuples.
             If no key is specified, start from the first key in
             the index.
-            </para><para>
-            For TREE indexes, this returns tuples in sorted order, 
-            and can be used to iterate over the entire space.
-            For HASH indexes, this returns at most one tuple,
-            unless <code>key</code> is nil or unspecified, in which case it
-            returns all tuples.
+            </para>
+            <para>
+            For TREE indexes, this returns tuples in sorted order.
+            For HASH indexes, the order of tuples is unspecified, and
+            can change significantly if data is inserted or deleted
+            between two calls to <code>box.select_range()</code>.
+            If <code>key</code> is <code>nil</code> or unspecified,
+            the selection starts from start of the index.
+            <bridgehead renderas="sect4">Example</bridgehead>
+<programlisting>localhost> show configuration
+---
+...
+  space[4].cardinality: "-1"
+  space[4].estimated_rows: "0"
+  space[4].index[0].type: "HASH"
+  space[4].index[0].unique: "true"
+  space[4].index[0].key_field[0].fieldno: "0"
+  space[4].index[0].key_field[0].type: "STR"
+  space[4].index[1].type: "TREE"
+  space[4].index[1].unique: "false"
+  space[4].index[1].key_field[0].fieldno: "1"
+  space[4].index[1].key_field[0].type: "STR"
+...
+localhost> insert into t4 values ('0', '0')
+Insert OK, 1 rows affected
+localhost> insert into t4 values ('1', '1')
+Insert OK, 1 rows affected
+localhost> insert into t4 values ('2', '2')
+Insert OK, 1 rows affected
+localhost> insert into t4 values ('3', '3')
+Insert OK, 1 rows affected
+ocalhost> lua box.select_range(4, 0, 10)
+---
+ - '3': {'3'}
+ - '0': {'0'}
+ - '1': {'1'}
+ - '2': {'2'}
+...
+localhost> lua box.select_range(4, 1, 10)
+---
+ - '0': {'0'}
+ - '1': {'1'}
+ - '2': {'2'}
+ - '3': {'3'}
+...
+localhost> lua box.select_range(4, 1, 2)
+---
+ - '0': {'0'}
+ - '1': {'1'}
+...
+localhost> lua box.select_range(4, 1, 2, '1')
+---
+ - '1': {'1'}
+ - '2': {'2'}
+...
+</programlisting>
         </para></listitem>
     </varlistentry>
-
     <varlistentry>
         <term><emphasis role="lua">box.pack(format, ...)</emphasis></term>
         <listitem><para>
- To use Tarantool/Box binary protocol primitives from Lua, 
- it's necessary to pack Lua variables into a binary representation.
- This is a helper function to do it.
- It's prototyped aftre Perl 'pack', which takes a format and a list of
- arguments, and returns a binary string with all arguments
- packed according to the format.  See also doc/box-protocol.txt,
- the binary protocol description.
+            To use Tarantool binary protocol primitives from Lua,
+            it's necessary to convert Lua variables to binary
+            format. This helper function is prototyped after Perl
+            'pack'. It takes a format and a list of arguments, and
+            returns a binary string with all arguments packed
+            according to the format.
+            <bridgehead renderas="sect4">Format specifiers</bridgehead>
+            <simplelist>
+                <member><code>i</code> &mdash; converts Lua
+                variable to a 4-byte
+                integer, and stores the integer in the resulting
+                string, low byte first,
+                </member>
+                <member><code>p</code> &mdash; stores the length
+                of the argument as a 4-byte int, low byte first,
+                followed by the argument itself: a 4-byte int, low
+                byte first, for integers, or a binary blob for
+                anything else,
+                </member>
+                <member><code>=, +, &amp;, |, ^, : </code>&mdash;
+                stores the corresponding Tarantool UPDATE
+                operation code: field assignment, addition,
+                conjunction, disjunction, exclusive disjunction,
+                splice (from Perl SPLICE function). Expects
+                field number to update as an argument. These format
+                specifiers only store the corresponding operation
+                code and field number to update, but do not
+                describe operation arguments.
+                </member>
+            </simplelist>
+        <bridgehead renderas="sect4">Errors</bridgehead>
+        Unknown format specifier.
         <bridgehead renderas="sect4">Example</bridgehead>
 <programlisting>
- pkt = box.pack("iiiiiip", -- pack format
-                         0, -- space id
-                         0, -- index id
-                         0, -- offset
-                         2^32, -- limit
-                         1, -- number of SELECT arguments
-                         1, -- tuple cardinality
-                         key) -- the key to use for SELECT
+localhost> lua box.insert(0, 0, 'hello world')
+---
+ - 0: {'hello world'}
+...
+localhost> lua box.update(0, 0, "=p", 1, 'bye world')
+---
+ - 0: {'bye world'}
+...
+localhost> lua box.update(0, 0, ":p", 1, box.pack('ppp', 0, 3, 'hello'))
+---
+ - 0: {'hello world'}
+...
+localhost> lua box.update(0, 0, "=p", 1, 4)
+---
+ - 0: {4}
+...
+localhost> lua box.update(0, 0, "+p", 1, 4)
+---
+ - 0: {8}
+...
+localhost> lua box.update(0, 0, "^p", 1, 4)
+---
+ - 0: {12}
+...
 </programlisting>
         </para></listitem>
     </varlistentry>
@@ -300,7 +537,22 @@ localhost> lua box.select(5, 1, 'firstname', 'lastname')
     <varlistentry>
         <term><emphasis role="lua">box.unpack(format, ...)</emphasis></term>
         <listitem><para>
-            Counterpart to <code>box.pack().</code>
+            Counterpart to <code>box.pack()</code>. Only supports
+            <code>'i'</code> format specifier, and can be used
+            to convert packed integers to Lua numbers.
+            <bridgehead renderas="sect4">Example</bridgehead>
+<programlisting>localhost> lua tuple=box.replace(2, 0)
+---
+...
+localhost> lua string.len(tuple[0])
+---
+ - 4
+...
+localhost> lua box.unpack('i', tuple[0])
+---
+ - 0
+...
+</programlisting>
         </para></listitem>
     </varlistentry>
 
@@ -309,7 +561,7 @@ localhost> lua box.select(5, 1, 'firstname', 'lastname')
             <emphasis role="lua">box.print(...)</emphasis>
         </term>
         <listitem><para>
-Redefine Lua <code>print()</code> built-in to print either to the log file
+Redefines Lua <code>print()</code> built-in to print either to the log file
 (when Lua is used from the binary port) or back to the user (for the
 administrative console).
 </para><para>
@@ -323,58 +575,402 @@ Note: the administrative console output must be YAML-compatible.
 </variablelist>
 
 <variablelist>
-    <title>Packages <code>box.space</code>  and <code>box.index</code></title>
-    <para>These packages combine access to space and index
-    configuration, such as <code>enabled</code>, <code>cardinality</code>,
-    etc, with object-oriented access to space and index functions
-    (<code>insert(), update(), select(), ...</code>. Each space
-    entry is a container for all space indexes, which are
-    available in array box.space[].index[].</para>
-    <varlistentry>
-        <term><emphasis role="lua">
-        box.space[i].n
-        </emphasis></term>
+    <title>Package <code xml:id="box.tuple" xreflabel="box.tuple">box.tuple</code></title>
+    <para>The package contains no functions, but stands for
+    <code>box.tuple</code> userdata type. It is possible to access individual
+    tuple fields using an index, iterate over all fields in a
+    tuple or convert a tuple to a Lua table. Tuples are immutable.</para>
+    <varlistentry>
+        <term><emphasis role="lua"> </emphasis></term>
+        <listitem><para>
+        <bridgehead renderas="sect4">Example</bridgehead>
+<programlisting>
+localhost> lua t=box.insert(0, 1, 'abc', 'cde', 'efg', 'ghq', 'qkl')
+---
+...
+localhost> lua #t
+---
+ - 6
+...
+localhost> lua t[1], t[5]
+---
+ - abc
+ - qkl
+...
+localhost> lua t[6]
+---
+error: 'Lua error: [string "return t[6]"]:1: box.tuple: index 6 is out of bounds (0..5)'
+...
+localhost> lua for k,v in t:pairs() do print(v) end
+---
+
+abc
+cde
+efg
+ghq
+qkl
+...
+localhost> lua t:unpack()
+---
+ - 
+ - abc
+ - cde
+ - efg
+ - ghq
+ - qkl
+...
+</programlisting>
+
+        </para></listitem>
+    </varlistentry>
+</variablelist>
+
+<variablelist>
+    <title>Package <code xml:id="box.space" xreflabel="box.space">box.space</code></title>
+    <para>This package is a container for all
+    configured spaces. A space object provides access to space
+    attributes, such as id, whether or not a space is
+    enabled, space cardinality, estimated number of rows. It also
+    contains object-oriented versions of <code>box</code>
+    functions. For example, instead of <code>box.insert(0, ...)</code>
+    one can write <code>box.space[0]:insert(...)</code>.
+    Package source code is available in file <filename
+    xlink:href="https://github.com/mailru/tarantool/blob/master/mod/box/box.lua">mod/box/box.lua</filename></para>
+    <para>A list of all <code>space</code> members follows.</para>
+    <varlistentry>
+        <term><emphasis role="lua">space.n</emphasis></term>
+        <listitem><simpara>Ordinal space number, <code>box.space[i].n == i</code></simpara></listitem>
+    </varlistentry>
+    <varlistentry>
+        <term><emphasis role="lua">space.enabled</emphasis></term>
+        <listitem><simpara>
+            Whether or not this space is enabled in the
+            configuration file.
+        </simpara></listitem>
+    </varlistentry>
+
+    <varlistentry>
+        <term>
+            <emphasis role="lua">space.cardinality</emphasis>
+        </term>
+        <listitem><simpara>
+            A limit on tuple cardinality for tuples in this space.
+            This limit can be set in the configuration file. Value 0
+            stands for <quote>unlimited</quote>.
+        </simpara></listitem>
+    </varlistentry>
+
+    <varlistentry>
+        <term>
+            <emphasis role="lua">space.index[]</emphasis>
+        </term>
+        <listitem><simpara>
+            A container for all defined indexes. An index is a Lua object
+            of type <code xlink:href="#box.index">box.index</code> which
+            allows to search tuples and iterate over them in predefined order.
+        </simpara></listitem>
+    </varlistentry>
+    <varlistentry>
+        <term>
+            <emphasis role="lua">space:select(index_no, ...)</emphasis>
+        </term>
+        <listitem><simpara></simpara></listitem>
+    </varlistentry>
+
+    <varlistentry>
+        <term>
+            <emphasis role="lua">space:select_range(index_no, limit, key, ...)</emphasis>
+        </term>
+        <listitem><simpara></simpara></listitem>
+    </varlistentry>
+
+    <varlistentry>
+        <term>
+            <emphasis role="lua">space:insert(...)</emphasis>
+        </term>
+        <listitem><simpara></simpara></listitem>
+    </varlistentry>
+
+    <varlistentry>
+        <term>
+            <emphasis role="lua">space:replace(...)</emphasis>
+        </term>
+        <listitem><simpara></simpara></listitem>
+    </varlistentry>
+
+    <varlistentry>
+        <term>
+            <emphasis role="lua">space:delete(key)</emphasis>
+        </term>
         <listitem><simpara></simpara></listitem>
     </varlistentry>
+
+    <varlistentry>
+        <term>
+            <emphasis role="lua">space:update(key, format, ...)</emphasis>
+        </term>
+        <listitem><simpara></simpara></listitem>
+    </varlistentry>
+
+    <varlistentry>
+        <term>
+            <emphasis role="lua">space:insert(...)</emphasis>
+        </term>
+        <listitem><simpara>
+            Object-oriented forms of respective <code
+            xlink:href="#box">box</code> methods.
+        </simpara></listitem>
+    </varlistentry>
+
+    <varlistentry>
+        <term>
+            <emphasis role="lua">space:len()</emphasis>
+        </term>
+        <listitem><simpara>
+            Returns number of tuples in the space.
+        </simpara></listitem>
+    </varlistentry>
+
+    <varlistentry>
+        <term>
+            <emphasis role="lua">space:truncate()</emphasis>
+        </term>
+        <listitem><simpara>
+            Deletes all tuples.
+        </simpara></listitem>
+    </varlistentry>
+
+    <varlistentry>
+        <term>
+            <emphasis role="lua">space:pairs()</emphasis>
+        </term>
+        <listitem><simpara>
+            A helper function to iterate over all space tuples,
+            Lua style.
+        </simpara>
+                <bridgehead renderas="sect4">Example</bridgehead>
+<programlisting>
+localhost> lua for k,v in box.space[0]:pairs() do print(v) end
+---
+1: {'hello'}
+2: {'my     '}
+3: {'Lua    '}
+4: {'world'}
+...
+</programlisting>
+        </listitem>
+    </varlistentry>
+</variablelist>
+
+<variablelist>
+    <title>Package <code xml:id="box.index"
+xreflabel="box.index">box.index</code></title>
+    <para>
+        This package implements methods of type <code>box.index</code>.
+        Indexes are contained in <code
+        xlink:href="#box.space">box.space[i].index[]</code> array
+        within each space object. They provide an API for
+        ordered iteration over tuples.
+    </para>
+    <varlistentry>
+        <term><emphasis role="lua">index.unique</emphasis></term>
+        <listitem><simpara>
+            Boolean, true if the index is unique.
+        </simpara></listitem>
+    </varlistentry>
+
+    <varlistentry>
+        <term>
+            <emphasis role="lua">index.type</emphasis>
+        </term>
+        <listitem><simpara>
+            A string for index type, either 'TREE' or 'HASH'.
+        </simpara></listitem>
+    </varlistentry>
+
+    <varlistentry>
+        <term>
+            <emphasis role="lua">index.key_field[]</emphasis>
+        </term>
+        <listitem><simpara>
+            An array describing index key fields.
+        </simpara></listitem>
+    </varlistentry>
+
+    <varlistentry>
+        <term>
+            <emphasis role="lua">index.idx</emphasis>
+        </term>
+        <listitem><simpara>
+            The underlying userdata which does all the magic.
+        </simpara></listitem>
+    </varlistentry>
+
+    <varlistentry>
+        <term>
+            <emphasis role="lua">space:select_range(limit, key)</emphasis>
+        </term>
+        <listitem><simpara>Select a range of tuples, limited by
+        <code>limit</code>, starting from <code>key</code>.
+        </simpara></listitem>
+    </varlistentry>
+
+    <varlistentry>
+        <term>
+            <emphasis role="lua">index:pairs()</emphasis>
+        </term>
+        <listitem><simpara>
+            A helper function to iterate over all tuples in the index.
+        </simpara>
+        </listitem>
+    </varlistentry>
+
+    <varlistentry>
+        <term>
+            <emphasis role="lua">index:min()</emphasis>
+        </term>
+        <listitem><simpara>
+            The smallest value in the index. Available only for
+            indexes of type 'TREE'.
+        </simpara>
+        </listitem>
+    </varlistentry>
+
+    <varlistentry>
+        <term>
+            <emphasis role="lua">index:max()</emphasis>
+        </term>
+        <listitem><simpara>
+            The biggest value in the index. Available only for
+            indexes of type 'TREE'.
+        </simpara>
+        </listitem>
+    </varlistentry>
+
+    <varlistentry>
+        <term>
+            <emphasis role="lua">index:pairs()</emphasis>
+        </term>
+        <listitem><simpara>
+            A helper function to iterate over all tuples in an
+            index.
+        </simpara>
+        </listitem>
+    </varlistentry>
+
+    <varlistentry>
+        <term>
+            <emphasis role="lua">index:next(iteration_state, key)</emphasis>
+        </term>
+        <listitem><simpara>
+            This function can be used for positioned iteration, or
+            resuming iteration from a given key. It follows the
+            <link xlink:href='http://pgl.yoyo.org/luai/i/next'>Lua
+            iteration pattern</link> and returns a pair
+            <code>&gt;iteration_state, tuple&lt;</code>.
+            When called with no arguments, it starts iteration
+            from the beginning. If called with userdata
+            <code>iteration_state</code>, it returns a tuple
+            corresponding to iterator position, plus a new
+            <code>iteration_state</code>. When called with a key,
+            it positions the iterator on the key, and returns the
+            respective tuple and <code>iteration_state</code>.
+        </simpara>
+        <bridgehead renderas="sect4">Example</bridgehead>
+<programlisting>
+localhost> insert into t0 values (1, 'Russia')
+Insert OK, 1 rows affected
+localhost> insert into t0 values (2, 'Serbia')
+Insert OK, 1 rows affected
+localhost> insert into t0 values (3, 'Bulgaria')
+Insert OK, 1 rows affected
+localhost> lua i = box.space[0].index[0]
+---
+...
+localhost> lua k,v=i:next()
+---
+...
+localhost> lua print(v)
+--
+1: {'Russia'}
+...
+localhost> lua k,v=i:next(k)
+---
+...
+localhost> lua print(v)
+---
+2: {'Serbia'}
+...
+localhost> lua k,v=i:next(k)
+---
+...
+localhost> lua print(v)
+---
+3: {'Bulgaria'}
+...
+localhost> lua k,v=i:next(k)
+---
+...
+localhost> lua print(v)
+---
+nil
+...
+localhost> lua k,v=i:next(2)
+---
+...
+localhost> lua print(v)
+---
+2: {'Serbia'}
+...
+localhost> lua for k,v in i.next, i, nil do print(v) end
+---
+1: {'Russia'}
+2: {'Serbia'}
+3: {'Bulgaria'}
+...
+</programlisting>
+        </listitem>
+    </varlistentry>
+
 </variablelist>
 
 <variablelist>
     <title>Package <code>box.fiber</code></title>
-    <para>Create, run and manage existing <emphasis>fibers</emphasis>.
+    <para>Functions in this package allow to create, run and
+    manage existing <emphasis>fibers</emphasis>.
     </para>
     <para>
-A fiber is an independent execution thread, implemented
-using the mechanism of cooperative multitasking.
+A fiber is an independent execution thread implemented
+using a mechanism of cooperative multitasking.
 Each fiber can be running, suspended or dead.
 A fiber is created (<code>box.fiber.create()</code>) suspended.
 It can be started with <code>box.fiber.resume()</code>, yield
-the control back with <code>box.fiber.yield()</code>
-end with <code>return</code> or just by reaching the end of the
-function.
+control back to the caller with <code>box.fiber.yield()</code>
+end with <code>return</code> or just by reaching the end of
+fiber function.
     </para>
     <para>
 A fiber can also be attached or detached.
 An attached fiber is a child of the creator,
 and is running only if the creator has called
 <code>box.fiber.resume()</code>. A detached fiber is a child of
-Tarntool/Box internal <quote>sched</quote> fiber, and is gets
+Tarantool internal <quote>sched</quote> fiber, and gets
 scheduled only if there is a libev event associated
 with it.
     </para>
     <para>
-To detach, a running fiber must invoke box.fiber.detach().
-A detached fiber loses connection with its parent
-forever.
+To detach, a running fiber must invoke <code>box.fiber.detach()</code>.
+A detached fiber loses connection with its parent forever.
     </para>
     <para>
-All fibers are part of the fiber registry, box.fiber.
+All fibers are part of the fiber registry, <code>box.fiber</code>.
 This registry can be searched (<code>box.fiber.find()</code>)
 either by fiber id (fid), which is numeric, or by fiber name,
 which is a string. If there is more than one fiber with the given
 name, the first fiber that matches is returned.
     </para>
     <para>
-Once fiber chunk is done or calls <code>return</code>,
+Once fiber function is done or calls <code>return</code>,
 the fiber is considered dead. Its carcass is put into
 a fiber pool, and can be reused when another fiber is
 created.
@@ -388,21 +984,20 @@ or <code>box.update()</code>, are calling <code>box.fiber.testcancel()</code>.
 <code>box.select()</code> doesn't.
     </para>
     <para>
-A runaway fiber can really only become cuckoo
+In practice, a runaway fiber can only become unresponsive
 if it does a lot of computations and doesn't check
-whether it's been cancelled. In addition
-to the advisory cancellation, configuration parameter lua_timeout
+whether it's been canceled. In addition
+to the advisory cancellation, configuration parameter <code>lua_timeout</code>
 can be used to cancel runaway Lua procedures.
     </para>
     <para>
 The other potential problem comes from detached
-fibers which never get scheduled, because are subscribed
-or get no events. Such morphing fibers can be killed
-with <code>box.fiber.cancel()</code> at any time,
+fibers which never get scheduled, because are not subscribed
+to any events, or no relevant events occur. Such morphing fibers
+can be killed with <code>box.fiber.cancel()</code> at any time,
 since <code>box.fiber.cancel()</code>
 sends an asynchronous wakeup event to the fiber,
-and when returning from <code>box.fiber.yield()</code>
-<code>box.fiber.testcancel()</code> is invoked.
+and <code>box.fiber.testcancel()</code> is checked whenever such an event occurs.
     </para>
     <para>Like all Lua objects, dead fibers are
     garbage collected.</para>
@@ -410,23 +1005,22 @@ and when returning from <code>box.fiber.yield()</code>
         <term>
             <emphasis role="lua">box.fiber.id(fiber) </emphasis>
         </term>
-        <listitem><simpara>Returns a numeric id of the fiber.</simpara></listitem>
+        <listitem><simpara>Return a numeric id of the fiber.</simpara></listitem>
     </varlistentry>
 
     <varlistentry>
         <term>
             <emphasis role="lua">box.fiber.self() </emphasis>
         </term>
-        <listitem><simpara>Returns <code>box.fiber</code> userdata
-        object for the currently scheduled
-        fiber.</simpara></listitem>
+        <listitem><simpara>Return <code>box.fiber</code> userdata
+        object for the currently scheduled fiber.</simpara></listitem>
     </varlistentry>
 
     <varlistentry>
         <term>
             <emphasis role="lua">box.fiber.find(id) </emphasis>
         </term>
-        <listitem><simpara></simpara></listitem>
+        <listitem><simpara>Locate a fiber userdata object by id.</simpara></listitem>
     </varlistentry>
 
     <varlistentry>
@@ -434,10 +1028,11 @@ and when returning from <code>box.fiber.yield()</code>
             <emphasis role="lua">box.fiber.create(function) </emphasis>
         </term>
         <listitem><simpara>
-		Create a fiber for the passed change.
-        Can hit a recursion limit. Is a cancellation
-        point.
-        </simpara></listitem>
+		Create a fiber for <code>function</code>.
+        </simpara>
+        <bridgehead renderas="sect4">Errors</bridgehead>
+        <simpara>Can hit a recursion limit.</simpara>
+        </listitem>
     </varlistentry>
 
     <varlistentry>
@@ -447,26 +1042,20 @@ and when returning from <code>box.fiber.yield()</code>
         <listitem><simpara>Resume a created
         or suspended fiber.</simpara></listitem>
     </varlistentry>
-        
+
     <varlistentry>
         <term>
             <emphasis role="lua">box.fiber.yield(...) </emphasis>
         </term>
         <listitem><para>
-        Yield control to the calling fiber &mdash; if the fiber
+        Yield control to the calling fiber, if the fiber
         is attached, or to sched otherwise.
         </para>
         <para>
         If the fiber is attached, whatever arguments are passed
         to this call, are passed on to the calling fiber.
-        If the fiber is detached, simply returns everything back.
-	 Yield to the caller. The caller will take care of
-	 whatever arguments are taken.
-	fiber_testcancel(); /* throws an error if we were cancelled. */
-	 * Got resumed. Return whatever the caller has passed
-	 * to us with box.fiber.resume().
-	 * As a side effect, the detached fiber which yields
-	 * to sched always gets back whatever it yields.
+        If the fiber is detached, <code>box.fiber.yield()</code>
+        returns back everything passed into it.
         </para></listitem>
     </varlistentry>
 
@@ -484,8 +1073,7 @@ and when returning from <code>box.fiber.yield()</code>
             <emphasis role="lua">box.fiber.sleep(time)</emphasis>
         </term>
         <listitem><simpara>
-    Yield to the sched fiber and sleep.
-    @param[in]  amount of time to sleep (double)
+    Yield to the sched fiber and sleep <code>time</code> seconds.
     Only the current fiber can be made to sleep.
         </simpara></listitem>
     </varlistentry>
@@ -495,19 +1083,19 @@ and when returning from <code>box.fiber.yield()</code>
             <emphasis role="lua">box.fiber.cancel(fiber)</emphasis>
         </term>
         <listitem><simpara>
-Running and suspended fibers can be cancelled.
-Zombie fibers can't. Returns an error if
-subject fiber does not permit cancel.
+        Cancel a <code>fiber</code>.
+        Running and suspended fibers can be canceled.
+        Returns an error if the subject fiber does not permit cancel.
         </simpara></listitem>
     </varlistentry>
-    
+
     <varlistentry>
         <term>
             <emphasis role="lua">box.fiber.testcancel()</emphasis>
         </term>
         <listitem><simpara>
-Check if this current fiber has been cancelled and
-throw an exception if this is the case.
+            Check if the current fiber has been canceled and
+            throw an exception if this is the case.
         </simpara></listitem>
     </varlistentry>
 
@@ -515,7 +1103,7 @@ throw an exception if this is the case.
 
 <variablelist>
     <title>Package <code>box.cfg</code></title>
-    <para>This package provides read-only access to 
+    <para>This package provides read-only access to
     all server configuration parameters.</para>
     <varlistentry>
         <term><emphasis role="lua">box.cfg</emphasis></term>
@@ -535,11 +1123,72 @@ logger = cat - >> tarantool.log
 </variablelist>
 
 <para>
-    Additional examples can be found in the open source Lua stored
-    procedures repository and in the server test suite.
+    Additional examples can be found in the open source <link
+    xlink:href="https://github.com/mailru/tntlua">Lua stored
+    procedures repository</link> and in the server test suite.
+</para>
+
+<section>
+<title>Limitation of stored programs</title>
+
+<para>
+    There are two limitations in stored program support one should
+    be aware of: execution atomicity and lack of typing.
+</para>
+<bridgehead renderas="sect4">Cooperative multitasking environment</bridgehead>
+<para>
+    Tarantool core is built around cooperative multi-tasking
+    paradigm: unless a running fiber deliberately yields control
+    to some other fiber, it is not preempted.
+    <quote>Yield points</quote> are built into all
+    calls from Tarantool core to the operating system.
+    Any system call which can block is performed in
+    asynchronous manner and the fiber waiting
+    on the system call is preempted with a fiber ready to
+    run. This model makes all programmatic locks unnecessary:
+    cooperative multitasking ensures that there is no concurrency
+    around a resource, no race conditions and no memory
+    consistency issues.
+</para>
+<para>
+    When requests are small, e.g. simple UPDATE, INSERT, DELETE,
+    SELECT, fiber scheduling is fair: it takes only a little time
+    to process the request, schedule a disk write, and yield to
+    a fiber serving the next client.
+</para>
+<para>
+    A stored procedure, however, can perform complex computations,
+    or be written in such a way that control is not given away for a
+    long time. This can lead to unfair scheduling, when a single
+    client throttles the rest of the system, or to apparent stalls
+    in request processing.
+    Avoiding this situation is responsibility of the stored procedure
+    author. Most of <code>box</code> calls, such as
+    <code>box.insert()</code>, <code>box.update()</code>,
+    <code>box.delete()</code> are yield points; <code>box.select()</code>
+    and <code>box.select_range()</code>, however, are not.
+</para>
+<para>
+    It should also be noted, that in absence of transactions,
+    any yield in a stored procedure is a potential change in the
+    database state. Effectively, it's only possible
+    to have CAS (compare-and-swap) -like atomic stored
+    procedures: i.e. procedures which select and then modify a record.
+
+    Multiple data change requests always run through a built-in
+    yield point.
+</para>
+<bridgehead renderas="sect4">Lack of field types</bridgehead>
+<para>
+   When invoking a stored procedure from the binary protocol,
+   it's not possible to convey types of arguments. Tuples are
+   type-agnostic. The conventional workaround is to use
+   strings to pass all (textual and numeric) data.
 </para>
 
 </section>
+</section>
+
 <!--
 vim: tw=66 syntax=docbk
 vim: spell spelllang=en_us
diff --git a/doc/user/target.db b/doc/user/target.db
index b7be49cea9d93101926b3a9981907b62437e27be..ee73c37c4bb101c8d69b5bed3f1858d03bd65d96 100644
--- a/doc/user/target.db
+++ b/doc/user/target.db
@@ -1 +1 @@
-<div element="book" href="#tarantool-user-guide" number="" targetptr="tarantool-user-guide"><ttl>Tarantool/Box User Guide, version 1.4.3-9-g472a615</ttl><xreftext>Tarantool/Box User Guide, version 1.4.3-9-g472a615</xreftext><div element="chapter" href="#idm34816" number="1"><ttl>Preface</ttl><xreftext>Chapter 1, <i>Preface</i></xreftext><div element="section" href="#preface" number="" targetptr="preface"><ttl>Tarantool/Box: an overview</ttl><xreftext>the section called “Tarantool/Box: an overview”</xreftext></div><div element="section" href="#idp17776" number=""><ttl>Conventions</ttl><xreftext>the section called “Conventions”</xreftext></div><div element="section" href="#idp26672" number=""><ttl>Reporting bugs</ttl><xreftext>the section called “Reporting bugs”</xreftext></div></div><div element="chapter" href="#idp220784" number="2"><ttl>Getting started</ttl><xreftext>Chapter 2, <i>Getting started</i></xreftext></div><div element="chapter" href="#idp221040" number="3"><ttl>Dynamic data model</ttl><xreftext>Chapter 3, <i>Dynamic data model</i></xreftext></div><div element="chapter" href="#language-reference" number="4" targetptr="language-reference"><ttl>Language reference</ttl><xreftext>Chapter 4, <i>Language reference</i></xreftext><div element="section" href="#idp384848" number=""><ttl>Data manipulation</ttl><xreftext>the section called “Data manipulation”</xreftext><div element="section" href="#idp378896" number=""><ttl>Memcached protocol</ttl><xreftext>the section called “Memcached protocol”</xreftext></div></div><div element="section" href="#idp376016" number=""><ttl>Administrative console</ttl><xreftext>the section called “Administrative console”</xreftext><obj element="term" href="#save-snapshot" number="" targetptr="save-snapshot"><ttl>???TITLE???</ttl><xreftext>SAVE SNAPSHOT</xreftext></obj><obj element="term" href="#reload-configuration" number="" targetptr="reload-configuration"><ttl>???TITLE???</ttl><xreftext>RELOAD CONFIGURATION</xreftext></obj><obj element="term" href="#show-configuration" number="" targetptr="show-configuration"><ttl>???TITLE???</ttl><xreftext>SHOW CONFIGURATION</xreftext></obj><obj element="term" href="#show-info" number="" targetptr="show-info"><ttl>???TITLE???</ttl><xreftext>SHOW INFO</xreftext></obj><obj element="term" href="#show-stat" number="" targetptr="show-stat"><ttl>???TITLE???</ttl><xreftext>SHOW STAT</xreftext></obj><obj element="term" href="#show-slab" number="" targetptr="show-slab"><ttl>???TITLE???</ttl><xreftext>SHOW SLAB</xreftext></obj><obj element="term" href="#show-palloc" number="" targetptr="show-palloc"><ttl>???TITLE???</ttl><xreftext>SHOW PALLOC</xreftext></obj><obj element="term" href="#save-coredump" number="" targetptr="save-coredump"><ttl>???TITLE???</ttl><xreftext>SAVE COREDUMP</xreftext></obj><obj element="term" href="#show-fiber" number="" targetptr="show-fiber"><ttl>???TITLE???</ttl><xreftext>SHOW FIBER</xreftext></obj><obj element="term" href="#lua-command" number="" targetptr="lua-command"><ttl>???TITLE???</ttl><xreftext>LUA ...</xreftext></obj></div><div element="section" href="#stored-programs" number="" targetptr="stored-programs"><ttl>Writing stored procedures in Lua</ttl><xreftext>the section called “Writing stored procedures in Lua”</xreftext></div></div><div element="chapter" href="#replication" number="5" targetptr="replication"><ttl>Replication</ttl><xreftext>Chapter 5, <i>Replication</i></xreftext><div element="section" href="#idp348448" number=""><ttl>Replication architecture</ttl><xreftext>the section called “Replication architecture”</xreftext></div><div element="section" href="#idp353184" number=""><ttl>Setting up the master</ttl><xreftext>the section called “Setting up the master”</xreftext></div><div element="section" href="#idp262720" number=""><ttl>Setting up a replica</ttl><xreftext>the section called “Setting up a replica”</xreftext></div><div element="section" href="#idp269168" number=""><ttl>Recovering from a degraded state</ttl><xreftext>the section called “Recovering from a degraded state”</xreftext></div></div><div element="chapter" href="#configuration-reference" number="6" targetptr="configuration-reference"><ttl>Configuration reference</ttl><xreftext>Chapter 6, <i>Configuration reference</i></xreftext><div element="section" href="#idp679424" number=""><ttl>Command line options</ttl><xreftext>the section called “Command line options”</xreftext><obj element="listitem" href="#help-option" number="" targetptr="help-option"><ttl>???TITLE???</ttl><xreftext/></obj><obj element="listitem" href="#version-option" number="" targetptr="version-option"><ttl>???TITLE???</ttl><xreftext/></obj><obj element="listitem" href="#config-option" number="" targetptr="config-option"><ttl>???TITLE???</ttl><xreftext/></obj><obj element="option" href="#init-storage-option" number="" targetptr="init-storage-option"><ttl>???TITLE???</ttl><xreftext>--init-storage</xreftext></obj><obj element="option" href="#cat-option" number="" targetptr="cat-option"><ttl>???TITLE???</ttl><xreftext>--cat</xreftext></obj></div><div element="section" href="#option-file" number="" targetptr="option-file"><ttl>The option file</ttl><xreftext>option file</xreftext><obj element="table" href="#idp692032" number="6.1"><ttl>Basic parameters</ttl><xreftext>Table 6.1, “Basic parameters”</xreftext></obj><obj element="entry" href="#wal_dir" number="" targetptr="wal_dir"><ttl>???TITLE???</ttl><xreftext>wal_dir</xreftext></obj><obj element="entry" href="#snap_dir" number="" targetptr="snap_dir"><ttl>???TITLE???</ttl><xreftext>snap_dir</xreftext></obj><obj element="entry" href="#primary_port" number="" targetptr="primary_port"><ttl>???TITLE???</ttl><xreftext>primary_port</xreftext></obj><obj element="entry" href="#secondary_port" number="" targetptr="secondary_port"><ttl>???TITLE???</ttl><xreftext>secondary_port</xreftext></obj><obj element="entry" href="#admin_port" number="" targetptr="admin_port"><ttl>???TITLE???</ttl><xreftext>admin_port</xreftext></obj><obj element="entry" href="#custom_proc_title" number="" targetptr="custom_proc_title"><ttl>???TITLE???</ttl><xreftext>custom_proc_title</xreftext></obj><obj element="table" href="#idp740672" number="6.2"><ttl>Configuring the storage</ttl><xreftext>Table 6.2, “Configuring the storage”</xreftext></obj><obj element="anchor" href="#slab_alloc_arena" number="" targetptr="slab_alloc_arena"><ttl>???TITLE???</ttl><xreftext>slab_alloc_arena</xreftext></obj><obj element="para" href="#space" number="" targetptr="space"><ttl>???TITLE???</ttl><xreftext>the section called “The option file”</xreftext></obj><obj element="table" href="#idp776688" number="6.3"><ttl>Binary logging and snapshots</ttl><xreftext>Table 6.3, “Binary logging and snapshots”</xreftext></obj><obj element="table" href="#idp808096" number="6.4"><ttl>Replication</ttl><xreftext>Table 6.4, “Replication”</xreftext></obj><obj element="entry" href="#replication_port" number="" targetptr="replication_port"><ttl>???TITLE???</ttl><xreftext>replication_port</xreftext></obj><obj element="entry" href="#replication_source" number="" targetptr="replication_source"><ttl>???TITLE???</ttl><xreftext>replication_source</xreftext></obj><obj element="table" href="#idp827072" number="6.5"><ttl>Networking</ttl><xreftext>Table 6.5, “Networking”</xreftext></obj><obj element="table" href="#idp845840" number="6.6"><ttl>Logging</ttl><xreftext>Table 6.6, “Logging”</xreftext></obj><obj element="table" href="#idp870736" number="6.7"><ttl>Memcached protocol support</ttl><xreftext>Table 6.7, “Memcached protocol support”</xreftext></obj><obj element="anchor" href="#memcached_port" number="" targetptr="memcached_port"><ttl>???TITLE???</ttl><xreftext>memcached_port</xreftext></obj><obj element="anchor" href="#memcached_space" number="" targetptr="memcached_space"><ttl>???TITLE???</ttl><xreftext>memcached_space</xreftext></obj><obj element="anchor" href="#memcached_expire" number="" targetptr="memcached_expire"><ttl>???TITLE???</ttl><xreftext>memcached_expire</xreftext></obj></div></div><div element="chapter" href="#connectors" number="7" targetptr="connectors"><ttl>Connectors</ttl><xreftext>Chapter 7, <i>Connectors</i></xreftext><div element="section" href="#idp247104" number=""><ttl>C</ttl><xreftext>the section called “C”</xreftext></div><div element="section" href="#idp253312" number=""><ttl>Perl</ttl><xreftext>the section called “Perl”</xreftext></div><div element="section" href="#idp645056" number=""><ttl>PHP</ttl><xreftext>the section called “PHP”</xreftext></div><div element="section" href="#idp646400" number=""><ttl>Python</ttl><xreftext>the section called “Python”</xreftext></div><div element="section" href="#idp647744" number=""><ttl>Ruby</ttl><xreftext>the section called “Ruby”</xreftext><obj element="example" href="#idp652656" number="7.1"><ttl>userbox.rb</ttl><xreftext>Example 7.1, “userbox.rb”</xreftext></obj></div></div><div element="appendix" href="#proctitle" number="A" targetptr="proctitle"><ttl>Server process titles</ttl><xreftext>Appendix A, <i>Server process titles</i></xreftext></div><div element="appendix" href="#errcode" number="B" targetptr="errcode"><ttl>List of error codes</ttl><xreftext>Appendix B, <i>List of error codes</i></xreftext><obj element="term" href="#ER_NONMASTER" number="" targetptr="ER_NONMASTER"><ttl>???TITLE???</ttl><xreftext>ER_NONMASTER</xreftext></obj><obj element="term" href="#ER_ILLEGAL_PARAMS" number="" targetptr="ER_ILLEGAL_PARAMS"><ttl>???TITLE???</ttl><xreftext>ER_ILLEGAL_PARAMS</xreftext></obj><obj element="term" href="#ER_TUPLE_IS_RO" number="" targetptr="ER_TUPLE_IS_RO"><ttl>???TITLE???</ttl><xreftext>ER_TUPLE_IS_RO</xreftext></obj><obj element="term" href="#ER_MEMORY_ISSUE" number="" targetptr="ER_MEMORY_ISSUE"><ttl>???TITLE???</ttl><xreftext>ER_MEMORY_ISSUE</xreftext></obj><obj element="term" href="#ER_WAL_IO" number="" targetptr="ER_WAL_IO"><ttl>???TITLE???</ttl><xreftext>ER_WAL_IO</xreftext></obj><obj element="term" href="#ER_INDEX_VIOLATION" number="" targetptr="ER_INDEX_VIOLATION"><ttl>???TITLE???</ttl><xreftext>ER_INDEX_VIOLATION</xreftext></obj><obj element="term" href="#ER_NO_SUCH_SPACE" number="" targetptr="ER_NO_SUCH_SPACE"><ttl>???TITLE???</ttl><xreftext>ER_NO_SUCH_SPACE</xreftext></obj><obj element="term" href="#ER_NO_SUCH_INDEX" number="" targetptr="ER_NO_SUCH_INDEX"><ttl>???TITLE???</ttl><xreftext>ER_NO_SUCH_INDEX</xreftext></obj><obj element="term" href="#ER_PROC_LUA" number="" targetptr="ER_PROC_LUA"><ttl>???TITLE???</ttl><xreftext>ER_PROC_LUA</xreftext></obj></div></div>
+<div element="book" href="#tarantool-user-guide" number="" targetptr="tarantool-user-guide"><ttl>Tarantool/Box User Guide, version 1.4.4-127-g8c61695</ttl><xreftext>Tarantool/Box User Guide, version 1.4.4-127-g8c61695</xreftext><div element="chapter" href="#idm34816" number="1"><ttl>Preface</ttl><xreftext>Chapter 1, <i>Preface</i></xreftext><div element="section" href="#preface" number="" targetptr="preface"><ttl>Tarantool/Box: an overview</ttl><xreftext>the section called “Tarantool/Box: an overview”</xreftext></div><div element="section" href="#idp200384" number=""><ttl>Conventions</ttl><xreftext>the section called “Conventions”</xreftext></div><div element="section" href="#idp209280" number=""><ttl>Reporting bugs</ttl><xreftext>the section called “Reporting bugs”</xreftext></div></div><div element="chapter" href="#idp229968" number="2"><ttl>Getting started</ttl><xreftext>Chapter 2, <i>Getting started</i></xreftext></div><div element="chapter" href="#data-and-persistence" number="3" targetptr="data-and-persistence"><ttl>Data model and data persitence</ttl><xreftext>Chapter 3, <i>Data model and data persitence</i></xreftext><div element="section" href="#idp312384" number=""><ttl>Dynamic data model</ttl><xreftext>the section called “Dynamic data model”</xreftext></div><div element="section" href="#idp403344" number=""><ttl>Data persistence</ttl><xreftext>the section called “Data persistence”</xreftext></div></div><div element="chapter" href="#language-reference" number="4" targetptr="language-reference"><ttl>Language reference</ttl><xreftext>Chapter 4, <i>Language reference</i></xreftext><div element="section" href="#idp643840" number=""><ttl>Data manipulation</ttl><xreftext>the section called “Data manipulation”</xreftext><div element="section" href="#idp637888" number=""><ttl>Memcached protocol</ttl><xreftext>the section called “Memcached protocol”</xreftext></div></div><div element="section" href="#idp636960" number=""><ttl>Administrative console</ttl><xreftext>the section called “Administrative console”</xreftext><obj element="term" href="#save-snapshot" number="" targetptr="save-snapshot"><ttl>???TITLE???</ttl><xreftext>SAVE SNAPSHOT</xreftext></obj><obj element="term" href="#reload-configuration" number="" targetptr="reload-configuration"><ttl>???TITLE???</ttl><xreftext>RELOAD CONFIGURATION</xreftext></obj><obj element="term" href="#show-configuration" number="" targetptr="show-configuration"><ttl>???TITLE???</ttl><xreftext>SHOW CONFIGURATION</xreftext></obj><obj element="term" href="#show-info" number="" targetptr="show-info"><ttl>???TITLE???</ttl><xreftext>SHOW INFO</xreftext></obj><obj element="term" href="#show-stat" number="" targetptr="show-stat"><ttl>???TITLE???</ttl><xreftext>SHOW STAT</xreftext></obj><obj element="term" href="#show-slab" number="" targetptr="show-slab"><ttl>???TITLE???</ttl><xreftext>SHOW SLAB</xreftext></obj><obj element="term" href="#show-palloc" number="" targetptr="show-palloc"><ttl>???TITLE???</ttl><xreftext>SHOW PALLOC</xreftext></obj><obj element="term" href="#save-coredump" number="" targetptr="save-coredump"><ttl>???TITLE???</ttl><xreftext>SAVE COREDUMP</xreftext></obj><obj element="term" href="#show-fiber" number="" targetptr="show-fiber"><ttl>???TITLE???</ttl><xreftext>SHOW FIBER</xreftext></obj><obj element="term" href="#lua-command" number="" targetptr="lua-command"><ttl>???TITLE???</ttl><xreftext>LUA ...</xreftext></obj></div><div element="section" href="#stored-programs" number="" targetptr="stored-programs"><ttl>Writing stored procedures in Lua</ttl><xreftext>the section called “Writing stored procedures in Lua”</xreftext><obj element="code" href="#box" number="" targetptr="box"><ttl>???TITLE???</ttl><xreftext>box</xreftext></obj><obj element="code" href="#box.tuple" number="" targetptr="box.tuple"><ttl>???TITLE???</ttl><xreftext>box.tuple</xreftext></obj><obj element="code" href="#box.space" number="" targetptr="box.space"><ttl>???TITLE???</ttl><xreftext>box.space</xreftext></obj><obj element="code" href="#box.index" number="" targetptr="box.index"><ttl>???TITLE???</ttl><xreftext>box.index</xreftext></obj><div element="section" href="#idp844528" number=""><ttl>Limitation of stored programs</ttl><xreftext>the section called “Limitation of stored programs”</xreftext></div></div></div><div element="chapter" href="#replication" number="5" targetptr="replication"><ttl>Replication</ttl><xreftext>Chapter 5, <i>Replication</i></xreftext><div element="section" href="#idp31184" number=""><ttl>Replication architecture</ttl><xreftext>the section called “Replication architecture”</xreftext></div><div element="section" href="#idp304320" number=""><ttl>Setting up the master</ttl><xreftext>the section called “Setting up the master”</xreftext></div><div element="section" href="#idp307200" number=""><ttl>Setting up a replica</ttl><xreftext>the section called “Setting up a replica”</xreftext></div><div element="section" href="#idp393936" number=""><ttl>Recovering from a degraded state</ttl><xreftext>the section called “Recovering from a degraded state”</xreftext></div></div><div element="chapter" href="#configuration-reference" number="6" targetptr="configuration-reference"><ttl>Configuration reference</ttl><xreftext>Chapter 6, <i>Configuration reference</i></xreftext><div element="section" href="#idp890800" number=""><ttl>Command line options</ttl><xreftext>the section called “Command line options”</xreftext><obj element="listitem" href="#help-option" number="" targetptr="help-option"><ttl>???TITLE???</ttl><xreftext/></obj><obj element="listitem" href="#version-option" number="" targetptr="version-option"><ttl>???TITLE???</ttl><xreftext/></obj><obj element="listitem" href="#config-option" number="" targetptr="config-option"><ttl>???TITLE???</ttl><xreftext/></obj><obj element="option" href="#init-storage-option" number="" targetptr="init-storage-option"><ttl>???TITLE???</ttl><xreftext>--init-storage</xreftext></obj><obj element="option" href="#cat-option" number="" targetptr="cat-option"><ttl>???TITLE???</ttl><xreftext>--cat</xreftext></obj></div><div element="section" href="#option-file" number="" targetptr="option-file"><ttl>The option file</ttl><xreftext>option file</xreftext><obj element="table" href="#idp903568" number="6.1"><ttl>Basic parameters</ttl><xreftext>Table 6.1, “Basic parameters”</xreftext></obj><obj element="entry" href="#wal_dir" number="" targetptr="wal_dir"><ttl>???TITLE???</ttl><xreftext>wal_dir</xreftext></obj><obj element="entry" href="#snap_dir" number="" targetptr="snap_dir"><ttl>???TITLE???</ttl><xreftext>snap_dir</xreftext></obj><obj element="entry" href="#primary_port" number="" targetptr="primary_port"><ttl>???TITLE???</ttl><xreftext>primary_port</xreftext></obj><obj element="entry" href="#secondary_port" number="" targetptr="secondary_port"><ttl>???TITLE???</ttl><xreftext>secondary_port</xreftext></obj><obj element="entry" href="#admin_port" number="" targetptr="admin_port"><ttl>???TITLE???</ttl><xreftext>admin_port</xreftext></obj><obj element="entry" href="#custom_proc_title" number="" targetptr="custom_proc_title"><ttl>???TITLE???</ttl><xreftext>custom_proc_title</xreftext></obj><obj element="table" href="#idp952208" number="6.2"><ttl>Configuring the storage</ttl><xreftext>Table 6.2, “Configuring the storage”</xreftext></obj><obj element="anchor" href="#slab_alloc_arena" number="" targetptr="slab_alloc_arena"><ttl>???TITLE???</ttl><xreftext>slab_alloc_arena</xreftext></obj><obj element="para" href="#space" number="" targetptr="space"><ttl>???TITLE???</ttl><xreftext>the section called “The option file”</xreftext></obj><obj element="table" href="#idp988224" number="6.3"><ttl>Binary logging and snapshots</ttl><xreftext>Table 6.3, “Binary logging and snapshots”</xreftext></obj><obj element="entry" href="#rows_per_wal" number="" targetptr="rows_per_wal"><ttl>???TITLE???</ttl><xreftext>rows_per_wal</xreftext></obj><obj element="entry" href="#wal_writer_inbox_size" number="" targetptr="wal_writer_inbox_size"><ttl>???TITLE???</ttl><xreftext>wal_writer_inbox_size</xreftext></obj><obj element="table" href="#idp1020912" number="6.4"><ttl>Replication</ttl><xreftext>Table 6.4, “Replication”</xreftext></obj><obj element="entry" href="#replication_port" number="" targetptr="replication_port"><ttl>???TITLE???</ttl><xreftext>replication_port</xreftext></obj><obj element="entry" href="#replication_source" number="" targetptr="replication_source"><ttl>???TITLE???</ttl><xreftext>replication_source</xreftext></obj><obj element="table" href="#idp1039888" number="6.5"><ttl>Networking</ttl><xreftext>Table 6.5, “Networking”</xreftext></obj><obj element="table" href="#idp1058656" number="6.6"><ttl>Logging</ttl><xreftext>Table 6.6, “Logging”</xreftext></obj><obj element="table" href="#idp1083552" number="6.7"><ttl>Memcached protocol support</ttl><xreftext>Table 6.7, “Memcached protocol support”</xreftext></obj><obj element="anchor" href="#memcached_port" number="" targetptr="memcached_port"><ttl>???TITLE???</ttl><xreftext>memcached_port</xreftext></obj><obj element="anchor" href="#memcached_space" number="" targetptr="memcached_space"><ttl>???TITLE???</ttl><xreftext>memcached_space</xreftext></obj><obj element="anchor" href="#memcached_expire" number="" targetptr="memcached_expire"><ttl>???TITLE???</ttl><xreftext>memcached_expire</xreftext></obj></div></div><div element="chapter" href="#connectors" number="7" targetptr="connectors"><ttl>Connectors</ttl><xreftext>Chapter 7, <i>Connectors</i></xreftext><div element="section" href="#idp191920" number=""><ttl>C</ttl><xreftext>the section called “C”</xreftext></div><div element="section" href="#idp678512" number=""><ttl>Perl</ttl><xreftext>the section called “Perl”</xreftext></div><div element="section" href="#idp680512" number=""><ttl>PHP</ttl><xreftext>the section called “PHP”</xreftext></div><div element="section" href="#idp734112" number=""><ttl>Python</ttl><xreftext>the section called “Python”</xreftext></div><div element="section" href="#idp757312" number=""><ttl>Ruby</ttl><xreftext>the section called “Ruby”</xreftext></div></div><div element="appendix" href="#proctitle" number="A" targetptr="proctitle"><ttl>Server process titles</ttl><xreftext>Appendix A, <i>Server process titles</i></xreftext></div><div element="appendix" href="#errcode" number="B" targetptr="errcode"><ttl>List of error codes</ttl><xreftext>Appendix B, <i>List of error codes</i></xreftext><obj element="term" href="#ER_NONMASTER" number="" targetptr="ER_NONMASTER"><ttl>???TITLE???</ttl><xreftext>ER_NONMASTER</xreftext></obj><obj element="term" href="#ER_ILLEGAL_PARAMS" number="" targetptr="ER_ILLEGAL_PARAMS"><ttl>???TITLE???</ttl><xreftext>ER_ILLEGAL_PARAMS</xreftext></obj><obj element="term" href="#ER_TUPLE_IS_RO" number="" targetptr="ER_TUPLE_IS_RO"><ttl>???TITLE???</ttl><xreftext>ER_TUPLE_IS_RO</xreftext></obj><obj element="term" href="#ER_MEMORY_ISSUE" number="" targetptr="ER_MEMORY_ISSUE"><ttl>???TITLE???</ttl><xreftext>ER_MEMORY_ISSUE</xreftext></obj><obj element="term" href="#ER_WAL_IO" number="" targetptr="ER_WAL_IO"><ttl>???TITLE???</ttl><xreftext>ER_WAL_IO</xreftext></obj><obj element="term" href="#ER_INDEX_VIOLATION" number="" targetptr="ER_INDEX_VIOLATION"><ttl>???TITLE???</ttl><xreftext>ER_INDEX_VIOLATION</xreftext></obj><obj element="term" href="#ER_NO_SUCH_SPACE" number="" targetptr="ER_NO_SUCH_SPACE"><ttl>???TITLE???</ttl><xreftext>ER_NO_SUCH_SPACE</xreftext></obj><obj element="term" href="#ER_NO_SUCH_INDEX" number="" targetptr="ER_NO_SUCH_INDEX"><ttl>???TITLE???</ttl><xreftext>ER_NO_SUCH_INDEX</xreftext></obj><obj element="term" href="#ER_PROC_LUA" number="" targetptr="ER_PROC_LUA"><ttl>???TITLE???</ttl><xreftext>ER_PROC_LUA</xreftext></obj></div></div>
diff --git a/doc/user/user.xml b/doc/user/user.xml
index 2ae51a5595c943134b669b9915ee4ff5a08947aa..fec0f7bc3d87e378a3f005b33ff3e47468dbaa5b 100644
--- a/doc/user/user.xml
+++ b/doc/user/user.xml
@@ -10,7 +10,7 @@
 <title>Tarantool/Box User Guide, version &tnt_version;</title>
 <xi:include href="preface.xml"/>
 <xi:include href="tutorial.xml"/>
-<xi:include href="data-model.xml"/>
+<xi:include href="data-and-persistence.xml"/>
 <xi:include href="language-reference.xml"/>
 <xi:include href="replication.xml"/>
 <xi:include href="configuration-reference.xml"/>
diff --git a/doc/www-data/index.html b/doc/www-data/index.html
index b072a087296dec9c5f2d305ec9e5455f48f6be6a..91c52bc9a8cc6815a4691b4d4ef3360c7d837318 100644
--- a/doc/www-data/index.html
+++ b/doc/www-data/index.html
@@ -111,7 +111,7 @@
     <div class="mBox w280">
       <br /><br />
       <h3>What's new</h3>
-      <p>2011-10-07: <a href="http://tarantool.org/dist/tarantool-1.4.3-3-g45804c6-src.tar.gz">Tarantool 1.4.3</a>, stable version of 1.4 branch.</p>
+      <p>2011-12-14: <a href="http://tarantool.org/dist/tarantool-1.4.4-2-g9b208cb-src.tar.gz">Tarantool 1.4.4</a>, a maintenace version of stable 1.4 branch.</p>
       <p>2011-08-25: Tarantool 1.4.2, featuring Lua stored prcoedures, is out.</p>
       <p>2011-06-05: <a href="http://tarantool.org/tarantool_user_guide.html">Tarantool User Guide</a> is published.</p>
       <p>2011-05-14: <a href="http://launchpad.net/tarantool/1.3/1.3.5/+download/tarantool-1.3.5-src.tar.gz">tarantool-1.3.5</a> (stable) is out.</p>
diff --git a/doc/www-data/index.ru.html b/doc/www-data/index.ru.html
index b07bc6165749855952c8421ae09aae0686275efd..a34379a4e379cca687c1136440030324c600ab15 100644
--- a/doc/www-data/index.ru.html
+++ b/doc/www-data/index.ru.html
@@ -100,7 +100,7 @@
     <div class="mBox w280">
       <br /><br />
       <h3>Новости</h3>
-      <p>2011-10-07: <a href="http://tarantool.org/dist/tarantool-1.4.3-3-g45804c6-src.tar.gz">Tarantool 1.4.3</a>, стабильная версия ветки 1.4.</p>
+      <p>2011-12-14: <a href="http://tarantool.org/dist/tarantool-1.4.4-2-g9b208cb-src.tar.gz">Tarantool 1.4.4</a>, стабильная версия ветки 1.4.</p>
       <p>2011-08-25: Tarantool 1.4.2, с поддержкой Lua.</p>
       <p>2011-06-05: Опубликован <a href="http://tarantool.org/tarantool_user_guide.html">tarantool user guide</a>.</p>
       <p>2011-05-14: <a href="http://launchpad.net/tarantool/1.3/1.3.5/+download/tarantool-1.3.5-src.tar.gz">tarantool-1.3.5</a>, стабильная версия.</p>
diff --git a/include/config.h.cmake b/include/config.h.cmake
index 69fd01096760d7bdb936d973f08efdcb32531577..80ae5a6c5d0a2c109c5e97ae928c8d4d90b80a2c 100644
--- a/include/config.h.cmake
+++ b/include/config.h.cmake
@@ -51,7 +51,7 @@
  */
 #cmakedefine HAVE_BYTE_ORDER_BIG_ENDIAN 1
 
-#define INSTALL_PREFIX "@CMAKE_INSTALL_PREFIX@"
+#define SYSCONF_DIR "@CMAKE_SYSCONF_DIR@"
 /*
  * vim: syntax=c
  */
diff --git a/include/cpu_feature.h b/include/cpu_feature.h
new file mode 100644
index 0000000000000000000000000000000000000000..7a97590861a2fe6c63d619940bd3138b97d7cb7f
--- /dev/null
+++ b/include/cpu_feature.h
@@ -0,0 +1,51 @@
+#ifndef TARANTOOL_CPU_FEATURES_H
+#define TARANTOOL_CPU_FEATURES_H
+/*
+ * Copyright (C) 2010 Mail.RU
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED.  IN NO EVENT SHALL AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#include <sys/types.h>
+
+/* CPU feature capabilities to use with cpu_has (feature) */
+enum {
+	cpuf_ht = 0, cpuf_sse4_1, cpuf_sse4_2, cpuf_hypervisor
+};
+
+/*	return 1=feature is available, 0=unavailable, -EINVAL = unsupported CPU,
+	-ERANGE = invalid feature
+*/
+int cpu_has (unsigned int feature);
+
+
+/* hardware-calculate CRC32 for the given data buffer
+ * NB: 	requires 1 == cpu_has (cpuf_sse4_2),
+ * 		CALLING IT W/O CHECKING for sse4_2 CAN CAUSE SIGABRT
+ */
+u_int32_t crc32c_hw(u_int32_t crc, unsigned char const *p, size_t len);
+
+
+#endif /* TARANTOOL_CPU_FEATURES_H */
+
+/* __EOF__ */
+
diff --git a/include/tarantool.h b/include/tarantool.h
index 9ba2910d824036f0bba351a69e4eb4534fab56ff..9e63e889fd8ad7fe8f2ec1373d87c7964d4a8c91 100644
--- a/include/tarantool.h
+++ b/include/tarantool.h
@@ -66,6 +66,7 @@ tarantool_lua_register_type(struct lua_State *L, const char *type_name,
  * @return  L on success, 0 if out of memory
  */
 struct lua_State *tarantool_lua_init();
+void tarantool_lua_close(struct lua_State *L);
 
 /*
  * Single global lua_State shared by core and modules.
diff --git a/mod/box/CMakeLists.txt b/mod/box/CMakeLists.txt
index 71f40692e03a321ca1294e3637fa624a782af1c6..e0ad40a566d9027293863dba6d4f2f9ee598bf44 100644
--- a/mod/box/CMakeLists.txt
+++ b/mod/box/CMakeLists.txt
@@ -16,7 +16,7 @@ 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
+    COMMAND cd ${CMAKE_SOURCE_DIR}/mod/box && ${LD} -r -b binary box.lua -o ${CMAKE_CURRENT_BINARY_DIR}/box.lua.o
     DEPENDS box.lua)
 
 set_source_files_properties(box.lua.o
diff --git a/mod/box/box.lua b/mod/box/box.lua
index 9a1a0cd8f641ec7cfd35285a0291d3896c364a6d..1556becb922fc018ae3118a5c439e29b95687607 100644
--- a/mod/box/box.lua
+++ b/mod/box/box.lua
@@ -20,7 +20,7 @@ end
 -- starts from the key.
 --
 function box.select_range(sno, ino, limit, ...)
-    return box.space[tonumber(sno)].index[tonumber(ino)]:range(tonumber(limit), ...)
+    return box.space[tonumber(sno)].index[tonumber(ino)]:select_range(tonumber(limit), ...)
 end
 
 --
@@ -57,7 +57,7 @@ function box.insert(space, ...)
                                 unpack(tuple)))
 end
 
--- 
+--
 function box.update(space, key, format, ...)
     local ops = {...}
     return box.process(19,
@@ -84,7 +84,10 @@ function box.on_reload_configuration()
     index_mt.pairs = function(index)
         return index.idx.next, index.idx, nil end
     --
-    index_mt.range = function(index, limit, ...)
+    index_mt.next = function(index, ...)
+        return index.idx:next(...) end
+    --
+    index_mt.select_range = function(index, limit, ...)
         local range = {}
         for k, v in index.idx.next, index.idx, ... do
             if #range >= limit then
@@ -99,6 +102,9 @@ function box.on_reload_configuration()
     space_mt.len = function(space) return space.index[0]:len() end
     space_mt.__newindex = index_mt.__newindex
     space_mt.select = function(space, ...) return box.select(space.n, ...) end
+    space_mt.select_range = function(space, ino, limit, ...)
+        return space.index[ino]:select_range(limit, ...)
+    end
     space_mt.insert = function(space, ...) return box.insert(space.n, ...) end
     space_mt.update = function(space, ...) return box.update(space.n, ...) end
     space_mt.replace = function(space, ...) return box.replace(space.n, ...) end
@@ -128,3 +134,11 @@ if initfile ~= nil then
     io.close(initfile)
     dofile("init.lua")
 end
+-- security: nullify some of the most serious os.* holes
+--
+os.execute = nil
+os.exit = nil
+os.rename = nil
+os.tmpname = nil
+os.remove = nil
+require = nil
diff --git a/mod/box/box.m b/mod/box/box.m
index c62a3c6b3a24d65fecd4255e69f2f8cfb7bf067b..e50d4ba9c858129a0098015b79d4905184399746 100644
--- a/mod/box/box.m
+++ b/mod/box/box.m
@@ -126,27 +126,27 @@ validate_indexes(struct box_txn *txn)
 	/* There is more than one index. */
 	foreach_index(txn->n, index) {
 		/* XXX: skip the first index here! */
-		for (u32 f = 0; f < index->key.part_count; ++f) {
-			if (index->key.parts[f].fieldno >= txn->tuple->cardinality)
+		for (u32 f = 0; f < index->key_def.part_count; ++f) {
+			if (index->key_def.parts[f].fieldno >= txn->tuple->cardinality)
 				tnt_raise(IllegalParams, :"tuple must have all indexed fields");
 
-			if (index->key.parts[f].type == STRING)
+			if (index->key_def.parts[f].type == STRING)
 				continue;
 
-			void *field = tuple_field(txn->tuple, index->key.parts[f].fieldno);
+			void *field = tuple_field(txn->tuple, index->key_def.parts[f].fieldno);
 			u32 len = load_varint32(&field);
 
-			if (index->key.parts[f].type == NUM && len != sizeof(u32))
+			if (index->key_def.parts[f].type == NUM && len != sizeof(u32))
 				tnt_raise(IllegalParams, :"field must be NUM");
 
-			if (index->key.parts[f].type == NUM64 && len != sizeof(u64))
+			if (index->key_def.parts[f].type == NUM64 && len != sizeof(u64))
 				tnt_raise(IllegalParams, :"field must be NUM64");
 		}
-		if (index->type == TREE && index->key.is_unique == false)
+		if (index->type == TREE && index->key_def.is_unique == false)
 			/* Don't check non unique indexes */
 			continue;
 
-		struct box_tuple *tuple = [index findBy: txn->tuple];
+		struct box_tuple *tuple = [index findByTuple: txn->tuple];
 
 		if (tuple != NULL && tuple != txn->old_tuple)
 			tnt_raise(ClientError, :ER_INDEX_VIOLATION);
@@ -168,7 +168,7 @@ prepare_replace(struct box_txn *txn, size_t cardinality, struct tbuf *data)
 	txn->tuple->cardinality = cardinality;
 	memcpy(txn->tuple->data, data->data, data->size);
 
-	txn->old_tuple = [txn->index findBy: txn->tuple];
+	txn->old_tuple = [txn->index findByTuple: txn->tuple];
 
 	if (txn->old_tuple != NULL)
 		tuple_txn_ref(txn, txn->old_tuple);
@@ -184,8 +184,8 @@ prepare_replace(struct box_txn *txn, size_t cardinality, struct tbuf *data)
 	if (txn->old_tuple != NULL) {
 #ifndef NDEBUG
 		void *ka, *kb;
-		ka = tuple_field(txn->tuple, txn->index->key.parts[0].fieldno);
-		kb = tuple_field(txn->old_tuple, txn->index->key.parts[0].fieldno);
+		ka = tuple_field(txn->tuple, txn->index->key_def.parts[0].fieldno);
+		kb = tuple_field(txn->old_tuple, txn->index->key_def.parts[0].fieldno);
 		int kal, kab;
 		kal = load_varint32(&ka);
 		kab = load_varint32(&kb);
@@ -937,12 +937,12 @@ space_free(void)
 }
 
 static void
-key_init(struct key *key, struct tarantool_cfg_space_index *cfg_index)
+key_init(struct key_def *def, struct tarantool_cfg_space_index *cfg_index)
 {
-	key->max_fieldno = 0;
-	key->part_count = 0;
+	def->max_fieldno = 0;
+	def->part_count = 0;
 
-	/* calculate key cardinality and maximal field number */
+	/* Calculate key cardinality and maximal field number. */
 	for (int k = 0; cfg_index->key_field[k] != NULL; ++k) {
 		typeof(cfg_index->key_field[k]) cfg_key = cfg_index->key_field[k];
 
@@ -951,23 +951,23 @@ key_init(struct key *key, struct tarantool_cfg_space_index *cfg_index)
 			break;
 		}
 
-		key->max_fieldno = MAX(key->max_fieldno, cfg_key->fieldno);
-		key->part_count++;
+		def->max_fieldno = MAX(def->max_fieldno, cfg_key->fieldno);
+		def->part_count++;
 	}
 
-	/* init key array */
-	key->parts = salloc(sizeof(struct key_part) * key->part_count);
-	if (key->parts == NULL) {
-		panic("can't allocate key parts array for index");
+	/* init def array */
+	def->parts = salloc(sizeof(struct key_part) * def->part_count);
+	if (def->parts == NULL) {
+		panic("can't allocate def parts array for index");
 	}
 
 	/* init compare order array */
-	key->max_fieldno++;
-	key->cmp_order = salloc(key->max_fieldno * sizeof(u32));
-	if (key->cmp_order == NULL) {
-		panic("can't allocate key cmp_order array for index");
+	def->max_fieldno++;
+	def->cmp_order = salloc(def->max_fieldno * sizeof(u32));
+	if (def->cmp_order == NULL) {
+		panic("can't allocate def cmp_order array for index");
 	}
-	memset(key->cmp_order, -1, key->max_fieldno * sizeof(u32));
+	memset(def->cmp_order, -1, def->max_fieldno * sizeof(u32));
 
 	/* fill fields and compare order */
 	for (int k = 0; cfg_index->key_field[k] != NULL; ++k) {
@@ -979,12 +979,12 @@ key_init(struct key *key, struct tarantool_cfg_space_index *cfg_index)
 		}
 
 		/* fill keys */
-		key->parts[k].fieldno = cfg_key->fieldno;
-		key->parts[k].type = STR2ENUM(field_data_type, cfg_key->type);
+		def->parts[k].fieldno = cfg_key->fieldno;
+		def->parts[k].type = STR2ENUM(field_data_type, cfg_key->type);
 		/* fill compare order */
-		key->cmp_order[cfg_key->fieldno] = k;
+		def->cmp_order[cfg_key->fieldno] = k;
 	}
-	key->is_unique = cfg_index->unique;
+	def->is_unique = cfg_index->unique;
 }
 
 static void
@@ -1010,11 +1010,11 @@ space_config(void)
 		/* fill space indexes */
 		for (int j = 0; cfg_space->index[j] != NULL; ++j) {
 			typeof(cfg_space->index[j]) cfg_index = cfg_space->index[j];
-			struct key info;
-			key_init(&info, cfg_index);
+			struct key_def key_def;
+			key_init(&key_def, cfg_index);
 			enum index_type type = STR2ENUM(index_type, cfg_index->type);
-			Index *index = [Index alloc: type :&info];
-			[index init: type :&info :space + i :j];
+			Index *index = [Index alloc: type :&key_def];
+			[index init: type :&key_def:space + i :j];
 			space[i].index[j] = index;
 		}
 
@@ -1428,6 +1428,7 @@ void
 mod_free(void)
 {
 	space_free();
+	memcached_free();
 }
 
 void
diff --git a/mod/box/box_lua.m b/mod/box/box_lua.m
index d68efa40fdf0562f9b663a706988c7de18fe9d7e..b8a1364f502880b97fcbd7d7782271e8f2846a77 100644
--- a/mod/box/box_lua.m
+++ b/mod/box/box_lua.m
@@ -38,6 +38,7 @@
 
 #include "pickle.h"
 #include "tuple.h"
+#include "salloc.h"
 
 /* contents of box.lua */
 extern const char _binary_box_lua_start;
@@ -182,7 +183,7 @@ lbox_tuple_next(struct lua_State *L)
 {
 	struct box_tuple *tuple = lua_checktuple(L, 1);
 	int argc = lua_gettop(L) - 1;
-	u8 *field;
+	u8 *field = NULL;
 	size_t len;
 
 	if (argc == 0 || (argc == 1 && lua_type(L, 2) == LUA_TNIL))
@@ -192,6 +193,7 @@ lbox_tuple_next(struct lua_State *L)
 	else
 		luaL_error(L, "tuple.next(): bad arguments");
 
+	(void)field;
 	assert(field >= tuple->data);
 	if (field < tuple->data + tuple->bsize) {
 		len = load_varint32((void **) &field);
@@ -232,6 +234,32 @@ static const struct luaL_reg lbox_tuple_meta [] = {
  */
 
 static const char *indexlib_name = "box.index";
+static const char *iteratorlib_name = "box.index.iterator";
+
+static struct iterator *
+lua_checkiterator(struct lua_State *L, int i)
+{
+	struct iterator **it = luaL_checkudata(L, i, iteratorlib_name);
+	assert(it != NULL);
+	return *it;
+}
+
+static void
+lbox_pushiterator(struct lua_State *L, struct iterator *it)
+{
+	void **ptr = lua_newuserdata(L, sizeof(void *));
+	luaL_getmetatable(L, iteratorlib_name);
+	lua_setmetatable(L, -2);
+	*ptr = it;
+}
+
+static int
+lbox_iterator_gc(struct lua_State *L)
+{
+	struct iterator *it = lua_checkiterator(L, -1);
+	it->free(it);
+	return 0;
+}
 
 static Index *
 lua_checkindex(struct lua_State *L, int i)
@@ -363,18 +391,20 @@ lbox_index_next(struct lua_State *L)
 {
 	Index *index = lua_checkindex(L, 1);
 	int argc = lua_gettop(L) - 1;
+	struct iterator *it = NULL;
 	if (argc == 0 || (argc == 1 && lua_type(L, 2) == LUA_TNIL)) {
 		/*
 		 * If there is nothing or nil on top of the stack,
 		 * start iteration from the beginning.
 		 */
-		[index initIterator: index->position];
-	} else if (argc > 1 || !lua_islightuserdata(L, 2)) {
+		it = [index allocIterator];
+		[index initIterator: it];
+		lbox_pushiterator(L, it);
+	} else if (argc > 1 || lua_type(L, 2) != LUA_TUSERDATA) {
 		/*
 		 * We've got something different from iterator's
-		 * light userdata: must be a key
-		 * to start iteration from an offset. Seed the
-		 * iterator with this key.
+		 * userdata: must be a key to start iteration from
+		 * an offset. Seed the iterator with this key.
 		 */
 		int cardinality;
 		void *key;
@@ -390,7 +420,7 @@ lbox_index_next(struct lua_State *L)
 			struct tbuf *data = tbuf_alloc(fiber->gc_pool);
 			for (int i = 0; i < argc; ++i)
 				append_key_part(L, i + 2, data,
-						index->key.parts[i].type);
+						index->key_def.parts[i].type);
 			key = data->data;
 		}
 		/*
@@ -399,15 +429,17 @@ lbox_index_next(struct lua_State *L)
 		 * keys.
 		*/
 		assert(cardinality != 0);
-		if (cardinality > index->key.part_count)
+		if (cardinality > index->key_def.part_count)
 			luaL_error(L, "index.next(): key part count (%d) "
 				   "does not match index cardinality (%d)",
-				   cardinality, index->key.part_count);
-		[index initIterator: index->position :key :cardinality];
+				   cardinality, index->key_def.part_count);
+		it = [index allocIterator];
+		[index initIterator: it :key :cardinality];
+		lbox_pushiterator(L, it);
+	} else { /* 1 item on the stack and it's a userdata. */
+		it = lua_checkiterator(L, 2);
 	}
-	struct box_tuple *tuple = index->position->next(index->position);
-	if (tuple)
-		lua_pushlightuserdata(L, index->position);
+	struct box_tuple *tuple = it->next(it);
 	/* If tuple is NULL, pushes nil as end indicator. */
 	lbox_pushtuple(L, tuple);
 	return tuple ? 2 : 1;
@@ -427,6 +459,11 @@ static const struct luaL_reg indexlib [] = {
 	{NULL, NULL}
 };
 
+static const struct luaL_reg lbox_iterator_meta[] = {
+	{"__gc", lbox_iterator_gc},
+	{NULL, NULL}
+};
+
 /* }}} */
 
 /** {{{ Lua I/O: facilities to intercept box output
@@ -713,6 +750,7 @@ mod_lua_init(struct lua_State *L)
 	tarantool_lua_register_type(L, indexlib_name, lbox_index_meta);
 	luaL_register(L, "box.index", indexlib);
 	lua_pop(L, 1);
+	tarantool_lua_register_type(L, iteratorlib_name, lbox_iterator_meta);
 	/* Load box.lua */
 	if (luaL_dostring(L, &_binary_box_lua_start))
 		panic("Error loading box.lua: %s", lua_tostring(L, -1));
diff --git a/mod/box/index.h b/mod/box/index.h
index 05a59250a6e497ef9f2084d3adefa724ecae7b55..0cc6e85297219e7e4058c073699bad43b06de74b 100644
--- a/mod/box/index.h
+++ b/mod/box/index.h
@@ -51,7 +51,7 @@ struct key_part {
 };
 
 /* Descriptor of a multipart key. */
-struct key {
+struct key_def {
 	/* Description of parts of a multipart index. */
 	struct key_part *parts;
 	/*
@@ -85,21 +85,21 @@ struct key {
 	 */
 	struct iterator *position;
 	/* Description of a possibly multipart key. */
-	struct key key;
+	struct key_def key_def;
 	enum index_type type;
 	bool enabled;
 	/* Relative offset of the index in its namespace. */
 	u32 n;
 };
 
-+ (Index *) alloc: (enum index_type) type_arg :(struct key *) key_arg;
++ (Index *) alloc: (enum index_type) type_arg :(struct key_def *) key_def_arg;
 /**
  * Initialize index instance.
  *
  * @param space    space the index belongs to
  * @param key      key part description
  */
-- (id) init: (enum index_type) type_arg :(struct key *) key_arg
+- (id) init: (enum index_type) type_arg :(struct key_def *) key_def_arg
 	:(struct space *) space_arg :(u32) n_arg;
 /** Destroy and free index instance. */
 - (void) free;
@@ -111,7 +111,7 @@ struct key {
 - (struct box_tuple *) min;
 - (struct box_tuple *) max;
 - (struct box_tuple *) find: (void *) key_arg; /* only for unique lookups */
-- (struct box_tuple *) findBy: (struct box_tuple *) tuple;
+- (struct box_tuple *) findByTuple: (struct box_tuple *) tuple;
 - (void) remove: (struct box_tuple *) tuple;
 - (void) replace: (struct box_tuple *) old_tuple :(struct box_tuple *) new_tuple;
 /**
@@ -120,13 +120,14 @@ struct key {
  */
 - (struct iterator *) allocIterator;
 - (void) initIterator: (struct iterator *) iterator;
-- (void) initIterator: (struct iterator *) iterator :(void *) key_arg
+- (void) initIterator: (struct iterator *) iterator :(void *) key
 			:(int) part_count;
 @end
 
 struct iterator {
 	struct box_tuple *(*next)(struct iterator *);
 	struct box_tuple *(*next_equal)(struct iterator *);
+	void (*free)(struct iterator *);
 };
 
 #define foreach_index(n, index_var)					\
diff --git a/mod/box/index.m b/mod/box/index.m
index 6c75ed9b6ab942bf035e3decf7f9d2d912445747..7786efb7f70f0d356034526005b473267bc75828 100644
--- a/mod/box/index.m
+++ b/mod/box/index.m
@@ -57,14 +57,14 @@ iterator_first_equal(struct iterator *it)
 @class HashStrIndex;
 @class TreeIndex;
 
-+ (Index *) alloc: (enum index_type) type :(struct key *) key
++ (Index *) alloc: (enum index_type) type :(struct key_def *) key_def
 {
 	switch (type) {
 	case HASH:
 		/* Hash index, check key type.
 		 * Hash indes always has a single-field key.
 		 */
-		switch (key->parts[0].type) {
+		switch (key_def->parts[0].type) {
 		case NUM:
 			return [Hash32Index alloc]; /* 32-bit integer hash */
 		case NUM64:
@@ -83,11 +83,11 @@ iterator_first_equal(struct iterator *it)
 	panic("unsupported index type");
 }
 
-- (id) init: (enum index_type) type_arg :(struct key *) key_arg
+- (id) init: (enum index_type) type_arg :(struct key_def *) key_def_arg
 	:(struct space *) space_arg :(u32) n_arg;
 {
 	self = [super init];
-	key = *key_arg;
+	key_def = *key_def_arg;
 	type = type_arg;
 	n = n_arg;
 	space = space_arg;
@@ -98,9 +98,9 @@ iterator_first_equal(struct iterator *it)
 
 - (void) free
 {
-	sfree(key.parts);
-	sfree(key.cmp_order);
-	sfree(position);
+	sfree(key_def.parts);
+	sfree(key_def.cmp_order);
+	position->free(position);
 	[super free];
 }
 
@@ -127,14 +127,14 @@ iterator_first_equal(struct iterator *it)
 	return NULL;
 }
 
-- (struct box_tuple *) find: (void *) key_arg
+- (struct box_tuple *) find: (void *) key
 {
-	(void) key_arg;
+	(void) key;
 	[self subclassResponsibility: _cmd];
 	return NULL;
 }
 
-- (struct box_tuple *) findBy: (struct box_tuple *) pattern
+- (struct box_tuple *) findByTuple: (struct box_tuple *) pattern
 {
 	(void) pattern;
 	[self subclassResponsibility: _cmd];
@@ -167,12 +167,12 @@ iterator_first_equal(struct iterator *it)
 	[self subclassResponsibility: _cmd];
 }
 
-- (void) initIterator: (struct iterator *) iterator :(void *) key_arg
+- (void) initIterator: (struct iterator *) iterator :(void *) key
 			:(int) part_count
 {
 	(void) iterator;
 	(void) part_count;
-	(void) key_arg;
+	(void) key;
 	[self subclassResponsibility: _cmd];
 }
 @end
@@ -211,6 +211,13 @@ hash_iterator_next(struct iterator *iterator)
 	return NULL;
 }
 
+void
+hash_iterator_free(struct iterator *iterator)
+{
+	assert(iterator->next = hash_iterator_next);
+	sfree(iterator);
+}
+
 
 @implementation HashIndex
 - (void) free
@@ -230,21 +237,22 @@ hash_iterator_next(struct iterator *iterator)
 	return NULL;
 }
 
-- (struct box_tuple *) findBy: (struct box_tuple *) tuple
+- (struct box_tuple *) findByTuple: (struct box_tuple *) tuple
 {
 	/* Hash index currently is always single-part. */
-	void *field = tuple_field(tuple, key.parts[0].fieldno);
+	void *field = tuple_field(tuple, key_def.parts[0].fieldno);
 	if (field == NULL)
-		tnt_raise(ClientError, :ER_NO_SUCH_FIELD, key.parts[0].fieldno);
+		tnt_raise(ClientError, :ER_NO_SUCH_FIELD, key_def.parts[0].fieldno);
 	return [self find: field];
 }
 
 - (struct iterator *) allocIterator
 {
-	struct hash_iterator *it = salloc(sizeof(hash_iterator));
+	struct hash_iterator *it = salloc(sizeof(struct hash_iterator));
 	if (it) {
 		memset(it, 0, sizeof(struct hash_iterator));
 		it->base.next = hash_iterator_next;
+		it->base.free = hash_iterator_free;
 	}
 	return (struct iterator *) it;
 }
@@ -290,14 +298,14 @@ hash_iterator_next(struct iterator *iterator)
 	if (k != mh_end(int_hash))
 		ret = mh_value(int_hash, k);
 #ifdef DEBUG
-	say_debug("index_hash_num_find(self:%p, key:%i) = %p", self, num, ret);
+	say_debug("Hash32Index find(self:%p, key:%i) = %p", self, num, ret);
 #endif
 	return ret;
 }
 
 - (void) remove: (struct box_tuple *) tuple
 {
-	void *field = tuple_field(tuple, self->key.parts[0].fieldno);
+	void *field = tuple_field(tuple, key_def.parts[0].fieldno);
 	unsigned int field_size = load_varint32(&field);
 	u32 num = *(u32 *)field;
 
@@ -308,14 +316,14 @@ hash_iterator_next(struct iterator *iterator)
 	if (k != mh_end(int_hash))
 		mh_i32ptr_del(int_hash, k);
 #ifdef DEBUG
-	say_debug("index_hash_num_remove(self:%p, key:%i)", self, num);
+	say_debug("Hash32Index remove(self:%p, key:%i)", self, num);
 #endif
 }
 
 - (void) replace: (struct box_tuple *) old_tuple
 	:(struct box_tuple *) new_tuple
 {
-	void *field = tuple_field(new_tuple, key.parts[0].fieldno);
+	void *field = tuple_field(new_tuple, key_def.parts[0].fieldno);
 	u32 field_size = load_varint32(&field);
 	u32 num = *(u32 *)field;
 
@@ -323,7 +331,7 @@ hash_iterator_next(struct iterator *iterator)
 		tnt_raise(IllegalParams, :"key is not u32");
 
 	if (old_tuple != NULL) {
-		void *old_field = tuple_field(old_tuple, key.parts[0].fieldno);
+		void *old_field = tuple_field(old_tuple, key_def.parts[0].fieldno);
 		load_varint32(&old_field);
 		u32 old_num = *(u32 *)old_field;
 		mh_int_t k = mh_i32ptr_get(int_hash, old_num);
@@ -334,7 +342,7 @@ hash_iterator_next(struct iterator *iterator)
 	mh_i32ptr_put(int_hash, num, new_tuple, NULL);
 
 #ifdef DEBUG
-	say_debug("index_hash_num_replace(self:%p, old_tuple:%p, new_tuple:%p) key:%i",
+	say_debug("Hash32Index replace(self:%p, old_tuple:%p, new_tuple:%p) key:%i",
 		  self, old_tuple, new_tuple, num);
 #endif
 }
@@ -350,16 +358,17 @@ hash_iterator_next(struct iterator *iterator)
 	it->hash = int_hash;
 }
 
-- (void) initIterator: (struct iterator *) iterator :(void *) key_arg
+- (void) initIterator: (struct iterator *) iterator :(void *) key
 			:(int) part_count
 {
 	struct hash_iterator *it = hash_iterator(iterator);
 
+	(void) part_count;
 	assert(part_count == 1);
 	assert(iterator->next = hash_iterator_next);
 
-	u32 field_size = load_varint32(&key_arg);
-	u32 num = *(u32 *)key_arg;
+	u32 field_size = load_varint32(&key);
+	u32 num = *(u32 *)key;
 
 	if (field_size != 4)
 		tnt_raise(IllegalParams, :"key is not u32");
@@ -410,14 +419,14 @@ hash_iterator_next(struct iterator *iterator)
 	if (k != mh_end(int64_hash))
 		ret = mh_value(int64_hash, k);
 #ifdef DEBUG
-	say_debug("index_hash_num64_find(self:%p, key:%"PRIu64") = %p", self, num, ret);
+	say_debug("Hash64Index find(self:%p, key:%"PRIu64") = %p", self, num, ret);
 #endif
 	return ret;
 }
 
 - (void) remove: (struct box_tuple *) tuple
 {
-	void *field = tuple_field(tuple, key.parts[0].fieldno);
+	void *field = tuple_field(tuple, key_def.parts[0].fieldno);
 	unsigned int field_size = load_varint32(&field);
 	u64 num = *(u64 *)field;
 
@@ -428,14 +437,14 @@ hash_iterator_next(struct iterator *iterator)
 	if (k != mh_end(int64_hash))
 		mh_i64ptr_del(int64_hash, k);
 #ifdef DEBUG
-	say_debug("index_hash_num64_remove(self:%p, key:%"PRIu64")", self, num);
+	say_debug("Hash64Index remove(self:%p, key:%"PRIu64")", self, num);
 #endif
 }
 
 - (void) replace: (struct box_tuple *) old_tuple
 	:(struct box_tuple *) new_tuple
 {
-	void *field = tuple_field(new_tuple, key.parts[0].fieldno);
+	void *field = tuple_field(new_tuple, key_def.parts[0].fieldno);
 	u32 field_size = load_varint32(&field);
 	u64 num = *(u64 *)field;
 
@@ -443,7 +452,8 @@ hash_iterator_next(struct iterator *iterator)
 		tnt_raise(IllegalParams, :"key is not u64");
 
 	if (old_tuple != NULL) {
-		void *old_field = tuple_field(old_tuple, key.parts[0].fieldno);
+		void *old_field = tuple_field(old_tuple,
+					      key_def.parts[0].fieldno);
 		load_varint32(&old_field);
 		u64 old_num = *(u64 *)old_field;
 		mh_int_t k = mh_i64ptr_get(int64_hash, old_num);
@@ -453,7 +463,7 @@ hash_iterator_next(struct iterator *iterator)
 
 	mh_i64ptr_put(int64_hash, num, new_tuple, NULL);
 #ifdef DEBUG
-	say_debug("index_hash_num64_replace(self:%p, old_tuple:%p, tuple:%p) key:%"PRIu64,
+	say_debug("Hash64Index replace(self:%p, old_tuple:%p, tuple:%p) key:%"PRIu64,
 		  self, old_tuple, new_tuple, num);
 #endif
 }
@@ -475,6 +485,7 @@ hash_iterator_next(struct iterator *iterator)
 {
 	assert(iterator->next = hash_iterator_next);
 	assert(part_count == 1);
+	(void) part_count;
 
 	struct hash_iterator *it = hash_iterator(iterator);
 
@@ -526,7 +537,7 @@ hash_iterator_next(struct iterator *iterator)
 		ret = mh_value(str_hash, k);
 #ifdef DEBUG
 	u32 field_size = load_varint32(&field);
-	say_debug("index_hash_str_find(self:%p, key:(%i)'%.*s') = %p",
+	say_debug("HashStrIndex find(self:%p, key:(%i)'%.*s') = %p",
 		  self, field_size, field_size, (u8 *)field, ret);
 #endif
 	return ret;
@@ -534,14 +545,14 @@ hash_iterator_next(struct iterator *iterator)
 
 - (void) remove: (struct box_tuple *) tuple
 {
-	void *field = tuple_field(tuple, key.parts[0].fieldno);
+	void *field = tuple_field(tuple, key_def.parts[0].fieldno);
 
 	mh_int_t k = mh_lstrptr_get(str_hash, field);
 	if (k != mh_end(str_hash))
 		mh_lstrptr_del(str_hash, k);
 #ifdef DEBUG
 	u32 field_size = load_varint32(&field);
-	say_debug("index_hash_str_remove(self:%p, key:'%.*s')",
+	say_debug("HashStrIndex remove(self:%p, key:'%.*s')",
 		  self, field_size, (u8 *)field);
 #endif
 }
@@ -549,13 +560,15 @@ hash_iterator_next(struct iterator *iterator)
 - (void) replace: (struct box_tuple *) old_tuple
 	:(struct box_tuple *) new_tuple
 {
-	void *field = tuple_field(new_tuple, key.parts[0].fieldno);
+	void *field = tuple_field(new_tuple, key_def.parts[0].fieldno);
 
 	if (field == NULL)
-		tnt_raise(ClientError, :ER_NO_SUCH_FIELD, key.parts[0].fieldno);
+		tnt_raise(ClientError, :ER_NO_SUCH_FIELD,
+			  key_def.parts[0].fieldno);
 
 	if (old_tuple != NULL) {
-		void *old_field = tuple_field(old_tuple, key.parts[0].fieldno);
+		void *old_field = tuple_field(old_tuple,
+					      key_def.parts[0].fieldno);
 		mh_int_t k = mh_lstrptr_get(str_hash, old_field);
 		if (k != mh_end(str_hash))
 			mh_lstrptr_del(str_hash, k);
@@ -564,7 +577,7 @@ hash_iterator_next(struct iterator *iterator)
 	mh_lstrptr_put(str_hash, field, new_tuple, NULL);
 #ifdef DEBUG
 	u32 field_size = load_varint32(&field);
-	say_debug("index_hash_str_replace(self:%p, old_tuple:%p, tuple:%p) key:'%.*s'",
+	say_debug("HashStrIndex replace(self:%p, old_tuple:%p, tuple:%p) key:'%.*s'",
 		  self, old_tuple, new_tuple, field_size, (u8 *)field);
 #endif
 }
@@ -580,16 +593,17 @@ hash_iterator_next(struct iterator *iterator)
 	it->hash = (struct mh_i32ptr_t *) str_hash;
 }
 
-- (void) initIterator: (struct iterator *) iterator :(void *) field
+- (void) initIterator: (struct iterator *) iterator :(void *) key
 			:(int) part_count
 {
 	assert(iterator->next = hash_iterator_next);
 	assert(part_count== 1);
+	(void) part_count;
 
 	struct hash_iterator *it = hash_iterator(iterator);
 
 	it->base.next_equal = iterator_first_equal;
-	it->h_pos = mh_lstrptr_get(str_hash, field);
+	it->h_pos = mh_lstrptr_get(str_hash, key);
 	it->hash = (struct mh_i32ptr_t *) str_hash;
 }
 @end
@@ -672,21 +686,21 @@ field_compare(struct field *f1, struct field *f2, enum field_data_type type)
 	panic("impossible happened");
 }
 
-struct index_tree_el {
+struct tree_el {
 	struct box_tuple *tuple;
 	struct field key[];
 };
 
-#define INDEX_TREE_EL_SIZE(key) \
-	(sizeof(struct index_tree_el) + sizeof(struct field) * (key)->part_count)
+#define TREE_EL_SIZE(key) \
+	(sizeof(struct tree_el) + sizeof(struct field) * (key)->part_count)
 
 void
-index_tree_el_init(struct index_tree_el *elem,
-		   struct key *key, struct box_tuple *tuple)
+tree_el_init(struct tree_el *elem,
+		   struct key_def *key_def, struct box_tuple *tuple)
 {
 	void *tuple_data = tuple->data;
 
-	for (i32 i = 0; i < key->max_fieldno; ++i) {
+	for (i32 i = 0; i < key_def->max_fieldno; ++i) {
 		struct field f;
 
 		if (i < tuple->cardinality) {
@@ -700,48 +714,48 @@ index_tree_el_init(struct index_tree_el *elem,
 		} else
 			f = ASTERISK;
 
-		u32 key_field_no = key->cmp_order[i];
+		u32 fieldno = key_def->cmp_order[i];
 
-		if (key_field_no == -1)
+		if (fieldno == -1)
 			continue;
 
-		if (key->parts[key_field_no].type == NUM) {
+		if (key_def->parts[fieldno].type == NUM) {
 			if (f.len != 4)
 				tnt_raise(IllegalParams, :"key is not u32");
-		} else if (key->parts[key_field_no].type == NUM64 && f.len != 8) {
+		} else if (key_def->parts[fieldno].type == NUM64 && f.len != 8) {
 				tnt_raise(IllegalParams, :"key is not u64");
 		}
 
-		elem->key[key_field_no] = f;
+		elem->key[fieldno] = f;
 	}
 	elem->tuple = tuple;
 }
 
 void
-init_search_pattern(struct index_tree_el *pattern,
-		    struct key *key, int part_count, void *key_field)
+init_search_pattern(struct tree_el *pattern,
+		    struct key_def *key_def, int part_count, void *key)
 {
-	assert(part_count <= key->part_count);
+	assert(part_count <= key_def->part_count);
 
-	for (i32 i = 0; i < key->part_count; ++i)
+	for (i32 i = 0; i < key_def->part_count; ++i)
 		pattern->key[i] = ASTERISK;
 	for (int i = 0; i < part_count; i++) {
 		u32 len;
 
-		len = pattern->key[i].len = load_varint32(&key_field);
-		if (key->parts[i].type == NUM) {
+		len = pattern->key[i].len = load_varint32(&key);
+		if (key_def->parts[i].type == NUM) {
 			if (len != 4)
 				tnt_raise(IllegalParams, :"key is not u32");
-		} else if (key->parts[i].type == NUM64 && len != 8) {
+		} else if (key_def->parts[i].type == NUM64 && len != 8) {
 				tnt_raise(IllegalParams, :"key is not u64");
 		}
 		if (len <= sizeof(pattern->key[i].data)) {
 			memset(pattern->key[i].data, 0, sizeof(pattern->key[i].data));
-			memcpy(pattern->key[i].data, key_field, len);
+			memcpy(pattern->key[i].data, key, len);
 		} else
-			pattern->key[i].data_ptr = key_field;
+			pattern->key[i].data_ptr = key;
 
-		key_field += len;
+		key += len;
 	}
 
 	pattern->tuple = NULL;
@@ -756,14 +770,14 @@ init_search_pattern(struct index_tree_el *pattern,
  *              > 0  - a is greater than b
  */
 static int
-index_tree_el_unique_cmp(struct index_tree_el *elem_a,
-			 struct index_tree_el *elem_b,
-			 struct key *key)
+tree_el_unique_cmp(struct tree_el *elem_a,
+			 struct tree_el *elem_b,
+			 struct key_def *key_def)
 {
 	int r = 0;
-	for (i32 i = 0, end = key->part_count; i < end; ++i) {
+	for (i32 i = 0, end = key_def->part_count; i < end; ++i) {
 		r = field_compare(&elem_a->key[i], &elem_b->key[i],
-				  key->parts[i].type);
+				  key_def->parts[i].type);
 		if (r != 0)
 			break;
 	}
@@ -771,10 +785,10 @@ index_tree_el_unique_cmp(struct index_tree_el *elem_a,
 }
 
 static int
-index_tree_el_cmp(struct index_tree_el *elem_a, struct index_tree_el *elem_b,
-		  struct key *key)
+tree_el_cmp(struct tree_el *elem_a, struct tree_el *elem_b,
+		  struct key_def *key_def)
 {
-	int r = index_tree_el_unique_cmp(elem_a, elem_b, key);
+	int r = tree_el_unique_cmp(elem_a, elem_b, key_def);
 	if (r == 0 && elem_a->tuple && elem_b->tuple)
 		r = (elem_a->tuple < elem_b->tuple ?
 		     -1 : elem_a->tuple > elem_b->tuple);
@@ -786,7 +800,7 @@ SPTREE_DEF(str_t, realloc);
 
 @interface TreeIndex: Index {
 	sptree_str_t *tree;
-	struct index_tree_el *pattern;
+	struct tree_el *pattern;
 };
 - (void) build: (Index *) pk;
 @end
@@ -794,9 +808,8 @@ SPTREE_DEF(str_t, realloc);
 struct tree_iterator {
 	struct iterator base;
 	struct sptree_str_t_iterator *t_iter;
-	struct index_tree_el *pattern;
-	sptree_str_t *tree;
-	struct key *key;
+	struct tree_el *pattern;
+	struct key_def *key_def;
 };
 
 static inline struct tree_iterator *
@@ -812,11 +825,22 @@ tree_iterator_next(struct iterator *iterator)
 
 	struct tree_iterator *it = tree_iterator(iterator);
 
-	struct index_tree_el *elem = sptree_str_t_iterator_next(it->t_iter);
+	struct tree_el *elem = sptree_str_t_iterator_next(it->t_iter);
 
 	return elem ? elem->tuple : NULL;
 }
 
+void
+tree_iterator_free(struct iterator *iterator)
+{
+	assert(iterator->next == tree_iterator_next);
+	struct tree_iterator *it = tree_iterator(iterator);
+
+	if (it->t_iter)
+		sptree_str_t_iterator_free(it->t_iter);
+	sfree(it);
+}
+
 static struct box_tuple *
 tree_iterator_next_equal(struct iterator *iterator)
 {
@@ -824,11 +848,13 @@ tree_iterator_next_equal(struct iterator *iterator)
 
 	struct tree_iterator *it = tree_iterator(iterator);
 
-	struct index_tree_el *elem =
+	struct tree_el *elem =
 		sptree_str_t_iterator_next(it->t_iter);
 
-	if (elem != NULL && index_tree_el_unique_cmp(it->pattern, elem, it->key) == 0)
+	if (elem != NULL &&
+	    tree_el_unique_cmp(it->pattern, elem, it->key_def) == 0) {
 		return elem->tuple;
+	}
 
 	return NULL;
 }
@@ -838,6 +864,7 @@ tree_iterator_next_equal(struct iterator *iterator)
 - (void) free
 {
 	sfree(pattern);
+	sptree_str_t_destroy(tree);
 	sfree(tree);
 	[super free];
 }
@@ -845,14 +872,14 @@ tree_iterator_next_equal(struct iterator *iterator)
 - (void) enable
 {
 	enabled = false;
-	pattern = salloc(INDEX_TREE_EL_SIZE(&key));
+	pattern = salloc(TREE_EL_SIZE(&key_def));
 	tree = salloc(sizeof(*tree));
 	memset(tree, 0, sizeof(*tree));
 	if (n == 0) {/* pk */
 		sptree_str_t_init(tree,
-				  INDEX_TREE_EL_SIZE(&key),
+				  TREE_EL_SIZE(&key_def),
 				  NULL, 0, 0,
-				  (void *)index_tree_el_unique_cmp, &key);
+				  (void *)tree_el_unique_cmp, &key_def);
 		enabled = true;
 	}
 }
@@ -864,84 +891,88 @@ tree_iterator_next_equal(struct iterator *iterator)
 
 - (struct box_tuple *) min
 {
-	struct index_tree_el *elem = sptree_str_t_first(tree);
+	struct tree_el *elem = sptree_str_t_first(tree);
 
 	return elem ? elem->tuple : NULL;
 }
 
 - (struct box_tuple *) max
 {
-	struct index_tree_el *elem = sptree_str_t_last(tree);
+	struct tree_el *elem = sptree_str_t_last(tree);
 
 	return elem ? elem->tuple : NULL;
 }
 
-- (struct box_tuple *) find: (void *) key_arg
+- (struct box_tuple *) find: (void *) key
 {
-	init_search_pattern(pattern, &key, 1, key_arg);
-	struct index_tree_el *elem = sptree_str_t_find(tree, pattern);
+	init_search_pattern(pattern, &key_def, 1, key);
+	struct tree_el *elem = sptree_str_t_find(tree, pattern);
 
 	return elem ? elem->tuple : NULL;
 }
 
-- (struct box_tuple *) findBy: (struct box_tuple *) tuple
+- (struct box_tuple *) findByTuple: (struct box_tuple *) tuple
 {
-	index_tree_el_init(pattern, &key, tuple);
+	tree_el_init(pattern, &key_def, tuple);
 
-	struct index_tree_el *elem = sptree_str_t_find(tree, pattern);
+	struct tree_el *elem = sptree_str_t_find(tree, pattern);
 
 	return elem ? elem->tuple : NULL;
 }
 
 - (void) remove: (struct box_tuple *) tuple
 {
-	index_tree_el_init(pattern, &key, tuple);
+	tree_el_init(pattern, &key_def, tuple);
 	sptree_str_t_delete(tree, pattern);
 }
 
 - (void) replace: (struct box_tuple *) old_tuple
 	:(struct box_tuple *) new_tuple
 {
-	if (new_tuple->cardinality < key.max_fieldno)
-		tnt_raise(ClientError, :ER_NO_SUCH_FIELD, key.max_fieldno);
+	if (new_tuple->cardinality < key_def.max_fieldno)
+		tnt_raise(ClientError, :ER_NO_SUCH_FIELD,
+			  key_def.max_fieldno);
 
 	if (old_tuple) {
-		index_tree_el_init(pattern, &key, old_tuple);
+		tree_el_init(pattern, &key_def, old_tuple);
 		sptree_str_t_delete(tree, pattern);
 	}
-	index_tree_el_init(pattern, &key, new_tuple);
+	tree_el_init(pattern, &key_def, new_tuple);
 	sptree_str_t_insert(tree, pattern);
 }
 
 - (struct iterator *) allocIterator
 {
 	struct tree_iterator *it = salloc(sizeof(struct tree_iterator) +
-					  INDEX_TREE_EL_SIZE(&key));
-	it->pattern = (struct index_tree_el *) (it + 1);
-	it->base.next = tree_iterator_next;
-	it->key = &key;
-	it->tree = tree;
+					  TREE_EL_SIZE(&key_def));
+	if (it) {
+		memset(it, 0, sizeof(struct tree_iterator));
+		it->base.next = tree_iterator_next;
+		it->base.free = tree_iterator_free;
+		it->pattern = (struct tree_el *) (it + 1);
+		it->key_def = &key_def;
+	}
 	return (struct iterator *) it;
 }
 
-- (void) initIterator: (struct iterator *) iterator
+- (void) initIterator: (struct iterator *) it
 {
-	[self initIterator: iterator :NULL :0];
+	[self initIterator: it :NULL :0];
 }
 
-- (void) initIterator: (struct iterator *) iterator :(void *) key_arg
+- (void) initIterator: (struct iterator *) iterator :(void *) key
 			:(int) part_count
 {
 	assert(iterator->next == tree_iterator_next);
 
 	struct tree_iterator *it = tree_iterator(iterator);
 
-	if (key.is_unique && part_count == key.part_count)
+	if (key_def.is_unique && part_count == key_def.part_count)
 		it->base.next_equal = iterator_first_equal;
 	else
 		it->base.next_equal = tree_iterator_next_equal;
 
-	init_search_pattern(it->pattern, &key, part_count, key_arg);
+	init_search_pattern(it->pattern, &key_def, part_count, key);
 	sptree_str_t_iterator_init_set(tree, &it->t_iter, it->pattern);
 }
 
@@ -952,19 +983,19 @@ tree_iterator_next_equal(struct iterator *iterator)
 
 	assert(enabled == false);
 
-	struct index_tree_el *elem = NULL;
+	struct tree_el *elem = NULL;
 	if (n_tuples) {
 		/*
 		 * Allocate a little extra to avoid
 		 * unnecessary realloc() when more data is
 		 * inserted.
 		*/
-		size_t sz = estimated_tuples * INDEX_TREE_EL_SIZE(&key);
+		size_t sz = estimated_tuples * TREE_EL_SIZE(&key_def);
 		elem = malloc(sz);
 		if (elem == NULL)
 			panic("malloc(): failed to allocate %"PRI_SZ" bytes", sz);
 	}
-	struct index_tree_el *m;
+	struct tree_el *m;
 	u32 i = 0;
 
 	struct iterator *it = pk->position;
@@ -972,10 +1003,10 @@ tree_iterator_next_equal(struct iterator *iterator)
 	struct box_tuple *tuple;
 	while ((tuple = it->next(it))) {
 
-		m = (struct index_tree_el *)
-			((char *)elem + i * INDEX_TREE_EL_SIZE(&key));
+		m = (struct tree_el *)
+			((char *)elem + i * TREE_EL_SIZE(&key_def));
 
-		index_tree_el_init(m, &key, tuple);
+		tree_el_init(m, &key_def, tuple);
 		++i;
 	}
 
@@ -983,10 +1014,10 @@ tree_iterator_next_equal(struct iterator *iterator)
 		say_info("Sorting %"PRIu32 " keys in index %" PRIu32 "...", n_tuples, self->n);
 
 	/* If n_tuples == 0 then estimated_tuples = 0, elem == NULL, tree is empty */
-	sptree_str_t_init(tree, INDEX_TREE_EL_SIZE(&key),
+	sptree_str_t_init(tree, TREE_EL_SIZE(&key_def),
 			  elem, n_tuples, estimated_tuples,
-			  (void *) (key.is_unique ? index_tree_el_unique_cmp :
-			  index_tree_el_cmp), &key);
+			  (void *) (key_def.is_unique ?  tree_el_unique_cmp
+				    : tree_el_cmp), &key_def);
 	enabled = true;
 }
 @end
diff --git a/mod/box/memcached.h b/mod/box/memcached.h
index b5ec2c64bba420b162e36a4cee1204817aecff59..10d4df108c2bd1a8c1fd9e6a581c018195dccc59 100644
--- a/mod/box/memcached.h
+++ b/mod/box/memcached.h
@@ -33,6 +33,9 @@ struct tarantool_cfg;
 void
 memcached_init();
 
+void
+memcached_free();
+
 void
 memcached_space_init();
 
diff --git a/mod/box/memcached.m b/mod/box/memcached.m
index 0b873e4501471fa4f791f5bc7be8b2ee612dc03a..1209cb3cbe495f603b495117b1e457bd60125866 100644
--- a/mod/box/memcached.m
+++ b/mod/box/memcached.m
@@ -47,6 +47,7 @@ static int stat_base;
 static struct fiber *memcached_expire = NULL;
 
 static Index *memcached_index;
+static struct iterator *memcached_it;
 
 /* memcached tuple format:
    <key, meta, data> */
@@ -288,7 +289,7 @@ flush_all(void *data)
 	while ((tuple = it->next(it))) {
 	       meta(tuple)->exptime = 1;
 	}
-	sfree(it);
+	it->free(it);
 }
 
 #define STORE									\
@@ -418,6 +419,13 @@ memcached_init(void)
 	memcached_index = space[cfg.memcached_space].index[0];
 }
 
+void
+memcached_free()
+{
+	if (memcached_it)
+		memcached_it->free(memcached_it);
+}
+
 void
 memcached_space_init()
 {
@@ -430,26 +438,26 @@ memcached_space_init()
 	memc_s->cardinality = 4;
 	memc_s->n = cfg.memcached_space;
 
-	struct key key;
+	struct key_def key_def;
 	/* Configure memcached index key. */
-	key.part_count = 1;
-	key.is_unique = true;
+	key_def.part_count = 1;
+	key_def.is_unique = true;
 
-	key.parts = salloc(sizeof(struct key_part));
-	key.cmp_order = salloc(sizeof(u32));
+	key_def.parts = salloc(sizeof(struct key_part));
+	key_def.cmp_order = salloc(sizeof(u32));
 
-	if (key.parts == NULL || key.cmp_order == NULL)
+	if (key_def.parts == NULL || key_def.cmp_order == NULL)
 		panic("out of memory when configuring memcached_space");
 
-	key.parts[0].fieldno = 0;
-	key.parts[0].type = STRING;
+	key_def.parts[0].fieldno = 0;
+	key_def.parts[0].type = STRING;
 
-	key.max_fieldno = 1;
-	key.cmp_order[0] = 0;
+	key_def.max_fieldno = 1;
+	key_def.cmp_order[0] = 0;
 
 	/* Configure memcached index. */
-	Index *memc_index = memc_s->index[0] = [Index alloc: HASH :&key];
-	[memc_index init: HASH :&key :memc_s :0];
+	Index *memc_index = memc_s->index[0] = [Index alloc: HASH :&key_def];
+	[memc_index init: HASH :&key_def :memc_s :0];
 }
 
 /** Delete a bunch of expired keys. */
@@ -488,17 +496,17 @@ memcached_expire_loop(void *data __attribute__((unused)))
 	struct box_tuple *tuple = NULL;
 
 	say_info("memcached expire fiber started");
-	struct iterator *it = [memcached_index allocIterator];
+	memcached_it = [memcached_index allocIterator];
 	@try {
 restart:
 		if (tuple == NULL)
-			[memcached_index initIterator: it];
+			[memcached_index initIterator: memcached_it];
 
 		struct tbuf *keys_to_delete = tbuf_alloc(fiber->gc_pool);
 
 		for (int j = 0; j < cfg.memcached_expire_per_loop; j++) {
 
-			tuple = it->next(it);
+			tuple = memcached_it->next(memcached_it);
 
 			if (tuple == NULL)
 				break;
@@ -513,7 +521,8 @@ restart:
 		fiber_gc();
 		goto restart;
 	} @finally {
-		sfree(it);
+		memcached_it->free(memcached_it);
+		memcached_it = NULL;
 	}
 }
 
@@ -523,7 +532,7 @@ void memcached_start_expire()
 		return;
 
 	assert(memcached_expire == NULL);
-	memcached_expire = fiber_create("memecached_expire", -1,
+	memcached_expire = fiber_create("memcached_expire", -1,
 					-1, memcached_expire_loop, NULL);
 	if (memcached_expire == NULL)
 		say_error("can't start the expire fiber");
diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt
index 1179fee969e67128fa66d3d920c3f72d818f0dcc..e7e1fadb89454ac36f68cb6cfca8444139fda4fd 100644
--- a/test/CMakeLists.txt
+++ b/test/CMakeLists.txt
@@ -1,9 +1,8 @@
 add_custom_target(test
-    COMMAND python ${PROJECT_SOURCE_DIR}/test/test-run.py --builddir=${PROJECT_BINARY_DIR} --vardir=${PROJECT_BINARY_DIR}/test/var
-    )
+    COMMAND python ${PROJECT_SOURCE_DIR}/test/test-run.py --builddir=${PROJECT_BINARY_DIR} --vardir=${PROJECT_BINARY_DIR}/test/var)
 
-tarantool_client("box/protocol" ${CMAKE_SOURCE_DIR}/test/box/protocol.c)
-tarantool_client("connector_c/tt" ${CMAKE_SOURCE_DIR}/test/connector_c/tt.c)
+add_subdirectory(box)
+add_subdirectory(connector_c)
 
 if ("${CPACK_GENERATOR}" STREQUAL "RPM")
 	install (FILES ${CMAKE_SOURCE_DIR}/test/box/tarantool.cfg
@@ -12,7 +11,7 @@ if ("${CPACK_GENERATOR}" STREQUAL "RPM")
 		 DESTINATION share/tarantool)
 else()
 	install (FILES ${CMAKE_SOURCE_DIR}/test/box/tarantool.cfg
-		DESTINATION bin)
+		DESTINATION "${CMAKE_SYSCONF_DIR}")
 	install (FILES ${CMAKE_SOURCE_DIR}/test/box/00000000000000000001.snap
-		DESTINATION bin)
+		DESTINATION "${CMAKE_LOCALSTATE_DIR}/lib/tarantool")
 endif()
diff --git a/test/box/CMakeLists.txt b/test/box/CMakeLists.txt
new file mode 100644
index 0000000000000000000000000000000000000000..40f4fa20edf3c7c199f5f52de0a233474884ff36
--- /dev/null
+++ b/test/box/CMakeLists.txt
@@ -0,0 +1,2 @@
+
+tarantool_client("protocol" protocol.c)
diff --git a/test/box/args.result b/test/box/args.result
index 7bf5ec89e516da4773186aabf15654d84593f3f4..eaf9313bfbb06245c6f0cbd792e53912e94e04b6 100644
--- a/test/box/args.result
+++ b/test/box/args.result
@@ -94,3 +94,10 @@ tarantool_box: creating `snapshots/00000000000000000001.snap.inprogress'
 tarantool_box: saving snapshot `snapshots/00000000000000000001.snap'
 tarantool_box: done
 
+#
+# A test case for Bug#897162, cat command should
+# not require a configuration file.
+
+tarantool_box --config=nonexists.cfg --cat=nonexists.xlog
+tarantool_box: access("nonexists.xlog"): No such file or directory
+
diff --git a/test/box/args.test b/test/box/args.test
index f51766a25af318a735dc407b3dbb6b6a1eb7d9db..2d014d162aafd4ee67245817bf08200b0a61b069 100644
--- a/test/box/args.test
+++ b/test/box/args.test
@@ -56,5 +56,13 @@ sys.stdout.pop_filter()
 os.unlink(cfg)
 shutil.rmtree(os.path.join(vardir, "bug726778"))
 
+print """#
+# A test case for Bug#897162, cat command should
+# not require a configuration file.
+"""
+sys.stdout.push_filter("(/\S+)+/tarantool", "tarantool")
+server.test_option("--config=nonexists.cfg --cat=nonexists.xlog")
+sys.stdout.pop_filter()
+
 # Args filter cleanup
 # vim: syntax=python
diff --git a/test/box/configuration.result b/test/box/configuration.result
index 58aec176101693a755a7ee80568904ad5c404c9a..f4c527e1c6d978c5ab8adda89bc2853fcd41689a 100644
--- a/test/box/configuration.result
+++ b/test/box/configuration.result
@@ -4,15 +4,62 @@
 #  compatibility
 #  (https://bugs.launchpad.net/bugs/708685)
 
-show stat
+show configuration
 ---
-statistics:
-  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           }
+configuration:
+  username: (null)
+  bind_ipaddr: "INADDR_ANY"
+  coredump: "false"
+  admin_port: "33015"
+  replication_port: "0"
+  log_level: "4"
+  slab_alloc_arena: "0.1"
+  slab_alloc_minimal: "64"
+  slab_alloc_factor: "2"
+  work_dir: (null)
+  pid_file: "box.pid"
+  logger: "cat - >> tarantool.log"
+  logger_nonblock: "true"
+  io_collect_interval: "0"
+  backlog: "1024"
+  readahead: "16320"
+  snap_dir: "."
+  wal_dir: "."
+  primary_port: "33013"
+  secondary_port: "33014"
+  too_long_threshold: "0.5"
+  custom_proc_title: (null)
+  memcached_port: "0"
+  memcached_space: "23"
+  memcached_expire: "false"
+  memcached_expire_per_loop: "1024"
+  memcached_expire_full_sweep: "3600"
+  snap_io_rate_limit: "0"
+  rows_per_wal: "50"
+  wal_fsync_delay: "0"
+  wal_writer_inbox_size: "128"
+  local_hot_standby: "false"
+  wal_dir_rescan_delay: "0.1"
+  panic_on_snap_error: "true"
+  panic_on_wal_error: "false"
+  replication_source: (null)
+  space[0].enabled: "true"
+  space[0].cardinality: "-1"
+  space[0].estimated_rows: "0"
+  space[0].index[0].type: "HASH"
+  space[0].index[0].unique: "true"
+  space[0].index[0].key_field[0].fieldno: "0"
+  space[0].index[0].key_field[0].type: "NUM"
+  space[1].enabled: "false"
+  space[1].cardinality: "-1"
+  space[1].estimated_rows: "0"
+  space[2].enabled: "true"
+  space[2].cardinality: "-1"
+  space[2].estimated_rows: "0"
+  space[2].index[0].type: "HASH"
+  space[2].index[0].unique: "true"
+  space[2].index[0].key_field[0].fieldno: "0"
+  space[2].index[0].key_field[0].type: "NUM"
 ...
 
 # Bug #884768:
@@ -129,3 +176,12 @@ configuration:
   space[9].index[0].key_field[0].fieldno: "0"
   space[9].index[0].key_field[0].type: "NUM"
 ...
+
+# Bug #876541:
+#  Test floating point values (wal_fsync_delay) with fractional part
+#  (https://bugs.launchpad.net/bugs/876541)
+
+lua box.cfg.wal_fsync_delay
+---
+ - 0.01
+...
diff --git a/test/box/configuration.test b/test/box/configuration.test
index ae6ee0a5d13a492aedf5cff092bd6eae85cc94d3..e58585a34a5781b38b5184994da019897d7b8289 100644
--- a/test/box/configuration.test
+++ b/test/box/configuration.test
@@ -11,7 +11,7 @@ server.stop()
 # start server from config with holes in spaces
 server.deploy("box/tarantool_bug708685.cfg")
 # check connection
-exec admin "show stat"
+exec admin "show configuration"
 
 print """
 # Bug #884768:
@@ -25,6 +25,18 @@ server.deploy("box/tarantool_bug884768.cfg")
 # check values
 exec admin "show configuration"
 
+print """
+# Bug #876541:
+#  Test floating point values (wal_fsync_delay) with fractional part
+#  (https://bugs.launchpad.net/bugs/876541)
+"""
+# stop current server
+server.stop()
+# start server from config with different boolean represenation
+server.deploy("box/tarantool_bug876541.cfg")
+# check values
+exec admin "lua box.cfg.wal_fsync_delay"
+
 # restore default server
 server.stop()
 server.deploy(self.suite_ini["config"])
diff --git a/test/box/lua.result b/test/box/lua.result
index 878660e0dc46e0c68111a2bcec51ec423ca82c05..adbcc025534e4c945dbd2b1668538a9f52b0b316 100644
--- a/test/box/lua.result
+++ b/test/box/lua.result
@@ -727,3 +727,112 @@ Found 1 tuple:
 call f1('jason', 1, 'test', 2, 'stewart')
 Found 1 tuple:
 ['jason', '1', 1953719668, '2', 'stewart']
+lua  function box.crossjoin(space0, space1, limit)   space0 = tonumber(space0)   space1 = tonumber(space1)   limit = tonumber(limit)   local result = {}   for k0, v0 in box.space[space0]:pairs() do     for k1, v1 in box.space[space1]:pairs() do       if limit <= 0 then         return unpack(result)       end       newtuple = {v0:unpack()}       for _, v in v1:pairs() do table.insert(newtuple, v) end       table.insert(result, newtuple)       limit = limit - 1     end   end   return unpack(result) end
+---
+...
+lua box.crossjoin(0, 0, 0)
+---
+...
+lua box.crossjoin(0, 0, 10000)
+---
+...
+lua box.space[0]:insert(1)
+---
+ - 1: {}
+...
+call box.crossjoin('0', '0', '10000')
+Found 1 tuple:
+[1, 1]
+lua box.space[0]:insert(2)
+---
+ - 2: {}
+...
+call box.crossjoin('0', '0', '10000')
+Found 4 tuples:
+[1, 1]
+[1, 2]
+[2, 1]
+[2, 2]
+lua box.space[0]:insert(3, 'hello')
+---
+ - 3: {'hello'}
+...
+call box.crossjoin('0', '0', '10000')
+Found 9 tuples:
+[1, 1]
+[1, 2]
+[1, 3, 'hello']
+[2, 1]
+[2, 2]
+[2, 3, 'hello']
+[3, 'hello', 1]
+[3, 'hello', 2]
+[3, 'hello', 3, 'hello']
+lua box.space[0]:insert(4, 'world')
+---
+ - 4: {'world'}
+...
+lua box.space[0]:insert(5, 'hello world')
+---
+ - 5: {'hello world'}
+...
+call box.crossjoin('0', '0', '10000')
+Found 25 tuples:
+[1, 1]
+[1, 2]
+[1, 3, 'hello']
+[1, 4, 'world']
+[1, 5, 'hello world']
+[2, 1]
+[2, 2]
+[2, 3, 'hello']
+[2, 4, 'world']
+[2, 5, 'hello world']
+[3, 'hello', 1]
+[3, 'hello', 2]
+[3, 'hello', 3, 'hello']
+[3, 'hello', 4, 'world']
+[3, 'hello', 5, 'hello world']
+[4, 'world', 1]
+[4, 'world', 2]
+[4, 'world', 3, 'hello']
+[4, 'world', 4, 'world']
+[4, 'world', 5, 'hello world']
+[5, 'hello world', 1]
+[5, 'hello world', 2]
+[5, 'hello world', 3, 'hello']
+[5, 'hello world', 4, 'world']
+[5, 'hello world', 5, 'hello world']
+lua box.space[0]:truncate()
+---
+...
+
+# A test case for Bug#901674
+# No way to inspect exceptions from Box in Lua
+
+lua pcall(box.insert, 99, 1, 'test')
+---
+ - false
+ - Space 99 is disabled
+...
+lua pcall(box.insert, 0, 1, 'hello')
+---
+ - true
+ - 1: {'hello'}
+...
+lua pcall(box.insert, 0, 1, 'hello')
+---
+ - false
+ - Tuple already exists
+...
+lua box.space[0]:truncate()
+---
+...
+
+# A test case for Bug#908094
+# Lua provides access to os.execute()
+
+lua os.execute('ls')
+---
+error: 'Lua error: [string "return os.execute(''ls'')"]:1: attempt to call field ''execute'' (a nil value)'
+...
diff --git a/test/box/lua.test b/test/box/lua.test
index 9ae9819358ab9d57b6cf00bdca1ccc9786cca115..211e4679bf1a471695baa4b2f5f87f0b94d1c016 100644
--- a/test/box/lua.test
+++ b/test/box/lua.test
@@ -202,4 +202,48 @@ exec sql "call f1()"
 exec sql "call f2()"
 exec sql "call f1('jason')"
 exec sql "call f1('jason', 1, 'test', 2, 'stewart')"
-
+lua = """
+function box.crossjoin(space0, space1, limit)
+  space0 = tonumber(space0)
+  space1 = tonumber(space1)
+  limit = tonumber(limit)
+  local result = {}
+  for k0, v0 in box.space[space0]:pairs() do
+    for k1, v1 in box.space[space1]:pairs() do
+      if limit <= 0 then
+        return unpack(result)
+      end
+      newtuple = {v0:unpack()}
+      for _, v in v1:pairs() do table.insert(newtuple, v) end
+      table.insert(result, newtuple)
+      limit = limit - 1
+    end
+  end
+  return unpack(result)
+end"""
+exec admin "lua " + lua.replace('\n', ' ')
+exec admin "lua box.crossjoin(0, 0, 0)"
+exec admin "lua box.crossjoin(0, 0, 10000)"
+exec admin "lua box.space[0]:insert(1)"
+exec sql "call box.crossjoin('0', '0', '10000')"
+exec admin "lua box.space[0]:insert(2)"
+exec sql "call box.crossjoin('0', '0', '10000')"
+exec admin "lua box.space[0]:insert(3, 'hello')"
+exec sql "call box.crossjoin('0', '0', '10000')"
+exec admin "lua box.space[0]:insert(4, 'world')"
+exec admin "lua box.space[0]:insert(5, 'hello world')"
+exec sql "call box.crossjoin('0', '0', '10000')"
+exec admin "lua box.space[0]:truncate()"
+print """
+# A test case for Bug#901674
+# No way to inspect exceptions from Box in Lua
+"""
+exec admin "lua pcall(box.insert, 99, 1, 'test')"
+exec admin "lua pcall(box.insert, 0, 1, 'hello')"
+exec admin "lua pcall(box.insert, 0, 1, 'hello')"
+exec admin "lua box.space[0]:truncate()"
+print """
+# A test case for Bug#908094
+# Lua provides access to os.execute()
+"""
+exec admin "lua os.execute('ls')"
diff --git a/test/box/suite.ini b/test/box/suite.ini
index ebc1e935aa1536eaeee1d3b2978609188bb0719c..ae4bde328a080541bcb84acc35210c39573e5727 100644
--- a/test/box/suite.ini
+++ b/test/box/suite.ini
@@ -2,6 +2,6 @@
 description = tarantool/box, minimal configuration
 config = tarantool.cfg
 # put disabled tests here
-#disabled = sql.test
+#disabled = xlog.test
 # put disabled in valgrind test here
 valgrind_disabled = admin_coredump.test
diff --git a/test/box/tarantool_bug876541.cfg b/test/box/tarantool_bug876541.cfg
new file mode 100644
index 0000000000000000000000000000000000000000..44163ffc275aeeddb9ac469f39d39a28f6a4819f
--- /dev/null
+++ b/test/box/tarantool_bug876541.cfg
@@ -0,0 +1,19 @@
+slab_alloc_arena = 0.1
+
+pid_file = "box.pid"
+
+logger="cat - >> tarantool.log"
+
+primary_port = 33013
+secondary_port = 33014
+admin_port = 33015
+
+rows_per_wal = 50
+
+space[0].enabled = 1
+space[0].index[0].type = "HASH"
+space[0].index[0].unique = 1
+space[0].index[0].key_field[0].fieldno = 0
+space[0].index[0].key_field[0].type = "NUM"
+
+wal_fsync_delay = 0.01
diff --git a/test/box_big/sql.result b/test/box_big/sql.result
index e8c0ce6c7da6a6d6529d5a8374cbcbe06a9c1eaa..4c99702d1afdd11e930acd415e99ad4869609616 100644
--- a/test/box_big/sql.result
+++ b/test/box_big/sql.result
@@ -154,8 +154,8 @@ Found 1 tuple:
 ['21234567', 'part1', 'part2_a']
 select * from t5 where k1='part1'
 Found 3 tuples:
-['11234567', 'part1', 'part2']
 ['01234567', 'part1', 'part2']
+['11234567', 'part1', 'part2']
 ['21234567', 'part1', 'part2_a']
 select * from t5 where k1='part1_a'
 Found 2 tuples:
@@ -165,8 +165,8 @@ select * from t5 where k1='part_none'
 No match
 lua box.space[5]:select(1, 'part1', 'part2')
 ---
- - '11234567': {'part1', 'part2'}
  - '01234567': {'part1', 'part2'}
+ - '11234567': {'part1', 'part2'}
 ...
 select * from t1 where k0='key1'
 Found 1 tuple:
diff --git a/test/box_big/sql.test b/test/box_big/sql.test
index 76afa77bb9235cd82e291fa229b1b9f0ef31bf30..fb163ba2de54990dbaeaea17a62176f29d0a30d2 100644
--- a/test/box_big/sql.test
+++ b/test/box_big/sql.test
@@ -76,10 +76,12 @@ exec admin "lua for k, v in box.space[5]:pairs() do print(v) end"
 exec sql "select * from t5 where k0='01234567'"
 exec sql "select * from t5 where k0='11234567'"
 exec sql "select * from t5 where k0='21234567'"
+sql.sort = True
 exec sql "select * from t5 where k1='part1'"
 exec sql "select * from t5 where k1='part1_a'"
 exec sql "select * from t5 where k1='part_none'"
 exec admin "lua box.space[5]:select(1, 'part1', 'part2')"
+sql.sort = False 
 # Check how build_idnexes() works
 server.stop()
 server.start()
diff --git a/test/box_big/tree_pk.result b/test/box_big/tree_pk.result
index 4a3e468081aeacb3f0b5650073d65d6707314c8b..319e07a0bbb177c52eb5dbb1a88ef716b741b113 100644
--- a/test/box_big/tree_pk.result
+++ b/test/box_big/tree_pk.result
@@ -63,3 +63,33 @@ delete from t3 where k0 = 'second'
 Delete OK, 1 row affected
 delete from t3 where k0 = 'third'
 Delete OK, 1 row affected
+insert into t2 values (1, 'tuple')
+Insert OK, 1 row affected
+insert into t3 values (1, 'tuple')
+Insert OK, 1 row affected
+insert into t3 values (2, 'tuple')
+Insert OK, 1 row affected
+lua  function box.crossjoin(space0, space1, limit)   space0 = tonumber(space0)   space1 = tonumber(space1)   limit = tonumber(limit)   local result = {}   for k0, v0 in box.space[space0]:pairs() do     for k1, v1 in box.space[space1]:pairs() do       if limit <= 0 then         return unpack(result)       end       newtuple = {v0:unpack()}       for _, v in v1:pairs() do table.insert(newtuple, v) end       table.insert(result, newtuple)       limit = limit - 1     end   end   return unpack(result) end
+---
+...
+call box.crossjoin(3, 3, 0)
+No match
+call box.crossjoin(3, 3, 5)
+Found 4 tuples:
+[1, 'tuple', 1, 'tuple']
+[1, 'tuple', 2, 'tuple']
+[2, 'tuple', 1, 'tuple']
+[2, 'tuple', 2, 'tuple']
+call box.crossjoin(3, 3, 10000)
+Found 4 tuples:
+[1, 'tuple', 1, 'tuple']
+[1, 'tuple', 2, 'tuple']
+[2, 'tuple', 1, 'tuple']
+[2, 'tuple', 2, 'tuple']
+call box.crossjoin(3, 2, 10000)
+Found 2 tuples:
+[1, 'tuple', 1, 'tuple']
+[2, 'tuple', 1, 'tuple']
+lua box.space[3]:truncate()
+---
+...
diff --git a/test/box_big/tree_pk.test b/test/box_big/tree_pk.test
index 3792956c9f97b7f74ca1b73b9a6fe9dafac4777f..090a90182aa1da98fb4f17f2dbfd314e629c343d 100644
--- a/test/box_big/tree_pk.test
+++ b/test/box_big/tree_pk.test
@@ -32,3 +32,31 @@ exec sql "select * from t3 where k0 = 'third'"
 exec sql "delete from t3 where k0 = 'identifier'"
 exec sql "delete from t3 where k0 = 'second'"
 exec sql "delete from t3 where k0 = 'third'"
+lua = """
+function box.crossjoin(space0, space1, limit)
+  space0 = tonumber(space0)
+  space1 = tonumber(space1)
+  limit = tonumber(limit)
+  local result = {}
+  for k0, v0 in box.space[space0]:pairs() do
+    for k1, v1 in box.space[space1]:pairs() do
+      if limit <= 0 then
+        return unpack(result)
+      end
+      newtuple = {v0:unpack()}
+      for _, v in v1:pairs() do table.insert(newtuple, v) end
+      table.insert(result, newtuple)
+      limit = limit - 1
+    end
+  end
+  return unpack(result)
+end"""
+exec sql "insert into t2 values (1, 'tuple')"
+exec sql "insert into t3 values (1, 'tuple')"
+exec sql "insert into t3 values (2, 'tuple')"
+exec admin "lua " + lua.replace('\n', ' ')
+exec sql "call box.crossjoin(3, 3, 0)"
+exec sql "call box.crossjoin(3, 3, 5)"
+exec sql "call box.crossjoin(3, 3, 10000)"
+exec sql "call box.crossjoin(3, 2, 10000)"
+exec admin "lua box.space[3]:truncate()"
diff --git a/test/connector_c/CMakeLists.txt b/test/connector_c/CMakeLists.txt
new file mode 100644
index 0000000000000000000000000000000000000000..91759d7325d700e2f21674d0533955465a9e10b3
--- /dev/null
+++ b/test/connector_c/CMakeLists.txt
@@ -0,0 +1,2 @@
+
+tarantool_client("tt" tt.c)
diff --git a/test/connector_c/connector.result b/test/connector_c/connector.result
index 227948c4cb9aea4ac1921c05ac311f1028e20e60..922997eee87c659cfea02bb647230287e17758c9 100644
--- a/test/connector_c/connector.result
+++ b/test/connector_c/connector.result
@@ -3,7 +3,8 @@
 > list                          [OK]
 > stream buffer                 [OK]
 > iterator tuple                [OK]
-> iterator tuple field          [OK]
+> iterator tuple (single field) [OK]
+> iterator tuple (tnt_field)    [OK]
 > iterator list                 [OK]
 > connect                       [OK]
 > ping                          [OK]
@@ -12,6 +13,8 @@
 > select                        [OK]
 > delete                        [OK]
 > call                          [OK]
+> call (no args)                [OK]
+> reply                         [OK]
 > lex ws                        [OK]
 > lex integer                   [OK]
 > lex string                    [OK]
@@ -26,5 +29,6 @@
 > sql insert                    [OK]
 > sql update                    [OK]
 > sql select                    [OK]
+> sql select limit              [OK]
 > sql delete                    [OK]
 > sql call                      [OK]
diff --git a/test/connector_c/connector.test b/test/connector_c/connector.test
index 6ee376c95c1e5ff7904be314a387e20075e3c982..01e0b108455c44a83fa9deb6a2c5c82a26639de4 100644
--- a/test/connector_c/connector.test
+++ b/test/connector_c/connector.test
@@ -1,7 +1,9 @@
 import subprocess
 import sys
+import os
 
-p = subprocess.Popen([ "connector_c/tt" ], stdout=subprocess.PIPE)
+p = subprocess.Popen([ os.path.join(builddir, "test/connector_c/tt") ],
+                     stdout=subprocess.PIPE)
 p.wait()
 for line in p.stdout.readlines():
       sys.stdout.write(line)
diff --git a/test/connector_c/tt.c b/test/connector_c/tt.c
index 05b4e2da4856785278c7d705e78738c03e52881f..0a26916796ce20a0d9a1c915cf3fd089e0c3d6ce 100644
--- a/test/connector_c/tt.c
+++ b/test/connector_c/tt.c
@@ -32,6 +32,7 @@
 
 #include <tnt.h>
 #include <tnt_net.h>
+#include <tnt_io.h>
 #include <tnt_queue.h>
 #include <tnt_utf8.h>
 #include <tnt_lex.h>
@@ -424,6 +425,112 @@ static void tt_tnt_net_call(struct tt_test *test) {
 	tnt_iter_free(&i);
 }
 
+/* call (no args) */
+static void tt_tnt_net_call_na(struct tt_test *test) {
+	struct tnt_tuple args;
+	tnt_tuple_init(&args);
+	TT_ASSERT(tnt_call(&net, 0, "box.insert", &args) > 0);
+	TT_ASSERT(tnt_flush(&net) > 0);
+	tnt_tuple_free(&args);
+	struct tnt_iter i;
+	tnt_iter_stream(&i, &net);
+	while (tnt_next(&i)) {
+		struct tnt_reply *r = TNT_ISTREAM_REPLY(&i);
+		TT_ASSERT(r->code != 0);
+		TT_ASSERT(strcmp(r->error, "Illegal parameters, tuple cardinality is 0") == 0);
+	}
+	tnt_iter_free(&i);
+}
+
+/* reply */
+static void tt_tnt_net_reply(struct tt_test *test) {
+	struct tnt_tuple kv1, kv2;
+	tnt_tuple_init(&kv1);
+	tnt_tuple(&kv1, "%d%s", 587, "foo");
+	TT_ASSERT(tnt_insert(&net, 0, TNT_FLAG_RETURN, &kv1) > 0);
+	tnt_tuple_free(&kv1);
+	tnt_tuple_init(&kv2);
+	tnt_tuple(&kv2, "%d%s", 785, "bar");
+	TT_ASSERT(tnt_insert(&net, 0, TNT_FLAG_RETURN, &kv2) > 0);
+	tnt_tuple_free(&kv2);
+	TT_ASSERT(tnt_flush(&net) > 0);
+
+	struct tnt_stream_net *s = TNT_SNET_CAST(&net);
+	int current = 0;
+	size_t off = 0;
+	ssize_t size = 0;
+	char buffer[512];
+
+	while (current != 2) {
+		struct tnt_reply r;
+		tnt_reply_init(&r);
+		int rc = tnt_reply(&r, buffer, size, &off);
+		TT_ASSERT(rc != -1);
+		if (rc == 1) {
+			ssize_t res = tnt_io_recv_raw(s, buffer + size, off, 1);
+			TT_ASSERT(res > 0);
+			size += off;
+			continue;
+		}
+		TT_ASSERT(rc == 0);
+		TT_ASSERT(r.code == 0);
+		TT_ASSERT(r.op == TNT_OP_INSERT);
+		TT_ASSERT(r.count == 1);
+		if (current == 0) {
+			struct tnt_iter il;
+			tnt_iter_list(&il, TNT_REPLY_LIST(&r));
+			TT_ASSERT(tnt_next(&il) == 1);
+			struct tnt_tuple *tp = TNT_ILIST_TUPLE(&il);
+			TT_ASSERT(tp->cardinality == 2);
+			TT_ASSERT(tp->alloc == 1);
+			TT_ASSERT(tp->data != NULL);
+			TT_ASSERT(tp->size != 0);
+			struct tnt_iter ifl;
+			tnt_iter(&ifl, tp);
+			TT_ASSERT(tnt_next(&ifl) == 1);
+			TT_ASSERT(TNT_IFIELD_IDX(&ifl) == 0);
+			TT_ASSERT(TNT_IFIELD_SIZE(&ifl) == 4);
+			TT_ASSERT(*(uint32_t*)TNT_IFIELD_DATA(&ifl) == 587);
+			TT_ASSERT(tnt_next(&ifl) == 1);
+			TT_ASSERT(TNT_IFIELD_IDX(&ifl) == 1);
+			TT_ASSERT(TNT_IFIELD_SIZE(&ifl) == 3);
+			TT_ASSERT(memcmp(TNT_IFIELD_DATA(&ifl), "foo", 3) == 0);
+			TT_ASSERT(tnt_next(&ifl) == 0);
+			tnt_iter_free(&ifl);
+			tnt_iter_free(&il);
+			off = 0;
+			size = 0;
+		} else
+		if (current == 1) {
+			struct tnt_iter il;
+			tnt_iter_list(&il, TNT_REPLY_LIST(&r));
+			TT_ASSERT(tnt_next(&il) == 1);
+			struct tnt_tuple *tp = TNT_ILIST_TUPLE(&il);
+			TT_ASSERT(tp->cardinality == 2);
+			TT_ASSERT(tp->alloc == 1);
+			TT_ASSERT(tp->data != NULL);
+			TT_ASSERT(tp->size != 0);
+			struct tnt_iter ifl;
+			tnt_iter(&ifl, tp);
+			TT_ASSERT(tnt_next(&ifl) == 1);
+			TT_ASSERT(TNT_IFIELD_IDX(&ifl) == 0);
+			TT_ASSERT(TNT_IFIELD_SIZE(&ifl) == 4);
+			TT_ASSERT(*(uint32_t*)TNT_IFIELD_DATA(&ifl) == 785);
+			TT_ASSERT(tnt_next(&ifl) == 1);
+			TT_ASSERT(TNT_IFIELD_IDX(&ifl) == 1);
+			TT_ASSERT(TNT_IFIELD_SIZE(&ifl) == 3);
+			TT_ASSERT(memcmp(TNT_IFIELD_DATA(&ifl), "bar", 3) == 0);
+			TT_ASSERT(tnt_next(&ifl) == 0);
+			tnt_iter_free(&ifl);
+			tnt_iter_free(&il);
+		}
+		tnt_reply_free(&r);
+		current++;
+	}
+
+	net.wrcnt -= 2;
+}
+
 /* lex ws */
 static void tt_tnt_lex_ws(struct tt_test *test) {
 	unsigned char sz[] = " 	# abcde fghjk ## hh\n   # zzz\n";
@@ -597,7 +704,7 @@ static void tt_tnt_lex_badstr2(struct tt_test *test) {
 static void tt_tnt_sql_ping(struct tt_test *test) {
 	char *e = NULL;
 	char q[] = "PING";
-	TT_ASSERT(tnt_query(&net, q, sizeof(q) - 1, &e) != -1);
+	TT_ASSERT(tnt_query(&net, q, sizeof(q) - 1, &e) == 0);
 	TT_ASSERT(tnt_flush(&net) > 0);
 	struct tnt_iter i;
 	tnt_iter_stream(&i, &net);
@@ -613,7 +720,7 @@ static void tt_tnt_sql_ping(struct tt_test *test) {
 static void tt_tnt_sql_insert(struct tt_test *test) {
 	char *e = NULL;
 	char q[] = "insert into t0 values (222, 'baz')";
-	TT_ASSERT(tnt_query(&net, q, sizeof(q) - 1, &e) != -1);
+	TT_ASSERT(tnt_query(&net, q, sizeof(q) - 1, &e) == 0);
 	TT_ASSERT(tnt_flush(&net) > 0);
 	struct tnt_iter i;
 	tnt_iter_stream(&i, &net);
@@ -630,25 +737,25 @@ static void tt_tnt_sql_insert(struct tt_test *test) {
 static void tt_tnt_sql_update(struct tt_test *test) {
 	char *e;
 	char q1[] = "update t0 set k0 = 7 where k0 = 222";
-	TT_ASSERT(tnt_query(&net, q1, sizeof(q1) - 1, &e) != -1);
+	TT_ASSERT(tnt_query(&net, q1, sizeof(q1) - 1, &e) == 0);
 	/* 7 + 1 = 8 */
 	char q2[] = "update t0 set k0 = k0 + 1 where k0 = 7";
-	TT_ASSERT(tnt_query(&net, q2, sizeof(q2) - 1, &e) != -1);
+	TT_ASSERT(tnt_query(&net, q2, sizeof(q2) - 1, &e) == 0);
 	/* 8 | 2 = 10 */
 	char q3[] = "update t0 set k0 = k0 | 2 where k0 = 8";
-	TT_ASSERT(tnt_query(&net, q3, sizeof(q3) - 1, &e) != -1);
+	TT_ASSERT(tnt_query(&net, q3, sizeof(q3) - 1, &e) == 0);
 	/* 10 & 2 = 2 */
 	char q4[] = "update t0 set k0 = k0 & 2 where k0 = 10";
-	TT_ASSERT(tnt_query(&net, q4, sizeof(q4) - 1, &e) != -1);
+	TT_ASSERT(tnt_query(&net, q4, sizeof(q4) - 1, &e) == 0);
 	/* 2 ^ 123 = 121 */
 	char q5[] = "update t0 set k0 = k0 ^ 123 where k0 = 2";
-	TT_ASSERT(tnt_query(&net, q5, sizeof(q5) - 1, &e) != -1);
+	TT_ASSERT(tnt_query(&net, q5, sizeof(q5) - 1, &e) == 0);
 	/* assign */
 	char q6[] = "update t0 set k0 = 222, k1 = 'hello world' where k0 = 121";
-	TT_ASSERT(tnt_query(&net, q6, sizeof(q6) - 1, &e) != -1);
+	TT_ASSERT(tnt_query(&net, q6, sizeof(q6) - 1, &e) == 0);
 	/* splice */
 	char q7[] = "update t0 set k1 = splice(k1, 0, 2, 'AB') where k0 = 222";
-	TT_ASSERT(tnt_query(&net, q7, sizeof(q7) - 1, &e) != -1);
+	TT_ASSERT(tnt_query(&net, q7, sizeof(q7) - 1, &e) == 0);
 	TT_ASSERT(tnt_flush(&net) > 0);
 	struct tnt_iter i;
 	tnt_iter_stream(&i, &net);
@@ -663,18 +770,17 @@ static void tt_tnt_sql_update(struct tt_test *test) {
 
 /* sql select */
 static void tt_tnt_sql_select(struct tt_test *test) {
-	struct tnt_list *search =
-		tnt_list(NULL, tnt_tuple(NULL, "%d", 222), NULL);
-	TT_ASSERT(tnt_select(&net, 0, 0, 0, 1, search) > 0);
+	char *e = NULL;
+	char q[] = "select * from t0 where k0 = 222 or k0 = 222";
+	TT_ASSERT(tnt_query(&net, q, sizeof(q) - 1, &e) == 0);
 	TT_ASSERT(tnt_flush(&net) > 0);
-	tnt_list_free(search);
 	struct tnt_iter i;
 	tnt_iter_stream(&i, &net);
 	while (tnt_next(&i)) {
 		struct tnt_reply *r = TNT_ISTREAM_REPLY(&i);
 		TT_ASSERT(r->code == 0);
 		TT_ASSERT(r->op == TNT_OP_SELECT);
-		TT_ASSERT(r->count == 1);
+		TT_ASSERT(r->count == 2);
 		struct tnt_iter il;
 		tnt_iter_list(&il, TNT_REPLY_LIST(r));
 		TT_ASSERT(tnt_next(&il) == 1);
@@ -698,13 +804,31 @@ static void tt_tnt_sql_select(struct tt_test *test) {
 		tnt_iter_free(&ifl);
 		tnt_iter_free(&il);
 	}
+	tnt_iter_free(&i);
+}
+
+/* sql select limit */
+static void tt_tnt_sql_select_limit(struct tt_test *test) {
+	char *e = NULL;
+	char q[] = "select * from t0 where k0 = 222 limit 0";
+	TT_ASSERT(tnt_query(&net, q, sizeof(q) - 1, &e) == 0);
+	TT_ASSERT(tnt_flush(&net) > 0);
+	struct tnt_iter i;
+	tnt_iter_stream(&i, &net);
+	while (tnt_next(&i)) {
+		struct tnt_reply *r = TNT_ISTREAM_REPLY(&i);
+		TT_ASSERT(r->code == 0);
+		TT_ASSERT(r->op == TNT_OP_SELECT);
+		TT_ASSERT(r->count == 0);
+	}
+	tnt_iter_free(&i);
 }
 
 /* sql delete */
 static void tt_tnt_sql_delete(struct tt_test *test) {
 	char *e = NULL;
 	char q[] = "delete from t0 where k0 = 222";
-	TT_ASSERT(tnt_query(&net, q, sizeof(q) - 1, &e) != -1);
+	TT_ASSERT(tnt_query(&net, q, sizeof(q) - 1, &e) == 0);
 	TT_ASSERT(tnt_flush(&net) > 0);
 	struct tnt_iter i;
 	tnt_iter_stream(&i, &net);
@@ -721,7 +845,7 @@ static void tt_tnt_sql_delete(struct tt_test *test) {
 static void tt_tnt_sql_call(struct tt_test *test) {
 	char *e = NULL;
 	char q[] = "call box.insert(0, 454, 'abc', 'cba')";
-	TT_ASSERT(tnt_query(&net, q, sizeof(q) - 1, &e) != -1);
+	TT_ASSERT(tnt_query(&net, q, sizeof(q) - 1, &e) == 0);
 	TT_ASSERT(tnt_flush(&net) > 0);
 	struct tnt_iter i;
 	tnt_iter_stream(&i, &net);
@@ -759,6 +883,8 @@ main(int argc, char * argv[])
 	tt_test(&t, "select", tt_tnt_net_select);
 	tt_test(&t, "delete", tt_tnt_net_delete);
 	tt_test(&t, "call", tt_tnt_net_call);
+	tt_test(&t, "call (no args)", tt_tnt_net_call_na);
+	tt_test(&t, "reply", tt_tnt_net_reply);
 	/* sql lexer */
 	tt_test(&t, "lex ws", tt_tnt_lex_ws);
 	tt_test(&t, "lex integer", tt_tnt_lex_int);
@@ -775,6 +901,7 @@ main(int argc, char * argv[])
 	tt_test(&t, "sql insert", tt_tnt_sql_insert);
 	tt_test(&t, "sql update", tt_tnt_sql_update);
 	tt_test(&t, "sql select", tt_tnt_sql_select);
+	tt_test(&t, "sql select limit", tt_tnt_sql_select_limit);
 	tt_test(&t, "sql delete", tt_tnt_sql_delete);
 	tt_test(&t, "sql call", tt_tnt_sql_call);
 
diff --git a/test/lib/test_suite.py b/test/lib/test_suite.py
index 3273a836a4476fbeb4e1d5a5c88f096c6fb80d7c..69a2657bfa94ed5377e6f575b6a31a93ba1fe4dd 100644
--- a/test/lib/test_suite.py
+++ b/test/lib/test_suite.py
@@ -79,7 +79,8 @@ class Test:
         self.result = name.replace(".test", ".result")
         self.tmp_result = os.path.join(self.args.vardir,
                                        os.path.basename(self.result))
-        self.reject = name.replace(".test", ".reject")
+	self.reject = "{0}/test/{1}".format(self.args.builddir,
+			                    name.replace(".test", ".reject"))
         self.is_executed = False
         self.is_executed_ok = None
         self.is_equal_result = None
diff --git a/test/share/tarantool_box.sup b/test/share/tarantool_box.sup
index 1efcdd908c28cd6bd6ae5cb1789a030fa68fe0ee..b7fbfebafa622462f916b7d16f4267604947e3de 100644
--- a/test/share/tarantool_box.sup
+++ b/test/share/tarantool_box.sup
@@ -139,6 +139,17 @@
    ...
 }
 
+{
+   <box upadte command>
+   Memcheck:Leak
+   fun:salloc
+   fun:tuple_alloc
+   fun:prepare_update
+   fun:box_dispatch
+   fun:box_process*
+   ...
+}
+
 ##
 ## tarantool/lua suppressions
 ##
@@ -243,32 +254,22 @@
    ...
 }
 
-## sptree
-##
-
-## third_party sptree implementation doesn't have
-## destroy function.
+# third_party/luajit/src/lj.supp -- add as recommended in
+# http://lua-users.org/lists/lua-l/2011-08/msg00736.html
 
+# Valgrind suppression file for LuaJIT 2.x.
 {
-   <insert_a_suppression_name_here>
-   Memcheck:Leak
-   fun:malloc
-   fun:realloc
-   fun:sptree_str_t_init
-   fun:build_indexes
-   ...
+   Optimized string compare
+   Memcheck:Addr4
+   fun:lj_str_cmp
 }
-
-# iterator_init_set calls realloc only if iterator value not
-# initialized or have lower depth.
-
 {
-   <insert_a_suppression_name_here>
-   Memcheck:Leak
-   fun:malloc
-   fun:realloc
-   fun:sptree_str_t_iterator_init_set
-   fun:index_iterator_init_tree_str
-   fun:process_select
-   ...
+   Optimized string compare
+   Memcheck:Addr4
+   fun:lj_str_new
+}
+{
+   Optimized string compare
+   Memcheck:Cond
+   fun:lj_str_new
 }
diff --git a/third_party/sptree.h b/third_party/sptree.h
index b8428042efe71e47b6c07227cdf4f82dfdf5c0fc..cb5551ba10def7d753eb907f62092cc923599f2e 100644
--- a/third_party/sptree.h
+++ b/third_party/sptree.h
@@ -159,6 +159,13 @@ sptree_##name##_init(sptree_##name *t, size_t elemsize, void *m,
     }                                                                                     \
 }                                                                                         \
                                                                                           \
+static inline void                                                                        \
+sptree_##name##_destroy(sptree_##name *t) {                                               \
+        if (t == NULL)    return;                                                         \
+	free(t->members);			                                          \
+	free(t->lrpointers);			                                          \
+}                                                                                         \
+                                                                                          \
 static inline void*                                                                       \
 sptree_##name##_find(sptree_##name *t, void *k)    {                                      \
     spnode_t    node = t->root;                                                           \
@@ -520,12 +527,15 @@ sptree_##name##_iterator_init_set(sptree_##name *t, sptree_##name##_iterator **i
     spnode_t node;                                                                        \
     int      lastLevelEq = -1, cmp;                                                       \
                                                                                           \
-    if ((*i) == NULL || t->max_depth > (*i)->max_depth)					  \
+    if ((*i) == NULL || t->max_depth > (*i)->max_depth)                                   \
         *i = realloc(*i, sizeof(**i) + sizeof(spnode_t) * (t->max_depth + 1));            \
                                                                                           \
     (*i)->t = t;                                                                          \
     (*i)->level = -1;                                                                     \
-    if (t->root == SPNIL) return;                                                         \
+    if (t->root == SPNIL) {                                                               \
+            (*i)->max_depth = 0; /* valgrind points out it's used in the check above ^.*/ \
+            return;                                                                       \
+    }                                                                                     \
                                                                                           \
     (*i)->max_depth = t->max_depth;                                                       \
     (*i)->stack[0] = t->root;                                                             \