Tarantool development patches archive
 help / color / mirror / Atom feed
* [tarantool-patches] [PATCH 0/7] Finish SQL DD integration
@ 2018-08-23 22:55 Nikita Pettik
       [not found] ` <cover.1535064700.git.korablev@tarantool.org>
  0 siblings, 1 reply; 28+ messages in thread
From: Nikita Pettik @ 2018-08-23 22:55 UTC (permalink / raw)
  To: tarantool-patches; +Cc: v.shpilevoy, Nikita Pettik

Branch: https://github.com/tarantool/tarantool/tree/np/gh-3561-finish-DD-integration
Issue:
https://github.com/tarantool/tarantool/issues/3561
https://github.com/tarantool/tarantool/issues/2217

Current patch-set is last in series and finishes SQL DD integration.

In second patch we get rid of SQLite struct index. Instead, we move
struct space to struct Table and maintain array of surrogate indexes,
which in turn basically contain only index_def's. Also, it allowed
to use indef_def throughout query planner.

Fifth patch substitutes lookups in table hash with lookups in space
hash and wrapping that space in struct Table in order to avoid enormous
code refactoring.

Finally, in the last patch a lot of SQLite specific routines have
been removed: callback function invoked during OP_ParseSchema and
opcode itself; OP_DropIndex and OP_DropTable; struct Schema and
so forth.

Nikita Pettik (7):
  sql: remove struct schema from struct Table
  sql: remove SQLite original struct Index
  sql: remove struct Table from analyze routine
  sql: refactor ALTER RENAME code generation
  sql: remove lookups in Table hash
  sql: don't add system spaces to Table hash
  sql: finish DD integration

 src/box/alter.cc                         |  15 +-
 src/box/sql.c                            | 193 ++-----
 src/box/sql.h                            |   3 +-
 src/box/sql/alter.c                      | 114 ++--
 src/box/sql/analyze.c                    | 233 ++++----
 src/box/sql/build.c                      | 918 +++++++------------------------
 src/box/sql/callback.c                   |  42 --
 src/box/sql/delete.c                     |  96 ++--
 src/box/sql/expr.c                       | 123 +----
 src/box/sql/fkey.c                       |  15 +-
 src/box/sql/insert.c                     | 134 ++---
 src/box/sql/main.c                       |   1 -
 src/box/sql/parse.y                      |  10 +-
 src/box/sql/pragma.c                     | 355 ++++++------
 src/box/sql/prepare.c                    | 131 -----
 src/box/sql/select.c                     |  58 +-
 src/box/sql/sqliteInt.h                  | 151 ++---
 src/box/sql/status.c                     |  16 -
 src/box/sql/tarantoolInt.h               |  54 +-
 src/box/sql/trigger.c                    |  15 +-
 src/box/sql/update.c                     |  13 +-
 src/box/sql/vdbe.c                       | 100 ----
 src/box/sql/vdbe.h                       |   7 +-
 src/box/sql/vdbeaux.c                    |  12 +-
 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/alter.test.lua              |   4 +-
 test/sql-tap/analyze1.test.lua           |   2 +-
 test/sql-tap/analyze3.test.lua           |   2 +-
 test/sql-tap/analyze7.test.lua           |   8 +-
 test/sql-tap/analyze9.test.lua           |  12 +-
 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/index1.test.lua             |   2 +-
 test/sql-tap/join.test.lua               |   6 +-
 test/sql-tap/lua-tables.test.lua         |   8 +-
 test/sql/delete.result                   |   4 +-
 40 files changed, 1121 insertions(+), 2346 deletions(-)

-- 
2.15.1

^ permalink raw reply	[flat|nested] 28+ messages in thread

* [tarantool-patches] [PATCH 1/7] sql: remove struct schema from struct Table
       [not found] ` <cover.1535064700.git.korablev@tarantool.org>
@ 2018-08-23 22:55   ` Nikita Pettik
  2018-08-29  0:58     ` [tarantool-patches] " Vladislav Shpilevoy
  2018-09-16 19:32     ` Vladislav Shpilevoy
  2018-08-23 22:55   ` [tarantool-patches] [PATCH 2/7] sql: remove SQLite original struct Index Nikita Pettik
                     ` (5 subsequent siblings)
  6 siblings, 2 replies; 28+ messages in thread
From: Nikita Pettik @ 2018-08-23 22:55 UTC (permalink / raw)
  To: tarantool-patches; +Cc: v.shpilevoy, Nikita Pettik

Part of #3561
---
 src/box/sql/build.c     | 7 ++-----
 src/box/sql/select.c    | 2 +-
 src/box/sql/sqliteInt.h | 1 -
 3 files changed, 3 insertions(+), 7 deletions(-)

diff --git a/src/box/sql/build.c b/src/box/sql/build.c
index dc00b5d8c..47fa7c305 100644
--- a/src/box/sql/build.c
+++ b/src/box/sql/build.c
@@ -392,8 +392,6 @@ sqlite3PrimaryKeyIndex(Table * pTab)
 static Table *
 sql_table_new(Parse *parser, char *name)
 {
-	sqlite3 *db = parser->db;
-
 	struct Table *table = sql_ephemeral_table_new(parser, name);
 	if (table == NULL)
 		return NULL;
@@ -401,7 +399,6 @@ sql_table_new(Parse *parser, char *name)
 	strcpy(table->def->engine_name,
 	       sql_storage_engine_strs[current_session()->sql_default_engine]);
 
-	table->pSchema = db->pSchema;
 	table->nTabRef = 1;
 	return table;
 }
@@ -1650,8 +1647,8 @@ sqlite3EndTable(Parse * pParse,	/* Parse context */
 		 * Add the table to the in-memory representation
 		 * of the database.
 		 */
-		struct Table *pOld = sqlite3HashInsert(&p->pSchema->tblHash,
-							p->def->name, p);
+		struct Table *pOld = sqlite3HashInsert(&db->pSchema->tblHash,
+						       p->def->name, p);
 		if (pOld != NULL) {
 			assert(p == pOld);
 			sqlite3OomFault(db);
diff --git a/src/box/sql/select.c b/src/box/sql/select.c
index d22f4e0a9..12f55cedf 100644
--- a/src/box/sql/select.c
+++ b/src/box/sql/select.c
@@ -1672,7 +1672,7 @@ columnTypeImpl(NameContext * pNC, Expr * pExpr
 					    columnType(&sNC, p, &zOrigTab,
 						       &zOrigCol);
 				}
-			} else if (pTab->pSchema) {
+			} else {
 				/* A real table */
 				assert(!pS);
 				assert(iCol >= 0 &&
diff --git a/src/box/sql/sqliteInt.h b/src/box/sql/sqliteInt.h
index d2ef85846..35d3f4cec 100644
--- a/src/box/sql/sqliteInt.h
+++ b/src/box/sql/sqliteInt.h
@@ -1857,7 +1857,6 @@ struct Table {
 	 * can be fetched from space struct.
 	 */
 	LogEst tuple_log_count;
-	Schema *pSchema;	/* Schema that contains this table */
 	Table *pNextZombie;	/* Next on the Parse.pZombieTab list */
 	/** Space definition with Tarantool metadata. */
 	struct space_def *def;
-- 
2.15.1

^ permalink raw reply	[flat|nested] 28+ messages in thread

* [tarantool-patches] [PATCH 2/7] sql: remove SQLite original struct Index
       [not found] ` <cover.1535064700.git.korablev@tarantool.org>
  2018-08-23 22:55   ` [tarantool-patches] [PATCH 1/7] sql: remove struct schema from struct Table Nikita Pettik
@ 2018-08-23 22:55   ` Nikita Pettik
  2018-08-29  0:58     ` [tarantool-patches] " Vladislav Shpilevoy
  2018-08-23 22:55   ` [tarantool-patches] [PATCH 3/7] sql: remove struct Table from analyze routine Nikita Pettik
                     ` (4 subsequent siblings)
  6 siblings, 1 reply; 28+ messages in thread
From: Nikita Pettik @ 2018-08-23 22:55 UTC (permalink / raw)
  To: tarantool-patches; +Cc: v.shpilevoy, Nikita Pettik

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

^ permalink raw reply	[flat|nested] 28+ messages in thread

* [tarantool-patches] [PATCH 3/7] sql: remove struct Table from analyze routine
       [not found] ` <cover.1535064700.git.korablev@tarantool.org>
  2018-08-23 22:55   ` [tarantool-patches] [PATCH 1/7] sql: remove struct schema from struct Table Nikita Pettik
  2018-08-23 22:55   ` [tarantool-patches] [PATCH 2/7] sql: remove SQLite original struct Index Nikita Pettik
@ 2018-08-23 22:55   ` Nikita Pettik
  2018-08-29  0:58     ` [tarantool-patches] " Vladislav Shpilevoy
  2018-08-23 22:55   ` [tarantool-patches] [PATCH 4/7] sql: refactor ALTER RENAME code generation Nikita Pettik
                     ` (3 subsequent siblings)
  6 siblings, 1 reply; 28+ messages in thread
From: Nikita Pettik @ 2018-08-23 22:55 UTC (permalink / raw)
  To: tarantool-patches; +Cc: v.shpilevoy, Nikita Pettik

Instead of using struct Table which is found by lookup in internal hash
lets use struct space from Tarantool DD.

Part of #3561
---
 src/box/sql/analyze.c          | 185 +++++++++++++++++++++++------------------
 test/sql-tap/analyze1.test.lua |   2 +-
 2 files changed, 107 insertions(+), 80 deletions(-)

diff --git a/src/box/sql/analyze.c b/src/box/sql/analyze.c
index abed9d31b..e835a8697 100644
--- a/src/box/sql/analyze.c
+++ b/src/box/sql/analyze.c
@@ -124,7 +124,7 @@
  * @param parse Parsing context.
  * @param stat_cursor Open the _sql_stat1 table on this cursor.
  *        you should allocate |stat_names| cursors before call.
- * @param table_name Delete records of this index if specified.
+ * @param table_name Delete records of this table if specified.
  */
 static void
 vdbe_emit_stat_space_open(struct Parse *parse, int stat_cursor,
@@ -137,12 +137,6 @@ vdbe_emit_stat_space_open(struct Parse *parse, int stat_cursor,
 	assert(sqlite3VdbeDb(v) == parse->db);
 	for (uint i = 0; i < lengthof(stat_names); ++i) {
 		const char *space_name = stat_names[i];
-		/*
-		 * The table already exists, because it is a
-		 * system space.
-		 */
-		assert(sqlite3HashFind(&parse->db->pSchema->tblHash,
-				       space_name) != NULL);
 		if (table_name != NULL) {
 			vdbe_emit_stat_space_clear(parse, space_name, NULL,
 						   table_name);
@@ -770,61 +764,62 @@ callStatGet(Vdbe * v, int regStat4, int iParam, int regOut)
 	sqlite3VdbeChangeP5(v, 2);
 }
 
-/*
+/**
  * Generate code to do an analysis of all indices associated with
  * a single table.
+ *
+ * @param pParse Current parsing context.
+ * @param space Space to be analyzed.
+ * @param stat_cursor Cursor pointing to spaces containing
+ *        statistics: _sql_stat1 (stat_cursor) and
+ *        _sql_stat4 (stat_cursor + 1).
  */
 static void
-analyzeOneTable(Parse * pParse,	/* Parser context */
-		Table * pTab,	/* Table whose indices are to be analyzed */
-		int iStatCur,	/* Index of VdbeCursor that writes the _sql_stat1 table */
-		int iMem,	/* Available memory locations begin here */
-		int iTab	/* Next available cursor */
-    )
+vdbe_emit_analyze_space(struct Parse *pParse, struct space *space,
+			int stat_cursor)
 {
 	sqlite3 *db = pParse->db;	/* Database handle */
 	int iIdxCur;		/* Cursor open on index being analyzed */
 	int iTabCur;		/* Table cursor */
 	Vdbe *v;		/* The virtual machine being built up */
 	int i;			/* Loop counter */
-	int regStat4 = iMem++;	/* Register to hold Stat4Accum object */
-	int regChng = iMem++;	/* Index of changed index field */
-	int regKey = iMem++;	/* Key argument passed to stat_push() */
-	int regTemp = iMem++;	/* Temporary use register */
-	int regTabname = iMem++;	/* Register containing table name */
-	int regIdxname = iMem++;	/* Register containing index name */
-	int regStat1 = iMem++;	/* Value for the stat column of _sql_stat1 */
-	int regPrev = iMem;	/* MUST BE LAST (see below) */
-
-	pParse->nMem = MAX(pParse->nMem, iMem);
+	/* Register to hold Stat4Accum object. */
+	int regStat4 = ++pParse->nMem;
+	/* Index of changed index field. */
+	int regChng = ++pParse->nMem;
+	/* Key argument passed to stat_push(). */
+	int regKey = ++pParse->nMem;
+	/* Temporary use register. */
+	int regTemp = ++pParse->nMem;
+	/* Register containing table name. */
+	int regTabname = ++pParse->nMem;
+	/* Register containing index name. */
+	int regIdxname = ++pParse->nMem;
+	/* Value for the stat column of _sql_stat1. */
+	int regStat1 = ++pParse->nMem;
+	/* MUST BE LAST (see below). */
+	int regPrev = ++pParse->nMem;
+
+	assert(space != NULL);
 	v = sqlite3GetVdbe(pParse);
-	if (v == 0 || NEVER(pTab == 0)) {
-		return;
-	}
-	assert(pTab->def->id != 0);
-	if (sqlite3_strlike("\\_%", pTab->def->name, '\\') == 0) {
+	assert(v != NULL);
+	if (sqlite3_strlike("\\_%", space->def->name, '\\') == 0) {
 		/* Do not gather statistics on system tables */
 		return;
 	}
 
-	/* Establish a read-lock on the table at the shared-cache level.
-	 * Open a read-only cursor on the table. Also allocate a cursor number
-	 * to use for scanning indexes (iIdxCur). No index cursor is opened at
-	 * this time though.
-	 */
-	iTabCur = iTab++;
-	iIdxCur = iTab++;
-	pParse->nTab = MAX(pParse->nTab, iTab);
-	sqlite3OpenTable(pParse, iTabCur, pTab, OP_OpenRead);
-	sqlite3VdbeLoadString(v, regTabname, pTab->def->name);
 	/*
-	 * Here we need real space from Tarantool DD since
-	 * further it is passed to cursor opening routine.
+	 * Open a read-only cursor on the table. Also allocate
+	 * a cursor number to use for scanning indexes.
 	 */
-	struct space *space = space_by_id(pTab->def->id);
-	assert(space != NULL);
+	iTabCur = pParse->nTab;
+	pParse->nTab += 2;
+	assert(space->index_count != 0);
+	sqlite3VdbeAddOp4(v, OP_OpenRead, iTabCur, 0, 0, (void *) space,
+			  P4_SPACEPTR);
+	sqlite3VdbeLoadString(v, regTabname, space->def->name);
 	for (uint32_t j = 0; j < space->index_count; ++j) {
-		struct index *idx = pTab->space->index[j];
+		struct index *idx = space->index[j];
 		int addrRewind;	/* Address of "OP_Rewind iIdxCur" */
 		int addrNextRow;	/* Address of "next_row:" */
 		const char *idx_name;	/* Name of the index */
@@ -834,14 +829,14 @@ analyzeOneTable(Parse * pParse,	/* Parser context */
 		 * instead more familiar table name.
 		 */
 		if (idx->def->iid == 0)
-			idx_name = pTab->def->name;
+			idx_name = space->def->name;
 		else
 			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);
-		VdbeComment((v, "Analysis for %s.%s", pTab->def->name,
+		VdbeComment((v, "Analysis for %s.%s", space->def->name,
 			    idx_name));
 
 		/*
@@ -884,9 +879,16 @@ analyzeOneTable(Parse * pParse,	/* Parser context */
 		pParse->nMem = MAX(pParse->nMem, regPrev + part_count);
 
 		/* Open a read-only cursor on the index being analyzed. */
-		sqlite3VdbeAddOp4(v, OP_OpenRead, iIdxCur, idx->def->iid, 0,
-				  (void *) space, P4_SPACEPTR);
-		VdbeComment((v, "%s", idx->def->name));
+		if (j != 0) {
+			iIdxCur = pParse->nTab - 1;
+			sqlite3VdbeAddOp4(v, OP_OpenRead, iIdxCur,
+					  idx->def->iid, 0,
+					  (void *) space, P4_SPACEPTR);
+			VdbeComment((v, "%s", idx->def->name));
+		} else {
+			/* We have already opened cursor on PK. */
+			iIdxCur = iTabCur;
+		}
 
 		/* Invoke the stat_init() function. The arguments are:
 		 *
@@ -989,7 +991,7 @@ analyzeOneTable(Parse * pParse,	/* Parser context */
 		 *   if !eof(csr) goto next_row;
 		 */
 		assert(regKey == (regStat4 + 2));
-		struct index *pPk = sql_table_primary_key(pTab);
+		struct index *pPk = space_index(space, 0);
 		int pk_part_count = pPk->def->key_def->part_count;
 		/* Allocate memory for array. */
 		pParse->nMem = MAX(pParse->nMem,
@@ -997,10 +999,10 @@ analyzeOneTable(Parse * pParse,	/* Parser context */
 		int regKeyStat = regPrev + part_count;
 		for (i = 0; i < pk_part_count; i++) {
 			uint32_t k = pPk->def->key_def->parts[i].fieldno;
-			assert(k < pTab->def->field_count);
+			assert(k < space->def->field_count);
 			sqlite3VdbeAddOp3(v, OP_Column, iIdxCur, k,
 					  regKeyStat + i);
-			VdbeComment((v, "%s", pTab->def->fields[k].name));
+			VdbeComment((v, "%s", space->def->fields[k].name));
 		}
 		sqlite3VdbeAddOp3(v, OP_MakeRecord, regKeyStat,
 				  pk_part_count, regKey);
@@ -1017,7 +1019,7 @@ analyzeOneTable(Parse * pParse,	/* Parser context */
 		assert("BBB"[0] == AFFINITY_TEXT);
 		sqlite3VdbeAddOp4(v, OP_MakeRecord, regTabname, 3, regTemp,
 				  "BBB", 0);
-		sqlite3VdbeAddOp2(v, OP_IdxInsert, iStatCur, regTemp);
+		sqlite3VdbeAddOp2(v, OP_IdxInsert, stat_cursor, regTemp);
 
 		/* Add the entries to the stat4 table. */
 
@@ -1054,7 +1056,7 @@ analyzeOneTable(Parse * pParse,	/* Parser context */
 		sqlite3VdbeAddOp3(v, OP_MakeRecord, regCol, part_count,
 				  regSample);
 		sqlite3VdbeAddOp3(v, OP_MakeRecord, regTabname, 6, regTemp);
-		sqlite3VdbeAddOp2(v, OP_IdxReplace, iStatCur + 1, regTemp);
+		sqlite3VdbeAddOp2(v, OP_IdxReplace, stat_cursor + 1, regTemp);
 		sqlite3VdbeAddOp2(v, OP_Goto, 1, addrNext);	/* P1==1 for end-of-loop */
 		sqlite3VdbeJumpHere(v, addrIsNull);
 
@@ -1076,27 +1078,43 @@ loadAnalysis(Parse * pParse)
 	}
 }
 
-/*
- * Generate code that will do an analysis of an entire database
+/**
+ * Wrapper to pass args to space_foreach callback.
+ */
+struct analyze_data {
+	struct Parse *parse_context;
+	/**
+	 * Cursor numbers pointing to stat spaces:
+	 * stat_cursor is opened on _sql_stat1 and
+	 * stat_cursor + 1 - on _sql_stat4.
+	 */
+	int stat_cursor;
+};
+
+static int
+sql_space_foreach_analyze(struct space *space, void *data)
+{
+	if (space->def->opts.sql == NULL || space->def->opts.is_view)
+		return 0;
+	struct analyze_data *anal_data = (struct analyze_data *) data;
+	vdbe_emit_analyze_space(anal_data->parse_context, space,
+				anal_data->stat_cursor);
+	return 0;
+}
+
+/**
+ * Generate code that will do an analysis of all spaces created
+ * via SQL facilities.
  */
 static void
-sql_analyze_database(Parse *parser)
+sql_analyze_database(struct Parse *parser)
 {
-	struct Schema *schema = parser->db->pSchema;
 	sql_set_multi_write(parser, false);
 	int stat_cursor = parser->nTab;
-	parser->nTab += 3;
+	parser->nTab += 2;
 	vdbe_emit_stat_space_open(parser, stat_cursor, NULL);
-	int reg = parser->nMem + 1;
-	int tab_cursor = parser->nTab;
-	for (struct HashElem *k = sqliteHashFirst(&schema->tblHash); k != NULL;
-	     k = sqliteHashNext(k)) {
-		struct Table *table = (struct Table *) sqliteHashData(k);
-		if (! table->def->opts.is_view) {
-			analyzeOneTable(parser, table, stat_cursor, reg,
-					tab_cursor);
-		}
-	}
+	struct analyze_data anal_data = { parser, stat_cursor };
+	space_foreach(sql_space_foreach_analyze, (void *) &anal_data);
 	loadAnalysis(parser);
 }
 
@@ -1108,15 +1126,18 @@ sql_analyze_database(Parse *parser)
  * @param table Target table to analyze.
  */
 static void
-vdbe_emit_analyze_table(struct Parse *parse, struct Table *table)
+vdbe_emit_analyze_table(struct Parse *parse, struct space *space)
 {
-	assert(table != NULL);
+	assert(space != NULL);
 	sql_set_multi_write(parse, false);
+	/*
+	 * There are two system spaces for statistics: _sql_stat1
+	 * and _sql_stat4.
+	 */
 	int stat_cursor = parse->nTab;
-	parse->nTab += 3;
-	vdbe_emit_stat_space_open(parse, stat_cursor, table->def->name);
-	analyzeOneTable(parse, table, stat_cursor, parse->nMem + 1,
-			parse->nTab);
+	parse->nTab += 2;
+	vdbe_emit_stat_space_open(parse, stat_cursor, space->def->name);
+	vdbe_emit_analyze_space(parse, space, stat_cursor);
 	loadAnalysis(parse);
 }
 
@@ -1142,15 +1163,21 @@ sqlite3Analyze(Parse * pParse, Token * pName)
 		/* Form 2:  Analyze table named */
 		char *z = sqlite3NameFromToken(db, pName);
 		if (z != NULL) {
-			Table *pTab = sqlite3LocateTable(pParse, 0, z);
-			if (pTab != NULL) {
-				if (pTab->def->opts.is_view) {
+			uint32_t space_id = box_space_id_by_name(z, strlen(z));
+			if (space_id != BOX_ID_NIL) {
+				struct space *sp = space_by_id(space_id);
+				assert(sp != NULL);
+				if (sp->def->opts.is_view) {
 					sqlite3ErrorMsg(pParse, "VIEW isn't "\
 							"allowed to be "\
 							"analyzed");
 				} else {
-					vdbe_emit_analyze_table(pParse, pTab);
+					vdbe_emit_analyze_table(pParse, sp);
 				}
+			} else {
+				diag_set(ClientError, ER_NO_SUCH_SPACE, z);
+				pParse->rc = SQL_TARANTOOL_ERROR;
+				pParse->nErr++;
 			}
 			sqlite3DbFree(db, z);
 		}
diff --git a/test/sql-tap/analyze1.test.lua b/test/sql-tap/analyze1.test.lua
index dab7255e1..2d8eed950 100755
--- a/test/sql-tap/analyze1.test.lua
+++ b/test/sql-tap/analyze1.test.lua
@@ -26,7 +26,7 @@ test:do_catchsql_test(
         ANALYZE no_such_table
     ]], {
         -- <analyze-1.1>
-        1, "no such table: NO_SUCH_TABLE"
+        1, "Space 'NO_SUCH_TABLE' does not exist"
         -- </analyze-1.1>
     })
 
-- 
2.15.1

^ permalink raw reply	[flat|nested] 28+ messages in thread

* [tarantool-patches] [PATCH 4/7] sql: refactor ALTER RENAME code generation
       [not found] ` <cover.1535064700.git.korablev@tarantool.org>
                     ` (2 preceding siblings ...)
  2018-08-23 22:55   ` [tarantool-patches] [PATCH 3/7] sql: remove struct Table from analyze routine Nikita Pettik
@ 2018-08-23 22:55   ` Nikita Pettik
  2018-08-29  0:58     ` [tarantool-patches] " Vladislav Shpilevoy
  2018-08-23 22:55   ` [tarantool-patches] [PATCH 5/7] sql: remove lookups in Table hash Nikita Pettik
                     ` (2 subsequent siblings)
  6 siblings, 1 reply; 28+ messages in thread
From: Nikita Pettik @ 2018-08-23 22:55 UTC (permalink / raw)
  To: tarantool-patches; +Cc: v.shpilevoy, Nikita Pettik

We are going to remove legacy cache of struct Table, so lets get rid of
using SQLite tables in alter routine.

Part of #3561
---
 src/box/sql/alter.c         | 114 ++++++++++++++------------------------------
 src/box/sql/parse.y         |   2 +-
 src/box/sql/sqliteInt.h     |  13 ++++-
 src/box/sql/vdbe.c          |   5 +-
 test/sql-tap/alter.test.lua |   4 +-
 5 files changed, 53 insertions(+), 85 deletions(-)

diff --git a/src/box/sql/alter.c b/src/box/sql/alter.c
index 349589be4..320dfdd05 100644
--- a/src/box/sql/alter.c
+++ b/src/box/sql/alter.c
@@ -34,91 +34,51 @@
  * that implements the ALTER TABLE command.
  */
 #include "sqliteInt.h"
-#include "src/box/session.h"
-#include "tarantoolInt.h"
+#include "box/box.h"
+#include "box/schema.h"
 
-/*
- * Generate code to drop and reload the internal representation of table
- * pTab from the database, including triggers.
- * Argument zName is the name of the table in the database schema at
- * the time the generated code is executed. This can be different from
- * pTab->zName if this function is being called to code part of an
- * "ALTER TABLE RENAME TO" statement.
- */
-static void
-reloadTableSchema(Parse * pParse, Table * pTab, const char *zName)
-{
-	Vdbe *v;
-	v = sqlite3GetVdbe(pParse);
-	if (NEVER(v == 0))
-		return;
-
-	char *zNewName = sqlite3MPrintf(pParse->db, "%s", zName);
-	sqlite3VdbeAddOp4(v, OP_RenameTable, pTab->def->id, 0, 0, zNewName,
-			  P4_DYNAMIC);
-}
-
-/*
- * Generate code to implement the "ALTER TABLE xxx RENAME TO yyy"
- * command.
- */
 void
-sqlite3AlterRenameTable(Parse * pParse,	/* Parser context. */
-			SrcList * pSrc,	/* The table to rename. */
-			Token * pName)	/* The new table name. */
+sql_alter_table_rename(struct Parse *parse, struct SrcList *src_tab,
+		       struct Token *new_name_tk)
 {
-	Table *pTab;		/* Table being renamed */
-	char *zName = 0;	/* NULL-terminated version of pName */
-	sqlite3 *db = pParse->db;	/* Database connection */
-	Vdbe *v;
-	uint32_t savedDbFlags;	/* Saved value of db->flags */
-	struct session *user_session = current_session();
-
-	savedDbFlags = user_session->sql_flags;
-
-	if (NEVER(db->mallocFailed))
-		goto exit_rename_table;
-	assert(pSrc->nSrc == 1);
-
-	pTab = sqlite3LocateTable(pParse, 0, pSrc->a[0].zName);
-	if (pTab == NULL)
-		goto exit_rename_table;
-
-	user_session->sql_flags |= SQLITE_PreferBuiltin;
-
-	/* Get a NULL terminated version of the new table name. */
-	zName = sqlite3NameFromToken(db, pName);
-	if (!zName)
-		goto exit_rename_table;
-
-	/* Check that a table named 'zName' does not already exist
-	 * in database. If so, this is an error.
-	 */
-	if (sqlite3HashFind(&db->pSchema->tblHash, zName) != NULL) {
-		sqlite3ErrorMsg(pParse,
-				"there is already another table or index with this name: %s",
-				zName);
+	assert(src_tab->nSrc == 1);
+	struct sqlite3 *db = parse->db;
+	char *new_name = sqlite3NameFromToken(db, new_name_tk);
+	if (new_name == NULL)
 		goto exit_rename_table;
+	/* Check that new name isn't occupied by another table. */
+	uint32_t space_id = box_space_id_by_name(new_name, strlen(new_name));
+	if (space_id != BOX_ID_NIL) {
+		diag_set(ClientError, ER_SQL_EXECUTE, tt_sprintf("there is "
+			"already another table with this name: %s", new_name));
+		goto tnt_error;
 	}
-	if (pTab->def->opts.is_view) {
-		sqlite3ErrorMsg(pParse, "view %s may not be altered",
-				pTab->def->name);
-		goto exit_rename_table;
+	const char *tbl_name = src_tab->a[0].zName;
+	space_id = box_space_id_by_name(tbl_name, strlen(tbl_name));
+	if (space_id == BOX_ID_NIL) {
+		diag_set(ClientError, ER_NO_SUCH_SPACE, tbl_name);
+		goto tnt_error;
 	}
-	/* Begin a transaction for database. */
-	v = sqlite3GetVdbe(pParse);
-	if (v == 0) {
-		goto exit_rename_table;
+	struct space *space = space_by_id(space_id);
+	assert(space != NULL);
+	if (space->def->opts.is_view) {
+		diag_set(ClientError, ER_SQL_EXECUTE,
+			 "view may not be altered");
+		goto tnt_error;
 	}
-	sql_set_multi_write(pParse, false);
-
+	sql_set_multi_write(parse, false);
 	/* Drop and reload the internal table schema. */
-	reloadTableSchema(pParse, pTab, zName);
-
- exit_rename_table:
-	sqlite3SrcListDelete(db, pSrc);
-	sqlite3DbFree(db, zName);
-	user_session->sql_flags = savedDbFlags;
+	struct Vdbe *v = sqlite3GetVdbe(parse);
+	sqlite3VdbeAddOp4(v, OP_RenameTable, space_id, 0, 0, new_name,
+			  P4_DYNAMIC);
+exit_rename_table:
+	sqlite3SrcListDelete(db, src_tab);
+	return;
+tnt_error:
+	sqlite3DbFree(db, new_name);
+	parse->rc = SQL_TARANTOOL_ERROR;
+	parse->nErr++;
+	goto exit_rename_table;
 }
 
 /*
diff --git a/src/box/sql/parse.y b/src/box/sql/parse.y
index 60c0a8eed..92ce5a5bc 100644
--- a/src/box/sql/parse.y
+++ b/src/box/sql/parse.y
@@ -1432,7 +1432,7 @@ cmd ::= ANALYZE nm(X).          {sqlite3Analyze(pParse, &X);}
 
 //////////////////////// ALTER TABLE table ... ////////////////////////////////
 cmd ::= ALTER TABLE fullname(X) RENAME TO nm(Z). {
-  sqlite3AlterRenameTable(pParse,X,&Z);
+  sql_alter_table_rename(pParse,X,&Z);
 }
 
 cmd ::= ALTER TABLE fullname(X) ADD CONSTRAINT nm(Z) FOREIGN KEY
diff --git a/src/box/sql/sqliteInt.h b/src/box/sql/sqliteInt.h
index 96e2fff9f..3ae8db1bc 100644
--- a/src/box/sql/sqliteInt.h
+++ b/src/box/sql/sqliteInt.h
@@ -4427,7 +4427,18 @@ extern int sqlite3PendingByte;
 #endif
 #endif
 void sqlite3Reindex(Parse *, Token *, Token *);
-void sqlite3AlterRenameTable(Parse *, SrcList *, Token *);
+
+/**
+ * Generate code to implement the "ALTER TABLE xxx RENAME TO yyy"
+ * command.
+ *
+ * @param parse Current parsing context.
+ * @param src_tab The table to rename.
+ * @param new_name_tk Token containing new name of the table.
+ */
+void
+sql_alter_table_rename(struct Parse *parse, struct SrcList *src_tab,
+		       struct Token *new_name_tk);
 
 /**
  * Return the length (in bytes) of the token that begins at z[0].
diff --git a/src/box/sql/vdbe.c b/src/box/sql/vdbe.c
index d54d169ee..ad251ca99 100644
--- a/src/box/sql/vdbe.c
+++ b/src/box/sql/vdbe.c
@@ -4599,7 +4599,6 @@ case OP_RenameTable: {
 	struct space *space;
 	const char *zOldTableName;
 	const char *zNewTableName;
-	Table *pTab;
 	struct init_data init;
 	char *zSqlStmt;
 
@@ -4610,15 +4609,13 @@ case OP_RenameTable: {
 	struct sql_trigger *triggers = space->sql_triggers;
 	zOldTableName = space_name(space);
 	assert(zOldTableName);
-	pTab = sqlite3HashFind(&db->pSchema->tblHash, zOldTableName);
-	assert(pTab);
 	zNewTableName = pOp->p4.z;
 	zOldTableName = sqlite3DbStrNDup(db, zOldTableName,
 					 sqlite3Strlen30(zOldTableName));
 	rc = sql_rename_table(space_id, zNewTableName, &zSqlStmt);
 	if (rc) goto abort_due_to_error;
 
-	sqlite3UnlinkAndDeleteTable(db, pTab->def->name);
+	sqlite3UnlinkAndDeleteTable(db, space->def->name);
 
 	init.db = db;
 	init.pzErrMsg = &p->zErrMsg;
diff --git a/test/sql-tap/alter.test.lua b/test/sql-tap/alter.test.lua
index db87c7003..ffd0ba6dc 100755
--- a/test/sql-tap/alter.test.lua
+++ b/test/sql-tap/alter.test.lua
@@ -72,7 +72,7 @@ test:do_catchsql_test(
         ALTER TABLE none RENAME TO hi;
     ]], {
         -- <alter-2.1>
-        1, "no such table: NONE"
+        1, "Space 'NONE' does not exist"
         -- </alter-2.1>
     })
 
@@ -83,7 +83,7 @@ test:do_catchsql_test(
         ALTER TABLE t2 RENAME TO t3;
     ]], {
         -- <alter-2.2>
-        1, "there is already another table or index with this name: T3"
+        1, "Failed to execute SQL statement: there is already another table with this name: T3"
         -- </alter-2.2>
     })
 
-- 
2.15.1

^ permalink raw reply	[flat|nested] 28+ messages in thread

* [tarantool-patches] [PATCH 5/7] sql: remove lookups in Table hash
       [not found] ` <cover.1535064700.git.korablev@tarantool.org>
                     ` (3 preceding siblings ...)
  2018-08-23 22:55   ` [tarantool-patches] [PATCH 4/7] sql: refactor ALTER RENAME code generation Nikita Pettik
@ 2018-08-23 22:55   ` Nikita Pettik
  2018-08-29  0:58     ` [tarantool-patches] " Vladislav Shpilevoy
  2018-08-23 22:55   ` [tarantool-patches] [PATCH 6/7] sql: don't add system spaces to " Nikita Pettik
  2018-08-23 22:55   ` [tarantool-patches] [PATCH 7/7] sql: finish DD integration Nikita Pettik
  6 siblings, 1 reply; 28+ messages in thread
From: Nikita Pettik @ 2018-08-23 22:55 UTC (permalink / raw)
  To: tarantool-patches; +Cc: v.shpilevoy, Nikita Pettik

Instead of looking at Table hash lets extract space from Tarantool
internals and create Table wrapper around it.

Part of #3561
---
 src/box/sql/build.c            |  16 +---
 src/box/sql/delete.c           |  84 ++++++++------------
 src/box/sql/expr.c             |   2 +-
 src/box/sql/fkey.c             |  15 +++-
 src/box/sql/insert.c           |  48 ++++++-----
 src/box/sql/pragma.c           | 176 ++++++++++++++++++++++-------------------
 src/box/sql/select.c           |  41 +---------
 src/box/sql/sqliteInt.h        |  35 +++++---
 src/box/sql/trigger.c          |  10 +--
 src/box/sql/update.c           |  10 +--
 test/sql-tap/analyze9.test.lua |   2 +-
 test/sql-tap/join.test.lua     |   6 +-
 test/sql/delete.result         |   4 +-
 13 files changed, 204 insertions(+), 245 deletions(-)

diff --git a/src/box/sql/build.c b/src/box/sql/build.c
index 79dc46592..840604aa3 100644
--- a/src/box/sql/build.c
+++ b/src/box/sql/build.c
@@ -217,22 +217,10 @@ sqlite3CommitInternalChanges()
 	current_session()->sql_flags &= ~SQLITE_InternChanges;
 }
 
-/*
- * Return true if given column is part of primary key.
- * If field number is less than 63, corresponding bit
- * in column mask is tested. Otherwise, check whether 64-th bit
- * in mask is set or not. If it is set, then iterate through
- * key parts of primary index and check field number.
- * In case it isn't set, there are no key columns among
- * the rest of fields.
- */
 bool
-table_column_is_in_pk(Table *table, uint32_t column)
+sql_space_column_is_in_pk(struct space *space, uint32_t column)
 {
-	struct space *space = space_by_id(table->def->id);
-	assert(space != NULL);
-
-	struct index *primary_idx = index_find(space, 0 /* PK */);
+	struct index *primary_idx = space->index[0];
 	/* Views don't have any indexes. */
 	if (primary_idx == NULL)
 		return false;
diff --git a/src/box/sql/delete.c b/src/box/sql/delete.c
index 0f285cc8b..a457a71d1 100644
--- a/src/box/sql/delete.c
+++ b/src/box/sql/delete.c
@@ -36,16 +36,31 @@
 #include "tarantoolInt.h"
 
 struct Table *
-sql_list_lookup_table(struct Parse *parse, SrcList *src_list)
+sql_lookup_table(struct Parse *parse, struct SrcList_item *tbl_name)
 {
-	struct SrcList_item *item = src_list->a;
-	assert(item != NULL && src_list->nSrc == 1);
-	struct Table *table = sqlite3LocateTable(parse, 0, item->zName);
-	sqlite3DeleteTable(parse->db, item->pTab);
-	item->pTab = table;
-	if (table != NULL)
-		item->pTab->nTabRef++;
-	if (sqlite3IndexedByLookup(parse, item) != 0)
+	assert(tbl_name != NULL);
+	uint32_t space_id = box_space_id_by_name(tbl_name->zName,
+						 strlen(tbl_name->zName));
+	sqlite3DeleteTable(parse->db, tbl_name->pTab);
+	struct space *space = space_by_id(space_id);
+	if (space_id == BOX_ID_NIL || space == NULL) {
+		sqlite3ErrorMsg(parse, "no such table: %s", tbl_name->zName);
+		return NULL;
+	}
+	assert(space != NULL);
+	if (space->def->field_count == 0) {
+		sqlite3ErrorMsg(parse, "no format for space: %s",
+				tbl_name->zName);
+		return NULL;
+	}
+	struct Table *table = sqlite3DbMallocZero(parse->db, sizeof(*table));
+	if (table == NULL)
+		return NULL;
+	table->def = space_def_dup(space->def);
+	table->space = space;
+	table->nTabRef = 1;
+	tbl_name->pTab = table;
+	if (sqlite3IndexedByLookup(parse, tbl_name) != 0)
 		table = NULL;
 	return table;
 }
@@ -130,9 +145,6 @@ sql_table_delete_from(struct Parse *parse, struct SrcList *tab_list,
 	 * with multiple tables and expect an SrcList* parameter
 	 * instead of just a Table* parameter.
 	 */
-	struct Table *table = NULL;
-	struct space *space = NULL;
-	uint32_t space_id;
 	/* Figure out if we have any triggers and if the table
 	 * being deleted from is a view.
 	 */
@@ -140,44 +152,14 @@ sql_table_delete_from(struct Parse *parse, struct SrcList *tab_list,
 	/* True if there are triggers or FKs or subqueries in the
 	 * WHERE clause.
 	 */
-	bool is_complex = false;
-	const char *tab_name = tab_list->a->zName;
-	struct Table tmp_tab;
-	if (sqlite3LocateTable(parse, LOCATE_NOERR, tab_name) == NULL) {
-		space_id = box_space_id_by_name(tab_name,
-						strlen(tab_name));
-		if (space_id == BOX_ID_NIL) {
-			diag_set(ClientError, ER_NO_SUCH_SPACE, tab_name);
-			parse->rc = SQL_TARANTOOL_ERROR;
-			parse->nErr++;
-			goto delete_from_cleanup;
-		}
-		/*
-		 * In this case space has been created via Lua
-		 * facilities, so there is no corresponding entry
-		 * in table hash. Thus, lets create simple
-		 * wrapper around space_def to support interface.
-		 */
-		space = space_by_id(space_id);
-		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 {
-		table = sql_list_lookup_table(parse, tab_list);
-		if (table == NULL)
-			goto delete_from_cleanup;
-		space_id = table->def->id;
-		space = space_by_id(space_id);
-		assert(space != NULL);
-		trigger_list = sql_triggers_exist(table, TK_DELETE, NULL, NULL);
-		is_complex = trigger_list != NULL ||
-			     fkey_is_required(table->def->id, NULL);
-	}
-	assert(space != NULL);
-
+	struct Table *table = sql_lookup_table(parse, tab_list->a);
+	if (table == NULL)
+		goto delete_from_cleanup;
+	assert(table->space != NULL);
+	trigger_list = sql_triggers_exist(table, TK_DELETE, NULL, NULL);
+	bool is_complex = trigger_list != NULL ||
+			  fkey_is_required(table->def->id, NULL);
+	struct space *space = table->space;
 	bool is_view = space->def->opts.is_view;
 
 	/* If table is really a view, make sure it has been
@@ -230,7 +212,7 @@ sql_table_delete_from(struct Parse *parse, struct SrcList *tab_list,
 	if (where == NULL && !is_complex) {
 		assert(!is_view);
 
-		sqlite3VdbeAddOp1(v, OP_Clear, space_id);
+		sqlite3VdbeAddOp1(v, OP_Clear, space->def->id);
 
 		/* Do not start Tarantool's transaction in case of
 		 * truncate optimization. This is workaround until
diff --git a/src/box/sql/expr.c b/src/box/sql/expr.c
index cb4f89e35..7b6300c75 100644
--- a/src/box/sql/expr.c
+++ b/src/box/sql/expr.c
@@ -278,7 +278,7 @@ comparisonAffinity(Expr * pExpr)
 		aff =
 		    sqlite3CompareAffinity(pExpr->x.pSelect->pEList->a[0].pExpr,
 					   aff);
-	} else if (NEVER(aff == 0)) {
+	} else {
 		aff = AFFINITY_BLOB;
 	}
 	return aff;
diff --git a/src/box/sql/fkey.c b/src/box/sql/fkey.c
index 62337792b..215ad2867 100644
--- a/src/box/sql/fkey.c
+++ b/src/box/sql/fkey.c
@@ -612,11 +612,18 @@ fkey_emit_check(struct Parse *parser, struct Table *tab, int reg_old,
 		struct SrcList_item *item = src->a;
 		struct space *child = space_by_id(fk->def->child_id);
 		assert(child != NULL);
-		struct Table *child_tab = sqlite3HashFind(&db->pSchema->tblHash,
-							  child->def->name);
-		item->pTab = child_tab;
+		/*
+		 * Create surrogate struct Table wrapper around
+		 * space_def to support legacy interface.
+		 */
+		struct Table fake_tab;
+		memset(&fake_tab, 0, sizeof(fake_tab));
+		fake_tab.def = child->def;
+		fake_tab.space = child;
+		item->pTab = &fake_tab;
 		item->zName = sqlite3DbStrDup(db, child->def->name);
-		item->pTab->nTabRef++;
+		/* Prevent from deallocationg fake_tab. */
+		item->pTab->nTabRef = 2;
 		item->iCursor = parser->nTab++;
 
 		if (reg_new != 0) {
diff --git a/src/box/sql/insert.c b/src/box/sql/insert.c
index 9220d34e1..d3950254e 100644
--- a/src/box/sql/insert.c
+++ b/src/box/sql/insert.c
@@ -38,6 +38,7 @@
 #include "box/session.h"
 #include "box/schema.h"
 #include "bit/bit.h"
+#include "box/box.h"
 
 /*
  * Generate code that will open pTab as cursor iCur.
@@ -150,7 +151,6 @@ vdbe_has_table_read(struct Parse *parser, const struct Table *table)
 	return false;
 }
 
-
 /* Forward declaration */
 static int
 xferOptimization(Parse * pParse,	/* Parser context */
@@ -314,7 +314,7 @@ sqlite3Insert(Parse * pParse,	/* Parser context */
 	zTab = pTabList->a[0].zName;
 	if (NEVER(zTab == 0))
 		goto insert_cleanup;
-	pTab = sql_list_lookup_table(pParse, pTabList);
+	pTab = sql_lookup_table(pParse, pTabList->a);
 	if (pTab == NULL)
 		goto insert_cleanup;
 
@@ -1161,7 +1161,6 @@ xferOptimization(Parse * pParse,	/* Parser context */
 		 int onError)		/* How to handle constraint errors */
 {
 	ExprList *pEList;	/* The result set of the SELECT */
-	Table *pSrc;		/* The table in the FROM clause of SELECT */
 	struct index *pSrcIdx, *pDestIdx;
 	struct SrcList_item *pItem;	/* An element of pSelect->pSrc */
 	int i;			/* Loop counter */
@@ -1234,40 +1233,40 @@ xferOptimization(Parse * pParse,	/* Parser context */
 	 * we have to check the semantics.
 	 */
 	pItem = pSelect->pSrc->a;
-	pSrc = sqlite3LocateTable(pParse, 0, pItem->zName);
+	uint32_t src_id = box_space_id_by_name(pItem->zName,
+						strlen(pItem->zName));
 	/* FROM clause does not contain a real table. */
-	if (pSrc == NULL)
+	if (src_id == BOX_ID_NIL)
 		return 0;
+	struct space *src = space_by_id(src_id);
+	assert(src != NULL);
 	/* Src and dest may not be the same table. */
-	if (pSrc == pDest)
+	if (src->def->id == pDest->space->def->id)
 		return 0;
 	/* Src may not be a view. */
-	if (pSrc->def->opts.is_view)
+	if (src->def->opts.is_view)
 		return 0;
 	/* Number of columns must be the same in src and dst. */
-	if (pDest->def->field_count != pSrc->def->field_count)
+	if (pDest->def->field_count != src->def->field_count)
 		return 0;
 	for (i = 0; i < (int)pDest->def->field_count; i++) {
 		enum affinity_type dest_affinity =
 			pDest->def->fields[i].affinity;
 		enum affinity_type src_affinity =
-			pSrc->def->fields[i].affinity;
+			src->def->fields[i].affinity;
 		/* Affinity must be the same on all columns. */
 		if (dest_affinity != src_affinity)
 			return 0;
 		uint32_t id;
 		if (sql_column_collation(pDest->def, i, &id) !=
-		    sql_column_collation(pSrc->def, i, &id)) {
-			return 0;	/* Collating sequence must be the same on all columns */
-		}
-		if (!pDest->def->fields[i].is_nullable
-		    && pSrc->def->fields[i].is_nullable) {
-			return 0;	/* tab2 must be NOT NULL if tab1 is */
-		}
+		    sql_column_collation(src->def, i, &id))
+			return 0;
+		if (!pDest->def->fields[i].is_nullable &&
+		    src->def->fields[i].is_nullable)
+			return 0;
 		/* Default values for second and subsequent columns need to match. */
 		if (i > 0) {
-			char *src_expr_str =
-				pSrc->def->fields[i].default_value;
+			char *src_expr_str = src->def->fields[i].default_value;
 			char *dest_expr_str =
 				pDest->def->fields[i].default_value;
 			if ((dest_expr_str == NULL) != (src_expr_str == NULL) ||
@@ -1282,8 +1281,8 @@ xferOptimization(Parse * pParse,	/* Parser context */
 		pDestIdx = pDest->space->index[i];
 		if (pDestIdx->def->opts.is_unique)
 			destHasUniqueIdx = 1;
-		for (uint32_t j = 0; j < pSrc->space->index_count; ++j) {
-			pSrcIdx = pSrc->space->index[j];
+		for (uint32_t j = 0; j < src->index_count; ++j) {
+			pSrcIdx = src->index[j];
 			if (sql_index_is_xfer_compatible(pDestIdx->def,
 							 pSrcIdx->def))
 				break;
@@ -1293,7 +1292,7 @@ xferOptimization(Parse * pParse,	/* Parser context */
 			return 0;
 	}
 	/* Get server checks. */
-	ExprList *pCheck_src = space_checks_expr_list(pSrc->def->id);
+	ExprList *pCheck_src = space_checks_expr_list(src->def->id);
 	ExprList *pCheck_dest = space_checks_expr_list(pDest->def->id);
 	if (pCheck_dest != NULL &&
 	    sqlite3ExprListCompare(pCheck_src, pCheck_dest, -1) != 0) {
@@ -1353,11 +1352,8 @@ xferOptimization(Parse * pParse,	/* Parser context */
 	}
 
 		assert(pSrcIdx);
-		struct space *src_space =
-			space_by_id(pSrc->def->id);
-		vdbe_emit_open_cursor(pParse, iSrc,
-				      pSrcIdx->def->iid,
-				      src_space);
+
+		vdbe_emit_open_cursor(pParse, iSrc, pSrcIdx->def->iid, src);
 		VdbeComment((v, "%s", pSrcIdx->def->name));
 		struct space *dest_space = space_by_id(pDest->def->id);
 		vdbe_emit_open_cursor(pParse, iDest,
diff --git a/src/box/sql/pragma.c b/src/box/sql/pragma.c
index 6173462ff..063836989 100644
--- a/src/box/sql/pragma.c
+++ b/src/box/sql/pragma.c
@@ -245,6 +245,95 @@ sql_default_engine_set(const char *engine_name)
 	return 0;
 }
 
+/**
+ * This function handles PRAGMA TABLE_INFO(<table>).
+ *
+ * Return a single row for each column of the named table.
+ * The columns of the returned data set are:
+ *
+ * - cid: Column id (numbered from left to right, starting at 0);
+ * - name: Column name;
+ * - type: Column declaration type;
+ * - notnull: True if 'NOT NULL' is part of column declaration;
+ * - dflt_value: The default value for the column, if any.
+ *
+ * @param parse Current parsing context.
+ * @param tbl_name Name of table to be examined.
+ */
+static void
+sql_pragma_table_info(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);
+	struct space_def *def = space->def;
+	struct index *pk = space->index[0];
+	parse->nMem = 6;
+	if (def->opts.is_view) {
+		const char *sql = def->opts.sql;
+		sql_view_assign_cursors(parse, sql);
+	}
+	struct Vdbe *v = sqlite3GetVdbe(parse);
+	for (uint32_t i = 0, k; i < def->field_count; ++i) {
+		if (!sql_space_column_is_in_pk(space, i)) {
+			k = 0;
+		} else if (pk == NULL) {
+			k = 1;
+		} else {
+			struct key_def *kdef = pk->def->key_def;
+			k = key_def_find(kdef, i) - kdef->parts + 1;
+		}
+		bool is_nullable = def->fields[i].is_nullable;
+		char *expr_str = def->fields[i].default_value;
+		const char *name = def->fields[i].name;
+		enum field_type type = def->fields[i].type;
+		sqlite3VdbeMultiLoad(v, 1, "issisi", i, name,
+				     field_type_strs[type], !is_nullable,
+				     expr_str, k);
+		sqlite3VdbeAddOp2(v, OP_ResultRow, 1, 6);
+	}
+}
+
+/**
+ * This function handles PRAGMA stats.
+ * It displays estimate (log) number of tuples in space and
+ * average size of tuple in each index.
+ *
+ * @param space Space to be examined.
+ * @param data Parsing context passed as callback argument.
+ */
+static int
+sql_pragma_table_stats(struct space *space, void *data)
+{
+	if (space->def->opts.sql == NULL || space->def->opts.is_view)
+		return 0;
+	struct Parse *parse = (struct Parse *) data;
+	struct index *pk = space_index(space, 0);
+	if (pk == NULL)
+		return 0;
+	struct Vdbe *v = sqlite3GetVdbe(parse);
+	LogEst tuple_count_est = sqlite3LogEst(pk->vtab->size(pk));
+	size_t avg_tuple_size_pk = sql_index_tuple_size(space, pk);
+	parse->nMem = 4;
+	sqlite3VdbeMultiLoad(v, 1, "ssii", space->def->name, 0,
+			     avg_tuple_size_pk, tuple_count_est);
+	sqlite3VdbeAddOp2(v, OP_ResultRow, 1, 4);
+	for (uint32_t i = 0; i < space->index_count; ++i) {
+		struct index *idx = space->index[i];
+		assert(idx != NULL);
+		size_t avg_tuple_size_idx = sql_index_tuple_size(space, idx);
+		sqlite3VdbeMultiLoad(v, 2, "sii", idx->def->name,
+				     avg_tuple_size_idx,
+				     index_field_tuple_est(idx->def, 0));
+		sqlite3VdbeAddOp2(v, OP_ResultRow, 1, 4);
+	}
+	return 0;
+}
+
 /**
  * This function handles PRAGMA INDEX_INFO and PRAGMA INDEX_XINFO
  * statements.
@@ -431,93 +520,18 @@ sqlite3Pragma(Parse * pParse, Token * pId,	/* First part of [schema.]id field */
 #endif				/* SQLITE_OMIT_FLAG_PRAGMAS */
 
 #ifndef SQLITE_OMIT_SCHEMA_PRAGMAS
-		/* *   PRAGMA table_info(<table>) *
-		 *
-		 * Return a single row for each column of the named table. The
-		 * columns of * the returned data set are: *
-		 *
-		 * cid:        Column id (numbered from left to right, starting at
-		 * 0) * name:       Column name * type:       Column
-		 * declaration type. * notnull:    True if 'NOT NULL' is part
-		 * of column declaration * dflt_value: The default value for
-		 * the column, if any.
-		 */
 	case PragTyp_TABLE_INFO:
-		if (zRight == NULL)
-			break;
-		struct Table *table =
-			sqlite3LocateTable(pParse, LOCATE_NOERR, zRight);
-		if (table == NULL)
-			break;
-		struct space *space = space_cache_find(table->def->id);
-		struct space_def *def = space->def;
-		struct index *pk = sql_table_primary_key(table);
-		pParse->nMem = 6;
-		if (def->opts.is_view) {
-			const char *sql = table->def->opts.sql;
-			sql_view_assign_cursors(pParse, sql);
-		}
-		for (uint32_t i = 0, k; i < def->field_count; ++i) {
-			if (!table_column_is_in_pk(table, i)) {
-				k = 0;
-			} else if (pk == NULL) {
-				k = 1;
-			} else {
-				struct key_def *kdef = pk->def->key_def;
-				k = key_def_find(kdef, i) - kdef->parts + 1;
-			}
-			bool is_nullable = def->fields[i].is_nullable;
-			char *expr_str = def->fields[i].default_value;
-			const char *name = def->fields[i].name;
-			enum field_type type = def->fields[i].type;
-			sqlite3VdbeMultiLoad(v, 1, "issisi", i, name,
-					     field_type_strs[type],
-					     !is_nullable, expr_str, k);
-			sqlite3VdbeAddOp2(v, OP_ResultRow, 1, 6);
-		}
+		sql_pragma_table_info(pParse, zRight);
 		break;
-
-	case PragTyp_STATS:{
-			HashElem *i;
-			pParse->nMem = 4;
-			for (i = sqliteHashFirst(&db->pSchema->tblHash); i;
-			     i = sqliteHashNext(i)) {
-				Table *pTab = sqliteHashData(i);
-				struct space *space = space_by_id(pTab->def->id);
-				assert(space != NULL);
-				struct index *pk = space_index(space, 0);
-				size_t avg_tuple_size_pk =
-					sql_index_tuple_size(space, pk);
-				sqlite3VdbeMultiLoad(v, 1, "ssii",
-						     pTab->def->name,
-						     0,
-						     avg_tuple_size_pk,
-						     sql_space_tuple_log_count(pTab));
-				sqlite3VdbeAddOp2(v, OP_ResultRow, 1, 4);
-				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",
-							     idx->def->name,
-							     avg_tuple_size_idx,
-							     index_field_tuple_est(idx->def, 0));
-					sqlite3VdbeAddOp2(v, OP_ResultRow, 1,
-							  4);
-				}
-			}
-			break;
-		}
-
-	case PragTyp_INDEX_INFO:{
+	case PragTyp_STATS:
+		space_foreach(sql_pragma_table_stats, (void *) pParse);
+		break;
+	case PragTyp_INDEX_INFO:
 		sql_pragma_index_info(pParse, pPragma, zTable, zRight);
 		break;
-	}
-	case PragTyp_INDEX_LIST:{
+	case PragTyp_INDEX_LIST:
 		sql_pragma_index_list(pParse, zRight);
 		break;
-	}
 
 	case PragTyp_COLLATION_LIST:{
 		int i = 0;
diff --git a/src/box/sql/select.c b/src/box/sql/select.c
index dd6a8f1b0..a20d0bb3f 100644
--- a/src/box/sql/select.c
+++ b/src/box/sql/select.c
@@ -4812,44 +4812,9 @@ selectExpander(Walker * pWalker, Select * p)
 			 * FROM clause.
 			 */
 			assert(pFrom->pTab == NULL);
-			const char *t_name = pFrom->zName;
-			pFrom->pTab = pTab =
-			    sqlite3LocateTable(pParse, LOCATE_NOERR, t_name);
-			if (pTab == NULL) {
-				int space_id =
-					box_space_id_by_name(t_name,
-							     strlen(t_name));
-				struct space *space = space_by_id(space_id);
-				if (space == NULL) {
-					sqlite3ErrorMsg(pParse,
-							"no such table: %s",
-							t_name);
-					return WRC_Abort;
-				}
-				if (space->def->field_count <= 0) {
-					sqlite3ErrorMsg(pParse, "no format for"\
-							" space: %s", t_name);
-					return WRC_Abort;
-				}
-				struct Table *tab =
-					sqlite3DbMallocZero(db, sizeof(*tab));
-				if (tab == NULL)
-					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) {
-					sqlite3ErrorMsg(pParse, "too many "\
-							"references to "\
-							"\"%s\": max 65535",
-							t_name);
-					pFrom->pTab = NULL;
-					return WRC_Abort;
-				}
-				pTab->nTabRef++;
-			}
+			pTab = sql_lookup_table(pParse, pFrom);
+			if (pTab == NULL)
+				return WRC_Abort;
 			if (cannotBeFunction(pParse, pFrom))
 				return WRC_Abort;
 			if (pTab->def->opts.is_view) {
diff --git a/src/box/sql/sqliteInt.h b/src/box/sql/sqliteInt.h
index 3ae8db1bc..08893c473 100644
--- a/src/box/sql/sqliteInt.h
+++ b/src/box/sql/sqliteInt.h
@@ -3333,6 +3333,11 @@ void sqlite3ExprListSetSpan(Parse *, ExprList *, ExprSpan *);
 u32 sqlite3ExprListFlags(const ExprList *);
 int sqlite3Init(sqlite3 *);
 
+void sqlite3Pragma(Parse *, Token *, Token *, Token *, int);
+void sqlite3ResetAllSchemasOfConnection(sqlite3 *);
+void sqlite3CommitInternalChanges();
+void sqlite3DeleteColumnNames(sqlite3 *, Table *);
+
 /**
  * This is the callback routine for the code that initializes the
  * database.  See sqlite3Init() below for additional information.
@@ -3351,11 +3356,17 @@ int
 sql_init_callback(struct init_data *init, const char *name,
 		  uint32_t space_id, uint32_t index_id, const char *sql);
 
-void sqlite3Pragma(Parse *, Token *, Token *, Token *, int);
-void sqlite3ResetAllSchemasOfConnection(sqlite3 *);
-void sqlite3CommitInternalChanges();
-void sqlite3DeleteColumnNames(sqlite3 *, Table *);
-bool table_column_is_in_pk(Table *, uint32_t);
+/**
+ * Return true if given column is part of primary key.
+ * If field number is less than 63, corresponding bit
+ * in column mask is tested. Otherwise, check whether 64-th bit
+ * in mask is set or not. If it is set, then iterate through
+ * key parts of primary index and check field number.
+ * In case it isn't set, there are no key columns among
+ * the rest of fields.
+ */
+bool
+sql_space_column_is_in_pk(struct space *space, uint32_t);
 
 /**
  * Given an expression list (which is really the list of expressions
@@ -3558,10 +3569,10 @@ Select *sqlite3SelectNew(Parse *, ExprList *, SrcList *, Expr *, ExprList *,
  * While a SrcList can in general represent multiple tables and
  * subqueries (as in the FROM clause of a SELECT statement) in
  * this case it contains the name of a single table, as one might
- * find in an INSERT, DELETE, or UPDATE statement.  Look up that
- * table in the symbol table and return a pointer.  Set an error
- * message and return NULL if the table name is not found or if
- * any other error occurs.
+ * find in an INSERT, DELETE, or UPDATE statement. Look up that
+ * space in the cache and create Table wrapper around it.
+ * Set an error message and return NULL if the table name is not
+ * found or if space doesn't have format.
  *
  * The following fields are initialized appropriate in src_list:
  *
@@ -3570,11 +3581,11 @@ Select *sqlite3SelectNew(Parse *, ExprList *, SrcList *, Expr *, ExprList *,
  *                              if there is one.
  *
  * @param parse Parsing context.
- * @param src_list List containing single table element.
- * @retval Table object if found, NULL oterwise.
+ * @param tbl_name Table element.
+ * @retval Table object if found, NULL otherwise.
  */
 struct Table *
-sql_list_lookup_table(struct Parse *parse, struct SrcList *src_list);
+sql_lookup_table(struct Parse *parse, struct SrcList_item *tbl_name);
 
 void sqlite3OpenTable(Parse *, int iCur, Table *, int);
 /**
diff --git a/src/box/sql/trigger.c b/src/box/sql/trigger.c
index bd730c401..b6b727fdd 100644
--- a/src/box/sql/trigger.c
+++ b/src/box/sql/trigger.c
@@ -204,18 +204,14 @@ sql_trigger_finish(struct Parse *parse, struct TriggerStep *step_list,
 		if (v == 0)
 			goto triggerfinish_cleanup;
 
-		struct Table *sys_trigger =
-			sqlite3HashFind(&parse->db->pSchema->tblHash,
-					TARANTOOL_SYS_TRIGGER_NAME);
-		if (NEVER(sys_trigger == NULL))
-			goto triggerfinish_cleanup;
-
 		sql_str = sqlite3MPrintf(db, "CREATE TRIGGER %s", token->z);
 		if (db->mallocFailed)
 			goto triggerfinish_cleanup;
 
 		int cursor = parse->nTab++;
-		sqlite3OpenTable(parse, cursor, sys_trigger, OP_OpenWrite);
+		struct space *_trigger = space_by_id(BOX_TRIGGER_ID);
+		assert(_trigger != NULL);
+		vdbe_emit_open_cursor(parse, cursor, 0, _trigger);
 
 		/*
 		 * makerecord(cursor(iRecord),
diff --git a/src/box/sql/update.c b/src/box/sql/update.c
index 63d48705e..e3876f293 100644
--- a/src/box/sql/update.c
+++ b/src/box/sql/update.c
@@ -121,7 +121,7 @@ sqlite3Update(Parse * pParse,		/* The parser context */
 
 	/* Locate the table which we want to update.
 	 */
-	pTab = sql_list_lookup_table(pParse, pTabList);
+	pTab = sql_lookup_table(pParse, pTabList->a);
 	if (pTab == NULL)
 		goto update_cleanup;
 
@@ -171,9 +171,9 @@ sqlite3Update(Parse * pParse,		/* The parser context */
 		for (j = 0; j < (int)def->field_count; j++) {
 			if (strcmp(def->fields[j].name,
 				   pChanges->a[i].zName) == 0) {
-				if (pPk && table_column_is_in_pk(pTab, j)) {
+				if (pPk &&
+				    sql_space_column_is_in_pk(pTab->space, j))
 					is_pk_modified = true;
-				}
 				if (aXRef[j] != -1) {
 					sqlite3ErrorMsg(pParse,
 							"set id list: duplicate"
@@ -329,8 +329,8 @@ sqlite3Update(Parse * pParse,		/* The parser context */
 					       pTab, on_error);
 		for (i = 0; i < (int)def->field_count; i++) {
 			if (oldmask == 0xffffffff
-			    || (i < 32 && (oldmask & MASKBIT32(i)) != 0)
-			    || table_column_is_in_pk(pTab, i)) {
+			    || (i < 32 && (oldmask & MASKBIT32(i)) != 0) ||
+				sql_space_column_is_in_pk(pTab->space, i)) {
 				testcase(oldmask != 0xffffffff && i == 31);
 				sqlite3ExprCodeGetColumnOfTable(v, def,
 								pk_cursor, i,
diff --git a/test/sql-tap/analyze9.test.lua b/test/sql-tap/analyze9.test.lua
index 2b37e3ad5..05ed50192 100755
--- a/test/sql-tap/analyze9.test.lua
+++ b/test/sql-tap/analyze9.test.lua
@@ -1021,7 +1021,7 @@ test:do_execsql_test(
         INSERT INTO x1 VALUES(3, 4);
         INSERT INTO x1 VALUES(5, 6);
         ANALYZE;
-        INSERT INTO "_sql_stat4" VALUES('x1', 'abc', 0, 0, 0, '');
+        INSERT INTO "_sql_stat4" VALUES('x1', 'abc', '', '', '', '');
     ]])
 
 test:do_execsql_test(
diff --git a/test/sql-tap/join.test.lua b/test/sql-tap/join.test.lua
index 04b7674ed..ac05a98b2 100755
--- a/test/sql-tap/join.test.lua
+++ b/test/sql-tap/join.test.lua
@@ -1080,9 +1080,9 @@ jointest("join-12.9", 1000, {1, 'at most 64 tables in a join'})
 --    if X(703, "X!cmd", [=[["expr","[lsearch [db eval {PRAGMA compile_options}] MEMDEBUG]<0"]]=])
 -- then
 jointest("join-12.10", 65534, {1, 'at most 64 tables in a join'})
-jointest("join-12.11", 65535, {1, 'too many references to "T14": max 65535'})
-jointest("join-12.12", 65536, {1, 'too many references to "T14": max 65535'})
-jointest("join-12.13", 65537, {1, 'too many references to "T14": max 65535'})
+jointest("join-12.11", 65535, {1, 'at most 64 tables in a join'})
+jointest("join-12.12", 65536, {1, 'at most 64 tables in a join'})
+jointest("join-12.13", 65537, {1, 'at most 64 tables in a join'})
 --    end
 --end
 
diff --git a/test/sql/delete.result b/test/sql/delete.result
index 52f9969c1..993e9e04d 100644
--- a/test/sql/delete.result
+++ b/test/sql/delete.result
@@ -44,7 +44,7 @@ box.sql.execute("DROP TABLE t1;");
 --
 box.sql.execute("DELETE FROM t1;")
 ---
-- error: Space 'T1' does not exist
+- error: 'no such table: T1'
 ...
 box.sql.execute("CREATE TABLE t2 (s1 INT PRIMARY KEY);")
 ---
@@ -54,7 +54,7 @@ box.sql.execute("CREATE TRIGGER t2 BEFORE INSERT ON t2 BEGIN DELETE FROM t1; END
 ...
 box.sql.execute("INSERT INTO t2 VALUES (0);")
 ---
-- error: Space 'T1' does not exist
+- error: 'no such table: T1'
 ...
 box.sql.execute("DROP TABLE t2;")
 ---
-- 
2.15.1

^ permalink raw reply	[flat|nested] 28+ messages in thread

* [tarantool-patches] [PATCH 6/7] sql: don't add system spaces to Table hash
       [not found] ` <cover.1535064700.git.korablev@tarantool.org>
                     ` (4 preceding siblings ...)
  2018-08-23 22:55   ` [tarantool-patches] [PATCH 5/7] sql: remove lookups in Table hash Nikita Pettik
@ 2018-08-23 22:55   ` Nikita Pettik
  2018-08-29  0:58     ` [tarantool-patches] " Vladislav Shpilevoy
  2018-08-23 22:55   ` [tarantool-patches] [PATCH 7/7] sql: finish DD integration Nikita Pettik
  6 siblings, 1 reply; 28+ messages in thread
From: Nikita Pettik @ 2018-08-23 22:55 UTC (permalink / raw)
  To: tarantool-patches; +Cc: v.shpilevoy, Nikita Pettik

Part of #3561
---
 src/box/sql.c              | 105 ---------------------------------------------
 src/box/sql/prepare.c      |  13 ------
 src/box/sql/tarantoolInt.h |  20 ---------
 3 files changed, 138 deletions(-)

diff --git a/src/box/sql.c b/src/box/sql.c
index 61c766540..bec74bf29 100644
--- a/src/box/sql.c
+++ b/src/box/sql.c
@@ -1066,111 +1066,6 @@ cursor_advance(BtCursor *pCur, int *pRes)
 	return SQLITE_OK;
 }
 
-/*********************************************************************
- * Schema support.
- */
-
-static int
-space_foreach_put_cb(struct space *space, void *udata)
-{
-	if (space->def->opts.sql == NULL)
-		return 0; /* Not SQL space. */
-	sql_init_callback((struct init_data *) udata, space->def->name,
-			  space->def->id, 0, space->def->opts.sql);
-	for (uint32_t i = 0; i < space->index_count; ++i) {
-		struct index_def *def = space_index_def(space, i);
-		if (def->opts.sql != NULL) {
-			sql_init_callback((struct init_data *) udata, def->name,
-					  def->space_id, def->iid, def->opts.sql);
-		}
-	}
-	return 0;
-}
-
-/* Load database schema from Tarantool. */
-void tarantoolSqlite3LoadSchema(struct init_data *init)
-{
-	sql_init_callback(
-		init, TARANTOOL_SYS_SCHEMA_NAME,
-		BOX_SCHEMA_ID, 0,
-		"CREATE TABLE \""TARANTOOL_SYS_SCHEMA_NAME
-		"\" (\"key\" TEXT PRIMARY KEY, \"value\")"
-	);
-
-	sql_init_callback(
-		init, TARANTOOL_SYS_SPACE_NAME,
-		BOX_SPACE_ID, 0,
-		"CREATE TABLE \""TARANTOOL_SYS_SPACE_NAME
-		"\" (\"id\" INT PRIMARY KEY, \"owner\" INT, \"name\" TEXT, "
-		"\"engine\" TEXT, \"field_count\" INT, \"opts\", \"format\")"
-	);
-
-	sql_init_callback(
-		init, TARANTOOL_SYS_INDEX_NAME,
-		BOX_INDEX_ID, 0,
-		"CREATE TABLE \""TARANTOOL_SYS_INDEX_NAME"\" "
-		"(\"id\" INT, \"iid\" INT, \"name\" TEXT, \"type\" TEXT,"
-		"\"opts\", \"parts\", PRIMARY KEY (\"id\", \"iid\"))"
-	);
-
-	sql_init_callback(
-		init, TARANTOOL_SYS_TRIGGER_NAME,
-		BOX_TRIGGER_ID, 0,
-		"CREATE TABLE \""TARANTOOL_SYS_TRIGGER_NAME"\" ("
-		"\"name\" TEXT PRIMARY KEY, \"space_id\" INT, \"opts\")"
-	);
-
-	sql_init_callback(
-		init, TARANTOOL_SYS_TRUNCATE_NAME,
-		BOX_TRUNCATE_ID, 0,
-		"CREATE TABLE \""TARANTOOL_SYS_TRUNCATE_NAME
-		"\" (\"id\" INT PRIMARY KEY, \"count\" INT NOT NULL)"
-	);
-
-	sql_init_callback(init, TARANTOOL_SYS_SEQUENCE_NAME, BOX_SEQUENCE_ID, 0,
-			  "CREATE TABLE \""TARANTOOL_SYS_SEQUENCE_NAME
-			  "\" (\"id\" INT PRIMARY KEY, \"uid\" INT, \"name\" TEXT, \"step\" INT, "
-			  "\"max\" INT, \"min\" INT, \"start\" INT, \"cache\" INT, \"cycle\" INT)");
-
-	sql_init_callback(init, TARANTOOL_SYS_SPACE_SEQUENCE_NAME,
-			  BOX_SPACE_SEQUENCE_ID, 0,
-			  "CREATE TABLE \""TARANTOOL_SYS_SPACE_SEQUENCE_NAME
-			  "\" (\"space_id\" INT PRIMARY KEY, \"sequence_id\" INT, \"flag\" INT)");
-
-	sql_init_callback(init, TARANTOOL_SYS_SQL_STAT1_NAME,
-			  BOX_SQL_STAT1_ID, 0,
-			  "CREATE TABLE \""TARANTOOL_SYS_SQL_STAT1_NAME
-			       "\"(\"tbl\" text,"
-			       "\"idx\" text,"
-			       "\"stat\" not null,"
-			       "PRIMARY KEY(\"tbl\", \"idx\"))");
-
-	sql_init_callback(init, TARANTOOL_SYS_SQL_STAT4_NAME,
-			  BOX_SQL_STAT4_ID, 0,
-			  "CREATE TABLE \""TARANTOOL_SYS_SQL_STAT4_NAME
-			       "\"(\"tbl\" text,"
-			       "\"idx\" text,"
-			       "\"neq\" text,"
-			       "\"nlt\" text,"
-			       "\"ndlt\" text,"
-			       "\"sample\","
-			       "PRIMARY KEY(\"tbl\", \"idx\", \"sample\"))");
-
-	sql_init_callback(init, TARANTOOL_SYS_FK_CONSTRAINT_NAME,
-			  BOX_FK_CONSTRAINT_ID, 0,
-			  "CREATE TABLE \""TARANTOOL_SYS_FK_CONSTRAINT_NAME
-			  "\"(\"name\" TEXT, \"parent_id\" INT, \"child_id\" INT,"
-			  "\"deferred\" INT, \"match\" TEXT, \"on_delete\" TEXT,"
-			  "\"on_update\" TEXT, \"child_cols\", \"parent_cols\","
-			  "PRIMARY KEY(\"name\", \"child_id\"))");
-
-	/* Read _space */
-	if (space_foreach(space_foreach_put_cb, init) != 0) {
-		init->rc = SQL_TARANTOOL_ERROR;
-		return;
-	}
-}
-
 /*********************************************************************
  * Metainformation about available spaces and indices is stored in
  * _space and _index system spaces respectively.
diff --git a/src/box/sql/prepare.c b/src/box/sql/prepare.c
index bea9dc583..a59e70dd0 100644
--- a/src/box/sql/prepare.c
+++ b/src/box/sql/prepare.c
@@ -140,14 +140,6 @@ sqlite3InitDatabase(sqlite3 * db)
 
 	memset(&init, 0, sizeof(init));
 	init.db = db;
-
-	/* Load schema from Tarantool - into the primary db only. */
-	tarantoolSqlite3LoadSchema(&init);
-
-	if (init.rc) {
-		rc = init.rc;
-		goto error_out;
-	}
 	/* Read the schema information out of the schema tables
 	 */
 	assert(db->init.busy);
@@ -160,11 +152,6 @@ sqlite3InitDatabase(sqlite3 * db)
 		rc = SQLITE_NOMEM_BKPT;
 		sqlite3ResetAllSchemasOfConnection(db);
 	}
-
- error_out:
-	if (rc == SQLITE_NOMEM || rc == SQLITE_IOERR_NOMEM) {
-		sqlite3OomFault(db);
-	}
 	return rc;
 }
 
diff --git a/src/box/sql/tarantoolInt.h b/src/box/sql/tarantoolInt.h
index 69af6e723..9334de52d 100644
--- a/src/box/sql/tarantoolInt.h
+++ b/src/box/sql/tarantoolInt.h
@@ -10,30 +10,10 @@
 
 struct fkey_def;
 
-/*
- * Tarantool system spaces.
- */
-#define TARANTOOL_SYS_SEQUENCE_NAME "_sequence"
-#define TARANTOOL_SYS_SPACE_SEQUENCE_NAME "_space_sequence"
-#define TARANTOOL_SYS_SCHEMA_NAME  "_schema"
-#define TARANTOOL_SYS_SPACE_NAME   "_space"
-#define TARANTOOL_SYS_INDEX_NAME   "_index"
-#define TARANTOOL_SYS_TRIGGER_NAME "_trigger"
-#define TARANTOOL_SYS_TRUNCATE_NAME "_truncate"
-#define TARANTOOL_SYS_SQL_STAT1_NAME "_sql_stat1"
-#define TARANTOOL_SYS_SQL_STAT4_NAME "_sql_stat4"
-#define TARANTOOL_SYS_FK_CONSTRAINT_NAME "_fk_constraint"
-
-/* Max space id seen so far. */
-#define TARANTOOL_SYS_SCHEMA_MAXID_KEY "max_id"
-
 /* Insert or replace operation types - necessary for vdbe */
 #define TARANTOOL_INDEX_INSERT 1
 #define TARANTOOL_INDEX_REPLACE 2
 
-/* Load database schema from Tarantool. */
-void tarantoolSqlite3LoadSchema(struct init_data *init);
-
 /* Misc */
 const char *tarantoolErrorMessage();
 
-- 
2.15.1

^ permalink raw reply	[flat|nested] 28+ messages in thread

* [tarantool-patches] [PATCH 7/7] sql: finish DD integration
       [not found] ` <cover.1535064700.git.korablev@tarantool.org>
                     ` (5 preceding siblings ...)
  2018-08-23 22:55   ` [tarantool-patches] [PATCH 6/7] sql: don't add system spaces to " Nikita Pettik
@ 2018-08-23 22:55   ` Nikita Pettik
  2018-08-29  0:58     ` [tarantool-patches] " Vladislav Shpilevoy
  6 siblings, 1 reply; 28+ messages in thread
From: Nikita Pettik @ 2018-08-23 22:55 UTC (permalink / raw)
  To: tarantool-patches; +Cc: v.shpilevoy, Nikita Pettik

This patch finishes SQL DD integration with Tarantool core. Within it
Table hash has been removed alongside with callback routine and
OP_ParseSchema legacy opcode: we don't need anymore to update separate
SQL DD. Moreover, now we can get rid of struct Schema.

It is worth mentioning that some allocations during table and index
creations now can be moved to region, since those things are not used
after parsing and anyway destroyed.

Closes #3561
Closes #2217
---
 src/box/alter.cc             |  15 +-
 src/box/sql.c                |  36 ++--
 src/box/sql.h                |   3 +-
 src/box/sql/analyze.c        |   1 -
 src/box/sql/build.c          | 414 +++++++++----------------------------------
 src/box/sql/callback.c       |  42 -----
 src/box/sql/delete.c         |   2 +-
 src/box/sql/expr.c           |   1 -
 src/box/sql/main.c           |   1 -
 src/box/sql/pragma.c         |   5 -
 src/box/sql/prepare.c        | 117 ------------
 src/box/sql/sqliteInt.h      |  61 -------
 src/box/sql/status.c         |  16 --
 src/box/sql/trigger.c        |   5 +-
 src/box/sql/vdbe.c           |  97 ----------
 src/box/sql/vdbe.h           |   1 -
 src/box/sql/vdbeaux.c        |   6 -
 test/sql-tap/index1.test.lua |   2 +-
 18 files changed, 106 insertions(+), 719 deletions(-)

diff --git a/src/box/alter.cc b/src/box/alter.cc
index d5872961c..e49780caa 100644
--- a/src/box/alter.cc
+++ b/src/box/alter.cc
@@ -3400,16 +3400,14 @@ on_replace_trigger_rollback(struct trigger *trigger, void *event)
 		/* Rollback DELETE trigger. */
 		if (old_trigger == NULL)
 			return;
-		if (sql_trigger_replace(sql_get(),
-					sql_trigger_name(old_trigger),
+		if (sql_trigger_replace(sql_trigger_name(old_trigger),
 					sql_trigger_space_id(old_trigger),
 					old_trigger, &new_trigger) != 0)
 			panic("Out of memory on insertion into trigger hash");
 		assert(new_trigger == NULL);
 	}  else if (stmt->new_tuple != NULL && stmt->old_tuple == NULL) {
 		/* Rollback INSERT trigger. */
-		int rc = sql_trigger_replace(sql_get(),
-					     sql_trigger_name(old_trigger),
+		int rc = sql_trigger_replace(sql_trigger_name(old_trigger),
 					     sql_trigger_space_id(old_trigger),
 					     NULL, &new_trigger);
 		(void)rc;
@@ -3418,8 +3416,7 @@ on_replace_trigger_rollback(struct trigger *trigger, void *event)
 		sql_trigger_delete(sql_get(), new_trigger);
 	} else {
 		/* Rollback REPLACE trigger. */
-		if (sql_trigger_replace(sql_get(),
-					sql_trigger_name(old_trigger),
+		if (sql_trigger_replace(sql_trigger_name(old_trigger),
 					sql_trigger_space_id(old_trigger),
 					old_trigger, &new_trigger) != 0)
 			panic("Out of memory on insertion into trigger hash");
@@ -3472,8 +3469,8 @@ on_replace_dd_trigger(struct trigger * /* trigger */, void *event)
 		trigger_name[trigger_name_len] = 0;
 
 		struct sql_trigger *old_trigger;
-		int rc = sql_trigger_replace(sql_get(), trigger_name, space_id,
-					     NULL, &old_trigger);
+		int rc = sql_trigger_replace(trigger_name, space_id, NULL,
+					     &old_trigger);
 		(void)rc;
 		assert(rc == 0);
 
@@ -3520,7 +3517,7 @@ on_replace_dd_trigger(struct trigger * /* trigger */, void *event)
 		}
 
 		struct sql_trigger *old_trigger;
-		if (sql_trigger_replace(sql_get(), trigger_name,
+		if (sql_trigger_replace(trigger_name,
 					sql_trigger_space_id(new_trigger),
 					new_trigger, &old_trigger) != 0)
 			diag_raise();
diff --git a/src/box/sql.c b/src/box/sql.c
index bec74bf29..10b1e6418 100644
--- a/src/box/sql.c
+++ b/src/box/sql.c
@@ -76,36 +76,32 @@ sql_init()
 		panic("failed to initialize SQL subsystem");
 
 	assert(db != NULL);
-	/*
-	 * Initialize pSchema to use SQL parser on initialization:
-	 * e.g. Trigger objects (compiled from SQL on tuple
-	 * insertion in _trigger) need to refer it.
-	 */
-	db->pSchema = sqlite3SchemaCreate(db);
-	if (db->pSchema == NULL) {
-		sqlite3_close(db);
-		panic("failed to initialize SQL Schema subsystem");
-	}
 }
 
 void
 sql_load_schema()
 {
-	assert(db->pSchema != NULL);
-	int rc;
 	struct session *user_session = current_session();
 	int commit_internal = !(user_session->sql_flags
 				& SQLITE_InternChanges);
 
 	assert(db->init.busy == 0);
+	/*
+	 * This function is called before version upgrade.
+	 * Old versions (< 2.0) lack system spaces containing
+	 * statistics (_sql_stat1 and _sql_stat4). Thus, we can
+	 * skip statistics loading.
+	 */
+	struct space *stat = space_by_id(BOX_SQL_STAT1_ID);
+	assert(stat != NULL);
+	if (stat->def->field_count == 0)
+		return;
 	db->init.busy = 1;
-	rc = sqlite3InitDatabase(db);
-	if (rc != SQLITE_OK) {
-		sqlite3SchemaClear(db);
+	sql_analysis_load(db);
+	if (db->errCode != SQLITE_OK)
 		panic("failed to initialize SQL subsystem");
-	}
 	db->init.busy = 0;
-	if (rc == SQLITE_OK && commit_internal)
+	if (commit_internal)
 		sqlite3CommitInternalChanges();
 }
 
@@ -1492,14 +1488,16 @@ sql_ephemeral_table_new(Parse *parser, const char *name)
 		sqlite3DbFree(db, table);
 		return NULL;
 	}
-	table->space = (struct space *) calloc(1, sizeof(struct space));
+	table->space = (struct space *) region_alloc(&parser->region,
+						     sizeof(struct space));
 	if (table->space == NULL) {
-		diag_set(OutOfMemory, sizeof(struct space), "calloc", "space");
+		diag_set(OutOfMemory, sizeof(struct space), "region", "space");
 		parser->rc = SQL_TARANTOOL_ERROR;
 		parser->nErr++;
 		sqlite3DbFree(db, table);
 		return NULL;
 	}
+	memset(table->space, 0, sizeof(struct space));
 
 	table->def = def;
 	return table;
diff --git a/src/box/sql.h b/src/box/sql.h
index 5350d390c..ec6d9d3cc 100644
--- a/src/box/sql.h
+++ b/src/box/sql.h
@@ -124,7 +124,6 @@ space_trigger_list(uint32_t space_id);
 
 /**
  * Perform replace trigger in SQL internals with new AST object.
- * @param db SQL handle.
  * @param name a name of the trigger.
  * @param space_id of the space to insert trigger.
  * @param trigger AST object to insert.
@@ -134,7 +133,7 @@ space_trigger_list(uint32_t space_id);
  * @retval -1 on error.
  */
 int
-sql_trigger_replace(struct sqlite3 *db, const char *name, uint32_t space_id,
+sql_trigger_replace(const char *name, uint32_t space_id,
 		    struct sql_trigger *trigger,
 		    struct sql_trigger **old_trigger);
 
diff --git a/src/box/sql/analyze.c b/src/box/sql/analyze.c
index e835a8697..21d02ab8c 100644
--- a/src/box/sql/analyze.c
+++ b/src/box/sql/analyze.c
@@ -1155,7 +1155,6 @@ void
 sqlite3Analyze(Parse * pParse, Token * pName)
 {
 	sqlite3 *db = pParse->db;
-	assert(db->pSchema != NULL);
 	if (pName == NULL) {
 		/* Form 1:  Analyze everything */
 		sql_analyze_database(pParse);
diff --git a/src/box/sql/build.c b/src/box/sql/build.c
index 840604aa3..0d83587df 100644
--- a/src/box/sql/build.c
+++ b/src/box/sql/build.c
@@ -114,98 +114,25 @@ sql_finish_coding(struct Parse *parse_context)
 		parse_context->rc = SQLITE_ERROR;
 	}
 }
-
-/*
- * Locate the in-memory structure that describes a particular database
- * table given the name of that table. Return NULL if not found.
- * Also leave an error message in pParse->zErrMsg.
+/**
+ * Find index by its name.
+ *
+ * @param space Space index belongs to.
+ * @param name Name of index to be found.
+ *
+ * @retval NULL in case index doesn't exist.
  */
-Table *
-sqlite3LocateTable(Parse * pParse,	/* context in which to report errors */
-		   u32 flags,	/* LOCATE_VIEW or LOCATE_NOERR */
-		   const char *zName	/* Name of the table we are looking for */
-    )
-{
-	Table *p;
-
-	assert(pParse->db->pSchema != NULL);
-
-	p = sqlite3HashFind(&pParse->db->pSchema->tblHash, zName);
-	if (p == NULL) {
-		const char *zMsg =
-		    flags & LOCATE_VIEW ? "no such view" : "no such table";
-		if ((flags & LOCATE_NOERR) == 0)
-			sqlite3ErrorMsg(pParse, "%s: %s", zMsg, zName);
-	}
-
-	return p;
-}
-
-struct index *
-sqlite3LocateIndex(sqlite3 * db, const char *zName, const char *zTable)
+static struct index *
+sql_space_index_by_name(struct space *space, const char *name)
 {
-	assert(zName);
-	assert(zTable);
-
-	Table *pTab = sqlite3HashFind(&db->pSchema->tblHash, zTable);
-
-	if (pTab == NULL)
+	if (space == NULL)
 		return NULL;
-	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;
-}
-
-void
-sql_space_index_delete(struct space *space, uint32_t iid)
-{
-	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;
-		}
-	}
-	struct session *user_session = current_session();
-	user_session->sql_flags |= SQLITE_InternChanges;
-}
-
-/*
- * Erase all schema information from all attached databases (including
- * "main" and "temp") for a single database connection.
- */
-void
-sqlite3ResetAllSchemasOfConnection(sqlite3 * db)
-{
-	struct session *user_session = current_session();
-	if (db->pSchema) {
-		sqlite3SchemaClear(db);
+		if (strcmp(name, idx->def->name) == 0)
+			return idx;
 	}
-	user_session->sql_flags &= ~SQLITE_InternChanges;
+	return NULL;
 }
 
 /*
@@ -248,7 +175,7 @@ sql_space_column_is_in_pk(struct space *space, uint32_t column)
 static void
 table_delete(struct sqlite3 *db, struct Table *tab)
 {
-	if (tab->space->def != NULL)
+	if (!tab->def->opts.is_temporary)
 		goto skip_index_delete;
 	/* Delete all indices associated with this table. */
 	for (uint32_t i = 0; i < tab->space->index_count; ++i) {
@@ -259,16 +186,12 @@ table_delete(struct sqlite3 *db, struct Table *tab)
 		 */
 		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
+	if (tab->def->opts.is_temporary)
 		sql_expr_list_delete(db, tab->def->opts.checks);
 	sqlite3DbFree(db, tab);
 }
@@ -284,22 +207,6 @@ sqlite3DeleteTable(sqlite3 * db, Table * pTable)
 	table_delete(db, pTable);
 }
 
-/*
- * Unlink the given table from the hash tables and the delete the
- * table structure with all its indices and foreign keys.
- */
-void
-sqlite3UnlinkAndDeleteTable(sqlite3 * db, const char *zTabName)
-{
-	Table *p;
-
-	assert(db != 0);
-	assert(zTabName);
-	testcase(zTabName[0] == 0);	/* Zero-length table names are allowed */
-	p = sqlite3HashInsert(&db->pSchema->tblHash, zTabName, 0);
-	sqlite3DeleteTable(db, p);
-}
-
 /*
  * Given a token, return a string that consists of the text of that
  * token.  Space to hold the returned string
@@ -418,12 +325,10 @@ sqlite3StartTable(Parse *pParse, Token *pName, int noErr)
 	if (sqlite3CheckIdentifierName(pParse, zName) != SQLITE_OK)
 		goto cleanup;
 
-	assert(db->pSchema != NULL);
-	pTable = sqlite3HashFind(&db->pSchema->tblHash, zName);
-	if (pTable != NULL) {
+	uint32_t space_id = box_space_id_by_name(zName, strlen(zName));
+	if (space_id != BOX_ID_NIL) {
 		if (!noErr) {
-			sqlite3ErrorMsg(pParse,
-					"table %s already exists",
+			sqlite3ErrorMsg(pParse, "table %s already exists",
 					zName);
 		} else {
 			assert(!db->init.busy || CORRUPT_DB);
@@ -1194,54 +1099,6 @@ vdbe_emit_create_index(struct Parse *parse, struct space_def *def,
 		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
-vdbe_emit_index_schema_record(struct Parse *parse, const char *idx_name,
-			      int space_id, int iid, const char *sql_stmt)
-{
-	struct Vdbe *v = sqlite3GetVdbe(parse);
-	int entry_reg = parse->nMem + 1;
-	parse->nMem += 4;
-
-	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 space_id
-		 * is a register, but index id is literal.
-		 */
-		sqlite3VdbeAddOp2(v, OP_SCopy, space_id, entry_reg + 1);
-		sqlite3VdbeAddOp2(v, OP_Integer, iid, entry_reg + 2);
-	} else {
-		/*
-		 * An existing table is being modified; space id
-		 * is literal, but index id is a register.
-		 */
-		sqlite3VdbeAddOp2(v, OP_Integer, space_id, entry_reg + 1);
-		sqlite3VdbeAddOp2(v, OP_SCopy, iid, entry_reg + 2);
-	}
-	int p4_type = P4_DYNAMIC;
-	if (sql_stmt == NULL) {
-		sql_stmt = "";
-		p4_type = P4_STATIC;
-	}
-	sqlite3VdbeAddOp4(v, OP_String8, 0, entry_reg + 3, 0, sql_stmt,
-			  p4_type);
-	return entry_reg;
-}
-
 /*
  * Generate code to create a new space.
  * iSpaceId is a register storing the id of the space.
@@ -1296,35 +1153,6 @@ createSpace(Parse * pParse, int iSpaceId, char *zStmt)
 	sqlite3VdbeChangeP5(v, OPFLAG_NCHANGE);
 }
 
-/*
- * Generate code to emit and parse table schema record.
- * iSpaceId is a register storing the id of the space.
- * Consumes zStmt.
- */
-static void
-parseTableSchemaRecord(Parse * pParse, int iSpaceId, char *zStmt)
-{
-	Table *p = pParse->pNewTable;
-	Vdbe *v = sqlite3GetVdbe(pParse);
-	int iTop = pParse->nMem + 1;
-	pParse->nMem += 4;
-
-	sqlite3VdbeAddOp4(v, OP_String8, 0, iTop, 0,
-			  sqlite3DbStrDup(pParse->db, p->def->name), P4_DYNAMIC);
-	sqlite3VdbeAddOp2(v, OP_SCopy, iSpaceId, iTop + 1);
-	sqlite3VdbeAddOp2(v, OP_Integer, 0, iTop + 2);
-	sqlite3VdbeAddOp4(v, OP_String8, 0, iTop + 3, 0, zStmt, P4_DYNAMIC);
-
-	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);
-}
-
 int
 emitNewSysSequenceRecord(Parse *pParse, int reg_seq_id, const char *seq_name)
 {
@@ -1556,19 +1384,7 @@ sqlite3EndTable(Parse * pParse,	/* Parse context */
 	if (p == 0)
 		return;
 
-	assert(!db->init.busy || !pSelect);
-
-	/* If db->init.busy == 1, then we're called from
-	 * OP_ParseSchema2 and is about to update in-memory
-	 * schema.
-	 */
-	if (db->init.busy) {
-		p->def->id = db->init.space_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;
-		}
-	}
+	assert(!db->init.busy);
 
 	if (!p->def->opts.is_view) {
 		if (sql_table_primary_key(p) == NULL) {
@@ -1592,34 +1408,6 @@ sqlite3EndTable(Parse * pParse,	/* Parse context */
 			field->is_nullable = true;
 		}
 	}
-
-	if (db->init.busy) {
-		/*
-		 * As rebuild creates a new ExpList tree and
-		 * old_def is allocated on region release old
-		 * tree manually. This procedure is necessary
-		 * only at second stage of table creation, i.e.
-		 * before adding to table hash.
-		 */
-		struct ExprList *old_checks = p->def->opts.checks;
-		if (sql_table_def_rebuild(db, p) != 0)
-			goto cleanup;
-		sql_expr_list_delete(db, old_checks);
-		/*
-		 * Add the table to the in-memory representation
-		 * of the database.
-		 */
-		struct Table *pOld = sqlite3HashInsert(&db->pSchema->tblHash,
-						       p->def->name, p);
-		if (pOld != NULL) {
-			assert(p == pOld);
-			sqlite3OomFault(db);
-			goto cleanup;
-		}
-		pParse->pNewTable = NULL;
-		current_session()->sql_flags |= SQLITE_InternChanges;
-		goto finish;
-	}
 	/*
 	 * If not initializing, then create new Tarantool space.
 	 */
@@ -1675,14 +1463,6 @@ sqlite3EndTable(Parse * pParse,	/* Parse context */
 		sqlite3VdbeAddOp2(v, OP_SInsert, BOX_SPACE_SEQUENCE_ID,
 				  reg_space_seq_record);
 	}
-
-	/*
-	 * Reparse everything to update our internal data
-	 * structures.
-	 */
-	parseTableSchemaRecord(pParse, reg_space_id, stmt);
-	/* 'stmt' is kept inside the call. */
-
 	/* Code creation of FK constraints, if any. */
 	struct fkey_parse *fk_parse;
 	rlist_foreach_entry(fk_parse, &pParse->new_fkey, link) {
@@ -1718,14 +1498,6 @@ sqlite3EndTable(Parse * pParse,	/* Parse context */
 		fk->child_id = reg_space_id;
 		vdbe_emit_fkey_create(pParse, fk);
 	}
-
-finish:
-	/*
-	 * Checks are useless for now as all operations process
-	 * with the server checks instance. Remove to do not
-	 * consume extra memory, don't require make a copy on
-	 * space_def_dup and to improve debuggability.
-	 */
 cleanup:
 	sql_expr_list_delete(db, p->def->opts.checks);
 	p->def->opts.checks = NULL;
@@ -2035,8 +1807,6 @@ sql_code_drop_table(struct Parse *parse_context, struct space *space,
 	sqlite3VdbeAddOp2(v, OP_SDelete, BOX_SPACE_ID, idx_rec_reg);
 	sqlite3VdbeChangeP5(v, OPFLAG_NCHANGE);
 	VdbeComment((v, "Delete entry from _space"));
-	/* Remove the table entry from SQLite's internal schema. */
-	sqlite3VdbeAddOp4(v, OP_DropTable, 0, 0, 0, space->def->name, 0);
 }
 
 /**
@@ -2398,9 +2168,9 @@ sql_drop_foreign_key(struct Parse *parse_context, struct SrcList *table,
 
 /*
  * Generate code to determine next free Iid in the space identified by
- * the iSpaceId. Return register number holding the result.
+ * the iSpaceId.
  */
-static int
+static void
 getNewIid(Parse * pParse, int iSpaceId, int iCursor)
 {
 	Vdbe *v = sqlite3GetVdbe(pParse);
@@ -2434,7 +2204,6 @@ getNewIid(Parse * pParse, int iSpaceId, int iCursor)
 	sqlite3VdbeJumpHere(v, iGotoInst);
 	sqlite3VdbeAddOp3(v, OP_Column, iCursor, 1, iRes);
 	sqlite3VdbeAddOp2(v, OP_AddImm, iRes, 1);
-	return iRes;
 }
 
 /**
@@ -2445,24 +2214,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)
+table_add_index(struct space *space, struct index *index)
 {
-	uint32_t idx_count = tab->space->index_count;
+	uint32_t idx_count = space->index_count;
 	size_t indexes_sz = sizeof(struct index *) * (idx_count + 1);
-	struct index **idx = (struct index **) realloc(tab->space->index,
+	struct index **idx = (struct index **) realloc(space->index,
 						       indexes_sz);
 	if (idx == NULL) {
 		diag_set(OutOfMemory, indexes_sz, "malloc", "idx");
 		return;
 	}
-	tab->space->index = idx;
+	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;
+		struct index *tmp = space->index[0];
+		space->index[0] = index;
 		index = tmp;
 	}
-	tab->space->index[tab->space->index_count++] = index;
+	space->index[space->index_count++] = index;
 }
 
 /**
@@ -2487,11 +2256,10 @@ table_add_index(struct Table *tab, struct index *index)
  */
 static int
 index_fill_def(struct Parse *parse, struct index *index,
-	       struct Table *table, uint32_t iid, const char *name,
+	       struct space_def *space_def, uint32_t iid, const char *name,
 	       uint32_t name_len, struct ExprList *expr_list,
 	       enum sql_index_type idx_type, char *sql_stmt)
 {
-	struct space_def *space_def = table->def;
 	struct index_opts opts;
 	index_opts_create(&opts);
 	opts.is_unique = idx_type != SQL_INDEX_TYPE_NON_UNIQUE;
@@ -2502,10 +2270,13 @@ index_fill_def(struct Parse *parse, struct index *index,
 	struct key_def *key_def = key_def_new(expr_list->nExpr);
 	if (key_def == NULL)
 		goto tnt_error;
-
+	struct Table tmp_tab;
+	tmp_tab.def = space_def;
+	tmp_tab.nTabRef = 2;
 	for (int i = 0; i < expr_list->nExpr; i++) {
 		struct Expr *expr = expr_list->a[i].pExpr;
-		sql_resolve_self_reference(parse, table, NC_IdxExpr, expr, 0);
+		sql_resolve_self_reference(parse, &tmp_tab, NC_IdxExpr,
+					   expr, 0);
 		if (parse->nErr > 0)
 			goto cleanup;
 
@@ -2584,7 +2355,7 @@ sql_create_index(struct Parse *parse, struct Token *token,
 	/* Name of the index. */
 	char *name = NULL;
 	struct sqlite3 *db = parse->db;
-	struct session *user_session = current_session();
+	assert(!db->init.busy);
 
 	if (db->mallocFailed || parse->nErr > 0)
 		goto exit_create_index;
@@ -2595,27 +2366,38 @@ sql_create_index(struct Parse *parse, struct Token *token,
 			goto exit_create_index;
 		sqlite3VdbeCountChanges(v);
 	}
-	assert(db->pSchema != NULL);
 
 	/*
 	 * Find the table that is to be indexed.
 	 * Return early if not found.
 	 */
-	struct Table *table = NULL;
+	struct space *space = NULL;
+	struct space_def *def = NULL;
 	if (tbl_name != NULL) {
 		assert(token != NULL && token->z != NULL);
-		table = sqlite3LocateTable(parse, 0, tbl_name->a[0].zName);
-		assert(db->mallocFailed == 0 || table == NULL);
+		const char *name = tbl_name->a[0].zName;
+		uint32_t space_id = box_space_id_by_name(name, strlen(name));
+		if (space_id == BOX_ID_NIL) {
+			if (! if_not_exist) {
+				diag_set(ClientError, ER_NO_SUCH_SPACE, name);
+				parse->rc = SQL_TARANTOOL_ERROR;
+				parse->nErr++;
+			}
+			goto exit_create_index;
+		}
+		space = space_by_id(space_id);
+		assert(space != NULL);
+		def = space->def;
 	} else {
+		if (parse->pNewTable == NULL)
+			goto exit_create_index;
 		assert(token == NULL);
 		assert(start == NULL);
-		table = parse->pNewTable;
+		space = parse->pNewTable->space;
+		def = parse->pNewTable->def;
 	}
 
-	if (table == NULL)
-		goto exit_create_index;
-
-	if (table->def->opts.is_view) {
+	if (def->opts.is_view) {
 		sqlite3ErrorMsg(parse, "views can not be indexed");
 		goto exit_create_index;
 	}
@@ -2644,11 +2426,11 @@ sql_create_index(struct Parse *parse, struct Token *token,
 		name = sqlite3NameFromToken(db, token);
 		if (name == NULL)
 			goto exit_create_index;
-		if (sqlite3LocateIndex(db, name, table->def->name) != NULL) {
+		if (sql_space_index_by_name(space, name) != NULL) {
 			if (!if_not_exist) {
 				sqlite3ErrorMsg(parse,
 						"index %s.%s already exists",
-						table->def->name, name);
+						def->name, name);
 			}
 			goto exit_create_index;
 		}
@@ -2677,11 +2459,11 @@ sql_create_index(struct Parse *parse, struct Token *token,
 			prefix = constraint_name == NULL ?
 				"pk_unnamed_%s_%d" : "pk_%s_%d";
 		}
-		uint32_t idx_count = table->space->index_count;
+		uint32_t idx_count = space->index_count;
 		if (constraint_name == NULL ||
 		    strcmp(constraint_name, "") == 0) {
-			name = sqlite3MPrintf(db, prefix,
-					      table->def->name, idx_count + 1);
+			name = sqlite3MPrintf(db, prefix, def->name,
+					      idx_count + 1);
 		} else {
 			name = sqlite3MPrintf(db, prefix,
 					      constraint_name, idx_count + 1);
@@ -2692,12 +2474,8 @@ sql_create_index(struct Parse *parse, struct Token *token,
 	if (name == NULL || sqlite3CheckIdentifierName(parse, name) != 0)
 		goto exit_create_index;
 
-	bool is_system_space = BOX_SYSTEM_ID_MIN < table->def->id &&
-			       table->def->id < BOX_SYSTEM_ID_MAX;
-	if (is_system_space && (idx_type == SQL_INDEX_TYPE_NON_UNIQUE ||
-				idx_type == SQL_INDEX_TYPE_UNIQUE)) {
-		diag_set(ClientError, ER_MODIFY_INDEX, name,
-			 table->def->name,
+	if (tbl_name != NULL && space_is_system(space)) {
+		diag_set(ClientError, ER_MODIFY_INDEX, name, def->name,
 			 "can't create index on system space");
 		parse->nErr++;
 		parse->rc = SQL_TARANTOOL_ERROR;
@@ -2712,9 +2490,8 @@ sql_create_index(struct Parse *parse, struct Token *token,
 	 */
 	if (col_list == NULL) {
 		struct Token prev_col;
-		uint32_t last_field = table->def->field_count - 1;
-		sqlite3TokenInit(&prev_col,
-				 table->def->fields[last_field].name);
+		uint32_t last_field = def->field_count - 1;
+		sqlite3TokenInit(&prev_col, def->fields[last_field].name);
 		col_list = sql_expr_list_append(parse->db, NULL,
 						sqlite3ExprAlloc(db, TK_ID,
 								 &prev_col, 0));
@@ -2726,13 +2503,14 @@ sql_create_index(struct Parse *parse, struct Token *token,
 		sqlite3ExprListCheckLength(parse, col_list, "index");
 	}
 
-	index = (struct index *) calloc(1, sizeof(*index));
+	index = (struct index *) region_alloc(&parse->region, sizeof(*index));
 	if (index == NULL) {
-		diag_set(OutOfMemory, sizeof(*index), "calloc", "index");
+		diag_set(OutOfMemory, sizeof(*index), "region", "index");
 		parse->rc = SQL_TARANTOOL_ERROR;
 		parse->nErr++;
 		goto exit_create_index;
 	}
+	memset(index, 0, sizeof(*index));
 
 	/*
 	 * TODO: Issue a warning if two or more columns of the
@@ -2741,7 +2519,7 @@ sql_create_index(struct Parse *parse, struct Token *token,
 	 * as part of the index key.
 	 */
 	char *sql_stmt = NULL;
-	if (!db->init.busy && tbl_name != NULL) {
+	if (tbl_name != NULL) {
 		int n = (int) (parse->sLastToken.z - token->z) +
 			parse->sLastToken.n;
 		if (token->z[n - 1] == ';')
@@ -2758,7 +2536,7 @@ sql_create_index(struct Parse *parse, struct Token *token,
 	 * still must have iid == 0.
 	 */
 	uint32_t iid = idx_type != SQL_INDEX_TYPE_CONSTRAINT_PK;
-	if (index_fill_def(parse, index, table, iid, name, strlen(name),
+	if (index_fill_def(parse, index, def, iid, name, strlen(name),
 			   col_list, idx_type, sql_stmt) != 0)
 		goto exit_create_index;
 	/*
@@ -2782,7 +2560,7 @@ sql_create_index(struct Parse *parse, struct Token *token,
 	}
 	index->def->key_def->part_count = new_part_count;
 
-	if (!index_def_is_valid(index->def, table->def->name))
+	if (!index_def_is_valid(index->def, def->name))
 		goto exit_create_index;
 
 	/*
@@ -2814,9 +2592,9 @@ sql_create_index(struct Parse *parse, struct Token *token,
 	 * constraint, but has different onError (behavior on
 	 * constraint violation), then an error is raised.
 	 */
-	if (table == parse->pNewTable) {
-		for (uint32_t i = 0; i < table->space->index_count; ++i) {
-			struct index *existing_idx = table->space->index[i];
+	if (parse->pNewTable != NULL) {
+		for (uint32_t i = 0; i < space->index_count; ++i) {
+			struct index *existing_idx = space->index[i];
 			struct key_def *key_def = index->def->key_def;
 			struct key_def *exst_key_def =
 				existing_idx->def->key_def;
@@ -2852,17 +2630,6 @@ sql_create_index(struct Parse *parse, struct Token *token,
 				goto exit_create_index;
 		}
 	}
-	/*
-	 * Link the new Index structure to its table and to the
-	 * other in-memory database structures. If index created
-	 * within CREATE TABLE statement, its iid is assigned
-	 * in sql_init_callback().
-	 */
-	assert(parse->nErr == 0);
-	if (db->init.busy && tbl_name != NULL) {
-		user_session->sql_flags |= SQLITE_InternChanges;
-		index->def->iid = db->init.index_id;
-	}
 
 	/*
 	 * If this is the initial CREATE INDEX statement (or
@@ -2881,7 +2648,6 @@ sql_create_index(struct Parse *parse, struct Token *token,
 	else if (tbl_name != NULL) {
 		Vdbe *vdbe;
 		int cursor = parse->nTab++;
-		int space_id, index_id, first_schema_col;
 
 		vdbe = sqlite3GetVdbe(parse);
 		if (vdbe == 0)
@@ -2894,36 +2660,23 @@ sql_create_index(struct Parse *parse, struct Token *token,
 		sqlite3VdbeChangeP5(vdbe, OPFLAG_SEEKEQ);
 
 		assert(start != NULL);
-		space_id = table->def->id;
-		index_id = getNewIid(parse, space_id, cursor);
+		getNewIid(parse, def->id, cursor);
 		sqlite3VdbeAddOp1(vdbe, OP_Close, cursor);
-		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 =
-			vdbe_emit_index_schema_record(parse, index->def->name,
-						      space_id, index_id,
-						      sql_stmt);
-
-		/*
-		 * Reparse the schema. Code an OP_Expire
-		 * to invalidate all pre-compiled statements.
-		 */
-		sqlite3VdbeAddParseSchema2Op(vdbe, first_schema_col, 4);
+		struct index *pk = space_index(space, 0);
+		vdbe_emit_create_index(parse, def, index->def, pk->def,
+				       def->id);
 		sqlite3VdbeAddOp0(vdbe, OP_Expire);
 	}
 
-	if (!db->init.busy && tbl_name != NULL)
+	if (tbl_name != NULL)
 		goto exit_create_index;
-	table_add_index(table, index);
+	table_add_index(space, index);
 	index = NULL;
 
 	/* Clean up before exiting. */
  exit_create_index:
 	if (index != NULL)
 		free(index->def);
-	free(index);
 	sql_expr_list_delete(db, col_list);
 	sqlite3SrcListDelete(db, tbl_name);
 	sqlite3DbFree(db, name);
@@ -2992,15 +2745,6 @@ sql_drop_index(struct Parse *parse_context, struct SrcList *index_name_list,
 	sqlite3VdbeAddOp3(v, OP_MakeRecord, space_id_reg, 2, record_reg);
 	sqlite3VdbeAddOp2(v, OP_SDelete, BOX_INDEX_ID, record_reg);
 	sqlite3VdbeChangeP5(v, OPFLAG_NCHANGE);
-	/*
-	 * Still need to cleanup internal SQL structures.
-	 * Should be removed when SQL and Tarantool
-	 * data-dictionaries will be completely merged.
-	 */
-	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);
 	sqlite3DbFree(db, (void *) table_name);
diff --git a/src/box/sql/callback.c b/src/box/sql/callback.c
index d1a48597c..3cf3a835d 100644
--- a/src/box/sql/callback.c
+++ b/src/box/sql/callback.c
@@ -268,45 +268,3 @@ sqlite3FindFunction(sqlite3 * db,	/* An open database */
 	}
 	return 0;
 }
-
-/*
- * Free all resources held by the schema structure. The void* argument points
- * at a Schema struct. This function does not call sqlite3DbFree(db, ) on the
- * pointer itself, it just cleans up subsidiary resources (i.e. the contents
- * of the schema hash tables).
- *
- * The Schema.cache_size variable is not cleared.
- */
-void
-sqlite3SchemaClear(sqlite3 * db)
-{
-	assert(db->pSchema != NULL);
-
-	Hash temp1;
-	HashElem *pElem;
-	Schema *pSchema = db->pSchema;
-
-	temp1 = pSchema->tblHash;
-	sqlite3HashInit(&pSchema->tblHash);
-	for (pElem = sqliteHashFirst(&temp1); pElem;
-	     pElem = sqliteHashNext(pElem)) {
-		Table *pTab = sqliteHashData(pElem);
-		sqlite3DeleteTable(0, pTab);
-	}
-	sqlite3HashClear(&temp1);
-
-	db->pSchema = NULL;
-}
-
-/* Create a brand new schema. */
-Schema *
-sqlite3SchemaCreate(sqlite3 * db)
-{
-	struct Schema *p =
-		(struct Schema *) sqlite3DbMallocZero(0, sizeof(Schema));
-	if (p == NULL)
-		sqlite3OomFault(db);
-	else
-		sqlite3HashInit(&p->tblHash);
-	return p;
-}
diff --git a/src/box/sql/delete.c b/src/box/sql/delete.c
index a457a71d1..16679ea99 100644
--- a/src/box/sql/delete.c
+++ b/src/box/sql/delete.c
@@ -56,7 +56,7 @@ sql_lookup_table(struct Parse *parse, struct SrcList_item *tbl_name)
 	struct Table *table = sqlite3DbMallocZero(parse->db, sizeof(*table));
 	if (table == NULL)
 		return NULL;
-	table->def = space_def_dup(space->def);
+	table->def = space->def;
 	table->space = space;
 	table->nTabRef = 1;
 	tbl_name->pTab = table;
diff --git a/src/box/sql/expr.c b/src/box/sql/expr.c
index 7b6300c75..f560ada09 100644
--- a/src/box/sql/expr.c
+++ b/src/box/sql/expr.c
@@ -1515,7 +1515,6 @@ sqlite3SrcListDup(sqlite3 * db, SrcList * p, int flags)
 		struct SrcList_item *pNewItem = &pNew->a[i];
 		struct SrcList_item *pOldItem = &p->a[i];
 		Table *pTab;
-		pNewItem->pSchema = pOldItem->pSchema;
 		pNewItem->zName = sqlite3DbStrDup(db, pOldItem->zName);
 		pNewItem->zAlias = sqlite3DbStrDup(db, pOldItem->zAlias);
 		pNewItem->fg = pOldItem->fg;
diff --git a/src/box/sql/main.c b/src/box/sql/main.c
index 69b2fec80..996f13c25 100644
--- a/src/box/sql/main.c
+++ b/src/box/sql/main.c
@@ -1859,7 +1859,6 @@ sql_init_db(sqlite3 **out_db)
 	db->szMmap = sqlite3GlobalConfig.szMmap;
 	db->nMaxSorterMmap = 0x7FFFFFFF;
 
-	db->pSchema = NULL;
 	db->magic = SQLITE_MAGIC_OPEN;
 	if (db->mallocFailed) {
 		goto opendb_out;
diff --git a/src/box/sql/pragma.c b/src/box/sql/pragma.c
index 063836989..b594c8521 100644
--- a/src/box/sql/pragma.c
+++ b/src/box/sql/pragma.c
@@ -474,11 +474,6 @@ sqlite3Pragma(Parse * pParse, Token * pId,	/* First part of [schema.]id field */
 		sqlite3ErrorMsg(pParse, "no such pragma: %s", zLeft);
 		goto pragma_out;
 	}
-
-	/* Make sure the database schema is loaded if the pragma requires that */
-	if ((pPragma->mPragFlg & PragFlg_NeedSchema) != 0) {
-		assert(db->pSchema != NULL);
-	}
 	/* Register the result column names for pragmas that return results */
 	if ((pPragma->mPragFlg & PragFlg_NoColumns) == 0
 	    && ((pPragma->mPragFlg & PragFlg_NoColumns1) == 0 || zRight == 0)
diff --git a/src/box/sql/prepare.c b/src/box/sql/prepare.c
index a59e70dd0..34484a0e3 100644
--- a/src/box/sql/prepare.c
+++ b/src/box/sql/prepare.c
@@ -39,123 +39,6 @@
 #include "box/space.h"
 #include "box/session.h"
 
-/*
- * Fill the InitData structure with an error message that indicates
- * that the database is corrupt.
- */
-static void
-corruptSchema(struct init_data *data,	/* Initialization context */
-	      const char *zObj,	/* Object being parsed at the point of error */
-	      const char *zExtra	/* Error information */
-    )
-{
-	sqlite3 *db = data->db;
-	if (!db->mallocFailed) {
-		char *z;
-		if (zObj == 0)
-			zObj = "?";
-		z = sqlite3MPrintf(db, "malformed database schema (%s)", zObj);
-		if (zExtra)
-			z = sqlite3MPrintf(db, "%z - %s", z, zExtra);
-		sqlite3DbFree(db, *data->pzErrMsg);
-		*data->pzErrMsg = z;
-	}
-	data->rc = db->mallocFailed ? SQLITE_NOMEM_BKPT : SQLITE_CORRUPT_BKPT;
-}
-
-/* Necessary for appropriate value return in InitCallback.
- * Otherwise it will return uint32_t instead of 64 bit pointer.
- */
-struct space *space_by_id(uint32_t id);
-
-int
-sql_init_callback(struct init_data *init, const char *name,
-		  uint32_t space_id, uint32_t index_id, const char *sql)
-{
-	sqlite3 *db = init->db;
-	if (db->mallocFailed) {
-		corruptSchema(init, name, 0);
-		return 1;
-	}
-
-	assert(space_id > 0);
-	if ((strlen(sql) > 7) &&
-	    sqlite3_strnicmp(sql, "create ", 7) == 0) {
-		/* Call the parser to process a CREATE TABLE, INDEX or VIEW.
-		 * But because db->init.busy is set to 1, no VDBE code is generated
-		 * or executed.  All the parser does is build the internal data
-		 * structures that describe the table, index, or view.
-		 */
-		int rc;
-		sqlite3_stmt *pStmt;
-		TESTONLY(int rcp);	/* Return code from sqlite3_prepare() */
-
-		assert(db->init.busy);
-		db->init.space_id = space_id;
-		db->init.index_id = index_id;
-		db->init.orphanTrigger = 0;
-		TESTONLY(rcp =) sqlite3_prepare(db, sql,
-						strlen(sql) + 1, &pStmt, 0);
-		rc = db->errCode;
-		assert((rc & 0xFF) == (rcp & 0xFF));
-		if (SQLITE_OK != rc) {
-			init->rc = rc;
-			if (rc == SQLITE_NOMEM)
-				sqlite3OomFault(db);
-			else if (rc != SQLITE_INTERRUPT &&
-				 (rc & 0xFF) != SQLITE_LOCKED)
-				corruptSchema(init, name, sqlite3_errmsg(db));
-		}
-		sqlite3_finalize(pStmt);
-	} else if (name == NULL || (sql != NULL && sql[0] != 0)) {
-		corruptSchema(init, name, 0);
-	} else {
-		/* If the SQL column is blank it means this is an index that
-		 * was created to be the PRIMARY KEY or to fulfill a UNIQUE
-		 * constraint for a CREATE TABLE.  The index should have already
-		 * been created when we processed the CREATE TABLE.  All we have
-		 * to do here is record the root page number for that index.
-		 */
-		struct space *space = space_by_id(space_id);
-		const char *zSpace = space_name(space);
-		struct index *idx = sqlite3LocateIndex(db, name, zSpace);
-		assert(idx != NULL);
-		idx->def->iid = index_id;
-	}
-	return 0;
-}
-
-/*
- * Attempt to read the database schema and initialize internal
- * data structures for a single database file.
- * Return one of the SQLITE_ error codes to indicate or failure.
- */
-extern int
-sqlite3InitDatabase(sqlite3 * db)
-{
-	int rc;
-	struct init_data init;
-
-	assert(db->pSchema != NULL);
-
-	memset(&init, 0, sizeof(init));
-	init.db = db;
-	/* Read the schema information out of the schema tables
-	 */
-	assert(db->init.busy);
-	{
-		rc = init.rc;
-		if (rc == SQLITE_OK)
-			sql_analysis_load(db);
-	}
-	if (db->mallocFailed) {
-		rc = SQLITE_NOMEM_BKPT;
-		sqlite3ResetAllSchemasOfConnection(db);
-	}
-	return rc;
-}
-
-
 /*
  * Compile the UTF-8 encoded SQL statement zSql into a statement handle.
  */
diff --git a/src/box/sql/sqliteInt.h b/src/box/sql/sqliteInt.h
index 08893c473..f6b930266 100644
--- a/src/box/sql/sqliteInt.h
+++ b/src/box/sql/sqliteInt.h
@@ -1428,7 +1428,6 @@ void *sqlite3_wsd_find(void *K, int L);
 typedef struct AggInfo AggInfo;
 typedef struct Bitvec Bitvec;
 typedef struct Column Column;
-typedef struct Schema Schema;
 typedef struct Expr Expr;
 typedef struct ExprList ExprList;
 typedef struct ExprSpan ExprSpan;
@@ -1476,13 +1475,6 @@ typedef int VList;
 #include "vdbe.h"
 #include "os.h"
 
-/*
- * An instance of the following structure stores a database schema.
- */
-struct Schema {
-	Hash tblHash;		/* All tables indexed by name */
-};
-
 /*
  * The number of different kinds of things that can be limited
  * using the sqlite3_limit() interface.
@@ -1543,7 +1535,6 @@ struct sqlite3 {
 	sqlite3_vfs *pVfs;	/* OS Interface */
 	struct Vdbe *pVdbe;	/* List of active virtual machines */
 	struct coll *pDfltColl;	/* The default collating sequence (BINARY) */
-	struct Schema *pSchema; /* Schema of the database */
 	i64 szMmap;		/* Default mmap_size setting */
 	int errCode;		/* Most recent error code (SQLITE_*) */
 	int errMask;		/* & result codes with this before returning */
@@ -2355,7 +2346,6 @@ struct SrcList {
 	int nSrc;		/* Number of tables or subqueries in the FROM clause */
 	u32 nAlloc;		/* Number of entries allocated in a[] below */
 	struct SrcList_item {
-		Schema *pSchema;	/* Schema to which this item is fixed */
 		char *zName;	/* Name of the table */
 		char *zAlias;	/* The "B" part of a "A AS B" phrase.  zName is the "A" */
 		Table *pTab;	/* An SQL table corresponding to zName */
@@ -2982,19 +2972,6 @@ struct StrAccum {
 
 #define isMalloced(X)  (((X)->printfFlags & SQLITE_PRINTF_MALLOCED)!=0)
 
-/**
- * A pointer to this structure is used to communicate information
- * from sqlite3Init and OP_ParseSchema into the sql_init_callback.
- */
-struct init_data {
-	/** The database being initialized. */
-	sqlite3 *db;
-	/** Error message stored here. */
-	char **pzErrMsg;
-	/** Result code stored here. */
-	int rc;
-};
-
 /*
  * Structure containing global configuration data for the SQLite library.
  *
@@ -3334,28 +3311,9 @@ u32 sqlite3ExprListFlags(const ExprList *);
 int sqlite3Init(sqlite3 *);
 
 void sqlite3Pragma(Parse *, Token *, Token *, Token *, int);
-void sqlite3ResetAllSchemasOfConnection(sqlite3 *);
 void sqlite3CommitInternalChanges();
 void sqlite3DeleteColumnNames(sqlite3 *, Table *);
 
-/**
- * This is the callback routine for the code that initializes the
- * database.  See sqlite3Init() below for additional information.
- * This routine is also called from the OP_ParseSchema2 opcode of
- * the VDBE.
- *
- * @param init Initialization context.
- * @param name Name of thing being created.
- * @param space_id Space identifier.
- * @param index_id Index identifier.
- * @param sql Text of SQL query.
- *
- * @retval 0 on success, 1 otherwise.
- */
-int
-sql_init_callback(struct init_data *init, const char *name,
-		  uint32_t space_id, uint32_t index_id, const char *sql);
-
 /**
  * Return true if given column is part of primary key.
  * If field number is less than 63, corresponding bit
@@ -3697,23 +3655,6 @@ void sqlite3ExprIfFalse(Parse *, Expr *, int, int);
 void sqlite3ExprIfFalseDup(Parse *, Expr *, int, int);
 #define LOCATE_VIEW    0x01
 #define LOCATE_NOERR   0x02
-Table *sqlite3LocateTable(Parse *, u32 flags, const char *);
-
-struct index *
-sqlite3LocateIndex(sqlite3 *, const char *, const char *);
-void sqlite3UnlinkAndDeleteTable(sqlite3 *, const char *);
-
-/**
- * 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);
@@ -4561,8 +4502,6 @@ sql_analysis_load(struct sqlite3 *db);
 
 void sqlite3RegisterLikeFunctions(sqlite3 *, int);
 int sqlite3IsLikeFunction(sqlite3 *, Expr *, int *, char *);
-void sqlite3SchemaClear(sqlite3 *);
-Schema *sqlite3SchemaCreate(sqlite3 *);
 int sqlite3CreateFunc(sqlite3 *, const char *, int, int, void *,
 		      void (*)(sqlite3_context *, int, sqlite3_value **),
 		      void (*)(sqlite3_context *, int, sqlite3_value **),
diff --git a/src/box/sql/status.c b/src/box/sql/status.c
index 209ed8571..f1501e57a 100644
--- a/src/box/sql/status.c
+++ b/src/box/sql/status.c
@@ -240,22 +240,6 @@ sqlite3_db_status(sqlite3 * db,	/* The database connection whose status is desir
 	case SQLITE_DBSTATUS_SCHEMA_USED:{
 			int nByte = 0;	/* Used to accumulate return value */
 
-			db->pnBytesFreed = &nByte;
-			Schema *pSchema = db->pSchema;
-			if (ALWAYS(pSchema != 0)) {
-				HashElem *p;
-				nByte += ROUND8(sizeof(HashElem)) *
-					 pSchema->tblHash.count;
-
-				for (p = sqliteHashFirst(&pSchema->tblHash); p;
-				     p = sqliteHashNext(p)) {
-					sqlite3DeleteTable(db,
-							   (Table *)
-							   sqliteHashData(p));
-				}
-			}
-			db->pnBytesFreed = 0;
-
 			*pHighwater = 0;
 			*pCurrent = nByte;
 			break;
diff --git a/src/box/sql/trigger.c b/src/box/sql/trigger.c
index b6b727fdd..4fbb855c4 100644
--- a/src/box/sql/trigger.c
+++ b/src/box/sql/trigger.c
@@ -422,7 +422,6 @@ vdbe_code_drop_trigger(struct Parse *parser, const char *trigger_name,
 		       bool account_changes)
 {
 	sqlite3 *db = parser->db;
-	assert(db->pSchema != NULL);
 	struct Vdbe *v = sqlite3GetVdbe(parser);
 	if (v == NULL)
 		return;
@@ -449,7 +448,6 @@ sql_drop_trigger(struct Parse *parser, struct SrcList *name, bool no_err)
 	sqlite3 *db = parser->db;
 	if (db->mallocFailed)
 		goto drop_trigger_cleanup;
-	assert(db->pSchema != NULL);
 
 	struct Vdbe *v = sqlite3GetVdbe(parser);
 	if (v != NULL)
@@ -477,11 +475,10 @@ sql_drop_trigger(struct Parse *parser, struct SrcList *name, bool no_err)
 }
 
 int
-sql_trigger_replace(struct sqlite3 *db, const char *name, uint32_t space_id,
+sql_trigger_replace(const char *name, uint32_t space_id,
 		    struct sql_trigger *trigger,
 		    struct sql_trigger **old_trigger)
 {
-	assert(db->pSchema != NULL);
 	assert(trigger == NULL || strcmp(name, trigger->zName) == 0);
 
 	struct space *space = space_cache_find(space_id);
diff --git a/src/box/sql/vdbe.c b/src/box/sql/vdbe.c
index ad251ca99..8973d68d8 100644
--- a/src/box/sql/vdbe.c
+++ b/src/box/sql/vdbe.c
@@ -584,7 +584,6 @@ int sqlite3VdbeExec(Vdbe *p)
 #endif
 	int rc = SQLITE_OK;        /* Value to return */
 	sqlite3 *db = p->db;       /* The database */
-	u8 resetSchemaOnFault = 0; /* Reset schema after an error if positive */
 	int iCompare = 0;          /* Result of last comparison */
 	unsigned nVmStep = 0;      /* Number of virtual machine steps */
 #ifndef SQLITE_OMIT_PROGRESS_CALLBACK
@@ -4533,55 +4532,6 @@ case OP_ResetSorter: {
 	break;
 }
 
-/* Opcode: ParseSchema2 P1 P2 * * *
- * Synopsis: rows=r[P1@P2]
- *
- * For each 4-tuple from r[P1@P2] range convert to following
- * format and update the schema with the resulting entry:
- *  <name, pageno (which is hash(spaceId, indexId)), sql>
- */
-case OP_ParseSchema2: {
-	struct init_data init;
-	Mem *pRec, *pRecEnd;
-	assert(db->pSchema != NULL);
-
-	init.db = db;
-	init.pzErrMsg = &p->zErrMsg;
-
-	assert(db->init.busy==0);
-	db->init.busy = 1;
-	init.rc = SQLITE_OK;
-	assert(!db->mallocFailed);
-
-	pRec = &aMem[pOp->p1];
-	pRecEnd = pRec + pOp->p2;
-
-	/*
-	 * A register range contains
-	 *   name1, spaceId1, indexId1, sql1,
-	 *   ...
-	 *   nameN, spaceIdN, indexIdN, sqlN.
-	 *
-	 * Uppdate the schema.
-	 */
-	for(; pRecEnd - pRec >= 4 && init.rc == SQLITE_OK; pRec += 4) {
-		sql_init_callback(&init, pRec[0].z, pRec[1].u.i, pRec[2].u.i,
-				  pRec[3].z);
-	}
-
-	rc = init.rc;
-	db->init.busy = 0;
-
-	if (rc) {
-		sqlite3ResetAllSchemasOfConnection(db);
-		if (rc==SQLITE_NOMEM) {
-			goto no_mem;
-		}
-		goto abort_due_to_error;
-	}
-	break;
-}
-
 /* Opcode: RenameTable P1 * * P4 *
  * Synopsis: P1 = space_id, P4 = name
  *
@@ -4599,7 +4549,6 @@ case OP_RenameTable: {
 	struct space *space;
 	const char *zOldTableName;
 	const char *zNewTableName;
-	struct init_data init;
 	char *zSqlStmt;
 
 	space_id = pOp->p1;
@@ -4614,22 +4563,6 @@ case OP_RenameTable: {
 					 sqlite3Strlen30(zOldTableName));
 	rc = sql_rename_table(space_id, zNewTableName, &zSqlStmt);
 	if (rc) goto abort_due_to_error;
-
-	sqlite3UnlinkAndDeleteTable(db, space->def->name);
-
-	init.db = db;
-	init.pzErrMsg = &p->zErrMsg;
-	assert(db->init.busy == 0);
-	db->init.busy = 1;
-	init.rc = SQLITE_OK;
-	sql_init_callback(&init, zNewTableName, space_id, 0, zSqlStmt);
-	db->init.busy = 0;
-	rc = init.rc;
-	if (rc) {
-		sqlite3CommitInternalChanges();
-		goto abort_due_to_error;
-	}
-
 	/*
 	 * Rebuild 'CREATE TRIGGER' expressions of all triggers
 	 * created on this table. Sure, this action is not atomic
@@ -4674,33 +4607,6 @@ case OP_LoadAnalysis: {
 	break;
 }
 
-/* Opcode: DropTable P1 * * P4 *
- *
- * Remove the internal (in-memory) data structures that describe
- * the table named P4 in database P1.  This is called after a table
- * is dropped from disk (using the Destroy opcode) in order to keep
- * the internal representation of the
- * schema consistent with what is on disk.
- */
-case OP_DropTable: {
-	sqlite3UnlinkAndDeleteTable(db, pOp->p4.z);
-	break;
-}
-
-/* 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 Tarantool DD.
- */
-case OP_DropIndex: {
-	sql_space_index_delete(pOp->p4.space, pOp->p1);
-	break;
-}
-
 /* Opcode: Program P1 P2 P3 P4 P5
  *
  * Execute the trigger program passed as P4 (type P4_SUBPROGRAM).
@@ -5319,9 +5225,6 @@ abort_due_to_error:
 	sqlite3VdbeHalt(p);
 	if (rc==SQLITE_IOERR_NOMEM) sqlite3OomFault(db);
 	rc = SQLITE_ERROR;
-	if (resetSchemaOnFault>0) {
-		sqlite3SchemaClear(db);
-	}
 
 	/* This is the only way out of this procedure. */
 vdbe_return:
diff --git a/src/box/sql/vdbe.h b/src/box/sql/vdbe.h
index 6ee83a5de..2d75605bb 100644
--- a/src/box/sql/vdbe.h
+++ b/src/box/sql/vdbe.h
@@ -213,7 +213,6 @@ void sqlite3VdbeVerifyNoResultRow(Vdbe * p);
 #else
 #define sqlite3VdbeVerifyNoResultRow(A)
 #endif
-void sqlite3VdbeAddParseSchema2Op(Vdbe * p, int, int);
 void sqlite3VdbeChangeOpcode(Vdbe *, u32 addr, u8);
 void sqlite3VdbeChangeP1(Vdbe *, u32 addr, int P1);
 void sqlite3VdbeChangeP2(Vdbe *, u32 addr, int P2);
diff --git a/src/box/sql/vdbeaux.c b/src/box/sql/vdbeaux.c
index 12c2edffe..cb906d385 100644
--- a/src/box/sql/vdbeaux.c
+++ b/src/box/sql/vdbeaux.c
@@ -404,12 +404,6 @@ sqlite3VdbeAddOp4Dup8(Vdbe * p,	/* Add the opcode to this VM */
 	return sqlite3VdbeAddOp4(p, op, p1, p2, p3, p4copy, p4type);
 }
 
-void
-sqlite3VdbeAddParseSchema2Op(Vdbe * p, int iRec, int n)
-{
-	sqlite3VdbeAddOp3(p, OP_ParseSchema2, iRec, n, 0);
-}
-
 /*
  * Add an opcode that includes the p4 value as an integer.
  */
diff --git a/test/sql-tap/index1.test.lua b/test/sql-tap/index1.test.lua
index fdce2683c..5d060fcdf 100755
--- a/test/sql-tap/index1.test.lua
+++ b/test/sql-tap/index1.test.lua
@@ -83,7 +83,7 @@ test:do_catchsql_test(
         CREATE INDEX index1 ON test1(f1)
     ]], {
         -- <index-2.1>
-        1, "no such table: TEST1"
+        1, "Space 'TEST1' does not exist"
         -- </index-2.1>
     })
 
-- 
2.15.1

^ permalink raw reply	[flat|nested] 28+ messages in thread

* [tarantool-patches] Re: [PATCH 7/7] sql: finish DD integration
  2018-08-23 22:55   ` [tarantool-patches] [PATCH 7/7] sql: finish DD integration Nikita Pettik
@ 2018-08-29  0:58     ` Vladislav Shpilevoy
  2018-09-20 14:45       ` Kirill Yukhin
  0 siblings, 1 reply; 28+ messages in thread
From: Vladislav Shpilevoy @ 2018-08-29  0:58 UTC (permalink / raw)
  To: tarantool-patches, Nikita Pettik

Thanks for the patch! See my fixes on the branch and below:

commit 7976184bfe14a6a829c4be432fd206e750224c6f
Author: Vladislav Shpilevoy <v.shpilevoy@tarantool.org>
Date:   Tue Aug 28 21:53:54 2018 -0300

     Review fixes

diff --git a/src/box/sql.c b/src/box/sql.c
index 85d1602da..191925b43 100644
--- a/src/box/sql.c
+++ b/src/box/sql.c
@@ -77,10 +77,6 @@ sql_init()
  void
  sql_load_schema()
  {
-	struct session *user_session = current_session();
-	int commit_internal = !(user_session->sql_flags
-				& SQLITE_InternChanges);
-
  	assert(db->init.busy == 0);
  	/*
  	 * This function is called before version upgrade.
@@ -97,8 +93,6 @@ sql_load_schema()
  	if (db->errCode != SQLITE_OK)
  		panic("failed to initialize SQL subsystem");
  	db->init.busy = 0;
-	if (commit_internal)
-		sqlite3CommitInternalChanges();
  }
  
  void
diff --git a/src/box/sql/build.c b/src/box/sql/build.c
index da1c42248..5f889740f 100644
--- a/src/box/sql/build.c
+++ b/src/box/sql/build.c
@@ -135,15 +135,6 @@ sql_space_index_by_name(struct space *space, const char *name)
  	return NULL;
  }
  
-/*
- * This routine is called when a commit occurs.
- */
-void
-sqlite3CommitInternalChanges()
-{
-	current_session()->sql_flags &= ~SQLITE_InternChanges;
-}
-
  bool
  sql_space_column_is_in_pk(struct space *space, uint32_t column)
  {
diff --git a/src/box/sql/cursor.c b/src/box/sql/cursor.c
index b0966910c..f984f8c32 100644
--- a/src/box/sql/cursor.c
+++ b/src/box/sql/cursor.c
@@ -108,8 +108,7 @@ sqlite3CursorPayload(BtCursor *pCur, u32 offset, u32 amt, void *pBuf)
  	const void *pPayload;
  	u32 sz;
  	pPayload = tarantoolSqlite3PayloadFetch(pCur, &sz);
-	if ((uptr) (offset + amt) > sz)
-		return SQLITE_CORRUPT_BKPT;
+	assert((uptr) (offset + amt) <= sz);
  	memcpy(pBuf, pPayload + offset, amt);
  	return SQLITE_OK;
  }
diff --git a/src/box/sql/main.c b/src/box/sql/main.c
index 996f13c25..782f99452 100644
--- a/src/box/sql/main.c
+++ b/src/box/sql/main.c
@@ -850,9 +850,6 @@ sqlite3ErrName(int rc)
  		case SQLITE_IOERR_CONVPATH:
  			zName = "SQLITE_IOERR_CONVPATH";
  			break;
-		case SQLITE_CORRUPT:
-			zName = "SQLITE_CORRUPT";
-			break;
  		case SQLITE_NOTFOUND:
  			zName = "SQLITE_NOTFOUND";
  			break;
@@ -942,7 +939,6 @@ sqlite3ErrStr(int rc)
  		/* SQLITE_NOMEM       */ "out of memory",
  		/* SQLITE_INTERRUPT   */ "interrupted",
  		/* SQLITE_IOERR       */ "disk I/O error",
-		/* SQLITE_CORRUPT     */ "database disk image is malformed",
  		/* SQLITE_NOTFOUND    */ "unknown operation",
  		/* SQLITE_FULL        */ "database or disk is full",
  		/* SQLITE_CANTOPEN    */ "unable to open database file",
@@ -1949,7 +1945,7 @@ opendb_out:
  }
  
  /*
- * The following routines are substitutes for constants SQLITE_CORRUPT,
+ * The following routines are substitutes for constants
   * SQLITE_MISUSE, SQLITE_CANTOPEN, SQLITE_NOMEM and possibly other error
   * constants.  They serve two purposes:
   *
@@ -1967,13 +1963,6 @@ reportError(int iErr, int lineno, const char *zType)
  	return iErr;
  }
  
-int
-sqlite3CorruptError(int lineno)
-{
-	testcase(sqlite3GlobalConfig.xLog != 0);
-	return reportError(SQLITE_CORRUPT, lineno, "database corruption");
-}
-
  int
  sqlite3MisuseError(int lineno)
  {
diff --git a/src/box/sql/sqliteInt.h b/src/box/sql/sqliteInt.h
index 3047d41c3..dc919f0c0 100644
--- a/src/box/sql/sqliteInt.h
+++ b/src/box/sql/sqliteInt.h
@@ -399,8 +399,6 @@ enum sql_ret_code {
  	SQLITE_INTERRUPT,
  	/** Some kind of disk I/O error occurred. */
  	SQLITE_IOERR,
-	/** The database disk image is malformed. */
-	SQLITE_CORRUPT,
  	/** Unknown opcode in sqlite3_file_control(). */
  	SQLITE_NOTFOUND,
  	/** Insertion failed because database is full. */
@@ -1593,7 +1591,6 @@ struct sqlite3 {
   * Possible values for the sqlite3.flags.
   */
  #define SQLITE_VdbeTrace      0x00000001	/* True to trace VDBE execution */
-#define SQLITE_InternChanges  0x00000002	/* Uncommitted Hash table changes */
  #define SQLITE_FullColNames   0x00000004	/* Show full column names on SELECT */
  #define SQLITE_ShortColNames  0x00000040	/* Show short columns names */
  #define SQLITE_CountRows      0x00000080	/* Count rows changed by INSERT, */
@@ -3125,10 +3122,8 @@ struct TreeView {
   * using sqlite3_log().  The routines also provide a convenient place
   * to set a debugger breakpoint.
   */
-int sqlite3CorruptError(int);
  int sqlite3MisuseError(int);
  int sqlite3CantopenError(int);
-#define SQLITE_CORRUPT_BKPT sqlite3CorruptError(__LINE__)
  #define SQLITE_MISUSE_BKPT sqlite3MisuseError(__LINE__)
  #define SQLITE_CANTOPEN_BKPT sqlite3CantopenError(__LINE__)
  #ifdef SQLITE_DEBUG
@@ -3310,7 +3305,6 @@ u32 sqlite3ExprListFlags(const ExprList *);
  int sqlite3Init(sqlite3 *);
  
  void sqlite3Pragma(Parse *, Token *, Token *, Token *, int);
-void sqlite3CommitInternalChanges();
  void sqlite3DeleteColumnNames(sqlite3 *, Table *);
  
  /**
@@ -4799,8 +4793,6 @@ void sqlite3VectorErrorMsg(Parse *, Expr *);
   */
  extern int sqlSubProgramsRemaining;
  
-extern int sqlite3InitDatabase(sqlite3 * db);
-
  /**
   * Generate VDBE code to halt execution with correct error if
   * the object with specified key is already present (or doesn't
diff --git a/src/box/sql/vdbe.c b/src/box/sql/vdbe.c
index 8973d68d8..813b0a041 100644
--- a/src/box/sql/vdbe.c
+++ b/src/box/sql/vdbe.c
@@ -2903,12 +2903,8 @@ case OP_Savepoint: {
  				}
  				rc = p->rc;
  			} else {
-				if (p1==SAVEPOINT_ROLLBACK) {
+				if (p1==SAVEPOINT_ROLLBACK)
  					box_txn_rollback_to_savepoint(pSavepoint->tnt_savepoint);
-					if ((user_session->sql_flags &
-					     SQLITE_InternChanges) != 0)
-						sqlite3ExpirePreparedStatements(db);
-				}
  			}
  
  			/* Regardless of whether this is a RELEASE or ROLLBACK, destroy all
@@ -4584,7 +4580,6 @@ case OP_RenameTable: {
  			 * In this case, rename table back and
  			 * try again.
  			 */
-			sqlite3CommitInternalChanges();
  			goto abort_due_to_error;
  		}
  		trigger = next_trigger;
diff --git a/src/box/sql/vdbeaux.c b/src/box/sql/vdbeaux.c
index 94696cef5..c843bc786 100644
--- a/src/box/sql/vdbeaux.c
+++ b/src/box/sql/vdbeaux.c
@@ -2428,8 +2428,6 @@ sqlite3VdbeHalt(Vdbe * p)
  					closeCursorsAndFree(p);
  					sqlite3RollbackAll(p);
  					p->nChange = 0;
-				} else {
-					sqlite3CommitInternalChanges();
  				}
  			} else {
  				box_txn_rollback();

^ permalink raw reply	[flat|nested] 28+ messages in thread

* [tarantool-patches] Re: [PATCH 5/7] sql: remove lookups in Table hash
  2018-08-23 22:55   ` [tarantool-patches] [PATCH 5/7] sql: remove lookups in Table hash Nikita Pettik
@ 2018-08-29  0:58     ` Vladislav Shpilevoy
  2018-09-02 23:52       ` n.pettik
  0 siblings, 1 reply; 28+ messages in thread
From: Vladislav Shpilevoy @ 2018-08-29  0:58 UTC (permalink / raw)
  To: tarantool-patches, Nikita Pettik

Thanks for the patch! See 11 comments below and a
commit on the branch.

On 23/08/2018 19:55, Nikita Pettik wrote:
> Instead of looking at Table hash lets extract space from Tarantool
> internals and create Table wrapper around it.

The major question - why do we still need struct Table? Just because
of Table.nTabRef? This looks like an insignificant stopper since
anyway we need refs for struct space and multiversion transactional
DDL. We could create surrogate struct spaces for some temporary things
instead of surrogate Tables. And do not create anything for ordinal
spaces.

> 
> Part of #3561
> ---
>   src/box/sql/build.c            |  16 +---
>   src/box/sql/delete.c           |  84 ++++++++------------
>   src/box/sql/expr.c             |   2 +-
>   src/box/sql/fkey.c             |  15 +++-
>   src/box/sql/insert.c           |  48 ++++++-----
>   src/box/sql/pragma.c           | 176 ++++++++++++++++++++++-------------------
>   src/box/sql/select.c           |  41 +---------
>   src/box/sql/sqliteInt.h        |  35 +++++---
>   src/box/sql/trigger.c          |  10 +--
>   src/box/sql/update.c           |  10 +--
>   test/sql-tap/analyze9.test.lua |   2 +-
>   test/sql-tap/join.test.lua     |   6 +-
>   test/sql/delete.result         |   4 +-
>   13 files changed, 204 insertions(+), 245 deletions(-)
> 
> diff --git a/src/box/sql/build.c b/src/box/sql/build.c
> index 79dc46592..840604aa3 100644
> --- a/src/box/sql/build.c
> +++ b/src/box/sql/build.c
> @@ -217,22 +217,10 @@ sqlite3CommitInternalChanges()
>   	current_session()->sql_flags &= ~SQLITE_InternChanges;
>   }
>   
> -/*
> - * Return true if given column is part of primary key.
> - * If field number is less than 63, corresponding bit
> - * in column mask is tested. Otherwise, check whether 64-th bit
> - * in mask is set or not. If it is set, then iterate through
> - * key parts of primary index and check field number.
> - * In case it isn't set, there are no key columns among
> - * the rest of fields.
> - */
>   bool
> -table_column_is_in_pk(Table *table, uint32_t column)
> +sql_space_column_is_in_pk(struct space *space, uint32_t column)
>   {
> -	struct space *space = space_by_id(table->def->id);
> -	assert(space != NULL);
> -
> -	struct index *primary_idx = index_find(space, 0 /* PK */);
> +	struct index *primary_idx = space->index[0];

1. space_index(space, 0). Also I see below check for idx == NULL.
But if a space has no indexes, its space->index is NULL as well
AFAIK. How does it work? The previous version was checking if a
space has an index.

>   	/* Views don't have any indexes. */
>   	if (primary_idx == NULL)
>   		return false;
> diff --git a/src/box/sql/delete.c b/src/box/sql/delete.c
> index 0f285cc8b..a457a71d1 100644
> --- a/src/box/sql/delete.c
> +++ b/src/box/sql/delete.c
> @@ -36,16 +36,31 @@
>   #include "tarantoolInt.h"
>   
>   struct Table *
> -sql_list_lookup_table(struct Parse *parse, SrcList *src_list)
> +sql_lookup_table(struct Parse *parse, struct SrcList_item *tbl_name)
>   {
> -	struct SrcList_item *item = src_list->a;
> -	assert(item != NULL && src_list->nSrc == 1);
> -	struct Table *table = sqlite3LocateTable(parse, 0, item->zName);
> -	sqlite3DeleteTable(parse->db, item->pTab);
> -	item->pTab = table;
> -	if (table != NULL)
> -		item->pTab->nTabRef++;
> -	if (sqlite3IndexedByLookup(parse, item) != 0)
> +	assert(tbl_name != NULL);
> +	uint32_t space_id = box_space_id_by_name(tbl_name->zName,
> +						 strlen(tbl_name->zName));
> +	sqlite3DeleteTable(parse->db, tbl_name->pTab);

2. What is happening here? If tbl_name->pTab != NULL, why do you
recreate it?

> +	struct space *space = space_by_id(space_id);
> +	if (space_id == BOX_ID_NIL || space == NULL) {

3. If 'space_id' == BOX_ID_NIL then space == NULL as well,
always. You should not even try to lookup a space when
box_space_id_by_name returned BOX_ID_NIL.

> +		sqlite3ErrorMsg(parse, "no such table: %s", tbl_name->zName);

4. ER_NO_SUCH_SPACE.

> +		return NULL;
> +	}
> +	assert(space != NULL);
> +	if (space->def->field_count == 0) {
> +		sqlite3ErrorMsg(parse, "no format for space: %s",
> +				tbl_name->zName);

5. ER_UNSUPPORTED or a new error code.

> +		return NULL;
> +	}
> +	struct Table *table = sqlite3DbMallocZero(parse->db, sizeof(*table));
> +	if (table == NULL)
> +		return NULL;
> +	table->def = space_def_dup(space->def);
> +	table->space = space;
> +	table->nTabRef = 1;
> +	tbl_name->pTab = table;
> +	if (sqlite3IndexedByLookup(parse, tbl_name) != 0)
>   		table = NULL;

6. And here table leaks.

>   	return table;
>   }
> diff --git a/src/box/sql/expr.c b/src/box/sql/expr.c
> index cb4f89e35..7b6300c75 100644
> --- a/src/box/sql/expr.c
> +++ b/src/box/sql/expr.c
> @@ -278,7 +278,7 @@ comparisonAffinity(Expr * pExpr)
>   		aff =
>   		    sqlite3CompareAffinity(pExpr->x.pSelect->pEList->a[0].pExpr,
>   					   aff);
> -	} else if (NEVER(aff == 0)) {
> +	} else {
>   		aff = AFFINITY_BLOB;
>   	}
>   	return aff;

7. Why? How is affinity linked with the patch?

> diff --git a/src/box/sql/insert.c b/src/box/sql/insert.c
> index 9220d34e1..d3950254e 100644
> --- a/src/box/sql/insert.c
> +++ b/src/box/sql/insert.c
> @@ -1234,40 +1233,40 @@ xferOptimization(Parse * pParse,	/* Parser context */
>   	 * we have to check the semantics.
>   	 */
>   	pItem = pSelect->pSrc->a;
> -	pSrc = sqlite3LocateTable(pParse, 0, pItem->zName);
> +	uint32_t src_id = box_space_id_by_name(pItem->zName,
> +						strlen(pItem->zName));
>   	/* FROM clause does not contain a real table. */
> -	if (pSrc == NULL)
> +	if (src_id == BOX_ID_NIL)
>   		return 0;
> +	struct space *src = space_by_id(src_id);
> +	assert(src != NULL);

8. In this function you do not need pDest as Table. It is
enough to pass a space here. Only sqlite3OpenTable still
uses struct Table in this function, but anyway in the first
lines it does space lookup. I fixed this remark partially and
dirty. Please, finish my fixes (refactor or remove
sqlite3OpenTable etc).

> diff --git a/src/box/sql/pragma.c b/src/box/sql/pragma.c
> index 6173462ff..063836989 100644
> --- a/src/box/sql/pragma.c
> +++ b/src/box/sql/pragma.c
> @@ -245,6 +245,95 @@ sql_default_engine_set(const char *engine_name)
>   	return 0;
>   }
>   
> +/**
> + * This function handles PRAGMA TABLE_INFO(<table>).
> + *
> + * Return a single row for each column of the named table.
> + * The columns of the returned data set are:
> + *
> + * - cid: Column id (numbered from left to right, starting at 0);
> + * - name: Column name;
> + * - type: Column declaration type;
> + * - notnull: True if 'NOT NULL' is part of column declaration;
> + * - dflt_value: The default value for the column, if any.
> + *
> + * @param parse Current parsing context.
> + * @param tbl_name Name of table to be examined.
> + */
> +static void
> +sql_pragma_table_info(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);
> +	struct space_def *def = space->def;
> +	struct index *pk = space->index[0];

9. space_index(). What is strange, you use space_index sometimes.

> +	parse->nMem = 6;
> +	if (def->opts.is_view) {
> +		const char *sql = def->opts.sql;
> +		sql_view_assign_cursors(parse, sql);
> +	}
> +	struct Vdbe *v = sqlite3GetVdbe(parse);
> +	for (uint32_t i = 0, k; i < def->field_count; ++i) {
> +		if (!sql_space_column_is_in_pk(space, i)) {
> +			k = 0;
> +		} else if (pk == NULL) {
> +			k = 1;
> +		} else {
> +			struct key_def *kdef = pk->def->key_def;
> +			k = key_def_find(kdef, i) - kdef->parts + 1;
> +		}
> +		bool is_nullable = def->fields[i].is_nullable;
> +		char *expr_str = def->fields[i].default_value;
> +		const char *name = def->fields[i].name;
> +		enum field_type type = def->fields[i].type;
> +		sqlite3VdbeMultiLoad(v, 1, "issisi", i, name,
> +				     field_type_strs[type], !is_nullable,
> +				     expr_str, k);
> +		sqlite3VdbeAddOp2(v, OP_ResultRow, 1, 6);
> +	}
> +}
> diff --git a/src/box/sql/sqliteInt.h b/src/box/sql/sqliteInt.h
> index 3ae8db1bc..08893c473 100644
> --- a/src/box/sql/sqliteInt.h
> +++ b/src/box/sql/sqliteInt.h
> @@ -3333,6 +3333,11 @@ void sqlite3ExprListSetSpan(Parse *, ExprList *, ExprSpan *);
>   u32 sqlite3ExprListFlags(const ExprList *);
>   int sqlite3Init(sqlite3 *);
>   
> +void sqlite3Pragma(Parse *, Token *, Token *, Token *, int);
> +void sqlite3ResetAllSchemasOfConnection(sqlite3 *);
> +void sqlite3CommitInternalChanges();
> +void sqlite3DeleteColumnNames(sqlite3 *, Table *);

10. Why do you need these movements?

> +
>   /**
>    * This is the callback routine for the code that initializes the
>    * database.  See sqlite3Init() below for additional information.
> diff --git a/test/sql-tap/analyze9.test.lua b/test/sql-tap/analyze9.test.lua
> index 2b37e3ad5..05ed50192 100755
> --- a/test/sql-tap/analyze9.test.lua
> +++ b/test/sql-tap/analyze9.test.lua
> @@ -1021,7 +1021,7 @@ test:do_execsql_test(
>           INSERT INTO x1 VALUES(3, 4);
>           INSERT INTO x1 VALUES(5, 6);
>           ANALYZE;
> -        INSERT INTO "_sql_stat4" VALUES('x1', 'abc', 0, 0, 0, '');
> +        INSERT INTO "_sql_stat4" VALUES('x1', 'abc', '', '', '', '');

11. Why are tests changed? As I understand, you just did refactoring.

>       ]])
>   
>   test:do_execsql_test(
> diff --git a/test/sql-tap/join.test.lua b/test/sql-tap/join.test.lua
> index 04b7674ed..ac05a98b2 100755
> --- a/test/sql-tap/join.test.lua
> +++ b/test/sql-tap/join.test.lua
> @@ -1080,9 +1080,9 @@ jointest("join-12.9", 1000, {1, 'at most 64 tables in a join'})
>   --    if X(703, "X!cmd", [=[["expr","[lsearch [db eval {PRAGMA compile_options}] MEMDEBUG]<0"]]=])
>   -- then
>   jointest("join-12.10", 65534, {1, 'at most 64 tables in a join'})
> -jointest("join-12.11", 65535, {1, 'too many references to "T14": max 65535'})
> -jointest("join-12.12", 65536, {1, 'too many references to "T14": max 65535'})
> -jointest("join-12.13", 65537, {1, 'too many references to "T14": max 65535'})
> +jointest("join-12.11", 65535, {1, 'at most 64 tables in a join'})
> +jointest("join-12.12", 65536, {1, 'at most 64 tables in a join'})
> +jointest("join-12.13", 65537, {1, 'at most 64 tables in a join'})
>   --    end
>   --end
>   
> diff --git a/test/sql/delete.result b/test/sql/delete.result
> index 52f9969c1..993e9e04d 100644
> --- a/test/sql/delete.result
> +++ b/test/sql/delete.result
> @@ -44,7 +44,7 @@ box.sql.execute("DROP TABLE t1;");
>   --
>   box.sql.execute("DELETE FROM t1;")
>   ---
> -- error: Space 'T1' does not exist
> +- error: 'no such table: T1'
>   ...
>   box.sql.execute("CREATE TABLE t2 (s1 INT PRIMARY KEY);")
>   ---
> @@ -54,7 +54,7 @@ box.sql.execute("CREATE TRIGGER t2 BEFORE INSERT ON t2 BEGIN DELETE FROM t1; END
>   ...
>   box.sql.execute("INSERT INTO t2 VALUES (0);")
>   ---
> -- error: Space 'T1' does not exist
> +- error: 'no such table: T1'
>   ...
>   box.sql.execute("DROP TABLE t2;")
>   ---
> 

My diff is on the branch and below:

commit df9555fabbed80c080858f5662e0bbd351056cc2
Author: Vladislav Shpilevoy <v.shpilevoy@tarantool.org>
Date:   Tue Aug 28 21:21:35 2018 -0300

     Review fixes

diff --git a/src/box/sql/build.c b/src/box/sql/build.c
index ff8594fba..6ac3463f5 100644
--- a/src/box/sql/build.c
+++ b/src/box/sql/build.c
@@ -224,17 +224,12 @@ sql_space_column_is_in_pk(struct space *space, uint32_t column)
  	/* Views don't have any indexes. */
  	if (primary_idx == NULL)
  		return false;
-	struct index_def *idx_def = primary_idx->def;
-	uint64_t pk_mask = idx_def->key_def->column_mask;
-	if (column < 63) {
-		return pk_mask & (((uint64_t) 1) << column);
-	} else if ((pk_mask & (((uint64_t) 1) << 63)) != 0) {
-		for (uint32_t i = 0; i < idx_def->key_def->part_count; ++i) {
-			struct key_part *part = &idx_def->key_def->parts[i];
-			if (part->fieldno == column)
-				return true;
-		}
-	}
+	struct key_def *key_def = primary_idx->def->key_def;
+	uint64_t pk_mask = key_def->column_mask;
+	if (column < 63)
+		return (pk_mask & (((uint64_t) 1) << column)) != 0;
+	else if ((pk_mask & (((uint64_t) 1) << 63)) != 0)
+		return key_def_find(key_def, column) != NULL;
  	return false;
  }
  
diff --git a/src/box/sql/fkey.c b/src/box/sql/fkey.c
index 215ad2867..b2d2a19e7 100644
--- a/src/box/sql/fkey.c
+++ b/src/box/sql/fkey.c
@@ -620,10 +620,10 @@ fkey_emit_check(struct Parse *parser, struct Table *tab, int reg_old,
  		memset(&fake_tab, 0, sizeof(fake_tab));
  		fake_tab.def = child->def;
  		fake_tab.space = child;
+		/* Prevent from deallocationg fake_tab. */
+		fake_tab.nTabRef = 2;
  		item->pTab = &fake_tab;
  		item->zName = sqlite3DbStrDup(db, child->def->name);
-		/* Prevent from deallocationg fake_tab. */
-		item->pTab->nTabRef = 2;
  		item->iCursor = parser->nTab++;
  
  		if (reg_new != 0) {
diff --git a/src/box/sql/insert.c b/src/box/sql/insert.c
index a2f323f04..49bac58f0 100644
--- a/src/box/sql/insert.c
+++ b/src/box/sql/insert.c
@@ -46,17 +46,15 @@
  void
  sqlite3OpenTable(Parse * pParse,	/* Generate code into this VDBE */
  		 int iCur,	/* The cursor number of the table */
-		 Table * pTab,	/* The table to be opened */
+		 struct space *space,	/* The table to be opened */
  		 int opcode)	/* OP_OpenRead or OP_OpenWrite */
  {
  	Vdbe *v;
  	v = sqlite3GetVdbe(pParse);
  	assert(opcode == OP_OpenWrite || opcode == OP_OpenRead);
-
-	struct space *space = space_by_id(pTab->def->id);
  	assert(space->index_count > 0);
  	vdbe_emit_open_cursor(pParse, iCur, 0, space);
-	VdbeComment((v, "%s", pTab->def->name));
+	VdbeComment((v, "%s", space->def->name));
  }
  
  char *
@@ -1159,6 +1157,8 @@ xferOptimization(Parse * pParse,	/* Parser context */
  	int destHasUniqueIdx = 0;	/* True if pDest has a UNIQUE index */
  	int regData, regTupleid;	/* Registers holding data and tupleid */
  	struct session *user_session = current_session();
+	struct space *dest = space_by_id(pDest->def->id);
+	assert(dest != NULL);
  
  	if (pSelect == NULL)
  		return 0;	/* Must be of the form  INSERT INTO ... SELECT ... */
@@ -1170,7 +1170,7 @@ xferOptimization(Parse * pParse,	/* Parser context */
  		return 0;
  	}
  	/* The pDest must not have triggers. */
-	if (space_trigger_list(pDest->def->id) != NULL)
+	if (dest->sql_triggers != NULL)
  		return 0;
  	if (onError == ON_CONFLICT_ACTION_DEFAULT) {
  		if (onError == ON_CONFLICT_ACTION_DEFAULT)
@@ -1221,41 +1221,41 @@ xferOptimization(Parse * pParse,	/* Parser context */
  	 */
  	pItem = pSelect->pSrc->a;
  	uint32_t src_id = box_space_id_by_name(pItem->zName,
-						strlen(pItem->zName));
+					       strlen(pItem->zName));
  	/* FROM clause does not contain a real table. */
  	if (src_id == BOX_ID_NIL)
  		return 0;
  	struct space *src = space_by_id(src_id);
  	assert(src != NULL);
  	/* Src and dest may not be the same table. */
-	if (src->def->id == pDest->space->def->id)
+	if (src->def->id == dest->def->id)
  		return 0;
  	/* Src may not be a view. */
  	if (src->def->opts.is_view)
  		return 0;
  	/* Number of columns must be the same in src and dst. */
-	if (pDest->def->field_count != src->def->field_count)
+	if (dest->def->field_count != src->def->field_count)
  		return 0;
-	for (i = 0; i < (int)pDest->def->field_count; i++) {
+	for (i = 0; i < (int)dest->def->field_count; i++) {
  		enum affinity_type dest_affinity =
-			pDest->def->fields[i].affinity;
+			dest->def->fields[i].affinity;
  		enum affinity_type src_affinity =
  			src->def->fields[i].affinity;
  		/* Affinity must be the same on all columns. */
  		if (dest_affinity != src_affinity)
  			return 0;
  		uint32_t id;
-		if (sql_column_collation(pDest->def, i, &id) !=
+		if (sql_column_collation(dest->def, i, &id) !=
  		    sql_column_collation(src->def, i, &id))
  			return 0;
-		if (!pDest->def->fields[i].is_nullable &&
+		if (!dest->def->fields[i].is_nullable &&
  		    src->def->fields[i].is_nullable)
  			return 0;
  		/* Default values for second and subsequent columns need to match. */
  		if (i > 0) {
  			char *src_expr_str = src->def->fields[i].default_value;
  			char *dest_expr_str =
-				pDest->def->fields[i].default_value;
+				dest->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)
@@ -1264,8 +1264,8 @@ xferOptimization(Parse * pParse,	/* Parser context */
  			}
  		}
  	}
-	for (uint32_t i = 0; i < pDest->space->index_count; ++i) {
-		pDestIdx = pDest->space->index[i];
+	for (uint32_t i = 0; i < dest->index_count; ++i) {
+		pDestIdx = dest->index[i];
  		if (pDestIdx->def->opts.is_unique)
  			destHasUniqueIdx = 1;
  		for (uint32_t j = 0; j < src->index_count; ++j) {
@@ -1280,7 +1280,7 @@ xferOptimization(Parse * pParse,	/* Parser context */
  	}
  	/* Get server checks. */
  	ExprList *pCheck_src = space_checks_expr_list(src->def->id);
-	ExprList *pCheck_dest = space_checks_expr_list(pDest->def->id);
+	ExprList *pCheck_dest = space_checks_expr_list(dest->def->id);
  	if (pCheck_dest != NULL &&
  	    sqlite3ExprListCompare(pCheck_src, pCheck_dest, -1) != 0) {
  		/* Tables have different CHECK constraints.  Ticket #2252 */
@@ -1291,8 +1291,6 @@ xferOptimization(Parse * pParse,	/* Parser context */
  	 * So the extra complication to make this rule less restrictive is probably
  	 * not worth the effort.  Ticket [6284df89debdfa61db8073e062908af0c9b6118e]
  	 */
-	struct space *dest = space_by_id(pDest->def->id);
-	assert(dest != NULL);
  	if ((user_session->sql_flags & SQLITE_ForeignKeys) != 0 &&
  	    !rlist_empty(&dest->child_fkey))
  		return 0;
@@ -1312,7 +1310,7 @@ xferOptimization(Parse * pParse,	/* Parser context */
  	iDest = pParse->nTab++;
  	regData = sqlite3GetTempReg(pParse);
  	regTupleid = sqlite3GetTempReg(pParse);
-	sqlite3OpenTable(pParse, iDest, pDest, OP_OpenWrite);
+	sqlite3OpenTable(pParse, iDest, dest, OP_OpenWrite);
  	assert(destHasUniqueIdx);
  	if (destHasUniqueIdx	/* (2) */
  	    || (onError != ON_CONFLICT_ACTION_ABORT
@@ -1342,10 +1340,7 @@ xferOptimization(Parse * pParse,	/* Parser context */
  
  		vdbe_emit_open_cursor(pParse, iSrc, pSrcIdx->def->iid, src);
  		VdbeComment((v, "%s", pSrcIdx->def->name));
-		struct space *dest_space = space_by_id(pDest->def->id);
-		vdbe_emit_open_cursor(pParse, iDest,
-				      pDestIdx->def->iid,
-				      dest_space);
+		vdbe_emit_open_cursor(pParse, iDest, pDestIdx->def->iid, dest);
  		VdbeComment((v, "%s", pDestIdx->def->name));
  		addr1 = sqlite3VdbeAddOp2(v, OP_Rewind, iSrc, 0);
  		VdbeCoverage(v);
diff --git a/src/box/sql/pragma.c b/src/box/sql/pragma.c
index 063836989..3451d7c79 100644
--- a/src/box/sql/pragma.c
+++ b/src/box/sql/pragma.c
@@ -270,15 +270,13 @@ sql_pragma_table_info(struct Parse *parse, const char *tbl_name)
  		return;
  	struct space *space = space_by_id(space_id);
  	assert(space != NULL);
-	struct space_def *def = space->def;
  	struct index *pk = space->index[0];
  	parse->nMem = 6;
-	if (def->opts.is_view) {
-		const char *sql = def->opts.sql;
-		sql_view_assign_cursors(parse, sql);
-	}
+	if (space->def->opts.is_view)
+		sql_view_assign_cursors(parse, space->def->opts.sql);
  	struct Vdbe *v = sqlite3GetVdbe(parse);
-	for (uint32_t i = 0, k; i < def->field_count; ++i) {
+	struct field_def *field = space->def->fields;
+	for (uint32_t i = 0, k; i < space->def->field_count; ++i, ++field) {
  		if (!sql_space_column_is_in_pk(space, i)) {
  			k = 0;
  		} else if (pk == NULL) {
@@ -287,13 +285,10 @@ sql_pragma_table_info(struct Parse *parse, const char *tbl_name)
  			struct key_def *kdef = pk->def->key_def;
  			k = key_def_find(kdef, i) - kdef->parts + 1;
  		}
-		bool is_nullable = def->fields[i].is_nullable;
-		char *expr_str = def->fields[i].default_value;
-		const char *name = def->fields[i].name;
-		enum field_type type = def->fields[i].type;
-		sqlite3VdbeMultiLoad(v, 1, "issisi", i, name,
-				     field_type_strs[type], !is_nullable,
-				     expr_str, k);
+		sqlite3VdbeMultiLoad(v, 1, "issisi", i, field->name,
+				     field_type_strs[field->type],
+				     !field->is_nullable, field->default_value,
+				     k);
  		sqlite3VdbeAddOp2(v, OP_ResultRow, 1, 6);
  	}
  }
@@ -316,7 +311,7 @@ sql_pragma_table_stats(struct space *space, void *data)
  	if (pk == NULL)
  		return 0;
  	struct Vdbe *v = sqlite3GetVdbe(parse);
-	LogEst tuple_count_est = sqlite3LogEst(pk->vtab->size(pk));
+	LogEst tuple_count_est = sqlite3LogEst(index_size(pk));
  	size_t avg_tuple_size_pk = sql_index_tuple_size(space, pk);
  	parse->nMem = 4;
  	sqlite3VdbeMultiLoad(v, 1, "ssii", space->def->name, 0,
diff --git a/src/box/sql/sqliteInt.h b/src/box/sql/sqliteInt.h
index 596e38fdc..0c9801e1c 100644
--- a/src/box/sql/sqliteInt.h
+++ b/src/box/sql/sqliteInt.h
@@ -3585,7 +3585,7 @@ Select *sqlite3SelectNew(Parse *, ExprList *, SrcList *, Expr *, ExprList *,
  struct Table *
  sql_lookup_table(struct Parse *parse, struct SrcList_item *tbl_name);
  
-void sqlite3OpenTable(Parse *, int iCur, Table *, int);
+void sqlite3OpenTable(Parse *, int iCur, struct space *, int);
  /**
   * Generate code for a DELETE FROM statement.
   *
diff --git a/src/box/sql/where.c b/src/box/sql/where.c
index 1b9ba436c..ce93b0940 100644
--- a/src/box/sql/where.c
+++ b/src/box/sql/where.c
@@ -4582,6 +4582,7 @@ sqlite3WhereBegin(Parse * pParse,	/* The parser context */
  		struct SrcList_item *pTabItem = &pTabList->a[pLevel->iFrom];
  		Table *pTab = pTabItem->pTab;
  		pLoop = pLevel->pWLoop;
+		struct space *space = space_cache_find(pTabItem->pTab->def->id);
  		if (pTab->def->id == 0 || pTab->def->opts.is_view) {
  			/* Do nothing */
  		} else if ((pLoop->wsFlags & WHERE_IDX_ONLY) == 0 &&
@@ -4591,7 +4592,7 @@ sqlite3WhereBegin(Parse * pParse,	/* The parser context */
  				op = OP_OpenWrite;
  				pWInfo->aiCurOnePass[0] = pTabItem->iCursor;
  			};
-			sqlite3OpenTable(pParse, pTabItem->iCursor, pTab, op);
+			sqlite3OpenTable(pParse, pTabItem->iCursor, space, op);
  			assert(pTabItem->iCursor == pLevel->iTabCur);
  			testcase(pWInfo->eOnePass == ONEPASS_OFF
  				 && pTab->nCol == BMS - 1);
@@ -4607,7 +4608,6 @@ sqlite3WhereBegin(Parse * pParse,	/* The parser context */
  		}
  		if (pLoop->wsFlags & WHERE_INDEXED) {
  			struct index_def *idx_def = pLoop->index_def;
-			struct space *space = space_cache_find(pTabItem->pTab->def->id);
  			int iIndexCur;
  			int op = OP_OpenRead;
  			/* Check if index is primary. Either of

^ permalink raw reply	[flat|nested] 28+ messages in thread

* [tarantool-patches] Re: [PATCH 4/7] sql: refactor ALTER RENAME code generation
  2018-08-23 22:55   ` [tarantool-patches] [PATCH 4/7] sql: refactor ALTER RENAME code generation Nikita Pettik
@ 2018-08-29  0:58     ` Vladislav Shpilevoy
  2018-09-02 23:52       ` n.pettik
  0 siblings, 1 reply; 28+ messages in thread
From: Vladislav Shpilevoy @ 2018-08-29  0:58 UTC (permalink / raw)
  To: tarantool-patches, Nikita Pettik

Thanks for the patch! See 2 comments below.

On 23/08/2018 19:55, Nikita Pettik wrote:
> We are going to remove legacy cache of struct Table, so lets get rid of
> using SQLite tables in alter routine.
> 
> Part of #3561
> ---
>   src/box/sql/alter.c         | 114 ++++++++++++++------------------------------
>   src/box/sql/parse.y         |   2 +-
>   src/box/sql/sqliteInt.h     |  13 ++++-
>   src/box/sql/vdbe.c          |   5 +-
>   test/sql-tap/alter.test.lua |   4 +-
>   5 files changed, 53 insertions(+), 85 deletions(-)
> 
> diff --git a/src/box/sql/alter.c b/src/box/sql/alter.c
> index 349589be4..320dfdd05 100644
> --- a/src/box/sql/alter.c
> +++ b/src/box/sql/alter.c
> @@ -34,91 +34,51 @@
>    * that implements the ALTER TABLE command.
>    */
>   #include "sqliteInt.h"
> -#include "src/box/session.h"
> -#include "tarantoolInt.h"
> +#include "box/box.h"
> +#include "box/schema.h"
>   
> -/*
> - * Generate code to drop and reload the internal representation of table
> - * pTab from the database, including triggers.
> - * Argument zName is the name of the table in the database schema at
> - * the time the generated code is executed. This can be different from
> - * pTab->zName if this function is being called to code part of an
> - * "ALTER TABLE RENAME TO" statement.
> - */
> -static void
> -reloadTableSchema(Parse * pParse, Table * pTab, const char *zName)
> -{
> -	Vdbe *v;
> -	v = sqlite3GetVdbe(pParse);
> -	if (NEVER(v == 0))
> -		return;
> -
> -	char *zNewName = sqlite3MPrintf(pParse->db, "%s", zName);
> -	sqlite3VdbeAddOp4(v, OP_RenameTable, pTab->def->id, 0, 0, zNewName,
> -			  P4_DYNAMIC);
> -}
> -
> -/*
> - * Generate code to implement the "ALTER TABLE xxx RENAME TO yyy"
> - * command.
> - */
>   void
> -sqlite3AlterRenameTable(Parse * pParse,	/* Parser context. */
> -			SrcList * pSrc,	/* The table to rename. */
> -			Token * pName)	/* The new table name. */
> +sql_alter_table_rename(struct Parse *parse, struct SrcList *src_tab,
> +		       struct Token *new_name_tk)
>   {
> -	Table *pTab;		/* Table being renamed */
> -	char *zName = 0;	/* NULL-terminated version of pName */
> -	sqlite3 *db = pParse->db;	/* Database connection */
> -	Vdbe *v;
> -	uint32_t savedDbFlags;	/* Saved value of db->flags */
> -	struct session *user_session = current_session();
> -
> -	savedDbFlags = user_session->sql_flags;
> -
> -	if (NEVER(db->mallocFailed))
> -		goto exit_rename_table;
> -	assert(pSrc->nSrc == 1);
> -
> -	pTab = sqlite3LocateTable(pParse, 0, pSrc->a[0].zName);
> -	if (pTab == NULL)
> -		goto exit_rename_table;
> -
> -	user_session->sql_flags |= SQLITE_PreferBuiltin;
> -
> -	/* Get a NULL terminated version of the new table name. */
> -	zName = sqlite3NameFromToken(db, pName);
> -	if (!zName)
> -		goto exit_rename_table;
> -
> -	/* Check that a table named 'zName' does not already exist
> -	 * in database. If so, this is an error.
> -	 */
> -	if (sqlite3HashFind(&db->pSchema->tblHash, zName) != NULL) {
> -		sqlite3ErrorMsg(pParse,
> -				"there is already another table or index with this name: %s",
> -				zName);
> +	assert(src_tab->nSrc == 1);
> +	struct sqlite3 *db = parse->db;
> +	char *new_name = sqlite3NameFromToken(db, new_name_tk);
> +	if (new_name == NULL)
>   		goto exit_rename_table;
> +	/* Check that new name isn't occupied by another table. */
> +	uint32_t space_id = box_space_id_by_name(new_name, strlen(new_name));
> +	if (space_id != BOX_ID_NIL) {
> +		diag_set(ClientError, ER_SQL_EXECUTE, tt_sprintf("there is "
> +			"already another table with this name: %s", new_name));

1. ER_SPACE_EXISTS

> +		goto tnt_error;
>   	}
> -	if (pTab->def->opts.is_view) {
> -		sqlite3ErrorMsg(pParse, "view %s may not be altered",
> -				pTab->def->name);
> -		goto exit_rename_table;
> +	const char *tbl_name = src_tab->a[0].zName;
> +	space_id = box_space_id_by_name(tbl_name, strlen(tbl_name));
> +	if (space_id == BOX_ID_NIL) {
> +		diag_set(ClientError, ER_NO_SUCH_SPACE, tbl_name);
> +		goto tnt_error;
>   	}
> -	/* Begin a transaction for database. */
> -	v = sqlite3GetVdbe(pParse);
> -	if (v == 0) {
> -		goto exit_rename_table;
> +	struct space *space = space_by_id(space_id);
> +	assert(space != NULL);
> +	if (space->def->opts.is_view) {
> +		diag_set(ClientError, ER_SQL_EXECUTE,
> +			 "view may not be altered");
> +		goto tnt_error;

2. ER_ALTER_SPACE or ER_UNSUPPORTED, on your choice.

>   	}
> -	sql_set_multi_write(pParse, false);
> -
> +	sql_set_multi_write(parse, false);
>   	/* Drop and reload the internal table schema. */
> -	reloadTableSchema(pParse, pTab, zName);
> -
> - exit_rename_table:
> -	sqlite3SrcListDelete(db, pSrc);
> -	sqlite3DbFree(db, zName);
> -	user_session->sql_flags = savedDbFlags;
> +	struct Vdbe *v = sqlite3GetVdbe(parse);
> +	sqlite3VdbeAddOp4(v, OP_RenameTable, space_id, 0, 0, new_name,
> +			  P4_DYNAMIC);
> +exit_rename_table:
> +	sqlite3SrcListDelete(db, src_tab);
> +	return;
> +tnt_error:
> +	sqlite3DbFree(db, new_name);
> +	parse->rc = SQL_TARANTOOL_ERROR;
> +	parse->nErr++;
> +	goto exit_rename_table;
>   }
>   
>   /*

^ permalink raw reply	[flat|nested] 28+ messages in thread

* [tarantool-patches] Re: [PATCH 6/7] sql: don't add system spaces to Table hash
  2018-08-23 22:55   ` [tarantool-patches] [PATCH 6/7] sql: don't add system spaces to " Nikita Pettik
@ 2018-08-29  0:58     ` Vladislav Shpilevoy
  2018-09-02 23:52       ` n.pettik
  0 siblings, 1 reply; 28+ messages in thread
From: Vladislav Shpilevoy @ 2018-08-29  0:58 UTC (permalink / raw)
  To: tarantool-patches, Nikita Pettik

Thanks for the patch! See my review fixes on the branch and
below:

commit 882627479793ae3958cec7274cc3e353395aea40
Author: Vladislav Shpilevoy <v.shpilevoy@tarantool.org>
Date:   Tue Aug 28 21:28:49 2018 -0300

     Review fixes

diff --git a/src/box/sql/prepare.c b/src/box/sql/prepare.c
index a59e70dd0..0dfdf251f 100644
--- a/src/box/sql/prepare.c
+++ b/src/box/sql/prepare.c
@@ -133,26 +133,14 @@ sql_init_callback(struct init_data *init, const char *name,
  extern int
  sqlite3InitDatabase(sqlite3 * db)
  {
-	int rc;
-	struct init_data init;
-
  	assert(db->pSchema != NULL);
-
-	memset(&init, 0, sizeof(init));
-	init.db = db;
-	/* Read the schema information out of the schema tables
-	 */
  	assert(db->init.busy);
-	{
-		rc = init.rc;
-		if (rc == SQLITE_OK)
-			sql_analysis_load(db);
-	}
+	sql_analysis_load(db);
  	if (db->mallocFailed) {
-		rc = SQLITE_NOMEM_BKPT;
  		sqlite3ResetAllSchemasOfConnection(db);
+		return SQLITE_NOMEM_BKPT;
  	}
-	return rc;
+	return SQLITE_OK;
  }
  
  
diff --git a/src/box/sql/tarantoolInt.h b/src/box/sql/tarantoolInt.h
index 9334de52d..8622cd19f 100644
--- a/src/box/sql/tarantoolInt.h
+++ b/src/box/sql/tarantoolInt.h
@@ -10,10 +10,6 @@
  
  struct fkey_def;
  
-/* Insert or replace operation types - necessary for vdbe */
-#define TARANTOOL_INDEX_INSERT 1
-#define TARANTOOL_INDEX_REPLACE 2
-
  /* Misc */
  const char *tarantoolErrorMessage();
  

^ permalink raw reply	[flat|nested] 28+ messages in thread

* [tarantool-patches] Re: [PATCH 3/7] sql: remove struct Table from analyze routine
  2018-08-23 22:55   ` [tarantool-patches] [PATCH 3/7] sql: remove struct Table from analyze routine Nikita Pettik
@ 2018-08-29  0:58     ` Vladislav Shpilevoy
  2018-09-02 23:52       ` n.pettik
  0 siblings, 1 reply; 28+ messages in thread
From: Vladislav Shpilevoy @ 2018-08-29  0:58 UTC (permalink / raw)
  To: tarantool-patches, Nikita Pettik

Thanks for the patch! See 1 comment below.

On 23/08/2018 19:55, Nikita Pettik wrote:
> Instead of using struct Table which is found by lookup in internal hash
> lets use struct space from Tarantool DD.
> 
> Part of #3561
> ---
>   src/box/sql/analyze.c          | 185 +++++++++++++++++++++++------------------
>   test/sql-tap/analyze1.test.lua |   2 +-
>   2 files changed, 107 insertions(+), 80 deletions(-)
> 
> diff --git a/src/box/sql/analyze.c b/src/box/sql/analyze.c
> index abed9d31b..e835a8697 100644
> --- a/src/box/sql/analyze.c
> +++ b/src/box/sql/analyze.c
> @@ -770,61 +764,62 @@ callStatGet(Vdbe * v, int regStat4, int iParam, int regOut)
>   	sqlite3VdbeChangeP5(v, 2);
>   }
>   
> -/*
> +/**
>    * Generate code to do an analysis of all indices associated with
>    * a single table.
> + *
> + * @param pParse Current parsing context.
> + * @param space Space to be analyzed.
> + * @param stat_cursor Cursor pointing to spaces containing
> + *        statistics: _sql_stat1 (stat_cursor) and
> + *        _sql_stat4 (stat_cursor + 1).
>    */
>   static void
> -analyzeOneTable(Parse * pParse,	/* Parser context */
> -		Table * pTab,	/* Table whose indices are to be analyzed */
> -		int iStatCur,	/* Index of VdbeCursor that writes the _sql_stat1 table */
> -		int iMem,	/* Available memory locations begin here */
> -		int iTab	/* Next available cursor */
> -    )
> +vdbe_emit_analyze_space(struct Parse *pParse, struct space *space,
> +			int stat_cursor)

1. Once you started this function formatting, please, finish it.
Use parse instead of pParse. Use reg_stat4 instead of regStat4 etc.

>   {
>   	sqlite3 *db = pParse->db;	/* Database handle */
>   	int iIdxCur;		/* Cursor open on index being analyzed */
>   	int iTabCur;		/* Table cursor */
>   	Vdbe *v;		/* The virtual machine being built up */
>   	int i;			/* Loop counter */
> -	int regStat4 = iMem++;	/* Register to hold Stat4Accum object */
> -	int regChng = iMem++;	/* Index of changed index field */
> -	int regKey = iMem++;	/* Key argument passed to stat_push() */
> -	int regTemp = iMem++;	/* Temporary use register */
> -	int regTabname = iMem++;	/* Register containing table name */
> -	int regIdxname = iMem++;	/* Register containing index name */
> -	int regStat1 = iMem++;	/* Value for the stat column of _sql_stat1 */
> -	int regPrev = iMem;	/* MUST BE LAST (see below) */
> -
> -	pParse->nMem = MAX(pParse->nMem, iMem);
> +	/* Register to hold Stat4Accum object. */
> +	int regStat4 = ++pParse->nMem;
> +	/* Index of changed index field. */
> +	int regChng = ++pParse->nMem;
> +	/* Key argument passed to stat_push(). */
> +	int regKey = ++pParse->nMem;
> +	/* Temporary use register. */
> +	int regTemp = ++pParse->nMem;
> +	/* Register containing table name. */
> +	int regTabname = ++pParse->nMem;
> +	/* Register containing index name. */
> +	int regIdxname = ++pParse->nMem;
> +	/* Value for the stat column of _sql_stat1. */
> +	int regStat1 = ++pParse->nMem;
> +	/* MUST BE LAST (see below). */
> +	int regPrev = ++pParse->nMem;
> +
> @@ -1076,27 +1078,43 @@ loadAnalysis(Parse * pParse)
>   	}
>   }
>   
> -/*
> - * Generate code that will do an analysis of an entire database
> +/**
> + * Wrapper to pass args to space_foreach callback.
> + */
> +struct analyze_data {
> +	struct Parse *parse_context;
> +	/**
> +	 * Cursor numbers pointing to stat spaces:
> +	 * stat_cursor is opened on _sql_stat1 and
> +	 * stat_cursor + 1 - on _sql_stat4.
> +	 */
> +	int stat_cursor;
> +};
> +
> +static int
> +sql_space_foreach_analyze(struct space *space, void *data)
> +{
> +	if (space->def->opts.sql == NULL || space->def->opts.is_view)
> +		return 0;
> +	struct analyze_data *anal_data = (struct analyze_data *) data;

Lol

> +	vdbe_emit_analyze_space(anal_data->parse_context, space,
> +				anal_data->stat_cursor);
> +	return 0;
> +}
> +

^ permalink raw reply	[flat|nested] 28+ messages in thread

* [tarantool-patches] Re: [PATCH 2/7] sql: remove SQLite original struct Index
  2018-08-23 22:55   ` [tarantool-patches] [PATCH 2/7] sql: remove SQLite original struct Index Nikita Pettik
@ 2018-08-29  0:58     ` Vladislav Shpilevoy
  2018-09-02 23:51       ` n.pettik
  0 siblings, 1 reply; 28+ messages in thread
From: Vladislav Shpilevoy @ 2018-08-29  0:58 UTC (permalink / raw)
  To: tarantool-patches, Nikita Pettik

Thanks for the patch! See 16 comments below and a
commit on the branch.

On 23/08/2018 19:55, Nikita Pettik wrote:
> 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/analyze.c b/src/box/sql/analyze.c
> index 74f5ae827..abed9d31b 100644
> --- a/src/box/sql/analyze.c
> +++ b/src/box/sql/analyze.c
> @@ -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;

1. Below you have check 'if (part_count > 0)', but it is
always true, it is not?

>   
>   		/* Populate the register containing the index name. */
>   		sqlite3VdbeLoadString(v, regIdxname, idx_name);
> 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
> @@ -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];

2. You have mentioned that space indexes are stored as an
array, but it is not the only true. The indexes are stored in
two ways: as an array and a map. A map allows to find an index
by id in O(1) time. Look at space_index function.

Here you do not clear space->index_map and do not
reallocate it. Looks like you do not touch index_map
at all in this patch. But maybe you should. See my
other comments here and in next patches.

> +		/*
> +		 * 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);

3. idx->def->key_def leaks. cmp_def as well. Please,
use index_def_delete. Same in other places where you
delete index_def (whereLoopClearUnion, etc).

> +			free(idx);
> +			size_t idx_sz = sizeof(struct index *);
> +			uint32_t idx_count = --space->index_count;

4. If malloc below fails, index_count will remain decremented.

> +			struct index **new_idexes =
> +				(struct index **) malloc(idx_sz * idx_count);

5. Typo: 'idexes'.

> +			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);

6. Use after free (tab->space is freed here).

> +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
> @@ -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];

7. If you had index_map, you could do it like this:

     if (tab->space-index_count == 0)
         return NULL;
     return tab->space->index_map[0];

No checking for index[0]->iid == 0, it looks very
strange. Likewise a primary index could have id != 0.

You can allocate index_map in the same memory block
as index array.

>   }
>   
>   /**
> @@ -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)

8. Reindex still has a plenty of mentions over the code,
including the parser and commented tests. What are you going
to do with that? I propose to remove it from all the places.

> @@ -2558,16 +2457,24 @@ getNewIid(Parse * pParse, int iSpaceId, int iCursor)
> +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;

9. Do not care about index array. You should only
care about index_map - here the pk should be on the
first place. So this whole 'if' can be replaced with
a single tab->space->index_map[iid] = index.

>   	}
> +	tab->space->index[tab->space->index_count++] = index;
>   }
>   
>   /**
> @@ -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);

10. Noise diff hunk.

>   		if (sqlite3LocateIndex(db, name, table->def->name) != NULL) {
>   			if (!if_not_exist) {
>   				sqlite3ErrorMsg(parse,
> 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> @@ -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;
> -		}

11. Why are you sure that pDestIdx and pSrcIdx are xfercompatible
now? And the indentation is broken.

As I see, pDestIdx and pSrcIdx above are checked for xfer in a
cycle, but it is not stopped once they are found.

>   		assert(pSrcIdx);
>   		struct space *src_space =
>   			space_by_id(pSrc->def->id);
> 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
> @@ -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) {

12. I put here assert(p->index_def->key_def == NULL)
and it did not fire. Then I tried assert(false) and the
result was the same. So this code is not tested at all.
Can you fix it?

> +		free(p->index_def);
> +		p->index_def = NULL;
>   	}
> @@ -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;

13. This 'break' makes no sense. You already
limited loop iterations with 1 for fake_index != NULL.
Either break, or limit 'for'.

> +
>   	}
> -	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);

14. Just allocate fake_index->opts.stat like it
works in ordinary index_defs and use index_def_delete
only.

> +		index_def_delete(fake_index);
>   	}
>   	return rc;
>   }

15. Why index_is_unordered still exists? Why can you
get key_def->opts.stat->is_unordered right from SQL index_def?

> @@ -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);

16. idx_def == NULL is filtered out one line above. The same about other
'idx_def !=/== NULL' below.

>   			if (is_primary
>   			    && (wctrlFlags & WHERE_OR_SUBCLAUSE) != 0) {
>   				/* This is one term of an OR-optimization using

Please, consider my fixes on the branch and here:

commit cccbcaa444453aba3373ef53febb83389d2a113d
Author: Vladislav Shpilevoy <v.shpilevoy@tarantool.org>
Date:   Mon Aug 27 17:07:53 2018 -0300

     Review fixes

diff --git a/src/box/key_def.c b/src/box/key_def.c
index 75eba79ec..e171e88fa 100644
--- a/src/box/key_def.c
+++ b/src/box/key_def.c
@@ -250,6 +250,8 @@ key_part_cmp(const struct key_part *parts1, uint32_t part_count1,
  		if (part1->coll != part2->coll)
  			return (uintptr_t) part1->coll <
  			       (uintptr_t) part2->coll ? -1 : 1;
+		if (part1->sort_order != part2->sort_order)
+			return part1->sort_order < part2->sort_order ? -1 : 1;
  		if (key_part_is_nullable(part1) != key_part_is_nullable(part2))
  			return key_part_is_nullable(part1) <
  			       key_part_is_nullable(part2) ? -1 : 1;
diff --git a/src/box/sql.c b/src/box/sql.c
index 61c766540..f85daf9bd 100644
--- a/src/box/sql.c
+++ b/src/box/sql.c
@@ -31,10 +31,6 @@
  #include <assert.h>
  #include "field_def.h"
  #include "sql.h"
-/*
- * Both Tarantool and SQLite codebases declare Index, hence the
- * workaround below.
- */
  #include "sql/sqliteInt.h"
  #include "sql/tarantoolInt.h"
  #include "sql/vdbeInt.h"
diff --git a/src/box/sql/analyze.c b/src/box/sql/analyze.c
index abed9d31b..33a3e1da8 100644
--- a/src/box/sql/analyze.c
+++ b/src/box/sql/analyze.c
@@ -918,68 +918,67 @@ analyzeOneTable(Parse * pParse,	/* Parser context */
  		sqlite3VdbeAddOp2(v, OP_Integer, 0, regChng);
  		addrNextRow = sqlite3VdbeCurrentAddr(v);
  
-		if (part_count > 0) {
-			int endDistinctTest = sqlite3VdbeMakeLabel(v);
-			int *aGotoChng;	/* Array of jump instruction addresses */
-			aGotoChng =
-			    sqlite3DbMallocRawNN(db, sizeof(int) * part_count);
-			if (aGotoChng == 0)
-				continue;
-
+		int endDistinctTest = sqlite3VdbeMakeLabel(v);
+		/* Array of jump instruction addresses. */
+		int *aGotoChng =
+			sqlite3DbMallocRawNN(db, sizeof(int) * part_count);
+		if (aGotoChng == NULL)
+			continue;
+		/*
+		 *  next_row:
+		 *   regChng = 0
+		 *   if( idx(0) != regPrev(0) ) goto chng_addr_0
+		 *   regChng = 1
+		 *   if( idx(1) != regPrev(1) ) goto chng_addr_1
+		 *   ...
+		 *   regChng = N
+		 *   goto endDistinctTest
+		 */
+		sqlite3VdbeAddOp0(v, OP_Goto);
+		addrNextRow = sqlite3VdbeCurrentAddr(v);
+		if (part_count == 1 && idx->def->opts.is_unique) {
  			/*
-			 *  next_row:
-			 *   regChng = 0
-			 *   if( idx(0) != regPrev(0) ) goto chng_addr_0
-			 *   regChng = 1
-			 *   if( idx(1) != regPrev(1) ) goto chng_addr_1
-			 *   ...
-			 *   regChng = N
-			 *   goto endDistinctTest
+			 * 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.
  			 */
-			sqlite3VdbeAddOp0(v, OP_Goto);
-			addrNextRow = sqlite3VdbeCurrentAddr(v);
-			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.
-				 */
-				sqlite3VdbeAddOp2(v, OP_NotNull, regPrev,
-						  endDistinctTest);
-				VdbeCoverage(v);
-			}
-			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);
-				sqlite3VdbeAddOp3(v, OP_Column, iIdxCur,
-						  part->fieldno, regTemp);
-				aGotoChng[i] =
-				    sqlite3VdbeAddOp4(v, OP_Ne, regTemp, 0,
-						      regPrev + i, (char *)coll,
-						      P4_COLLSEQ);
-				sqlite3VdbeChangeP5(v, SQLITE_NULLEQ);
-				VdbeCoverage(v);
-			}
-			sqlite3VdbeAddOp2(v, OP_Integer, part_count, regChng);
-			sqlite3VdbeGoto(v, endDistinctTest);
+			sqlite3VdbeAddOp2(v, OP_NotNull, regPrev,
+					  endDistinctTest);
+			VdbeCoverage(v);
+		}
+		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);
+			sqlite3VdbeAddOp3(v, OP_Column, iIdxCur, part->fieldno,
+					  regTemp);
+			aGotoChng[i] = sqlite3VdbeAddOp4(v, OP_Ne, regTemp, 0,
+							 regPrev + i,
+							 (char *)coll,
+							 P4_COLLSEQ);
+			sqlite3VdbeChangeP5(v, SQLITE_NULLEQ);
+			VdbeCoverage(v);
+		}
+		sqlite3VdbeAddOp2(v, OP_Integer, part_count, regChng);
+		sqlite3VdbeGoto(v, endDistinctTest);
  
-			/*
-			 *  chng_addr_0:
-			 *   regPrev(0) = idx(0)
-			 *  chng_addr_1:
-			 *   regPrev(1) = idx(1)
-			 *  ...
-			 */
-			sqlite3VdbeJumpHere(v, addrNextRow - 1);
-			part = idx->def->key_def->parts;
-			for (i = 0; i < part_count; ++i, ++part) {
-				sqlite3VdbeJumpHere(v, aGotoChng[i]);
-				sqlite3VdbeAddOp3(v, OP_Column, iIdxCur,
-						  part->fieldno, regPrev + i);
-			}
-			sqlite3VdbeResolveLabel(v, endDistinctTest);
-			sqlite3DbFree(db, aGotoChng);
+		/*
+		 *  chng_addr_0:
+		 *   regPrev(0) = idx(0)
+		 *  chng_addr_1:
+		 *   regPrev(1) = idx(1)
+		 *  ...
+		 */
+		sqlite3VdbeJumpHere(v, addrNextRow - 1);
+		part = idx->def->key_def->parts;
+		for (i = 0; i < part_count; ++i, ++part) {
+			sqlite3VdbeJumpHere(v, aGotoChng[i]);
+			sqlite3VdbeAddOp3(v, OP_Column, iIdxCur, part->fieldno,
+					  regPrev + i);
  		}
+		sqlite3VdbeResolveLabel(v, endDistinctTest);
+		sqlite3DbFree(db, aGotoChng);
  
  		/*
  		 *  chng_addr_N:
@@ -989,14 +988,14 @@ analyzeOneTable(Parse * pParse,	/* Parser context */
  		 *   if !eof(csr) goto next_row;
  		 */
  		assert(regKey == (regStat4 + 2));
-		struct index *pPk = sql_table_primary_key(pTab);
-		int pk_part_count = pPk->def->key_def->part_count;
+		struct index *pk = sql_table_primary_key(pTab);
+		int pk_part_count = pk->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 (i = 0; i < pk_part_count; i++) {
-			uint32_t k = pPk->def->key_def->parts[i].fieldno;
+			uint32_t k = pk->def->key_def->parts[i].fieldno;
  			assert(k < pTab->def->field_count);
  			sqlite3VdbeAddOp3(v, OP_Column, iIdxCur, k,
  					  regKeyStat + i);
diff --git a/src/box/sql/build.c b/src/box/sql/build.c
index 79dc46592..534afc154 100644
--- a/src/box/sql/build.c
+++ b/src/box/sql/build.c
@@ -273,8 +273,8 @@ table_delete(struct sqlite3 *db, struct Table *tab)
  		free(idx->def);
  		free(idx);
  	}
-	free(tab->space);
  	free(tab->space->index);
+	free(tab->space);
  skip_index_delete:
  	assert(tab->def != NULL);
  	/* Do not delete table->def allocated on region. */
@@ -1214,8 +1214,8 @@ vdbe_emit_create_index(struct Parse *parse, struct space_def *def,
   * @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 iid Id of index (or register containing it) to be
+ *        created.
   * @param sql_stmt String containing 'CREATE INDEX ...' statement.
   *                 NULL for UNIQUE and PK constraints.
   */
@@ -1229,7 +1229,7 @@ vdbe_emit_index_schema_record(struct Parse *parse, const char *idx_name,
  
  	sqlite3VdbeAddOp4(v, OP_String8, 0, entry_reg, 0,
  			  sqlite3DbStrDup(parse->db, idx_name), P4_DYNAMIC);
-	if (parse->pNewTable) {
+	if (parse->pNewTable != NULL) {
  		/*
  		 * A new table is being created, hence space_id
  		 * is a register, but index id is literal.
@@ -1327,11 +1327,12 @@ 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);
  
-	if (!p->def->opts.is_view)
+	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);
@@ -2464,7 +2465,7 @@ table_add_index(struct Table *tab, struct index *index)
  	struct index **idx = (struct index **) realloc(tab->space->index,
  						       indexes_sz);
  	if (idx == NULL) {
-		diag_set(OutOfMemory, indexes_sz, "malloc", "idx");
+		diag_set(OutOfMemory, indexes_sz, "realloc", "idx");
  		return;
  	}
  	tab->space->index = idx;
@@ -3010,7 +3011,7 @@ sql_drop_index(struct Parse *parse_context, struct SrcList *index_name_list,
  	 * data-dictionaries will be completely merged.
  	 */
  	struct Table *tab = sqlite3HashFind(&db->pSchema->tblHash, table_name);
-	sqlite3VdbeAddOp3(v, OP_DropIndex, index->def->iid, 0, 0);
+	sqlite3VdbeAddOp1(v, OP_DropIndex, index->def->iid);
  	sqlite3VdbeAppendP4(v, tab->space, P4_SPACEPTR);
  
   exit_drop_index:
diff --git a/src/box/sql/insert.c b/src/box/sql/insert.c
index 9220d34e1..ce704c0a2 100644
--- a/src/box/sql/insert.c
+++ b/src/box/sql/insert.c
@@ -1113,22 +1113,9 @@ sql_index_is_xfer_compatible(const struct index_def *dest,
  {
  	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 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 false;
-		if (src_part->sort_order != dest_part->sort_order)
-			return false;
-		if (src_part->coll != dest_part->coll)
-			return false;
-	}
-	return true;
+	return key_part_cmp(src->key_def->parts, src->key_def->part_count,
+			    dest->key_def->parts,
+			    dest->key_def->part_count) == 0;
  }
  
  /*
diff --git a/src/box/sql/sqliteInt.h b/src/box/sql/sqliteInt.h
index 19a5e7dd9..2bc6a98f6 100644
--- a/src/box/sql/sqliteInt.h
+++ b/src/box/sql/sqliteInt.h
@@ -3083,7 +3083,6 @@ struct Walker {
  		SrcList *pSrcList;	/* FROM clause */
  		struct SrcCount *pSrcCount;	/* Counting column references */
  		int *aiCol;	/* array of column indexes */
-		struct IdxCover *pIdxCover;	/* Check for index coverage */
  		/** Space definition. */
  		struct space_def *space_def;
  	} u;
@@ -3682,7 +3681,6 @@ int sqlite3ExprCodeExprList(Parse *, ExprList *, int, int, u8);
  #define SQLITE_ECEL_OMITREF  0x08	/* Omit if ExprList.u.x.iOrderByCol */
  void sqlite3ExprIfTrue(Parse *, Expr *, int, int);
  void sqlite3ExprIfFalse(Parse *, Expr *, int, int);
-void sqlite3ExprIfFalseDup(Parse *, Expr *, int, int);
  #define LOCATE_VIEW    0x01
  #define LOCATE_NOERR   0x02
  Table *sqlite3LocateTable(Parse *, u32 flags, const char *);
diff --git a/src/box/sql/vdbe.h b/src/box/sql/vdbe.h
index 6ee83a5de..3ba01198d 100644
--- a/src/box/sql/vdbe.h
+++ b/src/box/sql/vdbe.h
@@ -229,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 idx_def Definition of index to get key_def from.
+ * @param key_def Definition of a key to set.
   */
  void
-sql_vdbe_set_p4_key_def(struct Parse *parse, struct index_def *idx_def);
+sql_vdbe_set_p4_key_def(struct Parse *parse, struct key_def *key_def);
  
  VdbeOp *sqlite3VdbeGetOp(Vdbe *, int);
  int sqlite3VdbeMakeLabel(Vdbe *);
diff --git a/src/box/sql/vdbeaux.c b/src/box/sql/vdbeaux.c
index 12c2edffe..dda030234 100644
--- a/src/box/sql/vdbeaux.c
+++ b/src/box/sql/vdbeaux.c
@@ -1055,16 +1055,16 @@ sqlite3VdbeAppendP4(Vdbe * p, void *pP4, int n)
  }
  
  void
-sql_vdbe_set_p4_key_def(struct Parse *parse, struct index_def *idx_def)
+sql_vdbe_set_p4_key_def(struct Parse *parse, struct key_def *key_def)
  {
  	struct Vdbe *v = parse->pVdbe;
  	assert(v != NULL);
-	assert(idx_def != NULL);
-	struct key_def *def = key_def_dup(idx_def->key_def);
-	if (def == NULL)
+	assert(key_def != NULL);
+	key_def = key_def_dup(key_def);
+	if (key_def == NULL)
  		sqlite3OomFault(parse->db);
  	else
-		sqlite3VdbeAppendP4(v, def, P4_KEYDEF);
+		sqlite3VdbeAppendP4(v, key_def, P4_KEYDEF);
  }
  
  #ifdef SQLITE_ENABLE_EXPLAIN_COMMENTS
diff --git a/src/box/sql/where.c b/src/box/sql/where.c
index d8a2c1a79..1b9ba436c 100644
--- a/src/box/sql/where.c
+++ b/src/box/sql/where.c
@@ -617,19 +617,18 @@ isDistinctRedundant(Parse * pParse,		/* Parsing context */
  	 *      contain a "col=X" term are subject to a NOT NULL constraint.
  	 */
  	for (uint32_t j = 0; j < pTab->space->index_count; ++j) {
-		struct index *idx = pTab->space->index[j];
-		if (!idx->def->opts.is_unique)
+		struct index_def *def = pTab->space->index[j]->def;
+		if (!def->opts.is_unique)
  			continue;
-		uint32_t col_count = idx->def->key_def->part_count;
+		uint32_t col_count = 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, idx->def)) {
-				if (findIndexCol
-				    (pParse, pDistinct, iBase, idx->def, i) < 0)
+			if (sqlite3WhereFindTerm(pWC, iBase, i, ~(Bitmask) 0,
+						 WO_EQ, def) == 0) {
+				if (findIndexCol(pParse, pDistinct, iBase, def,
+						 i) < 0)
  					break;
-				uint32_t x = idx->def->key_def->parts[i].fieldno;
+				uint32_t x = def->key_def->parts[i].fieldno;
  				if (pTab->def->fields[x].is_nullable)
  					break;
  			}
@@ -638,7 +637,7 @@ isDistinctRedundant(Parse * pParse,		/* Parsing context */
  		 * This index implies that the DISTINCT
  		 * qualifier is redundant.
  		 */
-		if (i == idx->def->key_def->part_count)
+		if (i == col_count)
  			return 1;
  	}
  
@@ -858,7 +857,7 @@ constructAutomaticIndex(Parse * pParse,			/* The parsing context */
  	assert(pLevel->iIdxCur >= 0);
  	pLevel->iIdxCur = pParse->nTab++;
  	sqlite3VdbeAddOp2(v, OP_OpenAutoindex, pLevel->iIdxCur, nKeyCol + 1);
-	sql_vdbe_set_p4_key_def(pParse, pIdx);
+	sql_vdbe_set_p4_key_def(pParse, pIdx->key_def);
  	VdbeComment((v, "for %s", pTable->def->name));
  
  	/* Fill the automatic index with content */
@@ -2827,6 +2826,7 @@ whereLoopAddBtree(WhereLoopBuilder * pBuilder,	/* WHERE clause information */
  		memset(&fake_index, 0, sizeof(fake_index));
  		struct key_def *key_def = key_def_new(1);
  		if (key_def == NULL) {
+tnt_error:
  			pWInfo->pParse->nErr++;
  			pWInfo->pParse->rc = SQL_TARANTOOL_ERROR;
  			return SQL_TARANTOOL_ERROR;
@@ -2843,18 +2843,18 @@ whereLoopAddBtree(WhereLoopBuilder * pBuilder,	/* WHERE clause information */
  					   sizeof("fake_autoindex") - 1,
  					   TREE, &opts, key_def, NULL);
  		key_def_delete(key_def);
-		if (fake_index == NULL) {
-			pWInfo->pParse->nErr++;
-			pWInfo->pParse->rc = SQL_TARANTOOL_ERROR;
-			return SQL_TARANTOOL_ERROR;
-		}
+		if (fake_index == NULL)
+			goto tnt_error;
  		/* Special marker for  non-existent index. */
  		fake_index->iid = UINT32_MAX;
+		int size = sizeof(struct index_stat) + sizeof(log_est_t) * 2;
  
-		struct index_stat *stat =
-			(struct index_stat *) malloc(sizeof(struct index_stat));
-		stat->tuple_log_est =
-			(log_est_t *) malloc(sizeof(log_est_t) * 2);
+		struct index_stat *stat = (struct index_stat *) malloc(size);
+		if (stat == NULL) {
+			diag_set(OutOfMemory, size, "malloc", "stat");
+			goto tnt_error;
+		}
+		stat->tuple_log_est = (log_est_t *) ((char *) (stat + 1));
  		stat->tuple_log_est[0] = sql_space_tuple_log_count(pTab);
  		stat->tuple_log_est[1] = 0;
  		fake_index->opts.stat = stat;
@@ -2917,7 +2917,12 @@ whereLoopAddBtree(WhereLoopBuilder * pBuilder,	/* WHERE clause information */
  		}
  	}
  #endif				/* SQLITE_OMIT_AUTOMATIC_INDEX */
-	uint32_t idx_count = fake_index == NULL ? pTab->space->index_count : 1;
+	/*
+	 * If there was an INDEXED BY clause, then only that one
+	 * index is considered.
+	 */
+	uint32_t idx_count = fake_index == NULL || pSrc->pIBIndex != 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;
@@ -2971,20 +2976,9 @@ whereLoopAddBtree(WhereLoopBuilder * pBuilder,	/* WHERE clause information */
  		sqlite3Stat4ProbeFree(pBuilder->pRec);
  		pBuilder->nRecValid = 0;
  		pBuilder->pRec = 0;
-
-		/* If there was an INDEXED BY clause, then only that one index is
-		 * considered.
-		 */
-		if (pSrc->pIBIndex != NULL)
-			break;
-		if (fake_index != NULL)
-			break;
-
  	}
-	if (fake_index != NULL) {
-		free(fake_index->opts.stat->tuple_log_est);
+	if (fake_index != NULL)
  		index_def_delete(fake_index);
-	}
  	return rc;
  }
  
@@ -3209,7 +3203,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 */
-	struct index_def *pIndex;
+	struct index_def *idx_def;
  	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 */
@@ -3313,14 +3307,14 @@ wherePathSatisfiesOrderBy(WhereInfo * pWInfo,	/* The WHERE clause */
  
  		if ((pLoop->wsFlags & WHERE_ONEROW) == 0) {
  			if (pLoop->wsFlags & WHERE_IPK) {
-				pIndex = NULL;
+				idx_def = NULL;
  				nColumn = 1;
-			} else if ((pIndex = pLoop->index_def) == NULL ||
-				   index_is_unordered(pIndex)) {
+			} else if ((idx_def = pLoop->index_def) == NULL ||
+				   index_is_unordered(idx_def)) {
  				return 0;
  			} else {
-				nColumn = pIndex->key_def->part_count;
-				isOrderDistinct = pIndex->opts.is_unique;
+				nColumn = idx_def->key_def->part_count;
+				isOrderDistinct = idx_def->opts.is_unique;
  			}
  
  			/* Loop through all columns of the index and deal with the ones
@@ -3380,8 +3374,8 @@ wherePathSatisfiesOrderBy(WhereInfo * pWInfo,	/* The WHERE clause */
  				/* Get the column number in the table (iColumn) and sort order
  				 * (revIdx) for the j-th column of the index.
  				 */
-				if (pIndex != NULL) {
-					struct key_def *def = pIndex->key_def;
+				if (idx_def != NULL) {
+					struct key_def *def = idx_def->key_def;
  					iColumn = def->parts[j].fieldno;
  					revIdx = def->parts[j].sort_order;
  				} else {
@@ -3393,9 +3387,9 @@ wherePathSatisfiesOrderBy(WhereInfo * pWInfo,	/* The WHERE clause */
  				 * WhereLoop is not well-ordered
  				 */
  				if (isOrderDistinct && iColumn >= 0 &&
-				    j >= pLoop->nEq && pIndex != NULL) {
+				    j >= pLoop->nEq && idx_def != NULL) {
  					struct space *space =
-						space_by_id(pIndex->space_id);
+						space_by_id(idx_def->space_id);
  					assert(space != NULL);
  					if (space->def->fields[iColumn].is_nullable)
  						isOrderDistinct = 0;
@@ -3432,7 +3426,7 @@ wherePathSatisfiesOrderBy(WhereInfo * pWInfo,	/* The WHERE clause */
  								      pOrderBy->a[i].pExpr,
  								      &is_found, &id);
  						struct coll *idx_coll =
-							pIndex->key_def->parts[j].coll;
+							idx_def->key_def->parts[j].coll;
  						if (is_found &&
  						    coll != idx_coll)
  							continue;
@@ -4630,9 +4624,7 @@ sqlite3WhereBegin(Parse * pParse,	/* The parser context */
  			 */
  			if (idx_def == NULL)
  				continue;
-			bool is_primary = (idx_def != NULL &&
-					   idx_def->iid == 0);
-			if (is_primary
+			if (idx_def->iid == 0
  			    && (wctrlFlags & WHERE_OR_SUBCLAUSE) != 0) {
  				/* This is one term of an OR-optimization using
  				 * the PRIMARY KEY.  No need for a separate index
@@ -4640,8 +4632,7 @@ sqlite3WhereBegin(Parse * pParse,	/* The parser context */
  				iIndexCur = pLevel->iTabCur;
  				op = 0;
  			} else if (pWInfo->eOnePass != ONEPASS_OFF) {
-				if (idx_def != NULL &&
-				    pTabItem->pTab->space->index_count != 0) {
+				if (pTabItem->pTab->space->index_count != 0) {
  					uint32_t iid = 0;
  					struct index *pJ = pTabItem->pTab->space->index[iid];
  					iIndexCur = iAuxArg;
@@ -4675,19 +4666,10 @@ sqlite3WhereBegin(Parse * pParse,	/* The parser context */
  			pLevel->iIdxCur = iIndexCur;
  			assert(iIndexCur >= 0);
  			if (op) {
-				if (idx_def != NULL) {
-					uint32_t space_id =
-						idx_def->space_id;
-					struct space *space =
-						space_by_id(space_id);
-					vdbe_emit_open_cursor(pParse, iIndexCur,
-							      idx_def->iid,
-							      space);
-				} else {
-					vdbe_emit_open_cursor(pParse, iIndexCur,
-							      idx_def->iid,
-							      space);
-				}
+				uint32_t space_id = idx_def->space_id;
+				struct space *space = space_by_id(space_id);
+				vdbe_emit_open_cursor(pParse, iIndexCur,
+						      idx_def->iid, space);
  				if ((pLoop->wsFlags & WHERE_CONSTRAINT) != 0
  				    && (pLoop->
  					wsFlags & (WHERE_COLUMN_RANGE |
@@ -4696,8 +4678,7 @@ sqlite3WhereBegin(Parse * pParse,	/* The parser context */
  					wctrlFlags & WHERE_ORDERBY_MIN) == 0) {
  					sqlite3VdbeChangeP5(v, OPFLAG_SEEKEQ);	/* Hint to COMDB2 */
  				}
-				if (idx_def != NULL)
-					VdbeComment((v, "%s", idx_def->name));
+				VdbeComment((v, "%s", idx_def->name));
  #ifdef SQLITE_ENABLE_COLUMN_USED_MASK
  				{
  					u64 colUsed = 0;
diff --git a/src/box/sql/wherecode.c b/src/box/sql/wherecode.c
index 628c6f9ad..5d8663a92 100644
--- a/src/box/sql/wherecode.c
+++ b/src/box/sql/wherecode.c
@@ -219,7 +219,7 @@ sqlite3WhereExplainOneScan(Parse * pParse,	/* Parse context */
  
  			assert(!(flags & WHERE_AUTO_INDEX)
  			       || (flags & WHERE_IDX_ONLY));
-			if (idx_def != NULL && idx_def->iid == 0) {
+			if (idx_def->iid == 0) {
  				if (isSearch) {
  					zFmt = "PRIMARY KEY";
  				}
@@ -234,10 +234,7 @@ sqlite3WhereExplainOneScan(Parse * pParse,	/* Parse context */
  			}
  			if (zFmt) {
  				sqlite3StrAccumAppend(&str, " USING ", 7);
-				if (idx_def != NULL)
-					sqlite3XPrintf(&str, zFmt, idx_def->name);
-				else
-					sqlite3XPrintf(&str, zFmt, "EPHEMERAL INDEX");
+				sqlite3XPrintf(&str, zFmt, idx_def->name);
  				explainIndexRange(&str, pLoop);
  			}
  		} else if ((flags & WHERE_IPK) != 0
@@ -477,7 +474,7 @@ codeEqualityTerm(Parse * pParse,	/* The parsing context */
  		int nEq = 0;
  		int *aiMap = 0;
  
-		if (pLoop->index_def != 0 &&
+		if (pLoop->index_def != NULL &&
  		    pLoop->index_def->key_def->parts[iEq].sort_order) {
  			testcase(iEq == 0);
  			testcase(bRev);
@@ -1187,7 +1184,7 @@ sqlite3WhereCodeOneLoopStart(WhereInfo * pWInfo,	/* Complete information about t
  			int limit = pRangeStart == NULL ? nEq : nEq + 1;
  			for (int i = 0; i < limit; i++) {
  				if (idx_def->key_def->parts[i].fieldno ==
-				     idx_pk->key_def->parts[0].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. */
@@ -1295,11 +1292,9 @@ 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) {
-			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++) {
-				k = pPk->def->key_def->parts[j].fieldno;
+			for (j = 0; j < (int) pk_part_count; j++) {
+				k = idx_pk->key_def->parts[j].fieldno;
  				sqlite3VdbeAddOp3(v, OP_Column, iIdxCur, k,
  						  iKeyReg + j);
  			}
@@ -1344,7 +1339,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 */
-		struct index_def *pCov = 0;	/* Potential covering index (or NULL) */
+		struct index_def *cov = NULL;	/* 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 */
@@ -1357,6 +1352,9 @@ sqlite3WhereCodeOneLoopStart(WhereInfo * pWInfo,	/* Complete information about t
  		u16 wctrlFlags;	/* Flags for sub-WHERE clause */
  		Expr *pAndExpr = 0;	/* An ".. AND (...)" expression */
  		Table *pTab = pTabItem->pTab;
+		struct key_def *pk_key_def =
+			sql_table_primary_key(pTab)->def->key_def;
+		uint32_t pk_part_count = pk_key_def->part_count;
  
  		pTerm = pLoop->aLTerm[0];
  		assert(pTerm != 0);
@@ -1403,12 +1401,10 @@ sqlite3WhereCodeOneLoopStart(WhereInfo * pWInfo,	/* Complete information about t
  		 * called on an uninitialized cursor.
  		 */
  		if ((pWInfo->wctrlFlags & WHERE_DUPLICATES_OK) == 0) {
-			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->def);
+			sql_vdbe_set_p4_key_def(pParse, pk_key_def);
  			regPk = ++pParse->nMem;
  		}
  		iRetInit = sqlite3VdbeAddOp2(v, OP_Integer, 0, regReturn);
@@ -1507,19 +1503,15 @@ sqlite3WhereCodeOneLoopStart(WhereInfo * pWInfo,	/* Complete information about t
  						int r;
  						int iSet =
  						    ((ii == pOrWc->nTerm - 1) ? -1 : ii);
-						struct index *pPk =
-							sql_table_primary_key(pTab);
-						struct key_def *def =
-							pPk->def->key_def;
  
  						/* Read the PK into an array of temp registers. */
  						r = sqlite3GetTempRange(pParse,
-									def->part_count);
+									pk_part_count);
  						for (uint32_t iPk = 0;
-						     iPk < def->part_count;
+						     iPk < pk_part_count;
  						     iPk++) {
  							uint32_t fieldno =
-								def->parts[iPk].
+								pk_key_def->parts[iPk].
  								fieldno;
  							sqlite3ExprCodeGetColumnToReg
  								(pParse,
@@ -1546,20 +1538,20 @@ sqlite3WhereCodeOneLoopStart(WhereInfo * pWInfo,	/* Complete information about t
  								(v, OP_Found,
  								 regRowset, 0,
  								 r,
-								 def->part_count);
+								 pk_part_count);
  							VdbeCoverage(v);
  						}
  						if (iSet >= 0) {
  							sqlite3VdbeAddOp3
  								(v, OP_MakeRecord,
-								 r, def->part_count, regPk);
+								 r, pk_part_count, regPk);
  							sqlite3VdbeAddOp2
  								(v, OP_IdxInsert,
  								 regRowset, regPk);
  						}
  
  						/* Release the array of temp registers */
-						sqlite3ReleaseTempRange(pParse, r, def->part_count);
+						sqlite3ReleaseTempRange(pParse, r, pk_part_count);
  					}
  
  					/* Invoke the main loop body as a subroutine */
@@ -1588,21 +1580,21 @@ sqlite3WhereCodeOneLoopStart(WhereInfo * pWInfo,	/* Complete information about t
  					 * If the call to sqlite3WhereBegin() above resulted in a scan that
  					 * uses an index, and this is either the first OR-connected term
  					 * processed or the index is the same as that used by all previous
-					 * terms, set pCov to the candidate covering index. Otherwise, set
-					 * pCov to NULL to indicate that no candidate covering index will
+					 * terms, set cov to the candidate covering index. Otherwise, set
+					 * cov to NULL to indicate that no candidate covering index will
  					 * be available.
  					 */
  					pSubLoop = pSubWInfo->a[0].pWLoop;
  					assert((pSubLoop->wsFlags & WHERE_AUTO_INDEX) == 0);
  					if ((pSubLoop->wsFlags & WHERE_INDEXED) != 0
-					    && (ii == 0 || (pCov != NULL &&
-						pSubLoop->index_def->iid == pCov->iid))
+					    && (ii == 0 || (cov != NULL &&
+						pSubLoop->index_def->iid == cov->iid))
  					    && (pSubLoop->index_def->iid != 0)) {
  						assert(pSubWInfo->a[0].
  						       iIdxCur == iCovCur);
-						pCov = pSubLoop->index_def;
+						cov = pSubLoop->index_def;
  					} else {
-						pCov = 0;
+						cov = 0;
  					}
  
  					/* Finish the loop through table entries that match term pOrTerm. */
@@ -1610,8 +1602,8 @@ sqlite3WhereCodeOneLoopStart(WhereInfo * pWInfo,	/* Complete information about t
  				}
  			}
  		}
-		pLevel->u.pCovidx = pCov;
-		if (pCov)
+		pLevel->u.pCovidx = cov;
+		if (cov)
  			pLevel->iIdxCur = iCovCur;
  		if (pAndExpr) {
  			pAndExpr->pLeft = 0;

^ permalink raw reply	[flat|nested] 28+ messages in thread

* [tarantool-patches] Re: [PATCH 1/7] sql: remove struct schema from struct Table
  2018-08-23 22:55   ` [tarantool-patches] [PATCH 1/7] sql: remove struct schema from struct Table Nikita Pettik
@ 2018-08-29  0:58     ` Vladislav Shpilevoy
  2018-09-02 23:51       ` n.pettik
  2018-09-16 19:32     ` Vladislav Shpilevoy
  1 sibling, 1 reply; 28+ messages in thread
From: Vladislav Shpilevoy @ 2018-08-29  0:58 UTC (permalink / raw)
  To: tarantool-patches, Nikita Pettik

Thanks for the patch!
I have pushed my review fix on the branch. Here
it is:

diff --git a/src/box/sql/expr.c b/src/box/sql/expr.c
index a41ea8f13..7ddedaf8a 100644
--- a/src/box/sql/expr.c
+++ b/src/box/sql/expr.c
@@ -1515,7 +1515,6 @@ sqlite3SrcListDup(sqlite3 * db, SrcList * p, int flags)
  		struct SrcList_item *pNewItem = &pNew->a[i];
  		struct SrcList_item *pOldItem = &p->a[i];
  		Table *pTab;
-		pNewItem->pSchema = pOldItem->pSchema;
  		pNewItem->zName = sqlite3DbStrDup(db, pOldItem->zName);
  		pNewItem->zAlias = sqlite3DbStrDup(db, pOldItem->zAlias);
  		pNewItem->fg = pOldItem->fg;
diff --git a/src/box/sql/sqliteInt.h b/src/box/sql/sqliteInt.h
index 35d3f4cec..6bca506e7 100644
--- a/src/box/sql/sqliteInt.h
+++ b/src/box/sql/sqliteInt.h
@@ -2377,7 +2377,6 @@ struct SrcList {
  	int nSrc;		/* Number of tables or subqueries in the FROM clause */
  	u32 nAlloc;		/* Number of entries allocated in a[] below */
  	struct SrcList_item {
-		Schema *pSchema;	/* Schema to which this item is fixed */
  		char *zName;	/* Name of the table */
  		char *zAlias;	/* The "B" part of a "A AS B" phrase.  zName is the "A" */
  		Table *pTab;	/* An SQL table corresponding to zName */


SrcList_item is actually a table most times, so lets
remove Schema from it as well.

^ permalink raw reply	[flat|nested] 28+ messages in thread

* [tarantool-patches] Re: [PATCH 1/7] sql: remove struct schema from struct Table
  2018-08-29  0:58     ` [tarantool-patches] " Vladislav Shpilevoy
@ 2018-09-02 23:51       ` n.pettik
  0 siblings, 0 replies; 28+ messages in thread
From: n.pettik @ 2018-09-02 23:51 UTC (permalink / raw)
  To: tarantool-patches; +Cc: Vladislav Shpilevoy


> Thanks for the patch!
> I have pushed my review fix on the branch. Here
> it is:
> 
> diff --git a/src/box/sql/expr.c b/src/box/sql/expr.c
> index a41ea8f13..7ddedaf8a 100644
> --- a/src/box/sql/expr.c
> +++ b/src/box/sql/expr.c
> @@ -1515,7 +1515,6 @@ sqlite3SrcListDup(sqlite3 * db, SrcList * p, int flags)
> 		struct SrcList_item *pNewItem = &pNew->a[i];
> 		struct SrcList_item *pOldItem = &p->a[i];
> 		Table *pTab;
> -		pNewItem->pSchema = pOldItem->pSchema;
> 		pNewItem->zName = sqlite3DbStrDup(db, pOldItem->zName);
> 		pNewItem->zAlias = sqlite3DbStrDup(db, pOldItem->zAlias);
> 		pNewItem->fg = pOldItem->fg;
> diff --git a/src/box/sql/sqliteInt.h b/src/box/sql/sqliteInt.h
> index 35d3f4cec..6bca506e7 100644
> --- a/src/box/sql/sqliteInt.h
> +++ b/src/box/sql/sqliteInt.h
> @@ -2377,7 +2377,6 @@ struct SrcList {
> 	int nSrc;		/* Number of tables or subqueries in the FROM clause */
> 	u32 nAlloc;		/* Number of entries allocated in a[] below */
> 	struct SrcList_item {
> -		Schema *pSchema;	/* Schema to which this item is fixed */
> 		char *zName;	/* Name of the table */
> 		char *zAlias;	/* The "B" part of a "A AS B" phrase.  zName is the "A" */
> 		Table *pTab;	/* An SQL table corresponding to zName */
> 
> 
> SrcList_item is actually a table most times, so lets
> remove Schema from it as well.

Well, actually I did it in the last patch where I completely
removed struct Schema at all. But OK, I will squash your fix.

^ permalink raw reply	[flat|nested] 28+ messages in thread

* [tarantool-patches] Re: [PATCH 2/7] sql: remove SQLite original struct Index
  2018-08-29  0:58     ` [tarantool-patches] " Vladislav Shpilevoy
@ 2018-09-02 23:51       ` n.pettik
  2018-09-06 19:54         ` Vladislav Shpilevoy
  0 siblings, 1 reply; 28+ messages in thread
From: n.pettik @ 2018-09-02 23:51 UTC (permalink / raw)
  To: tarantool-patches; +Cc: Vladislav Shpilevoy


>> --- a/src/box/sql/analyze.c
>> +++ b/src/box/sql/analyze.c
>> @@ -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;
> 
> 1. Below you have check 'if (part_count > 0)', but it is
> always true, it is not?

Well, you are right. As I see, you already fixed it on branch, so skipped.
The only remark to your diff is (addrNextRow is unused until next assignment):

+++ b/src/box/sql/analyze.c
@@ -916,8 +916,6 @@ analyzeOneTable(Parse * pParse,     /* Parser context */
                addrRewind = sqlite3VdbeAddOp1(v, OP_Rewind, iIdxCur);
                VdbeCoverage(v);
                sqlite3VdbeAddOp2(v, OP_Integer, 0, regChng);
-               addrNextRow = sqlite3VdbeCurrentAddr(v);
-
                int endDistinctTest = sqlite3VdbeMakeLabel(v);
                /* Array of jump instruction addresses. */


>>    		/* Populate the register containing the index name. */
>>  		sqlite3VdbeLoadString(v, regIdxname, idx_name);
>> 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
>> @@ -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];
> 
> 2. You have mentioned that space indexes are stored as an
> array, but it is not the only true. The indexes are stored in
> two ways: as an array and a map. A map allows to find an index
> by id in O(1) time. Look at space_index function.

It is cool and I know about it, but the most common use case is simple
iteration over all indexes like:
for (i = 0; i < space->index_count; ++i) {
	struct index *idx = space->index[I];
	...
}

> Here you do not clear space->index_map and do not
> reallocate it. Looks like you do not touch index_map
> at all in this patch. But maybe you should. See my
> other comments here and in next patches.

I deliberately didn’t fill in index map. I think maintaining
map and array at the same time seems to be over-engineering.
These indexes are really used only during table creation
(after last patch in series). Moreover, during building routine
we may need only PK index, which anyway can be fetched as index[0]

> 
>> +		/*
>> +		 * 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);
> 
> 3. idx->def->key_def leaks. cmp_def as well. Please,
> use index_def_delete. Same in other places where you
> delete index_def (whereLoopClearUnion, etc).

Thx, terrible leak:

+++ b/src/box/sql/build.c
@@ -171,22 +171,23 @@ sql_space_index_delete(struct space *space, uint32_t iid)
                 * to be deleted.
                 */
                if (idx->def->iid == iid) {
-                       free(idx->def);
+                       index_def_delete(idx->def);
                        free(idx);
                        size_t idx_sz = sizeof(struct index *);
-                       uint32_t idx_count = --space->index_count;
-                       struct index **new_idexes =
+                       uint32_t idx_count = space->index_count - 1;
+                       struct index **new_indexes =
                                (struct index **) malloc(idx_sz * idx_count);
-                       if (new_idexes == NULL) {
+                       if (new_indexes == 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,
+                       memcpy(new_indexes, space->index, i * idx_sz);
+                       memcpy(new_indexes + i, space->index + i + 1,
                               idx_sz * (idx_count - i));
                        free(space->index);
-                       space->index = new_idexes;
+                       space->index = new_indexes;
+                       space->index_count--;
                        break;
                }
        }

+++ b/src/box/sql/build.c
@@ -2963,7 +2963,7 @@ sql_create_index(struct Parse *parse, struct Token *token,
        /* Clean up before exiting. */
  exit_create_index:
+       if (index != NULL && index->def != NULL)
-               free(index->def);
+               index_def_delete(index->def);

+++ b/src/box/sql/where.c
@@ -1742,7 +1742,7 @@ static void
 whereLoopClearUnion(WhereLoop * p)
 {
        if ((p->wsFlags & WHERE_AUTO_INDEX) != 0) {
-               free(p->index_def);
+               index_def_delete(p->index_def);
                p->index_def = NULL;
        }
 }

> 
>> +			free(idx);
>> +			size_t idx_sz = sizeof(struct index *);
>> +			uint32_t idx_count = --space->index_count;
> 
> 4. If malloc below fails, index_count will remain decremented.

*If malloc fails, wrong index count is likely to be the least burning issue…*
Anyway, ofc fixed, see above.

> 
>> +			struct index **new_idexes =
>> +				(struct index **) malloc(idx_sz * idx_count);
> 
> 5. Typo: 'idexes’.

Fixed, see above.

>> -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);
> 
> 6. Use after free (tab->space is freed here).

Yep, got it and skipped (you already fixed it).

>>  -/*
>> - * 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];
> 
> 7. If you had index_map, you could do it like this:
> 
>    if (tab->space-index_count == 0)
>        return NULL;
>    return tab->space->index_map[0];
> 
> No checking for index[0]->iid == 0, it looks very
> strange. Likewise a primary index could have id != 0.

Again: during table creation routine indexes are added to this
array in straight order except for PK. Example:

CREATE TABLE t1 (a INT UNIQUE, b INT UNIQUE, id INT PRIMARY KEY, c INT UNIQUE);

1) index [0] = a, count = 1;
2) index [0] = a, index [1] = b, count = 2;
3) index [0] = id, index [1] = b, index [2] = a, count = 3;
4) index [0] = id, index [1] = b, index [2] = a, index [3] = c, count = 4.

Thus, index[0] really can feature id != 0. However, after table is created,
index[0] is always PK with id == 0.

> 
> You can allocate index_map in the same memory block
> as index array.

I saw how it is allocated in space_create(), but deliberately avoided
dealing with map.

> 
>>  }
>>    /**
>> @@ -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)
> 
> 8. Reindex still has a plenty of mentions over the code,
> including the parser and commented tests. What are you going
> to do with that? I propose to remove it from all the places.

I didn’t remove REINDEX from parser, since it is quite painful to remove/add
keywords to parser. Moreover REINDEX anyway is going to be reimplemented
somewhen (https://github.com/tarantool/tarantool/issues/3195).
But OK, if you propose to delete it, lets do it:

+++ b/extra/mkkeywordhash.c
@@ -193,7 +193,6 @@ static Keyword aKeywordTable[] = {
   { "RECURSIVE",              "TK_RECURSIVE",   CTE,              true  },
   { "REFERENCES",             "TK_REFERENCES",  FKEY,             true  },
   { "REGEXP",                 "TK_LIKE_KW",     ALWAYS,           false },
-  { "REINDEX",                "TK_REINDEX",     REINDEX,          true  },
   { "RELEASE",                "TK_RELEASE",     ALWAYS,           true  },
   { "RENAME",                 "TK_RENAME",      ALTER,            true  },
   { "REPLACE",                "TK_REPLACE",     CONFLICT,         true  },

diff --git a/src/box/sql/parse.y b/src/box/sql/parse.y
index 60c0a8eed..040161b53 100644
--- a/src/box/sql/parse.y
+++ b/src/box/sql/parse.y
@@ -211,7 +211,7 @@ columnname(A) ::= nm(A) typetoken(Y). {sqlite3AddColumn(pParse,&A,&Y);}
 %ifdef SQLITE_OMIT_COMPOUND_SELECT
   INTERSECT 
 %endif SQLITE_OMIT_COMPOUND_SELECT
-  REINDEX RENAME CTIME_KW IF
+  RENAME CTIME_KW IF
   .
 %wildcard ANY.
 
diff --git a/src/box/sql/sqliteInt.h b/src/box/sql/sqliteInt.h
index 2bc6a98f6..0a9d1d487 100644
--- a/src/box/sql/sqliteInt.h
+++ b/src/box/sql/sqliteInt.h
@@ -4423,7 +4423,6 @@ extern FuncDefHash sqlite3BuiltinFunctions;
 extern int sqlite3PendingByte;
 #endif
 #endif
-void sqlite3Reindex(Parse *, Token *, Token *);
 void sqlite3AlterRenameTable(Parse *, SrcList *, Token *);
 
 /**

deleted file mode 100755
index baa67b4f7..000000000
--- a/test/sql-tap/gh-2174-ban-reindex-syntax.test.lua
+++ /dev/null
@@ -1,29 +0,0 @@
-#!/usr/bin/env tarantool
-
--- this test will be deleted in scope of #3195
-test = require("sqltester")
-test:plan(3)
-
-test:execsql("DROP TABLE IF EXISTS t1");
-test:execsql("CREATE TABLE t1(a INT PRIMARY KEY)");
-test:execsql("CREATE INDEX i1 on t1(a)");
-
-test:do_catchsql_test(
-       "1",
-       "REINDEX i1 ON t1",
-       {1, "keyword \"REINDEX\" is reserved"}
-)
-
-test:do_catchsql_test(
-       "2",
-       "REINDEX t1",
-        {1, "keyword \"REINDEX\" is reserved"}
-)
-
-test:do_catchsql_test(
-       "3",
-       "REINDEX",
-       {1, "keyword \"REINDEX\" is reserved"}
-)
-
-test:finish_test()

diff --git a/test/sql-tap/keyword1.test.lua b/test/sql-tap/keyword1.test.lua
index 23a561f4d..fbcd17327 100755
--- a/test/sql-tap/keyword1.test.lua
+++ b/test/sql-tap/keyword1.test.lua
@@ -1,6 +1,6 @@
 #!/usr/bin/env tarantool
 test = require("sqltester")
-test:plan(176)
+test:plan(175)
 
 --!./tcltestrunner.lua
 -- 2009 January 29
@@ -107,7 +107,6 @@ local bannedkws = {
        "primary",
        "recursive",
        "references",
-       "reindex",
        "release",
        "rename",
        "replace",

> 
>> @@ -2558,16 +2457,24 @@ getNewIid(Parse * pParse, int iSpaceId, int iCursor)
>> +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;
> 
> 9. Do not care about index array. You should only
> care about index_map - here the pk should be on the
> first place. So this whole 'if' can be replaced with
> a single tab->space->index_map[iid] = index.

See comments above concerning index map.

> 
>>  	}
>> +	tab->space->index[tab->space->index_count++] = index;
>>  }
>>    /**
>> @@ -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);
> 
> 10. Noise diff hunk.

Oh, cmon. I see many similar diffs in your reviews. For example (from review fixes to current patch):

@@ -1229,7 +1229,7 @@ vdbe_emit_index_schema_record(struct Parse *parse, const char *idx_name,

	sqlite3VdbeAddOp4(v, OP_String8, 0, entry_reg, 0,
			  sqlite3DbStrDup(parse->db, idx_name), P4_DYNAMIC);
	if (parse->pNewTable) {
	if (parse->pNewTable != NULL) {


This assert really makes no sense after calling sqlite3NameFromToken():
if token->z == NULL then sqlite3NameFromToken() will return NULL
and this assert will never fire.

> 
>>  		if (sqlite3LocateIndex(db, name, table->def->name) != NULL) {
>>  			if (!if_not_exist) {
>>  				sqlite3ErrorMsg(parse,
>> 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> @@ -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;
>> -		}
> 
> 11. Why are you sure that pDestIdx and pSrcIdx are xfercompatible
> now? And the indentation is broken.
> 
> As I see, pDestIdx and pSrcIdx above are checked for xfer in a
> cycle, but it is not stopped once they are found.

It is my bad: this code snippet was slightly changed within patch
on fixing xfer optimization (38f013c854dc723102390eb73b245a358f00fdcb).
So I decided not to rewrite completely this code. Now (after I rebased on fresh 2.0)
everything seems to be OK, check out new patch version in the end of letter.

>>  		assert(pSrcIdx);
>>  		struct space *src_space =
>>  			space_by_id(pSrc->def->id);
>> 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
>> @@ -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) {
> 
> 12. I put here assert(p->index_def->key_def == NULL)
> and it did not fire. Then I tried assert(false) and the
> result was the same. So this code is not tested at all.
> Can you fix it?

This code is related to auto indexes, which currently are disabled.
There are a lot of code under #ifndef SQLITE_OMIT_AUTOMATIC_INDEX
which haven’t been tested since ~January 2018..
So, in fact I can’t now add test trace reaching this function.
Autoindexes should be enabled in https://github.com/tarantool/tarantool/issues/2583
Why does code implementing autoindexes still exist? I guess it hasn’t been deleted to
guide smb who will reimplement them.

> 
>> +		free(p->index_def);
>> +		p->index_def = NULL;
>>  	}
>> @@ -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;
> 
> 13. This 'break' makes no sense. You already
> limited loop iterations with 1 for fake_index != NULL.
> Either break, or limit 'for’.

Checked and skipped (you already fixed it).

> 
>> +
>>  	}
>> -	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);
> 
> 14. Just allocate fake_index->opts.stat like it
> works in ordinary index_defs and use index_def_delete
> only.

Checked and skipped.

> 
>> +		index_def_delete(fake_index);
>>  	}
>>  	return rc;
>>  }
> 
> 15. Why index_is_unordered still exists? Why can you
> get key_def->opts.stat->is_unordered right from SQL index_def?

Because for SQL index opts.stat is NULL: inside index_fill_def assigns
def->opts to index_opts_default. Real statistics is held only real indexes
inside space from server. Can be removed in last patch of this patch-set.

> 
>> @@ -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);
> 
> 16. idx_def == NULL is filtered out one line above. The same about other
> 'idx_def !=/== NULL' below.

Checked and skipped.

> 
>>  			if (is_primary
>>  			    && (wctrlFlags & WHERE_OR_SUBCLAUSE) != 0) {
>>  				/* This is one term of an OR-optimization using
> 
> Please, consider my fixes on the branch and here:

I’ve squashed your fixes, thanks a lot.

Whole updated patch:


commit 8d9003171bb72641fa6e26ef4ab2d9526ee9ed70
Author: Nikita Pettik <korablev@tarantool.org>
Date:   Fri Aug 17 21:09:22 2018 +0300

    sql: remove SQLite original struct Index
    
    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

diff --git a/extra/mkkeywordhash.c b/extra/mkkeywordhash.c
index 388a03cca..e856d64c7 100644
--- a/extra/mkkeywordhash.c
+++ b/extra/mkkeywordhash.c
@@ -193,7 +193,6 @@ static Keyword aKeywordTable[] = {
   { "RECURSIVE",              "TK_RECURSIVE",   CTE,              true  },
   { "REFERENCES",             "TK_REFERENCES",  FKEY,             true  },
   { "REGEXP",                 "TK_LIKE_KW",     ALWAYS,           false },
-  { "REINDEX",                "TK_REINDEX",     REINDEX,          true  },
   { "RELEASE",                "TK_RELEASE",     ALWAYS,           true  },
   { "RENAME",                 "TK_RENAME",      ALTER,            true  },
   { "REPLACE",                "TK_REPLACE",     CONFLICT,         true  },
diff --git a/src/box/key_def.c b/src/box/key_def.c
index 5546126db..e3d8382e0 100644
--- a/src/box/key_def.c
+++ b/src/box/key_def.c
@@ -250,6 +250,8 @@ key_part_cmp(const struct key_part *parts1, uint32_t part_count1,
 		if (part1->coll != part2->coll)
 			return (uintptr_t) part1->coll <
 			       (uintptr_t) part2->coll ? -1 : 1;
+		if (part1->sort_order != part2->sort_order)
+			return part1->sort_order < part2->sort_order ? -1 : 1;
 		if (key_part_is_nullable(part1) != key_part_is_nullable(part2))
 			return key_part_is_nullable(part1) <
 			       key_part_is_nullable(part2) ? -1 : 1;
diff --git a/src/box/sql.c b/src/box/sql.c
index b158c5056..0c1df9b75 100644
--- a/src/box/sql.c
+++ b/src/box/sql.c
@@ -31,15 +31,9 @@
 #include <assert.h>
 #include "field_def.h"
 #include "sql.h"
-/*
- * 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"
@@ -848,7 +842,8 @@ set_encode_error(void *error_ctx)
  * @param opts Index options to encode.
  */
 static void
-mpstream_encode_index_opts(struct mpstream *stream, struct index_opts *opts)
+mpstream_encode_index_opts(struct mpstream *stream,
+			   const struct index_opts *opts)
 {
 	mpstream_encode_map(stream, 2);
 	mpstream_encode_str(stream, "unique");
@@ -1302,7 +1297,7 @@ sql_encode_table(struct region *region, struct Table *table, uint32_t *size)
 	 * If table's PK is single column which is INTEGER, then
 	 * treat it as strict type, not affinity.
 	 */
-	struct SqliteIndex *pk_idx = sqlite3PrimaryKeyIndex(table);
+	struct index *pk_idx = sql_table_primary_key(table);
 	uint32_t pk_forced_int = UINT32_MAX;
 	if (pk_idx != NULL && pk_idx->def->key_def->part_count == 1) {
 		int pk = pk_idx->def->key_def->parts[0].fieldno;
@@ -1453,8 +1448,9 @@ fkey_encode_links(struct region *region, const struct fkey_def *def, int type,
 }
 
 char *
-sql_encode_index_parts(struct region *region, struct SqliteIndex *index,
-		       uint32_t *size)
+sql_encode_index_parts(struct region *region, const struct field_def *fields,
+		       const struct index_def *idx_def,
+		       const struct index_def *pk_def, uint32_t *size)
 {
 	size_t used = region_used(region);
 	struct mpstream stream;
@@ -1466,12 +1462,10 @@ sql_encode_index_parts(struct region *region, struct SqliteIndex *index,
 	 * treat it as strict type, not affinity.
 	 */
 	uint32_t pk_forced_int = UINT32_MAX;
-	struct SqliteIndex *pk = sqlite3PrimaryKeyIndex(index->pTable);
-	struct field_def *fields = index->pTable->def->fields;
-	if (pk->def->key_def->part_count == 1) {
-		int fieldno = pk->def->key_def->parts[0].fieldno;
-		if (fields[fieldno].type == FIELD_TYPE_INTEGER)
-			pk_forced_int = 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;
 	}
 
 	/* gh-2187
@@ -1480,7 +1474,7 @@ sql_encode_index_parts(struct region *region, struct SqliteIndex *index,
 	 * primary key columns. Query planner depends on this particular
 	 * data layout.
 	 */
-	struct key_def *key_def = index->def->key_def;
+	struct key_def *key_def = idx_def->key_def;
 	struct key_part *part = key_def->parts;
 	mpstream_encode_array(&stream, key_def->part_count);
 	for (uint32_t i = 0; i < key_def->part_count; ++i, ++part) {
@@ -1532,7 +1526,7 @@ sql_encode_index_parts(struct region *region, struct SqliteIndex *index,
 }
 
 char *
-sql_encode_index_opts(struct region *region, struct index_opts *opts,
+sql_encode_index_opts(struct region *region, const struct index_opts *opts,
 		      uint32_t *size)
 {
 	size_t used = region_used(region);
@@ -1669,6 +1663,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 76ae15386..80293c6ed 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:
 		 *
@@ -917,70 +916,67 @@ analyzeOneTable(Parse * pParse,	/* Parser context */
 		addrRewind = sqlite3VdbeAddOp1(v, OP_Rewind, iIdxCur);
 		VdbeCoverage(v);
 		sqlite3VdbeAddOp2(v, OP_Integer, 0, regChng);
+		int endDistinctTest = sqlite3VdbeMakeLabel(v);
+		/* Array of jump instruction addresses. */
+		int *aGotoChng =
+			sqlite3DbMallocRawNN(db, sizeof(int) * part_count);
+		if (aGotoChng == NULL)
+			continue;
+		/*
+		 *  next_row:
+		 *   regChng = 0
+		 *   if( idx(0) != regPrev(0) ) goto chng_addr_0
+		 *   regChng = 1
+		 *   if( idx(1) != regPrev(1) ) goto chng_addr_1
+		 *   ...
+		 *   regChng = N
+		 *   goto endDistinctTest
+		 */
+		sqlite3VdbeAddOp0(v, OP_Goto);
 		addrNextRow = sqlite3VdbeCurrentAddr(v);
-
-		if (part_count > 0) {
-			int endDistinctTest = sqlite3VdbeMakeLabel(v);
-			int *aGotoChng;	/* Array of jump instruction addresses */
-			aGotoChng =
-			    sqlite3DbMallocRawNN(db, sizeof(int) * part_count);
-			if (aGotoChng == 0)
-				continue;
-
+		if (part_count == 1 && idx->def->opts.is_unique) {
 			/*
-			 *  next_row:
-			 *   regChng = 0
-			 *   if( idx(0) != regPrev(0) ) goto chng_addr_0
-			 *   regChng = 1
-			 *   if( idx(1) != regPrev(1) ) goto chng_addr_1
-			 *   ...
-			 *   regChng = N
-			 *   goto endDistinctTest
+			 * 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.
 			 */
-			sqlite3VdbeAddOp0(v, OP_Goto);
-			addrNextRow = sqlite3VdbeCurrentAddr(v);
-			if (part_count == 1 && pIdx->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.
-				 */
-				sqlite3VdbeAddOp2(v, OP_NotNull, regPrev,
-						  endDistinctTest);
-				VdbeCoverage(v);
-			}
-			struct key_part *part = pIdx->def->key_def->parts;
-			for (i = 0; i < part_count; ++i, ++part) {
-				struct coll *coll = part->coll;
-				sqlite3VdbeAddOp2(v, OP_Integer, i, regChng);
-				sqlite3VdbeAddOp3(v, OP_Column, iIdxCur,
-						  part->fieldno, regTemp);
-				aGotoChng[i] =
-				    sqlite3VdbeAddOp4(v, OP_Ne, regTemp, 0,
-						      regPrev + i, (char *)coll,
-						      P4_COLLSEQ);
-				sqlite3VdbeChangeP5(v, SQLITE_NULLEQ);
-				VdbeCoverage(v);
-			}
-			sqlite3VdbeAddOp2(v, OP_Integer, part_count, regChng);
-			sqlite3VdbeGoto(v, endDistinctTest);
+			sqlite3VdbeAddOp2(v, OP_NotNull, regPrev,
+					  endDistinctTest);
+			VdbeCoverage(v);
+		}
+		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);
+			sqlite3VdbeAddOp3(v, OP_Column, iIdxCur, part->fieldno,
+					  regTemp);
+			aGotoChng[i] = sqlite3VdbeAddOp4(v, OP_Ne, regTemp, 0,
+							 regPrev + i,
+							 (char *)coll,
+							 P4_COLLSEQ);
+			sqlite3VdbeChangeP5(v, SQLITE_NULLEQ);
+			VdbeCoverage(v);
+		}
+		sqlite3VdbeAddOp2(v, OP_Integer, part_count, regChng);
+		sqlite3VdbeGoto(v, endDistinctTest);
 
-			/*
-			 *  chng_addr_0:
-			 *   regPrev(0) = idx(0)
-			 *  chng_addr_1:
-			 *   regPrev(1) = idx(1)
-			 *  ...
-			 */
-			sqlite3VdbeJumpHere(v, addrNextRow - 1);
-			part = pIdx->def->key_def->parts;
-			for (i = 0; i < part_count; ++i, ++part) {
-				sqlite3VdbeJumpHere(v, aGotoChng[i]);
-				sqlite3VdbeAddOp3(v, OP_Column, iIdxCur,
-						  part->fieldno, regPrev + i);
-			}
-			sqlite3VdbeResolveLabel(v, endDistinctTest);
-			sqlite3DbFree(db, aGotoChng);
+		/*
+		 *  chng_addr_0:
+		 *   regPrev(0) = idx(0)
+		 *  chng_addr_1:
+		 *   regPrev(1) = idx(1)
+		 *  ...
+		 */
+		sqlite3VdbeJumpHere(v, addrNextRow - 1);
+		part = idx->def->key_def->parts;
+		for (i = 0; i < part_count; ++i, ++part) {
+			sqlite3VdbeJumpHere(v, aGotoChng[i]);
+			sqlite3VdbeAddOp3(v, OP_Column, iIdxCur, part->fieldno,
+					  regPrev + i);
 		}
+		sqlite3VdbeResolveLabel(v, endDistinctTest);
+		sqlite3DbFree(db, aGotoChng);
 
 		/*
 		 *  chng_addr_N:
@@ -990,17 +986,17 @@ analyzeOneTable(Parse * pParse,	/* Parser context */
 		 *   if !eof(csr) goto next_row;
 		 */
 		assert(regKey == (regStat4 + 2));
-		Index *pPk = sqlite3PrimaryKeyIndex(pIdx->pTable);
-		int pk_part_count = pPk->def->key_def->part_count;
+		struct index *pk = sql_table_primary_key(pTab);
+		int pk_part_count = pk->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 = pk->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 +1044,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 +1090,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 +1112,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 +1632,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 d1d952330..617d0ea47 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,47 @@ 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) {
+			index_def_delete(idx->def);
+			free(idx);
+			size_t idx_sz = sizeof(struct index *);
+			uint32_t idx_count = space->index_count - 1;
+			struct index **new_indexes =
+				(struct index **) malloc(idx_sz * idx_count);
+			if (new_indexes == NULL) {
+				diag_set(OutOfMemory, idx_sz * idx_count,
+					 "malloc", "new_indexes");
+				return;
 			}
+			memcpy(new_indexes, space->index, i * idx_sz);
+			memcpy(new_indexes + i, space->index + i + 1,
+			       idx_sz * (idx_count - i));
+			free(space->index);
+			space->index = new_indexes;
+			space->index_count--;
+			break;
 		}
-		freeIndex(db, pIndex);
 	}
-
+	struct session *user_session = current_session();
 	user_session->sql_flags |= SQLITE_InternChanges;
 }
 
@@ -260,38 +252,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.
- *
- * 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.
+ * 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->index);
+	free(tab->space);
+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
@@ -302,7 +295,7 @@ sqlite3DeleteTable(sqlite3 * db, Table * pTable)
 		return;
 	if (((!db || db->pnBytesFreed == 0) && (--pTable->nTabRef) > 0))
 		return;
-	deleteTable(db, pTable);
+	table_delete(db, pTable);
 }
 
 /*
@@ -369,16 +362,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];
 }
 
 /**
@@ -463,14 +452,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);
 
@@ -831,7 +812,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);
@@ -891,7 +872,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++) {
@@ -953,11 +934,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);
 			}
 		}
@@ -1156,137 +1137,132 @@ 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 */
-	struct index_opts opts = pIndex->def->opts;
-	opts.sql = (char *)zSql;
-	struct region *region = &pParse->region;
+	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. */
+	struct region *region = &parse->region;
 	uint32_t index_opts_sz = 0;
-	char *index_opts =
-		sql_encode_index_opts(region, &opts, &index_opts_sz);
+	char *index_opts = sql_encode_index_opts(region, &idx_def->opts,
+						 &index_opts_sz);
 	if (index_opts == NULL)
 		goto error;
 	uint32_t index_parts_sz = 0;
-	char *index_parts =
-		sql_encode_index_parts(region, pIndex, &index_parts_sz);
+	char *index_parts = sql_encode_index_parts(region, def->fields, idx_def,
+						   pk_def, &index_parts_sz);
 	if (index_parts == NULL)
 		goto error;
-	char *raw = sqlite3DbMallocRaw(pParse->db,
-				       index_opts_sz + index_parts_sz);
+	char *raw = sqlite3DbMallocRaw(parse->db,
+				       index_opts_sz +index_parts_sz);
 	if (raw == NULL)
 		return;
-
 	memcpy(raw, index_opts, index_opts_sz);
 	index_opts = raw;
 	raw += index_opts_sz;
 	memcpy(raw, index_parts, index_parts_sz);
 	index_parts = raw;
 
-	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, index_opts_sz, iFirstCol + 4,
+	sqlite3VdbeAddOp4(v, OP_Blob, index_opts_sz, entry_reg + 4,
 			  SQL_SUBTYPE_MSGPACK, index_opts, P4_DYNAMIC);
-	/* zOpts and zParts are co-located, hence STATIC */
-	sqlite3VdbeAddOp4(v, OP_Blob, index_parts_sz, iFirstCol + 5,
+	/* opts and parts are co-located, hence STATIC. */
+	sqlite3VdbeAddOp4(v, OP_Blob, index_parts_sz, entry_reg + 5,
 			  SQL_SUBTYPE_MSGPACK, index_parts, P4_STATIC);
-	sqlite3VdbeAddOp3(v, OP_MakeRecord, iFirstCol, 6, iRecord);
-	sqlite3VdbeAddOp2(v, OP_SInsert, BOX_INDEX_ID, iRecord);
+	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);
 	return;
 error:
-	pParse->rc = SQL_TARANTOOL_ERROR;
-	pParse->nErr++;
+	parse->rc = SQL_TARANTOOL_ERROR;
+	parse->nErr++;
+
 }
 
-/*
+/**
  * 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 iid 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 != NULL) {
 		/*
-		 * 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;
 }
 
 /*
@@ -1357,8 +1333,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,
@@ -1367,11 +1342,12 @@ 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);
@@ -1629,13 +1605,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);
@@ -1709,10 +1686,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);
+		}
 	}
 
 	/*
@@ -1759,7 +1739,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 "\
@@ -2457,84 +2437,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.
@@ -2584,16 +2486,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, "realloc", "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;
 }
 
 /**
@@ -2617,7 +2527,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)
@@ -2705,19 +2615,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;
@@ -2777,10 +2681,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,
@@ -2810,23 +2714,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);
 	}
@@ -2868,18 +2767,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;
@@ -2954,8 +2856,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;
@@ -2980,7 +2882,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;
 			}
@@ -3036,12 +2938,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
@@ -3058,8 +2962,9 @@ sql_create_index(struct Parse *parse, struct Token *token,
 
 	/* Clean up before exiting. */
  exit_create_index:
-	if (index != NULL)
-		freeIndex(db, index);
+	if (index != NULL && index->def != NULL)
+		index_def_delete(index->def);
+	free(index);
 	sql_expr_list_delete(db, col_list);
 	sqlite3SrcListDelete(db, tbl_name);
 	sqlite3DbFree(db, name);
@@ -3133,10 +3038,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);
+	sqlite3VdbeAddOp1(v, OP_DropIndex, index->def->iid);
+	sqlite3VdbeAppendP4(v, tab->space, P4_SPACEPTR);
 
  exit_drop_index:
 	sqlite3SrcListDelete(db, index_name_list);
@@ -3674,160 +3578,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 fc1b156f7..dcb57e9b3 100644
--- a/src/box/sql/expr.c
+++ b/src/box/sql/expr.c
@@ -2363,7 +2363,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;
 
@@ -2398,15 +2397,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
@@ -2418,7 +2423,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
@@ -2470,14 +2475,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 +
@@ -3137,9 +3140,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 =
@@ -3464,22 +3466,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)
@@ -4984,22 +4970,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
@@ -5156,62 +5126,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 550514c03..9a7fdffbb 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,7 +1001,8 @@ 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)
 			continue;
@@ -1080,40 +1082,30 @@ vdbe_emit_insertion_completion(struct Vdbe *v, int cursor_id, int raw_data_reg,
 }
 
 #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 True, if two indexes are compatible in terms of
+ *         xfer optimization.
  */
-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;
-	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;
-	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 */
-		if (src_part->sort_order != dest_part->sort_order)
-			return 0;	/* Different sort orders */
-		if (src_part->coll != dest_part->coll)
-			return 0;	/* Different collating sequences */
-	}
-
-	/* If no test above fails then the indices must be compatible */
-	return 1;
+	assert(dest != NULL && src != NULL);
+	assert(dest->space_id != src->space_id);
+	return key_part_cmp(src->key_def->parts, src->key_def->part_count,
+			    dest->key_def->parts,
+			    dest->key_def->part_count) == 0;
 }
 
 /*
@@ -1147,7 +1139,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 */
@@ -1255,9 +1247,9 @@ xferOptimization(Parse * pParse,	/* Parser context */
 		/* Default values for second and subsequent columns need to match. */
 		if (i > 0) {
 			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)
@@ -1266,9 +1258,12 @@ xferOptimization(Parse * pParse,	/* Parser context */
 			}
 		}
 	}
-	for (pDestIdx = pDest->pIndex; pDestIdx; pDestIdx = pDestIdx->pNext) {
-		for (pSrcIdx = pSrc->pIndex; pSrcIdx; pSrcIdx = pSrcIdx->pNext) {
-			if (xferCompatibleIndex(pDestIdx, pSrcIdx))
+	for (uint32_t i = 0; i < pDest->space->index_count; ++i) {
+		pDestIdx = pDest->space->index[i];
+		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. */
diff --git a/src/box/sql/parse.y b/src/box/sql/parse.y
index d8532d378..040161b53 100644
--- a/src/box/sql/parse.y
+++ b/src/box/sql/parse.y
@@ -211,7 +211,7 @@ columnname(A) ::= nm(A) typetoken(Y). {sqlite3AddColumn(pParse,&A,&Y);}
 %ifdef SQLITE_OMIT_COMPOUND_SELECT
   INTERSECT 
 %endif SQLITE_OMIT_COMPOUND_SELECT
-  REINDEX RENAME CTIME_KW IF
+  RENAME CTIME_KW IF
   .
 %wildcard ANY.
 
@@ -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 988507154..3ba7ad022 100644
--- a/src/box/sql/pragma.c
+++ b/src/box/sql/pragma.c
@@ -237,6 +237,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.
@@ -360,7 +443,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;
@@ -387,7 +470,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;
@@ -404,17 +486,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);
 				}
@@ -423,93 +503,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 e8925792f..4ba1b048a 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 eef7eb39f..02732b59c 100644
--- a/src/box/sql/sqliteInt.h
+++ b/src/box/sql/sqliteInt.h
@@ -1402,7 +1402,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;
@@ -1814,7 +1813,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.
@@ -1826,6 +1824,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;
 };
 
 /**
@@ -1925,28 +1925,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
@@ -1962,12 +1940,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
@@ -2368,7 +2346,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 */
 };
 
@@ -2737,7 +2715,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 */
@@ -3076,7 +3053,6 @@ struct Walker {
 		SrcList *pSrcList;	/* FROM clause */
 		struct SrcCount *pSrcCount;	/* Counting column references */
 		int *aiCol;	/* array of column indexes */
-		struct IdxCover *pIdxCover;	/* Check for index coverage */
 		/** Space definition. */
 		struct space_def *space_def;
 	} u;
@@ -3366,7 +3342,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 *);
 
@@ -3604,7 +3586,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
@@ -3670,20 +3651,32 @@ int sqlite3ExprCodeExprList(Parse *, ExprList *, int, int, u8);
 #define SQLITE_ECEL_OMITREF  0x08	/* Omit if ExprList.u.x.iOrderByCol */
 void sqlite3ExprIfTrue(Parse *, Expr *, int, int);
 void sqlite3ExprIfFalse(Parse *, Expr *, int, int);
-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
@@ -3813,8 +3806,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
@@ -3904,17 +3897,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 *);
@@ -4410,7 +4393,6 @@ extern FuncDefHash sqlite3BuiltinFunctions;
 extern int sqlite3PendingByte;
 #endif
 #endif
-void sqlite3Reindex(Parse *, Token *, Token *);
 void sqlite3AlterRenameTable(Parse *, SrcList *, Token *);
 
 /**
@@ -4546,7 +4528,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 efc531bbd..f3e65303b 100644
--- a/src/box/sql/tarantoolInt.h
+++ b/src/box/sql/tarantoolInt.h
@@ -207,6 +207,7 @@ fkey_encode_links(struct region *region, const struct fkey_def *def, int type,
 		  uint32_t *size);
 
 /**
+<<<<<<< HEAD
  * Encode index parts of given foreign key constraint into
  * MsgPack on @region.
  * @param region Region to use.
@@ -217,7 +218,9 @@ fkey_encode_links(struct region *region, const struct fkey_def *def, int type,
  * @retval not NULL Pointer to msgpack on success
  */
 char *
-sql_encode_index_parts(struct region *region, struct Index *index,
+sql_encode_index_parts(struct region *region, const struct field_def *fields,
+		       const struct index_def *idx_def,
+		       const struct index_def *pk_def,
 		       uint32_t *size);
 
 /**
@@ -230,7 +233,7 @@ sql_encode_index_parts(struct region *region, struct Index *index,
  * @retval not NULL pointer to msgpack on success
  */
 char *
-sql_encode_index_opts(struct region *region, struct index_opts *opts,
+sql_encode_index_opts(struct region *region, const struct index_opts *opts,
 		      uint32_t *size);
 
 /**
diff --git a/src/box/sql/update.c b/src/box/sql/update.c
index 3e39688a5..076b28a94 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);
 	i = sizeof(int) * def->field_count;
 	aXRef = (int *) region_alloc(&pParse->region, i);
 	if (aXRef == NULL) {
diff --git a/src/box/sql/vdbe.c b/src/box/sql/vdbe.c
index a5b907d59..b956726e4 100644
--- a/src/box/sql/vdbe.c
+++ b/src/box/sql/vdbe.c
@@ -4736,17 +4736,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..3ba01198d 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 key_def Definition of a key to set.
  */
 void
-sql_vdbe_set_p4_key_def(struct Parse *parse, struct Index *index);
+sql_vdbe_set_p4_key_def(struct Parse *parse, struct key_def *key_def);
 
 VdbeOp *sqlite3VdbeGetOp(Vdbe *, int);
 int sqlite3VdbeMakeLabel(Vdbe *);
diff --git a/src/box/sql/vdbeaux.c b/src/box/sql/vdbeaux.c
index 3b0c90ce3..dda030234 100644
--- a/src/box/sql/vdbeaux.c
+++ b/src/box/sql/vdbeaux.c
@@ -1055,16 +1055,16 @@ 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 key_def *key_def)
 {
 	struct Vdbe *v = parse->pVdbe;
 	assert(v != NULL);
-	assert(idx != NULL);
-	struct key_def *def = key_def_dup(idx->def->key_def);
-	if (def == NULL)
+	assert(key_def != NULL);
+	key_def = key_def_dup(key_def);
+	if (key_def == NULL)
 		sqlite3OomFault(parse->db);
 	else
-		sqlite3VdbeAppendP4(v, def, P4_KEYDEF);
+		sqlite3VdbeAppendP4(v, key_def, P4_KEYDEF);
 }
 
 #ifdef SQLITE_ENABLE_EXPLAIN_COMMENTS
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..b9a8d1734 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,29 @@ 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_def *def = pTab->space->index[j]->def;
+		if (!def->opts.is_unique)
 			continue;
-		int col_count = pIdx->def->key_def->part_count;
+		uint32_t col_count = 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)) {
-				if (findIndexCol
-				    (pParse, pDistinct, iBase, pIdx, i) < 0)
+			if (sqlite3WhereFindTerm(pWC, iBase, i, ~(Bitmask) 0,
+						 WO_EQ, def) == 0) {
+				if (findIndexCol(pParse, pDistinct, iBase, 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 = 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 == col_count)
 			return 1;
-		}
 	}
 
 	return 0;
@@ -851,7 +857,7 @@ constructAutomaticIndex(Parse * pParse,			/* The parsing context */
 	assert(pLevel->iIdxCur >= 0);
 	pLevel->iIdxCur = pParse->nTab++;
 	sqlite3VdbeAddOp2(v, OP_OpenAutoindex, pLevel->iIdxCur, nKeyCol + 1);
-	sql_vdbe_set_p4_key_def(pParse, pIdx);
+	sql_vdbe_set_p4_key_def(pParse, pIdx->key_def);
 	VdbeComment((v, "for %s", pTable->def->name));
 
 	/* Fill the automatic index with content */
@@ -908,15 +914,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 +1190,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 +1319,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 +1377,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 +1391,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 +1533,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 +1541,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 +1561,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 +1589,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 +1694,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 +1739,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) {
+		index_def_delete(p->index_def);
+		p->index_def = NULL;
 	}
 }
 
@@ -1758,7 +1755,7 @@ whereLoopClear(sqlite3 * db, WhereLoop * p)
 {
 	if (p->aLTerm != p->aLTermSpace)
 		sqlite3DbFree(db, p->aLTerm);
-	whereLoopClearUnion(db, p);
+	whereLoopClearUnion(p);
 	whereLoopInit(p);
 }
 
@@ -1789,19 +1786,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 +2137,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 +2248,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 +2276,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 +2314,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 +2336,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 +2352,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 +2380,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 +2390,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 +2465,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 +2487,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 +2512,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 +2536,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 +2579,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 +2597,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 +2609,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 +2634,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 +2664,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 +2686,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 +2700,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 +2724,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 +2740,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 +2791,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,23 +2814,19 @@ 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) {
+tnt_error:
 			pWInfo->pParse->nErr++;
 			pWInfo->pParse->rc = SQL_TARANTOOL_ERROR;
 			return SQL_TARANTOOL_ERROR;
@@ -2849,36 +2839,27 @@ 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);
+		if (fake_index == NULL)
+			goto tnt_error;
 		/* Special marker for  non-existent index. */
-		fake_index.def->iid = UINT32_MAX;
+		fake_index->iid = UINT32_MAX;
+		int size = sizeof(struct index_stat) + sizeof(log_est_t) * 2;
 
-		if (fake_index.def == NULL) {
-			pWInfo->pParse->nErr++;
-			pWInfo->pParse->rc = SQL_TARANTOOL_ERROR;
-			return SQL_TARANTOOL_ERROR;
+		struct index_stat *stat = (struct index_stat *) malloc(size);
+		if (stat == NULL) {
+			diag_set(OutOfMemory, size, "malloc", "stat");
+			goto tnt_error;
 		}
-
-		struct index_stat *stat =
-			(struct index_stat *) malloc(sizeof(struct index_stat));
-		stat->tuple_log_est =
-			(log_est_t *) malloc(sizeof(log_est_t) * 2);
+		stat->tuple_log_est = (log_est_t *) ((char *) (stat + 1));
 		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,16 @@ whereLoopAddBtree(WhereLoopBuilder * pBuilder,	/* WHERE clause information */
 		}
 	}
 #endif				/* SQLITE_OMIT_AUTOMATIC_INDEX */
-
-	/* Loop over all indices
+	/*
+	 * If there was an INDEXED BY clause, then only that one
+	 * index is considered.
 	 */
-	for (; rc == SQLITE_OK && pProbe; pProbe = pProbe->pNext, iSortIdx++) {
-		rSize = index_field_tuple_est(pProbe, 0);
+	uint32_t idx_count = fake_index == NULL || pSrc->pIBIndex != 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 +2936,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 +2954,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 +2963,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,22 +2972,13 @@ 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;
-
-		/* If there was an INDEXED BY clause, then only that one index is
-		 * considered.
-		 */
-		if (pSrc->pIBIndex)
-			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)
+		index_def_delete(fake_index);
 	return rc;
 }
 
@@ -3113,7 +3085,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 +3203,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 *idx_def;
 	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 +3307,14 @@ wherePathSatisfiesOrderBy(WhereInfo * pWInfo,	/* The WHERE clause */
 
 		if ((pLoop->wsFlags & WHERE_ONEROW) == 0) {
 			if (pLoop->wsFlags & WHERE_IPK) {
-				pIndex = 0;
+				idx_def = NULL;
 				nColumn = 1;
-			} else if ((pIndex = pLoop->pIndex) == NULL ||
-				   index_is_unordered(pIndex)) {
+			} else if ((idx_def = pLoop->index_def) == NULL ||
+				   index_is_unordered(idx_def)) {
 				return 0;
 			} else {
-				nColumn = pIndex->def->key_def->part_count;
-				isOrderDistinct = pIndex->def->opts.is_unique;
+				nColumn = idx_def->key_def->part_count;
+				isOrderDistinct = idx_def->opts.is_unique;
 			}
 
 			/* Loop through all columns of the index and deal with the ones
@@ -3402,9 +3374,8 @@ wherePathSatisfiesOrderBy(WhereInfo * pWInfo,	/* The WHERE clause */
 				/* Get the column number in the table (iColumn) and sort order
 				 * (revIdx) for the j-th column of the index.
 				 */
-				if (pIndex != NULL) {
-					struct key_def *def =
-						pIndex->def->key_def;
+				if (idx_def != NULL) {
+					struct key_def *def = idx_def->key_def;
 					iColumn = def->parts[j].fieldno;
 					revIdx = def->parts[j].sort_order;
 				} else {
@@ -3415,12 +3386,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 && idx_def != NULL) {
+					struct space *space =
+						space_by_id(idx_def->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 +3426,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;
+							idx_def->key_def->parts[j].coll;
 						if (is_found &&
 						    coll != idx_coll)
 							continue;
@@ -4141,7 +4113,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 +4606,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,11 +4622,9 @@ 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));
-			if (is_primary
+			if (idx_def->iid == 0
 			    && (wctrlFlags & WHERE_OR_SUBCLAUSE) != 0) {
 				/* This is one term of an OR-optimization using
 				 * the PRIMARY KEY.  No need for a separate index
@@ -4663,28 +4632,25 @@ 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 (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,19 +4666,10 @@ sqlite3WhereBegin(Parse * pParse,	/* The parser context */
 			pLevel->iIdxCur = iIndexCur;
 			assert(iIndexCur >= 0);
 			if (op) {
-				if (pIx != NULL) {
-					uint32_t space_id =
-						pIx->pTable->def->id;
-					struct space *space =
-						space_by_id(space_id);
-					vdbe_emit_open_cursor(pParse, iIndexCur,
-							      pIx->def->iid,
-							      space);
-				} else {
-					vdbe_emit_open_cursor(pParse, iIndexCur,
-							      idx_def->iid,
-							      space);
-				}
+				uint32_t space_id = idx_def->space_id;
+				struct space *space = space_by_id(space_id);
+				vdbe_emit_open_cursor(pParse, iIndexCur,
+						      idx_def->iid, space);
 				if ((pLoop->wsFlags & WHERE_CONSTRAINT) != 0
 				    && (pLoop->
 					wsFlags & (WHERE_COLUMN_RANGE |
@@ -4721,10 +4678,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
-					VdbeComment((v, "%s", idx_def->name));
+				VdbeComment((v, "%s", idx_def->name));
 #ifdef SQLITE_ENABLE_COLUMN_USED_MASK
 				{
 					u64 colUsed = 0;
@@ -4855,7 +4809,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 +4886,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 +4901,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 889a667ae..8a3f2ac77 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 13a045c34..1aa858ac1 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->iid == 0) {
 				if (isSearch) {
 					zFmt = "PRIMARY KEY";
 				}
@@ -240,12 +232,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)
-					sqlite3XPrintf(&str, zFmt, idx_def->name);
-				else
-					sqlite3XPrintf(&str, zFmt, "EPHEMERAL INDEX");
+				sqlite3XPrintf(&str, zFmt, idx_def->name);
 				explainIndexRange(&str, pLoop);
 			}
 		} else if ((flags & WHERE_IPK) != 0
@@ -485,8 +472,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 != NULL &&
+		    pLoop->index_def->key_def->parts[iEq].sort_order) {
 			testcase(iEq == 0);
 			testcase(bRev);
 			bRev = !bRev;
@@ -710,9 +697,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.
 	 */
@@ -720,20 +706,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) {
@@ -741,7 +718,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),
@@ -751,9 +728,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)));
 		}
 	}
 
@@ -1026,9 +1003,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);
 
@@ -1043,14 +1022,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.
@@ -1061,7 +1036,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;
@@ -1098,7 +1074,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 ==
@@ -1106,8 +1082,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;
 			}
 		}
@@ -1119,7 +1096,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);
@@ -1181,34 +1158,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
@@ -1217,12 +1181,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. */
@@ -1330,11 +1290,9 @@ 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);
-			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++) {
-				k = pPk->def->key_def->parts[j].fieldno;
+			for (j = 0; j < (int) pk_part_count; j++) {
+				k = idx_pk->key_def->parts[j].fieldno;
 				sqlite3VdbeAddOp3(v, OP_Column, iIdxCur, k,
 						  iKeyReg + j);
 			}
@@ -1379,7 +1337,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 *cov = NULL;	/* 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 */
@@ -1392,6 +1350,9 @@ sqlite3WhereCodeOneLoopStart(WhereInfo * pWInfo,	/* Complete information about t
 		u16 wctrlFlags;	/* Flags for sub-WHERE clause */
 		Expr *pAndExpr = 0;	/* An ".. AND (...)" expression */
 		Table *pTab = pTabItem->pTab;
+		struct key_def *pk_key_def =
+			sql_table_primary_key(pTab)->def->key_def;
+		uint32_t pk_part_count = pk_key_def->part_count;
 
 		pTerm = pLoop->aLTerm[0];
 		assert(pTerm != 0);
@@ -1438,12 +1399,10 @@ sqlite3WhereCodeOneLoopStart(WhereInfo * pWInfo,	/* Complete information about t
 		 * called on an uninitialized cursor.
 		 */
 		if ((pWInfo->wctrlFlags & WHERE_DUPLICATES_OK) == 0) {
-			Index *pPk = sqlite3PrimaryKeyIndex(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, pk_key_def);
 			regPk = ++pParse->nMem;
 		}
 		iRetInit = sqlite3VdbeAddOp2(v, OP_Integer, 0, regReturn);
@@ -1542,18 +1501,15 @@ sqlite3WhereCodeOneLoopStart(WhereInfo * pWInfo,	/* Complete information about t
 						int r;
 						int iSet =
 						    ((ii == pOrWc->nTerm - 1) ? -1 : ii);
-						Index *pPk = sqlite3PrimaryKeyIndex (pTab);
-						struct key_def *def =
-							pPk->def->key_def;
 
 						/* Read the PK into an array of temp registers. */
 						r = sqlite3GetTempRange(pParse,
-									def->part_count);
+									pk_part_count);
 						for (uint32_t iPk = 0;
-						     iPk < def->part_count;
+						     iPk < pk_part_count;
 						     iPk++) {
 							uint32_t fieldno =
-								def->parts[iPk].
+								pk_key_def->parts[iPk].
 								fieldno;
 							sqlite3ExprCodeGetColumnToReg
 								(pParse,
@@ -1580,20 +1536,20 @@ sqlite3WhereCodeOneLoopStart(WhereInfo * pWInfo,	/* Complete information about t
 								(v, OP_Found,
 								 regRowset, 0,
 								 r,
-								 def->part_count);
+								 pk_part_count);
 							VdbeCoverage(v);
 						}
 						if (iSet >= 0) {
 							sqlite3VdbeAddOp3
 								(v, OP_MakeRecord,
-								 r, def->part_count, regPk);
+								 r, pk_part_count, regPk);
 							sqlite3VdbeAddOp2
 								(v, OP_IdxInsert,
 								 regRowset, regPk);
 						}
 
 						/* Release the array of temp registers */
-						sqlite3ReleaseTempRange(pParse, r, def->part_count);
+						sqlite3ReleaseTempRange(pParse, r, pk_part_count);
 					}
 
 					/* Invoke the main loop body as a subroutine */
@@ -1622,20 +1578,21 @@ sqlite3WhereCodeOneLoopStart(WhereInfo * pWInfo,	/* Complete information about t
 					 * If the call to sqlite3WhereBegin() above resulted in a scan that
 					 * uses an index, and this is either the first OR-connected term
 					 * processed or the index is the same as that used by all previous
-					 * terms, set pCov to the candidate covering index. Otherwise, set
-					 * pCov to NULL to indicate that no candidate covering index will
+					 * terms, set cov to the candidate covering index. Otherwise, set
+					 * cov to NULL to indicate that no candidate covering index will
 					 * be available.
 					 */
 					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 || (cov != NULL &&
+						pSubLoop->index_def->iid == cov->iid))
+					    && (pSubLoop->index_def->iid != 0)) {
 						assert(pSubWInfo->a[0].
 						       iIdxCur == iCovCur);
-						pCov = pSubLoop->pIndex;
+						cov = pSubLoop->index_def;
 					} else {
-						pCov = 0;
+						cov = 0;
 					}
 
 					/* Finish the loop through table entries that match term pOrTerm. */
@@ -1643,8 +1600,8 @@ sqlite3WhereCodeOneLoopStart(WhereInfo * pWInfo,	/* Complete information about t
 				}
 			}
 		}
-		pLevel->u.pCovidx = pCov;
-		if (pCov)
+		pLevel->u.pCovidx = cov;
+		if (cov)
 			pLevel->iIdxCur = iCovCur;
 		if (pAndExpr) {
 			pAndExpr->pLeft = 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-2174-ban-reindex-syntax.test.lua b/test/sql-tap/gh-2174-ban-reindex-syntax.test.lua
deleted file mode 100755
index baa67b4f7..000000000
--- a/test/sql-tap/gh-2174-ban-reindex-syntax.test.lua
+++ /dev/null
@@ -1,29 +0,0 @@
-#!/usr/bin/env tarantool
-
--- this test will be deleted in scope of #3195
-test = require("sqltester")
-test:plan(3)
-
-test:execsql("DROP TABLE IF EXISTS t1");
-test:execsql("CREATE TABLE t1(a INT PRIMARY KEY)");
-test:execsql("CREATE INDEX i1 on t1(a)");
-
-test:do_catchsql_test(
-	"1",
-	"REINDEX i1 ON t1",
-	{1, "keyword \"REINDEX\" is reserved"}
-)
-
-test:do_catchsql_test(
-	"2",
-	"REINDEX t1",
-	 {1, "keyword \"REINDEX\" is reserved"}
-)
-
-test:do_catchsql_test(
-	"3",
-	"REINDEX",
-	{1, "keyword \"REINDEX\" is reserved"}
-)
-
-test:finish_test()
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/keyword1.test.lua b/test/sql-tap/keyword1.test.lua
index 23a561f4d..fbcd17327 100755
--- a/test/sql-tap/keyword1.test.lua
+++ b/test/sql-tap/keyword1.test.lua
@@ -1,6 +1,6 @@
 #!/usr/bin/env tarantool
 test = require("sqltester")
-test:plan(176)
+test:plan(175)
 
 --!./tcltestrunner.lua
 -- 2009 January 29
@@ -107,7 +107,6 @@ local bannedkws = {
 	"primary",
 	"recursive",
 	"references",
-	"reindex",
 	"release",
 	"rename",
 	"replace",
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()

^ permalink raw reply	[flat|nested] 28+ messages in thread

* [tarantool-patches] Re: [PATCH 3/7] sql: remove struct Table from analyze routine
  2018-08-29  0:58     ` [tarantool-patches] " Vladislav Shpilevoy
@ 2018-09-02 23:52       ` n.pettik
  0 siblings, 0 replies; 28+ messages in thread
From: n.pettik @ 2018-09-02 23:52 UTC (permalink / raw)
  To: tarantool-patches; +Cc: Vladislav Shpilevoy


>>  static void
>> -analyzeOneTable(Parse * pParse,	/* Parser context */
>> -		Table * pTab,	/* Table whose indices are to be analyzed */
>> -		int iStatCur,	/* Index of VdbeCursor that writes the _sql_stat1 table */
>> -		int iMem,	/* Available memory locations begin here */
>> -		int iTab	/* Next available cursor */
>> -    )
>> +vdbe_emit_analyze_space(struct Parse *pParse, struct space *space,
>> +			int stat_cursor)
> 
> 1. Once you started this function formatting, please, finish it.
> Use parse instead of pParse. Use reg_stat4 instead of regStat4 etc.

Ok, check it out:

--- a/src/box/sql/analyze.c
+++ b/src/box/sql/analyze.c
@@ -768,63 +768,53 @@ callStatGet(Vdbe * v, int regStat4, int iParam, int regOut)
  * Generate code to do an analysis of all indices associated with
  * a single table.
  *
- * @param pParse Current parsing context.
+ * @param parse Current parsing context.
  * @param space Space to be analyzed.
  * @param stat_cursor Cursor pointing to spaces containing
  *        statistics: _sql_stat1 (stat_cursor) and
  *        _sql_stat4 (stat_cursor + 1).
  */
 static void
-vdbe_emit_analyze_space(struct Parse *pParse, struct space *space,
+vdbe_emit_analyze_space(struct Parse *parse, struct space *space,
                        int stat_cursor)
 {
-       sqlite3 *db = pParse->db;       /* Database handle */
-       int iIdxCur;            /* Cursor open on index being analyzed */
-       int iTabCur;            /* Table cursor */
-       Vdbe *v;                /* The virtual machine being built up */
-       int i;                  /* Loop counter */
+       assert(space != NULL);
        /* Register to hold Stat4Accum object. */
-       int regStat4 = ++pParse->nMem;
+       int stat4_reg = ++parse->nMem;
        /* Index of changed index field. */
-       int regChng = ++pParse->nMem;
+       int chng_reg = ++parse->nMem;
        /* Key argument passed to stat_push(). */
-       int regKey = ++pParse->nMem;
+       int key_reg = ++parse->nMem;
        /* Temporary use register. */
-       int regTemp = ++pParse->nMem;
+       int tmp_reg = ++parse->nMem;
        /* Register containing table name. */
-       int regTabname = ++pParse->nMem;
+       int tab_name_reg = ++parse->nMem;
        /* Register containing index name. */
-       int regIdxname = ++pParse->nMem;
+       int idx_name_reg = ++parse->nMem;
        /* Value for the stat column of _sql_stat1. */
-       int regStat1 = ++pParse->nMem;
+       int stat1_reg = ++parse->nMem;
        /* MUST BE LAST (see below). */
-       int regPrev = ++pParse->nMem;
-
-       assert(space != NULL);
-       v = sqlite3GetVdbe(pParse);
-       assert(v != NULL);
-       if (sqlite3_strlike("\\_%", space->def->name, '\\') == 0) {
-               /* Do not gather statistics on system tables */
+       int prev_reg = ++parse->nMem;
+       /* Do not gather statistics on system tables. */
+       if (space_is_system(space))
                return;
-       }
-
        /*
         * Open a read-only cursor on the table. Also allocate
         * a cursor number to use for scanning indexes.
         */
-       iTabCur = pParse->nTab;
-       pParse->nTab += 2;
+       int tab_cursor = parse->nTab;
+       parse->nTab += 2;
        assert(space->index_count != 0);
-       sqlite3VdbeAddOp4(v, OP_OpenRead, iTabCur, 0, 0, (void *) space,
+       struct Vdbe *v = sqlite3GetVdbe(parse);
+       assert(v != NULL);
+       sqlite3VdbeAddOp4(v, OP_OpenRead, tab_cursor, 0, 0, (void *) space,
                          P4_SPACEPTR);
-       sqlite3VdbeLoadString(v, regTabname, space->def->name);
+       sqlite3VdbeLoadString(v, tab_name_reg, space->def->name);
        for (uint32_t j = 0; j < space->index_count; ++j) {
                struct index *idx = space->index[j];
-               int addrRewind; /* Address of "OP_Rewind iIdxCur" */
-               int addrNextRow;        /* Address of "next_row:" */
-               const char *idx_name;   /* Name of the index */
-
-               /* Primary indexes feature automatically generated
+               const char *idx_name;
+               /*
+                * Primary indexes feature automatically generated
                 * names. Thus, for the sake of clarity, use
                 * instead more familiar table name.
                 */
@@ -833,109 +823,114 @@ vdbe_emit_analyze_space(struct Parse *pParse, struct space *space,
                else
                        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);
+               sqlite3VdbeLoadString(v, idx_name_reg, idx_name);
                VdbeComment((v, "Analysis for %s.%s", space->def->name,
                            idx_name));
-
                /*
                 * Pseudo-code for loop that calls stat_push():
                 *
                 *   Rewind csr
                 *   if eof(csr) goto end_of_scan;
-                *   regChng = 0
+                *   chng_reg = 0
                 *   goto chng_addr_0;
                 *
                 *  next_row:
-                *   regChng = 0
-                *   if( idx(0) != regPrev(0) ) goto chng_addr_0
-                *   regChng = 1
-                *   if( idx(1) != regPrev(1) ) goto chng_addr_1
+                *   chng_reg = 0
+                *   if( idx(0) != prev_reg(0) ) goto chng_addr_0
+                *   chng_reg = 1
+                *   if( idx(1) != prev_reg(1) ) goto chng_addr_1
                 *   ...
-                *   regChng = N
+                *   chng_reg = N
                 *   goto chng_addr_N
                 *
                 *  chng_addr_0:
-                *   regPrev(0) = idx(0)
+                *   prev_reg(0) = idx(0)
                 *  chng_addr_1:
-                *   regPrev(1) = idx(1)
+                *   prev_reg(1) = idx(1)
                 *  ...
                 *
-                *  endDistinctTest:
-                *   regKey = idx(key)
-                *   stat_push(P, regChng, regKey)
+                *  distinct_addr:
+                *   key_reg = idx(key)
+                *   stat_push(P, chng_reg, key_reg)
                 *   Next csr
                 *   if !eof(csr) goto next_row;
                 *
                 *  end_of_scan:
                 */
 
-               /* Make sure there are enough memory cells allocated to accommodate
-                * the regPrev array and a trailing key (the key slot is required
-                * when building a record to insert into the sample column of
-                * the _sql_stat4 table).
+               /*
+                * Make sure there are enough memory cells
+                * allocated to accommodate the prev_reg array
+                * and a trailing key (the key slot is required
+                * when building a record to insert into
+                * the sample column of the _sql_stat4 table).
                 */
-               pParse->nMem = MAX(pParse->nMem, regPrev + part_count);
-
-               /* Open a read-only cursor on the index being analyzed. */
+               parse->nMem = MAX(parse->nMem, prev_reg + part_count);
+               /* Open a cursor on the index being analyzed. */
+               int idx_cursor;
                if (j != 0) {
-                       iIdxCur = pParse->nTab - 1;
-                       sqlite3VdbeAddOp4(v, OP_OpenRead, iIdxCur,
+                       idx_cursor = parse->nTab - 1;
+                       sqlite3VdbeAddOp4(v, OP_OpenRead, idx_cursor,
                                          idx->def->iid, 0,
                                          (void *) space, P4_SPACEPTR);
                        VdbeComment((v, "%s", idx->def->name));
                } else {
                        /* We have already opened cursor on PK. */
-                       iIdxCur = iTabCur;
+                       idx_cursor = tab_cursor;
                }
-
-               /* Invoke the stat_init() function. The arguments are:
-                *
-                *    (1) the number of columns in the index
-                *        (including the number of PK columns)
-                *    (2) the number of columns in the key without the pk
-                *    (3) the number of rows in the index
-                *
+               /*
+                * Invoke the stat_init() function.
+                * The arguments are:
+                *  (1) The number of columns in the index
+                *      (including the number of PK columns);
+                *  (2) The number of columns in the key
+                *       without the pk;
+                *  (3) the number of rows in the index;
+                * FIXME: for Tarantool first and second args
+                * are the same.
                 *
                 * The third argument is only used for STAT4
                 */
-               sqlite3VdbeAddOp2(v, OP_Count, iIdxCur, regStat4 + 3);
-               sqlite3VdbeAddOp2(v, OP_Integer, part_count, regStat4 + 1);
-               sqlite3VdbeAddOp2(v, OP_Integer, part_count, regStat4 + 2);
-               sqlite3VdbeAddOp4(v, OP_Function0, 0, regStat4 + 1, regStat4,
+               sqlite3VdbeAddOp2(v, OP_Count, idx_cursor, stat4_reg + 3);
+               sqlite3VdbeAddOp2(v, OP_Integer, part_count, stat4_reg + 1);
+               sqlite3VdbeAddOp2(v, OP_Integer, part_count, stat4_reg + 2);
+               sqlite3VdbeAddOp4(v, OP_Function0, 0, stat4_reg + 1, stat4_reg,
                                  (char *)&statInitFuncdef, P4_FUNCDEF);
                sqlite3VdbeChangeP5(v, 3);
-
-               /* Implementation of the following:
+               /*
+                * Implementation of the following:
                 *
                 *   Rewind csr
                 *   if eof(csr) goto end_of_scan;
-                *   regChng = 0
+                *   chng_reg = 0
                 *   goto next_push_0;
-                *
                 */
-               addrRewind = sqlite3VdbeAddOp1(v, OP_Rewind, iIdxCur);
-               VdbeCoverage(v);
-               sqlite3VdbeAddOp2(v, OP_Integer, 0, regChng);
-               int endDistinctTest = sqlite3VdbeMakeLabel(v);
+               int rewind_addr = sqlite3VdbeAddOp1(v, OP_Rewind, idx_cursor);
+               sqlite3VdbeAddOp2(v, OP_Integer, 0, chng_reg);
+               int distinct_addr = sqlite3VdbeMakeLabel(v);
                /* Array of jump instruction addresses. */
-               int *aGotoChng =
-                       sqlite3DbMallocRawNN(db, sizeof(int) * part_count);
-               if (aGotoChng == NULL)
-                       continue;
+               int *jump_addrs = region_alloc(&parse->region,
+                                              sizeof(int) * part_count);
+               if (jump_addrs == NULL) {
+                       diag_set(OutOfMemory, sizeof(int) * part_count,
+                                "region", "jump_addrs");
+                       parse->rc = SQL_TARANTOOL_ERROR;
+                       parse->nErr++;
+                       return;
+               }
                /*
                 *  next_row:
-                *   regChng = 0
-                *   if( idx(0) != regPrev(0) ) goto chng_addr_0
-                *   regChng = 1
-                *   if( idx(1) != regPrev(1) ) goto chng_addr_1
+                *   chng_reg = 0
+                *   if( idx(0) != prev_reg(0) ) goto chng_addr_0
+                *   chng_reg = 1
+                *   if( idx(1) != prev_reg(1) ) goto chng_addr_1
                 *   ...
-                *   regChng = N
-                *   goto endDistinctTest
+                *   chng_reg = N
+                *   goto distinct_addr
                 */
                sqlite3VdbeAddOp0(v, OP_Goto);
-               addrNextRow = sqlite3VdbeCurrentAddr(v);
+               int next_row_addr = sqlite3VdbeCurrentAddr(v);
                if (part_count == 1 && idx->def->opts.is_unique) {
                        /*
                         * For a single-column UNIQUE index, once
@@ -943,122 +938,110 @@ vdbe_emit_analyze_space(struct Parse *pParse, struct space *space,
                         * that all the rest will be distinct, so
                         * skip subsequent distinctness tests.
                         */
-                       sqlite3VdbeAddOp2(v, OP_NotNull, regPrev,
-                                         endDistinctTest);
-                       VdbeCoverage(v);
+                       sqlite3VdbeAddOp2(v, OP_NotNull, prev_reg,
+                                         distinct_addr);
                }
                struct key_part *part = idx->def->key_def->parts;
-               for (i = 0; i < part_count; ++i, ++part) {
+               for (int i = 0; i < part_count; ++i, ++part) {
                        struct coll *coll = part->coll;
-                       sqlite3VdbeAddOp2(v, OP_Integer, i, regChng);
-                       sqlite3VdbeAddOp3(v, OP_Column, iIdxCur, part->fieldno,
-                                         regTemp);
-                       aGotoChng[i] = sqlite3VdbeAddOp4(v, OP_Ne, regTemp, 0,
-                                                        regPrev + i,
+                       sqlite3VdbeAddOp2(v, OP_Integer, i, chng_reg);
+                       sqlite3VdbeAddOp3(v, OP_Column, idx_cursor,
+                                         part->fieldno, tmp_reg);
+                       jump_addrs[i] = sqlite3VdbeAddOp4(v, OP_Ne, tmp_reg, 0,
+                                                        prev_reg + i,
                                                         (char *)coll,
                                                         P4_COLLSEQ);
                        sqlite3VdbeChangeP5(v, SQLITE_NULLEQ);
-                       VdbeCoverage(v);
                }
-               sqlite3VdbeAddOp2(v, OP_Integer, part_count, regChng);
-               sqlite3VdbeGoto(v, endDistinctTest);
-
+               sqlite3VdbeAddOp2(v, OP_Integer, part_count, chng_reg);
+               sqlite3VdbeGoto(v, distinct_addr);
                /*
                 *  chng_addr_0:
-                *   regPrev(0) = idx(0)
+                *   prev_reg(0) = idx(0)
                 *  chng_addr_1:
-                *   regPrev(1) = idx(1)
+                *   prev_reg(1) = idx(1)
                 *  ...
                 */
-               sqlite3VdbeJumpHere(v, addrNextRow - 1);
+               sqlite3VdbeJumpHere(v, next_row_addr - 1);
                part = idx->def->key_def->parts;
-               for (i = 0; i < part_count; ++i, ++part) {
-                       sqlite3VdbeJumpHere(v, aGotoChng[i]);
-                       sqlite3VdbeAddOp3(v, OP_Column, iIdxCur, part->fieldno,
-                                         regPrev + i);
+               for (int i = 0; i < part_count; ++i, ++part) {
+                       sqlite3VdbeJumpHere(v, jump_addrs[i]);
+                       sqlite3VdbeAddOp3(v, OP_Column, idx_cursor,
+                                         part->fieldno, prev_reg + i);
                }
-               sqlite3VdbeResolveLabel(v, endDistinctTest);
-               sqlite3DbFree(db, aGotoChng);
-
+               sqlite3VdbeResolveLabel(v, distinct_addr);
                /*
                 *  chng_addr_N:
-                *   regKey = idx(key)              // STAT4 only
-                *   stat_push(P, regChng, regKey)  // 3rd parameter STAT4 only
+                *   key_reg = idx(key)
+                *   stat_push(P, chng_reg, key_reg)
                 *   Next csr
                 *   if !eof(csr) goto next_row;
                 */
-               assert(regKey == (regStat4 + 2));
+               assert(key_reg == (stat4_reg + 2));
                struct index *pk = space_index(space, 0);
                int pk_part_count = pk->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 (i = 0; i < pk_part_count; i++) {
+               parse->nMem = MAX(parse->nMem,
+                                 prev_reg + part_count + pk_part_count);
+               int stat_key_reg = prev_reg + part_count;
+               for (int i = 0; i < pk_part_count; i++) {
                        uint32_t k = pk->def->key_def->parts[i].fieldno;
                        assert(k < space->def->field_count);
-                       sqlite3VdbeAddOp3(v, OP_Column, iIdxCur, k,
-                                         regKeyStat + i);
+                       sqlite3VdbeAddOp3(v, OP_Column, idx_cursor, k,
+                                         stat_key_reg + i);
                        VdbeComment((v, "%s", space->def->fields[k].name));
                }
-               sqlite3VdbeAddOp3(v, OP_MakeRecord, regKeyStat,
-                                 pk_part_count, regKey);
-
-               assert(regChng == (regStat4 + 1));
-               sqlite3VdbeAddOp4(v, OP_Function0, 1, regStat4, regTemp,
+               sqlite3VdbeAddOp3(v, OP_MakeRecord, stat_key_reg,
+                                 pk_part_count, key_reg);
+               assert(chng_reg == (stat4_reg + 1));
+               sqlite3VdbeAddOp4(v, OP_Function0, 1, stat4_reg, tmp_reg,
                                  (char *)&statPushFuncdef, P4_FUNCDEF);
                sqlite3VdbeChangeP5(v, 3);
-               sqlite3VdbeAddOp2(v, OP_Next, iIdxCur, addrNextRow);
-               VdbeCoverage(v);
-
+               sqlite3VdbeAddOp2(v, OP_Next, idx_cursor, next_row_addr);
                /* Add the entry to the stat1 table. */
-               callStatGet(v, regStat4, STAT_GET_STAT1, regStat1);
+               callStatGet(v, stat4_reg, STAT_GET_STAT1, stat1_reg);
                assert("BBB"[0] == AFFINITY_TEXT);
-               sqlite3VdbeAddOp4(v, OP_MakeRecord, regTabname, 3, regTemp,
+               sqlite3VdbeAddOp4(v, OP_MakeRecord, tab_name_reg, 3, tmp_reg,
                                  "BBB", 0);
-               sqlite3VdbeAddOp2(v, OP_IdxInsert, stat_cursor, regTemp);
-
+               sqlite3VdbeAddOp2(v, OP_IdxInsert, stat_cursor, tmp_reg);
                /* Add the entries to the stat4 table. */
-
-               int regEq = regStat1;
-               int regLt = regStat1 + 1;
-               int regDLt = regStat1 + 2;
-               int regSample = regStat1 + 3;
-               int regCol = regStat1 + 4;
-               int regSampleKey = regCol + part_count;
-               int addrNext;
-               int addrIsNull;
-
-               pParse->nMem = MAX(pParse->nMem, regCol + part_count);
-
-               addrNext = sqlite3VdbeCurrentAddr(v);
-               callStatGet(v, regStat4, STAT_GET_KEY, regSampleKey);
-               addrIsNull = sqlite3VdbeAddOp1(v, OP_IsNull, regSampleKey);
-               VdbeCoverage(v);
-               callStatGet(v, regStat4, STAT_GET_NEQ, regEq);
-               callStatGet(v, regStat4, STAT_GET_NLT, regLt);
-               callStatGet(v, regStat4, STAT_GET_NDLT, regDLt);
-               sqlite3VdbeAddOp4Int(v, OP_NotFound, iTabCur, addrNext,
-                                    regSampleKey, 0);
-               /* We know that the regSampleKey row exists because it was read by
-                * the previous loop.  Thus the not-found jump of seekOp will never
-                * be taken
+               int eq_reg = stat1_reg;
+               int lt_reg = stat1_reg + 1;
+               int dlt_reg = stat1_reg + 2;
+               int sample_reg = stat1_reg + 3;
+               int col_reg = stat1_reg + 4;
+               int sample_key_reg = col_reg + part_count;
+               parse->nMem = MAX(parse->nMem, col_reg + part_count);
+               int next_addr = sqlite3VdbeCurrentAddr(v);
+               callStatGet(v, stat4_reg, STAT_GET_KEY, sample_key_reg);
+               int is_null_addr = sqlite3VdbeAddOp1(v, OP_IsNull,
+                                                    sample_key_reg);
+               callStatGet(v, stat4_reg, STAT_GET_NEQ, eq_reg);
+               callStatGet(v, stat4_reg, STAT_GET_NLT, lt_reg);
+               callStatGet(v, stat4_reg, STAT_GET_NDLT, dlt_reg);
+               sqlite3VdbeAddOp4Int(v, OP_NotFound, tab_cursor, next_addr,
+                                    sample_key_reg, 0);
+               /*
+                * We know that the sample_key_reg row exists
+                * because it was read by the previous loop.
+                * Thus the not-found jump of seekOp will never
+                * be taken.
                 */
-               VdbeCoverageNeverTaken(v);
-               for (i = 0; i < part_count; i++) {
+               for (int i = 0; i < part_count; i++) {
                        uint32_t tabl_col = idx->def->key_def->parts[i].fieldno;
-                       sqlite3ExprCodeGetColumnOfTable(v, space->def, iTabCur,
-                                                       tabl_col, regCol + i);
+                       sqlite3ExprCodeGetColumnOfTable(v, space->def,
+                                                       tab_cursor, tabl_col,
+                                                       col_reg + i);
                }
-               sqlite3VdbeAddOp3(v, OP_MakeRecord, regCol, part_count,
-                                 regSample);
-               sqlite3VdbeAddOp3(v, OP_MakeRecord, regTabname, 6, regTemp);
-               sqlite3VdbeAddOp2(v, OP_IdxReplace, stat_cursor + 1, regTemp);
-               sqlite3VdbeAddOp2(v, OP_Goto, 1, addrNext);     /* P1==1 for end-of-loop */
-               sqlite3VdbeJumpHere(v, addrIsNull);
-
-               /* End of analysis */
-               sqlite3VdbeJumpHere(v, addrRewind);
+               sqlite3VdbeAddOp3(v, OP_MakeRecord, col_reg, part_count,
+                                 sample_reg);
+               sqlite3VdbeAddOp3(v, OP_MakeRecord, tab_name_reg, 6, tmp_reg);
+               sqlite3VdbeAddOp2(v, OP_IdxReplace, stat_cursor + 1, tmp_reg);
+               /* P1==1 for end-of-loop. */
+               sqlite3VdbeAddOp2(v, OP_Goto, 1, next_addr);
+               sqlite3VdbeJumpHere(v, is_null_addr);
+               /* End of analysis. */
+               sqlite3VdbeJumpHere(v, rewind_addr);
        }
 }

^ permalink raw reply	[flat|nested] 28+ messages in thread

* [tarantool-patches] Re: [PATCH 4/7] sql: refactor ALTER RENAME code generation
  2018-08-29  0:58     ` [tarantool-patches] " Vladislav Shpilevoy
@ 2018-09-02 23:52       ` n.pettik
  0 siblings, 0 replies; 28+ messages in thread
From: n.pettik @ 2018-09-02 23:52 UTC (permalink / raw)
  To: tarantool-patches; +Cc: Vladislav Shpilevoy


>> +	assert(src_tab->nSrc == 1);
>> +	struct sqlite3 *db = parse->db;
>> +	char *new_name = sqlite3NameFromToken(db, new_name_tk);
>> +	if (new_name == NULL)
>>  		goto exit_rename_table;
>> +	/* Check that new name isn't occupied by another table. */
>> +	uint32_t space_id = box_space_id_by_name(new_name, strlen(new_name));
>> +	if (space_id != BOX_ID_NIL) {
>> +		diag_set(ClientError, ER_SQL_EXECUTE, tt_sprintf("there is "
>> +			"already another table with this name: %s", new_name));
> 
> 1. ER_SPACE_EXISTS

Fixed, see below.

> 
>> +		goto tnt_error;
>>  	}
>> -	if (pTab->def->opts.is_view) {
>> -		sqlite3ErrorMsg(pParse, "view %s may not be altered",
>> -				pTab->def->name);
>> -		goto exit_rename_table;
>> +	const char *tbl_name = src_tab->a[0].zName;
>> +	space_id = box_space_id_by_name(tbl_name, strlen(tbl_name));
>> +	if (space_id == BOX_ID_NIL) {
>> +		diag_set(ClientError, ER_NO_SUCH_SPACE, tbl_name);
>> +		goto tnt_error;
>>  	}
>> -	/* Begin a transaction for database. */
>> -	v = sqlite3GetVdbe(pParse);
>> -	if (v == 0) {
>> -		goto exit_rename_table;
>> +	struct space *space = space_by_id(space_id);
>> +	assert(space != NULL);
>> +	if (space->def->opts.is_view) {
>> +		diag_set(ClientError, ER_SQL_EXECUTE,
>> +			 "view may not be altered");
>> +		goto tnt_error;
> 
> 2. ER_ALTER_SPACE or ER_UNSUPPORTED, on your choice.

diff --git a/src/box/sql/alter.c b/src/box/sql/alter.c
index 320dfdd05..3d72e311a 100644
--- a/src/box/sql/alter.c
+++ b/src/box/sql/alter.c
@@ -49,8 +49,7 @@ sql_alter_table_rename(struct Parse *parse, struct SrcList *src_tab,
        /* Check that new name isn't occupied by another table. */
        uint32_t space_id = box_space_id_by_name(new_name, strlen(new_name));
        if (space_id != BOX_ID_NIL) {
-               diag_set(ClientError, ER_SQL_EXECUTE, tt_sprintf("there is "
-                       "already another table with this name: %s", new_name));
+               diag_set(ClientError, ER_SPACE_EXISTS, new_name);
                goto tnt_error;
        }
        const char *tbl_name = src_tab->a[0].zName;
@@ -62,7 +61,7 @@ sql_alter_table_rename(struct Parse *parse, struct SrcList *src_tab,
        struct space *space = space_by_id(space_id);
        assert(space != NULL);
        if (space->def->opts.is_view) {
-               diag_set(ClientError, ER_SQL_EXECUTE,
+               diag_set(ClientError, ER_ALTER_SPACE, tbl_name,
                         "view may not be altered");
                goto tnt_error;
        }
diff --git a/test/sql-tap/alter.test.lua b/test/sql-tap/alter.test.lua
index ffd0ba6dc..355c87a09 100755
--- a/test/sql-tap/alter.test.lua
+++ b/test/sql-tap/alter.test.lua
@@ -83,7 +83,7 @@ test:do_catchsql_test(
         ALTER TABLE t2 RENAME TO t3;
     ]], {
         -- <alter-2.2>
-        1, "Failed to execute SQL statement: there is already another table with this name: T3"
+        1, "Space 'T3' already exists"
         -- </alter-2.2>
     })

^ permalink raw reply	[flat|nested] 28+ messages in thread

* [tarantool-patches] Re: [PATCH 5/7] sql: remove lookups in Table hash
  2018-08-29  0:58     ` [tarantool-patches] " Vladislav Shpilevoy
@ 2018-09-02 23:52       ` n.pettik
  0 siblings, 0 replies; 28+ messages in thread
From: n.pettik @ 2018-09-02 23:52 UTC (permalink / raw)
  To: tarantool-patches; +Cc: Vladislav Shpilevoy


> On 23/08/2018 19:55, Nikita Pettik wrote:
>> Instead of looking at Table hash lets extract space from Tarantool
>> internals and create Table wrapper around it.
> 
> The major question - why do we still need struct Table? Just because
> of Table.nTabRef? This looks like an insignificant stopper since
> anyway we need refs for struct space and multiversion transactional
> DDL. We could create surrogate struct spaces for some temporary things
> instead of surrogate Tables. And do not create anything for ordinal
> spaces.

I guess it is possible to come up with workaround and avoid
using nTabRef. The real reason was to reduce size of patch and
finish DD ASAP: complete removing struct Table seems to result
in quite huge diff (struct Table still is included to struct Expr).
So, in theory nothing prevents us from removing struct Table
from source code except for time and meticulous refactoring.

>>  13 files changed, 204 insertions(+), 245 deletions(-)
>> diff --git a/src/box/sql/build.c b/src/box/sql/build.c
>> index 79dc46592..840604aa3 100644
>> --- a/src/box/sql/build.c
>> +++ b/src/box/sql/build.c
>> @@ -217,22 +217,10 @@ sqlite3CommitInternalChanges()
>>  	current_session()->sql_flags &= ~SQLITE_InternChanges;
>>  }
>>  -/*
>> - * Return true if given column is part of primary key.
>> - * If field number is less than 63, corresponding bit
>> - * in column mask is tested. Otherwise, check whether 64-th bit
>> - * in mask is set or not. If it is set, then iterate through
>> - * key parts of primary index and check field number.
>> - * In case it isn't set, there are no key columns among
>> - * the rest of fields.
>> - */
>>  bool
>> -table_column_is_in_pk(Table *table, uint32_t column)
>> +sql_space_column_is_in_pk(struct space *space, uint32_t column)
>>  {
>> -	struct space *space = space_by_id(table->def->id);
>> -	assert(space != NULL);
>> -
>> -	struct index *primary_idx = index_find(space, 0 /* PK */);
>> +	struct index *primary_idx = space->index[0];
> 
> 1. space_index(space, 0).

Ok, I slightly refactored this part:

+++ b/src/box/sql/build.c
@@ -221,10 +221,10 @@ sqlite3CommitInternalChanges()
 bool
 sql_space_column_is_in_pk(struct space *space, uint32_t column)
 {
-       struct index *primary_idx = space->index[0];
-       /* Views don't have any indexes. */
-       if (primary_idx == NULL)
+       if (space->def->opts.is_view)
                return false;
+       struct index *primary_idx = space_index(space, 0);
+       assert(primary_idx != NULL);
        struct key_def *key_def = primary_idx->def->key_def;
        uint64_t pk_mask = key_def->column_mask;
        if (column < 63)


> Also I see below check for idx == NULL.
> But if a space has no indexes, its space->index is NULL as well
> AFAIK. How does it work? The previous version was checking if a
> space has an index.

It is not true, actually. You can check it by adding assert():

 sql_space_column_is_in_pk(struct space *space, uint32_t column)
 {
-       struct index *primary_idx = space->index[0];
-       /* Views don't have any indexes. */
-       if (primary_idx == NULL)
+       if (space->def->opts.is_view) {
+               assert(space->index == NULL);
                return false;
+       }

And launch sql-tap/trigger9.test.lua. It would fire this assert,
even despite the fact that VIEW has no any indexes.

> 
>>  	/* Views don't have any indexes. */
>>  	if (primary_idx == NULL)
>>  		return false;
>> diff --git a/src/box/sql/delete.c b/src/box/sql/delete.c
>> index 0f285cc8b..a457a71d1 100644
>> --- a/src/box/sql/delete.c
>> +++ b/src/box/sql/delete.c
>> @@ -36,16 +36,31 @@
>>  #include "tarantoolInt.h"
>>    struct Table *
>> -sql_list_lookup_table(struct Parse *parse, SrcList *src_list)
>> +sql_lookup_table(struct Parse *parse, struct SrcList_item *tbl_name)
>>  {
>> -	struct SrcList_item *item = src_list->a;
>> -	assert(item != NULL && src_list->nSrc == 1);
>> -	struct Table *table = sqlite3LocateTable(parse, 0, item->zName);
>> -	sqlite3DeleteTable(parse->db, item->pTab);
>> -	item->pTab = table;
>> -	if (table != NULL)
>> -		item->pTab->nTabRef++;
>> -	if (sqlite3IndexedByLookup(parse, item) != 0)
>> +	assert(tbl_name != NULL);
>> +	uint32_t space_id = box_space_id_by_name(tbl_name->zName,
>> +						 strlen(tbl_name->zName));
>> +	sqlite3DeleteTable(parse->db, tbl_name->pTab);
> 
> 2. What is happening here? If tbl_name->pTab != NULL, why do you
> recreate it?

I checked, it can’t be != NULL. Lets add simple assert:

+++ b/src/box/sql/delete.c
@@ -39,9 +39,9 @@ struct Table *
 sql_lookup_table(struct Parse *parse, struct SrcList_item *tbl_name)
 {
        assert(tbl_name != NULL);
+       assert(tbl_name->pTab == NULL);
        uint32_t space_id = box_space_id_by_name(tbl_name->zName,
                                                 strlen(tbl_name->zName));
-       sqlite3DeleteTable(parse->db, tbl_name->pTab);
        struct space *space = space_by_id(space_id);
        if (space_id == BOX_ID_NIL || space == NULL) {
                sqlite3ErrorMsg(parse, "no such table: %s", tbl_name->zName);

+++ b/src/box/sql/select.c
@@ -4811,7 +4811,6 @@ selectExpander(Walker * pWalker, Select * p)
                         * An ordinary table or view name in the
                         * FROM clause.
                         */
-                       assert(pFrom->pTab == NULL);
                        pTab = sql_lookup_table(pParse, pFrom);
                        if (pTab == NULL)
                                return WRC_Abort;

> 
>> +	struct space *space = space_by_id(space_id);
>> +	if (space_id == BOX_ID_NIL || space == NULL) {
> 
> 3. If 'space_id' == BOX_ID_NIL then space == NULL as well,
> always.

Not quite. Check out this issue https://github.com/tarantool/tarantool/issues/3611
and patch resolving it 16c3989c7432c27a0e06ff1fc4bf571d63d394ff

Here we may face with the same problem. If I removed this additional check,
sql/checks.test.lua would fail.

> You should not even try to lookup a space when
> box_space_id_by_name returned BOX_ID_NIL.
> 
>> +		sqlite3ErrorMsg(parse, "no such table: %s", tbl_name->zName);
> 
> 4. ER_NO_SUCH_SPACE.

I left this error message on purpose: firstly replacing it to ’no such space’
would lead to massive refactoring in test suite; secondly, I am not sure
that we should use ’space’ terminology in SQL. For instance, Peter is
against even using ‘field’ instead of ‘column’: https://github.com/tarantool/tarantool/issues/3621

>Minor problem: the error message contains the word 'field' rather than 'column’.

If we finally decide to use ’space’ term in SQL, I will fix it.
*In before* I know that in some SQL parts we already use errors messages
containing Tarantool’s terminology, but anyway…
As a compromise I can use ER_SQL and “no such table”.

> 
>> +		return NULL;
>> +	}
>> +	assert(space != NULL);
>> +	if (space->def->field_count == 0) {
>> +		sqlite3ErrorMsg(parse, "no format for space: %s",
>> +				tbl_name->zName);
> 
> 5. ER_UNSUPPORTED or a new error code.

Ok:

+++ b/src/box/sql/delete.c
@@ -49,8 +49,10 @@ sql_lookup_table(struct Parse *parse, struct SrcList_item *tbl_name)
        }
        assert(space != NULL);
        if (space->def->field_count == 0) {
-               sqlite3ErrorMsg(parse, "no format for space: %s",
-                               tbl_name->zName);
+               diag_set(ClientError, ER_UNSUPPORTED, "SQL",
+                        "space without format");
+               parse->rc = SQL_TARANTOOL_ERROR;
+               parse->nErr++;
                return NULL;
        }
        struct Table *table = sqlite3DbMallocZero(parse->db, sizeof(*table));
diff --git a/test/sql-tap/lua-tables.test.lua b/test/sql-tap/lua-tables.test.lua
index 0e79c61a8..19d80b611 100755
--- a/test/sql-tap/lua-tables.test.lua
+++ b/test/sql-tap/lua-tables.test.lua
@@ -46,7 +46,7 @@ test:do_catchsql_test(
 test:do_catchsql_test(
     "lua-tables-5",
     [[SELECT * from "t1"]],
-    {1, "no format for space: t1"}
+    {1, "SQL does not support space without format"}

> 
>> +		return NULL;
>> +	}
>> +	struct Table *table = sqlite3DbMallocZero(parse->db, sizeof(*table));
>> +	if (table == NULL)
>> +		return NULL;
>> +	table->def = space_def_dup(space->def);
>> +	table->space = space;
>> +	table->nTabRef = 1;
>> +	tbl_name->pTab = table;
>> +	if (sqlite3IndexedByLookup(parse, tbl_name) != 0)
>>  		table = NULL;
> 
> 6. And here table leaks.

No, it doesn’t:

 tbl_name->pTab = table;

sql_lookup_table will return NULL and launch cleanup mechanism in
caller function which in turn calls sqlite3SrcListDelete().

> 
>>  	return table;
>>  }
>> diff --git a/src/box/sql/expr.c b/src/box/sql/expr.c
>> index cb4f89e35..7b6300c75 100644
>> --- a/src/box/sql/expr.c
>> +++ b/src/box/sql/expr.c
>> @@ -278,7 +278,7 @@ comparisonAffinity(Expr * pExpr)
>>  		aff =
>>  		    sqlite3CompareAffinity(pExpr->x.pSelect->pEList->a[0].pExpr,
>>  					   aff);
>> -	} else if (NEVER(aff == 0)) {
>> +	} else {
>>  		aff = AFFINITY_BLOB;
>>  	}
>>  	return aff;
> 
> 7. Why? How is affinity linked with the patch?

Look, spaces created outside SQL have fields with affinity == AFFINITY_UNDEFINED,
unless otherwise indicated:

const struct field_def field_def_default = {
       .type = FIELD_TYPE_ANY,
       .affinity = AFFINITY_UNDEFINED,
       .name = NULL,
       .is_nullable = false,
       .nullable_action = ON_CONFLICT_ACTION_DEFAULT,
       .coll_id = COLL_NONE,
       .default_value = NULL,
       .default_value_expr = NULL
};

Thus, in some cases it may result in reaching that ‘else’ branch.
For example, SELECT count(*) FROM "_space" WHERE "name" IN ('T', 'S’);
I’m not going to dive into details, it is quite boring (but if you want to I can
attach call stack and explain what is going on).

> 
>> diff --git a/src/box/sql/insert.c b/src/box/sql/insert.c
>> index 9220d34e1..d3950254e 100644
>> --- a/src/box/sql/insert.c
>> +++ b/src/box/sql/insert.c
>> @@ -1234,40 +1233,40 @@ xferOptimization(Parse * pParse,	/* Parser context */
>>  	 * we have to check the semantics.
>>  	 */
>>  	pItem = pSelect->pSrc->a;
>> -	pSrc = sqlite3LocateTable(pParse, 0, pItem->zName);
>> +	uint32_t src_id = box_space_id_by_name(pItem->zName,
>> +						strlen(pItem->zName));
>>  	/* FROM clause does not contain a real table. */
>> -	if (pSrc == NULL)
>> +	if (src_id == BOX_ID_NIL)
>>  		return 0;
>> +	struct space *src = space_by_id(src_id);
>> +	assert(src != NULL);
> 
> 8. In this function you do not need pDest as Table. It is
> enough to pass a space here. Only sqlite3OpenTable still
> uses struct Table in this function, but anyway in the first
> lines it does space lookup. I fixed this remark partially and
> dirty. Please, finish my fixes (refactor or remove
> sqlite3OpenTable etc).

Ok, fixed:

+++ b/src/box/sql/insert.c
@@ -152,7 +152,7 @@ vdbe_has_table_read(struct Parse *parser, const struct Table *table)
 /* Forward declaration */
 static int
 xferOptimization(Parse * pParse,       /* Parser context */
-                Table * pDest, /* The table we are inserting into */
+                struct space *dest,
                 Select * pSelect,      /* A SELECT statement to use as the data source */
                 int onError);  /* How to handle constraint errors */
 
@@ -358,7 +358,8 @@ sqlite3Insert(Parse * pParse,       /* Parser context */
         *
         * This is the 2nd template.
         */
-       if (pColumn == 0 && xferOptimization(pParse, pTab, pSelect, on_error)) {
+       if (pColumn == 0 &&
+           xferOptimization(pParse, pTab->space, pSelect, on_error)) {
                assert(trigger == NULL);
                assert(pList == 0);
                goto insert_end;
@@ -1131,7 +1132,7 @@ sql_index_is_xfer_compatible(const struct index_def *dest,
  */
 static int
 xferOptimization(Parse * pParse,       /* Parser context */
-                Table * pDest,         /* The table we are inserting into */
+                struct space *dest,
                 Select * pSelect,      /* A SELECT statement to use as the data source */
                 int onError)           /* How to handle constraint errors */
 {
@@ -1145,8 +1146,6 @@ xferOptimization(Parse * pParse,  /* Parser context */
        int regData, regTupleid;        /* Registers holding data and tupleid */
        struct session *user_session = current_session();
        bool is_err_action_default = false;
-       struct space *dest = space_by_id(pDest->def->id);
-       assert(dest != NULL);
 
        if (pSelect == NULL)
                return 0;       /* Must be of the form  INSERT INTO ... SELECT ... */
@@ -1298,7 +1297,7 @@ xferOptimization(Parse * pParse,  /* Parser context */
        regTupleid = sqlite3GetTempReg(pParse);
 
        vdbe_emit_open_cursor(pParse, iDest, 0, dest);
-       VdbeComment((v, "%s", pDest->def->name));
+       VdbeComment((v, "%s", dest->def->name));

> 
>> diff --git a/src/box/sql/pragma.c b/src/box/sql/pragma.c
>> index 6173462ff..063836989 100644
>> --- a/src/box/sql/pragma.c
>> +++ b/src/box/sql/pragma.c
>> @@ -245,6 +245,95 @@ sql_default_engine_set(const char *engine_name)
>>  	return 0;
>>  }
>>  +/**
>> + * This function handles PRAGMA TABLE_INFO(<table>).
>> + *
>> + * Return a single row for each column of the named table.
>> + * The columns of the returned data set are:
>> + *
>> + * - cid: Column id (numbered from left to right, starting at 0);
>> + * - name: Column name;
>> + * - type: Column declaration type;
>> + * - notnull: True if 'NOT NULL' is part of column declaration;
>> + * - dflt_value: The default value for the column, if any.
>> + *
>> + * @param parse Current parsing context.
>> + * @param tbl_name Name of table to be examined.
>> + */
>> +static void
>> +sql_pragma_table_info(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);
>> +	struct space_def *def = space->def;
>> +	struct index *pk = space->index[0];
> 
> 9. space_index(). What is strange, you use space_index sometimes.

I use (and used) space_index() when it comes for real space from cache.
When we are operating on ‘fake’ space from struct Table (like in previous patch)
we can’t use map (such spaces simply lack it). Starting from this patch, we can
use map everywhere except for building (i.e. creating table/index) routine.

Also, I see a lot of usages of index[0] through the sever code, so
what is wrong with using it?

+++ b/src/box/sql/pragma.c
@@ -270,7 +270,7 @@ sql_pragma_table_info(struct Parse *parse, const char *tbl_name)
                return;
        struct space *space = space_by_id(space_id);
        assert(space != NULL);
-       struct index *pk = space->index[0];
+       struct index *pk = space_index(space, 0);

> 
>> +	parse->nMem = 6;
>> +	if (def->opts.is_view) {
>> +		const char *sql = def->opts.sql;
>> +		sql_view_assign_cursors(parse, sql);
>> +	}
>> +	struct Vdbe *v = sqlite3GetVdbe(parse);
>> +	for (uint32_t i = 0, k; i < def->field_count; ++i) {
>> +		if (!sql_space_column_is_in_pk(space, i)) {
>> +			k = 0;
>> +		} else if (pk == NULL) {
>> +			k = 1;
>> +		} else {
>> +			struct key_def *kdef = pk->def->key_def;
>> +			k = key_def_find(kdef, i) - kdef->parts + 1;
>> +		}
>> +		bool is_nullable = def->fields[i].is_nullable;
>> +		char *expr_str = def->fields[i].default_value;
>> +		const char *name = def->fields[i].name;
>> +		enum field_type type = def->fields[i].type;
>> +		sqlite3VdbeMultiLoad(v, 1, "issisi", i, name,
>> +				     field_type_strs[type], !is_nullable,
>> +				     expr_str, k);
>> +		sqlite3VdbeAddOp2(v, OP_ResultRow, 1, 6);
>> +	}
>> +}
>> diff --git a/src/box/sql/sqliteInt.h b/src/box/sql/sqliteInt.h
>> index 3ae8db1bc..08893c473 100644
>> --- a/src/box/sql/sqliteInt.h
>> +++ b/src/box/sql/sqliteInt.h
>> @@ -3333,6 +3333,11 @@ void sqlite3ExprListSetSpan(Parse *, ExprList *, ExprSpan *);
>>  u32 sqlite3ExprListFlags(const ExprList *);
>>  int sqlite3Init(sqlite3 *);
>>  +void sqlite3Pragma(Parse *, Token *, Token *, Token *, int);
>> +void sqlite3ResetAllSchemasOfConnection(sqlite3 *);
>> +void sqlite3CommitInternalChanges();
>> +void sqlite3DeleteColumnNames(sqlite3 *, Table *);
> 
> 10. Why do you need these movements?

Idk, some refactoring artefacts I guess. Moved it back:

+++ b/src/box/sql/sqliteInt.h
@@ -3331,11 +3331,6 @@ void sqlite3ExprListSetSpan(Parse *, ExprList *, ExprSpan *);
 u32 sqlite3ExprListFlags(const ExprList *);
 int sqlite3Init(sqlite3 *);
 
-void sqlite3Pragma(Parse *, Token *, Token *, Token *, int);
-void sqlite3ResetAllSchemasOfConnection(sqlite3 *);
-void sqlite3CommitInternalChanges();
-void sqlite3DeleteColumnNames(sqlite3 *, Table *);
-
 /**
  * This is the callback routine for the code that initializes the
  * database.  See sqlite3Init() below for additional information.
@@ -3354,6 +3349,11 @@ int
 sql_init_callback(struct init_data *init, const char *name,
                  uint32_t space_id, uint32_t index_id, const char *sql);
 
+void sqlite3Pragma(Parse *, Token *, Token *, Token *, int);
+void sqlite3ResetAllSchemasOfConnection(sqlite3 *);
+void sqlite3CommitInternalChanges();
+void sqlite3DeleteColumnNames(sqlite3 *, Table *);
+
 /**
  * Return true if given column is part of primary key.
  * If field number is less than 63, corresponding bit

> 
>> +
>>  /**
>>   * This is the callback routine for the code that initializes the
>>   * database.  See sqlite3Init() below for additional information.
>> diff --git a/test/sql-tap/analyze9.test.lua b/test/sql-tap/analyze9.test.lua
>> index 2b37e3ad5..05ed50192 100755
>> --- a/test/sql-tap/analyze9.test.lua
>> +++ b/test/sql-tap/analyze9.test.lua
>> @@ -1021,7 +1021,7 @@ test:do_execsql_test(
>>          INSERT INTO x1 VALUES(3, 4);
>>          INSERT INTO x1 VALUES(5, 6);
>>          ANALYZE;
>> -        INSERT INTO "_sql_stat4" VALUES('x1', 'abc', 0, 0, 0, '');
>> +        INSERT INTO "_sql_stat4" VALUES('x1', 'abc', '', '', '', '');
> 
> 11. Why are tests changed? As I understand, you just did refactoring.

According to format of _sql_stat4, we should pass empty string
instead of integers 0. After fetching space from Tarantool, this
test started to fail:
"Tuple field 3 type does not match one required by operation: expected string #"  
(and it seems to be fair, IDK why it passed before).

> 
>>      ]])
>>    test:do_execsql_test(
>> diff --git a/test/sql-tap/join.test.lua b/test/sql-tap/join.test.lua
>> index 04b7674ed..ac05a98b2 100755
>> --- a/test/sql-tap/join.test.lua
>> +++ b/test/sql-tap/join.test.lua
>> @@ -1080,9 +1080,9 @@ jointest("join-12.9", 1000, {1, 'at most 64 tables in a join'})
>>  --    if X(703, "X!cmd", [=[["expr","[lsearch [db eval {PRAGMA compile_options}] MEMDEBUG]<0"]]=])
>>  -- then
>>  jointest("join-12.10", 65534, {1, 'at most 64 tables in a join'})
>> -jointest("join-12.11", 65535, {1, 'too many references to "T14": max 65535'})
>> -jointest("join-12.12", 65536, {1, 'too many references to "T14": max 65535'})
>> -jointest("join-12.13", 65537, {1, 'too many references to "T14": max 65535'})
>> +jointest("join-12.11", 65535, {1, 'at most 64 tables in a join'})
>> +jointest("join-12.12", 65536, {1, 'at most 64 tables in a join'})
>> +jointest("join-12.13", 65537, {1, 'at most 64 tables in a join’})

And these tests are changed since we don’t use tables from hash:
previously we fetch table from hash increasing its ref counter, i.e.
one table instance was shared. Now, each invocation to table results
in creating of new table instance and reseting ref counter.
However, it have become even better IMHO.

>>  --    end
>>  --end
>>  diff --git a/test/sql/delete.result b/test/sql/delete.result
>> index 52f9969c1..993e9e04d 100644
>> --- a/test/sql/delete.result
>> +++ b/test/sql/delete.result
>> @@ -44,7 +44,7 @@ box.sql.execute("DROP TABLE t1;");
>>  --
>>  box.sql.execute("DELETE FROM t1;")
>>  ---
>> -- error: Space 'T1' does not exist
>> +- error: 'no such table: T1'
>>  ...
>>  box.sql.execute("CREATE TABLE t2 (s1 INT PRIMARY KEY);")
>>  ---
>> @@ -54,7 +54,7 @@ box.sql.execute("CREATE TRIGGER t2 BEFORE INSERT ON t2 BEGIN DELETE FROM t1; END
>>  ...
>>  box.sql.execute("INSERT INTO t2 VALUES (0);")
>>  ---
>> -- error: Space 'T1' does not exist
>> +- error: 'no such table: T1'
>>  ...
>>  box.sql.execute("DROP TABLE t2;")
>>  ---
> 
> My diff is on the branch and below:

Seems OK, so simply applied.

Also, I have removed index_is_unordered() function as I promised:

+++ b/src/box/sql/where.c
@@ -2690,30 +2690,6 @@ whereLoopAddBtreeIndex(WhereLoopBuilder * pBuilder,      /* The WhereLoop factory */
        return rc;
 }
 
-/**
- * Check if is_unordered flag is set for given index.
- * Since now there may not be corresponding Tarantool's index
- * (e.g. for temporary objects such as ephemeral tables),
- * a few preliminary checks are made.
- *
- * @param idx Index to be tested.
- * @retval True, if statistics exist and unordered flag is set.
- */
-static bool
-index_is_unordered(const struct index_def *idx)
-{
-       assert(idx != NULL);
-       struct space *space = space_by_id(idx->space_id);
-       if (space == NULL)
-               return false;
-       struct index *tnt_idx = space_index(space, idx->iid);
-       if (tnt_idx == NULL)
-               return false;
-       if (tnt_idx->def->opts.stat != NULL)
-               return tnt_idx->def->opts.stat->is_unordered;
-       return false;
-}
-
 /*
  * Return True if it is possible that pIndex might be useful in
  * implementing the ORDER BY clause in pBuilder.
@@ -2729,7 +2705,7 @@ indexMightHelpWithOrderBy(WhereLoopBuilder * pBuilder,
        ExprList *pOB;
        int ii, jj;
        int part_count = idx_def->key_def->part_count;
-       if (index_is_unordered(idx_def))
+       if (idx_def->opts.stat != NULL && idx_def->opts.stat->is_unordered)
                return 0;
        if ((pOB = pBuilder->pWInfo->pOrderBy) == 0)
                return 0;
@@ -3310,7 +3286,8 @@ wherePathSatisfiesOrderBy(WhereInfo * pWInfo,     /* The WHERE clause */
                                idx_def = NULL;
                                nColumn = 1;
                        } else if ((idx_def = pLoop->index_def) == NULL ||
-                                  index_is_unordered(idx_def)) {
+                                  (idx_def->opts.stat != NULL &&
+                                   idx_def->opts.stat->is_unordered)) {
                                return 0;
                        } else {
                                nColumn = idx_def->key_def->part_count;

Full diff:

+++ b/src/box/sql/build.c
@@ -221,10 +221,10 @@ sqlite3CommitInternalChanges()
 bool
 sql_space_column_is_in_pk(struct space *space, uint32_t column)
 {
-       struct index *primary_idx = space->index[0];
-       /* Views don't have any indexes. */
-       if (primary_idx == NULL)
+       if (space->def->opts.is_view)
                return false;
+       struct index *primary_idx = space_index(space, 0);
+       assert(primary_idx != NULL);
        struct key_def *key_def = primary_idx->def->key_def;
        uint64_t pk_mask = key_def->column_mask;
        if (column < 63)
diff --git a/src/box/sql/delete.c b/src/box/sql/delete.c
index a457a71d1..702e5d676 100644
--- a/src/box/sql/delete.c
+++ b/src/box/sql/delete.c
@@ -39,9 +39,9 @@ struct Table *
 sql_lookup_table(struct Parse *parse, struct SrcList_item *tbl_name)
 {
        assert(tbl_name != NULL);
+       assert(tbl_name->pTab == NULL);
        uint32_t space_id = box_space_id_by_name(tbl_name->zName,
                                                 strlen(tbl_name->zName));
-       sqlite3DeleteTable(parse->db, tbl_name->pTab);
        struct space *space = space_by_id(space_id);
        if (space_id == BOX_ID_NIL || space == NULL) {
                sqlite3ErrorMsg(parse, "no such table: %s", tbl_name->zName);
@@ -49,8 +49,10 @@ sql_lookup_table(struct Parse *parse, struct SrcList_item *tbl_name)
        }
        assert(space != NULL);
        if (space->def->field_count == 0) {
-               sqlite3ErrorMsg(parse, "no format for space: %s",
-                               tbl_name->zName);
+               diag_set(ClientError, ER_UNSUPPORTED, "SQL",
+                        "space without format");
+               parse->rc = SQL_TARANTOOL_ERROR;
+               parse->nErr++;
                return NULL;
        }
        struct Table *table = sqlite3DbMallocZero(parse->db, sizeof(*table));

+++ b/src/box/sql/insert.c
@@ -152,7 +152,7 @@ vdbe_has_table_read(struct Parse *parser, const struct Table *table)
 /* Forward declaration */
 static int
 xferOptimization(Parse * pParse,       /* Parser context */
-                Table * pDest, /* The table we are inserting into */
+                struct space *dest,
                 Select * pSelect,      /* A SELECT statement to use as the data source */
                 int onError);  /* How to handle constraint errors */
 
@@ -358,7 +358,8 @@ sqlite3Insert(Parse * pParse,       /* Parser context */
         *
         * This is the 2nd template.
         */
-       if (pColumn == 0 && xferOptimization(pParse, pTab, pSelect, on_error)) {
+       if (pColumn == 0 &&
+           xferOptimization(pParse, pTab->space, pSelect, on_error)) {
                assert(trigger == NULL);
                assert(pList == 0);
                goto insert_end;
@@ -1131,7 +1132,7 @@ sql_index_is_xfer_compatible(const struct index_def *dest,
  */
 static int
 xferOptimization(Parse * pParse,       /* Parser context */
-                Table * pDest,         /* The table we are inserting into */
+                struct space *dest,
                 Select * pSelect,      /* A SELECT statement to use as the data source */
                 int onError)           /* How to handle constraint errors */
 {
@@ -1145,8 +1146,6 @@ xferOptimization(Parse * pParse,  /* Parser context */
        int regData, regTupleid;        /* Registers holding data and tupleid */
        struct session *user_session = current_session();
        bool is_err_action_default = false;
-       struct space *dest = space_by_id(pDest->def->id);
-       assert(dest != NULL);
 
        if (pSelect == NULL)
                return 0;       /* Must be of the form  INSERT INTO ... SELECT ... */
@@ -1298,7 +1297,7 @@ xferOptimization(Parse * pParse,  /* Parser context */
        regTupleid = sqlite3GetTempReg(pParse);
 
        vdbe_emit_open_cursor(pParse, iDest, 0, dest);
-       VdbeComment((v, "%s", pDest->def->name));
+       VdbeComment((v, "%s", dest->def->name));

diff --git a/src/box/sql/pragma.c b/src/box/sql/pragma.c
index 3451d7c79..90fe0b7da 100644
--- a/src/box/sql/pragma.c
+++ b/src/box/sql/pragma.c
@@ -270,7 +270,7 @@ sql_pragma_table_info(struct Parse *parse, const char *tbl_name)
                return;
        struct space *space = space_by_id(space_id);
        assert(space != NULL);
-       struct index *pk = space->index[0];
+       struct index *pk = space_index(space, 0);
        parse->nMem = 6;
        if (space->def->opts.is_view)
                sql_view_assign_cursors(parse, space->def->opts.sql);
diff --git a/src/box/sql/select.c b/src/box/sql/select.c
index a20d0bb3f..dd8d89b99 100644
--- a/src/box/sql/select.c
+++ b/src/box/sql/select.c
@@ -4811,7 +4811,6 @@ selectExpander(Walker * pWalker, Select * p)
                         * An ordinary table or view name in the
                         * FROM clause.
                         */
-                       assert(pFrom->pTab == NULL);
                        pTab = sql_lookup_table(pParse, pFrom);
                        if (pTab == NULL)
                                return WRC_Abort;
diff --git a/src/box/sql/sqliteInt.h b/src/box/sql/sqliteInt.h
index 04716cb5c..8d8abbffc 100644
--- a/src/box/sql/sqliteInt.h
+++ b/src/box/sql/sqliteInt.h
@@ -3331,11 +3331,6 @@ void sqlite3ExprListSetSpan(Parse *, ExprList *, ExprSpan *);
 u32 sqlite3ExprListFlags(const ExprList *);
 int sqlite3Init(sqlite3 *);
 
-void sqlite3Pragma(Parse *, Token *, Token *, Token *, int);
-void sqlite3ResetAllSchemasOfConnection(sqlite3 *);
-void sqlite3CommitInternalChanges();
-void sqlite3DeleteColumnNames(sqlite3 *, Table *);
-
 /**
  * This is the callback routine for the code that initializes the
  * database.  See sqlite3Init() below for additional information.
@@ -3354,6 +3349,11 @@ int
 sql_init_callback(struct init_data *init, const char *name,
                  uint32_t space_id, uint32_t index_id, const char *sql);
 
+void sqlite3Pragma(Parse *, Token *, Token *, Token *, int);
+void sqlite3ResetAllSchemasOfConnection(sqlite3 *);
+void sqlite3CommitInternalChanges();
+void sqlite3DeleteColumnNames(sqlite3 *, Table *);
+
 /**
  * Return true if given column is part of primary key.
  * If field number is less than 63, corresponding bit

diff --git a/src/box/sql/sqliteInt.h b/src/box/sql/where.c
@@ -2690,30 +2690,6 @@ whereLoopAddBtreeIndex(WhereLoopBuilder * pBuilder,      /* The WhereLoop factory */
        return rc;
 }
 
-/**
- * Check if is_unordered flag is set for given index.
- * Since now there may not be corresponding Tarantool's index
- * (e.g. for temporary objects such as ephemeral tables),
- * a few preliminary checks are made.
- *
- * @param idx Index to be tested.
- * @retval True, if statistics exist and unordered flag is set.
- */
-static bool
-index_is_unordered(const struct index_def *idx)
-{
-       assert(idx != NULL);
-       struct space *space = space_by_id(idx->space_id);
-       if (space == NULL)
-               return false;
-       struct index *tnt_idx = space_index(space, idx->iid);
-       if (tnt_idx == NULL)
-               return false;
-       if (tnt_idx->def->opts.stat != NULL)
-               return tnt_idx->def->opts.stat->is_unordered;
-       return false;
-}
-
 /*
  * Return True if it is possible that pIndex might be useful in
  * implementing the ORDER BY clause in pBuilder.
@@ -2729,7 +2705,7 @@ indexMightHelpWithOrderBy(WhereLoopBuilder * pBuilder,
        ExprList *pOB;
        int ii, jj;
        int part_count = idx_def->key_def->part_count;
-       if (index_is_unordered(idx_def))
+       if (idx_def->opts.stat != NULL && idx_def->opts.stat->is_unordered)
                return 0;
        if ((pOB = pBuilder->pWInfo->pOrderBy) == 0)
                return 0;
@@ -3310,7 +3286,8 @@ wherePathSatisfiesOrderBy(WhereInfo * pWInfo,     /* The WHERE clause */
                                idx_def = NULL;
                                nColumn = 1;
                        } else if ((idx_def = pLoop->index_def) == NULL ||
-                                  index_is_unordered(idx_def)) {
+                                  (idx_def->opts.stat != NULL &&
+                                   idx_def->opts.stat->is_unordered)) {
                                return 0;
                        } else {
                                nColumn = idx_def->key_def->part_count;
 
 test:do_execsql_test(
diff --git a/test/sql-tap/lua-tables.test.lua b/test/sql-tap/lua-tables.test.lua
index 0e79c61a8..19d80b611 100755
--- a/test/sql-tap/lua-tables.test.lua
+++ b/test/sql-tap/lua-tables.test.lua
@@ -46,7 +46,7 @@ test:do_catchsql_test(
 test:do_catchsql_test(
     "lua-tables-5",
     [[SELECT * from "t1"]],
-    {1, "no format for space: t1"}
+    {1, "SQL does not support space without format"}
 )
 -- Extract from tkt3527.test.lua
 test:do_test(

^ permalink raw reply	[flat|nested] 28+ messages in thread

* [tarantool-patches] Re: [PATCH 6/7] sql: don't add system spaces to Table hash
  2018-08-29  0:58     ` [tarantool-patches] " Vladislav Shpilevoy
@ 2018-09-02 23:52       ` n.pettik
  2018-09-06 19:54         ` Vladislav Shpilevoy
  0 siblings, 1 reply; 28+ messages in thread
From: n.pettik @ 2018-09-02 23:52 UTC (permalink / raw)
  To: tarantool-patches; +Cc: Vladislav Shpilevoy


> Thanks for the patch! See my review fixes on the branch and
> below:
> 
> commit 882627479793ae3958cec7274cc3e353395aea40
> Author: Vladislav Shpilevoy <v.shpilevoy@tarantool.org>
> Date:   Tue Aug 28 21:28:49 2018 -0300
> 
>    Review fixes
> 
> diff --git a/src/box/sql/prepare.c b/src/box/sql/prepare.c
> index a59e70dd0..0dfdf251f 100644
> --- a/src/box/sql/prepare.c
> +++ b/src/box/sql/prepare.c
> @@ -133,26 +133,14 @@ sql_init_callback(struct init_data *init, const char *name,
> extern int
> sqlite3InitDatabase(sqlite3 * db)
> {
> -	int rc;
> -	struct init_data init;
> -
> 	assert(db->pSchema != NULL);
> -
> -	memset(&init, 0, sizeof(init));
> -	init.db = db;
> -	/* Read the schema information out of the schema tables
> -	 */
> 	assert(db->init.busy);
> -	{
> -		rc = init.rc;
> -		if (rc == SQLITE_OK)
> -			sql_analysis_load(db);
> -	}
> +	sql_analysis_load(db);
> 	if (db->mallocFailed) {
> -		rc = SQLITE_NOMEM_BKPT;
> 		sqlite3ResetAllSchemasOfConnection(db);
> +		return SQLITE_NOMEM_BKPT;
> 	}
> -	return rc;
> +	return SQLITE_OK;
> }

I disregard this diff since in the next patch (sql: finish DD integration) I completely
remove this function at all.

>  diff --git a/src/box/sql/tarantoolInt.h b/src/box/sql/tarantoolInt.h
> index 9334de52d..8622cd19f 100644
> --- a/src/box/sql/tarantoolInt.h
> +++ b/src/box/sql/tarantoolInt.h
> @@ -10,10 +10,6 @@
>  struct fkey_def;
> -/* Insert or replace operation types - necessary for vdbe */
> -#define TARANTOOL_INDEX_INSERT 1
> -#define TARANTOOL_INDEX_REPLACE 2
> -
> /* Misc */
> const char *tarantoolErrorMessage();

This diff I am going to apply (nevertheless Idk how it relates).

^ permalink raw reply	[flat|nested] 28+ messages in thread

* [tarantool-patches] Re: [PATCH 6/7] sql: don't add system spaces to Table hash
  2018-09-02 23:52       ` n.pettik
@ 2018-09-06 19:54         ` Vladislav Shpilevoy
  2018-09-16 19:04           ` n.pettik
  0 siblings, 1 reply; 28+ messages in thread
From: Vladislav Shpilevoy @ 2018-09-06 19:54 UTC (permalink / raw)
  To: tarantool-patches, n.pettik

Tests fail on this commit. Probably, you
should squash it with the next one.

^ permalink raw reply	[flat|nested] 28+ messages in thread

* [tarantool-patches] Re: [PATCH 2/7] sql: remove SQLite original struct Index
  2018-09-02 23:51       ` n.pettik
@ 2018-09-06 19:54         ` Vladislav Shpilevoy
  2018-09-16 19:04           ` n.pettik
  0 siblings, 1 reply; 28+ messages in thread
From: Vladislav Shpilevoy @ 2018-09-06 19:54 UTC (permalink / raw)
  To: tarantool-patches, n.pettik

Hi! Thanks for the fixes! See my own ones
on the branch and comments below.

>>> +	assert(space != NULL);
>>> +	for (uint32_t i = 0; i < space->index_count; ++i) {
>>> +		struct index *idx = space->index[i];
>>
>> 2. You have mentioned that space indexes are stored as an
>> array, but it is not the only true. The indexes are stored in
>> two ways: as an array and a map. A map allows to find an index
>> by id in O(1) time. Look at space_index function.
> 
> It is cool and I know about it, but the most common use case is simple
> iteration over all indexes like:
> for (i = 0; i < space->index_count; ++i) {
> 	struct index *idx = space->index[I];
> 	...
> }> 
>> Here you do not clear space->index_map and do not
>> reallocate it. Looks like you do not touch index_map
>> at all in this patch. But maybe you should. See my
>> other comments here and in next patches.
> 
> I deliberately didn’t fill in index map. I think maintaining
> map and array at the same time seems to be over-engineering.
> These indexes are really used only during table creation
> (after last patch in series). Moreover, during building routine
> we may need only PK index, which anyway can be fetched as index[0]

It is not over-engineering. This is how real spaces work and I
want these surrogate spaces be as much similar as possible to
them. So I implemented index_map support in my review fixes.

> diff --git a/src/box/sql/build.c b/src/box/sql/build.c
> index d1d952330..617d0ea47 100644
> --- a/src/box/sql/build.c
> +++ b/src/box/sql/build.c
> @@ -260,38 +252,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.
> - *
> - * 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.
> + * 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);

Leak of idx->def->key_def/cmp_def.

> +		free(idx);
> +	}
> +	free(tab->space->index);
> +	free(tab->space);
> +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
> @@ -1156,137 +1137,132 @@ 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 */
> -	struct index_opts opts = pIndex->def->opts;
> -	opts.sql = (char *)zSql;
> -	struct region *region = &pParse->region;
> +	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. */
> +	struct region *region = &parse->region;
>   	uint32_t index_opts_sz = 0;
> -	char *index_opts =
> -		sql_encode_index_opts(region, &opts, &index_opts_sz);
> +	char *index_opts = sql_encode_index_opts(region, &idx_def->opts,
> +						 &index_opts_sz);
>   	if (index_opts == NULL)
>   		goto error;
>   	uint32_t index_parts_sz = 0;
> -	char *index_parts =
> -		sql_encode_index_parts(region, pIndex, &index_parts_sz);
> +	char *index_parts = sql_encode_index_parts(region, def->fields, idx_def,
> +						   pk_def, &index_parts_sz);
>   	if (index_parts == NULL)
>   		goto error;
> -	char *raw = sqlite3DbMallocRaw(pParse->db,
> -				       index_opts_sz + index_parts_sz);
> +	char *raw = sqlite3DbMallocRaw(parse->db,
> +				       index_opts_sz +index_parts_sz);
>   	if (raw == NULL)
>   		return;
> -
>   	memcpy(raw, index_opts, index_opts_sz);
>   	index_opts = raw;
>   	raw += index_opts_sz;
>   	memcpy(raw, index_parts, index_parts_sz);
>   	index_parts = raw;
>   
> -	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.

The comment is wrong. Here space_id_reg is literal
and idx_def->iid is a register. And here a problem is
hidden. idx_def->iid is initialized above as either 0
for primary indexes or 1 for other. But by a coincidence
it matched result of getNewIid() so it worked ok. Once
I added index_map and more strict index identifiers
calculation, this place fired. It is fixed in my
review fixes.

> diff --git a/src/box/sql/tarantoolInt.h b/src/box/sql/tarantoolInt.h
> index efc531bbd..f3e65303b 100644
> --- a/src/box/sql/tarantoolInt.h
> +++ b/src/box/sql/tarantoolInt.h
> @@ -207,6 +207,7 @@ fkey_encode_links(struct region *region, const struct fkey_def *def, int type,
>   		  uint32_t *size);
>   
>   /**
> +<<<<<<< HEAD

Ooops.

>    * Encode index parts of given foreign key constraint into
>    * MsgPack on @region.
>    * @param region Region to use.

Fixes:

================================================================

commit dbeb4281577df192f1cbbc670070de74c93dea39
Author: Vladislav Shpilevoy <v.shpilevoy@tarantool.org>
Date:   Thu Sep 6 20:18:17 2018 +0300

     Review fixes

diff --git a/extra/mkkeywordhash.c b/extra/mkkeywordhash.c
index e856d64c7..2c54c1835 100644
--- a/extra/mkkeywordhash.c
+++ b/extra/mkkeywordhash.c
@@ -82,11 +82,6 @@ struct Keyword {
  #else
  #  define PRAGMA     0x00000400
  #endif
-#ifdef SQLITE_OMIT_REINDEX
-#  define REINDEX    0
-#else
-#  define REINDEX    0x00000800
-#endif
  #define SUBQUERY     0x00001000
  #  define TRIGGER    0x00002000
  #  define VIEW       0x00008000
diff --git a/src/box/sql.c b/src/box/sql.c
index 0c1df9b75..97948ab3b 100644
--- a/src/box/sql.c
+++ b/src/box/sql.c
@@ -1297,7 +1297,7 @@ sql_encode_table(struct region *region, struct Table *table, uint32_t *size)
  	 * If table's PK is single column which is INTEGER, then
  	 * treat it as strict type, not affinity.
  	 */
-	struct index *pk_idx = sql_table_primary_key(table);
+	struct index *pk_idx = space_index(table->space, 0);
  	uint32_t pk_forced_int = UINT32_MAX;
  	if (pk_idx != NULL && pk_idx->def->key_def->part_count == 1) {
  		int pk = pk_idx->def->key_def->parts[0].fieldno;
@@ -1659,21 +1659,30 @@ sql_ephemeral_table_new(Parse *parser, const char *name)
  	Table *table = sqlite3DbMallocZero(db, sizeof(Table));
  	if (table != NULL)
  		def = sql_ephemeral_space_def_new(parser, name);
-	if (def == NULL) {
-		sqlite3DbFree(db, table);
-		return NULL;
-	}
+	if (def == NULL)
+		goto err_def;
  	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;
+		goto err_space;
+	}
+	table->space->index_map =
+		(struct index **) calloc(1, sizeof(struct index *));
+	if (table->space->index_map == NULL) {
+		diag_set(OutOfMemory, sizeof(struct index *), "calloc",
+			 "table->space->index_map");
+		goto err_map;
  	}
-
  	table->def = def;
  	return table;
+err_map:
+	free(table->space);
+err_space:
+	parser->rc = SQL_TARANTOOL_ERROR;
+	parser->nErr++;
+err_def:
+	sqlite3DbFree(db, table);
+	return NULL;
  }
  
  int
diff --git a/src/box/sql/analyze.c b/src/box/sql/analyze.c
index 80293c6ed..1c9a672d4 100644
--- a/src/box/sql/analyze.c
+++ b/src/box/sql/analyze.c
@@ -802,7 +802,14 @@ analyzeOneTable(Parse * pParse,	/* Parser context */
  		return;
  	}
  	assert(pTab->def->id != 0);
-	if (sqlite3_strlike("\\_%", pTab->def->name, '\\') == 0) {
+	/*
+	 * 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);
+	const char *tab_name = space_name(space);
+	if (sqlite3_strlike("\\_%", tab_name, '\\') == 0) {
  		/* Do not gather statistics on system tables */
  		return;
  	}
@@ -816,15 +823,9 @@ analyzeOneTable(Parse * pParse,	/* Parser context */
  	iIdxCur = iTab++;
  	pParse->nTab = MAX(pParse->nTab, iTab);
  	sqlite3OpenTable(pParse, iTabCur, pTab, OP_OpenRead);
-	sqlite3VdbeLoadString(v, regTabname, pTab->def->name);
-	/*
-	 * 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);
+	sqlite3VdbeLoadString(v, regTabname, tab_name);
  	for (uint32_t j = 0; j < space->index_count; ++j) {
-		struct index *idx = pTab->space->index[j];
+		struct index *idx = space->index[j];
  		int addrRewind;	/* Address of "OP_Rewind iIdxCur" */
  		int addrNextRow;	/* Address of "next_row:" */
  		const char *idx_name;	/* Name of the index */
@@ -834,15 +835,14 @@ analyzeOneTable(Parse * pParse,	/* Parser context */
  		 * instead more familiar table name.
  		 */
  		if (idx->def->iid == 0)
-			idx_name = pTab->def->name;
+			idx_name = tab_name;
  		else
  			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);
-		VdbeComment((v, "Analysis for %s.%s", pTab->def->name,
-			    idx_name));
+		VdbeComment((v, "Analysis for %s.%s", tab_name, idx_name));
  
  		/*
  		 * Pseudo-code for loop that calls stat_push():
@@ -986,7 +986,7 @@ analyzeOneTable(Parse * pParse,	/* Parser context */
  		 *   if !eof(csr) goto next_row;
  		 */
  		assert(regKey == (regStat4 + 2));
-		struct index *pk = sql_table_primary_key(pTab);
+		struct index *pk = space_index(space, 0);
  		int pk_part_count = pk->def->key_def->part_count;
  		/* Allocate memory for array. */
  		pParse->nMem = MAX(pParse->nMem,
@@ -994,10 +994,10 @@ analyzeOneTable(Parse * pParse,	/* Parser context */
  		int regKeyStat = regPrev + part_count;
  		for (i = 0; i < pk_part_count; i++) {
  			uint32_t k = pk->def->key_def->parts[i].fieldno;
-			assert(k < pTab->def->field_count);
+			assert(k < space->def->field_count);
  			sqlite3VdbeAddOp3(v, OP_Column, iIdxCur, k,
  					  regKeyStat + i);
-			VdbeComment((v, "%s", pTab->def->fields[k].name));
+			VdbeComment((v, "%s", space->def->fields[k].name));
  		}
  		sqlite3VdbeAddOp3(v, OP_MakeRecord, regKeyStat,
  				  pk_part_count, regKey);
diff --git a/src/box/sql/build.c b/src/box/sql/build.c
index 617d0ea47..d708dbeda 100644
--- a/src/box/sql/build.c
+++ b/src/box/sql/build.c
@@ -159,40 +159,52 @@ sqlite3LocateIndex(sqlite3 * db, const char *zName, const char *zTable)
  	return NULL;
  }
  
-void
+int
  sql_space_index_delete(struct space *space, uint32_t iid)
  {
  	assert(space != NULL);
+	struct index *to_delete = NULL;
+	uint32_t new_index_id_max = 0;
  	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) {
-			index_def_delete(idx->def);
-			free(idx);
-			size_t idx_sz = sizeof(struct index *);
-			uint32_t idx_count = space->index_count - 1;
-			struct index **new_indexes =
-				(struct index **) malloc(idx_sz * idx_count);
-			if (new_indexes == NULL) {
-				diag_set(OutOfMemory, idx_sz * idx_count,
-					 "malloc", "new_indexes");
-				return;
-			}
-			memcpy(new_indexes, space->index, i * idx_sz);
-			memcpy(new_indexes + i, space->index + i + 1,
-			       idx_sz * (idx_count - i));
-			free(space->index);
-			space->index = new_indexes;
-			space->index_count--;
-			break;
-		}
+		if (space->index[i]->def->iid == iid)
+			to_delete = space->index[i];
+		else if (new_index_id_max < space->index[i]->def->iid)
+			new_index_id_max = space->index[i]->def->iid;
  	}
+	/*
+	 * Allocate new chunk with size reduced by 1 slot. Copy
+	 * all indexes to that chunk except for one to be deleted.
+	 */
+	assert(to_delete != NULL);
+	uint32_t new_index_count = space->index_count - 1;
+	uint32_t size = (new_index_count + new_index_id_max + 1) *
+			sizeof(struct index *);
+	struct index **new_index_map = (struct index **) malloc(size);
+	if (new_index_map == NULL) {
+		diag_set(OutOfMemory, size, "malloc", "new_index_map");
+		return -1;
+	}
+	/* Nullify gaps. */
+	memset(new_index_map, 0,
+	       sizeof(struct index *) * (new_index_id_max + 1));
+	struct index **new_indexes = new_index_map + new_index_id_max + 1;
+	for (uint32_t i = 0, j = 0; i < space->index_count; ++i) {
+		struct index *idx = space->index[i];
+		if (idx == to_delete)
+			continue;
+		new_index_map[idx->def->iid] = space->index_map[idx->def->iid];
+		new_indexes[j++] = idx;
+	}
+	index_def_delete(to_delete->def);
+	free(to_delete);
+	free(space->index_map);
+	space->index_map = new_index_map;
+	space->index = new_indexes;
+	space->index_count--;
+	space->index_id_max = new_index_id_max;
  	struct session *user_session = current_session();
  	user_session->sql_flags |= SQLITE_InternChanges;
+	return 0;
  }
  
  /*
@@ -266,16 +278,11 @@ table_delete(struct sqlite3 *db, struct Table *tab)
  		goto skip_index_delete;
  	/* Delete all indices associated with this table. */
  	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);
+		index_def_delete(idx->def);
  		free(idx);
  	}
-	free(tab->space->index);
+	free(tab->space->index_map);
  	free(tab->space);
  skip_index_delete:
  	assert(tab->def != NULL);
@@ -362,14 +369,6 @@ sqlite3CheckIdentifierName(Parse *pParse, char *zName)
  	return SQLITE_OK;
  }
  
-struct index *
-sql_table_primary_key(const struct Table *tab)
-{
-	if (tab->space->index_count == 0 || tab->space->index[0]->def->iid != 0)
-		return NULL;
-	return tab->space->index[0];
-}
-
  /**
   * Create and initialize a new SQL Table object.
   * All memory except table object itself is allocated on region.
@@ -812,7 +811,7 @@ sqlite3AddPrimaryKey(Parse * pParse,	/* Parsing context */
  	int nTerm;
  	if (pTab == 0)
  		goto primary_key_exit;
-	if (sql_table_primary_key(pTab) != NULL) {
+	if (space_index(pTab->space, 0) != NULL) {
  		sqlite3ErrorMsg(pParse,
  				"table \"%s\" has more than one primary key",
  				pTab->def->name);
@@ -872,7 +871,7 @@ sqlite3AddPrimaryKey(Parse * pParse,	/* Parsing context */
  			goto primary_key_exit;
  	}
  
-	struct index *pk = sql_table_primary_key(pTab);
+	struct index *pk = space_index(pTab->space, 0);
  	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++) {
@@ -1146,11 +1145,13 @@ getNewSpaceId(Parse * pParse)
   * @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.
+ * @param index_id_reg Register containing generated index id.
   */
  static void
  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)
+		       const struct index_def *pk_def, int space_id_reg,
+		       int index_id_reg)
  {
  	struct Vdbe *v = sqlite3GetVdbe(parse);
  	int entry_reg = ++parse->nMem;
@@ -1187,10 +1188,11 @@ vdbe_emit_create_index(struct Parse *parse, struct space_def *def,
  	} else {
  		/*
  		 * An existing table is being modified;
-		 * space_id_reg is register, but iid is literal.
+		 * space_id_reg is literal, index_id_reg is
+		 * register.
  		 */
  		sqlite3VdbeAddOp2(v, OP_Integer, space_id_reg, entry_reg);
-		sqlite3VdbeAddOp2(v, OP_SCopy, idx_def->iid, entry_reg + 1);
+		sqlite3VdbeAddOp2(v, OP_SCopy, index_id_reg, entry_reg + 1);
  	}
  	sqlite3VdbeAddOp4(v, OP_String8, 0, entry_reg + 2, 0,
  			  sqlite3DbStrDup(parse->db, idx_def->name),
@@ -1612,7 +1614,7 @@ sqlite3EndTable(Parse * pParse,	/* Parse context */
  	}
  
  	if (!p->def->opts.is_view) {
-		if (sql_table_primary_key(p) == NULL) {
+		if (space_index(p->space, 0) == NULL) {
  			sqlite3ErrorMsg(pParse,
  					"PRIMARY KEY missing on table %s",
  					p->def->name);
@@ -1686,12 +1688,12 @@ 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 *pk = sql_table_primary_key(p);
+		struct index *pk = space_index(p->space, 0);
  		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);
+					       pk->def, reg_space_id,
+					       idx->def->iid);
  		}
  	}
  
@@ -1739,7 +1741,7 @@ sqlite3EndTable(Parse * pParse,	/* Parse context */
  			}
  			fk->parent_id = reg_space_id;
  		} else if (fk_parse->is_self_referenced) {
-			struct index *pk = sql_table_primary_key(p);
+			struct index *pk = space_index(p->space, 0);
  			if (pk->def->key_def->part_count != fk->field_count) {
  				diag_set(ClientError, ER_CREATE_FK_CONSTRAINT,
  					 fk->name, "number of columns in "\
@@ -2480,30 +2482,39 @@ getNewIid(Parse * pParse, int iSpaceId, int iCursor)
  
  /**
   * Add new index to table's indexes list.
- * We follow convention that PK comes first in list.
- *
- * @param index Index to be added to list.
   * @param tab Table to which belongs given index.
+ * @param index Index to be added to list.
   */
  static void
  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, "realloc", "idx");
+	struct space *space = tab->space;
+	uint32_t new_index_count = space->index_count + 1;
+	uint32_t new_index_id_max = MAX(space->index_id_max, index->def->iid);
+	uint32_t size =
+		(new_index_id_max + 1 + new_index_count) * sizeof(index);
+	struct index **new_index_map = (struct index **) malloc(size);
+	if (new_index_map == NULL) {
+		diag_set(OutOfMemory, size, "malloc", "new_index_map");
  		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;
+	struct index **new_index = new_index_map + new_index_id_max + 1;
+	memcpy(new_index_map, space->index_map,
+	       sizeof(index) * (space->index_id_max + 1));
+	memcpy(new_index, space->index, sizeof(index) * space->index_count);
+	new_index_map[index->def->iid] = index;
+	/*
+	 * Make sure that PK always comes as first member in order
+	 * to prefer it among other indexes when possible.
+	 */
+	if (index->def->iid == 0 && space->index_count > 0)
+		SWAP(index, new_index[0]);
+	new_index[new_index_count - 1] = index;
+	free(space->index_map);
+	space->index_map = new_index_map;
+	space->index = new_index;
+	space->index_count = new_index_count;
+	space->index_id_max = new_index_id_max;
  }
  
  /**
@@ -2798,7 +2809,11 @@ sql_create_index(struct Parse *parse, struct Token *token,
  	 * (for the simplicity sake we set it to 1), but PK
  	 * still must have iid == 0.
  	 */
-	uint32_t iid = idx_type != SQL_INDEX_TYPE_CONSTRAINT_PK;
+	uint32_t iid;
+	if (idx_type != SQL_INDEX_TYPE_CONSTRAINT_PK)
+		iid = table->space->index_id_max + 1;
+	else
+		iid = 0;
  	if (index_fill_def(parse, index, table, iid, name, strlen(name),
  			   col_list, idx_type, sql_stmt) != 0)
  		goto exit_create_index;
@@ -2858,6 +2873,7 @@ sql_create_index(struct Parse *parse, struct Token *token,
  	if (table == parse->pNewTable) {
  		for (uint32_t i = 0; i < table->space->index_count; ++i) {
  			struct index *existing_idx = table->space->index[i];
+			uint32_t iid = existing_idx->def->iid;
  			struct key_def *key_def = index->def->key_def;
  			struct key_def *exst_key_def =
  				existing_idx->def->key_def;
@@ -2882,7 +2898,9 @@ 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 &&
-			    existing_idx->def->iid != 0 && !is_named) {
+			    iid != 0 && !is_named) {
+				table->space->index_map[0] = existing_idx;
+				table->space->index_map[iid] = NULL;
  				existing_idx->def->iid = 0;
  				goto exit_create_index;
  			}
@@ -2938,9 +2956,9 @@ 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);
-		struct index *pk = sql_table_primary_key(table);
+		struct index *pk = space_index(table->space, 0);
  		vdbe_emit_create_index(parse, table->def, index->def, pk->def,
-				       space_id);
+				       space_id, index_id);
  		/* Consumes sql_stmt. */
  		first_schema_col =
  			vdbe_emit_index_schema_record(parse, index->def->name,
diff --git a/src/box/sql/delete.c b/src/box/sql/delete.c
index 0f285cc8b..8ec198d88 100644
--- a/src/box/sql/delete.c
+++ b/src/box/sql/delete.c
@@ -351,7 +351,7 @@ sql_table_delete_from(struct Parse *parse, struct SrcList *tab_list,
  			 * key.
  			 */
  			key_len = 0;
-			struct index *pk = sql_table_primary_key(table);
+			struct index *pk = space_index(space, 0);
  			const char *zAff = is_view ? NULL :
  					   sql_space_index_affinity_str(parse->db,
  									space->def,
diff --git a/src/box/sql/expr.c b/src/box/sql/expr.c
index dcb57e9b3..f26372ce7 100644
--- a/src/box/sql/expr.c
+++ b/src/box/sql/expr.c
@@ -3140,7 +3140,7 @@ sqlite3ExprCodeIN(Parse * pParse,	/* Parsing and code generating context */
  
  		struct Table *tab = src_list->a[0].pTab;
  		assert(tab != NULL);
-		struct index *pk = sql_table_primary_key(tab);
+		struct index *pk = space_index(tab->space, 0);
  		assert(pk != NULL);
  
  		uint32_t fieldno = pk->def->key_def->parts[0].fieldno;
diff --git a/src/box/sql/insert.c b/src/box/sql/insert.c
index 9a7fdffbb..cde3bc34c 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 = sql_table_primary_key(tab);
+	struct index *pk = space_index(tab->space, 0);
  	uint32_t part_count = pk->def->key_def->part_count;
  	if (part_count == 1) {
  		uint32_t fieldno = pk->def->key_def->parts[0].fieldno;
diff --git a/src/box/sql/pragma.c b/src/box/sql/pragma.c
index 3ba7ad022..c5b664414 100644
--- a/src/box/sql/pragma.c
+++ b/src/box/sql/pragma.c
@@ -443,7 +443,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 = sql_table_primary_key(table);
+		struct index *pk = space_index(space, 0);
  		pParse->nMem = 6;
  		if (def->opts.is_view) {
  			const char *sql = table->def->opts.sql;
diff --git a/src/box/sql/sqliteInt.h b/src/box/sql/sqliteInt.h
index 02732b59c..9f1791a6e 100644
--- a/src/box/sql/sqliteInt.h
+++ b/src/box/sql/sqliteInt.h
@@ -3343,12 +3343,6 @@ int sqlite3ColumnsFromExprList(Parse *parse, ExprList *expr_list, Table *table);
  void sqlite3SelectAddColumnTypeAndCollation(Parse *, Table *, Select *);
  Table *sqlite3ResultSetOfSelect(Parse *, Select *);
  
-/**
- * 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 *);
  
@@ -3667,8 +3661,11 @@ void sqlite3UnlinkAndDeleteTable(sqlite3 *, const char *);
   *
   * @param space Space which index belongs to.
   * @param iid Id of index to be deleted.
+ *
+ * @retval 0 Success.
+ * @retval -1 Error.
   */
-void
+int
  sql_space_index_delete(struct space *space, uint32_t iid);
  
  char *sqlite3NameFromToken(sqlite3 *, Token *);
diff --git a/src/box/sql/tarantoolInt.h b/src/box/sql/tarantoolInt.h
index f3e65303b..d2475bbc8 100644
--- a/src/box/sql/tarantoolInt.h
+++ b/src/box/sql/tarantoolInt.h
@@ -207,7 +207,6 @@ fkey_encode_links(struct region *region, const struct fkey_def *def, int type,
  		  uint32_t *size);
  
  /**
-<<<<<<< HEAD
   * Encode index parts of given foreign key constraint into
   * MsgPack on @region.
   * @param region Region to use.
diff --git a/src/box/sql/update.c b/src/box/sql/update.c
index 076b28a94..852be6fba 100644
--- a/src/box/sql/update.c
+++ b/src/box/sql/update.c
@@ -146,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;
-	struct index *pPk = sql_table_primary_key(pTab);
+	struct index *pPk = space_index(pTab->space, 0);
  	i = sizeof(int) * def->field_count;
  	aXRef = (int *) region_alloc(&pParse->region, i);
  	if (aXRef == NULL) {
diff --git a/src/box/sql/vdbe.c b/src/box/sql/vdbe.c
index b956726e4..21136688c 100644
--- a/src/box/sql/vdbe.c
+++ b/src/box/sql/vdbe.c
@@ -4746,7 +4746,10 @@ case OP_DropTable: {
   * This is called after an index is dropped from Tarantool DD.
   */
  case OP_DropIndex: {
-	sql_space_index_delete(pOp->p4.space, pOp->p1);
+	if (sql_space_index_delete(pOp->p4.space, pOp->p1) != 0) {
+		rc = SQL_TARANTOOL_ERROR;
+		goto abort_due_to_error;
+	}
  	break;
  }
  
diff --git a/src/box/sql/vdbemem.c b/src/box/sql/vdbemem.c
index 3e50f9b89..ef70442b3 100644
--- a/src/box/sql/vdbemem.c
+++ b/src/box/sql/vdbemem.c
@@ -1089,7 +1089,7 @@ valueNew(sqlite3 * db, struct ValueNewStat4Ctx *p)
  			uint32_t part_count = idx->key_def->part_count;
  
  			int nByte = sizeof(Mem) * part_count +
-				ROUND8(sizeof(UnpackedRecord));
+				    ROUND8(sizeof(UnpackedRecord));
  			pRec = (UnpackedRecord *) sqlite3DbMallocZero(db,
  								      nByte);
  			if (pRec == NULL)
diff --git a/src/box/sql/wherecode.c b/src/box/sql/wherecode.c
index 1aa858ac1..e2aaca3c3 100644
--- a/src/box/sql/wherecode.c
+++ b/src/box/sql/wherecode.c
@@ -1351,7 +1351,7 @@ sqlite3WhereCodeOneLoopStart(WhereInfo * pWInfo,	/* Complete information about t
  		Expr *pAndExpr = 0;	/* An ".. AND (...)" expression */
  		Table *pTab = pTabItem->pTab;
  		struct key_def *pk_key_def =
-			sql_table_primary_key(pTab)->def->key_def;
+			space_index(pTab->space, 0)->def->key_def;
  		uint32_t pk_part_count = pk_key_def->part_count;
  
  		pTerm = pLoop->aLTerm[0];
diff --git a/test/sql-tap/gh2130-index-refer-table.test.lua b/test/sql-tap/gh2130-index-refer-table.test.lua
index 3a6064cb9..b5fc1106d 100755
--- a/test/sql-tap/gh2130-index-refer-table.test.lua
+++ b/test/sql-tap/gh2130-index-refer-table.test.lua
@@ -69,16 +69,4 @@ test:do_execsql_test(
  	-- <index-1.5>
  	})
  
--- This part of test is banned in scope of #2174
--- test:do_execsql_test(
---	"index-1.6",
---	[[
---		REINDEX t1ix1 ON t1;
---	]],
---	{
---	-- <index-1.6>
---
---	-- <index-1.6>
---	})
-
  test:finish_test()
diff --git a/test/sql-tap/index1.test.lua b/test/sql-tap/index1.test.lua
index fdce2683c..90ce4b232 100755
--- a/test/sql-tap/index1.test.lua
+++ b/test/sql-tap/index1.test.lua
@@ -119,67 +119,6 @@ test:do_test(
          -- </index-2.2>
      })
  
--- MUST_WORK_TEST REINDEX and integrity_check
-if (0 > 0)
- then
-    -- Try creating a bunch of indices on the same table
-    --
-    local r = {}
-    for i = 1, 99, 1 do
-        table.insert(r,string.format("index%02d", i))
-    end
-    test:do_test(
-        "index-3.1",
-        function()
-            test:execsql("CREATE TABLE test1(f1 int primary key, f2 int, f3 int, f4 int, f5 int)")
-            for i = 1, 99, 1 do
-                local sql = string.format("CREATE INDEX %s ON test1(f%s)", string.format("index%02d", i), (i%5)+1)
-                test:execsql(sql)
-            end
-            return test:execsql [[SELECT name FROM sqlite_master
-              WHERE type='index' AND tbl_name='test1'
-              ORDER BY name]]
-        end, {
-            -- <index-3.1>
-            r
-            -- </index-3.1>
-        })
-
-    X(104, "X!cmd", [=[["integrity_check","index-3.2.1"]]=])
-    test:do_execsql_test(
-        "index-3.2.2",
-        [[
-            REINDEX
-        ]], {
-            -- <index-3.2.2>
-
-            -- </index-3.2.2>
-        })
-
-
-
-    --X(110, "X!cmd", [=[["integrity_check","index-3.2.3"]]=])
-    -- Verify that all the indices go away when we drop the table.
-    --
-    test:do_test(
-        "index-3.3",
-        function()
-            test:execsql "DROP TABLE test1"
-            return test:execsql [[SELECT name FROM sqlite_master
-              WHERE type='index' AND tbl_name='test1'
-              ORDER BY name]]
-        end, {
-            -- <index-3.3>
-
-            -- </index-3.3>
-        })
-
-    -- Create a table and insert values into that table. Then create
-    -- an index on that table. Verify that we can select values
-    -- from the table correctly using the index
-    -- Note that the index names index9 and indext are chosen because
-    -- they both have the same hash.
-end
  test:do_test(
      "index-4.1",
      function()
@@ -1017,7 +956,7 @@ test:do_execsql_test(
          SELECT "_index"."name" FROM "_index" JOIN "_space" WHERE "_index"."id" = "_space"."id" AND "_space"."name"='T7';
      ]], {
          -- <index-17.1>
-        "pk_unnamed_T7_3", "unique_unnamed_T7_2", "unique_unnamed_T7_1"
+        "pk_unnamed_T7_3", "unique_unnamed_T7_1", "unique_unnamed_T7_2"
          -- </index-17.1>
      })
  
diff --git a/test/sql-tap/index6.test.lua b/test/sql-tap/index6.test.lua
index 069623f66..af18e89d4 100755
--- a/test/sql-tap/index6.test.lua
+++ b/test/sql-tap/index6.test.lua
@@ -106,13 +106,6 @@ test:plan(14)
  --     SELECT idx, stat FROM sqlite_stat1 ORDER BY idx;
  --   }
  -- } {{} 15 t1a {10 1} t1b {8 1} ok}
--- do_test index6-1.14 {
---   execsql {
---     REINDEX;
---     ANALYZE;
---     SELECT idx, stat FROM sqlite_stat1 ORDER BY idx;
---   }
--- } {{} 15 t1a {10 1} t1b {8 1} ok}
  -- do_test index6-1.15 {
  --   execsql {
  --     CREATE INDEX t1c ON t1(c);
diff --git a/test/sql-tap/index7.test.lua b/test/sql-tap/index7.test.lua
index 4f9070813..7d4a54723 100755
--- a/test/sql-tap/index7.test.lua
+++ b/test/sql-tap/index7.test.lua
@@ -126,14 +126,6 @@ end
  --     PRAGMA integrity_check;
  --   }
  -- } {t1 {15 1} t1a {10 1} t1b {8 1} ok}
--- do_test index7-1.14 {
---   execsql {
---     REINDEX;
---     ANALYZE;
---     SELECT idx, stat FROM sqlite_stat1 ORDER BY idx;
---     PRAGMA integrity_check;
---   }
--- } {t1 {15 1} t1a {10 1} t1b {8 1} ok}
  -- do_test index7-1.15 {
  --   execsql {
  --     CREATE INDEX t1c ON t1(c);
diff --git a/test/sql-tap/insert1.test.lua b/test/sql-tap/insert1.test.lua
index 750732d37..cfca0025f 100755
--- a/test/sql-tap/insert1.test.lua
+++ b/test/sql-tap/insert1.test.lua
@@ -209,11 +209,6 @@ end, {
  -- do_test insert-3.4 {
  --   execsql {SELECT * FROM test2 WHERE f1=22 AND f2=-4.44}
  -- } {22 -4.44 hi abc-123 wham}
--- ifcapable {reindex} {
---   do_test insert-3.5 {
---     execsql REINDEX
---   } {}
--- }
  -- integrity_check insert-3.5
  -- Test of expressions in the VALUES clause
  --
@@ -395,11 +390,6 @@ test:do_execsql_test("insert-4.7", [[
    --     SELECT * FROM t1 WHERE b=3;
    --   }
    -- } {}
-  -- ifcapable {reindex} {
-  --   do_test insert-6.5 {
-  --     execsql REINDEX
-  --   } {}
-  -- }
    -- do_test insert-6.6 {
    --   execsql {
    --     DROP TABLE t1;
diff --git a/test/sql-tap/misc3.test.lua b/test/sql-tap/misc3.test.lua
index 92f8210c9..dc1545f8f 100755
--- a/test/sql-tap/misc3.test.lua
+++ b/test/sql-tap/misc3.test.lua
@@ -443,46 +443,6 @@ test:do_test(
          -- </misc3-6.3>
      })
  
--- Do some additional EXPLAIN operations to exercise the displayP4 logic.
--- This part of test is disabled in scope of #2174
--- test:do_test(
---    "misc3-6.10",
---    function()
---        local x = test:execsql([[
---            CREATE TABLE ex1(
---              id PRIMARY KEY,
---              a INTEGER DEFAULT 54321,
---              b TEXT DEFAULT "hello",
---              c REAL DEFAULT 3.1415926
---            );
---            CREATE UNIQUE INDEX ex1i1 ON ex1(a);
---            EXPLAIN REINDEX;
---        ]])
---        x = json.encode(x)
---        return string.find(x, "\"SorterCompare\",%d+,%d+,%d+") > 0
---    end, true)
---
--- test:do_test(
---     "misc3-6.11-utf8",
---     function()
---         local x = test:execsql([[
---             EXPLAIN SELECT a+123456789012, b*4.5678, c FROM ex1 ORDER BY +a, b DESC
---         ]])
---         x = json.encode(x)
---         local y = {}
---         table.insert(y, string.find(x, "123456789012")>0)
---         table.insert(y, string.find(x, "4.5678")>0)
---         table.insert(y, string.find(x, "hello")>0)
---         table.insert(y, string.find(x, "-B")>0)
---         return y
---     end, {
---         -- <misc3-6.11-utf8>
---         1, 1, 1, 1
---         -- </misc3-6.11-utf8>
---     })
-
-
-
  -- MUST_WORK_TEST autoincrement for pk
  if (0 > 0) then
      -- Ticket #640:  vdbe stack overflow with a LIMIT clause on a SELECT inside
diff --git a/test/sql-tap/reindex.test.lua b/test/sql-tap/reindex.test.lua
deleted file mode 100755
index d4f8c8188..000000000
--- a/test/sql-tap/reindex.test.lua
+++ /dev/null
@@ -1,36 +0,0 @@
-#!/usr/bin/env tarantool
-test = require("sqltester")
-test:plan(3)
-
-
-test:execsql("CREATE TABLE t1(a INT PRIMARY KEY);")
-test:execsql("CREATE INDEX t1ix1 ON t1(a)")
-
-test:do_execsql_test(
-	"reindex-1.1",
-	"REINDEX t1ix1 ON t1",
-	{
-	-- <reindex-1.1>
-	
-	-- <reindex-1.1>
-	})
-
-test:do_catchsql_test(
-	"reindex-1.2",
-	"REINDEX t1ix2 ON t1",
-	{
-		-- <reindex-1.1>
-		1, "unable to identify the object to be reindexed"
-		-- <reindex-1.1>
-	})
-
-test:do_catchsql_test(
-	"reindex-1.3",
-	"REINDEX t1ix1 ON t3",
-	{
-		-- <reindex-1.1>
-		1, "no such table: T3"
-		-- <reindex-1.1>
-	})
-
-test:finish_test()
diff --git a/test/sql-tap/suite.ini b/test/sql-tap/suite.ini
index 0637cffc1..2455f5e8d 100644
--- a/test/sql-tap/suite.ini
+++ b/test/sql-tap/suite.ini
@@ -1,8 +1,6 @@
  [default]
  core = app
  description = Database tests with #! using TAP
-disabled =
-	reindex.test.lua ; This test is banned in scope of #2174
  lua_libs = lua/sqltester.lua ../sql/lua/sql_tokenizer.lua ../box/lua/identifier.lua
  is_parallel = True
  release_disabled = debug_mode_only.test.lua

^ permalink raw reply	[flat|nested] 28+ messages in thread

* [tarantool-patches] Re: [PATCH 6/7] sql: don't add system spaces to Table hash
  2018-09-06 19:54         ` Vladislav Shpilevoy
@ 2018-09-16 19:04           ` n.pettik
  0 siblings, 0 replies; 28+ messages in thread
From: n.pettik @ 2018-09-16 19:04 UTC (permalink / raw)
  To: tarantool-patches; +Cc: Vladislav Shpilevoy


> Tests fail on this commit. Probably, you
> should squash it with the next one.

Indeed. I’ve squashed them.

^ permalink raw reply	[flat|nested] 28+ messages in thread

* [tarantool-patches] Re: [PATCH 2/7] sql: remove SQLite original struct Index
  2018-09-06 19:54         ` Vladislav Shpilevoy
@ 2018-09-16 19:04           ` n.pettik
  0 siblings, 0 replies; 28+ messages in thread
From: n.pettik @ 2018-09-16 19:04 UTC (permalink / raw)
  To: tarantool-patches; +Cc: Vladislav Shpilevoy


>>> 2. You have mentioned that space indexes are stored as an
>>> array, but it is not the only true. The indexes are stored in
>>> two ways: as an array and a map. A map allows to find an index
>>> by id in O(1) time. Look at space_index function.
>> It is cool and I know about it, but the most common use case is simple
>> iteration over all indexes like:
>> for (i = 0; i < space->index_count; ++i) {
>> 	struct index *idx = space->index[I];
>> 	...
>> }> 
>>> Here you do not clear space->index_map and do not
>>> reallocate it. Looks like you do not touch index_map
>>> at all in this patch. But maybe you should. See my
>>> other comments here and in next patches.
>> I deliberately didn’t fill in index map. I think maintaining
>> map and array at the same time seems to be over-engineering.
>> These indexes are really used only during table creation
>> (after last patch in series). Moreover, during building routine
>> we may need only PK index, which anyway can be fetched as index[0]
> 
> It is not over-engineering. This is how real spaces work and I
> want these surrogate spaces be as much similar as possible to
> them. So I implemented index_map support in my review fixes.

I talked to you (Kirill Y also agreed): there is no reason to use map -
those spaces are not real and involved only in CREATE TABLE
processing. There is no chance that such ‘ephemeral’ space
would be messed with real one. Hence, map really makes no
sense - it wouldn’t be ever used.

Also, unfortunately after your fixes some artefacts such as
OP_DropIndex, sqlite3LocateTable() etc appeared in the last commit,
so I had to remove them again.
I applied those changes which are not related to map story:
removing the rest if REINDEX routine, using correct index id during
processing index construction, minor fixes in analyze routine.

Changes like:
space->index[i] --> space_index(space, i);
I moved to the last patch.

Finally, I have noticed that box/stat.test.lua fails:

[001] box/stat.test.lua                                               [ fail ]
[001] 
[001] Test failed! Result content mismatch:
[001] --- box/stat.result	Tue Apr 10 14:15:11 2018
[001] +++ box/stat.reject	Sat Sep 15 22:25:18 2018
[001] @@ -24,7 +24,7 @@
[001]  ...
[001]  box.stat.SELECT.total
[001]  ---
[001] -- 1
[001] +- 2
[001]  ...
[001]  box.stat.ERROR.total
[001]  ---
[001] @@ -59,7 +59,7 @@
[001]  ...
[001]  box.stat.SELECT.total
[001]  ---
[001] -- 4
[001] +- 5
[001]  ...
[001]  -- check exceptions
[001]  space:get('Impossible value')
[001] @@ -118,7 +118,7 @@
[001]  ...
[001]  box.stat.SELECT.total
[001]  ---
[001] -- 1
[001] +- 2
[001]  ...
[001]  box.stat.ERROR.total
[001]  ---
[001] 
[001] Last 15 lines of Tarantool Log file [Instance "box"][/Users/n.pettik/tarantool/test/var/001_box/box.log]:

It happens due to additional call of box_space_id_by_name in sql_lookup_table
while initializing DB and loading SQL statistics:

  * frame #0: 0x00000001002f8364 tarantool`rmean_collect(rmean=0x0000000102b13830, name=1, value=1) at rmean.c:62
    frame #1: 0x0000000100013358 tarantool`::box_index_get(space_id=281, index_id=2, key="?
    frame #2: 0x00000001000e3938 tarantool`::box_space_id_by_name(name="_sql_stat1", len=10) at box.cc:951
    frame #3: 0x000000010038e61d tarantool`sql_lookup_table(parse=0x000000010501f278, tbl_name=0x0000000103847a10) at delete.c:43
    frame #4: 0x00000001003c9f73 tarantool`selectExpander(pWalker=0x000000010501e900, p=0x0000000103847608) at select.c:4814
    frame #5: 0x0000000100401085 tarantool`sqlite3WalkSelect(pWalker=0x000000010501e900, p=0x0000000103847608) at walker.c:192
    frame #6: 0x00000001003c0df2 tarantool`sqlite3SelectExpand(pParse=0x000000010501f278, pSelect=0x0000000103847608) at select.c:5095
    frame #7: 0x00000001003c087b tarantool`sqlite3SelectPrep(pParse=0x000000010501f278, p=0x0000000103847608, pOuterNC=0x0000000000000000) at select.c:5185
    frame #8: 0x00000001003c12a8 tarantool`sqlite3Select(pParse=0x000000010501f278, p=0x0000000103847608, pDest=0x000000010501f078) at select.c:5472
…
    frame #16: 0x000000010037978c tarantool`sql_analysis_load(db=0x0000000102b13ea8) at analyze.c:1742
    frame #17: 0x00000001003b6190 tarantool`sqlite3InitDatabase(db=0x0000000102b13ea8) at prepare.c:157
    frame #18: 0x000000010010835e tarantool`lua_sql_execute(L=0x0000000105008090) at sql.c:76

I fixed that test. Diff:

diff --git a/test/box/stat.result b/test/box/stat.result
index af1607db7..60ba88fea 100644
--- a/test/box/stat.result
+++ b/test/box/stat.result
@@ -24,7 +24,7 @@ box.stat.REPLACE.total
 ...
 box.stat.SELECT.total
 ---
-- 1
+- 2
 ...
 box.stat.ERROR.total
 ---
@@ -59,7 +59,7 @@ box.stat.REPLACE.total
 ...
 box.stat.SELECT.total
 ---
-- 4
+- 5
 ...
 -- check exceptions
 space:get('Impossible value')
@@ -118,7 +118,7 @@ box.stat.REPLACE.total
 ...
 box.stat.SELECT.total
 ---
-- 1
+- 2
 ...
 box.stat.ERROR.total

>> -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);
> 
> Leak of idx->def->key_def/cmp_def.

Fixed by using index_def_delete().

>>  -	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.
> 
> The comment is wrong. Here space_id_reg is literal
> and idx_def->iid is a register. And here a problem is
> hidden. idx_def->iid is initialized above as either 0
> for primary indexes or 1 for other. But by a coincidence
> it matched result of getNewIid() so it worked ok. Once
> I added index_map and more strict index identifiers
> calculation, this place fired. It is fixed in my
> review fixes.

Thx for catch, I extract this fix from whole diff and applied it.

Except for changes below, I also found one leak of space def
(this is diff for the last patch):

 bool
@@ -249,28 +159,44 @@ sql_space_column_is_in_pk(struct space *space, uint32_t column)
 static void
 table_delete(struct sqlite3 *db, struct Table *tab)
 {
+       /*
+        * There are three possible cases:
+        * 1. Table comes from building routine (i.e. it
+        *    was born during CREATE TABLE processing).
+        *    In this case only index defs and check expressions
+        *    are allocated using malloc; the rest - on region.
+        *    'is_temporary' flag is set to TRUE.
+        * 2. Table comes from query processing (e.g.
+        *    SELECT, INSERT etc). Hence, table is only
+        *    wrapper around space and its def from real
+        *    space cache. As a result we don't need to free
+        *    anything except for table itself. For such tables
+        *    flag 'is_temporary' set to FALSE and id != 0.
+        * 3. Table is 'ephemeral' and represents metadata for
+        *    flattened subquery or materialized view. It is quite
+        *    similar to tables from p.1, but their space_defs
+        *    are rebuilt (see selectExpander() function) using
+        *    malloc. Such rebuild is required since subquery
+        *    flattening may occur in trigger's body, which in
+        *    turn handled in a separate parsing context.
+        *    At the end of trigger's parsing, those tables may
+        *    not be deleted, but added to the zombie list of
+        *    top-level parsing context. Each parsing context
+        *    features individual region allocator. Hence, when
+        *    top-level parsing context starts to release zombie
+        *    tables, they have already corrupted memory layout.
+        *    Reproducer for this case can be found in
+        *    tkt-7bbfb7d442 test. For such tables flag
+        *    'is_temporary' set to false and id == 0.
+        */
+       if (tab->def->opts.is_temporary) {
+               for (uint32_t i = 0; i < tab->space->index_count; ++i)
+                       index_def_delete(tab->space->index[i]->def);
+               /* Do not delete table->def allocated on region. */
                sql_expr_list_delete(db, tab->def->opts.checks);
+       } else if (tab->def->id == 0) {
+               space_def_delete(tab->def);
+       }
        sqlite3DbFree(db, tab);
 }

=======================================================================

My diff for this patch:

diff --git a/extra/mkkeywordhash.c b/extra/mkkeywordhash.c
index e856d64c7..2c54c1835 100644
--- a/extra/mkkeywordhash.c
+++ b/extra/mkkeywordhash.c
@@ -82,11 +82,6 @@ struct Keyword {
#else
#  define PRAGMA     0x00000400
#endif
-#ifdef SQLITE_OMIT_REINDEX
-#  define REINDEX    0
-#else
-#  define REINDEX    0x00000800
-#endif
#define SUBQUERY     0x00001000
#  define TRIGGER    0x00002000
#  define VIEW       0x00008000

diff --git a/src/box/sql/analyze.c b/src/box/sql/analyze.c
index 80293c6ed..1c9a672d4 100644
--- a/src/box/sql/analyze.c
+++ b/src/box/sql/analyze.c
@@ -802,7 +802,14 @@ analyzeOneTable(Parse * pParse,	/* Parser context */
		return;
	}
	assert(pTab->def->id != 0);
-	if (sqlite3_strlike("\\_%", pTab->def->name, '\\') == 0) {
+	/*
+	 * 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);
+	const char *tab_name = space_name(space);
+	if (sqlite3_strlike("\\_%", tab_name, '\\') == 0) {
		/* Do not gather statistics on system tables */
		return;
	}
@@ -816,15 +823,9 @@ analyzeOneTable(Parse * pParse,	/* Parser context */
	iIdxCur = iTab++;
	pParse->nTab = MAX(pParse->nTab, iTab);
	sqlite3OpenTable(pParse, iTabCur, pTab, OP_OpenRead);
-	sqlite3VdbeLoadString(v, regTabname, pTab->def->name);
-	/*
-	 * 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);
+	sqlite3VdbeLoadString(v, regTabname, tab_name);
	for (uint32_t j = 0; j < space->index_count; ++j) {
-		struct index *idx = pTab->space->index[j];
+		struct index *idx = space->index[j];
		int addrRewind;	/* Address of "OP_Rewind iIdxCur" */
		int addrNextRow;	/* Address of "next_row:" */
		const char *idx_name;	/* Name of the index */
@@ -834,15 +835,14 @@ analyzeOneTable(Parse * pParse,	/* Parser context */
		 * instead more familiar table name.
		 */
		if (idx->def->iid == 0)
-			idx_name = pTab->def->name;
+			idx_name = tab_name;
		else
			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);
-		VdbeComment((v, "Analysis for %s.%s", pTab->def->name,
-			    idx_name));
+		VdbeComment((v, "Analysis for %s.%s", tab_name, idx_name));
 		/*
		 * Pseudo-code for loop that calls stat_push():
@@ -986,7 +986,7 @@ analyzeOneTable(Parse * pParse,	/* Parser context */
		 *   if !eof(csr) goto next_row;
		 */
		assert(regKey == (regStat4 + 2));
-		struct index *pk = sql_table_primary_key(pTab);
+		struct index *pk = space_index(space, 0);
		int pk_part_count = pk->def->key_def->part_count;
		/* Allocate memory for array. */
		pParse->nMem = MAX(pParse->nMem,
@@ -994,10 +994,10 @@ analyzeOneTable(Parse * pParse,	/* Parser context */
		int regKeyStat = regPrev + part_count;
		for (i = 0; i < pk_part_count; i++) {
			uint32_t k = pk->def->key_def->parts[i].fieldno;
-			assert(k < pTab->def->field_count);
+			assert(k < space->def->field_count);
			sqlite3VdbeAddOp3(v, OP_Column, iIdxCur, k,
					  regKeyStat + i);
-			VdbeComment((v, "%s", pTab->def->fields[k].name));
+			VdbeComment((v, "%s", space->def->fields[k].name));
		}
		sqlite3VdbeAddOp3(v, OP_MakeRecord, regKeyStat,
				  pk_part_count, regKey);
diff --git a/src/box/sql/build.c b/src/box/sql/build.c
index 617d0ea47..cc6874f2e 100644
--- a/src/box/sql/build.c
+++ b/src/box/sql/build.c
@@ -163,34 +163,39 @@ void
 sql_space_index_delete(struct space *space, uint32_t iid)
 {
        assert(space != NULL);
+       struct index *to_delete = NULL;
+       uint32_t new_index_id_max = 0;
        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) {
-                       index_def_delete(idx->def);
-                       free(idx);
-                       size_t idx_sz = sizeof(struct index *);
-                       uint32_t idx_count = space->index_count - 1;
-                       struct index **new_indexes =
-                               (struct index **) malloc(idx_sz * idx_count);
-                       if (new_indexes == NULL) {
-                               diag_set(OutOfMemory, idx_sz * idx_count,
-                                        "malloc", "new_indexes");
-                               return;
-                       }
-                       memcpy(new_indexes, space->index, i * idx_sz);
-                       memcpy(new_indexes + i, space->index + i + 1,
-                              idx_sz * (idx_count - i));
-                       free(space->index);
-                       space->index = new_indexes;
-                       space->index_count--;
-                       break;
-               }
+               if (space->index[i]->def->iid == iid)
+                       to_delete = space->index[i];
+               else if (new_index_id_max < space->index[i]->def->iid)
+                       new_index_id_max = space->index[i]->def->iid;
+       }
+       /*
+        * Allocate new chunk with size reduced by 1 slot.
+        * Copy all indexes to that chunk except for one
+        * to be deleted.
+        */
+       assert(to_delete != NULL);
+       uint32_t new_index_count = space->index_count - 1;
+       size_t size = sizeof(struct index *) * new_index_count;
+       struct index **new_indexes = (struct index **) malloc(size);
+       if (new_indexes == NULL) {
+               diag_set(OutOfMemory, size, "malloc", "new_indexes");
+               return;
        }
+       for (uint32_t i = 0, j = 0; i < space->index_count; ++i) {
+               struct index *idx = space->index[i];
+               if (idx == to_delete)
+                       continue;
+               new_indexes[j++] = idx;
+       }
+       index_def_delete(to_delete->def);
+       free(to_delete);
+       free(space->index);
+       space->index = new_indexes;
+       space->index_count--;
+       space->index_id_max = new_index_id_max;
        struct session *user_session = current_session();
        user_session->sql_flags |= SQLITE_InternChanges;
 }
@@ -272,7 +277,7 @@ table_delete(struct sqlite3 *db, struct Table *tab)
                 * index_delete().
                 */
                struct index *idx = tab->space->index[i];
-               free(idx->def);
+               index_def_delete(idx->def);
                free(idx);
        }
        free(tab->space->index);
@@ -1146,11 +1151,13 @@ getNewSpaceId(Parse * pParse)
  * @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.
+ * @param index_id_reg Register containing generated index id.
  */
 static void
 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)
+                      const struct index_def *pk_def, int space_id_reg,
+                      int index_id_reg)
 {
        struct Vdbe *v = sqlite3GetVdbe(parse);
        int entry_reg = ++parse->nMem;
@@ -1187,10 +1194,11 @@ vdbe_emit_create_index(struct Parse *parse, struct space_def *def,
        } else {
                /*
                 * An existing table is being modified;
-                * space_id_reg is register, but iid is literal.
+                * space_id_reg is literal, but index_id_reg is
+                * register.
                 */
                sqlite3VdbeAddOp2(v, OP_Integer, space_id_reg, entry_reg);
-               sqlite3VdbeAddOp2(v, OP_SCopy, idx_def->iid, entry_reg + 1);
+               sqlite3VdbeAddOp2(v, OP_SCopy, index_id_reg, entry_reg + 1);
        }
        sqlite3VdbeAddOp4(v, OP_String8, 0, entry_reg + 2, 0,
                          sqlite3DbStrDup(parse->db, idx_def->name),
@@ -1689,9 +1697,9 @@ sqlite3EndTable(Parse * pParse,   /* Parse context */
                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);
+                                              pk->def, reg_space_id,
+                                              idx->def->iid);
                }
        }
 
@@ -2486,24 +2494,22 @@ 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)
+table_add_index(struct space *space, struct index *index)
 {
-       uint32_t idx_count = tab->space->index_count;
+       uint32_t idx_count = space->index_count;
        size_t indexes_sz = sizeof(struct index *) * (idx_count + 1);
-       struct index **idx = (struct index **) realloc(tab->space->index,
+       struct index **idx = (struct index **) realloc(space->index,
                                                       indexes_sz);
        if (idx == NULL) {
                diag_set(OutOfMemory, indexes_sz, "realloc", "idx");
                return;
        }
-       tab->space->index = idx;
+       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;
+       if (index->def->iid == 0 && idx_count != 0)
+               SWAP(space->index[0], index);
+       space->index[space->index_count++] = index;
+       space->index_id_max =  MAX(space->index_id_max, index->def->iid);;
 }
 
 /**
@@ -2546,7 +2552,8 @@ index_fill_def(struct Parse *parse, struct index *index,
 
        for (int i = 0; i < expr_list->nExpr; i++) {
                struct Expr *expr = expr_list->a[i].pExpr;
-               sql_resolve_self_reference(parse, table, NC_IdxExpr, expr, 0);
+               sql_resolve_self_reference(parse, table, NC_IdxExpr,
+                                          expr, 0);
                if (parse->nErr > 0)
                        goto cleanup;
 
@@ -2793,12 +2800,11 @@ sql_create_index(struct Parse *parse, struct Token *token,
                if (sql_stmt == NULL)
                        goto exit_create_index;
        }
-       /*
-        * If it is parsing stage, then iid may have any value
-        * (for the simplicity sake we set it to 1), but PK
-        * still must have iid == 0.
-        */
-       uint32_t iid = idx_type != SQL_INDEX_TYPE_CONSTRAINT_PK;
+       uint32_t iid;
+       if (idx_type != SQL_INDEX_TYPE_CONSTRAINT_PK)
+               iid = table->space->index_id_max + 1;
+       else
+               iid = 0;
        if (index_fill_def(parse, index, table, iid, name, strlen(name),
                           col_list, idx_type, sql_stmt) != 0)
                goto exit_create_index;
@@ -2858,6 +2864,7 @@ sql_create_index(struct Parse *parse, struct Token *token,
        if (table == parse->pNewTable) {
                for (uint32_t i = 0; i < table->space->index_count; ++i) {
                        struct index *existing_idx = table->space->index[i];
+                       uint32_t iid = existing_idx->def->iid;
                        struct key_def *key_def = index->def->key_def;
                        struct key_def *exst_key_def =
                                existing_idx->def->key_def;
@@ -2882,7 +2889,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 &&
-                           existing_idx->def->iid != 0 && !is_named) {
+                           iid != 0 && !is_named) {
                                existing_idx->def->iid = 0;
                                goto exit_create_index;
                        }
@@ -2940,7 +2947,7 @@ sql_create_index(struct Parse *parse, struct Token *token,
                sqlite3VdbeAddOp1(vdbe, OP_Close, cursor);
                struct index *pk = sql_table_primary_key(table);
                vdbe_emit_create_index(parse, table->def, index->def, pk->def,
-                                      space_id);
+                                      space_id, index_id);
                /* Consumes sql_stmt. */
                first_schema_col =
                        vdbe_emit_index_schema_record(parse, index->def->name,
@@ -2957,7 +2964,7 @@ sql_create_index(struct Parse *parse, struct Token *token,
 
        if (!db->init.busy && tbl_name != NULL)
                goto exit_create_index;
-       table_add_index(table, index);
+       table_add_index(table->space, index);
        index = NULL;
 
        /* Clean up before exiting. */
diff --git a/src/box/sql/tarantoolInt.h b/src/box/sql/tarantoolInt.h
index f3e65303b..d2475bbc8 100644
--- a/src/box/sql/tarantoolInt.h
+++ b/src/box/sql/tarantoolInt.h
@@ -207,7 +207,6 @@ fkey_encode_links(struct region *region, const struct fkey_def *def, int type,
      uint32_t *size);
 /**
-<<<<<<< HEAD
 * Encode index parts of given foreign key constraint into
 * MsgPack on @region.
 * @param region Region to use.

diff --git a/src/box/sql/vdbemem.c b/src/box/sql/vdbemem.c
index 3e50f9b89..ef70442b3 100644
--- a/src/box/sql/vdbemem.c
+++ b/src/box/sql/vdbemem.c
@@ -1089,7 +1089,7 @@ valueNew(sqlite3 * db, struct ValueNewStat4Ctx *p)
			uint32_t part_count = idx->key_def->part_count;
 			int nByte = sizeof(Mem) * part_count +
-				ROUND8(sizeof(UnpackedRecord));
+				    ROUND8(sizeof(UnpackedRecord));
			pRec = (UnpackedRecord *) sqlite3DbMallocZero(db,
								      nByte);
			if (pRec == NULL)
diff --git a/test/sql-tap/gh2130-index-refer-table.test.lua b/test/sql-tap/gh2130-index-refer-table.test.lua
index 3a6064cb9..b5fc1106d 100755
--- a/test/sql-tap/gh2130-index-refer-table.test.lua
+++ b/test/sql-tap/gh2130-index-refer-table.test.lua
@@ -69,16 +69,4 @@ test:do_execsql_test(
	-- <index-1.5>
	})
--- This part of test is banned in scope of #2174
--- test:do_execsql_test(
---	"index-1.6",
---	[[
---		REINDEX t1ix1 ON t1;
---	]],
---	{
---	-- <index-1.6>
---
---	-- <index-1.6>
---	})
-
test:finish_test()
diff --git a/test/sql-tap/index1.test.lua b/test/sql-tap/index1.test.lua
index fdce2683c..90ce4b232 100755
--- a/test/sql-tap/index1.test.lua
+++ b/test/sql-tap/index1.test.lua
@@ -119,67 +119,6 @@ test:do_test(
        -- </index-2.2>
    })
--- MUST_WORK_TEST REINDEX and integrity_check
-if (0 > 0)
- then
-    -- Try creating a bunch of indices on the same table
-    --
-    local r = {}
-    for i = 1, 99, 1 do
-        table.insert(r,string.format("index%02d", i))
-    end
-    test:do_test(
-        "index-3.1",
-        function()
-            test:execsql("CREATE TABLE test1(f1 int primary key, f2 int, f3 int, f4 int, f5 int)")
-            for i = 1, 99, 1 do
-                local sql = string.format("CREATE INDEX %s ON test1(f%s)", string.format("index%02d", i), (i%5)+1)
-                test:execsql(sql)
-            end
-            return test:execsql [[SELECT name FROM sqlite_master
-              WHERE type='index' AND tbl_name='test1'
-              ORDER BY name]]
-        end, {
-            -- <index-3.1>
-            r
-            -- </index-3.1>
-        })
-
-    X(104, "X!cmd", [=[["integrity_check","index-3.2.1"]]=])
-    test:do_execsql_test(
-        "index-3.2.2",
-        [[
-            REINDEX
-        ]], {
-            -- <index-3.2.2>
-
-            -- </index-3.2.2>
-        })
-
-
-
-    --X(110, "X!cmd", [=[["integrity_check","index-3.2.3"]]=])
-    -- Verify that all the indices go away when we drop the table.
-    --
-    test:do_test(
-        "index-3.3",
-        function()
-            test:execsql "DROP TABLE test1"
-            return test:execsql [[SELECT name FROM sqlite_master
-              WHERE type='index' AND tbl_name='test1'
-              ORDER BY name]]
-        end, {
-            -- <index-3.3>
-
-            -- </index-3.3>
-        })
-
-    -- Create a table and insert values into that table. Then create
-    -- an index on that table. Verify that we can select values
-    -- from the table correctly using the index
-    -- Note that the index names index9 and indext are chosen because
-    -- they both have the same hash.
-end
test:do_test(
    "index-4.1",
    function()
@@ -1017,7 +956,7 @@ test:do_execsql_test(
        SELECT "_index"."name" FROM "_index" JOIN "_space" WHERE "_index"."id" = "_space"."id" AND "_space"."name"='T7';
    ]], {
        -- <index-17.1>
-        "pk_unnamed_T7_3", "unique_unnamed_T7_2", "unique_unnamed_T7_1"
+        "pk_unnamed_T7_3", "unique_unnamed_T7_1", "unique_unnamed_T7_2"
        -- </index-17.1>
    })
diff --git a/test/sql-tap/index6.test.lua b/test/sql-tap/index6.test.lua
index 069623f66..af18e89d4 100755
--- a/test/sql-tap/index6.test.lua
+++ b/test/sql-tap/index6.test.lua
@@ -106,13 +106,6 @@ test:plan(14)
--     SELECT idx, stat FROM sqlite_stat1 ORDER BY idx;
--   }
-- } {{} 15 t1a {10 1} t1b {8 1} ok}
--- do_test index6-1.14 {
---   execsql {
---     REINDEX;
---     ANALYZE;
---     SELECT idx, stat FROM sqlite_stat1 ORDER BY idx;
---   }
--- } {{} 15 t1a {10 1} t1b {8 1} ok}
-- do_test index6-1.15 {
--   execsql {
--     CREATE INDEX t1c ON t1(c);
diff --git a/test/sql-tap/index7.test.lua b/test/sql-tap/index7.test.lua
index 4f9070813..7d4a54723 100755
--- a/test/sql-tap/index7.test.lua
+++ b/test/sql-tap/index7.test.lua
@@ -126,14 +126,6 @@ end
--     PRAGMA integrity_check;
--   }
-- } {t1 {15 1} t1a {10 1} t1b {8 1} ok}
--- do_test index7-1.14 {
---   execsql {
---     REINDEX;
---     ANALYZE;
---     SELECT idx, stat FROM sqlite_stat1 ORDER BY idx;
---     PRAGMA integrity_check;
---   }
--- } {t1 {15 1} t1a {10 1} t1b {8 1} ok}
-- do_test index7-1.15 {
--   execsql {
--     CREATE INDEX t1c ON t1(c);
diff --git a/test/sql-tap/insert1.test.lua b/test/sql-tap/insert1.test.lua
index 750732d37..cfca0025f 100755
--- a/test/sql-tap/insert1.test.lua
+++ b/test/sql-tap/insert1.test.lua
@@ -209,11 +209,6 @@ end, {
-- do_test insert-3.4 {
--   execsql {SELECT * FROM test2 WHERE f1=22 AND f2=-4.44}
-- } {22 -4.44 hi abc-123 wham}
--- ifcapable {reindex} {
---   do_test insert-3.5 {
---     execsql REINDEX
---   } {}
--- }
-- integrity_check insert-3.5
-- Test of expressions in the VALUES clause
--
@@ -395,11 +390,6 @@ test:do_execsql_test("insert-4.7", [[
  --     SELECT * FROM t1 WHERE b=3;
  --   }
  -- } {}
-  -- ifcapable {reindex} {
-  --   do_test insert-6.5 {
-  --     execsql REINDEX
-  --   } {}
-  -- }
  -- do_test insert-6.6 {
  --   execsql {
  --     DROP TABLE t1;
diff --git a/test/sql-tap/misc3.test.lua b/test/sql-tap/misc3.test.lua
index 92f8210c9..dc1545f8f 100755
--- a/test/sql-tap/misc3.test.lua
+++ b/test/sql-tap/misc3.test.lua
@@ -443,46 +443,6 @@ test:do_test(
        -- </misc3-6.3>
    })
--- Do some additional EXPLAIN operations to exercise the displayP4 logic.
--- This part of test is disabled in scope of #2174
--- test:do_test(
---    "misc3-6.10",
---    function()
---        local x = test:execsql([[
---            CREATE TABLE ex1(
---              id PRIMARY KEY,
---              a INTEGER DEFAULT 54321,
---              b TEXT DEFAULT "hello",
---              c REAL DEFAULT 3.1415926
---            );
---            CREATE UNIQUE INDEX ex1i1 ON ex1(a);
---            EXPLAIN REINDEX;
---        ]])
---        x = json.encode(x)
---        return string.find(x, "\"SorterCompare\",%d+,%d+,%d+") > 0
---    end, true)
---
--- test:do_test(
---     "misc3-6.11-utf8",
---     function()
---         local x = test:execsql([[
---             EXPLAIN SELECT a+123456789012, b*4.5678, c FROM ex1 ORDER BY +a, b DESC
---         ]])
---         x = json.encode(x)
---         local y = {}
---         table.insert(y, string.find(x, "123456789012")>0)
---         table.insert(y, string.find(x, "4.5678")>0)
---         table.insert(y, string.find(x, "hello")>0)
---         table.insert(y, string.find(x, "-B")>0)
---         return y
---     end, {
---         -- <misc3-6.11-utf8>
---         1, 1, 1, 1
---         -- </misc3-6.11-utf8>
---     })
-
-
-
-- MUST_WORK_TEST autoincrement for pk
if (0 > 0) then
    -- Ticket #640:  vdbe stack overflow with a LIMIT clause on a SELECT inside
diff --git a/test/sql-tap/reindex.test.lua b/test/sql-tap/reindex.test.lua
deleted file mode 100755
index d4f8c8188..000000000
--- a/test/sql-tap/reindex.test.lua
+++ /dev/null
@@ -1,36 +0,0 @@
-#!/usr/bin/env tarantool
-test = require("sqltester")
-test:plan(3)
-
-
-test:execsql("CREATE TABLE t1(a INT PRIMARY KEY);")
-test:execsql("CREATE INDEX t1ix1 ON t1(a)")
-
-test:do_execsql_test(
-	"reindex-1.1",
-	"REINDEX t1ix1 ON t1",
-	{
-	-- <reindex-1.1>
-	
-	-- <reindex-1.1>
-	})
-
-test:do_catchsql_test(
-	"reindex-1.2",
-	"REINDEX t1ix2 ON t1",
-	{
-		-- <reindex-1.1>
-		1, "unable to identify the object to be reindexed"
-		-- <reindex-1.1>
-	})
-
-test:do_catchsql_test(
-	"reindex-1.3",
-	"REINDEX t1ix1 ON t3",
-	{
-		-- <reindex-1.1>
-		1, "no such table: T3"
-		-- <reindex-1.1>
-	})
-
-test:finish_test()
diff --git a/test/sql-tap/suite.ini b/test/sql-tap/suite.ini
index 0637cffc1..2455f5e8d 100644
--- a/test/sql-tap/suite.ini
+++ b/test/sql-tap/suite.ini
@@ -1,8 +1,6 @@
[default]
core = app
description = Database tests with #! using TAP
-disabled =
-	reindex.test.lua ; This test is banned in scope of #2174
lua_libs = lua/sqltester.lua ../sql/lua/sql_tokenizer.lua ../box/lua/identifier.lua
is_parallel = True
release_disabled = debug_mode_only.test.lua

Whole patch is below:

=======================================================================

commit 5f5fd72ecee1d390c27d4788e194754bc5327f88
Author: Nikita Pettik <korablev@tarantool.org>
Date:   Fri Aug 17 21:09:22 2018 +0300

    sql: remove SQLite original struct Index
    
    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.
    
    Moreover, alongside with substituting index struct, <REINDEX> statement
    and routine connected with it has been completely removed.
    
    Part of #3561

diff --git a/extra/mkkeywordhash.c b/extra/mkkeywordhash.c
index 388a03cca..2c54c1835 100644
--- a/extra/mkkeywordhash.c
+++ b/extra/mkkeywordhash.c
@@ -82,11 +82,6 @@ struct Keyword {
 #else
 #  define PRAGMA     0x00000400
 #endif
-#ifdef SQLITE_OMIT_REINDEX
-#  define REINDEX    0
-#else
-#  define REINDEX    0x00000800
-#endif
 #define SUBQUERY     0x00001000
 #  define TRIGGER    0x00002000
 #  define VIEW       0x00008000
@@ -193,7 +188,6 @@ static Keyword aKeywordTable[] = {
   { "RECURSIVE",              "TK_RECURSIVE",   CTE,              true  },
   { "REFERENCES",             "TK_REFERENCES",  FKEY,             true  },
   { "REGEXP",                 "TK_LIKE_KW",     ALWAYS,           false },
-  { "REINDEX",                "TK_REINDEX",     REINDEX,          true  },
   { "RELEASE",                "TK_RELEASE",     ALWAYS,           true  },
   { "RENAME",                 "TK_RENAME",      ALTER,            true  },
   { "REPLACE",                "TK_REPLACE",     CONFLICT,         true  },
diff --git a/src/box/key_def.c b/src/box/key_def.c
index 5546126db..e3d8382e0 100644
--- a/src/box/key_def.c
+++ b/src/box/key_def.c
@@ -250,6 +250,8 @@ key_part_cmp(const struct key_part *parts1, uint32_t part_count1,
 		if (part1->coll != part2->coll)
 			return (uintptr_t) part1->coll <
 			       (uintptr_t) part2->coll ? -1 : 1;
+		if (part1->sort_order != part2->sort_order)
+			return part1->sort_order < part2->sort_order ? -1 : 1;
 		if (key_part_is_nullable(part1) != key_part_is_nullable(part2))
 			return key_part_is_nullable(part1) <
 			       key_part_is_nullable(part2) ? -1 : 1;
diff --git a/src/box/sql.c b/src/box/sql.c
index b158c5056..0c1df9b75 100644
--- a/src/box/sql.c
+++ b/src/box/sql.c
@@ -31,15 +31,9 @@
 #include <assert.h>
 #include "field_def.h"
 #include "sql.h"
-/*
- * 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"
@@ -848,7 +842,8 @@ set_encode_error(void *error_ctx)
  * @param opts Index options to encode.
  */
 static void
-mpstream_encode_index_opts(struct mpstream *stream, struct index_opts *opts)
+mpstream_encode_index_opts(struct mpstream *stream,
+			   const struct index_opts *opts)
 {
 	mpstream_encode_map(stream, 2);
 	mpstream_encode_str(stream, "unique");
@@ -1302,7 +1297,7 @@ sql_encode_table(struct region *region, struct Table *table, uint32_t *size)
 	 * If table's PK is single column which is INTEGER, then
 	 * treat it as strict type, not affinity.
 	 */
-	struct SqliteIndex *pk_idx = sqlite3PrimaryKeyIndex(table);
+	struct index *pk_idx = sql_table_primary_key(table);
 	uint32_t pk_forced_int = UINT32_MAX;
 	if (pk_idx != NULL && pk_idx->def->key_def->part_count == 1) {
 		int pk = pk_idx->def->key_def->parts[0].fieldno;
@@ -1453,8 +1448,9 @@ fkey_encode_links(struct region *region, const struct fkey_def *def, int type,
 }
 
 char *
-sql_encode_index_parts(struct region *region, struct SqliteIndex *index,
-		       uint32_t *size)
+sql_encode_index_parts(struct region *region, const struct field_def *fields,
+		       const struct index_def *idx_def,
+		       const struct index_def *pk_def, uint32_t *size)
 {
 	size_t used = region_used(region);
 	struct mpstream stream;
@@ -1466,12 +1462,10 @@ sql_encode_index_parts(struct region *region, struct SqliteIndex *index,
 	 * treat it as strict type, not affinity.
 	 */
 	uint32_t pk_forced_int = UINT32_MAX;
-	struct SqliteIndex *pk = sqlite3PrimaryKeyIndex(index->pTable);
-	struct field_def *fields = index->pTable->def->fields;
-	if (pk->def->key_def->part_count == 1) {
-		int fieldno = pk->def->key_def->parts[0].fieldno;
-		if (fields[fieldno].type == FIELD_TYPE_INTEGER)
-			pk_forced_int = 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;
 	}
 
 	/* gh-2187
@@ -1480,7 +1474,7 @@ sql_encode_index_parts(struct region *region, struct SqliteIndex *index,
 	 * primary key columns. Query planner depends on this particular
 	 * data layout.
 	 */
-	struct key_def *key_def = index->def->key_def;
+	struct key_def *key_def = idx_def->key_def;
 	struct key_part *part = key_def->parts;
 	mpstream_encode_array(&stream, key_def->part_count);
 	for (uint32_t i = 0; i < key_def->part_count; ++i, ++part) {
@@ -1532,7 +1526,7 @@ sql_encode_index_parts(struct region *region, struct SqliteIndex *index,
 }
 
 char *
-sql_encode_index_opts(struct region *region, struct index_opts *opts,
+sql_encode_index_opts(struct region *region, const struct index_opts *opts,
 		      uint32_t *size)
 {
 	size_t used = region_used(region);
@@ -1669,6 +1663,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 76ae15386..1c9a672d4 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 */
@@ -804,7 +802,14 @@ analyzeOneTable(Parse * pParse,	/* Parser context */
 		return;
 	}
 	assert(pTab->def->id != 0);
-	if (sqlite3_strlike("\\_%", pTab->def->name, '\\') == 0) {
+	/*
+	 * 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);
+	const char *tab_name = space_name(space);
+	if (sqlite3_strlike("\\_%", tab_name, '\\') == 0) {
 		/* Do not gather statistics on system tables */
 		return;
 	}
@@ -818,29 +823,26 @@ analyzeOneTable(Parse * pParse,	/* Parser context */
 	iIdxCur = iTab++;
 	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) {
+	sqlite3VdbeLoadString(v, regTabname, tab_name);
+	for (uint32_t j = 0; j < space->index_count; ++j) {
+		struct index *idx = 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))
-			idx_name = pTab->def->name;
+		if (idx->def->iid == 0)
+			idx_name = tab_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);
-		VdbeComment((v, "Analysis for %s.%s", pTab->def->name,
-			    idx_name));
+		VdbeComment((v, "Analysis for %s.%s", tab_name, idx_name));
 
 		/*
 		 * Pseudo-code for loop that calls stat_push():
@@ -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:
 		 *
@@ -917,70 +916,67 @@ analyzeOneTable(Parse * pParse,	/* Parser context */
 		addrRewind = sqlite3VdbeAddOp1(v, OP_Rewind, iIdxCur);
 		VdbeCoverage(v);
 		sqlite3VdbeAddOp2(v, OP_Integer, 0, regChng);
+		int endDistinctTest = sqlite3VdbeMakeLabel(v);
+		/* Array of jump instruction addresses. */
+		int *aGotoChng =
+			sqlite3DbMallocRawNN(db, sizeof(int) * part_count);
+		if (aGotoChng == NULL)
+			continue;
+		/*
+		 *  next_row:
+		 *   regChng = 0
+		 *   if( idx(0) != regPrev(0) ) goto chng_addr_0
+		 *   regChng = 1
+		 *   if( idx(1) != regPrev(1) ) goto chng_addr_1
+		 *   ...
+		 *   regChng = N
+		 *   goto endDistinctTest
+		 */
+		sqlite3VdbeAddOp0(v, OP_Goto);
 		addrNextRow = sqlite3VdbeCurrentAddr(v);
-
-		if (part_count > 0) {
-			int endDistinctTest = sqlite3VdbeMakeLabel(v);
-			int *aGotoChng;	/* Array of jump instruction addresses */
-			aGotoChng =
-			    sqlite3DbMallocRawNN(db, sizeof(int) * part_count);
-			if (aGotoChng == 0)
-				continue;
-
+		if (part_count == 1 && idx->def->opts.is_unique) {
 			/*
-			 *  next_row:
-			 *   regChng = 0
-			 *   if( idx(0) != regPrev(0) ) goto chng_addr_0
-			 *   regChng = 1
-			 *   if( idx(1) != regPrev(1) ) goto chng_addr_1
-			 *   ...
-			 *   regChng = N
-			 *   goto endDistinctTest
+			 * 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.
 			 */
-			sqlite3VdbeAddOp0(v, OP_Goto);
-			addrNextRow = sqlite3VdbeCurrentAddr(v);
-			if (part_count == 1 && pIdx->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.
-				 */
-				sqlite3VdbeAddOp2(v, OP_NotNull, regPrev,
-						  endDistinctTest);
-				VdbeCoverage(v);
-			}
-			struct key_part *part = pIdx->def->key_def->parts;
-			for (i = 0; i < part_count; ++i, ++part) {
-				struct coll *coll = part->coll;
-				sqlite3VdbeAddOp2(v, OP_Integer, i, regChng);
-				sqlite3VdbeAddOp3(v, OP_Column, iIdxCur,
-						  part->fieldno, regTemp);
-				aGotoChng[i] =
-				    sqlite3VdbeAddOp4(v, OP_Ne, regTemp, 0,
-						      regPrev + i, (char *)coll,
-						      P4_COLLSEQ);
-				sqlite3VdbeChangeP5(v, SQLITE_NULLEQ);
-				VdbeCoverage(v);
-			}
-			sqlite3VdbeAddOp2(v, OP_Integer, part_count, regChng);
-			sqlite3VdbeGoto(v, endDistinctTest);
+			sqlite3VdbeAddOp2(v, OP_NotNull, regPrev,
+					  endDistinctTest);
+			VdbeCoverage(v);
+		}
+		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);
+			sqlite3VdbeAddOp3(v, OP_Column, iIdxCur, part->fieldno,
+					  regTemp);
+			aGotoChng[i] = sqlite3VdbeAddOp4(v, OP_Ne, regTemp, 0,
+							 regPrev + i,
+							 (char *)coll,
+							 P4_COLLSEQ);
+			sqlite3VdbeChangeP5(v, SQLITE_NULLEQ);
+			VdbeCoverage(v);
+		}
+		sqlite3VdbeAddOp2(v, OP_Integer, part_count, regChng);
+		sqlite3VdbeGoto(v, endDistinctTest);
 
-			/*
-			 *  chng_addr_0:
-			 *   regPrev(0) = idx(0)
-			 *  chng_addr_1:
-			 *   regPrev(1) = idx(1)
-			 *  ...
-			 */
-			sqlite3VdbeJumpHere(v, addrNextRow - 1);
-			part = pIdx->def->key_def->parts;
-			for (i = 0; i < part_count; ++i, ++part) {
-				sqlite3VdbeJumpHere(v, aGotoChng[i]);
-				sqlite3VdbeAddOp3(v, OP_Column, iIdxCur,
-						  part->fieldno, regPrev + i);
-			}
-			sqlite3VdbeResolveLabel(v, endDistinctTest);
-			sqlite3DbFree(db, aGotoChng);
+		/*
+		 *  chng_addr_0:
+		 *   regPrev(0) = idx(0)
+		 *  chng_addr_1:
+		 *   regPrev(1) = idx(1)
+		 *  ...
+		 */
+		sqlite3VdbeJumpHere(v, addrNextRow - 1);
+		part = idx->def->key_def->parts;
+		for (i = 0; i < part_count; ++i, ++part) {
+			sqlite3VdbeJumpHere(v, aGotoChng[i]);
+			sqlite3VdbeAddOp3(v, OP_Column, iIdxCur, part->fieldno,
+					  regPrev + i);
 		}
+		sqlite3VdbeResolveLabel(v, endDistinctTest);
+		sqlite3DbFree(db, aGotoChng);
 
 		/*
 		 *  chng_addr_N:
@@ -990,18 +986,18 @@ analyzeOneTable(Parse * pParse,	/* Parser context */
 		 *   if !eof(csr) goto next_row;
 		 */
 		assert(regKey == (regStat4 + 2));
-		Index *pPk = sqlite3PrimaryKeyIndex(pIdx->pTable);
-		int pk_part_count = pPk->def->key_def->part_count;
+		struct index *pk = space_index(space, 0);
+		int pk_part_count = pk->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;
-			assert(k < pTab->def->field_count);
+		for (i = 0; i < pk_part_count; i++) {
+			uint32_t k = pk->def->key_def->parts[i].fieldno;
+			assert(k < space->def->field_count);
 			sqlite3VdbeAddOp3(v, OP_Column, iIdxCur, k,
-					  regKeyStat + j);
-			VdbeComment((v, "%s", pTab->def->fields[k].name));
+					  regKeyStat + i);
+			VdbeComment((v, "%s", space->def->fields[k].name));
 		}
 		sqlite3VdbeAddOp3(v, OP_MakeRecord, regKeyStat,
 				  pk_part_count, regKey);
@@ -1048,8 +1044,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 +1090,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 +1112,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 +1632,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 d1d952330..cc6874f2e 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,52 @@ 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;
-			}
-		}
-		freeIndex(db, pIndex);
+	assert(space != NULL);
+	struct index *to_delete = NULL;
+	uint32_t new_index_id_max = 0;
+	for (uint32_t i = 0; i < space->index_count; ++i) {
+		if (space->index[i]->def->iid == iid)
+			to_delete = space->index[i];
+		else if (new_index_id_max < space->index[i]->def->iid)
+			new_index_id_max = space->index[i]->def->iid;
 	}
-
+	/*
+	 * Allocate new chunk with size reduced by 1 slot.
+	 * Copy all indexes to that chunk except for one
+	 * to be deleted.
+	 */
+	assert(to_delete != NULL);
+	uint32_t new_index_count = space->index_count - 1;
+	size_t size = sizeof(struct index *) * new_index_count;
+	struct index **new_indexes = (struct index **) malloc(size);
+	if (new_indexes == NULL) {
+		diag_set(OutOfMemory, size, "malloc", "new_indexes");
+		return;
+	}
+	for (uint32_t i = 0, j = 0; i < space->index_count; ++i) {
+		struct index *idx = space->index[i];
+		if (idx == to_delete)
+			continue;
+		new_indexes[j++] = idx;
+	}
+	index_def_delete(to_delete->def);
+	free(to_delete);
+	free(space->index);
+	space->index = new_indexes;
+	space->index_count--;
+	space->index_id_max = new_index_id_max;
+	struct session *user_session = current_session();
 	user_session->sql_flags |= SQLITE_InternChanges;
 }
 
@@ -260,38 +257,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.
- *
- * 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.
+ * 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];
+		index_def_delete(idx->def);
+		free(idx);
+	}
+	free(tab->space->index);
+	free(tab->space);
+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
@@ -302,7 +300,7 @@ sqlite3DeleteTable(sqlite3 * db, Table * pTable)
 		return;
 	if (((!db || db->pnBytesFreed == 0) && (--pTable->nTabRef) > 0))
 		return;
-	deleteTable(db, pTable);
+	table_delete(db, pTable);
 }
 
 /*
@@ -369,16 +367,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];
 }
 
 /**
@@ -463,14 +457,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);
 
@@ -831,7 +817,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);
@@ -891,7 +877,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++) {
@@ -953,11 +939,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);
 			}
 		}
@@ -1156,137 +1142,135 @@ 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.
+ * @param index_id_reg Register containing generated index 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,
+		       int index_id_reg)
 {
-	Vdbe *v = sqlite3GetVdbe(pParse);
-	int iFirstCol = ++pParse->nMem;
-	int iRecord = (pParse->nMem += 6);	/* 6 total columns */
-	struct index_opts opts = pIndex->def->opts;
-	opts.sql = (char *)zSql;
-	struct region *region = &pParse->region;
+	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. */
+	struct region *region = &parse->region;
 	uint32_t index_opts_sz = 0;
-	char *index_opts =
-		sql_encode_index_opts(region, &opts, &index_opts_sz);
+	char *index_opts = sql_encode_index_opts(region, &idx_def->opts,
+						 &index_opts_sz);
 	if (index_opts == NULL)
 		goto error;
 	uint32_t index_parts_sz = 0;
-	char *index_parts =
-		sql_encode_index_parts(region, pIndex, &index_parts_sz);
+	char *index_parts = sql_encode_index_parts(region, def->fields, idx_def,
+						   pk_def, &index_parts_sz);
 	if (index_parts == NULL)
 		goto error;
-	char *raw = sqlite3DbMallocRaw(pParse->db,
-				       index_opts_sz + index_parts_sz);
+	char *raw = sqlite3DbMallocRaw(parse->db,
+				       index_opts_sz +index_parts_sz);
 	if (raw == NULL)
 		return;
-
 	memcpy(raw, index_opts, index_opts_sz);
 	index_opts = raw;
 	raw += index_opts_sz;
 	memcpy(raw, index_parts, index_parts_sz);
 	index_parts = raw;
 
-	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 literal, but index_id_reg is
+		 * register.
 		 */
-		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, index_id_reg, 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, index_opts_sz, iFirstCol + 4,
+	sqlite3VdbeAddOp4(v, OP_Blob, index_opts_sz, entry_reg + 4,
 			  SQL_SUBTYPE_MSGPACK, index_opts, P4_DYNAMIC);
-	/* zOpts and zParts are co-located, hence STATIC */
-	sqlite3VdbeAddOp4(v, OP_Blob, index_parts_sz, iFirstCol + 5,
+	/* opts and parts are co-located, hence STATIC. */
+	sqlite3VdbeAddOp4(v, OP_Blob, index_parts_sz, entry_reg + 5,
 			  SQL_SUBTYPE_MSGPACK, index_parts, P4_STATIC);
-	sqlite3VdbeAddOp3(v, OP_MakeRecord, iFirstCol, 6, iRecord);
-	sqlite3VdbeAddOp2(v, OP_SInsert, BOX_INDEX_ID, iRecord);
+	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);
 	return;
 error:
-	pParse->rc = SQL_TARANTOOL_ERROR;
-	pParse->nErr++;
+	parse->rc = SQL_TARANTOOL_ERROR;
+	parse->nErr++;
+
 }
 
-/*
+/**
  * 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 iid 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;
-
-	sqlite3VdbeAddOp4(v,
-			  OP_String8, 0, iFirstCol, 0,
-			  sqlite3DbStrDup(pParse->db, pIndex->def->name),
-			  P4_DYNAMIC);
+	struct Vdbe *v = sqlite3GetVdbe(parse);
+	int entry_reg = parse->nMem + 1;
+	parse->nMem += 4;
 
-	if (pParse->pNewTable) {
+	sqlite3VdbeAddOp4(v, OP_String8, 0, entry_reg, 0,
+			  sqlite3DbStrDup(parse->db, idx_name), P4_DYNAMIC);
+	if (parse->pNewTable != NULL) {
 		/*
-		 * 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;
 }
 
 /*
@@ -1357,8 +1341,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,
@@ -1367,11 +1350,12 @@ 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);
@@ -1629,13 +1613,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);
@@ -1709,10 +1694,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];
+			vdbe_emit_create_index(pParse, p->def, idx->def,
+					       pk->def, reg_space_id,
+					       idx->def->iid);
+		}
 	}
 
 	/*
@@ -1759,7 +1747,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 "\
@@ -2457,84 +2445,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.
@@ -2584,16 +2494,22 @@ 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 space *space, struct index *index)
+{
+	uint32_t idx_count = space->index_count;
+	size_t indexes_sz = sizeof(struct index *) * (idx_count + 1);
+	struct index **idx = (struct index **) realloc(space->index,
+						       indexes_sz);
+	if (idx == NULL) {
+		diag_set(OutOfMemory, indexes_sz, "realloc", "idx");
+		return;
 	}
+	space->index = idx;
+	/* Make sure that PK always comes as first member. */
+	if (index->def->iid == 0 && idx_count != 0)
+		SWAP(space->index[0], index);
+	space->index[space->index_count++] = index;
+	space->index_id_max =  MAX(space->index_id_max, index->def->iid);;
 }
 
 /**
@@ -2617,7 +2533,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)
@@ -2636,7 +2552,8 @@ index_fill_def(struct Parse *parse, struct Index *index,
 
 	for (int i = 0; i < expr_list->nExpr; i++) {
 		struct Expr *expr = expr_list->a[i].pExpr;
-		sql_resolve_self_reference(parse, table, NC_IdxExpr, expr, 0);
+		sql_resolve_self_reference(parse, table, NC_IdxExpr,
+					   expr, 0);
 		if (parse->nErr > 0)
 			goto cleanup;
 
@@ -2705,19 +2622,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;
@@ -2777,10 +2688,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,
@@ -2810,23 +2721,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);
 	}
@@ -2868,18 +2774,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;
@@ -2891,12 +2800,11 @@ sql_create_index(struct Parse *parse, struct Token *token,
 		if (sql_stmt == NULL)
 			goto exit_create_index;
 	}
-	/*
-	 * If it is parsing stage, then iid may have any value
-	 * (for the simplicity sake we set it to 1), but PK
-	 * still must have iid == 0.
-	 */
-	uint32_t iid = idx_type != SQL_INDEX_TYPE_CONSTRAINT_PK;
+	uint32_t iid;
+	if (idx_type != SQL_INDEX_TYPE_CONSTRAINT_PK)
+		iid = table->space->index_id_max + 1;
+	else
+		iid = 0;
 	if (index_fill_def(parse, index, table, iid, name, strlen(name),
 			   col_list, idx_type, sql_stmt) != 0)
 		goto exit_create_index;
@@ -2954,8 +2862,9 @@ 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];
+			uint32_t iid = existing_idx->def->iid;
 			struct key_def *key_def = index->def->key_def;
 			struct key_def *exst_key_def =
 				existing_idx->def->key_def;
@@ -2980,7 +2889,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) {
+			    iid != 0 && !is_named) {
 				existing_idx->def->iid = 0;
 				goto exit_create_index;
 			}
@@ -3036,12 +2945,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, index_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
@@ -3053,13 +2964,14 @@ sql_create_index(struct Parse *parse, struct Token *token,
 
 	if (!db->init.busy && tbl_name != NULL)
 		goto exit_create_index;
-	table_add_index(table, index);
+	table_add_index(table->space, index);
 	index = NULL;
 
 	/* Clean up before exiting. */
  exit_create_index:
-	if (index != NULL)
-		freeIndex(db, index);
+	if (index != NULL && index->def != NULL)
+		index_def_delete(index->def);
+	free(index);
 	sql_expr_list_delete(db, col_list);
 	sqlite3SrcListDelete(db, tbl_name);
 	sqlite3DbFree(db, name);
@@ -3133,10 +3045,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);
+	sqlite3VdbeAddOp1(v, OP_DropIndex, index->def->iid);
+	sqlite3VdbeAppendP4(v, tab->space, P4_SPACEPTR);
 
  exit_drop_index:
 	sqlite3SrcListDelete(db, index_name_list);
@@ -3674,160 +3585,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 fc1b156f7..dcb57e9b3 100644
--- a/src/box/sql/expr.c
+++ b/src/box/sql/expr.c
@@ -2363,7 +2363,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;
 
@@ -2398,15 +2397,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
@@ -2418,7 +2423,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
@@ -2470,14 +2475,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 +
@@ -3137,9 +3140,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 =
@@ -3464,22 +3466,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)
@@ -4984,22 +4970,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
@@ -5156,62 +5126,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 550514c03..9a7fdffbb 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,7 +1001,8 @@ 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)
 			continue;
@@ -1080,40 +1082,30 @@ vdbe_emit_insertion_completion(struct Vdbe *v, int cursor_id, int raw_data_reg,
 }
 
 #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 True, if two indexes are compatible in terms of
+ *         xfer optimization.
  */
-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;
-	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;
-	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 */
-		if (src_part->sort_order != dest_part->sort_order)
-			return 0;	/* Different sort orders */
-		if (src_part->coll != dest_part->coll)
-			return 0;	/* Different collating sequences */
-	}
-
-	/* If no test above fails then the indices must be compatible */
-	return 1;
+	assert(dest != NULL && src != NULL);
+	assert(dest->space_id != src->space_id);
+	return key_part_cmp(src->key_def->parts, src->key_def->part_count,
+			    dest->key_def->parts,
+			    dest->key_def->part_count) == 0;
 }
 
 /*
@@ -1147,7 +1139,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 */
@@ -1255,9 +1247,9 @@ xferOptimization(Parse * pParse,	/* Parser context */
 		/* Default values for second and subsequent columns need to match. */
 		if (i > 0) {
 			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)
@@ -1266,9 +1258,12 @@ xferOptimization(Parse * pParse,	/* Parser context */
 			}
 		}
 	}
-	for (pDestIdx = pDest->pIndex; pDestIdx; pDestIdx = pDestIdx->pNext) {
-		for (pSrcIdx = pSrc->pIndex; pSrcIdx; pSrcIdx = pSrcIdx->pNext) {
-			if (xferCompatibleIndex(pDestIdx, pSrcIdx))
+	for (uint32_t i = 0; i < pDest->space->index_count; ++i) {
+		pDestIdx = pDest->space->index[i];
+		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. */
diff --git a/src/box/sql/parse.y b/src/box/sql/parse.y
index d8532d378..040161b53 100644
--- a/src/box/sql/parse.y
+++ b/src/box/sql/parse.y
@@ -211,7 +211,7 @@ columnname(A) ::= nm(A) typetoken(Y). {sqlite3AddColumn(pParse,&A,&Y);}
 %ifdef SQLITE_OMIT_COMPOUND_SELECT
   INTERSECT 
 %endif SQLITE_OMIT_COMPOUND_SELECT
-  REINDEX RENAME CTIME_KW IF
+  RENAME CTIME_KW IF
   .
 %wildcard ANY.
 
@@ -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 988507154..3ba7ad022 100644
--- a/src/box/sql/pragma.c
+++ b/src/box/sql/pragma.c
@@ -237,6 +237,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.
@@ -360,7 +443,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;
@@ -387,7 +470,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;
@@ -404,17 +486,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);
 				}
@@ -423,93 +503,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 e8925792f..4ba1b048a 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 eef7eb39f..02732b59c 100644
--- a/src/box/sql/sqliteInt.h
+++ b/src/box/sql/sqliteInt.h
@@ -1402,7 +1402,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;
@@ -1814,7 +1813,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.
@@ -1826,6 +1824,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;
 };
 
 /**
@@ -1925,28 +1925,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
@@ -1962,12 +1940,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
@@ -2368,7 +2346,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 */
 };
 
@@ -2737,7 +2715,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 */
@@ -3076,7 +3053,6 @@ struct Walker {
 		SrcList *pSrcList;	/* FROM clause */
 		struct SrcCount *pSrcCount;	/* Counting column references */
 		int *aiCol;	/* array of column indexes */
-		struct IdxCover *pIdxCover;	/* Check for index coverage */
 		/** Space definition. */
 		struct space_def *space_def;
 	} u;
@@ -3366,7 +3342,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 *);
 
@@ -3604,7 +3586,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
@@ -3670,20 +3651,32 @@ int sqlite3ExprCodeExprList(Parse *, ExprList *, int, int, u8);
 #define SQLITE_ECEL_OMITREF  0x08	/* Omit if ExprList.u.x.iOrderByCol */
 void sqlite3ExprIfTrue(Parse *, Expr *, int, int);
 void sqlite3ExprIfFalse(Parse *, Expr *, int, int);
-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
@@ -3813,8 +3806,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
@@ -3904,17 +3897,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 *);
@@ -4410,7 +4393,6 @@ extern FuncDefHash sqlite3BuiltinFunctions;
 extern int sqlite3PendingByte;
 #endif
 #endif
-void sqlite3Reindex(Parse *, Token *, Token *);
 void sqlite3AlterRenameTable(Parse *, SrcList *, Token *);
 
 /**
@@ -4546,7 +4528,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 efc531bbd..d2475bbc8 100644
--- a/src/box/sql/tarantoolInt.h
+++ b/src/box/sql/tarantoolInt.h
@@ -217,7 +217,9 @@ fkey_encode_links(struct region *region, const struct fkey_def *def, int type,
  * @retval not NULL Pointer to msgpack on success
  */
 char *
-sql_encode_index_parts(struct region *region, struct Index *index,
+sql_encode_index_parts(struct region *region, const struct field_def *fields,
+		       const struct index_def *idx_def,
+		       const struct index_def *pk_def,
 		       uint32_t *size);
 
 /**
@@ -230,7 +232,7 @@ sql_encode_index_parts(struct region *region, struct Index *index,
  * @retval not NULL pointer to msgpack on success
  */
 char *
-sql_encode_index_opts(struct region *region, struct index_opts *opts,
+sql_encode_index_opts(struct region *region, const struct index_opts *opts,
 		      uint32_t *size);
 
 /**
diff --git a/src/box/sql/update.c b/src/box/sql/update.c
index 3e39688a5..076b28a94 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);
 	i = sizeof(int) * def->field_count;
 	aXRef = (int *) region_alloc(&pParse->region, i);
 	if (aXRef == NULL) {
diff --git a/src/box/sql/vdbe.c b/src/box/sql/vdbe.c
index a5b907d59..b956726e4 100644
--- a/src/box/sql/vdbe.c
+++ b/src/box/sql/vdbe.c
@@ -4736,17 +4736,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..3ba01198d 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 key_def Definition of a key to set.
  */
 void
-sql_vdbe_set_p4_key_def(struct Parse *parse, struct Index *index);
+sql_vdbe_set_p4_key_def(struct Parse *parse, struct key_def *key_def);
 
 VdbeOp *sqlite3VdbeGetOp(Vdbe *, int);
 int sqlite3VdbeMakeLabel(Vdbe *);
diff --git a/src/box/sql/vdbeaux.c b/src/box/sql/vdbeaux.c
index 3b0c90ce3..dda030234 100644
--- a/src/box/sql/vdbeaux.c
+++ b/src/box/sql/vdbeaux.c
@@ -1055,16 +1055,16 @@ 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 key_def *key_def)
 {
 	struct Vdbe *v = parse->pVdbe;
 	assert(v != NULL);
-	assert(idx != NULL);
-	struct key_def *def = key_def_dup(idx->def->key_def);
-	if (def == NULL)
+	assert(key_def != NULL);
+	key_def = key_def_dup(key_def);
+	if (key_def == NULL)
 		sqlite3OomFault(parse->db);
 	else
-		sqlite3VdbeAppendP4(v, def, P4_KEYDEF);
+		sqlite3VdbeAppendP4(v, key_def, P4_KEYDEF);
 }
 
 #ifdef SQLITE_ENABLE_EXPLAIN_COMMENTS
diff --git a/src/box/sql/vdbemem.c b/src/box/sql/vdbemem.c
index 65bb67d8f..ef70442b3 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 +
-				ROUND8(sizeof(UnpackedRecord));
-			pRec =
-			    (UnpackedRecord *) sqlite3DbMallocZero(db, nByte);
+			int nByte = sizeof(Mem) * part_count +
+				    ROUND8(sizeof(UnpackedRecord));
+			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..b9a8d1734 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,29 @@ 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_def *def = pTab->space->index[j]->def;
+		if (!def->opts.is_unique)
 			continue;
-		int col_count = pIdx->def->key_def->part_count;
+		uint32_t col_count = 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)) {
-				if (findIndexCol
-				    (pParse, pDistinct, iBase, pIdx, i) < 0)
+			if (sqlite3WhereFindTerm(pWC, iBase, i, ~(Bitmask) 0,
+						 WO_EQ, def) == 0) {
+				if (findIndexCol(pParse, pDistinct, iBase, 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 = 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 == col_count)
 			return 1;
-		}
 	}
 
 	return 0;
@@ -851,7 +857,7 @@ constructAutomaticIndex(Parse * pParse,			/* The parsing context */
 	assert(pLevel->iIdxCur >= 0);
 	pLevel->iIdxCur = pParse->nTab++;
 	sqlite3VdbeAddOp2(v, OP_OpenAutoindex, pLevel->iIdxCur, nKeyCol + 1);
-	sql_vdbe_set_p4_key_def(pParse, pIdx);
+	sql_vdbe_set_p4_key_def(pParse, pIdx->key_def);
 	VdbeComment((v, "for %s", pTable->def->name));
 
 	/* Fill the automatic index with content */
@@ -908,15 +914,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 +1190,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 +1319,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 +1377,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 +1391,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 +1533,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 +1541,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 +1561,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 +1589,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 +1694,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 +1739,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) {
+		index_def_delete(p->index_def);
+		p->index_def = NULL;
 	}
 }
 
@@ -1758,7 +1755,7 @@ whereLoopClear(sqlite3 * db, WhereLoop * p)
 {
 	if (p->aLTerm != p->aLTermSpace)
 		sqlite3DbFree(db, p->aLTerm);
-	whereLoopClearUnion(db, p);
+	whereLoopClearUnion(p);
 	whereLoopInit(p);
 }
 
@@ -1789,19 +1786,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 +2137,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 +2248,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 +2276,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 +2314,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 +2336,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 +2352,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 +2380,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 +2390,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 +2465,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 +2487,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 +2512,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 +2536,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 +2579,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 +2597,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 +2609,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 +2634,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 +2664,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 +2686,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 +2700,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 +2724,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 +2740,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 +2791,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,23 +2814,19 @@ 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) {
+tnt_error:
 			pWInfo->pParse->nErr++;
 			pWInfo->pParse->rc = SQL_TARANTOOL_ERROR;
 			return SQL_TARANTOOL_ERROR;
@@ -2849,36 +2839,27 @@ 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);
+		if (fake_index == NULL)
+			goto tnt_error;
 		/* Special marker for  non-existent index. */
-		fake_index.def->iid = UINT32_MAX;
+		fake_index->iid = UINT32_MAX;
+		int size = sizeof(struct index_stat) + sizeof(log_est_t) * 2;
 
-		if (fake_index.def == NULL) {
-			pWInfo->pParse->nErr++;
-			pWInfo->pParse->rc = SQL_TARANTOOL_ERROR;
-			return SQL_TARANTOOL_ERROR;
+		struct index_stat *stat = (struct index_stat *) malloc(size);
+		if (stat == NULL) {
+			diag_set(OutOfMemory, size, "malloc", "stat");
+			goto tnt_error;
 		}
-
-		struct index_stat *stat =
-			(struct index_stat *) malloc(sizeof(struct index_stat));
-		stat->tuple_log_est =
-			(log_est_t *) malloc(sizeof(log_est_t) * 2);
+		stat->tuple_log_est = (log_est_t *) ((char *) (stat + 1));
 		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,16 @@ whereLoopAddBtree(WhereLoopBuilder * pBuilder,	/* WHERE clause information */
 		}
 	}
 #endif				/* SQLITE_OMIT_AUTOMATIC_INDEX */
-
-	/* Loop over all indices
+	/*
+	 * If there was an INDEXED BY clause, then only that one
+	 * index is considered.
 	 */
-	for (; rc == SQLITE_OK && pProbe; pProbe = pProbe->pNext, iSortIdx++) {
-		rSize = index_field_tuple_est(pProbe, 0);
+	uint32_t idx_count = fake_index == NULL || pSrc->pIBIndex != 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 +2936,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 +2954,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 +2963,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,22 +2972,13 @@ 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;
-
-		/* If there was an INDEXED BY clause, then only that one index is
-		 * considered.
-		 */
-		if (pSrc->pIBIndex)
-			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)
+		index_def_delete(fake_index);
 	return rc;
 }
 
@@ -3113,7 +3085,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 +3203,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 *idx_def;
 	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 +3307,14 @@ wherePathSatisfiesOrderBy(WhereInfo * pWInfo,	/* The WHERE clause */
 
 		if ((pLoop->wsFlags & WHERE_ONEROW) == 0) {
 			if (pLoop->wsFlags & WHERE_IPK) {
-				pIndex = 0;
+				idx_def = NULL;
 				nColumn = 1;
-			} else if ((pIndex = pLoop->pIndex) == NULL ||
-				   index_is_unordered(pIndex)) {
+			} else if ((idx_def = pLoop->index_def) == NULL ||
+				   index_is_unordered(idx_def)) {
 				return 0;
 			} else {
-				nColumn = pIndex->def->key_def->part_count;
-				isOrderDistinct = pIndex->def->opts.is_unique;
+				nColumn = idx_def->key_def->part_count;
+				isOrderDistinct = idx_def->opts.is_unique;
 			}
 
 			/* Loop through all columns of the index and deal with the ones
@@ -3402,9 +3374,8 @@ wherePathSatisfiesOrderBy(WhereInfo * pWInfo,	/* The WHERE clause */
 				/* Get the column number in the table (iColumn) and sort order
 				 * (revIdx) for the j-th column of the index.
 				 */
-				if (pIndex != NULL) {
-					struct key_def *def =
-						pIndex->def->key_def;
+				if (idx_def != NULL) {
+					struct key_def *def = idx_def->key_def;
 					iColumn = def->parts[j].fieldno;
 					revIdx = def->parts[j].sort_order;
 				} else {
@@ -3415,12 +3386,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 && idx_def != NULL) {
+					struct space *space =
+						space_by_id(idx_def->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 +3426,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;
+							idx_def->key_def->parts[j].coll;
 						if (is_found &&
 						    coll != idx_coll)
 							continue;
@@ -4141,7 +4113,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 +4606,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,11 +4622,9 @@ 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));
-			if (is_primary
+			if (idx_def->iid == 0
 			    && (wctrlFlags & WHERE_OR_SUBCLAUSE) != 0) {
 				/* This is one term of an OR-optimization using
 				 * the PRIMARY KEY.  No need for a separate index
@@ -4663,28 +4632,25 @@ 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 (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,19 +4666,10 @@ sqlite3WhereBegin(Parse * pParse,	/* The parser context */
 			pLevel->iIdxCur = iIndexCur;
 			assert(iIndexCur >= 0);
 			if (op) {
-				if (pIx != NULL) {
-					uint32_t space_id =
-						pIx->pTable->def->id;
-					struct space *space =
-						space_by_id(space_id);
-					vdbe_emit_open_cursor(pParse, iIndexCur,
-							      pIx->def->iid,
-							      space);
-				} else {
-					vdbe_emit_open_cursor(pParse, iIndexCur,
-							      idx_def->iid,
-							      space);
-				}
+				uint32_t space_id = idx_def->space_id;
+				struct space *space = space_by_id(space_id);
+				vdbe_emit_open_cursor(pParse, iIndexCur,
+						      idx_def->iid, space);
 				if ((pLoop->wsFlags & WHERE_CONSTRAINT) != 0
 				    && (pLoop->
 					wsFlags & (WHERE_COLUMN_RANGE |
@@ -4721,10 +4678,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
-					VdbeComment((v, "%s", idx_def->name));
+				VdbeComment((v, "%s", idx_def->name));
 #ifdef SQLITE_ENABLE_COLUMN_USED_MASK
 				{
 					u64 colUsed = 0;
@@ -4855,7 +4809,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 +4886,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 +4901,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 889a667ae..8a3f2ac77 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 13a045c34..1aa858ac1 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->iid == 0) {
 				if (isSearch) {
 					zFmt = "PRIMARY KEY";
 				}
@@ -240,12 +232,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)
-					sqlite3XPrintf(&str, zFmt, idx_def->name);
-				else
-					sqlite3XPrintf(&str, zFmt, "EPHEMERAL INDEX");
+				sqlite3XPrintf(&str, zFmt, idx_def->name);
 				explainIndexRange(&str, pLoop);
 			}
 		} else if ((flags & WHERE_IPK) != 0
@@ -485,8 +472,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 != NULL &&
+		    pLoop->index_def->key_def->parts[iEq].sort_order) {
 			testcase(iEq == 0);
 			testcase(bRev);
 			bRev = !bRev;
@@ -710,9 +697,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.
 	 */
@@ -720,20 +706,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) {
@@ -741,7 +718,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),
@@ -751,9 +728,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)));
 		}
 	}
 
@@ -1026,9 +1003,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);
 
@@ -1043,14 +1022,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.
@@ -1061,7 +1036,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;
@@ -1098,7 +1074,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 ==
@@ -1106,8 +1082,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;
 			}
 		}
@@ -1119,7 +1096,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);
@@ -1181,34 +1158,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
@@ -1217,12 +1181,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. */
@@ -1330,11 +1290,9 @@ 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);
-			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++) {
-				k = pPk->def->key_def->parts[j].fieldno;
+			for (j = 0; j < (int) pk_part_count; j++) {
+				k = idx_pk->key_def->parts[j].fieldno;
 				sqlite3VdbeAddOp3(v, OP_Column, iIdxCur, k,
 						  iKeyReg + j);
 			}
@@ -1379,7 +1337,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 *cov = NULL;	/* 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 */
@@ -1392,6 +1350,9 @@ sqlite3WhereCodeOneLoopStart(WhereInfo * pWInfo,	/* Complete information about t
 		u16 wctrlFlags;	/* Flags for sub-WHERE clause */
 		Expr *pAndExpr = 0;	/* An ".. AND (...)" expression */
 		Table *pTab = pTabItem->pTab;
+		struct key_def *pk_key_def =
+			sql_table_primary_key(pTab)->def->key_def;
+		uint32_t pk_part_count = pk_key_def->part_count;
 
 		pTerm = pLoop->aLTerm[0];
 		assert(pTerm != 0);
@@ -1438,12 +1399,10 @@ sqlite3WhereCodeOneLoopStart(WhereInfo * pWInfo,	/* Complete information about t
 		 * called on an uninitialized cursor.
 		 */
 		if ((pWInfo->wctrlFlags & WHERE_DUPLICATES_OK) == 0) {
-			Index *pPk = sqlite3PrimaryKeyIndex(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, pk_key_def);
 			regPk = ++pParse->nMem;
 		}
 		iRetInit = sqlite3VdbeAddOp2(v, OP_Integer, 0, regReturn);
@@ -1542,18 +1501,15 @@ sqlite3WhereCodeOneLoopStart(WhereInfo * pWInfo,	/* Complete information about t
 						int r;
 						int iSet =
 						    ((ii == pOrWc->nTerm - 1) ? -1 : ii);
-						Index *pPk = sqlite3PrimaryKeyIndex (pTab);
-						struct key_def *def =
-							pPk->def->key_def;
 
 						/* Read the PK into an array of temp registers. */
 						r = sqlite3GetTempRange(pParse,
-									def->part_count);
+									pk_part_count);
 						for (uint32_t iPk = 0;
-						     iPk < def->part_count;
+						     iPk < pk_part_count;
 						     iPk++) {
 							uint32_t fieldno =
-								def->parts[iPk].
+								pk_key_def->parts[iPk].
 								fieldno;
 							sqlite3ExprCodeGetColumnToReg
 								(pParse,
@@ -1580,20 +1536,20 @@ sqlite3WhereCodeOneLoopStart(WhereInfo * pWInfo,	/* Complete information about t
 								(v, OP_Found,
 								 regRowset, 0,
 								 r,
-								 def->part_count);
+								 pk_part_count);
 							VdbeCoverage(v);
 						}
 						if (iSet >= 0) {
 							sqlite3VdbeAddOp3
 								(v, OP_MakeRecord,
-								 r, def->part_count, regPk);
+								 r, pk_part_count, regPk);
 							sqlite3VdbeAddOp2
 								(v, OP_IdxInsert,
 								 regRowset, regPk);
 						}
 
 						/* Release the array of temp registers */
-						sqlite3ReleaseTempRange(pParse, r, def->part_count);
+						sqlite3ReleaseTempRange(pParse, r, pk_part_count);
 					}
 
 					/* Invoke the main loop body as a subroutine */
@@ -1622,20 +1578,21 @@ sqlite3WhereCodeOneLoopStart(WhereInfo * pWInfo,	/* Complete information about t
 					 * If the call to sqlite3WhereBegin() above resulted in a scan that
 					 * uses an index, and this is either the first OR-connected term
 					 * processed or the index is the same as that used by all previous
-					 * terms, set pCov to the candidate covering index. Otherwise, set
-					 * pCov to NULL to indicate that no candidate covering index will
+					 * terms, set cov to the candidate covering index. Otherwise, set
+					 * cov to NULL to indicate that no candidate covering index will
 					 * be available.
 					 */
 					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 || (cov != NULL &&
+						pSubLoop->index_def->iid == cov->iid))
+					    && (pSubLoop->index_def->iid != 0)) {
 						assert(pSubWInfo->a[0].
 						       iIdxCur == iCovCur);
-						pCov = pSubLoop->pIndex;
+						cov = pSubLoop->index_def;
 					} else {
-						pCov = 0;
+						cov = 0;
 					}
 
 					/* Finish the loop through table entries that match term pOrTerm. */
@@ -1643,8 +1600,8 @@ sqlite3WhereCodeOneLoopStart(WhereInfo * pWInfo,	/* Complete information about t
 				}
 			}
 		}
-		pLevel->u.pCovidx = pCov;
-		if (pCov)
+		pLevel->u.pCovidx = cov;
+		if (cov)
 			pLevel->iIdxCur = iCovCur;
 		if (pAndExpr) {
 			pAndExpr->pLeft = 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-2174-ban-reindex-syntax.test.lua b/test/sql-tap/gh-2174-ban-reindex-syntax.test.lua
deleted file mode 100755
index baa67b4f7..000000000
--- a/test/sql-tap/gh-2174-ban-reindex-syntax.test.lua
+++ /dev/null
@@ -1,29 +0,0 @@
-#!/usr/bin/env tarantool
-
--- this test will be deleted in scope of #3195
-test = require("sqltester")
-test:plan(3)
-
-test:execsql("DROP TABLE IF EXISTS t1");
-test:execsql("CREATE TABLE t1(a INT PRIMARY KEY)");
-test:execsql("CREATE INDEX i1 on t1(a)");
-
-test:do_catchsql_test(
-	"1",
-	"REINDEX i1 ON t1",
-	{1, "keyword \"REINDEX\" is reserved"}
-)
-
-test:do_catchsql_test(
-	"2",
-	"REINDEX t1",
-	 {1, "keyword \"REINDEX\" is reserved"}
-)
-
-test:do_catchsql_test(
-	"3",
-	"REINDEX",
-	{1, "keyword \"REINDEX\" is reserved"}
-)
-
-test:finish_test()
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/gh2130-index-refer-table.test.lua b/test/sql-tap/gh2130-index-refer-table.test.lua
index 3a6064cb9..b5fc1106d 100755
--- a/test/sql-tap/gh2130-index-refer-table.test.lua
+++ b/test/sql-tap/gh2130-index-refer-table.test.lua
@@ -69,16 +69,4 @@ test:do_execsql_test(
 	-- <index-1.5>
 	})
 
--- This part of test is banned in scope of #2174
--- test:do_execsql_test(
---	"index-1.6",
---	[[
---		REINDEX t1ix1 ON t1;
---	]],
---	{
---	-- <index-1.6>
---
---	-- <index-1.6>
---	})
-
 test:finish_test()
diff --git a/test/sql-tap/index1.test.lua b/test/sql-tap/index1.test.lua
index fdce2683c..a9abd08aa 100755
--- a/test/sql-tap/index1.test.lua
+++ b/test/sql-tap/index1.test.lua
@@ -119,67 +119,6 @@ test:do_test(
         -- </index-2.2>
     })
 
--- MUST_WORK_TEST REINDEX and integrity_check
-if (0 > 0)
- then
-    -- Try creating a bunch of indices on the same table
-    --
-    local r = {}
-    for i = 1, 99, 1 do
-        table.insert(r,string.format("index%02d", i))
-    end
-    test:do_test(
-        "index-3.1",
-        function()
-            test:execsql("CREATE TABLE test1(f1 int primary key, f2 int, f3 int, f4 int, f5 int)")
-            for i = 1, 99, 1 do
-                local sql = string.format("CREATE INDEX %s ON test1(f%s)", string.format("index%02d", i), (i%5)+1)
-                test:execsql(sql)
-            end
-            return test:execsql [[SELECT name FROM sqlite_master 
-              WHERE type='index' AND tbl_name='test1'
-              ORDER BY name]]
-        end, {
-            -- <index-3.1>
-            r
-            -- </index-3.1>
-        })
-
-    X(104, "X!cmd", [=[["integrity_check","index-3.2.1"]]=])
-    test:do_execsql_test(
-        "index-3.2.2",
-        [[
-            REINDEX
-        ]], {
-            -- <index-3.2.2>
-            
-            -- </index-3.2.2>
-        })
-
-
-
-    --X(110, "X!cmd", [=[["integrity_check","index-3.2.3"]]=])
-    -- Verify that all the indices go away when we drop the table.
-    --
-    test:do_test(
-        "index-3.3",
-        function()
-            test:execsql "DROP TABLE test1"
-            return test:execsql [[SELECT name FROM sqlite_master 
-              WHERE type='index' AND tbl_name='test1'
-              ORDER BY name]]
-        end, {
-            -- <index-3.3>
-            
-            -- </index-3.3>
-        })
-
-    -- Create a table and insert values into that table. Then create
-    -- an index on that table. Verify that we can select values
-    -- from the table correctly using the index
-    -- Note that the index names index9 and indext are chosen because
-    -- they both have the same hash.
-end
 test:do_test(
     "index-4.1",
     function()
@@ -1017,7 +956,7 @@ test:do_execsql_test(
         SELECT "_index"."name" FROM "_index" JOIN "_space" WHERE "_index"."id" = "_space"."id" AND "_space"."name"='T7';
     ]], {
         -- <index-17.1>
-        "pk_unnamed_T7_3", "unique_unnamed_T7_2", "unique_unnamed_T7_1"
+        "pk_unnamed_T7_3","unique_unnamed_T7_1","unique_unnamed_T7_2"
         -- </index-17.1>
     })
 
diff --git a/test/sql-tap/index6.test.lua b/test/sql-tap/index6.test.lua
index 069623f66..af18e89d4 100755
--- a/test/sql-tap/index6.test.lua
+++ b/test/sql-tap/index6.test.lua
@@ -106,13 +106,6 @@ test:plan(14)
 --     SELECT idx, stat FROM sqlite_stat1 ORDER BY idx;
 --   }
 -- } {{} 15 t1a {10 1} t1b {8 1} ok}
--- do_test index6-1.14 {
---   execsql {
---     REINDEX;
---     ANALYZE;
---     SELECT idx, stat FROM sqlite_stat1 ORDER BY idx;
---   }
--- } {{} 15 t1a {10 1} t1b {8 1} ok}
 -- do_test index6-1.15 {
 --   execsql {
 --     CREATE INDEX t1c ON t1(c);
diff --git a/test/sql-tap/index7.test.lua b/test/sql-tap/index7.test.lua
index 4f9070813..7d4a54723 100755
--- a/test/sql-tap/index7.test.lua
+++ b/test/sql-tap/index7.test.lua
@@ -126,14 +126,6 @@ end
 --     PRAGMA integrity_check;
 --   }
 -- } {t1 {15 1} t1a {10 1} t1b {8 1} ok}
--- do_test index7-1.14 {
---   execsql {
---     REINDEX;
---     ANALYZE;
---     SELECT idx, stat FROM sqlite_stat1 ORDER BY idx;
---     PRAGMA integrity_check;
---   }
--- } {t1 {15 1} t1a {10 1} t1b {8 1} ok}
 -- do_test index7-1.15 {
 --   execsql {
 --     CREATE INDEX t1c ON t1(c);
diff --git a/test/sql-tap/insert1.test.lua b/test/sql-tap/insert1.test.lua
index 750732d37..cfca0025f 100755
--- a/test/sql-tap/insert1.test.lua
+++ b/test/sql-tap/insert1.test.lua
@@ -209,11 +209,6 @@ end, {
 -- do_test insert-3.4 {
 --   execsql {SELECT * FROM test2 WHERE f1=22 AND f2=-4.44}
 -- } {22 -4.44 hi abc-123 wham}
--- ifcapable {reindex} {
---   do_test insert-3.5 {
---     execsql REINDEX
---   } {}
--- }
 -- integrity_check insert-3.5
 -- Test of expressions in the VALUES clause
 --
@@ -395,11 +390,6 @@ test:do_execsql_test("insert-4.7", [[
   --     SELECT * FROM t1 WHERE b=3;
   --   }
   -- } {}
-  -- ifcapable {reindex} {
-  --   do_test insert-6.5 {
-  --     execsql REINDEX
-  --   } {}
-  -- }
   -- do_test insert-6.6 {
   --   execsql {
   --     DROP TABLE t1;
diff --git a/test/sql-tap/keyword1.test.lua b/test/sql-tap/keyword1.test.lua
index 23a561f4d..fbcd17327 100755
--- a/test/sql-tap/keyword1.test.lua
+++ b/test/sql-tap/keyword1.test.lua
@@ -1,6 +1,6 @@
 #!/usr/bin/env tarantool
 test = require("sqltester")
-test:plan(176)
+test:plan(175)
 
 --!./tcltestrunner.lua
 -- 2009 January 29
@@ -107,7 +107,6 @@ local bannedkws = {
 	"primary",
 	"recursive",
 	"references",
-	"reindex",
 	"release",
 	"rename",
 	"replace",
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()
diff --git a/test/sql-tap/misc3.test.lua b/test/sql-tap/misc3.test.lua
index 92f8210c9..dc1545f8f 100755
--- a/test/sql-tap/misc3.test.lua
+++ b/test/sql-tap/misc3.test.lua
@@ -443,46 +443,6 @@ test:do_test(
         -- </misc3-6.3>
     })
 
--- Do some additional EXPLAIN operations to exercise the displayP4 logic.
--- This part of test is disabled in scope of #2174
--- test:do_test(
---    "misc3-6.10",
---    function()
---        local x = test:execsql([[
---            CREATE TABLE ex1(
---              id PRIMARY KEY,
---              a INTEGER DEFAULT 54321,
---              b TEXT DEFAULT "hello",
---              c REAL DEFAULT 3.1415926
---            );
---            CREATE UNIQUE INDEX ex1i1 ON ex1(a);
---            EXPLAIN REINDEX;
---        ]])
---        x = json.encode(x)
---        return string.find(x, "\"SorterCompare\",%d+,%d+,%d+") > 0
---    end, true)
---
--- test:do_test(
---     "misc3-6.11-utf8",
---     function()
---         local x = test:execsql([[
---             EXPLAIN SELECT a+123456789012, b*4.5678, c FROM ex1 ORDER BY +a, b DESC
---         ]])
---         x = json.encode(x)
---         local y = {}
---         table.insert(y, string.find(x, "123456789012")>0)
---         table.insert(y, string.find(x, "4.5678")>0)
---         table.insert(y, string.find(x, "hello")>0)
---         table.insert(y, string.find(x, "-B")>0)
---         return y
---     end, {
---         -- <misc3-6.11-utf8>
---         1, 1, 1, 1
---         -- </misc3-6.11-utf8>
---     })
-
-
-
 -- MUST_WORK_TEST autoincrement for pk
 if (0 > 0) then
     -- Ticket #640:  vdbe stack overflow with a LIMIT clause on a SELECT inside
diff --git a/test/sql-tap/reindex.test.lua b/test/sql-tap/reindex.test.lua
deleted file mode 100755
index d4f8c8188..000000000
--- a/test/sql-tap/reindex.test.lua
+++ /dev/null
@@ -1,36 +0,0 @@
-#!/usr/bin/env tarantool
-test = require("sqltester")
-test:plan(3)
-
-
-test:execsql("CREATE TABLE t1(a INT PRIMARY KEY);")
-test:execsql("CREATE INDEX t1ix1 ON t1(a)")
-
-test:do_execsql_test(
-	"reindex-1.1",
-	"REINDEX t1ix1 ON t1",
-	{
-	-- <reindex-1.1>
-	
-	-- <reindex-1.1>
-	})
-
-test:do_catchsql_test(
-	"reindex-1.2",
-	"REINDEX t1ix2 ON t1",
-	{
-		-- <reindex-1.1>
-		1, "unable to identify the object to be reindexed"
-		-- <reindex-1.1>
-	})
-
-test:do_catchsql_test(
-	"reindex-1.3",
-	"REINDEX t1ix1 ON t3",
-	{
-		-- <reindex-1.1>
-		1, "no such table: T3"
-		-- <reindex-1.1>
-	})
-
-test:finish_test()
diff --git a/test/sql-tap/suite.ini b/test/sql-tap/suite.ini
index 0637cffc1..2455f5e8d 100644
--- a/test/sql-tap/suite.ini
+++ b/test/sql-tap/suite.ini
@@ -1,8 +1,6 @@
 [default]
 core = app
 description = Database tests with #! using TAP
-disabled =
-	reindex.test.lua ; This test is banned in scope of #2174
 lua_libs = lua/sqltester.lua ../sql/lua/sql_tokenizer.lua ../box/lua/identifier.lua
 is_parallel = True
 release_disabled = debug_mode_only.test.lua

^ permalink raw reply	[flat|nested] 28+ messages in thread

* [tarantool-patches] Re: [PATCH 1/7] sql: remove struct schema from struct Table
  2018-08-23 22:55   ` [tarantool-patches] [PATCH 1/7] sql: remove struct schema from struct Table Nikita Pettik
  2018-08-29  0:58     ` [tarantool-patches] " Vladislav Shpilevoy
@ 2018-09-16 19:32     ` Vladislav Shpilevoy
  2018-09-19 10:58       ` n.pettik
  1 sibling, 1 reply; 28+ messages in thread
From: Vladislav Shpilevoy @ 2018-09-16 19:32 UTC (permalink / raw)
  To: tarantool-patches, Nikita Pettik

Patchset LGTM. I've not reviewed it again, but the fixes
look ok.

On 24/08/2018 00:55, Nikita Pettik wrote:
> Part of #3561
> ---
>   src/box/sql/build.c     | 7 ++-----
>   src/box/sql/select.c    | 2 +-
>   src/box/sql/sqliteInt.h | 1 -
>   3 files changed, 3 insertions(+), 7 deletions(-)
> 
> diff --git a/src/box/sql/build.c b/src/box/sql/build.c
> index dc00b5d8c..47fa7c305 100644
> --- a/src/box/sql/build.c
> +++ b/src/box/sql/build.c
> @@ -392,8 +392,6 @@ sqlite3PrimaryKeyIndex(Table * pTab)
>   static Table *
>   sql_table_new(Parse *parser, char *name)
>   {
> -	sqlite3 *db = parser->db;
> -
>   	struct Table *table = sql_ephemeral_table_new(parser, name);
>   	if (table == NULL)
>   		return NULL;
> @@ -401,7 +399,6 @@ sql_table_new(Parse *parser, char *name)
>   	strcpy(table->def->engine_name,
>   	       sql_storage_engine_strs[current_session()->sql_default_engine]);
>   
> -	table->pSchema = db->pSchema;
>   	table->nTabRef = 1;
>   	return table;
>   }
> @@ -1650,8 +1647,8 @@ sqlite3EndTable(Parse * pParse,	/* Parse context */
>   		 * Add the table to the in-memory representation
>   		 * of the database.
>   		 */
> -		struct Table *pOld = sqlite3HashInsert(&p->pSchema->tblHash,
> -							p->def->name, p);
> +		struct Table *pOld = sqlite3HashInsert(&db->pSchema->tblHash,
> +						       p->def->name, p);
>   		if (pOld != NULL) {
>   			assert(p == pOld);
>   			sqlite3OomFault(db);
> diff --git a/src/box/sql/select.c b/src/box/sql/select.c
> index d22f4e0a9..12f55cedf 100644
> --- a/src/box/sql/select.c
> +++ b/src/box/sql/select.c
> @@ -1672,7 +1672,7 @@ columnTypeImpl(NameContext * pNC, Expr * pExpr
>   					    columnType(&sNC, p, &zOrigTab,
>   						       &zOrigCol);
>   				}
> -			} else if (pTab->pSchema) {
> +			} else {
>   				/* A real table */
>   				assert(!pS);
>   				assert(iCol >= 0 &&
> diff --git a/src/box/sql/sqliteInt.h b/src/box/sql/sqliteInt.h
> index d2ef85846..35d3f4cec 100644
> --- a/src/box/sql/sqliteInt.h
> +++ b/src/box/sql/sqliteInt.h
> @@ -1857,7 +1857,6 @@ struct Table {
>   	 * can be fetched from space struct.
>   	 */
>   	LogEst tuple_log_count;
> -	Schema *pSchema;	/* Schema that contains this table */
>   	Table *pNextZombie;	/* Next on the Parse.pZombieTab list */
>   	/** Space definition with Tarantool metadata. */
>   	struct space_def *def;
> 

^ permalink raw reply	[flat|nested] 28+ messages in thread

* [tarantool-patches] Re: [PATCH 1/7] sql: remove struct schema from struct Table
  2018-09-16 19:32     ` Vladislav Shpilevoy
@ 2018-09-19 10:58       ` n.pettik
  0 siblings, 0 replies; 28+ messages in thread
From: n.pettik @ 2018-09-19 10:58 UTC (permalink / raw)
  To: tarantool-patches; +Cc: Kirill Yukhin, Vladislav Shpilevoy


> Patchset LGTM. I've not reviewed it again, but the fixes
> look ok.

Kirill, is it OK to push? I’ve already rebased it on fresh 2.0

^ permalink raw reply	[flat|nested] 28+ messages in thread

* [tarantool-patches] Re: [PATCH 7/7] sql: finish DD integration
  2018-08-29  0:58     ` [tarantool-patches] " Vladislav Shpilevoy
@ 2018-09-20 14:45       ` Kirill Yukhin
  0 siblings, 0 replies; 28+ messages in thread
From: Kirill Yukhin @ 2018-09-20 14:45 UTC (permalink / raw)
  To: tarantool-patches; +Cc: Nikita Pettik

Hello,
On 28 авг 21:58, Vladislav Shpilevoy wrote:
> Thanks for the patch! See my fixes on the branch and below:

Due to some reason 0/7 mail was separated.

Anyway, I've checked in whole patches into 2.0 branch.

--
Regards, Kirill Yukhin

^ permalink raw reply	[flat|nested] 28+ messages in thread

end of thread, other threads:[~2018-09-20 14:45 UTC | newest]

Thread overview: 28+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2018-08-23 22:55 [tarantool-patches] [PATCH 0/7] Finish SQL DD integration Nikita Pettik
     [not found] ` <cover.1535064700.git.korablev@tarantool.org>
2018-08-23 22:55   ` [tarantool-patches] [PATCH 1/7] sql: remove struct schema from struct Table Nikita Pettik
2018-08-29  0:58     ` [tarantool-patches] " Vladislav Shpilevoy
2018-09-02 23:51       ` n.pettik
2018-09-16 19:32     ` Vladislav Shpilevoy
2018-09-19 10:58       ` n.pettik
2018-08-23 22:55   ` [tarantool-patches] [PATCH 2/7] sql: remove SQLite original struct Index Nikita Pettik
2018-08-29  0:58     ` [tarantool-patches] " Vladislav Shpilevoy
2018-09-02 23:51       ` n.pettik
2018-09-06 19:54         ` Vladislav Shpilevoy
2018-09-16 19:04           ` n.pettik
2018-08-23 22:55   ` [tarantool-patches] [PATCH 3/7] sql: remove struct Table from analyze routine Nikita Pettik
2018-08-29  0:58     ` [tarantool-patches] " Vladislav Shpilevoy
2018-09-02 23:52       ` n.pettik
2018-08-23 22:55   ` [tarantool-patches] [PATCH 4/7] sql: refactor ALTER RENAME code generation Nikita Pettik
2018-08-29  0:58     ` [tarantool-patches] " Vladislav Shpilevoy
2018-09-02 23:52       ` n.pettik
2018-08-23 22:55   ` [tarantool-patches] [PATCH 5/7] sql: remove lookups in Table hash Nikita Pettik
2018-08-29  0:58     ` [tarantool-patches] " Vladislav Shpilevoy
2018-09-02 23:52       ` n.pettik
2018-08-23 22:55   ` [tarantool-patches] [PATCH 6/7] sql: don't add system spaces to " Nikita Pettik
2018-08-29  0:58     ` [tarantool-patches] " Vladislav Shpilevoy
2018-09-02 23:52       ` n.pettik
2018-09-06 19:54         ` Vladislav Shpilevoy
2018-09-16 19:04           ` n.pettik
2018-08-23 22:55   ` [tarantool-patches] [PATCH 7/7] sql: finish DD integration Nikita Pettik
2018-08-29  0:58     ` [tarantool-patches] " Vladislav Shpilevoy
2018-09-20 14:45       ` Kirill Yukhin

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox