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

n.pettik korablev at tarantool.org
Mon Sep 3 02:51:58 MSK 2018


>> --- 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 at 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()






More information about the Tarantool-patches mailing list