[tarantool-patches] [PATCH 2/7] sql: remove SQLite original struct Index

Nikita Pettik korablev at tarantool.org
Fri Aug 24 01:55:48 MSK 2018


As a part of SQL DD integration it is required to substitute SQLite
structure representing index with one from Tarantool internals.
To make this happen, lets add surrogate space to Table, which will
hold array of indexes. Those indexes are not real copies from Tarantool
core, but contain only index_def, since only def is useful during query
compilation.

Note that in new implementation indexes are held as array and added to
that array in straight order. In SQLite indexes are arranged in list and
added to the head. Hence, the order of indexes is reversed. It results
in different query plans: if planner must make choice of two equal
indexes, it chooses simply first one. Due to this change, some tests are
fixed.

Part of #3561
---
 src/box/sql.c                            |  56 +--
 src/box/sql/analyze.c                    |  69 ++--
 src/box/sql/build.c                      | 689 ++++++++++---------------------
 src/box/sql/delete.c                     |  14 +-
 src/box/sql/expr.c                       | 120 +-----
 src/box/sql/insert.c                     |  92 ++---
 src/box/sql/parse.y                      |   8 -
 src/box/sql/pragma.c                     | 188 ++++-----
 src/box/sql/prepare.c                    |   7 +-
 src/box/sql/select.c                     |  17 +-
 src/box/sql/sqliteInt.h                  |  79 ++--
 src/box/sql/tarantoolInt.h               |  34 +-
 src/box/sql/update.c                     |   3 +-
 src/box/sql/vdbe.c                       |  12 +-
 src/box/sql/vdbe.h                       |   6 +-
 src/box/sql/vdbeaux.c                    |   6 +-
 src/box/sql/vdbemem.c                    |  28 +-
 src/box/sql/where.c                      | 381 ++++++++---------
 src/box/sql/whereInt.h                   |   8 +-
 src/box/sql/wherecode.c                  | 167 +++-----
 test/sql-tap/analyze3.test.lua           |   2 +-
 test/sql-tap/analyze7.test.lua           |   8 +-
 test/sql-tap/analyze9.test.lua           |  10 +-
 test/sql-tap/analyzeF.test.lua           |   2 +-
 test/sql-tap/eqp.test.lua                |  14 +-
 test/sql-tap/gh-2996-indexed-by.test.lua |  10 +-
 test/sql-tap/lua-tables.test.lua         |   8 +-
 27 files changed, 807 insertions(+), 1231 deletions(-)

diff --git a/src/box/sql.c b/src/box/sql.c
index e1d4bc26a..61c766540 100644
--- a/src/box/sql.c
+++ b/src/box/sql.c
@@ -35,11 +35,9 @@
  * Both Tarantool and SQLite codebases declare Index, hence the
  * workaround below.
  */
-#define Index SqliteIndex
 #include "sql/sqliteInt.h"
 #include "sql/tarantoolInt.h"
 #include "sql/vdbeInt.h"
-#undef Index
 
 #include "index.h"
 #include "info.h"
@@ -1284,7 +1282,7 @@ int tarantoolSqlite3MakeTableFormat(Table *pTable, void *buf)
 	const struct Enc *enc = get_enc(buf);
 	const struct space_def *def = pTable->def;
 	assert(def != NULL);
-	struct SqliteIndex *pk_idx = sqlite3PrimaryKeyIndex(pTable);
+	struct index *pk_idx = sql_table_primary_key(pTable);
 	int pk_forced_int = -1;
 	char *base = buf, *p;
 	int i, n = def->field_count;
@@ -1406,27 +1404,20 @@ fkey_encode_links(const struct fkey_def *def, int type, char *buf)
 	return p - buf;
 }
 
-/*
- * Format "parts" array for _index entry.
- * Returns result size.
- * If buf==NULL estimate result size.
- *
- * Ex: [[0, "integer"]]
- */
-int tarantoolSqlite3MakeIdxParts(SqliteIndex *pIndex, void *buf)
+int
+sql_construct_index_parts(const struct field_def *fields,
+			  const struct index_def *idx_def,
+			  const struct index_def *pk_def, void *buf)
 {
-	struct field_def *fields = pIndex->pTable->def->fields;
-	struct key_def *key_def = pIndex->def->key_def;
+	struct key_def *key_def = idx_def->key_def;
 	const struct Enc *enc = get_enc(buf);
 	char *base = buf;
 	uint32_t pk_forced_int = UINT32_MAX;
-	struct SqliteIndex *primary_index =
-		sqlite3PrimaryKeyIndex(pIndex->pTable);
 
 	/* If table's PK is single column which is INTEGER, then
 	 * treat it as strict type, not affinity.  */
-	if (primary_index->def->key_def->part_count == 1) {
-		int pk = primary_index->def->key_def->parts[0].fieldno;
+	if (pk_def->key_def->part_count == 1) {
+		int pk = pk_def->key_def->parts[0].fieldno;
 		if (fields[pk].type == FIELD_TYPE_INTEGER)
 			pk_forced_int = pk;
 	}
@@ -1478,29 +1469,18 @@ int tarantoolSqlite3MakeIdxParts(SqliteIndex *pIndex, void *buf)
 	return p - base;
 }
 
-/*
- * Format "opts" dictionary for _index entry.
- * Returns result size.
- * If buf==NULL estimate result size.
- *
- * Ex: {
- *   "unique": "true",
- *   "sql": "CREATE INDEX student_by_name ON students(name)"
- * }
- */
-int tarantoolSqlite3MakeIdxOpts(SqliteIndex *index, const char *zSql, void *buf)
+int
+sql_construct_index_opts(const struct index_def *idx_def, void *buf)
 {
 	const struct Enc *enc = get_enc(buf);
 	char *base = buf, *p;
-
-	(void)index;
-
 	p = enc->encode_map(base, 2);
-	/* Mark as unique pk and unique indexes */
 	p = enc->encode_str(p, "unique", 6);
-	p = enc->encode_bool(p, index->def->opts.is_unique);
+	p = enc->encode_bool(p, idx_def->opts.is_unique);
 	p = enc->encode_str(p, "sql", 3);
-	p = enc->encode_str(p, zSql, zSql ? strlen(zSql) : 0);
+	const char *sql_stmt = idx_def->opts.sql;
+	p = enc->encode_str(p, sql_stmt, sql_stmt != NULL ?
+					 strlen(sql_stmt) : 0);
 	return (int)(p - base);
 }
 
@@ -1617,6 +1597,14 @@ sql_ephemeral_table_new(Parse *parser, const char *name)
 		sqlite3DbFree(db, table);
 		return NULL;
 	}
+	table->space = (struct space *) calloc(1, sizeof(struct space));
+	if (table->space == NULL) {
+		diag_set(OutOfMemory, sizeof(struct space), "calloc", "space");
+		parser->rc = SQL_TARANTOOL_ERROR;
+		parser->nErr++;
+		sqlite3DbFree(db, table);
+		return NULL;
+	}
 
 	table->def = def;
 	return table;
diff --git a/src/box/sql/analyze.c b/src/box/sql/analyze.c
index 74f5ae827..abed9d31b 100644
--- a/src/box/sql/analyze.c
+++ b/src/box/sql/analyze.c
@@ -777,14 +777,12 @@ callStatGet(Vdbe * v, int regStat4, int iParam, int regOut)
 static void
 analyzeOneTable(Parse * pParse,	/* Parser context */
 		Table * pTab,	/* Table whose indices are to be analyzed */
-		Index * pOnlyIdx,	/* If not NULL, only analyze this one index */
 		int iStatCur,	/* Index of VdbeCursor that writes the _sql_stat1 table */
 		int iMem,	/* Available memory locations begin here */
 		int iTab	/* Next available cursor */
     )
 {
 	sqlite3 *db = pParse->db;	/* Database handle */
-	Index *pIdx;		/* An index to being analyzed */
 	int iIdxCur;		/* Cursor open on index being analyzed */
 	int iTabCur;		/* Table cursor */
 	Vdbe *v;		/* The virtual machine being built up */
@@ -819,23 +817,27 @@ analyzeOneTable(Parse * pParse,	/* Parser context */
 	pParse->nTab = MAX(pParse->nTab, iTab);
 	sqlite3OpenTable(pParse, iTabCur, pTab, OP_OpenRead);
 	sqlite3VdbeLoadString(v, regTabname, pTab->def->name);
-
-	for (pIdx = pTab->pIndex; pIdx; pIdx = pIdx->pNext) {
+	/*
+	 * Here we need real space from Tarantool DD since
+	 * further it is passed to cursor opening routine.
+	 */
+	struct space *space = space_by_id(pTab->def->id);
+	assert(space != NULL);
+	for (uint32_t j = 0; j < space->index_count; ++j) {
+		struct index *idx = pTab->space->index[j];
 		int addrRewind;	/* Address of "OP_Rewind iIdxCur" */
 		int addrNextRow;	/* Address of "next_row:" */
 		const char *idx_name;	/* Name of the index */
 
-		if (pOnlyIdx && pOnlyIdx != pIdx)
-			continue;
 		/* Primary indexes feature automatically generated
 		 * names. Thus, for the sake of clarity, use
 		 * instead more familiar table name.
 		 */
-		if (sql_index_is_primary(pIdx))
+		if (idx->def->iid == 0)
 			idx_name = pTab->def->name;
 		else
-			idx_name = pIdx->def->name;
-		int part_count = pIdx->def->key_def->part_count;
+			idx_name = idx->def->name;
+		int part_count = idx->def->key_def->part_count;
 
 		/* Populate the register containing the index name. */
 		sqlite3VdbeLoadString(v, regIdxname, idx_name);
@@ -882,12 +884,9 @@ analyzeOneTable(Parse * pParse,	/* Parser context */
 		pParse->nMem = MAX(pParse->nMem, regPrev + part_count);
 
 		/* Open a read-only cursor on the index being analyzed. */
-		struct space *space = space_by_id(pIdx->def->space_id);
-		int idx_id = pIdx->def->iid;
-		assert(space != NULL);
-		sqlite3VdbeAddOp4(v, OP_OpenRead, iIdxCur, idx_id, 0,
+		sqlite3VdbeAddOp4(v, OP_OpenRead, iIdxCur, idx->def->iid, 0,
 				  (void *) space, P4_SPACEPTR);
-		VdbeComment((v, "%s", pIdx->def->name));
+		VdbeComment((v, "%s", idx->def->name));
 
 		/* Invoke the stat_init() function. The arguments are:
 		 *
@@ -939,7 +938,7 @@ analyzeOneTable(Parse * pParse,	/* Parser context */
 			 */
 			sqlite3VdbeAddOp0(v, OP_Goto);
 			addrNextRow = sqlite3VdbeCurrentAddr(v);
-			if (part_count == 1 && pIdx->def->opts.is_unique) {
+			if (part_count == 1 && idx->def->opts.is_unique) {
 				/* For a single-column UNIQUE index, once we have found a non-NULL
 				 * row, we know that all the rest will be distinct, so skip
 				 * subsequent distinctness tests.
@@ -948,7 +947,7 @@ analyzeOneTable(Parse * pParse,	/* Parser context */
 						  endDistinctTest);
 				VdbeCoverage(v);
 			}
-			struct key_part *part = pIdx->def->key_def->parts;
+			struct key_part *part = idx->def->key_def->parts;
 			for (i = 0; i < part_count; ++i, ++part) {
 				struct coll *coll = part->coll;
 				sqlite3VdbeAddOp2(v, OP_Integer, i, regChng);
@@ -972,7 +971,7 @@ analyzeOneTable(Parse * pParse,	/* Parser context */
 			 *  ...
 			 */
 			sqlite3VdbeJumpHere(v, addrNextRow - 1);
-			part = pIdx->def->key_def->parts;
+			part = idx->def->key_def->parts;
 			for (i = 0; i < part_count; ++i, ++part) {
 				sqlite3VdbeJumpHere(v, aGotoChng[i]);
 				sqlite3VdbeAddOp3(v, OP_Column, iIdxCur,
@@ -990,17 +989,17 @@ analyzeOneTable(Parse * pParse,	/* Parser context */
 		 *   if !eof(csr) goto next_row;
 		 */
 		assert(regKey == (regStat4 + 2));
-		Index *pPk = sqlite3PrimaryKeyIndex(pIdx->pTable);
+		struct index *pPk = sql_table_primary_key(pTab);
 		int pk_part_count = pPk->def->key_def->part_count;
 		/* Allocate memory for array. */
 		pParse->nMem = MAX(pParse->nMem,
 				   regPrev + part_count + pk_part_count);
 		int regKeyStat = regPrev + part_count;
-		for (int j = 0; j < pk_part_count; ++j) {
-			uint32_t k = pPk->def->key_def->parts[j].fieldno;
+		for (i = 0; i < pk_part_count; i++) {
+			uint32_t k = pPk->def->key_def->parts[i].fieldno;
 			assert(k < pTab->def->field_count);
 			sqlite3VdbeAddOp3(v, OP_Column, iIdxCur, k,
-					  regKeyStat + j);
+					  regKeyStat + i);
 			VdbeComment((v, "%s", pTab->def->fields[k].name));
 		}
 		sqlite3VdbeAddOp3(v, OP_MakeRecord, regKeyStat,
@@ -1048,8 +1047,9 @@ analyzeOneTable(Parse * pParse,	/* Parser context */
 		 */
 		VdbeCoverageNeverTaken(v);
 		for (i = 0; i < part_count; i++) {
-			sqlite3ExprCodeLoadIndexColumn(pParse, pIdx, iTabCur, i,
-						       regCol + i);
+			uint32_t tabl_col = idx->def->key_def->parts[i].fieldno;
+			sqlite3ExprCodeGetColumnOfTable(v, space->def, iTabCur,
+							tabl_col, regCol + i);
 		}
 		sqlite3VdbeAddOp3(v, OP_MakeRecord, regCol, part_count,
 				  regSample);
@@ -1093,7 +1093,7 @@ sql_analyze_database(Parse *parser)
 	     k = sqliteHashNext(k)) {
 		struct Table *table = (struct Table *) sqliteHashData(k);
 		if (! table->def->opts.is_view) {
-			analyzeOneTable(parser, table, NULL, stat_cursor, reg,
+			analyzeOneTable(parser, table, stat_cursor, reg,
 					tab_cursor);
 		}
 	}
@@ -1115,7 +1115,7 @@ vdbe_emit_analyze_table(struct Parse *parse, struct Table *table)
 	int stat_cursor = parse->nTab;
 	parse->nTab += 3;
 	vdbe_emit_stat_space_open(parse, stat_cursor, table->def->name);
-	analyzeOneTable(parse, table, NULL, stat_cursor, parse->nMem + 1,
+	analyzeOneTable(parse, table, stat_cursor, parse->nMem + 1,
 			parse->nTab);
 	loadAnalysis(parse);
 }
@@ -1635,22 +1635,25 @@ sql_space_tuple_log_count(struct Table *tab)
 }
 
 log_est_t
-index_field_tuple_est(struct Index *idx, uint32_t field)
+index_field_tuple_est(const struct index_def *idx_def, uint32_t field)
 {
-	struct space *space = space_by_id(idx->pTable->def->id);
-	if (space == NULL || strcmp(idx->def->opts.sql, "fake_autoindex") == 0)
-		return idx->def->opts.stat->tuple_log_est[field];
-	struct index *tnt_idx = space_index(space, idx->def->iid);
+	assert(idx_def != NULL);
+	struct space *space = space_by_id(idx_def->space_id);
+	if (space == NULL || (idx_def->opts.sql != NULL &&
+			      strcmp(idx_def->opts.sql, "fake_autoindex") == 0))
+		return idx_def->opts.stat->tuple_log_est[field];
+	assert(field <= idx_def->key_def->part_count);
+	/* Statistics is held only in real indexes. */
+	struct index *tnt_idx = space_index(space, idx_def->iid);
 	assert(tnt_idx != NULL);
-	assert(field <= tnt_idx->def->key_def->part_count);
 	if (tnt_idx->def->opts.stat == NULL) {
 		/*
 		 * Last number for unique index is always 0:
 		 * only one tuple exists with given full key
 		 * in unique index and log(1) == 0.
 		 */
-		if (field == tnt_idx->def->key_def->part_count &&
-		    tnt_idx->def->opts.is_unique)
+		if (field == idx_def->key_def->part_count &&
+		    idx_def->opts.is_unique)
 			return 0;
 		return default_tuple_est[field + 1 >= 6 ? 6 : field];
 	}
diff --git a/src/box/sql/build.c b/src/box/sql/build.c
index 47fa7c305..79dc46592 100644
--- a/src/box/sql/build.c
+++ b/src/box/sql/build.c
@@ -141,7 +141,7 @@ sqlite3LocateTable(Parse * pParse,	/* context in which to report errors */
 	return p;
 }
 
-Index *
+struct index *
 sqlite3LocateIndex(sqlite3 * db, const char *zName, const char *zTable)
 {
 	assert(zName);
@@ -151,55 +151,46 @@ sqlite3LocateIndex(sqlite3 * db, const char *zName, const char *zTable)
 
 	if (pTab == NULL)
 		return NULL;
-	for (struct Index *idx = pTab->pIndex; idx != NULL; idx = idx->pNext) {
+	for (uint32_t i = 0; i < pTab->space->index_count; ++i) {
+		struct index *idx = pTab->space->index[i];
 		if (strcmp(zName, idx->def->name) == 0)
 			return idx;
 	}
 	return NULL;
 }
 
-/*
- * Reclaim the memory used by an index
- */
-static void
-freeIndex(sqlite3 * db, Index * p)
-{
-	if (p->def != NULL)
-		index_def_delete(p->def);
-	sqlite3DbFree(db, p);
-}
-
-/*
- * For the index called zIdxName which is found in the database,
- * unlike that index from its Table then remove the index from
- * the index hash table and free all memory structures associated
- * with the index.
- */
 void
-sqlite3UnlinkAndDeleteIndex(sqlite3 * db, Index * pIndex)
+sql_space_index_delete(struct space *space, uint32_t iid)
 {
-	assert(pIndex != 0);
-
-	struct session *user_session = current_session();
-	if (ALWAYS(pIndex)) {
-		if (pIndex->pTable->pIndex == pIndex) {
-			pIndex->pTable->pIndex = pIndex->pNext;
-		} else {
-			Index *p;
-			/* Justification of ALWAYS();  The index must be on the list of
-			 * indices.
-			 */
-			p = pIndex->pTable->pIndex;
-			while (ALWAYS(p) && p->pNext != pIndex) {
-				p = p->pNext;
-			}
-			if (ALWAYS(p && p->pNext == pIndex)) {
-				p->pNext = pIndex->pNext;
+	assert(space != NULL);
+	for (uint32_t i = 0; i < space->index_count; ++i) {
+		struct index *idx = space->index[i];
+		/*
+		 * Allocate new chunk with size reduced by 1 slot.
+		 * Copy all indexes to that chunk except for one
+		 * to be deleted.
+		 */
+		if (idx->def->iid == iid) {
+			free(idx->def);
+			free(idx);
+			size_t idx_sz = sizeof(struct index *);
+			uint32_t idx_count = --space->index_count;
+			struct index **new_idexes =
+				(struct index **) malloc(idx_sz * idx_count);
+			if (new_idexes == NULL) {
+				diag_set(OutOfMemory, idx_sz * idx_count,
+					 "malloc", "new_indexes");
+				return;
 			}
+			memcpy(new_idexes, space->index, i * idx_sz);
+			memcpy(new_idexes + i, space->index + i + 1,
+			       idx_sz * (idx_count - i));
+			free(space->index);
+			space->index = new_idexes;
+			break;
 		}
-		freeIndex(db, pIndex);
 	}
-
+	struct session *user_session = current_session();
 	user_session->sql_flags |= SQLITE_InternChanges;
 }
 
@@ -259,38 +250,39 @@ table_column_is_in_pk(Table *table, uint32_t column)
 	return false;
 }
 
-/*
+/**
  * Remove the memory data structures associated with the given
- * Table.  No changes are made to disk by this routine.
+ * Table.
  *
- * This routine just deletes the data structure.  It does not unlink
- * the table data structure from the hash table.  But it does destroy
- * memory structures of the indices and foreign keys associated with
- * the table.
- *
- * The db parameter is optional.  It is needed if the Table object
- * contains lookaside memory.  (Table objects in the schema do not use
- * lookaside memory, but some ephemeral Table objects do.)  Or the
- * db parameter can be used with db->pnBytesFreed to measure the memory
- * used by the Table object.
+ * @param db Database handler.
+ * @param tab Table to be deleted.
  */
-static void SQLITE_NOINLINE
-deleteTable(sqlite3 * db, Table * pTable)
+static void
+table_delete(struct sqlite3 *db, struct Table *tab)
 {
-	Index *pIndex, *pNext;
-
+	if (tab->space->def != NULL)
+		goto skip_index_delete;
 	/* Delete all indices associated with this table. */
-	for (pIndex = pTable->pIndex; pIndex; pIndex = pNext) {
-		pNext = pIndex->pNext;
-		freeIndex(db, pIndex);
-	}
-	assert(pTable->def != NULL);
-	/* Do not delete pTable->def allocated on region. */
-	if (!pTable->def->opts.is_temporary)
-		space_def_delete(pTable->def);
+	for (uint32_t i = 0; i < tab->space->index_count; ++i) {
+		/*
+		 * These indexes are just wrapper for
+		 * index_def's, so it makes no sense to call
+		 * index_delete().
+		 */
+		struct index *idx = tab->space->index[i];
+		free(idx->def);
+		free(idx);
+	}
+	free(tab->space);
+	free(tab->space->index);
+skip_index_delete:
+	assert(tab->def != NULL);
+	/* Do not delete table->def allocated on region. */
+	if (!tab->def->opts.is_temporary)
+		space_def_delete(tab->def);
 	else
-		sql_expr_list_delete(db, pTable->def->opts.checks);
-	sqlite3DbFree(db, pTable);
+		sql_expr_list_delete(db, tab->def->opts.checks);
+	sqlite3DbFree(db, tab);
 }
 
 void
@@ -301,7 +293,7 @@ sqlite3DeleteTable(sqlite3 * db, Table * pTable)
 		return;
 	if (((!db || db->pnBytesFreed == 0) && (--pTable->nTabRef) > 0))
 		return;
-	deleteTable(db, pTable);
+	table_delete(db, pTable);
 }
 
 /*
@@ -368,16 +360,12 @@ sqlite3CheckIdentifierName(Parse *pParse, char *zName)
 	return SQLITE_OK;
 }
 
-/*
- * Return the PRIMARY KEY index of a table
- */
-Index *
-sqlite3PrimaryKeyIndex(Table * pTab)
+struct index *
+sql_table_primary_key(const struct Table *tab)
 {
-	Index *p;
-	for (p = pTab->pIndex; p != NULL && !sql_index_is_primary(p);
-	     p = p->pNext);
-	return p;
+	if (tab->space->index_count == 0 || tab->space->index[0]->def->iid != 0)
+		return NULL;
+	return tab->space->index[0];
 }
 
 /**
@@ -462,14 +450,6 @@ sqlite3StartTable(Parse *pParse, Token *pName, int noErr)
 	assert(pParse->pNewTable == 0);
 	pParse->pNewTable = pTable;
 
-	/* Begin generating the code that will create a new table.
-	 * Note in particular that we must go ahead and allocate the
-	 * record number for the table entry now.  Before any
-	 * PRIMARY KEY or UNIQUE keywords are parsed.  Those keywords will cause
-	 * indices to be created and the table record must come before the
-	 * indices.  Hence, the record number for the table must be allocated
-	 * now.
-	 */
 	if (!db->init.busy && (v = sqlite3GetVdbe(pParse)) != 0)
 		sql_set_multi_write(pParse, true);
 
@@ -830,7 +810,7 @@ sqlite3AddPrimaryKey(Parse * pParse,	/* Parsing context */
 	int nTerm;
 	if (pTab == 0)
 		goto primary_key_exit;
-	if (sqlite3PrimaryKeyIndex(pTab) != NULL) {
+	if (sql_table_primary_key(pTab) != NULL) {
 		sqlite3ErrorMsg(pParse,
 				"table \"%s\" has more than one primary key",
 				pTab->def->name);
@@ -890,7 +870,7 @@ sqlite3AddPrimaryKey(Parse * pParse,	/* Parsing context */
 			goto primary_key_exit;
 	}
 
-	struct Index *pk = sqlite3PrimaryKeyIndex(pTab);
+	struct index *pk = sql_table_primary_key(pTab);
 	assert(pk != NULL);
 	struct key_def *pk_key_def = pk->def->key_def;
 	for (uint32_t i = 0; i < pk_key_def->part_count; i++) {
@@ -952,11 +932,11 @@ sqlite3AddCollateType(Parse * pParse, Token * pToken)
 		 * then an index may have been created on this column before the
 		 * collation type was added. Correct this if it is the case.
 		 */
-		for (struct Index *pIdx = p->pIndex; pIdx != NULL;
-		     pIdx = pIdx->pNext) {
-			assert(pIdx->def->key_def->part_count == 1);
-			if (pIdx->def->key_def->parts[0].fieldno == i) {
-				coll_id = &pIdx->def->key_def->parts[0].coll_id;
+		for (uint32_t i = 0; i < p->space->index_count; ++i) {
+			struct index *idx = p->space->index[i];
+			assert(idx->def->key_def->part_count == 1);
+			if (idx->def->key_def->parts[0].fieldno == i) {
+				coll_id = &idx->def->key_def->parts[0].coll_id;
 				(void)sql_column_collation(p->def, i, coll_id);
 			}
 		}
@@ -1155,129 +1135,123 @@ getNewSpaceId(Parse * pParse)
 	return iRes;
 }
 
-/*
- * Generate VDBE code to create an Index. This is acomplished by adding
- * an entry to the _index table. ISpaceId either contains the literal
- * space id or designates a register storing the id.
+/**
+ * Generate VDBE code to create an Index. This is accomplished by
+ * adding an entry to the _index table.
+ *
+ * @param parse Current parsing context.
+ * @param def Definition of space which index belongs to.
+ * @param idx_def Definition of index under construction.
+ * @param pk_def Definition of primary key index.
+ * @param space_id_reg Register containing generated space id.
  */
 static void
-createIndex(Parse * pParse, Index * pIndex, int iSpaceId, int iIndexId,
-	    const char *zSql)
+vdbe_emit_create_index(struct Parse *parse, struct space_def *def,
+		       const struct index_def *idx_def,
+		       const struct index_def *pk_def, int space_id_reg)
 {
-	Vdbe *v = sqlite3GetVdbe(pParse);
-	int iFirstCol = ++pParse->nMem;
-	int iRecord = (pParse->nMem += 6);	/* 6 total columns */
-	char *zOpts, *zParts;
-	int zOptsSz, zPartsSz;
-
+	struct Vdbe *v = sqlite3GetVdbe(parse);
+	int entry_reg = ++parse->nMem;
+	/*
+	 * Entry in _index space contains 6 fields.
+	 * The last one contains encoded tuple.
+	 */
+	int tuple_reg = (parse->nMem += 6);
 	/* Format "opts" and "parts" for _index entry. */
-	zOpts = sqlite3DbMallocRaw(pParse->db,
-				   tarantoolSqlite3MakeIdxOpts(pIndex, zSql,
-							       NULL) +
-				   tarantoolSqlite3MakeIdxParts(pIndex,
-								NULL) + 2);
-	if (!zOpts)
+	char *opts =
+		sqlite3DbMallocRaw(parse->db,
+				   sql_construct_index_opts(idx_def, NULL) +
+				   sql_construct_index_parts(def->fields,
+							     idx_def, pk_def,
+							     NULL) + 2);
+	if (opts == NULL)
 		return;
-	zOptsSz = tarantoolSqlite3MakeIdxOpts(pIndex, zSql, zOpts);
-	zParts = zOpts + zOptsSz + 1;
-	zPartsSz = tarantoolSqlite3MakeIdxParts(pIndex, zParts);
+	uint32_t opts_sz = sql_construct_index_opts(idx_def, opts);
+	char *parts = opts + opts_sz + 1;
+	uint32_t parts_sz = sql_construct_index_parts(def->fields, idx_def,
+						      pk_def, parts);
 #if SQLITE_DEBUG
 	/* NUL-termination is necessary for VDBE trace facility only */
-	zOpts[zOptsSz] = 0;
-	zParts[zPartsSz] = 0;
+	opts[opts_sz] = 0;
+	parts[parts_sz] = 0;
 #endif
-
-	if (pParse->pNewTable) {
-		int reg;
-		/*
-		 * A new table is being created, hence iSpaceId is a register, but
-		 * iIndexId is literal.
-		 */
-		sqlite3VdbeAddOp2(v, OP_SCopy, iSpaceId, iFirstCol);
-		sqlite3VdbeAddOp2(v, OP_Integer, iIndexId, iFirstCol + 1);
-
-		/* Generate code to save new pageno into a register.
-		 * This is runtime implementation of SQLITE_PAGENO_FROM_SPACEID_AND_INDEXID:
-		 *   pageno = (spaceid << 10) | indexid
-		 */
-		pParse->regRoot = ++pParse->nMem;
-		reg = ++pParse->nMem;
-		sqlite3VdbeAddOp2(v, OP_Integer, 1 << 10, reg);
-		sqlite3VdbeAddOp3(v, OP_Multiply, reg, iSpaceId,
-				  pParse->regRoot);
-		sqlite3VdbeAddOp3(v, OP_AddImm, pParse->regRoot, iIndexId,
-				  pParse->regRoot);
+	if (parse->pNewTable != NULL) {
+		sqlite3VdbeAddOp2(v, OP_SCopy, space_id_reg, entry_reg);
+		sqlite3VdbeAddOp2(v, OP_Integer, idx_def->iid, entry_reg + 1);
 	} else {
 		/*
-		 * An existing table is being modified; iSpaceId is literal, but
-		 * iIndexId is a register.
+		 * An existing table is being modified;
+		 * space_id_reg is register, but iid is literal.
 		 */
-		sqlite3VdbeAddOp2(v, OP_Integer, iSpaceId, iFirstCol);
-		sqlite3VdbeAddOp2(v, OP_SCopy, iIndexId, iFirstCol + 1);
+		sqlite3VdbeAddOp2(v, OP_Integer, space_id_reg, entry_reg);
+		sqlite3VdbeAddOp2(v, OP_SCopy, idx_def->iid, entry_reg + 1);
 	}
-	sqlite3VdbeAddOp4(v,
-			  OP_String8, 0, iFirstCol + 2, 0,
-			  sqlite3DbStrDup(pParse->db, pIndex->def->name),
+	sqlite3VdbeAddOp4(v, OP_String8, 0, entry_reg + 2, 0,
+			  sqlite3DbStrDup(parse->db, idx_def->name),
 			  P4_DYNAMIC);
-	sqlite3VdbeAddOp4(v, OP_String8, 0, iFirstCol + 3, 0, "tree",
+	sqlite3VdbeAddOp4(v, OP_String8, 0, entry_reg + 3, 0, "tree",
 			  P4_STATIC);
-	sqlite3VdbeAddOp4(v, OP_Blob, zOptsSz, iFirstCol + 4,
-			  SQL_SUBTYPE_MSGPACK, zOpts, P4_DYNAMIC);
-	/* zOpts and zParts are co-located, hence STATIC */
-	sqlite3VdbeAddOp4(v, OP_Blob, zPartsSz, iFirstCol + 5,
-			  SQL_SUBTYPE_MSGPACK,zParts, P4_STATIC);
-	sqlite3VdbeAddOp3(v, OP_MakeRecord, iFirstCol, 6, iRecord);
-	sqlite3VdbeAddOp2(v, OP_SInsert, BOX_INDEX_ID, iRecord);
+	sqlite3VdbeAddOp4(v, OP_Blob, opts_sz, entry_reg + 4,
+			  SQL_SUBTYPE_MSGPACK, opts, P4_DYNAMIC);
+	/* opts and parts are co-located, hence STATIC. */
+	sqlite3VdbeAddOp4(v, OP_Blob, parts_sz, entry_reg + 5,
+			  SQL_SUBTYPE_MSGPACK, parts, P4_STATIC);
+	sqlite3VdbeAddOp3(v, OP_MakeRecord, entry_reg, 6, tuple_reg);
+	sqlite3VdbeAddOp2(v, OP_SInsert, BOX_INDEX_ID, tuple_reg);
 	/*
 	 * Non-NULL value means that index has been created via
 	 * separate CREATE INDEX statement.
 	 */
-	if (zSql != NULL)
+	if (idx_def->opts.sql != NULL)
 		sqlite3VdbeChangeP5(v, OPFLAG_NCHANGE);
 }
 
-/*
+/**
  * Generate code to initialize register range with arguments for
  * ParseSchema2. Consumes zSql. Returns the first register used.
+ *
+ * @param parse Current parsing context.
+ * @param idx_name Name of index to be created.
+ * @param space_id Space id (or register containing it)
+ *                 which index belongs to.
+ * @param iIndexId Id of index (or register containing it)
+ *                 to be created.
+ * @param sql_stmt String containing 'CREATE INDEX ...' statement.
+ *                 NULL for UNIQUE and PK constraints.
  */
 static int
-makeIndexSchemaRecord(Parse * pParse,
-		      Index * pIndex,
-		      int iSpaceId, int iIndexId, const char *zSql)
+vdbe_emit_index_schema_record(struct Parse *parse, const char *idx_name,
+			      int space_id, int iid, const char *sql_stmt)
 {
-	Vdbe *v = sqlite3GetVdbe(pParse);
-	int iP4Type;
-	int iFirstCol = pParse->nMem + 1;
-	pParse->nMem += 4;
+	struct Vdbe *v = sqlite3GetVdbe(parse);
+	int entry_reg = parse->nMem + 1;
+	parse->nMem += 4;
 
-	sqlite3VdbeAddOp4(v,
-			  OP_String8, 0, iFirstCol, 0,
-			  sqlite3DbStrDup(pParse->db, pIndex->def->name),
-			  P4_DYNAMIC);
-
-	if (pParse->pNewTable) {
+	sqlite3VdbeAddOp4(v, OP_String8, 0, entry_reg, 0,
+			  sqlite3DbStrDup(parse->db, idx_name), P4_DYNAMIC);
+	if (parse->pNewTable) {
 		/*
-		 * A new table is being created, hence iSpaceId is a register, but
-		 * iIndexId is literal.
+		 * A new table is being created, hence space_id
+		 * is a register, but index id is literal.
 		 */
-		sqlite3VdbeAddOp2(v, OP_SCopy, iSpaceId, iFirstCol + 1);
-		sqlite3VdbeAddOp2(v, OP_Integer, iIndexId, iFirstCol + 2);
+		sqlite3VdbeAddOp2(v, OP_SCopy, space_id, entry_reg + 1);
+		sqlite3VdbeAddOp2(v, OP_Integer, iid, entry_reg + 2);
 	} else {
 		/*
-		 * An existing table is being modified; iSpaceId is literal, but
-		 * iIndexId is a register.
+		 * An existing table is being modified; space id
+		 * is literal, but index id is a register.
 		 */
-		sqlite3VdbeAddOp2(v, OP_Integer, iSpaceId, iFirstCol + 1);
-		sqlite3VdbeAddOp2(v, OP_SCopy, iIndexId, iFirstCol + 2);
+		sqlite3VdbeAddOp2(v, OP_Integer, space_id, entry_reg + 1);
+		sqlite3VdbeAddOp2(v, OP_SCopy, iid, entry_reg + 2);
 	}
-
-	iP4Type = P4_DYNAMIC;
-	if (zSql == 0) {
-		zSql = "";
-		iP4Type = P4_STATIC;
+	int p4_type = P4_DYNAMIC;
+	if (sql_stmt == NULL) {
+		sql_stmt = "";
+		p4_type = P4_STATIC;
 	}
-	sqlite3VdbeAddOp4(v, OP_String8, 0, iFirstCol + 3, 0, zSql, iP4Type);
-	return iFirstCol;
+	sqlite3VdbeAddOp4(v, OP_String8, 0, entry_reg + 3, 0, sql_stmt,
+			  p4_type);
+	return entry_reg;
 }
 
 /*
@@ -1344,8 +1318,7 @@ parseTableSchemaRecord(Parse * pParse, int iSpaceId, char *zStmt)
 {
 	Table *p = pParse->pNewTable;
 	Vdbe *v = sqlite3GetVdbe(pParse);
-	Index *pIdx, *pPrimaryIdx;
-	int i, iTop = pParse->nMem + 1;
+	int iTop = pParse->nMem + 1;
 	pParse->nMem += 4;
 
 	sqlite3VdbeAddOp4(v, OP_String8, 0, iTop, 0,
@@ -1354,11 +1327,11 @@ parseTableSchemaRecord(Parse * pParse, int iSpaceId, char *zStmt)
 	sqlite3VdbeAddOp2(v, OP_Integer, 0, iTop + 2);
 	sqlite3VdbeAddOp4(v, OP_String8, 0, iTop + 3, 0, zStmt, P4_DYNAMIC);
 
-	pPrimaryIdx = sqlite3PrimaryKeyIndex(p);
-	for (pIdx = p->pIndex, i = 0; pIdx; pIdx = pIdx->pNext) {
-		if (pIdx == pPrimaryIdx)
-			continue;
-		makeIndexSchemaRecord(pParse, pIdx, iSpaceId, ++i, NULL);
+	if (!p->def->opts.is_view)
+		for (uint32_t i = 1; i < p->space->index_count; ++i) {
+			const char *idx_name = p->space->index[i]->def->name;
+			vdbe_emit_index_schema_record(pParse, idx_name,
+						      iSpaceId, i, NULL);
 	}
 
 	sqlite3VdbeAddParseSchema2Op(v, iTop, pParse->nMem - iTop + 1);
@@ -1603,13 +1576,14 @@ sqlite3EndTable(Parse * pParse,	/* Parse context */
 	 */
 	if (db->init.busy) {
 		p->def->id = db->init.space_id;
-		for(struct Index *idx = p->pIndex; idx != NULL;
-		    idx = idx->pNext)
-			idx->def->space_id = p->def->id;
+		if (!p->def->opts.is_view) {
+			for (uint32_t i = 0; i < p->space->index_count; ++i)
+				p->space->index[i]->def->space_id = p->def->id;
+		}
 	}
 
 	if (!p->def->opts.is_view) {
-		if (sqlite3PrimaryKeyIndex(p) == NULL) {
+		if (sql_table_primary_key(p) == NULL) {
 			sqlite3ErrorMsg(pParse,
 					"PRIMARY KEY missing on table %s",
 					p->def->name);
@@ -1683,10 +1657,13 @@ sqlite3EndTable(Parse * pParse,	/* Parse context */
 	createSpace(pParse, reg_space_id, stmt);
 	/* Indexes aren't required for VIEW's.. */
 	if (!p->def->opts.is_view) {
-		struct Index *idx = sqlite3PrimaryKeyIndex(p);
-		assert(idx != NULL);
-		for (uint32_t i = 0; idx != NULL; idx = idx->pNext, i++)
-			createIndex(pParse, idx, reg_space_id, i, NULL);
+		struct index *pk = sql_table_primary_key(p);
+		for (uint32_t i = 0; i < p->space->index_count; ++i) {
+			struct index *idx = p->space->index[i];
+			idx->def->iid = i;
+			vdbe_emit_create_index(pParse, p->def, idx->def,
+					       pk->def, reg_space_id);
+		}
 	}
 
 	/*
@@ -1733,7 +1710,7 @@ sqlite3EndTable(Parse * pParse,	/* Parse context */
 			}
 			fk->parent_id = reg_space_id;
 		} else if (fk_parse->is_self_referenced) {
-			struct Index *pk = sqlite3PrimaryKeyIndex(p);
+			struct index *pk = sql_table_primary_key(p);
 			if (pk->def->key_def->part_count != fk->field_count) {
 				diag_set(ClientError, ER_CREATE_FK_CONSTRAINT,
 					 fk->name, "number of columns in "\
@@ -2431,84 +2408,6 @@ sql_drop_foreign_key(struct Parse *parse_context, struct SrcList *table,
 		vdbe_emit_fkey_drop(parse_context, constraint_name, child_id);
 }
 
-/*
- * Generate code that will erase and refill index *pIdx.  This is
- * used to initialize a newly created index or to recompute the
- * content of an index in response to a REINDEX command.
- */
-static void
-sqlite3RefillIndex(Parse * pParse, Index * pIndex)
-{
-	Table *pTab = pIndex->pTable;	/* The table that is indexed */
-	int iTab = pParse->nTab++;	/* Btree cursor used for pTab */
-	int iIdx = pParse->nTab++;	/* Btree cursor used for pIndex */
-	int iSorter;		/* Cursor opened by OpenSorter (if in use) */
-	int addr1;		/* Address of top of loop */
-	int addr2;		/* Address to jump to for next iteration */
-	Vdbe *v;		/* Generate code into this virtual machine */
-	int regRecord;		/* Register holding assembled index record */
-	sqlite3 *db = pParse->db;	/* The database connection */
-	v = sqlite3GetVdbe(pParse);
-	if (v == 0)
-		return;
-	struct key_def *def = key_def_dup(pIndex->def->key_def);
-	if (def == NULL) {
-		sqlite3OomFault(db);
-		return;
-	}
-	/* Open the sorter cursor if we are to use one. */
-	iSorter = pParse->nTab++;
-	sqlite3VdbeAddOp4(v, OP_SorterOpen, iSorter, 0,
-			  pIndex->def->key_def->part_count, (char *)def,
-			  P4_KEYDEF);
-
-	/* Open the table. Loop through all rows of the table, inserting index
-	 * records into the sorter.
-	 */
-	sqlite3OpenTable(pParse, iTab, pTab, OP_OpenRead);
-	addr1 = sqlite3VdbeAddOp2(v, OP_Rewind, iTab, 0);
-	VdbeCoverage(v);
-	regRecord = sqlite3GetTempReg(pParse);
-
-	sql_generate_index_key(pParse, pIndex, iTab, regRecord, NULL, 0);
-	sqlite3VdbeAddOp2(v, OP_SorterInsert, iSorter, regRecord);
-	sqlite3VdbeAddOp2(v, OP_Next, iTab, addr1 + 1);
-	VdbeCoverage(v);
-	sqlite3VdbeJumpHere(v, addr1);
-	sqlite3VdbeAddOp2(v, OP_Clear, pIndex->pTable->def->id, 0);
-	struct space *space = space_by_id(pIndex->pTable->def->id);
-	vdbe_emit_open_cursor(pParse, iIdx, pIndex->def->iid,
-			      space);
-	sqlite3VdbeChangeP5(v, 0);
-
-	addr1 = sqlite3VdbeAddOp2(v, OP_SorterSort, iSorter, 0);
-	VdbeCoverage(v);
-	if (pIndex->def->opts.is_unique) {
-		int j2 = sqlite3VdbeCurrentAddr(v) + 3;
-		sqlite3VdbeGoto(v, j2);
-		addr2 = sqlite3VdbeCurrentAddr(v);
-		sqlite3VdbeAddOp4Int(v, OP_SorterCompare, iSorter, j2,
-				     regRecord,
-				     pIndex->def->key_def->part_count);
-		VdbeCoverage(v);
-		parser_emit_unique_constraint(pParse, ON_CONFLICT_ACTION_ABORT,
-					      pIndex);
-	} else {
-		addr2 = sqlite3VdbeCurrentAddr(v);
-	}
-	sqlite3VdbeAddOp3(v, OP_SorterData, iSorter, regRecord, iIdx);
-	sqlite3VdbeAddOp3(v, OP_Last, iIdx, 0, -1);
-	sqlite3VdbeAddOp2(v, OP_IdxInsert, iIdx, regRecord);
-	sqlite3ReleaseTempReg(pParse, regRecord);
-	sqlite3VdbeAddOp2(v, OP_SorterNext, iSorter, addr2);
-	VdbeCoverage(v);
-	sqlite3VdbeJumpHere(v, addr1);
-
-	sqlite3VdbeAddOp1(v, OP_Close, iTab);
-	sqlite3VdbeAddOp1(v, OP_Close, iIdx);
-	sqlite3VdbeAddOp1(v, OP_Close, iSorter);
-}
-
 /*
  * Generate code to determine next free Iid in the space identified by
  * the iSpaceId. Return register number holding the result.
@@ -2558,16 +2457,24 @@ getNewIid(Parse * pParse, int iSpaceId, int iCursor)
  * @param tab Table to which belongs given index.
  */
 static void
-table_add_index(struct Table *tab, struct Index *index)
-{
-	struct Index *pk = sqlite3PrimaryKeyIndex(tab);
-	if (pk != NULL) {
-		index->pNext = pk->pNext;
-		pk->pNext = index;
-	} else {
-		index->pNext = tab->pIndex;
-		tab->pIndex = index;
+table_add_index(struct Table *tab, struct index *index)
+{
+	uint32_t idx_count = tab->space->index_count;
+	size_t indexes_sz = sizeof(struct index *) * (idx_count + 1);
+	struct index **idx = (struct index **) realloc(tab->space->index,
+						       indexes_sz);
+	if (idx == NULL) {
+		diag_set(OutOfMemory, indexes_sz, "malloc", "idx");
+		return;
+	}
+	tab->space->index = idx;
+	/* Make sure that PK always comes as first member. */
+	if (index->def->iid == 0 && idx_count != 0) {
+		struct index *tmp = tab->space->index[0];
+		tab->space->index[0] = index;
+		index = tmp;
 	}
+	tab->space->index[tab->space->index_count++] = index;
 }
 
 /**
@@ -2591,7 +2498,7 @@ table_add_index(struct Table *tab, struct Index *index)
  * @retval 0 on success, -1 on error.
  */
 static int
-index_fill_def(struct Parse *parse, struct Index *index,
+index_fill_def(struct Parse *parse, struct index *index,
 	       struct Table *table, uint32_t iid, const char *name,
 	       uint32_t name_len, struct ExprList *expr_list,
 	       enum sql_index_type idx_type, char *sql_stmt)
@@ -2679,19 +2586,13 @@ constraint_is_named(const char *name)
 		strncmp(name, "unique_unnamed_", strlen("unique_unnamed_"));
 }
 
-bool
-sql_index_is_primary(const struct Index *idx)
-{
-	return idx->def->iid == 0;
-}
-
 void
 sql_create_index(struct Parse *parse, struct Token *token,
 		 struct SrcList *tbl_name, struct ExprList *col_list,
 		 struct Token *start, enum sort_order sort_order,
 		 bool if_not_exist, enum sql_index_type idx_type) {
 	/* The index to be created. */
-	struct Index *index = NULL;
+	struct index *index = NULL;
 	/* Name of the index. */
 	char *name = NULL;
 	struct sqlite3 *db = parse->db;
@@ -2751,10 +2652,10 @@ sql_create_index(struct Parse *parse, struct Token *token,
 	 *    auto-index name will be generated.
 	 */
 	if (token != NULL) {
+		assert(token->z != NULL);
 		name = sqlite3NameFromToken(db, token);
 		if (name == NULL)
 			goto exit_create_index;
-		assert(token->z != NULL);
 		if (sqlite3LocateIndex(db, name, table->def->name) != NULL) {
 			if (!if_not_exist) {
 				sqlite3ErrorMsg(parse,
@@ -2784,23 +2685,18 @@ sql_create_index(struct Parse *parse, struct Token *token,
 		if (idx_type == SQL_INDEX_TYPE_CONSTRAINT_UNIQUE) {
 			prefix = constraint_name == NULL ?
 				"unique_unnamed_%s_%d" : "unique_%s_%d";
-		}
-		else { /* idx_type == SQL_INDEX_TYPE_CONSTRAINT_PK */
+		} else {
 			prefix = constraint_name == NULL ?
 				"pk_unnamed_%s_%d" : "pk_%s_%d";
 		}
-
-		uint32_t n = 1;
-		for (struct Index *idx = table->pIndex; idx != NULL;
-		     idx = idx->pNext, n++);
-
+		uint32_t idx_count = table->space->index_count;
 		if (constraint_name == NULL ||
 		    strcmp(constraint_name, "") == 0) {
 			name = sqlite3MPrintf(db, prefix,
-					      table->def->name, n);
+					      table->def->name, idx_count + 1);
 		} else {
 			name = sqlite3MPrintf(db, prefix,
-					      constraint_name, n);
+					      constraint_name, idx_count + 1);
 		}
 		sqlite3DbFree(db, constraint_name);
 	}
@@ -2842,18 +2738,21 @@ sql_create_index(struct Parse *parse, struct Token *token,
 		sqlite3ExprListCheckLength(parse, col_list, "index");
 	}
 
-	index = sqlite3DbMallocZero(db, sizeof(*index));
-	if (index == NULL)
+	index = (struct index *) calloc(1, sizeof(*index));
+	if (index == NULL) {
+		diag_set(OutOfMemory, sizeof(*index), "calloc", "index");
+		parse->rc = SQL_TARANTOOL_ERROR;
+		parse->nErr++;
 		goto exit_create_index;
+	}
 
-	index->pTable = table;
 	/*
 	 * TODO: Issue a warning if two or more columns of the
 	 * index are identical.
 	 * TODO: Issue a warning if the table primary key is used
 	 * as part of the index key.
 	 */
-	char *sql_stmt = "";
+	char *sql_stmt = NULL;
 	if (!db->init.busy && tbl_name != NULL) {
 		int n = (int) (parse->sLastToken.z - token->z) +
 			parse->sLastToken.n;
@@ -2928,8 +2827,8 @@ sql_create_index(struct Parse *parse, struct Token *token,
 	 * constraint violation), then an error is raised.
 	 */
 	if (table == parse->pNewTable) {
-		for (struct Index *existing_idx = table->pIndex;
-		     existing_idx != NULL; existing_idx = existing_idx->pNext) {
+		for (uint32_t i = 0; i < table->space->index_count; ++i) {
+			struct index *existing_idx = table->space->index[i];
 			struct key_def *key_def = index->def->key_def;
 			struct key_def *exst_key_def =
 				existing_idx->def->key_def;
@@ -2954,7 +2853,7 @@ sql_create_index(struct Parse *parse, struct Token *token,
 				constraint_is_named(existing_idx->def->name);
 			/* CREATE TABLE t(a, UNIQUE(a), PRIMARY KEY(a)). */
 			if (idx_type == SQL_INDEX_TYPE_CONSTRAINT_PK &&
-			    !sql_index_is_primary(existing_idx) && !is_named) {
+			    existing_idx->def->iid != 0 && !is_named) {
 				existing_idx->def->iid = 0;
 				goto exit_create_index;
 			}
@@ -3010,12 +2909,14 @@ sql_create_index(struct Parse *parse, struct Token *token,
 		space_id = table->def->id;
 		index_id = getNewIid(parse, space_id, cursor);
 		sqlite3VdbeAddOp1(vdbe, OP_Close, cursor);
-		createIndex(parse, index, space_id, index_id, sql_stmt);
-
+		struct index *pk = sql_table_primary_key(table);
+		vdbe_emit_create_index(parse, table->def, index->def, pk->def,
+				       space_id);
 		/* Consumes sql_stmt. */
-		first_schema_col = makeIndexSchemaRecord(parse, index,
-							 space_id, index_id,
-							 sql_stmt);
+		first_schema_col =
+			vdbe_emit_index_schema_record(parse, index->def->name,
+						      space_id, index_id,
+						      sql_stmt);
 
 		/*
 		 * Reparse the schema. Code an OP_Expire
@@ -3033,7 +2934,8 @@ sql_create_index(struct Parse *parse, struct Token *token,
 	/* Clean up before exiting. */
  exit_create_index:
 	if (index != NULL)
-		freeIndex(db, index);
+		free(index->def);
+	free(index);
 	sql_expr_list_delete(db, col_list);
 	sqlite3SrcListDelete(db, tbl_name);
 	sqlite3DbFree(db, name);
@@ -3107,10 +3009,9 @@ sql_drop_index(struct Parse *parse_context, struct SrcList *index_name_list,
 	 * Should be removed when SQL and Tarantool
 	 * data-dictionaries will be completely merged.
 	 */
-	Index *pIndex = sqlite3LocateIndex(db, index_name, table_name);
-	assert(pIndex != NULL);
-	sqlite3VdbeAddOp3(v, OP_DropIndex, 0, 0, 0);
-	sqlite3VdbeAppendP4(v, pIndex, P4_INDEX);
+	struct Table *tab = sqlite3HashFind(&db->pSchema->tblHash, table_name);
+	sqlite3VdbeAddOp3(v, OP_DropIndex, index->def->iid, 0, 0);
+	sqlite3VdbeAppendP4(v, tab->space, P4_SPACEPTR);
 
  exit_drop_index:
 	sqlite3SrcListDelete(db, index_name_list);
@@ -3648,160 +3549,6 @@ sqlite3HaltConstraint(Parse * pParse,	/* Parsing context */
 	sqlite3VdbeChangeP5(v, p5Errmsg);
 }
 
-void
-parser_emit_unique_constraint(struct Parse *parser,
-			      enum on_conflict_action on_error,
-			      const struct Index *index)
-{
-	const struct space_def *def = index->pTable->def;
-	StrAccum err_accum;
-	sqlite3StrAccumInit(&err_accum, parser->db, 0, 0, 200);
-	struct key_part *part = index->def->key_def->parts;
-	for (uint32_t j = 0; j < index->def->key_def->part_count; ++j, ++part) {
-		const char *col_name = def->fields[part->fieldno].name;
-		if (j != 0)
-			sqlite3StrAccumAppend(&err_accum, ", ", 2);
-		sqlite3XPrintf(&err_accum, "%s.%s", def->name, col_name);
-	}
-	char *err_msg = sqlite3StrAccumFinish(&err_accum);
-	sqlite3HaltConstraint(parser, sql_index_is_primary(index) ?
-			      SQLITE_CONSTRAINT_PRIMARYKEY :
-			      SQLITE_CONSTRAINT_UNIQUE, on_error, err_msg,
-			      P4_DYNAMIC, P5_ConstraintUnique);
-}
-
-/*
- * Check to see if pIndex uses the collating sequence pColl.  Return
- * true if it does and false if it does not.
- */
-#ifndef SQLITE_OMIT_REINDEX
-static bool
-collationMatch(struct coll *coll, struct Index *index)
-{
-	assert(coll != NULL);
-	struct key_part *part = index->def->key_def->parts;
-	for (uint32_t i = 0; i < index->def->key_def->part_count; i++, part++) {
-		struct coll *idx_coll = part->coll;
-		assert(idx_coll != NULL);
-		if (coll == idx_coll)
-			return true;
-	}
-	return false;
-}
-#endif
-
-/*
- * Recompute all indices of pTab that use the collating sequence pColl.
- * If pColl==0 then recompute all indices of pTab.
- */
-#ifndef SQLITE_OMIT_REINDEX
-static void
-reindexTable(Parse * pParse, Table * pTab, struct coll *coll)
-{
-	Index *pIndex;		/* An index associated with pTab */
-
-	for (pIndex = pTab->pIndex; pIndex; pIndex = pIndex->pNext) {
-		if (coll == 0 || collationMatch(coll, pIndex)) {
-			sql_set_multi_write(pParse, false);
-			sqlite3RefillIndex(pParse, pIndex);
-		}
-	}
-}
-#endif
-
-/*
- * Recompute all indices of all tables in all databases where the
- * indices use the collating sequence pColl.  If pColl==0 then recompute
- * all indices everywhere.
- */
-#ifndef SQLITE_OMIT_REINDEX
-static void
-reindexDatabases(Parse * pParse, struct coll *coll)
-{
-	sqlite3 *db = pParse->db;	/* The database connection */
-	HashElem *k;		/* For looping over tables in pSchema */
-	Table *pTab;		/* A table in the database */
-
-	assert(db->pSchema != NULL);
-	for (k = sqliteHashFirst(&db->pSchema->tblHash); k;
-	     k = sqliteHashNext(k)) {
-		pTab = (Table *) sqliteHashData(k);
-		reindexTable(pParse, pTab, coll);
-	}
-}
-#endif
-
-/*
- * Generate code for the REINDEX command.
- *
- *        REINDEX                             -- 1
- *        REINDEX  <collation>                -- 2
- *        REINDEX  <tablename>                -- 3
- *        REINDEX  <indexname> ON <tablename> -- 4
- *
- * Form 1 causes all indices in all attached databases to be rebuilt.
- * Form 2 rebuilds all indices in all databases that use the named
- * collating function.  Forms 3 and 4 rebuild the named index or all
- * indices associated with the named table.
- */
-#ifndef SQLITE_OMIT_REINDEX
-void
-sqlite3Reindex(Parse * pParse, Token * pName1, Token * pName2)
-{
-	char *z = 0;		/* Name of index */
-	char *zTable = 0;	/* Name of indexed table */
-	Table *pTab;		/* A table in the database */
-	sqlite3 *db = pParse->db;	/* The database connection */
-
-	assert(db->pSchema != NULL);
-
-	if (pName1 == 0) {
-		reindexDatabases(pParse, 0);
-		return;
-	} else if (NEVER(pName2 == 0) || pName2->z == 0) {
-		assert(pName1->z);
-		char *zColl = sqlite3NameFromToken(pParse->db, pName1);
-		if (zColl == NULL)
-			return;
-		if (strcasecmp(zColl, "binary") != 0) {
-			struct coll_id *coll_id =
-				coll_by_name(zColl, strlen(zColl));
-			if (coll_id != NULL) {
-				reindexDatabases(pParse, coll_id->coll);
-				sqlite3DbFree(db, zColl);
-				return;
-			}
-		}
-		sqlite3DbFree(db, zColl);
-	}
-	z = sqlite3NameFromToken(db, pName1);
-	if (z == 0)
-		return;
-	pTab = sqlite3HashFind(&db->pSchema->tblHash, z);
-	if (pTab != NULL) {
-		reindexTable(pParse, pTab, 0);
-		sqlite3DbFree(db, z);
-		return;
-	}
-	if (pName2->n > 0) {
-		zTable = sqlite3NameFromToken(db, pName2);
-	}
-
-	pTab = sqlite3HashFind(&db->pSchema->tblHash, zTable);
-	if (pTab == 0) {
-		sqlite3ErrorMsg(pParse, "no such table: %s", zTable);
-		goto exit_reindex;
-	}
-
-	sqlite3ErrorMsg(pParse,
-			"unable to identify the object to be reindexed");
-
- exit_reindex:
-	sqlite3DbFree(db, z);
-	sqlite3DbFree(db, zTable);
-}
-#endif
-
 #ifndef SQLITE_OMIT_CTE
 /*
  * This routine is invoked once per CTE by the parser while parsing a
diff --git a/src/box/sql/delete.c b/src/box/sql/delete.c
index 93ada3a9e..0f285cc8b 100644
--- a/src/box/sql/delete.c
+++ b/src/box/sql/delete.c
@@ -162,6 +162,7 @@ sql_table_delete_from(struct Parse *parse, struct SrcList *tab_list,
 		memset(&tmp_tab, 0, sizeof(tmp_tab));
 		tmp_tab.def = space->def;
 		/* Prevent from freeing memory in DeleteTable. */
+		tmp_tab.space = space;
 		tmp_tab.nTabRef = 2;
 		tab_list->a[0].pTab = &tmp_tab;
 	} else {
@@ -350,7 +351,7 @@ sql_table_delete_from(struct Parse *parse, struct SrcList *tab_list,
 			 * key.
 			 */
 			key_len = 0;
-			struct Index *pk = sqlite3PrimaryKeyIndex(table);
+			struct index *pk = sql_table_primary_key(table);
 			const char *zAff = is_view ? NULL :
 					   sql_space_index_affinity_str(parse->db,
 									space->def,
@@ -582,14 +583,16 @@ sql_generate_row_delete(struct Parse *parse, struct Table *table,
 }
 
 int
-sql_generate_index_key(struct Parse *parse, struct Index *index, int cursor,
-		       int reg_out, struct Index *prev, int reg_prev)
+sql_generate_index_key(struct Parse *parse, struct index *index, int cursor,
+		       int reg_out, struct index *prev, int reg_prev)
 {
 	struct Vdbe *v = parse->pVdbe;
 	int col_cnt = index->def->key_def->part_count;
 	int reg_base = sqlite3GetTempRange(parse, col_cnt);
 	if (prev != NULL && reg_base != reg_prev)
 		prev = NULL;
+	struct space *space = space_by_id(index->def->space_id);
+	assert(space != NULL);
 	for (int j = 0; j < col_cnt; j++) {
 		if (prev != NULL && prev->def->key_def->parts[j].fieldno ==
 				    index->def->key_def->parts[j].fieldno) {
@@ -599,8 +602,9 @@ sql_generate_index_key(struct Parse *parse, struct Index *index, int cursor,
 			 */
 			continue;
 		}
-		sqlite3ExprCodeLoadIndexColumn(parse, index, cursor, j,
-					       reg_base + j);
+		uint32_t tabl_col = index->def->key_def->parts[j].fieldno;
+		sqlite3ExprCodeGetColumnOfTable(v, space->def, cursor, tabl_col,
+						reg_base + j);
 		/*
 		 * If the column affinity is REAL but the number
 		 * is an integer, then it might be stored in the
diff --git a/src/box/sql/expr.c b/src/box/sql/expr.c
index a41ea8f13..cb4f89e35 100644
--- a/src/box/sql/expr.c
+++ b/src/box/sql/expr.c
@@ -2364,7 +2364,6 @@ sqlite3FindInIndex(Parse * pParse,	/* Parsing context */
 		pTab = p->pSrc->a[0].pTab;
 		assert(v);	/* sqlite3GetVdbe() has always been previously called */
 
-		Index *pIdx;	/* Iterator variable */
 		int affinity_ok = 1;
 		int i;
 
@@ -2399,15 +2398,21 @@ sqlite3FindInIndex(Parse * pParse,	/* Parsing context */
 		}
 
 		if (affinity_ok) {
+			/*
+			 * Here we need real space since further
+			 * it is used in cursor opening routine.
+			 */
+			struct space *space = space_by_id(pTab->def->id);
 			/* Search for an existing index that will work for this IN operator */
-			for (pIdx = pTab->pIndex; pIdx && eType == 0;
-			     pIdx = pIdx->pNext) {
+			for (uint32_t k = 0; k < space->index_count &&
+			     eType == 0; ++k) {
+				struct index *idx = space->index[k];
 				Bitmask colUsed; /* Columns of the index used */
 				Bitmask mCol;	/* Mask for the current column */
 				uint32_t part_count =
-					pIdx->def->key_def->part_count;
+					idx->def->key_def->part_count;
 				struct key_part *parts =
-					pIdx->def->key_def->parts;
+					idx->def->key_def->parts;
 				if ((int)part_count < nExpr)
 					continue;
 				/* Maximum nColumn is BMS-2, not BMS-1, so that we can compute
@@ -2419,7 +2424,7 @@ sqlite3FindInIndex(Parse * pParse,	/* Parsing context */
 					continue;
 				if (mustBeUnique &&
 				    ((int)part_count > nExpr ||
-				     !pIdx->def->opts.is_unique)) {
+				     !idx->def->opts.is_unique)) {
 					/*
 					 * This index is not
 					 * unique over the IN RHS
@@ -2471,14 +2476,12 @@ sqlite3FindInIndex(Parse * pParse,	/* Parsing context */
 							  0, 0, 0,
 							  sqlite3MPrintf(db,
 							  "USING INDEX %s FOR IN-OPERATOR",
-							  pIdx->def->name),
+							  idx->def->name),
 							  P4_DYNAMIC);
-					struct space *space =
-						space_by_id(pIdx->pTable->def->id);
-					uint32_t idx_id = pIdx->def->iid;
 					vdbe_emit_open_cursor(pParse, iTab,
-							      idx_id, space);
-					VdbeComment((v, "%s", pIdx->def->name));
+							      idx->def->iid,
+							      space);
+					VdbeComment((v, "%s", idx->def->name));
 					assert(IN_INDEX_INDEX_DESC ==
 					       IN_INDEX_INDEX_ASC + 1);
 					eType = IN_INDEX_INDEX_ASC +
@@ -3138,9 +3141,8 @@ sqlite3ExprCodeIN(Parse * pParse,	/* Parsing and code generating context */
 
 		struct Table *tab = src_list->a[0].pTab;
 		assert(tab != NULL);
-
-		struct Index *pk = sqlite3PrimaryKeyIndex(tab);
-		assert(pk);
+		struct index *pk = sql_table_primary_key(tab);
+		assert(pk != NULL);
 
 		uint32_t fieldno = pk->def->key_def->parts[0].fieldno;
 		enum affinity_type affinity =
@@ -3465,22 +3467,6 @@ sqlite3ExprCachePinRegister(Parse * pParse, int iReg)
 	}
 }
 
-/* Generate code that will load into register regOut a value that is
- * appropriate for the iIdxCol-th column of index pIdx.
- */
-void
-sqlite3ExprCodeLoadIndexColumn(Parse * pParse,	/* The parsing context */
-			       Index * pIdx,	/* The index whose column is to be loaded */
-			       int iTabCur,	/* Cursor pointing to a table row */
-			       int iIdxCol,	/* The column of the index to be loaded */
-			       int regOut	/* Store the index column value in this register */
-    )
-{
-	i16 iTabCol = pIdx->def->key_def->parts[iIdxCol].fieldno;
-	sqlite3ExprCodeGetColumnOfTable(pParse->pVdbe, pIdx->pTable->def,
-					iTabCur, iTabCol, regOut);
-}
-
 void
 sqlite3ExprCodeGetColumnOfTable(Vdbe *v, struct space_def *space_def,
 				int iTabCur, int iCol, int regOut)
@@ -4985,22 +4971,6 @@ sqlite3ExprIfFalse(Parse * pParse, Expr * pExpr, int dest, int jumpIfNull)
 	sqlite3ReleaseTempReg(pParse, regFree2);
 }
 
-/*
- * Like sqlite3ExprIfFalse() except that a copy is made of pExpr before
- * code generation, and that copy is deleted after code generation. This
- * ensures that the original pExpr is unchanged.
- */
-void
-sqlite3ExprIfFalseDup(Parse * pParse, Expr * pExpr, int dest, int jumpIfNull)
-{
-	sqlite3 *db = pParse->db;
-	Expr *pCopy = sqlite3ExprDup(db, pExpr, 0);
-	if (db->mallocFailed == 0) {
-		sqlite3ExprIfFalse(pParse, pCopy, dest, jumpIfNull);
-	}
-	sql_expr_delete(db, pCopy, false);
-}
-
 /*
  * Do a deep comparison of two expression trees.  Return 0 if the two
  * expressions are completely identical.  Return 1 if they differ only
@@ -5157,62 +5127,6 @@ sqlite3ExprImpliesExpr(Expr * pE1, Expr * pE2, int iTab)
 	return 0;
 }
 
-/*
- * An instance of the following structure is used by the tree walker
- * to determine if an expression can be evaluated by reference to the
- * index only, without having to do a search for the corresponding
- * table entry.  The IdxCover.pIdx field is the index.  IdxCover.iCur
- * is the cursor for the table.
- */
-struct IdxCover {
-	Index *pIdx;		/* The index to be tested for coverage */
-	int iCur;		/* Cursor number for the table corresponding to the index */
-};
-
-/*
- * Check to see if there are references to columns in table
- * pWalker->u.pIdxCover->iCur can be satisfied using the index
- * pWalker->u.pIdxCover->pIdx.
- */
-static int
-exprIdxCover(Walker * pWalker, Expr * pExpr)
-{
-	if (pExpr->op == TK_COLUMN
-	    && pExpr->iTable == pWalker->u.pIdxCover->iCur
-	    && pExpr->iColumn < 0) {
-		pWalker->eCode = 1;
-		return WRC_Abort;
-	}
-	return WRC_Continue;
-}
-
-/*
- * Determine if an index pIdx on table with cursor iCur contains will
- * the expression pExpr.  Return true if the index does cover the
- * expression and false if the pExpr expression references table columns
- * that are not found in the index pIdx.
- *
- * An index covering an expression means that the expression can be
- * evaluated using only the index and without having to lookup the
- * corresponding table entry.
- */
-int
-sqlite3ExprCoveredByIndex(Expr * pExpr,	/* The index to be tested */
-			  int iCur,	/* The cursor number for the corresponding table */
-			  Index * pIdx	/* The index that might be used for coverage */
-    )
-{
-	Walker w;
-	struct IdxCover xcov;
-	memset(&w, 0, sizeof(w));
-	xcov.iCur = iCur;
-	xcov.pIdx = pIdx;
-	w.xExprCallback = exprIdxCover;
-	w.u.pIdxCover = &xcov;
-	sqlite3WalkExpr(&w, pExpr);
-	return !w.eCode;
-}
-
 /*
  * An instance of the following structure is used by the tree walker
  * to count references to table columns in the arguments of an
diff --git a/src/box/sql/insert.c b/src/box/sql/insert.c
index 7780bf749..9220d34e1 100644
--- a/src/box/sql/insert.c
+++ b/src/box/sql/insert.c
@@ -960,7 +960,7 @@ vdbe_emit_constraint_checks(struct Parse *parse_context, struct Table *tab,
 	 * FIXME: should be removed after introducing
 	 * strict typing.
 	 */
-	struct Index *pk = sqlite3PrimaryKeyIndex(tab);
+	struct index *pk = sql_table_primary_key(tab);
 	uint32_t part_count = pk->def->key_def->part_count;
 	if (part_count == 1) {
 		uint32_t fieldno = pk->def->key_def->parts[0].fieldno;
@@ -984,7 +984,8 @@ vdbe_emit_constraint_checks(struct Parse *parse_context, struct Table *tab,
 		return;
 	/* Calculate MAX range of register we may occupy. */
 	uint32_t reg_count = 0;
-	for (struct Index *idx = tab->pIndex; idx != NULL; idx = idx->pNext) {
+	for (uint32_t i = 0; i < tab->space->index_count; ++i) {
+		struct index *idx = tab->space->index[i];
 		if (idx->def->key_def->part_count > reg_count)
 			reg_count = idx->def->key_def->part_count;
 	}
@@ -1000,9 +1001,10 @@ vdbe_emit_constraint_checks(struct Parse *parse_context, struct Table *tab,
 	 * Otherwise, we should skip removal of old entry and
 	 * insertion of new one.
 	 */
-	for (struct Index *idx = tab->pIndex; idx != NULL; idx = idx->pNext) {
+	for (uint32_t i = 0; i < tab->space->index_count; ++i) {
+		struct index *idx = tab->space->index[i];
 		/* Conflicts may occur only in UNIQUE indexes. */
-		if (!idx->def->opts.is_unique)
+		if (! idx->def->opts.is_unique)
 			continue;
 		if (on_conflict == ON_CONFLICT_ACTION_IGNORE) {
 			/*
@@ -1091,40 +1093,42 @@ int sqlite3_xferopt_count;
 #endif				/* SQLITE_TEST */
 
 #ifndef SQLITE_OMIT_XFER_OPT
-/*
- * Check to see if index pSrc is compatible as a source of data
- * for index pDest in an insert transfer optimization.  The rules
+/**
+ * Check to see if index @src is compatible as a source of data
+ * for index @dest in an insert transfer optimization. The rules
  * for a compatible index:
  *
- *    *   The index is over the same set of columns
- *    *   The same DESC and ASC markings occurs on all columns
- *    *   The same onError processing (ON_CONFLICT_ACTION_ABORT, _IGNORE, etc)
- *    *   The same collating sequence on each column
- *    *   The index has the exact same WHERE clause
+ * - The index is over the same set of columns;
+ * - The same DESC and ASC markings occurs on all columns;
+ * - The same collating sequence on each column.
+ *
+ * @param dest Index of destination space.
+ * @param src Index of source space.
+ *
+ * @retval
  */
-static int
-xferCompatibleIndex(Index * pDest, Index * pSrc)
+static bool
+sql_index_is_xfer_compatible(const struct index_def *dest,
+			     const struct index_def *src)
 {
-	assert(pDest && pSrc);
-	assert(pDest->pTable != pSrc->pTable);
-	uint32_t dest_idx_part_count = pDest->def->key_def->part_count;
-	uint32_t src_idx_part_count = pSrc->def->key_def->part_count;
+	assert(dest != NULL && src != NULL);
+	assert(dest->space_id != src->space_id);
+	uint32_t dest_idx_part_count = dest->key_def->part_count;
+	uint32_t src_idx_part_count = src->key_def->part_count;
 	if (dest_idx_part_count != src_idx_part_count)
-		return 0;
-	struct key_part *src_part = pSrc->def->key_def->parts;
-	struct key_part *dest_part = pDest->def->key_def->parts;
+		return false;
+	struct key_part *src_part = src->key_def->parts;
+	struct key_part *dest_part = dest->key_def->parts;
 	for (uint32_t i = 0; i < src_idx_part_count;
 	     ++i, ++src_part, ++dest_part) {
 		if (src_part->fieldno != dest_part->fieldno)
-			return 0;	/* Different columns indexed */
+			return false;
 		if (src_part->sort_order != dest_part->sort_order)
-			return 0;	/* Different sort orders */
+			return false;
 		if (src_part->coll != dest_part->coll)
-			return 0;	/* Different collating sequences */
+			return false;
 	}
-
-	/* If no test above fails then the indices must be compatible */
-	return 1;
+	return true;
 }
 
 /*
@@ -1158,7 +1162,7 @@ xferOptimization(Parse * pParse,	/* Parser context */
 {
 	ExprList *pEList;	/* The result set of the SELECT */
 	Table *pSrc;		/* The table in the FROM clause of SELECT */
-	Index *pSrcIdx, *pDestIdx;	/* Source and destination indices */
+	struct index *pSrcIdx, *pDestIdx;
 	struct SrcList_item *pItem;	/* An element of pSelect->pSrc */
 	int i;			/* Loop counter */
 	int iSrc, iDest;	/* Cursors from source and destination */
@@ -1262,17 +1266,10 @@ xferOptimization(Parse * pParse,	/* Parser context */
 		}
 		/* Default values for second and subsequent columns need to match. */
 		if (i > 0) {
-			uint32_t src_space_id = pSrc->def->id;
-			struct space *src_space =
-				space_cache_find(src_space_id);
-			uint32_t dest_space_id = pDest->def->id;
-			struct space *dest_space =
-				space_cache_find(dest_space_id);
-			assert(src_space != NULL && dest_space != NULL);
 			char *src_expr_str =
-				src_space->def->fields[i].default_value;
+				pSrc->def->fields[i].default_value;
 			char *dest_expr_str =
-				dest_space->def->fields[i].default_value;
+				pDest->def->fields[i].default_value;
 			if ((dest_expr_str == NULL) != (src_expr_str == NULL) ||
 			    (dest_expr_str &&
 			     strcmp(src_expr_str, dest_expr_str) != 0)
@@ -1281,11 +1278,14 @@ xferOptimization(Parse * pParse,	/* Parser context */
 			}
 		}
 	}
-	for (pDestIdx = pDest->pIndex; pDestIdx; pDestIdx = pDestIdx->pNext) {
+	for (uint32_t i = 0; i < pDest->space->index_count; ++i) {
+		pDestIdx = pDest->space->index[i];
 		if (pDestIdx->def->opts.is_unique)
 			destHasUniqueIdx = 1;
-		for (pSrcIdx = pSrc->pIndex; pSrcIdx; pSrcIdx = pSrcIdx->pNext) {
-			if (xferCompatibleIndex(pDestIdx, pSrcIdx))
+		for (uint32_t j = 0; j < pSrc->space->index_count; ++j) {
+			pSrcIdx = pSrc->space->index[j];
+			if (sql_index_is_xfer_compatible(pDestIdx->def,
+							 pSrcIdx->def))
 				break;
 		}
 		/* pDestIdx has no corresponding index in pSrc. */
@@ -1328,8 +1328,7 @@ xferOptimization(Parse * pParse,	/* Parser context */
 	regTupleid = sqlite3GetTempReg(pParse);
 	sqlite3OpenTable(pParse, iDest, pDest, OP_OpenWrite);
 	assert(destHasUniqueIdx);
-	if ((pDest->pIndex != 0)	/* (1) */
-	    ||destHasUniqueIdx	/* (2) */
+	if (destHasUniqueIdx	/* (2) */
 	    || (onError != ON_CONFLICT_ACTION_ABORT
 		&& onError != ON_CONFLICT_ACTION_ROLLBACK)	/* (3) */
 	    ) {
@@ -1353,12 +1352,6 @@ xferOptimization(Parse * pParse,	/* Parser context */
 		sqlite3VdbeJumpHere(v, addr1);
 	}
 
-	for (pDestIdx = pDest->pIndex; pDestIdx; pDestIdx = pDestIdx->pNext) {
-		for (pSrcIdx = pSrc->pIndex; ALWAYS(pSrcIdx);
-		     pSrcIdx = pSrcIdx->pNext) {
-			if (xferCompatibleIndex(pDestIdx, pSrcIdx))
-				break;
-		}
 		assert(pSrcIdx);
 		struct space *src_space =
 			space_by_id(pSrc->def->id);
@@ -1375,14 +1368,13 @@ xferOptimization(Parse * pParse,	/* Parser context */
 		VdbeCoverage(v);
 		sqlite3VdbeAddOp2(v, OP_RowData, iSrc, regData);
 		sqlite3VdbeAddOp2(v, OP_IdxInsert, iDest, regData);
-		if (sql_index_is_primary(pDestIdx))
+		if (pDestIdx->def->iid == 0)
 			sqlite3VdbeChangeP5(v, OPFLAG_NCHANGE);
 		sqlite3VdbeAddOp2(v, OP_Next, iSrc, addr1 + 1);
 		VdbeCoverage(v);
 		sqlite3VdbeJumpHere(v, addr1);
 		sqlite3VdbeAddOp2(v, OP_Close, iSrc, 0);
 		sqlite3VdbeAddOp2(v, OP_Close, iDest, 0);
-	}
 	if (emptySrcTest)
 		sqlite3VdbeJumpHere(v, emptySrcTest);
 	sqlite3ReleaseTempReg(pParse, regTupleid);
diff --git a/src/box/sql/parse.y b/src/box/sql/parse.y
index d8532d378..60c0a8eed 100644
--- a/src/box/sql/parse.y
+++ b/src/box/sql/parse.y
@@ -1426,14 +1426,6 @@ cmd ::= DROP TRIGGER ifexists(NOERR) fullname(X). {
   sql_drop_trigger(pParse,X,NOERR);
 }
 
-////////////////////////// REINDEX collation //////////////////////////////////
-/* gh-2174: Commended until REINDEX is implemented in scope of gh-3195 */
-/* %ifndef SQLITE_OMIT_REINDEX */
-/* cmd ::= REINDEX.                {sqlite3Reindex(pParse, 0, 0);} */
-/* cmd ::= REINDEX nm(X).          {sqlite3Reindex(pParse, &X, 0);} */
-/* cmd ::= REINDEX nm(X) ON nm(Y). {sqlite3Reindex(pParse, &X, &Y);} */
-/* %endif  SQLITE_OMIT_REINDEX */
-
 /////////////////////////////////// ANALYZE ///////////////////////////////////
 cmd ::= ANALYZE.                {sqlite3Analyze(pParse, 0);}
 cmd ::= ANALYZE nm(X).          {sqlite3Analyze(pParse, &X);}
diff --git a/src/box/sql/pragma.c b/src/box/sql/pragma.c
index 822db69ba..6173462ff 100644
--- a/src/box/sql/pragma.c
+++ b/src/box/sql/pragma.c
@@ -245,6 +245,89 @@ sql_default_engine_set(const char *engine_name)
 	return 0;
 }
 
+/**
+ * This function handles PRAGMA INDEX_INFO and PRAGMA INDEX_XINFO
+ * statements.
+ *
+ * @param parse Current parsing content.
+ * @param pragma Definition of index_info pragma.
+ * @param table_name Name of table index belongs to.
+ * @param idx_name Name of index to display info about.
+ */
+static void
+sql_pragma_index_info(struct Parse *parse, const PragmaName *pragma,
+		      const char *tbl_name, const char *idx_name)
+{
+	if (idx_name == NULL || tbl_name == NULL)
+		return;
+	uint32_t space_id = box_space_id_by_name(tbl_name, strlen(tbl_name));
+	if (space_id == BOX_ID_NIL)
+		return;
+	struct space *space = space_by_id(space_id);
+	assert(space != NULL);
+	if (space->def->opts.sql == NULL)
+		return;
+	uint32_t iid = box_index_id_by_name(space_id, idx_name,
+					     strlen(idx_name));
+	if (iid == BOX_ID_NIL)
+		return;
+	struct index *idx = space_index(space, iid);
+	assert(idx != NULL);
+	/* PRAGMA index_xinfo (more informative version). */
+	if (pragma->iArg > 0) {
+		parse->nMem = 6;
+	} else {
+		/* PRAGMA index_info ... */
+		parse->nMem = 3;
+	}
+	struct Vdbe *v = sqlite3GetVdbe(parse);
+	assert(v != NULL);
+	uint32_t part_count = idx->def->key_def->part_count;
+	assert(parse->nMem <= pragma->nPragCName);
+	struct key_part *part = idx->def->key_def->parts;
+	for (uint32_t i = 0; i < part_count; i++, part++) {
+		sqlite3VdbeMultiLoad(v, 1, "iis", i, part->fieldno,
+				     space->def->fields[part->fieldno].name);
+		if (pragma->iArg > 0) {
+			const char *c_n;
+			uint32_t id = part->coll_id;
+			struct coll *coll = part->coll;
+			if (coll != NULL)
+				c_n = coll_by_id(id)->name;
+			else
+				c_n = "BINARY";
+			sqlite3VdbeMultiLoad(v, 4, "isi", part->sort_order,
+					     c_n, i < part_count);
+		}
+		sqlite3VdbeAddOp2(v, OP_ResultRow, 1, parse->nMem);
+	}
+}
+
+/**
+ * This function handles PRAGMA INDEX_LIST statement.
+ *
+ * @param parse Current parsing content.
+ * @param table_name Name of table to display list of indexes.
+ */
+void
+sql_pragma_index_list(struct Parse *parse, const char *tbl_name)
+{
+	if (tbl_name == NULL)
+		return;
+	uint32_t space_id = box_space_id_by_name(tbl_name, strlen(tbl_name));
+	if (space_id == BOX_ID_NIL)
+		return;
+	struct space *space = space_by_id(space_id);
+	assert(space != NULL);
+	parse->nMem = 5;
+	struct Vdbe *v = sqlite3GetVdbe(parse);
+	for (uint32_t i = 0; i < space->index_count; ++i) {
+		struct index *idx = space->index[i];
+		sqlite3VdbeMultiLoad(v, 1, "isisi", i, idx->def->name,
+				     idx->def->opts.is_unique);
+		sqlite3VdbeAddOp2(v, OP_ResultRow, 1, 5);
+	}
+}
 
 /*
  * Process a pragma statement.
@@ -368,7 +451,7 @@ sqlite3Pragma(Parse * pParse, Token * pId,	/* First part of [schema.]id field */
 			break;
 		struct space *space = space_cache_find(table->def->id);
 		struct space_def *def = space->def;
-		struct Index *pk = sqlite3PrimaryKeyIndex(table);
+		struct index *pk = sql_table_primary_key(table);
 		pParse->nMem = 6;
 		if (def->opts.is_view) {
 			const char *sql = table->def->opts.sql;
@@ -395,7 +478,6 @@ sqlite3Pragma(Parse * pParse, Token * pId,	/* First part of [schema.]id field */
 		break;
 
 	case PragTyp_STATS:{
-			Index *pIdx;
 			HashElem *i;
 			pParse->nMem = 4;
 			for (i = sqliteHashFirst(&db->pSchema->tblHash); i;
@@ -412,17 +494,15 @@ sqlite3Pragma(Parse * pParse, Token * pId,	/* First part of [schema.]id field */
 						     avg_tuple_size_pk,
 						     sql_space_tuple_log_count(pTab));
 				sqlite3VdbeAddOp2(v, OP_ResultRow, 1, 4);
-				for (pIdx = pTab->pIndex; pIdx;
-				     pIdx = pIdx->pNext) {
-					struct index *idx =
-						space_index(space, pIdx->def->iid);
+				for (uint32_t i = 0; i < pTab->space->index_count; ++i) {
+					struct index *idx = pTab->space->index[i];
 					assert(idx != NULL);
 					size_t avg_tuple_size_idx =
 						sql_index_tuple_size(space, idx);
 					sqlite3VdbeMultiLoad(v, 2, "sii",
-							     pIdx->def->name,
+							     idx->def->name,
 							     avg_tuple_size_idx,
-							     index_field_tuple_est(pIdx, 0));
+							     index_field_tuple_est(idx->def, 0));
 					sqlite3VdbeAddOp2(v, OP_ResultRow, 1,
 							  4);
 				}
@@ -431,93 +511,13 @@ sqlite3Pragma(Parse * pParse, Token * pId,	/* First part of [schema.]id field */
 		}
 
 	case PragTyp_INDEX_INFO:{
-			if (zRight && zTable) {
-				Index *pIdx;
-				pIdx = sqlite3LocateIndex(db, zRight, zTable);
-				if (pIdx) {
-					int i;
-					int mx;
-					if (pPragma->iArg) {
-						/* PRAGMA index_xinfo (newer
-						 * version with more rows and
-						 * columns)
-						 */
-						pParse->nMem = 6;
-					} else {
-						/* PRAGMA index_info (legacy
-						 * version)
-						 */
-						pParse->nMem = 3;
-					}
-					mx = pIdx->def->key_def->part_count;
-					assert(pParse->nMem <=
-					       pPragma->nPragCName);
-					struct key_part *part =
-						pIdx->def->key_def->parts;
-					for (i = 0; i < mx; i++, part++) {
-						i16 cnum = (int) part->fieldno;
-						assert(pIdx->pTable);
-						sqlite3VdbeMultiLoad(v, 1,
-								     "iis", i,
-								     cnum,
-								     cnum <
-								     0 ? 0 :
-								     pIdx->
-								     pTable->
-								     def->
-								     fields[cnum].
-								     name);
-						if (pPragma->iArg) {
-							const char *c_n;
-							uint32_t id =
-								part->coll_id;
-							struct coll *coll =
-								part->coll;
-							if (coll != NULL)
-								c_n = coll_by_id(id)->name;
-							else
-								c_n = "BINARY";
-							sqlite3VdbeMultiLoad(v,
-									     4,
-									     "isi",
-									     part->
-									     sort_order,
-									     c_n,
-									     i <
-									     mx);
-						}
-						sqlite3VdbeAddOp2(v,
-								  OP_ResultRow,
-								  1,
-								  pParse->nMem);
-					}
-				}
-			}
-			break;
-		}
+		sql_pragma_index_info(pParse, pPragma, zTable, zRight);
+		break;
+	}
 	case PragTyp_INDEX_LIST:{
-			if (zRight) {
-				Index *pIdx;
-				Table *pTab;
-				int i;
-				pTab = sqlite3HashFind(&db->pSchema->tblHash,
-						       zRight);
-				if (pTab != NULL) {
-					pParse->nMem = 5;
-					for (pIdx = pTab->pIndex, i = 0; pIdx;
-					     pIdx = pIdx->pNext, i++) {
-						sqlite3VdbeMultiLoad(v, 1,
-								     "isisi", i,
-								     pIdx->def->name,
-								     pIdx->def->opts.is_unique);
-						sqlite3VdbeAddOp2(v,
-								  OP_ResultRow,
-								  1, 5);
-					}
-				}
-			}
-			break;
-		}
+		sql_pragma_index_list(pParse, zRight);
+		break;
+	}
 
 	case PragTyp_COLLATION_LIST:{
 		int i = 0;
diff --git a/src/box/sql/prepare.c b/src/box/sql/prepare.c
index e8b8e94ae..bea9dc583 100644
--- a/src/box/sql/prepare.c
+++ b/src/box/sql/prepare.c
@@ -116,12 +116,11 @@ sql_init_callback(struct init_data *init, const char *name,
 		 * been created when we processed the CREATE TABLE.  All we have
 		 * to do here is record the root page number for that index.
 		 */
-		Index *pIndex;
 		struct space *space = space_by_id(space_id);
 		const char *zSpace = space_name(space);
-		pIndex = sqlite3LocateIndex(db, name, zSpace);
-		assert(pIndex != NULL);
-		pIndex->def->iid = index_id;
+		struct index *idx = sqlite3LocateIndex(db, name, zSpace);
+		assert(idx != NULL);
+		idx->def->iid = index_id;
 	}
 	return 0;
 }
diff --git a/src/box/sql/select.c b/src/box/sql/select.c
index 12f55cedf..dd6a8f1b0 100644
--- a/src/box/sql/select.c
+++ b/src/box/sql/select.c
@@ -4378,16 +4378,20 @@ sqlite3IndexedByLookup(Parse * pParse, struct SrcList_item *pFrom)
 	if (pFrom->pTab && pFrom->fg.isIndexedBy) {
 		Table *pTab = pFrom->pTab;
 		char *zIndexedBy = pFrom->u1.zIndexedBy;
-		Index *pIdx;
-		for (pIdx = pTab->pIndex;
-		     pIdx && strcmp(pIdx->def->name, zIndexedBy);
-		     pIdx = pIdx->pNext) ;
-		if (!pIdx) {
+		struct index *idx = NULL;
+		for (uint32_t i = 0; i < pTab->space->index_count; ++i) {
+			if (strcmp(pTab->space->index[i]->def->name,
+				   zIndexedBy) == 0) {
+				idx = pTab->space->index[i];
+				break;
+			}
+		}
+		if (idx == NULL) {
 			sqlite3ErrorMsg(pParse, "no such index: %s", zIndexedBy,
 					0);
 			return SQLITE_ERROR;
 		}
-		pFrom->pIBIndex = pIdx;
+		pFrom->pIBIndex = idx->def;
 	}
 	return SQLITE_OK;
 }
@@ -4833,6 +4837,7 @@ selectExpander(Walker * pWalker, Select * p)
 					return WRC_Abort;
 				tab->nTabRef = 1;
 				tab->def = space_def_dup(space->def);
+				tab->space = space;
 				pFrom->pTab = pTab = tab;
 			} else {
 				if (pTab->nTabRef >= 0xffff) {
diff --git a/src/box/sql/sqliteInt.h b/src/box/sql/sqliteInt.h
index 35d3f4cec..96e2fff9f 100644
--- a/src/box/sql/sqliteInt.h
+++ b/src/box/sql/sqliteInt.h
@@ -1436,7 +1436,6 @@ typedef struct FuncDestructor FuncDestructor;
 typedef struct FuncDef FuncDef;
 typedef struct FuncDefHash FuncDefHash;
 typedef struct IdList IdList;
-typedef struct Index Index;
 typedef struct KeyClass KeyClass;
 typedef struct Lookaside Lookaside;
 typedef struct LookasideSlot LookasideSlot;
@@ -1848,7 +1847,6 @@ struct Savepoint {
  * by an instance of the following structure.
  */
 struct Table {
-	Index *pIndex;		/* List of SQL indexes on this table. */
 	u32 nTabRef;		/* Number of pointers to this Table */
 	/**
 	 * Estimated number of entries in table.
@@ -1860,6 +1858,8 @@ struct Table {
 	Table *pNextZombie;	/* Next on the Parse.pZombieTab list */
 	/** Space definition with Tarantool metadata. */
 	struct space_def *def;
+	/** Surrogate space containing array of indexes. */
+	struct space *space;
 };
 
 /**
@@ -1959,28 +1959,6 @@ enum sql_index_type {
     SQL_INDEX_TYPE_CONSTRAINT_PK,
 };
 
-/** Simple wrapper to test index id on zero. */
-bool
-sql_index_is_primary(const struct Index *idx);
-
-/*
- * Each SQL index is represented in memory by an
- * instance of the following structure.
- *
- * While parsing a CREATE TABLE or CREATE INDEX statement in order to
- * generate VDBE code (as opposed to reading from Tarantool's _space
- * space as part of parsing an existing database schema), transient instances
- * of this structure may be created.
- */
-struct Index {
-	/** The SQL table being indexed. */
-	Table *pTable;
-	/** The next index associated with the same table. */
-	Index *pNext;
-	/** Index definition. */
-	struct index_def *def;
-};
-
 /**
  * Fetch statistics concerning tuples to be selected:
  * logarithm of number of tuples which has the same key as for
@@ -1996,12 +1974,12 @@ struct Index {
  * If there is no appropriate Tarantool's index,
  * return one of default values.
  *
- * @param idx Index.
+ * @param idx Index definition.
  * @param field Number of field to be examined.
  * @retval Estimate logarithm of tuples selected by given field.
  */
 log_est_t
-index_field_tuple_est(struct Index *idx, uint32_t field);
+index_field_tuple_est(const struct index_def *idx, uint32_t field);
 
 #ifdef DEFAULT_TUPLE_COUNT
 #undef DEFAULT_TUPLE_COUNT
@@ -2403,7 +2381,7 @@ struct SrcList {
 			char *zIndexedBy;	/* Identifier from "INDEXED BY <zIndex>" clause */
 			ExprList *pFuncArg;	/* Arguments to table-valued-function */
 		} u1;
-		Index *pIBIndex;	/* Index structure corresponding to u1.zIndexedBy */
+		struct index_def *pIBIndex;
 	} a[1];			/* One entry for each identifier on the list */
 };
 
@@ -2772,7 +2750,6 @@ struct Parse {
 	int *aLabel;		/* Space to hold the labels */
 	ExprList *pConstExpr;	/* Constant expressions */
 	Token constraintName;	/* Name of the constraint currently being parsed */
-	int regRoot;		/* Register holding root page number for new objects */
 	int nMaxArg;		/* Max args passed to user function by sub-program */
 	int nSelect;		/* Number of SELECT statements seen */
 	int nSelectIndent;	/* How far to indent SELECTTRACE() output */
@@ -3397,7 +3374,13 @@ int sqlite3ColumnsFromExprList(Parse *parse, ExprList *expr_list, Table *table);
 
 void sqlite3SelectAddColumnTypeAndCollation(Parse *, Table *, Select *);
 Table *sqlite3ResultSetOfSelect(Parse *, Select *);
-Index *sqlite3PrimaryKeyIndex(Table *);
+
+/**
+ * Return the PRIMARY KEY index of a table.
+ */
+struct index *
+sql_table_primary_key(const struct Table *tab);
+
 void sqlite3StartTable(Parse *, Token *, int);
 void sqlite3AddColumn(Parse *, Token *, Token *);
 
@@ -3635,7 +3618,6 @@ int sqlite3WhereOkOnePass(WhereInfo *, int *);
 #define ONEPASS_OFF      0	/* Use of ONEPASS not allowed */
 #define ONEPASS_SINGLE   1	/* ONEPASS valid for a single row update */
 #define ONEPASS_MULTI    2	/* ONEPASS is valid for multiple rows */
-void sqlite3ExprCodeLoadIndexColumn(Parse *, Index *, int, int, int);
 
 /**
  * Generate code that will extract the iColumn-th column from
@@ -3705,16 +3687,29 @@ void sqlite3ExprIfFalseDup(Parse *, Expr *, int, int);
 #define LOCATE_VIEW    0x01
 #define LOCATE_NOERR   0x02
 Table *sqlite3LocateTable(Parse *, u32 flags, const char *);
-Index *sqlite3LocateIndex(sqlite3 *, const char *, const char *);
+
+struct index *
+sqlite3LocateIndex(sqlite3 *, const char *, const char *);
 void sqlite3UnlinkAndDeleteTable(sqlite3 *, const char *);
-void sqlite3UnlinkAndDeleteIndex(sqlite3 *, Index *);
+
+/**
+ * Release memory for index with given iid and
+ * reallocate memory for an array of indexes.
+ * FIXME: should be removed after finishing merging SQLite DD
+ * with server one.
+ *
+ * @param space Space which index belongs to.
+ * @param iid Id of index to be deleted.
+ */
+void
+sql_space_index_delete(struct space *space, uint32_t iid);
+
 char *sqlite3NameFromToken(sqlite3 *, Token *);
 int sqlite3ExprCompare(Expr *, Expr *, int);
 int sqlite3ExprListCompare(ExprList *, ExprList *, int);
 int sqlite3ExprImpliesExpr(Expr *, Expr *, int);
 void sqlite3ExprAnalyzeAggregates(NameContext *, Expr *);
 void sqlite3ExprAnalyzeAggList(NameContext *, ExprList *);
-int sqlite3ExprCoveredByIndex(Expr *, int iCur, Index * pIdx);
 int sqlite3FunctionUsesThisSrc(Expr *, SrcList *);
 Vdbe *sqlite3GetVdbe(Parse *);
 #ifndef SQLITE_UNTESTABLE
@@ -3844,8 +3839,8 @@ sql_generate_row_delete(struct Parse *parse, struct Table *table,
  * this routine returns.
  */
 int
-sql_generate_index_key(struct Parse *parse, struct Index *index, int cursor,
-		       int reg_out, struct Index *prev, int reg_prev);
+sql_generate_index_key(struct Parse *parse, struct index *index, int cursor,
+		       int reg_out, struct index *prev, int reg_prev);
 
 /**
  * Generate code to do constraint checks prior to an INSERT or
@@ -3935,17 +3930,7 @@ void
 sql_set_multi_write(Parse *, bool);
 void sqlite3MayAbort(Parse *);
 void sqlite3HaltConstraint(Parse *, int, int, char *, i8, u8);
-/**
- * Code an OP_Halt due to UNIQUE or PRIMARY KEY constraint
- * violation.
- * @param parser SQL parser.
- * @param on_error Constraint type.
- * @param index Index triggered the constraint.
- */
-void
-parser_emit_unique_constraint(struct Parse *parser,
-			      enum on_conflict_action on_error,
-			      const struct Index *index);
+
 Expr *sqlite3ExprDup(sqlite3 *, Expr *, int);
 SrcList *sqlite3SrcListDup(sqlite3 *, SrcList *, int);
 IdList *sqlite3IdListDup(sqlite3 *, IdList *);
@@ -4577,7 +4562,7 @@ Expr *sqlite3CreateColumnExpr(sqlite3 *, SrcList *, int, int);
 int sqlite3ExprCheckIN(Parse *, Expr *);
 
 void sqlite3AnalyzeFunctions(void);
-int sqlite3Stat4ProbeSetValue(Parse *, Index *, UnpackedRecord **, Expr *, int,
+int sqlite3Stat4ProbeSetValue(Parse *, struct index_def *, UnpackedRecord **, Expr *, int,
 			      int, int *);
 int sqlite3Stat4ValueFromExpr(Parse *, Expr *, u8, sqlite3_value **);
 void sqlite3Stat4ProbeFree(UnpackedRecord *);
diff --git a/src/box/sql/tarantoolInt.h b/src/box/sql/tarantoolInt.h
index 31dc1b1bc..69af6e723 100644
--- a/src/box/sql/tarantoolInt.h
+++ b/src/box/sql/tarantoolInt.h
@@ -178,19 +178,37 @@ int tarantoolSqlite3MakeTableOpts(Table * pTable, const char *zSql, char *buf);
 int
 fkey_encode_links(const struct fkey_def *def, int type, char *buf);
 
-/*
+/**
  * Format "parts" array for _index entry.
- * Returns result size.
- * If buf==NULL estimate result size.
+ * If buf == NULL estimate result size.
+ *
+ * @param fields Fields of space index belongs to.
+ * @param idx_def Definition of index under construction.
+ * @param pk_def Definition of primary index.
+ * @param[out] buf Encoded record. Can be NULL.
+ *
+ * @retval Size of encoded parts.
  */
-int tarantoolSqlite3MakeIdxParts(Index * index, void *buf);
+int
+sql_construct_index_parts(const struct field_def *fields,
+			  const struct index_def *idx_def,
+			  const struct index_def *pk_def, void *buf);
 
-/*
+/**
  * Format "opts" dictionary for _index entry.
- * Returns result size.
- * If buf==NULL estimate result size.
+ * If buf == NULL estimate result size.
+ *
+ * Ex: {
+ *   "unique": "true",
+ *   "sql": "CREATE INDEX student_by_name ON students(name)"
+ * }
+ *
+ * @param idx_def Definition of index under construction.
+ * @param[out] buf Encoded record. Can be NULL.
+ *
+ * @retval Size of encoded parts.
  */
-int tarantoolSqlite3MakeIdxOpts(Index * index, const char *zSql, void *buf);
+int sql_construct_index_opts(const struct index_def *idx_def, void *buf);
 
 /**
  * Extract next id from _sequence space.
diff --git a/src/box/sql/update.c b/src/box/sql/update.c
index 459ee3540..63d48705e 100644
--- a/src/box/sql/update.c
+++ b/src/box/sql/update.c
@@ -84,7 +84,6 @@ sqlite3Update(Parse * pParse,		/* The parser context */
 	int addrTop = 0;	/* VDBE instruction address of the start of the loop */
 	WhereInfo *pWInfo;	/* Information about the WHERE clause */
 	Vdbe *v;		/* The virtual database engine */
-	Index *pPk;		/* The PRIMARY KEY index */
 	sqlite3 *db;		/* The database structure */
 	int *aXRef = 0;		/* aXRef[i] is the index in pChanges->a[] of the
 				 * an expression for the i-th column of the table.
@@ -147,7 +146,7 @@ sqlite3Update(Parse * pParse,		/* The parser context */
 	/* Allocate cursor on primary index. */
 	int pk_cursor = pParse->nTab++;
 	pTabList->a[0].iCursor = pk_cursor;
-	pPk = is_view ? NULL : sqlite3PrimaryKeyIndex(pTab);
+	struct index *pPk = sql_table_primary_key(pTab);
 
 	aXRef = sqlite3DbMallocRawNN(db, sizeof(int) * def->field_count);
 	if (aXRef == NULL)
diff --git a/src/box/sql/vdbe.c b/src/box/sql/vdbe.c
index 46beae24b..d54d169ee 100644
--- a/src/box/sql/vdbe.c
+++ b/src/box/sql/vdbe.c
@@ -4690,17 +4690,17 @@ case OP_DropTable: {
 	break;
 }
 
-/* Opcode: DropIndex * * *  P4
+/* Opcode: DropIndex P1 * *  P4
+ *
+ * @P1 Contains index id of index to be removed.
+ * @P4 Space of removed index.
  *
  * Remove the internal (in-memory) data structures that describe
  * the index named P4 for table.
- * This is called after an index is dropped from disk
- * (using the Destroy opcode) in order to keep
- * the internal representation of the schema consistent with what
- * is on disk.
+ * This is called after an index is dropped from Tarantool DD.
  */
 case OP_DropIndex: {
-	sqlite3UnlinkAndDeleteIndex(db, pOp->p4.pIndex);
+	sql_space_index_delete(pOp->p4.space, pOp->p1);
 	break;
 }
 
diff --git a/src/box/sql/vdbe.h b/src/box/sql/vdbe.h
index 2987d7ab0..6ee83a5de 100644
--- a/src/box/sql/vdbe.h
+++ b/src/box/sql/vdbe.h
@@ -79,7 +79,6 @@ struct VdbeOp {
 		bool b;         /* Used when p4type is P4_BOOL */
 		int *ai;	/* Used when p4type is P4_INTARRAY */
 		SubProgram *pProgram;	/* Used when p4type is P4_SUBPROGRAM */
-		Index *pIndex;	/* Used when p4type is P4_INDEX */
 		int (*xAdvance) (BtCursor *, int *);
 		/** Used when p4type is P4_KEYDEF. */
 		struct key_def *key_def;
@@ -127,7 +126,6 @@ struct SubProgram {
 #define P4_INTARRAY (-12)	/* P4 is a vector of 32-bit integers */
 #define P4_SUBPROGRAM  (-13)	/* P4 is a pointer to a SubProgram structure */
 #define P4_ADVANCE  (-14)	/* P4 is a pointer to BtreeNext() or BtreePrev() */
-#define P4_INDEX    (-15)	/* P4 is a pointer to a Index structure */
 #define P4_FUNCCTX  (-16)	/* P4 is a pointer to an sqlite3_context object */
 #define P4_BOOL     (-17)	/* P4 is a bool value */
 #define P4_PTR      (-18)	/* P4 is a generic pointer */
@@ -231,10 +229,10 @@ void sqlite3VdbeAppendP4(Vdbe *, void *pP4, int p4type);
  * Set the P4 on the most recently added opcode to the key_def for the
  * index given.
  * @param Parse context, for error reporting.
- * @param Index to get key_def from.
+ * @param idx_def Definition of index to get key_def from.
  */
 void
-sql_vdbe_set_p4_key_def(struct Parse *parse, struct Index *index);
+sql_vdbe_set_p4_key_def(struct Parse *parse, struct index_def *idx_def);
 
 VdbeOp *sqlite3VdbeGetOp(Vdbe *, int);
 int sqlite3VdbeMakeLabel(Vdbe *);
diff --git a/src/box/sql/vdbeaux.c b/src/box/sql/vdbeaux.c
index 3b0c90ce3..12c2edffe 100644
--- a/src/box/sql/vdbeaux.c
+++ b/src/box/sql/vdbeaux.c
@@ -1055,12 +1055,12 @@ sqlite3VdbeAppendP4(Vdbe * p, void *pP4, int n)
 }
 
 void
-sql_vdbe_set_p4_key_def(struct Parse *parse, struct Index *idx)
+sql_vdbe_set_p4_key_def(struct Parse *parse, struct index_def *idx_def)
 {
 	struct Vdbe *v = parse->pVdbe;
 	assert(v != NULL);
-	assert(idx != NULL);
-	struct key_def *def = key_def_dup(idx->def->key_def);
+	assert(idx_def != NULL);
+	struct key_def *def = key_def_dup(idx_def->key_def);
 	if (def == NULL)
 		sqlite3OomFault(parse->db);
 	else
diff --git a/src/box/sql/vdbemem.c b/src/box/sql/vdbemem.c
index 65bb67d8f..3e50f9b89 100644
--- a/src/box/sql/vdbemem.c
+++ b/src/box/sql/vdbemem.c
@@ -1062,7 +1062,7 @@ sqlite3ValueNew(sqlite3 * db)
  */
 struct ValueNewStat4Ctx {
 	Parse *pParse;
-	Index *pIdx;
+	struct index_def *pIdx;
 	UnpackedRecord **ppRec;
 	int iVal;
 };
@@ -1084,19 +1084,17 @@ valueNew(sqlite3 * db, struct ValueNewStat4Ctx *p)
 	if (p) {
 		UnpackedRecord *pRec = p->ppRec[0];
 
-		if (pRec == 0) {
-			Index *pIdx = p->pIdx;	/* Index being probed */
-			int nByte;	/* Bytes of space to allocate */
-			int i;	/* Counter variable */
-			int part_count = pIdx->def->key_def->part_count;
+		if (pRec == NULL) {
+			struct index_def *idx = p->pIdx;
+			uint32_t part_count = idx->key_def->part_count;
 
-			nByte = sizeof(Mem) * part_count +
+			int nByte = sizeof(Mem) * part_count +
 				ROUND8(sizeof(UnpackedRecord));
-			pRec =
-			    (UnpackedRecord *) sqlite3DbMallocZero(db, nByte);
+			pRec = (UnpackedRecord *) sqlite3DbMallocZero(db,
+								      nByte);
 			if (pRec == NULL)
 				return NULL;
-			pRec->key_def = key_def_dup(pIdx->def->key_def);
+			pRec->key_def = key_def_dup(idx->key_def);
 			if (pRec->key_def == NULL) {
 				sqlite3DbFree(db, pRec);
 				sqlite3OomFault(db);
@@ -1104,7 +1102,7 @@ valueNew(sqlite3 * db, struct ValueNewStat4Ctx *p)
 			}
 			pRec->aMem = (Mem *)((char *) pRec +
 					     ROUND8(sizeof(UnpackedRecord)));
-			for (i = 0; i < (int) part_count; i++) {
+			for (uint32_t i = 0; i < part_count; i++) {
 				pRec->aMem[i].flags = MEM_Null;
 				pRec->aMem[i].db = db;
 			}
@@ -1530,7 +1528,7 @@ stat4ValueFromExpr(Parse * pParse,	/* Parse context */
  */
 int
 sqlite3Stat4ProbeSetValue(Parse * pParse,	/* Parse context */
-			  Index * pIdx,	/* Index being probed */
+			  struct index_def *idx,
 			  UnpackedRecord ** ppRec,	/* IN/OUT: Probe record */
 			  Expr * pExpr,	/* The expression to extract a value from */
 			  int nElem,	/* Maximum number of values to append */
@@ -1546,16 +1544,16 @@ sqlite3Stat4ProbeSetValue(Parse * pParse,	/* Parse context */
 		struct ValueNewStat4Ctx alloc;
 
 		alloc.pParse = pParse;
-		alloc.pIdx = pIdx;
+		alloc.pIdx = idx;
 		alloc.ppRec = ppRec;
 
-		struct space *space = space_by_id(pIdx->def->space_id);
+		struct space *space = space_by_id(idx->space_id);
 		assert(space != NULL);
 		for (i = 0; i < nElem; i++) {
 			sqlite3_value *pVal = 0;
 			Expr *pElem =
 			    (pExpr ? sqlite3VectorFieldSubexpr(pExpr, i) : 0);
-			u8 aff = sql_space_index_part_affinity(space->def, pIdx->def,
+			u8 aff = sql_space_index_part_affinity(space->def, idx,
 							       iVal + i);
 			alloc.iVal = iVal + i;
 			rc = stat4ValueFromExpr(pParse, pElem, aff, &alloc,
diff --git a/src/box/sql/where.c b/src/box/sql/where.c
index a4a1c456f..d8a2c1a79 100644
--- a/src/box/sql/where.c
+++ b/src/box/sql/where.c
@@ -362,7 +362,7 @@ whereScanInit(WhereScan * pScan,	/* The WhereScan object being initialized */
 	      int iCur,		/* Cursor to scan for */
 	      int iColumn,	/* Column to scan for */
 	      u32 opMask,	/* Operator(s) to scan for */
-	      Index * pIdx)	/* Must be compatible with this index */
+	      struct index_def *idx_def)
 {
 	pScan->pOrigWC = pWC;
 	pScan->pWC = pWC;
@@ -370,17 +370,21 @@ whereScanInit(WhereScan * pScan,	/* The WhereScan object being initialized */
 	pScan->idxaff = 0;
 	pScan->coll = NULL;
 	pScan->is_column_seen = false;
-	if (pIdx != NULL) {
+	if (idx_def != NULL) {
 		int j = iColumn;
 		/*
 		 * pIdx->def->iid == UINT32_MAX means that
 		 * pIdx is a fake integer primary key index.
 		 */
-		if (pIdx->def->iid != UINT32_MAX) {
-			iColumn = pIdx->def->key_def->parts[iColumn].fieldno;
-			pScan->idxaff =
-				pIdx->pTable->def->fields[iColumn].affinity;
-			pScan->coll = pIdx->def->key_def->parts[j].coll;
+		if (idx_def->iid != UINT32_MAX) {
+			iColumn = idx_def->key_def->parts[iColumn].fieldno;
+			struct space *sp = space_by_id(idx_def->space_id);
+			assert(sp != NULL);
+			if (sp->def->field_count == 0)
+				pScan->idxaff = AFFINITY_BLOB;
+			else
+				pScan->idxaff = sp->def->fields[iColumn].affinity;
+			pScan->coll = idx_def->key_def->parts[j].coll;
 			pScan->is_column_seen = true;
 		} else {
 			iColumn = -1;
@@ -472,13 +476,13 @@ sqlite3WhereFindTerm(WhereClause * pWC,	/* The WHERE clause to be searched */
 		     int iColumn,	/* Column number of LHS */
 		     Bitmask notReady,	/* RHS must not overlap with this mask */
 		     u32 op,		/* Mask of WO_xx values describing operator */
-		     Index * pIdx)	/* Must be compatible with this index, if not NULL */
+		     struct index_def *idx_def)
 {
 	WhereTerm *pResult = 0;
 	WhereTerm *p;
 	WhereScan scan;
 
-	p = whereScanInit(&scan, pWC, iCur, iColumn, op, pIdx);
+	p = whereScanInit(&scan, pWC, iCur, iColumn, op, idx_def);
 	op &= WO_EQ;
 	while (p) {
 		if ((p->prereqRight & notReady) == 0) {
@@ -542,10 +546,10 @@ static int
 findIndexCol(Parse * pParse,	/* Parse context */
 	     ExprList * pList,	/* Expression list to search */
 	     int iBase,		/* Cursor for table associated with pIdx */
-	     Index * pIdx,	/* Index to match column of */
+	     struct index_def *idx_def,
 	     int iCol)		/* Column of index to match */
 {
-	struct key_part *part_to_match = &pIdx->def->key_def->parts[iCol];
+	struct key_part *part_to_match = &idx_def->key_def->parts[iCol];
 	for (int i = 0; i < pList->nExpr; i++) {
 		Expr *p = sqlite3ExprSkipCollate(pList->a[i].pExpr);
 		if (p->op == TK_COLUMN && p->iTable == iBase &&
@@ -577,8 +581,6 @@ isDistinctRedundant(Parse * pParse,		/* Parsing context */
 		    ExprList * pDistinct)	/* The result set that needs to be DISTINCT */
 {
 	Table *pTab;
-	Index *pIdx;
-	int i;
 	int iBase;
 
 	/* If there is more than one table or sub-select in the FROM clause of
@@ -594,12 +596,13 @@ isDistinctRedundant(Parse * pParse,		/* Parsing context */
 	 * true. Note: The (p->iTable==iBase) part of this test may be false if the
 	 * current SELECT is a correlated sub-query.
 	 */
-	for (i = 0; i < pDistinct->nExpr; i++) {
+	for (int i = 0; i < pDistinct->nExpr; i++) {
 		Expr *p = sqlite3ExprSkipCollate(pDistinct->a[i].pExpr);
 		if (p->op == TK_COLUMN && p->iTable == iBase && p->iColumn < 0)
 			return 1;
 	}
-
+	if (pTab->space == NULL)
+		return 0;
 	/* Loop through all indices on the table, checking each to see if it makes
 	 * the DISTINCT qualifier redundant. It does so if:
 	 *
@@ -613,26 +616,30 @@ isDistinctRedundant(Parse * pParse,		/* Parsing context */
 	 *   3. All of those index columns for which the WHERE clause does not
 	 *      contain a "col=X" term are subject to a NOT NULL constraint.
 	 */
-	for (pIdx = pTab->pIndex; pIdx; pIdx = pIdx->pNext) {
-		if (!pIdx->def->opts.is_unique)
+	for (uint32_t j = 0; j < pTab->space->index_count; ++j) {
+		struct index *idx = pTab->space->index[j];
+		if (!idx->def->opts.is_unique)
 			continue;
-		int col_count = pIdx->def->key_def->part_count;
+		uint32_t col_count = idx->def->key_def->part_count;
+		uint32_t i;
 		for (i = 0; i < col_count; i++) {
 			if (0 ==
 			    sqlite3WhereFindTerm(pWC, iBase, i, ~(Bitmask) 0,
-						 WO_EQ, pIdx)) {
+						 WO_EQ, idx->def)) {
 				if (findIndexCol
-				    (pParse, pDistinct, iBase, pIdx, i) < 0)
+				    (pParse, pDistinct, iBase, idx->def, i) < 0)
 					break;
-				uint32_t j = pIdx->def->key_def->parts[i].fieldno;
-				if (pIdx->pTable->def->fields[j].is_nullable)
+				uint32_t x = idx->def->key_def->parts[i].fieldno;
+				if (pTab->def->fields[x].is_nullable)
 					break;
 			}
 		}
-		if (i == (int) pIdx->def->key_def->part_count) {
-			/* This index implies that the DISTINCT qualifier is redundant. */
+		/*
+		 * This index implies that the DISTINCT
+		 * qualifier is redundant.
+		 */
+		if (i == idx->def->key_def->part_count)
 			return 1;
-		}
 	}
 
 	return 0;
@@ -908,15 +915,14 @@ constructAutomaticIndex(Parse * pParse,			/* The parsing context */
  */
 static int
 whereKeyStats(Parse * pParse,	/* Database connection */
-	      Index * pIdx,	/* Index to consider domain of */
+	      struct index_def *idx_def,
 	      UnpackedRecord * pRec,	/* Vector of values to consider */
 	      int roundUp,	/* Round up if true.  Round down if false */
 	      tRowcnt * aStat)	/* OUT: stats written here */
 {
-	struct space *space = space_by_id(pIdx->pTable->def->id);
+	struct space *space = space_by_id(idx_def->space_id);
 	assert(space != NULL);
-	uint32_t iid = pIdx->def->iid;
-	struct index *idx = space_index(space, iid);
+	struct index *idx = space_index(space, idx_def->iid);
 	assert(idx != NULL && idx->def->opts.stat != NULL);
 	struct index_sample *samples = idx->def->opts.stat->samples;
 	assert(idx->def->opts.stat->sample_count > 0);
@@ -1185,23 +1191,23 @@ whereRangeSkipScanEst(Parse * pParse,		/* Parsing & code generating context */
 		      WhereLoop * pLoop,	/* Update the .nOut value of this loop */
 		      int *pbDone)		/* Set to true if at least one expr. value extracted */
 {
-	Index *p = pLoop->pIndex;
-	struct space *space = space_by_id(p->pTable->def->id);
+	struct index_def *p = pLoop->index_def;
+	struct space *space = space_by_id(p->space_id);
 	assert(space != NULL);
-	struct index *index = space_index(space, p->def->iid);
+	struct index *index = space_index(space, p->iid);
 	assert(index != NULL && index->def->opts.stat != NULL);
 	int nEq = pLoop->nEq;
 	sqlite3 *db = pParse->db;
 	int nLower = -1;
 	int nUpper = index->def->opts.stat->sample_count + 1;
 	int rc = SQLITE_OK;
-	u8 aff = sql_space_index_part_affinity(space->def, p->def, nEq);
+	u8 aff = sql_space_index_part_affinity(space->def, p, nEq);
 
 	sqlite3_value *p1 = 0;	/* Value extracted from pLower */
 	sqlite3_value *p2 = 0;	/* Value extracted from pUpper */
 	sqlite3_value *pVal = 0;	/* Value extracted from record */
 
-	struct coll *coll = p->def->key_def->parts[nEq].coll;
+	struct coll *coll = p->key_def->parts[nEq].coll;
 	if (pLower) {
 		rc = sqlite3Stat4ValueFromExpr(pParse, pLower->pExpr->pRight,
 					       aff, &p1);
@@ -1314,12 +1320,11 @@ whereRangeScanEst(Parse * pParse,	/* Parsing & code generating context */
 	int nOut = pLoop->nOut;
 	LogEst nNew;
 
-	Index *p = pLoop->pIndex;
+	struct index_def *p = pLoop->index_def;
 	int nEq = pLoop->nEq;
-	uint32_t space_id = p->pTable->def->id;
-	struct space *space = space_by_id(space_id);
+	struct space *space = space_by_id(p->space_id);
 	assert(space != NULL);
-	struct index *idx = space_index(space, p->def->iid);
+	struct index *idx = space_index(space, p->iid);
 	assert(idx != NULL);
 	struct index_stat *stat = idx->def->opts.stat;
 	/*
@@ -1373,12 +1378,6 @@ whereRangeScanEst(Parse * pParse,	/* Parsing & code generating context */
 				 * are in range.
 				 */
 				iLower = 0;
-				uint32_t space_id = p->def->space_id;
-				struct space *space = space_by_id(space_id);
-				assert(space != NULL);
-				struct index *idx =
-					space_index(space, p->def->iid);
-				assert(idx != NULL);
 				iUpper = index_size(idx);
 			} else {
 				/* Note: this call could be optimized away - since the same values must
@@ -1393,7 +1392,7 @@ whereRangeScanEst(Parse * pParse,	/* Parsing & code generating context */
 			       || (pLower->eOperator & (WO_GT | WO_GE)) != 0);
 			assert(pUpper == 0
 			       || (pUpper->eOperator & (WO_LT | WO_LE)) != 0);
-			if (p->def->key_def->parts[nEq].sort_order !=
+			if (p->key_def->parts[nEq].sort_order !=
 			    SORT_ORDER_ASC) {
 				/* The roles of pLower and pUpper are swapped for a DESC index */
 				SWAP(pLower, pUpper);
@@ -1535,7 +1534,7 @@ whereEqualScanEst(Parse * pParse,	/* Parsing & code generating context */
 		  WhereLoopBuilder * pBuilder, Expr * pExpr,	/* Expression for VALUE in the x=VALUE constraint */
 		  tRowcnt * pnRow)	/* Write the revised row estimate here */
 {
-	Index *p = pBuilder->pNew->pIndex;
+	struct index_def *p = pBuilder->pNew->index_def;
 	int nEq = pBuilder->pNew->nEq;
 	UnpackedRecord *pRec = pBuilder->pRec;
 	int rc;			/* Subfunction return code */
@@ -1543,7 +1542,7 @@ whereEqualScanEst(Parse * pParse,	/* Parsing & code generating context */
 	int bOk;
 
 	assert(nEq >= 1);
-	assert(nEq <= (int) p->def->key_def->part_count);
+	assert(nEq <= (int) p->key_def->part_count);
 	assert(pBuilder->nRecValid < nEq);
 
 	/* If values are not available for all fields of the index to the left
@@ -1563,8 +1562,8 @@ whereEqualScanEst(Parse * pParse,	/* Parsing & code generating context */
 	pBuilder->nRecValid = nEq;
 
 	whereKeyStats(pParse, p, pRec, 0, a);
-	WHERETRACE(0x10, ("equality scan regions %s(%d): %d\n",
-			  p->def->name, nEq - 1, (int)a[1]));
+	WHERETRACE(0x10, ("equality scan regions %s(%d): %d\n", p->name,
+		   nEq - 1, (int)a[1]));
 	*pnRow = a[1];
 
 	return rc;
@@ -1591,7 +1590,7 @@ whereInScanEst(Parse * pParse,	/* Parsing & code generating context */
 	       WhereLoopBuilder * pBuilder, ExprList * pList,	/* The value list on the RHS of "x IN (v1,v2,v3,...)" */
 	       tRowcnt * pnRow)	/* Write the revised row estimate here */
 {
-	Index *p = pBuilder->pNew->pIndex;
+	struct index_def *p = pBuilder->pNew->index_def;
 	i64 nRow0 = sqlite3LogEstToInt(index_field_tuple_est(p, 0));
 	int nRecValid = pBuilder->nRecValid;
 	int rc = SQLITE_OK;	/* Subfunction return code */
@@ -1696,7 +1695,7 @@ whereLoopPrint(WhereLoop * p, WhereClause * pWC)
 			   pItem->zAlias ? pItem->zAlias : pTab->def->name);
 #endif
 	const char *zName;
-	if (p->pIndex != NULL && (zName = p->pIndex->def->name) != NULL) {
+	if (p->index_def != NULL && (zName = p->index_def->name) != NULL) {
 		if (strncmp(zName, "sql_autoindex_", 17) == 0) {
 			int i = sqlite3Strlen30(zName) - 1;
 			while (zName[i] != '_')
@@ -1741,12 +1740,11 @@ whereLoopInit(WhereLoop * p)
  * Clear the WhereLoop.u union.  Leave WhereLoop.pLTerm intact.
  */
 static void
-whereLoopClearUnion(sqlite3 * db, WhereLoop * p)
+whereLoopClearUnion(WhereLoop * p)
 {
-	if ((p->wsFlags & WHERE_AUTO_INDEX) != 0 &&
-	    (p->wsFlags & WHERE_AUTO_INDEX) != 0 && p->pIndex != 0) {
-		sqlite3DbFree(db, p->pIndex);
-		p->pIndex = 0;
+	if ((p->wsFlags & WHERE_AUTO_INDEX) != 0) {
+		free(p->index_def);
+		p->index_def = NULL;
 	}
 }
 
@@ -1758,7 +1756,7 @@ whereLoopClear(sqlite3 * db, WhereLoop * p)
 {
 	if (p->aLTerm != p->aLTermSpace)
 		sqlite3DbFree(db, p->aLTerm);
-	whereLoopClearUnion(db, p);
+	whereLoopClearUnion(p);
 	whereLoopInit(p);
 }
 
@@ -1789,19 +1787,19 @@ whereLoopResize(sqlite3 * db, WhereLoop * p, int n)
 static int
 whereLoopXfer(sqlite3 * db, WhereLoop * pTo, WhereLoop * pFrom)
 {
-	whereLoopClearUnion(db, pTo);
+	whereLoopClearUnion(pTo);
 	if (whereLoopResize(db, pTo, pFrom->nLTerm)) {
 		pTo->nEq = 0;
 		pTo->nBtm = 0;
 		pTo->nTop = 0;
-		pTo->pIndex = NULL;
+		pTo->index_def = NULL;
 		return SQLITE_NOMEM_BKPT;
 	}
 	memcpy(pTo, pFrom, WHERE_LOOP_XFER_SZ);
 	memcpy(pTo->aLTerm, pFrom->aLTerm,
 	       pTo->nLTerm * sizeof(pTo->aLTerm[0]));
 	if ((pFrom->wsFlags & WHERE_AUTO_INDEX) != 0)
-		pFrom->pIndex = 0;
+		pFrom->index_def = NULL;
 	return SQLITE_OK;
 }
 
@@ -2140,9 +2138,9 @@ whereLoopInsert(WhereLoopBuilder * pBuilder, WhereLoop * pTemplate)
 		}
 	}
 	rc = whereLoopXfer(db, p, pTemplate);
-	Index *pIndex = p->pIndex;
-	if (pIndex != NULL && pIndex->pTable->def->id == 0)
-		p->pIndex = NULL;
+	struct index_def *idx = p->index_def;
+	if (idx != NULL && idx->space_id == 0)
+		p->index_def = NULL;
 	return rc;
 }
 
@@ -2251,14 +2249,14 @@ whereLoopOutputAdjust(WhereClause * pWC,	/* The WHERE clause */
 static int
 whereRangeVectorLen(Parse * pParse,	/* Parsing context */
 		    int iCur,		/* Cursor open on pIdx */
-		    Index * pIdx,	/* The index to be used for a inequality constraint */
+		    struct index_def *idx_def,
 		    int nEq,		/* Number of prior equality constraints on same index */
 		    WhereTerm * pTerm)	/* The vector inequality constraint */
 {
 	int nCmp = sqlite3ExprVectorSize(pTerm->pExpr->pLeft);
 	int i;
 
-	nCmp = MIN(nCmp, (int)(pIdx->def->key_def->part_count - nEq));
+	nCmp = MIN(nCmp, (int)(idx_def->key_def->part_count - nEq));
 	for (i = 1; i < nCmp; i++) {
 		/* Test if comparison i of pTerm is compatible with column (i+nEq)
 		 * of the index. If not, exit the loop.
@@ -2279,23 +2277,24 @@ whereRangeVectorLen(Parse * pParse,	/* Parsing context */
 		 * order of the index column is the same as the sort order of the
 		 * leftmost index column.
 		 */
-		struct key_part *parts = pIdx->def->key_def->parts;
+		struct key_part *parts = idx_def->key_def->parts;
 		if (pLhs->op != TK_COLUMN || pLhs->iTable != iCur ||
 		    pLhs->iColumn != (int)parts[i + nEq].fieldno ||
 		    parts[i + nEq].sort_order != parts[nEq].sort_order)
 			break;
 
+		struct space *space = space_by_id(idx_def->space_id);
+		assert(space != NULL);
 		aff = sqlite3CompareAffinity(pRhs, sqlite3ExprAffinity(pLhs));
 		idxaff =
-		    sqlite3TableColumnAffinity(pIdx->pTable->def,
-					       pLhs->iColumn);
+		    sqlite3TableColumnAffinity(space->def, pLhs->iColumn);
 		if (aff != idxaff)
 			break;
 		uint32_t id;
 		pColl = sql_binary_compare_coll_seq(pParse, pLhs, pRhs, &id);
 		if (pColl == 0)
 			break;
-		if (pIdx->def->key_def->parts[i + nEq].coll != pColl)
+		if (idx_def->key_def->parts[i + nEq].coll != pColl)
 			break;
 	}
 	return i;
@@ -2316,7 +2315,7 @@ whereRangeVectorLen(Parse * pParse,	/* Parsing context */
 static int
 whereLoopAddBtreeIndex(WhereLoopBuilder * pBuilder,	/* The WhereLoop factory */
 		       struct SrcList_item *pSrc,	/* FROM clause term being analyzed */
-		       Index * pProbe,			/* An index on pSrc */
+		       struct index_def *probe,
 		       LogEst nInMul)			/* log(Number of iterations due to IN) */
 {
 	WhereInfo *pWInfo = pBuilder->pWInfo;	/* WHERE analyse context */
@@ -2338,13 +2337,13 @@ whereLoopAddBtreeIndex(WhereLoopBuilder * pBuilder,	/* The WhereLoop factory */
 	LogEst rSize;		/* Number of rows in the table */
 	LogEst rLogSize;	/* Logarithm of table size */
 	WhereTerm *pTop = 0, *pBtm = 0;	/* Top and bottom range constraints */
-	uint32_t probe_part_count = pProbe->def->key_def->part_count;
+	uint32_t probe_part_count = probe->key_def->part_count;
 
 	pNew = pBuilder->pNew;
 	if (db->mallocFailed)
 		return SQLITE_NOMEM_BKPT;
 	WHERETRACE(0x800, ("BEGIN addBtreeIdx(%s), nEq=%d\n",
-			   pProbe->def->name, pNew->nEq));
+			   probe->name, pNew->nEq));
 
 	assert((pNew->wsFlags & WHERE_TOP_LIMIT) == 0);
 	if (pNew->wsFlags & WHERE_BTM_LIMIT) {
@@ -2354,11 +2353,10 @@ whereLoopAddBtreeIndex(WhereLoopBuilder * pBuilder,	/* The WhereLoop factory */
 		opMask =
 		    WO_EQ | WO_IN | WO_GT | WO_GE | WO_LT | WO_LE | WO_ISNULL;
 	}
-	struct space *space = space_by_id(pProbe->def->space_id);
-	struct index *idx = NULL;
+	struct space *space = space_by_id(probe->space_id);
 	struct index_stat *stat = NULL;
-	if (space != NULL && pProbe->def->iid != UINT32_MAX) {
-		idx = space_index(space, pProbe->def->iid);
+	if (space != NULL && probe->iid != UINT32_MAX) {
+		struct index *idx = space_index(space, probe->iid);
 		assert(idx != NULL);
 		stat = idx->def->opts.stat;
 	}
@@ -2383,9 +2381,9 @@ whereLoopAddBtreeIndex(WhereLoopBuilder * pBuilder,	/* The WhereLoop factory */
 	saved_prereq = pNew->prereq;
 	saved_nOut = pNew->nOut;
 	pTerm = whereScanInit(&scan, pBuilder->pWC, pSrc->iCursor, saved_nEq,
-			      opMask, pProbe);
+			      opMask, probe);
 	pNew->rSetup = 0;
-	rSize = index_field_tuple_est(pProbe, 0);
+	rSize = index_field_tuple_est(probe, 0);
 	rLogSize = estLog(rSize);
 	for (; rc == SQLITE_OK && pTerm != 0; pTerm = whereScanNext(&scan)) {
 		u16 eOp = pTerm->eOperator;	/* Shorthand for pTerm->eOperator */
@@ -2393,9 +2391,9 @@ whereLoopAddBtreeIndex(WhereLoopBuilder * pBuilder,	/* The WhereLoop factory */
 		LogEst nOutUnadjusted;	/* nOut before IN() and WHERE adjustments */
 		int nIn = 0;
 		int nRecValid = pBuilder->nRecValid;
-		uint32_t j = pProbe->def->key_def->parts[saved_nEq].fieldno;
+		uint32_t j = probe->key_def->parts[saved_nEq].fieldno;
 		if ((eOp == WO_ISNULL || (pTerm->wtFlags & TERM_VNULL) != 0) &&
-		    !pProbe->pTable->def->fields[j].is_nullable) {
+		    !space->def->fields[j].is_nullable) {
 			/*
 			 * Ignore IS [NOT] NULL constraints on NOT
 			 * NULL columns.
@@ -2468,15 +2466,15 @@ whereLoopAddBtreeIndex(WhereLoopBuilder * pBuilder,	/* The WhereLoop factory */
 							 */
 			}
 		} else if (eOp & WO_EQ) {
-			int iCol = pProbe->def->key_def->parts[saved_nEq].fieldno;
+			int iCol = probe->key_def->parts[saved_nEq].fieldno;
 			pNew->wsFlags |= WHERE_COLUMN_EQ;
 			assert(saved_nEq == pNew->nEq);
 			if (iCol > 0 && nInMul == 0 &&
 			    saved_nEq == probe_part_count - 1) {
 				bool index_is_unique_not_null =
-					pProbe->def->key_def->is_nullable &&
-					pProbe->def->opts.is_unique;
-				if (pProbe->def->space_id != 0 &&
+					probe->key_def->is_nullable &&
+					probe->opts.is_unique;
+				if (probe->space_id != 0 &&
 				    !index_is_unique_not_null) {
 					pNew->wsFlags |= WHERE_UNQ_WANTED;
 				} else {
@@ -2490,7 +2488,7 @@ whereLoopAddBtreeIndex(WhereLoopBuilder * pBuilder,	/* The WhereLoop factory */
 			testcase(eOp & WO_GE);
 			pNew->wsFlags |= WHERE_COLUMN_RANGE | WHERE_BTM_LIMIT;
 			pNew->nBtm =
-			    whereRangeVectorLen(pParse, pSrc->iCursor, pProbe,
+			    whereRangeVectorLen(pParse, pSrc->iCursor, probe,
 						saved_nEq, pTerm);
 			pBtm = pTerm;
 			pTop = 0;
@@ -2515,7 +2513,7 @@ whereLoopAddBtreeIndex(WhereLoopBuilder * pBuilder,	/* The WhereLoop factory */
 			testcase(eOp & WO_LE);
 			pNew->wsFlags |= WHERE_COLUMN_RANGE | WHERE_TOP_LIMIT;
 			pNew->nTop =
-			    whereRangeVectorLen(pParse, pSrc->iCursor, pProbe,
+			    whereRangeVectorLen(pParse, pSrc->iCursor, probe,
 						saved_nEq, pTerm);
 			pTop = pTerm;
 			pBtm = (pNew->wsFlags & WHERE_BTM_LIMIT) != 0 ?
@@ -2539,7 +2537,7 @@ whereLoopAddBtreeIndex(WhereLoopBuilder * pBuilder,	/* The WhereLoop factory */
 			assert(eOp & (WO_ISNULL | WO_EQ | WO_IN));
 
 			assert(pNew->nOut == saved_nOut);
-			if (pTerm->truthProb <= 0 && pProbe->pTable->def->id != 0) {
+			if (pTerm->truthProb <= 0 && probe->space_id != 0) {
 				assert((eOp & WO_IN) || nIn == 0);
 				testcase(eOp & WO_IN);
 				pNew->nOut += pTerm->truthProb;
@@ -2582,8 +2580,8 @@ whereLoopAddBtreeIndex(WhereLoopBuilder * pBuilder,	/* The WhereLoop factory */
 				}
 				if (nOut == 0) {
 					pNew->nOut +=
-						(index_field_tuple_est(pProbe, nEq) -
-						 index_field_tuple_est(pProbe, nEq -1));
+						(index_field_tuple_est(probe, nEq) -
+						 index_field_tuple_est(probe, nEq -1));
 					if (eOp & WO_ISNULL) {
 						/* TUNING: If there is no likelihood() value, assume that a
 						 * "col IS NULL" expression matches twice as many rows
@@ -2600,11 +2598,7 @@ whereLoopAddBtreeIndex(WhereLoopBuilder * pBuilder,	/* The WhereLoop factory */
 		 * seek only. Then, if this is a non-covering index, add the cost of
 		 * visiting the rows in the main table.
 		 */
-		struct space *space =
-			space_by_id(pProbe->pTable->def->id);
-		assert(space != NULL);
-		struct index *idx =
-			space_index(space, pProbe->def->iid);
+		struct index *idx = space_index(space, probe->iid);
 		assert(idx != NULL);
 		/*
 		 * FIXME: currently, the procedure below makes no
@@ -2616,7 +2610,6 @@ whereLoopAddBtreeIndex(WhereLoopBuilder * pBuilder,	/* The WhereLoop factory */
 		 */
 		ssize_t avg_tuple_size = sql_index_tuple_size(space, idx);
 		struct index *pk = space_index(space, 0);
-		assert(pProbe->pTable == pSrc->pTab);
 		ssize_t avg_tuple_size_pk = sql_index_tuple_size(space, pk);
 		uint32_t partial_index_cost =
 			avg_tuple_size_pk != 0 ?
@@ -2642,7 +2635,7 @@ whereLoopAddBtreeIndex(WhereLoopBuilder * pBuilder,	/* The WhereLoop factory */
 
 		if ((pNew->wsFlags & WHERE_TOP_LIMIT) == 0 &&
 		    pNew->nEq < probe_part_count) {
-			whereLoopAddBtreeIndex(pBuilder, pSrc, pProbe,
+			whereLoopAddBtreeIndex(pBuilder, pSrc, probe,
 					       nInMul + nIn);
 		}
 		pNew->nOut = saved_nOut;
@@ -2672,21 +2665,21 @@ whereLoopAddBtreeIndex(WhereLoopBuilder * pBuilder,	/* The WhereLoop factory */
 	if (saved_nEq == saved_nSkip && saved_nEq + 1U < probe_part_count &&
 	    stat->skip_scan_enabled == true &&
 	    /* TUNING: Minimum for skip-scan */
-	    index_field_tuple_est(pProbe, saved_nEq + 1) >= 42 &&
+	    index_field_tuple_est(probe, saved_nEq + 1) >= 42 &&
 	    (rc = whereLoopResize(db, pNew, pNew->nLTerm + 1)) == SQLITE_OK) {
 		LogEst nIter;
 		pNew->nEq++;
 		pNew->nSkip++;
 		pNew->aLTerm[pNew->nLTerm++] = 0;
 		pNew->wsFlags |= WHERE_SKIPSCAN;
-		nIter = index_field_tuple_est(pProbe, saved_nEq) -
-			index_field_tuple_est(pProbe, saved_nEq + 1);
+		nIter = index_field_tuple_est(probe, saved_nEq) -
+			index_field_tuple_est(probe, saved_nEq + 1);
 		pNew->nOut -= nIter;
 		/* TUNING:  Because uncertainties in the estimates for skip-scan queries,
 		 * add a 1.375 fudge factor to make skip-scan slightly less likely.
 		 */
 		nIter += 5;
-		whereLoopAddBtreeIndex(pBuilder, pSrc, pProbe, nIter + nInMul);
+		whereLoopAddBtreeIndex(pBuilder, pSrc, probe, nIter + nInMul);
 		pNew->nOut = saved_nOut;
 		pNew->nEq = saved_nEq;
 		pNew->nSkip = saved_nSkip;
@@ -2694,7 +2687,7 @@ whereLoopAddBtreeIndex(WhereLoopBuilder * pBuilder,	/* The WhereLoop factory */
 	}
 
 	WHERETRACE(0x800, ("END addBtreeIdx(%s), nEq=%d, rc=%d\n",
-			   pProbe->def->name, saved_nEq, rc));
+			   probe->name, saved_nEq, rc));
 	return rc;
 }
 
@@ -2708,13 +2701,13 @@ whereLoopAddBtreeIndex(WhereLoopBuilder * pBuilder,	/* The WhereLoop factory */
  * @retval True, if statistics exist and unordered flag is set.
  */
 static bool
-index_is_unordered(struct Index *idx)
+index_is_unordered(const struct index_def *idx)
 {
 	assert(idx != NULL);
-	struct space *space = space_by_id(idx->pTable->def->id);
+	struct space *space = space_by_id(idx->space_id);
 	if (space == NULL)
 		return false;
-	struct index *tnt_idx = space_index(space, idx->def->iid);
+	struct index *tnt_idx = space_index(space, idx->iid);
 	if (tnt_idx == NULL)
 		return false;
 	if (tnt_idx->def->opts.stat != NULL)
@@ -2732,12 +2725,12 @@ index_is_unordered(struct Index *idx)
  */
 static int
 indexMightHelpWithOrderBy(WhereLoopBuilder * pBuilder,
-			  Index * pIndex, int iCursor)
+			  const struct index_def *idx_def, int iCursor)
 {
 	ExprList *pOB;
 	int ii, jj;
-	int part_count = pIndex->def->key_def->part_count;
-	if (index_is_unordered(pIndex))
+	int part_count = idx_def->key_def->part_count;
+	if (index_is_unordered(idx_def))
 		return 0;
 	if ((pOB = pBuilder->pWInfo->pOrderBy) == 0)
 		return 0;
@@ -2748,7 +2741,7 @@ indexMightHelpWithOrderBy(WhereLoopBuilder * pBuilder,
 				return 1;
 			for (jj = 0; jj < part_count; jj++) {
 				if (pExpr->iColumn == (int)
-				    pIndex->def->key_def->parts[jj].fieldno)
+				    idx_def->key_def->parts[jj].fieldno)
 					return 1;
 			}
 		}
@@ -2799,8 +2792,10 @@ whereLoopAddBtree(WhereLoopBuilder * pBuilder,	/* WHERE clause information */
 		  Bitmask mPrereq)		/* Extra prerequesites for using this table */
 {
 	WhereInfo *pWInfo;	/* WHERE analysis context */
-	Index *pProbe;		/* An index we are evaluating */
-	Index fake_index;		/* A fake index object for the primary key */
+	/* An index we are evaluating. */
+	struct index_def *probe;
+	/* A fake index object for the primary key. */
+	struct index_def *fake_index = NULL;
 	SrcList *pTabList;	/* The FROM clause */
 	struct SrcList_item *pSrc;	/* The FROM clause btree term to add */
 	WhereLoop *pNew;	/* Template WhereLoop object */
@@ -2820,21 +2815,16 @@ whereLoopAddBtree(WhereLoopBuilder * pBuilder,	/* WHERE clause information */
 
 	if (pSrc->pIBIndex) {
 		/* An INDEXED BY clause specifies a particular index to use */
-		pProbe = pSrc->pIBIndex;
-		fake_index.def = NULL;
-	} else if (pTab->pIndex) {
-		pProbe = pTab->pIndex;
-		fake_index.def = NULL;
+		probe = pSrc->pIBIndex;
+	} else if (pTab->space->index_count != 0) {
+		probe = pTab->space->index[0]->def;
 	} else {
 		/* There is no INDEXED BY clause.  Create a fake Index object in local
 		 * variable fake_index to represent the primary key index.  Make this
 		 * fake index the first in a chain of Index objects with all of the real
 		 * indices to follow
 		 */
-		Index *pFirst;	/* First of real indices on the table */
-		memset(&fake_index, 0, sizeof(Index));
-		fake_index.pTable = pTab;
-
+		memset(&fake_index, 0, sizeof(fake_index));
 		struct key_def *key_def = key_def_new(1);
 		if (key_def == NULL) {
 			pWInfo->pParse->nErr++;
@@ -2849,19 +2839,17 @@ whereLoopAddBtree(WhereLoopBuilder * pBuilder,	/* WHERE clause information */
 		struct index_opts opts;
 		index_opts_create(&opts);
 		opts.sql = "fake_autoindex";
-		fake_index.def =
-			index_def_new(pTab->def->id, 0,"fake_autoindex",
-					sizeof("fake_autoindex") - 1,
-					TREE, &opts, key_def, NULL);
+		fake_index = index_def_new(pTab->def->id, 0,"fake_autoindex",
+					   sizeof("fake_autoindex") - 1,
+					   TREE, &opts, key_def, NULL);
 		key_def_delete(key_def);
-		/* Special marker for  non-existent index. */
-		fake_index.def->iid = UINT32_MAX;
-
-		if (fake_index.def == NULL) {
+		if (fake_index == NULL) {
 			pWInfo->pParse->nErr++;
 			pWInfo->pParse->rc = SQL_TARANTOOL_ERROR;
 			return SQL_TARANTOOL_ERROR;
 		}
+		/* Special marker for  non-existent index. */
+		fake_index->iid = UINT32_MAX;
 
 		struct index_stat *stat =
 			(struct index_stat *) malloc(sizeof(struct index_stat));
@@ -2869,16 +2857,9 @@ whereLoopAddBtree(WhereLoopBuilder * pBuilder,	/* WHERE clause information */
 			(log_est_t *) malloc(sizeof(log_est_t) * 2);
 		stat->tuple_log_est[0] = sql_space_tuple_log_count(pTab);
 		stat->tuple_log_est[1] = 0;
-		fake_index.def->opts.stat = stat;
+		fake_index->opts.stat = stat;
 
-		pFirst = pSrc->pTab->pIndex;
-		if (pSrc->fg.notIndexed == 0) {
-			/* The real indices of the table are only considered if the
-			 * NOT INDEXED qualifier is omitted from the FROM clause
-			 */
-			fake_index.pNext = pFirst;
-		}
-		pProbe = &fake_index;
+		probe = fake_index;
 	}
 
 #ifndef SQLITE_OMIT_AUTOMATIC_INDEX
@@ -2936,11 +2917,11 @@ whereLoopAddBtree(WhereLoopBuilder * pBuilder,	/* WHERE clause information */
 		}
 	}
 #endif				/* SQLITE_OMIT_AUTOMATIC_INDEX */
-
-	/* Loop over all indices
-	 */
-	for (; rc == SQLITE_OK && pProbe; pProbe = pProbe->pNext, iSortIdx++) {
-		rSize = index_field_tuple_est(pProbe, 0);
+	uint32_t idx_count = fake_index == NULL ? pTab->space->index_count : 1;
+	for (uint32_t i = 0; i < idx_count; iSortIdx++, i++) {
+		if (i > 0)
+			probe = pTab->space->index[i]->def;
+		rSize = index_field_tuple_est(probe, 0);
 		pNew->nEq = 0;
 		pNew->nBtm = 0;
 		pNew->nTop = 0;
@@ -2950,17 +2931,15 @@ whereLoopAddBtree(WhereLoopBuilder * pBuilder,	/* WHERE clause information */
 		pNew->rSetup = 0;
 		pNew->prereq = mPrereq;
 		pNew->nOut = rSize;
-		pNew->pIndex = pProbe;
-		b = indexMightHelpWithOrderBy(pBuilder, pProbe, pSrc->iCursor);
+		pNew->index_def = probe;
+		b = indexMightHelpWithOrderBy(pBuilder, probe, pSrc->iCursor);
 		/* The ONEPASS_DESIRED flags never occurs together with ORDER BY */
 		assert((pWInfo->wctrlFlags & WHERE_ONEPASS_DESIRED) == 0
 		       || b == 0);
-		if (pProbe->def->iid == UINT32_MAX) {
+		pNew->iSortIdx = b ? iSortIdx : 0;
+		if (probe->iid == UINT32_MAX) {
 			/* Integer primary key index */
 			pNew->wsFlags = WHERE_IPK;
-
-			/* Full table scan */
-			pNew->iSortIdx = b ? iSortIdx : 0;
 			/* TUNING: Cost of full table scan is (N*3.0). */
 			pNew->rRun = rSize + 16;
 			whereLoopOutputAdjust(pWC, pNew, rSize);
@@ -2970,9 +2949,6 @@ whereLoopAddBtree(WhereLoopBuilder * pBuilder,	/* WHERE clause information */
 				break;
 		} else {
 			pNew->wsFlags = WHERE_IDX_ONLY | WHERE_INDEXED;
-			/* Full scan via index */
-			pNew->iSortIdx = b ? iSortIdx : 0;
-
 			/* The cost of visiting the index rows is N*K, where K is
 			 * between 1.1 and 3.0 (3.0 and 4.0 for tarantool),
 			 * depending on the relative sizes of the
@@ -2982,7 +2958,7 @@ whereLoopAddBtree(WhereLoopBuilder * pBuilder,	/* WHERE clause information */
 			 * of secondary indexes, because secondary indexes
 			 * are not really store any data (only pointers to tuples).
 			 */
-			int notPkPenalty = sql_index_is_primary(pProbe) ? 0 : 4;
+			int notPkPenalty = probe->iid == 0 ? 0 : 4;
 			pNew->rRun = rSize + 16 + notPkPenalty;
 			whereLoopOutputAdjust(pWC, pNew, rSize);
 			rc = whereLoopInsert(pBuilder, pNew);
@@ -2991,7 +2967,7 @@ whereLoopAddBtree(WhereLoopBuilder * pBuilder,	/* WHERE clause information */
 				break;
 		}
 
-		rc = whereLoopAddBtreeIndex(pBuilder, pSrc, pProbe, 0);
+		rc = whereLoopAddBtreeIndex(pBuilder, pSrc, probe, 0);
 		sqlite3Stat4ProbeFree(pBuilder->pRec);
 		pBuilder->nRecValid = 0;
 		pBuilder->pRec = 0;
@@ -2999,13 +2975,15 @@ whereLoopAddBtree(WhereLoopBuilder * pBuilder,	/* WHERE clause information */
 		/* If there was an INDEXED BY clause, then only that one index is
 		 * considered.
 		 */
-		if (pSrc->pIBIndex)
+		if (pSrc->pIBIndex != NULL)
+			break;
+		if (fake_index != NULL)
 			break;
+
 	}
-	if (fake_index.def != NULL)
-	{
-		free(fake_index.def->opts.stat->tuple_log_est);
-		index_def_delete(fake_index.def);
+	if (fake_index != NULL) {
+		free(fake_index->opts.stat->tuple_log_est);
+		index_def_delete(fake_index);
 	}
 	return rc;
 }
@@ -3113,7 +3091,7 @@ whereLoopAddOr(WhereLoopBuilder * pBuilder, Bitmask mPrereq, Bitmask mUnusable)
 			pNew->nEq = 0;
 			pNew->nBtm = 0;
 			pNew->nTop = 0;
-			pNew->pIndex = NULL;
+			pNew->index_def = NULL;
 			for (i = 0; rc == SQLITE_OK && i < sSum.n; i++) {
 				/* TUNING: Currently sSum.a[i].rRun is set to the sum of the costs
 				 * of all sub-scans required by the OR-scan. However, due to rounding
@@ -3231,7 +3209,7 @@ wherePathSatisfiesOrderBy(WhereInfo * pWInfo,	/* The WHERE clause */
 	WhereLoop *pLoop = 0;	/* Current WhereLoop being processed. */
 	WhereTerm *pTerm;	/* A single term of the WHERE clause */
 	Expr *pOBExpr;		/* An expression from the ORDER BY clause */
-	Index *pIndex;		/* The index associated with pLoop */
+	struct index_def *pIndex;
 	sqlite3 *db = pWInfo->pParse->db;	/* Database connection */
 	Bitmask obSat = 0;	/* Mask of ORDER BY terms satisfied so far */
 	Bitmask obDone;		/* Mask of all ORDER BY terms */
@@ -3335,14 +3313,14 @@ wherePathSatisfiesOrderBy(WhereInfo * pWInfo,	/* The WHERE clause */
 
 		if ((pLoop->wsFlags & WHERE_ONEROW) == 0) {
 			if (pLoop->wsFlags & WHERE_IPK) {
-				pIndex = 0;
+				pIndex = NULL;
 				nColumn = 1;
-			} else if ((pIndex = pLoop->pIndex) == NULL ||
+			} else if ((pIndex = pLoop->index_def) == NULL ||
 				   index_is_unordered(pIndex)) {
 				return 0;
 			} else {
-				nColumn = pIndex->def->key_def->part_count;
-				isOrderDistinct = pIndex->def->opts.is_unique;
+				nColumn = pIndex->key_def->part_count;
+				isOrderDistinct = pIndex->opts.is_unique;
 			}
 
 			/* Loop through all columns of the index and deal with the ones
@@ -3403,8 +3381,7 @@ wherePathSatisfiesOrderBy(WhereInfo * pWInfo,	/* The WHERE clause */
 				 * (revIdx) for the j-th column of the index.
 				 */
 				if (pIndex != NULL) {
-					struct key_def *def =
-						pIndex->def->key_def;
+					struct key_def *def = pIndex->key_def;
 					iColumn = def->parts[j].fieldno;
 					revIdx = def->parts[j].sort_order;
 				} else {
@@ -3415,12 +3392,13 @@ wherePathSatisfiesOrderBy(WhereInfo * pWInfo,	/* The WHERE clause */
 				/* An unconstrained column that might be NULL means that this
 				 * WhereLoop is not well-ordered
 				 */
-				if (isOrderDistinct
-				    && iColumn >= 0
-				    && j >= pLoop->nEq
-				    && pIndex->pTable->def->fields[
-					iColumn].is_nullable) {
-					isOrderDistinct = 0;
+				if (isOrderDistinct && iColumn >= 0 &&
+				    j >= pLoop->nEq && pIndex != NULL) {
+					struct space *space =
+						space_by_id(pIndex->space_id);
+					assert(space != NULL);
+					if (space->def->fields[iColumn].is_nullable)
+						isOrderDistinct = 0;
 				}
 
 				/* Find the ORDER BY term that corresponds to the j-th column
@@ -3454,7 +3432,7 @@ wherePathSatisfiesOrderBy(WhereInfo * pWInfo,	/* The WHERE clause */
 								      pOrderBy->a[i].pExpr,
 								      &is_found, &id);
 						struct coll *idx_coll =
-							pIndex->def->key_def->parts[j].coll;
+							pIndex->key_def->parts[j].coll;
 						if (is_found &&
 						    coll != idx_coll)
 							continue;
@@ -4141,7 +4119,7 @@ where_loop_builder_shortcut(struct WhereLoopBuilder *builder)
 	struct WhereLoop *loop = builder->pNew;
 	loop->wsFlags = 0;
 	loop->nSkip = 0;
-	loop->pIndex = NULL;
+	loop->index_def = NULL;
 	struct WhereTerm *term = sqlite3WhereFindTerm(clause, cursor, -1, 0,
 						      WO_EQ, 0);
 	if (term != NULL) {
@@ -4634,7 +4612,6 @@ sqlite3WhereBegin(Parse * pParse,	/* The parser context */
 #endif
 		}
 		if (pLoop->wsFlags & WHERE_INDEXED) {
-			Index *pIx = pLoop->pIndex;
 			struct index_def *idx_def = pLoop->index_def;
 			struct space *space = space_cache_find(pTabItem->pTab->def->id);
 			int iIndexCur;
@@ -4651,10 +4628,10 @@ sqlite3WhereBegin(Parse * pParse,	/* The parser context */
 			 *    It is something w/ defined space_def
 			 *    and nothing else. Skip such loops.
 			 */
-			if (idx_def == NULL && pIx == NULL)
+			if (idx_def == NULL)
 				continue;
-			bool is_primary = (pIx != NULL && sql_index_is_primary(pIx)) ||
-					  (idx_def != NULL && (idx_def->iid == 0));
+			bool is_primary = (idx_def != NULL &&
+					   idx_def->iid == 0);
 			if (is_primary
 			    && (wctrlFlags & WHERE_OR_SUBCLAUSE) != 0) {
 				/* This is one term of an OR-optimization using
@@ -4663,28 +4640,26 @@ sqlite3WhereBegin(Parse * pParse,	/* The parser context */
 				iIndexCur = pLevel->iTabCur;
 				op = 0;
 			} else if (pWInfo->eOnePass != ONEPASS_OFF) {
-				if (pIx != NULL) {
-					Index *pJ = pTabItem->pTab->pIndex;
+				if (idx_def != NULL &&
+				    pTabItem->pTab->space->index_count != 0) {
+					uint32_t iid = 0;
+					struct index *pJ = pTabItem->pTab->space->index[iid];
 					iIndexCur = iAuxArg;
 					assert(wctrlFlags &
 					       WHERE_ONEPASS_DESIRED);
-					while (ALWAYS(pJ) && pJ != pIx) {
+					while (pJ->def->iid != idx_def->iid) {
 						iIndexCur++;
-						pJ = pJ->pNext;
+						iid++;
+						pJ = pTabItem->pTab->space->index[iid];
 					}
 				} else {
-					if (space != NULL) {
-						for(uint32_t i = 0;
-						    i < space->index_count;
-						    ++i) {
-							if (space->index[i]->def ==
-							    idx_def) {
-								iIndexCur = iAuxArg + i;
-								break;
-							}
+					for(uint32_t i = 0;
+					    i < space->index_count; ++i) {
+						if (space->index[i]->def ==
+						    idx_def) {
+							iIndexCur = iAuxArg + i;
+							break;
 						}
-					} else {
-						iIndexCur = iAuxArg;
 					}
 				}
 				assert(wctrlFlags & WHERE_ONEPASS_DESIRED);
@@ -4700,13 +4675,13 @@ sqlite3WhereBegin(Parse * pParse,	/* The parser context */
 			pLevel->iIdxCur = iIndexCur;
 			assert(iIndexCur >= 0);
 			if (op) {
-				if (pIx != NULL) {
+				if (idx_def != NULL) {
 					uint32_t space_id =
-						pIx->pTable->def->id;
+						idx_def->space_id;
 					struct space *space =
 						space_by_id(space_id);
 					vdbe_emit_open_cursor(pParse, iIndexCur,
-							      pIx->def->iid,
+							      idx_def->iid,
 							      space);
 				} else {
 					vdbe_emit_open_cursor(pParse, iIndexCur,
@@ -4721,9 +4696,7 @@ sqlite3WhereBegin(Parse * pParse,	/* The parser context */
 					wctrlFlags & WHERE_ORDERBY_MIN) == 0) {
 					sqlite3VdbeChangeP5(v, OPFLAG_SEEKEQ);	/* Hint to COMDB2 */
 				}
-				if (pIx != NULL)
-					VdbeComment((v, "%s", pIx->def->name));
-				else
+				if (idx_def != NULL)
 					VdbeComment((v, "%s", idx_def->name));
 #ifdef SQLITE_ENABLE_COLUMN_USED_MASK
 				{
@@ -4855,7 +4828,7 @@ sqlite3WhereEnd(WhereInfo * pWInfo)
 		if (pLevel->addrSkip) {
 			sqlite3VdbeGoto(v, pLevel->addrSkip);
 			VdbeComment((v, "next skip-scan on %s",
-				     pLoop->pIndex->def->name));
+				     pLoop->index_def->name));
 			sqlite3VdbeJumpHere(v, pLevel->addrSkip);
 			sqlite3VdbeJumpHere(v, pLevel->addrSkip - 2);
 		}
@@ -4932,15 +4905,13 @@ sqlite3WhereEnd(WhereInfo * pWInfo)
 		 * that reference the table and converts them into opcodes that
 		 * reference the index.
 		 */
-		Index *pIdx = NULL;
 		struct index_def *def = NULL;
 		if (pLoop->wsFlags & (WHERE_INDEXED | WHERE_IDX_ONLY)) {
-			pIdx = pLoop->pIndex;
 			def = pLoop->index_def;
 		} else if (pLoop->wsFlags & WHERE_MULTI_OR) {
-			pIdx = pLevel->u.pCovidx;
+			def = pLevel->u.pCovidx;
 		}
-		if ((pIdx != NULL || def != NULL) && !db->mallocFailed) {
+		if (def != NULL && !db->mallocFailed) {
 			last = sqlite3VdbeCurrentAddr(v);
 			k = pLevel->addrBody;
 			pOp = sqlite3VdbeGetOp(v, k);
@@ -4949,8 +4920,8 @@ sqlite3WhereEnd(WhereInfo * pWInfo)
 					continue;
 				if (pOp->opcode == OP_Column) {
 					int x = pOp->p2;
-					assert(pIdx == NULL ||
-					       pIdx->pTable == pTab);
+					assert(def == NULL ||
+					       def->space_id == pTab->def->id);
 					if (x >= 0) {
 						pOp->p2 = x;
 						pOp->p1 = pLevel->iIdxCur;
diff --git a/src/box/sql/whereInt.h b/src/box/sql/whereInt.h
index 548cbcb2a..99b36df13 100644
--- a/src/box/sql/whereInt.h
+++ b/src/box/sql/whereInt.h
@@ -102,7 +102,7 @@ struct WhereLevel {
 				u8 eEndLoopOp;	/* IN Loop terminator. OP_Next or OP_Prev */
 			} *aInLoop;	/* Information about each nested IN operator */
 		} in;		/* Used when pWLoop->wsFlags&WHERE_IN_ABLE */
-		Index *pCovidx;	/* Possible covering index for WHERE_MULTI_OR */
+		struct index_def *pCovidx;	/* Possible covering index for WHERE_MULTI_OR */
 	} u;
 	struct WhereLoop *pWLoop;	/* The selected WhereLoop object */
 	Bitmask notReady;	/* FROM entries not usable at this level */
@@ -140,8 +140,7 @@ struct WhereLoop {
 	u16 nEq;	/* Number of equality constraints */
 	u16 nBtm;	/* Size of BTM vector */
 	u16 nTop;	/* Size of TOP vector */
-	Index *pIndex;	/* Index used, or NULL */
-	/** Index definition, if there's no pIndex. */
+	/** Index definition. */
 	struct index_def *index_def;
 	u32 wsFlags;		/* WHERE_* flags describing the plan */
 	u16 nLTerm;		/* Number of entries in aLTerm[] */
@@ -452,8 +451,7 @@ WhereTerm *sqlite3WhereFindTerm(WhereClause * pWC,	/* The WHERE clause to be sea
 				int iColumn,	/* Column number of LHS */
 				Bitmask notReady,	/* RHS must not overlap with this mask */
 				u32 op,	/* Mask of WO_xx values describing operator */
-				Index * pIdx	/* Must be compatible with this index, if not NULL */
-    );
+				struct index_def *idx_def);
 
 /* wherecode.c: */
 int sqlite3WhereExplainOneScan(Parse * pParse,	/* Parse context */
diff --git a/src/box/sql/wherecode.c b/src/box/sql/wherecode.c
index 31c7a3bc4..628c6f9ad 100644
--- a/src/box/sql/wherecode.c
+++ b/src/box/sql/wherecode.c
@@ -46,10 +46,12 @@
  * Return the name of the i-th column of the pIdx index.
  */
 static const char *
-explainIndexColumnName(Index * pIdx, int i)
+explainIndexColumnName(const struct index_def *idx_def, int i)
 {
-	i = pIdx->def->key_def->parts[i].fieldno;
-	return pIdx->pTable->def->fields[i].name;
+	i = idx_def->key_def->parts[i].fieldno;
+	struct space *space = space_by_id(idx_def->space_id);
+	assert(space != NULL);
+	return space->def->fields[i].name;
 }
 
 /*
@@ -62,7 +64,6 @@ explainIndexColumnName(Index * pIdx, int i)
  */
 static void
 explainAppendTerm(StrAccum * pStr,	/* The text expression being built */
-		  Index * pIdx,		/* Index to read column names from */
 		  struct index_def *def,
 		  int nTerm,		/* Number of terms */
 		  int iTerm,		/* Zero-based index of first term. */
@@ -80,15 +81,9 @@ explainAppendTerm(StrAccum * pStr,	/* The text expression being built */
 	for (i = 0; i < nTerm; i++) {
 		if (i)
 			sqlite3StrAccumAppend(pStr, ",", 1);
-		const char *name;
-		if (pIdx != NULL) {
-			name = explainIndexColumnName(pIdx, iTerm + i);
-		} else {
-			assert(def != NULL);
-                        struct space *space = space_cache_find(def->space_id);
-                        assert(space != NULL);
-                        name = space->def->fields[i + iTerm].name;
-		}
+		const char *name = "";
+		if (def != NULL)
+			name = explainIndexColumnName(def, iTerm + i);
 		sqlite3StrAccumAppendAll(pStr, name);
 	}
 	if (nTerm > 1)
@@ -124,13 +119,12 @@ explainAppendTerm(StrAccum * pStr,	/* The text expression being built */
 static void
 explainIndexRange(StrAccum * pStr, WhereLoop * pLoop)
 {
-	Index *pIndex = pLoop->pIndex;
 	struct index_def *def = pLoop->index_def;
 	u16 nEq = pLoop->nEq;
 	u16 nSkip = pLoop->nSkip;
 	int i, j;
 
-	assert(pIndex != NULL || def != NULL);
+	assert(def != NULL);
 
 	if (nEq == 0
 	    && (pLoop->wsFlags & (WHERE_BTM_LIMIT | WHERE_TOP_LIMIT)) == 0)
@@ -138,8 +132,8 @@ explainIndexRange(StrAccum * pStr, WhereLoop * pLoop)
 	sqlite3StrAccumAppend(pStr, " (", 2);
 	for (i = 0; i < nEq; i++) {
 		const char *z;
-		if (pIndex != NULL) {
-			z = explainIndexColumnName(pIndex, i);
+		if (def != NULL) {
+			z = explainIndexColumnName(def, i);
 		} else {
 			struct space *space = space_cache_find(def->space_id);
 			assert(space != NULL);
@@ -153,11 +147,11 @@ explainIndexRange(StrAccum * pStr, WhereLoop * pLoop)
 
 	j = i;
 	if (pLoop->wsFlags & WHERE_BTM_LIMIT) {
-		explainAppendTerm(pStr, pIndex, def, pLoop->nBtm, j, i, ">");
+		explainAppendTerm(pStr, def, pLoop->nBtm, j, i, ">");
 		i = 1;
 	}
 	if (pLoop->wsFlags & WHERE_TOP_LIMIT) {
-		explainAppendTerm(pStr, pIndex, def, pLoop->nTop, j, i, "<");
+		explainAppendTerm(pStr, def, pLoop->nTop, j, i, "<");
 	}
 	sqlite3StrAccumAppend(pStr, ")", 1);
 }
@@ -219,15 +213,13 @@ sqlite3WhereExplainOneScan(Parse * pParse,	/* Parse context */
 		}
 		if ((flags & WHERE_IPK) == 0) {
 			const char *zFmt = 0;
-			Index *pIdx = pLoop->pIndex;
 			struct index_def *idx_def = pLoop->index_def;
-			if (pIdx == NULL && idx_def == NULL) return 0;
+			if (idx_def == NULL)
+				return 0;
 
-			assert(pIdx != NULL || idx_def != NULL);
 			assert(!(flags & WHERE_AUTO_INDEX)
 			       || (flags & WHERE_IDX_ONLY));
-			if ((pIdx != NULL && sql_index_is_primary(pIdx)) ||
-			    (idx_def != NULL && idx_def->iid == 0)) {
+			if (idx_def != NULL && idx_def->iid == 0) {
 				if (isSearch) {
 					zFmt = "PRIMARY KEY";
 				}
@@ -242,9 +234,7 @@ sqlite3WhereExplainOneScan(Parse * pParse,	/* Parse context */
 			}
 			if (zFmt) {
 				sqlite3StrAccumAppend(&str, " USING ", 7);
-				if (pIdx != NULL)
-					sqlite3XPrintf(&str, zFmt, pIdx->def->name);
-				else if (idx_def != NULL)
+				if (idx_def != NULL)
 					sqlite3XPrintf(&str, zFmt, idx_def->name);
 				else
 					sqlite3XPrintf(&str, zFmt, "EPHEMERAL INDEX");
@@ -487,8 +477,8 @@ codeEqualityTerm(Parse * pParse,	/* The parsing context */
 		int nEq = 0;
 		int *aiMap = 0;
 
-		if (pLoop->pIndex != 0 &&
-		    pLoop->pIndex->def->key_def->parts[iEq].sort_order) {
+		if (pLoop->index_def != 0 &&
+		    pLoop->index_def->key_def->parts[iEq].sort_order) {
 			testcase(iEq == 0);
 			testcase(bRev);
 			bRev = !bRev;
@@ -712,9 +702,8 @@ codeAllEqualityTerms(Parse * pParse,	/* Parsing context */
 	pLoop = pLevel->pWLoop;
 	nEq = pLoop->nEq;
 	nSkip = pLoop->nSkip;
-	struct Index *pIdx = pLoop->pIndex;
 	struct index_def *idx_def = pLoop->index_def;
-	assert(pIdx != NULL || idx_def != NULL);
+	assert(idx_def != NULL);
 
 	/* Figure out how many memory cells we will need then allocate them.
 	 */
@@ -722,20 +711,11 @@ codeAllEqualityTerms(Parse * pParse,	/* Parsing context */
 	nReg = pLoop->nEq + nExtraReg;
 	pParse->nMem += nReg;
 
-	char *zAff;
-	if (pIdx != NULL) {
-		struct space *space = space_by_id(pIdx->def->space_id);
-		assert(space != NULL);
-		zAff = sqlite3DbStrDup(pParse->db,
-				       sql_space_index_affinity_str(pParse->db,
-								    space->def,
-								    pIdx->def));
-	} else {
-		struct space *space = space_by_id(idx_def->space_id);
-		assert(space != NULL);
-		zAff = sql_space_index_affinity_str(pParse->db, space->def,
-						    idx_def);
-	}
+
+	struct space *space = space_by_id(idx_def->space_id);
+	assert(space != NULL);
+	char *zAff = sql_space_index_affinity_str(pParse->db, space->def,
+						  idx_def);
 	assert(zAff != 0 || pParse->db->mallocFailed);
 
 	if (nSkip) {
@@ -743,7 +723,7 @@ codeAllEqualityTerms(Parse * pParse,	/* Parsing context */
 		sqlite3VdbeAddOp1(v, (bRev ? OP_Last : OP_Rewind), iIdxCur);
 		VdbeCoverageIf(v, bRev == 0);
 		VdbeCoverageIf(v, bRev != 0);
-		VdbeComment((v, "begin skip-scan on %s", pIdx->def->name));
+		VdbeComment((v, "begin skip-scan on %s", idx_def->name));
 		j = sqlite3VdbeAddOp0(v, OP_Goto);
 		pLevel->addrSkip =
 		    sqlite3VdbeAddOp4Int(v, (bRev ? OP_SeekLT : OP_SeekGT),
@@ -753,9 +733,9 @@ codeAllEqualityTerms(Parse * pParse,	/* Parsing context */
 		sqlite3VdbeJumpHere(v, j);
 		for (j = 0; j < nSkip; j++) {
 			sqlite3VdbeAddOp3(v, OP_Column, iIdxCur,
-					  pIdx->def->key_def->parts[j].fieldno,
+					  idx_def->key_def->parts[j].fieldno,
 					  regBase + j);
-			VdbeComment((v, "%s", explainIndexColumnName(pIdx, j)));
+			VdbeComment((v, "%s", explainIndexColumnName(idx_def, j)));
 		}
 	}
 
@@ -1028,9 +1008,11 @@ sqlite3WhereCodeOneLoopStart(WhereInfo * pWInfo,	/* Complete information about t
 					      * to integer type, used for IPK.
 					      */
 
-		struct Index *pIdx = pLoop->pIndex;
 		struct index_def *idx_def = pLoop->index_def;
-		assert(pIdx != NULL || idx_def != NULL);
+		assert(idx_def != NULL);
+		struct space *space = space_by_id(idx_def->space_id);
+		assert(space != NULL);
+		bool is_format_set = space->def->field_count != 0;
 		iIdxCur = pLevel->iIdxCur;
 		assert(nEq >= pLoop->nSkip);
 
@@ -1045,14 +1027,10 @@ sqlite3WhereCodeOneLoopStart(WhereInfo * pWInfo,	/* Complete information about t
 		assert(pWInfo->pOrderBy == 0
 		       || pWInfo->pOrderBy->nExpr == 1
 		       || (pWInfo->wctrlFlags & WHERE_ORDERBY_MIN) == 0);
-		uint32_t part_count;
-		if (pIdx != NULL)
-			part_count = pIdx->def->key_def->part_count;
-		else
-			part_count = idx_def->key_def->part_count;
+		uint32_t part_count = idx_def->key_def->part_count;
 		if ((pWInfo->wctrlFlags & WHERE_ORDERBY_MIN) != 0 &&
 		    pWInfo->nOBSat > 0 && part_count > nEq) {
-			j = pIdx->def->key_def->parts[nEq].fieldno;
+			j = idx_def->key_def->parts[nEq].fieldno;
 			/* Allow seek for column with `NOT NULL` == false attribute.
 			 * If a column may contain NULL-s, the comparator installed
 			 * by Tarantool is prepared to seek using a NULL value.
@@ -1063,7 +1041,8 @@ sqlite3WhereCodeOneLoopStart(WhereInfo * pWInfo,	/* Complete information about t
 			 * FYI: entries in an index are ordered as follows:
 			 *      NULL, ... NULL, min_value, ...
 			 */
-			if (pIdx->pTable->def->fields[j].is_nullable) {
+			if (is_format_set &&
+			    space->def->fields[j].is_nullable) {
 				assert(pLoop->nSkip == 0);
 				bSeekPastNull = 1;
 				nExtraReg = 1;
@@ -1100,7 +1079,7 @@ sqlite3WhereCodeOneLoopStart(WhereInfo * pWInfo,	/* Complete information about t
 				testcase(pIdx->aSortOrder[nEq] ==
 					 SORT_ORDER_DESC);
 				assert((bRev & ~1) == 0);
-				struct key_def *def = pIdx->def->key_def;
+				struct key_def *def = idx_def->key_def;
 				pLevel->iLikeRepCntr <<= 1;
 				pLevel->iLikeRepCntr |=
 					bRev ^ (def->parts[nEq].sort_order ==
@@ -1108,8 +1087,9 @@ sqlite3WhereCodeOneLoopStart(WhereInfo * pWInfo,	/* Complete information about t
 			}
 #endif
 			if (pRangeStart == 0) {
-				j = pIdx->def->key_def->parts[nEq].fieldno;
-				if (pIdx->pTable->def->fields[j].is_nullable)
+				j = idx_def->key_def->parts[nEq].fieldno;
+				if (is_format_set &&
+				    space->def->fields[j].is_nullable)
 					bSeekPastNull = 1;
 			}
 		}
@@ -1121,7 +1101,7 @@ sqlite3WhereCodeOneLoopStart(WhereInfo * pWInfo,	/* Complete information about t
 		 * start and end terms (pRangeStart and pRangeEnd).
 		 */
 		if ((nEq < part_count &&
-		     bRev == (pIdx->def->key_def->parts[nEq].sort_order ==
+		     bRev == (idx_def->key_def->parts[nEq].sort_order ==
 			      SORT_ORDER_ASC)) || (bRev && part_count == nEq)) {
 			SWAP(pRangeEnd, pRangeStart);
 			SWAP(bSeekPastNull, bStopAtNull);
@@ -1183,34 +1163,21 @@ sqlite3WhereCodeOneLoopStart(WhereInfo * pWInfo,	/* Complete information about t
 			startEq = 0;
 			start_constraints = 1;
 		}
-		struct Index *pk = NULL;
-		struct index_def *idx_pk = NULL;
-		char affinity;
-		if (pIdx == NULL) {
-			struct space *space = space_cache_find(idx_def->space_id);
-			assert(space != NULL);
-			idx_pk = space->index[0]->def;
-			int fieldno = idx_pk->key_def->parts[0].fieldno;
-			affinity = space->def->fields[fieldno].affinity;
-			if (affinity == AFFINITY_UNDEFINED) {
-				if (idx_pk->key_def->part_count == 1 &&
-				    space->def->fields[fieldno].type ==
-				    FIELD_TYPE_INTEGER)
-					affinity = AFFINITY_INTEGER;
-				else
-					affinity = AFFINITY_BLOB;
-			}
-		} else {
-			pk = sqlite3PrimaryKeyIndex(pIdx->pTable);
-			uint32_t fieldno = pk->def->key_def->parts[0].fieldno;
-			affinity = pIdx->pTable->def->fields[fieldno].affinity;
+		struct index_def *idx_pk = space->index[0]->def;
+		int fieldno = idx_pk->key_def->parts[0].fieldno;
+		char affinity = is_format_set ?
+				space->def->fields[fieldno].affinity :
+				AFFINITY_BLOB;
+		if (affinity == AFFINITY_UNDEFINED) {
+			if (idx_pk->key_def->part_count == 1 &&
+			    space->def->fields[fieldno].type ==
+			    FIELD_TYPE_INTEGER)
+				affinity = AFFINITY_INTEGER;
+			else
+				affinity = AFFINITY_BLOB;
 		}
 
-		uint32_t pk_part_count;
-		if (pk != NULL)
-			pk_part_count = pk->def->key_def->part_count;
-		else
-			pk_part_count = idx_pk->key_def->part_count;
+		uint32_t pk_part_count = idx_pk->key_def->part_count;
 		if (pk_part_count == 1 && affinity == AFFINITY_INTEGER) {
 			/* Right now INTEGER PRIMARY KEY is the only option to
 			 * get Tarantool's INTEGER column type. Need special handling
@@ -1219,12 +1186,8 @@ sqlite3WhereCodeOneLoopStart(WhereInfo * pWInfo,	/* Complete information about t
 			 */
 			int limit = pRangeStart == NULL ? nEq : nEq + 1;
 			for (int i = 0; i < limit; i++) {
-				if ((pIdx != NULL &&
-				     pIdx->def->key_def->parts[i].fieldno ==
-				     pk->def->key_def->parts[0].fieldno) ||
-				    (idx_pk != NULL &&
-				     idx_def->key_def->parts[i].fieldno ==
-				     idx_pk->key_def->parts[0].fieldno)) {
+				if (idx_def->key_def->parts[i].fieldno ==
+				     idx_pk->key_def->parts[0].fieldno) {
 					/* Here: we know for sure that table has INTEGER
 					   PRIMARY KEY, single column, and Index we're
 					   trying to use for scan contains this column. */
@@ -1332,7 +1295,7 @@ sqlite3WhereCodeOneLoopStart(WhereInfo * pWInfo,	/* Complete information about t
 		if (omitTable) {
 			/* pIdx is a covering index.  No need to access the main table. */
 		}  else if (iCur != iIdxCur) {
-			Index *pPk = sqlite3PrimaryKeyIndex(pIdx->pTable);
+			struct index *pPk = space->index_map[0];
 			int pk_part_count = pPk->def->key_def->part_count;
 			int iKeyReg = sqlite3GetTempRange(pParse, pk_part_count);
 			for (j = 0; j < pk_part_count; j++) {
@@ -1381,7 +1344,7 @@ sqlite3WhereCodeOneLoopStart(WhereInfo * pWInfo,	/* Complete information about t
 		 */
 		WhereClause *pOrWc;	/* The OR-clause broken out into subterms */
 		SrcList *pOrTab;	/* Shortened table list or OR-clause generation */
-		Index *pCov = 0;	/* Potential covering index (or NULL) */
+		struct index_def *pCov = 0;	/* Potential covering index (or NULL) */
 		int iCovCur = pParse->nTab++;	/* Cursor used for index scans (if any) */
 
 		int regReturn = ++pParse->nMem;	/* Register used with OP_Gosub */
@@ -1440,12 +1403,12 @@ sqlite3WhereCodeOneLoopStart(WhereInfo * pWInfo,	/* Complete information about t
 		 * called on an uninitialized cursor.
 		 */
 		if ((pWInfo->wctrlFlags & WHERE_DUPLICATES_OK) == 0) {
-			Index *pPk = sqlite3PrimaryKeyIndex(pTab);
+			struct index *pPk = sql_table_primary_key(pTab);
 			int pk_part_count = pPk->def->key_def->part_count;
 			regRowset = pParse->nTab++;
 			sqlite3VdbeAddOp2(v, OP_OpenTEphemeral,
 					  regRowset, pk_part_count);
-			sql_vdbe_set_p4_key_def(pParse, pPk);
+			sql_vdbe_set_p4_key_def(pParse, pPk->def);
 			regPk = ++pParse->nMem;
 		}
 		iRetInit = sqlite3VdbeAddOp2(v, OP_Integer, 0, regReturn);
@@ -1544,7 +1507,8 @@ sqlite3WhereCodeOneLoopStart(WhereInfo * pWInfo,	/* Complete information about t
 						int r;
 						int iSet =
 						    ((ii == pOrWc->nTerm - 1) ? -1 : ii);
-						Index *pPk = sqlite3PrimaryKeyIndex (pTab);
+						struct index *pPk =
+							sql_table_primary_key(pTab);
 						struct key_def *def =
 							pPk->def->key_def;
 
@@ -1631,11 +1595,12 @@ sqlite3WhereCodeOneLoopStart(WhereInfo * pWInfo,	/* Complete information about t
 					pSubLoop = pSubWInfo->a[0].pWLoop;
 					assert((pSubLoop->wsFlags & WHERE_AUTO_INDEX) == 0);
 					if ((pSubLoop->wsFlags & WHERE_INDEXED) != 0
-					    && (ii == 0 || pSubLoop->pIndex == pCov)
-					    && !sql_index_is_primary(pSubLoop->pIndex)) {
+					    && (ii == 0 || (pCov != NULL &&
+						pSubLoop->index_def->iid == pCov->iid))
+					    && (pSubLoop->index_def->iid != 0)) {
 						assert(pSubWInfo->a[0].
 						       iIdxCur == iCovCur);
-						pCov = pSubLoop->pIndex;
+						pCov = pSubLoop->index_def;
 					} else {
 						pCov = 0;
 					}
diff --git a/test/sql-tap/analyze3.test.lua b/test/sql-tap/analyze3.test.lua
index 8c5fbf197..b879429d4 100755
--- a/test/sql-tap/analyze3.test.lua
+++ b/test/sql-tap/analyze3.test.lua
@@ -603,8 +603,8 @@ test:do_test(
         test:execsql([[
             DROP INDEX IF EXISTS i1 ON t1;
             DROP INDEX IF EXISTS i2 ON t1;
-            CREATE INDEX i1 ON t1(a, b);
             CREATE INDEX i2 ON t1(c);
+            CREATE INDEX i1 ON t1(a, b);
         ]])
         return test:execsql("ANALYZE")
     end, {
diff --git a/test/sql-tap/analyze7.test.lua b/test/sql-tap/analyze7.test.lua
index 98bfb08dd..81e1eb410 100755
--- a/test/sql-tap/analyze7.test.lua
+++ b/test/sql-tap/analyze7.test.lua
@@ -162,8 +162,8 @@ test:do_execsql_test(
 		-- After running second ANALYZE query, there are equal statistics for
 		-- indexes t1a and t1b, so it doesn't really matter which index planner uses.
         -- <analyze7-3.3>
-        -- 0, 0, 0, "SEARCH TABLE t1 USING COVERING INDEX t1a (a=?)"
-        0, 0, 0, "SEARCH TABLE T1 USING COVERING INDEX T1B (B=?)"
+        0, 0, 0, "SEARCH TABLE T1 USING COVERING INDEX T1A (A=?)"
+        --0, 0, 0, "SEARCH TABLE T1 USING COVERING INDEX T1B (B=?)"
         -- </analyze7-3.3>
     })
 
@@ -173,7 +173,7 @@ test:do_execsql_test(
 		EXPLAIN QUERY PLAN SELECT * FROM t1 WHERE c=123 AND b=123;
 	]], {
         -- <analyze7-3.4>
-        0, 0, 0, "SEARCH TABLE T1 USING COVERING INDEX T1B (B=?)"
+        0, 0, 0, "SEARCH TABLE T1 USING COVERING INDEX T1CD (C=?)"
         -- </analyze7-3.4>
     })
 
@@ -183,7 +183,7 @@ test:do_execsql_test(
 		EXPLAIN QUERY PLAN SELECT * FROM t1 WHERE c=123 AND d=123 AND b=123;
 	]], {
        -- <analyze7-3.6>
-       0, 0, 0, "SEARCH TABLE T1 USING COVERING INDEX T1B (B=?)"
+       0, 0, 0, "SEARCH TABLE T1 USING COVERING INDEX T1CD (C=? AND D=?)"
        -- </analyze7-3.6>
     })
 
diff --git a/test/sql-tap/analyze9.test.lua b/test/sql-tap/analyze9.test.lua
index 1dbfe5d2b..2b37e3ad5 100755
--- a/test/sql-tap/analyze9.test.lua
+++ b/test/sql-tap/analyze9.test.lua
@@ -678,8 +678,8 @@ test:do_execsql_test(
     "11.0",
     [[
         CREATE TABLE t4(id INTEGER PRIMARY KEY AUTOINCREMENT, a COLLATE "unicode_ci", b);
-        CREATE INDEX t4a ON t4(a);
         CREATE INDEX t4b ON t4(b);
+        CREATE INDEX t4a ON t4(a);
     ]], {
         -- <11.0>
         -- </11.0>
@@ -729,8 +729,8 @@ test:do_execsql_test(
     [[
         DROP TABLE IF EXISTS t4;
         CREATE TABLE t4(id INTEGER PRIMARY KEY AUTOINCREMENT, a, b);
-        CREATE INDEX t4a ON t4(a COLLATE "unicode_ci");
         CREATE INDEX t4b ON t4(b);
+        CREATE INDEX t4a ON t4(a COLLATE "unicode_ci");
     ]], {
         -- <11.4>
         -- </11.4>
@@ -790,8 +790,8 @@ test:do_execsql_test(
     [[
         DROP TABLE IF EXISTS t4;
         CREATE TABLE t4(id INTEGER PRIMARY KEY AUTOINCREMENT, x, a COLLATE "unicode_ci", b);
-        CREATE INDEX t4a ON t4(x, a);
         CREATE INDEX t4b ON t4(x, b);
+        CREATE INDEX t4a ON t4(x, a);
     ]], {
         -- <12.0>
         -- </12.0>
@@ -841,8 +841,8 @@ test:do_execsql_test(
     [[
         DROP TABLE IF EXISTS t4;
         CREATE TABLE t4(id INTEGER PRIMARY KEY AUTOINCREMENT, x, a, b);
-        CREATE INDEX t4a ON t4(x, a COLLATE "unicode_ci");
         CREATE INDEX t4b ON t4(x, b);
+        CREATE INDEX t4a ON t4(x, a COLLATE "unicode_ci");
     ]], {
         -- <12.4>
         -- </12.4>
@@ -1169,7 +1169,7 @@ test:do_execsql_test(
         EXPLAIN QUERY PLAN SELECT * FROM t1 WHERE d IS NOT NULL AND a=0 AND b=10 AND c=10;
     ]], {
         -- <17.5>
-        0, 0, 0, "SEARCH TABLE T1 USING COVERING INDEX I2 (C=? AND D>?)"
+	0, 0, 0, "SEARCH TABLE T1 USING COVERING INDEX I1 (A=? AND B=?)"
         -- </17.5>
     })
 
diff --git a/test/sql-tap/analyzeF.test.lua b/test/sql-tap/analyzeF.test.lua
index 10cb574bd..e043c8f0e 100755
--- a/test/sql-tap/analyzeF.test.lua
+++ b/test/sql-tap/analyzeF.test.lua
@@ -30,8 +30,8 @@ test:do_execsql_test(
     	DROP TABLE IF EXISTS t1;
         CREATE TABLE t1(id PRIMARY KEY, x INTEGER, y INTEGER);
         WITH data(i) AS (SELECT 1 UNION ALL SELECT i+1 FROM data) INSERT INTO t1 SELECT i, isqrt(i), isqrt(i) FROM data LIMIT 500;
-        CREATE INDEX t1x ON t1(x);
         CREATE INDEX t1y ON t1(y);
+        CREATE INDEX t1x ON t1(x);
         ANALYZE;
     ]])
 
diff --git a/test/sql-tap/eqp.test.lua b/test/sql-tap/eqp.test.lua
index 15d428814..8a2c5e269 100755
--- a/test/sql-tap/eqp.test.lua
+++ b/test/sql-tap/eqp.test.lua
@@ -551,7 +551,7 @@ test:do_execsql_test(
 test:do_eqp_test("5.3.1", "SELECT a, b FROM t1 WHERE a=1", {
     -- It is equal for tarantol wheather to use i1 or i2
     -- because both of them are covering
-    {0, 0, 0, "SEARCH TABLE T1 USING COVERING INDEX I2 (A=?)"},
+    {0, 0, 0, "SEARCH TABLE T1 USING COVERING INDEX I1 (A=?)"},
     --{0, 0, 0, "SEARCH TABLE T1 USING COVERING INDEX I1 (A=?)"},
 })
 -- EVIDENCE-OF: R-09991-48941 sqlite> EXPLAIN QUERY PLAN
@@ -592,8 +592,8 @@ test:do_execsql_test(
 test:do_eqp_test("5.6.1", "SELECT a, b FROM t1 WHERE a=1 OR b=2", {
     -- It is equal for tarantol wheather to use i1 or i2
     -- because both of them are covering
-    {0, 0, 0, "SEARCH TABLE T1 USING COVERING INDEX I2 (A=?)"},
-    --{0, 0, 0, "SEARCH TABLE T1 USING COVERING INDEX I1 (A=?)"},
+    --{0, 0, 0, "SEARCH TABLE T1 USING COVERING INDEX I2 (A=?)"},
+    {0, 0, 0, "SEARCH TABLE T1 USING COVERING INDEX I1 (A=?)"},
     {0, 0, 0, "SEARCH TABLE T1 USING COVERING INDEX I3 (B=?)"},
 })
 -- EVIDENCE-OF: R-24577-38891 sqlite> EXPLAIN QUERY PLAN
@@ -633,8 +633,8 @@ test:do_eqp_test(5.9, [[
     {0, 0, 0, "EXECUTE SCALAR SUBQUERY 1"},
     -- It is equally for tarantol wheather to use i1 or i2
     -- because both of them are covering
-    {1, 0, 0, "SEARCH TABLE T1 USING COVERING INDEX I2 (A=?)"},
-    --{1, 0, 0, "SEARCH TABLE T1 USING COVERING INDEX I1 (A=?)"},
+    --{1, 0, 0, "SEARCH TABLE T1 USING COVERING INDEX I2 (A=?)"},
+    {1, 0, 0, "SEARCH TABLE T1 USING COVERING INDEX I1 (A=?)"},
     {0, 0, 0, "EXECUTE CORRELATED SCALAR SUBQUERY 2"},
     {2, 0, 0, "SEARCH TABLE T1 USING COVERING INDEX I3 (B=?)"},
 })
@@ -647,7 +647,7 @@ test:do_eqp_test(5.9, [[
 test:do_eqp_test(5.10, [[
   SELECT count(*) FROM (SELECT max(b) AS x FROM t1 GROUP BY a) GROUP BY x
 ]], {
-    {1, 0, 0, "SCAN TABLE T1 USING COVERING INDEX I2"},
+    {1, 0, 0, "SCAN TABLE T1 USING COVERING INDEX I1"},
     {0, 0, 0, "SCAN SUBQUERY 1"},
     {0, 0, 0, "USE TEMP B-TREE FOR GROUP BY"},
 })
@@ -678,7 +678,7 @@ test:do_eqp_test(5.12, "SELECT a,b FROM t1 UNION SELECT c, 99 FROM t2", {
 -- 0|0|0|COMPOUND SUBQUERIES 1 AND 2 (EXCEPT)
 --
 test:do_eqp_test(5.13, "SELECT a FROM t1 EXCEPT SELECT d FROM t2 ORDER BY 1", {
-    {1, 0, 0, "SCAN TABLE T1 USING COVERING INDEX I2"},
+    {1, 0, 0, "SCAN TABLE T1 USING COVERING INDEX I1"},
     {2, 0, 0, "SCAN TABLE T2"},
     {2, 0, 0, "USE TEMP B-TREE FOR ORDER BY"},
     {0, 0, 0, "COMPOUND SUBQUERIES 1 AND 2 (EXCEPT)"},
diff --git a/test/sql-tap/gh-2996-indexed-by.test.lua b/test/sql-tap/gh-2996-indexed-by.test.lua
index 2525e46e0..4b1dae4b4 100755
--- a/test/sql-tap/gh-2996-indexed-by.test.lua
+++ b/test/sql-tap/gh-2996-indexed-by.test.lua
@@ -8,8 +8,8 @@ test:plan(13)
 
 test:execsql [[
     CREATE TABLE t1(a INT PRIMARY KEY, b);
-    CREATE INDEX t1ix1 ON t1(b);
     CREATE INDEX t1ix2 on t1(b);
+    CREATE INDEX t1ix1 on t1(b);
 ]]
 
 sample_size = 1000
@@ -74,8 +74,8 @@ test:do_catchsql_test(
 
 -- Make sure that DELETE statement works correctly with INDEXED BY.
 test:execsql [[
-    CREATE INDEX t1ix1 ON t1(b);
-    CREATE INDEX t1ix2 on t1(b);
+    CREATE INDEX t1ix2 ON t1(b);
+    CREATE INDEX t1ix1 on t1(b);
 ]]
 
 test:do_eqp_test(
@@ -116,8 +116,8 @@ test:do_catchsql_test(
     })
 
 test:execsql [[
-    CREATE INDEX t1ix1 ON t1(b);
-    CREATE INDEX t1ix2 ON t1(b);
+   CREATE INDEX t1ix2 ON t1(b);
+   CREATE INDEX t1ix1 ON t1(b);
 ]]
 
 test:do_eqp_test(
diff --git a/test/sql-tap/lua-tables.test.lua b/test/sql-tap/lua-tables.test.lua
index 6177839d8..0e79c61a8 100755
--- a/test/sql-tap/lua-tables.test.lua
+++ b/test/sql-tap/lua-tables.test.lua
@@ -24,11 +24,11 @@ test:do_test(
 
 test:do_execsql_test(
     "lua-tables-2",
-    [[SELECT *, count(*)
+    [[SELECT *
         FROM "t" as t1, "t" as t2
         WHERE t1."id" = t2."f2"
     ]],
-    {4, 3, 1, 4, 4})
+    {4, 3, 1, 4, 2, 2, 2, 2, 3, 3, 3, 3, 3, 3, 4, 3})
 
 test:do_execsql_test(
     "lua-tables-3",
@@ -119,9 +119,9 @@ test:do_execsql_test(
      "3",3,"Elem3",4,"","","",0,1,
      "4",4,"Elem4",5,"","","",0,1})
 
-test:do_catchsql_test(
+test:do_execsql_test(
     "lua-tables-9",
     [[SELECT * FROM "t" INDEXED BY "i"]],
-    {1,"no such index: i"})
+    {1, 4, 2, 2, 3, 3, 4, 3})
 
 test:finish_test()
-- 
2.15.1





More information about the Tarantool-patches mailing list