diff --git a/include/errcode.h b/include/errcode.h
index 256b1e2c89a04ef7077802bd0a6289015585d2d9..685d612a3fd12d880c556b631293f2f6616cef2b 100644
--- a/include/errcode.h
+++ b/include/errcode.h
@@ -84,7 +84,7 @@ enum { TNT_ERRMSG_MAX = 512 };
 	/* 29 */_(ER_LAST_DROP,			2, "Can't drop the primary key in a system space, space id %u") \
 	/* 30 */_(ER_DROP_PRIMARY_KEY,		2, "Can't drop primary key in space %u while secondary keys exist") \
 	/* 31 */_(ER_SPACE_ARITY,		2, "Tuple field count %u does not match space %u arity %u") \
-	/* 32 */_(ER_UNUSED32,			2, "Unused32") \
+	/* 32 */_(ER_INDEX_ARITY,		2, "Tuple field count %u is less than required by a defined index (expected %u)") \
 	/* 33 */_(ER_UNUSED33,			2, "Unused33") \
 	/* 34 */_(ER_UNUSED34,			2, "Unused34") \
 	/* 35 */_(ER_UNUSED35,			2, "Unused35") \
diff --git a/src/box/alter.cc b/src/box/alter.cc
index f60fd1030487f5126a3187cda4648b58778a74ce..013d3e0e516cb2f3e950d33a0922c756fc97a0e3 100644
--- a/src/box/alter.cc
+++ b/src/box/alter.cc
@@ -69,8 +69,9 @@ key_def_new_from_tuple(struct tuple *tuple)
 	enum index_type type = STR2ENUM(index_type, type_str);
 	uint32_t is_unique = tuple_field_u32(tuple, INDEX_IS_UNIQUE);
 	uint32_t part_count = tuple_field_u32(tuple, INDEX_PART_COUNT);
+	const char *name = tuple_field_cstr(tuple, NAME);
 
-	struct key_def *key_def = key_def_new(index_id, type,
+	struct key_def *key_def = key_def_new(id, index_id, name, type,
 					      is_unique > 0, part_count);
 	auto scoped_guard =
 		make_scoped_guard([=] { key_def_delete(key_def); });
@@ -88,7 +89,7 @@ key_def_new_from_tuple(struct tuple *tuple)
 			STR2ENUM(field_type, field_type_str);
 		key_def_set_part(key_def, i, fieldno, field_type);
 	}
-	key_def_check(id, key_def);
+	key_def_check(key_def);
 	scoped_guard.is_active = false;
 	return key_def;
 }
@@ -259,7 +260,8 @@ alter_space_commit(struct trigger *trigger, void * /* event */)
 			space_swap_index(alter->old_space,
 					 alter->new_space,
 					 index_id(old_index),
-					 index_id(new_index));
+					 index_id(new_index),
+					 false);
 		}
 	}
 	/*
@@ -562,7 +564,7 @@ ModifyIndex::commit(struct alter_space *alter)
 {
 	/* Move the old index to the new place but preserve */
 	space_swap_index(alter->old_space, alter->new_space,
-			 old_key_def->id, new_key_def->id);
+			 old_key_def->iid, new_key_def->iid, true);
 }
 
 ModifyIndex::~ModifyIndex()
@@ -661,7 +663,7 @@ AddIndex::prepare(struct alter_space *alter)
 void
 AddIndex::alter_def(struct alter_space *alter)
 {
-	rlist_add_entry(&alter->key_list, new_key_def, link);
+	rlist_add_tail_entry(&alter->key_list, new_key_def, link);
 }
 
 /**
@@ -716,7 +718,7 @@ AddIndex::alter(struct alter_space *alter)
 	 * Possible both during and after recovery.
 	 */
 	if (alter->new_space->engine.state == READY_NO_KEYS) {
-		if (new_key_def->id == 0) {
+		if (new_key_def->iid == 0) {
 			/*
 			 * Adding a primary key: bring the space
 			 * up to speed with the current recovery
@@ -745,10 +747,10 @@ AddIndex::alter(struct alter_space *alter)
 		return;
 	}
 	Index *pk = index_find(alter->old_space, 0);
-	Index *new_index = index_find(alter->new_space, new_key_def->id);
+	Index *new_index = index_find(alter->new_space, new_key_def->iid);
 	/* READY_PRIMARY_KEY is a state that only occurs during WAL recovery. */
 	if (alter->new_space->engine.state == READY_PRIMARY_KEY) {
-		if (new_key_def->id == 0) {
+		if (new_key_def->iid == 0) {
 			/*
 			 * Bulk rebuild of the new primary key
 			 * from old primary key - it is safe to do
@@ -780,11 +782,17 @@ AddIndex::alter(struct alter_space *alter)
 	new_index->endBuild();
 	/* Build the new index. */
 	struct tuple *tuple;
+	struct tuple_format *format = alter->new_space->format;
+	char *field_map = ((char *) palloc(fiber->gc_pool,
+					   format->field_map_size) +
+			   format->field_map_size);
 	while ((tuple = it->next(it))) {
 		/*
-		 * @todo:
-		 * tuple_format_validate(alter->new_space->format,
-		 * tuple)
+		 * Check that the tuple is OK according to the
+		 * new format.
+		 */
+		tuple_init_field_map(format, tuple, (uint32_t *) field_map);
+		/*
 		 * @todo: better message if there is a duplicate.
 		 */
 		struct tuple *old_tuple =
@@ -994,10 +1002,10 @@ on_replace_dd_index(struct trigger * /* trigger */, void *event)
 	struct tuple *old_tuple = txn->old_tuple;
 	struct tuple *new_tuple = txn->new_tuple;
 	uint32_t id = tuple_field_u32(old_tuple ? old_tuple : new_tuple, ID);
-	uint32_t index_id = tuple_field_u32(old_tuple ? old_tuple : new_tuple,
-					    INDEX_ID);
+	uint32_t iid = tuple_field_u32(old_tuple ? old_tuple : new_tuple,
+				       INDEX_ID);
 	struct space *old_space = space_find(id);
-	Index *old_index = space_index(old_space, index_id);
+	Index *old_index = space_index(old_space, iid);
 	struct alter_space *alter = alter_space_new();
 	auto scoped_guard =
 		make_scoped_guard([=] { alter_space_delete(alter); });
diff --git a/src/box/box_lua.cc b/src/box/box_lua.cc
index 7d43c1c28381782d0bf61ccac063708e4ff4540b..868ec4fd93724fde3d6a7162a5fc628ba87a235f 100644
--- a/src/box/box_lua.cc
+++ b/src/box/box_lua.cc
@@ -688,7 +688,7 @@ lua_checkindex(struct lua_State *L, int i)
 }
 
 static int
-lbox_index_new(struct lua_State *L)
+lbox_index_bind(struct lua_State *L)
 {
 	uint32_t id = (uint32_t) luaL_checkint(L, 1); /* get space id */
 	uint32_t iid = (uint32_t) luaL_checkint(L, 2); /* get index id in */
@@ -714,7 +714,7 @@ static int
 lbox_index_tostring(struct lua_State *L)
 {
 	Index *index = lua_checkindex(L, 1);
-	lua_pushfstring(L, "index %d", (int) index_id(index));
+	lua_pushfstring(L, " index %d", (int) index_id(index));
 	return 1;
 }
 
@@ -976,7 +976,7 @@ static const struct luaL_reg lbox_index_meta[] = {
 };
 
 static const struct luaL_reg indexlib [] = {
-	{"new", lbox_index_new},
+	{"bind", lbox_index_bind},
 	{NULL, NULL}
 };
 
diff --git a/src/box/box_lua_space.cc b/src/box/box_lua_space.cc
index a3ea0ebe4433ba06a83edd89391d1e387596f304..edc3c5aa28748a7a97058873ea09454ded864188 100644
--- a/src/box/box_lua_space.cc
+++ b/src/box/box_lua_space.cc
@@ -93,16 +93,23 @@ lbox_fillspace(struct lua_State *L, struct space *space, int i)
 		if (index == NULL)
 			continue;
 		struct key_def *key_def = index->key_def;
-		lua_pushnumber(L, key_def->id);
+		lua_pushnumber(L, key_def->iid);
 		lua_newtable(L);		/* space.index[i] */
 
-		lua_pushstring(L, "unique");
 		lua_pushboolean(L, key_def->is_unique);
-		lua_settable(L, -3);
+		lua_setfield(L, -2, "unique");
 
-		lua_pushstring(L, "type");
 		lua_pushstring(L, index_type_strs[key_def->type]);
-		lua_settable(L, -3);
+		lua_setfield(L, -2, "type");
+
+		lua_pushnumber(L, key_def->iid);
+		lua_setfield(L, -2, "id");
+
+		lua_pushnumber(L, key_def->space_id);
+		lua_setfield(L, -2, "n");
+
+		lua_pushstring(L, key_def->name);
+		lua_setfield(L, -2, "name");
 
 		lua_pushstring(L, "key_field");
 		lua_newtable(L);
@@ -111,21 +118,21 @@ lbox_fillspace(struct lua_State *L, struct space *space, int i)
 			lua_pushnumber(L, j);
 			lua_newtable(L);
 
-			lua_pushstring(L, "type");
 			lua_pushstring(L,
 			       field_type_strs[key_def->parts[j].type]);
-			lua_settable(L, -3);
+			lua_setfield(L, -2, "type");
 
-			lua_pushstring(L, "fieldno");
 			lua_pushnumber(L, key_def->parts[j].fieldno);
-			lua_settable(L, -3);
+			lua_setfield(L, -2, "fieldno");
 
-			lua_settable(L, -3);
+			lua_settable(L, -3); /* index[i].key_field[j] */
 		}
 
-		lua_settable(L, -3);	/* index[i].key_field */
+		lua_settable(L, -3); /* space.index[i].key_field */
 
-		lua_settable(L, -3);	/* space.index[i] */
+		lua_settable(L, -3); /* space.index[i] */
+		lua_rawgeti(L, -1, key_def->iid);
+		lua_setfield(L, -2, key_def->name);
 	}
 
 	lua_pop(L, 1); /* pop the index field */
@@ -175,9 +182,7 @@ box_lua_space_new(struct lua_State *L, struct space *space)
 		lua_settable(L, -4);
 	}
 	lbox_fillspace(L, space, lua_gettop(L));
-	lua_pushstring(L, space_name(space));
-	lua_insert(L, -2);
-	lua_rawset(L, -3);
+	lua_setfield(L, -2, space_name(space));
 
 	lua_pop(L, 2); /* box, space */
 }
diff --git a/src/box/index.h b/src/box/index.h
index a90b5c71c5aa90fb266dc572c83b8ee8f1ec2832..860fb0e01481d97243788884b82f12f1b89a28e0 100644
--- a/src/box/index.h
+++ b/src/box/index.h
@@ -232,7 +232,7 @@ replace_check_dup(struct tuple *old_tuple, struct tuple *dup_tuple,
 static inline uint32_t
 index_id(const Index *index)
 {
-	return index->key_def->id;
+	return index->key_def->iid;
 }
 
 /** True if this index is a primary key. */
diff --git a/src/box/key_def.cc b/src/box/key_def.cc
index 0fb0320ded756048cdf9c6dfcd81ba55bce4b438..03d363f07e9e42b38fd876b2c6c4f5f2979973ee 100644
--- a/src/box/key_def.cc
+++ b/src/box/key_def.cc
@@ -28,23 +28,33 @@
  */
 #include "key_def.h"
 #include <stdlib.h>
+#include <stdio.h>
 #include "exception.h"
 
 const char *field_type_strs[] = {"UNKNOWN", "NUM", "NUM64", "STR", "\0"};
 STRS(index_type, ENUM_INDEX_TYPE);
 
 struct key_def *
-key_def_new(uint32_t id, enum index_type type, bool is_unique,
-	    uint32_t part_count)
+key_def_new(uint32_t space_id, uint32_t iid, const char *name,
+	    enum index_type type, bool is_unique, uint32_t part_count)
 {
 	uint32_t parts_size = sizeof(struct key_part) * part_count;
 	size_t sz = parts_size + sizeof(struct key_def);
 	struct key_def *def = (struct key_def *) malloc(sz);
-	if (def == NULL)
+	if (def == NULL) {
 		tnt_raise(LoggedError, ER_MEMORY_ISSUE,
 			  sz, "struct key_def", "malloc");
+	}
+	int n = snprintf(def->name, sizeof(def->name), "%s", name);
+	if (n >= sizeof(def->name)) {
+		free(def);
+		tnt_raise(LoggedError, ER_MODIFY_INDEX,
+			  (unsigned) iid, (unsigned) space_id,
+			  "index name is too long");
+	}
 	def->type = type;
-	def->id = id;
+	def->space_id = space_id;
+	def->iid = iid;
 	def->is_unique = is_unique;
 	def->part_count = part_count;
 
@@ -79,8 +89,10 @@ key_part_cmp(const struct key_part *parts1, uint32_t part_count1,
 int
 key_def_cmp(const struct key_def *key1, const struct key_def *key2)
 {
-	if (key1->id != key2->id)
-		return key1->id < key2->id ? -1 : 1;
+	if (key1->iid != key2->iid)
+		return key1->iid < key2->iid ? -1 : 1;
+	if (strcmp(key1->name, key2->name))
+		return strcmp(key1->name, key2->name);
 	if (key1->type != key2->type)
 		return (int) key1->type < (int) key2->type ? -1 : 1;
 	if (key1->is_unique != key2->is_unique)
@@ -91,11 +103,11 @@ key_def_cmp(const struct key_def *key1, const struct key_def *key2)
 }
 
 void
-key_list_del_key(struct rlist *key_list, uint32_t id)
+key_list_del_key(struct rlist *key_list, uint32_t iid)
 {
 	struct key_def *key;
 	rlist_foreach_entry(key, key_list, link) {
-		if (key->id == id) {
+		if (key->iid == iid) {
 			rlist_del_entry(key, link);
 			return;
 		}
@@ -104,32 +116,43 @@ key_list_del_key(struct rlist *key_list, uint32_t id)
 }
 
 void
-key_def_check(uint32_t id, struct key_def *key_def)
+key_def_check(struct key_def *key_def)
 {
-	if (key_def->id >= BOX_INDEX_MAX) {
+	if (key_def->iid >= BOX_INDEX_MAX) {
 		tnt_raise(ClientError, ER_MODIFY_INDEX,
-			  (unsigned) key_def->id, (unsigned) id,
+			  (unsigned) key_def->iid,
+			  (unsigned) key_def->space_id,
 			  "index id too big");
 	}
+	if (key_def->iid == 0 && key_def->is_unique == false) {
+		tnt_raise(ClientError, ER_MODIFY_INDEX,
+			  (unsigned) key_def->iid,
+			  (unsigned) key_def->space_id,
+			  "primary key must be unique");
+	}
 	if (key_def->part_count == 0) {
 		tnt_raise(ClientError, ER_MODIFY_INDEX,
-			  (unsigned) key_def->id, (unsigned) id,
+			  (unsigned) key_def->iid,
+			  (unsigned) key_def->space_id,
 			  "part count must be positive");
 	}
 	if (key_def->part_count > BOX_INDEX_PART_MAX) {
 		tnt_raise(ClientError, ER_MODIFY_INDEX,
-			  (unsigned) key_def->id, (unsigned) id,
+			  (unsigned) key_def->iid,
+			  (unsigned) key_def->space_id,
 			  "too many key parts");
 	}
 	for (uint32_t i = 0; i < key_def->part_count; i++) {
 		if (key_def->parts[i].type == field_type_MAX) {
 			tnt_raise(ClientError, ER_MODIFY_INDEX,
-				  (unsigned) key_def->id, (unsigned) id,
+				  (unsigned) key_def->iid,
+				  (unsigned) key_def->space_id,
 				  "unknown field type");
 		}
 		if (key_def->parts[i].fieldno > BOX_INDEX_FIELD_MAX) {
 			tnt_raise(ClientError, ER_MODIFY_INDEX,
-				  (unsigned) key_def->id, (unsigned) id,
+				  (unsigned) key_def->iid,
+				  (unsigned) key_def->space_id,
 				  "field no is too big");
 		}
 		for (uint32_t j = 0; j < i; j++) {
@@ -140,8 +163,8 @@ key_def_check(uint32_t id, struct key_def *key_def)
 			if (key_def->parts[i].fieldno ==
 			    key_def->parts[j].fieldno) {
 				tnt_raise(ClientError, ER_MODIFY_INDEX,
-					  (unsigned) key_def->id,
-					  (unsigned) id,
+					  (unsigned) key_def->iid,
+					  (unsigned) key_def->space_id,
 					  "same key part is indexed twice");
 			}
 		}
@@ -150,7 +173,8 @@ key_def_check(uint32_t id, struct key_def *key_def)
 	case HASH:
 		if (! key_def->is_unique) {
 			tnt_raise(ClientError, ER_MODIFY_INDEX,
-				  (unsigned) key_def->id, (unsigned) id,
+				  (unsigned) key_def->iid,
+				  (unsigned) key_def->space_id,
 				  "HASH index must be unique");
 		}
 		break;
@@ -160,18 +184,21 @@ key_def_check(uint32_t id, struct key_def *key_def)
 	case BITSET:
 		if (key_def->part_count != 1) {
 			tnt_raise(ClientError, ER_MODIFY_INDEX,
-				  (unsigned) key_def->id, (unsigned) id,
-				    "BITSET index key can not be multipart");
+				  (unsigned) key_def->iid,
+				  (unsigned) key_def->space_id,
+				  "BITSET index key can not be multipart");
 		}
 		if (key_def->is_unique) {
 			tnt_raise(ClientError, ER_MODIFY_INDEX,
-				  (unsigned) key_def->id, (unsigned) id,
+				  (unsigned) key_def->iid,
+				  (unsigned) key_def->space_id,
 				  "BITSET can not be unique");
 		}
 		break;
 	default:
 		tnt_raise(ClientError, ER_INDEX_TYPE,
-			  (unsigned) key_def->id, (unsigned) id);
+			  (unsigned) key_def->iid,
+			  (unsigned) key_def->space_id);
 		break;
 	}
 }
diff --git a/src/box/key_def.h b/src/box/key_def.h
index 10daefc5821bd1ec9a3d6c83c91ec0226d531ebd..56bfc26dff50a2472b9bcd69ad2638bf5044fdc2 100644
--- a/src/box/key_def.h
+++ b/src/box/key_def.h
@@ -82,7 +82,11 @@ struct key_def {
 	/* A link in key list. */
 	struct rlist link;
 	/** Ordinal index number in the index array. */
-	uint32_t id;
+	uint32_t iid;
+	/* Space id. */
+	uint32_t space_id;
+	/** Index name. */
+	char name[BOX_NAME_MAX + 1];
 	/** The size of the 'parts' array. */
 	uint32_t part_count;
 	/** Index type. */
@@ -95,13 +99,14 @@ struct key_def {
 
 /** Initialize a pre-allocated key_def. */
 struct key_def *
-key_def_new(uint32_t id, enum index_type type,
-	    bool is_unique, uint32_t part_count);
+key_def_new(uint32_t space_id, uint32_t iid, const char *name,
+	    enum index_type type, bool is_unique, uint32_t part_count);
 
 static inline struct key_def *
 key_def_dup(struct key_def *def)
 {
-	struct key_def *dup = key_def_new(def->id, def->type, def->is_unique,
+	struct key_def *dup = key_def_new(def->space_id, def->iid, def->name,
+					  def->type, def->is_unique,
 					  def->part_count);
 	if (dup) {
 		memcpy(dup->parts, def->parts,
@@ -146,8 +151,8 @@ key_part_cmp(const struct key_part *parts1, uint32_t part_count1,
 
 /**
  * One key definition is greater than the other if it's id is
- * greater, it's index type is greater (HASH < TREE < BITSET)
- * or its key part array is greater.
+ * greater, it's name is greater,  it's index type is greater
+ * (HASH < TREE < BITSET) or its key part array is greater.
  */
 int
 key_def_cmp(const struct key_def *key1, const struct key_def *key2);
@@ -175,7 +180,7 @@ key_list_del_key(struct rlist *key_list, uint32_t id);
  * @param type_str  type name (to produce a nice error)
  */
 void
-key_def_check(uint32_t id, struct key_def *key_def);
+key_def_check(struct key_def *key_def);
 
 /** Space metadata. */
 struct space_def {
diff --git a/src/box/lua/schema.lua b/src/box/lua/schema.lua
index 04bdcf82dd2215b22d8f5f1a0fc9893e9f1587cb..f92066fb79e468691642401ccac3989f91ed2e30 100644
--- a/src/box/lua/schema.lua
+++ b/src/box/lua/schema.lua
@@ -91,6 +91,32 @@ box.schema.index.drop = function(space_id, index_id)
     local _index = box.space[box.schema.INDEX_ID]
     _index:delete(space_id, index_id)
 end
+box.schema.index.rename = function(space_id, index_id, name)
+    local _index = box.space[box.schema.INDEX_ID]
+    _index:update({space_id, index_id}, "=p", 2, name)
+end
+box.schema.index.alter = function(space_id, index_id, options)
+    if options == nil then
+        return
+    end
+    local ops = ""
+    local args = {}
+    local function add_op(op, opno)
+        if op then
+            ops = ops.."=p"
+            table.insert(args, opno)
+            table.insert(args, op)
+        end
+    end
+    add_op(options.id, 1)
+    add_op(options.name, 2)
+    add_op(options.type, 3)
+    if options.unique ~= nil then
+        add_op(options.unique and 1 or 0, 4)
+    end
+    local _index = box.space[box.schema.INDEX_ID]
+    _index:update({space_id, index_id}, ops, unpack(args))
+end
 
 function box.schema.space.bless(space)
     local index_mt = {}
@@ -158,9 +184,18 @@ function box.schema.space.bless(space)
         end
         return unpack(range)
     end
+    index_mt.select = function(index, ...)
+        return box.select(index.n, index.id, ...)
+    end
     index_mt.drop = function(index)
         return box.schema.index.drop(index.n, index.id)
     end
+    index_mt.rename = function(index, name)
+        return box.schema.index.rename(index.n, index.id, name)
+    end
+    index_mt.alter= function(index, options)
+        return box.schema.index.alter(index.n, index.id, options)
+    end
     --
     local space_mt = {}
     space_mt.len = function(space) return space.index[0]:len() end
@@ -219,10 +254,10 @@ function box.schema.space.bless(space)
     setmetatable(space, space_mt)
     if type(space.index) == 'table' and space.enabled then
         for j, index in pairs(space.index) do
-            rawset(index, 'idx', box.index.new(space.n, j))
-            rawset(index, 'id', j)
-            rawset(index, 'n', space.n)
-            setmetatable(index, index_mt)
+            if type(j) == 'number' then
+                rawset(index, 'idx', box.index.bind(space.n, j))
+                setmetatable(index, index_mt)
+            end
         end
     end
 end
diff --git a/src/box/schema.cc b/src/box/schema.cc
index 967d48d0880ff230d36ba0ab377e1d1097480f0f..065f28ec10c5dfc5de59a2d76afa3f00255fe820 100644
--- a/src/box/schema.cc
+++ b/src/box/schema.cc
@@ -72,6 +72,13 @@ space_by_id(uint32_t id)
 	return (struct space *) mh_i32ptr_node(spaces, space)->val;
 }
 
+extern "C" const char *
+space_name_by_id(uint32_t id)
+{
+	struct space *space = space_by_id(id);
+	return space ? space_name(space) : "";
+}
+
 /**
  * Visit all spaces and apply 'func'.
  */
@@ -210,25 +217,30 @@ schema_init()
 	 * (and re-created) first.
 	 */
 	/* _schema - key/value space with schema description */
-	struct space_def space_def = { SC_SCHEMA_ID, 0, "_schema" };
-	struct key_def *key_def = key_def_new(0 /* id */,
+	struct space_def def = { SC_SCHEMA_ID, 0, "_schema" };
+	struct key_def *key_def = key_def_new(def.id,
+					      0 /* index id */,
+					      "primary", /* name */
 					      TREE /* index type */,
 					      true /* unique */,
 					      1 /* part count */);
 	key_def_set_part(key_def, 0 /* part no */, 0 /* field no */, STRING);
-	(void) sc_space_new(&space_def, key_def, NULL);
+	(void) sc_space_new(&def, key_def, NULL);
 
 	/* _space - home for all spaces. */
-	space_def.id = SC_SPACE_ID;
-	snprintf(space_def.name, sizeof(space_def.name), "_space");
+	key_def->space_id = def.id = SC_SPACE_ID;
+	snprintf(def.name, sizeof(def.name), "_space");
 	key_def_set_part(key_def, 0 /* part no */, 0 /* field no */, NUM);
 
-	(void) sc_space_new(&space_def, key_def,
-			    &alter_space_on_replace_space);
+	(void) sc_space_new(&def, key_def, &alter_space_on_replace_space);
 	key_def_delete(key_def);
 
 	/* _index - definition of indexes in all spaces */
-	key_def = key_def_new(0 /* id */,
+	def.id = SC_INDEX_ID;
+	snprintf(def.name, sizeof(def.name), "_index");
+	key_def = key_def_new(def.id,
+			      0 /* index id */,
+			      "primary",
 			      TREE /* index type */,
 			      true /* unique */,
 			      2 /* part count */);
@@ -236,10 +248,7 @@ schema_init()
 	key_def_set_part(key_def, 0 /* part no */, 0 /* field no */, NUM);
 	/* index no */
 	key_def_set_part(key_def, 1 /* part no */, 1 /* field no */, NUM);
-	space_def.id = SC_INDEX_ID;
-	snprintf(space_def.name, sizeof(space_def.name), "_index");
-	(void) sc_space_new(&space_def, key_def,
-			    &alter_space_on_replace_index);
+	(void) sc_space_new(&def, key_def, &alter_space_on_replace_index);
 	key_def_delete(key_def);
 }
 
diff --git a/src/box/schema.h b/src/box/schema.h
index a97076ce073981b80637443983185228e9e9a6a6..76873bb9495151197e1a3405bc988447b2508ea4 100644
--- a/src/box/schema.h
+++ b/src/box/schema.h
@@ -60,6 +60,10 @@ space_foreach(void (*func)(struct space *sp, void *udata), void *udata);
 extern "C" struct space *
 space_by_id(uint32_t id);
 
+/** No-throw conversion of space id to space name */
+extern "C" const char *
+space_name_by_id(uint32_t id);
+
 static inline struct space *
 space_find(uint32_t id)
 {
diff --git a/src/box/space.cc b/src/box/space.cc
index 2b08ad60366a872a9a4f5ab6495f017e3562b14f..644f279fe105a2954a7ae6d811cf6fdc8978f01c 100644
--- a/src/box/space.cc
+++ b/src/box/space.cc
@@ -52,7 +52,7 @@ space_new(struct space_def *def, struct rlist *key_list)
 	struct key_def *key_def;
 	rlist_foreach_entry(key_def, key_list, link) {
 		index_count++;
-		index_id_max = MAX(index_id_max, key_def->id);
+		index_id_max = MAX(index_id_max, key_def->iid);
 	}
 	size_t sz = sizeof(struct space) +
 		(index_count + index_id_max + 1) * sizeof(Index *);
@@ -76,7 +76,7 @@ space_new(struct space_def *def, struct rlist *key_list)
 	space->index_id_max = index_id_max;
 	/* fill space indexes */
 	rlist_foreach_entry(key_def, key_list, link) {
-		space->index_map[key_def->id] = Index::factory(key_def);
+		space->index_map[key_def->iid] = Index::factory(key_def);
 	}
 	space_fill_index_map(space);
 	space->engine = engine_no_keys;
@@ -307,11 +307,17 @@ space_dump_def(const struct space *space, struct rlist *key_list)
 
 void
 space_swap_index(struct space *lhs, struct space *rhs, uint32_t lhs_id,
-		 uint32_t rhs_id)
+		 uint32_t rhs_id, bool keep_key_def)
 {
 	Index *tmp = lhs->index_map[lhs_id];
 	lhs->index_map[lhs_id] = rhs->index_map[rhs_id];
 	rhs->index_map[rhs_id] = tmp;
+	if (keep_key_def) {
+		struct key_def *tmp = lhs->index_map[lhs_id]->key_def;
+		lhs->index_map[lhs_id]->key_def =
+			rhs->index_map[rhs_id]->key_def;
+		rhs->index_map[rhs_id]->key_def = tmp;
+	}
 }
 
 extern "C" void
diff --git a/src/box/space.h b/src/box/space.h
index 4932abf478943d2b192337da9ec126d81a178056..98ba9fb211440270bbd56e56e645a9514ce6810e 100644
--- a/src/box/space.h
+++ b/src/box/space.h
@@ -259,7 +259,7 @@ space_dump_def(const struct space *space, struct rlist *key_list);
  */
 void
 space_swap_index(struct space *lhs, struct space *rhs, uint32_t lhs_id,
-		 uint32_t rhs_id);
+		 uint32_t rhs_id, bool keep_key_def);
 
 /** Rebuild index map in a space after a series of swap index. */
 void
diff --git a/src/box/tuple.cc b/src/box/tuple.cc
index 33375d7cc89555b5a4727ae47068b6a0182c4bbf..bc4a2862fff02e87116298b8f13b211a6da16e79 100644
--- a/src/box/tuple.cc
+++ b/src/box/tuple.cc
@@ -63,7 +63,7 @@ field_type_create(enum field_type *types, uint32_t field_count,
 			if (*ptype != UNKNOWN && *ptype != part->type) {
 				tnt_raise(ClientError,
 					  ER_FIELD_TYPE_MISMATCH,
-					  key_def->id, part - key_def->parts,
+					  key_def->iid, part - key_def->parts,
 					  field_type_strs[part->type],
 					  field_type_strs[*ptype]);
 			}
@@ -216,19 +216,19 @@ tuple_format_new(struct rlist *key_list)
  * Validate a new tuple format and initialize tuple-local
  * format data.
  */
-static inline void
-tuple_init_field_map(struct tuple *tuple, struct tuple_format *format)
+void
+tuple_init_field_map(struct tuple_format *format, struct tuple *tuple, uint32_t *field_map)
 {
 	/* Check to see if the tuple has a sufficient number of fields. */
 	if (tuple->field_count < format->field_count)
-		tnt_raise(IllegalParams,
-			  "tuple must have all indexed fields");
+		tnt_raise(ClientError, ER_INDEX_ARITY,
+			  (unsigned) tuple->field_count,
+			  (unsigned) format->field_count);
 
 	int32_t *offset = format->offset;
 	enum field_type *type = format->types;
 	enum field_type *end = format->types + format->field_count;
 	const char *pos = tuple->data;
-	uint32_t *field_map = (uint32_t *) tuple;
 	uint32_t i = 0;
 
 	for (; type < end; offset++, type++, i++) {
@@ -457,7 +457,7 @@ tuple_update(struct tuple_format *format,
 
 	try {
 		tuple_update_execute(update, new_tuple->data);
-		tuple_init_field_map(new_tuple, format);
+		tuple_init_field_map(format, new_tuple, (uint32_t *)new_tuple);
 	} catch (const Exception&) {
 		tuple_delete(new_tuple);
 		throw;
@@ -494,7 +494,7 @@ tuple_new(struct tuple_format *format, uint32_t field_count,
 	new_tuple->field_count = field_count;
 	memcpy(new_tuple->data, end - tuple_len, tuple_len);
 	try {
-		tuple_init_field_map(new_tuple, format);
+		tuple_init_field_map(format, new_tuple, (uint32_t *)new_tuple);
 	} catch (...) {
 		tuple_delete(new_tuple);
 		throw;
diff --git a/src/box/tuple.h b/src/box/tuple.h
index 0297dc7677fc3d2b4eb8aa1c57497b85a528123c..b0ba2ff7dd6fd759582011d94e5ef1049e41ac7c 100644
--- a/src/box/tuple.h
+++ b/src/box/tuple.h
@@ -353,6 +353,10 @@ tuple_next_cstr(struct tuple_iterator *it);
 void
 tuple_print(struct tbuf *buf, const struct tuple *tuple);
 
+void
+tuple_init_field_map(struct tuple_format *format,
+		     struct tuple *tuple, uint32_t *field_map);
+
 struct tuple *
 tuple_update(struct tuple_format *new_format,
 	     void *(*region_alloc)(void *, size_t), void *alloc_ctx,
diff --git a/test/big/sql.result b/test/big/sql.result
index 699f7d0a80103b15f6b6c1d7b2f26a65871e2169..55cfbcea3bb3fab882882953cde5cfe903f8b25a 100644
--- a/test/big/sql.result
+++ b/test/big/sql.result
@@ -64,14 +64,14 @@ box.space[0]:truncate()
 #
 insert into t0 values ('Britney')
 ---
-- error: 'Illegal parameters, tuple must have all indexed fields'
+- error: 'Tuple field count 1 is less than required by a defined index (expected 2)'
 ...
 select * from t0 where k1='Anything'
 ---
 ...
 insert into t0 values ('Stephanie')
 ---
-- error: 'Illegal parameters, tuple must have all indexed fields'
+- error: 'Tuple field count 1 is less than required by a defined index (expected 2)'
 ...
 select * from t0 where k1='Anything'
 ---
@@ -385,7 +385,7 @@ select * from t0 where k1='Britney'
 ...
 replace into t0 values ('Spears')
 ---
-- error: 'Illegal parameters, tuple must have all indexed fields'
+- error: 'Tuple field count 1 is less than required by a defined index (expected 2)'
 ...
 select * from t0 where k0='Spears'
 ---
diff --git a/test/big/tree_pk_multipart.result b/test/big/tree_pk_multipart.result
index f20e075cbcd82a2f227f65751dca68c40d96f8c2..b0c884a9dcb0b26a8eb445d38b389865b7278970 100644
--- a/test/big/tree_pk_multipart.result
+++ b/test/big/tree_pk_multipart.result
@@ -439,11 +439,11 @@ space = box.space[0]
 ...
 space:insert(1, 1)
 ---
-- error: Illegal parameters, tuple must have all indexed fields
+- error: Tuple field count 2 is less than required by a defined index (expected 3)
 ...
 space:replace_if_exists(1, 1)
 ---
-- error: Illegal parameters, tuple must have all indexed fields
+- error: Tuple field count 2 is less than required by a defined index (expected 3)
 ...
 space:drop()
 ---
diff --git a/test/box/alter_limits.result b/test/box/alter_limits.result
index 6acadfc4c913d2165239c1bca4c41d7558a58562..1c545a9b9aa51bc3c7da120e59577e65d10e529d 100644
--- a/test/box/alter_limits.result
+++ b/test/box/alter_limits.result
@@ -133,7 +133,7 @@ s:select(0)
 ...
 s:select_range(0, 0, 0)
 ---
-- error: '[string "-- schema.lua (internal file)..."]:170: attempt to index a nil
+- error: '[string "-- schema.lua (internal file)..."]:205: attempt to index a nil
     value'
 ...
 s:delete(0)
@@ -477,36 +477,383 @@ s:create_index('t1', 'hash', {parts = parts});
 s:drop()
 ---
 ...
--- add index:
--- ---------
---     - a test case for contraints in tuple_format_new
---     - index rebuild:
---        - a duplicate in the new index
---        - no field for the new index
---        - wrong field type in the new index
---     - alter algorithm correctly detects
---        test that during the rebuild there is a duplicate
---     according to the new index
---     - index rebuild -> no field in the index (validate tuple
---     during build)
---     - alter unique -> non unique
---     - alter index type
---     - index access by name
---     - alter add key part
---     - arbitrary index
---     - test that during commit phase
---       -> inject error at commit, inject error at rollback
---     - add check that doesn't allow drop of a primary
---       key in presence of other keys, or moves the space
---       to disabled state otherwise.
+-- check costraints in tuple_format_new()
+s = box.schema.create_space('test')
+---
+...
+s:create_index('t1', 'hash', { parts = { 0, 'num' }})
+---
+...
+-- field type contradicts field type of another index
+s:create_index('t2', 'hash', { parts = { 0, 'str' }})
+---
+- error: Ambiguous field type in index 1, key part 0. Requested type is STR but the
+    field has previously been defined as NUM
+...
+-- ok
+s:create_index('t2', 'hash', { parts = { 1, 'str' }})
+---
+...
+-- don't allow drop of the primary key in presence of other keys
+s.index[0]:drop()
+---
+- error: Can't drop primary key in space 512 while secondary keys exist
+...
+-- cleanup
+s:drop()
+---
+...
+-- index name, name manipulation
+s = box.schema.create_space('test')
+---
+...
+s:create_index('primary', 'hash')
+---
+...
+-- space cache is updated correctly
+s.index[0].name
+---
+- primary
+...
+s.index[0].id
+---
+- 0
+...
+s.index[0].type
+---
+- HASH
+...
+s.index['primary'].name
+---
+- primary
+...
+s.index['primary'].id
+---
+- 0
+...
+s.index['primary'].type
+---
+- HASH
+...
+s.index.primary.name
+---
+- primary
+...
+s.index.primary.id
+---
+- 0
+...
+-- other properties are preserved
+s.index.primary.type
+---
+- HASH
+...
+s.index.primary.unique
+---
+- true
+...
+s.index.primary:rename('new')
+---
+...
+s.index[0].name
+---
+- new
+...
+s.index.primary
+---
+- null
+...
+s.index.new.name
+---
+- new
+...
+-- too long name
+s.index[0]:rename(string.rep('t', box.schema.NAME_MAX)..'_')
+---
+- error: 'Can''t create or modify index 0 in space 512: index name is too long'
+...
+s.index[0].name
+---
+- new
+...
+s.index[0]:rename(string.rep('t', box.schema.NAME_MAX - 1)..'_')
+---
+...
+s.index[0].name
+---
+- ttttttttttttttttttttttttttttttt_
+...
+s.index[0]:rename(string.rep('t', box.schema.NAME_MAX - 2)..'_')
+---
+...
+s.index[0].name
+---
+- tttttttttttttttttttttttttttttt_
+...
+s.index[0]:rename('primary')
+---
+...
+s.index.primary.name
+---
+- primary
+...
+-- cleanup
+s:drop()
+---
+...
+-- modify index
+s = box.schema.create_space('test')
+---
+...
+s:create_index('primary', 'hash')
+---
+...
+s.index.primary:alter({unique=false})
+---
+- error: 'Can''t create or modify index 0 in space 512: primary key must be unique'
+...
+-- unique -> non-unique, index type
+s.index.primary:alter({type='tree', unique=false, name='pk'})
+---
+- error: 'Can''t create or modify index 0 in space 512: primary key must be unique'
+...
+s.index.primary
+---
+- unique: true
+  idx: index 0
+  n: 512
+  type: HASH
+  key_field:
+    0:
+      type: NUM
+      fieldno: 0
+  name: primary
+  id: 0
+...
+s.index.pk.type
+---
+- error: '[string "return s.index.pk.type"]:1: attempt to index field ''pk'' (a nil
+    value)'
+...
+s.index.pk.unique
+---
+- error: '[string "return s.index.pk.unique"]:1: attempt to index field ''pk'' (a
+    nil value)'
+...
+s.index.pk:rename('primary')
+---
+- error: '[string "return s.index.pk:rename(''primary'')"]:1: attempt to index field
+    ''pk'' (a nil value)'
+...
+s:create_index('second', 'tree', { parts = {  1, 'str' } })
+---
+...
+s.index.second.id
+---
+- 1
+...
+s:create_index('third', 'hash', { parts = {  2, 'num64' } })
+---
+...
+s.index.third:rename('second')
+---
+- error: Duplicate key exists in unique index 1
+...
+s.index.third.id
+---
+- 2
+...
+s.index.second:drop()
+---
+...
+s.index.third:alter({id = 1, name = 'second'})
+---
+...
+s.index.third
+---
+- null
+...
+s.index.second.name
+---
+- second
+...
+s.index.second.id
+---
+- 1
+...
+s:drop()
+---
+...
+-- ----------------------------------------------------------------
+-- BUILD INDEX: changes of a non-empty index
+-- ----------------------------------------------------------------
+s = box.schema.create_space('full')
+---
+...
+s:create_index('primary', 'tree', {parts =  { 0, 'str' }})
+---
+...
+s:insert('No such movie', 999)
+---
+- ['No such movie', 999]
+...
+s:insert('Barbara', 2012)
+---
+- ['Barbara', 2012]
+...
+s:insert('Cloud Atlas', 2012)
+---
+- ['Cloud Atlas', 2012]
+...
+s:insert('Almanya - Willkommen in Deutschland', 2011)
+---
+- ['Almanya - Willkommen in Deutschland', 2011]
+...
+s:insert('Halt auf freier Strecke', 2011)
+---
+- ['Halt auf freier Strecke', 2011]
+...
+s:insert('Homevideo', 2011)
+---
+- ['Homevideo', 2011]
+...
+s:insert('Die Fremde', 2010)
+---
+- ['Die Fremde', 2010]
+...
+-- create index with data
+s:create_index('year', 'tree', { unique=false, parts = { 1, 'num'} })
+---
+...
+s.index.primary:select()
+---
+- ['Almanya - Willkommen in Deutschland', 2011]
+- ['Barbara', 2012]
+- ['Cloud Atlas', 2012]
+- ['Die Fremde', 2010]
+- ['Halt auf freier Strecke', 2011]
+- ['Homevideo', 2011]
+- ['No such movie', 999]
+...
+-- a duplicate in the created index
+s:create_index('nodups', 'tree', { unique=true, parts = { 1, 'num'} })
+---
+- error: Duplicate key exists in unique index 2
+...
+-- change of non-unique index to unique: same effect
+s.index.year:alter({unique=true})
+---
+- error: Duplicate key exists in unique index 1
+...
+-- num -> str -> num transition
+box.space['_index']:update({s.n, s.index.year.id}, "=p", 7, 'str')
+---
+- [512, 1, 1918985593, 1701147252, 0, 1, 1, 'str']
+...
+s.index.primary:select()
+---
+- ['Almanya - Willkommen in Deutschland', 2011]
+- ['Barbara', 2012]
+- ['Cloud Atlas', 2012]
+- ['Die Fremde', 2010]
+- ['Halt auf freier Strecke', 2011]
+- ['Homevideo', 2011]
+- ['No such movie', 999]
+...
+box.space['_index']:update({s.n, s.index.year.id}, "=p", 7, 'num')
+---
+- [512, 1, 1918985593, 1701147252, 0, 1, 1, 'num']
+...
+-- ambiguous field type
+s:create_index('str', 'tree', {unique =  false, parts = { 1, 'str'}})
+---
+- error: Ambiguous field type in index 2, key part 0. Requested type is STR but the
+    field has previously been defined as NUM
+...
+-- create index on a non-existing field
+s:create_index('nosuchfield', 'tree', {unique = true, parts = { 2, 'str'}})
+---
+- error: Tuple field count 2 is less than required by a defined index (expected 3)
+...
+s.index.year:drop()
+---
+...
+s:insert('Der Baader Meinhof Komplex', '2009 ')
+---
+- ['Der Baader Meinhof Komplex', '2009 ']
+...
+-- create an index on a field with a wrong type
+s:create_index('year', 'tree', {unique = false, parts = { 1, 'num'}})
+---
+- error: 'Tuple field 1 type does not match one required by operation: expected NUM'
+...
+-- a field is missing
+s:replace('Der Baader Meinhof Komplex')
+---
+- ['Der Baader Meinhof Komplex']
+...
+s:create_index('year', 'tree', {unique = false, parts = { 1, 'num'}})
+---
+- error: Tuple field count 1 is less than required by a defined index (expected 2)
+...
+s:drop()
+---
+...
+-- unique -> non-unique transition
+s = box.schema.create_space('test')
+---
+...
+-- primary key must be unique
+s:create_index('primary', 'tree', { unique = false, parts = {0, 'num'}})
+---
+- error: 'Can''t create or modify index 0 in space 512: primary key must be unique'
+...
+-- create primary key
+s:create_index('primary', 'hash', { unique = true, parts = {0, 'num'}})
+---
+...
+s:insert(1, 1)
+---
+- [1, 1]
+...
+s:create_index('secondary', 'tree', { unique = false, parts = {1, 'num'}})
+---
+...
+s:insert(2, 1)
+---
+- [2, 1]
+...
+s.index.secondary:alter({ unique = true })
+---
+- error: Duplicate key exists in unique index 1
+...
+s:delete(2)
+---
+- [2, 1]
+...
+s.index.secondary:alter({ unique = true })
+---
+...
+s:insert(2, 1)
+---
+- error: Duplicate key exists in unique index 1
+...
+s:insert(2, 2)
+---
+- [2, 2]
+...
+s.index.secondary:alter({ unique = false})
+---
+...
+s:insert(3, 2)
+---
+- [3, 2]
+...
+s:drop()
+---
+...
+-- -----------
 --
---     - add identical index - verify there is no rebuild
---     - rename index (all non-essential propeties)
---       -> duplicate key
---     - inject fiber sleep during commit, so that some stuff is added
---     (test crap which happens while there is a record to the wal
---     - test ambiguous field type when adding an index (ER_FIELD_TYPE_MISMATCH)
---     - test addition of a new index on data which it can't handle
 --
 -- space cache
 -- -----------
@@ -521,6 +868,8 @@ s:drop()
 --
 -- -- inject error at various stages of commit and see that
 -- the alter has no effects
+--     - test that during commit phase
+--       -> inject error at commit, inject error at rollback
 --
 -- usability
 -- ---------
diff --git a/test/box/alter_limits.test.lua b/test/box/alter_limits.test.lua
index 582c3ba0b292aa2fa754186fed5dd1a28a1cc45d..9dae0f4ae83df86da2ad24466386d8ceba90010b 100644
--- a/test/box/alter_limits.test.lua
+++ b/test/box/alter_limits.test.lua
@@ -179,36 +179,122 @@ s:create_index('t1', 'hash', {parts = parts});
 #s.index[0].key_field
 -- cleanup
 s:drop()
--- add index:
--- ---------
---     - a test case for contraints in tuple_format_new
---     - index rebuild:
---        - a duplicate in the new index
---        - no field for the new index
---        - wrong field type in the new index
---     - alter algorithm correctly detects
---        test that during the rebuild there is a duplicate
---     according to the new index
---     - index rebuild -> no field in the index (validate tuple
---     during build)
---     - alter unique -> non unique
---     - alter index type
---     - index access by name
---     - alter add key part
---     - arbitrary index
---     - test that during commit phase
---       -> inject error at commit, inject error at rollback
---     - add check that doesn't allow drop of a primary
---       key in presence of other keys, or moves the space
---       to disabled state otherwise.
+-- check costraints in tuple_format_new()
+s = box.schema.create_space('test')
+s:create_index('t1', 'hash', { parts = { 0, 'num' }})
+-- field type contradicts field type of another index
+s:create_index('t2', 'hash', { parts = { 0, 'str' }})
+-- ok
+s:create_index('t2', 'hash', { parts = { 1, 'str' }})
+-- don't allow drop of the primary key in presence of other keys
+s.index[0]:drop()
+-- cleanup
+s:drop()
+-- index name, name manipulation
+s = box.schema.create_space('test')
+s:create_index('primary', 'hash')
+-- space cache is updated correctly
+s.index[0].name
+s.index[0].id
+s.index[0].type
+s.index['primary'].name
+s.index['primary'].id
+s.index['primary'].type
+s.index.primary.name
+s.index.primary.id
+-- other properties are preserved
+s.index.primary.type
+s.index.primary.unique
+s.index.primary:rename('new')
+s.index[0].name
+s.index.primary
+s.index.new.name
+-- too long name
+s.index[0]:rename(string.rep('t', box.schema.NAME_MAX)..'_')
+s.index[0].name
+s.index[0]:rename(string.rep('t', box.schema.NAME_MAX - 1)..'_')
+s.index[0].name
+s.index[0]:rename(string.rep('t', box.schema.NAME_MAX - 2)..'_')
+s.index[0].name
+s.index[0]:rename('primary')
+s.index.primary.name
+-- cleanup
+s:drop()
+-- modify index
+s = box.schema.create_space('test')
+s:create_index('primary', 'hash')
+s.index.primary:alter({unique=false})
+-- unique -> non-unique, index type
+s.index.primary:alter({type='tree', unique=false, name='pk'})
+s.index.primary
+s.index.pk.type
+s.index.pk.unique
+s.index.pk:rename('primary')
+s:create_index('second', 'tree', { parts = {  1, 'str' } })
+s.index.second.id
+s:create_index('third', 'hash', { parts = {  2, 'num64' } })
+s.index.third:rename('second')
+s.index.third.id
+s.index.second:drop()
+s.index.third:alter({id = 1, name = 'second'})
+s.index.third
+s.index.second.name
+s.index.second.id
+s:drop()
+-- ----------------------------------------------------------------
+-- BUILD INDEX: changes of a non-empty index
+-- ----------------------------------------------------------------
+s = box.schema.create_space('full')
+s:create_index('primary', 'tree', {parts =  { 0, 'str' }})
+s:insert('No such movie', 999)
+s:insert('Barbara', 2012)
+s:insert('Cloud Atlas', 2012)
+s:insert('Almanya - Willkommen in Deutschland', 2011)
+s:insert('Halt auf freier Strecke', 2011)
+s:insert('Homevideo', 2011)
+s:insert('Die Fremde', 2010)
+-- create index with data
+s:create_index('year', 'tree', { unique=false, parts = { 1, 'num'} })
+s.index.primary:select()
+-- a duplicate in the created index
+s:create_index('nodups', 'tree', { unique=true, parts = { 1, 'num'} })
+-- change of non-unique index to unique: same effect
+s.index.year:alter({unique=true})
+-- num -> str -> num transition
+box.space['_index']:update({s.n, s.index.year.id}, "=p", 7, 'str')
+s.index.primary:select()
+box.space['_index']:update({s.n, s.index.year.id}, "=p", 7, 'num')
+-- ambiguous field type
+s:create_index('str', 'tree', {unique =  false, parts = { 1, 'str'}})
+-- create index on a non-existing field
+s:create_index('nosuchfield', 'tree', {unique = true, parts = { 2, 'str'}})
+s.index.year:drop()
+s:insert('Der Baader Meinhof Komplex', '2009 ')
+-- create an index on a field with a wrong type
+s:create_index('year', 'tree', {unique = false, parts = { 1, 'num'}})
+-- a field is missing
+s:replace('Der Baader Meinhof Komplex')
+s:create_index('year', 'tree', {unique = false, parts = { 1, 'num'}})
+s:drop()
+-- unique -> non-unique transition
+s = box.schema.create_space('test')
+-- primary key must be unique
+s:create_index('primary', 'tree', { unique = false, parts = {0, 'num'}})
+-- create primary key
+s:create_index('primary', 'hash', { unique = true, parts = {0, 'num'}})
+s:insert(1, 1)
+s:create_index('secondary', 'tree', { unique = false, parts = {1, 'num'}})
+s:insert(2, 1)
+s.index.secondary:alter({ unique = true })
+s:delete(2)
+s.index.secondary:alter({ unique = true })
+s:insert(2, 1)
+s:insert(2, 2)
+s.index.secondary:alter({ unique = false})
+s:insert(3, 2)
+s:drop()
+-- -----------
 --
---     - add identical index - verify there is no rebuild
---     - rename index (all non-essential propeties)
---       -> duplicate key
---     - inject fiber sleep during commit, so that some stuff is added
---     (test crap which happens while there is a record to the wal
---     - test ambiguous field type when adding an index (ER_FIELD_TYPE_MISMATCH)
---     - test addition of a new index on data which it can't handle
 --
 -- space cache
 -- -----------
@@ -223,6 +309,8 @@ s:drop()
 --
 -- -- inject error at various stages of commit and see that
 -- the alter has no effects
+--     - test that during commit phase
+--       -> inject error at commit, inject error at rollback
 --
 -- usability
 -- ---------
diff --git a/test/box/lua.result b/test/box/lua.result
index 62b57b9fb1cde397ddc3036646eaa2698492099b..efbea0f96c638ff4639574e246ce623f8e01f453 100644
--- a/test/box/lua.result
+++ b/test/box/lua.result
@@ -581,23 +581,23 @@ box.cfg.nosuchoption = 1
 box.space[300] = 1
 ---
 ...
-box.index.new('abc', 'cde')
+box.index.bind('abc', 'cde')
 ---
 - error: 'bad argument #1 to ''?'' (number expected, got string)'
 ...
-box.index.new(1, 2)
+box.index.bind(1, 2)
 ---
 - error: Space 1 does not exist
 ...
-box.index.new(0, 1)
+box.index.bind(0, 1)
 ---
 - error: 'No index #1 is defined in space 0'
 ...
-box.index.new(0, 0)
+box.index.bind(0, 0)
 ---
--index 0
+- index 0
 ...
-#box.index.new(0,0)
+#box.index.bind(0,0)
 ---
 - 0
 ...
@@ -613,7 +613,7 @@ box.insert(0, 'abcd')
 ---
 - [1684234849]
 ...
-#box.index.new(0,0)
+#box.index.bind(0,0)
 ---
 - 2
 ...
@@ -625,7 +625,7 @@ box.delete(0, 'test')
 ---
 - [1953719668]
 ...
-#box.index.new(0,0)
+#box.index.bind(0,0)
 ---
 - 1
 ...
@@ -637,7 +637,7 @@ box.delete(0, 'abcd')
 ---
 - 0
 ...
-#box.index.new(0,0)
+#box.index.bind(0,0)
 ---
 - 0
 ...
@@ -1300,7 +1300,7 @@ call box.update('0', 'tes4', '#p', 0, '')
 ...
 box.update(0, 'tes5', '#p', 0, '')
 ---
-- error: Illegal parameters, tuple must have all indexed fields
+- error: Tuple field count 0 is less than required by a defined index (expected 1)
 ...
 box.space[0]:truncate()
 ---
diff --git a/test/box/lua.test.py b/test/box/lua.test.py
index 4cdf7618e559aa109aa8ab40132a0b844cc18852..8852e9f9b1d5dd8f764e2d8974526c6d7813beb1 100644
--- a/test/box/lua.test.py
+++ b/test/box/lua.test.py
@@ -145,21 +145,21 @@ admin("t")
 admin("box.cfg.nosuchoption = 1")
 admin("box.space[300] = 1")
 
-admin("box.index.new('abc', 'cde')")
-admin("box.index.new(1, 2)")
-admin("box.index.new(0, 1)")
-admin("box.index.new(0, 0)")
-admin("#box.index.new(0,0)")
+admin("box.index.bind('abc', 'cde')")
+admin("box.index.bind(1, 2)")
+admin("box.index.bind(0, 1)")
+admin("box.index.bind(0, 0)")
+admin("#box.index.bind(0,0)")
 admin("#box.space[0].index[0].idx")
 admin("box.insert(0, 'test')")
 admin("box.insert(0, 'abcd')")
-admin("#box.index.new(0,0)")
+admin("#box.index.bind(0,0)")
 admin("#box.space[0].index[0].idx")
 admin("box.delete(0, 'test')")
-admin("#box.index.new(0,0)")
+admin("#box.index.bind(0,0)")
 admin("box.delete(0, 'abcd')")
 admin("#box.space[0].index[0].idx")
-admin("#box.index.new(0,0)")
+admin("#box.index.bind(0,0)")
 admin("box.space[0]:insert('test', 'hello world')")
 admin("box.space[0]:update('test', '=p', 1, 'bye, world')")
 admin("box.space[0]:delete('test')")
diff --git a/test/box/lua_misc.result b/test/box/lua_misc.result
index 2fcff38cde65a8f4e99a55d3ea0bfea7969c424a..4f741cc54d826e82e9ab7a92d4a6967c357e0ace 100644
--- a/test/box/lua_misc.result
+++ b/test/box/lua_misc.result
@@ -137,9 +137,10 @@ t;
   - 'box.error.ER_FIELD_TYPE : 10242'
   - 'box.error.ER_OK : 0'
   - 'box.error.ER_TUPLE_NOT_FOUND : 12546'
+  - 'box.error.ER_INDEX_ARITY : 8194'
   - 'box.error.ER_WAL_IO : 9986'
   - 'box.error.ER_INJECTION : 2306'
-  - 'box.error.ER_DROP_PRIMARY_KEY : 7682'
+  - 'box.error.ER_LAST_DROP : 7426'
   - 'box.error.ER_INDEX_TYPE : 1282'
   - 'box.error.ER_ARG_TYPE : 10498'
   - 'box.error.ER_KEY_PART_COUNT : 12034'
@@ -153,7 +154,7 @@ t;
   - 'box.error.ER_SECONDARY : 770'
   - 'box.error.ER_UPDATE_FIELD : 14338'
   - 'box.error.ER_DROP_SPACE : 6146'
-  - 'box.error.ER_UNKNOWN_UPDATE_OP : 11266'
+  - 'box.error.ER_SPLICE : 10754'
   - 'box.error.ER_NO_SUCH_SPACE : 14594'
   - 'box.error.ER_UNSUPPORTED : 2562'
   - 'box.error.ER_TUPLE_FOUND : 14082'
@@ -161,12 +162,12 @@ t;
   - 'box.error.ER_SPACE_DISABLED : 13314'
   - 'box.error.ER_PROC_LUA : 13058'
   - 'box.error.ER_ALTER_SPACE : 6402'
-  - 'box.error.ER_TUPLE_IS_RO : 1025'
   - 'box.error.ER_FIBER_STACK : 6658'
+  - 'box.error.ER_TUPLE_IS_RO : 1025'
   - 'box.error.ER_NO_SUCH_PROC : 12802'
-  - 'box.error.ER_LAST_DROP : 7426'
-  - 'box.error.ER_SPLICE : 10754'
+  - 'box.error.ER_DROP_PRIMARY_KEY : 7682'
   - 'box.error.ER_SPACE_ARITY : 7938'
+  - 'box.error.ER_UNKNOWN_UPDATE_OP : 11266'
   - 'box.error.ER_FIELD_TYPE_MISMATCH : 11778'
   - 'box.error.ER_SPACE_EXISTS : 1538'
 ...
diff --git a/test/lib/sql_ast.py b/test/lib/sql_ast.py
index 88884e31b18cf1e0ee12bbf83b60f7af99ba74f5..620da0d198a361b40a38ab0661b9a0d2d4b983e5 100644
--- a/test/lib/sql_ast.py
+++ b/test/lib/sql_ast.py
@@ -53,7 +53,7 @@ ER = {
    29: "ER_LAST_DROP"           ,
    30: "ER_DROP_PRIMARY_KEY"    ,
    31: "ER_SPACE_ARITY"         ,
-   32: "ER_UNUSED32"            ,
+   32: "ER_INDEX_ARITY"         ,
    33: "ER_UNUSED33"            ,
    34: "ER_UNUSED34"            ,
    35: "ER_UNUSED35"            ,