diff --git a/changelogs/unreleased/gh-7356-populate-index-with-keydef.md b/changelogs/unreleased/gh-7356-populate-index-with-keydef.md
new file mode 100644
index 0000000000000000000000000000000000000000..8c11872699cfe44ef5da9d47af1e5943511c9446
--- /dev/null
+++ b/changelogs/unreleased/gh-7356-populate-index-with-keydef.md
@@ -0,0 +1,5 @@
+## feature/core
+
+* Now `index_object.parts` contains the following methods, similar to the
+  `key_def` Lua module: `extract_key()`, `compare()`, `compare_with_key()`,
+  `merge()` (gh-7356).
diff --git a/src/box/lua/key_def.c b/src/box/lua/key_def.c
index e5fc370e49803ae6167fdd78bb2c9009b0c653de..b520251ea1ea0d2ee1936a76c75f8d2b9f2b5d98 100644
--- a/src/box/lua/key_def.c
+++ b/src/box/lua/key_def.c
@@ -40,8 +40,39 @@
 
 static uint32_t CTID_STRUCT_KEY_DEF_REF = 0;
 
+/**
+ * Free a key_def from a Lua code.
+ */
+static int
+lbox_key_def_gc(struct lua_State *L)
+{
+	struct key_def *key_def = luaT_is_key_def(L, 1);
+	assert(key_def != NULL);
+	key_def_delete(key_def);
+	return 0;
+}
+
+/**
+ * Push key_def as a cdata object to a Lua stack. This function takes ownership
+ * of key_def, and sets finalizer lbox_key_def_gc for it.
+ */
+static void
+luaT_push_key_def_nodup(struct lua_State *L, const struct key_def *key_def)
+{
+	void *ptr = luaL_pushcdata(L, CTID_STRUCT_KEY_DEF_REF);
+	*(const struct key_def **)ptr = key_def;
+	lua_pushcfunction(L, lbox_key_def_gc);
+	luaL_setcdatagc(L, -2);
+}
+
 void
 luaT_push_key_def(struct lua_State *L, const struct key_def *key_def)
+{
+	luaT_push_key_def_nodup(L, key_def_dup(key_def));
+}
+
+void
+luaT_push_key_def_parts(struct lua_State *L, const struct key_def *key_def)
 {
 	lua_createtable(L, key_def->part_count, 0);
 	for (uint32_t i = 0; i < key_def->part_count; ++i) {
@@ -242,7 +273,7 @@ luaT_key_def_check_tuple(struct lua_State *L, struct key_def *key_def, int idx)
 }
 
 struct key_def *
-luaT_check_key_def(struct lua_State *L, int idx)
+luaT_is_key_def(struct lua_State *L, int idx)
 {
 	if (lua_type(L, idx) != LUA_TCDATA)
 		return NULL;
@@ -254,33 +285,16 @@ luaT_check_key_def(struct lua_State *L, int idx)
 	return *key_def_ptr;
 }
 
-/**
- * Free a key_def from a Lua code.
- */
-static int
-lbox_key_def_gc(struct lua_State *L)
+int
+luaT_key_def_extract_key(struct lua_State *L, int idx)
 {
-	struct key_def *key_def = luaT_check_key_def(L, 1);
+	struct key_def *key_def = luaT_is_key_def(L, idx);
 	assert(key_def != NULL);
-	key_def_delete(key_def);
-	return 0;
-}
 
-/**
- * Extract key from tuple by given key definition and return
- * tuple representing this key.
- * Push the new key tuple as cdata to a LUA stack on success.
- * Raise error otherwise.
- */
-static int
-lbox_key_def_extract_key(struct lua_State *L)
-{
-	struct key_def *key_def;
-	if (lua_gettop(L) != 2 || (key_def = luaT_check_key_def(L, 1)) == NULL)
-		return luaL_error(L, "Usage: key_def:extract_key(tuple)");
-
-	struct tuple *tuple;
-	if ((tuple = luaT_key_def_check_tuple(L, key_def, 2)) == NULL)
+	if (key_def->is_multikey)
+		return luaL_error(L, "multikey path is unsupported");
+	struct tuple *tuple = luaT_key_def_check_tuple(L, key_def, -1);
+	if (tuple == NULL)
 		return luaT_error(L);
 
 	struct region *region = &fiber()->gc;
@@ -300,24 +314,14 @@ lbox_key_def_extract_key(struct lua_State *L)
 	return 1;
 }
 
-/**
- * Compare tuples using the key definition.
- * Push 0  if key_fields(tuple_a) == key_fields(tuple_b)
- *      <0 if key_fields(tuple_a) < key_fields(tuple_b)
- *      >0 if key_fields(tuple_a) > key_fields(tuple_b)
- * integer to a LUA stack on success.
- * Raise error otherwise.
- */
-static int
-lbox_key_def_compare(struct lua_State *L)
+int
+luaT_key_def_compare(struct lua_State *L, int idx)
 {
-	struct key_def *key_def;
-	if (lua_gettop(L) != 3 ||
-	    (key_def = luaT_check_key_def(L, 1)) == NULL) {
-		return luaL_error(L, "Usage: key_def:"
-				     "compare(tuple_a, tuple_b)");
-	}
+	struct key_def *key_def = luaT_is_key_def(L, idx);
+	assert(key_def != NULL);
 
+	if (key_def->is_multikey)
+		return luaL_error(L, "multikey path is unsupported");
 	if (key_def->tuple_compare == NULL) {
 		enum field_type type = key_def_incomparable_type(key_def);
 		assert(type != field_type_MAX);
@@ -326,10 +330,11 @@ lbox_key_def_compare(struct lua_State *L)
 		return luaT_error(L);
 	}
 
-	struct tuple *tuple_a, *tuple_b;
-	if ((tuple_a = luaT_key_def_check_tuple(L, key_def, 2)) == NULL)
+	struct tuple *tuple_a = luaT_key_def_check_tuple(L, key_def, -2);
+	if (tuple_a == NULL)
 		return luaT_error(L);
-	if ((tuple_b = luaT_key_def_check_tuple(L, key_def, 3)) == NULL) {
+	struct tuple *tuple_b = luaT_key_def_check_tuple(L, key_def, -1);
+	if (tuple_b == NULL) {
 		tuple_unref(tuple_a);
 		return luaT_error(L);
 	}
@@ -341,24 +346,14 @@ lbox_key_def_compare(struct lua_State *L)
 	return 1;
 }
 
-/**
- * Compare tuple with key using the key definition.
- * Push 0  if key_fields(tuple) == parts(key)
- *      <0 if key_fields(tuple) < parts(key)
- *      >0 if key_fields(tuple) > parts(key)
- * integer to a LUA stack on success.
- * Raise error otherwise.
- */
-static int
-lbox_key_def_compare_with_key(struct lua_State *L)
+int
+luaT_key_def_compare_with_key(struct lua_State *L, int idx)
 {
-	struct key_def *key_def;
-	if (lua_gettop(L) != 3 ||
-	    (key_def = luaT_check_key_def(L, 1)) == NULL) {
-		return luaL_error(L, "Usage: key_def:"
-				     "compare_with_key(tuple, key)");
-	}
+	struct key_def *key_def = luaT_is_key_def(L, idx);
+	assert(key_def != NULL);
 
+	if (key_def->is_multikey)
+		return luaL_error(L, "multikey path is unsupported");
 	if (key_def->tuple_compare_with_key == NULL) {
 		enum field_type type = key_def_incomparable_type(key_def);
 		assert(type != field_type_MAX);
@@ -367,13 +362,13 @@ lbox_key_def_compare_with_key(struct lua_State *L)
 		return luaT_error(L);
 	}
 
-	struct tuple *tuple = luaT_key_def_check_tuple(L, key_def, 2);
+	struct tuple *tuple = luaT_key_def_check_tuple(L, key_def, -2);
 	if (tuple == NULL)
 		return luaT_error(L);
 
 	struct region *region = &fiber()->gc;
 	size_t region_svp = region_used(region);
-	const char *key = luaT_tuple_encode(L, 3, NULL);
+	const char *key = luaT_tuple_encode(L, -1, NULL);
 	if (key == NULL || box_key_def_validate_key(key_def, key, NULL) != 0) {
 		region_truncate(region, region_svp);
 		tuple_unref(tuple);
@@ -387,60 +382,94 @@ lbox_key_def_compare_with_key(struct lua_State *L)
 	return 1;
 }
 
-/**
- * Construct and export to LUA a new key definition with a set
- * union of key parts from first and second key defs. Parts of
- * the new key_def consist of the first key_def's parts and those
- * parts of the second key_def that were not among the first
- * parts.
- * Push the new key_def as cdata to a LUA stack on success.
- * Raise error otherwise.
- */
-static int
-lbox_key_def_merge(struct lua_State *L)
+int
+luaT_key_def_merge(struct lua_State *L, int idx_a, int idx_b)
 {
-	struct key_def *key_def_a, *key_def_b;
-	if (lua_gettop(L) != 2 ||
-	    (key_def_a = luaT_check_key_def(L, 1)) == NULL ||
-	    (key_def_b = luaT_check_key_def(L, 2)) == NULL)
-		return luaL_error(L, "Usage: key_def:merge(second_key_def)");
-
+	struct key_def *key_def_a = luaT_is_key_def(L, idx_a);
+	struct key_def *key_def_b = luaT_is_key_def(L, idx_b);
+	assert(key_def_a != NULL);
+	assert(key_def_b != NULL);
+
+	if (key_def_a->is_multikey)
+		return luaL_error(L, "multikey path is unsupported");
+	assert(!key_def_b->is_multikey);
 	struct key_def *new_key_def = key_def_merge(key_def_a, key_def_b);
 	if (new_key_def == NULL)
 		return luaT_error(L);
 
-	*(struct key_def **) luaL_pushcdata(L,
-				CTID_STRUCT_KEY_DEF_REF) = new_key_def;
-	lua_pushcfunction(L, lbox_key_def_gc);
-	luaL_setcdatagc(L, -2);
+	luaT_push_key_def(L, new_key_def);
 	return 1;
 }
 
+/**
+ * key_def:extract_key(tuple)
+ * Stack: [1] key_def; [2] tuple.
+ */
+static int
+lbox_key_def_extract_key(struct lua_State *L)
+{
+	if (lua_gettop(L) != 2 || luaT_is_key_def(L, 1) == NULL)
+		return luaL_error(L, "Usage: key_def:extract_key(tuple)");
+	return luaT_key_def_extract_key(L, 1);
+}
 
 /**
- * Push a new table representing a key_def to a Lua stack.
+ * key_def:compare(tuple_a, tuple_b)
+ * Stack: [1] key_def; [2] tuple_a; [3] tuple_b.
  */
 static int
-lbox_key_def_to_table(struct lua_State *L)
+lbox_key_def_compare(struct lua_State *L)
 {
-	struct key_def *key_def;
-	if (lua_gettop(L) != 1 || (key_def = luaT_check_key_def(L, 1)) == NULL)
-		return luaL_error(L, "Usage: key_def:totable()");
+	if (lua_gettop(L) != 3 || luaT_is_key_def(L, 1) == NULL) {
+		return luaL_error(L, "Usage: key_def:compare("
+				     "tuple_a, tuple_b)");
+	}
+	return luaT_key_def_compare(L, 1);
+}
 
-	luaT_push_key_def(L, key_def);
-	return 1;
+/**
+ * key_def:compare_with_key(tuple, key)
+ * Stack: [1] key_def; [2] tuple; [3] key.
+ */
+static int
+lbox_key_def_compare_with_key(struct lua_State *L)
+{
+	if (lua_gettop(L) != 3 || luaT_is_key_def(L, 1) == NULL) {
+		return luaL_error(L, "Usage: key_def:compare_with_key("
+				     "tuple, key)");
+	}
+	return luaT_key_def_compare_with_key(L, 1);
 }
 
 /**
- * Create a new key_def from a Lua table.
- *
- * Expected a table of key parts on the Lua stack. The format is
- * the same as box.space.<...>.index.<...>.parts or corresponding
- * net.box's one.
- *
- * Push the new key_def as cdata to a Lua stack.
+ * key_def:merge(second_key_def)
+ * Stack: [1] key_def; [2] second_key_def.
  */
 static int
+lbox_key_def_merge(struct lua_State *L)
+{
+	int idx_a = 1;
+	int idx_b = 2;
+	if (lua_gettop(L) != 2 || luaT_is_key_def(L, idx_a) == NULL ||
+	    luaT_is_key_def(L, idx_b) == NULL)
+		return luaL_error(L, "Usage: key_def:merge(second_key_def)");
+	return luaT_key_def_merge(L, idx_a, idx_b);
+}
+
+/**
+ * Push a new table representing a key_def to a Lua stack.
+ */
+static int
+lbox_key_def_to_table(struct lua_State *L)
+{
+	struct key_def *key_def = luaT_is_key_def(L, 1);
+	if (lua_gettop(L) != 1 || key_def == NULL)
+		return luaL_error(L, "Usage: key_def:totable()");
+	luaT_push_key_def_parts(L, key_def);
+	return 1;
+}
+
+int
 lbox_key_def_new(struct lua_State *L)
 {
 	if (lua_gettop(L) != 1 || lua_istable(L, 1) != 1)
@@ -493,11 +522,7 @@ lbox_key_def_new(struct lua_State *L)
 	 */
 	key_def_update_optionality(key_def, 0);
 
-	*(struct key_def **) luaL_pushcdata(L,
-				CTID_STRUCT_KEY_DEF_REF) = key_def;
-	lua_pushcfunction(L, lbox_key_def_gc);
-	luaL_setcdatagc(L, -2);
-
+	luaT_push_key_def(L, key_def);
 	return 1;
 }
 
diff --git a/src/box/lua/key_def.h b/src/box/lua/key_def.h
index 88b8a0019231f0b1d7f4d40c29937f31645b3103..d7035d0f0810fbf9424a7cca00d04629c154c9f9 100644
--- a/src/box/lua/key_def.h
+++ b/src/box/lua/key_def.h
@@ -38,6 +38,13 @@ extern "C" {
 struct lua_State;
 struct key_def;
 
+/**
+ * Push a copy of key_def as a cdata object to a Lua stack, and set finalizer
+ * function lbox_key_def_gc for it.
+ */
+void
+luaT_push_key_def(struct lua_State *L, const struct key_def *key_def);
+
 /**
  * Push a new table representing a key_def to a Lua stack.
  * Table is consists of key_def::parts tables that describe
@@ -46,7 +53,7 @@ struct key_def;
  * object doesn't declare them where not necessary.
  */
 void
-luaT_push_key_def(struct lua_State *L, const struct key_def *key_def);
+luaT_push_key_def_parts(struct lua_State *L, const struct key_def *key_def);
 
 /**
  * Check key_def pointer in LUA stack by specified index.
@@ -54,7 +61,62 @@ luaT_push_key_def(struct lua_State *L, const struct key_def *key_def);
  * Returns not NULL tuple pointer on success, NULL otherwise.
  */
 struct key_def *
-luaT_check_key_def(struct lua_State *L, int idx);
+luaT_is_key_def(struct lua_State *L, int idx);
+
+/**
+ * Extract key from tuple by given key definition and return
+ * tuple representing this key.
+ * Push the new key tuple as cdata to a LUA stack on success.
+ * Raise error otherwise.
+ */
+int
+luaT_key_def_extract_key(struct lua_State *L, int idx);
+
+/**
+ * Compare tuples using the key definition.
+ * Push 0  if key_fields(tuple_a) == key_fields(tuple_b)
+ *      <0 if key_fields(tuple_a) < key_fields(tuple_b)
+ *      >0 if key_fields(tuple_a) > key_fields(tuple_b)
+ * integer to a LUA stack on success.
+ * Raise error otherwise.
+ */
+int
+luaT_key_def_compare(struct lua_State *L, int idx);
+
+/**
+ * Compare tuple with key using the key definition.
+ * Push 0  if key_fields(tuple) == parts(key)
+ *      <0 if key_fields(tuple) < parts(key)
+ *      >0 if key_fields(tuple) > parts(key)
+ * integer to a LUA stack on success.
+ * Raise error otherwise.
+ */
+int
+luaT_key_def_compare_with_key(struct lua_State *L, int idx);
+
+/**
+ * Construct and export to LUA a new key definition with a set
+ * union of key parts from first and second key defs. Parts of
+ * the new key_def consist of the first key_def's parts and those
+ * parts of the second key_def that were not among the first
+ * parts.
+ * Push the new key_def as cdata to a LUA stack on success.
+ * Raise error otherwise.
+ */
+int
+luaT_key_def_merge(struct lua_State *L, int idx_a, int idx_b);
+
+/**
+ * Create a new key_def from a Lua table.
+ *
+ * Expected a table of key parts on the Lua stack. The format is
+ * the same as box.space.<...>.index.<...>.parts or corresponding
+ * net.box's one.
+ *
+ * Push the new key_def as cdata to a Lua stack.
+ */
+int
+lbox_key_def_new(struct lua_State *L);
 
 /**
  * Register the module.
diff --git a/src/box/lua/merger.c b/src/box/lua/merger.c
index fb860dce580aa3cea4b6352eeb1e3d469d7e3ab5..bbe8ca3d02ac6ba3b67d013e5553f0d8f16531e7 100644
--- a/src/box/lua/merger.c
+++ b/src/box/lua/merger.c
@@ -50,7 +50,7 @@
 #include "lua/utils.h"       /* luaL_pushcdata(),
 				luaL_iterator_*() */
 
-#include "box/lua/key_def.h" /* luaT_check_key_def() */
+#include "box/lua/key_def.h" /* luaT_is_key_def() */
 #include "box/lua/tuple.h"   /* luaT_tuple_new() */
 
 #include "small/ibuf.h"      /* struct ibuf */
@@ -352,7 +352,7 @@ lbox_merger_new(struct lua_State *L)
 	int top = lua_gettop(L);
 	bool ok = (top == 2 || top == 3) &&
 		/* key_def. */
-		(key_def = luaT_check_key_def(L, 1)) != NULL &&
+		(key_def = luaT_is_key_def(L, 1)) != NULL &&
 		/* Sources. */
 		lua_istable(L, 2) == 1 &&
 		/* Opts. */
diff --git a/src/box/lua/space.cc b/src/box/lua/space.cc
index 688f6b969e7d0ff0a5aa89f0e1c5a5cbf71a24bd..8896cf94f9023dc62122731f18205d201151db28 100644
--- a/src/box/lua/space.cc
+++ b/src/box/lua/space.cc
@@ -279,6 +279,123 @@ lbox_push_space_foreign_key(struct lua_State *L, struct space *space, int i)
 	lua_setfield(L, i, "foreign_key");
 }
 
+/**
+ * index.parts:extract_key(tuple)
+ * Stack: [1] unused; [2] tuple.
+ * key_def is passed in the upvalue.
+ */
+static int
+lbox_index_parts_extract_key(struct lua_State *L)
+{
+	if (lua_gettop(L) != 2)
+		return luaL_error(L, "Usage: index.parts:extract_key(tuple)");
+	return luaT_key_def_extract_key(L, lua_upvalueindex(1));
+}
+
+/**
+ * index.parts:compare(tuple_a, tuple_b)
+ * Stack: [1] unused; [2] tuple_a; [3] tuple_b.
+ * key_def is passed in the upvalue.
+ */
+static int
+lbox_index_parts_compare(struct lua_State *L)
+{
+	if (lua_gettop(L) != 3) {
+		return luaL_error(L, "Usage: index.parts:compare("
+				     "tuple_a, tuple_b)");
+	}
+	return luaT_key_def_compare(L, lua_upvalueindex(1));
+}
+
+/**
+ * index.parts:compare_with_key(tuple, key)
+ * Stack: [1] unused; [2] tuple; [3] key.
+ * key_def is passed in the upvalue.
+ */
+static int
+lbox_index_parts_compare_with_key(struct lua_State *L)
+{
+	if (lua_gettop(L) != 3) {
+		return luaL_error(L, "Usage: index.parts:compare_with_key("
+				     "tuple, key)");
+	}
+	return luaT_key_def_compare_with_key(L, lua_upvalueindex(1));
+}
+
+/**
+ * index.parts:merge(second_index_parts)
+ * Stack: [1] unused; [2] second_index_parts.
+ * First key_def is passed in the upvalue.
+ */
+static int
+lbox_index_parts_merge(struct lua_State *L)
+{
+	int idx_b;
+	struct key_def *key_def_b;
+	if (lua_gettop(L) != 2) {
+		return luaL_error(L, "Usage: index.parts:merge("
+				     "second_index_parts)");
+	}
+	lua_pushcfunction(L, lbox_key_def_new);
+	lua_replace(L, 1);
+	/*
+	 * Stack:
+	 * [1] lbox_key_def_new
+	 * [2] second_index_parts (first argument for lbox_key_def_new)
+	 */
+	if (lua_pcall(L, 1, 1, 0) != 0)
+		goto key_def_b_error;
+	/*
+	 * Stack:
+	 * [1] key_def_b
+	 */
+	idx_b = 1;
+	key_def_b = luaT_is_key_def(L, idx_b);
+	if (key_def_b == NULL)
+		goto key_def_b_error;
+	return luaT_key_def_merge(L, lua_upvalueindex(1), idx_b);
+
+key_def_b_error:
+	return luaL_error(L,
+			  "Can't create key_def from the second index.parts");
+}
+
+/**
+ * Populate __index metamethod of index_object.parts table with the methods,
+ * which work like require('key_def').new(index_object.parts) methods.
+ * Each method is implemented as a C closure, associated with `struct key_def'.
+ */
+static void
+luaT_add_index_parts_methods(struct lua_State *L, const struct key_def *key_def)
+{
+	/* Metatable. */
+	lua_newtable(L);
+	/* __index */
+	lua_newtable(L);
+	int idx_index = lua_gettop(L);
+
+	/* Push 4 references to cdata onto the stack, one for each closure. */
+	luaT_push_key_def(L, key_def);
+	lua_pushvalue(L, -1);
+	lua_pushvalue(L, -1);
+	lua_pushvalue(L, -1);
+
+	lua_pushcclosure(L, &lbox_index_parts_extract_key, 1);
+	lua_setfield(L, idx_index, "extract_key");
+
+	lua_pushcclosure(L, &lbox_index_parts_compare, 1);
+	lua_setfield(L, idx_index, "compare");
+
+	lua_pushcclosure(L, &lbox_index_parts_compare_with_key, 1);
+	lua_setfield(L, idx_index, "compare_with_key");
+
+	lua_pushcclosure(L, &lbox_index_parts_merge, 1);
+	lua_setfield(L, idx_index, "merge");
+
+	lua_setfield(L, -2, "__index");
+	lua_setmetatable(L, -2);
+}
+
 /**
  * Make a single space available in Lua,
  * via box.space[] array.
@@ -452,8 +569,8 @@ lbox_fillspace(struct lua_State *L, struct space *space, int i)
 		lua_setfield(L, -2, "name");
 
 		lua_pushstring(L, "parts");
-		luaT_push_key_def(L, index_def->key_def);
-
+		luaT_push_key_def_parts(L, index_def->key_def);
+		luaT_add_index_parts_methods(L, index_def->key_def);
 		lua_settable(L, -3); /* space.index[k].parts */
 
 		lua_pushstring(L, "sequence_id");
diff --git a/test/box-luatest/gh_7356_index_parts_methods_test.lua b/test/box-luatest/gh_7356_index_parts_methods_test.lua
new file mode 100644
index 0000000000000000000000000000000000000000..1ea3c26c7a8ca735cb415ced56bfb34b939a7229
--- /dev/null
+++ b/test/box-luatest/gh_7356_index_parts_methods_test.lua
@@ -0,0 +1,161 @@
+local t = require('luatest')
+local g = t.group('gh-7356')
+
+g.before_all(function(cg)
+    local server = require('luatest.server')
+    cg.server = server:new({alias = 'gh_7356'})
+    cg.server:start()
+end)
+
+g.after_all(function(cg)
+    cg.server:drop()
+end)
+
+g.after_each(function(cg)
+    cg.server:exec(function()
+        if box.space.test then box.space.test:drop() end
+    end)
+end)
+
+-- Test index_object.parts:extract_key(tuple)
+g.test_extract_key = function(cg)
+    cg.server:exec(function()
+        local s = box.schema.space.create('test')
+        local pk = s:create_index('pk')
+        s:insert{1, 99.5, 'X', nil, {'a', 'b'}}
+        local sk = s:create_index('sk', {parts = {3, 'string', 1, 'unsigned'}})
+        local k = sk.parts:extract_key(pk:get{1})
+        t.assert_equals(k, {'X', 1})
+
+        t.assert_error_msg_content_equals(
+            'Usage: index.parts:extract_key(tuple)',
+            sk.parts.extract_key)
+        t.assert_error_msg_content_equals(
+            'Usage: index.parts:extract_key(tuple)',
+            sk.parts.extract_key, sk.parts)
+        t.assert_error_msg_content_equals(
+            'A tuple or a table expected, got number',
+            sk.parts.extract_key, sk.parts, 0)
+        t.assert_error_msg_content_equals(
+            'Tuple field [3] required by space format is missing',
+            sk.parts.extract_key, sk.parts, {0})
+
+        local mk = s:create_index('mk', {parts = {{path = '[*]', field = 5}}})
+        t.assert_error_msg_content_equals(
+            'multikey path is unsupported',
+            mk.parts.extract_key, mk.parts, pk:get{1})
+
+        -- Check that extract_key() method is recreated with the correct key_def
+        -- object after alter().
+        sk:alter({parts = {1, 'unsigned', 2, 'double'}})
+        t.assert_equals(sk.parts:extract_key(pk:get{1}), {1, 99.5})
+    end)
+end
+
+-- Test index_object.parts:compare(tuple_a, tuple_b)
+g.test_compare = function(cg)
+    cg.server:exec(function()
+        local s = box.schema.space.create('test')
+        local i = s:create_index('i', {parts = {
+            {field = 3, type = 'string', collation = 'unicode_ci'},
+            {field = 1, type = 'unsigned'}
+        }})
+        local tuple_a = {1, 99.5, 'x', nil, {'a', 'b'}}
+        local tuple_b = {1, 99.5, 'X', nil, {'a', 'b'}}
+        local tuple_c = {2, 99.5, 'X', nil, {'a', 'b'}}
+        t.assert_equals(i.parts:compare(tuple_a, tuple_b), 0)
+        t.assert_equals(i.parts:compare(tuple_a, tuple_c), -1)
+        t.assert_equals(i.parts.compare(nil, tuple_c, tuple_a), 1)
+
+        t.assert_error_msg_content_equals(
+            'Usage: index.parts:compare(tuple_a, tuple_b)',
+            i.parts.compare)
+        t.assert_error_msg_content_equals(
+            'Usage: index.parts:compare(tuple_a, tuple_b)',
+            i.parts.compare, i.parts, tuple_a)
+        t.assert_error_msg_content_equals(
+            'A tuple or a table expected, got cdata',
+            i.parts.compare, i.parts, tuple_a, box.NULL)
+        t.assert_error_msg_content_equals(
+            'Supplied key type of part 0 does not match index part type: ' ..
+            'expected string', i.parts.compare, nil, {0, [3]=0}, tuple_b)
+
+        local mk = s:create_index('mk', {parts = {{path = '[*]', field = 5}}})
+        t.assert_error_msg_content_equals(
+            'multikey path is unsupported',
+            mk.parts.compare, mk.parts, tuple_a, tuple_b)
+    end)
+end
+
+-- Test index_object.parts:compare_with_key(tuple_a, tuple_b)
+g.test_compare_with_key = function(cg)
+    cg.server:exec(function()
+        local s = box.schema.space.create('test')
+        local i = s:create_index('i', {parts = {
+            {field = 3, type = 'string', collation = 'unicode_ci'},
+            {field = 1, type = 'unsigned'}
+        }})
+        local tuple = {1, 99.5, 'x', nil, {'a', 'b'}}
+        t.assert_equals(i.parts:compare_with_key(tuple, {'x', 1}), 0)
+        t.assert_equals(i.parts:compare_with_key(tuple, {'X', 1}), 0)
+        t.assert_equals(i.parts:compare_with_key(tuple, {'X', 2}), -1)
+
+        t.assert_error_msg_content_equals(
+            'Usage: index.parts:compare_with_key(tuple, key)',
+            i.parts.compare_with_key)
+        t.assert_error_msg_content_equals(
+            'Usage: index.parts:compare_with_key(tuple, key)',
+            i.parts.compare_with_key, box.NULL)
+        t.assert_error_msg_content_equals(
+            'Usage: index.parts:compare_with_key(tuple, key)',
+            i.parts.compare_with_key, box.NULL, {0, nil, ''})
+        t.assert_error_msg_content_equals(
+            'Supplied key type of part 1 does not match index part type: ' ..
+            'expected unsigned',
+            i.parts.compare_with_key, box.NULL, {0, nil, ''}, {'', ''})
+
+        local mk = s:create_index('mk', {parts = {{path = '[*]', field = 5}}})
+        t.assert_error_msg_content_equals(
+            'multikey path is unsupported',
+            mk.parts.compare_with_key, mk.parts, tuple, {'x', 1})
+    end)
+end
+
+-- Test index_object.parts:merge(second_index_parts)
+g.test_merge = function(cg)
+    cg.server:exec(function()
+        local s = box.schema.space.create('test')
+        local i = s:create_index('i', {parts = {{type = 'string', field = 3},
+                                                {type = 'scalar', field = 2}}})
+        local j = s:create_index('j', {parts = {{type = 'unsigned', field = 1},
+                                                {type = 'string', field = 3}}})
+        local exp = {{fieldno = 3, type = 'string', is_nullable = false},
+                     {fieldno = 2, type = 'scalar', is_nullable = false},
+                     {fieldno = 1, type = 'unsigned', is_nullable = false}}
+        t.assert_equals(i.parts:merge(j.parts):totable(), exp)
+
+        t.assert_error_msg_content_equals(
+            'Usage: index.parts:merge(second_index_parts)',
+            i.parts.merge)
+        t.assert_error_msg_content_equals(
+            'Usage: index.parts:merge(second_index_parts)',
+            i.parts.merge, i.parts)
+        t.assert_error_msg_content_equals(
+            'Usage: index.parts:merge(second_index_parts)',
+            i.parts.merge, i.parts, j.parts, box.NULL)
+        t.assert_error_msg_content_equals(
+            "Can't create key_def from the second index.parts",
+            i.parts.merge, i.parts, 100)
+        t.assert_error_msg_content_equals(
+            "Can't create key_def from the second index.parts",
+            i.parts.merge, i.parts, {{type = 'scalar'}})
+
+        local mk = s:create_index('mk', {parts = {{path = '[*]', field = 5}}})
+        t.assert_error_msg_content_equals(
+            'multikey path is unsupported',
+            mk.parts.merge, mk.parts, j.parts)
+        t.assert_error_msg_content_equals(
+            "Can't create key_def from the second index.parts",
+            i.parts.merge, i.parts, mk.parts)
+    end)
+end
diff --git a/test/box-tap/key_def.test.lua b/test/box-tap/key_def.test.lua
index 4f101b7aa440c68dd3f39f546ca0587da989afc5..1b89dea1960af98ca9e25d2ddaeabb1f5c644f3a 100755
--- a/test/box-tap/key_def.test.lua
+++ b/test/box-tap/key_def.test.lua
@@ -182,7 +182,7 @@ local key_def_new_cases = {
 
 local test = tap.test('key_def')
 
-test:plan(#key_def_new_cases - 1 + 7)
+test:plan(#key_def_new_cases - 1 + 8)
 for _, case in ipairs(key_def_new_cases) do
     if type(case) == 'function' then
         case()
@@ -557,4 +557,25 @@ test:test('merge()', function(test)
         'composite case')
 end)
 
+-- Check the usage error messages.
+test:test('Usage errors', function(test)
+    test:plan(5)
+    -- key_def_lib.new() is tested above.
+    test:is_deeply({pcall(key_def_lib.extract_key)},
+                   {false, 'Usage: key_def:extract_key(tuple)'},
+                   'extract_key()')
+    test:is_deeply({pcall(key_def_lib.compare)},
+                   {false, 'Usage: key_def:compare(tuple_a, tuple_b)'},
+                   'compare()')
+    test:is_deeply({pcall(key_def_lib.compare_with_key)},
+                   {false, 'Usage: key_def:compare_with_key(tuple, key)'},
+                   'compare_with_key()')
+    test:is_deeply({pcall(key_def_lib.merge)},
+                   {false, 'Usage: key_def:merge(second_key_def)'},
+                   'merge()')
+    test:is_deeply({pcall(key_def_lib.totable)},
+                   {false, 'Usage: key_def:totable()'},
+                   'totable()')
+end)
+
 os.exit(test:check() and 0 or 1)