<html>
  <head>

    <meta http-equiv="content-type" content="text/html; charset=utf-8">
  </head>
  <body text="#000000" bgcolor="#FFFFFF">
    <tt>Sorry, in both previous letters patch was not properly
      formatted. Now I checked it<br>
      sending it to myself, it should be ok.<br>
    </tt>
    <pre><tt>--
sql: add index_def to Index

Now every sqlite struct Index is created with tnt struct
index_def inside. This allows us to use tnt index_def
in work with sqlite indexes in the same manner as with
tnt index and is a step to remove sqlite Index with
tnt index.
Fields coll_array, coll_id_array, aiColumn, sort_order,
aiRowLogEst and zName are removed from Index. All usages
of this fields changed to usage of corresponding index_def
or index_def->opts fields.
index_is_unique(), sql_index_collation() and
index_column_count() are removed with calls of
index_def corresponding fields.
Also there is small change in user-visible behavior:
before the patch a statement like
CREATE TABLE t1(a,b, PRIMARY KEY(a,b), UNIQUE(a,b))
created only one constraint index (for primary key)
and no index for UNIQUE constraint (since it is upon the
same columns), neither it is named or non-named constraint.
After the patch index will be always created for named
constraints. It is a temporary solution. In future it's
preferable not to create an index, but to make some record
in _constraints space that this named unique constraint
implemented with the same index as primary key constraint.

Closes: #3369, #3498
---
</tt>
<tt>Branch:
<a class="moz-txt-link-freetext" href="https://github.com/tarantool/tarantool/tree/sb/gh-3369-use-index-def-in-select-and-where">https://github.com/tarantool/tarantool/tree/sb/gh-3369-use-index-def-in-select-and-where</a>  
Issue:<a class="moz-txt-link-freetext" href="https://github.com/tarantool/tarantool/issues/3369">https://github.com/tarantool/tarantool/issues/3369</a>

 src/box/errcode.h                           |   1 +
 src/box/sql.c                               |  54 +-
 src/box/sql/analyze.c                       |  87 ++-
 src/box/sql/build.c                         | 847 ++++++++++++----------------
 src/box/sql/delete.c                        |  10 +-
 src/box/sql/expr.c                          |  61 +-
 src/box/sql/fkey.c                          | 216 +++----
 src/box/sql/insert.c                        | 147 +++--
 src/box/sql/parse.y                         |  14 +-
 src/box/sql/pragma.c                        |  34 +-
 src/box/sql/select.c                        |   2 +-
 src/box/sql/sqliteInt.h                     | 154 ++---
 src/box/sql/update.c                        |  39 +-
 src/box/sql/vdbeaux.c                       |   2 +-
 src/box/sql/vdbemem.c                       |  21 +-
 src/box/sql/where.c                         | 225 ++++----
 src/box/sql/wherecode.c                     | 102 ++--
 test/box/misc.result                        |   1 +
 test/sql-tap/analyze6.test.lua              |   6 +-
 test/sql-tap/collation.test.lua             |   7 +-
 test/sql-tap/colname.test.lua               |   4 +-
 test/sql-tap/gh-2931-savepoints.test.lua    |   2 +-
 test/sql-tap/gh2140-trans.test.lua          |   2 +-
 test/sql-tap/gh2259-in-stmt-trans.test.lua  |   8 +-
 test/sql-tap/gh2964-abort.test.lua          |   2 +-
 test/sql-tap/identifier-characters.test.lua |   2 +-
 test/sql-tap/identifier_case.test.lua       |   4 +-
 test/sql-tap/index1.test.lua                |  32 +-
 test/sql-tap/index7.test.lua                |  74 ++-
 test/sql-tap/intpkey.test.lua               |   4 +-
 test/sql-tap/misc1.test.lua                 |   2 +-
 test/sql-tap/unique.test.lua                |   8 +-
 test/sql-tap/update.test.lua                |   6 +-
 test/sql/insert-unique.result               |   3 +-
 test/sql/iproto.result                      |   2 +-
 test/sql/message-func-indexes.result        |   8 +-
 test/sql/on-conflict.result                 |   2 +-
 test/sql/persistency.result                 |   6 +-
 test/sql/transition.result                  |   6 +-
 39 files changed, 1083 insertions(+), 1124 deletions(-)

diff --git a/src/box/errcode.h b/src/box/errcode.h
index c76018cbf..2229c5cbd 100644
--- a/src/box/errcode.h
+++ b/src/box/errcode.h
@@ -215,6 +215,7 @@ struct errcode_record {
        /*160 */_(ER_ACTION_MISMATCH,           "Field %d contains %s on conflict action, but %s in index parts") \
        /*161 */_(ER_VIEW_MISSING_SQL,          "Space declared as a view must have SQL statement") \
        /*162 */_(ER_FOREIGN_KEY_CONSTRAINT,    "Can not commit transaction: deferred foreign keys violations are not resolved") \
+       /*163 */_(ER_NO_SUCH_COLLATION,         "Collation '%s' does not exist") \
 
 /*
  * !IMPORTANT! Please follow instructions at start of the file
diff --git a/src/box/sql.c b/src/box/sql.c
index 063743e87..142dcc2da 100644
--- a/src/box/sql.c
+++ b/src/box/sql.c
@@ -1420,8 +1420,8 @@ int tarantoolSqlite3MakeTableFormat(Table *pTable, void *buf)
 
        /* If table's PK is single column which is INTEGER, then
         * treat it as strict type, not affinity.  */
-       if (pk_idx && pk_idx->nColumn == 1) {
-               int pk = pk_idx->aiColumn[0];
+       if (pk_idx != NULL && pk_idx->def->key_def->part_count == 1) {
+               int pk = pk_idx->def->key_def->parts[0].fieldno;
                if (def->fields[pk].type == FIELD_TYPE_INTEGER)
                        pk_forced_int = pk;
        }
@@ -1532,20 +1532,19 @@ tarantoolSqlite3MakeTableOpts(Table *pTable, const char *zSql, char *buf)
  */
 int tarantoolSqlite3MakeIdxParts(SqliteIndex *pIndex, void *buf)
 {
-       struct space_def *def = pIndex->pTable->def;
-       assert(def != NULL);
+       struct field_def *fields = pIndex->pTable->def->fields;
+       struct key_def *key_def = pIndex->def->key_def;
        const struct Enc *enc = get_enc(buf);
-       struct SqliteIndex *primary_index;
-       char *base = buf, *p;
-       int pk_forced_int = -1;
-
-       primary_index = sqlite3PrimaryKeyIndex(pIndex->pTable);
+       char *base = buf;
+       uint32_t pk_forced_int = UINT32_MAX;
+       struct SqliteIndex *primary_index =
+               sqlite3PrimaryKeyIndex(pIndex->pTable);
 
        /* If table's PK is single column which is INTEGER, then
         * treat it as strict type, not affinity.  */
-       if (primary_index->nColumn == 1) {
-               int pk = primary_index->aiColumn[0];
-               if (def->fields[pk].type == FIELD_TYPE_INTEGER)
+       if (primary_index->def->key_def->part_count == 1) {
+               int pk = primary_index->def->key_def->parts[0].fieldno;
+               if (fields[pk].type == FIELD_TYPE_INTEGER)
                        pk_forced_int = pk;
        }
 
@@ -1555,46 +1554,45 @@ int tarantoolSqlite3MakeIdxParts(SqliteIndex *pIndex, void *buf)
         * primary key columns. Query planner depends on this particular
         * data layout.
         */
-       int i, n = pIndex->nColumn;
-
-       p = enc->encode_array(base, n);
-       for (i = 0; i < n; i++) {
-               int col = pIndex->aiColumn[i];
-               assert(def->fields[col].is_nullable ==
-                      action_is_nullable(def->fields[col].nullable_action));
+       struct key_part *part = key_def->parts;
+       char *p = enc->encode_array(base, key_def->part_count);
+       for (uint32_t i = 0; i < key_def->part_count; ++i, ++part) {
+               uint32_t col = part->fieldno;
+               assert(fields[col].is_nullable ==
+                      action_is_nullable(fields[col].nullable_action));
                const char *t;
                if (pk_forced_int == col) {
                        t = "integer";
                } else {
-                       enum affinity_type affinity = def->fields[col].affinity;
-                       t = convertSqliteAffinity(affinity,
-                                                 def->fields[col].is_nullable);
+                       t = convertSqliteAffinity(fields[col].affinity,
+                                                 fields[col].is_nullable);
                }
                /* do not decode default collation */
-               uint32_t cid = pIndex->coll_id_array[i];
+               uint32_t cid = part->coll_id;
                p = enc->encode_map(p, cid == COLL_NONE ? 5 : 6);
                p = enc->encode_str(p, "type", sizeof("type")-1);
                p = enc->encode_str(p, t, strlen(t));
                p = enc->encode_str(p, "field", sizeof("field")-1);
                p = enc->encode_uint(p, col);
                if (cid != COLL_NONE) {
-                       p = enc->encode_str(p, "collation", sizeof("collation")-1);
+                       p = enc->encode_str(p, "collation",
+                                           sizeof("collation") - 1);
                        p = enc->encode_uint(p, cid);
                }
                p = enc->encode_str(p, "is_nullable", 11);
-               p = enc->encode_bool(p, def->fields[col].is_nullable);
+               p = enc->encode_bool(p, fields[col].is_nullable);
                p = enc->encode_str(p, "nullable_action", 15);
                const char *action_str =
-                       on_conflict_action_strs[def->fields[col].nullable_action];
+                       on_conflict_action_strs[fields[col].nullable_action];
                p = enc->encode_str(p, action_str, strlen(action_str));
 
                p = enc->encode_str(p, "sort_order", 10);
-               enum sort_order sort_order = pIndex->sort_order[i];
+               enum sort_order sort_order = part->sort_order;
                assert(sort_order < sort_order_MAX);
                const char *sort_order_str = sort_order_strs[sort_order];
                p = enc->encode_str(p, sort_order_str, strlen(sort_order_str));
        }
-       return (int)(p - base);
+       return p - base;
 }
 
 /*
diff --git a/src/box/sql/analyze.c b/src/box/sql/analyze.c
index 5f73f026e..82fcad859 100644
--- a/src/box/sql/analyze.c
+++ b/src/box/sql/analyze.c
@@ -848,8 +848,7 @@ analyzeOneTable(Parse * pParse,     /* Parser context */
        for (pIdx = pTab->pIndex; pIdx; pIdx = pIdx->pNext) {
                int addrRewind; /* Address of "OP_Rewind iIdxCur" */
                int addrNextRow;        /* Address of "next_row:" */
-               const char *zIdxName;   /* Name of the index */
-               int nColTest;   /* Number of columns to test for changes */
+               const char *idx_name;   /* Name of the index */
 
                if (pOnlyIdx && pOnlyIdx != pIdx)
                        continue;
@@ -857,17 +856,16 @@ analyzeOneTable(Parse * pParse,   /* Parser context */
                 * names. Thus, for the sake of clarity, use
                 * instead more familiar table name.
                 */
-               if (IsPrimaryKeyIndex(pIdx)) {
-                       zIdxName = pTab->def->name;
-               } else {
-                       zIdxName = pIdx->zName;
-               }
-               nColTest = index_column_count(pIdx);
+               if (IsPrimaryKeyIndex(pIdx))
+                       idx_name = pTab->def->name;
+               else
+                       idx_name = pIdx->def->name;
+               int part_count = pIdx->def->key_def->part_count;
 
                /* Populate the register containing the index name. */
-               sqlite3VdbeLoadString(v, regIdxname, zIdxName);
+               sqlite3VdbeLoadString(v, regIdxname, idx_name);
                VdbeComment((v, "Analysis for %s.%s", pTab->def->name,
-                       zIdxName));
+                           idx_name));
 
                /*
                 * Pseudo-code for loop that calls stat_push():
@@ -906,7 +904,7 @@ analyzeOneTable(Parse * pParse,     /* Parser context */
                 * when building a record to insert into the sample column of
                 * the _sql_stat4 table).
                 */
-               pParse->nMem = MAX(pParse->nMem, regPrev + nColTest);
+               pParse->nMem = MAX(pParse->nMem, regPrev + part_count);
 
                /* Open a read-only cursor on the index being analyzed. */
                struct space *space =
@@ -917,7 +915,7 @@ analyzeOneTable(Parse * pParse,     /* Parser context */
                sqlite3VdbeAddOp3(v, OP_OpenRead, iIdxCur, pIdx->tnum,
                                  space_ptr_reg);
                sql_vdbe_set_p4_key_def(pParse, pIdx);
-               VdbeComment((v, "%s", pIdx->zName));
+               VdbeComment((v, "%s", pIdx->def->name));
 
                /* Invoke the stat_init() function. The arguments are:
                 *
@@ -930,8 +928,8 @@ analyzeOneTable(Parse * pParse,     /* Parser context */
                 * The third argument is only used for STAT4
                 */
                sqlite3VdbeAddOp2(v, OP_Count, iIdxCur, regStat4 + 3);
-               sqlite3VdbeAddOp2(v, OP_Integer, nColTest, regStat4 + 1);
-               sqlite3VdbeAddOp2(v, OP_Integer, nColTest, regStat4 + 2);
+               sqlite3VdbeAddOp2(v, OP_Integer, part_count, regStat4 + 1);
+               sqlite3VdbeAddOp2(v, OP_Integer, part_count, regStat4 + 2);
                sqlite3VdbeAddOp4(v, OP_Function0, 0, regStat4 + 1, regStat4,
                                  (char *)&statInitFuncdef, P4_FUNCDEF);
                sqlite3VdbeChangeP5(v, 3);
@@ -949,11 +947,11 @@ analyzeOneTable(Parse * pParse,   /* Parser context */
                sqlite3VdbeAddOp2(v, OP_Integer, 0, regChng);
                addrNextRow = sqlite3VdbeCurrentAddr(v);
 
-               if (nColTest > 0) {
+               if (part_count > 0) {
                        int endDistinctTest = sqlite3VdbeMakeLabel(v);
                        int *aGotoChng; /* Array of jump instruction addresses */
                        aGotoChng =
-                           sqlite3DbMallocRawNN(db, sizeof(int) * nColTest);
+                           sqlite3DbMallocRawNN(db, sizeof(int) * part_count);
                        if (aGotoChng == 0)
                                continue;
 
@@ -969,7 +967,7 @@ analyzeOneTable(Parse * pParse,     /* Parser context */
                         */
                        sqlite3VdbeAddOp0(v, OP_Goto);
                        addrNextRow = sqlite3VdbeCurrentAddr(v);
-                       if (nColTest == 1 && index_is_unique(pIdx)) {
+                       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.
@@ -978,13 +976,12 @@ analyzeOneTable(Parse * pParse,   /* Parser context */
                                                  endDistinctTest);
                                VdbeCoverage(v);
                        }
-                       for (i = 0; i < nColTest; i++) {
-                               uint32_t id;
-                               struct coll *coll =
-                                       sql_index_collation(pIdx, i, &id);
+                       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,
-                                                 pIdx->aiColumn[i], regTemp);
+                                                 part->fieldno, regTemp);
                                aGotoChng[i] =
                                    sqlite3VdbeAddOp4(v, OP_Ne, regTemp, 0,
                                                      regPrev + i, (char *)coll,
@@ -992,7 +989,7 @@ analyzeOneTable(Parse * pParse,     /* Parser context */
                                sqlite3VdbeChangeP5(v, SQLITE_NULLEQ);
                                VdbeCoverage(v);
                        }
-                       sqlite3VdbeAddOp2(v, OP_Integer, nColTest, regChng);
+                       sqlite3VdbeAddOp2(v, OP_Integer, part_count, regChng);
                        sqlite3VdbeGoto(v, endDistinctTest);
 
                        /*
@@ -1003,11 +1000,11 @@ analyzeOneTable(Parse * pParse, /* Parser context */
                         *  ...
                         */
                        sqlite3VdbeJumpHere(v, addrNextRow - 1);
-                       for (i = 0; i < nColTest; i++) {
+                       part = pIdx->def->key_def->parts;
+                       for (i = 0; i < part_count; ++i, ++part) {
                                sqlite3VdbeJumpHere(v, aGotoChng[i]);
                                sqlite3VdbeAddOp3(v, OP_Column, iIdxCur,
-                                                 pIdx->aiColumn[i],
-                                                 regPrev + i);
+                                                 part->fieldno, regPrev + i);
                        }
                        sqlite3VdbeResolveLabel(v, endDistinctTest);
                        sqlite3DbFree(db, aGotoChng);
@@ -1022,19 +1019,18 @@ analyzeOneTable(Parse * pParse, /* Parser context */
                 */
                assert(regKey == (regStat4 + 2));
                Index *pPk = sqlite3PrimaryKeyIndex(pIdx->pTable);
-               int j, k, regKeyStat;
-               int nPkColumn = (int)index_column_count(pPk);
-               regKeyStat = sqlite3GetTempRange(pParse, nPkColumn);
-               for (j = 0; j < nPkColumn; j++) {
-                       k = pPk->aiColumn[j];
-                       assert(k >= 0 && k < (int)pTab->def->field_count);
-                       sqlite3VdbeAddOp3(v, OP_Column, iIdxCur, k, regKeyStat + j);
-                       VdbeComment((v, "%s",
-                               pTab->def->fields[pPk->aiColumn[j]].name));
+               uint32_t pk_part_count = pPk->def->key_def->part_count;
+               int regKeyStat = sqlite3GetTempRange(pParse, pk_part_count);
+               for (uint32_t j = 0; j < pk_part_count; ++j) {
+                       uint32_t k = pPk->def->key_def->parts[j].fieldno;
+                       assert(k < pTab->def->field_count);
+                       sqlite3VdbeAddOp3(v, OP_Column, iIdxCur, k,
+                                         regKeyStat + j);
+                       VdbeComment((v, "%s", pTab->def->fields[k].name));
                }
                sqlite3VdbeAddOp3(v, OP_MakeRecord, regKeyStat,
-                                 nPkColumn, regKey);
-               sqlite3ReleaseTempRange(pParse, regKeyStat, nPkColumn);
+                                 pk_part_count, regKey);
+               sqlite3ReleaseTempRange(pParse, regKeyStat, pk_part_count);
 
                assert(regChng == (regStat4 + 1));
                sqlite3VdbeAddOp4(v, OP_Function0, 1, regStat4, regTemp,
@@ -1057,11 +1053,11 @@ analyzeOneTable(Parse * pParse, /* Parser context */
                int regDLt = regStat1 + 2;
                int regSample = regStat1 + 3;
                int regCol = regStat1 + 4;
-               int regSampleKey = regCol + nColTest;
+               int regSampleKey = regCol + part_count;
                int addrNext;
                int addrIsNull;
 
-               pParse->nMem = MAX(pParse->nMem, regCol + nColTest);
+               pParse->nMem = MAX(pParse->nMem, regCol + part_count);
 
                addrNext = sqlite3VdbeCurrentAddr(v);
                callStatGet(v, regStat4, STAT_GET_KEY, regSampleKey);
@@ -1077,12 +1073,11 @@ analyzeOneTable(Parse * pParse, /* Parser context */
                 * be taken
                 */
                VdbeCoverageNeverTaken(v);
-               for (i = 0; i < nColTest; i++) {
-                       sqlite3ExprCodeLoadIndexColumn(pParse, pIdx,
-                                                                        iTabCur, i,
-                                                                        regCol + i);
+               for (i = 0; i < part_count; i++) {
+                       sqlite3ExprCodeLoadIndexColumn(pParse, pIdx, iTabCur, i,
+                                                      regCol + i);
                }
-               sqlite3VdbeAddOp3(v, OP_MakeRecord, regCol, nColTest,
+               sqlite3VdbeAddOp3(v, OP_MakeRecord, regCol, part_count,
                                  regSample);
                sqlite3VdbeAddOp3(v, OP_MakeRecord, regTabname, 6, regTemp);
                sqlite3VdbeAddOp2(v, OP_IdxReplace, iStatCur + 1, regTemp);
@@ -1146,7 +1141,7 @@ analyzeTable(Parse * pParse, Table * pTab, Index * pOnlyIdx)
        iStatCur = pParse->nTab;
        pParse->nTab += 3;
        if (pOnlyIdx) {
-               openStatTable(pParse, iStatCur, pOnlyIdx->zName, "idx");
+               openStatTable(pParse, iStatCur, pOnlyIdx->def->name, "idx");
        } else {
                openStatTable(pParse, iStatCur, pTab->def->name, "tbl");
        }
@@ -1674,7 +1669,7 @@ index_field_tuple_est(struct Index *idx, uint32_t field)
 {
        struct space *space = space_by_id(SQLITE_PAGENO_TO_SPACEID(idx->tnum));
        if (space == NULL)
-               return idx->aiRowLogEst[field];
+               return idx->def->opts.stat->tuple_log_est[field];
        struct index *tnt_idx =
                space_index(space, SQLITE_PAGENO_TO_INDEXID(idx->tnum));
        assert(tnt_idx != NULL);
diff --git a/src/box/sql/build.c b/src/box/sql/build.c
index 0072f842e..a92078390 100644
--- a/src/box/sql/build.c
+++ b/src/box/sql/build.c
@@ -241,6 +241,8 @@ static void
 freeIndex(sqlite3 * db, Index * p)
 {
        sql_expr_delete(db, p->pPartIdxWhere, false);
+       if (p->def != NULL)
+               index_def_delete(p->def);
        sqlite3DbFree(db, p->zColAff);
        sqlite3DbFree(db, p);
 }
@@ -259,7 +261,8 @@ sqlite3UnlinkAndDeleteIndex(sqlite3 * db, Index * pIndex)
 
        struct session *user_session = current_session();
 
-       pIndex = sqlite3HashInsert(&pIndex->pTable->idxHash, pIndex->zName, 0);
+       pIndex = sqlite3HashInsert(&pIndex->pTable->idxHash,
+                                  pIndex->def->name, 0);
        if (ALWAYS(pIndex)) {
                if (pIndex->pTable->pIndex == pIndex) {
                        pIndex->pTable->pIndex = pIndex->pNext;
@@ -364,7 +367,7 @@ deleteTable(sqlite3 * db, Table * pTable)
                pNext = pIndex->pNext;
                assert(pIndex->pSchema == pTable->pSchema);
                if ((db == 0 || db->pnBytesFreed == 0)) {
-                       char *zName = pIndex->zName;
+                       char *zName = pIndex->def->name;
                        TESTONLY(Index *
                                 pOld =) sqlite3HashInsert(&pTable->idxHash,
                                                           zName, 0);
@@ -982,7 +985,8 @@ sqlite3AddPrimaryKey(Parse * pParse,        /* Parsing context */
                                "INTEGER PRIMARY KEY or INT PRIMARY KEY");
        } else {
                sql_create_index(pParse, 0, 0, pList, onError, 0,
-                                0, sortOrder, false, SQLITE_IDXTYPE_PRIMARYKEY);
+                                0, sortOrder, false,
+                                SQL_INDEX_TYPE_CONSTRAINT_PK);
                pList = 0;
        }
 
@@ -1029,7 +1033,7 @@ sqlite3AddCollateType(Parse * pParse, Token * pToken)
        Table *p = pParse->pNewTable;
        if (p == NULL)
                return;
-       int i = p->def->field_count - 1;
+       uint32_t i = p->def->field_count - 1;
        sqlite3 *db = pParse->db;
        char *zColl = sqlite3NameFromToken(db, pToken);
        if (!zColl)
@@ -1037,22 +1041,21 @@ sqlite3AddCollateType(Parse * pParse, Token * pToken)
        uint32_t *id = &p->def->fields[i].coll_id;
        p->aCol[i].coll = sql_get_coll_seq(pParse, zColl, id);
        if (p->aCol[i].coll != NULL) {
-               Index *pIdx;
                /* If the column is declared as "<name> PRIMARY KEY COLLATE <type>",
                 * then an index may have been created on this column before the
                 * collation type was added. Correct this if it is the case.
                 */
-               for (pIdx = p->pIndex; pIdx; pIdx = pIdx->pNext) {
-                       assert(pIdx->nColumn == 1);
-                       if (pIdx->aiColumn[0] == i) {
-                               id = &pIdx->coll_id_array[0];
-                               pIdx->coll_array[0] =
+               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) {
+                               id = &pIdx->def->key_def->parts[0].coll_id;
+                               pIdx->def->key_def->parts[0].coll =
                                        sql_column_collation(p->def, i, id);
                        }
                }
-       } else {
-               sqlite3DbFree(db, zColl);
        }
+       sqlite3DbFree(db, zColl);
 }
 
 struct coll *
@@ -1082,66 +1085,6 @@ sql_column_collation(struct space_def *def, uint32_t column, uint32_t *coll_id)
        return space->format->fields[column].coll;
 }
 
-struct key_def*
-sql_index_key_def(struct Index *idx)
-{
-       uint32_t space_id = SQLITE_PAGENO_TO_SPACEID(idx->tnum);
-       uint32_t index_id = SQLITE_PAGENO_TO_INDEXID(idx->tnum);
-       struct space *space = space_by_id(space_id);
-       assert(space != NULL);
-       struct index *index = space_index(space, index_id);
-       assert(index != NULL && index->def != NULL);
-       return index->def->key_def;
-}
-
-struct coll *
-sql_index_collation(Index *idx, uint32_t column, uint32_t *coll_id)
-{
-       assert(idx != NULL);
-       uint32_t space_id = SQLITE_PAGENO_TO_SPACEID(idx->pTable->tnum);
-       struct space *space = space_by_id(space_id);
-
-       assert(column < idx->nColumn);
-       /*
-        * If space is still under construction, or it is
-        * an ephemeral space, then fetch collation from
-        * SQL internal structure.
-        */
-       if (space == NULL) {
-               assert(column < idx->nColumn);
-               *coll_id = idx->coll_id_array[column];
-               return idx->coll_array[column];
-       }
-
-       struct key_def *key_def = sql_index_key_def(idx);
-       assert(key_def != NULL && key_def->part_count >= column);
-       *coll_id = key_def->parts[column].coll_id;
-       return key_def->parts[column].coll;
-}
-
-enum sort_order
-sql_index_column_sort_order(Index *idx, uint32_t column)
-{
-       assert(idx != NULL);
-       uint32_t space_id = SQLITE_PAGENO_TO_SPACEID(idx->pTable->tnum);
-       struct space *space = space_by_id(space_id);
-
-       assert(column < idx->nColumn);
-       /*
-        * If space is still under construction, or it is
-        * an ephemeral space, then fetch collation from
-        * SQL internal structure.
-        */
-       if (space == NULL) {
-               assert(column < idx->nColumn);
-               return idx->sort_order[column];
-       }
-
-       struct key_def *key_def = sql_index_key_def(idx);
-       assert(key_def != NULL && key_def->part_count >= column);
-       return key_def->parts[column].sort_order;
-}
-
 struct ExprList *
 space_checks_expr_list(uint32_t space_id)
 {
@@ -1325,17 +1268,6 @@ createTableStmt(sqlite3 * db, Table * p)
        return zStmt;
 }
 
-/* Return true if value x is found any of the first nCol entries of aiCol[]
- */
-static int
-hasColumn(const i16 * aiCol, int nCol, int x)
-{
-       while (nCol-- > 0)
-               if (x == *(aiCol++))
-                       return 1;
-       return 0;
-}
-
 /*
  * This routine runs at the end of parsing a CREATE TABLE statement.
  * The job of this routine is to convert both
@@ -1352,13 +1284,12 @@ static void
 convertToWithoutRowidTable(Parse * pParse, Table * pTab)
 {
        Index *pPk;
-       int i, j;
        sqlite3 *db = pParse->db;
 
        /* Mark every PRIMARY KEY column as NOT NULL (except for imposter tables)
         */
        if (!db->init.imposterTable) {
-               for (i = 0; i < (int)pTab->def->field_count; i++) {
+               for (uint32_t i = 0; i < pTab->def->field_count; i++) {
                        if (pTab->aCol[i].is_primkey) {
                                pTab->def->fields[i].nullable_action
                                        = ON_CONFLICT_ACTION_ABORT;
@@ -1383,27 +1314,13 @@ convertToWithoutRowidTable(Parse * pParse, Table * pTab)
                assert(pParse->pNewTable == pTab);
                sql_create_index(pParse, 0, 0, pList, pTab->keyConf, 0, 0,
                                 SORT_ORDER_ASC, false,
-                                SQLITE_IDXTYPE_PRIMARYKEY);
+                                SQL_INDEX_TYPE_CONSTRAINT_PK);
                if (db->mallocFailed)
                        return;
                pPk = sqlite3PrimaryKeyIndex(pTab);
                pTab->iPKey = -1;
        } else {
                pPk = sqlite3PrimaryKeyIndex(pTab);
-
-               /*
-                * Remove all redundant columns from the PRIMARY KEY.  For example, change
-                * "PRIMARY KEY(a,b,a,b,c,b,c,d)" into just "PRIMARY KEY(a,b,c,d)".  Later
-                * code assumes the PRIMARY KEY contains no repeated columns.
-                */
-               for (i = j = 1; i < pPk->nColumn; i++) {
-                       if (hasColumn(pPk->aiColumn, j, pPk->aiColumn[i])) {
-                               pPk->nColumn--;
-                       } else {
-                               pPk->aiColumn[j++] = pPk->aiColumn[i];
-                       }
-               }
-               pPk->nColumn = j;
        }
        assert(pPk != 0);
 }
@@ -1485,7 +1402,7 @@ createIndex(Parse * pParse, Index * pIndex, int iSpaceId, int iIndexId,
        }
        sqlite3VdbeAddOp4(v,
                          OP_String8, 0, iFirstCol + 2, 0,
-                         sqlite3DbStrDup(pParse->db, pIndex->zName),
+                         sqlite3DbStrDup(pParse->db, pIndex->def->name),
                          P4_DYNAMIC);
        sqlite3VdbeAddOp4(v, OP_String8, 0, iFirstCol + 3, 0, "tree",
                          P4_STATIC);
@@ -1502,7 +1419,8 @@ createIndex(Parse * pParse, Index * pIndex, int iSpaceId, int iIndexId,
         * autoindexes - they had been accounted as a part of
         * CREATE TABLE already.
         */
-       if (!pParse->nested && pIndex->idxType == SQLITE_IDXTYPE_APPDEF)
+       if (!pParse->nested && (pIndex->index_type == SQL_INDEX_TYPE_NON_UNIQUE
+                               || pIndex->index_type == SQL_INDEX_TYPE_UNIQUE))
                sqlite3VdbeChangeP5(v, OPFLAG_NCHANGE);
 }
 
@@ -1522,7 +1440,7 @@ makeIndexSchemaRecord(Parse * pParse,
 
        sqlite3VdbeAddOp4(v,
                          OP_String8, 0, iFirstCol, 0,
-                         sqlite3DbStrDup(pParse->db, pIndex->zName),
+                         sqlite3DbStrDup(pParse->db, pIndex->def->name),
                          P4_DYNAMIC);
 
        if (pParse->pNewTable) {
@@ -2452,15 +2370,16 @@ sqlite3RefillIndex(Parse * pParse, Index * pIndex, int memRootPage)
        } else {
                tnum = pIndex->tnum;
        }
-       struct key_def *def = key_def_dup(sql_index_key_def(pIndex));
+       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->nColumn,
-                         (char *)def, P4_KEYDEF);
+       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.
@@ -2491,7 +2410,8 @@ sqlite3RefillIndex(Parse * pParse, Index * pIndex, int memRootPage)
                sqlite3VdbeGoto(v, j2);
                addr2 = sqlite3VdbeCurrentAddr(v);
                sqlite3VdbeAddOp4Int(v, OP_SorterCompare, iSorter, j2,
-                                    regRecord, pIndex->nColumn);
+                                    regRecord,
+                                    pIndex->def->key_def->part_count);
                VdbeCoverage(v);
                parser_emit_unique_constraint(pParse, ON_CONFLICT_ACTION_ABORT,
                                              pIndex);
@@ -2511,44 +2431,11 @@ sqlite3RefillIndex(Parse * pParse, Index * pIndex, int memRootPage)
        sqlite3VdbeAddOp1(v, OP_Close, iSorter);
 }
 
-/*
- * Allocate heap space to hold an Index object with nCol columns.
- *
- * Increase the allocation size to provide an extra nExtra bytes
- * of 8-byte aligned space after the Index object and return a
- * pointer to this extra space in *ppExtra.
- */
-Index *
-sqlite3AllocateIndexObject(sqlite3 * db,       /* Database connection */
-                          i16 nCol,    /* Total number of columns in the index */
-                          int nExtra,  /* Number of bytes of extra space to alloc */
-                          char **ppExtra       /* Pointer to the "extra" space */
-    )
+struct Index *
+sql_index_alloc(struct sqlite3 *db)
 {
-       Index *p;               /* Allocated index object */
-       int nByte;              /* Bytes of space for Index object + arrays */
-
-       nByte = ROUND8(sizeof(Index)) +             /* Index structure   */
-           ROUND8(sizeof(struct coll *) * nCol) +  /* Index.coll_array  */
-           ROUND8(sizeof(uint32_t) * nCol) +       /* Index.coll_id_array*/
-           ROUND8(sizeof(LogEst) * (nCol + 1) +    /* Index.aiRowLogEst */
-                  sizeof(i16) * nCol +             /* Index.aiColumn    */
-                  sizeof(enum sort_order) * nCol); /* Index.sort_order  */
-       p = sqlite3DbMallocZero(db, nByte + nExtra);
-       if (p) {
-               char *pExtra = ((char *)p) + ROUND8(sizeof(Index));
-               p->coll_array = (struct coll **)pExtra;
-               pExtra += ROUND8(sizeof(struct coll **) * nCol);
-               p->coll_id_array = (uint32_t *) pExtra;
-               pExtra += ROUND8(sizeof(uint32_t) * nCol);
-               p->aiRowLogEst = (LogEst *) pExtra;
-               pExtra += sizeof(LogEst) * (nCol + 1);
-               p->aiColumn = (i16 *) pExtra;
-               pExtra += sizeof(i16) * nCol;
-               p->sort_order = (enum sort_order *) pExtra;
-               p->nColumn = nCol;
-               *ppExtra = ((char *)p) + nByte;
-       }
+       int index_size = ROUND8(sizeof(struct Index));
+       struct Index *p = sqlite3DbMallocZero(db, index_size);
        return p;
 }
 
@@ -2635,48 +2522,132 @@ addIndexToTable(Index * pIndex, Table * pTab)
        }
 }
 
-bool
-index_is_unique(Index *idx)
-{
-       assert(idx != NULL);
-       uint32_t space_id = SQLITE_PAGENO_TO_SPACEID(idx->tnum);
-       uint32_t index_id = SQLITE_PAGENO_TO_INDEXID(idx->tnum);
-       struct space *space = space_by_id(space_id);
-       assert(space != NULL);
-       struct index *tnt_index = space_index(space, index_id);
-       assert(tnt_index != NULL);
+/**
+ * Create and set index_def in the given Index.
+ *
+ * @param parse Parse context.
+ * @param index Index for which index_def should be created. It is
+ *              used only to set index_def at the end of the
+ *              function.
+ * @param table Table which is indexed by 'index' param.
+ * @param iid Index ID.
+ * @param name Index name.
+ * @param name_len Index name length.
+ * @param expr_list List of expressions, describe which columns
+ *                  of 'table' are used in index and also their
+ *                  collations, orders, etc.
+ * @param idx_type Index type: non-unique index, unique index,
+ *                 index implementing UNIQUE constraint or
+ *                 index implementing PK constraint.
+ * @param sql_stmt SQL statement, which creates the index.
+ * @retval 0 Success.
+ * @retval -1 Error.
+ */
+static int
+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)
+{
+       struct space_def *space_def = table->def;
+       struct index_opts opts;
+       index_opts_create(&opts);
+       opts.is_unique = idx_type != SQL_INDEX_TYPE_NON_UNIQUE;
+       opts.sql = sql_stmt;
+       index->def = NULL;
+       int rc = -1;
+
+       struct key_def *key_def = key_def_new(expr_list->nExpr);
+       if (key_def == NULL)
+               goto tnt_error;
+
+       for (int i = 0; i < expr_list->nExpr; i++) {
+               struct Expr *expr = expr_list->a[i].pExpr;
+               sql_resolve_self_reference(parse, table, NC_IdxExpr, expr, 0);
+               if (parse->nErr > 0)
+                       goto cleanup;
+
+               struct Expr *column_expr = sqlite3ExprSkipCollate(expr);
+               if (column_expr->op != TK_COLUMN) {
+                       diag_set(ClientError, ER_UNSUPPORTED, "Tarantool",
+                                "functional indexes");
+                       goto tnt_error;
+               }
+
+               uint32_t fieldno = column_expr->iColumn;
+               uint32_t coll_id;
+               struct coll *coll;
+               if (expr->op == TK_COLLATE) {
+                       coll = sql_get_coll_seq(parse, expr->u.zToken,
+                                               &coll_id);
+                       if (coll == NULL &&
+                           strcasecmp(expr->u.zToken, "binary") != 0) {
+                               diag_set(ClientError, ER_NO_SUCH_COLLATION,
+                                        expr->u.zToken);
+                               goto tnt_error;
+                       }
+               } else {
+                       coll = sql_column_collation(space_def, fieldno,
+                                                   &coll_id);
+               }
+               /*
+                * Tarantool: DESC indexes are not supported so
+                * far.
+                */
+               key_def_set_part(key_def, i, fieldno,
+                                space_def->fields[fieldno].type,
+                                space_def->fields[fieldno].nullable_action,
+                                coll, coll_id, SORT_ORDER_ASC);
+       }
+       if (parse->nErr > 0)
+               goto cleanup;
 
-       return tnt_index->def->opts.is_unique;
+       struct key_def *pk_key_def;
+       if (idx_type == SQL_INDEX_TYPE_UNIQUE ||
+           idx_type == SQL_INDEX_TYPE_NON_UNIQUE)
+               pk_key_def = table->pIndex->def->key_def;
+       else
+               pk_key_def = NULL;
+
+       index->def = index_def_new(space_def->id, iid, name, name_len, TREE,
+                                  &opts, key_def, pk_key_def);
+       if (index->def == NULL)
+               goto tnt_error;
+       rc = 0;
+cleanup:
+       if (key_def != NULL)
+               key_def_delete(key_def);
+       return rc;
+tnt_error:
+       parse->rc = SQL_TARANTOOL_ERROR;
+       ++parse->nErr;
+       goto cleanup;
 }
 
 void
 sql_create_index(struct Parse *parse, struct Token *token,
                 struct SrcList *tbl_name, struct ExprList *col_list,
-                int on_error, struct Token *start, struct Expr *where,
-                enum sort_order sort_order, bool if_not_exist, u8 idx_type)
-{
-       Table *pTab = 0;        /* Table to be indexed */
-       Index *pIndex = 0;      /* The index to be created */
-       char *zName = 0;        /* Name of the index */
-       int nName;              /* Number of characters in zName */
-       int i, j;
-       DbFixer sFix;           /* For assigning database names to pTable */
-       sqlite3 *db = parse->db;
-       struct ExprList_item *col_listItem;     /* For looping over col_list */
-       int nExtra = 0;         /* Space allocated for zExtra[] */
-       char *zExtra = 0;       /* Extra space after the Index object */
+                enum on_conflict_action on_error, struct Token *start,
+                struct Expr *where, enum sort_order sort_order,
+                bool if_not_exist, enum sql_index_type idx_type) {
+       /* The index to be created. */
+       struct Index *index = NULL;
+       /* Name of the index. */
+       char *name = NULL;
+       struct sqlite3 *db = parse->db;
        struct session *user_session = current_session();
 
-       if (db->mallocFailed || parse->nErr > 0) {
+       if (db->mallocFailed || parse->nErr > 0)
                goto exit_create_index;
-       }
-       /* Do not account nested operations: the count of such
-        * operations depends on Tarantool data dictionary internals,
-        * such as data layout in system spaces. Also do not account
-        * PRIMARY KEY and UNIQUE constraint - they had been accounted
-        * in CREATE TABLE already.
+       /*
+        * Do not account nested operations: the count of such
+        * operations depends on Tarantool data dictionary
+        * internals, such as data layout in system spaces. Also
+        * do not account PRIMARY KEY and UNIQUE constraint - they
+        * had been accounted in CREATE TABLE already.
         */
-       if (!parse->nested && idx_type == SQLITE_IDXTYPE_APPDEF) {
+       if (!parse->nested && (idx_type == SQL_INDEX_TYPE_UNIQUE ||
+                              idx_type == SQL_INDEX_TYPE_NON_UNIQUE)) {
                Vdbe *v = sqlite3GetVdbe(parse);
                if (v == NULL)
                        goto exit_create_index;
@@ -2685,39 +2656,30 @@ sql_create_index(struct Parse *parse, struct Token *token,
        assert(db->pSchema != NULL);
 
        /*
-        * Find the table that is to be indexed.  Return early if not found.
+        * Find the table that is to be indexed.
+        * Return early if not found.
         */
+       struct Table *table = NULL;
        if (tbl_name != NULL) {
-
-               /* Use the two-part index name to determine the database
-                * to search for the table. 'Fix' the table name to this db
-                * before looking up the table.
+               /*
+                * Use the two-part index name to determine the
+                * database to search for the table. 'Fix' the
+                * table name to this db before looking up the
+                * table.
                 */
-               assert(token && token->z);
-
-               sqlite3FixInit(&sFix, parse, "index", token);
-               if (sqlite3FixSrcList(&sFix, tbl_name)) {
-                       /* Because the parser constructs tbl_name from a single identifier,
-                        * sqlite3FixSrcList can never fail.
-                        */
-                       assert(0);
-               }
-               pTab = sqlite3LocateTable(parse, 0, tbl_name->a[0].zName);
-               assert(db->mallocFailed == 0 || pTab == 0);
-               if (pTab == 0)
-                       goto exit_create_index;
-               sqlite3PrimaryKeyIndex(pTab);
+               assert(token != NULL && token->z != NULL);
+               table = sqlite3LocateTable(parse, 0, tbl_name->a[0].zName);
+               assert(db->mallocFailed == 0 || table == NULL);
        } else {
                assert(token == NULL);
                assert(start == NULL);
-               pTab = parse->pNewTable;
-               if (!pTab)
-                       goto exit_create_index;
+               table = parse->pNewTable;
        }
 
-       assert(pTab != 0);
-       assert(parse->nErr == 0);
-       if (pTab->def->opts.is_view) {
+       if (table == NULL || parse->nErr > 0)
+               goto exit_create_index;
+
+       if (table->def->opts.is_view) {
                sqlite3ErrorMsg(parse, "views may not be indexed");
                goto exit_create_index;
        }
@@ -2734,45 +2696,68 @@ sql_create_index(struct Parse *parse, struct Token *token,
         * If token == NULL it means that we are dealing with a
         * primary key or UNIQUE constraint.  We have to invent
         * our own name.
+        *
+        * In case of UNIQUE constraint we have two options:
+        * 1) UNIQUE constraint is named and this name will
+        *    be a part of index name.
+        * 2) UNIQUE constraint is non-named and standard
+        *    auto-index name will be generated.
         */
-       if (token) {
-               zName = sqlite3NameFromToken(db, token);
-               if (zName == 0)
+       bool is_constraint_named = false;
+       if (token != NULL) {
+               name = sqlite3NameFromToken(db, token);
+               if (name == NULL)
                        goto exit_create_index;
-               assert(token->z != 0);
-               if (!db->init.busy) {
-                       if (sqlite3HashFind(&db->pSchema->tblHash, zName) !=
-                           NULL) {
-                               sqlite3ErrorMsg(parse,
-                                               "there is already a table named %s",
-                                               zName);
-                               goto exit_create_index;
-                       }
-               }
-               if (sqlite3HashFind(&pTab->idxHash, zName) != NULL) {
+               assert(token->z != NULL);
+               if (sqlite3HashFind(&table->idxHash, name) != NULL) {
                        if (!if_not_exist) {
                                sqlite3ErrorMsg(parse,
                                                "index %s.%s already exists",
-                                               pTab->def->name, zName);
+                                               table->def->name, name);
                        } else {
                                assert(!db->init.busy);
                        }
                        goto exit_create_index;
                }
        } else {
-               int n;
-               Index *pLoop;
-               for (pLoop = pTab->pIndex, n = 1; pLoop;
-                    pLoop = pLoop->pNext, n++) {
+               char *constraint_name = NULL;
+               if (parse->constraintName.z != NULL)
+                       constraint_name =
+                               sqlite3NameFromToken(db, &parse->constraintName);
+
+               if (constraint_name == NULL ||
+                   strcmp(constraint_name, "") == 0) {
+                       int n = 1;
+                       for (struct Index *idx = table->pIndex;
+                            idx != NULL;
+                            idx = idx->pNext, n++) {
+                       }
+                       name = sqlite3MPrintf(db, "sql_autoindex_%s_%d",
+                                             table->def->name, n);
+               } else {
+                       name = sqlite3MPrintf(db, "sql_autoindex_%s",
+                                             constraint_name);
+                       is_constraint_named = true;
                }
-               zName =
-                   sqlite3MPrintf(db, "sqlite_autoindex_%s_%d", pTab->def->name,
-                                  n);
-               if (zName == 0) {
-                       goto exit_create_index;
+               if (constraint_name != NULL) {
+                       sqlite3DbFree(db, constraint_name);
                }
        }
 
+       if (name == NULL)
+               goto exit_create_index;
+
+       bool is_system_space = BOX_SYSTEM_ID_MIN < table->def->id &&
+                              table->def->id < BOX_SYSTEM_ID_MAX;
+       if (is_system_space && (idx_type == SQL_INDEX_TYPE_NON_UNIQUE ||
+                               idx_type == SQL_INDEX_TYPE_UNIQUE)) {
+               diag_set(ClientError, ER_MODIFY_INDEX, name, table->def->name,
+                        "can't create index on system space");
+               parse->nErr++;
+               parse->rc = SQL_TARANTOOL_ERROR;
+               goto exit_create_index;
+       }
+
        /*
         * If col_list == NULL, it means this routine was called
         * to make a primary key out of the last column added to
@@ -2780,12 +2765,12 @@ sql_create_index(struct Parse *parse, struct Token *token,
         * simulate this.
         */
        if (col_list == NULL) {
-               Token prevCol;
-               uint32_t last_field = pTab->def->field_count - 1;
-               sqlite3TokenInit(&prevCol, pTab->def->fields[last_field].name);
+               struct Token prev_col;
+               uint32_t last_field = table->def->field_count - 1;
+               sqlite3TokenInit(&prev_col, table->def->fields[last_field].name);
                col_list = sql_expr_list_append(parse->db, NULL,
                                                sqlite3ExprAlloc(db, TK_ID,
-                                                                &prevCol, 0));
+                                                                &prev_col, 0));
                if (col_list == NULL)
                        goto exit_create_index;
                assert(col_list->nExpr == 1);
@@ -2794,191 +2779,160 @@ sql_create_index(struct Parse *parse, struct Token *token,
                sqlite3ExprListCheckLength(parse, col_list, "index");
        }
 
-       /* Figure out how many bytes of space are required to store explicitly
-        * specified collation sequence names.
-        */
-       for (i = 0; i < col_list->nExpr; i++) {
-               Expr *pExpr = col_list->a[i].pExpr;
-               assert(pExpr != 0);
-               if (pExpr->op == TK_COLLATE) {
-                       nExtra += (1 + sqlite3Strlen30(pExpr->u.zToken));
-               }
-       }
+       if (sqlite3CheckIdentifierName(parse, name) != SQLITE_OK)
+               goto exit_create_index;
 
-       /*
-        * Allocate the index structure.
-        */
-       nName = sqlite3Strlen30(zName);
-       pIndex = sqlite3AllocateIndexObject(db, col_list->nExpr,
-                                           nName + nExtra + 1, &zExtra);
-       if (db->mallocFailed) {
+       index = sql_index_alloc(db);
+       if (index == NULL)
                goto exit_create_index;
-       }
-       assert(EIGHT_BYTE_ALIGNMENT(pIndex->aiRowLogEst));
-       assert(EIGHT_BYTE_ALIGNMENT(pIndex->coll_array));
-       pIndex->zName = zExtra;
-       zExtra += nName + 1;
-       memcpy(pIndex->zName, zName, nName + 1);
-       pIndex->pTable = pTab;
-       pIndex->onError = (u8) on_error;
-       /*
-        * Don't make difference between UNIQUE indexes made by user
-        * using CREATE INDEX statement and those created during
-        * CREATE TABLE processing.
-        */
-       if (idx_type == SQLITE_IDXTYPE_APPDEF &&
-           on_error != ON_CONFLICT_ACTION_NONE) {
-               pIndex->idxType = SQLITE_IDXTYPE_UNIQUE;
-       } else {
-               pIndex->idxType = idx_type;
-       }
-       pIndex->pSchema = db->pSchema;
-       pIndex->nColumn = col_list->nExpr;
-       /* Tarantool have access to each column by any index */
-       if (where) {
-               sql_resolve_self_reference(parse, pTab, NC_PartIdx, where,
+
+       index->pTable = table;
+       index->onError = (u8) on_error;
+       index->index_type = idx_type;
+       index->pSchema = db->pSchema;
+       /* Tarantool have access to each column by any index. */
+       if (where != NULL) {
+               sql_resolve_self_reference(parse, table, NC_PartIdx, where,
                                           NULL);
-               pIndex->pPartIdxWhere = where;
+               index->pPartIdxWhere = where;
                where = NULL;
        }
 
-       /* Analyze the list of expressions that form the terms of the index and
-        * report any errors.  In the common case where the expression is exactly
-        * a table column, store that column in aiColumn[].
-        *
-        * 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.
+       /*
+        * 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.
         */
-       for (i = 0, col_listItem = col_list->a; i < col_list->nExpr;
-            i++, col_listItem++) {
-               Expr *pCExpr;   /* The i-th index expression */
-               sql_resolve_self_reference(parse, pTab, NC_IdxExpr,
-                                          col_listItem->pExpr, NULL);
-               if (parse->nErr > 0)
-                       goto exit_create_index;
-               pCExpr = sqlite3ExprSkipCollate(col_listItem->pExpr);
-               if (pCExpr->op != TK_COLUMN) {
-                       sqlite3ErrorMsg(parse,
-                                       "functional indexes aren't supported "
-                                       "in the current version");
+
+       char *sql_stmt = "";
+       if (!db->init.busy && tbl_name != NULL) {
+               int n = (int) (parse->sLastToken.z - token->z) +
+                       parse->sLastToken.n;
+               if (token->z[n - 1] == ';')
+                       n--;
+               sql_stmt = sqlite3MPrintf(db, "CREATE%s INDEX %.*s",
+                                         idx_type == SQL_INDEX_TYPE_NON_UNIQUE ?
+                                         "" : " UNIQUE", n, token->z);
+               if (db->mallocFailed || sql_stmt == NULL)
                        goto exit_create_index;
-               } else {
-                       j = pCExpr->iColumn;
-                       assert(j <= 0x7fff);
-                       if (j < 0) {
-                               j = pTab->iPKey;
-                       }
-                       pIndex->aiColumn[i] = (i16) j;
-               }
-               struct coll *coll;
-               uint32_t id;
-               if (col_listItem->pExpr->op == TK_COLLATE) {
-                       const char *coll_name = col_listItem->pExpr->u.zToken;
-                       coll = sql_get_coll_seq(parse, coll_name, &id);
+       }
 
-                       if (coll == NULL &&
-                           sqlite3StrICmp(coll_name, "binary") != 0) {
-                               goto exit_create_index;
-                       }
-               } else if (j >= 0) {
-                       coll = sql_column_collation(pTab->def, j, &id);
-               } else {
-                       id = COLL_NONE;
-                       coll = NULL;
+       /* If it is parsing stage, then iid may have any value. */
+       uint32_t iid = 1;
+       if (db->init.busy)
+               iid = SQLITE_PAGENO_TO_INDEXID(db->init.newTnum);
+
+       if (index_fill_def(parse, index, table, iid, name, strlen(name),
+                          col_list, idx_type, sql_stmt) != 0)
+               goto exit_create_index;
+       /*
+        * Remove all redundant columns from the PRIMARY KEY.
+        * For example, change "PRIMARY KEY(a,b,a,b,c,b,c,d)" into
+        * just "PRIMARY KEY(a,b,c,d)". Later code assumes the
+        * PRIMARY KEY contains no repeated columns.
+        */
+       struct key_part *parts = index->def->key_def->parts;
+       uint32_t part_count = index->def->key_def->part_count;
+       uint32_t new_part_count = 1;
+       for(uint32_t i = 1; i < part_count; i++) {
+               uint32_t j;
+               for(j = 0; j < new_part_count; j++) {
+                       if(parts[i].fieldno == parts[j].fieldno)
+                               break;
                }
-               pIndex->coll_array[i] = coll;
-               pIndex->coll_id_array[i] = id;
 
-               /* Tarantool: DESC indexes are not supported so far.
-                * See gh-3016.
-                */
-               pIndex->sort_order[i] = SORT_ORDER_ASC;
+               if (j == new_part_count)
+                       parts[new_part_count++] = parts[i];
        }
-       if (pTab == parse->pNewTable) {
-               /* This routine has been called to create an automatic index as a
-                * result of a PRIMARY KEY or UNIQUE clause on a column definition, or
-                * a PRIMARY KEY or UNIQUE clause following the column definitions.
-                * i.e. one of:
-                *
-                * CREATE TABLE t(x PRIMARY KEY, y);
-                * CREATE TABLE t(x, y, UNIQUE(x, y));
-                *
-                * Either way, check to see if the table already has such an index. If
-                * so, don't bother creating this one. This only applies to
-                * automatically created indices. Users can do as they wish with
-                * explicit indices.
-                *
-                * Two UNIQUE or PRIMARY KEY constraints are considered equivalent
-                * (and thus suppressing the second one) even if they have different
-                * sort orders.
-                *
-                * If there are different collating sequences or if the columns of
-                * the constraint occur in different orders, then the constraints are
-                * considered distinct and both result in separate indices.
-                */
-               Index *pIdx;
-               for (pIdx = pTab->pIndex; pIdx; pIdx = pIdx->pNext) {
-                       int k;
-                       assert(IsUniqueIndex(pIdx));
-                       assert(pIdx->idxType != SQLITE_IDXTYPE_APPDEF);
-                       assert(IsUniqueIndex(pIndex));
-
-                       if (pIdx->nColumn != pIndex->nColumn)
+       index->def->key_def->part_count = new_part_count;
+
+       if (!index_def_is_valid(index->def, table->def->name))
+               goto exit_create_index;
+
+       if (table == parse->pNewTable) {
+               for (struct Index *idx = table->pIndex; idx != NULL;
+                    idx = idx->pNext) {
+                       uint32_t k;
+                       assert(IsUniqueIndex(idx));
+                       assert(IsUniqueIndex(index));
+
+                       if (idx->def->key_def->part_count !=
+                           index->def->key_def->part_count)
                                continue;
-                       for (k = 0; k < pIdx->nColumn; k++) {
-                               assert(pIdx->aiColumn[k] >= 0);
-                               if (pIdx->aiColumn[k] != pIndex->aiColumn[k])
+                       for (k = 0; k < idx->def->key_def->part_count; k++) {
+                               if (idx->def->key_def->parts[k].fieldno !=
+                                   index->def->key_def->parts[k].fieldno)
                                        break;
                                struct coll *coll1, *coll2;
-                               uint32_t id;
-                               coll1 = sql_index_collation(pIdx, k, &id);
-                               coll2 = sql_index_collation(pIndex, k, &id);
+                               coll1 = idx->def->key_def->parts[k].coll;
+                               coll2 = index->def->key_def->parts[k].coll;
                                if (coll1 != coll2)
                                        break;
                        }
-                       if (k == pIdx->nColumn) {
-                               if (pIdx->onError != pIndex->onError) {
-                                       /* This constraint creates the same index as a previous
-                                        * constraint specified somewhere in the CREATE TABLE statement.
-                                        * However the ON CONFLICT clauses are different. If both this
-                                        * constraint and the previous equivalent constraint have explicit
-                                        * ON CONFLICT clauses this is an error. Otherwise, use the
-                                        * explicitly specified behavior for the index.
+                       if (k == idx->def->key_def->part_count) {
+                               if (idx->onError != index->onError) {
+                                       /*
+                                        * This constraint creates
+                                        * the same index as a
+                                        * previous
+                                        * constraint specified
+                                        * somewhere in the CREATE
+                                        * TABLE statement.
+                                        * However the ON CONFLICT
+                                        * clauses are different.
+                                        * If both this constraint
+                                        * and the previous
+                                        * equivalent constraint
+                                        * have explicit
+                                        * ON CONFLICT clauses
+                                        * this is an error.
+                                        * Otherwise, use the
+                                        * explicitly specified
+                                        * behavior for the index.
                                         */
-                                       if (!
-                                           (pIdx->onError == ON_CONFLICT_ACTION_DEFAULT
-                                            || pIndex->onError ==
-                                            ON_CONFLICT_ACTION_DEFAULT)) {
+                                       if (idx->onError !=
+                                           ON_CONFLICT_ACTION_DEFAULT &&
+                                           index->onError !=
+                                           ON_CONFLICT_ACTION_DEFAULT) {
                                                sqlite3ErrorMsg(parse,
-                                                               "conflicting ON CONFLICT clauses specified",
-                                                               0);
-                                       }
-                                       if (pIdx->onError == ON_CONFLICT_ACTION_DEFAULT) {
-                                               pIdx->onError = pIndex->onError;
+                                                               "conflicting "\
+                                                               "ON CONFLICT "\
+                                                               "clauses "\
+                                                               "specified");
                                        }
+                                       if (idx->onError ==
+                                           ON_CONFLICT_ACTION_DEFAULT)
+                                               idx->onError = index->onError;
+                               }
+                               if (idx_type == SQL_INDEX_TYPE_CONSTRAINT_PK)
+                                       idx->index_type = idx_type;
+
+                               if(is_constraint_named &&
+                                  idx_type == SQL_INDEX_TYPE_CONSTRAINT_UNIQUE) {
+                                       continue;
                                }
-                               if (idx_type == SQLITE_IDXTYPE_PRIMARYKEY)
-                                       pIdx->idxType = idx_type;
                                goto exit_create_index;
                        }
                }
        }
 
-       /* Link the new Index structure to its table and to the other
-        * in-memory database structures.
+       /*
+        * Link the new Index structure to its table and to the
+        * other in-memory database structures.
         */
        assert(parse->nErr == 0);
        if (db->init.busy) {
-               Index *p;
-               p = sqlite3HashInsert(&pTab->idxHash, pIndex->zName, pIndex);
-               if (p) {
-                       assert(p == pIndex);    /* Malloc must have failed */
+               struct Index *p = sqlite3HashInsert(&table->idxHash,
+                                                   index->def->name, index);
+               if (p != NULL) {
+                       /* Malloc must have failed. */
+                       assert(p == index);
                        sqlite3OomFault(db);
                        goto exit_create_index;
                }
                user_session->sql_flags |= SQLITE_InternChanges;
-               pIndex->tnum = db->init.newTnum;
+               index->tnum = db->init.newTnum;
        }
 
        /*
@@ -2996,117 +2950,68 @@ sql_create_index(struct Parse *parse, struct Token *token,
         * initialization step can be skipped.
         */
        else if (tbl_name != NULL) {
-               Vdbe *v;
-               char *zStmt;
-               int iCursor = parse->nTab++;
+               Vdbe *vdbe;
+               int cursor = parse->nTab++;
                int index_space_ptr_reg = parse->nTab++;
-               int iSpaceId, iIndexId, iFirstSchemaCol;
+               int space_id, index_id, first_schema_col;
 
-               v = sqlite3GetVdbe(parse);
-               if (v == 0)
+               vdbe = sqlite3GetVdbe(parse);
+               if (vdbe == 0)
                        goto exit_create_index;
 
                sql_set_multi_write(parse, true);
 
 
-               sqlite3VdbeAddOp2(v, OP_SIDtoPtr, BOX_INDEX_ID,
+               sqlite3VdbeAddOp2(vdbe, OP_SIDtoPtr, BOX_INDEX_ID,
                                  index_space_ptr_reg);
-               sqlite3VdbeAddOp4Int(v, OP_OpenWrite, iCursor, 0,
+               sqlite3VdbeAddOp4Int(vdbe, OP_OpenWrite, cursor, 0,
                                     index_space_ptr_reg, 6);
-               sqlite3VdbeChangeP5(v, OPFLAG_SEEKEQ);
+               sqlite3VdbeChangeP5(vdbe, OPFLAG_SEEKEQ);
 
-               /*
-                * Gather the complete text of the CREATE INDEX
-                * statement into the zStmt variable
-                */
                assert(start != NULL);
-               int n = (int)(parse->sLastToken.z - token->z) +
-                       parse->sLastToken.n;
-               if (token->z[n - 1] == ';')
-                       n--;
-               /* A named index with an explicit CREATE INDEX statement */
-               zStmt = sqlite3MPrintf(db, "CREATE%s INDEX %.*s", on_error ==
-                                      ON_CONFLICT_ACTION_NONE ? "" : " UNIQUE",
-                                      n, token->z);
-
-               iSpaceId = SQLITE_PAGENO_TO_SPACEID(pTab->tnum);
-               iIndexId = getNewIid(parse, iSpaceId, iCursor);
-               sqlite3VdbeAddOp1(v, OP_Close, iCursor);
-               createIndex(parse, pIndex, iSpaceId, iIndexId, zStmt);
-
-               /* consumes zStmt */
-               iFirstSchemaCol =
-                   makeIndexSchemaRecord(parse, pIndex, iSpaceId, iIndexId,
-                                         zStmt);
-
-               /* Reparse the schema. Code an OP_Expire
+               space_id = SQLITE_PAGENO_TO_SPACEID(table->tnum);
+               index_id = getNewIid(parse, space_id, cursor);
+               sqlite3VdbeAddOp1(vdbe, OP_Close, cursor);
+               createIndex(parse, index, space_id, index_id, sql_stmt);
+
+               /* Consumes sql_stmt. */
+               first_schema_col = makeIndexSchemaRecord(parse, index,
+                                                        space_id, index_id,
+                                                        sql_stmt);
+
+               /*
+                * Reparse the schema. Code an OP_Expire
                 * to invalidate all pre-compiled statements.
                 */
                sqlite3ChangeCookie(parse);
-               sqlite3VdbeAddParseSchema2Op(v, iFirstSchemaCol, 4);
-               sqlite3VdbeAddOp0(v, OP_Expire);
+               sqlite3VdbeAddParseSchema2Op(vdbe, first_schema_col, 4);
+               sqlite3VdbeAddOp0(vdbe, OP_Expire);
        }
 
-       /* When adding an index to the list of indexes for a table, we
-        * maintain special order of the indexes in the list:
+       /*
+        * When adding an index to the list of indexes for a table,
+        * we maintain special order of the indexes in the list:
         * 1. PK (go first just for simplicity)
         * 2. ON_CONFLICT_ACTION_REPLACE indexes
         * 3. ON_CONFLICT_ACTION_IGNORE indexes
         * This is necessary for the correct constraint check
-        * processing (in sqlite3GenerateConstraintChecks()) as part of
-        * UPDATE and INSERT statements.
+        * processing (in sqlite3GenerateConstraintChecks()) as
+        * part of UPDATE and INSERT statements.
         */
 
        if (!db->init.busy && tbl_name != NULL)
                goto exit_create_index;
-       addIndexToTable(pIndex, pTab);
-       pIndex = NULL;
+       addIndexToTable(index, table);
+       index = NULL;
 
-       /* Clean up before exiting */
+       /* Clean up before exiting. */
  exit_create_index:
-       if (pIndex)
-               freeIndex(db, pIndex);
+       if (index != NULL)
+               freeIndex(db, index);
        sql_expr_delete(db, where, false);
        sql_expr_list_delete(db, col_list);
        sqlite3SrcListDelete(db, tbl_name);
-       sqlite3DbFree(db, zName);
-}
-
-/**
- * Return number of columns in given index.
- * If space is ephemeral, use internal
- * SQL structure to fetch the value.
- */
-uint32_t
-index_column_count(const Index *idx)
-{
-       assert(idx != NULL);
-       uint32_t space_id = SQLITE_PAGENO_TO_SPACEID(idx->tnum);
-       struct space *space = space_by_id(space_id);
-       /* It is impossible to find an ephemeral space by id. */
-       if (space == NULL)
-               return idx->nColumn;
-
-       uint32_t index_id = SQLITE_PAGENO_TO_INDEXID(idx->tnum);
-       struct index *index = space_index(space, index_id);
-       assert(index != NULL);
-       return index->def->key_def->part_count;
-}
-
-/** Return true if given index is unique and not nullable. */
-bool
-index_is_unique_not_null(const Index *idx)
-{
-       assert(idx != NULL);
-       uint32_t space_id = SQLITE_PAGENO_TO_SPACEID(idx->tnum);
-       struct space *space = space_by_id(space_id);
-       assert(space != NULL);
-
-       uint32_t index_id = SQLITE_PAGENO_TO_INDEXID(idx->tnum);
-       struct index *index = space_index(space, index_id);
-       assert(index != NULL);
-       return (index->def->opts.is_unique &&
-               !index->def->key_def->is_nullable);
+       sqlite3DbFree(db, name);
 }
 
 void
@@ -3296,7 +3201,7 @@ sqlite3IdListIndex(IdList * pList, const char *zName)
 }
 
 /*
- * Expand the space allocated for the given SrcList object by
+ * Expand the space allocazted for the given SrcList object by
  * creating nExtra new slots beginning at iStart.  iStart is zero based.
  * New slots are zeroed.
  *
@@ -3732,9 +3637,9 @@ parser_emit_unique_constraint(struct Parse *parser,
        const struct space_def *def = index->pTable->def;
        StrAccum err_accum;
        sqlite3StrAccumInit(&err_accum, parser->db, 0, 0, 200);
-       for (int j = 0; j < index->nColumn; ++j) {
-               assert(index->aiColumn[j] >= 0);
-               const char *col_name = def->fields[index->aiColumn[j]].name;
+       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);
@@ -3755,11 +3660,11 @@ static bool
 collationMatch(struct coll *coll, struct Index *index)
 {
        assert(coll != NULL);
-       for (int i = 0; i < index->nColumn; i++) {
-               uint32_t id;
-               struct coll *idx_coll = sql_index_collation(index, i, &id);
-               assert(idx_coll != 0 || index->aiColumn[i] < 0);
-               if (index->aiColumn[i] >= 0 && coll == idx_coll)
+       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;
diff --git a/src/box/sql/delete.c b/src/box/sql/delete.c
index 5a799714d..5a7cf7652 100644
--- a/src/box/sql/delete.c
+++ b/src/box/sql/delete.c
@@ -268,11 +268,12 @@ sql_table_delete_from(struct Parse *parse, struct SrcList *tab_list,
 
                /* Extract the primary key for the current row */
                if (!is_view) {
-                       for (int i = 0; i < pk_len; i++) {
+                       struct key_part *part = pk_def->parts;
+                       for (int i = 0; i < pk_len; i++, part++) {
                                struct space_def *def = space->def;
                                sqlite3ExprCodeGetColumnOfTable(v, def,
                                                                tab_cursor,
-                                                               pk_def->parts[i].fieldno,
+                                                               part->fieldno,
                                                                reg_pk + i);
                        }
                } else {
@@ -568,13 +569,14 @@ sql_generate_index_key(struct Parse *parse, struct Index *index, int cursor,
                        *part_idx_label = 0;
                }
        }
-       int col_cnt = index_column_count(index);
+       int col_cnt = index->def->key_def->part_count;
        int reg_base = sqlite3GetTempRange(parse, col_cnt);
        if (prev != NULL && (reg_base != reg_prev ||
                             prev->pPartIdxWhere != NULL))
                prev = NULL;
        for (int j = 0; j < col_cnt; j++) {
-               if (prev != NULL && prev->aiColumn[j] == index->aiColumn[j]) {
+               if (prev != NULL && prev->def->key_def->parts[j].fieldno ==
+                                   index->def->key_def->parts[j].fieldno) {
                        /*
                         * This column was already computed by the
                         * previous index.
diff --git a/src/box/sql/expr.c b/src/box/sql/expr.c
index 3183e3dc7..6ca76fcd2 100644
--- a/src/box/sql/expr.c
+++ b/src/box/sql/expr.c
@@ -2405,21 +2405,28 @@ sqlite3FindInIndex(Parse * pParse,      /* Parsing context */
                             pIdx = pIdx->pNext) {
                                Bitmask colUsed; /* Columns of the index used */
                                Bitmask mCol;   /* Mask for the current column */
-                               if (pIdx->nColumn < nExpr)
+                               uint32_t part_count =
+                                       pIdx->def->key_def->part_count;
+                               struct key_part *parts =
+                                       pIdx->def->key_def->parts;
+                               if ((int)part_count < nExpr)
                                        continue;
                                /* Maximum nColumn is BMS-2, not BMS-1, so that we can compute
                                 * BITMASK(nExpr) without overflowing
                                 */
-                               testcase(pIdx->nColumn == BMS - 2);
-                               testcase(pIdx->nColumn == BMS - 1);
-                               if (pIdx->nColumn >= BMS - 1)
+                               testcase(part_count == BMS - 2);
+                               testcase(part_count == BMS - 1);
+                               if (part_count >= BMS - 1)
+                                       continue;
+                               if (mustBeUnique &&
+                                   ((int)part_count > nExpr ||
+                                    !pIdx->def->opts.is_unique)) {
+                                       /*
+                                        * This index is not
+                                        * unique over the IN RHS
+                                        * columns.
+                                        */
                                        continue;
-                               if (mustBeUnique) {
-                                       if (pIdx->nColumn > nExpr
-                                           || (pIdx->nColumn > nExpr
-                                           && !index_is_unique(pIdx))) {
-                                                       continue;       /* This index is not unique over the IN RHS columns */
-                                       }
                                }
 
                                colUsed = 0;    /* Columns of index used so far */
@@ -2432,16 +2439,15 @@ sqlite3FindInIndex(Parse * pParse,      /* Parsing context */
                                        int j;
 
                                        for (j = 0; j < nExpr; j++) {
-                                               if (pIdx->aiColumn[j] !=
-                                                   pRhs->iColumn) {
+                                               if ((int) parts[j].fieldno !=
+                                                   pRhs->iColumn)
                                                        continue;
-                                               }
-                                               struct coll *idx_coll;
-                                               idx_coll = sql_index_collation(pIdx, j, &id);
+
+                                               struct coll *idx_coll =
+                                                            parts[j].coll;
                                                if (pReq != NULL &&
-                                                   pReq != idx_coll) {
+                                                   pReq != idx_coll)
                                                        continue;
-                                               }
                                                break;
                                        }
                                        if (j == nExpr)
@@ -2466,18 +2472,17 @@ sqlite3FindInIndex(Parse * pParse,      /* Parsing context */
                                                          0, 0, 0,
                                                          sqlite3MPrintf(db,
                                                          "USING INDEX %s FOR IN-OPERATOR",
-                                                         pIdx->zName),
+                                                         pIdx->def->name),
                                                          P4_DYNAMIC);
                                        struct space *space =
                                                space_by_id(SQLITE_PAGENO_TO_SPACEID(pIdx->tnum));
                                        vdbe_emit_open_cursor(pParse, iTab,
                                                              pIdx->tnum, space);
-                                       VdbeComment((v, "%s", pIdx->zName));
+                                       VdbeComment((v, "%s", pIdx->def->name));
                                        assert(IN_INDEX_INDEX_DESC ==
                                               IN_INDEX_INDEX_ASC + 1);
                                        eType = IN_INDEX_INDEX_ASC +
-                                               sql_index_column_sort_order(pIdx,
-                                                                           0);
+                                               parts[0].sort_order;
 
                                        if (prRhsHasNull) {
 #ifdef SQLITE_ENABLE_COLUMN_USED_MASK
@@ -2499,7 +2504,7 @@ sqlite3FindInIndex(Parse * pParse,        /* Parsing context */
                                                        /* Tarantool: Check for null is performed on first key of the index.  */
                                                        sqlite3SetHasNullFlag(v,
                                                                              iTab,
-                                                                             pIdx->aiColumn[0],
+                                                                             parts[0].fieldno,
                                                                              *prRhsHasNull);
                                                }
                                        }
@@ -3137,12 +3142,12 @@ sqlite3ExprCodeIN(Parse * pParse,       /* Parsing and code generating context */
                struct Index *pk = sqlite3PrimaryKeyIndex(tab);
                assert(pk);
 
+               uint32_t fieldno = pk->def->key_def->parts[0].fieldno;
                enum affinity_type affinity =
-                       tab->def->fields[pk->aiColumn[0]].affinity;
-               if (pk->nColumn == 1
-                   && affinity == AFFINITY_INTEGER
-                   && pk->aiColumn[0] < nVector) {
-                       int reg_pk = rLhs + pk->aiColumn[0];
+                       tab->def->fields[fieldno].affinity;
+               if (pk->def->key_def->part_count == 1 &&
+                   affinity == AFFINITY_INTEGER && (int)fieldno < nVector) {
+                       int reg_pk = rLhs + (int)fieldno;
                        sqlite3VdbeAddOp2(v, OP_MustBeInt, reg_pk, destIfFalse);
                }
        }
@@ -3474,7 +3479,7 @@ sqlite3ExprCodeLoadIndexColumn(Parse * pParse,    /* The parsing context */
                               int regOut       /* Store the index column value in this register */
     )
 {
-       i16 iTabCol = pIdx->aiColumn[iIdxCol];
+       i16 iTabCol = pIdx->def->key_def->parts[iIdxCol].fieldno;
        sqlite3ExprCodeGetColumnOfTable(pParse->pVdbe, pIdx->pTable->def,
                                        iTabCur, iTabCol, regOut);
 }
diff --git a/src/box/sql/fkey.c b/src/box/sql/fkey.c
index 6c75c4772..c63419810 100644
--- a/src/box/sql/fkey.c
+++ b/src/box/sql/fkey.c
@@ -213,7 +213,6 @@ sqlite3FkLocateIndex(Parse * pParse,        /* Parse context to store any error in */
                     int **paiCol       /* OUT: Map of index columns in pFKey */
     )
 {
-       Index *pIdx = 0;        /* Value to return via *ppIdx */
        int *aiCol = 0;         /* Value to return via *paiCol */
        int nCol = pFKey->nCol;      /* Number of columns in parent key */
        char *zKey = pFKey->aCol[0].zCol;    /* Name of left-most parent key column */
@@ -255,83 +254,86 @@ sqlite3FkLocateIndex(Parse * pParse,      /* Parse context to store any error in */
                *paiCol = aiCol;
        }
 
-       for (pIdx = pParent->pIndex; pIdx; pIdx = pIdx->pNext) {
-               int nIdxCol = index_column_count(pIdx);
-               if (nIdxCol == nCol && index_is_unique(pIdx)
-                   && pIdx->pPartIdxWhere == 0) {
-                       /* pIdx is a UNIQUE index (or a PRIMARY KEY) and has the right number
-                        * of columns. If each indexed column corresponds to a foreign key
-                        * column of pFKey, then this index is a winner.
+       struct Index *index = NULL;
+       for (index = pParent->pIndex; index != NULL; index = index->pNext) {
+               int part_count = index->def->key_def->part_count;
+               if (part_count != nCol || !index->def->opts.is_unique ||
+                   index->pPartIdxWhere != NULL)
+                       continue;
+               /*
+                * Index is a UNIQUE index (or a PRIMARY KEY) and
+                * has the right number of columns. If each
+                * indexed column corresponds to a foreign key
+                * column of pFKey, then this index is a winner.
+                */
+               if (zKey == NULL) {
+                       /*
+                        * If zKey is NULL, then this foreign key
+                        * is implicitly mapped to the PRIMARY KEY
+                        * of table pParent. The PRIMARY KEY index
+                        * may be identified by the test.
                         */
-
-                       if (zKey == 0) {
-                               /* If zKey is NULL, then this foreign key is implicitly mapped to
-                                * the PRIMARY KEY of table pParent. The PRIMARY KEY index may be
-                                * identified by the test.
-                                */
-                               if (IsPrimaryKeyIndex(pIdx)) {
-                                       if (aiCol) {
-                                               int i;
-                                               for (i = 0; i < nCol; i++)
-                                                       aiCol[i] =
-                                                           pFKey->aCol[i].
-                                                           iFrom;
-                                       }
-                                       break;
+                       if (IsPrimaryKeyIndex(index)) {
+                               if (aiCol != NULL) {
+                                       for (int i = 0; i < nCol; i++)
+                                               aiCol[i] = pFKey->aCol[i].iFrom;
                                }
-                       } else {
-                               /* If zKey is non-NULL, then this foreign key was declared to
-                                * map to an explicit list of columns in table pParent. Check if this
-                                * index matches those columns. Also, check that the index uses
-                                * the default collation sequences for each column.
+                               break;
+                       }
+               } else {
+                       /*
+                        * If zKey is non-NULL, then this foreign
+                        * key was declared to map to an explicit
+                        * list of columns in table pParent. Check
+                        * if this index matches those columns.
+                        * Also, check that the index uses the
+                        * default collation sequences for each
+                        * column.
+                        */
+                       int i, j;
+                       struct key_part *part = index->def->key_def->parts;
+                       for (i = 0; i < nCol; i++, part++) {
+                               /*
+                                * Index of column in parent
+                                * table.
                                 */
-                               int i, j;
-                               for (i = 0; i < nCol; i++) {
-                                       i16 iCol = pIdx->aiColumn[i];        /* Index of column in parent tbl */
-                                       char *zIdxCol;  /* Name of indexed column */
-
-                                       if (iCol < 0)
-                                               break;  /* No foreign keys against expression indexes */
-
-                                       /* If the index uses a collation sequence that is different from
-                                        * the default collation sequence for the column, this index is
-                                        * unusable. Bail out early in this case.
-                                        */
-                                       struct coll *def_coll;
-                                       uint32_t id;
-                                       def_coll = sql_column_collation(pParent->def,
-                                                                       iCol,
-                                                                       &id);
-                                       struct coll *coll =
-                                               sql_index_collation(pIdx, i,
-                                                                   &id);
-                                       if (def_coll != coll)
-                                               break;
-
-                                       zIdxCol =
-                                               pParent->def->fields[iCol].name;
-                                       for (j = 0; j < nCol; j++) {
-                                               if (strcmp
-                                                   (pFKey->aCol[j].zCol,
-                                                    zIdxCol) == 0) {
-                                                       if (aiCol)
-                                                               aiCol[i] =
-                                                                   pFKey->
-                                                                   aCol[j].
-                                                                   iFrom;
-                                                       break;
-                                               }
-                                       }
-                                       if (j == nCol)
-                                               break;
+                               i16 iCol = (int) part->fieldno;
+                               /*
+                                * If the index uses a collation
+                                * sequence that is different from
+                                * the default collation sequence
+                                * for the column, this index is
+                                * unusable. Bail out early in
+                                * this case.
+                                */
+                               uint32_t id;
+                               struct coll *def_coll =
+                                       sql_column_collation(pParent->def,
+                                                            iCol, &id);
+                               struct coll *coll = part->coll;
+                               if (def_coll != coll)
+                                       break;
+
+                               char *zIdxCol = pParent->def->fields[iCol].name;
+                               for (j = 0; j < nCol; j++) {
+                                       if (strcmp(pFKey->aCol[j].zCol,
+                                                  zIdxCol) != 0)
+                                               continue;
+                                       if (aiCol)
+                                               aiCol[i] = pFKey->aCol[j].iFrom;
+                                       break;
                                }
-                               if (i == nCol)
-                                       break;  /* pIdx is usable */
+                               if (j == nCol)
+                                       break;
+                       }
+                       if (i == nCol) {
+                               /* Index is usable. */
+                               break;
                        }
                }
        }
 
-       if (!pIdx) {
+       if (index == NULL) {
                if (!pParse->disableTriggers) {
                        sqlite3ErrorMsg(pParse,
                                        "foreign key mismatch - \"%w\" referencing \"%w\"",
@@ -341,7 +343,7 @@ sqlite3FkLocateIndex(Parse * pParse,        /* Parse context to store any error in */
                return 1;
        }
 
-       *ppIdx = pIdx;
+       *ppIdx = index;
        return 0;
 }
 
@@ -460,14 +462,15 @@ fkLookupParent(Parse * pParse,    /* Parse context */
                         */
                        if (pTab == pFKey->pFrom && nIncr == 1) {
                                int iJump =
-                                   sqlite3VdbeCurrentAddr(v) + nCol + 1;
-                               for (i = 0; i < nCol; i++) {
+                                       sqlite3VdbeCurrentAddr(v) + nCol + 1;
+                               struct key_part *part =
+                                       pIdx->def->key_def->parts;
+                               for (i = 0; i < nCol; ++i, ++part) {
                                        int iChild = aiCol[i] + 1 + regData;
-                                       int iParent =
-                                           pIdx->aiColumn[i] + 1 + regData;
-                                       assert(pIdx->aiColumn[i] >= 0);
+                                       int iParent = 1 + regData +
+                                                     (int)part->fieldno;
                                        assert(aiCol[i] != pTab->iPKey);
-                                       if (pIdx->aiColumn[i] == pTab->iPKey) {
+                                       if ((int)part->fieldno == pTab->iPKey) {
                                                /* The parent key is a composite key that includes the IPK column */
                                                iParent = regData;
                                        }
@@ -614,16 +617,15 @@ fkScanChildren(Parse * pParse,    /* Parse context */
     )
 {
        sqlite3 *db = pParse->db;    /* Database handle */
-       int i;                  /* Iterator variable */
        Expr *pWhere = 0;       /* WHERE clause to scan with */
        NameContext sNameContext;       /* Context used to resolve WHERE clause */
        WhereInfo *pWInfo;      /* Context used by sqlite3WhereXXX() */
        int iFkIfZero = 0;      /* Address of OP_FkIfZero */
        Vdbe *v = sqlite3GetVdbe(pParse);
 
-       assert(pIdx == 0 || pIdx->pTable == pTab);
-       assert(pIdx == 0 || (int)index_column_count(pIdx) == pFKey->nCol);
-       assert(pIdx != 0);
+       assert(pIdx == NULL || pIdx->pTable == pTab);
+       assert(pIdx == NULL || (int) pIdx->def->key_def->part_count == pFKey->nCol);
+       assert(pIdx != NULL);
 
        if (nIncr < 0) {
                iFkIfZero =
@@ -639,19 +641,20 @@ fkScanChildren(Parse * pParse,    /* Parse context */
         * the parent key columns. The affinity of the parent key column should
         * be applied to each child key value before the comparison takes place.
         */
-       for (i = 0; i < pFKey->nCol; i++) {
+       for (int i = 0; i < pFKey->nCol; i++) {
                Expr *pLeft;    /* Value from parent table row */
                Expr *pRight;   /* Column ref to child table */
                Expr *pEq;      /* Expression (pLeft = pRight) */
                i16 iCol;       /* Index of column in child table */
-               const char *zCol;       /* Name of column in child table */
+               const char *column_name;
 
-               iCol = pIdx ? pIdx->aiColumn[i] : -1;
+               iCol = pIdx != NULL ?
+                      (int) pIdx->def->key_def->parts[i].fieldno : -1;
                pLeft = exprTableRegister(pParse, pTab, regData, iCol);
                iCol = aiCol ? aiCol[i] : pFKey->aCol[0].iFrom;
                assert(iCol >= 0);
-               zCol = pFKey->pFrom->def->fields[iCol].name;
-               pRight = sqlite3Expr(db, TK_ID, zCol);
+               column_name = pFKey->pFrom->def->fields[iCol].name;
+               pRight = sqlite3Expr(db, TK_ID, column_name);
                pEq = sqlite3PExpr(pParse, TK_EQ, pLeft, pRight);
                pWhere = sqlite3ExprAnd(db, pWhere, pEq);
        }
@@ -670,15 +673,14 @@ fkScanChildren(Parse * pParse,    /* Parse context */
 
                Expr *pEq, *pAll = 0;
                Index *pPk = sqlite3PrimaryKeyIndex(pTab);
-               assert(pIdx != 0);
-               int col_count = index_column_count(pPk);
-               for (i = 0; i < col_count; i++) {
-                       i16 iCol = pIdx->aiColumn[i];
-                       assert(iCol >= 0);
-                       pLeft = exprTableRegister(pParse, pTab, regData, iCol);
-                       pRight =
-                               exprTableColumn(db, pTab->def,
-                                               pSrc->a[0].iCursor, iCol);
+               assert(pIdx != NULL);
+               uint32_t part_count = pPk->def->key_def->part_count;
+               for (uint32_t i = 0; i < part_count; i++) {
+                       uint32_t fieldno = pIdx->def->key_def->parts[i].fieldno;
+                       pLeft = exprTableRegister(pParse, pTab, regData,
+                                                 fieldno);
+                       pRight = exprTableColumn(db, pTab->def,
+                                                pSrc->a[0].iCursor, fieldno);
                        pEq = sqlite3PExpr(pParse, TK_EQ, pLeft, pRight);
                        pAll = sqlite3ExprAnd(db, pAll, pEq);
                }
@@ -983,7 +985,6 @@ sqlite3FkCheck(Parse * pParse,      /* Parse context */
                        if (aiCol[i] == pTab->iPKey) {
                                aiCol[i] = -1;
                        }
-                       assert(pIdx == 0 || pIdx->aiColumn[i] >= 0);
                }
 
                pParse->nTab++;
@@ -1108,19 +1109,19 @@ sqlite3FkOldmask(Parse * pParse,        /* Parse context */
 
        if (user_session->sql_flags & SQLITE_ForeignKeys) {
                FKey *p;
-               int i;
                for (p = pTab->pFKey; p; p = p->pNextFrom) {
-                       for (i = 0; i < p->nCol; i++)
+                       for (int i = 0; i < p->nCol; i++)
                                mask |= COLUMN_MASK(p->aCol[i].iFrom);
                }
                for (p = sqlite3FkReferences(pTab); p; p = p->pNextTo) {
                        Index *pIdx = 0;
                        sqlite3FkLocateIndex(pParse, pTab, p, &pIdx, 0);
-                       if (pIdx) {
-                               int nIdxCol = index_column_count(pIdx);
-                               for (i = 0; i < nIdxCol; i++) {
-                                       assert(pIdx->aiColumn[i] >= 0);
-                                       mask |= COLUMN_MASK(pIdx->aiColumn[i]);
+                       if (pIdx != NULL) {
+                               uint32_t part_count =
+                                       pIdx->def->key_def->part_count;
+                               for (uint32_t i = 0; i < part_count; i++) {
+                                       mask |= COLUMN_MASK(pIdx->def->
+                                               key_def->parts[i].fieldno);
                                }
                        }
                }
@@ -1264,11 +1265,12 @@ fkActionTrigger(struct Parse *pParse, struct Table *pTab, struct FKey *pFKey,
                               || (pTab->iPKey >= 0
                                   && pTab->iPKey <
                                      (int)pTab->def->field_count));
-                       assert(pIdx == 0 || pIdx->aiColumn[i] >= 0);
+
+                       uint32_t fieldno = pIdx != NULL ?
+                                          pIdx->def->key_def->parts[i].fieldno :
+                                          (uint32_t)pTab->iPKey;
                        sqlite3TokenInit(&tToCol,
-                                        pTab->def->fields[pIdx ? pIdx->
-                                                   aiColumn[i] : pTab->iPKey].
-                                        name);
+                                        pTab->def->fields[fieldno].name);
                        sqlite3TokenInit(&tFromCol,
                                         pFKey->pFrom->def->fields[
                                                iFromCol].name);
diff --git a/src/box/sql/insert.c b/src/box/sql/insert.c
index c12043bde..383819a77 100644
--- a/src/box/sql/insert.c
+++ b/src/box/sql/insert.c
@@ -89,14 +89,14 @@ sqlite3IndexAffinityStr(sqlite3 *db, Index *index)
         * sqliteDeleteIndex() when the Index structure itself is
         * cleaned up.
         */
-       int column_count = index_column_count(index);
+       int column_count = index->def->key_def->part_count;
        index->zColAff = (char *) sqlite3DbMallocRaw(0, column_count + 1);
        if (index->zColAff == NULL) {
                sqlite3OomFault(db);
                return NULL;
        }
        for (int n = 0; n < column_count; n++) {
-               uint16_t x = index->aiColumn[n];
+               uint16_t x = index->def->key_def->parts[n].fieldno;
                index->zColAff[n] = index->pTable->def->fields[x].affinity;
        }
        index->zColAff[column_count] = 0;
@@ -647,7 +647,7 @@ sqlite3Insert(Parse * pParse,       /* Parser context */
                     pIdx = pIdx->pNext, i++) {
                        assert(pIdx);
                        aRegIdx[i] = ++pParse->nMem;
-                       pParse->nMem += index_column_count(pIdx);
+                       pParse->nMem += pIdx->def->key_def->part_count;
                }
        }
 
@@ -1069,12 +1069,8 @@ sqlite3GenerateConstraintChecks(Parse * pParse,          /* The parser context */
        Index *pIdx;            /* Pointer to one of the indices */
        Index *pPk = 0;         /* The PRIMARY KEY index */
        sqlite3 *db;            /* Database connection */
-       int i;                  /* loop counter */
-       int ix;                 /* Index loop counter */
-       int nCol;               /* Number of columns */
        int addr1;              /* Address of jump instruction */
        int seenReplace = 0;    /* True if REPLACE is used to resolve INT PK conflict */
-       int nPkField;           /* Number of fields in PRIMARY KEY. */
        u8 isUpdate;            /* True if this is an UPDATE operation */
        u8 bAffinityDone = 0;   /* True if the OP_Affinity operation has been run */
        struct session *user_session = current_session();
@@ -1086,10 +1082,8 @@ sqlite3GenerateConstraintChecks(Parse * pParse,          /* The parser context */
        struct space_def *def = pTab->def;
        /* This table is not a VIEW */
        assert(!def->opts.is_view);
-       nCol = def->field_count;
 
        pPk = sqlite3PrimaryKeyIndex(pTab);
-       nPkField = index_column_count(pPk);
 
        /* Record that this module has started */
        VdbeModuleComment((v, "BEGIN: GenCnstCks(%d,%d,%d,%d,%d)",
@@ -1099,17 +1093,16 @@ sqlite3GenerateConstraintChecks(Parse * pParse,         /* The parser context */
        enum on_conflict_action on_error;
        /* Test all NOT NULL constraints.
         */
-       for (i = 0; i < nCol; i++) {
-               if (i == pTab->iPKey) {
+       for (uint32_t i = 0; i < def->field_count; i++) {
+               if ((int) i == pTab->iPKey)
                        continue;
-               }
                if (aiChng && aiChng[i] < 0) {
                        /* Don't bother checking for NOT NULL on columns that do not change */
                        continue;
                }
                if (def->fields[i].is_nullable ||
                    (pTab->tabFlags & TF_Autoincrement &&
-                    pTab->iAutoIncPKey == i))
+                    pTab->iAutoIncPKey == (int) i))
                        continue;       /* This column is allowed to be NULL */
 
                on_error = table_column_nullable_action(pTab, i);
@@ -1179,7 +1172,7 @@ sqlite3GenerateConstraintChecks(Parse * pParse,           /* The parser context */
                else
                        on_error = ON_CONFLICT_ACTION_ABORT;
 
-               for (i = 0; i < checks->nExpr; i++) {
+               for (int i = 0; i < checks->nExpr; i++) {
                        int allOk;
                        Expr *pExpr = checks->a[i].pExpr;
                        if (aiChng
@@ -1206,13 +1199,16 @@ sqlite3GenerateConstraintChecks(Parse * pParse,         /* The parser context */
                }
        }
 
-       /* Test all UNIQUE constraints by creating entries for each UNIQUE
-        * index and making sure that duplicate entries do not already exist.
-        * Compute the revised record entries for indices as we go.
+       /*
+        * Test all UNIQUE constraints by creating entries for
+        * each UNIQUE index and making sure that duplicate entries
+        * do not already exist. Compute the revised record entries
+        * for indices as we go.
         *
         * This loop also handles the case of the PRIMARY KEY index.
         */
-       for (ix = 0, pIdx = pTab->pIndex; pIdx; pIdx = pIdx->pNext, ix++) {
+       pIdx = pTab->pIndex;
+       for (int ix = 0; pIdx != NULL; pIdx = pIdx->pNext, ix++) {
                int regIdx;     /* Range of registers hold conent for pIdx */
                int regR;       /* Range of registers holding conflicting PK */
                int iThisCur;   /* Cursor for this UNIQUE index */
@@ -1253,10 +1249,11 @@ sqlite3GenerateConstraintChecks(Parse * pParse,         /* The parser context */
                 * the insert or update.  Store that record in the aRegIdx[ix] register
                 */
                regIdx = aRegIdx[ix] + 1;
-               int nIdxCol = (int) index_column_count(pIdx);
+               uint32_t part_count = pIdx->def->key_def->part_count;
                if (uniqueByteCodeNeeded) {
-                       for (i = 0; i < nIdxCol; ++i) {
-                               int fieldno = pIdx->aiColumn[i];
+                       for (uint32_t i = 0; i < part_count; ++i) {
+                               uint32_t fieldno =
+                                       pIdx->def->key_def->parts[i].fieldno;
                                int reg;
                                /*
                                 * OP_SCopy copies value in
@@ -1267,11 +1264,10 @@ sqlite3GenerateConstraintChecks(Parse * pParse,         /* The parser context */
                                 * needed for proper UNIQUE
                                 * constraint handling.
                                 */
-                               if (fieldno == pTab->iPKey)
+                               if ((int) fieldno == pTab->iPKey)
                                        reg = regNewData;
                                else
                                        reg = fieldno + regNewData + 1;
-                               assert(fieldno >= 0);
                                sqlite3VdbeAddOp2(v, OP_SCopy, reg, regIdx + i);
                                VdbeComment((v, "%s",
                                            def->fields[fieldno].name));
@@ -1283,9 +1279,12 @@ sqlite3GenerateConstraintChecks(Parse * pParse,          /* The parser context */
                if (IsPrimaryKeyIndex(pIdx)) {
                        /* If PK is marked as INTEGER, use it as strict type,
                         * not as affinity. Emit code for type checking */
-                       if (nIdxCol == 1) {
-                               reg_pk = regNewData + 1 + pIdx->aiColumn[0];
-                               if (pTab->zColAff[pIdx->aiColumn[0]] ==
+                       if (part_count == 1) {
+                               uint32_t fieldno =
+                                       pIdx->def->key_def->parts[0].fieldno;
+                               reg_pk = regNewData + 1 + fieldno;
+
+                               if (pTab->zColAff[fieldno] ==
                                    AFFINITY_INTEGER) {
                                        int skip_if_null = sqlite3VdbeMakeLabel(v);
                                        if ((pTab->tabFlags & TF_Autoincrement) != 0) {
@@ -1303,7 +1302,7 @@ sqlite3GenerateConstraintChecks(Parse * pParse,           /* The parser context */
 
                        sqlite3VdbeAddOp3(v, OP_MakeRecord, regNewData + 1,
                                          def->field_count, aRegIdx[ix]);
-                       VdbeComment((v, "for %s", pIdx->zName));
+                       VdbeComment((v, "for %s", pIdx->def->name));
                }
 
                /* In an UPDATE operation, if this index is the PRIMARY KEY
@@ -1390,24 +1389,22 @@ sqlite3GenerateConstraintChecks(Parse * pParse,         /* The parser context */
                if (uniqueByteCodeNeeded) {
                        sqlite3VdbeAddOp4Int(v, OP_NoConflict, iThisCur,
                                             addrUniqueOk, regIdx,
-                                            index_column_count(pIdx));
+                                            pIdx->def->key_def->part_count);
                }
                VdbeCoverage(v);
 
+               uint32_t pk_part_count = pPk->def->key_def->part_count;
                /* Generate code to handle collisions */
-               regR =
-                   (pIdx == pPk) ? regIdx : sqlite3GetTempRange(pParse,
-                                                                nPkField);
+               regR = pIdx == pPk ? regIdx :
+                      sqlite3GetTempRange(pParse, pk_part_count);
                if (isUpdate || on_error == ON_CONFLICT_ACTION_REPLACE) {
                        int x;
-                       int nPkCol = index_column_count(pPk);
                        /* Extract the PRIMARY KEY from the end of the index entry and
                         * store it in registers regR..regR+nPk-1
                         */
                        if (pIdx != pPk) {
-                               for (i = 0; i < nPkCol; i++) {
-                                       assert(pPk->aiColumn[i] >= 0);
-                                       x = pPk->aiColumn[i];
+                               for (uint32_t i = 0; i < pk_part_count; i++) {
+                                       x = pPk->def->key_def->parts[i].fieldno;
                                        sqlite3VdbeAddOp3(v, OP_Column,
                                                          iThisCur, x, regR + i);
                                        VdbeComment((v, "%s.%s", def->name,
@@ -1422,22 +1419,25 @@ sqlite3GenerateConstraintChecks(Parse * pParse,         /* The parser context */
                                 * of the matched index row are different from the original PRIMARY
                                 * KEY values of this row before the update.
                                 */
-                               int addrJump =
-                                       sqlite3VdbeCurrentAddr(v) + nPkCol;
+                               int addrJump = sqlite3VdbeCurrentAddr(v) +
+                                              pk_part_count;
                                int op = OP_Ne;
-                               int regCmp = (IsPrimaryKeyIndex(pIdx) ?
-                                             regIdx : regR);
-
-                               for (i = 0; i < nPkCol; i++) {
-                                       uint32_t id;
-                                       char *p4 = (char *)sql_index_collation(pPk, i, &id);
-                                       x = pPk->aiColumn[i];
-                                       assert(x >= 0);
-                                       if (i == (nPkCol - 1)) {
+                               int regCmp = IsPrimaryKeyIndex(pIdx) ?
+                                            regIdx : regR;
+                               struct key_part *part =
+                                       pPk->def->key_def->parts;
+                               for (uint32_t i = 0; i < pk_part_count;
+                                    ++i, ++part) {
+                                       char *p4 = (char *) part->coll;
+                                       x = part->fieldno;
+                                       if (pPk->tnum==0)
+                                               x = -1;
+                                       if (i == (pk_part_count - 1)) {
                                                addrJump = addrUniqueOk;
                                                op = OP_Eq;
                                        }
-                                       sqlite3VdbeAddOp4(v, op, regOldData + 1 + x,
+                                       sqlite3VdbeAddOp4(v, op,
+                                                         regOldData + 1 + x,
                                                          addrJump, regCmp + i,
                                                          p4, P4_COLLSEQ);
                                        sqlite3VdbeChangeP5(v, SQLITE_NOTNULL);
@@ -1480,7 +1480,8 @@ sqlite3GenerateConstraintChecks(Parse * pParse,           /* The parser context */
                                                              NULL, NULL);
                        }
                        sql_generate_row_delete(pParse, pTab, trigger,
-                                               iDataCur, regR, nPkField, false,
+                                               iDataCur, regR, pk_part_count,
+                                               false,
                                                ON_CONFLICT_ACTION_REPLACE,
                                                pIdx == pPk ? ONEPASS_SINGLE :
                                                ONEPASS_OFF, -1);
@@ -1490,7 +1491,7 @@ sqlite3GenerateConstraintChecks(Parse * pParse,           /* The parser context */
                }
                sqlite3VdbeResolveLabel(v, addrUniqueOk);
                if (regR != regIdx)
-                       sqlite3ReleaseTempRange(pParse, regR, nPkField);
+                       sqlite3ReleaseTempRange(pParse, regR, pk_part_count);
        }
 
        *pbMayReplace = seenReplace;
@@ -1608,8 +1609,8 @@ sqlite3OpenTableAndIndices(Parse * pParse,        /* Parsing context */
                    IsPrimaryKeyIndex(pIdx) ||          /* Condition 2 */
                    sqlite3FkReferences(pTab) ||        /* Condition 3 */
                    /* Condition 4 */
-                   (index_is_unique(pIdx) && pIdx->onError !=
-                    ON_CONFLICT_ACTION_DEFAULT &&
+                   (pIdx->def->opts.is_unique &&
+                    pIdx->onError != ON_CONFLICT_ACTION_DEFAULT &&
                     /* Condition 4.1 */
                     pIdx->onError != ON_CONFLICT_ACTION_ABORT) ||
                     /* Condition 4.2 */
@@ -1627,7 +1628,7 @@ sqlite3OpenTableAndIndices(Parse * pParse,        /* Parsing context */
                                                  space_ptr_reg);
                                sql_vdbe_set_p4_key_def(pParse, pIdx);
                                sqlite3VdbeChangeP5(v, p5);
-                               VdbeComment((v, "%s", pIdx->zName));
+                               VdbeComment((v, "%s", pIdx->def->name));
                        }
                }
        }
@@ -1661,30 +1662,25 @@ int sqlite3_xferopt_count;
 static int
 xferCompatibleIndex(Index * pDest, Index * pSrc)
 {
-       uint32_t i;
        assert(pDest && pSrc);
        assert(pDest->pTable != pSrc->pTable);
-       uint32_t nDestCol = index_column_count(pDest);
-       uint32_t nSrcCol = index_column_count(pSrc);
-       if (nDestCol != nSrcCol) {
-               return 0;       /* Different number of columns */
-       }
+       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;
        if (pDest->onError != pSrc->onError) {
                return 0;       /* Different conflict resolution strategies */
        }
-       for (i = 0; i < nSrcCol; i++) {
-               if (pSrc->aiColumn[i] != pDest->aiColumn[i]) {
+       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 (sql_index_column_sort_order(pSrc, i) !=
-                   sql_index_column_sort_order(pDest, i)) {
+               if (src_part->sort_order != dest_part->sort_order)
                        return 0;       /* Different sort orders */
-               }
-               uint32_t id;
-               if (sql_index_collation(pSrc, i, &id) !=
-                   sql_index_collation(pDest, i, &id)) {
+               if (src_part->coll != dest_part->coll)
                        return 0;       /* Different collating sequences */
-               }
        }
        if (sqlite3ExprCompare(pSrc->pPartIdxWhere, pDest->pPartIdxWhere, -1)) {
                return 0;       /* Different WHERE clauses */
@@ -1856,16 +1852,15 @@ xferOptimization(Parse * pParse,        /* Parser context */
                }
        }
        for (pDestIdx = pDest->pIndex; pDestIdx; pDestIdx = pDestIdx->pNext) {
-               if (index_is_unique(pDestIdx)) {
+               if (pDestIdx->def->opts.is_unique)
                        destHasUniqueIdx = 1;
-               }
                for (pSrcIdx = pSrc->pIndex; pSrcIdx; pSrcIdx = pSrcIdx->pNext) {
                        if (xferCompatibleIndex(pDestIdx, pSrcIdx))
                                break;
                }
-               if (pSrcIdx == 0) {
-                       return 0;       /* pDestIdx has no corresponding index in pSrc */
-               }
+               /* pDestIdx has no corresponding index in pSrc. */
+               if (pSrcIdx == NULL)
+                       return 0;
        }
        /* Get server checks. */
        ExprList *pCheck_src = space_checks_expr_list(
@@ -1941,16 +1936,16 @@ xferOptimization(Parse * pParse,        /* Parser context */
                struct space *src_space =
                        space_by_id(SQLITE_PAGENO_TO_SPACEID(pSrcIdx->tnum));
                vdbe_emit_open_cursor(pParse, iSrc, pSrcIdx->tnum, src_space);
-               VdbeComment((v, "%s", pSrcIdx->zName));
+               VdbeComment((v, "%s", pSrcIdx->def->name));
                struct space *dest_space =
                        space_by_id(SQLITE_PAGENO_TO_SPACEID(pDestIdx->tnum));
                vdbe_emit_open_cursor(pParse, iDest, pDestIdx->tnum, dest_space);
-               VdbeComment((v, "%s", pDestIdx->zName));
+               VdbeComment((v, "%s", pDestIdx->def->name));
                addr1 = sqlite3VdbeAddOp2(v, OP_Rewind, iSrc, 0);
                VdbeCoverage(v);
                sqlite3VdbeAddOp2(v, OP_RowData, iSrc, regData);
                sqlite3VdbeAddOp2(v, OP_IdxInsert, iDest, regData);
-               if (pDestIdx->idxType == SQLITE_IDXTYPE_PRIMARYKEY)
+               if (pDestIdx->index_type == SQL_INDEX_TYPE_CONSTRAINT_PK)
                        sqlite3VdbeChangeP5(v, OPFLAG_NCHANGE);
                sqlite3VdbeAddOp2(v, OP_Next, iSrc, addr1 + 1);
                VdbeCoverage(v);
diff --git a/src/box/sql/parse.y b/src/box/sql/parse.y
index b2940b7c4..1b38c1c10 100644
--- a/src/box/sql/parse.y
+++ b/src/box/sql/parse.y
@@ -282,7 +282,7 @@ ccons ::= PRIMARY KEY sortorder(Z) onconf(R) autoinc(I).
                                  {sqlite3AddPrimaryKey(pParse,0,R,I,Z);}
 ccons ::= UNIQUE onconf(R).      {sql_create_index(pParse,0,0,0,R,0,0,
                                                   SORT_ORDER_ASC, false,
-                                                  SQLITE_IDXTYPE_UNIQUE);}
+                                                  SQL_INDEX_TYPE_CONSTRAINT_UNIQUE);}
 ccons ::= CHECK LP expr(X) RP.   {sql_add_check_constraint(pParse,&X);}
 ccons ::= REFERENCES nm(T) eidlist_opt(TA) refargs(R).
                                  {sqlite3CreateForeignKey(pParse,0,&T,TA,R);}
@@ -333,7 +333,7 @@ tcons ::= PRIMARY KEY LP sortlist(X) autoinc(I) RP onconf(R).
 tcons ::= UNIQUE LP sortlist(X) RP onconf(R).
                                  {sql_create_index(pParse,0,0,X,R,0,0,
                                                   SORT_ORDER_ASC,false,
-                                                  SQLITE_IDXTYPE_UNIQUE);}
+                                                  SQL_INDEX_TYPE_CONSTRAINT_UNIQUE);}
 tcons ::= CHECK LP expr(E) RP onconf.
                                  {sql_add_check_constraint(pParse,&E);}
 tcons ::= FOREIGN KEY LP eidlist(FA) RP
@@ -1194,13 +1194,15 @@ paren_exprlist(A) ::= LP exprlist(X) RP.  {A = X;}
 //
 cmd ::= createkw(S) uniqueflag(U) INDEX ifnotexists(NE) nm(X)
         ON nm(Y) LP sortlist(Z) RP. {
-  sql_create_index(pParse, &X, sqlite3SrcListAppend(pParse->db,0,&Y), Z, U, &S,
-                   NULL, SORT_ORDER_ASC, NE, SQLITE_IDXTYPE_APPDEF);
+  enum on_conflict_action on_error =
+          U ? ON_CONFLICT_ACTION_ABORT : ON_CONFLICT_ACTION_NONE;
+  sql_create_index(pParse, &X, sqlite3SrcListAppend(pParse->db,0,&Y), Z,
+                   on_error, &S, NULL, SORT_ORDER_ASC, NE, U);
 }
 
 %type uniqueflag {int}
-uniqueflag(A) ::= UNIQUE.  {A = ON_CONFLICT_ACTION_ABORT;}
-uniqueflag(A) ::= .        {A = ON_CONFLICT_ACTION_NONE;}
+uniqueflag(A) ::= UNIQUE.  {A = SQL_INDEX_TYPE_UNIQUE;}
+uniqueflag(A) ::= .        {A = SQL_INDEX_TYPE_NON_UNIQUE;}
 
 
 // The eidlist non-terminal (Expression Id List) generates an ExprList
diff --git a/src/box/sql/pragma.c b/src/box/sql/pragma.c
index 31581b17f..c1815bc8c 100644
--- a/src/box/sql/pragma.c
+++ b/src/box/sql/pragma.c
@@ -411,9 +411,8 @@ sqlite3Pragma(Parse * pParse, Token * pId,  /* First part of [schema.]id field */
                        } else if (pk == NULL) {
                                k = 1;
                        } else {
-                               for (k = 1; k <= def->field_count &&
-                                    pk->aiColumn[k - 1] != (int) i; ++k) {
-                               }
+                               struct key_def *kdef = pk->def->key_def;
+                               k = key_def_find(kdef, i) - kdef->parts + 1;
                        }
                        bool is_nullable = def->fields[i].is_nullable;
                        char *expr_str = def->fields[i].default_value;
@@ -456,7 +455,7 @@ sqlite3Pragma(Parse * pParse, Token * pId,  /* First part of [schema.]id field */
                                        size_t avg_tuple_size_idx =
                                                sql_index_tuple_size(space, idx);
                                        sqlite3VdbeMultiLoad(v, 2, "sii",
-                                                            pIdx->zName,
+                                                            pIdx->def->name,
                                                             avg_tuple_size_idx,
                                                             index_field_tuple_est(pIdx, 0));
                                        sqlite3VdbeAddOp2(v, OP_ResultRow, 1,
@@ -485,11 +484,13 @@ sqlite3Pragma(Parse * pParse, Token * pId,        /* First part of [schema.]id field */
                                                 */
                                                pParse->nMem = 3;
                                        }
-                                       mx = index_column_count(pIdx);
+                                       mx = pIdx->def->key_def->part_count;
                                        assert(pParse->nMem <=
                                               pPragma->nPragCName);
-                                       for (i = 0; i < mx; i++) {
-                                               i16 cnum = pIdx->aiColumn[i];
+                                       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,
@@ -503,19 +504,18 @@ sqlite3Pragma(Parse * pParse, Token * pId,        /* First part of [schema.]id field */
                                                                     name);
                                                if (pPragma->iArg) {
                                                        const char *c_n;
-                                                       uint32_t id;
+                                                       uint32_t id =
+                                                               part->coll_id;
                                                        struct coll *coll =
-                                                               sql_index_collation(pIdx, i, &id);
+                                                               part->coll;
                                                        if (coll != NULL)
                                                                c_n = coll_by_id(id)->name;
                                                        else
                                                                c_n = "BINARY";
-                                                       enum sort_order sort_order;
-                                                       sort_order = sql_index_column_sort_order(pIdx,
-                                                                                                i);
                                                        sqlite3VdbeMultiLoad(v,
                                                                             4,
                                                                             "isi",
+                                                                            part->
                                                                             sort_order,
                                                                             c_n,
                                                                             i <a class="moz-txt-link-rfc2396E" href="mailto:@@-542,16+542,14@@sqlite3Pragma(Parse*pParse,Token*pId,/*Firstpartof[schema.]idfield*/for(pIdx=pTab-"><
@@ -542,16 +542,14 @@ sqlite3Pragma(Parse * pParse, Token * pId,        /* First part of [schema.]id field */
                                        for (pIdx = pTab-></a>pIndex, i = 0; pIdx;
                                             pIdx = pIdx->pNext, i++) {
                                                const char *azOrigin[] =
-                                                   { "c", "u", "pk" };
+                                                   { "c", "u", "u", "pk" };
                                                sqlite3VdbeMultiLoad(v, 1,
                                                                     "isisi", i,
-                                                                    pIdx->
-                                                                    zName,
-                                                                    index_is_unique
-                                                                    (pIdx),
+                                                                    pIdx->def->name,
+                                                                    pIdx->def->opts.is_unique,
                                                                     azOrigin
                                                                     [pIdx->
-                                                                     idxType],
+                                                                     index_type],
                                                                     pIdx->
                                                                     pPartIdxWhere
                                                                     != 0);
diff --git a/src/box/sql/select.c b/src/box/sql/select.c
index 52b3fdd07..093346a63 100644
--- a/src/box/sql/select.c
+++ b/src/box/sql/select.c
@@ -4399,7 +4399,7 @@ sqlite3IndexedByLookup(Parse * pParse, struct SrcList_item *pFrom)
                char *zIndexedBy = pFrom->u1.zIndexedBy;
                Index *pIdx;
                for (pIdx = pTab->pIndex;
-                    pIdx && strcmp(pIdx->zName, zIndexedBy);
+                    pIdx && strcmp(pIdx->def->name, zIndexedBy);
                     pIdx = pIdx->pNext) ;
                if (!pIdx) {
                        sqlite3ErrorMsg(pParse, "no such index: %s", zIndexedBy,
diff --git a/src/box/sql/sqliteInt.h b/src/box/sql/sqliteInt.h
index 8b75ae888..c7e854e21 100644
--- a/src/box/sql/sqliteInt.h
+++ b/src/box/sql/sqliteInt.h
@@ -2065,32 +2065,31 @@ struct UnpackedRecord {
                                 */
 };
 
+/*
+ * Possible SQL index types. Note that PK and UNIQUE constraints
+ * are implemented as indexes and have their own types:
+ * SQL_INDEX_TYPE_CONSTRAINT_PK and
+ * SQL_INDEX_TYPE_CONSTRAINT_UNIQUE.
+ */
+enum sql_index_type {
+    SQL_INDEX_TYPE_NON_UNIQUE = 0,
+    SQL_INDEX_TYPE_UNIQUE,
+    SQL_INDEX_TYPE_CONSTRAINT_UNIQUE,
+    SQL_INDEX_TYPE_CONSTRAINT_PK,
+};
+
+/* Return true if index X is a PRIMARY KEY index */
+#define IsPrimaryKeyIndex(X)  ((X)->index_type==SQL_INDEX_TYPE_CONSTRAINT_PK)
+
+/* Return true if index X is a UNIQUE index */
+#define IsUniqueIndex(X)  (((X)->index_type == SQL_INDEX_TYPE_CONSTRAINT_UNIQUE) || \
+                          ((X)->index_type == SQL_INDEX_TYPE_CONSTRAINT_PK) || \
+                          ((X)->index_type == SQL_INDEX_TYPE_UNIQUE))
+
 /*
  * Each SQL index is represented in memory by an
  * instance of the following structure.
  *
- * The columns of the table that are to be indexed are described
- * by the aiColumn[] field of this structure.  For example, suppose
- * we have the following table and index:
- *
- *     CREATE TABLE Ex1(c1 int, c2 int, c3 text);
- *     CREATE INDEX Ex2 ON Ex1(c3,c1);
- *
- * In the Table structure describing Ex1, nCol==3 because there are
- * three columns in the table.  In the Index structure describing
- * Ex2, nColumn==2 since 2 of the 3 columns of Ex1 are indexed.
- * The value of aiColumn is {2, 0}.  aiColumn[0]==2 because the
- * first column to be indexed (c3) has an index of 2 in Ex1.aCol[].
- * The second column to be indexed (c1) has an index of 0 in
- * Ex1.aCol[], hence Ex2.aiColumn[1]==0.
- *
- * The Index.onError field determines whether or not the indexed columns
- * must be unique and what to do if they are not.  When Index.onError=
- * ON_CONFLICT_ACTION_NONE, it means this is not a unique index.
- * Otherwise it is a unique index and the value of Index.onError indicate
- * the which conflict resolution algorithm to employ whenever an attempt
- * is made to insert a non-unique element.
- *
  * 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
@@ -2100,26 +2099,32 @@ struct UnpackedRecord {
  * program is executed). See convertToWithoutRowidTable() for details.
  */
 struct Index {
-       char *zName;            /* Name of this index */
-       i16 *aiColumn;          /* Which columns are used by this index.  1st is 0 */
-       LogEst *aiRowLogEst;    /* From ANALYZE: Est. rows selected by each column */
-       Table *pTable;          /* The SQL table being indexed */
-       char *zColAff;          /* String defining the affinity of each column */
-       Index *pNext;           /* The next index associated with the same table */
-       Schema *pSchema;        /* Schema containing this index */
-       /** Sorting order for each column. */
-       enum sort_order *sort_order;
-       /** Array of collation sequences for index. */
-       struct coll **coll_array;
-       /** Array of collation identifiers. */
-       uint32_t *coll_id_array;
-       Expr *pPartIdxWhere;    /* WHERE clause for partial indices */
-       int tnum;               /* DB Page containing root of this index */
-       u16 nColumn;            /* Number of columns stored in the index */
-       u8 onError;             /* ON_CONFLICT_ACTION_ABORT, _IGNORE, _REPLACE,
-                                * or _NONE
-                                */
-       unsigned idxType:2;     /* 1==UNIQUE, 2==PRIMARY KEY, 0==CREATE INDEX */
+       /** The SQL table being indexed. */
+       Table *pTable;
+       /** String defining the affinity of each column. */
+       char *zColAff;
+       /** The next index associated with the same table. */
+       Index *pNext;
+       /** Schema containing this index. */
+       Schema *pSchema;
+       /** WHERE clause for partial indices. */
+       Expr *pPartIdxWhere;
+       /** DB Page containing root of this index. */
+       int tnum;
+       /**
+        * Conflict resolution algorithm to employ whenever an
+        * attempt is made to insert a non-unique element in
+        * unique index.
+        */
+       u8 onError;
+       /**
+        * Index type: non-unique index, unique index, index
+        * implementing UNIQUE constraint or index implementing
+        * PK constraint.
+        */
+       enum sql_index_type index_type;
+       /** Index definition. */
+       struct index_def *def;
 };
 
 /**
@@ -2144,20 +2149,6 @@ struct Index {
 log_est_t
 index_field_tuple_est(struct Index *idx, uint32_t field);
 
-/*
- * Allowed values for Index.idxType
- */
-#define SQLITE_IDXTYPE_APPDEF      0   /* Created using CREATE INDEX */
-#define SQLITE_IDXTYPE_UNIQUE      1   /* Implements a UNIQUE constraint */
-#define SQLITE_IDXTYPE_PRIMARYKEY  2   /* Is the PRIMARY KEY for the table */
-
-/* Return true if index X is a PRIMARY KEY index */
-#define IsPrimaryKeyIndex(X)  ((X)->idxType==SQLITE_IDXTYPE_PRIMARYKEY)
-
-/* Return true if index X is a UNIQUE index */
-#define IsUniqueIndex(X)      (((X)->idxType == SQLITE_IDXTYPE_UNIQUE) || \
-                               ((X)->idxType == SQLITE_IDXTYPE_PRIMARYKEY))
-
 #ifdef DEFAULT_TUPLE_COUNT
 #undef DEFAULT_TUPLE_COUNT
 #endif
@@ -3554,34 +3545,6 @@ void sqlite3AddCollateType(Parse *, Token *);
  */
 struct coll *
 sql_column_collation(struct space_def *def, uint32_t column, uint32_t *coll_id);
-/**
- * Return name of given column collation from index.
- *
- * @param idx Index which is used to fetch column.
- * @param column Number of column.
- * @param[out] coll_id Collation identifier.
- * @retval Pointer to collation.
- */
-struct coll *
-sql_index_collation(Index *idx, uint32_t column, uint32_t *id);
-
-/**
- * Return key_def of provided struct Index.
- * @param idx Pointer to `struct Index` object.
- * @retval Pointer to `struct key_def`.
- */
-struct key_def*
-sql_index_key_def(struct Index *idx);
-
-/**
- * Return sort order of given column from index.
- *
- * @param idx Index which is used to fetch column.
- * @param column Number of column.
- * @retval Sort order of requested column.
- */
-enum sort_order
-sql_index_column_sort_order(Index *idx, uint32_t column);
 
 void sqlite3EndTable(Parse *, Token *, Token *, Select *);
 
@@ -3668,9 +3631,16 @@ void sqlite3SrcListShiftJoinType(SrcList *);
 void sqlite3SrcListAssignCursors(Parse *, SrcList *);
 void sqlite3IdListDelete(sqlite3 *, IdList *);
 void sqlite3SrcListDelete(sqlite3 *, SrcList *);
-Index *sqlite3AllocateIndexObject(sqlite3 *, i16, int, char **);
-bool
-index_is_unique(Index *);
+/**
+ * Allocate SQL index object with part count fields.
+ * @param db SQLite environment.
+ * @param part_count Index part_count.
+ *
+ * @retval NULL Memory error.
+ * @retval not NULL Index object.
+ */
+struct Index *
+sql_index_alloc(struct sqlite3 *db);
 
 /**
  * Create a new index for an SQL table.  name is the name of the
@@ -3696,12 +3666,14 @@ index_is_unique(Index *);
  * @param sort_order Sort order of primary key when pList==NULL.
  * @param if_not_exist Omit error if index already exists.
  * @param idx_type The index type.
+ * @param is_unique Is index unique or not.
  */
 void
 sql_create_index(struct Parse *parse, struct Token *token,
                 struct SrcList *tbl_name, struct ExprList *col_list,
-                int on_error, struct Token *start, struct Expr *pi_where,
-                enum sort_order sort_order, bool if_not_exist, u8 idx_type);
+                enum on_conflict_action on_error, struct Token *start,
+                struct Expr *pi_where, enum sort_order sort_order,
+                bool if_not_exist, enum sql_index_type idx_type);
 
 /**
  * This routine will drop an existing named index.  This routine
@@ -4553,10 +4525,6 @@ int sqlite3InvokeBusyHandler(BusyHandler *);
 int
 sql_analysis_load(struct sqlite3 *db);
 
-uint32_t
-index_column_count(const Index *);
-bool
-index_is_unique_not_null(const Index *);
 void sqlite3RegisterLikeFunctions(sqlite3 *, int);
 int sqlite3IsLikeFunction(sqlite3 *, Expr *, int *, char *);
 void sqlite3SchemaClear(sqlite3 *);
diff --git a/src/box/sql/update.c b/src/box/sql/update.c
index 212adbcb3..113e3ba0e 100644
--- a/src/box/sql/update.c
+++ b/src/box/sql/update.c
@@ -239,17 +239,18 @@ sqlite3Update(Parse * pParse,             /* The parser context */
         */
        for (j = 0, pIdx = pTab->pIndex; pIdx; pIdx = pIdx->pNext, j++) {
                int reg;
-               int nIdxCol = index_column_count(pIdx);
+               uint32_t part_count = pIdx->def->key_def->part_count;
                if (chngPk || hasFK || pIdx->pPartIdxWhere || pIdx == pPk) {
                        reg = ++pParse->nMem;
-                       pParse->nMem += nIdxCol;
+                       pParse->nMem += part_count;
                } else {
                        reg = 0;
-                       for (i = 0; i < nIdxCol; i++) {
-                               i16 iIdxCol = pIdx->aiColumn[i];
-                               if (iIdxCol < 0 || aXRef[iIdxCol] >= 0) {
+                       for (uint32_t i = 0; i < part_count; i++) {
+                               uint32_t fieldno =
+                                       pIdx->def->key_def->parts[i].fieldno;
+                               if (aXRef[fieldno] >= 0) {
                                        reg = ++pParse->nMem;
-                                       pParse->nMem += nIdxCol;
+                                       pParse->nMem += part_count;
                                        break;
                                }
                        }
@@ -299,17 +300,18 @@ sqlite3Update(Parse * pParse,             /* The parser context */
         * In this case we have to manually load columns in order to make tuple.
         */
        int iPk;        /* First of nPk memory cells holding PRIMARY KEY value */
-       i16 nPk;        /* Number of components of the PRIMARY KEY */
+       /* Number of components of the PRIMARY KEY.  */
+       uint32_t pk_part_count;
        int addrOpen;   /* Address of the OpenEphemeral instruction */
 
        if (is_view) {
-               nPk = nKey;
+               pk_part_count = nKey;
        } else {
                assert(pPk != 0);
-               nPk = index_column_count(pPk);
+               pk_part_count = pPk->def->key_def->part_count;
        }
        iPk = pParse->nMem + 1;
-       pParse->nMem += nPk;
+       pParse->nMem += pk_part_count;
        regKey = ++pParse->nMem;
        iEph = pParse->nTab++;
        sqlite3VdbeAddOp2(v, OP_Null, 0, iPk);
@@ -318,7 +320,8 @@ sqlite3Update(Parse * pParse,               /* The parser context */
                addrOpen = sqlite3VdbeAddOp2(v, OP_OpenTEphemeral, iEph,
                                             nKey);
        } else {
-               addrOpen = sqlite3VdbeAddOp2(v, OP_OpenTEphemeral, iEph, nPk);
+               addrOpen = sqlite3VdbeAddOp2(v, OP_OpenTEphemeral, iEph,
+                                            pk_part_count);
                sql_vdbe_set_p4_key_def(pParse, pPk);
        }
 
@@ -328,27 +331,27 @@ sqlite3Update(Parse * pParse,             /* The parser context */
                goto update_cleanup;
        okOnePass = sqlite3WhereOkOnePass(pWInfo, aiCurOnePass);
        if (is_view) {
-               for (i = 0; i < nPk; i++) {
+               for (i = 0; i < (int) pk_part_count; i++) {
                        sqlite3VdbeAddOp3(v, OP_Column, iDataCur, i, iPk + i);
                }
        } else {
-               for (i = 0; i < nPk; i++) {
-                       assert(pPk->aiColumn[i] >= 0);
+               for (i = 0; i < (int) pk_part_count; i++) {
                        sqlite3ExprCodeGetColumnOfTable(v, def, iDataCur,
-                                                       pPk->aiColumn[i],
+                                                       pPk->def->key_def->
+                                                               parts[i].fieldno,
                                                        iPk + i);
                }
        }
 
        if (okOnePass) {
                sqlite3VdbeChangeToNoop(v, addrOpen);
-               nKey = nPk;
+               nKey = pk_part_count;
                regKey = iPk;
        } else {
                const char *zAff = is_view ? 0 :
                                   sqlite3IndexAffinityStr(pParse->db, pPk);
-               sqlite3VdbeAddOp4(v, OP_MakeRecord, iPk, nPk, regKey,
-                                         zAff, nPk);
+               sqlite3VdbeAddOp4(v, OP_MakeRecord, iPk, pk_part_count,
+                                 regKey, zAff, pk_part_count);
                sqlite3VdbeAddOp2(v, OP_IdxInsert, iEph, regKey);
                /* Set flag to save memory allocating one by malloc. */
                sqlite3VdbeChangeP5(v, 1);
diff --git a/src/box/sql/vdbeaux.c b/src/box/sql/vdbeaux.c
index cec0ed647..c96157a55 100644
--- a/src/box/sql/vdbeaux.c
+++ b/src/box/sql/vdbeaux.c
@@ -1150,7 +1150,7 @@ sql_vdbe_set_p4_key_def(struct Parse *parse, struct Index *idx)
        struct Vdbe *v = parse->pVdbe;
        assert(v != NULL);
        assert(idx != NULL);
-       struct key_def *def = key_def_dup(sql_index_key_def(idx));
+       struct key_def *def = key_def_dup(idx->def->key_def);
        if (def == NULL)
                sqlite3OomFault(parse->db);
        else
diff --git a/src/box/sql/vdbemem.c b/src/box/sql/vdbemem.c
index 2ce90747d..d0e16bafb 100644
--- a/src/box/sql/vdbemem.c
+++ b/src/box/sql/vdbemem.c
@@ -1087,15 +1087,15 @@ valueNew(sqlite3 * db, struct ValueNewStat4Ctx *p)
                        Index *pIdx = p->pIdx;       /* Index being probed */
                        int nByte;      /* Bytes of space to allocate */
                        int i;  /* Counter variable */
-                       int nCol = index_column_count(pIdx);
+                       int part_count = pIdx->def->key_def->part_count;
 
-                       nByte = sizeof(Mem) * nCol +
+                       nByte = sizeof(Mem) * part_count +
                                ROUND8(sizeof(UnpackedRecord));
                        pRec =
                            (UnpackedRecord *) sqlite3DbMallocZero(db, nByte);
                        if (pRec == NULL)
                                return NULL;
-                       pRec->key_def = key_def_dup(sql_index_key_def(pIdx));
+                       pRec->key_def = key_def_dup(pIdx->def->key_def);
                        if (pRec->key_def == NULL) {
                                sqlite3DbFree(db, pRec);
                                sqlite3OomFault(db);
@@ -1103,7 +1103,7 @@ valueNew(sqlite3 * db, struct ValueNewStat4Ctx *p)
                        }
                        pRec->aMem = (Mem *)((char *) pRec +
                                             ROUND8(sizeof(UnpackedRecord)));
-                       for (i = 0; i < nCol; i++) {
+                       for (i = 0; i < (int) part_count; i++) {
                                pRec->aMem[i].flags = MEM_Null;
                                pRec->aMem[i].db = db;
                        }
@@ -1621,15 +1621,12 @@ sql_stat4_column(struct sqlite3 *db, const char *record, uint32_t col_num,
 void
 sqlite3Stat4ProbeFree(UnpackedRecord * pRec)
 {
-       if (pRec) {
-               int i;
-               int nCol = pRec->key_def->part_count;
-               Mem *aMem = pRec->aMem;
-               sqlite3 *db = aMem[0].db;
-               for (i = 0; i < nCol; i++) {
+       if (pRec != NULL) {
+               int part_count = pRec->key_def->part_count;
+               struct Mem *aMem = pRec->aMem;
+               for (int i = 0; i < part_count; i++)
                        sqlite3VdbeMemRelease(&aMem[i]);
-               }
-               sqlite3DbFree(db, pRec);
+               sqlite3DbFree(aMem[0].db, pRec);
        }
 }
 
diff --git a/src/box/sql/where.c b/src/box/sql/where.c
index 85143ed20..0ad9a4025 100644
--- a/src/box/sql/where.c
+++ b/src/box/sql/where.c
@@ -370,16 +370,21 @@ whereScanInit(WhereScan * pScan,  /* The WhereScan object being initialized */
        pScan->idxaff = 0;
        pScan->coll = NULL;
        pScan->is_column_seen = false;
-       if (pIdx) {
+       if (pIdx != NULL) {
                int j = iColumn;
-               iColumn = pIdx->aiColumn[j];
-               if (iColumn >= 0) {
-                       char affinity =
+               /*
+                * pIdx->def->opts.sql == "fake_autoindex" means that
+                * pIdx is a fake integer primary key index.
+                */
+               if (pIdx->def->opts.sql != NULL &&
+                   strcmp(pIdx->def->opts.sql, "fake_autoindex") != 0) {
+                       iColumn = pIdx->def->key_def->parts[iColumn].fieldno;
+                       pScan->idxaff =
                                pIdx->pTable->def->fields[iColumn].affinity;
-                       pScan->idxaff = affinity;
-                       uint32_t id;
-                       pScan->coll = sql_index_collation(pIdx, j, &id);
+                       pScan->coll = pIdx->def->key_def->parts[j].coll;
                        pScan->is_column_seen = true;
+               } else {
+                       iColumn = -1;
                }
        }
        pScan->opMask = opMask;
@@ -541,47 +546,24 @@ findIndexCol(Parse * pParse,      /* Parse context */
             Index * pIdx,      /* Index to match column of */
             int iCol)          /* Column of index to match */
 {
+       struct key_part *part_to_match = &pIdx->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->iColumn == pIdx->aiColumn[iCol] &&
-                   p->iTable == iBase) {
+               if (p->op == TK_COLUMN && p->iTable == iBase &&
+                   p->iColumn == (int) part_to_match->fieldno) {
                        bool is_found;
                        uint32_t id;
                        struct coll *coll = sql_expr_coll(pParse,
                                                          pList->a[i].pExpr,
                                                          &is_found, &id);
-                       if (is_found &&
-                           coll == sql_index_collation(pIdx, iCol, &id)) {
+                       if (is_found && coll == part_to_match->coll)
                                return i;
-                       }
                }
        }
 
        return -1;
 }
 
-/*
- * Return TRUE if the iCol-th column of index pIdx is NOT NULL
- */
-static int
-indexColumnNotNull(Index * pIdx, int iCol)
-{
-       int j;
-       assert(pIdx != 0);
-       assert(iCol >= 0 && iCol < (int)index_column_count(pIdx));
-       j = pIdx->aiColumn[iCol];
-       if (j >= 0) {
-               return !pIdx->pTable->def->fields[j].is_nullable;
-       } else if (j == (-1)) {
-               return 1;
-       } else {
-               assert(j == (-2));
-               return 0;       /* Assume an indexed expression can always yield a NULL */
-
-       }
-}
-
 /*
  * Return true if the DISTINCT expression-list passed as the third argument
  * is redundant.
@@ -633,9 +615,9 @@ isDistinctRedundant(Parse * pParse,         /* Parsing context */
         *      contain a "col=X" term are subject to a NOT NULL constraint.
         */
        for (pIdx = pTab->pIndex; pIdx; pIdx = pIdx->pNext) {
-               if (!index_is_unique(pIdx))
+               if (!pIdx->def->opts.is_unique)
                        continue;
-               int col_count = index_column_count(pIdx);
+               int col_count = pIdx->def->key_def->part_count;
                for (i = 0; i < col_count; i++) {
                        if (0 ==
                            sqlite3WhereFindTerm(pWC, iBase, i, ~(Bitmask) 0,
@@ -643,11 +625,12 @@ isDistinctRedundant(Parse * pParse,               /* Parsing context */
                                if (findIndexCol
                                    (pParse, pDistinct, iBase, pIdx, i) < 0)
                                        break;
-                               if (indexColumnNotNull(pIdx, i) == 0)
+                               uint32_t j = pIdx->def->key_def->parts[i].fieldno;
+                               if (pIdx->pTable->def->fields[j].is_nullable)
                                        break;
                        }
                }
-               if (i == (int)index_column_count(pIdx)) {
+               if (i == (int) pIdx->def->key_def->part_count) {
                        /* This index implies that the DISTINCT qualifier is redundant. */
                        return 1;
                }
@@ -835,8 +818,7 @@ constructAutomaticIndex(Parse * pParse,                     /* The parsing context */
        }
 
        /* Construct the Index object to describe this index */
-       pIdx =
-           sqlite3AllocateIndexObject(pParse->db, nKeyCol + 1, 0, &zNotUsed);
+       pIdx = sql_index_alloc(pParse->db, nKeyCol + 1);
        if (pIdx == 0)
                goto end_auto_index_create;
        pLoop->pIndex = pIdx;
@@ -1184,7 +1166,7 @@ whereRangeAdjust(WhereTerm * pTerm, LogEst nNew)
 char
 sqlite3IndexColumnAffinity(sqlite3 * db, Index * pIdx, int iCol)
 {
-       assert(iCol >= 0 && iCol < (int)index_column_count(pIdx));
+       assert(iCol >= 0 && iCol < (int) pIdx->def->key_def->part_count);
        if (!pIdx->zColAff) {
                if (sqlite3IndexAffinityStr(db, pIdx) == 0)
                        return AFFINITY_BLOB;
@@ -1246,13 +1228,12 @@ whereRangeSkipScanEst(Parse * pParse,           /* Parsing & code generating context */
        int nUpper = index->def->opts.stat->sample_count + 1;
        int rc = SQLITE_OK;
        u8 aff = sqlite3IndexColumnAffinity(db, p, nEq);
-       uint32_t id;
 
        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 *pColl = sql_index_collation(p, nEq, &id);
+       struct coll *coll = p->def->key_def->parts[nEq].coll;
        if (pLower) {
                rc = sqlite3Stat4ValueFromExpr(pParse, pLower->pExpr->pRight,
                                               aff, &p1);
@@ -1273,12 +1254,12 @@ whereRangeSkipScanEst(Parse * pParse,           /* Parsing & code generating context */
                        rc = sql_stat4_column(db, samples[i].sample_key, nEq,
                                              &pVal);
                        if (rc == SQLITE_OK && p1) {
-                               int res = sqlite3MemCompare(p1, pVal, pColl);
+                               int res = sqlite3MemCompare(p1, pVal, coll);
                                if (res >= 0)
                                        nLower++;
                        }
                        if (rc == SQLITE_OK && p2) {
-                               int res = sqlite3MemCompare(p2, pVal, pColl);
+                               int res = sqlite3MemCompare(p2, pVal, coll);
                                if (res >= 0)
                                        nUpper++;
                        }
@@ -1448,7 +1429,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 (sql_index_column_sort_order(p, nEq) !=
+                       if (p->def->key_def->parts[nEq].sort_order !=
                            SORT_ORDER_ASC) {
                                /* The roles of pLower and pUpper are swapped for a DESC index */
                                SWAP(pLower, pUpper);
@@ -1598,7 +1579,7 @@ whereEqualScanEst(Parse * pParse, /* Parsing & code generating context */
        int bOk;
 
        assert(nEq >= 1);
-       assert(nEq <= (int)index_column_count(p));
+       assert(nEq <= (int) p->def->key_def->part_count);
        assert(pBuilder->nRecValid < nEq);
 
        /* If values are not available for all fields of the index to the left
@@ -1619,7 +1600,7 @@ whereEqualScanEst(Parse * pParse, /* Parsing & code generating context */
 
        whereKeyStats(pParse, p, pRec, 0, a);
        WHERETRACE(0x10, ("equality scan regions %s(%d): %d\n",
-                         p->zName, nEq - 1, (int)a[1]));
+                         p->def->name, nEq - 1, (int)a[1]));
        *pnRow = a[1];
 
        return rc;
@@ -1751,8 +1732,8 @@ whereLoopPrint(WhereLoop * p, WhereClause * pWC)
                           pItem->zAlias ? pItem->zAlias : pTab->def->name);
 #endif
        const char *zName;
-       if (p->pIndex && (zName = p->pIndex->zName) != 0) {
-               if (strncmp(zName, "sqlite_autoindex_", 17) == 0) {
+       if (p->pIndex != NULL && (zName = p->pIndex->def->name) != NULL) {
+               if (strncmp(zName, "sql_autoindex_", 17) == 0) {
                        int i = sqlite3Strlen30(zName) - 1;
                        while (zName[i] != '_')
                                i--;
@@ -2314,7 +2295,7 @@ whereRangeVectorLen(Parse * pParse,       /* Parsing context */
        int nCmp = sqlite3ExprVectorSize(pTerm->pExpr->pLeft);
        int i;
 
-       nCmp = MIN(nCmp, (int)(index_column_count(pIdx) - nEq));
+       nCmp = MIN(nCmp, (int)(pIdx->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.
@@ -2335,13 +2316,11 @@ whereRangeVectorLen(Parse * pParse,     /* Parsing context */
                 * order of the index column is the same as the sort order of the
                 * leftmost index column.
                 */
-               if (pLhs->op != TK_COLUMN
-                   || pLhs->iTable != iCur
-                   || pLhs->iColumn != pIdx->aiColumn[i + nEq]
-                   || sql_index_column_sort_order(pIdx, i + nEq) !=
-                      sql_index_column_sort_order(pIdx, nEq)) {
+               struct key_part *parts = pIdx->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;
-               }
 
                aff = sqlite3CompareAffinity(pRhs, sqlite3ExprAffinity(pLhs));
                idxaff =
@@ -2353,7 +2332,7 @@ whereRangeVectorLen(Parse * pParse,       /* Parsing context */
                pColl = sql_binary_compare_coll_seq(pParse, pLhs, pRhs, &id);
                if (pColl == 0)
                        break;
-               if (sql_index_collation(pIdx, i + nEq, &id) != pColl)
+               if (pIdx->def->key_def->parts[i + nEq].coll != pColl)
                        break;
        }
        return i;
@@ -2396,13 +2375,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 nProbeCol = index_column_count(pProbe);
+       uint32_t probe_part_count = pProbe->def->key_def->part_count;
 
        pNew = pBuilder->pNew;
        if (db->mallocFailed)
                return SQLITE_NOMEM_BKPT;
        WHERETRACE(0x800, ("BEGIN addBtreeIdx(%s), nEq=%d\n",
-                          pProbe->zName, pNew->nEq));
+                          pProbe->def->name, pNew->nEq));
 
        assert((pNew->wsFlags & WHERE_TOP_LIMIT) == 0);
        if (pNew->wsFlags & WHERE_BTM_LIMIT) {
@@ -2431,7 +2410,7 @@ whereLoopAddBtreeIndex(WhereLoopBuilder * pBuilder,       /* The WhereLoop factory */
                stat = &surrogate_stat;
        if (stat->is_unordered)
                opMask &= ~(WO_GT | WO_GE | WO_LT | WO_LE);
-       assert(pNew->nEq < nProbeCol);
+       assert(pNew->nEq < probe_part_count);
 
        saved_nEq = pNew->nEq;
        saved_nBtm = pNew->nBtm;
@@ -2452,10 +2431,14 @@ whereLoopAddBtreeIndex(WhereLoopBuilder * pBuilder,     /* The WhereLoop factory */
                LogEst nOutUnadjusted;  /* nOut before IN() and WHERE adjustments */
                int nIn = 0;
                int nRecValid = pBuilder->nRecValid;
-               if ((eOp == WO_ISNULL || (pTerm->wtFlags & TERM_VNULL) != 0)
-                   && indexColumnNotNull(pProbe, saved_nEq)
-                   ) {
-                       continue;       /* ignore IS [NOT] NULL constraints on NOT NULL columns */
+               uint32_t j = pProbe->def->key_def->parts[saved_nEq].fieldno;
+               if ((eOp == WO_ISNULL || (pTerm->wtFlags & TERM_VNULL) != 0) &&
+                   !pProbe->pTable->def->fields[j].is_nullable) {
+                       /*
+                        * Ignore IS [NOT] NULL constraints on NOT
+                        * NULL columns.
+                        */
+                       continue;
                }
                if (pTerm->prereqRight & pNew->maskSelf)
                        continue;
@@ -2523,14 +2506,16 @@ whereLoopAddBtreeIndex(WhereLoopBuilder * pBuilder,     /* The WhereLoop factory */
                                                         */
                        }
                } else if (eOp & WO_EQ) {
-                       int iCol = pProbe->aiColumn[saved_nEq];
+                       int iCol = pProbe->def->key_def->parts[saved_nEq].fieldno;
                        pNew->wsFlags |= WHERE_COLUMN_EQ;
                        assert(saved_nEq == pNew->nEq);
-                       if ((iCol > 0 && nInMul == 0
-                               && saved_nEq == nProbeCol - 1)
-                           ) {
-                               if (iCol >= 0 &&
-                                   !index_is_unique_not_null(pProbe)) {
+                       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->tnum != 0 &&
+                                   !index_is_unique_not_null) {
                                        pNew->wsFlags |= WHERE_UNQ_WANTED;
                                } else {
                                        pNew->wsFlags |= WHERE_ONEROW;
@@ -2592,8 +2577,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->aiColumn[saved_nEq] >= 0) {
+                       if (pTerm->truthProb <= 0 && pProbe->tnum != 0 ) {
                                assert((eOp & WO_IN) || nIn == 0);
                                testcase(eOp & WO_IN);
                                pNew->nOut += pTerm->truthProb;
@@ -2695,8 +2679,8 @@ whereLoopAddBtreeIndex(WhereLoopBuilder * pBuilder,       /* The WhereLoop factory */
                        pNew->nOut = nOutUnadjusted;
                }
 
-               if ((pNew->wsFlags & WHERE_TOP_LIMIT) == 0
-                   && pNew->nEq < nProbeCol) {
+               if ((pNew->wsFlags & WHERE_TOP_LIMIT) == 0 &&
+                   pNew->nEq < probe_part_count) {
                        whereLoopAddBtreeIndex(pBuilder, pSrc, pProbe,
                                               nInMul + nIn);
                }
@@ -2724,7 +2708,7 @@ whereLoopAddBtreeIndex(WhereLoopBuilder * pBuilder,       /* The WhereLoop factory */
         * more expensive.
         */
        assert(42 == sqlite3LogEst(18));
-       if (saved_nEq == saved_nSkip && saved_nEq + 1U < nProbeCol &&
+       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 &&
@@ -2749,7 +2733,7 @@ whereLoopAddBtreeIndex(WhereLoopBuilder * pBuilder,       /* The WhereLoop factory */
        }
 
        WHERETRACE(0x800, ("END addBtreeIdx(%s), nEq=%d, rc=%d\n",
-                          pProbe->zName, saved_nEq, rc));
+                          pProbe->def->name, saved_nEq, rc));
        return rc;
 }
 
@@ -2792,7 +2776,7 @@ indexMightHelpWithOrderBy(WhereLoopBuilder * pBuilder,
 {
        ExprList *pOB;
        int ii, jj;
-       int nIdxCol = index_column_count(pIndex);
+       int part_count = pIndex->def->key_def->part_count;
        if (index_is_unordered(pIndex))
                return 0;
        if ((pOB = pBuilder->pWInfo->pOrderBy) == 0)
@@ -2802,8 +2786,9 @@ indexMightHelpWithOrderBy(WhereLoopBuilder * pBuilder,
                if (pExpr->op == TK_COLUMN && pExpr->iTable == iCursor) {
                        if (pExpr->iColumn < 0)
                                return 1;
-                       for (jj = 0; jj < nIdxCol; jj++) {
-                               if (pExpr->iColumn == pIndex->aiColumn[jj])
+                       for (jj = 0; jj < part_count; jj++) {
+                               if (pExpr->iColumn == (int)
+                                   pIndex->def->key_def->parts[jj].fieldno)
                                        return 1;
                        }
                }
@@ -2880,9 +2865,7 @@ whereLoopAddBtree(WhereLoopBuilder * pBuilder,    /* WHERE clause information */
 {
        WhereInfo *pWInfo;      /* WHERE analysis context */
        Index *pProbe;          /* An index we are evaluating */
-       Index sPk;              /* A fake index object for the primary key */
-       LogEst aiRowEstPk[2];   /* The aiRowLogEst[] value for the sPk index */
-       i16 aiColumnPk = -1;    /* The aColumn[] value for the sPk index */
+       Index fake_index;               /* A fake index object for the primary key */
        SrcList *pTabList;      /* The FROM clause */
        struct SrcList_item *pSrc;      /* The FROM clause btree term to add */
        WhereLoop *pNew;        /* Template WhereLoop object */
@@ -2903,31 +2886,62 @@ 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;
        } else {
                /* There is no INDEXED BY clause.  Create a fake Index object in local
-                * variable sPk to represent the primary key index.  Make this
+                * 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(&sPk, 0, sizeof(Index));
-               sPk.nColumn = 1;
-               sPk.aiColumn = &aiColumnPk;
-               sPk.aiRowLogEst = aiRowEstPk;
-               sPk.onError = ON_CONFLICT_ACTION_REPLACE;
-               sPk.pTable = pTab;
-               aiRowEstPk[0] = sql_space_tuple_log_count(pTab);
-               aiRowEstPk[1] = 0;
+               memset(&fake_index, 0, sizeof(Index));
+               fake_index.onError = ON_CONFLICT_ACTION_REPLACE;
+               fake_index.pTable = pTab;
+
+               struct key_def *key_def = key_def_new(1);
+               if (key_def == NULL) {
+                       pWInfo->pParse->nErr++;
+                       pWInfo->pParse->rc = SQL_TARANTOOL_ERROR;
+                       return SQL_TARANTOOL_ERROR;
+               }
+
+               key_def_set_part(key_def, 0, 0, pTab->def->fields[0].type,
+                                ON_CONFLICT_ACTION_ABORT,
+                                NULL, COLL_NONE, SORT_ORDER_ASC);
+
+               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);
+               key_def_delete(key_def);
+
+               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(sizeof(struct index_stat));
+               stat->tuple_log_est =
+                       (log_est_t *) malloc(sizeof(log_est_t) * 2);
+               stat->tuple_log_est[0] = sql_space_tuple_log_count(pTab);
+               stat->tuple_log_est[1] = 0;
+               fake_index.def->opts.stat = stat;
+
                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
                         */
-                       sPk.pNext = pFirst;
+                       fake_index.pNext = pFirst;
                }
-               pProbe = &sPk;
+               pProbe = &fake_index;
        }
 
 #ifndef SQLITE_OMIT_AUTOMATIC_INDEX
@@ -3058,6 +3072,11 @@ whereLoopAddBtree(WhereLoopBuilder * pBuilder,   /* WHERE clause information */
                if (pSrc->pIBIndex)
                        break;
        }
+       if (fake_index.def != NULL)
+       {
+               free(fake_index.def->opts.stat->tuple_log_est);
+               index_def_delete(fake_index.def);
+       }
        return rc;
 }
 
@@ -3392,8 +3411,8 @@ wherePathSatisfiesOrderBy(WhereInfo * pWInfo,     /* The WHERE clause */
                                   index_is_unordered(pIndex)) {
                                return 0;
                        } else {
-                               nColumn = index_column_count(pIndex);
-                               isOrderDistinct = index_is_unique(pIndex);
+                               nColumn = pIndex->def->key_def->part_count;
+                               isOrderDistinct = pIndex->def->opts.is_unique;
                        }
 
                        /* Loop through all columns of the index and deal with the ones
@@ -3454,9 +3473,10 @@ wherePathSatisfiesOrderBy(WhereInfo * pWInfo,    /* The WHERE clause */
                                 * (revIdx) for the j-th column of the index.
                                 */
                                if (pIndex != NULL) {
-                                       iColumn = pIndex->aiColumn[j];
-                                       revIdx = sql_index_column_sort_order(pIndex,
-                                                                            j);
+                                       struct key_def *def =
+                                               pIndex->def->key_def;
+                                       iColumn = def->parts[j].fieldno;
+                                       revIdx = def->parts[j].sort_order;
                                        if (iColumn == pIndex->pTable->iPKey)
                                                iColumn = -1;
                                } else {
@@ -3506,8 +3526,7 @@ wherePathSatisfiesOrderBy(WhereInfo * pWInfo,     /* The WHERE clause */
                                                                      pOrderBy->a[i].pExpr,
                                                                      &is_found, &id);
                                                struct coll *idx_coll =
-                                                       sql_index_collation(pIndex,
-                                                                           j, &id);
+                                                       pIndex->def->key_def->parts[j].coll;
                                                if (is_found &&
                                                    coll != idx_coll)
                                                        continue;
@@ -4777,7 +4796,7 @@ sqlite3WhereBegin(Parse * pParse, /* The parser context */
                                        sqlite3VdbeChangeP5(v, OPFLAG_SEEKEQ);  /* Hint to COMDB2 */
                                }
                                if (pIx != NULL)
-                                       VdbeComment((v, "%s", pIx->zName));
+                                       VdbeComment((v, "%s", pIx->def->name));
                                else
                                        VdbeComment((v, "%s", idx_def->name));
 #ifdef SQLITE_ENABLE_COLUMN_USED_MASK
@@ -4910,7 +4929,7 @@ sqlite3WhereEnd(WhereInfo * pWInfo)
                if (pLevel->addrSkip) {
                        sqlite3VdbeGoto(v, pLevel->addrSkip);
                        VdbeComment((v, "next skip-scan on %s",
-                                    pLoop->pIndex->zName));
+                                    pLoop->pIndex->def->name));
                        sqlite3VdbeJumpHere(v, pLevel->addrSkip);
                        sqlite3VdbeJumpHere(v, pLevel->addrSkip - 2);
                }
diff --git a/src/box/sql/wherecode.c b/src/box/sql/wherecode.c
index c35c25ac4..1976583fa 100644
--- a/src/box/sql/wherecode.c
+++ b/src/box/sql/wherecode.c
@@ -48,7 +48,7 @@
 static const char *
 explainIndexColumnName(Index * pIdx, int i)
 {
-       i = pIdx->aiColumn[i];
+       i = pIdx->def->key_def->parts[i].fieldno;
        return pIdx->pTable->def->fields[i].name;
 }
 
@@ -243,7 +243,7 @@ sqlite3WhereExplainOneScan(Parse * pParse,  /* Parse context */
                        if (zFmt) {
                                sqlite3StrAccumAppend(&str, " USING ", 7);
                                if (pIdx != NULL)
-                                       sqlite3XPrintf(&str, zFmt, pIdx->zName);
+                                       sqlite3XPrintf(&str, zFmt, pIdx->def->name);
                                else if (idx_def != NULL)
                                        sqlite3XPrintf(&str, zFmt, idx_def->name);
                                else
@@ -488,7 +488,7 @@ codeEqualityTerm(Parse * pParse,    /* The parsing context */
                int *aiMap = 0;
 
                if (pLoop->pIndex != 0 &&
-                   sql_index_column_sort_order(pLoop->pIndex, iEq)) {
+                   pLoop->pIndex->def->key_def->parts[iEq].sort_order) {
                        testcase(iEq == 0);
                        testcase(bRev);
                        bRev = !bRev;
@@ -736,7 +736,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->zName));
+               VdbeComment((v, "begin skip-scan on %s", pIdx->def->name));
                j = sqlite3VdbeAddOp0(v, OP_Goto);
                pLevel->addrSkip =
                    sqlite3VdbeAddOp4Int(v, (bRev ? OP_SeekLT : OP_SeekGT),
@@ -746,7 +746,8 @@ codeAllEqualityTerms(Parse * pParse,        /* Parsing context */
                sqlite3VdbeJumpHere(v, j);
                for (j = 0; j < nSkip; j++) {
                        sqlite3VdbeAddOp3(v, OP_Column, iIdxCur,
-                                         pIdx->aiColumn[j], regBase + j);
+                                         pIdx->def->key_def->parts[j].fieldno,
+                                         regBase + j);
                        VdbeComment((v, "%s", explainIndexColumnName(pIdx, j)));
                }
        }
@@ -1037,14 +1038,14 @@ sqlite3WhereCodeOneLoopStart(WhereInfo * pWInfo,        /* Complete information about t
                assert(pWInfo->pOrderBy == 0
                       || pWInfo->pOrderBy->nExpr == 1
                       || (pWInfo->wctrlFlags & WHERE_ORDERBY_MIN) == 0);
-               int nIdxCol;
+               uint32_t part_count;
                if (pIdx != NULL)
-                       nIdxCol = index_column_count(pIdx);
+                       part_count = pIdx->def->key_def->part_count;
                else
-                       nIdxCol = idx_def->key_def->part_count;
-               if ((pWInfo->wctrlFlags & WHERE_ORDERBY_MIN) != 0
-                   && pWInfo->nOBSat > 0 && (nIdxCol > nEq)) {
-                       j = pIdx->aiColumn[nEq];
+                       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;
                        /* 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.
@@ -1055,8 +1056,7 @@ sqlite3WhereCodeOneLoopStart(WhereInfo * pWInfo,  /* Complete information about t
                         * FYI: entries in an index are ordered as follows:
                         *      NULL, ... NULL, min_value, ...
                         */
-                       if (j >= 0 &&
-                           pIdx->pTable->def->fields[j].is_nullable) {
+                       if (pIdx->pTable->def->fields[j].is_nullable) {
                                assert(pLoop->nSkip == 0);
                                bSeekPastNull = 1;
                                nExtraReg = 1;
@@ -1093,16 +1093,16 @@ 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;
                                pLevel->iLikeRepCntr <<= 1;
                                pLevel->iLikeRepCntr |=
-                                       bRev ^ (sql_index_column_sort_order(pIdx, nEq) ==
+                                       bRev ^ (def->parts[nEq].sort_order ==
                                                SORT_ORDER_DESC);
                        }
 #endif
                        if (pRangeStart == 0) {
-                               j = pIdx->aiColumn[nEq];
-                               if (j >= 0 &&
-                                   pIdx->pTable->def->fields[j].is_nullable)
+                               j = pIdx->def->key_def->parts[nEq].fieldno;
+                               if (pIdx->pTable->def->fields[j].is_nullable)
                                        bSeekPastNull = 1;
                        }
                }
@@ -1113,10 +1113,9 @@ sqlite3WhereCodeOneLoopStart(WhereInfo * pWInfo, /* Complete information about t
                 * a forward order scan on a descending index, interchange the
                 * start and end terms (pRangeStart and pRangeEnd).
                 */
-               if ((nEq < nIdxCol &&
-                    bRev == (sql_index_column_sort_order(pIdx, nEq) ==
-                             SORT_ORDER_ASC)) ||
-                   (bRev && nIdxCol == nEq)) {
+               if ((nEq < part_count &&
+                    bRev == (pIdx->def->key_def->parts[nEq].sort_order ==
+                             SORT_ORDER_ASC)) || (bRev && part_count == nEq)) {
                        SWAP(pRangeEnd, pRangeStart);
                        SWAP(bSeekPastNull, bStopAtNull);
                        SWAP(nBtm, nTop);
@@ -1196,16 +1195,16 @@ sqlite3WhereCodeOneLoopStart(WhereInfo * pWInfo,        /* Complete information about t
                        }
                } else {
                        pk = sqlite3PrimaryKeyIndex(pIdx->pTable);
-                       affinity =
-                               pIdx->pTable->def->fields[pk->aiColumn[0]].affinity;
+                       uint32_t fieldno = pk->def->key_def->parts[0].fieldno;
+                       affinity = pIdx->pTable->def->fields[fieldno].affinity;
                }
 
-               int nPkCol;
+               uint32_t pk_part_count;
                if (pk != NULL)
-                       nPkCol = index_column_count(pk);
+                       pk_part_count = pk->def->key_def->part_count;
                else
-                       nPkCol = idx_pk->key_def->part_count;
-               if (nPkCol == 1 && affinity == AFFINITY_INTEGER) {
+                       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
                         * here: try to loosely convert FLOAT to INT. If RHS type
@@ -1213,8 +1212,9 @@ 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->aiColumn[i] ==
-                                    pk->aiColumn[0]) ||
+                               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)) {
@@ -1326,17 +1326,17 @@ sqlite3WhereCodeOneLoopStart(WhereInfo * pWInfo,        /* Complete information about t
                        /* pIdx is a covering index.  No need to access the main table. */
                }  else if (iCur != iIdxCur) {
                        Index *pPk = sqlite3PrimaryKeyIndex(pIdx->pTable);
-                       int nPkCol = index_column_count(pPk);
-                       int iKeyReg = sqlite3GetTempRange(pParse, nPkCol);
-                       for (j = 0; j < nPkCol; j++) {
-                               k = pPk->aiColumn[j];
+                       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;
                                sqlite3VdbeAddOp3(v, OP_Column, iIdxCur, k,
                                                  iKeyReg + j);
                        }
                        sqlite3VdbeAddOp4Int(v, OP_NotFound, iCur, addrCont,
-                                            iKeyReg, nPkCol);
+                                            iKeyReg, pk_part_count);
                        VdbeCoverage(v);
-                       sqlite3ReleaseTempRange(pParse, iKeyReg, nPkCol);
+                       sqlite3ReleaseTempRange(pParse, iKeyReg, pk_part_count);
                }
 
                /* Record the instruction used to terminate the loop. */
@@ -1434,10 +1434,10 @@ sqlite3WhereCodeOneLoopStart(WhereInfo * pWInfo,        /* Complete information about t
                 */
                if ((pWInfo->wctrlFlags & WHERE_DUPLICATES_OK) == 0) {
                        Index *pPk = sqlite3PrimaryKeyIndex(pTab);
-                       int nPkCol = index_column_count(pPk);
+                       int pk_part_count = pPk->def->key_def->part_count;
                        regRowset = pParse->nTab++;
                        sqlite3VdbeAddOp2(v, OP_OpenTEphemeral,
-                                         regRowset, nPkCol);
+                                         regRowset, pk_part_count);
                        sql_vdbe_set_p4_key_def(pParse, pPk);
                        regPk = ++pParse->nMem;
                }
@@ -1538,16 +1538,23 @@ sqlite3WhereCodeOneLoopStart(WhereInfo * pWInfo,        /* Complete information about t
                                                int iSet =
                                                    ((ii == pOrWc->nTerm - 1) ? -1 : ii);
                                                Index *pPk = sqlite3PrimaryKeyIndex (pTab);
-                                               int nPk = index_column_count(pPk);
-                                               int iPk;
+                                               struct key_def *def =
+                                                       pPk->def->key_def;
 
                                                /* Read the PK into an array of temp registers. */
-                                               r = sqlite3GetTempRange(pParse, nPk);
-                                               for (iPk = 0; iPk < nPk; iPk++) {
-                                                       int iCol = pPk->aiColumn[iPk];
+                                               r = sqlite3GetTempRange(pParse,
+                                                                       def->part_count);
+                                               for (uint32_t iPk = 0;
+                                                    iPk < def->part_count;
+                                                    iPk++) {
+                                                       uint32_t fieldno =
+                                                               def->parts[iPk].
+                                                               fieldno;
                                                        sqlite3ExprCodeGetColumnToReg
-                                                               (pParse, pTab->def,
-                                                                iCol, iCur,
+                                                               (pParse,
+                                                                pTab->def,
+                                                                fieldno,
+                                                                iCur,
                                                                 r + iPk);
                                                }
 
@@ -1567,20 +1574,21 @@ sqlite3WhereCodeOneLoopStart(WhereInfo * pWInfo,        /* Complete information about t
                                                        jmp1 = sqlite3VdbeAddOp4Int
                                                                (v, OP_Found,
                                                                 regRowset, 0,
-                                                                r, nPk);
+                                                                r,
+                                                                def->part_count);
                                                        VdbeCoverage(v);
                                                }
                                                if (iSet >= 0) {
                                                        sqlite3VdbeAddOp3
                                                                (v, OP_MakeRecord,
-                                                                r, nPk, regPk);
+                                                                r, def->part_count, regPk);
                                                        sqlite3VdbeAddOp2
                                                                (v, OP_IdxInsert,
                                                                 regRowset, regPk);
                                                }
 
                                                /* Release the array of temp registers */
-                                               sqlite3ReleaseTempRange(pParse, r, nPk);
+                                               sqlite3ReleaseTempRange(pParse, r, def->part_count);
                                        }
 
                                        /* Invoke the main loop body as a subroutine */
diff --git a/test/box/misc.result b/test/box/misc.result
index a00d03365..a0b35ecc2 100644
--- a/test/box/misc.result
+++ b/test/box/misc.result
@@ -487,6 +487,7 @@ t;
   160: box.error.ACTION_MISMATCH
   161: box.error.VIEW_MISSING_SQL
   162: box.error.FOREIGN_KEY_CONSTRAINT
+  163: box.error.NO_SUCH_COLLATION
 ...
 test_run:cmd("setopt delimiter ''");
 ---
diff --git a/test/sql-tap/analyze6.test.lua b/test/sql-tap/analyze6.test.lua
index 7f4ce1e3e..cb1710a50 100755
--- a/test/sql-tap/analyze6.test.lua
+++ b/test/sql-tap/analyze6.test.lua
@@ -116,7 +116,7 @@ test:do_eqp_test(
     [[SELECT * FROM t201 WHERE y=5]],
     {
         -- <analyze6-2.2>
-        {0, 0, 0, "SEARCH TABLE T201 USING COVERING INDEX sqlite_autoindex_T201_1 (Y=?)"}
+        {0, 0, 0, "SEARCH TABLE T201 USING COVERING INDEX sql_autoindex_T201_1 (Y=?)"}
         -- </analyze6-2.2>
 })
 
@@ -148,7 +148,7 @@ test:do_eqp_test(
     [[SELECT * FROM t201 WHERE y=5]],
     {
         -- <analyze6-2.5>
-        {0, 0, 0, "SEARCH TABLE T201 USING COVERING INDEX sqlite_autoindex_T201_1 (Y=?)"}
+        {0, 0, 0, "SEARCH TABLE T201 USING COVERING INDEX sql_autoindex_T201_1 (Y=?)"}
         -- </analyze6-2.5>
 })
 
@@ -183,7 +183,7 @@ test:do_eqp_test(
     [[SELECT * FROM t201 WHERE y=5]],
     {
         -- <analyze6-2.8>
-        {0, 0, 0, "SEARCH TABLE T201 USING COVERING INDEX sqlite_autoindex_T201_1 (Y=?)"}
+        {0, 0, 0, "SEARCH TABLE T201 USING COVERING INDEX sql_autoindex_T201_1 (Y=?)"}
         -- </analyze6-2.8>
 })
 
diff --git a/test/sql-tap/collation.test.lua b/test/sql-tap/collation.test.lua
index 8a98de987..4d33d0c9d 100755
--- a/test/sql-tap/collation.test.lua
+++ b/test/sql-tap/collation.test.lua
@@ -1,6 +1,6 @@
 #!/usr/bin/env tarantool
 test = require("sqltester")
-test:plan(173)
+test:plan(174)
 
 local prefix = "collation-"
 
@@ -249,4 +249,9 @@ local like_testcases =
 
 test:do_catchsql_set_test(like_testcases, prefix)
 
+test:do_catchsql_test(
+        "collation-2.5.0",
+        'CREATE TABLE test3 (a int, b int, c int, PRIMARY KEY (a, a COLLATE foo, b, c))',
+        {1, "Collation 'FOO' does not exist"})
+
 test:finish_test()
diff --git a/test/sql-tap/colname.test.lua b/test/sql-tap/colname.test.lua
index c53a1e885..ddc06eea7 100755
--- a/test/sql-tap/colname.test.lua
+++ b/test/sql-tap/colname.test.lua
@@ -643,13 +643,13 @@ test:do_catchsql_test(
     "colname-11.2",
     [[CREATE TABLE t1(a, b, c, d, e, 
       PRIMARY KEY(a), UNIQUE('b' COLLATE "unicode_ci" DESC));]],
-    {1, "/functional indexes aren't supported in the current version/"})
+    {1, "/Tarantool does not support functional indexes/"})
 
 test:execsql("create table table1(a primary key, b, c)")
 
 test:do_catchsql_test(
     "colname-11.3",
     [[ CREATE INDEX t1c ON table1('c'); ]],
-    {1, "/functional indexes aren't supported in the current version/"})
+    {1, "/Tarantool does not support functional indexes/"})
 
 test:finish_test()
diff --git a/test/sql-tap/gh-2931-savepoints.test.lua b/test/sql-tap/gh-2931-savepoints.test.lua
index 9668d8fde..3861bb209 100755
--- a/test/sql-tap/gh-2931-savepoints.test.lua
+++ b/test/sql-tap/gh-2931-savepoints.test.lua
@@ -80,7 +80,7 @@ local testcases = {
                {0,{1,2,10,11,1,2,4,10,11}}},
        {"14",
                [[insert into t1 values(4);]],
-               {1,"Duplicate key exists in unique index 'sqlite_autoindex_T2_1' in space 'T2'"}},
+               {1,"Duplicate key exists in unique index 'sql_autoindex_T2_1' in space 'T2'"}},
        {"15",
                [[select * from t1 union all select * from t2;]],
                {0,{1,2,10,11,1,2,4,10,11}}},
diff --git a/test/sql-tap/gh2140-trans.test.lua b/test/sql-tap/gh2140-trans.test.lua
index 5539ed261..fe7af5f8e 100755
--- a/test/sql-tap/gh2140-trans.test.lua
+++ b/test/sql-tap/gh2140-trans.test.lua
@@ -32,7 +32,7 @@ for _, verb in ipairs({'ROLLBACK', 'ABORT'}) do
        if verb == 'ROLLBACK' then
                answer = 'UNIQUE constraint failed: T1.S1'
        else
-               answer = "Duplicate key exists in unique index 'sqlite_autoindex_T1_1' in space 'T1'"
+               answer = "Duplicate key exists in unique index 'sql_autoindex_T1_1' in space 'T1'"
        end
         test:do_catchsql_test('insert1_'..verb,
                               [[BEGIN;
diff --git a/test/sql-tap/gh2259-in-stmt-trans.test.lua b/test/sql-tap/gh2259-in-stmt-trans.test.lua
index 41d52feb6..e2ae1694b 100755
--- a/test/sql-tap/gh2259-in-stmt-trans.test.lua
+++ b/test/sql-tap/gh2259-in-stmt-trans.test.lua
@@ -18,7 +18,7 @@ for _, prefix in pairs({"BEFORE", "AFTER"}) do
 
     test:do_catchsql_test(prefix..'_insert1',
                           'INSERT INTO t1 VALUES(1, 2)',
-                          {1,"Duplicate key exists in unique index 'sqlite_autoindex_T2_1' in space 'T2'"})
+                          {1,"Duplicate key exists in unique index 'sql_autoindex_T2_1' in space 'T2'"})
 
     test:do_execsql_test(prefix..'_insert1_check1',
                          'SELECT *  FROM t1',
@@ -34,7 +34,7 @@ for _, prefix in pairs({"BEFORE", "AFTER"}) do
 
     test:do_catchsql_test(prefix..'_update1',
                           'UPDATE t1 SET s1=1',
-                          {1,"Duplicate key exists in unique index 'sqlite_autoindex_T2_1' in space 'T2'"})
+                          {1,"Duplicate key exists in unique index 'sql_autoindex_T2_1' in space 'T2'"})
 
     test:do_execsql_test(prefix..'_update1_check1',
                          'SELECT *  FROM t1',
@@ -52,7 +52,7 @@ for _, prefix in pairs({"BEFORE", "AFTER"}) do
 
     test:do_catchsql_test(prefix..'delete1',
                           'DELETE FROM t1;',
-                          {1, "Duplicate key exists in unique index 'sqlite_autoindex_T2_1' in space 'T2'"})
+                          {1, "Duplicate key exists in unique index 'sql_autoindex_T2_1' in space 'T2'"})
 
     -- Nothing should be inserted due to abort
     test:do_execsql_test('delete1_check1',
@@ -69,7 +69,7 @@ end
 -- Check multi-insert
 test:do_catchsql_test('insert2',
                       'INSERT INTO t1 VALUES (5, 6), (6, 7)',
-                      {1, "Duplicate key exists in unique index 'sqlite_autoindex_T2_1' in space 'T2'"})
+                      {1, "Duplicate key exists in unique index 'sql_autoindex_T2_1' in space 'T2'"})
 test:do_execsql_test('insert2_check',
                      'SELECT * FROM t1;',
                      {3, 3})
diff --git a/test/sql-tap/gh2964-abort.test.lua b/test/sql-tap/gh2964-abort.test.lua
index 193aabb25..a06b4fd15 100755
--- a/test/sql-tap/gh2964-abort.test.lua
+++ b/test/sql-tap/gh2964-abort.test.lua
@@ -13,7 +13,7 @@ test:do_catchsql_test(
     "CREATE TABLE t2 (a int primary key);")
 
 local insert_err = {1, "UNIQUE constraint failed: T2.A"}
-local insert_err_PK = {1, "Duplicate key exists in unique index 'sqlite_autoindex_T2_1' in space 'T2'"}
+local insert_err_PK = {1, "Duplicate key exists in unique index 'sql_autoindex_T2_1' in space 'T2'"}
 local data = {
 --id|TRIG TYPE|INSERT TYPE|insert error|commit error| result
  {1, "AFTER", "or abort",   insert_err_PK, {0},          {1,1,2}},
diff --git a/test/sql-tap/identifier-characters.test.lua b/test/sql-tap/identifier-characters.test.lua
index 31b45c6e7..24b5dd075 100755
--- a/test/sql-tap/identifier-characters.test.lua
+++ b/test/sql-tap/identifier-characters.test.lua
@@ -10,7 +10,7 @@ local testcases = {
        {"table",
        -- create
        function (id)
-               -- sql autogenerated index name rules add "sqlite_autoindex_") prefix
+               -- sql autogenerated index name rules add "sql_autoindex_") prefix
                if string.len(id) == box.schema.NAME_MAX then
                        id = string.sub(id, string.len(id))
                end
diff --git a/test/sql-tap/identifier_case.test.lua b/test/sql-tap/identifier_case.test.lua
index 5e7573ac4..ed9553c6b 100755
--- a/test/sql-tap/identifier_case.test.lua
+++ b/test/sql-tap/identifier_case.test.lua
@@ -206,8 +206,8 @@ data = {
     { 3,  [["binary"]], {0}},
     { 4,  [["bInaRy"]], {0}},
     { 5,  [["unicode"]], {0}},
-    { 6,  [[ unicode ]], {1,"no such collation sequence: UNICODE"}},
-    { 7,  [["UNICODE"]], {1,"no such collation sequence: UNICODE"}}
+    { 6,  [[ unicode ]], {1,"Collation 'UNICODE' does not exist"}},
+    { 7,  [["UNICODE"]], {1,"Collation 'UNICODE' does not exist"}}
 }
 
 test:do_catchsql_test(
diff --git a/test/sql-tap/index1.test.lua b/test/sql-tap/index1.test.lua
index 4329381e2..9ee40fa13 100755
--- a/test/sql-tap/index1.test.lua
+++ b/test/sql-tap/index1.test.lua
@@ -1,6 +1,6 @@
 #!/usr/bin/env tarantool
 test = require("sqltester")
-test:plan(79)
+test:plan(78)
 
 --!./tcltestrunner.lua
 -- 2001 September 15
@@ -374,24 +374,14 @@ test:do_catchsql_test(
         -- </index-6.1c>
     })
 
-test:do_catchsql_test(
-    "index-6.2",
-    [[
-        CREATE INDEX test1 ON test2(g1)
-    ]], {
-        -- <index-6.2>
-        1, "there is already a table named TEST1"
-        -- </index-6.2>
-    })
-
 test:do_execsql_test(
-    "index-6.2b",
+    "index-6.2",
     [[
         SELECT "name" FROM "_index" WHERE "name"='INDEX1' union SELECT "name" FROM "_space" WHERE "name"='TEST1' OR "name"='TEST2'
     ]], {
-        -- <index-6.2b>
+        -- <index-6.2>
         "INDEX1", "TEST1", "TEST2"
-        -- </index-6.2b>
+        -- </index-6.2>
     })
 
 test:do_test(
@@ -454,10 +444,10 @@ test:do_execsql_test(
 test:do_execsql_test(
     "index-7.3",
     [[
-        SELECT "name" FROM "_index" WHERE "name"='sqlite_autoindex_TEST1_1'
+        SELECT "name" FROM "_index" WHERE "name"='sql_autoindex_TEST1_1'
     ]], {
         -- <index-7.3>
-        "sqlite_autoindex_TEST1_1"
+        "sql_autoindex_TEST1_1"
         -- </index-7.3>
     })
 
@@ -1015,7 +1005,7 @@ test:do_execsql_test(
     })
 
 -- Test that automatically create indices are named correctly. The current
--- convention is: "sqlite_autoindex_<table name>_<integer>"
+-- convention is: "sql_autoindex_<table name>_<integer>"
 --
 -- Then check that it is an error to try to drop any automtically created
 -- indices.
@@ -1027,18 +1017,18 @@ test:do_execsql_test(
         SELECT "_index"."name" FROM "_index" JOIN "_space" WHERE "_index"."id" = "_space"."id" AND "_space"."name"='T7';
     ]], {
         -- <index-17.1>
-        "sqlite_autoindex_T7_3", "sqlite_autoindex_T7_2", "sqlite_autoindex_T7_1"
+        "sql_autoindex_T7_3", "sql_autoindex_T7_2", "sql_autoindex_T7_1"
         -- </index-17.1>
     })
 
 -- do_test index-17.2 {
 --   catchsql {
---     DROP INDEX sqlite_autoindex_t7_1;
+--     DROP INDEX sql_autoindex_t7_1;
 --   }
 -- } {1 {index associated with UNIQUE or PRIMARY KEY constraint cannot be dropped}}
 -- do_test index-17.3 {
 --   catchsql {
---     DROP INDEX IF EXISTS sqlite_autoindex_t7_1;
+--     DROP INDEX IF EXISTS sql_autoindex_t7_1;
 --   }
 -- } {1 {index associated with UNIQUE or PRIMARY KEY constraint cannot be dropped}}
 test:do_catchsql_test(
@@ -1081,7 +1071,7 @@ test:do_execsql_test(
             INSERT INTO t7 VALUES(1);
         ]], {
             -- <index-19.2>
-            1, "Duplicate key exists in unique index 'sqlite_autoindex_T7_1' in space 'T7'"
+            1, "Duplicate key exists in unique index 'sql_autoindex_T7_1' in space 'T7'"
             -- </index-19.2>
         })
 
diff --git a/test/sql-tap/index7.test.lua b/test/sql-tap/index7.test.lua
index 336f42796..a95ac380a 100755
--- a/test/sql-tap/index7.test.lua
+++ b/test/sql-tap/index7.test.lua
@@ -1,6 +1,6 @@
 #!/usr/bin/env tarantool
 test = require("sqltester")
-test:plan(5)
+test:plan(9)
 
 --!./tcltestrunner.lua
 -- 2013-11-04
@@ -48,7 +48,7 @@ end
 -- do_test index7-1.1a {
 --   capture_pragma db out {PRAGMA index_list(t1)}
 --   db eval {SELECT "name", "partial", '|' FROM out ORDER BY "name"}
--- } {sqlite_autoindex_t1_1 0 | t1a 1 | t1b 1 |}
+-- } {sql_autoindex_t1_1 0 | t1a 1 | t1b 1 |}
 -- # Make sure the count(*) optimization works correctly with
 -- # partial indices.  Ticket [a5c8ed66cae16243be6] 2013-10-03.
 -- #
@@ -303,4 +303,74 @@ test:do_catchsql_test(
         1, "keyword \"WHERE\" is reserved"
     })
 
+-- Currently, when a user tries to create index (or primary key,
+-- since we implement them as indexes underhood) with duplicated
+-- fields (like 'CREATE INDEX i1 ON t(a, a, a, a, b, c, b)')
+-- tarantool would silently remove duplicated fields and
+-- execute 'CREATE INDEX i1 ON t(a, b, c)'.
+-- This test checks that duplicates are removed correctly.
+--
+test:do_catchsql_test(
+        "index7-8.1",
+        [[
+            CREATE TABLE t(a,b,c, PRIMARY KEY(a));
+            CREATE INDEX i1 ON t(a, a, b, c, c, b, b, b, c, b, c);
+            pragma index_info = t.i1;
+        ]],
+        {0, {0,0,"A",1,1,"B",2,2,"C"}}
+)
+
+-- There was the following bug:
+-- > CREATE TABLE t1(a,b,c,d, PRIMARY KEY(a,a,a,b,c));
+-- ...
+-- > CREATE INDEX i1 ON t1(b,c,a,c)
+-- ...
+-- But index 'i1' was not actually created and no error was raised.
+-- This test checks that this does not happen anymore (and index is
+-- created successfully).
+--
+test:do_catchsql_test(
+        "index7-8.2",
+        [[
+            CREATE TABLE test4(a,b,c,d, PRIMARY KEY(a,a,a,b,c));
+            CREATE INDEX index1 on test4(b,c,a,c);
+            SELECT "_index"."name"
+            FROM "_index" JOIN "_space" WHERE
+                "_index"."id" = "_space"."id" AND
+                "_space"."name"='TEST4'       AND
+                "_index"."name"='INDEX1';
+        ]],
+        {0, {'INDEX1'}})
+
+-- This test checks that CREATE TABLE statement with PK constraint
+-- and NON-NAMED UNIQUE constraint (declared on THE SAME COLUMNS)
+-- creates only one index - for PK constraint.
+--
+test:do_catchsql_test(
+        "index7-8.3",
+        [[
+            CREATE TABLE test5(a,b,c,d, PRIMARY KEY(a), UNIQUE(a));
+            SELECT "_index"."name"
+            FROM "_index" JOIN "_space" WHERE
+                "_index"."id" = "_space"."id" AND
+                "_space"."name"='TEST5';
+        ]],
+        {0, {'sql_autoindex_TEST5_1'}})
+
+-- This test checks that CREATE TABLE statement with PK constraint
+-- and NAMED UNIQUE constraint (declared on THE SAME COLUMNS)
+-- creates two indexes - for PK constraint and for UNIQUE
+-- constraint.
+--
+test:do_catchsql_test(
+        "index7-8.4",
+        [[
+            CREATE TABLE test6(a,b,c,d, PRIMARY KEY(a), CONSTRAINT c1 UNIQUE(a));
+            SELECT "_index"."name"
+            FROM "_index" JOIN "_space" WHERE
+                "_index"."id" = "_space"."id" AND
+                "_space"."name"='TEST6';
+        ]],
+        {0, {"sql_autoindex_TEST6_1", "sql_autoindex_C1"}})
+
 test:finish_test()
diff --git a/test/sql-tap/intpkey.test.lua b/test/sql-tap/intpkey.test.lua
index ecff3c0ff..b5359b695 100755
--- a/test/sql-tap/intpkey.test.lua
+++ b/test/sql-tap/intpkey.test.lua
@@ -42,7 +42,7 @@ test:do_execsql_test(
         SELECT "_index"."name" FROM "_index" JOIN "_space" WHERE "_index"."id" = "_space"."id" AND "_space"."name"='T1'
     ]], {
         -- <intpkey-1.1>
-        "sqlite_autoindex_T1_1"
+        "sql_autoindex_T1_1"
         -- </intpkey-1.1>
     })
 
@@ -96,7 +96,7 @@ test:do_catchsql_test(
         INSERT INTO t1 VALUES(5,'second','entry');
     ]], {
         -- <intpkey-1.6>
-        1, "Duplicate key exists in unique index 'sqlite_autoindex_T1_1' in space 'T1'"
+        1, "Duplicate key exists in unique index 'sql_autoindex_T1_1' in space 'T1'"
         -- </intpkey-1.6>
     })
 
diff --git a/test/sql-tap/misc1.test.lua b/test/sql-tap/misc1.test.lua
index 5e1dfd1e6..a5078b5f5 100755
--- a/test/sql-tap/misc1.test.lua
+++ b/test/sql-tap/misc1.test.lua
@@ -380,7 +380,7 @@ test:do_catchsql_test(
         INSERT INTO t5 VALUES(1,2,4);
     ]], {
         -- <misc1-7.4>
-        1, "Duplicate key exists in unique index 'sqlite_autoindex_T5_1' in space 'T5'"
+        1, "Duplicate key exists in unique index 'sql_autoindex_T5_1' in space 'T5'"
         -- </misc1-7.4>
     })
 
diff --git a/test/sql-tap/unique.test.lua b/test/sql-tap/unique.test.lua
index 86d119751..63b50657b 100755
--- a/test/sql-tap/unique.test.lua
+++ b/test/sql-tap/unique.test.lua
@@ -70,7 +70,7 @@ test:do_catchsql_test(
         INSERT INTO t1(a,b,c) VALUES(1,3,4)
     ]], {
         -- <unique-1.3>
-        1, "Duplicate key exists in unique index 'sqlite_autoindex_T1_2' in space 'T1'"
+        1, "Duplicate key exists in unique index 'sql_autoindex_T1_2' in space 'T1'"
         -- </unique-1.3>
     })
 
@@ -91,7 +91,7 @@ test:do_catchsql_test(
         INSERT INTO t1(a,b,c) VALUES(3,2,4)
     ]], {
         -- <unique-1.5>
-        1, "Duplicate key exists in unique index 'sqlite_autoindex_T1_1' in space 'T1'"
+        1, "Duplicate key exists in unique index 'sql_autoindex_T1_1' in space 'T1'"
         -- </unique-1.5>
     })
 
@@ -287,7 +287,7 @@ test:do_catchsql_test(
         SELECT a,b,c,d FROM t3 ORDER BY a,b,c,d;
     ]], {
         -- <unique-3.4>
-        1, "Duplicate key exists in unique index 'sqlite_autoindex_T3_2' in space 'T3'"
+        1, "Duplicate key exists in unique index 'sql_autoindex_T3_2' in space 'T3'"
         -- </unique-3.4>
     })
 
@@ -444,7 +444,7 @@ test:do_catchsql_test(
         INSERT INTO t5 VALUES(2, 1,2,3,4,5,6);
     ]], {
         -- <unique-5.2>
-        1, "Duplicate key exists in unique index 'sqlite_autoindex_T5_2' in space 'T5'"
+        1, "Duplicate key exists in unique index 'sql_autoindex_T5_2' in space 'T5'"
         -- </unique-5.2>
     })
 
diff --git a/test/sql-tap/update.test.lua b/test/sql-tap/update.test.lua
index a4efe4015..1ed951da1 100755
--- a/test/sql-tap/update.test.lua
+++ b/test/sql-tap/update.test.lua
@@ -917,7 +917,7 @@ test:do_catchsql_test("update-10.3", [[
   SELECT * FROM t1;
 ]], {
   -- <update-10.3>
-  1, "Duplicate key exists in unique index 'sqlite_autoindex_T1_3' in space 'T1'"
+  1, "Duplicate key exists in unique index 'sql_autoindex_T1_3' in space 'T1'"
   -- </update-10.3>
 })
 
@@ -943,7 +943,7 @@ test:do_catchsql_test("update-10.6", [[
   SELECT * FROM t1;
 ]], {
   -- <update-10.6>
-  1, "Duplicate key exists in unique index 'sqlite_autoindex_T1_1' in space 'T1'"
+  1, "Duplicate key exists in unique index 'sql_autoindex_T1_1' in space 'T1'"
   -- </update-10.6>
 })
 
@@ -969,7 +969,7 @@ test:do_catchsql_test("update-10.9", [[
   SELECT * FROM t1;
 ]], {
   -- <update-10.9>
-  1, "Duplicate key exists in unique index 'sqlite_autoindex_T1_2' in space 'T1'"
+  1, "Duplicate key exists in unique index 'sql_autoindex_T1_2' in space 'T1'"
   -- </update-10.9>
 })
 
diff --git a/test/sql/insert-unique.result b/test/sql/insert-unique.result
index 048d6284c..359ac4346 100644
--- a/test/sql/insert-unique.result
+++ b/test/sql/insert-unique.result
@@ -24,8 +24,7 @@ box.sql.execute("INSERT INTO zoobar VALUES (111, 222, 'c3', 444)")
 -- PK must be unique
 box.sql.execute("INSERT INTO zoobar VALUES (112, 222, 'c3', 444)")
 ---
-- error: Duplicate key exists in unique index 'sqlite_autoindex_ZOOBAR_1' in space
-    'ZOOBAR'
+- error: Duplicate key exists in unique index 'sql_autoindex_ZOOBAR_1' in space 'ZOOBAR'
 ...
 -- Unique index must be respected
 box.sql.execute("INSERT INTO zoobar VALUES (111, 223, 'c3', 444)")
diff --git a/test/sql/iproto.result b/test/sql/iproto.result
index 4cf790169..26ad17b3a 100644
--- a/test/sql/iproto.result
+++ b/test/sql/iproto.result
@@ -553,7 +553,7 @@ future1:wait_result()
 future2:wait_result()
 ---
 - null
-- 'Failed to execute SQL statement: Duplicate key exists in unique index ''sqlite_autoindex_TEST_1''
+- 'Failed to execute SQL statement: Duplicate key exists in unique index ''sql_autoindex_TEST_1''
   in space ''TEST'''
 ...
 future3:wait_result()
diff --git a/test/sql/message-func-indexes.result b/test/sql/message-func-indexes.result
index 37ed4ec6e..5928a8ea8 100644
--- a/test/sql/message-func-indexes.result
+++ b/test/sql/message-func-indexes.result
@@ -18,25 +18,25 @@ box.sql.execute("CREATE TABLE t2(object INTEGER PRIMARY KEY, price INTEGER, coun
 -- should return certain message.
 box.sql.execute("CREATE INDEX i1 ON t1(a+1)")
 ---
-- error: functional indexes aren't supported in the current version
+- error: Tarantool does not support functional indexes
 ...
 box.sql.execute("CREATE INDEX i2 ON t1(a)")
 ---
 ...
 box.sql.execute("CREATE INDEX i3 ON t2(price + 100)")
 ---
-- error: functional indexes aren't supported in the current version
+- error: Tarantool does not support functional indexes
 ...
 box.sql.execute("CREATE INDEX i4 ON t2(price)")
 ---
 ...
 box.sql.execute("CREATE INDEX i5 ON t2(count + 1)")
 ---
-- error: functional indexes aren't supported in the current version
+- error: Tarantool does not support functional indexes
 ...
 box.sql.execute("CREATE INDEX i6 ON t2(count * price)")
 ---
-- error: functional indexes aren't supported in the current version
+- error: Tarantool does not support functional indexes
 ...
 -- Cleaning up.
 box.sql.execute("DROP TABLE t1<a class="moz-txt-link-rfc2396E" href="mailto:)diff--gita/test/sql/on-conflict.resultb/test/sql/on-conflict.resultindexc0d0de08d..4080648aa100644---a/test/sql/on-conflict.result+++b/test/sql/on-conflict.result@@-23,7+23,7@@box.sql.execute(">")
diff --git a/test/sql/on-conflict.result b/test/sql/on-conflict.result
index c0d0de08d..4080648aa 100644
--- a/test/sql/on-conflict.result
+++ b/test/sql/on-conflict.result
@@ -23,7 +23,7 @@ box.sql.execute("</a>CREATE TABLE e (id INTEGER PRIMARY KEY ON CONFLICT REPLACE, v I
 -- Insert values and select them
 box.sql.execute("INSERT INTO t values (1, 1), (2, 2), (3, 1)")
 ---
-- error: Duplicate key exists in unique index 'sqlite_autoindex_T_1' in space 'T'
+- error: Duplicate key exists in unique index 'sql_autoindex_T_1' in space 'T'
 ...
 box.sql.execute("SELECT * FROM t")
 ---
diff --git a/test/sql/persistency.result b/test/sql/persistency.result
index e3b4662de..f64e666cf 100644
--- a/test/sql/persistency.result
+++ b/test/sql/persistency.result
@@ -26,8 +26,7 @@ box.sql.execute("INSERT INTO foobar VALUES (1000, 'foobar')")
 ...
 box.sql.execute("INSERT INTO foobar VALUES (1, 'duplicate')")
 ---
-- error: Duplicate key exists in unique index 'sqlite_autoindex_FOOBAR_1' in space
-    'FOOBAR'
+- error: Duplicate key exists in unique index 'sql_autoindex_FOOBAR_1' in space 'FOOBAR'
 ...
 -- simple select
 box.sql.execute("SELECT bar, foo, 42, 'awesome' FROM foobar<a class="moz-txt-link-rfc2396E" href="mailto:)@@-209,8+208,7@@box.sql.execute(">")
@@ -209,8 +208,7 @@ box.sql.execute("</a>SELECT \"name\", \"opts\" FROM \"_trigger\"")
 -- prove barfoo2 still exists
 box.sql.execute("INSERT INTO barfoo VALUES ('xfoo', 1)")
 ---
-- error: Duplicate key exists in unique index 'sqlite_autoindex_BARFOO_1' in space
-    'BARFOO'
+- error: Duplicate key exists in unique index 'sql_autoindex_BARFOO_1' in space 'BARFOO'
 ...
 box.sql.execute("SELECT * FROM barfoo")
 ---
diff --git a/test/sql/transition.result b/test/sql/transition.result
index 70fb2db40..765b0f0bf 100644
--- a/test/sql/transition.result
+++ b/test/sql/transition.result
@@ -23,8 +23,7 @@ box.sql.execute("INSERT INTO foobar VALUES (1000, 'foobar')")
 ...
 box.sql.execute("INSERT INTO foobar VALUES (1, 'duplicate')")
 ---
-- error: Duplicate key exists in unique index 'sqlite_autoindex_FOOBAR_1' in space
-    'FOOBAR'
+- error: Duplicate key exists in unique index 'sql_autoindex_FOOBAR_1' in space 'FOOBAR'
 ...
 -- simple select
 box.sql.execute("SELECT bar, foo, 42, 'awesome' FROM foobar<a class="moz-txt-link-rfc2396E" href="mailto:)@@-142,8+141,7@@box.sql.execute(">")
@@ -142,8 +141,7 @@ box.sql.execute("</a>INSERT INTO barfoo VALUES ('foobar', 1000)")
 -- prove barfoo2 was created
 box.sql.execute("INSERT INTO barfoo VALUES ('xfoo', 1)")
 ---
-- error: Duplicate key exists in unique index 'sqlite_autoindex_BARFOO_1' in space
-    'BARFOO'
+- error: Duplicate key exists in unique index 'sql_autoindex_BARFOO_1' in space 'BARFOO'
 ...
 box.sql.execute("SELECT foo, bar FROM barfoo")
 ---
-- 
</tt></pre>
  </body>
</html>