diff --git a/.travis.yml b/.travis.yml
index 01a081a5ffb3a905baf0d54a187e6a2022c7ebad..b19d7775abf189261057cb0a329c58542523c86c 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -24,3 +24,6 @@ notifications:
     on_success: change
     on_failure: always
   email: false
+
+git:
+  depth: 500
diff --git a/client/tarantool/CMakeLists.txt b/client/tarantool/CMakeLists.txt
index c8f1a369ac60e870e3beb312771fadf09b82c69c..d03e72375cf7c3bd7f1b30a3afabb4646499cf6b 100644
--- a/client/tarantool/CMakeLists.txt
+++ b/client/tarantool/CMakeLists.txt
@@ -8,7 +8,8 @@ if (NOT READLINE_FOUND)
 endif()
 
 set (cli "tarantool")
-set (cli_sources tc.c tc_opt.c tc_admin.c tc_query.c tc_print.c tc_cli.c tc_store.c)
+set (cli_sources tc.c tc_opt.c tc_admin.c tc_query.c tc_print.c tc_buf.c
+		tc_cli.c tc_store.c tc_print_xlog.c tc_print_snap.c)
 set (cli_libs tntrpl tntnet tntsql tnt gopt ${READLINE_LIBRARIES})
 
 include_directories(${READLINE_INCLUDE_DIR})
diff --git a/client/tarantool/tc.c b/client/tarantool/tc.c
index ffb96d5c3c7a94e15f3c12dee1c4cc78cf950226..2b190f07b291361f9812fc28099222e376a68173 100644
--- a/client/tarantool/tc.c
+++ b/client/tarantool/tc.c
@@ -45,6 +45,8 @@
 #include "client/tarantool/tc.h"
 #include "client/tarantool/tc_cli.h"
 #include "client/tarantool/tc_print.h"
+#include "client/tarantool/tc_print_snap.h"
+#include "client/tarantool/tc_print_xlog.h"
 #include "client/tarantool/tc_store.h"
 
 struct tc tc;
@@ -103,10 +105,15 @@ static void tc_connect_admin(void)
 
 static void tc_validate(void)
 {
-	tc.opt.printer = tc_print_getcb(tc.opt.format);
-	if (tc.opt.printer == NULL)
-		return tc_error("unsupported output format '%s'",
+	tc.opt.xlog_printer = tc_print_getxlogcb(tc.opt.format);
+	tc.opt.snap_printer = tc_print_getsnapcb(tc.opt.format);
+	if (tc.opt.xlog_printer == NULL)
+		return tc_error("unsupported output xlog format '%s'",
 				tc.opt.format);
+	if (tc.opt.snap_printer == NULL)
+		return tc_error("unsupported output snap format '%s'",
+				tc.opt.format);
+	
 	if (tc.opt.format && strcmp(tc.opt.format, "raw") == 0)
 		tc.opt.raw = 1;
 }
diff --git a/client/tarantool/tc_admin.c b/client/tarantool/tc_admin.c
index bdd4a806837c5dd05fbe2f6e2d92e738ac1084a9..7c941ee49c8083d19cce300cecae2d3740b0e031 100644
--- a/client/tarantool/tc_admin.c
+++ b/client/tarantool/tc_admin.c
@@ -115,9 +115,11 @@ int tc_admin_reply(struct tc_admin *a, char **r, size_t *size)
 		ssize_t rxi = recv(a->fd, rx, sizeof(rx), 0);
 		if (rxi <= 0)
 			break;
-		char *bufn = realloc(buf, off + rxi + 1);
-		if (bufn == NULL)
+		char *bufn = (char *)realloc(buf, off + rxi + 1);
+		if (bufn == NULL) {
+			free(buf);
 			break;
+		}
 		buf = bufn;
 		memcpy(buf + off, rx, rxi);
 		off += rxi;
diff --git a/client/tarantool/tc_buf.c b/client/tarantool/tc_buf.c
new file mode 100644
index 0000000000000000000000000000000000000000..b4556bb796078d1b86f5b54a84b2146268e22c87
--- /dev/null
+++ b/client/tarantool/tc_buf.c
@@ -0,0 +1,126 @@
+/*
+ * 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 <COPYRIGHT HOLDER> ``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
+ * <COPYRIGHT HOLDER> 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 <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <stddef.h>
+#include <ctype.h>
+
+#include "client/tarantool/tc_buf.h"
+
+/* Strip trailing ws from (char*) */
+size_t strip_end_ws(char *str) {
+	size_t last = 0;
+	for (size_t i = 0; str[i] != 0; ++i)
+		if (!isspace(str[i]))
+			last = i + 1;
+	str[last] = '\0';
+	return last;
+}
+
+/* Init membuf */
+int tc_buf(struct tc_buf *buf) {
+	buf->size = TC_BUF_INIT_SIZE;
+	buf->used = 0;
+	buf->data = (char *)malloc(buf->size);
+	if (buf->data == NULL)
+		return -1;
+	return 0;
+}
+
+/* Append len bytes of memory from str pointed memory */
+int tc_buf_append(struct tc_buf *buf, void *str, size_t len) {
+	if (buf->size - buf->used < len) {
+		if (buf->size < len)
+			buf->size = len;
+		buf->size *= TC_BUF_MULTIPLIER;
+		char *nd = (char *)realloc(buf->data, buf->size);
+		if (nd == NULL)
+			return -1;
+		buf->data = nd;
+	}
+	memcpy(buf->data + buf->used, str, len);
+	buf->used += len;
+	return 0;
+}
+
+/* Remove last "num" symbols */
+size_t tc_buf_delete(struct tc_buf *buf, size_t num) {
+	if (buf->used > num) {
+		buf->used -= num;
+	} else {
+		num = buf->used;
+		buf->used = 0;
+	}
+	return num;
+}
+
+inline int tc_buf_isempty(struct tc_buf *buf) {
+	return (buf->used == 0);
+}
+
+inline void tc_buf_clear(struct tc_buf *buf) {
+	buf->used = 0;
+}
+
+/* Free membuffer */
+void tc_buf_free(struct tc_buf *buf) {
+	if (buf->data)
+		free(buf->data);
+}
+
+/* Init buffer as STR */
+int tc_buf_str(struct tc_buf *buf) {
+	if (tc_buf(buf))
+		return -1;
+	return tc_buf_append(buf, (void *)"\0", 1);
+}
+
+/* Append str to STR */
+int tc_buf_str_append(struct tc_buf *buf, char *str, size_t len) {
+	tc_buf_delete(buf, 1);
+	if (tc_buf_append(buf, (void *)str, len))
+		return -1;
+	if (tc_buf_append(buf, (void *)"\0", 1))
+		return -1;
+	return 0;
+}
+
+/* Remove trailing ws from STR */
+int tc_buf_str_stripws(struct tc_buf *buf) {
+	if (buf->data) {
+		buf->used = 1 + strip_end_ws(buf->data);
+		return 0;
+	}
+	return -1;
+}
+
+inline int tc_buf_str_isempty(struct tc_buf *buf) {
+	return (buf->used == 1 ? 1 : 0) || (tc_buf_isempty(buf));
+}
diff --git a/client/tarantool/tc_buf.h b/client/tarantool/tc_buf.h
new file mode 100644
index 0000000000000000000000000000000000000000..52383666e7a7adf1cd869c1e96ff3a4e197d781d
--- /dev/null
+++ b/client/tarantool/tc_buf.h
@@ -0,0 +1,51 @@
+/*
+ * 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 <COPYRIGHT HOLDER> ``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
+ * <COPYRIGHT HOLDER> 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.
+ */
+#define TC_BUF_INIT_SIZE 4096
+#define TC_BUF_MULTIPLIER 2
+
+size_t strip_end_ws(char *str);
+
+struct tc_buf {
+	size_t size;
+	size_t used;
+	char *data;
+};
+
+int tc_buf(struct tc_buf *buf);
+void *tc_buf_realloc(void *data, size_t size);
+int tc_buf_append(struct tc_buf *buf, void *str, size_t len);
+size_t tc_buf_delete(struct tc_buf *buf, size_t len);
+int tc_buf_isempty(struct tc_buf *buf);
+void tc_buf_clear(struct tc_buf *buf);
+void tc_buf_free(struct tc_buf *buf);
+
+int tc_buf_str(struct tc_buf *buf);
+int tc_buf_str_append(struct tc_buf *buf, char *str, size_t len);
+int tc_buf_str_stripws(struct tc_buf *buf);
+int tc_buf_str_isempty(struct tc_buf *buf);
diff --git a/client/tarantool/tc_cli.c b/client/tarantool/tc_cli.c
index efe47330fb0f64b764fd38665ddb6edd2cf9d8dc..b7255ef4c6c195c69f8f8cbb917da4ab96a1ebff 100644
--- a/client/tarantool/tc_cli.c
+++ b/client/tarantool/tc_cli.c
@@ -32,6 +32,7 @@
 #include <stdarg.h>
 #include <string.h>
 #include <stdint.h>
+#include <ctype.h>
 
 #include <sys/types.h>
 #include <sys/stat.h>
@@ -39,6 +40,7 @@
 #include <fcntl.h>
 #include <signal.h>
 #include <errno.h>
+#include <wchar.h>
 
 #include <readline/readline.h>
 #include <readline/history.h>
@@ -58,9 +60,13 @@
 #include "client/tarantool/tc_query.h"
 #include "client/tarantool/tc_cli.h"
 #include "client/tarantool/tc_print.h"
+#include "client/tarantool/tc_buf.h"
 
 #define TC_DEFAULT_HISTORY_FILE ".tarantool_history"
 
+#define TC_ALLOCATION_ERROR "error: memory allocation failed for %zu bytes\n"
+#define TC_REALLOCATION_ERROR "error: memory reallocation failed for %zu bytes\n"
+
 extern struct tc tc;
 
 static inline int tc_cli_error(char *e) {
@@ -89,7 +95,9 @@ enum tc_keywords {
 	TC_TEE,
 	TC_NOTEE,
 	TC_LOADFILE,
-	TC_HELP
+	TC_HELP,
+	TC_SETOPT,
+	TC_SETOPT_DELIM
 };
 
 static struct tnt_lex_keyword tc_lex_keywords[] =
@@ -106,6 +114,10 @@ static struct tnt_lex_keyword tc_lex_keywords[] =
 	{ "tee", 3, TC_TEE },
 	{ "notee", 5, TC_NOTEE },
 	{ "loadfile", 8, TC_LOADFILE },
+	{ "s", 1, TC_SETOPT},
+	{ "setopt", 6, TC_SETOPT},
+	{ "delim", 5, TC_SETOPT_DELIM},
+	{ "delimiter", 9, TC_SETOPT_DELIM},
 	{ NULL, 0, TNT_TK_NONE }
 };
 
@@ -125,6 +137,8 @@ tc_cmd_usage(void)
 		" - tee 'path'\n"
 		" - notee\n"
 		" - loadfile 'path'\n"
+		" - setopt key=val\n"
+		" - (possible pairs: delim=\'str\')\n"
 		"...\n";
 	tc_printf("%s", usage);
 }
@@ -194,9 +208,9 @@ tc_cmd_loadfile(char *path, int *reconnect)
 	int fd = open(path, O_RDONLY);
 	if (fd == -1)
 		return -1;
-	char *buf = malloc(st.st_size);
+	char *buf = (char *)malloc(st.st_size);
 	if (buf == NULL) {
-		tc_printf("error: memory allocation failed for %zu bytes\n",
+		tc_printf(TC_ALLOCATION_ERROR,
 			  st.st_size);
 		return -1;
 	}
@@ -252,6 +266,37 @@ tc_cmd_try(char *cmd, size_t size, int *reconnect)
 		if (tc_cmd_loadfile((char*)TNT_TK_S(tk)->data, reconnect) == -1)
 			rc = TC_CLI_ERROR;
 		goto done;
+	case TC_SETOPT:
+		switch (tnt_lex(&lex, &tk)) {
+		case TC_SETOPT_DELIM:
+			if (tnt_lex(&lex, &tk) == '=' &&
+			    tnt_lex(&lex, &tk) == TNT_TK_STRING) {
+				if (!TNT_TK_S(tk)->size) {
+					tc.opt.delim = "";
+					tc.opt.delim_len = 0;
+					goto done;
+				}
+				char * temp = (char *)malloc(TNT_TK_S(tk)->size);
+				if (temp == NULL)
+					tc_error(TC_ALLOCATION_ERROR,
+						 TNT_TK_S(tk)->size);
+				strncpy(temp,
+					(const char *)TNT_TK_S(tk)->data,
+					TNT_TK_S(tk)->size + 1);
+				tc.opt.delim = temp;
+				tc.opt.delim_len = strlen(tc.opt.delim);
+			} else {
+				tc_printf("---\n");
+				tc_printf(" - Expected delim='string'\n");
+				tc_printf("---\n");
+			}
+			break;
+		default:
+			tc_printf("---\n");
+			tc_printf(" - Unknown option to set\n");
+			tc_printf("---\n");
+		}
+		goto done;
 	}
 	*reconnect = tc_cli_admin(cmd, rc == TC_CLI_EXIT);
 	if (*reconnect)
@@ -321,6 +366,56 @@ static void tc_cli_init(void) {
 		tc_error("signal initialization failed\n");
 }
 
+static char* tc_cli_readline_pipe() {
+	int size = 8192, pos = 0;
+	const size_t wcsize = sizeof(wchar_t);
+	char *str = (char *)malloc(size);
+	if (str == NULL)
+		tc_error(TC_ALLOCATION_ERROR, size);
+	wchar_t c;
+	while ((c = getwchar())) {
+		if (size < (pos + wcsize)) {
+			size *= 2;
+			char *nd = (char *)realloc(str, size);
+			if (nd == NULL)
+				tc_error(TC_REALLOCATION_ERROR, size);
+			str = nd;
+		}
+		if (c == '\r' || c == '\n' || c == WEOF) {
+			char c_t = (c != WEOF ? getchar() : '\n');
+			if (c_t != '\r' && c_t != '\n')
+				ungetc(c_t, stdin);
+			wctomb(str + pos++, 0);
+			break;
+		}
+		else
+			pos += wctomb(str + pos, c);
+	}
+	if (pos == 1 && c == WEOF) {
+		free(str);
+		return NULL;
+	}
+	return str;
+}
+
+
+
+static int check_delim(char* str, size_t len, size_t sep_len) {
+	const char *sep = tc.opt.delim;
+	len = strip_end_ws(str);
+	if (sep_len == 0)
+		return 1;
+	if (len < sep_len)
+		return 0;
+	size_t i;
+	for (i = len; sep_len > 0; --sep_len, --i)
+		if (str[i - 1] != sep[sep_len - 1])
+			return 0;
+	str[i] = '\0';
+	len = strip_end_ws(str);
+	return 1;
+}
+
 int tc_cli(void)
 {
 	/* initializing cli */
@@ -335,25 +430,62 @@ int tc_cli(void)
 
 	/* setting prompt */
 	char prompt[128];
-	snprintf(prompt, sizeof(prompt), "%s> ", tc.opt.host);
-
+	int prompt_len = snprintf(prompt, sizeof(prompt), "%s> ", tc.opt.host) - 2;
+	char prompt_delim[128];
 	/* interactive mode */
-	char *cmd;
-	while ((cmd = readline(prompt))) {
-		if (!cmd[0])
-			goto next;
-		int cmd_len = strlen(cmd);
-		tc_print_cmd2tee(prompt, cmd, cmd_len);
-		enum tc_cli_cmd_ret ret = tc_cli_cmd(cmd, cmd_len);
-		if (ret == TC_CLI_EXIT)
+	char *part_cmd;
+	struct tc_buf cmd;
+	if (tc_buf_str(&cmd))
+		tc_error(TC_REALLOCATION_ERROR,
+			 cmd.size);
+	while (1) {
+		if (isatty(STDIN_FILENO)) {
+			snprintf(prompt_delim, sizeof(prompt_delim),
+				 "%*s> ", prompt_len, "-");
+			part_cmd = readline(!tc_buf_str_isempty(&cmd) ? prompt_delim
+							   : prompt);
+		} else {
+			clearerr(stdin);
+			part_cmd = tc_cli_readline_pipe();
+		}
+		if (!part_cmd)
 			break;
-		add_history(cmd);
+		size_t part_cmd_len = strlen(part_cmd);
+		int delim_exists = check_delim(part_cmd,
+					       part_cmd_len,
+					       tc.opt.delim_len);
+		if (tc_buf_str_append(&cmd, part_cmd, strlen(part_cmd)))
+			tc_error(TC_REALLOCATION_ERROR,
+				 cmd.size);
+		free(part_cmd);
+		if (!delim_exists && !feof(stdin)) {
+			if (tc_buf_str_append(&cmd, " ", 1))
+				tc_error(TC_REALLOCATION_ERROR,
+					 cmd.size);
+			continue;
+		}
+		tc_buf_str_stripws(&cmd);
+		if (delim_exists && tc_buf_str_isempty(&cmd))
+			goto next;
+		tc_print_cmd2tee(cmd.used != 1 ? prompt_delim : prompt,
+				 cmd.data, cmd.used - 1);
+		enum tc_cli_cmd_ret ret = tc_cli_cmd(cmd.data,
+						     cmd.used - 1);
+		if (isatty(STDIN_FILENO))
+			add_history(cmd.data);
 next:
-		free(cmd);
-	}
+		tc_buf_clear(&cmd);
+		if (ret == TC_CLI_EXIT || feof(stdin)) {
+			tc_buf_free(&cmd);
+			break;
+		}
+}
 
 	/* updating history file */
 	write_history(history);
 	clear_history();
 	return 0;
 }
+
+#undef TC_ALLOCATION_ERROR
+#undef TC_REALLOCATION_ERROR
diff --git a/client/tarantool/tc_opt.c b/client/tarantool/tc_opt.c
index c666b292110c7216f1539657afc8e71f9d8d10d0..331ad3e59a836853b98be0064a95b3f2ab7a94bf 100644
--- a/client/tarantool/tc_opt.c
+++ b/client/tarantool/tc_opt.c
@@ -35,6 +35,7 @@
 
 #include "client/tarantool/tc_opt.h"
 
+
 #define TC_DEFAULT_HOST "localhost"
 #define TC_DEFAULT_PORT 33013
 #define TC_DEFAULT_PORT_ADMIN 33015
@@ -63,6 +64,14 @@ static const void *tc_options_def = gopt_start(
 		    gopt_longs("header"), NULL, "add file headers for the raw output"),
 	gopt_option('R', GOPT_ARG, gopt_shorts('R'),
 		    gopt_longs("rpl"), " <lsn>", "act as replica for the specified server"),
+	gopt_option('B', 0, gopt_shorts('B'),
+		    gopt_longs("bin"), NULL, "print STR in lua printer instead"
+		    " of NUM32 and NUM64, except arithmetic update arguments"),
+	gopt_option('D', GOPT_ARG, gopt_shorts('D'),
+		    gopt_longs("delim"), " <delim>",
+		    "if you use --cat, then it will add delim to an end of every line of your"
+		    "lua file, when used at CLI start of client, then it's replacement of "
+		    "setopt delim='<delim>' command"),
 	gopt_option('?', 0, gopt_shorts(0), gopt_longs("help"),
 		    NULL, "display this help and exit"),
 	gopt_option('v', 0, gopt_shorts('v'), gopt_longs("version"),
@@ -148,6 +157,17 @@ enum tc_opt_mode tc_opt_init(struct tc_opt *opt, int argc, char **argv)
 	if (gopt(tc_options, 'H'))
 		opt->raw_with_headers = 1;
 
+	/* string instead of num and num64 */
+	opt->str_instead_int = 0;
+	if (gopt(tc_options, 'B'))
+		opt->str_instead_int = 1;
+
+	/* set delimiter on start */
+	opt->delim = "";
+	opt->delim_len = 0;
+	if (gopt_arg(tc_options, 'D', &opt->delim))
+		opt->delim_len = strlen(opt->delim);
+
 	/* replica mode */
 	if (gopt_arg(tc_options, 'R', &arg)) {
 		opt->mode = TC_OPT_RPL;
diff --git a/client/tarantool/tc_opt.h b/client/tarantool/tc_opt.h
index 931e95150fcca5db80b95f7be830216aa784a41c..cef473890b3b1009f4ae54e671bd917537448699 100644
--- a/client/tarantool/tc_opt.h
+++ b/client/tarantool/tc_opt.h
@@ -57,10 +57,14 @@ struct tc_opt {
 	const char *format;
 	int raw;
 	int raw_with_headers;
-	void *printer;
+	int str_instead_int;
+	void *xlog_printer;
+	void *snap_printer;
 	const char *file;
 	char **cmdv;
 	int cmdc;
+	const char *delim;
+	size_t delim_len;
 };
 
 void tc_opt_usage(void);
diff --git a/client/tarantool/tc_print.c b/client/tarantool/tc_print.c
index 78f64dce43d529773c431ff0c9dbfcffc6546a95..bacdef37eb3d390221cb1e31668df138447d8b0d 100644
--- a/client/tarantool/tc_print.c
+++ b/client/tarantool/tc_print.c
@@ -37,6 +37,7 @@
 #include <unistd.h>
 #include <errno.h>
 
+
 #include <connector/c/include/tarantool/tnt.h>
 #include <connector/c/include/tarantool/tnt_xlog.h>
 #include <connector/c/include/tarantool/tnt_rpl.h>
@@ -49,6 +50,8 @@
 
 extern struct tc tc;
 
+/*##################### Base printing functions #####################*/
+
 void tc_print_tee(char *buf, size_t size) {
 	if (tc.tee_fd == -1)
 		return;
@@ -95,6 +98,8 @@ void tc_printf(char *fmt, ...) {
 	}
 }
 
+/*##################### string functions #####################*/
+
 static int tc_str_valid(char *data, uint32_t size) {
 	int length;
 	wchar_t dest;
@@ -111,19 +116,34 @@ static int tc_str_valid(char *data, uint32_t size) {
 	return 1;
 }
 
-static void tc_print_str(char *data, uint32_t size) {
+void tc_print_string(char *data, uint32_t size, char lua)
+{
 	if (tc_str_valid(data, size)) {
 		wchar_t dest;
 		int length;
 		mbtowc (NULL, NULL, 0);
 		while ((length = mbtowc(&dest, data, size)) > -1 && size > 0) {
 			if (dest >= 0x20) {
-				tc_printf ("%lc", dest);
+				if (lua)
+					switch (dest) {
+					case '\'':
+						tc_printf("\\\'");
+						break;
+					case '\\':
+						tc_printf("\\\\");
+						break;
+					default:
+						tc_printf ("%lc", dest);
+					}
+				else
+					tc_printf ("%lc", dest);
 			}
 			else {
 				switch (dest) {
 				case 0x00:
 					tc_printf("\\0");
+					length++;
+					/* Cause of mbtowc returns 0 when \0 */
 					break;
 				case 0x07:
 					tc_printf("\\a");
@@ -147,12 +167,10 @@ static void tc_print_str(char *data, uint32_t size) {
 					tc_printf("\\r");
 					break;
 				default:
-					tc_printf("\\x%02lX", 
+					tc_printf("\\x%02lX",
 						(unsigned long int)dest);
 					break;
 				}
-				if (length == 0) 
-					++length;
 			}
 			size -= length;
 			data += length;
@@ -166,7 +184,11 @@ static void tc_print_str(char *data, uint32_t size) {
 	}
 }
 
-static void tc_print_fields(struct tnt_tuple *tu) {
+/*##################### Tuple and Fields #####################*/
+/* tarantool */
+
+void tc_print_fields(struct tnt_tuple *tu)
+{
 	struct tnt_iter ifl;
 	tnt_iter(&ifl, tu);
 	while (tnt_next(&ifl)) {
@@ -183,7 +205,7 @@ static void tc_print_fields(struct tnt_tuple *tu) {
 			tc_printf("%"PRIu64, *((uint64_t*)data));
 			break;
 		default:
-			tc_print_str(data, size);
+			tc_print_string(data, size, 0);
 		}
 		tc_printf("'");
 	}
@@ -210,46 +232,46 @@ void tc_print_list(struct tnt_list *l)
 	tnt_iter_free(&it);
 }
 
-static void
-tc_printer_tarantool(struct tnt_log_header_v11 *hdr,
-		     struct tnt_request *r)
+/* lua */
+
+void tc_print_lua_field(char *data, uint32_t size, char string)
 {
-	tc_printf("%s lsn: %"PRIu64", time: %f, len: %"PRIu32"\n",
-		  tc_query_type(r->h.type),
-		  hdr->lsn,
-		  hdr->tm,
-		  hdr->len);
-	switch (r->h.type) {
-	case TNT_OP_INSERT:
-		tc_print_tuple(&r->r.insert.t);
+	if (string)
+		goto _string;
+	switch (size){
+	case 4:
+		tc_printf("%"PRIu32, *((uint32_t*)data));
 		break;
-	case TNT_OP_DELETE:
-		tc_print_tuple(&r->r.del.t);
-		break;
-	case TNT_OP_UPDATE:
-		tc_print_tuple(&r->r.update.t);
+	case 8:
+		tc_printf("%"PRIu64, *((uint64_t*)data));
 		break;
+	default:
+_string:
+		tc_printf("\'");
+		tc_print_string(data, size, 1);
+		tc_printf("\'");
 	}
 }
 
-static void
-tc_printer_raw(struct tnt_log_header_v11 *hdr, struct tnt_request *r)
+void tc_print_lua_fields(struct tnt_tuple *tu)
 {
-	if (tc.opt.raw_with_headers) {
-		fwrite(&tnt_log_marker_v11,
-		       sizeof(tnt_log_marker_v11), 1, stdout);
+	struct tnt_iter ifl;
+	tnt_iter(&ifl, tu);
+	while (tnt_next(&ifl)) {
+		if ((TNT_IFIELD_IDX(&ifl)) != 0)
+			tc_printf(", ");
+		char *data = TNT_IFIELD_DATA(&ifl);
+		uint32_t size = TNT_IFIELD_SIZE(&ifl);
+		tc_print_lua_field(data, size, tc.opt.str_instead_int);
 	}
-	fwrite(hdr, sizeof(*hdr), 1, stdout);
-	fwrite(r->origin, r->origin_size, 1, stdout);
+	if (ifl.status == TNT_ITER_FAIL)
+		tc_printf("<parsing error>");
+	tnt_iter_free(&ifl);
 }
 
-tc_printerf_t tc_print_getcb(const char *name)
+void tc_print_lua_tuple(struct tnt_tuple *tu)
 {
-	if (name == NULL)
-		return tc_printer_tarantool;
-	if (!strcasecmp(name, "tarantool"))
-		return tc_printer_tarantool;
-	if (!strcasecmp(name, "raw"))
-		return tc_printer_raw;
-	return NULL;
+	tc_printf("{");
+	tc_print_lua_fields(tu);
+	tc_printf("}");
 }
diff --git a/client/tarantool/tc_print.h b/client/tarantool/tc_print.h
index 4af4b1b87d3104fc0d56cce61f48bde9c269096a..fe2612e74b113fdae5b1ccdceb2fca883c91d63f 100644
--- a/client/tarantool/tc_print.h
+++ b/client/tarantool/tc_print.h
@@ -29,17 +29,19 @@
  * SUCH DAMAGE.
  */
 
-typedef void (*tc_printerf_t)(struct tnt_log_header_v11 *hdr,
-		              struct tnt_request *r);
-
 void tc_print_tee(char *buf, size_t size);
 void tc_print_cmd2tee(char *prompt, char *cmd, int size);
 void tc_printf(char *fmt, ...);
 void tc_print_buf(char *buf, size_t size);
 
+void tc_print_string(char *data, uint32_t size, char lua);
+
+void tc_print_fields(struct tnt_tuple *tu);
 void tc_print_tuple(struct tnt_tuple *tu);
 void tc_print_list(struct tnt_list *l);
 
-tc_printerf_t tc_print_getcb(const char *name);
+void tc_print_lua_field(char *data, uint32_t size, char string);
+void tc_print_lua_fields(struct tnt_tuple *tu);
+void tc_print_lua_tuple(struct tnt_tuple *tu);
 
 #endif /* TC_PRINT_H_INCLUDED */
diff --git a/client/tarantool/tc_print_snap.c b/client/tarantool/tc_print_snap.c
new file mode 100644
index 0000000000000000000000000000000000000000..e08d183994722288657c9d7687efafa98f7cbadb
--- /dev/null
+++ b/client/tarantool/tc_print_snap.c
@@ -0,0 +1,68 @@
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+#include <inttypes.h>
+#include <limits.h>
+#include <stdint.h>
+
+#include <connector/c/include/tarantool/tnt.h>
+#include <connector/c/include/tarantool/tnt_xlog.h>
+#include <connector/c/include/tarantool/tnt_snapshot.h>
+
+#include "client/tarantool/tc_opt.h"
+#include "client/tarantool/tc_admin.h"
+#include "client/tarantool/tc.h"
+#include "client/tarantool/tc_print.h"
+#include "client/tarantool/tc_print_snap.h"
+#include "client/tarantool/tc_query.h"
+#include "client/tarantool/tc_store.h"
+
+extern struct tc tc;
+
+static void
+tc_printer_snap_raw( struct tnt_log_row_snap_v11 *row,
+		     struct tnt_tuple *tu)
+{
+	if (tc.opt.raw_with_headers) {
+		fwrite(&tnt_log_marker_v11,
+			sizeof(tnt_log_marker_v11), 1, stdout);
+	}
+	fwrite(row, sizeof(row), 1, stdout);
+	fwrite(tu->data, tu->size, 1, stdout);
+}
+static void
+tc_printer_snap_tarantool( struct tnt_log_row_snap_v11 *row,
+			   struct tnt_tuple *tu)
+{
+	tc_printf("tag: %"PRIu16", cookie: %"PRIu64", space: %"PRIu32"\n",
+		row->tag,
+		row->cookie,
+		row->space);
+	tc_print_tuple(tu);
+
+}
+static void
+tc_printer_snap_lua( struct tnt_log_row_snap_v11 *row,
+		     struct tnt_tuple *tu)
+{
+	tc_printf("lua box.insert(%"PRIu32", ", row->space);
+	tc_print_lua_fields(tu);
+	tc_printf(")");
+	if (tc.opt.delim_len > 0)
+		tc_printf("%s\n", tc.opt.delim);
+	else
+		tc_printf("\n");
+}
+
+tc_printerf_snap_t tc_print_getsnapcb(const char *name)
+{
+	if (name == NULL)
+		return tc_printer_snap_tarantool;
+	if (!strcasecmp(name, "tarantool"))
+		return tc_printer_snap_tarantool;
+	if (!strcasecmp(name, "raw"))
+		return tc_printer_snap_raw;
+	if (!strcasecmp(name, "lua"))
+		return tc_printer_snap_lua;
+	return NULL;
+}
diff --git a/client/tarantool/tc_print_snap.h b/client/tarantool/tc_print_snap.h
new file mode 100644
index 0000000000000000000000000000000000000000..9bf467a2bc062be0b8e9c8cfe55dec5bc5d99250
--- /dev/null
+++ b/client/tarantool/tc_print_snap.h
@@ -0,0 +1,37 @@
+#ifndef TC_PRINT_SNAP_H_INCLUDED
+#define TC_PRINT_SNAP_H_INCLUDED
+/*
+ * 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 <COPYRIGHT HOLDER> ``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
+ * <COPYRIGHT HOLDER> 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.
+ */
+
+typedef void (*tc_printerf_snap_t)(struct tnt_log_row_snap_v11 *row,
+				   struct tnt_tuple *tu);
+
+tc_printerf_snap_t tc_print_getsnapcb(const char *name);
+
+#endif /* TC_PRINT_SNAP_H_INCLUDED */
diff --git a/client/tarantool/tc_print_xlog.c b/client/tarantool/tc_print_xlog.c
new file mode 100644
index 0000000000000000000000000000000000000000..57f48513cd957f02eea149a2e3b01d3865c573b3
--- /dev/null
+++ b/client/tarantool/tc_print_xlog.c
@@ -0,0 +1,197 @@
+/*
+ * 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 <COPYRIGHT HOLDER> ``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
+ * <COPYRIGHT HOLDER> 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 <stddef.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <inttypes.h>
+#include <strings.h>
+
+#include <connector/c/include/tarantool/tnt.h>
+#include <connector/c/include/tarantool/tnt_xlog.h>
+#include <connector/c/include/tarantool/tnt_rpl.h>
+
+#include "client/tarantool/tc_opt.h"
+#include "client/tarantool/tc_admin.h"
+#include "client/tarantool/tc_print.h"
+#include "client/tarantool/tc_print_xlog.h"
+#include "client/tarantool/tc_query.h"
+#include "client/tarantool/tc_print.h"
+#include "client/tarantool/tc.h"
+
+extern struct tc tc;
+
+static void
+tc_printer_xlog_raw(struct tnt_log_header_v11 *hdr,
+		    struct tnt_request *r)
+{
+	if (tc.opt.raw_with_headers) {
+		fwrite(&tnt_log_marker_v11,
+			sizeof(tnt_log_marker_v11), 1, stdout);
+	}
+	fwrite(hdr, sizeof(*hdr), 1, stdout);
+	fwrite(r->origin, r->origin_size, 1, stdout);
+}
+
+static void
+tc_printer_xlog_tarantool(struct tnt_log_header_v11 *hdr,
+			  struct tnt_request *r)
+{
+	tc_printf("%s lsn: %"PRIu64", time: %f, len: %"PRIu32"\n",
+		tc_query_type(r->h.type),
+		hdr->lsn,
+		hdr->tm,
+		hdr->len);
+	switch (r->h.type) {
+	case TNT_OP_INSERT:
+		tc_print_tuple(&r->r.insert.t);
+		break;
+	case TNT_OP_DELETE:
+		tc_print_tuple(&r->r.del.t);
+		break;
+	case TNT_OP_UPDATE:
+		tc_print_tuple(&r->r.update.t);
+		break;
+	}
+}
+
+static void
+tc_printer_xlog_lua(struct tnt_log_header_v11 *hdr,
+		    struct tnt_request *r)
+{
+	tc_printf("lua box.");
+	switch (r->h.type) {
+	case TNT_OP_INSERT:
+		if (r->r.insert.h.flags && TNT_FLAG_REPLACE == TNT_FLAG_REPLACE)
+			tc_printf("replace(");
+		else
+			tc_printf("insert(");
+		tc_printf("%"PRIu32", ", r->r.insert.h.ns);
+		tc_print_lua_fields(&r->r.insert.t);
+		break;
+	case TNT_OP_DELETE:
+		tc_printf("delete(");
+		tc_printf("%"PRIu32", ", r->r.del.h.ns);
+		tc_print_lua_tuple(&r->r.del.t);
+		break;
+	case TNT_OP_UPDATE:
+		tc_printf("update(");
+		tc_printf("%"PRIu32", ", r->r.update.h.ns);
+		tc_print_lua_tuple(&r->r.update.t);
+		tc_printf(", '");
+		for (uint32_t i = 0; i < r->r.update.opc; i++) {
+			switch (r->r.update.opv[i].op) {
+			case TNT_UPDATE_ASSIGN:
+				tc_printf("=p");
+				break;
+			case TNT_UPDATE_ADD:
+				tc_printf("+p");
+				break;
+			case TNT_UPDATE_AND:
+				tc_printf("&p");
+				break;
+			case TNT_UPDATE_XOR:
+				tc_printf("^p");
+				break;
+			case TNT_UPDATE_OR:
+				tc_printf("|p");
+				break;
+			case TNT_UPDATE_SPLICE:
+				tc_printf(":p");
+				break;
+			case TNT_UPDATE_DELETE:
+				tc_printf("#p");
+				break;
+			case TNT_UPDATE_INSERT:
+				tc_printf("!p");
+				break;
+			}
+		}
+		tc_printf("'");
+		for (uint32_t i = 0; i < r->r.update.opc; i++) {
+			tc_printf(", %"PRIu32,
+				r->r.update.opv[i].field);
+			switch (r->r.update.opv[i].op){
+			case TNT_UPDATE_ADD:
+			case TNT_UPDATE_AND:
+			case TNT_UPDATE_XOR:
+			case TNT_UPDATE_OR:
+				tc_printf(", ");
+				tc_print_lua_field(r->r.update.opv[i].data,
+						r->r.update.opv[i].size, 0);
+				break;
+			case TNT_UPDATE_SPLICE:
+				tc_printf(", box.pack('ppp'");
+				char *data = r->r.update.opv[i].data;
+				size_t pos = 1;
+				tc_printf(", %"PRId32,
+					*(int32_t *)(data + pos));
+				pos += 5;
+				tc_printf(", %"PRId32", ",
+					*(int32_t *)(data + pos));
+				pos += 4 + r->r.update.opv[i].size_enc_len;
+				tc_printf("\'");
+				tc_print_string(data,
+					r->r.update.opv[i].size - pos, 1);
+				tc_printf("\'");
+				tc_printf(")");
+				break;
+			case TNT_UPDATE_DELETE:
+				tc_printf(", \'\'");
+				break;
+			case TNT_UPDATE_INSERT:
+			case TNT_UPDATE_ASSIGN:
+				tc_printf(", ");
+				tc_print_lua_field(r->r.update.opv[i].data,
+						r->r.update.opv[i].size,
+						tc.opt.str_instead_int);
+				break;
+			}
+		}
+		break;
+	}
+	tc_printf(") -- %"PRIu64, hdr->lsn);
+	if (tc.opt.delim_len > 0)
+		tc_printf("%s\n", tc.opt.delim);
+	else
+		tc_printf("\n");
+}
+
+tc_printerf_xlog_t tc_print_getxlogcb(const char *name)
+{
+	if (name == NULL)
+		return tc_printer_xlog_tarantool;
+	if (!strcasecmp(name, "tarantool"))
+		return tc_printer_xlog_tarantool;
+	if (!strcasecmp(name, "raw"))
+		return tc_printer_xlog_raw;
+	if (!strcasecmp(name, "lua"))
+		return tc_printer_xlog_lua;
+	return NULL;
+}
diff --git a/client/tarantool/tc_print_xlog.h b/client/tarantool/tc_print_xlog.h
new file mode 100644
index 0000000000000000000000000000000000000000..e9918606ee7d3278b3d3206f0aa23b404410c7d2
--- /dev/null
+++ b/client/tarantool/tc_print_xlog.h
@@ -0,0 +1,37 @@
+#ifndef TC_PRINT_XLOG_H_INCLUDED
+#define TC_PRINT_XLOG_H_INCLUDED
+/*
+ * 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 <COPYRIGHT HOLDER> ``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
+ * <COPYRIGHT HOLDER> 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.
+ */
+
+typedef void (*tc_printerf_xlog_t)(struct tnt_log_header_v11 *hdr,
+				   struct tnt_request *r);
+
+tc_printerf_xlog_t tc_print_getxlogcb(const char *name);
+
+#endif /* TC_PRINT_XLOG_H_INCLUDED */
diff --git a/client/tarantool/tc_store.c b/client/tarantool/tc_store.c
index c04d8c481b2fdd4c1149d51e65e486d3597bc8b4..ec2bc020e7b259ceebf9f49e53f5ab7b53b65f0f 100644
--- a/client/tarantool/tc_store.c
+++ b/client/tarantool/tc_store.c
@@ -44,6 +44,8 @@
 #include "client/tarantool/tc_admin.h"
 #include "client/tarantool/tc.h"
 #include "client/tarantool/tc_print.h"
+#include "client/tarantool/tc_print_xlog.h"
+#include "client/tarantool/tc_print_snap.h"
 #include "client/tarantool/tc_query.h"
 #include "client/tarantool/tc_store.h"
 
@@ -125,7 +127,7 @@ static int tc_store_printer(struct tnt_iter *i) {
 		return 0;
 	struct tnt_stream_xlog *s =
 		TNT_SXLOG_CAST(TNT_IREQUEST_STREAM(i));
-	((tc_printerf_t)tc.opt.printer)(&s->log.current.hdr, r);
+	((tc_printerf_xlog_t)tc.opt.xlog_printer)(&s->log.current.hdr, r);
 	return 0;
 }
 
@@ -133,21 +135,7 @@ static int tc_snapshot_printer(struct tnt_iter *i) {
 	struct tnt_tuple *tu = TNT_ISTORAGE_TUPLE(i);
 	struct tnt_stream_snapshot *ss =
 		TNT_SSNAPSHOT_CAST(TNT_ISTORAGE_STREAM(i));
-	if (tc.opt.raw) {
-		if (tc.opt.raw_with_headers) {
-			fwrite(&tnt_log_marker_v11,
-			       sizeof(tnt_log_marker_v11), 1, stdout);
-		}
-		fwrite(&ss->log.current.row_snap,
-		       sizeof(ss->log.current.row_snap), 1, stdout);
-		fwrite(tu->data, tu->size, 1, stdout);
-	} else {
-		tc_printf("tag: %"PRIu16", cookie: %"PRIu64", space: %"PRIu32"\n",
-			  ss->log.current.row_snap.tag,
-			  ss->log.current.row_snap.cookie,
-			  ss->log.current.row_snap.space);
-		tc_print_tuple(tu);
-	}
+	((tc_printerf_snap_t)tc.opt.snap_printer)(&ss->log.current.row_snap, tu);
 	return 0;
 }
 
@@ -200,6 +188,7 @@ int tc_store_cat(void)
 		fputs("\n", stdout);
 	}
 	int rc;
+
 	switch (type) {
 	case TNT_LOG_SNAPSHOT:
 		rc = tc_store_foreach_snapshot(tc_snapshot_printer);
diff --git a/cmake/CheckBuiltInFunctionExists.cmake b/cmake/CheckBuiltInFunctionExists.cmake
deleted file mode 100644
index 6b4128f129f2f3effa521edcd17508964b3a03bd..0000000000000000000000000000000000000000
--- a/cmake/CheckBuiltInFunctionExists.cmake
+++ /dev/null
@@ -1,7 +0,0 @@
-include(CheckCSourceCompiles)
-
-macro(check_builtin_function_exists function variable)
-    set(CMAKE_REQUIRED_FLAGS "-Wno-unused-value -Wno-error")
-    check_c_source_compiles("int main(void) { ${function}; return 0; }"
-        ${variable})
-endmacro(check_builtin_function_exists)
diff --git a/connector/c/include/tp.h b/connector/c/include/tp.h
index 4d886719bb2fe7515e8528bf57daefc05ff0e770..d76e827be6d569bcb59fca7b3abe92a5f0d5e670 100644
--- a/connector/c/include/tp.h
+++ b/connector/c/include/tp.h
@@ -329,6 +329,12 @@ tp_unused(struct tp *p) {
  * data must be manually freed when the buffer is no longer
  * needed.
  * (eg. free(p->s));
+ * if realloc will return NULL, then you must destroy previous memory.
+ * (eg.
+ * if (tp_realloc(p, ..) == NULL) {
+ * 	free(p->s)
+ * 	return NULL;
+ * }
 */
 tp_function_unused static char*
 tp_realloc(struct tp *p, size_t required, size_t *size) {
diff --git a/connector/c/tnt/tnt_buf.c b/connector/c/tnt/tnt_buf.c
index c5209dd1b2effcaf52220e88dc7828d7cf831941..a67016197d2de33feb4637f56886f0bfa8f98b25 100644
--- a/connector/c/tnt/tnt_buf.c
+++ b/connector/c/tnt/tnt_buf.c
@@ -68,8 +68,10 @@ static char* tnt_buf_resize(struct tnt_stream *s, size_t size) {
 	size_t off = sb->size;
 	size_t nsize = off + size;
 	char *nd = realloc(sb->data, nsize);
-	if (nd == NULL)
+	if (nd == NULL) {
+		free(sb->data);
 		return NULL;
+	}
 	sb->data = nd;
 	sb->size = nsize;
 	return sb->data + off;
diff --git a/connector/c/tnt/tnt_tuple.c b/connector/c/tnt/tnt_tuple.c
index 0a9c36baf7451ed8da96ff71d2708e8d44e7e950..9b8cec4a6ae2aeaee781fcef811e80193b55551f 100644
--- a/connector/c/tnt/tnt_tuple.c
+++ b/connector/c/tnt/tnt_tuple.c
@@ -394,6 +394,7 @@ struct tnt_tuple *tnt_list_at(struct tnt_list *l, struct tnt_tuple *t) {
 	/* reallocating tuple data */
 	char *ndata = realloc(l->list, sizeof(struct tnt_list_ptr) * (l->count + 1));
 	if (ndata == NULL) {
+		free(l->list);
 		if (allocated)
 			tnt_tuple_free(t);
 		return NULL;
diff --git a/doc/user/iterator-types.xml b/doc/user/iterator-types.xml
index 836442e21826f5a4d7e017f5e559ccb8fa6148e8..efb50c86a364555ede0a602469d59968ac9e09e6 100644
--- a/doc/user/iterator-types.xml
+++ b/doc/user/iterator-types.xml
@@ -55,7 +55,7 @@
             </simpara>
             <simpara>
             Semantics of the match depends on the index.
-            A HASH and TREE index only supports exact match: all parts
+            A HASH index only supports exact match: all parts
             of a key participating in the index must be provided.
             In case of TREE index, only few parts of a key or a
             key prefix are accepted for search.
@@ -64,12 +64,10 @@
             criteria.
             </simpara>
             <simpara>
-            A non-unique HASH index returns tuples in unspecified
-            order.
             When a TREE index is not unique, or only part of a key
             is given as a search criteria, matching tuples are
             returned in ascending order.
-            BITSET indexes are always unique.
+            BITSET and HASH indexes are always unique.
             </simpara>
         </entry>
     </row>
diff --git a/doc/user/space.xml b/doc/user/space.xml
index 348d21151f205f5f453f0cb73c267ceae4845c78..655af9e063fd7b85ca69943f1940a9e63425c3b0 100644
--- a/doc/user/space.xml
+++ b/doc/user/space.xml
@@ -94,8 +94,7 @@ please be aware of these restrictions:
     dynamically, currently you need to restart the server even to
     disable or enable a space,
   </simpara></listitem>
-  <listitem><simpara>HASH indexes may cover only one field and can
-    not be non-unique.
+  <listitem><simpara>HASH indexes can not be non-unique.
   </simpara></listitem>
 </itemizedlist>
 </para>
diff --git a/include/mhash.h b/include/mhash.h
index 722a9dffc9e973c038a056336a9524595200e07d..e6bfc1943896cc35fffa1589125b72cda194803f 100644
--- a/include/mhash.h
+++ b/include/mhash.h
@@ -170,6 +170,32 @@ _mh(next_slot)(mh_int_t slot, mh_int_t inc, mh_int_t size)
 	return slot >= size ? slot - size : slot;
 }
 
+#if defined(mh_hash_key) && defined(mh_eq_key)
+/**
+ * If it is necessary to search by something different
+ * than a hash node, define mh_hash_key and mh_eq_key
+ * and use mh_find().
+ */
+static inline mh_int_t
+_mh(find)(struct _mh(t) *h, mh_key_t key, mh_arg_t arg)
+{
+	(void) arg;
+
+	mh_int_t k = mh_hash_key(key, arg);
+	mh_int_t i = k % h->n_buckets;
+	mh_int_t inc = 1 + k % (h->n_buckets - 1);
+	for (;;) {
+		if ((mh_exist(h, i) && mh_eq_key(key, mh_node(h, i), arg)))
+			return i;
+
+		if (!mh_dirty(h, i))
+			return h->n_buckets;
+
+		i = _mh(next_slot)(i, inc, h->n_buckets);
+	}
+}
+#endif
+
 static inline mh_int_t
 _mh(get)(struct _mh(t) *h, const mh_node_t *node,
 	 mh_arg_t arg)
@@ -504,9 +530,12 @@ _mh(dump)(struct _mh(t) *h)
 #undef mh_int_t
 #undef mh_node_t
 #undef mh_arg_t
+#undef mh_key_t
 #undef mh_name
 #undef mh_hash
+#undef mh_hash_key
 #undef mh_eq
+#undef mh_eq_key
 #undef mh_node
 #undef mh_dirty
 #undef mh_place
diff --git a/src/box/box_lua.cc b/src/box/box_lua.cc
index 589d5f12be8da19cec67208aeba4df1ca72127ea..5679d16a17dd73e8efbac52e4d7c487a3ae4df92 100644
--- a/src/box/box_lua.cc
+++ b/src/box/box_lua.cc
@@ -159,7 +159,7 @@ lbox_tuple_slice(struct lua_State *L)
 	} else if (offset < 0 && -offset <= tuple->field_count) {
 		start = offset + tuple->field_count;
 	} else {
-		luaL_error(L, "tuple.slice(): start >= field count");
+		return luaL_error(L, "tuple.slice(): start >= field count");
 	}
 
 	if (argc == 2) {
@@ -169,13 +169,13 @@ lbox_tuple_slice(struct lua_State *L)
 		} else if (offset < 0 && -offset < tuple->field_count) {
 			end = offset + tuple->field_count;
 		} else {
-			luaL_error(L, "tuple.slice(): end > field count");
+			return luaL_error(L, "tuple.slice(): end > field count");
 		}
 	} else {
 		end = tuple->field_count;
 	}
 	if (end <= start)
-		luaL_error(L, "tuple.slice(): start must be less than end");
+		return luaL_error(L, "tuple.slice(): start must be less than end");
 
 	struct tuple_iterator it;
 	tuple_rewind(&it, tuple);
diff --git a/src/box/hash_index.cc b/src/box/hash_index.cc
index bb88876104dabd550e556dc97310c359412fdd51..4b726caf25bef92cb292909768e972de1ca73bba 100644
--- a/src/box/hash_index.cc
+++ b/src/box/hash_index.cc
@@ -32,75 +32,125 @@
 #include "pickle.h"
 #include "exception.h"
 #include "space.h"
-#include "assoc.h"
 #include "errinj.h"
 
-/* {{{ HashIndex Iterators ****************************************/
-
-struct hash_i32_iterator {
-	struct iterator base; /* Must be the first member. */
-	struct mh_i32ptr_t *hash;
-	mh_int_t h_pos;
-};
-
-struct hash_i64_iterator {
-	struct iterator base;
-	struct mh_i64ptr_t *hash;
-	mh_int_t h_pos;
-};
+#include "third_party/PMurHash.h"
 
-struct hash_lstr_iterator {
-	struct iterator base;
-	struct mh_lstrptr_t *hash;
-	mh_int_t h_pos;
+enum {
+	HASH_SEED = 13U
 };
 
-void
-hash_iterator_free(struct iterator *iterator)
+static inline bool
+mh_index_eq(struct tuple *const *tuple_a, struct tuple *const *tuple_b,
+	    const struct key_def *key_def)
 {
-	assert(iterator->free == hash_iterator_free);
-	free(iterator);
+	return tuple_compare(*tuple_a, *tuple_b, key_def) == 0;
 }
 
-struct tuple *
-hash_iterator_i32_ge(struct iterator *ptr)
+static inline bool
+mh_index_eq_key(const char *key, struct tuple *const *tuple,
+		const struct key_def *key_def)
 {
-	assert(ptr->free == hash_iterator_free);
-	struct hash_i32_iterator *it = (struct hash_i32_iterator *) ptr;
+	return tuple_compare_with_key(*tuple, key, key_def->part_count,
+				      key_def) == 0;
+}
 
-	while (it->h_pos < mh_end(it->hash)) {
-		if (mh_exist(it->hash, it->h_pos))
-			return (struct tuple *) mh_i32ptr_node(it->hash, it->h_pos++)->val;
-		it->h_pos++;
+
+static inline uint32_t
+mh_index_hash(struct tuple *const *tuple, const struct key_def *key_def)
+{
+	struct key_part *part = key_def->parts;
+	uint32_t size;
+	/*
+	 * Speed up the simplest case when we have a
+	 * single-part hash over an integer field.
+	 */
+	if (key_def->part_count == 1 && part->type == NUM)
+		return *(uint32_t *) tuple_field(*tuple, part->fieldno, &size);
+
+	uint32_t h = HASH_SEED;
+	uint32_t carry = 0;
+	uint32_t total_size = 0;
+
+	for ( ; part < key_def->parts + key_def->part_count; part++) {
+		const char *field = tuple_field(*tuple, part->fieldno, &size);
+		assert(size < INT32_MAX);
+		PMurHash32_Process(&h, &carry, field, size);
+		total_size += size;
 	}
-	return NULL;
+
+	return PMurHash32_Result(h, carry, total_size);
 }
 
-struct tuple *
-hash_iterator_i64_ge(struct iterator *ptr)
+static inline uint32_t
+mh_index_hash_key(const char *key, const struct key_def *key_def)
 {
-	assert(ptr->free == hash_iterator_free);
-	struct hash_i64_iterator *it = (struct hash_i64_iterator *) ptr;
+	struct key_part *part = key_def->parts;
 
-	while (it->h_pos < mh_end(it->hash)) {
-		if (mh_exist(it->hash, it->h_pos))
-			return (struct tuple *) mh_i64ptr_node(
-						it->hash,it->h_pos++)->val;
-		it->h_pos++;
+	if (key_def->part_count == 1 && part->type == NUM) {
+		(void) load_varint32(&key);
+		return *(uint32_t *) key;
 	}
-	return NULL;
+	uint32_t h = HASH_SEED;
+	uint32_t carry = 0;
+	uint32_t total_size = 0;
+
+	for ( ; part < key_def->parts + key_def->part_count; part++) {
+		uint32_t size = load_varint32(&key);
+		if (part->type == NUM64 && size == sizeof(uint32_t)) {
+			/* Allow search in NUM64 indexes using NUM keys. */
+			uint64_t u64 = *(uint32_t *) key;
+			PMurHash32_Process(&h, &carry, &u64, sizeof(uint64_t));
+			total_size += sizeof(uint64_t);
+		} else {
+			assert(size < INT32_MAX);
+			PMurHash32_Process(&h, &carry, key, size);
+			total_size += size;
+		}
+		key += size;
+	}
+
+	return PMurHash32_Result(h, carry, total_size);
+}
+
+#define mh_int_t uint32_t
+#define mh_arg_t const struct key_def *
+
+#define mh_hash(a, arg) mh_index_hash(a, arg)
+#define mh_hash_key(a, arg) mh_index_hash_key(a, arg)
+#define mh_eq(a, b, arg) mh_index_eq(a, b, arg)
+#define mh_eq_key(a, b, arg) mh_index_eq_key(a, b, arg)
+
+#define mh_key_t const char *
+typedef struct tuple * mh_node_t;
+#define mh_name _index
+#define MH_SOURCE 1
+#include <mhash.h>
+
+/* {{{ HashIndex Iterators ****************************************/
+
+struct hash_iterator {
+	struct iterator base; /* Must be the first member. */
+	struct mh_index_t *hash;
+	uint32_t h_pos;
+};
+
+void
+hash_iterator_free(struct iterator *iterator)
+{
+	assert(iterator->free == hash_iterator_free);
+	free(iterator);
 }
 
 struct tuple *
-hash_iterator_lstr_ge(struct iterator *ptr)
+hash_iterator_ge(struct iterator *ptr)
 {
 	assert(ptr->free == hash_iterator_free);
-	struct hash_lstr_iterator *it = (struct hash_lstr_iterator *) ptr;
+	struct hash_iterator *it = (struct hash_iterator *) ptr;
 
 	while (it->h_pos < mh_end(it->hash)) {
 		if (mh_exist(it->hash, it->h_pos))
-			return (struct tuple *) mh_lstrptr_node(
-						it->hash,it->h_pos++)->val;
+			return *mh_index_node(it->hash, it->h_pos++);
 		it->h_pos++;
 	}
 	return NULL;
@@ -113,147 +163,29 @@ hash_iterator_eq_next(struct iterator *it __attribute__((unused)))
 }
 
 static struct tuple *
-hash_iterator_i32_eq(struct iterator *it)
-{
-	it->next = hash_iterator_eq_next;
-	return hash_iterator_i32_ge(it);
-}
-
-static struct tuple *
-hash_iterator_i64_eq(struct iterator *it)
-{
-	it->next = hash_iterator_eq_next;
-	return hash_iterator_i64_ge(it);
-}
-
-static struct tuple *
-hash_iterator_lstr_eq(struct iterator *it)
+hash_iterator_eq(struct iterator *it)
 {
 	it->next = hash_iterator_eq_next;
-	return hash_iterator_lstr_ge(it);
+	return hash_iterator_ge(it);
 }
 
 /* }}} */
 
 /* {{{ HashIndex -- base class for all hashes. ********************/
 
-class Hash32Index: public HashIndex {
-public:
-	Hash32Index(struct key_def *key_def, struct space *space);
-	~Hash32Index();
-
-	virtual size_t
-	size() const;
-
-	virtual struct tuple *
-	random(u32 rnd) const;
-
-	virtual struct tuple *
-	findByKey(const char *key, u32 part_count) const;
-
-	virtual struct tuple *
-	replace(struct tuple *old_tuple, struct tuple *new_tuple,
-		enum dup_replace_mode mode);
-
-	struct iterator *
-	allocIterator() const;
-
-	void
-	initIterator(struct iterator *iterator, enum iterator_type type,
-		     const char *key, u32 part_count) const;
-
-	virtual void
-	reserve(u32 n_tuples);
-private:
-	struct mh_i32ptr_t *int_hash;
-};
-
-class Hash64Index: public HashIndex {
-public:
-	Hash64Index(struct key_def *key_def, struct space *space);
-	~Hash64Index();
-
-	virtual size_t
-	size() const;
-
-	virtual struct tuple *
-	random(u32 rnd) const;
-
-	virtual struct tuple *
-	findByKey(const char *key, u32 part_count) const;
-
-	virtual struct tuple *
-	replace(struct tuple *old_tuple, struct tuple *new_tuple,
-		enum dup_replace_mode mode);
-
-	struct iterator *
-	allocIterator() const;
-
-	void
-	initIterator(struct iterator *iterator, enum iterator_type type,
-		     const char *key, u32 part_count) const;
-
-	virtual void
-	reserve(u32 n_tuples);
-private:
-	struct mh_i64ptr_t *int64_hash;
-};
-
-class HashStrIndex: public HashIndex {
-public:
-	HashStrIndex(struct key_def *key_def, struct space *space);
-	~HashStrIndex();
-
-	virtual size_t
-	size() const;
-
-	virtual struct tuple *
-	random(u32 rnd) const;
-
-	virtual struct tuple *
-	findByKey(const char *key, u32 part_count) const;
-
-	virtual struct tuple *
-	replace(struct tuple *old_tuple, struct tuple *new_tuple,
-		enum dup_replace_mode mode);
-
-	struct iterator *
-	allocIterator() const;
-
-	void
-	initIterator(struct iterator *iterator, enum iterator_type type,
-		     const char *key, u32 part_count) const;
-
-	virtual void
-	reserve(u32 n_tuples);
-private:
-	struct mh_lstrptr_t *str_hash;
-};
-
-HashIndex *
-HashIndex::factory(struct key_def *key_def, struct space *space)
+HashIndex::HashIndex(struct key_def *key_def, struct space *space)
+	: Index(key_def, space)
 {
-	/*
-	 * Hash index always has a single-field key.
-	 */
-	switch (key_def->parts[0].type) {
-	case NUM:
-		return new Hash32Index(key_def, space);  /* 32-bit integer hash */
-	case NUM64:
-		return new Hash64Index(key_def, space);  /* 64-bit integer hash */
-	case STRING:
-		return new HashStrIndex(key_def, space); /* string hash */
-	default:
-		assert(false);
+	hash = mh_index_new();
+	if (hash == NULL) {
+		tnt_raise(ClientError, ER_MEMORY_ISSUE, sizeof(hash),
+			  "HashIndex", "hash");
 	}
-
-	return NULL;
 }
 
-HashIndex::HashIndex(struct key_def *key_def, struct space *space)
-	: Index(key_def, space)
+HashIndex::~HashIndex()
 {
-	/* Nothing */
+	mh_index_delete(hash);
 }
 
 void
@@ -293,299 +225,84 @@ HashIndex::build(Index *pk)
 	      replace(NULL, tuple, DUP_INSERT);
 }
 
-struct tuple *
-HashIndex::min() const
-{
-	tnt_raise(ClientError, ER_UNSUPPORTED, "Hash index", "min()");
-	return NULL;
-}
-
-struct tuple *
-HashIndex::max() const
-{
-	tnt_raise(ClientError, ER_UNSUPPORTED, "Hash index", "max()");
-	return NULL;
-}
-
-
-
-/* }}} */
-
-/* {{{ Hash32Index ************************************************/
-
-static inline struct mh_i32ptr_node_t
-int32_key_to_node(const char *key)
-{
-	u32 key_size = load_varint32(&key);
-	if (key_size != 4)
-		tnt_raise(ClientError, ER_KEY_FIELD_TYPE, "u32");
-	struct mh_i32ptr_node_t node = { *(u32 *) key, NULL };
-	return node;
-}
-
-static inline struct mh_i32ptr_node_t
-int32_tuple_to_node(struct tuple *tuple, struct key_def *key_def)
-{
-	const char *field = tuple_field_old(tuple, key_def->parts[0].fieldno);
-	struct mh_i32ptr_node_t node = int32_key_to_node(field);
-	node.val = tuple;
-	return node;
-}
-
-
-Hash32Index::Hash32Index(struct key_def *key_def, struct space *space)
-	: HashIndex(key_def, space)
-{
-	int_hash = mh_i32ptr_new();
-}
-
-Hash32Index::~Hash32Index()
-{
-	mh_i32ptr_delete(int_hash);
-}
-
 void
-Hash32Index::reserve(u32 n_tuples)
+HashIndex::reserve(u32 n_tuples)
 {
-	mh_i32ptr_reserve(int_hash, n_tuples, NULL);
+	mh_index_reserve(hash, n_tuples, key_def);
 }
 
 size_t
-Hash32Index::size() const
+HashIndex::size() const
 {
-	return mh_size(int_hash);
+	return mh_size(hash);
 }
 
 
 struct tuple *
-Hash32Index::random(u32 rnd) const
+HashIndex::min() const
 {
-	mh_int_t k = mh_i32ptr_random(int_hash, rnd);
-	if (k != mh_end(int_hash))
-		return (struct tuple *) mh_i32ptr_node(int_hash, k)->val;
+	tnt_raise(ClientError, ER_UNSUPPORTED, "Hash index", "min()");
 	return NULL;
 }
 
 struct tuple *
-Hash32Index::findByKey(const char *key, u32 part_count) const
-{
-	assert(key_def->is_unique && part_count == key_def->part_count);
-	struct tuple *ret = NULL;
-	struct mh_i32ptr_node_t node = int32_key_to_node(key);
-	mh_int_t k = mh_i32ptr_get(int_hash, &node, NULL);
-	if (k != mh_end(int_hash))
-		ret = (struct tuple *) mh_i32ptr_node(int_hash, k)->val;
-#ifdef DEBUG
-	say_debug("Hash32Index find(self:%p, key:%i) = %p", self, node.key, ret);
-#endif
-	return ret;
-}
-
-struct tuple *
-Hash32Index::replace(struct tuple *old_tuple, struct tuple *new_tuple,
-		     enum dup_replace_mode mode)
-{
-	struct mh_i32ptr_node_t new_node, old_node;
-	uint32_t errcode;
-
-	if (new_tuple) {
-		struct mh_i32ptr_node_t *dup_node = &old_node;
-		new_node = int32_tuple_to_node(new_tuple, key_def);
-		mh_int_t pos = mh_i32ptr_put(int_hash, &new_node,
-					     &dup_node, NULL);
-
-		ERROR_INJECT(ERRINJ_INDEX_ALLOC,
-		{
-			mh_i32ptr_del(int_hash, pos, NULL);
-			pos = mh_end(int_hash);
-		});
-
-		if (pos == mh_end(int_hash)) {
-			tnt_raise(LoggedError, ER_MEMORY_ISSUE, (ssize_t) pos,
-				  "int hash", "key");
-		}
-		struct tuple *dup_tuple = (dup_node ?
-					   (struct tuple *) dup_node->val
-					   : NULL);
-		errcode = replace_check_dup(old_tuple, dup_tuple, mode);
-
-		if (errcode) {
-			mh_i32ptr_remove(int_hash, &new_node, NULL);
-			if (dup_node) {
-				pos = mh_i32ptr_put(int_hash, dup_node,
-						    NULL, NULL);
-				if (pos == mh_end(int_hash)) {
-					panic("Failed to allocate memory in "
-					      "recover of int hash");
-				}
-			}
-			tnt_raise(ClientError, errcode, index_n(this));
-		}
-		if (dup_tuple)
-			return dup_tuple;
-	}
-	if (old_tuple) {
-		old_node = int32_tuple_to_node(old_tuple, key_def);
-		mh_i32ptr_remove(int_hash, &old_node, NULL);
-	}
-	return old_tuple;
-}
-
-
-struct iterator *
-Hash32Index::allocIterator() const
-{
-	struct hash_i32_iterator *it = (struct hash_i32_iterator *)
-			malloc(sizeof(struct hash_i32_iterator));
-	if (it) {
-		memset(it, 0, sizeof(*it));
-		it->base.next = hash_iterator_i32_ge;
-		it->base.free = hash_iterator_free;
-	}
-	return (struct iterator *) it;
-}
-
-void
-Hash32Index::initIterator(struct iterator *ptr, enum iterator_type type,
-			  const char *key, u32 part_count) const
-{
-	assert ((part_count == 1 && key != NULL) || part_count == 0);
-	(void) part_count;
-	assert(ptr->free == hash_iterator_free);
-
-	struct hash_i32_iterator *it = (struct hash_i32_iterator *) ptr;
-	struct mh_i32ptr_node_t node;
-
-	switch (type) {
-	case ITER_GE:
-		if (key != NULL) {
-			node = int32_key_to_node(key);
-			it->h_pos = mh_i32ptr_get(int_hash, &node, NULL);
-			it->base.next = hash_iterator_i32_ge;
-			break;
-		}
-		/* Fall through. */
-	case ITER_ALL:
-		it->h_pos = mh_begin(int_hash);
-		it->base.next = hash_iterator_i32_ge;
-		break;
-	case ITER_EQ:
-		node = int32_key_to_node(key);
-		it->h_pos = mh_i32ptr_get(int_hash, &node, NULL);
-		it->base.next = hash_iterator_i32_eq;
-		break;
-	default:
-		tnt_raise(ClientError, ER_UNSUPPORTED,
-			  "Hash index", "requested iterator type");
-	}
-	it->hash = int_hash;
-}
-
-/* }}} */
-
-/* {{{ Hash64Index ************************************************/
-
-static inline struct mh_i64ptr_node_t
-int64_key_to_node(const char *key)
-{
-	u32 key_size = load_varint32(&key);
-	if (key_size != 8)
-		tnt_raise(ClientError, ER_KEY_FIELD_TYPE, "u64");
-	struct mh_i64ptr_node_t node = { *(u64 *) key, NULL };
-	return node;
-}
-
-static inline struct mh_i64ptr_node_t
-int64_tuple_to_node(struct tuple *tuple, struct key_def *key_def)
-{
-	const char *field = tuple_field_old(tuple, key_def->parts[0].fieldno);
-	struct mh_i64ptr_node_t node = int64_key_to_node(field);
-	node.val = tuple;
-	return node;
-}
-
-Hash64Index::Hash64Index(struct key_def *key_def, struct space *space)
-	: HashIndex(key_def, space)
-{
-	int64_hash = mh_i64ptr_new();
-}
-
-Hash64Index::~Hash64Index()
-{
-	mh_i64ptr_delete(int64_hash);
-}
-
-void
-Hash64Index::reserve(u32 n_tuples)
-{
-	mh_i64ptr_reserve(int64_hash, n_tuples, NULL);
-}
-
-size_t
-Hash64Index::size() const
+HashIndex::max() const
 {
-	return mh_size(int64_hash);
+	tnt_raise(ClientError, ER_UNSUPPORTED, "Hash index", "max()");
+	return NULL;
 }
 
 struct tuple *
-Hash64Index::random(u32 rnd) const
+HashIndex::random(u32 rnd) const
 {
-	mh_int_t k = mh_i64ptr_random(int64_hash, rnd);
-	if (k != mh_end(int64_hash))
-		return (struct tuple *) mh_i64ptr_node(int64_hash, k)->val;
+	uint32_t k = mh_index_random(hash, rnd);
+	if (k != mh_end(hash))
+		return *mh_index_node(hash, k);
 	return NULL;
 }
 
 struct tuple *
-Hash64Index::findByKey(const char *key, u32 part_count) const
+HashIndex::findByKey(const char *key, u32 part_count) const
 {
 	assert(key_def->is_unique && part_count == key_def->part_count);
-	(void) part_count;
 
 	struct tuple *ret = NULL;
-	struct mh_i64ptr_node_t node = int64_key_to_node(key);
-	mh_int_t k = mh_i64ptr_get(int64_hash, &node, NULL);
-	if (k != mh_end(int64_hash))
-		ret = (struct tuple *) mh_i64ptr_node(int64_hash, k)->val;
-#ifdef DEBUG
-	say_debug("Hash64Index find(self:%p, key:%i) = %p", self, node.key, ret);
-#endif
+	uint32_t k = mh_index_find(hash, key, key_def);
+	if (k != mh_end(hash))
+		ret = *mh_index_node(hash, k);
 	return ret;
 }
 
 struct tuple *
-Hash64Index::replace(struct tuple *old_tuple, struct tuple *new_tuple,
-		     enum dup_replace_mode mode)
+HashIndex::replace(struct tuple *old_tuple, struct tuple *new_tuple,
+		   enum dup_replace_mode mode)
 {
-	struct mh_i64ptr_node_t new_node, old_node;
 	uint32_t errcode;
 
 	if (new_tuple) {
-		struct mh_i64ptr_node_t *dup_node = &old_node;
-		new_node = int64_tuple_to_node(new_tuple, key_def);
-		mh_int_t pos = mh_i64ptr_put(int64_hash, &new_node,
-					     &dup_node, NULL);
+		struct tuple *dup_tuple = NULL;
+		struct tuple **dup_node = &dup_tuple;
+		uint32_t pos = mh_index_put(hash, &new_tuple,
+					    &dup_node, key_def);
 
 		ERROR_INJECT(ERRINJ_INDEX_ALLOC,
 		{
-			mh_i64ptr_del(int64_hash, pos, NULL);
-			pos = mh_end(int64_hash);
+			mh_index_del(hash, pos, key_def);
+			pos = mh_end(hash);
 		});
 
-		if (pos == mh_end(int64_hash)) {
+		if (pos == mh_end(hash)) {
 			tnt_raise(LoggedError, ER_MEMORY_ISSUE, (ssize_t) pos,
-				  "int hash", "key");
+				  "hash", "key");
 		}
-		struct tuple *dup_tuple = (dup_node ?
-					   (struct tuple *) dup_node->val :
-					   NULL);
 		errcode = replace_check_dup(old_tuple, dup_tuple, mode);
 
 		if (errcode) {
-			mh_i64ptr_remove(int64_hash, &new_node, NULL);
-			if (dup_node) {
-				pos = mh_i64ptr_put(int64_hash, dup_node, NULL, NULL);
-				if (pos == mh_end(int64_hash)) {
+			mh_index_remove(hash, &new_tuple, key_def);
+			if (dup_tuple) {
+				pos = mh_index_put(hash, &dup_tuple, NULL,
+						   key_def);
+				if (pos == mh_end(hash)) {
 					panic("Failed to allocate memory in "
 					      "recover of int hash");
 				}
@@ -598,229 +315,57 @@ Hash64Index::replace(struct tuple *old_tuple, struct tuple *new_tuple,
 	}
 
 	if (old_tuple) {
-		old_node = int64_tuple_to_node(old_tuple, key_def);
-		mh_i64ptr_remove(int64_hash, &old_node, NULL);
+		mh_index_remove(hash, &old_tuple, key_def);
 	}
-
 	return old_tuple;
 }
 
-
 struct iterator *
-Hash64Index::allocIterator() const
-{
-	struct hash_i64_iterator *it = (struct hash_i64_iterator *)
-			malloc(sizeof(struct hash_i64_iterator));
-	if (it) {
-		memset(it, 0, sizeof(*it));
-		it->base.next = hash_iterator_i64_ge;
-		it->base.free = hash_iterator_free;
+HashIndex::allocIterator() const
+{
+	struct hash_iterator *it = (struct hash_iterator *)
+			calloc(1, sizeof(*it));
+	if (it == NULL) {
+		tnt_raise(ClientError, ER_MEMORY_ISSUE,
+			  sizeof(struct hash_iterator),
+			  "HashIndex", "iterator");
 	}
 
+	it->base.next = hash_iterator_ge;
+	it->base.free = hash_iterator_free;
 	return (struct iterator *) it;
 }
 
 void
-Hash64Index::initIterator(struct iterator *ptr, enum iterator_type type,
-			  const char *key, u32 part_count) const
+HashIndex::initIterator(struct iterator *ptr, enum iterator_type type,
+			const char *key, u32 part_count) const
 {
-	assert ((part_count == 1 && key != NULL) || part_count == 0);
+	assert (key != NULL || part_count == 0);
 	(void) part_count;
 	assert(ptr->free == hash_iterator_free);
-	struct hash_i64_iterator *it = (struct hash_i64_iterator *) ptr;
-	struct mh_i64ptr_node_t node;
 
-	switch (type) {
-	case ITER_GE:
-		if (key != NULL) {
-			node = int64_key_to_node(key);
-			it->h_pos = mh_i64ptr_get(int64_hash, &node, NULL);
-			it->base.next = hash_iterator_i64_ge;
-			break;
-		}
-		/* Fall through. */
-	case ITER_ALL:
-		it->h_pos = mh_begin(int64_hash);
-		it->base.next = hash_iterator_i64_ge;
-		break;
-	case ITER_EQ:
-		node = int64_key_to_node(key);
-		it->h_pos = mh_i64ptr_get(int64_hash, &node, NULL);
-		it->base.next = hash_iterator_i64_eq;
-		break;
-	default:
-		tnt_raise(ClientError, ER_UNSUPPORTED,
-			  "Hash index", "requested iterator type");
-	}
-	it->hash = int64_hash;
-}
-
-/* }}} */
-
-/* {{{ HashStrIndex ***********************************************/
-
-static inline struct mh_lstrptr_node_t
-lstrptr_tuple_to_node(struct tuple *tuple, struct key_def *key_def)
-{
-	const char *field = tuple_field_old(tuple, key_def->parts[0].fieldno);
-	if (field == NULL)
-		tnt_raise(ClientError, ER_NO_SUCH_FIELD,
-			  key_def->parts[0].fieldno);
-
-	struct mh_lstrptr_node_t node = { field, tuple };
-	return node;
-}
-
-HashStrIndex::HashStrIndex(struct key_def *key_def, struct space *space)
-	: HashIndex(key_def, space)
-{
-	str_hash = mh_lstrptr_new();
-}
-
-HashStrIndex::~HashStrIndex()
-{
-	mh_lstrptr_delete(str_hash);
-}
-
-void
-HashStrIndex::reserve(u32 n_tuples)
-{
-	mh_lstrptr_reserve(str_hash, n_tuples, NULL);
-}
-
-
-size_t
-HashStrIndex::size() const
-{
-	return mh_size(str_hash);
-}
-
-struct tuple *
-HashStrIndex::random(u32 rnd) const
-{
-	mh_int_t k = mh_lstrptr_random(str_hash, rnd);
-	if (k != mh_end(str_hash))
-		return (struct tuple *) mh_lstrptr_node(str_hash, k)->val;
-	return NULL;
-}
-
-struct tuple *
-HashStrIndex::findByKey(const char *key, u32 part_count) const
-{
-	assert(key_def->is_unique && part_count == key_def->part_count);
-	(void) part_count;
-
-	struct tuple *ret = NULL;
-	const struct mh_lstrptr_node_t node = { key, NULL };
-	mh_int_t k = mh_lstrptr_get(str_hash, &node, NULL);
-	if (k != mh_end(str_hash))
-		ret = (struct tuple *) mh_lstrptr_node(str_hash, k)->val;
-#ifdef DEBUG
-	u32 key_size = load_varint32(&key);
-	say_debug("HashStrIndex find(self:%p, key:(%i)'%.*s') = %p",
-		  self, key_size, key_size, key, ret);
-#endif
-	return ret;
-}
-
-struct tuple *
-HashStrIndex::replace(struct tuple *old_tuple, struct tuple *new_tuple,
-	enum dup_replace_mode mode)
-{
-	struct mh_lstrptr_node_t new_node, old_node;
-	uint32_t errcode;
-
-	if (new_tuple) {
-		struct mh_lstrptr_node_t *dup_node = &old_node;
-		new_node = lstrptr_tuple_to_node(new_tuple, key_def);
-		mh_int_t pos = mh_lstrptr_put(str_hash, &new_node,
-					      &dup_node, NULL);
-
-		ERROR_INJECT(ERRINJ_INDEX_ALLOC,
-		{
-			mh_lstrptr_del(str_hash, pos, NULL);
-			pos = mh_end(str_hash);
-		});
-
-		if (pos == mh_end(str_hash)) {
-			tnt_raise(LoggedError, ER_MEMORY_ISSUE, (ssize_t) pos,
-				  "str hash", "key");
-		}
-		struct tuple *dup_tuple = dup_node
-				? (struct tuple *) dup_node->val
-				: NULL;
-		errcode = replace_check_dup(old_tuple, dup_tuple, mode);
-
-		if (errcode) {
-			mh_lstrptr_remove(str_hash, &new_node, NULL);
-			if (dup_node) {
-				pos = mh_lstrptr_put(str_hash, dup_node,
-						     NULL, NULL);
-				if (pos == mh_end(str_hash)) {
-					panic("Failed to allocate memory in "
-					      "recover of str hash");
-				}
-			}
-			tnt_raise(ClientError, errcode, index_n(this));
-		}
-		if (dup_tuple)
-			return dup_tuple;
-	}
-	if (old_tuple) {
-		old_node = lstrptr_tuple_to_node(old_tuple, key_def);
-		mh_lstrptr_remove(str_hash, &old_node, NULL);
-	}
-	return old_tuple;
-}
-
-struct iterator *
-HashStrIndex::allocIterator() const
-{
-	struct hash_lstr_iterator *it = (struct hash_lstr_iterator *)
-			malloc(sizeof(struct hash_lstr_iterator));
-	if (it) {
-		memset(it, 0, sizeof(*it));
-		it->base.next = hash_iterator_lstr_ge;
-		it->base.free = hash_iterator_free;
-	}
-	return (struct iterator *) it;
-}
-
-void
-HashStrIndex::initIterator(struct iterator *ptr, enum iterator_type type,
-			   const char *key, u32 part_count) const
-{
-	assert ((part_count == 1 && key != NULL) || part_count == 0);
-	(void) part_count;
-
-	assert(ptr->free == hash_iterator_free);
-	struct hash_lstr_iterator *it = (struct hash_lstr_iterator *) ptr;
-	struct mh_lstrptr_node_t node;
+	struct hash_iterator *it = (struct hash_iterator *) ptr;
 
 	switch (type) {
 	case ITER_GE:
 		if (key != NULL) {
-			node.key = key;
-			it->h_pos = mh_lstrptr_get(str_hash, &node, NULL);
-			it->base.next = hash_iterator_lstr_ge;
+			it->h_pos = mh_index_find(hash, key, key_def);
+			it->base.next = hash_iterator_ge;
 			break;
 		}
 		/* Fall through. */
 	case ITER_ALL:
-		it->base.next = hash_iterator_lstr_ge;
-		it->h_pos = mh_begin(str_hash);
+		it->h_pos = mh_begin(hash);
+		it->base.next = hash_iterator_ge;
 		break;
 	case ITER_EQ:
-		node.key = key;
-		it->h_pos = mh_lstrptr_get(str_hash, &node, NULL);
-		it->base.next = hash_iterator_lstr_eq;
+		it->h_pos = mh_index_find(hash, key, key_def);
+		it->base.next = hash_iterator_eq;
 		break;
 	default:
 		tnt_raise(ClientError, ER_UNSUPPORTED,
 			  "Hash index", "requested iterator type");
 	}
-	it->hash = str_hash;
+	it->hash = hash;
 }
-
 /* }}} */
-
diff --git a/src/box/hash_index.h b/src/box/hash_index.h
index 409d171cb2cf08321d209dec7870447c47d5f1e6..b315e72ae9057e235b3e3e9b572ca6109324a837 100644
--- a/src/box/hash_index.h
+++ b/src/box/hash_index.h
@@ -31,33 +31,35 @@
 
 #include "index.h"
 
+struct mh_index_t;
 
 class HashIndex: public Index {
 public:
-	static HashIndex *
-	factory(struct key_def *key_def, struct space *space);
-
 	HashIndex(struct key_def *key_def, struct space *space);
+	~HashIndex();
 
 	virtual void beginBuild();
 	virtual void buildNext(struct tuple *tuple);
 	virtual void endBuild();
 	virtual void build(Index *pk);
-	virtual size_t size() const  = 0;
+	virtual size_t size() const;
 	virtual struct tuple *min() const;
 	virtual struct tuple *max() const;
-	virtual struct tuple *random(u32 rnd) const = 0;
-	virtual struct tuple *findByKey(const char *key, u32 part_count) const  = 0;
+	virtual struct tuple *random(u32 rnd) const;
+	virtual struct tuple *findByKey(const char *key, u32 part_count) const;
 	virtual struct tuple *replace(struct tuple *old_tuple,
 				      struct tuple *new_tuple,
-				      enum dup_replace_mode mode)  = 0;
+				      enum dup_replace_mode mode);
 
-	virtual struct iterator *allocIterator() const = 0;
+	virtual struct iterator *allocIterator() const;
 	virtual void initIterator(struct iterator *iterator,
 				  enum iterator_type type,
-				  const char *key, u32 part_count) const  = 0;
+				  const char *key, u32 part_count) const;
+
+	virtual void reserve(u32 n_tuples);
 
-	virtual void reserve(u32 n_tuples) = 0;
+protected:
+	struct mh_index_t *hash;
 };
 
 #endif /* TARANTOOL_BOX_HASH_INDEX_H_INCLUDED */
diff --git a/src/box/index.cc b/src/box/index.cc
index b5e07081fdeaee9acb36d7601fd5c804b7bb70a1..9a7c212c5c6a254df8237dc12ce37e803ce5f976 100644
--- a/src/box/index.cc
+++ b/src/box/index.cc
@@ -143,7 +143,7 @@ Index::factory(enum index_type type, struct key_def *key_def, struct space *spac
 {
 	switch (type) {
 	case HASH:
-		return HashIndex::factory(key_def, space);
+		return new HashIndex(key_def, space);
 	case TREE:
 		return new TreeIndex(key_def, space);
 	case BITSET:
diff --git a/src/box/space.cc b/src/box/space.cc
index dc9eb92017f1d9a74ecb3514e291fd0f9e264f9d..90b6e3f3eb00b082b646b443dbf278fd8be52c3c 100644
--- a/src/box/space.cc
+++ b/src/box/space.cc
@@ -586,12 +586,6 @@ check_spaces(struct tarantool_cfg *conf)
 			switch (index_type) {
 			case HASH:
 				/* check hash index */
-				/* hash index must has single-field key */
-				if (key_part_count != 1) {
-					out_warning(CNF_OK, "(space = %zu index = %zu) "
-						    "hash index must has a single-field key", i, j);
-					return -1;
-				}
 				/* hash index must be unique */
 				if (!index->unique) {
 					out_warning(CNF_OK, "(space = %zu index = %zu) "
diff --git a/src/log_io.cc b/src/log_io.cc
index fdecb1840de8bde4357ef22c15040ce837ebb72f..ff1c45ff659085dd0f34fcd1fc747275e51cfe54 100644
--- a/src/log_io.cc
+++ b/src/log_io.cc
@@ -588,12 +588,16 @@ log_io_open(struct log_dir *dir, enum log_mode mode,
 	l->dir = dir;
 	l->is_inprogress = suffix == INPROGRESS;
 	if (mode == LOG_READ) {
-		if (log_io_verify_meta(l, &errmsg) != 0)
+		if (log_io_verify_meta(l, &errmsg) != 0) {
+			errmsg = strerror(errno);
 			goto error;
+		}
 	} else { /* LOG_WRITE */
 		setvbuf(l->f, NULL, _IONBF, 0);
-		if (log_io_write_header(l) != 0)
+		if (log_io_write_header(l) != 0) {
+			errmsg = strerror(errno);
 			goto error;
+		}
 	}
 	return l;
 error:
diff --git a/test/big/hash.result b/test/big/hash.result
index 6099e94a9c3c51624d3f20c588b009316310a63f..1734cc3de78fc9df1f894d1266f36c53b15695d5 100644
--- a/test/big/hash.result
+++ b/test/big/hash.result
@@ -156,25 +156,41 @@ error: 'Invalid key part count in an exact match (expected 1, got 2)'
 
 # Insert valid fieds
 
-lua box.space[11]:insert('00000000', 'value1 v1.0', 'value2 v1.0')
+lua box.space[11]:insert(0ULL, 'value1 v1.0', 'value2 v1.0')
 ---
- - 3472328296227680304: {'value1 v1.0', 'value2 v1.0'}
+ - 0: {'value1 v1.0', 'value2 v1.0'}
 ...
-lua box.space[11]:insert('00000001', 'value1 v1.0', 'value2 v1.0')
+lua box.space[11]:insert(1ULL, 'value1 v1.0', 'value2 v1.0')
 ---
- - 3544385890265608240: {'value1 v1.0', 'value2 v1.0'}
+ - 1: {'value1 v1.0', 'value2 v1.0'}
 ...
-lua box.space[11]:insert('00000002', 'value1 v1.0', 'value2 v1.0')
+lua box.space[11]:insert(2ULL, 'value1 v1.0', 'value2 v1.0')
 ---
- - 3616443484303536176: {'value1 v1.0', 'value2 v1.0'}
+ - 2: {'value1 v1.0', 'value2 v1.0'}
 ...
-lua box.space[11]:insert('00000003', 'value1 v1.0', 'value2 v1.0')
+lua box.space[11]:insert(3ULL, 'value1 v1.0', 'value2 v1.0')
 ---
- - 3688501078341464112: {'value1 v1.0', 'value2 v1.0'}
+ - 3: {'value1 v1.0', 'value2 v1.0'}
 ...
 
 # Insert invalid fields
 
+lua box.space[11]:insert(100, 'value1 v1.0', 'value2 v1.0')
+---
+error: 'Supplied key field type does not match index type: expected u64'
+...
+lua box.space[11]:insert(101, 'value1 v1.0', 'value2 v1.0')
+---
+error: 'Supplied key field type does not match index type: expected u64'
+...
+lua box.space[11]:insert(102, 'value1 v1.0', 'value2 v1.0')
+---
+error: 'Supplied key field type does not match index type: expected u64'
+...
+lua box.space[11]:insert(103, 'value1 v1.0', 'value2 v1.0')
+---
+error: 'Supplied key field type does not match index type: expected u64'
+...
 lua box.space[11]:insert('invalid key', 'value1 v1.0', 'value2 v1.0')
 ---
 error: 'Supplied key field type does not match index type: expected u64'
@@ -187,24 +203,36 @@ error: 'Supplied key field type does not match index type: expected u64'
 
 # Replace valid fieds
 
-lua box.space[11]:replace('00000003', 'value1 v1.31', 'value2 1.12')
+lua box.space[11]:replace(3ULL, 'value1 v1.31', 'value2 1.12')
 ---
- - 3688501078341464112: {'value1 v1.31', 'value2 1.12'}
+ - 3: {'value1 v1.31', 'value2 1.12'}
 ...
-lua box.space[11]:replace('00000001', 'value1 v1.32', 'value2 1.72')
+lua box.space[11]:replace(1ULL, 'value1 v1.32', 'value2 1.72')
 ---
- - 3544385890265608240: {'value1 v1.32', 'value2 1.72'}
+ - 1: {'value1 v1.32', 'value2 1.72'}
 ...
-lua box.space[11]:replace('00000002', 'value1 v1.43', 'value2 1.92')
+lua box.space[11]:replace(2ULL, 'value1 v1.43', 'value2 1.92')
 ---
- - 3616443484303536176: {'value1 v1.43', 'value2 1.92'}
+ - 2: {'value1 v1.43', 'value2 1.92'}
 ...
 
 # Replace invalid fields
 
-lua box.space[10]:replace('invalid key', 'value1 v1.0', 'value2 v1.0')
+lua box.space[11]:replace(3, 'value1 v1.31', 'value2 1.12')
 ---
-error: 'Supplied key field type does not match index type: expected u32'
+error: 'Supplied key field type does not match index type: expected u64'
+...
+lua box.space[11]:replace(1, 'value1 v1.32', 'value2 1.72')
+---
+error: 'Supplied key field type does not match index type: expected u64'
+...
+lua box.space[11]:replace(2, 'value1 v1.43', 'value2 1.92')
+---
+error: 'Supplied key field type does not match index type: expected u64'
+...
+lua box.space[11]:replace('invalid key', 'value1 v1.0', 'value2 v1.0')
+---
+error: 'Supplied key field type does not match index type: expected u64'
 ...
 
 #-----------------------------------------------------------------------------#
@@ -214,26 +242,51 @@ error: 'Supplied key field type does not match index type: expected u32'
 
 # select by valid keys
 
-lua box.space[11]:select(0, '00000000')
+lua box.space[11]:select(0, 0ULL)
 ---
- - 3472328296227680304: {'value1 v1.0', 'value2 v1.0'}
+ - 0: {'value1 v1.0', 'value2 v1.0'}
 ...
-lua box.space[11]:select(0, '00000001')
+lua box.space[11]:select(0, 1ULL)
 ---
- - 3544385890265608240: {'value1 v1.32', 'value2 1.72'}
+ - 1: {'value1 v1.32', 'value2 1.72'}
 ...
-lua box.space[11]:select(0, '00000002')
+lua box.space[11]:select(0, 2ULL)
 ---
- - 3616443484303536176: {'value1 v1.43', 'value2 1.92'}
+ - 2: {'value1 v1.43', 'value2 1.92'}
 ...
-lua box.space[11]:select(0, '00000003')
+lua box.space[11]:select(0, 3ULL)
 ---
- - 3688501078341464112: {'value1 v1.31', 'value2 1.12'}
+ - 3: {'value1 v1.31', 'value2 1.12'}
 ...
-lua box.space[11]:select(0, '00000004')
+lua box.space[11]:select(0, 4ULL)
 ---
 ...
-lua box.space[11]:select(0, '00000005')
+lua box.space[11]:select(0, 5ULL)
+---
+...
+
+# select by valid NUM keys
+
+lua box.space[11]:select(0, 0)
+---
+ - 0: {'value1 v1.0', 'value2 v1.0'}
+...
+lua box.space[11]:select(0, 1)
+---
+ - 1: {'value1 v1.32', 'value2 1.72'}
+...
+lua box.space[11]:select(0, 2)
+---
+ - 2: {'value1 v1.43', 'value2 1.92'}
+...
+lua box.space[11]:select(0, 3)
+---
+ - 3: {'value1 v1.31', 'value2 1.12'}
+...
+lua box.space[11]:select(0, 4)
+---
+...
+lua box.space[11]:select(0, 5)
 ---
 ...
 
@@ -255,26 +308,67 @@ error: 'Invalid key part count (expected [0..1], got 2)'
 
 # delete by valid keys
 
-lua box.space[11]:delete('00000000')
+lua box.space[11]:delete(0ULL)
+---
+ - 0: {'value1 v1.0', 'value2 v1.0'}
+...
+lua box.space[11]:delete(1ULL)
+---
+ - 1: {'value1 v1.32', 'value2 1.72'}
+...
+lua box.space[11]:delete(2ULL)
 ---
- - 3472328296227680304: {'value1 v1.0', 'value2 v1.0'}
+ - 2: {'value1 v1.43', 'value2 1.92'}
 ...
-lua box.space[11]:delete('00000001')
+lua box.space[11]:delete(3ULL)
 ---
- - 3544385890265608240: {'value1 v1.32', 'value2 1.72'}
+ - 3: {'value1 v1.31', 'value2 1.12'}
 ...
-lua box.space[11]:delete('00000002')
+lua box.space[11]:delete(4ULL)
 ---
- - 3616443484303536176: {'value1 v1.43', 'value2 1.92'}
 ...
-lua box.space[11]:delete('00000003')
+lua box.space[11]:delete(5ULL)
+---
+...
+lua box.space[11]:insert(0ULL, 'value1 v1.0', 'value2 v1.0')
+---
+ - 0: {'value1 v1.0', 'value2 v1.0'}
+...
+lua box.space[11]:insert(1ULL, 'value1 v1.0', 'value2 v1.0')
 ---
- - 3688501078341464112: {'value1 v1.31', 'value2 1.12'}
+ - 1: {'value1 v1.0', 'value2 v1.0'}
+...
+lua box.space[11]:insert(2ULL, 'value1 v1.0', 'value2 v1.0')
+---
+ - 2: {'value1 v1.0', 'value2 v1.0'}
+...
+lua box.space[11]:insert(3ULL, 'value1 v1.0', 'value2 v1.0')
+---
+ - 3: {'value1 v1.0', 'value2 v1.0'}
+...
+
+# delete by valid NUM keys
+
+lua box.space[11]:delete(0)
+---
+ - 0: {'value1 v1.0', 'value2 v1.0'}
+...
+lua box.space[11]:delete(1)
+---
+ - 1: {'value1 v1.0', 'value2 v1.0'}
+...
+lua box.space[11]:delete(2)
+---
+ - 2: {'value1 v1.0', 'value2 v1.0'}
+...
+lua box.space[11]:delete(3)
+---
+ - 3: {'value1 v1.0', 'value2 v1.0'}
 ...
-lua box.space[11]:delete('00000004')
+lua box.space[11]:delete(4)
 ---
 ...
-lua box.space[11]:delete('00000005')
+lua box.space[11]:delete(5)
 ---
 ...
 
diff --git a/test/big/hash.test b/test/big/hash.test
index 51c46574092d540d1b34acf510427594c2ee57da..79b510266fb3c45aa865205e710497c77712f985 100644
--- a/test/big/hash.test
+++ b/test/big/hash.test
@@ -107,17 +107,20 @@ print """
 print """
 # Insert valid fieds
 """
-exec admin "lua box.space[11]:insert('00000000', 'value1 v1.0', 'value2 v1.0')"
-exec admin "lua box.space[11]:insert('00000001', 'value1 v1.0', 'value2 v1.0')"
-exec admin "lua box.space[11]:insert('00000002', 'value1 v1.0', 'value2 v1.0')"
-exec admin "lua box.space[11]:insert('00000003', 'value1 v1.0', 'value2 v1.0')"
+exec admin "lua box.space[11]:insert(0ULL, 'value1 v1.0', 'value2 v1.0')"
+exec admin "lua box.space[11]:insert(1ULL, 'value1 v1.0', 'value2 v1.0')"
+exec admin "lua box.space[11]:insert(2ULL, 'value1 v1.0', 'value2 v1.0')"
+exec admin "lua box.space[11]:insert(3ULL, 'value1 v1.0', 'value2 v1.0')"
 
 print """
 # Insert invalid fields
 """
+exec admin "lua box.space[11]:insert(100, 'value1 v1.0', 'value2 v1.0')"
+exec admin "lua box.space[11]:insert(101, 'value1 v1.0', 'value2 v1.0')"
+exec admin "lua box.space[11]:insert(102, 'value1 v1.0', 'value2 v1.0')"
+exec admin "lua box.space[11]:insert(103, 'value1 v1.0', 'value2 v1.0')"
 exec admin "lua box.space[11]:insert('invalid key', 'value1 v1.0', 'value2 v1.0')"
 
-
 print """
 #-----------------------------------------------------------------------------#
 # 64-bit hash replace fields tests
@@ -127,15 +130,17 @@ print """
 print """
 # Replace valid fieds
 """
-exec admin "lua box.space[11]:replace('00000003', 'value1 v1.31', 'value2 1.12')"
-exec admin "lua box.space[11]:replace('00000001', 'value1 v1.32', 'value2 1.72')"
-exec admin "lua box.space[11]:replace('00000002', 'value1 v1.43', 'value2 1.92')"
+exec admin "lua box.space[11]:replace(3ULL, 'value1 v1.31', 'value2 1.12')"
+exec admin "lua box.space[11]:replace(1ULL, 'value1 v1.32', 'value2 1.72')"
+exec admin "lua box.space[11]:replace(2ULL, 'value1 v1.43', 'value2 1.92')"
 
 print """
 # Replace invalid fields
 """
-exec admin "lua box.space[10]:replace('invalid key', 'value1 v1.0', 'value2 v1.0')"
-
+exec admin "lua box.space[11]:replace(3, 'value1 v1.31', 'value2 1.12')"
+exec admin "lua box.space[11]:replace(1, 'value1 v1.32', 'value2 1.72')"
+exec admin "lua box.space[11]:replace(2, 'value1 v1.43', 'value2 1.92')"
+exec admin "lua box.space[11]:replace('invalid key', 'value1 v1.0', 'value2 v1.0')"
 
 print """
 #-----------------------------------------------------------------------------#
@@ -146,12 +151,22 @@ print """
 print """
 # select by valid keys
 """
-exec admin "lua box.space[11]:select(0, '00000000')"
-exec admin "lua box.space[11]:select(0, '00000001')"
-exec admin "lua box.space[11]:select(0, '00000002')"
-exec admin "lua box.space[11]:select(0, '00000003')"
-exec admin "lua box.space[11]:select(0, '00000004')"
-exec admin "lua box.space[11]:select(0, '00000005')"
+exec admin "lua box.space[11]:select(0, 0ULL)"
+exec admin "lua box.space[11]:select(0, 1ULL)"
+exec admin "lua box.space[11]:select(0, 2ULL)"
+exec admin "lua box.space[11]:select(0, 3ULL)"
+exec admin "lua box.space[11]:select(0, 4ULL)"
+exec admin "lua box.space[11]:select(0, 5ULL)"
+
+print """
+# select by valid NUM keys
+"""
+exec admin "lua box.space[11]:select(0, 0)"
+exec admin "lua box.space[11]:select(0, 1)"
+exec admin "lua box.space[11]:select(0, 2)"
+exec admin "lua box.space[11]:select(0, 3)"
+exec admin "lua box.space[11]:select(0, 4)"
+exec admin "lua box.space[11]:select(0, 5)"
 
 print """
 # select by invalid keys
@@ -159,7 +174,6 @@ print """
 exec admin "lua box.space[11]:select(0, 'invalid key')"
 exec admin "lua box.space[11]:select(0, '00000001', '00000002')"
 
-
 print """
 #-----------------------------------------------------------------------------#
 # 64-bit hash delete fields test
@@ -169,12 +183,27 @@ print """
 print """
 # delete by valid keys
 """
-exec admin "lua box.space[11]:delete('00000000')"
-exec admin "lua box.space[11]:delete('00000001')"
-exec admin "lua box.space[11]:delete('00000002')"
-exec admin "lua box.space[11]:delete('00000003')"
-exec admin "lua box.space[11]:delete('00000004')"
-exec admin "lua box.space[11]:delete('00000005')"
+exec admin "lua box.space[11]:delete(0ULL)"
+exec admin "lua box.space[11]:delete(1ULL)"
+exec admin "lua box.space[11]:delete(2ULL)"
+exec admin "lua box.space[11]:delete(3ULL)"
+exec admin "lua box.space[11]:delete(4ULL)"
+exec admin "lua box.space[11]:delete(5ULL)"
+
+exec admin "lua box.space[11]:insert(0ULL, 'value1 v1.0', 'value2 v1.0')"
+exec admin "lua box.space[11]:insert(1ULL, 'value1 v1.0', 'value2 v1.0')"
+exec admin "lua box.space[11]:insert(2ULL, 'value1 v1.0', 'value2 v1.0')"
+exec admin "lua box.space[11]:insert(3ULL, 'value1 v1.0', 'value2 v1.0')"
+
+print """
+# delete by valid NUM keys
+"""
+exec admin "lua box.space[11]:delete(0)"
+exec admin "lua box.space[11]:delete(1)"
+exec admin "lua box.space[11]:delete(2)"
+exec admin "lua box.space[11]:delete(3)"
+exec admin "lua box.space[11]:delete(4)"
+exec admin "lua box.space[11]:delete(5)"
 
 print """
 # delete by invalid keys
diff --git a/test/big/hash_multipart.result b/test/big/hash_multipart.result
new file mode 100644
index 0000000000000000000000000000000000000000..9c2a07d05c84664fa7db991ebaa02cf16bbca93a
--- /dev/null
+++ b/test/big/hash_multipart.result
@@ -0,0 +1,73 @@
+insert into t27 values (0, 'foo', 0, '', 1)
+Insert OK, 1 row affected
+insert into t27 values (0, 'foo', 1, '', 1)
+Insert OK, 1 row affected
+insert into t27 values (1, 'foo', 0, '', 2)
+Insert OK, 1 row affected
+insert into t27 values (1, 'foo', 1, '', 2)
+Insert OK, 1 row affected
+insert into t27 values (0, 'bar', 0, '', 3)
+Insert OK, 1 row affected
+insert into t27 values (0, 'bar', 1, '', 3)
+Insert OK, 1 row affected
+insert into t27 values (1, 'bar', 0, '', 4)
+Insert OK, 1 row affected
+insert into t27 values (1, 'bar', 1, '', 4)
+Insert OK, 1 row affected
+insert into t27 values (1, 'bar', 1, '', 5)
+An error occurred: ER_TUPLE_FOUND, 'Duplicate key exists in unique index 0'
+lua  function box.select_all(space)     space = tonumber(space)     local result = {}         for k, v in box.space[space]:pairs() do             table.insert(result, v)     end     return unpack(result) end 
+---
+...
+call box.select_all('27')
+Found 8 tuples:
+[0, 'bar', 0, '', 3]
+[0, 'bar', 1, '', 3]
+[0, 'foo', 0, '', 1]
+[0, 'foo', 1, '', 1]
+[1, 'bar', 0, '', 4]
+[1, 'bar', 1, '', 4]
+[1, 'foo', 0, '', 2]
+[1, 'foo', 1, '', 2]
+lua box.select(27, 0, 1, 'foo', 0)
+---
+ - 1: {'foo', 0, '', 2}
+...
+lua box.select(27, 0, 1, 'bar', 0)
+---
+ - 1: {'bar', 0, '', 4}
+...
+lua box.select(27, 0, 1, 'foo')
+---
+error: 'Invalid key part count in an exact match (expected 3, got 2)'
+...
+lua box.select(27, 0, 1, 'foo', 0, 0)
+---
+error: 'Invalid key part count (expected [0..3], got 4)'
+...
+lua box.select(27, 0, 1, 'foo', 'baz')
+---
+error: 'Supplied key field type does not match index type: expected u32'
+...
+lua box.select(27, 1, 1, 4)
+---
+ - 1: {'bar', 1, '', 4}
+...
+lua box.select(27, 1, 1, 5)
+---
+...
+lua box.select(27, 1, 1)
+---
+error: 'Invalid key part count in an exact match (expected 2, got 1)'
+...
+lua box.select(27, 1, 1, 'baz')
+---
+error: 'Supplied key field type does not match index type: expected u32'
+...
+lua box.space[27]:truncate()
+---
+...
+lua box.space[27]:len()
+---
+ - 0
+...
diff --git a/test/big/hash_multipart.test b/test/big/hash_multipart.test
new file mode 100644
index 0000000000000000000000000000000000000000..8d4f7347a588802520bd1e4dc9ccda5cf93f3ed6
--- /dev/null
+++ b/test/big/hash_multipart.test
@@ -0,0 +1,53 @@
+# encoding: tarantool
+#
+
+# insert rows
+exec sql "insert into t27 values (0, 'foo', 0, '', 1)"
+exec sql "insert into t27 values (0, 'foo', 1, '', 1)"
+exec sql "insert into t27 values (1, 'foo', 0, '', 2)"
+exec sql "insert into t27 values (1, 'foo', 1, '', 2)"
+exec sql "insert into t27 values (0, 'bar', 0, '', 3)"
+exec sql "insert into t27 values (0, 'bar', 1, '', 3)"
+exec sql "insert into t27 values (1, 'bar', 0, '', 4)"
+exec sql "insert into t27 values (1, 'bar', 1, '', 4)"
+# try to insert a row with a duplicate key
+exec sql "insert into t27 values (1, 'bar', 1, '', 5)"
+
+# output all rows
+lua_code = """
+function box.select_all(space)
+    space = tonumber(space)
+    local result = {}
+        for k, v in box.space[space]:pairs() do
+            table.insert(result, v)
+    end
+    return unpack(result)
+end
+"""
+exec admin "lua " + lua_code.replace('\n', ' ')
+sql.sort = True
+exec sql "call box.select_all('27')"
+sql.sort = False
+
+# primary index select
+exec admin "lua box.select(27, 0, 1, 'foo', 0)"
+exec admin "lua box.select(27, 0, 1, 'bar', 0)"
+# primary index slect with missing part
+exec admin "lua box.select(27, 0, 1, 'foo')"
+# primary index slect with extra part
+exec admin "lua box.select(27, 0, 1, 'foo', 0, 0)"
+# primary index select with wrong type
+exec admin "lua box.select(27, 0, 1, 'foo', 'baz')"
+
+# secondary index select
+exec admin "lua box.select(27, 1, 1, 4)"
+# secondary index select with no such key
+exec admin "lua box.select(27, 1, 1, 5)"
+# secondary index select with missing part
+exec admin "lua box.select(27, 1, 1)"
+# secondary index select with wrong type
+exec admin "lua box.select(27, 1, 1, 'baz')"
+
+# cleanup
+exec admin "lua box.space[27]:truncate()"
+exec admin "lua box.space[27]:len()"
diff --git a/test/big/iterator.result b/test/big/iterator.result
index 21609efd1f75cbdd77a41fc7e0513989b4c3f294..1c832db3fdf2f25c8526613911879a6a91c76c10 100644
--- a/test/big/iterator.result
+++ b/test/big/iterator.result
@@ -768,7 +768,7 @@ error: 'utils.lua:27: Key part count 4 is greater than index part count 2'
 ...
 
 #-----------------------------------------------------------------------------#
-# Iterator: hash multi-part non-unique
+# Iterator: hash single-part unique
 #-----------------------------------------------------------------------------#
 
 lua iterate(20, 4, 0, 1)
@@ -840,6 +840,68 @@ lua iterate(20, 4, 0, 1, box.index.GE, 'pid_999')
 sorted output
 ...
 
+#-----------------------------------------------------------------------------#
+# Iterator: hash multi-part unique
+#-----------------------------------------------------------------------------#
+
+lua iterate(20, 5, 1, 3, box.index.ALL)
+---
+sorted output
+$sid_001$tid_997$
+$sid_001$tid_998$
+$sid_002$tid_996$
+$sid_002$tid_997$
+$sid_003$tid_996$
+$sid_004$tid_996$
+$sid_005$tid_994$
+$sid_005$tid_995$
+$sid_005$tid_996$
+$sid_006$tid_996$
+...
+lua iterate(20, 5, 1, 3, box.index.EQ)
+---
+error: 'Invalid key part count in an exact match (expected 2, got 0)'
+...
+lua iterate(20, 5, 1, 3, box.index.EQ, 'sid_005')
+---
+error: 'Invalid key part count in an exact match (expected 2, got 1)'
+...
+lua iterate(20, 5, 1, 3, box.index.GE)
+---
+sorted output
+$sid_001$tid_997$
+$sid_001$tid_998$
+$sid_002$tid_996$
+$sid_002$tid_997$
+$sid_003$tid_996$
+$sid_004$tid_996$
+$sid_005$tid_994$
+$sid_005$tid_995$
+$sid_005$tid_996$
+$sid_006$tid_996$
+...
+lua iterate(20, 2, 1, 3, box.index.EQ, 'sid_005', 'tid_995')
+---
+$sid_005$tid_995$
+...
+lua iterate(20, 2, 1, 3, box.index.EQ, 'sid_005', 'tid_999')
+---
+...
+lua iterate(20, 2, 1, 3, box.index.EQ, 'sid_005', 'tid_995', 'a')
+---
+error: 'utils.lua:27: Key part count 3 is greater than index part count 2'
+...
+lua iterate(20, 2, 1, 3, box.index.GE, 'sid_005', 'tid_995')
+---
+$sid_005$tid_995$
+$sid_005$tid_996$
+$sid_006$tid_996$
+...
+lua iterate(20, 2, 1, 3, box.index.GE, 'sid_005', 'tid_999')
+---
+$sid_006$tid_996$
+...
+
 #-----------------------------------------------------------------------------#
 # Iterator: various
 #-----------------------------------------------------------------------------#
diff --git a/test/big/iterator.test b/test/big/iterator.test
index 53fd5531bce1051cd2e18a455fad100f1f20f723..2a737ee5b490526e48d84efa3574598ff8e0a045 100644
--- a/test/big/iterator.test
+++ b/test/big/iterator.test
@@ -135,7 +135,7 @@ exec admin "lua iterate(20, 3, 2, 4, box.index.LT, 'tid_996', 'to', 'many', 'key
 
 print """
 #-----------------------------------------------------------------------------#
-# Iterator: hash multi-part non-unique
+# Iterator: hash single-part unique
 #-----------------------------------------------------------------------------#
 """
 
@@ -148,6 +148,21 @@ exec admin "lua iterate(20, 4, 0, 1, box.index.EQ, 'pid_666')"
 exec admin "lua iterate(20, 4, 0, 1, box.index.GE, 'pid_001')"
 exec admin "lua iterate(20, 4, 0, 1, box.index.GE, 'pid_999')"
 
+print """
+#-----------------------------------------------------------------------------#
+# Iterator: hash multi-part unique
+#-----------------------------------------------------------------------------#
+"""
+exec admin "lua iterate(20, 5, 1, 3, box.index.ALL)"
+exec admin "lua iterate(20, 5, 1, 3, box.index.EQ)"
+exec admin "lua iterate(20, 5, 1, 3, box.index.EQ, 'sid_005')"
+exec admin "lua iterate(20, 5, 1, 3, box.index.GE)"
+exec admin "lua iterate(20, 2, 1, 3, box.index.EQ, 'sid_005', 'tid_995')"
+exec admin "lua iterate(20, 2, 1, 3, box.index.EQ, 'sid_005', 'tid_999')"
+exec admin "lua iterate(20, 2, 1, 3, box.index.EQ, 'sid_005', 'tid_995', 'a')"
+exec admin "lua iterate(20, 2, 1, 3, box.index.GE, 'sid_005', 'tid_995')"
+exec admin "lua iterate(20, 2, 1, 3, box.index.GE, 'sid_005', 'tid_999')"
+
 print """
 #-----------------------------------------------------------------------------#
 # Iterator: various
diff --git a/test/big/tarantool.cfg b/test/big/tarantool.cfg
index 0f9f416055bc0a9060f935ceca19c73b0fdb1141..58def14511efd800f7beeb9f97ac5e19329b1e7d 100644
--- a/test/big/tarantool.cfg
+++ b/test/big/tarantool.cfg
@@ -283,6 +283,14 @@ space[20].index[4].unique = 1
 space[20].index[4].key_field[0].fieldno = 0
 space[20].index[4].key_field[0].type = "STR"
 
+# Hash multi-part unique
+space[20].index[5].type = "HASH"
+space[20].index[5].unique = 1
+space[20].index[5].key_field[0].fieldno = 1
+space[20].index[5].key_field[0].type = "STR"
+space[20].index[5].key_field[1].fieldno = 2
+space[20].index[5].key_field[1].type = "STR"
+
 # hash::replace
 space[21].enabled = true
 
@@ -367,3 +375,22 @@ space[26].index[1].type = "HASH"
 space[26].index[1].unique = true
 space[26].index[1].key_field[0].fieldno = 0
 space[26].index[1].key_field[0].type = "NUM"
+
+# Multi-part hash
+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[0].key_field[1].fieldno = 1
+space[27].index[0].key_field[1].type = STR
+space[27].index[0].key_field[2].fieldno = 2
+space[27].index[0].key_field[2].type = NUM
+
+space[27].index[1].type = HASH
+space[27].index[1].unique = 1
+space[27].index[1].key_field[0].fieldno = 2
+space[27].index[1].key_field[0].type = NUM
+space[27].index[1].key_field[1].fieldno = 4
+space[27].index[1].key_field[1].type = NUM