Tarantool development patches archive
 help / color / mirror / Atom feed
* [tarantool-patches] Re: [PATCH v3] sql: add index_def to struct Index
@ 2018-06-13  7:30 Ivan Koptelov
  2018-06-18 18:45 ` Kirill Shcherbatov
  0 siblings, 1 reply; 23+ messages in thread
From: Ivan Koptelov @ 2018-06-13  7:30 UTC (permalink / raw)
  To: tarantool-patches; +Cc: Vladislav Shpilevoy

[-- Attachment #1: Type: text/plain, Size: 110555 bytes --]



> 1. Please, do not paste SMTP headers in the body.
> 
> 2. Looks like in the diff below all tabs are turned into 4 spaces. Please
> cope with it. Maybe, your IDE made it.

> > 
> > 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
> > and zName are removed from Index. All usages of this
> > fields changed to usage of corresponding index_def
> > fields.
> > index_is_unique(), sql_index_collation() and
> > index_column_count() are removed with calls of
> > index_def corresponding fields.
> > 
> > Closes: #3369
> > Github branch: 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>
> 
> 3. Please, put branch link below ---.
> 
> > ---
> 
> Here. And do not forget about link to the issue.
> 
>      Branch: <link>
>      Issue: <link>
> 
> > src/box/sql.c | 18 +--
> > src/box/sql/analyze.c | 24 +--
> > src/box/sql/build.c | 398 ++++++++++++++++++++++++------------------------
> > src/box/sql/delete.c | 16 +-
> > src/box/sql/expr.c | 59 ++++---
> > src/box/sql/fkey.c | 41 +++--
> > src/box/sql/insert.c | 134 +++++++---------
> > src/box/sql/pragma.c | 19 ++-
> > src/box/sql/select.c | 2 +-
> > src/box/sql/sqliteInt.h | 25 +--
> > src/box/sql/trigger.c | 2 -
> > src/box/sql/update.c | 10 +-
> > src/box/sql/vdbemem.c | 2 +-
> > src/box/sql/where.c | 140 ++++++++---------
> > src/box/sql/wherecode.c | 43 +++---
> > src/box/sql/whereexpr.c | 15 --
> > 16 files changed, 433 insertions(+), 515 deletions(-)
> >> diff --git a/src/box/sql/build.c b/src/box/sql/build.c
> > index 28e4d7a4d..74fb66565 100644
> > --- a/src/box/sql/build.c
> > +++ b/src/box/sql/build.c
> > @@ -1072,11 +1075,9 @@ sqlite3AddCollateType(Parse * pParse, Token * pToken)
> > * 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] =
> > - sql_column_collation(p->def, i, id);
> > + assert(pIdx->def->key_def->part_count == 1);
> > + if ((int)pIdx->def->key_def->parts[0].fieldno == i) {
> > + id = &pIdx->def->key_def->parts[0].coll_id;
> 
> 4. I have just noticed the zColl leaks here. It is not deleted if
> coll != NULL, but must.
> 
> 5. Here you have removed coll * initialization.
> sql_column_collation function here was used to set coll id and collation,
> by column number. But now this whole cycle does nothing as you can see.
> Please, return this initialization. You sill must init
> key_def->parts[0].coll_id and coll.
> 
> > }
> > }
> > } else {
> > @@ -1123,52 +1124,10 @@ sql_index_key_def(struct Index *idx)
> > enum sort_order
> > sql_index_column_sort_order(Index *idx, uint32_t column)
> 
> 6. Now this function is useless one line wrapper. Please,
> remove it too.
> 
> > {
> > - 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;
> > + return idx->def->key_def->parts[column].sort_order;
> > }
> > 
> > /**
> > @@ -1383,14 +1342,16 @@ 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)
> > +hasColumn(const struct key_part *key_parts, int nCol, const struct key_part key_part)
> 
> 7. Passing a struct by value is very bad idea. Please, don't do it ever.
> And looks like here fieldno is enough.
> 
> > {
> > - while (nCol-- > 0)
> > - if (x == *(aiCol++))
> > + int i = 0;
> > + while (i < nCol) {
> > + if (key_part.fieldno == key_parts->fieldno)
> > return 1;
> > + key_parts++;
> > + i++;
> > + }
> > return 0;
> > }
> > 
> > @@ -1410,13 +1371,13 @@ static void
> > convertToWithoutRowidTable(Parse * pParse, Table * pTab)
> > {
> > Index *pPk;
> > - int i, j;
> > + uint32_t i, j;
> 
> 8. Please, do not predeclare cycle iterators when possible. It is
> SQLite code style, not Tarantool. When you change the SQLite code,
> it must turn into Tarantool style.
> 
> > @@ -1454,14 +1415,17 @@ convertToWithoutRowidTable(Parse * pParse, Table * pTab)
> > * "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--;
> > + for (i = j = 1; i < pPk->def->key_def->part_count; i++) {
> > + if (hasColumn(pPk->def->key_def->parts, j,
> > + pPk->def->key_def->parts[i])) {
> > + pPk->def->key_def->part_count--;
> > } else {
> > - pPk->aiColumn[j++] = pPk->aiColumn[i];
> > + pPk->def->key_def->parts[j++] =
> > + pPk->def->key_def->parts[i];
> > }
> 
> 9. Wrong alignments almost on all new lines. And please, save a
> key_def->parts in a separate variable and use it instead of the full
> path pPk->def->key_def->parts. It is too long to be used 4 times on
> 6 lines.
> 
> 10. I see that cycle iterates until pPk->def->key_def->part_count,
> but this variable is decremented inside the cycle. So the last
> columns are not checked. This value must be saved in a separate
> variable before usage as a 'for' guard.
> 
> > }
> > - pPk->nColumn = j;
> > +
> > + pPk->def->key_def->part_count = j;
> 
> 11. This line makes no sense. You have already updated part_count
> inside the cycle. Either you update here, or in the cycle.
> 
> > }
> > assert(pPk != 0);
> > }
> > @@ -2654,8 +2618,9 @@ sqlite3RefillIndex(Parse * pParse, Index * pIndex, int memRootPage)
> > }
> > /* 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);
> 
> 12. Wrong alignment.
> 
> > 
> > /* Open the table. Loop through all rows of the table, inserting index
> > * records into the sorter.
> > @@ -2687,7 +2652,7 @@ 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);
> 
> 13. Same.
> 
> > VdbeCoverage(v);
> > sqlite3UniqueConstraint(pParse, ON_CONFLICT_ACTION_ABORT,
> > pIndex);
> > @@ -2733,16 +2698,11 @@ sqlite3AllocateIndexObject(sqlite3 * db, /* Database connection */
> > p = sqlite3DbMallocZero(db, nByte + nExtra);
> > if (p) {
> > char *pExtra = ((char *)p) + ROUND8(sizeof(Index));
> > - p->coll_array = (struct coll **)pExtra;
> 
> 14. I still see coll_array in build.c in comments.
> 
> > pExtra += ROUND8(sizeof(struct coll **) * nCol);
> > - p->coll_id_array = (uint32_t *) pExtra;
> 
> 15. Same.
> 
> > pExtra += ROUND8(sizeof(uint32_t) * nCol);
> > p->aiRowLogEst = (LogEst *) pExtra;
> > pExtra += sizeof(LogEst) * (nCol + 1);
> > - p->aiColumn = (i16 *) pExtra;
> 
> 16. Same. And in very many places.
> 
> > pExtra += sizeof(i16) * nCol;
> > - p->sort_order = (enum sort_order *) pExtra;
> > - p->nColumn = nCol;
> > *ppExtra = ((char *)p) + nByte;
> 
> 17. You have removed the fields, but did not update
> sqlite3AllocateIndexObject that still allocates memory for them.
> 
> > }
> > return p;
> > @@ -2831,18 +2791,119 @@ addIndexToTable(Index * pIndex, Table * pTab)
> > }
> > }
> > 
> > -bool
> > -index_is_unique(Index *idx)
> > +void
> > +append(struct region *r, const char *str, size_t *total_sql_size)
> > {
> > - 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);
> > + memcpy(region_alloc(r, strlen(str)), str, strlen(str));
> > + *total_sql_size += strlen(str);
> > +}
> 18. Please, rename append function. This name is too common. And
> make it static inline.
> 
> 19. You do not check region_alloc fails.
> 
> > +
> > +char *
> > +create_sql(const char *idx_name, struct space_def *space_def, ExprList *expr_list)
> 
> 20. Same as 18.
> 
> 21. Out of 80 symbols.
> 
> 22. Please, use struct ExprList, not ExprList.
> 
> 23. I still can not understand why can not you merge this function into
> index_def building. It is needed in a single place, and makes redundant
> scan of ExprList.
> 
> > +{
> > + struct region *r = &fiber()->gc;
> > + size_t total_sql_size = 0;
> > + append(r, "CREATE INDEX ", &total_sql_size);
> > + append(r, idx_name, &total_sql_size);
> > + append(r, " ON ", &total_sql_size);
> > + append(r, space_def->name, &total_sql_size);
> > + append(r, " (", &total_sql_size);
> > +
> > + for (int i = 0; i < expr_list->nExpr; i++){
> > + Expr *expr = expr_list->a[i].pExpr;
> > + assert(expr->op == TK_COLLATE || expr->op == TK_COLUMN);
> > + Expr *column_expr = sqlite3ExprSkipCollate(expr);
> > + const char *name = space_def->fields[column_expr->iColumn].name;
> > +
> > + if (expr->op == TK_COLLATE){
> > + append(r, name, &total_sql_size);
> > + append(r, " COLLATE ", &total_sql_size);
> > + const char *coll_name = expr->u.zToken;
> > + append(r, coll_name, &total_sql_size);
> > + append(r, ", ", &total_sql_size);
> > + } else {
> > + append(r, name, &total_sql_size);
> > + append(r, ", ", &total_sql_size);
> > + }
> > + }
> > +
> > + memcpy(region_alloc(r, 1), "\0", 1);
> > + total_sql_size += 1;
> > + char *res = region_join(r, total_sql_size);
> > +
> > + /*
> > + * fix last ", " with ")\0"
> > + */
> > + res[strlen(res) - 2] = ')';
> > + res[strlen(res) - 1] = '\0';
> 
> 24. strlen has O(N) complexity. And here you already know the
> result: total_sql_size.
> 
> > + return res;
> > +}
> > +
> > +void
> > +set_index_def(Parse *parse, Index *index, Table *table, uint32_t iid,
> > + const char *name, uint32_t name_len, int on_error,
> > + ExprList *expr_list, u8 idx_type)
> 
> 25. Still bad alignment. And make this function be static.
> 
> 
> > +{
> > + struct space_def *space_def = table->def;
> > + struct index_opts opts;
> > + index_opts_create(&opts);
> > + opts.is_unique = on_error != ON_CONFLICT_ACTION_NONE;
> > +
> > + struct key_def *key_def = key_def_new(expr_list->nExpr);
> > + if (key_def == NULL)
> 
> 26. If key_def_new fails, you should inform the parser or
> db: either set Parse.nErr and Parser.rc, or sqlite3OomFaul(). Same
> about all other errors got from Tarantool functions.
> 
> > + return;
> > +
> > + for (int i = 0; i < expr_list->nExpr; i++) {
> > + Expr *expr = expr_list->a[i].pExpr;
> > + sql_resolve_self_reference(parse, table, NC_IdxExpr,
> > + expr, 0);
> 
> 27. Bad alignment. And as I can see it fits in one line.
> 
> Please check alignment during another self-review iteration in
> the whole patch. I will not mention it below again.
> 
> > + if (parse->nErr > 0)
> > + return;
> > +
> > + Expr *column_expr = sqlite3ExprSkipCollate(expr);
> > + if (column_expr->op != TK_COLUMN) {
> > + sqlite3ErrorMsg(parse,
> > + "functional indexes aren't supported "
> > + "in the current version");
> > + return;
> > + }
> > +
> > + 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);
> > + else
> > + coll = sql_column_collation(space_def, fieldno,
> > + &coll_id);
> 
> 28. Please, use {}, when 'if' body consists of multiple lines.
> 
> > +
> > + if (sqlite3StrICmp(expr->u.zToken, "binary") != 0 &&
> > + coll == NULL && expr->op == TK_COLLATE)
> 
> 29. This check is needed in 'if (expr->op == TK_COLLATE)' above.
> And why do you do this youself? sql_get_coll_seq already sets
> the nErr in parser for this error.
> 
> > + return;
> > +
> > + /* Tarantool: DESC indexes are not supported so far.
> > + * See gh-3016.
> 
> 30. Please, obey Tarantool comment style:
> /*
>   * My comment inside the function.
>   * Second line.
>   */
> 
> And wrap the comment line on 66 ruler.
> 
> > void
> > @@ -2853,12 +2914,11 @@ sql_create_index(struct Parse *parse, struct Token *token,
> > {
> > Table *pTab = 0; /* Table to be indexed */
> > Index *pIndex = 0; /* The index to be created */
> > - char *zName = 0; /* Name of the index */
> > + char *name = 0; /* Name of the index */
> 
> 31. Please, use explicit NULL.
> 
> > int nName; /* Number of characters in zName */
> > - int i, j;
> > + int i;
> > 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 */
> > struct session *user_session = current_session();
> > @@ -3159,6 +3189,7 @@ sql_create_index(struct Parse *parse, struct Token *token,
> > }
> > if (idx_type == SQLITE_IDXTYPE_PRIMARYKEY)
> > pIdx->idxType = idx_type;
> > +
> > goto exit_create_index;
> 
> 32. Garbage diff. Please, find other garbage diffs and remove them.
> 
> > }
> > }
> > @@ -3268,28 +3299,7 @@ sql_create_index(struct Parse *parse, struct Token *token,
> > sql_expr_delete(db, where, false);
> > sql_expr_list_delete(db, col_list);
> > sqlite3SrcListDelete(db, tbl_name);
> 
> 33. I have noticed than on line 3056 collation names add extra bytes
> to struct Index object. Can you please investigate if they are not
> needed anymore and remove this code?
> 
> I am talking about it:
> 
> /* 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));
> }
> }
> 
> We do not store collation names in struct Index anymore.
> 
> > @@ -3297,15 +3307,8 @@ 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);
> > + assert(idx->def != NULL);
> > + return (idx->def->key_def->is_nullable && idx->def->opts.is_unique);
> 
> 34. This one-line function is used in a single place. Please, inline
> it and remove.
> 
> > }
> > 
> > void
> > @@ -3933,18 +3936,18 @@ sqlite3UniqueConstraint(Parse * pParse, /* Parsing context */
> > )
> > {
> > char *zErr;
> > - int j;
> > + uint32_t j;
> > StrAccum errMsg;
> > Table *pTab = pIdx->pTable;
> > 
> > sqlite3StrAccumInit(&errMsg, pParse->db, 0, 0, 200);
> > if (pIdx->aColExpr) {
> > - sqlite3XPrintf(&errMsg, "index '%q'", pIdx->zName);
> > + sqlite3XPrintf(&errMsg, "index '%q'", pIdx->def->name);
> > } else {
> > - for (j = 0; j < pIdx->nColumn; j++) {
> > + for (j = 0; j < pIdx->def->key_def->part_count; j++) {
> > char *zCol;
> > - assert(pIdx->aiColumn[j] >= 0);
> > - zCol = pTab->def->fields[pIdx->aiColumn[j]].name;
> > + uint32_t fieldno = pIdx->def->key_def->parts[j].fieldno;
> > + zCol = pTab->def->fields[fieldno].name;
> > if (j)
> > sqlite3StrAccumAppend(&errMsg, ", ", 2);
> > sqlite3XPrintf(&errMsg, "%s.%s", pTab->def->name, zCol);
> > @@ -3967,11 +3970,10 @@ 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)
> > + for (uint32_t i = 0; i < index->def->key_def->part_count; i++) {
> > + struct coll *idx_coll = index->def->key_def->parts[i].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 ddad54b3e..504738cd5 100644
> > --- a/src/box/sql/delete.c
> > +++ b/src/box/sql/delete.c
> > @@ -252,11 +252,11 @@ 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++) {
> > - assert(pk->aiColumn[i] >= 0);
> > sqlite3ExprCodeGetColumnOfTable(v, table->def,
> > tab_cursor,
> > - pk->
> > - aiColumn[i],
> > + pk->def->
> > + key_def->
> > + parts[i].fieldno,
> 
> 35. Please, just save pk->def->key_def->parts above in a separate
> variable, and use here only part->fieldno and ++part above.
—
Thank you for the review. All the issues are fixed. Here is the patch:
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
and zName are removed from Index. All usages of this
fields changed to usage of corresponding index_def
fields.
index_is_unique(), sql_index_collation() and
index_column_count() are removed with calls of
index_def corresponding fields.

Closes: #3369
---
Branch: https://github.com/tarantool/tarantool/tree/sb/gh-3369-use-index-def-in-select-and-where
Issue: https://github.com/tarantool/tarantool/issues/3369

 src/box/sql.c           |  54 +++---
 src/box/sql/analyze.c   |  40 ++--
 src/box/sql/build.c     | 488 +++++++++++++++++++++++-------------------------
 src/box/sql/delete.c    |  17 +-
 src/box/sql/expr.c      |  60 +++---
 src/box/sql/fkey.c      |  47 ++---
 src/box/sql/insert.c    | 148 ++++++---------
 src/box/sql/pragma.c    |  32 ++--
 src/box/sql/select.c    |   2 +-
 src/box/sql/sqliteInt.h |  63 +------
 src/box/sql/trigger.c   |   2 -
 src/box/sql/update.c    |  10 +-
 src/box/sql/vdbeaux.c   |   2 +-
 src/box/sql/vdbemem.c   |   4 +-
 src/box/sql/where.c     | 168 ++++++++---------
 src/box/sql/wherecode.c |  54 +++---
 src/box/sql/whereexpr.c |  15 --
 17 files changed, 532 insertions(+), 674 deletions(-)

diff --git a/src/box/sql.c b/src/box/sql.c
index 7379cb418..213f8e453 100644
--- a/src/box/sql.c
+++ b/src/box/sql.c
@@ -1442,8 +1442,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;
 	}
@@ -1552,20 +1552,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;
 	}
 
@@ -1575,46 +1574,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 afc824a1a..31de7ab05 100644
--- a/src/box/sql/analyze.c
+++ b/src/box/sql/analyze.c
@@ -849,7 +849,6 @@ analyzeOneTable(Parse * pParse,	/* Parser context */
 		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 */
 
 		if (pOnlyIdx && pOnlyIdx != pIdx)
 			continue;
@@ -860,9 +859,9 @@ analyzeOneTable(Parse * pParse,	/* Parser context */
 		if (IsPrimaryKeyIndex(pIdx)) {
 			zIdxName = pTab->def->name;
 		} else {
-			zIdxName = pIdx->zName;
+			zIdxName = pIdx->def->name;
 		}
-		nColTest = index_column_count(pIdx);
+		int nColTest = pIdx->def->key_def->part_count;
 
 		/* Populate the register containing the index name. */
 		sqlite3VdbeLoadString(v, regIdxname, zIdxName);
@@ -917,7 +916,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:
 		 *
@@ -969,7 +968,7 @@ analyzeOneTable(Parse * pParse,	/* Parser context */
 			 */
 			sqlite3VdbeAddOp0(v, OP_Goto);
 			addrNextRow = sqlite3VdbeCurrentAddr(v);
-			if (nColTest == 1 && index_is_unique(pIdx)) {
+			if (nColTest == 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 +977,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 < nColTest; ++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,
@@ -1006,7 +1004,8 @@ analyzeOneTable(Parse * pParse,	/* Parser context */
 			for (i = 0; i < nColTest; i++) {
 				sqlite3VdbeJumpHere(v, aGotoChng[i]);
 				sqlite3VdbeAddOp3(v, OP_Column, iIdxCur,
-						  pIdx->aiColumn[i],
+						  pIdx->def->key_def->
+							  parts[i].fieldno,
 						  regPrev + i);
 			}
 			sqlite3VdbeResolveLabel(v, endDistinctTest);
@@ -1022,15 +1021,14 @@ 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));
+		int nPkColumn = (int) pPk->def->key_def->part_count;
+		int regKeyStat = sqlite3GetTempRange(pParse, nPkColumn);
+		for (int j = 0; j < nPkColumn; ++j) {
+			int k = pPk->def->key_def->parts[j].fieldno;
+			assert(k >= 0 && k < (int) 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);
@@ -1150,7 +1148,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");
 	}
diff --git a/src/box/sql/build.c b/src/box/sql/build.c
index 62d687b17..f18727c61 100644
--- a/src/box/sql/build.c
+++ b/src/box/sql/build.c
@@ -253,6 +253,8 @@ freeIndex(sqlite3 * db, Index * p)
 {
 	sql_expr_delete(db, p->pPartIdxWhere, false);
 	sql_expr_list_delete(db, p->aColExpr);
+	if (p->def != NULL)
+		index_def_delete(p->def);
 	sqlite3DbFree(db, p->zColAff);
 	sqlite3DbFree(db, p);
 }
@@ -271,7 +273,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;
@@ -388,7 +391,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);
@@ -1058,7 +1061,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)
@@ -1066,22 +1069,20 @@ 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; pIdx = pIdx->pNext) {
+			assert(pIdx->def->key_def->part_count == 1);
+			if (pIdx->def->key_def->parts[0].fieldno == i) {
+				pIdx->def->key_def->parts[0].coll_id = *id;
+				pIdx->def->key_def->parts[0].coll =
 					sql_column_collation(p->def, i, id);
 			}
 		}
-	} else {
-		sqlite3DbFree(db, zColl);
 	}
+	sqlite3DbFree(db, zColl);
 }
 
 struct coll *
@@ -1111,66 +1112,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;
-}
-
 /**
  * Return true if space which corresponds to
  * given table has view option.
@@ -1383,14 +1324,16 @@ 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)
+hasColumn(const struct key_part *key_parts, int nCol, uint32_t fieldno)
 {
-	while (nCol-- > 0)
-		if (x == *(aiCol++))
+	int i = 0;
+	while (i < nCol) {
+		if (fieldno == key_parts->fieldno)
 			return 1;
+		key_parts++;
+		i++;
+	}
 	return 0;
 }
 
@@ -1410,13 +1353,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;
@@ -1454,14 +1396,28 @@ convertToWithoutRowidTable(Parse * pParse, Table * pTab)
 		 * "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];
+
+		struct key_part *parts = pPk->def->key_def->parts;
+		uint32_t part_count = pPk->def->key_def->part_count;
+		uint32_t new_part_count = part_count;
+
+		for (uint32_t i = 1; i < part_count; i++) {
+			if (hasColumn(parts, i, parts[i].fieldno)){
+				new_part_count--;
+				bool is_found = false;
+				for (uint32_t j = i + 1; j < part_count; j++){
+					if (!(hasColumn(parts, j,
+							parts[j].fieldno))) {
+						parts[i] = parts[j];
+						is_found = true;
+						break;
+					}
+				}
+				if (!(is_found))
+					break;
 			}
 		}
-		pPk->nColumn = j;
+		pPk->def->key_def->part_count = new_part_count;
 	}
 	assert(pPk != 0);
 }
@@ -1543,7 +1499,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);
@@ -1580,7 +1536,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) {
@@ -2652,14 +2608,15 @@ 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,
+	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
@@ -2692,7 +2649,7 @@ 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);
 		sqlite3UniqueConstraint(pParse, ON_CONFLICT_ACTION_ABORT,
 					pIndex);
@@ -2730,24 +2687,13 @@ sqlite3AllocateIndexObject(sqlite3 * db,	/* Database connection */
 	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  */
+	    ROUND8(sizeof(LogEst) * (nCol + 1));    /* Index.aiRowLogEst */
 	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;
 	}
 	return p;
@@ -2836,18 +2782,133 @@ addIndexToTable(Index * pIndex, Table * pTab)
 	}
 }
 
-bool
-index_is_unique(Index *idx)
+static inline void
+append_string_part(struct region *r, const char *str,
+		  size_t *total_sql_size, Parse *parse)
 {
-	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);
+	char * str_part = region_alloc(r, strlen(str));
+	if (str_part == NULL){
+		diag_set(OutOfMemory, strlen(str),
+			 "region_alloc", "str_part");
+		parse->rc = SQL_TARANTOOL_ERROR;
+		parse->nErr++;
+	}
+	memcpy(str_part, str, strlen(str));
+	*total_sql_size += strlen(str);
+}
+
+void static
+set_index_def(Parse *parse, Index *index, Table *table, uint32_t iid,
+	      const char *name, uint32_t name_len, int on_error,
+	      struct ExprList *expr_list, u8 idx_type)
+{
+	struct space_def *space_def = table->def;
+	struct index_opts opts;
+	index_opts_create(&opts);
+	opts.is_unique = on_error != ON_CONFLICT_ACTION_NONE;
+
+	struct key_def *key_def = key_def_new(expr_list->nExpr);
+	if (key_def == NULL) {
+		parse->rc = SQL_TARANTOOL_ERROR;
+		parse->nErr++;
+		return;
+	}
+
+	/*
+	 * Build initial parts of SQL statement.
+	 */
+
+	struct region *r = &fiber()->gc;
+	size_t total_sql_size = 0;
+
+	if (idx_type == SQLITE_IDXTYPE_APPDEF) {
+		append_string_part(r, "CREATE INDEX ", &total_sql_size,
+				   parse);
+		append_string_part(r, name, &total_sql_size, parse);
+		append_string_part(r, " ON ", &total_sql_size, parse);
+		append_string_part(r, space_def->name, &total_sql_size,
+				   parse);
+		append_string_part(r, " (", &total_sql_size, parse);
+	}
+
+	for (int i = 0; i < expr_list->nExpr; i++) {
+		Expr *expr = expr_list->a[i].pExpr;
+		sql_resolve_self_reference(parse, table, NC_IdxExpr, expr, 0);
+		if (parse->nErr > 0)
+			return;
+
+		Expr *column_expr = sqlite3ExprSkipCollate(expr);
+		if (column_expr->op != TK_COLUMN) {
+			sqlite3ErrorMsg(parse,
+					"functional indexes aren't supported "
+					"in the current version");
+			return;
+		}
+
+		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 (idx_type == SQLITE_IDXTYPE_APPDEF) {
+				append_string_part(r, name,
+						   &total_sql_size, parse);
+				append_string_part(r, " COLLATE ",
+						   &total_sql_size, parse);
+				const char *coll_name = expr->u.zToken;
+				append_string_part(r, coll_name,
+						   &total_sql_size, parse);
+				append_string_part(r, ", ",
+						   &total_sql_size, parse);
+			}
+		} else {
+			coll = sql_column_collation(space_def, fieldno,
+						    &coll_id);
+			if (idx_type == SQLITE_IDXTYPE_APPDEF) {
+				append_string_part(r, name,
+						   &total_sql_size, parse);
+				append_string_part(r, ", ",
+						   &total_sql_size, parse);
+			}
+		}
+
+		/*
+		* Tarantool: DESC indexes are not supported so far.
+		* See gh-3016.
+		*/
+		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);
+	}
 
-	return tnt_index->def->opts.is_unique;
+	if (parse->nErr > 0) {
+		index->def = NULL;
+		return;
+	}
+
+	if (idx_type == SQLITE_IDXTYPE_APPDEF) {
+		memcpy(region_alloc(r, 1), "\0", 1);
+		total_sql_size += 1;
+		opts.sql = region_join(r, total_sql_size);
+
+		/*
+		 * fix last ", " with ")\0" to finish the statement.
+		 */
+		opts.sql[total_sql_size - 3] = ')';
+		opts.sql[total_sql_size - 2] = '\0';
+	}
+
+	struct key_def *pk_key_def;
+	if (idx_type == SQLITE_IDXTYPE_APPDEF)
+		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);
 }
 
 void
@@ -2856,16 +2917,14 @@ sql_create_index(struct Parse *parse, struct Token *token,
 		 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;
+	Table *pTab = NULL;	/* Table to be indexed */
+	Index *pIndex = NULL;	/* The index to be created */
+	char *name = NULL;	/* Name of the index */
+	int name_len;		/* Number of characters in zName */
 	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 */
+	char *zExtra = NULL;	/* Extra space after the Index object */
 	struct session *user_session = current_session();
 
 	if (db->mallocFailed || parse->nErr > 0) {
@@ -2939,24 +2998,24 @@ sql_create_index(struct Parse *parse, struct Token *token,
 	 * our own name.
 	 */
 	if (token) {
-		zName = sqlite3NameFromToken(db, token);
-		if (zName == 0)
+		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) !=
+			if (sqlite3HashFind(&db->pSchema->tblHash, name) !=
 			    NULL) {
 				sqlite3ErrorMsg(parse,
 						"there is already a table named %s",
-						zName);
+						name);
 				goto exit_create_index;
 			}
 		}
-		if (sqlite3HashFind(&pTab->idxHash, zName) != NULL) {
+		if (sqlite3HashFind(&pTab->idxHash, name) != NULL) {
 			if (!if_not_exist) {
 				sqlite3ErrorMsg(parse,
 						"index %s.%s already exists",
-						pTab->def->name, zName);
+						pTab->def->name, name);
 			} else {
 				assert(!db->init.busy);
 			}
@@ -2968,10 +3027,9 @@ sql_create_index(struct Parse *parse, struct Token *token,
 		for (pLoop = pTab->pIndex, n = 1; pLoop;
 		     pLoop = pLoop->pNext, n++) {
 		}
-		zName =
-		    sqlite3MPrintf(db, "sqlite_autoindex_%s_%d", pTab->def->name,
-				   n);
-		if (zName == 0) {
+		name = sqlite3MPrintf(db, "sqlite_autoindex_%s_%d",
+				      pTab->def->name, n);
+		if (name == NULL) {
 			goto exit_create_index;
 		}
 	}
@@ -2997,31 +3055,27 @@ 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));
-		}
-	}
-
 	/*
 	 * Allocate the index structure.
 	 */
-	nName = sqlite3Strlen30(zName);
+	name_len = sqlite3Strlen30(name);
+
+	if (name_len > BOX_NAME_MAX) {
+		sqlite3ErrorMsg(parse,
+				"%s.%s exceeds indexes' names length limit",
+				pTab->def->name, name);
+		goto exit_create_index;
+	}
+
+	if (sqlite3CheckIdentifierName(parse, name) != SQLITE_OK)
+		goto exit_create_index;
+
 	pIndex = sqlite3AllocateIndexObject(db, col_list->nExpr,
-					    nName + nExtra + 1, &zExtra);
+					    name_len + nExtra + 1, &zExtra);
 	if (db->mallocFailed) {
 		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;
 	/*
@@ -3036,7 +3090,6 @@ sql_create_index(struct Parse *parse, struct Token *token,
 		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,
@@ -3045,60 +3098,27 @@ sql_create_index(struct Parse *parse, struct Token *token,
 		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[].  For general expressions,
-	 * populate pIndex->aColExpr and store XN_EXPR (-2) 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.
 	 */
-	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");
-			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;
-		}
-		pIndex->coll_array[i] = coll;
-		pIndex->coll_id_array[i] = id;
+	uint32_t max_iid = 0;
+	for (Index *index = pTab->pIndex; index; index = index->pNext) {
+		max_iid = max_iid > index->def->iid ?
+			  max_iid :
+			  index->def->iid + 1;
+	}
 
-		/* Tarantool: DESC indexes are not supported so far.
-		 * See gh-3016.
-		 */
-		pIndex->sort_order[i] = SORT_ORDER_ASC;
+	set_index_def(parse, pIndex, pTab, max_iid, name, name_len, on_error,
+		      col_list, idx_type);
+
+	if (pIndex->def == NULL ||
+	    !index_def_is_valid(pIndex->def, pTab->def->name)) {
+		goto exit_create_index;
 	}
+
 	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
@@ -3123,25 +3143,27 @@ sql_create_index(struct Parse *parse, struct Token *token,
 		 */
 		Index *pIdx;
 		for (pIdx = pTab->pIndex; pIdx; pIdx = pIdx->pNext) {
-			int k;
+			uint32_t k;
 			assert(IsUniqueIndex(pIdx));
 			assert(pIdx->idxType != SQLITE_IDXTYPE_APPDEF);
 			assert(IsUniqueIndex(pIndex));
 
-			if (pIdx->nColumn != pIndex->nColumn)
+			if (pIdx->def->key_def->part_count !=
+			    pIndex->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 < pIdx->def->key_def->part_count; k++) {
+				if (pIdx->def->key_def->parts[k].fieldno !=
+				    pIndex->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 = pIdx->def->key_def->parts[k].coll;
+				coll2 = pIndex->def->key_def->parts[k].coll;
 				if (coll1 != coll2)
 					break;
 			}
-			if (k == pIdx->nColumn) {
+			if (k == pIdx->def->key_def->part_count) {
 				if (pIdx->onError != pIndex->onError) {
 					/* This constraint creates the same index as a previous
 					 * constraint specified somewhere in the CREATE TABLE statement.
@@ -3175,7 +3197,7 @@ sql_create_index(struct Parse *parse, struct Token *token,
 	assert(parse->nErr == 0);
 	if (db->init.busy) {
 		Index *p;
-		p = sqlite3HashInsert(&pTab->idxHash, pIndex->zName, pIndex);
+		p = sqlite3HashInsert(&pTab->idxHash, pIndex->def->name, pIndex);
 		if (p) {
 			assert(p == pIndex);	/* Malloc must have failed */
 			sqlite3OomFault(db);
@@ -3273,44 +3295,7 @@ sql_create_index(struct Parse *parse, struct Token *token,
 	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
@@ -3938,18 +3923,19 @@ sqlite3UniqueConstraint(Parse * pParse,	/* Parsing context */
     )
 {
 	char *zErr;
-	int j;
+	uint32_t j;
 	StrAccum errMsg;
 	Table *pTab = pIdx->pTable;
 
 	sqlite3StrAccumInit(&errMsg, pParse->db, 0, 0, 200);
 	if (pIdx->aColExpr) {
-		sqlite3XPrintf(&errMsg, "index '%q'", pIdx->zName);
+		sqlite3XPrintf(&errMsg, "index '%q'", pIdx->def->name);
 	} else {
-		for (j = 0; j < pIdx->nColumn; j++) {
+		struct key_part *part = pIdx->def->key_def->parts;
+		for (j = 0; j < pIdx->def->key_def->part_count; j++, part++) {
 			char *zCol;
-			assert(pIdx->aiColumn[j] >= 0);
-			zCol = pTab->def->fields[pIdx->aiColumn[j]].name;
+			uint32_t fieldno = part->fieldno;
+			zCol = pTab->def->fields[fieldno].name;
 			if (j)
 				sqlite3StrAccumAppend(&errMsg, ", ", 2);
 			sqlite3XPrintf(&errMsg, "%s.%s", pTab->def->name, zCol);
@@ -3972,11 +3958,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 ddad54b3e..0314382f7 100644
--- a/src/box/sql/delete.c
+++ b/src/box/sql/delete.c
@@ -209,7 +209,7 @@ sql_table_delete_from(struct Parse *parse, struct SrcList *tab_list,
 		} else {
 			pk = sqlite3PrimaryKeyIndex(table);
 			assert(pk != NULL);
-			pk_len = index_column_count(pk);
+			pk_len = pk->def->key_def->part_count;
 			parse->nMem += pk_len;
 			sqlite3VdbeAddOp2(v, OP_OpenTEphemeral, eph_cursor,
 					  pk_len);
@@ -251,12 +251,11 @@ 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++) {
-				assert(pk->aiColumn[i] >= 0);
+			struct key_part *part = pk->def->key_def->parts;
+			for (int i = 0; i < pk_len; i++, part++) {
 				sqlite3ExprCodeGetColumnOfTable(v, table->def,
 								tab_cursor,
-								pk->
-								aiColumn[i],
+								part->fieldno,
 								reg_pk + i);
 			}
 		} else {
@@ -326,7 +325,7 @@ sql_table_delete_from(struct Parse *parse, struct SrcList *tab_list,
 			sqlite3VdbeAddOp3(v, OP_OpenWrite, tab_cursor,
 					  table->tnum, space_ptr_reg);
 			sql_vdbe_set_p4_key_def(parse, pk);
-			VdbeComment((v, "%s", pk->zName));
+			VdbeComment((v, "%s", pk->def->name));
 
 			if (one_pass == ONEPASS_MULTI)
 				sqlite3VdbeJumpHere(v, iAddrOnce);
@@ -536,14 +535,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]
-		    && prev->aiColumn[j] != XN_EXPR) {
+		if (prev->def->key_def->parts[j].fieldno ==
+		    index->def->key_def->parts[j].fieldno && prev == NULL) {
 			/*
 			 * This column was already computed by the
 			 * previous index.
diff --git a/src/box/sql/expr.c b/src/box/sql/expr.c
index 8866f6fed..a756535f0 100644
--- a/src/box/sql/expr.c
+++ b/src/box/sql/expr.c
@@ -2422,20 +2422,24 @@ 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) {
-					if (pIdx->nColumn > nExpr
-					    || (pIdx->nColumn > nExpr
-					    && !index_is_unique(pIdx))) {
-							continue;	/* This index is not unique over the IN RHS columns */
+					if ((int)part_count > nExpr
+					    || !pIdx->def->opts.is_unique) {
+						/* This index is not unique over the IN RHS columns */
+						continue;
 					}
 				}
 
@@ -2449,12 +2453,13 @@ 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) {
 							continue;
@@ -2483,17 +2488,16 @@ sqlite3FindInIndex(Parse * pParse,	/* Parsing context */
 							  0, 0, 0,
 							  sqlite3MPrintf(db,
 							  "USING INDEX %s FOR IN-OPERATOR",
-							  pIdx->zName),
+							  pIdx->def->name),
 							  P4_DYNAMIC);
 					emit_open_cursor(pParse, iTab,
 							 pIdx->tnum);
 					sql_vdbe_set_p4_key_def(pParse, pIdx);
-					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
@@ -2515,7 +2519,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);
 						}
 					}
@@ -3146,12 +3150,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
+			tab->def->fields[fieldno].affinity;
+		if (pk->def->key_def->part_count == 1
 		    && affinity == AFFINITY_INTEGER
-		    && pk->aiColumn[0] < nVector) {
-			int reg_pk = rLhs + pk->aiColumn[0];
+		    && (int) fieldno < nVector) { int reg_pk = rLhs + (int)fieldno;
 			sqlite3VdbeAddOp2(v, OP_MustBeInt, reg_pk, destIfFalse);
 		}
 	}
@@ -3483,17 +3487,9 @@ sqlite3ExprCodeLoadIndexColumn(Parse * pParse,	/* The parsing context */
 			       int regOut	/* Store the index column value in this register */
     )
 {
-	i16 iTabCol = pIdx->aiColumn[iIdxCol];
-	if (iTabCol == XN_EXPR) {
-		assert(pIdx->aColExpr);
-		assert(pIdx->aColExpr->nExpr > iIdxCol);
-		pParse->iSelfTab = iTabCur;
-		sqlite3ExprCodeCopy(pParse, pIdx->aColExpr->a[iIdxCol].pExpr,
-				    regOut);
-	} else {
-		sqlite3ExprCodeGetColumnOfTable(pParse->pVdbe, pIdx->pTable->def,
-						iTabCur, iTabCol, regOut);
-	}
+	i16 iTabCol = pIdx->def->key_def->parts[iIdxCol].fieldno;
+	sqlite3ExprCodeGetColumnOfTable(pParse->pVdbe, pIdx->pTable->def,
+					iTabCur, iTabCol, regOut);
 }
 
 void
diff --git a/src/box/sql/fkey.c b/src/box/sql/fkey.c
index 70ebef89f..79320eced 100644
--- a/src/box/sql/fkey.c
+++ b/src/box/sql/fkey.c
@@ -256,8 +256,8 @@ sqlite3FkLocateIndex(Parse * pParse,	/* Parse context to store any error in */
 	}
 
 	for (pIdx = pParent->pIndex; pIdx; pIdx = pIdx->pNext) {
-		int nIdxCol = index_column_count(pIdx);
-		if (nIdxCol == nCol && index_is_unique(pIdx)
+		int nIdxCol = pIdx->def->key_def->part_count;
+		if (nIdxCol == nCol && pIdx->def->opts.is_unique
 		    && 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
@@ -286,8 +286,10 @@ sqlite3FkLocateIndex(Parse * pParse,	/* Parse context to store any error in */
 				 * the default collation sequences for each column.
 				 */
 				int i, j;
-				for (i = 0; i < nCol; i++) {
-					i16 iCol = pIdx->aiColumn[i];	/* Index of column in parent tbl */
+				struct key_part *part =
+					pIdx->def->key_def->parts;
+				for (i = 0; i < nCol; i++, part++) {
+					i16 iCol = (int) part->fieldno;	/* Index of column in parent tbl */
 					char *zIdxCol;	/* Name of indexed column */
 
 					if (iCol < 0)
@@ -302,9 +304,7 @@ sqlite3FkLocateIndex(Parse * pParse,	/* Parse context to store any error in */
 					def_coll = sql_column_collation(pParent->def,
 									iCol,
 									&id);
-					struct coll *coll =
-						sql_index_collation(pIdx, i,
-								    &id);
+					struct coll *coll = part->coll;
 					if (def_coll != coll)
 						break;
 
@@ -464,13 +464,15 @@ fkLookupParent(Parse * pParse,	/* Parse context */
 				for (i = 0; i < nCol; i++) {
 					int iChild = aiCol[i] + 1 + regData;
 					int iParent =
-					    pIdx->aiColumn[i] + 1 + regData;
-					assert(pIdx->aiColumn[i] >= 0);
+						(int) pIdx->def->key_def->parts[i].fieldno
+						+ 1 + regData;
 					assert(aiCol[i] != pTab->iPKey);
-					if (pIdx->aiColumn[i] == pTab->iPKey) {
+					if ((int)pIdx->def->key_def->
+						parts[i].fieldno == pTab->iPKey) {
 						/* The parent key is a composite key that includes the IPK column */
 						iParent = regData;
 					}
+
 					sqlite3VdbeAddOp3(v, OP_Ne, iChild,
 							  iJump, iParent);
 					VdbeCoverage(v);
@@ -622,7 +624,7 @@ fkScanChildren(Parse * pParse,	/* Parse context */
 	Vdbe *v = sqlite3GetVdbe(pParse);
 
 	assert(pIdx == 0 || pIdx->pTable == pTab);
-	assert(pIdx == 0 || (int)index_column_count(pIdx) == pFKey->nCol);
+	assert(pIdx == 0 || (int) pIdx->def->key_def->part_count == pFKey->nCol);
 	assert(pIdx != 0);
 
 	if (nIncr < 0) {
@@ -646,7 +648,7 @@ fkScanChildren(Parse * pParse,	/* Parse context */
 		i16 iCol;	/* Index of column in child table */
 		const char *zCol;	/* Name of column in child table */
 
-		iCol = pIdx ? pIdx->aiColumn[i] : -1;
+		iCol = pIdx ? 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);
@@ -671,10 +673,9 @@ fkScanChildren(Parse * pParse,	/* Parse context */
 		Expr *pEq, *pAll = 0;
 		Index *pPk = sqlite3PrimaryKeyIndex(pTab);
 		assert(pIdx != 0);
-		int col_count = index_column_count(pPk);
+		int col_count = pPk->def->key_def->part_count;
 		for (i = 0; i < col_count; i++) {
-			i16 iCol = pIdx->aiColumn[i];
-			assert(iCol >= 0);
+			i16 iCol = (int) pIdx->def->key_def->parts[i].fieldno;
 			pLeft = exprTableRegister(pParse, pTab, regData, iCol);
 			pRight =
 				exprTableColumn(db, pTab->def,
@@ -992,7 +993,6 @@ sqlite3FkCheck(Parse * pParse,	/* Parse context */
 			if (aiCol[i] == pTab->iPKey) {
 				aiCol[i] = -1;
 			}
-			assert(pIdx == 0 || pIdx->aiColumn[i] >= 0);
 		}
 
 		pParse->nTab++;
@@ -1126,10 +1126,10 @@ sqlite3FkOldmask(Parse * pParse,	/* Parse context */
 			Index *pIdx = 0;
 			sqlite3FkLocateIndex(pParse, pTab, p, &pIdx, 0);
 			if (pIdx) {
-				int nIdxCol = index_column_count(pIdx);
+				int nIdxCol = pIdx->def->key_def->part_count;
 				for (i = 0; i < nIdxCol; i++) {
-					assert(pIdx->aiColumn[i] >= 0);
-					mask |= COLUMN_MASK(pIdx->aiColumn[i]);
+					mask |= COLUMN_MASK(pIdx->def->
+						key_def->parts[i].fieldno);
 				}
 			}
 		}
@@ -1264,11 +1264,12 @@ fkActionTrigger(Parse * pParse,	/* Parse context */
 			       || (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
+					   : 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 59c61c703..fc9f85165 100644
--- a/src/box/sql/insert.c
+++ b/src/box/sql/insert.c
@@ -89,7 +89,7 @@ sqlite3IndexAffinityStr(sqlite3 * db, Index * pIdx)
 		 * up.
 		 */
 		int n;
-		int nColumn = index_column_count(pIdx);
+		int nColumn = pIdx->def->key_def->part_count;
 		pIdx->zColAff =
 		    (char *)sqlite3DbMallocRaw(0, nColumn + 1);
 		if (!pIdx->zColAff) {
@@ -97,22 +97,8 @@ sqlite3IndexAffinityStr(sqlite3 * db, Index * pIdx)
 			return 0;
 		}
 		for (n = 0; n < nColumn; n++) {
-			i16 x = pIdx->aiColumn[n];
-			if (x >= 0) {
-				char affinity = pIdx->pTable->
-					def->fields[x].affinity;
-				pIdx->zColAff[n] = affinity;
-			} else {
-				char aff;
-				assert(x == XN_EXPR);
-				assert(pIdx->aColExpr != 0);
-				aff =
-				    sqlite3ExprAffinity(pIdx->aColExpr->a[n].
-							pExpr);
-				if (aff == 0)
-					aff = AFFINITY_BLOB;
-				pIdx->zColAff[n] = aff;
-			}
+			i16 x = pIdx->def->key_def->parts[n].fieldno;
+			pIdx->zColAff[n] = pIdx->pTable->def->fields[x].affinity;
 		}
 		pIdx->zColAff[n] = 0;
 	}
@@ -645,7 +631,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;
 		}
 	}
 
@@ -1088,7 +1074,7 @@ sqlite3GenerateConstraintChecks(Parse * pParse,		/* The parser context */
 	nCol = pTab->def->field_count;
 
 	pPk = sqlite3PrimaryKeyIndex(pTab);
-	nPkField = index_column_count(pPk);
+	nPkField = pPk->def->key_def->part_count;
 
 	/* Record that this module has started */
 	VdbeModuleComment((v, "BEGIN: GenCnstCks(%d,%d,%d,%d,%d)",
@@ -1252,38 +1238,27 @@ 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);
+		int nIdxCol = pIdx->def->key_def->part_count;
 		for (i = 0; i < nIdxCol; i++) {
-			int iField = pIdx->aiColumn[i];
+			int iField = (int) pIdx->def->key_def->parts[i].fieldno;
 			int x;
-			if (iField == XN_EXPR) {
-				pParse->ckBase = regNewData + 1;
-				sqlite3ExprCodeCopy(pParse,
-						    pIdx->aColExpr->a[i].pExpr,
-						    regIdx + i);
-				pParse->ckBase = 0;
-				VdbeComment((v, "%s column %d", pIdx->zName,
-					     i));
-			} else {
-				/* OP_SCopy copies value in separate register,
-				 * which later will be used by OP_NoConflict.
-				 * But OP_NoConflict is necessary only in cases
-				 * when bytecode is needed for proper UNIQUE
-				 * constraint handling.
-				 */
-				if (uniqueByteCodeNeeded) {
-					if (iField == pTab->iPKey)
-						x = regNewData;
-					else
-						x = iField + regNewData + 1;
-
-					assert(iField >= 0);
-					sqlite3VdbeAddOp2(v, OP_SCopy,
-							  x, regIdx + i);
-					VdbeComment((v, "%s",
-						     pTab->def->fields[
-							iField].name));
-				}
+			/* OP_SCopy copies value in separate register,
+			 * which later will be used by OP_NoConflict.
+			 * But OP_NoConflict is necessary only in cases
+			 * when bytecode is needed for proper UNIQUE
+			 * constraint handling.
+			 */
+			if (uniqueByteCodeNeeded) {
+				if (iField == pTab->iPKey)
+					x = regNewData;
+				else
+					x = iField + regNewData + 1;
+
+				assert(iField >= 0);
+				sqlite3VdbeAddOp2(v, OP_SCopy,
+						  x, regIdx + i);
+				VdbeComment((v, "%s",
+					     pTab->def->fields[iField].name));
 			}
 		}
 
@@ -1293,8 +1268,12 @@ sqlite3GenerateConstraintChecks(Parse * pParse,		/* The parser context */
 			/* 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]] ==
+				reg_pk = regNewData + 1 +
+					pIdx->def->key_def->parts[0].fieldno;
+
+				int fieldno = (int)pIdx->def->key_def->
+					parts[0].fieldno;
+				if (pTab->zColAff[fieldno] ==
 				    AFFINITY_INTEGER) {
 					int skip_if_null = sqlite3VdbeMakeLabel(v);
 					if ((pTab->tabFlags & TF_Autoincrement) != 0) {
@@ -1311,8 +1290,8 @@ sqlite3GenerateConstraintChecks(Parse * pParse,		/* The parser context */
 			}
 
 			sqlite3VdbeAddOp3(v, OP_MakeRecord, regNewData + 1,
-					pTab->def->field_count, aRegIdx[ix]);
-			VdbeComment((v, "for %s", pIdx->zName));
+					  pTab->def->field_count, aRegIdx[ix]);
+			VdbeComment((v, "for %s", pIdx->def->name));
 		}
 
 		/* In an UPDATE operation, if this index is the PRIMARY KEY
@@ -1400,7 +1379,7 @@ 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);
 
@@ -1410,19 +1389,17 @@ sqlite3GenerateConstraintChecks(Parse * pParse,		/* The parser context */
 								 nPkField);
 		if (isUpdate || on_error == ON_CONFLICT_ACTION_REPLACE) {
 			int x;
-			int nPkCol = index_column_count(pPk);
+			int nPkCol = pPk->def->key_def->part_count;
 			/* 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];
+					x = pPk->def->key_def->parts[i].fieldno;
 					sqlite3VdbeAddOp3(v, OP_Column,
 							  iThisCur, x, regR + i);
 					VdbeComment((v, "%s.%s", pTab->def->name,
-						pTab->def->fields[
-							pPk->aiColumn[i]].name));
+						pTab->def->fields[x].name));
 				}
 			}
 			if (isUpdate && uniqueByteCodeNeeded) {
@@ -1440,10 +1417,11 @@ sqlite3GenerateConstraintChecks(Parse * pParse,		/* The parser context */
 					      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);
+					char *p4 = (char *) pPk->def->key_def->parts[i].coll;
+					x = pPk->def->key_def->parts[i].fieldno;
+					if (pPk->tnum==0) {
+						x = -1;
+					}
 					if (i == (nPkCol - 1)) {
 						addrJump = addrUniqueOk;
 						op = OP_Eq;
@@ -1620,8 +1598,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 */
@@ -1639,7 +1617,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));
 			}
 		}
 	}
@@ -1676,35 +1654,23 @@ 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);
+	uint32_t nDestCol = pDest->def->key_def->part_count;
+	uint32_t nSrcCol = pSrc->def->key_def->part_count;
 	if (nDestCol != nSrcCol) {
 		return 0;	/* Different number of columns */
 	}
 	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 (i = 0; i < nSrcCol; i++, src_part++, dest_part++) {
+		if (src_part->fieldno != dest_part->fieldno)
 			return 0;	/* Different columns indexed */
-		}
-		if (pSrc->aiColumn[i] == XN_EXPR) {
-			assert(pSrc->aColExpr != 0 && pDest->aColExpr != 0);
-			if (sqlite3ExprCompare(pSrc->aColExpr->a[i].pExpr,
-					       pDest->aColExpr->a[i].pExpr,
-					       -1) != 0) {
-				return 0;	/* Different expressions in the index */
-			}
-		}
-		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 */
@@ -1876,16 +1842,14 @@ 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) {
+		if (pSrcIdx == 0)
 			return 0;	/* pDestIdx has no corresponding index in pSrc */
-		}
 	}
 	/* Get server checks. */
 	ExprList *pCheck_src = space_checks_expr_list(
@@ -1960,11 +1924,11 @@ xferOptimization(Parse * pParse,	/* Parser context */
 		assert(pSrcIdx);
 		emit_open_cursor(pParse, iSrc, pSrcIdx->tnum);
 		sql_vdbe_set_p4_key_def(pParse, pSrcIdx);
-		VdbeComment((v, "%s", pSrcIdx->zName));
+		VdbeComment((v, "%s", pSrcIdx->def->name));
 		emit_open_cursor(pParse, iDest, pDestIdx->tnum);
 		sql_vdbe_set_p4_key_def(pParse, pDestIdx);
 		sqlite3VdbeChangeP5(v, OPFLAG_BULKCSR);
-		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);
diff --git a/src/box/sql/pragma.c b/src/box/sql/pragma.c
index 9dab5a7fd..45896811b 100644
--- a/src/box/sql/pragma.c
+++ b/src/box/sql/pragma.c
@@ -370,8 +370,11 @@ sqlite3Pragma(Parse * pParse, Token * pId,	/* First part of [schema.]id field */
 						for (k = 1;
 						     k <=
 						     (int)pTab->def->field_count
-						     && pPk->aiColumn[k - 1] !=
-						     i; k++) {
+						     && (int) pPk->def->
+							     key_def->
+							     parts[k - 1].
+							     fieldno != i;
+						     k++) {
 						}
 					}
 					bool nullable =
@@ -430,7 +433,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,
@@ -459,11 +462,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,
@@ -477,19 +482,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 <
@@ -519,10 +523,8 @@ sqlite3Pragma(Parse * pParse, Token * pId,	/* First part of [schema.]id field */
 						    { "c", "u", "pk" };
 						sqlite3VdbeMultiLoad(v, 1,
 								     "isisi", i,
-								     pIdx->
-								     zName,
-								     index_is_unique
-								     (pIdx),
+								     pIdx->def->name,
+								     pIdx->def->opts.is_unique,
 								     azOrigin
 								     [pIdx->
 								      idxType],
diff --git a/src/box/sql/select.c b/src/box/sql/select.c
index 2aa35a114..2646a99c3 100644
--- a/src/box/sql/select.c
+++ b/src/box/sql/select.c
@@ -4291,7 +4291,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 01351a183..36b46ed4f 100644
--- a/src/box/sql/sqliteInt.h
+++ b/src/box/sql/sqliteInt.h
@@ -2071,21 +2071,6 @@ struct UnpackedRecord {
  * 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.
@@ -2102,27 +2087,19 @@ 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 */
 	ExprList *aColExpr;	/* Column expressions */
 	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 */
+	struct index_def *def;
 };
 
 /**
@@ -2161,11 +2138,6 @@ index_field_tuple_est(struct Index *idx, uint32_t field);
 #define IsUniqueIndex(X)      (((X)->idxType == SQLITE_IDXTYPE_UNIQUE) || \
 				((X)->idxType == SQLITE_IDXTYPE_PRIMARYKEY))
 
-/* The Index.aiColumn[] values are normally positive integer.  But
- * there are some negative values that have special meaning:
- */
-#define XN_EXPR      (-2)	/* Indexed column is an expression */
-
 #ifdef DEFAULT_TUPLE_COUNT
 #undef DEFAULT_TUPLE_COUNT
 #endif
@@ -3526,37 +3498,10 @@ 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);
+
 bool
 space_is_view(Table *);
 
-/**
- * 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 *);
 int
 emit_open_cursor(Parse *, int, int);
@@ -3607,8 +3552,6 @@ void sqlite3SrcListAssignCursors(Parse *, SrcList *);
 void sqlite3IdListDelete(sqlite3 *, IdList *);
 void sqlite3SrcListDelete(sqlite3 *, SrcList *);
 Index *sqlite3AllocateIndexObject(sqlite3 *, i16, int, char **);
-bool
-index_is_unique(Index *);
 
 /**
  * Create a new index for an SQL table.  name is the name of the
@@ -4293,8 +4236,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);
diff --git a/src/box/sql/trigger.c b/src/box/sql/trigger.c
index e1126b2d2..ea3521133 100644
--- a/src/box/sql/trigger.c
+++ b/src/box/sql/trigger.c
@@ -872,8 +872,6 @@ codeRowTrigger(Parse * pParse,	/* Current parse context */
 	pSubParse->pToplevel = pTop;
 	pSubParse->eTriggerOp = pTrigger->op;
 	pSubParse->nQueryLoop = pParse->nQueryLoop;
-	struct region *region = &fiber()->gc;
-	pSubParse->region_initial_size = region_used(region);
 
 	v = sqlite3GetVdbe(pSubParse);
 	if (v) {
diff --git a/src/box/sql/update.c b/src/box/sql/update.c
index 590aad28b..6545b3b06 100644
--- a/src/box/sql/update.c
+++ b/src/box/sql/update.c
@@ -237,14 +237,14 @@ 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);
+		int nIdxCol = pIdx->def->key_def->part_count;
 		if (chngPk || hasFK || pIdx->pPartIdxWhere || pIdx == pPk) {
 			reg = ++pParse->nMem;
 			pParse->nMem += nIdxCol;
 		} else {
 			reg = 0;
 			for (i = 0; i < nIdxCol; i++) {
-				i16 iIdxCol = pIdx->aiColumn[i];
+				i16 iIdxCol = pIdx->def->key_def->parts[i].fieldno;
 				if (iIdxCol < 0 || aXRef[iIdxCol] >= 0) {
 					reg = ++pParse->nMem;
 					pParse->nMem += nIdxCol;
@@ -306,7 +306,7 @@ sqlite3Update(Parse * pParse,		/* The parser context */
 		nPk = nKey;
 	} else {
 		assert(pPk != 0);
-		nPk = index_column_count(pPk);
+		nPk = pPk->def->key_def->part_count;
 	}
 	iPk = pParse->nMem + 1;
 	pParse->nMem += nPk;
@@ -333,9 +333,9 @@ sqlite3Update(Parse * pParse,		/* The parser context */
 		}
 	} else {
 		for (i = 0; i < nPk; i++) {
-			assert(pPk->aiColumn[i] >= 0);
 			sqlite3ExprCodeGetColumnOfTable(v, pTab->def, iDataCur,
-							pPk->aiColumn[i],
+							pPk->def->key_def->
+								parts[i].fieldno,
 							iPk + i);
 		}
 	}
diff --git a/src/box/sql/vdbeaux.c b/src/box/sql/vdbeaux.c
index 679bd0bc1..520b309d9 100644
--- a/src/box/sql/vdbeaux.c
+++ b/src/box/sql/vdbeaux.c
@@ -1165,7 +1165,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 f408b7701..51b5d516e 100644
--- a/src/box/sql/vdbemem.c
+++ b/src/box/sql/vdbemem.c
@@ -1087,7 +1087,7 @@ 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 nCol = pIdx->def->key_def->part_count;
 
 			nByte = sizeof(Mem) * nCol +
 				ROUND8(sizeof(UnpackedRecord));
@@ -1095,7 +1095,7 @@ valueNew(sqlite3 * db, struct ValueNewStat4Ctx *p)
 			    (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);
diff --git a/src/box/sql/where.c b/src/box/sql/where.c
index e79164781..9f5de50f9 100644
--- a/src/box/sql/where.c
+++ b/src/box/sql/where.c
@@ -265,11 +265,6 @@ whereScanNext(WhereScan * pScan)
 			for (pTerm = pWC->a + k; k < pWC->nTerm; k++, pTerm++) {
 				if (pTerm->leftCursor == iCur
 				    && pTerm->u.leftColumn == iColumn
-				    && (iColumn != XN_EXPR
-					|| sqlite3ExprCompare(pTerm->pExpr->
-							      pLeft,
-							      pScan->pIdxExpr,
-							      iCur) == 0)
 				    && (pScan->iEquiv <= 1
 					|| !ExprHasProperty(pTerm->pExpr,
 							    EP_FromJoin))
@@ -376,19 +371,21 @@ whereScanInit(WhereScan * pScan,	/* The WhereScan object being initialized */
 	pScan->is_column_seen = false;
 	if (pIdx) {
 		int j = iColumn;
-		iColumn = pIdx->aiColumn[j];
-		if (iColumn == XN_EXPR) {
-			pScan->pIdxExpr = pIdx->aColExpr->a[j].pExpr;
-		} else if (iColumn >= 0) {
+		iColumn = pIdx->def->key_def->parts[j].fieldno;
+		/*
+		 * pIdx->tnum == 0 means that pIdx is a fake integer
+		 * primary key index
+		 */
+		if (pIdx->tnum == 0)
+			iColumn = -1;
+
+		if (iColumn >= 0) {
 			char affinity =
 				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 if (iColumn == XN_EXPR) {
-		return 0;
 	}
 	pScan->opMask = opMask;
 	pScan->k = 0;
@@ -464,18 +461,17 @@ 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;
 			}
 		}
@@ -484,27 +480,6 @@ findIndexCol(Parse * pParse,	/* Parse context */
 	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.
@@ -556,9 +531,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,
@@ -566,11 +541,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 == 0)
 					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;
 		}
@@ -1107,7 +1083,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;
@@ -1169,13 +1145,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 *pColl = p->def->key_def->parts[nEq].coll;
 	if (pLower) {
 		rc = sqlite3Stat4ValueFromExpr(pParse, pLower->pExpr->pRight,
 					       aff, &p1);
@@ -1371,7 +1346,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);
@@ -1521,7 +1496,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
@@ -1542,7 +1517,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;
@@ -1674,7 +1649,7 @@ 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 (p->pIndex && (zName = p->pIndex->def->name) != 0) {
 		if (strncmp(zName, "sqlite_autoindex_", 17) == 0) {
 			int i = sqlite3Strlen30(zName) - 1;
 			while (zName[i] != '_')
@@ -2236,7 +2211,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.
@@ -2257,11 +2232,10 @@ 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)) {
+		if (pLhs->op != TK_COLUMN || pLhs->iTable != iCur
+		    || pLhs->iColumn != (int)pIdx->def->key_def->parts[i + nEq].fieldno
+		    || pIdx->def->key_def->parts[i + nEq].sort_order !=
+		       pIdx->def->key_def->parts[nEq].sort_order) {
 			break;
 		}
 
@@ -2275,7 +2249,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;
@@ -2318,13 +2292,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 nProbeCol = 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) {
@@ -2374,8 +2348,9 @@ whereLoopAddBtreeIndex(WhereLoopBuilder * pBuilder,	/* The WhereLoop factory */
 		LogEst nOutUnadjusted;	/* nOut before IN() and WHERE adjustments */
 		int nIn = 0;
 		int nRecValid = pBuilder->nRecValid;
+		uint32_t j = pProbe->def->key_def->parts[saved_nEq].fieldno;
 		if ((eOp == WO_ISNULL || (pTerm->wtFlags & TERM_VNULL) != 0)
-		    && indexColumnNotNull(pProbe, saved_nEq)
+		    && !pProbe->pTable->def->fields[j].is_nullable
 		    ) {
 			continue;	/* ignore IS [NOT] NULL constraints on NOT NULL columns */
 		}
@@ -2445,14 +2420,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 == nProbeCol - 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;
@@ -2514,8 +2491,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;
@@ -2671,7 +2647,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;
 }
 
@@ -2715,7 +2691,7 @@ indexMightHelpWithOrderBy(WhereLoopBuilder * pBuilder,
 	ExprList *pOB;
 	ExprList *aColExpr;
 	int ii, jj;
-	int nIdxCol = index_column_count(pIndex);
+	int nIdxCol = pIndex->def->key_def->part_count;
 	if (index_is_unordered(pIndex))
 		return 0;
 	if ((pOB = pBuilder->pWInfo->pOrderBy) == 0)
@@ -2726,13 +2702,12 @@ indexMightHelpWithOrderBy(WhereLoopBuilder * pBuilder,
 			if (pExpr->iColumn < 0)
 				return 1;
 			for (jj = 0; jj < nIdxCol; jj++) {
-				if (pExpr->iColumn == pIndex->aiColumn[jj])
+				if (pExpr->iColumn == (int)
+				    pIndex->def->key_def->parts[jj].fieldno)
 					return 1;
 			}
 		} else if ((aColExpr = pIndex->aColExpr) != 0) {
 			for (jj = 0; jj < nIdxCol; jj++) {
-				if (pIndex->aiColumn[jj] != XN_EXPR)
-					continue;
 				if (sqlite3ExprCompare
 				    (pExpr, aColExpr->a[jj].pExpr,
 				     iCursor) == 0) {
@@ -2815,7 +2790,6 @@ whereLoopAddBtree(WhereLoopBuilder * pBuilder,	/* WHERE clause information */
 	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 */
 	SrcList *pTabList;	/* The FROM clause */
 	struct SrcList_item *pSrc;	/* The FROM clause btree term to add */
 	WhereLoop *pNew;	/* Template WhereLoop object */
@@ -2846,11 +2820,27 @@ whereLoopAddBtree(WhereLoopBuilder * pBuilder,	/* WHERE clause information */
 		 */
 		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;
+
+		struct key_def *key_def = key_def_new(1);
+		if (key_def == NULL)
+			return SQLITE_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 index_opts = index_opts_default;
+
+		sPk.def = index_def_new(pTab->def->id, 0, "primary",
+					sizeof("primary") - 1, TREE, &index_opts,
+					key_def, NULL);
+
+		if (sPk.def == NULL)
+			return SQLITE_ERROR;
+
 		aiRowEstPk[0] = sql_space_tuple_log_count(pTab);
 		aiRowEstPk[1] = 0;
 		pFirst = pSrc->pTab->pIndex;
@@ -3325,8 +3315,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
@@ -3387,9 +3377,10 @@ wherePathSatisfiesOrderBy(WhereInfo * pWInfo,	/* The WHERE clause */
 				 * (revIdx) for the j-th column of the index.
 				 */
 				if (pIndex) {
-					iColumn = pIndex->aiColumn[j];
-					revIdx = sql_index_column_sort_order(pIndex,
-									     j);
+					iColumn = pIndex->def->key_def->
+						parts[j].fieldno;
+					revIdx = pIndex->def->key_def->
+						parts[j].sort_order;
 					if (iColumn == pIndex->pTable->iPKey)
 						iColumn = -1;
 				} else {
@@ -3442,8 +3433,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;
@@ -4105,13 +4095,13 @@ whereShortCut(WhereLoopBuilder * pBuilder)
 	} else {
 		for (pIdx = pTab->pIndex; pIdx; pIdx = pIdx->pNext) {
 			int opMask;
-			int nIdxCol = index_column_count(pIdx);
+			int nIdxCol = pIdx->def->key_def->part_count;
 			assert(pLoop->aLTermSpace == pLoop->aLTerm);
-			if (!index_is_unique(pIdx)
+			if (!pIdx->def->opts.is_unique
 			    || pIdx->pPartIdxWhere != 0
-			    || nIdxCol > ArraySize(pLoop->aLTermSpace)
-			    )
+			    || nIdxCol > ArraySize(pLoop->aLTermSpace)) {
 				continue;
+			}
 			opMask = WO_EQ;
 			for (j = 0; j < nIdxCol; j++) {
 				pTerm =
@@ -4650,7 +4640,7 @@ sqlite3WhereBegin(Parse * pParse,	/* The parser context */
 					wctrlFlags & WHERE_ORDERBY_MIN) == 0) {
 					sqlite3VdbeChangeP5(v, OPFLAG_SEEKEQ);	/* Hint to COMDB2 */
 				}
-				VdbeComment((v, "%s", pIx->zName));
+				VdbeComment((v, "%s", pIx->def->name));
 #ifdef SQLITE_ENABLE_COLUMN_USED_MASK
 				{
 					u64 colUsed = 0;
@@ -4781,7 +4771,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 09b267194..22bb76013 100644
--- a/src/box/sql/wherecode.c
+++ b/src/box/sql/wherecode.c
@@ -47,9 +47,7 @@
 static const char *
 explainIndexColumnName(Index * pIdx, int i)
 {
-	i = pIdx->aiColumn[i];
-	if (i == XN_EXPR)
-		return "<expr>";
+	i = pIdx->def->key_def->parts[i].fieldno;
 	return pIdx->pTable->def->fields[i].name;
 }
 
@@ -222,7 +220,7 @@ sqlite3WhereExplainOneScan(Parse * pParse,	/* Parse context */
 			}
 			if (zFmt) {
 				sqlite3StrAccumAppend(&str, " USING ", 7);
-				sqlite3XPrintf(&str, zFmt, pIdx->zName);
+				sqlite3XPrintf(&str, zFmt, pIdx->def->name);
 				explainIndexRange(&str, pLoop);
 			}
 		} else if ((flags & WHERE_IPK) != 0
@@ -463,7 +461,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;
@@ -708,7 +706,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),
@@ -718,8 +716,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);
-			testcase(pIdx->aiColumn[j] == XN_EXPR);
+					  pIdx->def->key_def->parts[j].fieldno,
+					  regBase + j);
 			VdbeComment((v, "%s", explainIndexColumnName(pIdx, j)));
 		}
 	}
@@ -1245,10 +1243,10 @@ sqlite3WhereCodeOneLoopStart(WhereInfo * pWInfo,	/* Complete information about t
 		assert(pWInfo->pOrderBy == 0
 		       || pWInfo->pOrderBy->nExpr == 1
 		       || (pWInfo->wctrlFlags & WHERE_ORDERBY_MIN) == 0);
-		int nIdxCol = index_column_count(pIdx);
+		int nIdxCol = pIdx->def->key_def->part_count;
 		if ((pWInfo->wctrlFlags & WHERE_ORDERBY_MIN) != 0
 		    && pWInfo->nOBSat > 0 && (nIdxCol > nEq)) {
-			j = pIdx->aiColumn[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.
@@ -1259,8 +1257,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)
-			    || j == XN_EXPR) {
+			if (pIdx->pTable->def->fields[j].is_nullable) {
 				assert(pLoop->nSkip == 0);
 				bSeekPastNull = 1;
 				nExtraReg = 1;
@@ -1299,17 +1296,15 @@ sqlite3WhereCodeOneLoopStart(WhereInfo * pWInfo,	/* Complete information about t
 				assert((bRev & ~1) == 0);
 				pLevel->iLikeRepCntr <<= 1;
 				pLevel->iLikeRepCntr |=
-					bRev ^ (sql_index_column_sort_order(pIdx, nEq) ==
+					bRev ^ (pIdx->def->key_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 == XN_EXPR) {
+				j = pIdx->def->key_def->parts[nEq].fieldno;
+				if (pIdx->pTable->def->fields[j].is_nullable)
 					bSeekPastNull = 1;
-				}
 			}
 		}
 		assert(pRangeEnd == 0
@@ -1320,7 +1315,7 @@ sqlite3WhereCodeOneLoopStart(WhereInfo * pWInfo,	/* Complete information about t
 		 * start and end terms (pRangeStart and pRangeEnd).
 		 */
 		if ((nEq < nIdxCol &&
-		     bRev == (sql_index_column_sort_order(pIdx, nEq) ==
+		     bRev == (pIdx->def->key_def->parts[nEq].sort_order ==
 			      SORT_ORDER_ASC)) ||
 		    (bRev && nIdxCol == nEq)) {
 			SWAP(pRangeEnd, pRangeStart);
@@ -1386,9 +1381,10 @@ sqlite3WhereCodeOneLoopStart(WhereInfo * pWInfo,	/* Complete information about t
 		}
 		struct Index *pk = sqlite3PrimaryKeyIndex(pIdx->pTable);
 		assert(pk);
-		int nPkCol = index_column_count(pk);
+		int nPkCol = pk->def->key_def->part_count;
+		uint32_t zero_fieldno = pk->def->key_def->parts[0].fieldno;
 		char affinity =
-			pIdx->pTable->def->fields[pk->aiColumn[0]].affinity;
+			pIdx->pTable->def->fields[zero_fieldno].affinity;
 		if (nPkCol == 1 && affinity == AFFINITY_INTEGER) {
 			/* Right now INTEGER PRIMARY KEY is the only option to
 			 * get Tarantool's INTEGER column type. Need special handling
@@ -1397,7 +1393,8 @@ sqlite3WhereCodeOneLoopStart(WhereInfo * pWInfo,	/* Complete information about t
 			 */
 			int limit = pRangeStart == NULL ? nEq : nEq + 1;
 			for (int i = 0; i < limit; i++) {
-				if (pIdx->aiColumn[i] == pk->aiColumn[0]) {
+				if (pIdx->def->key_def->parts[i].fieldno ==
+				    zero_fieldno) {
 					/* Here: we know for sure that table has INTEGER
 					   PRIMARY KEY, single column, and Index we're
 					   trying to use for scan contains this column. */
@@ -1506,10 +1503,10 @@ 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 nPkCol = pPk->def->key_def->part_count;
 			int iKeyReg = sqlite3GetTempRange(pParse, nPkCol);
 			for (j = 0; j < nPkCol; j++) {
-				k = pPk->aiColumn[j];
+				k = pPk->def->key_def->parts[j].fieldno;
 				sqlite3VdbeAddOp3(v, OP_Column, iIdxCur, k,
 						  iKeyReg + j);
 			}
@@ -1614,7 +1611,7 @@ 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 nPkCol = pPk->def->key_def->part_count;
 			regRowset = pParse->nTab++;
 			sqlite3VdbeAddOp2(v, OP_OpenTEphemeral,
 					  regRowset, nPkCol);
@@ -1718,13 +1715,16 @@ 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 nPk = pPk->def->key_def->part_count;
 						int iPk;
 
 						/* Read the PK into an array of temp registers. */
 						r = sqlite3GetTempRange(pParse, nPk);
 						for (iPk = 0; iPk < nPk; iPk++) {
-							int iCol = pPk->aiColumn[iPk];
+							int iCol = pPk->def->
+								key_def->
+								parts[iPk].
+								fieldno;
 							sqlite3ExprCodeGetColumnToReg
 								(pParse, pTab->def,
 								 iCol, iCur,
diff --git a/src/box/sql/whereexpr.c b/src/box/sql/whereexpr.c
index aa6d4524d..40e4e2577 100644
--- a/src/box/sql/whereexpr.c
+++ b/src/box/sql/whereexpr.c
@@ -903,7 +903,6 @@ exprMightBeIndexed(SrcList * pFrom,	/* The FROM clause */
 		   int *piColumn	/* Write the referenced table column number here */
     )
 {
-	Index *pIdx;
 	int i;
 	int iCur;
 
@@ -930,20 +929,6 @@ exprMightBeIndexed(SrcList * pFrom,	/* The FROM clause */
 	for (i = 0; mPrereq > 1; i++, mPrereq >>= 1) {
 	}
 	iCur = pFrom->a[i].iCursor;
-	for (pIdx = pFrom->a[i].pTab->pIndex; pIdx; pIdx = pIdx->pNext) {
-		if (pIdx->aColExpr == 0)
-			continue;
-		for (i = 0; i < pIdx->nColumn; i++) {
-			if (pIdx->aiColumn[i] != XN_EXPR)
-				continue;
-			if (sqlite3ExprCompare
-			    (pExpr, pIdx->aColExpr->a[i].pExpr, iCur) == 0) {
-				*piCur = iCur;
-				*piColumn = XN_EXPR;
-				return 1;
-			}
-		}
-	}
 	return 0;
 }
 
-- 

[-- Attachment #2: Type: text/html, Size: 294870 bytes --]

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

* [tarantool-patches] Re: [PATCH v3] sql: add index_def to struct Index
  2018-06-13  7:30 [tarantool-patches] Re: [PATCH v3] sql: add index_def to struct Index Ivan Koptelov
@ 2018-06-18 18:45 ` Kirill Shcherbatov
  2018-06-21 12:57   ` [tarantool-patches] Re: [PATCH v4] " Ivan Koptelov
  0 siblings, 1 reply; 23+ messages in thread
From: Kirill Shcherbatov @ 2018-06-18 18:45 UTC (permalink / raw)
  To: tarantool-patches, Ivan Koptelov; +Cc: v.shpilevoy

Thank you for the patch. Glad to see indexes so much simplified.
Would you kindly use post program next time? The message was smashed and this made me cry a bit ;)
Please, fix following 16 comments (2 memory problems and 14 minor codestyle fixes):

>> Branch: https://github.com/tarantool/tarantool/tree/sb/gh-3369-use-index-def-in-select-and-where
>> Issue: https://github.com/tarantool/tarantool/issues/3369
>> 
>>  src/box/sql.c           |  54 +++---
>>  src/box/sql/analyze.c   |  40 ++--
>>  src/box/sql/build.c     | 488 +++++++++++++++++++++++-------------------------
>>  src/box/sql/delete.c    |  17 +-
>>  src/box/sql/expr.c      |  60 +++---
>>  src/box/sql/fkey.c      |  47 ++---
>>  src/box/sql/insert.c    | 148 ++++++---------
>>  src/box/sql/pragma.c    |  32 ++--
>>  src/box/sql/select.c    |   2 +-
>>  src/box/sql/sqliteInt.h |  63 +------
>>  src/box/sql/trigger.c   |   2 -
>>  src/box/sql/update.c    |  10 +-
>>  src/box/sql/vdbeaux.c   |   2 +-
>>  src/box/sql/vdbemem.c   |   4 +-
>>  src/box/sql/where.c     | 168 ++++++++---------
>>  src/box/sql/wherecode.c |  54 +++---
>>  src/box/sql/whereexpr.c |  15 --
>>  17 files changed, 532 insertions(+), 674 deletions(-)
>> 
>> diff --git a/src/box/sql.c b/src/box/sql.c
>> index 7379cb418..213f8e453 100644
>> --- a/src/box/sql.c
>> +++ b/src/box/sql.c
>> @@ -1442,8 +1442,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;
>>  	}
>> @@ -1552,20 +1552,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;
>>  	}
>>  
>> @@ -1575,46 +1574,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 afc824a1a..31de7ab05 100644
>> --- a/src/box/sql/analyze.c
>> +++ b/src/box/sql/analyze.c
>> @@ -849,7 +849,6 @@ analyzeOneTable(Parse * pParse,	/* Parser context */
>>  		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 */
>>  
>>  		if (pOnlyIdx && pOnlyIdx != pIdx)
>>  			continue;
>> @@ -860,9 +859,9 @@ analyzeOneTable(Parse * pParse,	/* Parser context */
>>  		if (IsPrimaryKeyIndex(pIdx)) {
>>  			zIdxName = pTab->def->name;
>>  		} else {
>> -			zIdxName = pIdx->zName;
>> +			zIdxName = pIdx->def->name;
>>  		}
>> -		nColTest = index_column_count(pIdx);
>> +		int nColTest = pIdx->def->key_def->part_count;
>>  
>>  		/* Populate the register containing the index name. */
>>  		sqlite3VdbeLoadString(v, regIdxname, zIdxName);
>> @@ -917,7 +916,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:
>>  		 *
>> @@ -969,7 +968,7 @@ analyzeOneTable(Parse * pParse,	/* Parser context */
>>  			 */
>>  			sqlite3VdbeAddOp0(v, OP_Goto);
>>  			addrNextRow = sqlite3VdbeCurrentAddr(v);
>> -			if (nColTest == 1 && index_is_unique(pIdx)) {
>> +			if (nColTest == 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 +977,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 < nColTest; ++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,
>> @@ -1006,7 +1004,8 @@ analyzeOneTable(Parse * pParse,	/* Parser context */
>>  			for (i = 0; i < nColTest; i++) {
>>  				sqlite3VdbeJumpHere(v, aGotoChng[i]);
>>  				sqlite3VdbeAddOp3(v, OP_Column, iIdxCur,
>> -						  pIdx->aiColumn[i],
>> +						  pIdx->def->key_def->
>> +							  parts[i].fieldno,
>>  						  regPrev + i);
>>  			}
>>  			sqlite3VdbeResolveLabel(v, endDistinctTest);
>> @@ -1022,15 +1021,14 @@ 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));
>> +		int nPkColumn = (int) pPk->def->key_def->part_count;
>> +		int regKeyStat = sqlite3GetTempRange(pParse, nPkColumn);
>> +		for (int j = 0; j < nPkColumn; ++j) {
>> +			int k = pPk->def->key_def->parts[j].fieldno;
>> +			assert(k >= 0 && k < (int) 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);
>> @@ -1150,7 +1148,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");
>>  	}
>> diff --git a/src/box/sql/build.c b/src/box/sql/build.c
>> index 62d687b17..f18727c61 100644
>> --- a/src/box/sql/build.c
>> +++ b/src/box/sql/build.c
>> @@ -253,6 +253,8 @@ freeIndex(sqlite3 * db, Index * p)
>>  {
>>  	sql_expr_delete(db, p->pPartIdxWhere, false);
>>  	sql_expr_list_delete(db, p->aColExpr);
>> +	if (p->def != NULL)
>> +		index_def_delete(p->def);
>>  	sqlite3DbFree(db, p->zColAff);
>>  	sqlite3DbFree(db, p);
>>  }
>> @@ -271,7 +273,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;
>> @@ -388,7 +391,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);
>> @@ -1058,7 +1061,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)
>> @@ -1066,22 +1069,20 @@ 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; pIdx = pIdx->pNext) {
>> +			assert(pIdx->def->key_def->part_count == 1);
>> +			if (pIdx->def->key_def->parts[0].fieldno == i) {
>> +				pIdx->def->key_def->parts[0].coll_id = *id;
>> +				pIdx->def->key_def->parts[0].coll =
>>  					sql_column_collation(p->def, i, id);
>>  			}
>>  		}
>> -	} else {
>> -		sqlite3DbFree(db, zColl);
>>  	}
>> +	sqlite3DbFree(db, zColl);
>>  }
>>  
>>  struct coll *
>> @@ -1111,66 +1112,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;
>> -}
>> -
>>  /**
>>   * Return true if space which corresponds to
>>   * given table has view option.
>> @@ -1383,14 +1324,16 @@ 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)
>> +hasColumn(const struct key_part *key_parts, int nCol, uint32_t fieldno)
>>  {
>> -	while (nCol-- > 0)
>> -		if (x == *(aiCol++))
>> +	int i = 0;
>> +	while (i < nCol) {
>> +		if (fieldno == key_parts->fieldno)
>>  			return 1;
>> +		key_parts++;
>> +		i++;
>> +	}
>>  	return 0;
>>  }
>>  
>> @@ -1410,13 +1353,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;
>> @@ -1454,14 +1396,28 @@ convertToWithoutRowidTable(Parse * pParse, Table * pTab)
>>  		 * "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];
>> +
>> +		struct key_part *parts = pPk->def->key_def->parts;
>> +		uint32_t part_count = pPk->def->key_def->part_count;
>> +		uint32_t new_part_count = part_count;
>> +
>> +		for (uint32_t i = 1; i < part_count; i++) {
>> +			if (hasColumn(parts, i, parts[i].fieldno)){
>> +				new_part_count--;
>> +				bool is_found = false;
>> +				for (uint32_t j = i + 1; j < part_count; j++){
>> +					if (!(hasColumn(parts, j,
>> +							parts[j].fieldno))) {
>> +						parts[i] = parts[j];
>> +						is_found = true;
>> +						break;
>> +					}
>> +				}
>> +				if (!(is_found))
>> +					break;
>>  			}
>>  		}
>> -		pPk->nColumn = j;
>> +		pPk->def->key_def->part_count = new_part_count;
>>  	}
>>  	assert(pPk != 0);
>>  }
>> @@ -1543,7 +1499,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);
>> @@ -1580,7 +1536,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) {
>> @@ -2652,14 +2608,15 @@ 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,
>> +	sqlite3VdbeAddOp4(v, OP_SorterOpen, iSorter, 0,
>> +			  pIndex->def->key_def->part_count,
>>  			  (char *)def, P4_KEYDEF);
1. Maybe a little better like this?
-                         pIndex->def->key_def->part_count,
-                         (char *)def, P4_KEYDEF);
+                         pIndex->def->key_def->part_count, (char *)def,
+                         P4_KEYDEF);

>>  
>>  	/* Open the table. Loop through all rows of the table, inserting index
>> @@ -2692,7 +2649,7 @@ 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);
2. Out of margin
-                                    regRecord, pIndex->def->key_def->part_count);
+                                    regRecord,
+                                    pIndex->def->key_def->part_count);

>>  		VdbeCoverage(v);
>>  		sqlite3UniqueConstraint(pParse, ON_CONFLICT_ACTION_ABORT,
>>  					pIndex);
>> @@ -2730,24 +2687,13 @@ sqlite3AllocateIndexObject(sqlite3 * db,	/* Database connection */
>>  	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  */
>> +	    ROUND8(sizeof(LogEst) * (nCol + 1));    /* Index.aiRowLogEst */
>>  	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;
>>  	}
>>  	return p;
>> @@ -2836,18 +2782,133 @@ addIndexToTable(Index * pIndex, Table * pTab)
>>  	}
>>  }
>>  
>> -bool
>> -index_is_unique(Index *idx)
>> +static inline void
>> +append_string_part(struct region *r, const char *str,
>> +		  size_t *total_sql_size, Parse *parse)
>>  {
>> -	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);
>> +	char * str_part = region_alloc(r, strlen(str));
>> +	if (str_part == NULL){
>> +		diag_set(OutOfMemory, strlen(str),
>> +			 "region_alloc", "str_part");
>> +		parse->rc = SQL_TARANTOOL_ERROR;
>> +		parse->nErr++;
>> +	}
>> +	memcpy(str_part, str, strlen(str));
>> +	*total_sql_size += strlen(str);
>> +}
>> +
>> +void static
3. GCC doesn't accept such specifiers order in default build.
Please change this to "static void".
You also better look to the Travis times to times.

>> +set_index_def(Parse *parse, Index *index, Table *table, uint32_t iid,
>> +	      const char *name, uint32_t name_len, int on_error,
>> +	      struct ExprList *expr_list, u8 idx_type)
>> +{
>> +	struct space_def *space_def = table->def;
>> +	struct index_opts opts;
>> +	index_opts_create(&opts);
>> +	opts.is_unique = on_error != ON_CONFLICT_ACTION_NONE;
>> +
>> +	struct key_def *key_def = key_def_new(expr_list->nExpr);
>> +	if (key_def == NULL) {
>> +		parse->rc = SQL_TARANTOOL_ERROR;
>> +		parse->nErr++;
>> +		return;
>> +	}
>> +
>> +	/*
>> +	 * Build initial parts of SQL statement.
>> +	 */
>> +
>> +	struct region *r = &fiber()->gc;
4. Please, rebase to the master. There would be parser region that should be used here, I believe.

>> +	size_t total_sql_size = 0;
>> +
>> +	if (idx_type == SQLITE_IDXTYPE_APPDEF) {
>> +		append_string_part(r, "CREATE INDEX ", &total_sql_size,
>> +				   parse);
>> +		append_string_part(r, name, &total_sql_size, parse);
>> +		append_string_part(r, " ON ", &total_sql_size, parse);
>> +		append_string_part(r, space_def->name, &total_sql_size,
>> +				   parse);
>> +		append_string_part(r, " (", &total_sql_size, parse);
>> +	}
>> +
>> +	for (int i = 0; i < expr_list->nExpr; i++) {
>> +		Expr *expr = expr_list->a[i].pExpr;
>> +		sql_resolve_self_reference(parse, table, NC_IdxExpr, expr, 0);
>> +		if (parse->nErr > 0)
>> +			return;
5. If I not mistaken, key_def is leaking here, with other returns and at the end of the function.

>> +
>> +		Expr *column_expr = sqlite3ExprSkipCollate(expr);
>> +		if (column_expr->op != TK_COLUMN) {
>> +			sqlite3ErrorMsg(parse,
>> +					"functional indexes aren't supported "
>> +					"in the current version");
>> +			return;
>> +		}
>> +
>> +		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 (idx_type == SQLITE_IDXTYPE_APPDEF) {
>> +				append_string_part(r, name,
>> +						   &total_sql_size, parse);
>> +				append_string_part(r, " COLLATE ",
>> +						   &total_sql_size, parse);
>> +				const char *coll_name = expr->u.zToken;
>> +				append_string_part(r, coll_name,
>> +						   &total_sql_size, parse);
>> +				append_string_part(r, ", ",
>> +						   &total_sql_size, parse);
>> +			}
>> +		} else {
>> +			coll = sql_column_collation(space_def, fieldno,
>> +						    &coll_id);
>> +			if (idx_type == SQLITE_IDXTYPE_APPDEF) {
>> +				append_string_part(r, name,
>> +						   &total_sql_size, parse);
>> +				append_string_part(r, ", ",
>> +						   &total_sql_size, parse);
>> +			}
>> +		}
>> +
>> +		/*
>> +		* Tarantool: DESC indexes are not supported so far.
>> +		* See gh-3016.
>> +		*/
>> +		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);
>> +	}
>>  
>> -	return tnt_index->def->opts.is_unique;
>> +	if (parse->nErr > 0) {
>> +		index->def = NULL;
>> +		return;
>> +	}
>> +
>> +	if (idx_type == SQLITE_IDXTYPE_APPDEF) {
>> +		memcpy(region_alloc(r, 1), "\0", 1);
>> +		total_sql_size += 1;
>> +		opts.sql = region_join(r, total_sql_size);
>> +
>> +		/*
>> +		 * fix last ", " with ")\0" to finish the statement.
>> +		 */
>> +		opts.sql[total_sql_size - 3] = ')';
>> +		opts.sql[total_sql_size - 2] = '\0';
>> +	}
>> +
>> +	struct key_def *pk_key_def;
>> +	if (idx_type == SQLITE_IDXTYPE_APPDEF)
>> +		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);
>>  }
>>  
>>  void
>> @@ -2856,16 +2917,14 @@ sql_create_index(struct Parse *parse, struct Token *token,
>>  		 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;
>> +	Table *pTab = NULL;	/* Table to be indexed */
>> +	Index *pIndex = NULL;	/* The index to be created */
>> +	char *name = NULL;	/* Name of the index */
>> +	int name_len;		/* Number of characters in zName */
>>  	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 */
>> +	char *zExtra = NULL;	/* Extra space after the Index object */
>>  	struct session *user_session = current_session();
>>  
>>  	if (db->mallocFailed || parse->nErr > 0) {
>> @@ -2939,24 +2998,24 @@ sql_create_index(struct Parse *parse, struct Token *token,
>>  	 * our own name.
>>  	 */
>>  	if (token) {
>> -		zName = sqlite3NameFromToken(db, token);
>> -		if (zName == 0)
>> +		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) !=
>> +			if (sqlite3HashFind(&db->pSchema->tblHash, name) !=
>>  			    NULL) {
>>  				sqlite3ErrorMsg(parse,
>>  						"there is already a table named %s",
>> -						zName);
>> +						name);
>>  				goto exit_create_index;
>>  			}
>>  		}
>> -		if (sqlite3HashFind(&pTab->idxHash, zName) != NULL) {
>> +		if (sqlite3HashFind(&pTab->idxHash, name) != NULL) {
>>  			if (!if_not_exist) {
>>  				sqlite3ErrorMsg(parse,
>>  						"index %s.%s already exists",
>> -						pTab->def->name, zName);
>> +						pTab->def->name, name);
>>  			} else {
>>  				assert(!db->init.busy);
>>  			}
>> @@ -2968,10 +3027,9 @@ sql_create_index(struct Parse *parse, struct Token *token,
>>  		for (pLoop = pTab->pIndex, n = 1; pLoop;
>>  		     pLoop = pLoop->pNext, n++) {
>>  		}
>> -		zName =
>> -		    sqlite3MPrintf(db, "sqlite_autoindex_%s_%d", pTab->def->name,
>> -				   n);
>> -		if (zName == 0) {
>> +		name = sqlite3MPrintf(db, "sqlite_autoindex_%s_%d",
>> +				      pTab->def->name, n);
>> +		if (name == NULL) {
>>  			goto exit_create_index;
>>  		}
>>  	}
>> @@ -2997,31 +3055,27 @@ 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));
>> -		}
>> -	}
>> -
>>  	/*
>>  	 * Allocate the index structure.
>>  	 */
>> -	nName = sqlite3Strlen30(zName);
>> +	name_len = sqlite3Strlen30(name);
>> +
>> +	if (name_len > BOX_NAME_MAX) {
>> +		sqlite3ErrorMsg(parse,
>> +				"%s.%s exceeds indexes' names length limit",
>> +				pTab->def->name, name);
>> +		goto exit_create_index;
>> +	}
>> +
>> +	if (sqlite3CheckIdentifierName(parse, name) != SQLITE_OK)
>> +		goto exit_create_index;
>> +
>>  	pIndex = sqlite3AllocateIndexObject(db, col_list->nExpr,
>> -					    nName + nExtra + 1, &zExtra);
>> +					    name_len + nExtra + 1, &zExtra);
>>  	if (db->mallocFailed) {
>>  		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;
>>  	/*
>> @@ -3036,7 +3090,6 @@ sql_create_index(struct Parse *parse, struct Token *token,
>>  		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,
>> @@ -3045,60 +3098,27 @@ sql_create_index(struct Parse *parse, struct Token *token,
>>  		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[].  For general expressions,
>> -	 * populate pIndex->aColExpr and store XN_EXPR (-2) 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.
>>  	 */
>> -	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");
>> -			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;
>> -		}
>> -		pIndex->coll_array[i] = coll;
>> -		pIndex->coll_id_array[i] = id;
>> +	uint32_t max_iid = 0;
>> +	for (Index *index = pTab->pIndex; index; index = index->pNext) {
>> +		max_iid = max_iid > index->def->iid ?
>> +			  max_iid :
>> +			  index->def->iid + 1;
>> +	}
>>  
>> -		/* Tarantool: DESC indexes are not supported so far.
>> -		 * See gh-3016.
>> -		 */
>> -		pIndex->sort_order[i] = SORT_ORDER_ASC;
>> +	set_index_def(parse, pIndex, pTab, max_iid, name, name_len, on_error,
>> +		      col_list, idx_type);
>> +
>> +	if (pIndex->def == NULL ||
>> +	    !index_def_is_valid(pIndex->def, pTab->def->name)) {
>> +		goto exit_create_index;
>>  	}
>> +
>>  	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
>> @@ -3123,25 +3143,27 @@ sql_create_index(struct Parse *parse, struct Token *token,
>>  		 */
>>  		Index *pIdx;
>>  		for (pIdx = pTab->pIndex; pIdx; pIdx = pIdx->pNext) {
>> -			int k;
>> +			uint32_t k;
>>  			assert(IsUniqueIndex(pIdx));
>>  			assert(pIdx->idxType != SQLITE_IDXTYPE_APPDEF);
>>  			assert(IsUniqueIndex(pIndex));
>>  
>> -			if (pIdx->nColumn != pIndex->nColumn)
>> +			if (pIdx->def->key_def->part_count !=
>> +			    pIndex->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 < pIdx->def->key_def->part_count; k++) {
>> +				if (pIdx->def->key_def->parts[k].fieldno !=
>> +				    pIndex->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 = pIdx->def->key_def->parts[k].coll;
>> +				coll2 = pIndex->def->key_def->parts[k].coll;
>>  				if (coll1 != coll2)
>>  					break;
>>  			}
>> -			if (k == pIdx->nColumn) {
>> +			if (k == pIdx->def->key_def->part_count) {
>>  				if (pIdx->onError != pIndex->onError) {
>>  					/* This constraint creates the same index as a previous
>>  					 * constraint specified somewhere in the CREATE TABLE statement.
>> @@ -3175,7 +3197,7 @@ sql_create_index(struct Parse *parse, struct Token *token,
>>  	assert(parse->nErr == 0);
>>  	if (db->init.busy) {
>>  		Index *p;
>> -		p = sqlite3HashInsert(&pTab->idxHash, pIndex->zName, pIndex);
>> +		p = sqlite3HashInsert(&pTab->idxHash, pIndex->def->name, pIndex);
>>  		if (p) {
>>  			assert(p == pIndex);	/* Malloc must have failed */
>>  			sqlite3OomFault(db);
>> @@ -3273,44 +3295,7 @@ sql_create_index(struct Parse *parse, struct Token *token,
>>  	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
>> @@ -3938,18 +3923,19 @@ sqlite3UniqueConstraint(Parse * pParse,	/* Parsing context */
>>      )
>>  {
>>  	char *zErr;
>> -	int j;
>> +	uint32_t j;
>>  	StrAccum errMsg;
>>  	Table *pTab = pIdx->pTable;
>>  
>>  	sqlite3StrAccumInit(&errMsg, pParse->db, 0, 0, 200);
>>  	if (pIdx->aColExpr) {
>> -		sqlite3XPrintf(&errMsg, "index '%q'", pIdx->zName);
>> +		sqlite3XPrintf(&errMsg, "index '%q'", pIdx->def->name);
>>  	} else {
>> -		for (j = 0; j < pIdx->nColumn; j++) {
>> +		struct key_part *part = pIdx->def->key_def->parts;
>> +		for (j = 0; j < pIdx->def->key_def->part_count; j++, part++) {
>>  			char *zCol;
>> -			assert(pIdx->aiColumn[j] >= 0);
>> -			zCol = pTab->def->fields[pIdx->aiColumn[j]].name;
>> +			uint32_t fieldno = part->fieldno;
>> +			zCol = pTab->def->fields[fieldno].name;
>>  			if (j)
>>  				sqlite3StrAccumAppend(&errMsg, ", ", 2);
>>  			sqlite3XPrintf(&errMsg, "%s.%s", pTab->def->name, zCol);
>> @@ -3972,11 +3958,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 ddad54b3e..0314382f7 100644
>> --- a/src/box/sql/delete.c
>> +++ b/src/box/sql/delete.c
>> @@ -209,7 +209,7 @@ sql_table_delete_from(struct Parse *parse, struct SrcList *tab_list,
>>  		} else {
>>  			pk = sqlite3PrimaryKeyIndex(table);
>>  			assert(pk != NULL);
>> -			pk_len = index_column_count(pk);
>> +			pk_len = pk->def->key_def->part_count;
>>  			parse->nMem += pk_len;
>>  			sqlite3VdbeAddOp2(v, OP_OpenTEphemeral, eph_cursor,
>>  					  pk_len);
>> @@ -251,12 +251,11 @@ 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++) {
>> -				assert(pk->aiColumn[i] >= 0);
>> +			struct key_part *part = pk->def->key_def->parts;
>> +			for (int i = 0; i < pk_len; i++, part++) {
>>  				sqlite3ExprCodeGetColumnOfTable(v, table->def,
>>  								tab_cursor,
>> -								pk->
>> -								aiColumn[i],
>> +								part->fieldno,
>>  								reg_pk + i);
>>  			}
>>  		} else {
>> @@ -326,7 +325,7 @@ sql_table_delete_from(struct Parse *parse, struct SrcList *tab_list,
>>  			sqlite3VdbeAddOp3(v, OP_OpenWrite, tab_cursor,
>>  					  table->tnum, space_ptr_reg);
>>  			sql_vdbe_set_p4_key_def(parse, pk);
>> -			VdbeComment((v, "%s", pk->zName));
>> +			VdbeComment((v, "%s", pk->def->name));
>>  
>>  			if (one_pass == ONEPASS_MULTI)
>>  				sqlite3VdbeJumpHere(v, iAddrOnce);
>> @@ -536,14 +535,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]
>> -		    && prev->aiColumn[j] != XN_EXPR) {
>> +		if (prev->def->key_def->parts[j].fieldno ==
>> +		    index->def->key_def->parts[j].fieldno && prev == NULL) {
>>  			/*
>>  			 * This column was already computed by the
>>  			 * previous index.
>> diff --git a/src/box/sql/expr.c b/src/box/sql/expr.c
>> index 8866f6fed..a756535f0 100644
>> --- a/src/box/sql/expr.c
>> +++ b/src/box/sql/expr.c
>> @@ -2422,20 +2422,24 @@ 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) {
>> -					if (pIdx->nColumn > nExpr
>> -					    || (pIdx->nColumn > nExpr
>> -					    && !index_is_unique(pIdx))) {
>> -							continue;	/* This index is not unique over the IN RHS columns */
>> +					if ((int)part_count > nExpr
>> +					    || !pIdx->def->opts.is_unique) {
>> +						/* This index is not unique over the IN RHS columns */
>> +						continue;
>>  					}
>>  				}
>>  
>> @@ -2449,12 +2453,13 @@ 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) {
>>  							continue;
>> @@ -2483,17 +2488,16 @@ sqlite3FindInIndex(Parse * pParse,	/* Parsing context */
>>  							  0, 0, 0,
>>  							  sqlite3MPrintf(db,
>>  							  "USING INDEX %s FOR IN-OPERATOR",
>> -							  pIdx->zName),
>> +							  pIdx->def->name),
>>  							  P4_DYNAMIC);
>>  					emit_open_cursor(pParse, iTab,
>>  							 pIdx->tnum);
>>  					sql_vdbe_set_p4_key_def(pParse, pIdx);
>> -					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
>> @@ -2515,7 +2519,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);
>>  						}
>>  					}
>> @@ -3146,12 +3150,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
>> +			tab->def->fields[fieldno].affinity;
>> +		if (pk->def->key_def->part_count == 1
>>  		    && affinity == AFFINITY_INTEGER
>> -		    && pk->aiColumn[0] < nVector) {
>> -			int reg_pk = rLhs + pk->aiColumn[0];
>> +		    && (int) fieldno < nVector) { int reg_pk = rLhs + (int)fieldno;
>>  			sqlite3VdbeAddOp2(v, OP_MustBeInt, reg_pk, destIfFalse);
>>  		}
>>  	}
>> @@ -3483,17 +3487,9 @@ sqlite3ExprCodeLoadIndexColumn(Parse * pParse,	/* The parsing context */
>>  			       int regOut	/* Store the index column value in this register */
>>      )
>>  {
>> -	i16 iTabCol = pIdx->aiColumn[iIdxCol];
>> -	if (iTabCol == XN_EXPR) {
>> -		assert(pIdx->aColExpr);
>> -		assert(pIdx->aColExpr->nExpr > iIdxCol);
>> -		pParse->iSelfTab = iTabCur;
>> -		sqlite3ExprCodeCopy(pParse, pIdx->aColExpr->a[iIdxCol].pExpr,
>> -				    regOut);
>> -	} else {
>> -		sqlite3ExprCodeGetColumnOfTable(pParse->pVdbe, pIdx->pTable->def,
>> -						iTabCur, iTabCol, regOut);
>> -	}
>> +	i16 iTabCol = pIdx->def->key_def->parts[iIdxCol].fieldno;
>> +	sqlite3ExprCodeGetColumnOfTable(pParse->pVdbe, pIdx->pTable->def,
>> +					iTabCur, iTabCol, regOut);
>>  }
>>  
>>  void
>> diff --git a/src/box/sql/fkey.c b/src/box/sql/fkey.c
>> index 70ebef89f..79320eced 100644
>> --- a/src/box/sql/fkey.c
>> +++ b/src/box/sql/fkey.c
>> @@ -256,8 +256,8 @@ sqlite3FkLocateIndex(Parse * pParse,	/* Parse context to store any error in */
>>  	}
>>  
>>  	for (pIdx = pParent->pIndex; pIdx; pIdx = pIdx->pNext) {
>> -		int nIdxCol = index_column_count(pIdx);
>> -		if (nIdxCol == nCol && index_is_unique(pIdx)
>> +		int nIdxCol = pIdx->def->key_def->part_count;
>> +		if (nIdxCol == nCol && pIdx->def->opts.is_unique
>>  		    && 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
>> @@ -286,8 +286,10 @@ sqlite3FkLocateIndex(Parse * pParse,	/* Parse context to store any error in */
>>  				 * the default collation sequences for each column.
>>  				 */
>>  				int i, j;
>> -				for (i = 0; i < nCol; i++) {
>> -					i16 iCol = pIdx->aiColumn[i];	/* Index of column in parent tbl */
>> +				struct key_part *part =
>> +					pIdx->def->key_def->parts;
>> +				for (i = 0; i < nCol; i++, part++) {
>> +					i16 iCol = (int) part->fieldno;	/* Index of column in parent tbl */
>>  					char *zIdxCol;	/* Name of indexed column */
>>  
>>  					if (iCol < 0)
>> @@ -302,9 +304,7 @@ sqlite3FkLocateIndex(Parse * pParse,	/* Parse context to store any error in */
>>  					def_coll = sql_column_collation(pParent->def,
>>  									iCol,
>>  									&id);
>> -					struct coll *coll =
>> -						sql_index_collation(pIdx, i,
>> -								    &id);
>> +					struct coll *coll = part->coll;
>>  					if (def_coll != coll)
>>  						break;
>>  
>> @@ -464,13 +464,15 @@ fkLookupParent(Parse * pParse,	/* Parse context */
>>  				for (i = 0; i < nCol; i++) {
>>  					int iChild = aiCol[i] + 1 + regData;
>>  					int iParent =
>> -					    pIdx->aiColumn[i] + 1 + regData;
>> -					assert(pIdx->aiColumn[i] >= 0);
>> +						(int) pIdx->def->key_def->parts[i].fieldno
>> +						+ 1 + regData;
>>  					assert(aiCol[i] != pTab->iPKey);
>> -					if (pIdx->aiColumn[i] == pTab->iPKey) {
>> +					if ((int)pIdx->def->key_def->
>> +						parts[i].fieldno == pTab->iPKey) {
>>  						/* The parent key is a composite key that includes the IPK column */
>>  						iParent = regData;
>>  					}
>> +
>>  					sqlite3VdbeAddOp3(v, OP_Ne, iChild,
>>  							  iJump, iParent);
>>  					VdbeCoverage(v);
>> @@ -622,7 +624,7 @@ fkScanChildren(Parse * pParse,	/* Parse context */
>>  	Vdbe *v = sqlite3GetVdbe(pParse);
>>  
>>  	assert(pIdx == 0 || pIdx->pTable == pTab);
>> -	assert(pIdx == 0 || (int)index_column_count(pIdx) == pFKey->nCol);
>> +	assert(pIdx == 0 || (int) pIdx->def->key_def->part_count == pFKey->nCol);
>>  	assert(pIdx != 0);
>>  
>>  	if (nIncr < 0) {
>> @@ -646,7 +648,7 @@ fkScanChildren(Parse * pParse,	/* Parse context */
>>  		i16 iCol;	/* Index of column in child table */
>>  		const char *zCol;	/* Name of column in child table */
>>  
>> -		iCol = pIdx ? pIdx->aiColumn[i] : -1;
>> +		iCol = pIdx ? pIdx->def->key_def->parts[i].fieldno : -1;
-               iCol = pIdx ? pIdx->def->key_def->parts[i].fieldno : -1;
+               iCol = pIdx != NULL ?
+                       (i16)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);
>> @@ -671,10 +673,9 @@ fkScanChildren(Parse * pParse,	/* Parse context */
>>  		Expr *pEq, *pAll = 0;
>>  		Index *pPk = sqlite3PrimaryKeyIndex(pTab);
>>  		assert(pIdx != 0);
>> -		int col_count = index_column_count(pPk);
>> +		int col_count = pPk->def->key_def->part_count;
>>  		for (i = 0; i < col_count; i++) {
>> -			i16 iCol = pIdx->aiColumn[i];
>> -			assert(iCol >= 0);
>> +			i16 iCol = (int) pIdx->def->key_def->parts[i].fieldno;
>>  			pLeft = exprTableRegister(pParse, pTab, regData, iCol);
>>  			pRight =
>>  				exprTableColumn(db, pTab->def,
>> @@ -992,7 +993,6 @@ sqlite3FkCheck(Parse * pParse,	/* Parse context */
>>  			if (aiCol[i] == pTab->iPKey) {
>>  				aiCol[i] = -1;
>>  			}
>> -			assert(pIdx == 0 || pIdx->aiColumn[i] >= 0);
>>  		}
>>  
>>  		pParse->nTab++;
>> @@ -1126,10 +1126,10 @@ sqlite3FkOldmask(Parse * pParse,	/* Parse context */
>>  			Index *pIdx = 0;
>>  			sqlite3FkLocateIndex(pParse, pTab, p, &pIdx, 0);
>>  			if (pIdx) {
>> -				int nIdxCol = index_column_count(pIdx);
>> +				int nIdxCol = pIdx->def->key_def->part_count;
>>  				for (i = 0; i < nIdxCol; i++) {
>> -					assert(pIdx->aiColumn[i] >= 0);
>> -					mask |= COLUMN_MASK(pIdx->aiColumn[i]);
>> +					mask |= COLUMN_MASK(pIdx->def->
>> +						key_def->parts[i].fieldno);
>>  				}
>>  			}
>>  		}
>> @@ -1264,11 +1264,12 @@ fkActionTrigger(Parse * pParse,	/* Parse context */
>>  			       || (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
>> +					   : pTab->iPKey;
6. GCC looks like a little more pedantic.
-                                          : pTab->iPKey;

+                                          : (uint32_t)pTab->iPKey;
7. Please put ":" at the prev. line.

>>  			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 59c61c703..fc9f85165 100644
>> --- a/src/box/sql/insert.c
>> +++ b/src/box/sql/insert.c
>> @@ -89,7 +89,7 @@ sqlite3IndexAffinityStr(sqlite3 * db, Index * pIdx)
>>  		 * up.
>>  		 */
>>  		int n;
>> -		int nColumn = index_column_count(pIdx);
>> +		int nColumn = pIdx->def->key_def->part_count;
>>  		pIdx->zColAff =
>>  		    (char *)sqlite3DbMallocRaw(0, nColumn + 1);
>>  		if (!pIdx->zColAff) {
>> @@ -97,22 +97,8 @@ sqlite3IndexAffinityStr(sqlite3 * db, Index * pIdx)
>>  			return 0;
>>  		}
>>  		for (n = 0; n < nColumn; n++) {
>> -			i16 x = pIdx->aiColumn[n];
>> -			if (x >= 0) {
>> -				char affinity = pIdx->pTable->
>> -					def->fields[x].affinity;
>> -				pIdx->zColAff[n] = affinity;
>> -			} else {
>> -				char aff;
>> -				assert(x == XN_EXPR);
>> -				assert(pIdx->aColExpr != 0);
>> -				aff =
>> -				    sqlite3ExprAffinity(pIdx->aColExpr->a[n].
>> -							pExpr);
>> -				if (aff == 0)
>> -					aff = AFFINITY_BLOB;
>> -				pIdx->zColAff[n] = aff;
>> -			}
>> +			i16 x = pIdx->def->key_def->parts[n].fieldno;
>> +			pIdx->zColAff[n] = pIdx->pTable->def->fields[x].affinity;
>>  		}
>>  		pIdx->zColAff[n] = 0;
>>  	}
>> @@ -645,7 +631,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;
>>  		}
>>  	}
>>  
>> @@ -1088,7 +1074,7 @@ sqlite3GenerateConstraintChecks(Parse * pParse,		/* The parser context */
>>  	nCol = pTab->def->field_count;
>>  
>>  	pPk = sqlite3PrimaryKeyIndex(pTab);
>> -	nPkField = index_column_count(pPk);
>> +	nPkField = pPk->def->key_def->part_count;
>>  
>>  	/* Record that this module has started */
>>  	VdbeModuleComment((v, "BEGIN: GenCnstCks(%d,%d,%d,%d,%d)",
>> @@ -1252,38 +1238,27 @@ 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);
>> +		int nIdxCol = pIdx->def->key_def->part_count;
>>  		for (i = 0; i < nIdxCol; i++) {
>> -			int iField = pIdx->aiColumn[i];
>> +			int iField = (int) pIdx->def->key_def->parts[i].fieldno;
>>  			int x;
>> -			if (iField == XN_EXPR) {
>> -				pParse->ckBase = regNewData + 1;
>> -				sqlite3ExprCodeCopy(pParse,
>> -						    pIdx->aColExpr->a[i].pExpr,
>> -						    regIdx + i);
>> -				pParse->ckBase = 0;
>> -				VdbeComment((v, "%s column %d", pIdx->zName,
>> -					     i));
>> -			} else {
>> -				/* OP_SCopy copies value in separate register,
>> -				 * which later will be used by OP_NoConflict.
>> -				 * But OP_NoConflict is necessary only in cases
>> -				 * when bytecode is needed for proper UNIQUE
>> -				 * constraint handling.
>> -				 */
>> -				if (uniqueByteCodeNeeded) {
>> -					if (iField == pTab->iPKey)
>> -						x = regNewData;
>> -					else
>> -						x = iField + regNewData + 1;
>> -
>> -					assert(iField >= 0);
>> -					sqlite3VdbeAddOp2(v, OP_SCopy,
>> -							  x, regIdx + i);
>> -					VdbeComment((v, "%s",
>> -						     pTab->def->fields[
>> -							iField].name));
>> -				}
>> +			/* OP_SCopy copies value in separate register,
>> +			 * which later will be used by OP_NoConflict.
>> +			 * But OP_NoConflict is necessary only in cases
>> +			 * when bytecode is needed for proper UNIQUE
>> +			 * constraint handling.
>> +			 */
>> +			if (uniqueByteCodeNeeded) {
>> +				if (iField == pTab->iPKey)
>> +					x = regNewData;
>> +				else
>> +					x = iField + regNewData + 1;
>> +
>> +				assert(iField >= 0);
>> +				sqlite3VdbeAddOp2(v, OP_SCopy,
>> +						  x, regIdx + i);
>> +				VdbeComment((v, "%s",
>> +					     pTab->def->fields[iField].name));
>>  			}
>>  		}
>>  
>> @@ -1293,8 +1268,12 @@ sqlite3GenerateConstraintChecks(Parse * pParse,		/* The parser context */
>>  			/* 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]] ==
>> +				reg_pk = regNewData + 1 +
>> +					pIdx->def->key_def->parts[0].fieldno;
>> +
>> +				int fieldno = (int)pIdx->def->key_def->
>> +					parts[0].fieldno;
>> +				if (pTab->zColAff[fieldno] ==
>>  				    AFFINITY_INTEGER) {
>>  					int skip_if_null = sqlite3VdbeMakeLabel(v);
>>  					if ((pTab->tabFlags & TF_Autoincrement) != 0) {
>> @@ -1311,8 +1290,8 @@ sqlite3GenerateConstraintChecks(Parse * pParse,		/* The parser context */
>>  			}
>>  
>>  			sqlite3VdbeAddOp3(v, OP_MakeRecord, regNewData + 1,
>> -					pTab->def->field_count, aRegIdx[ix]);
>> -			VdbeComment((v, "for %s", pIdx->zName));
>> +					  pTab->def->field_count, aRegIdx[ix]);
>> +			VdbeComment((v, "for %s", pIdx->def->name));
>>  		}
>>  
>>  		/* In an UPDATE operation, if this index is the PRIMARY KEY
>> @@ -1400,7 +1379,7 @@ 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);
>>  
>> @@ -1410,19 +1389,17 @@ sqlite3GenerateConstraintChecks(Parse * pParse,		/* The parser context */
>>  								 nPkField);
>>  		if (isUpdate || on_error == ON_CONFLICT_ACTION_REPLACE) {
>>  			int x;
>> -			int nPkCol = index_column_count(pPk);
>> +			int nPkCol = pPk->def->key_def->part_count;
>>  			/* 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];
>> +					x = pPk->def->key_def->parts[i].fieldno;
>>  					sqlite3VdbeAddOp3(v, OP_Column,
>>  							  iThisCur, x, regR + i);
>>  					VdbeComment((v, "%s.%s", pTab->def->name,
>> -						pTab->def->fields[
>> -							pPk->aiColumn[i]].name));
>> +						pTab->def->fields[x].name));
>>  				}
>>  			}
>>  			if (isUpdate && uniqueByteCodeNeeded) {
>> @@ -1440,10 +1417,11 @@ sqlite3GenerateConstraintChecks(Parse * pParse,		/* The parser context */
>>  					      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);
>> +					char *p4 = (char *) pPk->def->key_def->parts[i].coll;
>> +					x = pPk->def->key_def->parts[i].fieldno;
>> +					if (pPk->tnum==0) {
>> +						x = -1;
>> +					}
8. Redundant {}
-                                       if (pPk->tnum==0) {
+                                       if (pPk->tnum == 0)
                                                x = -1;
-                                       }

>>  					if (i == (nPkCol - 1)) {
>>  						addrJump = addrUniqueOk;
>>  						op = OP_Eq;
>> @@ -1620,8 +1598,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 */
>> @@ -1639,7 +1617,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));
>>  			}
>>  		}
>>  	}
>> @@ -1676,35 +1654,23 @@ 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);
>> +	uint32_t nDestCol = pDest->def->key_def->part_count;
>> +	uint32_t nSrcCol = pSrc->def->key_def->part_count;
>>  	if (nDestCol != nSrcCol) {
>>  		return 0;	/* Different number of columns */
>>  	}
>>  	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 (i = 0; i < nSrcCol; i++, src_part++, dest_part++) {
>> +		if (src_part->fieldno != dest_part->fieldno)
>>  			return 0;	/* Different columns indexed */
>> -		}
>> -		if (pSrc->aiColumn[i] == XN_EXPR) {
>> -			assert(pSrc->aColExpr != 0 && pDest->aColExpr != 0);
>> -			if (sqlite3ExprCompare(pSrc->aColExpr->a[i].pExpr,
>> -					       pDest->aColExpr->a[i].pExpr,
>> -					       -1) != 0) {
>> -				return 0;	/* Different expressions in the index */
>> -			}
>> -		}
>> -		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 */
>> @@ -1876,16 +1842,14 @@ 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) {
>> +		if (pSrcIdx == 0)
>>  			return 0;	/* pDestIdx has no corresponding index in pSrc */
>> -		}
9. Please, like this:
-               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(
>> @@ -1960,11 +1924,11 @@ xferOptimization(Parse * pParse,	/* Parser context */
>>  		assert(pSrcIdx);
>>  		emit_open_cursor(pParse, iSrc, pSrcIdx->tnum);
>>  		sql_vdbe_set_p4_key_def(pParse, pSrcIdx);
>> -		VdbeComment((v, "%s", pSrcIdx->zName));
>> +		VdbeComment((v, "%s", pSrcIdx->def->name));
>>  		emit_open_cursor(pParse, iDest, pDestIdx->tnum);
>>  		sql_vdbe_set_p4_key_def(pParse, pDestIdx);
>>  		sqlite3VdbeChangeP5(v, OPFLAG_BULKCSR);
>> -		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);
>> diff --git a/src/box/sql/pragma.c b/src/box/sql/pragma.c
>> index 9dab5a7fd..45896811b 100644
>> --- a/src/box/sql/pragma.c
>> +++ b/src/box/sql/pragma.c
>> @@ -370,8 +370,11 @@ sqlite3Pragma(Parse * pParse, Token * pId,	/* First part of [schema.]id field */
>>  						for (k = 1;
>>  						     k <=
>>  						     (int)pTab->def->field_count
>> -						     && pPk->aiColumn[k - 1] !=
>> -						     i; k++) {
>> +						     && (int) pPk->def->
>> +							     key_def->
>> +							     parts[k - 1].
>> +							     fieldno != i;
>> +						     k++) {
>>  						}
>>  					}
>>  					bool nullable =
>> @@ -430,7 +433,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,
>> @@ -459,11 +462,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,
>> @@ -477,19 +482,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 <
>> @@ -519,10 +523,8 @@ sqlite3Pragma(Parse * pParse, Token * pId,	/* First part of [schema.]id field */
>>  						    { "c", "u", "pk" };
>>  						sqlite3VdbeMultiLoad(v, 1,
>>  								     "isisi", i,
>> -								     pIdx->
>> -								     zName,
>> -								     index_is_unique
>> -								     (pIdx),
>> +								     pIdx->def->name,
>> +								     pIdx->def->opts.is_unique,
>>  								     azOrigin
>>  								     [pIdx->
>>  								      idxType],
>> diff --git a/src/box/sql/select.c b/src/box/sql/select.c
>> index 2aa35a114..2646a99c3 100644
>> --- a/src/box/sql/select.c
>> +++ b/src/box/sql/select.c
>> @@ -4291,7 +4291,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 01351a183..36b46ed4f 100644
>> --- a/src/box/sql/sqliteInt.h
>> +++ b/src/box/sql/sqliteInt.h
>> @@ -2071,21 +2071,6 @@ struct UnpackedRecord {
>>   * 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.
>> @@ -2102,27 +2087,19 @@ 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 */
>>  	ExprList *aColExpr;	/* Column expressions */
>>  	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 */
10. Will you keep an informative tarantool-style comment here?

>> +	struct index_def *def;
>>  };
>>  
>>  /**
>> @@ -2161,11 +2138,6 @@ index_field_tuple_est(struct Index *idx, uint32_t field);
>>  #define IsUniqueIndex(X)      (((X)->idxType == SQLITE_IDXTYPE_UNIQUE) || \
>>  				((X)->idxType == SQLITE_IDXTYPE_PRIMARYKEY))
>>  
>> -/* The Index.aiColumn[] values are normally positive integer.  But
>> - * there are some negative values that have special meaning:
>> - */
>> -#define XN_EXPR      (-2)	/* Indexed column is an expression */
>> -
>>  #ifdef DEFAULT_TUPLE_COUNT
>>  #undef DEFAULT_TUPLE_COUNT
>>  #endif
>> @@ -3526,37 +3498,10 @@ 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);
>> +
>>  bool
>>  space_is_view(Table *);
>>  
>> -/**
>> - * 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 *);
>>  int
>>  emit_open_cursor(Parse *, int, int);
>> @@ -3607,8 +3552,6 @@ void sqlite3SrcListAssignCursors(Parse *, SrcList *);
>>  void sqlite3IdListDelete(sqlite3 *, IdList *);
>>  void sqlite3SrcListDelete(sqlite3 *, SrcList *);
>>  Index *sqlite3AllocateIndexObject(sqlite3 *, i16, int, char **);
>> -bool
>> -index_is_unique(Index *);
>>  
>>  /**
>>   * Create a new index for an SQL table.  name is the name of the
>> @@ -4293,8 +4236,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);
>> diff --git a/src/box/sql/trigger.c b/src/box/sql/trigger.c
>> index e1126b2d2..ea3521133 100644
>> --- a/src/box/sql/trigger.c
>> +++ b/src/box/sql/trigger.c
>> @@ -872,8 +872,6 @@ codeRowTrigger(Parse * pParse,	/* Current parse context */
>>  	pSubParse->pToplevel = pTop;
>>  	pSubParse->eTriggerOp = pTrigger->op;
>>  	pSubParse->nQueryLoop = pParse->nQueryLoop;
>> -	struct region *region = &fiber()->gc;
>> -	pSubParse->region_initial_size = region_used(region);
>>  
>>  	v = sqlite3GetVdbe(pSubParse);
>>  	if (v) {
>> diff --git a/src/box/sql/update.c b/src/box/sql/update.c
>> index 590aad28b..6545b3b06 100644
>> --- a/src/box/sql/update.c
>> +++ b/src/box/sql/update.c
>> @@ -237,14 +237,14 @@ 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);
>> +		int nIdxCol = pIdx->def->key_def->part_count;
>>  		if (chngPk || hasFK || pIdx->pPartIdxWhere || pIdx == pPk) {
>>  			reg = ++pParse->nMem;
>>  			pParse->nMem += nIdxCol;
>>  		} else {
>>  			reg = 0;
>>  			for (i = 0; i < nIdxCol; i++) {
>> -				i16 iIdxCol = pIdx->aiColumn[i];
>> +				i16 iIdxCol = pIdx->def->key_def->parts[i].fieldno;
>>  				if (iIdxCol < 0 || aXRef[iIdxCol] >= 0) {
>>  					reg = ++pParse->nMem;
>>  					pParse->nMem += nIdxCol;
>> @@ -306,7 +306,7 @@ sqlite3Update(Parse * pParse,		/* The parser context */
>>  		nPk = nKey;
>>  	} else {
>>  		assert(pPk != 0);
>> -		nPk = index_column_count(pPk);
>> +		nPk = pPk->def->key_def->part_count;
>>  	}
>>  	iPk = pParse->nMem + 1;
>>  	pParse->nMem += nPk;
>> @@ -333,9 +333,9 @@ sqlite3Update(Parse * pParse,		/* The parser context */
>>  		}
>>  	} else {
>>  		for (i = 0; i < nPk; i++) {
>> -			assert(pPk->aiColumn[i] >= 0);
>>  			sqlite3ExprCodeGetColumnOfTable(v, pTab->def, iDataCur,
>> -							pPk->aiColumn[i],
>> +							pPk->def->key_def->
>> +								parts[i].fieldno,
>>  							iPk + i);
>>  		}
>>  	}
>> diff --git a/src/box/sql/vdbeaux.c b/src/box/sql/vdbeaux.c
>> index 679bd0bc1..520b309d9 100644
>> --- a/src/box/sql/vdbeaux.c
>> +++ b/src/box/sql/vdbeaux.c
>> @@ -1165,7 +1165,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 f408b7701..51b5d516e 100644
>> --- a/src/box/sql/vdbemem.c
>> +++ b/src/box/sql/vdbemem.c
>> @@ -1087,7 +1087,7 @@ 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 nCol = pIdx->def->key_def->part_count;
>>  
>>  			nByte = sizeof(Mem) * nCol +
>>  				ROUND8(sizeof(UnpackedRecord));
>> @@ -1095,7 +1095,7 @@ valueNew(sqlite3 * db, struct ValueNewStat4Ctx *p)
>>  			    (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);
>> diff --git a/src/box/sql/where.c b/src/box/sql/where.c
>> index e79164781..9f5de50f9 100644
>> --- a/src/box/sql/where.c
>> +++ b/src/box/sql/where.c
>> @@ -265,11 +265,6 @@ whereScanNext(WhereScan * pScan)
>>  			for (pTerm = pWC->a + k; k < pWC->nTerm; k++, pTerm++) {
>>  				if (pTerm->leftCursor == iCur
>>  				    && pTerm->u.leftColumn == iColumn
>> -				    && (iColumn != XN_EXPR
>> -					|| sqlite3ExprCompare(pTerm->pExpr->
>> -							      pLeft,
>> -							      pScan->pIdxExpr,
>> -							      iCur) == 0)
>>  				    && (pScan->iEquiv <= 1
>>  					|| !ExprHasProperty(pTerm->pExpr,
>>  							    EP_FromJoin))
>> @@ -376,19 +371,21 @@ whereScanInit(WhereScan * pScan,	/* The WhereScan object being initialized */
>>  	pScan->is_column_seen = false;
>>  	if (pIdx) {
>>  		int j = iColumn;
>> -		iColumn = pIdx->aiColumn[j];
>> -		if (iColumn == XN_EXPR) {
>> -			pScan->pIdxExpr = pIdx->aColExpr->a[j].pExpr;
>> -		} else if (iColumn >= 0) {
>> +		iColumn = pIdx->def->key_def->parts[j].fieldno;
>> +		/*
>> +		 * pIdx->tnum == 0 means that pIdx is a fake integer
>> +		 * primary key index
>> +		 */
11. Out of comment-type margin
-                * pIdx->tnum == 0 means that pIdx is a fake integer
-                * primary key index
+                * pIdx->tnum == 0 means that pIdx is a fake
+                * integer primary key index.

>> +		if (pIdx->tnum == 0)
>> +			iColumn = -1;
>> +
>> +		if (iColumn >= 0) {
>>  			char affinity =
>>  				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 if (iColumn == XN_EXPR) {
>> -		return 0;
>>  	}
>>  	pScan->opMask = opMask;
>>  	pScan->k = 0;
>> @@ -464,18 +461,17 @@ 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;
>>  			}
12. Extra braces:
-                       if (is_found && coll == part_to_match->coll) {
+                       if (is_found && coll == part_to_match->coll)
                                return i;
-                       }

>>  		}
>> @@ -484,27 +480,6 @@ findIndexCol(Parse * pParse,	/* Parse context */
>>  	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.
>> @@ -556,9 +531,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,
>> @@ -566,11 +541,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 == 0)
13. !.. == 0 is looking confusing for me.. Please, fix it.

>>  					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;
>>  		}
>> @@ -1107,7 +1083,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;
>> @@ -1169,13 +1145,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 *pColl = p->def->key_def->parts[nEq].coll;
>>  	if (pLower) {
>>  		rc = sqlite3Stat4ValueFromExpr(pParse, pLower->pExpr->pRight,
>>  					       aff, &p1);
>> @@ -1371,7 +1346,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);
>> @@ -1521,7 +1496,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
>> @@ -1542,7 +1517,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;
>> @@ -1674,7 +1649,7 @@ 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 (p->pIndex && (zName = p->pIndex->def->name) != 0) {
>>  		if (strncmp(zName, "sqlite_autoindex_", 17) == 0) {
>>  			int i = sqlite3Strlen30(zName) - 1;
>>  			while (zName[i] != '_')
>> @@ -2236,7 +2211,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.
>> @@ -2257,11 +2232,10 @@ 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)) {
>> +		if (pLhs->op != TK_COLUMN || pLhs->iTable != iCur
>> +		    || pLhs->iColumn != (int)pIdx->def->key_def->parts[i + nEq].fieldno
>> +		    || pIdx->def->key_def->parts[i + nEq].sort_order !=
>> +		       pIdx->def->key_def->parts[nEq].sort_order) {
>>  			break;
>>  		}
14. Extra braces:
-                      pIdx->def->key_def->parts[nEq].sort_order) {
+                      pIdx->def->key_def->parts[nEq].sort_order)
                        break;
-               }

>>  
>> @@ -2275,7 +2249,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;
>> @@ -2318,13 +2292,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 nProbeCol = 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) {
>> @@ -2374,8 +2348,9 @@ whereLoopAddBtreeIndex(WhereLoopBuilder * pBuilder,	/* The WhereLoop factory */
>>  		LogEst nOutUnadjusted;	/* nOut before IN() and WHERE adjustments */
>>  		int nIn = 0;
>>  		int nRecValid = pBuilder->nRecValid;
>> +		uint32_t j = pProbe->def->key_def->parts[saved_nEq].fieldno;
>>  		if ((eOp == WO_ISNULL || (pTerm->wtFlags & TERM_VNULL) != 0)
>> -		    && indexColumnNotNull(pProbe, saved_nEq)
>> +		    && !pProbe->pTable->def->fields[j].is_nullable
>>  		    ) {
>>  			continue;	/* ignore IS [NOT] NULL constraints on NOT NULL columns */
>>  		}
>> @@ -2445,14 +2420,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 == nProbeCol - 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;
>> @@ -2514,8 +2491,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;
>> @@ -2671,7 +2647,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;
>>  }
>>  
>> @@ -2715,7 +2691,7 @@ indexMightHelpWithOrderBy(WhereLoopBuilder * pBuilder,
>>  	ExprList *pOB;
>>  	ExprList *aColExpr;
>>  	int ii, jj;
>> -	int nIdxCol = index_column_count(pIndex);
>> +	int nIdxCol = pIndex->def->key_def->part_count;
>>  	if (index_is_unordered(pIndex))
>>  		return 0;
>>  	if ((pOB = pBuilder->pWInfo->pOrderBy) == 0)
>> @@ -2726,13 +2702,12 @@ indexMightHelpWithOrderBy(WhereLoopBuilder * pBuilder,
>>  			if (pExpr->iColumn < 0)
>>  				return 1;
>>  			for (jj = 0; jj < nIdxCol; jj++) {
>> -				if (pExpr->iColumn == pIndex->aiColumn[jj])
>> +				if (pExpr->iColumn == (int)
>> +				    pIndex->def->key_def->parts[jj].fieldno)
>>  					return 1;
>>  			}
>>  		} else if ((aColExpr = pIndex->aColExpr) != 0) {
>>  			for (jj = 0; jj < nIdxCol; jj++) {
>> -				if (pIndex->aiColumn[jj] != XN_EXPR)
>> -					continue;
>>  				if (sqlite3ExprCompare
>>  				    (pExpr, aColExpr->a[jj].pExpr,
>>  				     iCursor) == 0) {
>> @@ -2815,7 +2790,6 @@ whereLoopAddBtree(WhereLoopBuilder * pBuilder,	/* WHERE clause information */
>>  	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 */
>>  	SrcList *pTabList;	/* The FROM clause */
>>  	struct SrcList_item *pSrc;	/* The FROM clause btree term to add */
>>  	WhereLoop *pNew;	/* Template WhereLoop object */
>> @@ -2846,11 +2820,27 @@ whereLoopAddBtree(WhereLoopBuilder * pBuilder,	/* WHERE clause information */
>>  		 */
>>  		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;
>> +
>> +		struct key_def *key_def = key_def_new(1);
>> +		if (key_def == NULL)
>> +			return SQLITE_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 index_opts = index_opts_default;
>> +
>> +		sPk.def = index_def_new(pTab->def->id, 0, "primary",
>> +					sizeof("primary") - 1, TREE, &index_opts,
>> +					key_def, NULL);
>> +
>> +		if (sPk.def == NULL)
>> +			return SQLITE_ERROR;
15. key_def is leaking here, same as in other errors bellow and at the end of the function.

>> +
>>  		aiRowEstPk[0] = sql_space_tuple_log_count(pTab);
>>  		aiRowEstPk[1] = 0;
>>  		pFirst = pSrc->pTab->pIndex;
>> @@ -3325,8 +3315,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
>> @@ -3387,9 +3377,10 @@ wherePathSatisfiesOrderBy(WhereInfo * pWInfo,	/* The WHERE clause */
>>  				 * (revIdx) for the j-th column of the index.
>>  				 */
>>  				if (pIndex) {
>> -					iColumn = pIndex->aiColumn[j];
>> -					revIdx = sql_index_column_sort_order(pIndex,
>> -									     j);
>> +					iColumn = pIndex->def->key_def->
>> +						parts[j].fieldno;
>> +					revIdx = pIndex->def->key_def->
>> +						parts[j].sort_order;
>>  					if (iColumn == pIndex->pTable->iPKey)
>>  						iColumn = -1;
>>  				} else {
>> @@ -3442,8 +3433,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;
>> @@ -4105,13 +4095,13 @@ whereShortCut(WhereLoopBuilder * pBuilder)
>>  	} else {
>>  		for (pIdx = pTab->pIndex; pIdx; pIdx = pIdx->pNext) {
>>  			int opMask;
>> -			int nIdxCol = index_column_count(pIdx);
>> +			int nIdxCol = pIdx->def->key_def->part_count;
>>  			assert(pLoop->aLTermSpace == pLoop->aLTerm);
>> -			if (!index_is_unique(pIdx)
>> +			if (!pIdx->def->opts.is_unique
>>  			    || pIdx->pPartIdxWhere != 0
>> -			    || nIdxCol > ArraySize(pLoop->aLTermSpace)
>> -			    )
>> +			    || nIdxCol > ArraySize(pLoop->aLTermSpace)) {
>>  				continue;
>> +			}
>>  			opMask = WO_EQ;
>>  			for (j = 0; j < nIdxCol; j++) {
>>  				pTerm =
>> @@ -4650,7 +4640,7 @@ sqlite3WhereBegin(Parse * pParse,	/* The parser context */
>>  					wctrlFlags & WHERE_ORDERBY_MIN) == 0) {
>>  					sqlite3VdbeChangeP5(v, OPFLAG_SEEKEQ);	/* Hint to COMDB2 */
>>  				}
>> -				VdbeComment((v, "%s", pIx->zName));
>> +				VdbeComment((v, "%s", pIx->def->name));
>>  #ifdef SQLITE_ENABLE_COLUMN_USED_MASK
>>  				{
>>  					u64 colUsed = 0;
>> @@ -4781,7 +4771,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 09b267194..22bb76013 100644
>> --- a/src/box/sql/wherecode.c
>> +++ b/src/box/sql/wherecode.c
>> @@ -47,9 +47,7 @@
>>  static const char *
>>  explainIndexColumnName(Index * pIdx, int i)
>>  {
>> -	i = pIdx->aiColumn[i];
>> -	if (i == XN_EXPR)
>> -		return "<expr>";
>> +	i = pIdx->def->key_def->parts[i].fieldno;
>>  	return pIdx->pTable->def->fields[i].name;
>>  }
>>  
>> @@ -222,7 +220,7 @@ sqlite3WhereExplainOneScan(Parse * pParse,	/* Parse context */
>>  			}
>>  			if (zFmt) {
>>  				sqlite3StrAccumAppend(&str, " USING ", 7);
>> -				sqlite3XPrintf(&str, zFmt, pIdx->zName);
>> +				sqlite3XPrintf(&str, zFmt, pIdx->def->name);
>>  				explainIndexRange(&str, pLoop);
>>  			}
>>  		} else if ((flags & WHERE_IPK) != 0
>> @@ -463,7 +461,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;
>> @@ -708,7 +706,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),
>> @@ -718,8 +716,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);
>> -			testcase(pIdx->aiColumn[j] == XN_EXPR);
>> +					  pIdx->def->key_def->parts[j].fieldno,
>> +					  regBase + j);
>>  			VdbeComment((v, "%s", explainIndexColumnName(pIdx, j)));
>>  		}
>>  	}
>> @@ -1245,10 +1243,10 @@ sqlite3WhereCodeOneLoopStart(WhereInfo * pWInfo,	/* Complete information about t
>>  		assert(pWInfo->pOrderBy == 0
>>  		       || pWInfo->pOrderBy->nExpr == 1
>>  		       || (pWInfo->wctrlFlags & WHERE_ORDERBY_MIN) == 0);
>> -		int nIdxCol = index_column_count(pIdx);
>> +		int nIdxCol = pIdx->def->key_def->part_count;
>>  		if ((pWInfo->wctrlFlags & WHERE_ORDERBY_MIN) != 0
>>  		    && pWInfo->nOBSat > 0 && (nIdxCol > nEq)) {
>> -			j = pIdx->aiColumn[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.
>> @@ -1259,8 +1257,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)
>> -			    || j == XN_EXPR) {
>> +			if (pIdx->pTable->def->fields[j].is_nullable) {
>>  				assert(pLoop->nSkip == 0);
>>  				bSeekPastNull = 1;
>>  				nExtraReg = 1;
>> @@ -1299,17 +1296,15 @@ sqlite3WhereCodeOneLoopStart(WhereInfo * pWInfo,	/* Complete information about t
>>  				assert((bRev & ~1) == 0);
>>  				pLevel->iLikeRepCntr <<= 1;
>>  				pLevel->iLikeRepCntr |=
>> -					bRev ^ (sql_index_column_sort_order(pIdx, nEq) ==
>> +					bRev ^ (pIdx->def->key_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 == XN_EXPR) {
>> +				j = pIdx->def->key_def->parts[nEq].fieldno;
>> +				if (pIdx->pTable->def->fields[j].is_nullable)
>>  					bSeekPastNull = 1;
>> -				}
>>  			}
>>  		}
>>  		assert(pRangeEnd == 0
>> @@ -1320,7 +1315,7 @@ sqlite3WhereCodeOneLoopStart(WhereInfo * pWInfo,	/* Complete information about t
>>  		 * start and end terms (pRangeStart and pRangeEnd).
>>  		 */
>>  		if ((nEq < nIdxCol &&
>> -		     bRev == (sql_index_column_sort_order(pIdx, nEq) ==
>> +		     bRev == (pIdx->def->key_def->parts[nEq].sort_order ==
>>  			      SORT_ORDER_ASC)) ||
>>  		    (bRev && nIdxCol == nEq)) {
>>  			SWAP(pRangeEnd, pRangeStart);
>> @@ -1386,9 +1381,10 @@ sqlite3WhereCodeOneLoopStart(WhereInfo * pWInfo,	/* Complete information about t
>>  		}
>>  		struct Index *pk = sqlite3PrimaryKeyIndex(pIdx->pTable);
>>  		assert(pk);
>> -		int nPkCol = index_column_count(pk);
>> +		int nPkCol = pk->def->key_def->part_count;
>> +		uint32_t zero_fieldno = pk->def->key_def->parts[0].fieldno;
>>  		char affinity =
>> -			pIdx->pTable->def->fields[pk->aiColumn[0]].affinity;
>> +			pIdx->pTable->def->fields[zero_fieldno].affinity;
>>  		if (nPkCol == 1 && affinity == AFFINITY_INTEGER) {
>>  			/* Right now INTEGER PRIMARY KEY is the only option to
>>  			 * get Tarantool's INTEGER column type. Need special handling
>> @@ -1397,7 +1393,8 @@ sqlite3WhereCodeOneLoopStart(WhereInfo * pWInfo,	/* Complete information about t
>>  			 */
>>  			int limit = pRangeStart == NULL ? nEq : nEq + 1;
>>  			for (int i = 0; i < limit; i++) {
>> -				if (pIdx->aiColumn[i] == pk->aiColumn[0]) {
>> +				if (pIdx->def->key_def->parts[i].fieldno ==
>> +				    zero_fieldno) {
>>  					/* Here: we know for sure that table has INTEGER
>>  					   PRIMARY KEY, single column, and Index we're
>>  					   trying to use for scan contains this column. */
>> @@ -1506,10 +1503,10 @@ 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 nPkCol = pPk->def->key_def->part_count;
>>  			int iKeyReg = sqlite3GetTempRange(pParse, nPkCol);
>>  			for (j = 0; j < nPkCol; j++) {
>> -				k = pPk->aiColumn[j];
>> +				k = pPk->def->key_def->parts[j].fieldno;
>>  				sqlite3VdbeAddOp3(v, OP_Column, iIdxCur, k,
>>  						  iKeyReg + j);
>>  			}
>> @@ -1614,7 +1611,7 @@ 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 nPkCol = pPk->def->key_def->part_count;
>>  			regRowset = pParse->nTab++;
>>  			sqlite3VdbeAddOp2(v, OP_OpenTEphemeral,
>>  					  regRowset, nPkCol);
>> @@ -1718,13 +1715,16 @@ 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 nPk = pPk->def->key_def->part_count;
>>  						int iPk;
>>  
>>  						/* Read the PK into an array of temp registers. */
>>  						r = sqlite3GetTempRange(pParse, nPk);
>>  						for (iPk = 0; iPk < nPk; iPk++) {
>> -							int iCol = pPk->aiColumn[iPk];
>> +							int iCol = pPk->def->
>> +								key_def->
>> +								parts[iPk].
>> +								fieldno;
>>  							sqlite3ExprCodeGetColumnToReg
>>  								(pParse, pTab->def,
>>  								 iCol, iCur,
>> diff --git a/src/box/sql/whereexpr.c b/src/box/sql/whereexpr.c
>> index aa6d4524d..40e4e2577 100644
>> --- a/src/box/sql/whereexpr.c
>> +++ b/src/box/sql/whereexpr.c
>> @@ -903,7 +903,6 @@ exprMightBeIndexed(SrcList * pFrom,	/* The FROM clause */
>>  		   int *piColumn	/* Write the referenced table column number here */
>>      )
>>  {
>> -	Index *pIdx;
>>  	int i;
>>  	int iCur;
>>  
>> @@ -930,20 +929,6 @@ exprMightBeIndexed(SrcList * pFrom,	/* The FROM clause */
>>  	for (i = 0; mPrereq > 1; i++, mPrereq >>= 1) {
>>  	}
>>  	iCur = pFrom->a[i].iCursor;
>> -	for (pIdx = pFrom->a[i].pTab->pIndex; pIdx; pIdx = pIdx->pNext) {
>> -		if (pIdx->aColExpr == 0)
>> -			continue;
>> -		for (i = 0; i < pIdx->nColumn; i++) {
>> -			if (pIdx->aiColumn[i] != XN_EXPR)
>> -				continue;
>> -			if (sqlite3ExprCompare
>> -			    (pExpr, pIdx->aColExpr->a[i].pExpr, iCur) == 0) {
>> -				*piCur = iCur;
>> -				*piColumn = XN_EXPR;
>> -				return 1;
>> -			}
>> -		}
>> -	}
16. You don't need iCur anymore; please delete it and SrcList * pFrom argument that would come useless.
Please don't dorget to beautify function head to match our codestyle as you are going to touch it.

>>  	return 0;
>>  }
>>  
>> -- 

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

* [tarantool-patches] Re: [PATCH v4] sql: add index_def to struct Index
  2018-06-18 18:45 ` Kirill Shcherbatov
@ 2018-06-21 12:57   ` Ivan Koptelov
  2018-06-22  8:46     ` Kirill Shcherbatov
  0 siblings, 1 reply; 23+ messages in thread
From: Ivan Koptelov @ 2018-06-21 12:57 UTC (permalink / raw)
  To: tarantool-patches; +Cc: Kirill Shcherbatov

Thank you for the review. Sorry for that problem with smashed mail. I 
fixed my mail client, now everything should be ok.

See my patch at the end of the mail.

> Thank you for the patch. Glad to see indexes so much simplified.
> Would you kindly use post program next time? The message was smashed and this made me cry a bit ;)
> Please, fix following 16 comments (2 memory problems and 14 minor codestyle fixes):
>
>>> Branch:https://github.com/tarantool/tarantool/tree/sb/gh-3369-use-index-def-in-select-and-where
>>> Issue:https://github.com/tarantool/tarantool/issues/3369
>>>
>>>   src/box/sql.c           |  54 +++---
>>>   src/box/sql/analyze.c   |  40 ++--
>>>   src/box/sql/build.c     | 488 +++++++++++++++++++++++-------------------------
>>>   src/box/sql/delete.c    |  17 +-
>>>   src/box/sql/expr.c      |  60 +++---
>>>   src/box/sql/fkey.c      |  47 ++---
>>>   src/box/sql/insert.c    | 148 ++++++---------
>>>   src/box/sql/pragma.c    |  32 ++--
>>>   src/box/sql/select.c    |   2 +-
>>>   src/box/sql/sqliteInt.h |  63 +------
>>>   src/box/sql/trigger.c   |   2 -
>>>   src/box/sql/update.c    |  10 +-
>>>   src/box/sql/vdbeaux.c   |   2 +-
>>>   src/box/sql/vdbemem.c   |   4 +-
>>>   src/box/sql/where.c     | 168 ++++++++---------
>>>   src/box/sql/wherecode.c |  54 +++---
>>>   src/box/sql/whereexpr.c |  15 --
>>>   17 files changed, 532 insertions(+), 674 deletions(-)
>>>
>>> diff --git a/src/box/sql.c b/src/box/sql.c
>>> index 7379cb418..213f8e453 100644
>>> --- a/src/box/sql.c
>>> +++ b/src/box/sql.c
>>> @@ -1442,8 +1442,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;
>>>   	}
>>> @@ -1552,20 +1552,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;
>>>   	}
>>>   
>>> @@ -1575,46 +1574,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 afc824a1a..31de7ab05 100644
>>> --- a/src/box/sql/analyze.c
>>> +++ b/src/box/sql/analyze.c
>>> @@ -849,7 +849,6 @@ analyzeOneTable(Parse * pParse,	/* Parser context */
>>>   		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 */
>>>   
>>>   		if (pOnlyIdx && pOnlyIdx != pIdx)
>>>   			continue;
>>> @@ -860,9 +859,9 @@ analyzeOneTable(Parse * pParse,	/* Parser context */
>>>   		if (IsPrimaryKeyIndex(pIdx)) {
>>>   			zIdxName = pTab->def->name;
>>>   		} else {
>>> -			zIdxName = pIdx->zName;
>>> +			zIdxName = pIdx->def->name;
>>>   		}
>>> -		nColTest = index_column_count(pIdx);
>>> +		int nColTest = pIdx->def->key_def->part_count;
>>>   
>>>   		/* Populate the register containing the index name. */
>>>   		sqlite3VdbeLoadString(v, regIdxname, zIdxName);
>>> @@ -917,7 +916,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:
>>>   		 *
>>> @@ -969,7 +968,7 @@ analyzeOneTable(Parse * pParse,	/* Parser context */
>>>   			 */
>>>   			sqlite3VdbeAddOp0(v, OP_Goto);
>>>   			addrNextRow = sqlite3VdbeCurrentAddr(v);
>>> -			if (nColTest == 1 && index_is_unique(pIdx)) {
>>> +			if (nColTest == 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 +977,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 < nColTest; ++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,
>>> @@ -1006,7 +1004,8 @@ analyzeOneTable(Parse * pParse,	/* Parser context */
>>>   			for (i = 0; i < nColTest; i++) {
>>>   				sqlite3VdbeJumpHere(v, aGotoChng[i]);
>>>   				sqlite3VdbeAddOp3(v, OP_Column, iIdxCur,
>>> -						  pIdx->aiColumn[i],
>>> +						  pIdx->def->key_def->
>>> +							  parts[i].fieldno,
>>>   						  regPrev + i);
>>>   			}
>>>   			sqlite3VdbeResolveLabel(v, endDistinctTest);
>>> @@ -1022,15 +1021,14 @@ 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));
>>> +		int nPkColumn = (int) pPk->def->key_def->part_count;
>>> +		int regKeyStat = sqlite3GetTempRange(pParse, nPkColumn);
>>> +		for (int j = 0; j < nPkColumn; ++j) {
>>> +			int k = pPk->def->key_def->parts[j].fieldno;
>>> +			assert(k >= 0 && k < (int) 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);
>>> @@ -1150,7 +1148,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");
>>>   	}
>>> diff --git a/src/box/sql/build.c b/src/box/sql/build.c
>>> index 62d687b17..f18727c61 100644
>>> --- a/src/box/sql/build.c
>>> +++ b/src/box/sql/build.c
>>> @@ -253,6 +253,8 @@ freeIndex(sqlite3 * db, Index * p)
>>>   {
>>>   	sql_expr_delete(db, p->pPartIdxWhere, false);
>>>   	sql_expr_list_delete(db, p->aColExpr);
>>> +	if (p->def != NULL)
>>> +		index_def_delete(p->def);
>>>   	sqlite3DbFree(db, p->zColAff);
>>>   	sqlite3DbFree(db, p);
>>>   }
>>> @@ -271,7 +273,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;
>>> @@ -388,7 +391,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);
>>> @@ -1058,7 +1061,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)
>>> @@ -1066,22 +1069,20 @@ 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; pIdx = pIdx->pNext) {
>>> +			assert(pIdx->def->key_def->part_count == 1);
>>> +			if (pIdx->def->key_def->parts[0].fieldno == i) {
>>> +				pIdx->def->key_def->parts[0].coll_id = *id;
>>> +				pIdx->def->key_def->parts[0].coll =
>>>   					sql_column_collation(p->def, i, id);
>>>   			}
>>>   		}
>>> -	} else {
>>> -		sqlite3DbFree(db, zColl);
>>>   	}
>>> +	sqlite3DbFree(db, zColl);
>>>   }
>>>   
>>>   struct coll *
>>> @@ -1111,66 +1112,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;
>>> -}
>>> -
>>>   /**
>>>    * Return true if space which corresponds to
>>>    * given table has view option.
>>> @@ -1383,14 +1324,16 @@ 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)
>>> +hasColumn(const struct key_part *key_parts, int nCol, uint32_t fieldno)
>>>   {
>>> -	while (nCol-- > 0)
>>> -		if (x == *(aiCol++))
>>> +	int i = 0;
>>> +	while (i < nCol) {
>>> +		if (fieldno == key_parts->fieldno)
>>>   			return 1;
>>> +		key_parts++;
>>> +		i++;
>>> +	}
>>>   	return 0;
>>>   }
>>>   
>>> @@ -1410,13 +1353,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;
>>> @@ -1454,14 +1396,28 @@ convertToWithoutRowidTable(Parse * pParse, Table * pTab)
>>>   		 * "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];
>>> +
>>> +		struct key_part *parts = pPk->def->key_def->parts;
>>> +		uint32_t part_count = pPk->def->key_def->part_count;
>>> +		uint32_t new_part_count = part_count;
>>> +
>>> +		for (uint32_t i = 1; i < part_count; i++) {
>>> +			if (hasColumn(parts, i, parts[i].fieldno)){
>>> +				new_part_count--;
>>> +				bool is_found = false;
>>> +				for (uint32_t j = i + 1; j < part_count; j++){
>>> +					if (!(hasColumn(parts, j,
>>> +							parts[j].fieldno))) {
>>> +						parts[i] = parts[j];
>>> +						is_found = true;
>>> +						break;
>>> +					}
>>> +				}
>>> +				if (!(is_found))
>>> +					break;
>>>   			}
>>>   		}
>>> -		pPk->nColumn = j;
>>> +		pPk->def->key_def->part_count = new_part_count;
>>>   	}
>>>   	assert(pPk != 0);
>>>   }
>>> @@ -1543,7 +1499,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);
>>> @@ -1580,7 +1536,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) {
>>> @@ -2652,14 +2608,15 @@ 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,
>>> +	sqlite3VdbeAddOp4(v, OP_SorterOpen, iSorter, 0,
>>> +			  pIndex->def->key_def->part_count,
>>>   			  (char *)def, P4_KEYDEF);
> 1. Maybe a little better like this?
> -                         pIndex->def->key_def->part_count,
> -                         (char *)def, P4_KEYDEF);
> +                         pIndex->def->key_def->part_count, (char *)def,
> +                         P4_KEYDEF);
Ok, fixed.
>>>   
>>>   	/* Open the table. Loop through all rows of the table, inserting index
>>> @@ -2692,7 +2649,7 @@ 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);
> 2. Out of margin
> -                                    regRecord, pIndex->def->key_def->part_count);
> +                                    regRecord,
> +                                    pIndex->def->key_def->part_count);
Fixed.
>>>   		VdbeCoverage(v);
>>>   		sqlite3UniqueConstraint(pParse, ON_CONFLICT_ACTION_ABORT,
>>>   					pIndex);
>>> @@ -2730,24 +2687,13 @@ sqlite3AllocateIndexObject(sqlite3 * db,	/* Database connection */
>>>   	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  */
>>> +	    ROUND8(sizeof(LogEst) * (nCol + 1));    /* Index.aiRowLogEst */
>>>   	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;
>>>   	}
>>>   	return p;
>>> @@ -2836,18 +2782,133 @@ addIndexToTable(Index * pIndex, Table * pTab)
>>>   	}
>>>   }
>>>   
>>> -bool
>>> -index_is_unique(Index *idx)
>>> +static inline void
>>> +append_string_part(struct region *r, const char *str,
>>> +		  size_t *total_sql_size, Parse *parse)
>>>   {
>>> -	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);
>>> +	char * str_part = region_alloc(r, strlen(str));
>>> +	if (str_part == NULL){
>>> +		diag_set(OutOfMemory, strlen(str),
>>> +			 "region_alloc", "str_part");
>>> +		parse->rc = SQL_TARANTOOL_ERROR;
>>> +		parse->nErr++;
>>> +	}
>>> +	memcpy(str_part, str, strlen(str));
>>> +	*total_sql_size += strlen(str);
>>> +}
>>> +
>>> +void static
> 3. GCC doesn't accept such specifiers order in default build.
> Please change this to "static void".
> You also better look to the Travis times to times.
Ok, sorry, fixed.
>>> +set_index_def(Parse *parse, Index *index, Table *table, uint32_t iid,
>>> +	      const char *name, uint32_t name_len, int on_error,
>>> +	      struct ExprList *expr_list, u8 idx_type)
>>> +{
>>> +	struct space_def *space_def = table->def;
>>> +	struct index_opts opts;
>>> +	index_opts_create(&opts);
>>> +	opts.is_unique = on_error != ON_CONFLICT_ACTION_NONE;
>>> +
>>> +	struct key_def *key_def = key_def_new(expr_list->nExpr);
>>> +	if (key_def == NULL) {
>>> +		parse->rc = SQL_TARANTOOL_ERROR;
>>> +		parse->nErr++;
>>> +		return;
>>> +	}
>>> +
>>> +	/*
>>> +	 * Build initial parts of SQL statement.
>>> +	 */
>>> +
>>> +	struct region *r = &fiber()->gc;
> 4. Please, rebase to the master. There would be parser region that should be used here, I believe.
>
Ok, removed with  parser region
>>> +	size_t total_sql_size = 0;
>>> +
>>> +	if (idx_type == SQLITE_IDXTYPE_APPDEF) {
>>> +		append_string_part(r, "CREATE INDEX ", &total_sql_size,
>>> +				   parse);
>>> +		append_string_part(r, name, &total_sql_size, parse);
>>> +		append_string_part(r, " ON ", &total_sql_size, parse);
>>> +		append_string_part(r, space_def->name, &total_sql_size,
>>> +				   parse);
>>> +		append_string_part(r, " (", &total_sql_size, parse);
>>> +	}
>>> +
>>> +	for (int i = 0; i < expr_list->nExpr; i++) {
>>> +		Expr *expr = expr_list->a[i].pExpr;
>>> +		sql_resolve_self_reference(parse, table, NC_IdxExpr, expr, 0);
>>> +		if (parse->nErr > 0)
>>> +			return;
> 5. If I not mistaken, key_def is leaking here, with other returns and at the end of the function.
You are right. Now fixed.
>>> +
>>> +		Expr *column_expr = sqlite3ExprSkipCollate(expr);
>>> +		if (column_expr->op != TK_COLUMN) {
>>> +			sqlite3ErrorMsg(parse,
>>> +					"functional indexes aren't supported "
>>> +					"in the current version");
>>> +			return;
>>> +		}
>>> +
>>> +		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 (idx_type == SQLITE_IDXTYPE_APPDEF) {
>>> +				append_string_part(r, name,
>>> +						   &total_sql_size, parse);
>>> +				append_string_part(r, " COLLATE ",
>>> +						   &total_sql_size, parse);
>>> +				const char *coll_name = expr->u.zToken;
>>> +				append_string_part(r, coll_name,
>>> +						   &total_sql_size, parse);
>>> +				append_string_part(r, ", ",
>>> +						   &total_sql_size, parse);
>>> +			}
>>> +		} else {
>>> +			coll = sql_column_collation(space_def, fieldno,
>>> +						    &coll_id);
>>> +			if (idx_type == SQLITE_IDXTYPE_APPDEF) {
>>> +				append_string_part(r, name,
>>> +						   &total_sql_size, parse);
>>> +				append_string_part(r, ", ",
>>> +						   &total_sql_size, parse);
>>> +			}
>>> +		}
>>> +
>>> +		/*
>>> +		* Tarantool: DESC indexes are not supported so far.
>>> +		* See gh-3016.
>>> +		*/
>>> +		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);
>>> +	}
>>>   
>>> -	return tnt_index->def->opts.is_unique;
>>> +	if (parse->nErr > 0) {
>>> +		index->def = NULL;
>>> +		return;
>>> +	}
>>> +
>>> +	if (idx_type == SQLITE_IDXTYPE_APPDEF) {
>>> +		memcpy(region_alloc(r, 1), "\0", 1);
>>> +		total_sql_size += 1;
>>> +		opts.sql = region_join(r, total_sql_size);
>>> +
>>> +		/*
>>> +		 * fix last ", " with ")\0" to finish the statement.
>>> +		 */
>>> +		opts.sql[total_sql_size - 3] = ')';
>>> +		opts.sql[total_sql_size - 2] = '\0';
>>> +	}
>>> +
>>> +	struct key_def *pk_key_def;
>>> +	if (idx_type == SQLITE_IDXTYPE_APPDEF)
>>> +		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);
>>>   }
>>>   
>>>   void
>>> @@ -2856,16 +2917,14 @@ sql_create_index(struct Parse *parse, struct Token *token,
>>>   		 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;
>>> +	Table *pTab = NULL;	/* Table to be indexed */
>>> +	Index *pIndex = NULL;	/* The index to be created */
>>> +	char *name = NULL;	/* Name of the index */
>>> +	int name_len;		/* Number of characters in zName */
>>>   	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 */
>>> +	char *zExtra = NULL;	/* Extra space after the Index object */
>>>   	struct session *user_session = current_session();
>>>   
>>>   	if (db->mallocFailed || parse->nErr > 0) {
>>> @@ -2939,24 +2998,24 @@ sql_create_index(struct Parse *parse, struct Token *token,
>>>   	 * our own name.
>>>   	 */
>>>   	if (token) {
>>> -		zName = sqlite3NameFromToken(db, token);
>>> -		if (zName == 0)
>>> +		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) !=
>>> +			if (sqlite3HashFind(&db->pSchema->tblHash, name) !=
>>>   			    NULL) {
>>>   				sqlite3ErrorMsg(parse,
>>>   						"there is already a table named %s",
>>> -						zName);
>>> +						name);
>>>   				goto exit_create_index;
>>>   			}
>>>   		}
>>> -		if (sqlite3HashFind(&pTab->idxHash, zName) != NULL) {
>>> +		if (sqlite3HashFind(&pTab->idxHash, name) != NULL) {
>>>   			if (!if_not_exist) {
>>>   				sqlite3ErrorMsg(parse,
>>>   						"index %s.%s already exists",
>>> -						pTab->def->name, zName);
>>> +						pTab->def->name, name);
>>>   			} else {
>>>   				assert(!db->init.busy);
>>>   			}
>>> @@ -2968,10 +3027,9 @@ sql_create_index(struct Parse *parse, struct Token *token,
>>>   		for (pLoop = pTab->pIndex, n = 1; pLoop;
>>>   		     pLoop = pLoop->pNext, n++) {
>>>   		}
>>> -		zName =
>>> -		    sqlite3MPrintf(db, "sqlite_autoindex_%s_%d", pTab->def->name,
>>> -				   n);
>>> -		if (zName == 0) {
>>> +		name = sqlite3MPrintf(db, "sqlite_autoindex_%s_%d",
>>> +				      pTab->def->name, n);
>>> +		if (name == NULL) {
>>>   			goto exit_create_index;
>>>   		}
>>>   	}
>>> @@ -2997,31 +3055,27 @@ 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));
>>> -		}
>>> -	}
>>> -
>>>   	/*
>>>   	 * Allocate the index structure.
>>>   	 */
>>> -	nName = sqlite3Strlen30(zName);
>>> +	name_len = sqlite3Strlen30(name);
>>> +
>>> +	if (name_len > BOX_NAME_MAX) {
>>> +		sqlite3ErrorMsg(parse,
>>> +				"%s.%s exceeds indexes' names length limit",
>>> +				pTab->def->name, name);
>>> +		goto exit_create_index;
>>> +	}
>>> +
>>> +	if (sqlite3CheckIdentifierName(parse, name) != SQLITE_OK)
>>> +		goto exit_create_index;
>>> +
>>>   	pIndex = sqlite3AllocateIndexObject(db, col_list->nExpr,
>>> -					    nName + nExtra + 1, &zExtra);
>>> +					    name_len + nExtra + 1, &zExtra);
>>>   	if (db->mallocFailed) {
>>>   		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;
>>>   	/*
>>> @@ -3036,7 +3090,6 @@ sql_create_index(struct Parse *parse, struct Token *token,
>>>   		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,
>>> @@ -3045,60 +3098,27 @@ sql_create_index(struct Parse *parse, struct Token *token,
>>>   		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[].  For general expressions,
>>> -	 * populate pIndex->aColExpr and store XN_EXPR (-2) 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.
>>>   	 */
>>> -	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");
>>> -			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;
>>> -		}
>>> -		pIndex->coll_array[i] = coll;
>>> -		pIndex->coll_id_array[i] = id;
>>> +	uint32_t max_iid = 0;
>>> +	for (Index *index = pTab->pIndex; index; index = index->pNext) {
>>> +		max_iid = max_iid > index->def->iid ?
>>> +			  max_iid :
>>> +			  index->def->iid + 1;
>>> +	}
>>>   
>>> -		/* Tarantool: DESC indexes are not supported so far.
>>> -		 * See gh-3016.
>>> -		 */
>>> -		pIndex->sort_order[i] = SORT_ORDER_ASC;
>>> +	set_index_def(parse, pIndex, pTab, max_iid, name, name_len, on_error,
>>> +		      col_list, idx_type);
>>> +
>>> +	if (pIndex->def == NULL ||
>>> +	    !index_def_is_valid(pIndex->def, pTab->def->name)) {
>>> +		goto exit_create_index;
>>>   	}
>>> +
>>>   	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
>>> @@ -3123,25 +3143,27 @@ sql_create_index(struct Parse *parse, struct Token *token,
>>>   		 */
>>>   		Index *pIdx;
>>>   		for (pIdx = pTab->pIndex; pIdx; pIdx = pIdx->pNext) {
>>> -			int k;
>>> +			uint32_t k;
>>>   			assert(IsUniqueIndex(pIdx));
>>>   			assert(pIdx->idxType != SQLITE_IDXTYPE_APPDEF);
>>>   			assert(IsUniqueIndex(pIndex));
>>>   
>>> -			if (pIdx->nColumn != pIndex->nColumn)
>>> +			if (pIdx->def->key_def->part_count !=
>>> +			    pIndex->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 < pIdx->def->key_def->part_count; k++) {
>>> +				if (pIdx->def->key_def->parts[k].fieldno !=
>>> +				    pIndex->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 = pIdx->def->key_def->parts[k].coll;
>>> +				coll2 = pIndex->def->key_def->parts[k].coll;
>>>   				if (coll1 != coll2)
>>>   					break;
>>>   			}
>>> -			if (k == pIdx->nColumn) {
>>> +			if (k == pIdx->def->key_def->part_count) {
>>>   				if (pIdx->onError != pIndex->onError) {
>>>   					/* This constraint creates the same index as a previous
>>>   					 * constraint specified somewhere in the CREATE TABLE statement.
>>> @@ -3175,7 +3197,7 @@ sql_create_index(struct Parse *parse, struct Token *token,
>>>   	assert(parse->nErr == 0);
>>>   	if (db->init.busy) {
>>>   		Index *p;
>>> -		p = sqlite3HashInsert(&pTab->idxHash, pIndex->zName, pIndex);
>>> +		p = sqlite3HashInsert(&pTab->idxHash, pIndex->def->name, pIndex);
>>>   		if (p) {
>>>   			assert(p == pIndex);	/* Malloc must have failed */
>>>   			sqlite3OomFault(db);
>>> @@ -3273,44 +3295,7 @@ sql_create_index(struct Parse *parse, struct Token *token,
>>>   	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
>>> @@ -3938,18 +3923,19 @@ sqlite3UniqueConstraint(Parse * pParse,	/* Parsing context */
>>>       )
>>>   {
>>>   	char *zErr;
>>> -	int j;
>>> +	uint32_t j;
>>>   	StrAccum errMsg;
>>>   	Table *pTab = pIdx->pTable;
>>>   
>>>   	sqlite3StrAccumInit(&errMsg, pParse->db, 0, 0, 200);
>>>   	if (pIdx->aColExpr) {
>>> -		sqlite3XPrintf(&errMsg, "index '%q'", pIdx->zName);
>>> +		sqlite3XPrintf(&errMsg, "index '%q'", pIdx->def->name);
>>>   	} else {
>>> -		for (j = 0; j < pIdx->nColumn; j++) {
>>> +		struct key_part *part = pIdx->def->key_def->parts;
>>> +		for (j = 0; j < pIdx->def->key_def->part_count; j++, part++) {
>>>   			char *zCol;
>>> -			assert(pIdx->aiColumn[j] >= 0);
>>> -			zCol = pTab->def->fields[pIdx->aiColumn[j]].name;
>>> +			uint32_t fieldno = part->fieldno;
>>> +			zCol = pTab->def->fields[fieldno].name;
>>>   			if (j)
>>>   				sqlite3StrAccumAppend(&errMsg, ", ", 2);
>>>   			sqlite3XPrintf(&errMsg, "%s.%s", pTab->def->name, zCol);
>>> @@ -3972,11 +3958,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 ddad54b3e..0314382f7 100644
>>> --- a/src/box/sql/delete.c
>>> +++ b/src/box/sql/delete.c
>>> @@ -209,7 +209,7 @@ sql_table_delete_from(struct Parse *parse, struct SrcList *tab_list,
>>>   		} else {
>>>   			pk = sqlite3PrimaryKeyIndex(table);
>>>   			assert(pk != NULL);
>>> -			pk_len = index_column_count(pk);
>>> +			pk_len = pk->def->key_def->part_count;
>>>   			parse->nMem += pk_len;
>>>   			sqlite3VdbeAddOp2(v, OP_OpenTEphemeral, eph_cursor,
>>>   					  pk_len);
>>> @@ -251,12 +251,11 @@ 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++) {
>>> -				assert(pk->aiColumn[i] >= 0);
>>> +			struct key_part *part = pk->def->key_def->parts;
>>> +			for (int i = 0; i < pk_len; i++, part++) {
>>>   				sqlite3ExprCodeGetColumnOfTable(v, table->def,
>>>   								tab_cursor,
>>> -								pk->
>>> -								aiColumn[i],
>>> +								part->fieldno,
>>>   								reg_pk + i);
>>>   			}
>>>   		} else {
>>> @@ -326,7 +325,7 @@ sql_table_delete_from(struct Parse *parse, struct SrcList *tab_list,
>>>   			sqlite3VdbeAddOp3(v, OP_OpenWrite, tab_cursor,
>>>   					  table->tnum, space_ptr_reg);
>>>   			sql_vdbe_set_p4_key_def(parse, pk);
>>> -			VdbeComment((v, "%s", pk->zName));
>>> +			VdbeComment((v, "%s", pk->def->name));
>>>   
>>>   			if (one_pass == ONEPASS_MULTI)
>>>   				sqlite3VdbeJumpHere(v, iAddrOnce);
>>> @@ -536,14 +535,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]
>>> -		    && prev->aiColumn[j] != XN_EXPR) {
>>> +		if (prev->def->key_def->parts[j].fieldno ==
>>> +		    index->def->key_def->parts[j].fieldno && prev == NULL) {
>>>   			/*
>>>   			 * This column was already computed by the
>>>   			 * previous index.
>>> diff --git a/src/box/sql/expr.c b/src/box/sql/expr.c
>>> index 8866f6fed..a756535f0 100644
>>> --- a/src/box/sql/expr.c
>>> +++ b/src/box/sql/expr.c
>>> @@ -2422,20 +2422,24 @@ 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) {
>>> -					if (pIdx->nColumn > nExpr
>>> -					    || (pIdx->nColumn > nExpr
>>> -					    && !index_is_unique(pIdx))) {
>>> -							continue;	/* This index is not unique over the IN RHS columns */
>>> +					if ((int)part_count > nExpr
>>> +					    || !pIdx->def->opts.is_unique) {
>>> +						/* This index is not unique over the IN RHS columns */
>>> +						continue;
>>>   					}
>>>   				}
>>>   
>>> @@ -2449,12 +2453,13 @@ 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) {
>>>   							continue;
>>> @@ -2483,17 +2488,16 @@ sqlite3FindInIndex(Parse * pParse,	/* Parsing context */
>>>   							  0, 0, 0,
>>>   							  sqlite3MPrintf(db,
>>>   							  "USING INDEX %s FOR IN-OPERATOR",
>>> -							  pIdx->zName),
>>> +							  pIdx->def->name),
>>>   							  P4_DYNAMIC);
>>>   					emit_open_cursor(pParse, iTab,
>>>   							 pIdx->tnum);
>>>   					sql_vdbe_set_p4_key_def(pParse, pIdx);
>>> -					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
>>> @@ -2515,7 +2519,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);
>>>   						}
>>>   					}
>>> @@ -3146,12 +3150,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
>>> +			tab->def->fields[fieldno].affinity;
>>> +		if (pk->def->key_def->part_count == 1
>>>   		    && affinity == AFFINITY_INTEGER
>>> -		    && pk->aiColumn[0] < nVector) {
>>> -			int reg_pk = rLhs + pk->aiColumn[0];
>>> +		    && (int) fieldno < nVector) { int reg_pk = rLhs + (int)fieldno;
>>>   			sqlite3VdbeAddOp2(v, OP_MustBeInt, reg_pk, destIfFalse);
>>>   		}
>>>   	}
>>> @@ -3483,17 +3487,9 @@ sqlite3ExprCodeLoadIndexColumn(Parse * pParse,	/* The parsing context */
>>>   			       int regOut	/* Store the index column value in this register */
>>>       )
>>>   {
>>> -	i16 iTabCol = pIdx->aiColumn[iIdxCol];
>>> -	if (iTabCol == XN_EXPR) {
>>> -		assert(pIdx->aColExpr);
>>> -		assert(pIdx->aColExpr->nExpr > iIdxCol);
>>> -		pParse->iSelfTab = iTabCur;
>>> -		sqlite3ExprCodeCopy(pParse, pIdx->aColExpr->a[iIdxCol].pExpr,
>>> -				    regOut);
>>> -	} else {
>>> -		sqlite3ExprCodeGetColumnOfTable(pParse->pVdbe, pIdx->pTable->def,
>>> -						iTabCur, iTabCol, regOut);
>>> -	}
>>> +	i16 iTabCol = pIdx->def->key_def->parts[iIdxCol].fieldno;
>>> +	sqlite3ExprCodeGetColumnOfTable(pParse->pVdbe, pIdx->pTable->def,
>>> +					iTabCur, iTabCol, regOut);
>>>   }
>>>   
>>>   void
>>> diff --git a/src/box/sql/fkey.c b/src/box/sql/fkey.c
>>> index 70ebef89f..79320eced 100644
>>> --- a/src/box/sql/fkey.c
>>> +++ b/src/box/sql/fkey.c
>>> @@ -256,8 +256,8 @@ sqlite3FkLocateIndex(Parse * pParse,	/* Parse context to store any error in */
>>>   	}
>>>   
>>>   	for (pIdx = pParent->pIndex; pIdx; pIdx = pIdx->pNext) {
>>> -		int nIdxCol = index_column_count(pIdx);
>>> -		if (nIdxCol == nCol && index_is_unique(pIdx)
>>> +		int nIdxCol = pIdx->def->key_def->part_count;
>>> +		if (nIdxCol == nCol && pIdx->def->opts.is_unique
>>>   		    && 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
>>> @@ -286,8 +286,10 @@ sqlite3FkLocateIndex(Parse * pParse,	/* Parse context to store any error in */
>>>   				 * the default collation sequences for each column.
>>>   				 */
>>>   				int i, j;
>>> -				for (i = 0; i < nCol; i++) {
>>> -					i16 iCol = pIdx->aiColumn[i];	/* Index of column in parent tbl */
>>> +				struct key_part *part =
>>> +					pIdx->def->key_def->parts;
>>> +				for (i = 0; i < nCol; i++, part++) {
>>> +					i16 iCol = (int) part->fieldno;	/* Index of column in parent tbl */
>>>   					char *zIdxCol;	/* Name of indexed column */
>>>   
>>>   					if (iCol < 0)
>>> @@ -302,9 +304,7 @@ sqlite3FkLocateIndex(Parse * pParse,	/* Parse context to store any error in */
>>>   					def_coll = sql_column_collation(pParent->def,
>>>   									iCol,
>>>   									&id);
>>> -					struct coll *coll =
>>> -						sql_index_collation(pIdx, i,
>>> -								    &id);
>>> +					struct coll *coll = part->coll;
>>>   					if (def_coll != coll)
>>>   						break;
>>>   
>>> @@ -464,13 +464,15 @@ fkLookupParent(Parse * pParse,	/* Parse context */
>>>   				for (i = 0; i < nCol; i++) {
>>>   					int iChild = aiCol[i] + 1 + regData;
>>>   					int iParent =
>>> -					    pIdx->aiColumn[i] + 1 + regData;
>>> -					assert(pIdx->aiColumn[i] >= 0);
>>> +						(int) pIdx->def->key_def->parts[i].fieldno
>>> +						+ 1 + regData;
>>>   					assert(aiCol[i] != pTab->iPKey);
>>> -					if (pIdx->aiColumn[i] == pTab->iPKey) {
>>> +					if ((int)pIdx->def->key_def->
>>> +						parts[i].fieldno == pTab->iPKey) {
>>>   						/* The parent key is a composite key that includes the IPK column */
>>>   						iParent = regData;
>>>   					}
>>> +
>>>   					sqlite3VdbeAddOp3(v, OP_Ne, iChild,
>>>   							  iJump, iParent);
>>>   					VdbeCoverage(v);
>>> @@ -622,7 +624,7 @@ fkScanChildren(Parse * pParse,	/* Parse context */
>>>   	Vdbe *v = sqlite3GetVdbe(pParse);
>>>   
>>>   	assert(pIdx == 0 || pIdx->pTable == pTab);
>>> -	assert(pIdx == 0 || (int)index_column_count(pIdx) == pFKey->nCol);
>>> +	assert(pIdx == 0 || (int) pIdx->def->key_def->part_count == pFKey->nCol);
>>>   	assert(pIdx != 0);
>>>   
>>>   	if (nIncr < 0) {
>>> @@ -646,7 +648,7 @@ fkScanChildren(Parse * pParse,	/* Parse context */
>>>   		i16 iCol;	/* Index of column in child table */
>>>   		const char *zCol;	/* Name of column in child table */
>>>   
>>> -		iCol = pIdx ? pIdx->aiColumn[i] : -1;
>>> +		iCol = pIdx ? pIdx->def->key_def->parts[i].fieldno : -1;
> -               iCol = pIdx ? pIdx->def->key_def->parts[i].fieldno : -1;
> +               iCol = pIdx != NULL ?
> +                       (i16)pIdx->def->key_def->parts[i].fieldno : -1;
>
Fixed.
>>>   		pLeft = exprTableRegister(pParse, pTab, regData, iCol);
>>>   		iCol = aiCol ? aiCol[i] : pFKey->aCol[0].iFrom;
>>>   		assert(iCol >= 0);
>>> @@ -671,10 +673,9 @@ fkScanChildren(Parse * pParse,	/* Parse context */
>>>   		Expr *pEq, *pAll = 0;
>>>   		Index *pPk = sqlite3PrimaryKeyIndex(pTab);
>>>   		assert(pIdx != 0);
>>> -		int col_count = index_column_count(pPk);
>>> +		int col_count = pPk->def->key_def->part_count;
>>>   		for (i = 0; i < col_count; i++) {
>>> -			i16 iCol = pIdx->aiColumn[i];
>>> -			assert(iCol >= 0);
>>> +			i16 iCol = (int) pIdx->def->key_def->parts[i].fieldno;
>>>   			pLeft = exprTableRegister(pParse, pTab, regData, iCol);
>>>   			pRight =
>>>   				exprTableColumn(db, pTab->def,
>>> @@ -992,7 +993,6 @@ sqlite3FkCheck(Parse * pParse,	/* Parse context */
>>>   			if (aiCol[i] == pTab->iPKey) {
>>>   				aiCol[i] = -1;
>>>   			}
>>> -			assert(pIdx == 0 || pIdx->aiColumn[i] >= 0);
>>>   		}
>>>   
>>>   		pParse->nTab++;
>>> @@ -1126,10 +1126,10 @@ sqlite3FkOldmask(Parse * pParse,	/* Parse context */
>>>   			Index *pIdx = 0;
>>>   			sqlite3FkLocateIndex(pParse, pTab, p, &pIdx, 0);
>>>   			if (pIdx) {
>>> -				int nIdxCol = index_column_count(pIdx);
>>> +				int nIdxCol = pIdx->def->key_def->part_count;
>>>   				for (i = 0; i < nIdxCol; i++) {
>>> -					assert(pIdx->aiColumn[i] >= 0);
>>> -					mask |= COLUMN_MASK(pIdx->aiColumn[i]);
>>> +					mask |= COLUMN_MASK(pIdx->def->
>>> +						key_def->parts[i].fieldno);
>>>   				}
>>>   			}
>>>   		}
>>> @@ -1264,11 +1264,12 @@ fkActionTrigger(Parse * pParse,	/* Parse context */
>>>   			       || (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
>>> +					   : pTab->iPKey;
> 6. GCC looks like a little more pedantic.
> -                                          : pTab->iPKey;
>
> +                                          : (uint32_t)pTab->iPKey;
> 7. Please put ":" at the prev. line.
Fixed.
>>>   			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 59c61c703..fc9f85165 100644
>>> --- a/src/box/sql/insert.c
>>> +++ b/src/box/sql/insert.c
>>> @@ -89,7 +89,7 @@ sqlite3IndexAffinityStr(sqlite3 * db, Index * pIdx)
>>>   		 * up.
>>>   		 */
>>>   		int n;
>>> -		int nColumn = index_column_count(pIdx);
>>> +		int nColumn = pIdx->def->key_def->part_count;
>>>   		pIdx->zColAff =
>>>   		    (char *)sqlite3DbMallocRaw(0, nColumn + 1);
>>>   		if (!pIdx->zColAff) {
>>> @@ -97,22 +97,8 @@ sqlite3IndexAffinityStr(sqlite3 * db, Index * pIdx)
>>>   			return 0;
>>>   		}
>>>   		for (n = 0; n < nColumn; n++) {
>>> -			i16 x = pIdx->aiColumn[n];
>>> -			if (x >= 0) {
>>> -				char affinity = pIdx->pTable->
>>> -					def->fields[x].affinity;
>>> -				pIdx->zColAff[n] = affinity;
>>> -			} else {
>>> -				char aff;
>>> -				assert(x == XN_EXPR);
>>> -				assert(pIdx->aColExpr != 0);
>>> -				aff =
>>> -				    sqlite3ExprAffinity(pIdx->aColExpr->a[n].
>>> -							pExpr);
>>> -				if (aff == 0)
>>> -					aff = AFFINITY_BLOB;
>>> -				pIdx->zColAff[n] = aff;
>>> -			}
>>> +			i16 x = pIdx->def->key_def->parts[n].fieldno;
>>> +			pIdx->zColAff[n] = pIdx->pTable->def->fields[x].affinity;
>>>   		}
>>>   		pIdx->zColAff[n] = 0;
>>>   	}
>>> @@ -645,7 +631,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;
>>>   		}
>>>   	}
>>>   
>>> @@ -1088,7 +1074,7 @@ sqlite3GenerateConstraintChecks(Parse * pParse,		/* The parser context */
>>>   	nCol = pTab->def->field_count;
>>>   
>>>   	pPk = sqlite3PrimaryKeyIndex(pTab);
>>> -	nPkField = index_column_count(pPk);
>>> +	nPkField = pPk->def->key_def->part_count;
>>>   
>>>   	/* Record that this module has started */
>>>   	VdbeModuleComment((v, "BEGIN: GenCnstCks(%d,%d,%d,%d,%d)",
>>> @@ -1252,38 +1238,27 @@ 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);
>>> +		int nIdxCol = pIdx->def->key_def->part_count;
>>>   		for (i = 0; i < nIdxCol; i++) {
>>> -			int iField = pIdx->aiColumn[i];
>>> +			int iField = (int) pIdx->def->key_def->parts[i].fieldno;
>>>   			int x;
>>> -			if (iField == XN_EXPR) {
>>> -				pParse->ckBase = regNewData + 1;
>>> -				sqlite3ExprCodeCopy(pParse,
>>> -						    pIdx->aColExpr->a[i].pExpr,
>>> -						    regIdx + i);
>>> -				pParse->ckBase = 0;
>>> -				VdbeComment((v, "%s column %d", pIdx->zName,
>>> -					     i));
>>> -			} else {
>>> -				/* OP_SCopy copies value in separate register,
>>> -				 * which later will be used by OP_NoConflict.
>>> -				 * But OP_NoConflict is necessary only in cases
>>> -				 * when bytecode is needed for proper UNIQUE
>>> -				 * constraint handling.
>>> -				 */
>>> -				if (uniqueByteCodeNeeded) {
>>> -					if (iField == pTab->iPKey)
>>> -						x = regNewData;
>>> -					else
>>> -						x = iField + regNewData + 1;
>>> -
>>> -					assert(iField >= 0);
>>> -					sqlite3VdbeAddOp2(v, OP_SCopy,
>>> -							  x, regIdx + i);
>>> -					VdbeComment((v, "%s",
>>> -						     pTab->def->fields[
>>> -							iField].name));
>>> -				}
>>> +			/* OP_SCopy copies value in separate register,
>>> +			 * which later will be used by OP_NoConflict.
>>> +			 * But OP_NoConflict is necessary only in cases
>>> +			 * when bytecode is needed for proper UNIQUE
>>> +			 * constraint handling.
>>> +			 */
>>> +			if (uniqueByteCodeNeeded) {
>>> +				if (iField == pTab->iPKey)
>>> +					x = regNewData;
>>> +				else
>>> +					x = iField + regNewData + 1;
>>> +
>>> +				assert(iField >= 0);
>>> +				sqlite3VdbeAddOp2(v, OP_SCopy,
>>> +						  x, regIdx + i);
>>> +				VdbeComment((v, "%s",
>>> +					     pTab->def->fields[iField].name));
>>>   			}
>>>   		}
>>>   
>>> @@ -1293,8 +1268,12 @@ sqlite3GenerateConstraintChecks(Parse * pParse,		/* The parser context */
>>>   			/* 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]] ==
>>> +				reg_pk = regNewData + 1 +
>>> +					pIdx->def->key_def->parts[0].fieldno;
>>> +
>>> +				int fieldno = (int)pIdx->def->key_def->
>>> +					parts[0].fieldno;
>>> +				if (pTab->zColAff[fieldno] ==
>>>   				    AFFINITY_INTEGER) {
>>>   					int skip_if_null = sqlite3VdbeMakeLabel(v);
>>>   					if ((pTab->tabFlags & TF_Autoincrement) != 0) {
>>> @@ -1311,8 +1290,8 @@ sqlite3GenerateConstraintChecks(Parse * pParse,		/* The parser context */
>>>   			}
>>>   
>>>   			sqlite3VdbeAddOp3(v, OP_MakeRecord, regNewData + 1,
>>> -					pTab->def->field_count, aRegIdx[ix]);
>>> -			VdbeComment((v, "for %s", pIdx->zName));
>>> +					  pTab->def->field_count, aRegIdx[ix]);
>>> +			VdbeComment((v, "for %s", pIdx->def->name));
>>>   		}
>>>   
>>>   		/* In an UPDATE operation, if this index is the PRIMARY KEY
>>> @@ -1400,7 +1379,7 @@ 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);
>>>   
>>> @@ -1410,19 +1389,17 @@ sqlite3GenerateConstraintChecks(Parse * pParse,		/* The parser context */
>>>   								 nPkField);
>>>   		if (isUpdate || on_error == ON_CONFLICT_ACTION_REPLACE) {
>>>   			int x;
>>> -			int nPkCol = index_column_count(pPk);
>>> +			int nPkCol = pPk->def->key_def->part_count;
>>>   			/* 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];
>>> +					x = pPk->def->key_def->parts[i].fieldno;
>>>   					sqlite3VdbeAddOp3(v, OP_Column,
>>>   							  iThisCur, x, regR + i);
>>>   					VdbeComment((v, "%s.%s", pTab->def->name,
>>> -						pTab->def->fields[
>>> -							pPk->aiColumn[i]].name));
>>> +						pTab->def->fields[x].name));
>>>   				}
>>>   			}
>>>   			if (isUpdate && uniqueByteCodeNeeded) {
>>> @@ -1440,10 +1417,11 @@ sqlite3GenerateConstraintChecks(Parse * pParse,		/* The parser context */
>>>   					      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);
>>> +					char *p4 = (char *) pPk->def->key_def->parts[i].coll;
>>> +					x = pPk->def->key_def->parts[i].fieldno;
>>> +					if (pPk->tnum==0) {
>>> +						x = -1;
>>> +					}
> 8. Redundant {}
> -                                       if (pPk->tnum==0) {
> +                                       if (pPk->tnum == 0)
>                                                  x = -1;
> -                                       }
Fixed.
>>>   					if (i == (nPkCol - 1)) {
>>>   						addrJump = addrUniqueOk;
>>>   						op = OP_Eq;
>>> @@ -1620,8 +1598,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 */
>>> @@ -1639,7 +1617,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));
>>>   			}
>>>   		}
>>>   	}
>>> @@ -1676,35 +1654,23 @@ 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);
>>> +	uint32_t nDestCol = pDest->def->key_def->part_count;
>>> +	uint32_t nSrcCol = pSrc->def->key_def->part_count;
>>>   	if (nDestCol != nSrcCol) {
>>>   		return 0;	/* Different number of columns */
>>>   	}
>>>   	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 (i = 0; i < nSrcCol; i++, src_part++, dest_part++) {
>>> +		if (src_part->fieldno != dest_part->fieldno)
>>>   			return 0;	/* Different columns indexed */
>>> -		}
>>> -		if (pSrc->aiColumn[i] == XN_EXPR) {
>>> -			assert(pSrc->aColExpr != 0 && pDest->aColExpr != 0);
>>> -			if (sqlite3ExprCompare(pSrc->aColExpr->a[i].pExpr,
>>> -					       pDest->aColExpr->a[i].pExpr,
>>> -					       -1) != 0) {
>>> -				return 0;	/* Different expressions in the index */
>>> -			}
>>> -		}
>>> -		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 */
>>> @@ -1876,16 +1842,14 @@ 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) {
>>> +		if (pSrcIdx == 0)
>>>   			return 0;	/* pDestIdx has no corresponding index in pSrc */
>>> -		}
> 9. Please, like this:
> -               if (pSrcIdx == 0)
> -                       return 0;       /* pDestIdx has no corresponding index in pSrc */
> +               /* pDestIdx has no corresponding index in pSrc. */
> +               if (pSrcIdx == NULL)
> +                       return 0;
Fixed.
>>>   	}
>>>   	/* Get server checks. */
>>>   	ExprList *pCheck_src = space_checks_expr_list(
>>> @@ -1960,11 +1924,11 @@ xferOptimization(Parse * pParse,	/* Parser context */
>>>   		assert(pSrcIdx);
>>>   		emit_open_cursor(pParse, iSrc, pSrcIdx->tnum);
>>>   		sql_vdbe_set_p4_key_def(pParse, pSrcIdx);
>>> -		VdbeComment((v, "%s", pSrcIdx->zName));
>>> +		VdbeComment((v, "%s", pSrcIdx->def->name));
>>>   		emit_open_cursor(pParse, iDest, pDestIdx->tnum);
>>>   		sql_vdbe_set_p4_key_def(pParse, pDestIdx);
>>>   		sqlite3VdbeChangeP5(v, OPFLAG_BULKCSR);
>>> -		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);
>>> diff --git a/src/box/sql/pragma.c b/src/box/sql/pragma.c
>>> index 9dab5a7fd..45896811b 100644
>>> --- a/src/box/sql/pragma.c
>>> +++ b/src/box/sql/pragma.c
>>> @@ -370,8 +370,11 @@ sqlite3Pragma(Parse * pParse, Token * pId,	/* First part of [schema.]id field */
>>>   						for (k = 1;
>>>   						     k <=
>>>   						     (int)pTab->def->field_count
>>> -						     && pPk->aiColumn[k - 1] !=
>>> -						     i; k++) {
>>> +						     && (int) pPk->def->
>>> +							     key_def->
>>> +							     parts[k - 1].
>>> +							     fieldno != i;
>>> +						     k++) {
>>>   						}
>>>   					}
>>>   					bool nullable =
>>> @@ -430,7 +433,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,
>>> @@ -459,11 +462,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,
>>> @@ -477,19 +482,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 <
>>> @@ -519,10 +523,8 @@ sqlite3Pragma(Parse * pParse, Token * pId,	/* First part of [schema.]id field */
>>>   						    { "c", "u", "pk" };
>>>   						sqlite3VdbeMultiLoad(v, 1,
>>>   								     "isisi", i,
>>> -								     pIdx->
>>> -								     zName,
>>> -								     index_is_unique
>>> -								     (pIdx),
>>> +								     pIdx->def->name,
>>> +								     pIdx->def->opts.is_unique,
>>>   								     azOrigin
>>>   								     [pIdx->
>>>   								      idxType],
>>> diff --git a/src/box/sql/select.c b/src/box/sql/select.c
>>> index 2aa35a114..2646a99c3 100644
>>> --- a/src/box/sql/select.c
>>> +++ b/src/box/sql/select.c
>>> @@ -4291,7 +4291,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 01351a183..36b46ed4f 100644
>>> --- a/src/box/sql/sqliteInt.h
>>> +++ b/src/box/sql/sqliteInt.h
>>> @@ -2071,21 +2071,6 @@ struct UnpackedRecord {
>>>    * 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.
>>> @@ -2102,27 +2087,19 @@ 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 */
>>>   	ExprList *aColExpr;	/* Column expressions */
>>>   	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 */
> 10. Will you keep an informative tarantool-style comment here?
Ok, added comment.
>>> +	struct index_def *def;
>>>   };
>>>   
>>>   /**
>>> @@ -2161,11 +2138,6 @@ index_field_tuple_est(struct Index *idx, uint32_t field);
>>>   #define IsUniqueIndex(X)      (((X)->idxType == SQLITE_IDXTYPE_UNIQUE) || \
>>>   				((X)->idxType == SQLITE_IDXTYPE_PRIMARYKEY))
>>>   
>>> -/* The Index.aiColumn[] values are normally positive integer.  But
>>> - * there are some negative values that have special meaning:
>>> - */
>>> -#define XN_EXPR      (-2)	/* Indexed column is an expression */
>>> -
>>>   #ifdef DEFAULT_TUPLE_COUNT
>>>   #undef DEFAULT_TUPLE_COUNT
>>>   #endif
>>> @@ -3526,37 +3498,10 @@ 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);
>>> +
>>>   bool
>>>   space_is_view(Table *);
>>>   
>>> -/**
>>> - * 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 *);
>>>   int
>>>   emit_open_cursor(Parse *, int, int);
>>> @@ -3607,8 +3552,6 @@ void sqlite3SrcListAssignCursors(Parse *, SrcList *);
>>>   void sqlite3IdListDelete(sqlite3 *, IdList *);
>>>   void sqlite3SrcListDelete(sqlite3 *, SrcList *);
>>>   Index *sqlite3AllocateIndexObject(sqlite3 *, i16, int, char **);
>>> -bool
>>> -index_is_unique(Index *);
>>>   
>>>   /**
>>>    * Create a new index for an SQL table.  name is the name of the
>>> @@ -4293,8 +4236,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);
>>> diff --git a/src/box/sql/trigger.c b/src/box/sql/trigger.c
>>> index e1126b2d2..ea3521133 100644
>>> --- a/src/box/sql/trigger.c
>>> +++ b/src/box/sql/trigger.c
>>> @@ -872,8 +872,6 @@ codeRowTrigger(Parse * pParse,	/* Current parse context */
>>>   	pSubParse->pToplevel = pTop;
>>>   	pSubParse->eTriggerOp = pTrigger->op;
>>>   	pSubParse->nQueryLoop = pParse->nQueryLoop;
>>> -	struct region *region = &fiber()->gc;
>>> -	pSubParse->region_initial_size = region_used(region);
>>>   
>>>   	v = sqlite3GetVdbe(pSubParse);
>>>   	if (v) {
>>> diff --git a/src/box/sql/update.c b/src/box/sql/update.c
>>> index 590aad28b..6545b3b06 100644
>>> --- a/src/box/sql/update.c
>>> +++ b/src/box/sql/update.c
>>> @@ -237,14 +237,14 @@ 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);
>>> +		int nIdxCol = pIdx->def->key_def->part_count;
>>>   		if (chngPk || hasFK || pIdx->pPartIdxWhere || pIdx == pPk) {
>>>   			reg = ++pParse->nMem;
>>>   			pParse->nMem += nIdxCol;
>>>   		} else {
>>>   			reg = 0;
>>>   			for (i = 0; i < nIdxCol; i++) {
>>> -				i16 iIdxCol = pIdx->aiColumn[i];
>>> +				i16 iIdxCol = pIdx->def->key_def->parts[i].fieldno;
>>>   				if (iIdxCol < 0 || aXRef[iIdxCol] >= 0) {
>>>   					reg = ++pParse->nMem;
>>>   					pParse->nMem += nIdxCol;
>>> @@ -306,7 +306,7 @@ sqlite3Update(Parse * pParse,		/* The parser context */
>>>   		nPk = nKey;
>>>   	} else {
>>>   		assert(pPk != 0);
>>> -		nPk = index_column_count(pPk);
>>> +		nPk = pPk->def->key_def->part_count;
>>>   	}
>>>   	iPk = pParse->nMem + 1;
>>>   	pParse->nMem += nPk;
>>> @@ -333,9 +333,9 @@ sqlite3Update(Parse * pParse,		/* The parser context */
>>>   		}
>>>   	} else {
>>>   		for (i = 0; i < nPk; i++) {
>>> -			assert(pPk->aiColumn[i] >= 0);
>>>   			sqlite3ExprCodeGetColumnOfTable(v, pTab->def, iDataCur,
>>> -							pPk->aiColumn[i],
>>> +							pPk->def->key_def->
>>> +								parts[i].fieldno,
>>>   							iPk + i);
>>>   		}
>>>   	}
>>> diff --git a/src/box/sql/vdbeaux.c b/src/box/sql/vdbeaux.c
>>> index 679bd0bc1..520b309d9 100644
>>> --- a/src/box/sql/vdbeaux.c
>>> +++ b/src/box/sql/vdbeaux.c
>>> @@ -1165,7 +1165,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 f408b7701..51b5d516e 100644
>>> --- a/src/box/sql/vdbemem.c
>>> +++ b/src/box/sql/vdbemem.c
>>> @@ -1087,7 +1087,7 @@ 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 nCol = pIdx->def->key_def->part_count;
>>>   
>>>   			nByte = sizeof(Mem) * nCol +
>>>   				ROUND8(sizeof(UnpackedRecord));
>>> @@ -1095,7 +1095,7 @@ valueNew(sqlite3 * db, struct ValueNewStat4Ctx *p)
>>>   			    (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);
>>> diff --git a/src/box/sql/where.c b/src/box/sql/where.c
>>> index e79164781..9f5de50f9 100644
>>> --- a/src/box/sql/where.c
>>> +++ b/src/box/sql/where.c
>>> @@ -265,11 +265,6 @@ whereScanNext(WhereScan * pScan)
>>>   			for (pTerm = pWC->a + k; k < pWC->nTerm; k++, pTerm++) {
>>>   				if (pTerm->leftCursor == iCur
>>>   				    && pTerm->u.leftColumn == iColumn
>>> -				    && (iColumn != XN_EXPR
>>> -					|| sqlite3ExprCompare(pTerm->pExpr->
>>> -							      pLeft,
>>> -							      pScan->pIdxExpr,
>>> -							      iCur) == 0)
>>>   				    && (pScan->iEquiv <= 1
>>>   					|| !ExprHasProperty(pTerm->pExpr,
>>>   							    EP_FromJoin))
>>> @@ -376,19 +371,21 @@ whereScanInit(WhereScan * pScan,	/* The WhereScan object being initialized */
>>>   	pScan->is_column_seen = false;
>>>   	if (pIdx) {
>>>   		int j = iColumn;
>>> -		iColumn = pIdx->aiColumn[j];
>>> -		if (iColumn == XN_EXPR) {
>>> -			pScan->pIdxExpr = pIdx->aColExpr->a[j].pExpr;
>>> -		} else if (iColumn >= 0) {
>>> +		iColumn = pIdx->def->key_def->parts[j].fieldno;
>>> +		/*
>>> +		 * pIdx->tnum == 0 means that pIdx is a fake integer
>>> +		 * primary key index
>>> +		 */
> 11. Out of comment-type margin
> -                * pIdx->tnum == 0 means that pIdx is a fake integer
> -                * primary key index
> +                * pIdx->tnum == 0 means that pIdx is a fake
> +                * integer primary key index.
Fixed.
>>> +		if (pIdx->tnum == 0)
>>> +			iColumn = -1;
>>> +
>>> +		if (iColumn >= 0) {
>>>   			char affinity =
>>>   				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 if (iColumn == XN_EXPR) {
>>> -		return 0;
>>>   	}
>>>   	pScan->opMask = opMask;
>>>   	pScan->k = 0;
>>> @@ -464,18 +461,17 @@ 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;
>>>   			}
> 12. Extra braces:
> -                       if (is_found && coll == part_to_match->coll) {
> +                       if (is_found && coll == part_to_match->coll)
>                                  return i;
> -                       }
Fixed.
>>>   		}
>>> @@ -484,27 +480,6 @@ findIndexCol(Parse * pParse,	/* Parse context */
>>>   	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.
>>> @@ -556,9 +531,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,
>>> @@ -566,11 +541,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 == 0)
> 13. !.. == 0 is looking confusing for me.. Please, fix it.
Fixed.
>>>   					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;
>>>   		}
>>> @@ -1107,7 +1083,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;
>>> @@ -1169,13 +1145,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 *pColl = p->def->key_def->parts[nEq].coll;
>>>   	if (pLower) {
>>>   		rc = sqlite3Stat4ValueFromExpr(pParse, pLower->pExpr->pRight,
>>>   					       aff, &p1);
>>> @@ -1371,7 +1346,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);
>>> @@ -1521,7 +1496,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
>>> @@ -1542,7 +1517,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;
>>> @@ -1674,7 +1649,7 @@ 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 (p->pIndex && (zName = p->pIndex->def->name) != 0) {
>>>   		if (strncmp(zName, "sqlite_autoindex_", 17) == 0) {
>>>   			int i = sqlite3Strlen30(zName) - 1;
>>>   			while (zName[i] != '_')
>>> @@ -2236,7 +2211,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.
>>> @@ -2257,11 +2232,10 @@ 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)) {
>>> +		if (pLhs->op != TK_COLUMN || pLhs->iTable != iCur
>>> +		    || pLhs->iColumn != (int)pIdx->def->key_def->parts[i + nEq].fieldno
>>> +		    || pIdx->def->key_def->parts[i + nEq].sort_order !=
>>> +		       pIdx->def->key_def->parts[nEq].sort_order) {
>>>   			break;
>>>   		}
> 14. Extra braces:
> -                      pIdx->def->key_def->parts[nEq].sort_order) {
> +                      pIdx->def->key_def->parts[nEq].sort_order)
>                          break;
> -               }
Fixed.
>>>   
>>> @@ -2275,7 +2249,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;
>>> @@ -2318,13 +2292,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 nProbeCol = 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) {
>>> @@ -2374,8 +2348,9 @@ whereLoopAddBtreeIndex(WhereLoopBuilder * pBuilder,	/* The WhereLoop factory */
>>>   		LogEst nOutUnadjusted;	/* nOut before IN() and WHERE adjustments */
>>>   		int nIn = 0;
>>>   		int nRecValid = pBuilder->nRecValid;
>>> +		uint32_t j = pProbe->def->key_def->parts[saved_nEq].fieldno;
>>>   		if ((eOp == WO_ISNULL || (pTerm->wtFlags & TERM_VNULL) != 0)
>>> -		    && indexColumnNotNull(pProbe, saved_nEq)
>>> +		    && !pProbe->pTable->def->fields[j].is_nullable
>>>   		    ) {
>>>   			continue;	/* ignore IS [NOT] NULL constraints on NOT NULL columns */
>>>   		}
>>> @@ -2445,14 +2420,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 == nProbeCol - 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;
>>> @@ -2514,8 +2491,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;
>>> @@ -2671,7 +2647,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;
>>>   }
>>>   
>>> @@ -2715,7 +2691,7 @@ indexMightHelpWithOrderBy(WhereLoopBuilder * pBuilder,
>>>   	ExprList *pOB;
>>>   	ExprList *aColExpr;
>>>   	int ii, jj;
>>> -	int nIdxCol = index_column_count(pIndex);
>>> +	int nIdxCol = pIndex->def->key_def->part_count;
>>>   	if (index_is_unordered(pIndex))
>>>   		return 0;
>>>   	if ((pOB = pBuilder->pWInfo->pOrderBy) == 0)
>>> @@ -2726,13 +2702,12 @@ indexMightHelpWithOrderBy(WhereLoopBuilder * pBuilder,
>>>   			if (pExpr->iColumn < 0)
>>>   				return 1;
>>>   			for (jj = 0; jj < nIdxCol; jj++) {
>>> -				if (pExpr->iColumn == pIndex->aiColumn[jj])
>>> +				if (pExpr->iColumn == (int)
>>> +				    pIndex->def->key_def->parts[jj].fieldno)
>>>   					return 1;
>>>   			}
>>>   		} else if ((aColExpr = pIndex->aColExpr) != 0) {
>>>   			for (jj = 0; jj < nIdxCol; jj++) {
>>> -				if (pIndex->aiColumn[jj] != XN_EXPR)
>>> -					continue;
>>>   				if (sqlite3ExprCompare
>>>   				    (pExpr, aColExpr->a[jj].pExpr,
>>>   				     iCursor) == 0) {
>>> @@ -2815,7 +2790,6 @@ whereLoopAddBtree(WhereLoopBuilder * pBuilder,	/* WHERE clause information */
>>>   	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 */
>>>   	SrcList *pTabList;	/* The FROM clause */
>>>   	struct SrcList_item *pSrc;	/* The FROM clause btree term to add */
>>>   	WhereLoop *pNew;	/* Template WhereLoop object */
>>> @@ -2846,11 +2820,27 @@ whereLoopAddBtree(WhereLoopBuilder * pBuilder,	/* WHERE clause information */
>>>   		 */
>>>   		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;
>>> +
>>> +		struct key_def *key_def = key_def_new(1);
>>> +		if (key_def == NULL)
>>> +			return SQLITE_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 index_opts = index_opts_default;
>>> +
>>> +		sPk.def = index_def_new(pTab->def->id, 0, "primary",
>>> +					sizeof("primary") - 1, TREE, &index_opts,
>>> +					key_def, NULL);
>>> +
>>> +		if (sPk.def == NULL)
>>> +			return SQLITE_ERROR;
> 15. key_def is leaking here, same as in other errors bellow and at the end of the function.
Fixed.
>>> +
>>>   		aiRowEstPk[0] = sql_space_tuple_log_count(pTab);
>>>   		aiRowEstPk[1] = 0;
>>>   		pFirst = pSrc->pTab->pIndex;
>>> @@ -3325,8 +3315,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
>>> @@ -3387,9 +3377,10 @@ wherePathSatisfiesOrderBy(WhereInfo * pWInfo,	/* The WHERE clause */
>>>   				 * (revIdx) for the j-th column of the index.
>>>   				 */
>>>   				if (pIndex) {
>>> -					iColumn = pIndex->aiColumn[j];
>>> -					revIdx = sql_index_column_sort_order(pIndex,
>>> -									     j);
>>> +					iColumn = pIndex->def->key_def->
>>> +						parts[j].fieldno;
>>> +					revIdx = pIndex->def->key_def->
>>> +						parts[j].sort_order;
>>>   					if (iColumn == pIndex->pTable->iPKey)
>>>   						iColumn = -1;
>>>   				} else {
>>> @@ -3442,8 +3433,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;
>>> @@ -4105,13 +4095,13 @@ whereShortCut(WhereLoopBuilder * pBuilder)
>>>   	} else {
>>>   		for (pIdx = pTab->pIndex; pIdx; pIdx = pIdx->pNext) {
>>>   			int opMask;
>>> -			int nIdxCol = index_column_count(pIdx);
>>> +			int nIdxCol = pIdx->def->key_def->part_count;
>>>   			assert(pLoop->aLTermSpace == pLoop->aLTerm);
>>> -			if (!index_is_unique(pIdx)
>>> +			if (!pIdx->def->opts.is_unique
>>>   			    || pIdx->pPartIdxWhere != 0
>>> -			    || nIdxCol > ArraySize(pLoop->aLTermSpace)
>>> -			    )
>>> +			    || nIdxCol > ArraySize(pLoop->aLTermSpace)) {
>>>   				continue;
>>> +			}
>>>   			opMask = WO_EQ;
>>>   			for (j = 0; j < nIdxCol; j++) {
>>>   				pTerm =
>>> @@ -4650,7 +4640,7 @@ sqlite3WhereBegin(Parse * pParse,	/* The parser context */
>>>   					wctrlFlags & WHERE_ORDERBY_MIN) == 0) {
>>>   					sqlite3VdbeChangeP5(v, OPFLAG_SEEKEQ);	/* Hint to COMDB2 */
>>>   				}
>>> -				VdbeComment((v, "%s", pIx->zName));
>>> +				VdbeComment((v, "%s", pIx->def->name));
>>>   #ifdef SQLITE_ENABLE_COLUMN_USED_MASK
>>>   				{
>>>   					u64 colUsed = 0;
>>> @@ -4781,7 +4771,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 09b267194..22bb76013 100644
>>> --- a/src/box/sql/wherecode.c
>>> +++ b/src/box/sql/wherecode.c
>>> @@ -47,9 +47,7 @@
>>>   static const char *
>>>   explainIndexColumnName(Index * pIdx, int i)
>>>   {
>>> -	i = pIdx->aiColumn[i];
>>> -	if (i == XN_EXPR)
>>> -		return "<expr>";
>>> +	i = pIdx->def->key_def->parts[i].fieldno;
>>>   	return pIdx->pTable->def->fields[i].name;
>>>   }
>>>   
>>> @@ -222,7 +220,7 @@ sqlite3WhereExplainOneScan(Parse * pParse,	/* Parse context */
>>>   			}
>>>   			if (zFmt) {
>>>   				sqlite3StrAccumAppend(&str, " USING ", 7);
>>> -				sqlite3XPrintf(&str, zFmt, pIdx->zName);
>>> +				sqlite3XPrintf(&str, zFmt, pIdx->def->name);
>>>   				explainIndexRange(&str, pLoop);
>>>   			}
>>>   		} else if ((flags & WHERE_IPK) != 0
>>> @@ -463,7 +461,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;
>>> @@ -708,7 +706,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),
>>> @@ -718,8 +716,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);
>>> -			testcase(pIdx->aiColumn[j] == XN_EXPR);
>>> +					  pIdx->def->key_def->parts[j].fieldno,
>>> +					  regBase + j);
>>>   			VdbeComment((v, "%s", explainIndexColumnName(pIdx, j)));
>>>   		}
>>>   	}
>>> @@ -1245,10 +1243,10 @@ sqlite3WhereCodeOneLoopStart(WhereInfo * pWInfo,	/* Complete information about t
>>>   		assert(pWInfo->pOrderBy == 0
>>>   		       || pWInfo->pOrderBy->nExpr == 1
>>>   		       || (pWInfo->wctrlFlags & WHERE_ORDERBY_MIN) == 0);
>>> -		int nIdxCol = index_column_count(pIdx);
>>> +		int nIdxCol = pIdx->def->key_def->part_count;
>>>   		if ((pWInfo->wctrlFlags & WHERE_ORDERBY_MIN) != 0
>>>   		    && pWInfo->nOBSat > 0 && (nIdxCol > nEq)) {
>>> -			j = pIdx->aiColumn[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.
>>> @@ -1259,8 +1257,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)
>>> -			    || j == XN_EXPR) {
>>> +			if (pIdx->pTable->def->fields[j].is_nullable) {
>>>   				assert(pLoop->nSkip == 0);
>>>   				bSeekPastNull = 1;
>>>   				nExtraReg = 1;
>>> @@ -1299,17 +1296,15 @@ sqlite3WhereCodeOneLoopStart(WhereInfo * pWInfo,	/* Complete information about t
>>>   				assert((bRev & ~1) == 0);
>>>   				pLevel->iLikeRepCntr <<= 1;
>>>   				pLevel->iLikeRepCntr |=
>>> -					bRev ^ (sql_index_column_sort_order(pIdx, nEq) ==
>>> +					bRev ^ (pIdx->def->key_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 == XN_EXPR) {
>>> +				j = pIdx->def->key_def->parts[nEq].fieldno;
>>> +				if (pIdx->pTable->def->fields[j].is_nullable)
>>>   					bSeekPastNull = 1;
>>> -				}
>>>   			}
>>>   		}
>>>   		assert(pRangeEnd == 0
>>> @@ -1320,7 +1315,7 @@ sqlite3WhereCodeOneLoopStart(WhereInfo * pWInfo,	/* Complete information about t
>>>   		 * start and end terms (pRangeStart and pRangeEnd).
>>>   		 */
>>>   		if ((nEq < nIdxCol &&
>>> -		     bRev == (sql_index_column_sort_order(pIdx, nEq) ==
>>> +		     bRev == (pIdx->def->key_def->parts[nEq].sort_order ==
>>>   			      SORT_ORDER_ASC)) ||
>>>   		    (bRev && nIdxCol == nEq)) {
>>>   			SWAP(pRangeEnd, pRangeStart);
>>> @@ -1386,9 +1381,10 @@ sqlite3WhereCodeOneLoopStart(WhereInfo * pWInfo,	/* Complete information about t
>>>   		}
>>>   		struct Index *pk = sqlite3PrimaryKeyIndex(pIdx->pTable);
>>>   		assert(pk);
>>> -		int nPkCol = index_column_count(pk);
>>> +		int nPkCol = pk->def->key_def->part_count;
>>> +		uint32_t zero_fieldno = pk->def->key_def->parts[0].fieldno;
>>>   		char affinity =
>>> -			pIdx->pTable->def->fields[pk->aiColumn[0]].affinity;
>>> +			pIdx->pTable->def->fields[zero_fieldno].affinity;
>>>   		if (nPkCol == 1 && affinity == AFFINITY_INTEGER) {
>>>   			/* Right now INTEGER PRIMARY KEY is the only option to
>>>   			 * get Tarantool's INTEGER column type. Need special handling
>>> @@ -1397,7 +1393,8 @@ sqlite3WhereCodeOneLoopStart(WhereInfo * pWInfo,	/* Complete information about t
>>>   			 */
>>>   			int limit = pRangeStart == NULL ? nEq : nEq + 1;
>>>   			for (int i = 0; i < limit; i++) {
>>> -				if (pIdx->aiColumn[i] == pk->aiColumn[0]) {
>>> +				if (pIdx->def->key_def->parts[i].fieldno ==
>>> +				    zero_fieldno) {
>>>   					/* Here: we know for sure that table has INTEGER
>>>   					   PRIMARY KEY, single column, and Index we're
>>>   					   trying to use for scan contains this column. */
>>> @@ -1506,10 +1503,10 @@ 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 nPkCol = pPk->def->key_def->part_count;
>>>   			int iKeyReg = sqlite3GetTempRange(pParse, nPkCol);
>>>   			for (j = 0; j < nPkCol; j++) {
>>> -				k = pPk->aiColumn[j];
>>> +				k = pPk->def->key_def->parts[j].fieldno;
>>>   				sqlite3VdbeAddOp3(v, OP_Column, iIdxCur, k,
>>>   						  iKeyReg + j);
>>>   			}
>>> @@ -1614,7 +1611,7 @@ 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 nPkCol = pPk->def->key_def->part_count;
>>>   			regRowset = pParse->nTab++;
>>>   			sqlite3VdbeAddOp2(v, OP_OpenTEphemeral,
>>>   					  regRowset, nPkCol);
>>> @@ -1718,13 +1715,16 @@ 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 nPk = pPk->def->key_def->part_count;
>>>   						int iPk;
>>>   
>>>   						/* Read the PK into an array of temp registers. */
>>>   						r = sqlite3GetTempRange(pParse, nPk);
>>>   						for (iPk = 0; iPk < nPk; iPk++) {
>>> -							int iCol = pPk->aiColumn[iPk];
>>> +							int iCol = pPk->def->
>>> +								key_def->
>>> +								parts[iPk].
>>> +								fieldno;
>>>   							sqlite3ExprCodeGetColumnToReg
>>>   								(pParse, pTab->def,
>>>   								 iCol, iCur,
>>> diff --git a/src/box/sql/whereexpr.c b/src/box/sql/whereexpr.c
>>> index aa6d4524d..40e4e2577 100644
>>> --- a/src/box/sql/whereexpr.c
>>> +++ b/src/box/sql/whereexpr.c
>>> @@ -903,7 +903,6 @@ exprMightBeIndexed(SrcList * pFrom,	/* The FROM clause */
>>>   		   int *piColumn	/* Write the referenced table column number here */
>>>       )
>>>   {
>>> -	Index *pIdx;
>>>   	int i;
>>>   	int iCur;
>>>   
>>> @@ -930,20 +929,6 @@ exprMightBeIndexed(SrcList * pFrom,	/* The FROM clause */
>>>   	for (i = 0; mPrereq > 1; i++, mPrereq >>= 1) {
>>>   	}
>>>   	iCur = pFrom->a[i].iCursor;
>>> -	for (pIdx = pFrom->a[i].pTab->pIndex; pIdx; pIdx = pIdx->pNext) {
>>> -		if (pIdx->aColExpr == 0)
>>> -			continue;
>>> -		for (i = 0; i < pIdx->nColumn; i++) {
>>> -			if (pIdx->aiColumn[i] != XN_EXPR)
>>> -				continue;
>>> -			if (sqlite3ExprCompare
>>> -			    (pExpr, pIdx->aColExpr->a[i].pExpr, iCur) == 0) {
>>> -				*piCur = iCur;
>>> -				*piColumn = XN_EXPR;
>>> -				return 1;
>>> -			}
>>> -		}
>>> -	}
> 16. You don't need iCur anymore; please delete it and SrcList * pFrom argument that would come useless.
> Please don't dorget to beautify function head to match our codestyle as you are going to touch it.
Not actual after rebase onto master.
>>>   	return 0;
>>>   }
>>>   
>>> -- 
Here is the patch:
--
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
and zName are removed from Index. All usages of this
fields changed to usage of corresponding index_def
fields.
index_is_unique(), sql_index_collation() and
index_column_count() are removed with calls of
index_def corresponding fields.

Closes: #3369
---
Branch: 
https://github.com/tarantool/tarantool/tree/sb/gh-3369-use-index-def-in-select-and-where
Issue: https://github.com/tarantool/tarantool/issues/3369
  src/box/sql.c           |  54 +++---
  src/box/sql/analyze.c   |  40 ++--
  src/box/sql/build.c     | 488 
++++++++++++++++++++++++------------------------
  src/box/sql/delete.c    |  10 +-
  src/box/sql/expr.c      |  48 ++---
  src/box/sql/fkey.c      |  48 ++---
  src/box/sql/insert.c    |  76 ++++----
  src/box/sql/pragma.c    |  28 +--
  src/box/sql/select.c    |   2 +-
  src/box/sql/sqliteInt.h |  74 ++------
  src/box/sql/trigger.c   |   2 -
  src/box/sql/update.c    |  10 +-
  src/box/sql/vdbeaux.c   |   2 +-
  src/box/sql/vdbemem.c   |   4 +-
  src/box/sql/where.c     | 151 +++++++--------
  src/box/sql/wherecode.c |  51 ++---
  16 files changed, 517 insertions(+), 571 deletions(-)

diff --git a/src/box/sql.c b/src/box/sql.c
index 82f3d6d52..a24812c65 100644
--- a/src/box/sql.c
+++ b/src/box/sql.c
@@ -1452,8 +1452,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;
      }
@@ -1564,20 +1564,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;
      }

@@ -1587,46 +1586,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..ca699ecd9 100644
--- a/src/box/sql/analyze.c
+++ b/src/box/sql/analyze.c
@@ -849,7 +849,6 @@ analyzeOneTable(Parse * pParse,    /* Parser context */
          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 */

          if (pOnlyIdx && pOnlyIdx != pIdx)
              continue;
@@ -860,9 +859,9 @@ analyzeOneTable(Parse * pParse,    /* Parser context */
          if (IsPrimaryKeyIndex(pIdx)) {
              zIdxName = pTab->def->name;
          } else {
-            zIdxName = pIdx->zName;
+            zIdxName = pIdx->def->name;
          }
-        nColTest = index_column_count(pIdx);
+        int nColTest = pIdx->def->key_def->part_count;

          /* Populate the register containing the index name. */
          sqlite3VdbeLoadString(v, regIdxname, zIdxName);
@@ -917,7 +916,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:
           *
@@ -969,7 +968,7 @@ analyzeOneTable(Parse * pParse,    /* Parser context */
               */
              sqlite3VdbeAddOp0(v, OP_Goto);
              addrNextRow = sqlite3VdbeCurrentAddr(v);
-            if (nColTest == 1 && index_is_unique(pIdx)) {
+            if (nColTest == 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 +977,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 < nColTest; ++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,
@@ -1006,7 +1004,8 @@ analyzeOneTable(Parse * pParse,    /* Parser 
context */
              for (i = 0; i < nColTest; i++) {
                  sqlite3VdbeJumpHere(v, aGotoChng[i]);
                  sqlite3VdbeAddOp3(v, OP_Column, iIdxCur,
-                          pIdx->aiColumn[i],
+                          pIdx->def->key_def->
+                              parts[i].fieldno,
                            regPrev + i);
              }
              sqlite3VdbeResolveLabel(v, endDistinctTest);
@@ -1022,15 +1021,14 @@ 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));
+        int nPkColumn = (int) pPk->def->key_def->part_count;
+        int regKeyStat = sqlite3GetTempRange(pParse, nPkColumn);
+        for (int j = 0; j < nPkColumn; ++j) {
+            int k = pPk->def->key_def->parts[j].fieldno;
+            assert(k >= 0 && k < (int) 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);
@@ -1146,7 +1144,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");
      }
diff --git a/src/box/sql/build.c b/src/box/sql/build.c
index 592c9a6fa..84e481de3 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;
@@ -376,7 +379,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);
@@ -1041,7 +1044,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)
@@ -1049,22 +1052,20 @@ 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; pIdx = pIdx->pNext) {
+            assert(pIdx->def->key_def->part_count == 1);
+            if (pIdx->def->key_def->parts[0].fieldno == i) {
+                pIdx->def->key_def->parts[0].coll_id = *id;
+                pIdx->def->key_def->parts[0].coll =
                      sql_column_collation(p->def, i, id);
              }
          }
-    } else {
-        sqlite3DbFree(db, zColl);
      }
+    sqlite3DbFree(db, zColl);
  }

  struct coll *
@@ -1094,66 +1095,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)
  {
@@ -1337,14 +1278,16 @@ 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)
+hasColumn(const struct key_part *key_parts, int nCol, uint32_t fieldno)
  {
-    while (nCol-- > 0)
-        if (x == *(aiCol++))
+    int i = 0;
+    while (i < nCol) {
+        if (fieldno == key_parts->fieldno)
              return 1;
+        key_parts++;
+        i++;
+    }
      return 0;
  }

@@ -1364,13 +1307,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;
@@ -1408,14 +1350,28 @@ convertToWithoutRowidTable(Parse * pParse, Table 
* pTab)
           * "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];
+
+        struct key_part *parts = pPk->def->key_def->parts;
+        uint32_t part_count = pPk->def->key_def->part_count;
+        uint32_t new_part_count = part_count;
+
+        for (uint32_t i = 1; i < part_count; i++) {
+            if (hasColumn(parts, i, parts[i].fieldno)){
+                new_part_count--;
+                bool is_found = false;
+                for (uint32_t j = i + 1; j < part_count; j++){
+                    if (!(hasColumn(parts, j,
+                            parts[j].fieldno))) {
+                        parts[i] = parts[j];
+                        is_found = true;
+                        break;
+                    }
+                }
+                if (!(is_found))
+                    break;
              }
          }
-        pPk->nColumn = j;
+        pPk->def->key_def->part_count = new_part_count;
      }
      assert(pPk != 0);
  }
@@ -1497,7 +1453,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);
@@ -1534,7 +1490,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) {
@@ -2463,15 +2419,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.
@@ -2504,7 +2461,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);
@@ -2542,24 +2500,13 @@ sqlite3AllocateIndexObject(sqlite3 * db,    /* 
Database connection */
      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 */
+        ROUND8(sizeof(LogEst) * (nCol + 1));    /* Index.aiRowLogEst */
      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;
      }
      return p;
@@ -2648,18 +2595,136 @@ addIndexToTable(Index * pIndex, Table * pTab)
      }
  }

-bool
-index_is_unique(Index *idx)
+static void
+append_string_part(struct region *r, const char *str,
+          size_t *total_sql_size, Parse *parse)
  {
-    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);
+    char * str_part = region_alloc(r, strlen(str));
+    if (str_part == NULL){
+        diag_set(OutOfMemory, strlen(str),
+             "region_alloc", "str_part");
+        parse->rc = SQL_TARANTOOL_ERROR;
+        parse->nErr++;
+    }
+    memcpy(str_part, str, strlen(str));
+    *total_sql_size += strlen(str);
+}
+
+static void
+set_index_def(Parse *parse, Index *index, Table *table, uint32_t iid,
+          const char *name, uint32_t name_len, int on_error,
+          struct ExprList *expr_list, u8 idx_type)
+{
+    struct space_def *space_def = table->def;
+    struct index_opts opts;
+    index_opts_create(&opts);
+    opts.is_unique = on_error != ON_CONFLICT_ACTION_NONE;
+
+    struct key_def *key_def = key_def_new(expr_list->nExpr);
+    if (key_def == NULL) {
+        parse->rc = SQL_TARANTOOL_ERROR;
+        parse->nErr++;
+        goto cleanup;
+    }
+
+    /*
+     * Build initial parts of SQL statement.
+     */
+
+    struct region *r = &parse->region;
+    size_t total_sql_size = 0;
+
+    if (idx_type == SQLITE_IDXTYPE_APPDEF) {
+        append_string_part(r, "CREATE INDEX ", &total_sql_size,
+                   parse);
+        append_string_part(r, name, &total_sql_size, parse);
+        append_string_part(r, " ON ", &total_sql_size, parse);
+        append_string_part(r, space_def->name, &total_sql_size,
+                   parse);
+        append_string_part(r, " (", &total_sql_size, parse);
+    }
+
+    for (int i = 0; i < expr_list->nExpr; i++) {
+        Expr *expr = expr_list->a[i].pExpr;
+        sql_resolve_self_reference(parse, table, NC_IdxExpr, expr, 0);
+        if (parse->nErr > 0)
+            goto cleanup;
+
+        Expr *column_expr = sqlite3ExprSkipCollate(expr);
+        if (column_expr->op != TK_COLUMN) {
+            sqlite3ErrorMsg(parse,
+                    "functional indexes aren't supported "
+                    "in the current version");
+            goto cleanup;
+        }
+
+        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 (idx_type == SQLITE_IDXTYPE_APPDEF) {
+                append_string_part(r, name,
+                           &total_sql_size, parse);
+                append_string_part(r, " COLLATE ",
+                           &total_sql_size, parse);
+                const char *coll_name = expr->u.zToken;
+                append_string_part(r, coll_name,
+                           &total_sql_size, parse);
+                append_string_part(r, ", ",
+                           &total_sql_size, parse);
+            }
+        } else {
+            coll = sql_column_collation(space_def, fieldno,
+                            &coll_id);
+            if (idx_type == SQLITE_IDXTYPE_APPDEF) {
+                append_string_part(r, name,
+                           &total_sql_size, parse);
+                append_string_part(r, ", ",
+                           &total_sql_size, parse);
+            }
+        }

-    return tnt_index->def->opts.is_unique;
+        /*
+        * Tarantool: DESC indexes are not supported so far.
+        * See gh-3016.
+        */
+        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) {
+        index->def = NULL;
+        goto cleanup;
+    }
+
+    if (idx_type == SQLITE_IDXTYPE_APPDEF) {
+        memcpy(region_alloc(r, 1), "\0", 1);
+        total_sql_size += 1;
+        opts.sql = region_join(r, total_sql_size);
+
+        /*
+         * fix last ", " with ")\0" to finish the statement.
+         */
+        opts.sql[total_sql_size - 3] = ')';
+        opts.sql[total_sql_size - 2] = '\0';
+    }
+
+    struct key_def *pk_key_def;
+    if (idx_type == SQLITE_IDXTYPE_APPDEF)
+        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);
+    cleanup:
+        if (key_def != NULL)
+            key_def_delete(key_def);
  }

  void
@@ -2668,16 +2733,14 @@ sql_create_index(struct Parse *parse, struct 
Token *token,
           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;
+    Table *pTab = NULL;    /* Table to be indexed */
+    Index *pIndex = NULL;    /* The index to be created */
+    char *name = NULL;    /* Name of the index */
+    int name_len;        /* Number of characters in zName */
      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 */
+    char *zExtra = NULL;    /* Extra space after the Index object */
      struct session *user_session = current_session();

      if (db->mallocFailed || parse->nErr > 0) {
@@ -2749,24 +2812,24 @@ sql_create_index(struct Parse *parse, struct 
Token *token,
       * our own name.
       */
      if (token) {
-        zName = sqlite3NameFromToken(db, token);
-        if (zName == 0)
+        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) !=
+            if (sqlite3HashFind(&db->pSchema->tblHash, name) !=
                  NULL) {
                  sqlite3ErrorMsg(parse,
                          "there is already a table named %s",
-                        zName);
+                        name);
                  goto exit_create_index;
              }
          }
-        if (sqlite3HashFind(&pTab->idxHash, zName) != NULL) {
+        if (sqlite3HashFind(&pTab->idxHash, name) != NULL) {
              if (!if_not_exist) {
                  sqlite3ErrorMsg(parse,
                          "index %s.%s already exists",
-                        pTab->def->name, zName);
+                        pTab->def->name, name);
              } else {
                  assert(!db->init.busy);
              }
@@ -2778,10 +2841,9 @@ sql_create_index(struct Parse *parse, struct 
Token *token,
          for (pLoop = pTab->pIndex, n = 1; pLoop;
               pLoop = pLoop->pNext, n++) {
          }
-        zName =
-            sqlite3MPrintf(db, "sqlite_autoindex_%s_%d", pTab->def->name,
-                   n);
-        if (zName == 0) {
+        name = sqlite3MPrintf(db, "sqlite_autoindex_%s_%d",
+                      pTab->def->name, n);
+        if (name == NULL) {
              goto exit_create_index;
          }
      }
@@ -2807,31 +2869,27 @@ 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));
-        }
-    }
-
      /*
       * Allocate the index structure.
       */
-    nName = sqlite3Strlen30(zName);
+    name_len = sqlite3Strlen30(name);
+
+    if (name_len > BOX_NAME_MAX) {
+        sqlite3ErrorMsg(parse,
+                "%s.%s exceeds indexes' names length limit",
+                pTab->def->name, name);
+        goto exit_create_index;
+    }
+
+    if (sqlite3CheckIdentifierName(parse, name) != SQLITE_OK)
+        goto exit_create_index;
+
      pIndex = sqlite3AllocateIndexObject(db, col_list->nExpr,
-                        nName + nExtra + 1, &zExtra);
+                        name_len + nExtra + 1, &zExtra);
      if (db->mallocFailed) {
          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;
      /*
@@ -2846,7 +2904,6 @@ sql_create_index(struct Parse *parse, struct Token 
*token,
          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,
@@ -2855,59 +2912,27 @@ sql_create_index(struct Parse *parse, struct 
Token *token,
          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.
       */
-    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");
-            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;
-        }
-        pIndex->coll_array[i] = coll;
-        pIndex->coll_id_array[i] = id;
+    uint32_t max_iid = 0;
+    for (Index *index = pTab->pIndex; index; index = index->pNext) {
+        max_iid = max_iid > index->def->iid ?
+              max_iid :
+              index->def->iid + 1;
+    }

-        /* Tarantool: DESC indexes are not supported so far.
-         * See gh-3016.
-         */
-        pIndex->sort_order[i] = SORT_ORDER_ASC;
+    set_index_def(parse, pIndex, pTab, max_iid, name, name_len, on_error,
+              col_list, idx_type);
+
+    if (pIndex->def == NULL ||
+        !index_def_is_valid(pIndex->def, pTab->def->name)) {
+        goto exit_create_index;
      }
+
      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
@@ -2932,25 +2957,27 @@ sql_create_index(struct Parse *parse, struct 
Token *token,
           */
          Index *pIdx;
          for (pIdx = pTab->pIndex; pIdx; pIdx = pIdx->pNext) {
-            int k;
+            uint32_t k;
              assert(IsUniqueIndex(pIdx));
              assert(pIdx->idxType != SQLITE_IDXTYPE_APPDEF);
              assert(IsUniqueIndex(pIndex));

-            if (pIdx->nColumn != pIndex->nColumn)
+            if (pIdx->def->key_def->part_count !=
+                pIndex->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 < pIdx->def->key_def->part_count; k++) {
+                if (pIdx->def->key_def->parts[k].fieldno !=
+ pIndex->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 = pIdx->def->key_def->parts[k].coll;
+                coll2 = pIndex->def->key_def->parts[k].coll;
                  if (coll1 != coll2)
                      break;
              }
-            if (k == pIdx->nColumn) {
+            if (k == pIdx->def->key_def->part_count) {
                  if (pIdx->onError != pIndex->onError) {
                      /* This constraint creates the same index as a 
previous
                       * constraint specified somewhere in the CREATE 
TABLE statement.
@@ -2984,7 +3011,7 @@ sql_create_index(struct Parse *parse, struct Token 
*token,
      assert(parse->nErr == 0);
      if (db->init.busy) {
          Index *p;
-        p = sqlite3HashInsert(&pTab->idxHash, pIndex->zName, pIndex);
+        p = sqlite3HashInsert(&pTab->idxHash, pIndex->def->name, pIndex);
          if (p) {
              assert(p == pIndex);    /* Malloc must have failed */
              sqlite3OomFault(db);
@@ -3082,44 +3109,7 @@ sql_create_index(struct Parse *parse, struct 
Token *token,
      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
@@ -3745,9 +3735,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);
@@ -3768,11 +3758,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 8b13f6077..931a15a60 100644
--- a/src/box/sql/delete.c
+++ b/src/box/sql/delete.c
@@ -269,11 +269,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 {
@@ -569,13 +570,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 f03c7a3cd..b752084d4 100644
--- a/src/box/sql/expr.c
+++ b/src/box/sql/expr.c
@@ -2423,20 +2423,24 @@ 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) {
-                    if (pIdx->nColumn > nExpr
-                        || (pIdx->nColumn > nExpr
-                        && !index_is_unique(pIdx))) {
-                            continue;    /* This index is not unique 
over the IN RHS columns */
+                    if ((int)part_count > nExpr
+                        || !pIdx->def->opts.is_unique) {
+                        /* This index is not unique over the IN RHS 
columns */
+                        continue;
                      }
                  }

@@ -2450,12 +2454,13 @@ 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) {
                              continue;
@@ -2484,18 +2489,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
@@ -2517,7 +2521,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);
                          }
                      }
@@ -3148,12 +3152,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
+            tab->def->fields[fieldno].affinity;
+        if (pk->def->key_def->part_count == 1
              && affinity == AFFINITY_INTEGER
-            && pk->aiColumn[0] < nVector) {
-            int reg_pk = rLhs + pk->aiColumn[0];
+            && (int) fieldno < nVector) { int reg_pk = rLhs + (int)fieldno;
              sqlite3VdbeAddOp2(v, OP_MustBeInt, reg_pk, destIfFalse);
          }
      }
@@ -3485,7 +3489,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 e3fff37fe..c5fec3161 100644
--- a/src/box/sql/fkey.c
+++ b/src/box/sql/fkey.c
@@ -257,8 +257,8 @@ sqlite3FkLocateIndex(Parse * pParse,    /* Parse 
context to store any error in */
      }

      for (pIdx = pParent->pIndex; pIdx; pIdx = pIdx->pNext) {
-        int nIdxCol = index_column_count(pIdx);
-        if (nIdxCol == nCol && index_is_unique(pIdx)
+        int nIdxCol = pIdx->def->key_def->part_count;
+        if (nIdxCol == nCol && pIdx->def->opts.is_unique
              && 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
@@ -287,8 +287,10 @@ sqlite3FkLocateIndex(Parse * pParse,    /* Parse 
context to store any error in */
                   * the default collation sequences for each column.
                   */
                  int i, j;
-                for (i = 0; i < nCol; i++) {
-                    i16 iCol = pIdx->aiColumn[i];    /* Index of column 
in parent tbl */
+                struct key_part *part =
+                    pIdx->def->key_def->parts;
+                for (i = 0; i < nCol; i++, part++) {
+                    i16 iCol = (int) part->fieldno;    /* Index of 
column in parent tbl */
                      char *zIdxCol;    /* Name of indexed column */

                      if (iCol < 0)
@@ -303,9 +305,7 @@ sqlite3FkLocateIndex(Parse * pParse,    /* Parse 
context to store any error in */
                      def_coll = sql_column_collation(pParent->def,
                                      iCol,
                                      &id);
-                    struct coll *coll =
-                        sql_index_collation(pIdx, i,
-                                    &id);
+                    struct coll *coll = part->coll;
                      if (def_coll != coll)
                          break;

@@ -465,13 +465,15 @@ fkLookupParent(Parse * pParse,    /* Parse context */
                  for (i = 0; i < nCol; i++) {
                      int iChild = aiCol[i] + 1 + regData;
                      int iParent =
-                        pIdx->aiColumn[i] + 1 + regData;
-                    assert(pIdx->aiColumn[i] >= 0);
+                        (int) pIdx->def->key_def->parts[i].fieldno
+                        + 1 + regData;
                      assert(aiCol[i] != pTab->iPKey);
-                    if (pIdx->aiColumn[i] == pTab->iPKey) {
+                    if ((int)pIdx->def->key_def->
+                        parts[i].fieldno == pTab->iPKey) {
                          /* The parent key is a composite key that 
includes the IPK column */
                          iParent = regData;
                      }
+
                      sqlite3VdbeAddOp3(v, OP_Ne, iChild,
                                iJump, iParent);
                      VdbeCoverage(v);
@@ -623,7 +625,7 @@ fkScanChildren(Parse * pParse,    /* Parse context */
      Vdbe *v = sqlite3GetVdbe(pParse);

      assert(pIdx == 0 || pIdx->pTable == pTab);
-    assert(pIdx == 0 || (int)index_column_count(pIdx) == pFKey->nCol);
+    assert(pIdx == 0 || (int) pIdx->def->key_def->part_count == 
pFKey->nCol);
      assert(pIdx != 0);

      if (nIncr < 0) {
@@ -647,7 +649,8 @@ fkScanChildren(Parse * pParse,    /* Parse context */
          i16 iCol;    /* Index of column in child table */
          const char *zCol;    /* Name of column in child table */

-        iCol = pIdx ? pIdx->aiColumn[i] : -1;
+        iCol = pIdx != NULL ?
+               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);
@@ -672,10 +675,9 @@ fkScanChildren(Parse * pParse,    /* Parse context */
          Expr *pEq, *pAll = 0;
          Index *pPk = sqlite3PrimaryKeyIndex(pTab);
          assert(pIdx != 0);
-        int col_count = index_column_count(pPk);
+        int col_count = pPk->def->key_def->part_count;
          for (i = 0; i < col_count; i++) {
-            i16 iCol = pIdx->aiColumn[i];
-            assert(iCol >= 0);
+            i16 iCol = (int) pIdx->def->key_def->parts[i].fieldno;
              pLeft = exprTableRegister(pParse, pTab, regData, iCol);
              pRight =
                  exprTableColumn(db, pTab->def,
@@ -982,7 +984,6 @@ sqlite3FkCheck(Parse * pParse,    /* Parse context */
              if (aiCol[i] == pTab->iPKey) {
                  aiCol[i] = -1;
              }
-            assert(pIdx == 0 || pIdx->aiColumn[i] >= 0);
          }

          pParse->nTab++;
@@ -1116,10 +1117,10 @@ sqlite3FkOldmask(Parse * pParse,    /* Parse 
context */
              Index *pIdx = 0;
              sqlite3FkLocateIndex(pParse, pTab, p, &pIdx, 0);
              if (pIdx) {
-                int nIdxCol = index_column_count(pIdx);
+                int nIdxCol = pIdx->def->key_def->part_count;
                  for (i = 0; i < nIdxCol; i++) {
-                    assert(pIdx->aiColumn[i] >= 0);
-                    mask |= COLUMN_MASK(pIdx->aiColumn[i]);
+                    mask |= COLUMN_MASK(pIdx->def->
+                        key_def->parts[i].fieldno);
                  }
              }
          }
@@ -1254,11 +1255,12 @@ fkActionTrigger(Parse * pParse,    /* Parse 
context */
                     || (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 70555c3ec..b535763e9 100644
--- a/src/box/sql/insert.c
+++ b/src/box/sql/insert.c
@@ -90,14 +90,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;
          }
      }

@@ -1089,7 +1089,7 @@ sqlite3GenerateConstraintChecks(Parse * pParse,    
     /* The parser context */
      nCol = def->field_count;

      pPk = sqlite3PrimaryKeyIndex(pTab);
-    nPkField = index_column_count(pPk);
+    nPkField = pPk->def->key_def->part_count;

      /* Record that this module has started */
      VdbeModuleComment((v, "BEGIN: GenCnstCks(%d,%d,%d,%d,%d)",
@@ -1253,10 +1253,10 @@ 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);
+        int nIdxCol = (int) pIdx->def->key_def->part_count;
          if (uniqueByteCodeNeeded) {
              for (i = 0; i < nIdxCol; ++i) {
-                int fieldno = pIdx->aiColumn[i];
+                int fieldno = pIdx->def->key_def->parts[i].fieldno;
                  int reg;
                  /*
                   * OP_SCopy copies value in
@@ -1284,8 +1284,12 @@ sqlite3GenerateConstraintChecks(Parse * 
pParse,        /* The parser context */
              /* 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]] ==
+                reg_pk = regNewData + 1 +
+ pIdx->def->key_def->parts[0].fieldno;
+
+                int fieldno = (int) pIdx->def->key_def->
+                    parts[0].fieldno;
+                if (pTab->zColAff[fieldno] ==
                      AFFINITY_INTEGER) {
                      int skip_if_null = sqlite3VdbeMakeLabel(v);
                      if ((pTab->tabFlags & TF_Autoincrement) != 0) {
@@ -1303,7 +1307,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
@@ -1391,7 +1395,7 @@ 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);

@@ -1401,14 +1405,13 @@ sqlite3GenerateConstraintChecks(Parse * 
pParse,        /* The parser context */
                                   nPkField);
          if (isUpdate || on_error == ON_CONFLICT_ACTION_REPLACE) {
              int x;
-            int nPkCol = index_column_count(pPk);
+            int nPkCol = pPk->def->key_def->part_count;
              /* 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];
+                    x = pPk->def->key_def->parts[i].fieldno;
                      sqlite3VdbeAddOp3(v, OP_Column,
                                iThisCur, x, regR + i);
                      VdbeComment((v, "%s.%s", def->name,
@@ -1430,10 +1433,10 @@ sqlite3GenerateConstraintChecks(Parse * 
pParse,        /* The parser context */
                            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);
+                    char *p4 = (char *) pPk->def->key_def->parts[i].coll;
+                    x = pPk->def->key_def->parts[i].fieldno;
+                    if (pPk->tnum==0)
+                        x = -1;
                      if (i == (nPkCol - 1)) {
                          addrJump = addrUniqueOk;
                          op = OP_Eq;
@@ -1610,8 +1613,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 */
@@ -1629,7 +1632,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));
              }
          }
      }
@@ -1666,27 +1669,23 @@ 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);
+    uint32_t nDestCol = pDest->def->key_def->part_count;
+    uint32_t nSrcCol = pSrc->def->key_def->part_count;
      if (nDestCol != nSrcCol) {
          return 0;    /* Different number of columns */
      }
      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 (i = 0; i < nSrcCol; 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 */
@@ -1858,16 +1857,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 == 0)
+            return 0;
      }
      /* Get server checks. */
      ExprList *pCheck_src = space_checks_expr_list(
@@ -1943,12 +1941,12 @@ 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);
          sqlite3VdbeChangeP5(v, OPFLAG_BULKCSR);
-        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);
diff --git a/src/box/sql/pragma.c b/src/box/sql/pragma.c
index 5fb29c75c..7067a5ab1 100644
--- a/src/box/sql/pragma.c
+++ b/src/box/sql/pragma.c
@@ -370,7 +370,8 @@ sqlite3Pragma(Parse * pParse, Token * pId, /* First 
part of [schema.]id field */
                  k = 1;
              } else {
                  for (k = 1; k <= def->field_count &&
-                     pk->aiColumn[k - 1] != (int) i; ++k) {
+                     pk->def->key_def->parts[k - 1].fieldno
+                     != i; ++k) {
                  }
              }
              bool is_nullable = def->fields[i].is_nullable;
@@ -414,7 +415,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,
@@ -443,11 +444,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,
@@ -461,19 +464,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 <
@@ -503,10 +505,8 @@ sqlite3Pragma(Parse * pParse, Token * pId, /* First 
part of [schema.]id field */
                              { "c", "u", "pk" };
                          sqlite3VdbeMultiLoad(v, 1,
                                       "isisi", i,
-                                     pIdx->
-                                     zName,
-                                     index_is_unique
-                                     (pIdx),
+                                     pIdx->def->name,
+ pIdx->def->opts.is_unique,
                                       azOrigin
                                       [pIdx->
                                        idxType],
diff --git a/src/box/sql/select.c b/src/box/sql/select.c
index 368bcd6f0..c7c186d9d 100644
--- a/src/box/sql/select.c
+++ b/src/box/sql/select.c
@@ -4367,7 +4367,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 47360fa5b..f696591fd 100644
--- a/src/box/sql/sqliteInt.h
+++ b/src/box/sql/sqliteInt.h
@@ -2070,27 +2070,17 @@ struct UnpackedRecord {
   * 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.
+ * Indexes name, corresponding space_id, type (in tarantool
+ * sense - HASH, TREE, etc) are stored in index definition - in
+ * Index.def.
+ * SQL statement which created the index and 'is_unique' flag are
+ * stored in Index.def.opts. Information about index parts (part
+ * count, corresponding space fields' numbers, parts' collations
+ * and sort orders, etc) are stored in Index.def.key_def.parts
+ *
+ * Index.onError indicate the which conflict resolution algorithm
+ * to employ whenever an attempt is made to insert a non-unique
+ * element in unique index.
   *
   * While parsing a CREATE TABLE or CREATE INDEX statement in order to
   * generate VDBE code (as opposed to reading from Tarantool's _space
@@ -2101,26 +2091,18 @@ 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 */
+    struct index_def *def;
  };

  /**
@@ -3546,34 +3528,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 *);

@@ -3661,8 +3615,6 @@ void sqlite3SrcListAssignCursors(Parse *, SrcList *);
  void sqlite3IdListDelete(sqlite3 *, IdList *);
  void sqlite3SrcListDelete(sqlite3 *, SrcList *);
  Index *sqlite3AllocateIndexObject(sqlite3 *, i16, int, char **);
-bool
-index_is_unique(Index *);

  /**
   * Create a new index for an SQL table.  name is the name of the
@@ -4381,8 +4333,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);
diff --git a/src/box/sql/trigger.c b/src/box/sql/trigger.c
index 042226cde..ad8e2438f 100644
--- a/src/box/sql/trigger.c
+++ b/src/box/sql/trigger.c
@@ -873,8 +873,6 @@ codeRowTrigger(Parse * pParse,    /* Current parse 
context */
      pSubParse->pToplevel = pTop;
      pSubParse->eTriggerOp = pTrigger->op;
      pSubParse->nQueryLoop = pParse->nQueryLoop;
-    struct region *region = &fiber()->gc;
-    pSubParse->region_initial_size = region_used(region);

      v = sqlite3GetVdbe(pSubParse);
      if (v) {
diff --git a/src/box/sql/update.c b/src/box/sql/update.c
index 10385eb78..fc479fb05 100644
--- a/src/box/sql/update.c
+++ b/src/box/sql/update.c
@@ -238,14 +238,14 @@ 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);
+        int nIdxCol = pIdx->def->key_def->part_count;
          if (chngPk || hasFK || pIdx->pPartIdxWhere || pIdx == pPk) {
              reg = ++pParse->nMem;
              pParse->nMem += nIdxCol;
          } else {
              reg = 0;
              for (i = 0; i < nIdxCol; i++) {
-                i16 iIdxCol = pIdx->aiColumn[i];
+                i16 iIdxCol = pIdx->def->key_def->parts[i].fieldno;
                  if (iIdxCol < 0 || aXRef[iIdxCol] >= 0) {
                      reg = ++pParse->nMem;
                      pParse->nMem += nIdxCol;
@@ -307,7 +307,7 @@ sqlite3Update(Parse * pParse,        /* The parser 
context */
          nPk = nKey;
      } else {
          assert(pPk != 0);
-        nPk = index_column_count(pPk);
+        nPk = pPk->def->key_def->part_count;
      }
      iPk = pParse->nMem + 1;
      pParse->nMem += nPk;
@@ -334,9 +334,9 @@ sqlite3Update(Parse * pParse,        /* The parser 
context */
          }
      } else {
          for (i = 0; i < nPk; i++) {
-            assert(pPk->aiColumn[i] >= 0);
              sqlite3ExprCodeGetColumnOfTable(v, def, iDataCur,
-                            pPk->aiColumn[i],
+                            pPk->def->key_def->
+                                parts[i].fieldno,
                              iPk + i);
          }
      }
diff --git a/src/box/sql/vdbeaux.c b/src/box/sql/vdbeaux.c
index 679bd0bc1..520b309d9 100644
--- a/src/box/sql/vdbeaux.c
+++ b/src/box/sql/vdbeaux.c
@@ -1165,7 +1165,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 f408b7701..51b5d516e 100644
--- a/src/box/sql/vdbemem.c
+++ b/src/box/sql/vdbemem.c
@@ -1087,7 +1087,7 @@ 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 nCol = pIdx->def->key_def->part_count;

              nByte = sizeof(Mem) * nCol +
                  ROUND8(sizeof(UnpackedRecord));
@@ -1095,7 +1095,7 @@ valueNew(sqlite3 * db, struct ValueNewStat4Ctx *p)
                  (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);
diff --git a/src/box/sql/where.c b/src/box/sql/where.c
index e6c34f34a..3f95c4243 100644
--- a/src/box/sql/where.c
+++ b/src/box/sql/where.c
@@ -372,13 +372,19 @@ whereScanInit(WhereScan * pScan,    /* The 
WhereScan object being initialized */
      pScan->is_column_seen = false;
      if (pIdx) {
          int j = iColumn;
-        iColumn = pIdx->aiColumn[j];
+        iColumn = pIdx->def->key_def->parts[j].fieldno;
+        /*
+         * pIdx->tnum == 0 means that pIdx is a fake
+         * integer primary key index
+         */
+        if (pIdx->tnum == 0)
+            iColumn = -1;
+
          if (iColumn >= 0) {
              char affinity =
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;
          }
      }
@@ -541,47 +547,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 +616,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 +626,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;
          }
@@ -1184,7 +1168,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 +1230,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 *pColl = p->def->key_def->parts[nEq].coll;
      if (pLower) {
          rc = sqlite3Stat4ValueFromExpr(pParse, pLower->pExpr->pRight,
                             aff, &p1);
@@ -1448,7 +1431,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 +1581,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 +1602,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,7 +1734,7 @@ 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 (p->pIndex && (zName = p->pIndex->def->name) != 0) {
          if (strncmp(zName, "sqlite_autoindex_", 17) == 0) {
              int i = sqlite3Strlen30(zName) - 1;
              while (zName[i] != '_')
@@ -2314,7 +2297,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 +2318,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)) {
+        if (pLhs->op != TK_COLUMN || pLhs->iTable != iCur
+            || pLhs->iColumn != (int)pIdx->def->key_def->parts[i + 
nEq].fieldno
+            || pIdx->def->key_def->parts[i + nEq].sort_order !=
+ pIdx->def->key_def->parts[nEq].sort_order)
              break;
-        }

          aff = sqlite3CompareAffinity(pRhs, sqlite3ExprAffinity(pLhs));
          idxaff =
@@ -2353,7 +2334,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 +2377,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 nProbeCol = 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) {
@@ -2452,8 +2433,9 @@ whereLoopAddBtreeIndex(WhereLoopBuilder * 
pBuilder,    /* The WhereLoop factory */
          LogEst nOutUnadjusted;    /* nOut before IN() and WHERE 
adjustments */
          int nIn = 0;
          int nRecValid = pBuilder->nRecValid;
+        uint32_t j = pProbe->def->key_def->parts[saved_nEq].fieldno;
          if ((eOp == WO_ISNULL || (pTerm->wtFlags & TERM_VNULL) != 0)
-            && indexColumnNotNull(pProbe, saved_nEq)
+            && !pProbe->pTable->def->fields[j].is_nullable
              ) {
              continue;    /* ignore IS [NOT] NULL constraints on NOT 
NULL columns */
          }
@@ -2523,14 +2505,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 == nProbeCol - 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 +2576,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;
@@ -2749,7 +2732,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 +2775,7 @@ indexMightHelpWithOrderBy(WhereLoopBuilder * pBuilder,
  {
      ExprList *pOB;
      int ii, jj;
-    int nIdxCol = index_column_count(pIndex);
+    int nIdxCol = pIndex->def->key_def->part_count;
      if (index_is_unordered(pIndex))
          return 0;
      if ((pOB = pBuilder->pWInfo->pOrderBy) == 0)
@@ -2803,7 +2786,8 @@ indexMightHelpWithOrderBy(WhereLoopBuilder * pBuilder,
              if (pExpr->iColumn < 0)
                  return 1;
              for (jj = 0; jj < nIdxCol; jj++) {
-                if (pExpr->iColumn == pIndex->aiColumn[jj])
+                if (pExpr->iColumn == (int)
+ pIndex->def->key_def->parts[jj].fieldno)
                      return 1;
              }
          }
@@ -2882,7 +2866,6 @@ whereLoopAddBtree(WhereLoopBuilder * pBuilder,    
/* WHERE clause information */
      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 */
      SrcList *pTabList;    /* The FROM clause */
      struct SrcList_item *pSrc;    /* The FROM clause btree term to add */
      WhereLoop *pNew;    /* Template WhereLoop object */
@@ -2913,11 +2896,29 @@ whereLoopAddBtree(WhereLoopBuilder * 
pBuilder,    /* WHERE clause information */
           */
          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;
+
+        struct key_def *key_def = key_def_new(1);
+        if (key_def == NULL)
+            return SQLITE_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 index_opts = index_opts_default;
+
+        sPk.def = index_def_new(pTab->def->id, 0, "primary",
+                    sizeof("primary") - 1, TREE, &index_opts,
+                    key_def, NULL);
+        key_def_delete(key_def);
+
+        if (sPk.def == NULL) {
+            return SQLITE_ERROR;
+        }
+
          aiRowEstPk[0] = sql_space_tuple_log_count(pTab);
          aiRowEstPk[1] = 0;
          pFirst = pSrc->pTab->pIndex;
@@ -3392,8 +3393,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 +3455,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);
+                    iColumn = pIndex->def->key_def->
+                        parts[j].fieldno;
+                    revIdx = pIndex->def->key_def->
+                        parts[j].sort_order;
                      if (iColumn == pIndex->pTable->iPKey)
                          iColumn = -1;
                  } else {
@@ -3506,8 +3508,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;
@@ -4785,7 +4786,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
@@ -4918,7 +4919,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 eaab0b657..a04013835 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)));
          }
      }
@@ -1275,12 +1276,12 @@ sqlite3WhereCodeOneLoopStart(WhereInfo * 
pWInfo,    /* Complete information about t
                 || (pWInfo->wctrlFlags & WHERE_ORDERBY_MIN) == 0);
          int nIdxCol;
          if (pIdx != NULL)
-            nIdxCol = index_column_count(pIdx);
+            nIdxCol = 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];
+            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.
@@ -1291,8 +1292,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;
@@ -1331,14 +1331,14 @@ sqlite3WhereCodeOneLoopStart(WhereInfo * 
pWInfo,    /* Complete information about t
                  assert((bRev & ~1) == 0);
                  pLevel->iLikeRepCntr <<= 1;
                  pLevel->iLikeRepCntr |=
-                    bRev ^ (sql_index_column_sort_order(pIdx, nEq) ==
+                    bRev ^ (pIdx->def->key_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;
              }
          }
@@ -1350,7 +1350,7 @@ sqlite3WhereCodeOneLoopStart(WhereInfo * 
pWInfo,    /* Complete information about t
           * start and end terms (pRangeStart and pRangeEnd).
           */
          if ((nEq < nIdxCol &&
-             bRev == (sql_index_column_sort_order(pIdx, nEq) ==
+             bRev == (pIdx->def->key_def->parts[nEq].sort_order ==
                    SORT_ORDER_ASC)) ||
              (bRev && nIdxCol == nEq)) {
              SWAP(pRangeEnd, pRangeStart);
@@ -1433,13 +1433,14 @@ sqlite3WhereCodeOneLoopStart(WhereInfo * 
pWInfo,    /* Complete information about t
              }
          } else {
              pk = sqlite3PrimaryKeyIndex(pIdx->pTable);
+            uint32_t fieldno = pk->def->key_def->parts[0].fieldno;
              affinity =
- pIdx->pTable->def->fields[pk->aiColumn[0]].affinity;
+ pIdx->pTable->def->fields[fieldno].affinity;
          }

          int nPkCol;
          if (pk != NULL)
-            nPkCol = index_column_count(pk);
+            nPkCol = pk->def->key_def->part_count;
          else
              nPkCol = idx_pk->key_def->part_count;
          if (nPkCol == 1 && affinity == AFFINITY_INTEGER) {
@@ -1450,8 +1451,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)) {
@@ -1563,10 +1565,10 @@ 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 nPkCol = pPk->def->key_def->part_count;
              int iKeyReg = sqlite3GetTempRange(pParse, nPkCol);
              for (j = 0; j < nPkCol; j++) {
-                k = pPk->aiColumn[j];
+                k = pPk->def->key_def->parts[j].fieldno;
                  sqlite3VdbeAddOp3(v, OP_Column, iIdxCur, k,
                            iKeyReg + j);
              }
@@ -1671,7 +1673,7 @@ 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 nPkCol = pPk->def->key_def->part_count;
              regRowset = pParse->nTab++;
              sqlite3VdbeAddOp2(v, OP_OpenTEphemeral,
                        regRowset, nPkCol);
@@ -1775,13 +1777,16 @@ 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 nPk = pPk->def->key_def->part_count;
                          int iPk;

                          /* Read the PK into an array of temp registers. */
                          r = sqlite3GetTempRange(pParse, nPk);
                          for (iPk = 0; iPk < nPk; iPk++) {
-                            int iCol = pPk->aiColumn[iPk];
+                            int iCol = pPk->def->
+                                key_def->
+                                parts[iPk].
+                                fieldno;
                              sqlite3ExprCodeGetColumnToReg
                                  (pParse, pTab->def,
                                   iCol, iCur,
-- 

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

* [tarantool-patches] Re: [PATCH v4] sql: add index_def to struct Index
  2018-06-21 12:57   ` [tarantool-patches] Re: [PATCH v4] " Ivan Koptelov
@ 2018-06-22  8:46     ` Kirill Shcherbatov
  2018-06-27 17:46       ` [tarantool-patches] Re: [PATCH v5] " Ivan Koptelov
  0 siblings, 1 reply; 23+ messages in thread
From: Kirill Shcherbatov @ 2018-06-22  8:46 UTC (permalink / raw)
  To: tarantool-patches, Ivan Koptelov

Hi! Thank you for your patch. I'd like to tell that personally I hate netpicks like "same as 17"(to change variable name to tarantool codetyle on minor refactoring changes - you'll understand what I mean a bit later) and strongly recoment to ask Vlad, is this netpick really nessesary to fix.
As for me, I've refactored code this way so many times... So, I've wrote comments "Same as 17", but don't like them, really). Keep it in mind, You could use autorenaming feature of your IDE for this purpose.

The biggest problem now is writing in NULL ptr with malloc on region_alloc failure in append_string_part.

Here we go: 1. Take a look into commit history: you have picked an extra commit 'sql: remove zName and nColumn from SQL' on rebase.
As you see, it contains not too much changes; and they are wrong (compilation error).
+++ b/src/box/sql/trigger.c
@@ -873,6 +873,8 @@ codeRowTrigger(Parse * pParse,      /* Current parse context */
        pSubParse->pToplevel = pTop;
        pSubParse->eTriggerOp = pTrigger->op;
        pSubParse->nQueryLoop = pParse->nQueryLoop;
+       struct region *region = &fiber()->gc;
+       pSubParse->region_initial_size = region_used(region);

Use interactive rebase to drop this commit.

2. I still have compilation error on gcc build:
/home/kir/tarantool/src/box/sql/fkey.c: In function ‘fkScanChildren’:
/home/kir/tarantool/src/box/sql/fkey.c:653:47: error: signed and unsigned type in conditional expression [-Werror=sign-compare]
          pIdx->def->key_def->parts[i].fieldno : -1;
                                               ^
/home/kir/tarantool/src/box/sql/fkey.c: At top level:
cc1: error: unrecognized command line option ‘-Wno-format-truncation’ [-Werror]
cc1: all warnings being treated as errors
src/box/sql/CMakeFiles/sql.dir/build.make:374: recipe for target 'src/box/sql/CMakeFiles/sql.dir/fkey.c.o' failed
make[2]: *** [src/box/sql/CMakeFiles/sql.dir/fkey.c.o] Error 1
make[2]: *** Waiting for unfinished jobs....

You need to cast fieldno to int.

> Here is the patch:
> --
> 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
> and zName are removed from Index. All usages of this
> fields changed to usage of corresponding index_def
> fields.
> index_is_unique(), sql_index_collation() and
> index_column_count() are removed with calls of
> index_def corresponding fields.
> 
> Closes: #3369
> ---
> Branch: 
> https://github.com/tarantool/tarantool/tree/sb/gh-3369-use-index-def-in-select-and-where
> Issue: https://github.com/tarantool/tarantool/issues/3369
>   src/box/sql.c           |  54 +++---
>   src/box/sql/analyze.c   |  40 ++--
>   src/box/sql/build.c     | 488 
> ++++++++++++++++++++++++------------------------
>   src/box/sql/delete.c    |  10 +-
>   src/box/sql/expr.c      |  48 ++---
>   src/box/sql/fkey.c      |  48 ++---
>   src/box/sql/insert.c    |  76 ++++----
>   src/box/sql/pragma.c    |  28 +--
>   src/box/sql/select.c    |   2 +-
>   src/box/sql/sqliteInt.h |  74 ++------
>   src/box/sql/trigger.c   |   2 -
>   src/box/sql/update.c    |  10 +-
>   src/box/sql/vdbeaux.c   |   2 +-
>   src/box/sql/vdbemem.c   |   4 +-
>   src/box/sql/where.c     | 151 +++++++--------
>   src/box/sql/wherecode.c |  51 ++---
>   16 files changed, 517 insertions(+), 571 deletions(-)
> 
> diff --git a/src/box/sql.c b/src/box/sql.c
> index 82f3d6d52..a24812c65 100644
> --- a/src/box/sql.c
> +++ b/src/box/sql.c
> @@ -1452,8 +1452,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;
>       }
> @@ -1564,20 +1564,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;
>       }
> 
> @@ -1587,46 +1586,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..ca699ecd9 100644
> --- a/src/box/sql/analyze.c
> +++ b/src/box/sql/analyze.c
> @@ -849,7 +849,6 @@ analyzeOneTable(Parse * pParse,    /* Parser context */
>           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 */
> 
>           if (pOnlyIdx && pOnlyIdx != pIdx)
>               continue;
> @@ -860,9 +859,9 @@ analyzeOneTable(Parse * pParse,    /* Parser context */
>           if (IsPrimaryKeyIndex(pIdx)) {
>               zIdxName = pTab->def->name;
>           } else {
> -            zIdxName = pIdx->zName;
> +            zIdxName = pIdx->def->name;
>           }
3. Oh, extra braces here. Hate such diffs too, dude...
 > -        nColTest = index_column_count(pIdx);
> +        int nColTest = pIdx->def->key_def->part_count;
> 
>           /* Populate the register containing the index name. */
>           sqlite3VdbeLoadString(v, regIdxname, zIdxName);
> @@ -917,7 +916,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:
>            *
> @@ -969,7 +968,7 @@ analyzeOneTable(Parse * pParse,    /* Parser context */
>                */
>               sqlite3VdbeAddOp0(v, OP_Goto);
>               addrNextRow = sqlite3VdbeCurrentAddr(v);
> -            if (nColTest == 1 && index_is_unique(pIdx)) {
> +            if (nColTest == 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 +977,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 < nColTest; ++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,
> @@ -1006,7 +1004,8 @@ analyzeOneTable(Parse * pParse,    /* Parser 
> context */
>               for (i = 0; i < nColTest; i++) {
>                   sqlite3VdbeJumpHere(v, aGotoChng[i]);
>                   sqlite3VdbeAddOp3(v, OP_Column, iIdxCur,
> -                          pIdx->aiColumn[i],
> +                          pIdx->def->key_def->
> +                              parts[i].fieldno,
>                             regPrev + i);
>               }
>               sqlite3VdbeResolveLabel(v, endDistinctTest);
> @@ -1022,15 +1021,14 @@ 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));
> +        int nPkColumn = (int) pPk->def->key_def->part_count;
> +        int regKeyStat = sqlite3GetTempRange(pParse, nPkColumn);
4. Perhaps, Vlad would intend on tarantool-style names for new variables...Please, change.

> +        for (int j = 0; j < nPkColumn; ++j) {
> +            int k = pPk->def->key_def->parts[j].fieldno;
> +            assert(k >= 0 && k < (int) 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);
> @@ -1146,7 +1144,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");
>       }
> diff --git a/src/box/sql/build.c b/src/box/sql/build.c
> index 592c9a6fa..84e481de3 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)
5. Extra space before tab.

> +        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;
> @@ -376,7 +379,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);
> @@ -1041,7 +1044,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)
> @@ -1049,22 +1052,20 @@ 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; pIdx = pIdx->pNext) {
> +            assert(pIdx->def->key_def->part_count == 1);
> +            if (pIdx->def->key_def->parts[0].fieldno == i) {
> +                pIdx->def->key_def->parts[0].coll_id = *id;
> +                pIdx->def->key_def->parts[0].coll =
>                       sql_column_collation(p->def, i, id);
>               }
>           }
> -    } else {
> -        sqlite3DbFree(db, zColl);
>       }
> +    sqlite3DbFree(db, zColl);
>   }
> 
>   struct coll *
> @@ -1094,66 +1095,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)
>   {
> @@ -1337,14 +1278,16 @@ 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)
> +hasColumn(const struct key_part *key_parts, int nCol, uint32_t fieldno)
6. Please, find a better name for this function and make small new doxygen-stryle comment.
 >   {
> -    while (nCol-- > 0)
> -        if (x == *(aiCol++))
> +    int i = 0;
> +    while (i < nCol) {
> +        if (fieldno == key_parts->fieldno)
>               return 1;
> +        key_parts++;
> +        i++;
> +    }
>       return 0;
>   }
> 
> @@ -1364,13 +1307,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;
> @@ -1408,14 +1350,28 @@ convertToWithoutRowidTable(Parse * pParse, Table 
> * pTab)
>            * "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];
> +
> +        struct key_part *parts = pPk->def->key_def->parts;
> +        uint32_t part_count = pPk->def->key_def->part_count;
> +        uint32_t new_part_count = part_count;
> +
> +        for (uint32_t i = 1; i < part_count; i++) {
> +            if (hasColumn(parts, i, parts[i].fieldno)){
7. Please, compound directly with 0 via != operator.

> +                new_part_count--;
> +                bool is_found = false;
> +                for (uint32_t j = i + 1; j < part_count; j++){
> +                    if (!(hasColumn(parts, j,
8. same

> +                            parts[j].fieldno))) {
> +                        parts[i] = parts[j];
> +                        is_found = true;
> +                        break;
> +                    }
> +                }
> +                if (!(is_found))
9. extra braces?

> +                    break;
>               }
>           }
> -        pPk->nColumn = j;
> +        pPk->def->key_def->part_count = new_part_count;
>       }
>       assert(pPk != 0);
>   }
> @@ -1497,7 +1453,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);
> @@ -1534,7 +1490,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) {
> @@ -2463,15 +2419,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.
> @@ -2504,7 +2461,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);
> @@ -2542,24 +2500,13 @@ sqlite3AllocateIndexObject(sqlite3 * db,    /* 
> Database connection */
>       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 */
> +        ROUND8(sizeof(LogEst) * (nCol + 1));    /* Index.aiRowLogEst */
>       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;
>       }
>       return p;
> @@ -2648,18 +2595,136 @@ addIndexToTable(Index * pIndex, Table * pTab)
>       }
>   }
> 
> -bool
> -index_is_unique(Index *idx)
> +static void
> +append_string_part(struct region *r, const char *str,
> +          size_t *total_sql_size, Parse *parse)
>   {
> -    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);
10. please, calculate size_t strlen and reuse it.

> +    char * str_part = region_alloc(r, strlen(str> +    if (str_part == NULL){
> +        diag_set(OutOfMemory, strlen(str),
> +             "region_alloc", "str_part");
> +        parse->rc = SQL_TARANTOOL_ERROR;
> +        parse->nErr++;
> +    }
11. You'll write to NULL ptr on region_alloc failure.
12. I also don't like function interface: struct Parse already contain region that you specify as a first argument.
You better change it's signature to have only STRUCT Parser as a first argument or to  return -1 on failure (later, rc |= append_string_part... rc |= append_string_part.. if (rc != 0) ..)
13. I also suggest to make a good doxygen-style comment.

> +    memcpy(str_part, str, strlen(str));
> +    *total_sql_size += strlen(str);
> +}
> +
> +static void
> +set_index_def(Parse *parse, Index *index, Table *table, uint32_t iid,
> +          const char *name, uint32_t name_len, int on_error,
> +          struct ExprList *expr_list, u8 idx_type)
> +{
> +    struct space_def *space_def = table->def;
> +    struct index_opts opts;
> +    index_opts_create(&opts);
> +    opts.is_unique = on_error != ON_CONFLICT_ACTION_NONE;
> +
> +    struct key_def *key_def = key_def_new(expr_list->nExpr);
> +    if (key_def == NULL) {
> +        parse->rc = SQL_TARANTOOL_ERROR;
> +        parse->nErr++;
> +        goto cleanup;
> +    }
> +
> +    /*
> +     * Build initial parts of SQL statement.
> +     */
14. Please, make it a single line.

> +
> +    struct region *r = &parse->region;
> +    size_t total_sql_size = 0;
> +
> +    if (idx_type == SQLITE_IDXTYPE_APPDEF) {
> +        append_string_part(r, "CREATE INDEX ", &total_sql_size,
> +                   parse);
> +        append_string_part(r, name, &total_sql_size, parse);
> +        append_string_part(r, " ON ", &total_sql_size, parse);
> +        append_string_part(r, space_def->name, &total_sql_size,
> +                   parse);
> +        append_string_part(r, " (", &total_sql_size, parse);
> +    }
> +
> +    for (int i = 0; i < expr_list->nExpr; i++) {
> +        Expr *expr = expr_list->a[i].pExpr;
> +        sql_resolve_self_reference(parse, table, NC_IdxExpr, expr, 0);
> +        if (parse->nErr > 0)
> +            goto cleanup;
> +
> +        Expr *column_expr = sqlite3ExprSkipCollate(expr);
> +        if (column_expr->op != TK_COLUMN) {
> +            sqlite3ErrorMsg(parse,
> +                    "functional indexes aren't supported "
> +                    "in the current version");
> +            goto cleanup;
> +        }
> +
> +        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 (idx_type == SQLITE_IDXTYPE_APPDEF) {
> +                append_string_part(r, name,
> +                           &total_sql_size, parse);
> +                append_string_part(r, " COLLATE ",
> +                           &total_sql_size, parse);
> +                const char *coll_name = expr->u.zToken;
> +                append_string_part(r, coll_name,
> +                           &total_sql_size, parse);
> +                append_string_part(r, ", ",
> +                           &total_sql_size, parse);
> +            }
> +        } else {
> +            coll = sql_column_collation(space_def, fieldno,
> +                            &coll_id);
> +            if (idx_type == SQLITE_IDXTYPE_APPDEF) {
> +                append_string_part(r, name,
> +                           &total_sql_size, parse);
> +                append_string_part(r, ", ",
> +                           &total_sql_size, parse);
> +            }
> +        }
> 
> -    return tnt_index->def->opts.is_unique;
> +        /*
> +        * Tarantool: DESC indexes are not supported so far.
> +        * See gh-3016.
> +        */
> +        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) {
> +        index->def = NULL;
> +        goto cleanup;
> +    }
> +
> +    if (idx_type == SQLITE_IDXTYPE_APPDEF) {
> +        memcpy(region_alloc(r, 1), "\0", 1);
> +        total_sql_size += 1;
> +        opts.sql = region_join(r, total_sql_size);
> +
> +        /*
> +         * fix last ", " with ")\0" to finish the statement.
14, Please, start with capital letter. And also out-of-comment-type-margin.

> +         */
> +        opts.sql[total_sql_size - 3] = ')';
> +        opts.sql[total_sql_size - 2] = '\0';
15. Why don't you memcpy(&opts.sql[total_sql_size - 3], ")\0", 2); ? And why do you need two null-terminators? Could the string be shotter?

> +    }
> +
> +    struct key_def *pk_key_def;
> +    if (idx_type == SQLITE_IDXTYPE_APPDEF)
> +        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);
> +    cleanup:
> +        if (key_def != NULL)
> +            key_def_delete(key_def);
16. Invalid indent. cleanup shoud be at the beginnig of the line.

>   }
> 
>   void
> @@ -2668,16 +2733,14 @@ sql_create_index(struct Parse *parse, struct 
> Token *token,
>            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;
> +    Table *pTab = NULL;    /* Table to be indexed */
> +    Index *pIndex = NULL;    /* The index to be created */
> +    char *name = NULL;    /* Name of the index */
> +    int name_len;        /* Number of characters in zName */
17. Please, write new code in Tarantool style. I mean snake_style for variables names and comments on the line before terminated with dot.  /* Table to be indexed. */
struct Table *table = NULL;
  >       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 */
> +    char *zExtra = NULL;    /* Extra space after the Index object */
>       struct session *user_session = current_session();
> 
>       if (db->mallocFailed || parse->nErr > 0) {
> @@ -2749,24 +2812,24 @@ sql_create_index(struct Parse *parse, struct 
> Token *token,
>        * our own name.
>        */
>       if (token) {
> -        zName = sqlite3NameFromToken(db, token);
> -        if (zName == 0)
> +        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) !=
> +            if (sqlite3HashFind(&db->pSchema->tblHash, name) !=
>                   NULL) {
>                   sqlite3ErrorMsg(parse,
>                           "there is already a table named %s",
> -                        zName);
> +                        name);
>                   goto exit_create_index;
>               }
>           }
> -        if (sqlite3HashFind(&pTab->idxHash, zName) != NULL) {
> +        if (sqlite3HashFind(&pTab->idxHash, name) != NULL) {
>               if (!if_not_exist) {
>                   sqlite3ErrorMsg(parse,
>                           "index %s.%s already exists",
> -                        pTab->def->name, zName);
> +                        pTab->def->name, name);
>               } else {
>                   assert(!db->init.busy);
>               }
> @@ -2778,10 +2841,9 @@ sql_create_index(struct Parse *parse, struct 
> Token *token,
>           for (pLoop = pTab->pIndex, n = 1; pLoop;
>                pLoop = pLoop->pNext, n++) {
>           }
> -        zName =
> -            sqlite3MPrintf(db, "sqlite_autoindex_%s_%d", pTab->def->name,
> -                   n);
> -        if (zName == 0) {
> +        name = sqlite3MPrintf(db, "sqlite_autoindex_%s_%d",
> +                      pTab->def->name, n);
> +        if (name == NULL) {
>               goto exit_create_index;
>           }
18. Extra braces.

>       }
> @@ -2807,31 +2869,27 @@ 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));
> -        }
> -    }
> -
>       /*
>        * Allocate the index structure.
>        */
> -    nName = sqlite3Strlen30(zName);
> +    name_len = sqlite3Strlen30(name);
> +
> +    if (name_len > BOX_NAME_MAX) {
> +        sqlite3ErrorMsg(parse,
> +                "%s.%s exceeds indexes' names length limit",
> +                pTab->def->name, name);
> +        goto exit_create_index;
> +    }
> +
> +    if (sqlite3CheckIdentifierName(parse, name) != SQLITE_OK)
> +        goto exit_create_index;
> +
>       pIndex = sqlite3AllocateIndexObject(db, col_list->nExpr,
> -                        nName + nExtra + 1, &zExtra);
> +                        name_len + nExtra + 1, &zExtra);
>       if (db->mallocFailed) {
>           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;
>       /*
> @@ -2846,7 +2904,6 @@ sql_create_index(struct Parse *parse, struct Token 
> *token,
>           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,
> @@ -2855,59 +2912,27 @@ sql_create_index(struct Parse *parse, struct 
> Token *token,
>           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.
>        */
> -    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");
> -            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;
> -        }
> -        pIndex->coll_array[i] = coll;
> -        pIndex->coll_id_array[i] = id;
> +    uint32_t max_iid = 0;
> +    for (Index *index = pTab->pIndex; index; index = index->pNext) {
> +        max_iid = max_iid > index->def->iid ?
> +              max_iid :
> +              index->def->iid + 1;
19. Not like this...

> +    }
> 
> -        /* Tarantool: DESC indexes are not supported so far.
> -         * See gh-3016.
> -         */
> -        pIndex->sort_order[i] = SORT_ORDER_ASC;
> +    set_index_def(parse, pIndex, pTab, max_iid, name, name_len, on_error,
> +              col_list, idx_type);
> +
> +    if (pIndex->def == NULL ||
> +        !index_def_is_valid(pIndex->def, pTab->def->name)) {
> +        goto exit_create_index;
>       }
20. Extra braces.

> +
>       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
> @@ -2932,25 +2957,27 @@ sql_create_index(struct Parse *parse, struct 
> Token *token,
>            */
>           Index *pIdx;
>           for (pIdx = pTab->pIndex; pIdx; pIdx = pIdx->pNext) {
> -            int k;
> +            uint32_t k;
>               assert(IsUniqueIndex(pIdx));
>               assert(pIdx->idxType != SQLITE_IDXTYPE_APPDEF);
>               assert(IsUniqueIndex(pIndex));
> 
> -            if (pIdx->nColumn != pIndex->nColumn)
> +            if (pIdx->def->key_def->part_count !=
> +                pIndex->def->key_def->part_count) {
>                   continue;
21. same
> -            for (k = 0; k < pIdx->nColumn; k++) {
> -                assert(pIdx->aiColumn[k] >= 0);
> -                if (pIdx->aiColumn[k] != pIndex->aiColumn[k])
> +            }
> +            for (k = 0; k < pIdx->def->key_def->part_count; k++) {
> +                if (pIdx->def->key_def->parts[k].fieldno !=
> + pIndex->def->key_def->parts[k].fieldno) {
>                       break;
> +                }
22. same

>                   struct coll *coll1, *coll2;
> -                uint32_t id;
> -                coll1 = sql_index_collation(pIdx, k, &id);
> -                coll2 = sql_index_collation(pIndex, k, &id);
> +                coll1 = pIdx->def->key_def->parts[k].coll;
> +                coll2 = pIndex->def->key_def->parts[k].coll;
>                   if (coll1 != coll2)
>                       break;
>               }
> -            if (k == pIdx->nColumn) {
> +            if (k == pIdx->def->key_def->part_count) {
>                   if (pIdx->onError != pIndex->onError) {
>                       /* This constraint creates the same index as a 
> previous
>                        * constraint specified somewhere in the CREATE 
> TABLE statement.
> @@ -2984,7 +3011,7 @@ sql_create_index(struct Parse *parse, struct Token 
> *token,
>       assert(parse->nErr == 0);
>       if (db->init.busy) {
>           Index *p;
> -        p = sqlite3HashInsert(&pTab->idxHash, pIndex->zName, pIndex);
> +        p = sqlite3HashInsert(&pTab->idxHash, pIndex->def->name, pIndex);
>           if (p) {
>               assert(p == pIndex);    /* Malloc must have failed */
>               sqlite3OomFault(db);
> @@ -3082,44 +3109,7 @@ sql_create_index(struct Parse *parse, struct 
> Token *token,
>       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
> @@ -3745,9 +3735,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);
> @@ -3768,11 +3758,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 8b13f6077..931a15a60 100644
> --- a/src/box/sql/delete.c
> +++ b/src/box/sql/delete.c
> @@ -269,11 +269,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 {
> @@ -569,13 +570,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) {
23. Extra whitespace in tab area (visible in git diff).

>               /*
>                * This column was already computed by the
>                * previous index.
> diff --git a/src/box/sql/expr.c b/src/box/sql/expr.c
> index f03c7a3cd..b752084d4 100644
> --- a/src/box/sql/expr.c
> +++ b/src/box/sql/expr.c
> @@ -2423,20 +2423,24 @@ 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);
24. What the testcase is? Why ">part_count" is a valid construction? Drop them all.

> +                if (part_count >= BMS - 1)
>                       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 */
> +                    if ((int)part_count > nExpr
> +                        || !pIdx->def->opts.is_unique) {
> +                        /* This index is not unique over the IN RHS 
> columns */
> +                        continue;
>                       }
>                   }
> 
> @@ -2450,12 +2454,13 @@ 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) {
>                               continue;
> @@ -2484,18 +2489,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
> @@ -2517,7 +2521,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);
>                           }
>                       }
> @@ -3148,12 +3152,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
> +            tab->def->fields[fieldno].affinity;
> +        if (pk->def->key_def->part_count == 1
>               && affinity == AFFINITY_INTEGER
> -            && pk->aiColumn[0] < nVector) {
> -            int reg_pk = rLhs + pk->aiColumn[0];
> +            && (int) fieldno < nVector) { int reg_pk = rLhs + (int)fieldno;
>               sqlite3VdbeAddOp2(v, OP_MustBeInt, reg_pk, destIfFalse);
>           }
>       }
> @@ -3485,7 +3489,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 e3fff37fe..c5fec3161 100644
> --- a/src/box/sql/fkey.c
> +++ b/src/box/sql/fkey.c
> @@ -257,8 +257,8 @@ sqlite3FkLocateIndex(Parse * pParse,    /* Parse 
> context to store any error in */
>       }
> 
>       for (pIdx = pParent->pIndex; pIdx; pIdx = pIdx->pNext) {
> -        int nIdxCol = index_column_count(pIdx);
> -        if (nIdxCol == nCol && index_is_unique(pIdx)
> +        int nIdxCol = pIdx->def->key_def->part_count;
25. same as 17

> +        if (nIdxCol == nCol && pIdx->def->opts.is_unique
>               && 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
> @@ -287,8 +287,10 @@ sqlite3FkLocateIndex(Parse * pParse,    /* Parse 
> context to store any error in */
>                    * the default collation sequences for each column.
>                    */
>                   int i, j;
> -                for (i = 0; i < nCol; i++) {
> -                    i16 iCol = pIdx->aiColumn[i];    /* Index of column 
> in parent tbl */
26. Move comment to prev. line, terminate it with dot.

> +                struct key_part *part =
> +                    pIdx->def->key_def->parts;
> +                for (i = 0; i < nCol; i++, part++) {
> +                    i16 iCol = (int) part->fieldno;    /* Index of 
> column in parent tbl */
>                       char *zIdxCol;    /* Name of indexed column */
> 
>                       if (iCol < 0)
> @@ -303,9 +305,7 @@ sqlite3FkLocateIndex(Parse * pParse,    /* Parse 
> context to store any error in */
>                       def_coll = sql_column_collation(pParent->def,
>                                       iCol,
>                                       &id);
> -                    struct coll *coll =
> -                        sql_index_collation(pIdx, i,
> -                                    &id);
> +                    struct coll *coll = part->coll;
>                       if (def_coll != coll)
>                           break;
> 
> @@ -465,13 +465,15 @@ fkLookupParent(Parse * pParse,    /* Parse context */
>                   for (i = 0; i < nCol; i++) {
>                       int iChild = aiCol[i] + 1 + regData;
>                       int iParent =
> -                        pIdx->aiColumn[i] + 1 + regData;
> -                    assert(pIdx->aiColumn[i] >= 0);
> +                        (int) pIdx->def->key_def->parts[i].fieldno
> +                        + 1 + regData;
>                       assert(aiCol[i] != pTab->iPKey);
> -                    if (pIdx->aiColumn[i] == pTab->iPKey) {
> +                    if ((int)pIdx->def->key_def->
> +                        parts[i].fieldno == pTab->iPKey) {
>                           /* The parent key is a composite key that 
> includes the IPK column */
>                           iParent = regData;
>                       }
> +
>                       sqlite3VdbeAddOp3(v, OP_Ne, iChild,
>                                 iJump, iParent);
>                       VdbeCoverage(v);
> @@ -623,7 +625,7 @@ fkScanChildren(Parse * pParse,    /* Parse context */
>       Vdbe *v = sqlite3GetVdbe(pParse);
> 
>       assert(pIdx == 0 || pIdx->pTable == pTab);
> -    assert(pIdx == 0 || (int)index_column_count(pIdx) == pFKey->nCol);
> +    assert(pIdx == 0 || (int) pIdx->def->key_def->part_count == 
> pFKey->nCol);
27. pIdx == NULL

>       assert(pIdx != 0);
> 
>       if (nIncr < 0) {
> @@ -647,7 +649,8 @@ fkScanChildren(Parse * pParse,    /* Parse context */
>           i16 iCol;    /* Index of column in child table */
>           const char *zCol;    /* Name of column in child table */
> 
> -        iCol = pIdx ? pIdx->aiColumn[i] : -1;
> +        iCol = pIdx != NULL ?
> +               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);
> @@ -672,10 +675,9 @@ fkScanChildren(Parse * pParse,    /* Parse context */
>           Expr *pEq, *pAll = 0;
>           Index *pPk = sqlite3PrimaryKeyIndex(pTab);
>           assert(pIdx != 0);
> -        int col_count = index_column_count(pPk);
> +        int col_count = pPk->def->key_def->part_count;
>           for (i = 0; i < col_count; i++) {
> -            i16 iCol = pIdx->aiColumn[i];
> -            assert(iCol >= 0);
> +            i16 iCol = (int) pIdx->def->key_def->parts[i].fieldno;
28. same as 17.

>               pLeft = exprTableRegister(pParse, pTab, regData, iCol);
>               pRight =
>                   exprTableColumn(db, pTab->def,
> @@ -982,7 +984,6 @@ sqlite3FkCheck(Parse * pParse,    /* Parse context */
>               if (aiCol[i] == pTab->iPKey) {
>                   aiCol[i] = -1;
>               }
> -            assert(pIdx == 0 || pIdx->aiColumn[i] >= 0);
>           }
> 
>           pParse->nTab++;
> @@ -1116,10 +1117,10 @@ sqlite3FkOldmask(Parse * pParse,    /* Parse 
> context */
>               Index *pIdx = 0;
>               sqlite3FkLocateIndex(pParse, pTab, p, &pIdx, 0);
>               if (pIdx) {
> -                int nIdxCol = index_column_count(pIdx);
> +                int nIdxCol = pIdx->def->key_def->part_count;
29. same as 17.

>                   for (i = 0; i < nIdxCol; i++) {
> -                    assert(pIdx->aiColumn[i] >= 0);
> -                    mask |= COLUMN_MASK(pIdx->aiColumn[i]);
> +                    mask |= COLUMN_MASK(pIdx->def->
> +                        key_def->parts[i].fieldno);
>                   }
>               }
>           }
> @@ -1254,11 +1255,12 @@ fkActionTrigger(Parse * pParse,    /* Parse 
> context */
>                      || (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 70555c3ec..b535763e9 100644
> --- a/src/box/sql/insert.c
> +++ b/src/box/sql/insert.c
> @@ -90,14 +90,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;
>           }
>       }
> 
> @@ -1089,7 +1089,7 @@ sqlite3GenerateConstraintChecks(Parse * pParse,    
>      /* The parser context */
>       nCol = def->field_count;
> 
>       pPk = sqlite3PrimaryKeyIndex(pTab);
> -    nPkField = index_column_count(pPk);
> +    nPkField = pPk->def->key_def->part_count;
> 
>       /* Record that this module has started */
>       VdbeModuleComment((v, "BEGIN: GenCnstCks(%d,%d,%d,%d,%d)",
> @@ -1253,10 +1253,10 @@ 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);
> +        int nIdxCol = (int) pIdx->def->key_def->part_count;
30. same as 17

>           if (uniqueByteCodeNeeded) {
>               for (i = 0; i < nIdxCol; ++i) {
> -                int fieldno = pIdx->aiColumn[i];
> +                int fieldno = pIdx->def->key_def->parts[i].fieldno;
>                   int reg;
>                   /*
>                    * OP_SCopy copies value in
> @@ -1284,8 +1284,12 @@ sqlite3GenerateConstraintChecks(Parse * 
> pParse,        /* The parser context */
>               /* 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]] ==
> +                reg_pk = regNewData + 1 +
> + pIdx->def->key_def->parts[0].fieldno;
> +
> +                int fieldno = (int) pIdx->def->key_def->
> +                    parts[0].fieldno;
> +                if (pTab->zColAff[fieldno] ==
>                       AFFINITY_INTEGER) {
>                       int skip_if_null = sqlite3VdbeMakeLabel(v);
>                       if ((pTab->tabFlags & TF_Autoincrement) != 0) {
> @@ -1303,7 +1307,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
> @@ -1391,7 +1395,7 @@ 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);
> 
> @@ -1401,14 +1405,13 @@ sqlite3GenerateConstraintChecks(Parse * 
> pParse,        /* The parser context */
>                                    nPkField);
>           if (isUpdate || on_error == ON_CONFLICT_ACTION_REPLACE) {
>               int x;
> -            int nPkCol = index_column_count(pPk);
> +            int nPkCol = pPk->def->key_def->part_count;
31. same as 17

>               /* 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];
> +                    x = pPk->def->key_def->parts[i].fieldno;
>                       sqlite3VdbeAddOp3(v, OP_Column,
>                                 iThisCur, x, regR + i);
>                       VdbeComment((v, "%s.%s", def->name,
> @@ -1430,10 +1433,10 @@ sqlite3GenerateConstraintChecks(Parse * 
> pParse,        /* The parser context */
>                             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);
> +                    char *p4 = (char *) pPk->def->key_def->parts[i].coll;
> +                    x = pPk->def->key_def->parts[i].fieldno;
> +                    if (pPk->tnum==0)
> +                        x = -1;
>                       if (i == (nPkCol - 1)) {
>                           addrJump = addrUniqueOk;
>                           op = OP_Eq;
> @@ -1610,8 +1613,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 */
> @@ -1629,7 +1632,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));
>               }
>           }
>       }
> @@ -1666,27 +1669,23 @@ 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);
> +    uint32_t nDestCol = pDest->def->key_def->part_count;
> +    uint32_t nSrcCol = pSrc->def->key_def->part_count;
32. same as 17

>       if (nDestCol != nSrcCol) {
>           return 0;    /* Different number of columns */
>       }
>       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 (i = 0; i < nSrcCol; 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 */
> @@ -1858,16 +1857,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 == 0)
33. Terminate comment with dot, pSrcIdx == NULL

> +            return 0;
>       }
>       /* Get server checks. */
>       ExprList *pCheck_src = space_checks_expr_list(
> @@ -1943,12 +1941,12 @@ 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);
>           sqlite3VdbeChangeP5(v, OPFLAG_BULKCSR);
> -        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);
> diff --git a/src/box/sql/pragma.c b/src/box/sql/pragma.c
> index 5fb29c75c..7067a5ab1 100644
> --- a/src/box/sql/pragma.c
> +++ b/src/box/sql/pragma.c
> @@ -370,7 +370,8 @@ sqlite3Pragma(Parse * pParse, Token * pId, /* First 
> part of [schema.]id field */
>                   k = 1;
>               } else {
>                   for (k = 1; k <= def->field_count &&
> -                     pk->aiColumn[k - 1] != (int) i; ++k) {
> +                     pk->def->key_def->parts[k - 1].fieldno
> +                     != i; ++k) {
>                   }
>               }
>               bool is_nullable = def->fields[i].is_nullable;
> @@ -414,7 +415,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,
> @@ -443,11 +444,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,
> @@ -461,19 +464,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 <
> @@ -503,10 +505,8 @@ sqlite3Pragma(Parse * pParse, Token * pId, /* First 
> part of [schema.]id field */
>                               { "c", "u", "pk" };
>                           sqlite3VdbeMultiLoad(v, 1,
>                                        "isisi", i,
> -                                     pIdx->
> -                                     zName,
> -                                     index_is_unique
> -                                     (pIdx),
> +                                     pIdx->def->name,
> + pIdx->def->opts.is_unique,
>                                        azOrigin
>                                        [pIdx->
>                                         idxType],
> diff --git a/src/box/sql/select.c b/src/box/sql/select.c
> index 368bcd6f0..c7c186d9d 100644
> --- a/src/box/sql/select.c
> +++ b/src/box/sql/select.c
> @@ -4367,7 +4367,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 47360fa5b..f696591fd 100644
> --- a/src/box/sql/sqliteInt.h
> +++ b/src/box/sql/sqliteInt.h
> @@ -2070,27 +2070,17 @@ struct UnpackedRecord {
>    * 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.
> + * Indexes name, corresponding space_id, type (in tarantool
> + * sense - HASH, TREE, etc) are stored in index definition - in
> + * Index.def.
> + * SQL statement which created the index and 'is_unique' flag are
> + * stored in Index.def.opts. Information about index parts (part
> + * count, corresponding space fields' numbers, parts' collations
> + * and sort orders, etc) are stored in Index.def.key_def.parts
> + *
> + * Index.onError indicate the which conflict resolution algorithm
> + * to employ whenever an attempt is made to insert a non-unique
> + * element in unique index.
>    *
>    * While parsing a CREATE TABLE or CREATE INDEX statement in order to
>    * generate VDBE code (as opposed to reading from Tarantool's _space
> @@ -2101,26 +2091,18 @@ 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 */
> +    struct index_def *def;
34.
>>>> + struct index_def *def;
>>>> };>> 10. Will you keep an informative tarantool-style comment here?
> Ok, added comment.
I still can't see a comment for index_def field in Index struct in your patch.
/** Smth like this. */

>   };
> 
>   /**
> @@ -3546,34 +3528,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 *);
> 
> @@ -3661,8 +3615,6 @@ void sqlite3SrcListAssignCursors(Parse *, SrcList *);
>   void sqlite3IdListDelete(sqlite3 *, IdList *);
>   void sqlite3SrcListDelete(sqlite3 *, SrcList *);
>   Index *sqlite3AllocateIndexObject(sqlite3 *, i16, int, char **);
> -bool
> -index_is_unique(Index *);
> 
>   /**
>    * Create a new index for an SQL table.  name is the name of the
> @@ -4381,8 +4333,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);
> diff --git a/src/box/sql/trigger.c b/src/box/sql/trigger.c
> index 042226cde..ad8e2438f 100644
> --- a/src/box/sql/trigger.c
> +++ b/src/box/sql/trigger.c
> @@ -873,8 +873,6 @@ codeRowTrigger(Parse * pParse,    /* Current parse 
> context */
>       pSubParse->pToplevel = pTop;
>       pSubParse->eTriggerOp = pTrigger->op;
>       pSubParse->nQueryLoop = pParse->nQueryLoop;
> -    struct region *region = &fiber()->gc;
> -    pSubParse->region_initial_size = region_used(region);
> 
>       v = sqlite3GetVdbe(pSubParse);
>       if (v) {
> diff --git a/src/box/sql/update.c b/src/box/sql/update.c
> index 10385eb78..fc479fb05 100644
> --- a/src/box/sql/update.c
> +++ b/src/box/sql/update.c
> @@ -238,14 +238,14 @@ 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);
> +        int nIdxCol = pIdx->def->key_def->part_count;
35. Same as 17

>           if (chngPk || hasFK || pIdx->pPartIdxWhere || pIdx == pPk) {
>               reg = ++pParse->nMem;
>               pParse->nMem += nIdxCol;
>           } else {
>               reg = 0;
>               for (i = 0; i < nIdxCol; i++) {
> -                i16 iIdxCol = pIdx->aiColumn[i];
> +                i16 iIdxCol = pIdx->def->key_def->parts[i].fieldno;
36. Same as 17

>                   if (iIdxCol < 0 || aXRef[iIdxCol] >= 0) {
>                       reg = ++pParse->nMem;
>                       pParse->nMem += nIdxCol;
> @@ -307,7 +307,7 @@ sqlite3Update(Parse * pParse,        /* The parser 
> context */
>           nPk = nKey;
>       } else {
>           assert(pPk != 0);
> -        nPk = index_column_count(pPk);
> +        nPk = pPk->def->key_def->part_count;
37. Same as 17

>       }
>       iPk = pParse->nMem + 1;
>       pParse->nMem += nPk;
> @@ -334,9 +334,9 @@ sqlite3Update(Parse * pParse,        /* The parser 
> context */
>           }
>       } else {
>           for (i = 0; i < nPk; i++) {
> -            assert(pPk->aiColumn[i] >= 0);
>               sqlite3ExprCodeGetColumnOfTable(v, def, iDataCur,
> -                            pPk->aiColumn[i],
> +                            pPk->def->key_def->
> +                                parts[i].fieldno,
>                               iPk + i);
>           }
>       }
> diff --git a/src/box/sql/vdbeaux.c b/src/box/sql/vdbeaux.c
> index 679bd0bc1..520b309d9 100644
> --- a/src/box/sql/vdbeaux.c
> +++ b/src/box/sql/vdbeaux.c
> @@ -1165,7 +1165,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 f408b7701..51b5d516e 100644
> --- a/src/box/sql/vdbemem.c
> +++ b/src/box/sql/vdbemem.c
> @@ -1087,7 +1087,7 @@ 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 nCol = pIdx->def->key_def->part_count;
38. Same as 17..
> 
>               nByte = sizeof(Mem) * nCol +
>                   ROUND8(sizeof(UnpackedRecord));
> @@ -1095,7 +1095,7 @@ valueNew(sqlite3 * db, struct ValueNewStat4Ctx *p)
>                   (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);
> diff --git a/src/box/sql/where.c b/src/box/sql/where.c
> index e6c34f34a..3f95c4243 100644
> --- a/src/box/sql/where.c
> +++ b/src/box/sql/where.c
> @@ -372,13 +372,19 @@ whereScanInit(WhereScan * pScan,    /* The 
> WhereScan object being initialized */
>       pScan->is_column_seen = false;
>       if (pIdx) {
>           int j = iColumn;
> -        iColumn = pIdx->aiColumn[j];
> +        iColumn = pIdx->def->key_def->parts[j].fieldno;
> +        /*
> +         * pIdx->tnum == 0 means that pIdx is a fake
> +         * integer primary key index
> +         */
> +        if (pIdx->tnum == 0)
> +            iColumn = -1;
> +
>           if (iColumn >= 0) {
>               char affinity =
> 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;
>           }
>       }
> @@ -541,47 +547,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 +616,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 +626,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;
>           }
> @@ -1184,7 +1168,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 +1230,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 *pColl = p->def->key_def->parts[nEq].coll;
39. Same as 17
>       if (pLower) {
>           rc = sqlite3Stat4ValueFromExpr(pParse, pLower->pExpr->pRight,
>                              aff, &p1);
> @@ -1448,7 +1431,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 +1581,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 +1602,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,7 +1734,7 @@ 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 (p->pIndex && (zName = p->pIndex->def->name) != 0) {
>           if (strncmp(zName, "sqlite_autoindex_", 17) == 0) {
>               int i = sqlite3Strlen30(zName) - 1;
>               while (zName[i] != '_')
> @@ -2314,7 +2297,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 +2318,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)) {
> +        if (pLhs->op != TK_COLUMN || pLhs->iTable != iCur
> +            || pLhs->iColumn != (int)pIdx->def->key_def->parts[i + 
> nEq].fieldno
> +            || pIdx->def->key_def->parts[i + nEq].sort_order !=
> + pIdx->def->key_def->parts[nEq].sort_order)
>               break;
> -        }
> 
>           aff = sqlite3CompareAffinity(pRhs, sqlite3ExprAffinity(pLhs));
>           idxaff =
> @@ -2353,7 +2334,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 +2377,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 nProbeCol = 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) {
> @@ -2452,8 +2433,9 @@ whereLoopAddBtreeIndex(WhereLoopBuilder * 
> pBuilder,    /* The WhereLoop factory */
>           LogEst nOutUnadjusted;    /* nOut before IN() and WHERE 
> adjustments */
>           int nIn = 0;
>           int nRecValid = pBuilder->nRecValid;
> +        uint32_t j = pProbe->def->key_def->parts[saved_nEq].fieldno;
>           if ((eOp == WO_ISNULL || (pTerm->wtFlags & TERM_VNULL) != 0)
> -            && indexColumnNotNull(pProbe, saved_nEq)
> +            && !pProbe->pTable->def->fields[j].is_nullable
>               ) {
>               continue;    /* ignore IS [NOT] NULL constraints on NOT 
> NULL columns */
>           }
> @@ -2523,14 +2505,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 == nProbeCol - 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 +2576,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;
> @@ -2749,7 +2732,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 +2775,7 @@ indexMightHelpWithOrderBy(WhereLoopBuilder * pBuilder,
>   {
>       ExprList *pOB;
>       int ii, jj;
> -    int nIdxCol = index_column_count(pIndex);
> +    int nIdxCol = pIndex->def->key_def->part_count;
>       if (index_is_unordered(pIndex))
>           return 0;
>       if ((pOB = pBuilder->pWInfo->pOrderBy) == 0)
> @@ -2803,7 +2786,8 @@ indexMightHelpWithOrderBy(WhereLoopBuilder * pBuilder,
>               if (pExpr->iColumn < 0)
>                   return 1;
>               for (jj = 0; jj < nIdxCol; jj++) {
> -                if (pExpr->iColumn == pIndex->aiColumn[jj])
> +                if (pExpr->iColumn == (int)
> + pIndex->def->key_def->parts[jj].fieldno)
>                       return 1;
>               }
>           }
> @@ -2882,7 +2866,6 @@ whereLoopAddBtree(WhereLoopBuilder * pBuilder,    
> /* WHERE clause information */
>       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 */
>       SrcList *pTabList;    /* The FROM clause */
>       struct SrcList_item *pSrc;    /* The FROM clause btree term to add */
>       WhereLoop *pNew;    /* Template WhereLoop object */
> @@ -2913,11 +2896,29 @@ whereLoopAddBtree(WhereLoopBuilder * 
> pBuilder,    /* WHERE clause information */
>            */
>           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;
> +
> +        struct key_def *key_def = key_def_new(1);
> +        if (key_def == NULL)
> +            return SQLITE_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 index_opts = index_opts_default;
> +
> +        sPk.def = index_def_new(pTab->def->id, 0, "primary",
> +                    sizeof("primary") - 1, TREE, &index_opts,
> +                    key_def, NULL);
> +        key_def_delete(key_def);
> +
> +        if (sPk.def == NULL) {
> +            return SQLITE_ERROR;
> +        }
39. Extra braces.
> +
>           aiRowEstPk[0] = sql_space_tuple_log_count(pTab);
>           aiRowEstPk[1] = 0;
>           pFirst = pSrc->pTab->pIndex;
> @@ -3392,8 +3393,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 +3455,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);
> +                    iColumn = pIndex->def->key_def->
> +                        parts[j].fieldno;
> +                    revIdx = pIndex->def->key_def->
> +                        parts[j].sort_order;
>                       if (iColumn == pIndex->pTable->iPKey)
>                           iColumn = -1;
>                   } else {
> @@ -3506,8 +3508,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;
> @@ -4785,7 +4786,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
> @@ -4918,7 +4919,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 eaab0b657..a04013835 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)));
>           }
>       }
> @@ -1275,12 +1276,12 @@ sqlite3WhereCodeOneLoopStart(WhereInfo * 
> pWInfo,    /* Complete information about t
>                  || (pWInfo->wctrlFlags & WHERE_ORDERBY_MIN) == 0);
>           int nIdxCol;
>           if (pIdx != NULL)
> -            nIdxCol = index_column_count(pIdx);
> +            nIdxCol = 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];
> +            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.
> @@ -1291,8 +1292,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;
> @@ -1331,14 +1331,14 @@ sqlite3WhereCodeOneLoopStart(WhereInfo * 
> pWInfo,    /* Complete information about t
>                   assert((bRev & ~1) == 0);
>                   pLevel->iLikeRepCntr <<= 1;
>                   pLevel->iLikeRepCntr |=
> -                    bRev ^ (sql_index_column_sort_order(pIdx, nEq) ==
> +                    bRev ^ (pIdx->def->key_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;
>               }
>           }
> @@ -1350,7 +1350,7 @@ sqlite3WhereCodeOneLoopStart(WhereInfo * 
> pWInfo,    /* Complete information about t
>            * start and end terms (pRangeStart and pRangeEnd).
>            */
>           if ((nEq < nIdxCol &&
> -             bRev == (sql_index_column_sort_order(pIdx, nEq) ==
> +             bRev == (pIdx->def->key_def->parts[nEq].sort_order ==
>                     SORT_ORDER_ASC)) ||
>               (bRev && nIdxCol == nEq)) {
>               SWAP(pRangeEnd, pRangeStart);
> @@ -1433,13 +1433,14 @@ sqlite3WhereCodeOneLoopStart(WhereInfo * 
> pWInfo,    /* Complete information about t
>               }
>           } else {
>               pk = sqlite3PrimaryKeyIndex(pIdx->pTable);
> +            uint32_t fieldno = pk->def->key_def->parts[0].fieldno;
>               affinity =
> - pIdx->pTable->def->fields[pk->aiColumn[0]].affinity;
> + pIdx->pTable->def->fields[fieldno].affinity;
>           }
> 
>           int nPkCol;
>           if (pk != NULL)
> -            nPkCol = index_column_count(pk);
> +            nPkCol = pk->def->key_def->part_count;
>           else
>               nPkCol = idx_pk->key_def->part_count;
>           if (nPkCol == 1 && affinity == AFFINITY_INTEGER) {
> @@ -1450,8 +1451,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)) {
> @@ -1563,10 +1565,10 @@ 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 nPkCol = pPk->def->key_def->part_count;
41. Same as 17

>               int iKeyReg = sqlite3GetTempRange(pParse, nPkCol);
>               for (j = 0; j < nPkCol; j++) {
> -                k = pPk->aiColumn[j];
> +                k = pPk->def->key_def->parts[j].fieldno;
>                   sqlite3VdbeAddOp3(v, OP_Column, iIdxCur, k,
>                             iKeyReg + j);
>               }
> @@ -1671,7 +1673,7 @@ 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 nPkCol = pPk->def->key_def->part_count;
42. Same as 17

>               regRowset = pParse->nTab++;
>               sqlite3VdbeAddOp2(v, OP_OpenTEphemeral,
>                         regRowset, nPkCol);
> @@ -1775,13 +1777,16 @@ 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 nPk = pPk->def->key_def->part_count;
>                           int iPk;
> 
>                           /* Read the PK into an array of temp registers. */
>                           r = sqlite3GetTempRange(pParse, nPk);
>                           for (iPk = 0; iPk < nPk; iPk++) {
> -                            int iCol = pPk->aiColumn[iPk];
> +                            int iCol = pPk->def->
43. Same as 17
> +                                key_def->
> +                                parts[iPk].
> +                                fieldno;
>                               sqlite3ExprCodeGetColumnToReg
>                                   (pParse, pTab->def,
>                                    iCol, iCur,
> 

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

* [tarantool-patches] Re: [PATCH v5] sql: add index_def to struct Index
  2018-06-22  8:46     ` Kirill Shcherbatov
@ 2018-06-27 17:46       ` Ivan Koptelov
  2018-06-27 17:57         ` Kirill Shcherbatov
  0 siblings, 1 reply; 23+ messages in thread
From: Ivan Koptelov @ 2018-06-27 17:46 UTC (permalink / raw)
  To: Kirill Shcherbatov, tarantool-patches

Thank for the review! The new ver. of the patch is at the end of mail.
> Hi! Thank you for your patch. I'd like to tell that personally I hate netpicks like "same as 17"(to change variable name to tarantool codetyle on minor refactoring changes - you'll understand what I mean a bit later) and strongly recoment to ask Vlad, is this netpick really nessesary to fix.
> As for me, I've refactored code this way so many times... So, I've wrote comments "Same as 17", but don't like them, really). Keep it in mind, You could use autorenaming feature of your IDE for this purpose.
>
> The biggest problem now is writing in NULL ptr with malloc on region_alloc failure in append_string_part.
>
> Here we go: 1. Take a look into commit history: you have picked an extra commit 'sql: remove zName and nColumn from SQL' on rebase.
> As you see, it contains not too much changes; and they are wrong (compilation error).
> +++ b/src/box/sql/trigger.c
> @@ -873,6 +873,8 @@ codeRowTrigger(Parse * pParse,      /* Current parse context */
>          pSubParse->pToplevel = pTop;
>          pSubParse->eTriggerOp = pTrigger->op;
>          pSubParse->nQueryLoop = pParse->nQueryLoop;
> +       struct region *region = &fiber()->gc;
> +       pSubParse->region_initial_size = region_used(region);
>
> Use interactive rebase to drop this commit.
I don't remember, why it was needed. Rebased onto 2.0.
> 2. I still have compilation error on gcc build:
> /home/kir/tarantool/src/box/sql/fkey.c: In function ‘fkScanChildren’:
> /home/kir/tarantool/src/box/sql/fkey.c:653:47: error: signed and unsigned type in conditional expression [-Werror=sign-compare]
>            pIdx->def->key_def->parts[i].fieldno : -1;
>                                                 ^
> /home/kir/tarantool/src/box/sql/fkey.c: At top level:
> cc1: error: unrecognized command line option ‘-Wno-format-truncation’ [-Werror]
> cc1: all warnings being treated as errors
> src/box/sql/CMakeFiles/sql.dir/build.make:374: recipe for target 'src/box/sql/CMakeFiles/sql.dir/fkey.c.o' failed
> make[2]: *** [src/box/sql/CMakeFiles/sql.dir/fkey.c.o] Error 1
> make[2]: *** Waiting for unfinished jobs....
>
> You need to cast fieldno to int.
Fixed.
>> Here is the patch:
>> --
>> 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
>> and zName are removed from Index. All usages of this
>> fields changed to usage of corresponding index_def
>> fields.
>> index_is_unique(), sql_index_collation() and
>> index_column_count() are removed with calls of
>> index_def corresponding fields.
>>
>> Closes: #3369
>> ---
>> Branch:
>> https://github.com/tarantool/tarantool/tree/sb/gh-3369-use-index-def-in-select-and-where
>> Issue: https://github.com/tarantool/tarantool/issues/3369
>>    src/box/sql.c           |  54 +++---
>>    src/box/sql/analyze.c   |  40 ++--
>>    src/box/sql/build.c     | 488
>> ++++++++++++++++++++++++------------------------
>>    src/box/sql/delete.c    |  10 +-
>>    src/box/sql/expr.c      |  48 ++---
>>    src/box/sql/fkey.c      |  48 ++---
>>    src/box/sql/insert.c    |  76 ++++----
>>    src/box/sql/pragma.c    |  28 +--
>>    src/box/sql/select.c    |   2 +-
>>    src/box/sql/sqliteInt.h |  74 ++------
>>    src/box/sql/trigger.c   |   2 -
>>    src/box/sql/update.c    |  10 +-
>>    src/box/sql/vdbeaux.c   |   2 +-
>>    src/box/sql/vdbemem.c   |   4 +-
>>    src/box/sql/where.c     | 151 +++++++--------
>>    src/box/sql/wherecode.c |  51 ++---
>>    16 files changed, 517 insertions(+), 571 deletions(-)
>>
>> diff --git a/src/box/sql.c b/src/box/sql.c
>> index 82f3d6d52..a24812c65 100644
>> --- a/src/box/sql.c
>> +++ b/src/box/sql.c
>> @@ -1452,8 +1452,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;
>>        }
>> @@ -1564,20 +1564,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;
>>        }
>>
>> @@ -1587,46 +1586,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..ca699ecd9 100644
>> --- a/src/box/sql/analyze.c
>> +++ b/src/box/sql/analyze.c
>> @@ -849,7 +849,6 @@ analyzeOneTable(Parse * pParse,    /* Parser context */
>>            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 */
>>
>>            if (pOnlyIdx && pOnlyIdx != pIdx)
>>                continue;
>> @@ -860,9 +859,9 @@ analyzeOneTable(Parse * pParse,    /* Parser context */
>>            if (IsPrimaryKeyIndex(pIdx)) {
>>                zIdxName = pTab->def->name;
>>            } else {
>> -            zIdxName = pIdx->zName;
>> +            zIdxName = pIdx->def->name;
>>            }
> 3. Oh, extra braces here. Hate such diffs too, dude...
>   > -        nColTest = index_column_count(pIdx);
Fxd.
>> +        int nColTest = pIdx->def->key_def->part_count;
>>
>>            /* Populate the register containing the index name. */
>>            sqlite3VdbeLoadString(v, regIdxname, zIdxName);
>> @@ -917,7 +916,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:
>>             *
>> @@ -969,7 +968,7 @@ analyzeOneTable(Parse * pParse,    /* Parser context */
>>                 */
>>                sqlite3VdbeAddOp0(v, OP_Goto);
>>                addrNextRow = sqlite3VdbeCurrentAddr(v);
>> -            if (nColTest == 1 && index_is_unique(pIdx)) {
>> +            if (nColTest == 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 +977,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 < nColTest; ++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,
>> @@ -1006,7 +1004,8 @@ analyzeOneTable(Parse * pParse,    /* Parser
>> context */
>>                for (i = 0; i < nColTest; i++) {
>>                    sqlite3VdbeJumpHere(v, aGotoChng[i]);
>>                    sqlite3VdbeAddOp3(v, OP_Column, iIdxCur,
>> -                          pIdx->aiColumn[i],
>> +                          pIdx->def->key_def->
>> +                              parts[i].fieldno,
>>                              regPrev + i);
>>                }
>>                sqlite3VdbeResolveLabel(v, endDistinctTest);
>> @@ -1022,15 +1021,14 @@ 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));
>> +        int nPkColumn = (int) pPk->def->key_def->part_count;
>> +        int regKeyStat = sqlite3GetTempRange(pParse, nPkColumn);
> 4. Perhaps, Vlad would intend on tarantool-style names for new variables...Please, change.
Discussed with Vlad verbally, fixed where necessary.
>> +        for (int j = 0; j < nPkColumn; ++j) {
>> +            int k = pPk->def->key_def->parts[j].fieldno;
>> +            assert(k >= 0 && k < (int) 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);
>> @@ -1146,7 +1144,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");
>>        }
>> diff --git a/src/box/sql/build.c b/src/box/sql/build.c
>> index 592c9a6fa..84e481de3 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)
> 5. Extra space before tab.
Fxd.
>
>> +        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;
>> @@ -376,7 +379,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);
>> @@ -1041,7 +1044,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)
>> @@ -1049,22 +1052,20 @@ 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; pIdx = pIdx->pNext) {
>> +            assert(pIdx->def->key_def->part_count == 1);
>> +            if (pIdx->def->key_def->parts[0].fieldno == i) {
>> +                pIdx->def->key_def->parts[0].coll_id = *id;
>> +                pIdx->def->key_def->parts[0].coll =
>>                        sql_column_collation(p->def, i, id);
>>                }
>>            }
>> -    } else {
>> -        sqlite3DbFree(db, zColl);
>>        }
>> +    sqlite3DbFree(db, zColl);
>>    }
>>
>>    struct coll *
>> @@ -1094,66 +1095,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)
>>    {
>> @@ -1337,14 +1278,16 @@ 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)
>> +hasColumn(const struct key_part *key_parts, int nCol, uint32_t fieldno)
> 6. Please, find a better name for this function and make small new doxygen-stryle comment.
>   >   {
Done. Also changed return type to 'bool'
>> -    while (nCol-- > 0)
>> -        if (x == *(aiCol++))
>> +    int i = 0;
>> +    while (i < nCol) {
>> +        if (fieldno == key_parts->fieldno)
>>                return 1;
>> +        key_parts++;
>> +        i++;
>> +    }
>>        return 0;
>>    }
>>
>> @@ -1364,13 +1307,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;
>> @@ -1408,14 +1350,28 @@ convertToWithoutRowidTable(Parse * pParse, Table
>> * pTab)
>>             * "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];
>> +
>> +        struct key_part *parts = pPk->def->key_def->parts;
>> +        uint32_t part_count = pPk->def->key_def->part_count;
>> +        uint32_t new_part_count = part_count;
>> +
>> +        for (uint32_t i = 1; i < part_count; i++) {
>> +            if (hasColumn(parts, i, parts[i].fieldno)){
> 7. Please, compound directly with 0 via != operator.
Not actual.
>
>> +                new_part_count--;
>> +                bool is_found = false;
>> +                for (uint32_t j = i + 1; j < part_count; j++){
>> +                    if (!(hasColumn(parts, j,
> 8. same
Not actual, function returns bool now
>
>> +                            parts[j].fieldno))) {
>> +                        parts[i] = parts[j];
>> +                        is_found = true;
>> +                        break;
>> +                    }
>> +                }
>> +                if (!(is_found))
> 9. extra braces?
Fixed.
>
>> +                    break;
>>                }
>>            }
>> -        pPk->nColumn = j;
>> +        pPk->def->key_def->part_count = new_part_count;
>>        }
>>        assert(pPk != 0);
>>    }
>> @@ -1497,7 +1453,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);
>> @@ -1534,7 +1490,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) {
>> @@ -2463,15 +2419,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.
>> @@ -2504,7 +2461,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);
>> @@ -2542,24 +2500,13 @@ sqlite3AllocateIndexObject(sqlite3 * db,    /*
>> Database connection */
>>        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 */
>> +        ROUND8(sizeof(LogEst) * (nCol + 1));    /* Index.aiRowLogEst */
>>        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;
>>        }
>>        return p;
>> @@ -2648,18 +2595,136 @@ addIndexToTable(Index * pIndex, Table * pTab)
>>        }
>>    }
>>
>> -bool
>> -index_is_unique(Index *idx)
>> +static void
>> +append_string_part(struct region *r, const char *str,
>> +          size_t *total_sql_size, Parse *parse)
>>    {
>> -    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);
> 10. please, calculate size_t strlen and reuse it.
>
>> +    char * str_part = region_alloc(r, strlen(str> +    if (str_part == NULL){
>> +        diag_set(OutOfMemory, strlen(str),
>> +             "region_alloc", "str_part");
>> +        parse->rc = SQL_TARANTOOL_ERROR;
>> +        parse->nErr++;
>> +    }
> 11. You'll write to NULL ptr on region_alloc failure.
> 12. I also don't like function interface: struct Parse already contain region that you specify as a first argument.
> You better change it's signature to have only STRUCT Parser as a first argument or to  return -1 on failure (later, rc |= append_string_part... rc |= append_string_part.. if (rc != 0) ..)
> 13. I also suggest to make a good doxygen-style comment.
>
Fixed, added comment.
>> +    memcpy(str_part, str, strlen(str));
>> +    *total_sql_size += strlen(str);
>> +}
>> +
>> +static void
>> +set_index_def(Parse *parse, Index *index, Table *table, uint32_t iid,
>> +          const char *name, uint32_t name_len, int on_error,
>> +          struct ExprList *expr_list, u8 idx_type)
>> +{
>> +    struct space_def *space_def = table->def;
>> +    struct index_opts opts;
>> +    index_opts_create(&opts);
>> +    opts.is_unique = on_error != ON_CONFLICT_ACTION_NONE;
>> +
>> +    struct key_def *key_def = key_def_new(expr_list->nExpr);
>> +    if (key_def == NULL) {
>> +        parse->rc = SQL_TARANTOOL_ERROR;
>> +        parse->nErr++;
>> +        goto cleanup;
>> +    }
>> +
>> +    /*
>> +     * Build initial parts of SQL statement.
>> +     */
> 14. Please, make it a single line.
Done.
>
>> +
>> +    struct region *r = &parse->region;
>> +    size_t total_sql_size = 0;
>> +
>> +    if (idx_type == SQLITE_IDXTYPE_APPDEF) {
>> +        append_string_part(r, "CREATE INDEX ", &total_sql_size,
>> +                   parse);
>> +        append_string_part(r, name, &total_sql_size, parse);
>> +        append_string_part(r, " ON ", &total_sql_size, parse);
>> +        append_string_part(r, space_def->name, &total_sql_size,
>> +                   parse);
>> +        append_string_part(r, " (", &total_sql_size, parse);
>> +    }
>> +
>> +    for (int i = 0; i < expr_list->nExpr; i++) {
>> +        Expr *expr = expr_list->a[i].pExpr;
>> +        sql_resolve_self_reference(parse, table, NC_IdxExpr, expr, 0);
>> +        if (parse->nErr > 0)
>> +            goto cleanup;
>> +
>> +        Expr *column_expr = sqlite3ExprSkipCollate(expr);
>> +        if (column_expr->op != TK_COLUMN) {
>> +            sqlite3ErrorMsg(parse,
>> +                    "functional indexes aren't supported "
>> +                    "in the current version");
>> +            goto cleanup;
>> +        }
>> +
>> +        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 (idx_type == SQLITE_IDXTYPE_APPDEF) {
>> +                append_string_part(r, name,
>> +                           &total_sql_size, parse);
>> +                append_string_part(r, " COLLATE ",
>> +                           &total_sql_size, parse);
>> +                const char *coll_name = expr->u.zToken;
>> +                append_string_part(r, coll_name,
>> +                           &total_sql_size, parse);
>> +                append_string_part(r, ", ",
>> +                           &total_sql_size, parse);
>> +            }
>> +        } else {
>> +            coll = sql_column_collation(space_def, fieldno,
>> +                            &coll_id);
>> +            if (idx_type == SQLITE_IDXTYPE_APPDEF) {
>> +                append_string_part(r, name,
>> +                           &total_sql_size, parse);
>> +                append_string_part(r, ", ",
>> +                           &total_sql_size, parse);
>> +            }
>> +        }
>>
>> -    return tnt_index->def->opts.is_unique;
>> +        /*
>> +        * Tarantool: DESC indexes are not supported so far.
>> +        * See gh-3016.
>> +        */
>> +        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) {
>> +        index->def = NULL;
>> +        goto cleanup;
>> +    }
>> +
>> +    if (idx_type == SQLITE_IDXTYPE_APPDEF) {
>> +        memcpy(region_alloc(r, 1), "\0", 1);
>> +        total_sql_size += 1;
>> +        opts.sql = region_join(r, total_sql_size);
>> +
>> +        /*
>> +         * fix last ", " with ")\0" to finish the statement.
> 14, Please, start with capital letter. And also out-of-comment-type-margin.
Fxd.
>> +         */
>> +        opts.sql[total_sql_size - 3] = ')';
>> +        opts.sql[total_sql_size - 2] = '\0';
> 15. Why don't you memcpy(&opts.sql[total_sql_size - 3], ")\0", 2); ? And why do you need two null-terminators? Could the string be shotter?
Sorry, fixed it all.
>> +    }
>> +
>> +    struct key_def *pk_key_def;
>> +    if (idx_type == SQLITE_IDXTYPE_APPDEF)
>> +        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);
>> +    cleanup:
>> +        if (key_def != NULL)
>> +            key_def_delete(key_def);
> 16. Invalid indent. cleanup shoud be at the beginnig of the line.
Fxd.
>
>>    }
>>
>>    void
>> @@ -2668,16 +2733,14 @@ sql_create_index(struct Parse *parse, struct
>> Token *token,
>>             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;
>> +    Table *pTab = NULL;    /* Table to be indexed */
>> +    Index *pIndex = NULL;    /* The index to be created */
>> +    char *name = NULL;    /* Name of the index */
>> +    int name_len;        /* Number of characters in zName */
> 17. Please, write new code in Tarantool style. I mean snake_style for variables names and comments on the line before terminated with dot.  /* Table to be indexed. */
> struct Table *table = NULL;
>    >       DbFixer sFix;        /* For assigning database names to pTable */
Fixed.
>>        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 */
>> +    char *zExtra = NULL;    /* Extra space after the Index object */
>>        struct session *user_session = current_session();
>>
>>        if (db->mallocFailed || parse->nErr > 0) {
>> @@ -2749,24 +2812,24 @@ sql_create_index(struct Parse *parse, struct
>> Token *token,
>>         * our own name.
>>         */
>>        if (token) {
>> -        zName = sqlite3NameFromToken(db, token);
>> -        if (zName == 0)
>> +        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) !=
>> +            if (sqlite3HashFind(&db->pSchema->tblHash, name) !=
>>                    NULL) {
>>                    sqlite3ErrorMsg(parse,
>>                            "there is already a table named %s",
>> -                        zName);
>> +                        name);
>>                    goto exit_create_index;
>>                }
>>            }
>> -        if (sqlite3HashFind(&pTab->idxHash, zName) != NULL) {
>> +        if (sqlite3HashFind(&pTab->idxHash, name) != NULL) {
>>                if (!if_not_exist) {
>>                    sqlite3ErrorMsg(parse,
>>                            "index %s.%s already exists",
>> -                        pTab->def->name, zName);
>> +                        pTab->def->name, name);
>>                } else {
>>                    assert(!db->init.busy);
>>                }
>> @@ -2778,10 +2841,9 @@ sql_create_index(struct Parse *parse, struct
>> Token *token,
>>            for (pLoop = pTab->pIndex, n = 1; pLoop;
>>                 pLoop = pLoop->pNext, n++) {
>>            }
>> -        zName =
>> -            sqlite3MPrintf(db, "sqlite_autoindex_%s_%d", pTab->def->name,
>> -                   n);
>> -        if (zName == 0) {
>> +        name = sqlite3MPrintf(db, "sqlite_autoindex_%s_%d",
>> +                      pTab->def->name, n);
>> +        if (name == NULL) {
>>                goto exit_create_index;
>>            }
> 18. Extra braces.
Fixed.
>>        }
>> @@ -2807,31 +2869,27 @@ 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));
>> -        }
>> -    }
>> -
>>        /*
>>         * Allocate the index structure.
>>         */
>> -    nName = sqlite3Strlen30(zName);
>> +    name_len = sqlite3Strlen30(name);
>> +
>> +    if (name_len > BOX_NAME_MAX) {
>> +        sqlite3ErrorMsg(parse,
>> +                "%s.%s exceeds indexes' names length limit",
>> +                pTab->def->name, name);
>> +        goto exit_create_index;
>> +    }
>> +
>> +    if (sqlite3CheckIdentifierName(parse, name) != SQLITE_OK)
>> +        goto exit_create_index;
>> +
>>        pIndex = sqlite3AllocateIndexObject(db, col_list->nExpr,
>> -                        nName + nExtra + 1, &zExtra);
>> +                        name_len + nExtra + 1, &zExtra);
>>        if (db->mallocFailed) {
>>            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;
>>        /*
>> @@ -2846,7 +2904,6 @@ sql_create_index(struct Parse *parse, struct Token
>> *token,
>>            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,
>> @@ -2855,59 +2912,27 @@ sql_create_index(struct Parse *parse, struct
>> Token *token,
>>            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.
>>         */
>> -    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");
>> -            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;
>> -        }
>> -        pIndex->coll_array[i] = coll;
>> -        pIndex->coll_id_array[i] = id;
>> +    uint32_t max_iid = 0;
>> +    for (Index *index = pTab->pIndex; index; index = index->pNext) {
>> +        max_iid = max_iid > index->def->iid ?
>> +              max_iid :
>> +              index->def->iid + 1;
> 19. Not like this...
Fixed.
>
>> +    }
>>
>> -        /* Tarantool: DESC indexes are not supported so far.
>> -         * See gh-3016.
>> -         */
>> -        pIndex->sort_order[i] = SORT_ORDER_ASC;
>> +    set_index_def(parse, pIndex, pTab, max_iid, name, name_len, on_error,
>> +              col_list, idx_type);
>> +
>> +    if (pIndex->def == NULL ||
>> +        !index_def_is_valid(pIndex->def, pTab->def->name)) {
>> +        goto exit_create_index;
>>        }
> 20. Extra braces.
Fixed.
>
>> +
>>        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
>> @@ -2932,25 +2957,27 @@ sql_create_index(struct Parse *parse, struct
>> Token *token,
>>             */
>>            Index *pIdx;
>>            for (pIdx = pTab->pIndex; pIdx; pIdx = pIdx->pNext) {
>> -            int k;
>> +            uint32_t k;
>>                assert(IsUniqueIndex(pIdx));
>>                assert(pIdx->idxType != SQLITE_IDXTYPE_APPDEF);
>>                assert(IsUniqueIndex(pIndex));
>>
>> -            if (pIdx->nColumn != pIndex->nColumn)
>> +            if (pIdx->def->key_def->part_count !=
>> +                pIndex->def->key_def->part_count) {
>>                    continue;
> 21. same
Fixed.
>> -            for (k = 0; k < pIdx->nColumn; k++) {
>> -                assert(pIdx->aiColumn[k] >= 0);
>> -                if (pIdx->aiColumn[k] != pIndex->aiColumn[k])
>> +            }
>> +            for (k = 0; k < pIdx->def->key_def->part_count; k++) {
>> +                if (pIdx->def->key_def->parts[k].fieldno !=
>> + pIndex->def->key_def->parts[k].fieldno) {
>>                        break;
>> +                }
> 22. same
Fixed.
>
>>                    struct coll *coll1, *coll2;
>> -                uint32_t id;
>> -                coll1 = sql_index_collation(pIdx, k, &id);
>> -                coll2 = sql_index_collation(pIndex, k, &id);
>> +                coll1 = pIdx->def->key_def->parts[k].coll;
>> +                coll2 = pIndex->def->key_def->parts[k].coll;
>>                    if (coll1 != coll2)
>>                        break;
>>                }
>> -            if (k == pIdx->nColumn) {
>> +            if (k == pIdx->def->key_def->part_count) {
>>                    if (pIdx->onError != pIndex->onError) {
>>                        /* This constraint creates the same index as a
>> previous
>>                         * constraint specified somewhere in the CREATE
>> TABLE statement.
>> @@ -2984,7 +3011,7 @@ sql_create_index(struct Parse *parse, struct Token
>> *token,
>>        assert(parse->nErr == 0);
>>        if (db->init.busy) {
>>            Index *p;
>> -        p = sqlite3HashInsert(&pTab->idxHash, pIndex->zName, pIndex);
>> +        p = sqlite3HashInsert(&pTab->idxHash, pIndex->def->name, pIndex);
>>            if (p) {
>>                assert(p == pIndex);    /* Malloc must have failed */
>>                sqlite3OomFault(db);
>> @@ -3082,44 +3109,7 @@ sql_create_index(struct Parse *parse, struct
>> Token *token,
>>        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
>> @@ -3745,9 +3735,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);
>> @@ -3768,11 +3758,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 8b13f6077..931a15a60 100644
>> --- a/src/box/sql/delete.c
>> +++ b/src/box/sql/delete.c
>> @@ -269,11 +269,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 {
>> @@ -569,13 +570,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) {
> 23. Extra whitespace in tab area (visible in git diff).
Fixed.
>>                /*
>>                 * This column was already computed by the
>>                 * previous index.
>> diff --git a/src/box/sql/expr.c b/src/box/sql/expr.c
>> index f03c7a3cd..b752084d4 100644
>> --- a/src/box/sql/expr.c
>> +++ b/src/box/sql/expr.c
>> @@ -2423,20 +2423,24 @@ 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);
> 24. What the testcase is? Why ">part_count" is a valid construction? Drop them all.
Fxd.
>
>> +                if (part_count >= BMS - 1)
>>                        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 */
>> +                    if ((int)part_count > nExpr
>> +                        || !pIdx->def->opts.is_unique) {
>> +                        /* This index is not unique over the IN RHS
>> columns */
>> +                        continue;
>>                        }
>>                    }
>>
>> @@ -2450,12 +2454,13 @@ 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) {
>>                                continue;
>> @@ -2484,18 +2489,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
>> @@ -2517,7 +2521,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);
>>                            }
>>                        }
>> @@ -3148,12 +3152,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
>> +            tab->def->fields[fieldno].affinity;
>> +        if (pk->def->key_def->part_count == 1
>>                && affinity == AFFINITY_INTEGER
>> -            && pk->aiColumn[0] < nVector) {
>> -            int reg_pk = rLhs + pk->aiColumn[0];
>> +            && (int) fieldno < nVector) { int reg_pk = rLhs + (int)fieldno;
>>                sqlite3VdbeAddOp2(v, OP_MustBeInt, reg_pk, destIfFalse);
>>            }
>>        }
>> @@ -3485,7 +3489,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 e3fff37fe..c5fec3161 100644
>> --- a/src/box/sql/fkey.c
>> +++ b/src/box/sql/fkey.c
>> @@ -257,8 +257,8 @@ sqlite3FkLocateIndex(Parse * pParse,    /* Parse
>> context to store any error in */
>>        }
>>
>>        for (pIdx = pParent->pIndex; pIdx; pIdx = pIdx->pNext) {
>> -        int nIdxCol = index_column_count(pIdx);
>> -        if (nIdxCol == nCol && index_is_unique(pIdx)
>> +        int nIdxCol = pIdx->def->key_def->part_count;
> 25. same as 17
Fxd.
>
>> +        if (nIdxCol == nCol && pIdx->def->opts.is_unique
>>                && 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
>> @@ -287,8 +287,10 @@ sqlite3FkLocateIndex(Parse * pParse,    /* Parse
>> context to store any error in */
>>                     * the default collation sequences for each column.
>>                     */
>>                    int i, j;
>> -                for (i = 0; i < nCol; i++) {
>> -                    i16 iCol = pIdx->aiColumn[i];    /* Index of column
>> in parent tbl */
> 26. Move comment to prev. line, terminate it with dot.
Fixed.
>
>> +                struct key_part *part =
>> +                    pIdx->def->key_def->parts;
>> +                for (i = 0; i < nCol; i++, part++) {
>> +                    i16 iCol = (int) part->fieldno;    /* Index of
>> column in parent tbl */
>>                        char *zIdxCol;    /* Name of indexed column */
>>
>>                        if (iCol < 0)
>> @@ -303,9 +305,7 @@ sqlite3FkLocateIndex(Parse * pParse,    /* Parse
>> context to store any error in */
>>                        def_coll = sql_column_collation(pParent->def,
>>                                        iCol,
>>                                        &id);
>> -                    struct coll *coll =
>> -                        sql_index_collation(pIdx, i,
>> -                                    &id);
>> +                    struct coll *coll = part->coll;
>>                        if (def_coll != coll)
>>                            break;
>>
>> @@ -465,13 +465,15 @@ fkLookupParent(Parse * pParse,    /* Parse context */
>>                    for (i = 0; i < nCol; i++) {
>>                        int iChild = aiCol[i] + 1 + regData;
>>                        int iParent =
>> -                        pIdx->aiColumn[i] + 1 + regData;
>> -                    assert(pIdx->aiColumn[i] >= 0);
>> +                        (int) pIdx->def->key_def->parts[i].fieldno
>> +                        + 1 + regData;
>>                        assert(aiCol[i] != pTab->iPKey);
>> -                    if (pIdx->aiColumn[i] == pTab->iPKey) {
>> +                    if ((int)pIdx->def->key_def->
>> +                        parts[i].fieldno == pTab->iPKey) {
>>                            /* The parent key is a composite key that
>> includes the IPK column */
>>                            iParent = regData;
>>                        }
>> +
>>                        sqlite3VdbeAddOp3(v, OP_Ne, iChild,
>>                                  iJump, iParent);
>>                        VdbeCoverage(v);
>> @@ -623,7 +625,7 @@ fkScanChildren(Parse * pParse,    /* Parse context */
>>        Vdbe *v = sqlite3GetVdbe(pParse);
>>
>>        assert(pIdx == 0 || pIdx->pTable == pTab);
>> -    assert(pIdx == 0 || (int)index_column_count(pIdx) == pFKey->nCol);
>> +    assert(pIdx == 0 || (int) pIdx->def->key_def->part_count ==
>> pFKey->nCol);
> 27. pIdx == NULL
Fxd.
>
>>        assert(pIdx != 0);
>>
>>        if (nIncr < 0) {
>> @@ -647,7 +649,8 @@ fkScanChildren(Parse * pParse,    /* Parse context */
>>            i16 iCol;    /* Index of column in child table */
>>            const char *zCol;    /* Name of column in child table */
>>
>> -        iCol = pIdx ? pIdx->aiColumn[i] : -1;
>> +        iCol = pIdx != NULL ?
>> +               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);
>> @@ -672,10 +675,9 @@ fkScanChildren(Parse * pParse,    /* Parse context */
>>            Expr *pEq, *pAll = 0;
>>            Index *pPk = sqlite3PrimaryKeyIndex(pTab);
>>            assert(pIdx != 0);
>> -        int col_count = index_column_count(pPk);
>> +        int col_count = pPk->def->key_def->part_count;
>>            for (i = 0; i < col_count; i++) {
>> -            i16 iCol = pIdx->aiColumn[i];
>> -            assert(iCol >= 0);
>> +            i16 iCol = (int) pIdx->def->key_def->parts[i].fieldno;
> 28. same as 17.
Fixed.
>>                pLeft = exprTableRegister(pParse, pTab, regData, iCol);
>>                pRight =
>>                    exprTableColumn(db, pTab->def,
>> @@ -982,7 +984,6 @@ sqlite3FkCheck(Parse * pParse,    /* Parse context */
>>                if (aiCol[i] == pTab->iPKey) {
>>                    aiCol[i] = -1;
>>                }
>> -            assert(pIdx == 0 || pIdx->aiColumn[i] >= 0);
>>            }
>>
>>            pParse->nTab++;
>> @@ -1116,10 +1117,10 @@ sqlite3FkOldmask(Parse * pParse,    /* Parse
>> context */
>>                Index *pIdx = 0;
>>                sqlite3FkLocateIndex(pParse, pTab, p, &pIdx, 0);
>>                if (pIdx) {
>> -                int nIdxCol = index_column_count(pIdx);
>> +                int nIdxCol = pIdx->def->key_def->part_count;
> 29. same as 17.
Fixed.
>>                    for (i = 0; i < nIdxCol; i++) {
>> -                    assert(pIdx->aiColumn[i] >= 0);
>> -                    mask |= COLUMN_MASK(pIdx->aiColumn[i]);
>> +                    mask |= COLUMN_MASK(pIdx->def->
>> +                        key_def->parts[i].fieldno);
>>                    }
>>                }
>>            }
>> @@ -1254,11 +1255,12 @@ fkActionTrigger(Parse * pParse,    /* Parse
>> context */
>>                       || (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 70555c3ec..b535763e9 100644
>> --- a/src/box/sql/insert.c
>> +++ b/src/box/sql/insert.c
>> @@ -90,14 +90,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;
>>            }
>>        }
>>
>> @@ -1089,7 +1089,7 @@ sqlite3GenerateConstraintChecks(Parse * pParse,
>>       /* The parser context */
>>        nCol = def->field_count;
>>
>>        pPk = sqlite3PrimaryKeyIndex(pTab);
>> -    nPkField = index_column_count(pPk);
>> +    nPkField = pPk->def->key_def->part_count;
>>
>>        /* Record that this module has started */
>>        VdbeModuleComment((v, "BEGIN: GenCnstCks(%d,%d,%d,%d,%d)",
>> @@ -1253,10 +1253,10 @@ 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);
>> +        int nIdxCol = (int) pIdx->def->key_def->part_count;
> 30. same as 17
Fixed.
>
>>            if (uniqueByteCodeNeeded) {
>>                for (i = 0; i < nIdxCol; ++i) {
>> -                int fieldno = pIdx->aiColumn[i];
>> +                int fieldno = pIdx->def->key_def->parts[i].fieldno;
>>                    int reg;
>>                    /*
>>                     * OP_SCopy copies value in
>> @@ -1284,8 +1284,12 @@ sqlite3GenerateConstraintChecks(Parse *
>> pParse,        /* The parser context */
>>                /* 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]] ==
>> +                reg_pk = regNewData + 1 +
>> + pIdx->def->key_def->parts[0].fieldno;
>> +
>> +                int fieldno = (int) pIdx->def->key_def->
>> +                    parts[0].fieldno;
>> +                if (pTab->zColAff[fieldno] ==
>>                        AFFINITY_INTEGER) {
>>                        int skip_if_null = sqlite3VdbeMakeLabel(v);
>>                        if ((pTab->tabFlags & TF_Autoincrement) != 0) {
>> @@ -1303,7 +1307,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
>> @@ -1391,7 +1395,7 @@ 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);
>>
>> @@ -1401,14 +1405,13 @@ sqlite3GenerateConstraintChecks(Parse *
>> pParse,        /* The parser context */
>>                                     nPkField);
>>            if (isUpdate || on_error == ON_CONFLICT_ACTION_REPLACE) {
>>                int x;
>> -            int nPkCol = index_column_count(pPk);
>> +            int nPkCol = pPk->def->key_def->part_count;
> 31. same as 17
Fixed.
>
>>                /* 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];
>> +                    x = pPk->def->key_def->parts[i].fieldno;
>>                        sqlite3VdbeAddOp3(v, OP_Column,
>>                                  iThisCur, x, regR + i);
>>                        VdbeComment((v, "%s.%s", def->name,
>> @@ -1430,10 +1433,10 @@ sqlite3GenerateConstraintChecks(Parse *
>> pParse,        /* The parser context */
>>                              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);
>> +                    char *p4 = (char *) pPk->def->key_def->parts[i].coll;
>> +                    x = pPk->def->key_def->parts[i].fieldno;
>> +                    if (pPk->tnum==0)
>> +                        x = -1;
>>                        if (i == (nPkCol - 1)) {
>>                            addrJump = addrUniqueOk;
>>                            op = OP_Eq;
>> @@ -1610,8 +1613,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 */
>> @@ -1629,7 +1632,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));
>>                }
>>            }
>>        }
>> @@ -1666,27 +1669,23 @@ 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);
>> +    uint32_t nDestCol = pDest->def->key_def->part_count;
>> +    uint32_t nSrcCol = pSrc->def->key_def->part_count;
> 32. same as 17
Fixed.
>
>>        if (nDestCol != nSrcCol) {
>>            return 0;    /* Different number of columns */
>>        }
>>        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 (i = 0; i < nSrcCol; 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 */
>> @@ -1858,16 +1857,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 == 0)
> 33. Terminate comment with dot, pSrcIdx == NULL
Fixed.
>> +            return 0;
>>        }
>>        /* Get server checks. */
>>        ExprList *pCheck_src = space_checks_expr_list(
>> @@ -1943,12 +1941,12 @@ 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);
>>            sqlite3VdbeChangeP5(v, OPFLAG_BULKCSR);
>> -        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);
>> diff --git a/src/box/sql/pragma.c b/src/box/sql/pragma.c
>> index 5fb29c75c..7067a5ab1 100644
>> --- a/src/box/sql/pragma.c
>> +++ b/src/box/sql/pragma.c
>> @@ -370,7 +370,8 @@ sqlite3Pragma(Parse * pParse, Token * pId, /* First
>> part of [schema.]id field */
>>                    k = 1;
>>                } else {
>>                    for (k = 1; k <= def->field_count &&
>> -                     pk->aiColumn[k - 1] != (int) i; ++k) {
>> +                     pk->def->key_def->parts[k - 1].fieldno
>> +                     != i; ++k) {
>>                    }
>>                }
>>                bool is_nullable = def->fields[i].is_nullable;
>> @@ -414,7 +415,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,
>> @@ -443,11 +444,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,
>> @@ -461,19 +464,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 <
>> @@ -503,10 +505,8 @@ sqlite3Pragma(Parse * pParse, Token * pId, /* First
>> part of [schema.]id field */
>>                                { "c", "u", "pk" };
>>                            sqlite3VdbeMultiLoad(v, 1,
>>                                         "isisi", i,
>> -                                     pIdx->
>> -                                     zName,
>> -                                     index_is_unique
>> -                                     (pIdx),
>> +                                     pIdx->def->name,
>> + pIdx->def->opts.is_unique,
>>                                         azOrigin
>>                                         [pIdx->
>>                                          idxType],
>> diff --git a/src/box/sql/select.c b/src/box/sql/select.c
>> index 368bcd6f0..c7c186d9d 100644
>> --- a/src/box/sql/select.c
>> +++ b/src/box/sql/select.c
>> @@ -4367,7 +4367,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 47360fa5b..f696591fd 100644
>> --- a/src/box/sql/sqliteInt.h
>> +++ b/src/box/sql/sqliteInt.h
>> @@ -2070,27 +2070,17 @@ struct UnpackedRecord {
>>     * 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.
>> + * Indexes name, corresponding space_id, type (in tarantool
>> + * sense - HASH, TREE, etc) are stored in index definition - in
>> + * Index.def.
>> + * SQL statement which created the index and 'is_unique' flag are
>> + * stored in Index.def.opts. Information about index parts (part
>> + * count, corresponding space fields' numbers, parts' collations
>> + * and sort orders, etc) are stored in Index.def.key_def.parts
>> + *
>> + * Index.onError indicate the which conflict resolution algorithm
>> + * to employ whenever an attempt is made to insert a non-unique
>> + * element in unique index.
>>     *
>>     * While parsing a CREATE TABLE or CREATE INDEX statement in order to
>>     * generate VDBE code (as opposed to reading from Tarantool's _space
>> @@ -2101,26 +2091,18 @@ 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 */
>> +    struct index_def *def;
> 34.
>>>>> + struct index_def *def;
>>>>> };>> 10. Will you keep an informative tarantool-style comment here?
>> Ok, added comment.
> I still can't see a comment for index_def field in Index struct in your patch.
> /** Smth like this. */
All the information about index_def are in comment before struct Index 
definiton.
I am not sure, that something must be added before 'index_def' line.
>>    };
>>
>>    /**
>> @@ -3546,34 +3528,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 *);
>>
>> @@ -3661,8 +3615,6 @@ void sqlite3SrcListAssignCursors(Parse *, SrcList *);
>>    void sqlite3IdListDelete(sqlite3 *, IdList *);
>>    void sqlite3SrcListDelete(sqlite3 *, SrcList *);
>>    Index *sqlite3AllocateIndexObject(sqlite3 *, i16, int, char **);
>> -bool
>> -index_is_unique(Index *);
>>
>>    /**
>>     * Create a new index for an SQL table.  name is the name of the
>> @@ -4381,8 +4333,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);
>> diff --git a/src/box/sql/trigger.c b/src/box/sql/trigger.c
>> index 042226cde..ad8e2438f 100644
>> --- a/src/box/sql/trigger.c
>> +++ b/src/box/sql/trigger.c
>> @@ -873,8 +873,6 @@ codeRowTrigger(Parse * pParse,    /* Current parse
>> context */
>>        pSubParse->pToplevel = pTop;
>>        pSubParse->eTriggerOp = pTrigger->op;
>>        pSubParse->nQueryLoop = pParse->nQueryLoop;
>> -    struct region *region = &fiber()->gc;
>> -    pSubParse->region_initial_size = region_used(region);
>>
>>        v = sqlite3GetVdbe(pSubParse);
>>        if (v) {
>> diff --git a/src/box/sql/update.c b/src/box/sql/update.c
>> index 10385eb78..fc479fb05 100644
>> --- a/src/box/sql/update.c
>> +++ b/src/box/sql/update.c
>> @@ -238,14 +238,14 @@ 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);
>> +        int nIdxCol = pIdx->def->key_def->part_count;
> 35. Same as 17
Fxd.
>
>>            if (chngPk || hasFK || pIdx->pPartIdxWhere || pIdx == pPk) {
>>                reg = ++pParse->nMem;
>>                pParse->nMem += nIdxCol;
>>            } else {
>>                reg = 0;
>>                for (i = 0; i < nIdxCol; i++) {
>> -                i16 iIdxCol = pIdx->aiColumn[i];
>> +                i16 iIdxCol = pIdx->def->key_def->parts[i].fieldno;
> 36. Same as 17
Fxd.
>
>>                    if (iIdxCol < 0 || aXRef[iIdxCol] >= 0) {
>>                        reg = ++pParse->nMem;
>>                        pParse->nMem += nIdxCol;
>> @@ -307,7 +307,7 @@ sqlite3Update(Parse * pParse,        /* The parser
>> context */
>>            nPk = nKey;
>>        } else {
>>            assert(pPk != 0);
>> -        nPk = index_column_count(pPk);
>> +        nPk = pPk->def->key_def->part_count;
> 37. Same as 17
Fxd.
>>        }
>>        iPk = pParse->nMem + 1;
>>        pParse->nMem += nPk;
>> @@ -334,9 +334,9 @@ sqlite3Update(Parse * pParse,        /* The parser
>> context */
>>            }
>>        } else {
>>            for (i = 0; i < nPk; i++) {
>> -            assert(pPk->aiColumn[i] >= 0);
>>                sqlite3ExprCodeGetColumnOfTable(v, def, iDataCur,
>> -                            pPk->aiColumn[i],
>> +                            pPk->def->key_def->
>> +                                parts[i].fieldno,
>>                                iPk + i);
>>            }
>>        }
>> diff --git a/src/box/sql/vdbeaux.c b/src/box/sql/vdbeaux.c
>> index 679bd0bc1..520b309d9 100644
>> --- a/src/box/sql/vdbeaux.c
>> +++ b/src/box/sql/vdbeaux.c
>> @@ -1165,7 +1165,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 f408b7701..51b5d516e 100644
>> --- a/src/box/sql/vdbemem.c
>> +++ b/src/box/sql/vdbemem.c
>> @@ -1087,7 +1087,7 @@ 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 nCol = pIdx->def->key_def->part_count;
> 38. Same as 17..
Fxd.
>>                nByte = sizeof(Mem) * nCol +
>>                    ROUND8(sizeof(UnpackedRecord));
>> @@ -1095,7 +1095,7 @@ valueNew(sqlite3 * db, struct ValueNewStat4Ctx *p)
>>                    (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);
>> diff --git a/src/box/sql/where.c b/src/box/sql/where.c
>> index e6c34f34a..3f95c4243 100644
>> --- a/src/box/sql/where.c
>> +++ b/src/box/sql/where.c
>> @@ -372,13 +372,19 @@ whereScanInit(WhereScan * pScan,    /* The
>> WhereScan object being initialized */
>>        pScan->is_column_seen = false;
>>        if (pIdx) {
>>            int j = iColumn;
>> -        iColumn = pIdx->aiColumn[j];
>> +        iColumn = pIdx->def->key_def->parts[j].fieldno;
>> +        /*
>> +         * pIdx->tnum == 0 means that pIdx is a fake
>> +         * integer primary key index
>> +         */
>> +        if (pIdx->tnum == 0)
>> +            iColumn = -1;
>> +
>>            if (iColumn >= 0) {
>>                char affinity =
>> 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;
>>            }
>>        }
>> @@ -541,47 +547,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 +616,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 +626,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;
>>            }
>> @@ -1184,7 +1168,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 +1230,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 *pColl = p->def->key_def->parts[nEq].coll;
> 39. Same as 17
Fxd.
>>        if (pLower) {
>>            rc = sqlite3Stat4ValueFromExpr(pParse, pLower->pExpr->pRight,
>>                               aff, &p1);
>> @@ -1448,7 +1431,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 +1581,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 +1602,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,7 +1734,7 @@ 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 (p->pIndex && (zName = p->pIndex->def->name) != 0) {
>>            if (strncmp(zName, "sqlite_autoindex_", 17) == 0) {
>>                int i = sqlite3Strlen30(zName) - 1;
>>                while (zName[i] != '_')
>> @@ -2314,7 +2297,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 +2318,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)) {
>> +        if (pLhs->op != TK_COLUMN || pLhs->iTable != iCur
>> +            || pLhs->iColumn != (int)pIdx->def->key_def->parts[i +
>> nEq].fieldno
>> +            || pIdx->def->key_def->parts[i + nEq].sort_order !=
>> + pIdx->def->key_def->parts[nEq].sort_order)
>>                break;
>> -        }
>>
>>            aff = sqlite3CompareAffinity(pRhs, sqlite3ExprAffinity(pLhs));
>>            idxaff =
>> @@ -2353,7 +2334,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 +2377,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 nProbeCol = 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) {
>> @@ -2452,8 +2433,9 @@ whereLoopAddBtreeIndex(WhereLoopBuilder *
>> pBuilder,    /* The WhereLoop factory */
>>            LogEst nOutUnadjusted;    /* nOut before IN() and WHERE
>> adjustments */
>>            int nIn = 0;
>>            int nRecValid = pBuilder->nRecValid;
>> +        uint32_t j = pProbe->def->key_def->parts[saved_nEq].fieldno;
>>            if ((eOp == WO_ISNULL || (pTerm->wtFlags & TERM_VNULL) != 0)
>> -            && indexColumnNotNull(pProbe, saved_nEq)
>> +            && !pProbe->pTable->def->fields[j].is_nullable
>>                ) {
>>                continue;    /* ignore IS [NOT] NULL constraints on NOT
>> NULL columns */
>>            }
>> @@ -2523,14 +2505,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 == nProbeCol - 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 +2576,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;
>> @@ -2749,7 +2732,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 +2775,7 @@ indexMightHelpWithOrderBy(WhereLoopBuilder * pBuilder,
>>    {
>>        ExprList *pOB;
>>        int ii, jj;
>> -    int nIdxCol = index_column_count(pIndex);
>> +    int nIdxCol = pIndex->def->key_def->part_count;
>>        if (index_is_unordered(pIndex))
>>            return 0;
>>        if ((pOB = pBuilder->pWInfo->pOrderBy) == 0)
>> @@ -2803,7 +2786,8 @@ indexMightHelpWithOrderBy(WhereLoopBuilder * pBuilder,
>>                if (pExpr->iColumn < 0)
>>                    return 1;
>>                for (jj = 0; jj < nIdxCol; jj++) {
>> -                if (pExpr->iColumn == pIndex->aiColumn[jj])
>> +                if (pExpr->iColumn == (int)
>> + pIndex->def->key_def->parts[jj].fieldno)
>>                        return 1;
>>                }
>>            }
>> @@ -2882,7 +2866,6 @@ whereLoopAddBtree(WhereLoopBuilder * pBuilder,
>> /* WHERE clause information */
>>        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 */
>>        SrcList *pTabList;    /* The FROM clause */
>>        struct SrcList_item *pSrc;    /* The FROM clause btree term to add */
>>        WhereLoop *pNew;    /* Template WhereLoop object */
>> @@ -2913,11 +2896,29 @@ whereLoopAddBtree(WhereLoopBuilder *
>> pBuilder,    /* WHERE clause information */
>>             */
>>            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;
>> +
>> +        struct key_def *key_def = key_def_new(1);
>> +        if (key_def == NULL)
>> +            return SQLITE_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 index_opts = index_opts_default;
>> +
>> +        sPk.def = index_def_new(pTab->def->id, 0, "primary",
>> +                    sizeof("primary") - 1, TREE, &index_opts,
>> +                    key_def, NULL);
>> +        key_def_delete(key_def);
>> +
>> +        if (sPk.def == NULL) {
>> +            return SQLITE_ERROR;
>> +        }
> 39. Extra braces.
Fixed.
>> +
>>            aiRowEstPk[0] = sql_space_tuple_log_count(pTab);
>>            aiRowEstPk[1] = 0;
>>            pFirst = pSrc->pTab->pIndex;
>> @@ -3392,8 +3393,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 +3455,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);
>> +                    iColumn = pIndex->def->key_def->
>> +                        parts[j].fieldno;
>> +                    revIdx = pIndex->def->key_def->
>> +                        parts[j].sort_order;
>>                        if (iColumn == pIndex->pTable->iPKey)
>>                            iColumn = -1;
>>                    } else {
>> @@ -3506,8 +3508,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;
>> @@ -4785,7 +4786,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
>> @@ -4918,7 +4919,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 eaab0b657..a04013835 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)));
>>            }
>>        }
>> @@ -1275,12 +1276,12 @@ sqlite3WhereCodeOneLoopStart(WhereInfo *
>> pWInfo,    /* Complete information about t
>>                   || (pWInfo->wctrlFlags & WHERE_ORDERBY_MIN) == 0);
>>            int nIdxCol;
>>            if (pIdx != NULL)
>> -            nIdxCol = index_column_count(pIdx);
>> +            nIdxCol = 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];
>> +            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.
>> @@ -1291,8 +1292,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;
>> @@ -1331,14 +1331,14 @@ sqlite3WhereCodeOneLoopStart(WhereInfo *
>> pWInfo,    /* Complete information about t
>>                    assert((bRev & ~1) == 0);
>>                    pLevel->iLikeRepCntr <<= 1;
>>                    pLevel->iLikeRepCntr |=
>> -                    bRev ^ (sql_index_column_sort_order(pIdx, nEq) ==
>> +                    bRev ^ (pIdx->def->key_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;
>>                }
>>            }
>> @@ -1350,7 +1350,7 @@ sqlite3WhereCodeOneLoopStart(WhereInfo *
>> pWInfo,    /* Complete information about t
>>             * start and end terms (pRangeStart and pRangeEnd).
>>             */
>>            if ((nEq < nIdxCol &&
>> -             bRev == (sql_index_column_sort_order(pIdx, nEq) ==
>> +             bRev == (pIdx->def->key_def->parts[nEq].sort_order ==
>>                      SORT_ORDER_ASC)) ||
>>                (bRev && nIdxCol == nEq)) {
>>                SWAP(pRangeEnd, pRangeStart);
>> @@ -1433,13 +1433,14 @@ sqlite3WhereCodeOneLoopStart(WhereInfo *
>> pWInfo,    /* Complete information about t
>>                }
>>            } else {
>>                pk = sqlite3PrimaryKeyIndex(pIdx->pTable);
>> +            uint32_t fieldno = pk->def->key_def->parts[0].fieldno;
>>                affinity =
>> - pIdx->pTable->def->fields[pk->aiColumn[0]].affinity;
>> + pIdx->pTable->def->fields[fieldno].affinity;
>>            }
>>
>>            int nPkCol;
>>            if (pk != NULL)
>> -            nPkCol = index_column_count(pk);
>> +            nPkCol = pk->def->key_def->part_count;
>>            else
>>                nPkCol = idx_pk->key_def->part_count;
>>            if (nPkCol == 1 && affinity == AFFINITY_INTEGER) {
>> @@ -1450,8 +1451,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)) {
>> @@ -1563,10 +1565,10 @@ 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 nPkCol = pPk->def->key_def->part_count;
> 41. Same as 17
Fixed.
>
>>                int iKeyReg = sqlite3GetTempRange(pParse, nPkCol);
>>                for (j = 0; j < nPkCol; j++) {
>> -                k = pPk->aiColumn[j];
>> +                k = pPk->def->key_def->parts[j].fieldno;
>>                    sqlite3VdbeAddOp3(v, OP_Column, iIdxCur, k,
>>                              iKeyReg + j);
>>                }
>> @@ -1671,7 +1673,7 @@ 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 nPkCol = pPk->def->key_def->part_count;
> 42. Same as 17
Fxd.
>
>>                regRowset = pParse->nTab++;
>>                sqlite3VdbeAddOp2(v, OP_OpenTEphemeral,
>>                          regRowset, nPkCol);
>> @@ -1775,13 +1777,16 @@ 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 nPk = pPk->def->key_def->part_count;
>>                            int iPk;
>>
>>                            /* Read the PK into an array of temp registers. */
>>                            r = sqlite3GetTempRange(pParse, nPk);
>>                            for (iPk = 0; iPk < nPk; iPk++) {
>> -                            int iCol = pPk->aiColumn[iPk];
>> +                            int iCol = pPk->def->
> 43. Same as 17
Fixed.
>> +                                key_def->
>> +                                parts[iPk].
>> +                                fieldno;
>>                                sqlite3ExprCodeGetColumnToReg
>>                                    (pParse, pTab->def,
>>                                     iCol, iCur,
>>
Here is the patch:
---
Branch:https://github.com/tarantool/tarantool/tree/sb/gh-3369-use-index-def-in-select-and-where 

Issue:https://github.com/tarantool/tarantool/issues/3369

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
and zName are removed from Index. All usages of this
fields changed to usage of corresponding index_def
fields.
index_is_unique(), sql_index_collation() and
index_column_count() are removed with calls of
index_def corresponding fields.

Closes: #3369
---
  src/box/sql.c           |  54 ++--
  src/box/sql/analyze.c   |  79 +++---
  src/box/sql/build.c     | 642 
++++++++++++++++++++++++------------------------
  src/box/sql/delete.c    |  10 +-
  src/box/sql/expr.c      |  48 ++--
  src/box/sql/fkey.c      | 113 +++++----
  src/box/sql/insert.c    | 129 +++++-----
  src/box/sql/pragma.c    |  28 +--
  src/box/sql/select.c    |   2 +-
  src/box/sql/sqliteInt.h |  76 +-----
  src/box/sql/update.c    |  39 +--
  src/box/sql/vdbeaux.c   |   2 +-
  src/box/sql/vdbemem.c   |  12 +-
  src/box/sql/where.c     | 162 ++++++------
  src/box/sql/wherecode.c | 104 ++++----
  15 files changed, 742 insertions(+), 758 deletions(-)

diff --git a/src/box/sql.c b/src/box/sql.c
index 11353150e..24e37652e 100644
--- a/src/box/sql.c
+++ b/src/box/sql.c
@@ -1452,8 +1452,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;
      }
@@ -1564,20 +1564,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;
      }

@@ -1587,46 +1586,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..1e9d8ea36 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,10 +1000,11 @@ analyzeOneTable(Parse * pParse,    /* Parser 
context */
               *  ...
               */
              sqlite3VdbeJumpHere(v, addrNextRow - 1);
-            for (i = 0; i < nColTest; i++) {
+            for (i = 0; i < part_count; i++) {
                  sqlite3VdbeJumpHere(v, aGotoChng[i]);
                  sqlite3VdbeAddOp3(v, OP_Column, iIdxCur,
-                          pIdx->aiColumn[i],
+                          pIdx->def->key_def->
+                              parts[i].fieldno,
                            regPrev + i);
              }
              sqlite3VdbeResolveLabel(v, endDistinctTest);
@@ -1022,19 +1020,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));
+        int pk_part_count = (int) pPk->def->key_def->part_count;
+        int regKeyStat = sqlite3GetTempRange(pParse, pk_part_count);
+        for (int j = 0; j < pk_part_count; ++j) {
+            int k = pPk->def->key_def->parts[j].fieldno;
+            assert(k >= 0 && k < (int) 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 +1054,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 +1074,12 @@ analyzeOneTable(Parse * pParse,    /* Parser 
context */
           * be taken
           */
          VdbeCoverageNeverTaken(v);
-        for (i = 0; i < nColTest; 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 +1143,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");
      }
diff --git a/src/box/sql/build.c b/src/box/sql/build.c
index 0da7d805b..2c82644e6 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;
@@ -376,7 +379,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);
@@ -1041,7 +1044,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)
@@ -1049,22 +1052,20 @@ 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; pIdx = pIdx->pNext) {
+            assert(pIdx->def->key_def->part_count == 1);
+            if (pIdx->def->key_def->parts[0].fieldno == i) {
+                pIdx->def->key_def->parts[0].coll_id = *id;
+                pIdx->def->key_def->parts[0].coll =
                      sql_column_collation(p->def, i, id);
              }
          }
-    } else {
-        sqlite3DbFree(db, zColl);
      }
+    sqlite3DbFree(db, zColl);
  }

  struct coll *
@@ -1094,66 +1095,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)
  {
@@ -1337,15 +1278,26 @@ createTableStmt(sqlite3 * db, Table * p)
      return zStmt;
  }

-/* Return true if value x is found any of the first nCol entries of aiCol[]
+/**
+ * Look for duplicates for the given index key part in index key
+ * prefix. Prefix is formed the following way:
+ * (first_key_part, ..., key_part_just_before_given). Index key
+ * parts are duplicates of each other if their 'fieldno' are the
+ * same.
+ *
+ * @param key_parts index key parts.
+ * @param part_index index of part, for which we look for
+ *       duplicates.
+ * @retval is there a duplicate part in prefix or not.
   */
-static int
-hasColumn(const i16 * aiCol, int nCol, int x)
+static bool
+is_part_duplicated(const struct key_part *key_parts, uint32_t part_index)
  {
-    while (nCol-- > 0)
-        if (x == *(aiCol++))
-            return 1;
-    return 0;
+    for (uint32_t i = 0; i < part_index; i++) {
+         if (key_parts[i].fieldno == key_parts[part_index].fieldno)
+             return true;
+    }
+    return false;
  }

  /*
@@ -1364,13 +1316,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;
@@ -1404,18 +1355,33 @@ convertToWithoutRowidTable(Parse * pParse, Table 
* pTab)
          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.
+         * 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];
+
+        struct key_part *parts = pPk->def->key_def->parts;
+        uint32_t part_count = pPk->def->key_def->part_count;
+        uint32_t new_part_count = part_count;
+
+        for (uint32_t i = 1; i < part_count; i++) {
+            if (is_part_duplicated(parts, i)) {
+                new_part_count--;
+                bool is_found = false;
+                for (uint32_t j = i + 1; j < part_count; j++) {
+                    if (!is_part_duplicated(parts, j)) {
+                        parts[i] = parts[j];
+                        is_found = true;
+                        break;
+                    }
+                }
+                if (!is_found)
+                    break;
              }
          }
-        pPk->nColumn = j;
+        pPk->def->key_def->part_count = new_part_count;
      }
      assert(pPk != 0);
  }
@@ -1497,7 +1463,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);
@@ -1534,7 +1500,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) {
@@ -2463,15 +2429,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.
@@ -2502,7 +2469,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);
@@ -2531,34 +2499,20 @@ sqlite3RefillIndex(Parse * pParse, Index * 
pIndex, int memRootPage)
   */
  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 */
+               i16 nCol    /* Total number of columns in the index */
      )
  {
      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);
+        ROUND8(sizeof(LogEst) * (nCol + 1));    /* Index.aiRowLogEst */
+    p = sqlite3DbMallocZero(db, nByte);
      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;
      }
      return p;
  }
@@ -2646,18 +2600,142 @@ 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);
+/**
+ * Allocate memory on parser region and copy given string (part of the
+ * sql statement) into the allocated memory. Add size of the
+ * given string to 'total_sql_size' in order to keep size of sql
+ * statement string being hold.
+ *
+ * @param parse Parse context.
+ * @param str String (a part of sql statement) to be copied.
+ * @param total_sql_size Current size of the whole sql statement.
+ */
+static void
+append_string_part(Parse *parse, const char *str, size_t *total_sql_size)
+{
+    const size_t str_len = strlen(str);
+    char * str_part = region_alloc(&parse->region, str_len);
+    if (str_part == NULL) {
+        diag_set(OutOfMemory, str_len,
+             "region_alloc", "str_part");
+        parse->rc = SQL_TARANTOOL_ERROR;
+        parse->nErr++;
+        return;
+    }
+    memcpy(str_part, str, str_len);
+    *total_sql_size += str_len;
+}
+
+static void
+set_index_def(Parse *parse, Index *index, Table *table, uint32_t iid,
+          const char *name, uint32_t name_len, int on_error,
+          struct ExprList *expr_list, u8 idx_type)
+{
+    struct space_def *space_def = table->def;
+    struct index_opts opts;
+    index_opts_create(&opts);
+    opts.is_unique = on_error != ON_CONFLICT_ACTION_NONE;
+
+    struct key_def *key_def = key_def_new(expr_list->nExpr);
+    if (key_def == NULL) {
+        parse->rc = SQL_TARANTOOL_ERROR;
+        parse->nErr++;
+        goto cleanup;
+    }
+
+    /* Build initial parts of SQL statement.  */

-    return tnt_index->def->opts.is_unique;
+    size_t total_sql_size = 0;
+
+    if (idx_type == SQLITE_IDXTYPE_APPDEF) {
+        append_string_part(parse, "CREATE INDEX ", &total_sql_size);
+        append_string_part(parse, name, &total_sql_size);
+        append_string_part(parse, " ON ", &total_sql_size);
+        append_string_part(parse, space_def->name, &total_sql_size);
+        append_string_part(parse, " (", &total_sql_size);
+    }
+
+    for (int i = 0; i < expr_list->nExpr; i++) {
+        Expr *expr = expr_list->a[i].pExpr;
+        sql_resolve_self_reference(parse, table, NC_IdxExpr, expr, 0);
+        if (parse->nErr > 0)
+            goto cleanup;
+
+        Expr *column_expr = sqlite3ExprSkipCollate(expr);
+        if (column_expr->op != TK_COLUMN) {
+            sqlite3ErrorMsg(parse,
+                    "functional indexes aren't supported "
+                    "in the current version");
+            goto cleanup;
+        }
+
+        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 (idx_type == SQLITE_IDXTYPE_APPDEF) {
+                const char *column_name =
+                    column_expr->u.zToken;
+                append_string_part(parse, column_name,
+                           &total_sql_size);
+                append_string_part(parse, " COLLATE ",
+                           &total_sql_size);
+                const char *coll_name = expr->u.zToken;
+                append_string_part(parse, coll_name,
+                           &total_sql_size);
+                append_string_part(parse, ", ",
+                           &total_sql_size);
+            }
+        } else {
+            coll = sql_column_collation(space_def, fieldno,
+                            &coll_id);
+            if (idx_type == SQLITE_IDXTYPE_APPDEF) {
+                const char *column_name =
+                    column_expr->u.zToken;
+                append_string_part(parse, column_name,
+                           &total_sql_size);
+                append_string_part(parse, ", ",
+                           &total_sql_size);
+            }
+        }
+
+        /*
+        * Tarantool: DESC indexes are not supported so far.
+        * See gh-3016.
+        */
+        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) {
+        index->def = NULL;
+        goto cleanup;
+    }
+
+    if (idx_type == SQLITE_IDXTYPE_APPDEF) {
+        opts.sql = region_join(&parse->region, total_sql_size);
+        /*
+         * fix last ", " with ")\0" to finish statement.
+         */
+        memcpy(&opts.sql[total_sql_size - 2], ")\0", 2);
+    }
+
+    struct key_def *pk_key_def;
+    if (idx_type == SQLITE_IDXTYPE_APPDEF)
+        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);
+ cleanup:
+    if (key_def != NULL)
+        key_def_delete(key_def);
  }

  void
@@ -2666,26 +2744,26 @@ sql_create_index(struct Parse *parse, struct 
Token *token,
           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 */
+    /* Table to be indexed.  */
+    Table *table = NULL;
+    /* The index to be created.  */
+    Index *index = NULL;
+    /* Name of the index  */
+    char *name = NULL;
+    int name_len;
      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 */
      struct session *user_session = current_session();

      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) {
          Vdbe *v = sqlite3GetVdbe(parse);
@@ -2696,39 +2774,46 @@ 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.
       */
      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,
+        DbFixer db_fixer;
+        sqlite3FixInit(&db_fixer, parse, "index", token);
+        if (sqlite3FixSrcList(&db_fixer, 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)
+        table = sqlite3LocateTable(parse, 0, tbl_name->a[0].zName);
+        assert(db->mallocFailed == 0 || table == NULL);
+        if (table == NULL)
              goto exit_create_index;
-        sqlite3PrimaryKeyIndex(pTab);
+        sqlite3PrimaryKeyIndex(table);
      } else {
          assert(token == NULL);
          assert(start == NULL);
-        pTab = parse->pNewTable;
-        if (!pTab)
+        table = parse->pNewTable;
+        if (table == NULL)
              goto exit_create_index;
      }

-    assert(pTab != 0);
+    assert(table != NULL);
      assert(parse->nErr == 0);
-    if (pTab->def->opts.is_view) {
+    if (table->def->opts.is_view) {
          sqlite3ErrorMsg(parse, "views may not be indexed");
          goto exit_create_index;
      }
@@ -2747,24 +2832,24 @@ sql_create_index(struct Parse *parse, struct 
Token *token,
       * our own name.
       */
      if (token) {
-        zName = sqlite3NameFromToken(db, token);
-        if (zName == 0)
+        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) !=
+            if (sqlite3HashFind(&db->pSchema->tblHash, name) !=
                  NULL) {
                  sqlite3ErrorMsg(parse,
                          "there is already a table named %s",
-                        zName);
+                        name);
                  goto exit_create_index;
              }
          }
-        if (sqlite3HashFind(&pTab->idxHash, zName) != 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);
              }
@@ -2773,15 +2858,13 @@ sql_create_index(struct Parse *parse, struct 
Token *token,
      } else {
          int n;
          Index *pLoop;
-        for (pLoop = pTab->pIndex, n = 1; pLoop;
+        for (pLoop = table->pIndex, n = 1; pLoop;
               pLoop = pLoop->pNext, n++) {
          }
-        zName =
-            sqlite3MPrintf(db, "sqlite_autoindex_%s_%d", pTab->def->name,
-                   n);
-        if (zName == 0) {
+        name = sqlite3MPrintf(db, "sqlite_autoindex_%s_%d",
+                      table->def->name, n);
+        if (name == NULL)
              goto exit_create_index;
-        }
      }

      /*
@@ -2792,8 +2875,8 @@ sql_create_index(struct Parse *parse, struct Token 
*token,
       */
      if (col_list == NULL) {
          Token prevCol;
-        uint32_t last_field = pTab->def->field_count - 1;
-        sqlite3TokenInit(&prevCol, pTab->def->fields[last_field].name);
+        uint32_t last_field = table->def->field_count - 1;
+        sqlite3TokenInit(&prevCol, table->def->fields[last_field].name);
          col_list = sql_expr_list_append(parse->db, NULL,
                          sqlite3ExprAlloc(db, TK_ID,
                                   &prevCol, 0));
@@ -2805,33 +2888,26 @@ 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));
-        }
-    }
+    /* Allocate the index structure.  */
+    name_len = sqlite3Strlen30(name);

-    /*
-     * Allocate the index structure.
-     */
-    nName = sqlite3Strlen30(zName);
-    pIndex = sqlite3AllocateIndexObject(db, col_list->nExpr,
-                        nName + nExtra + 1, &zExtra);
-    if (db->mallocFailed) {
+    if (name_len > BOX_NAME_MAX) {
+        sqlite3ErrorMsg(parse,
+                "%s.%s exceeds indexes' names length limit",
+                table->def->name, name);
          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;
+
+    if (sqlite3CheckIdentifierName(parse, name) != SQLITE_OK)
+        goto exit_create_index;
+
+    index = sqlite3AllocateIndexObject(db, col_list->nExpr);
+    if (db->mallocFailed)
+        goto exit_create_index;
+
+    assert(EIGHT_BYTE_ALIGNMENT(index->aiRowLogEst));
+    index->pTable = table;
+    index->onError = (u8) on_error;
      /*
       * Don't make difference between UNIQUE indexes made by user
       * using CREATE INDEX statement and those created during
@@ -2839,74 +2915,40 @@ sql_create_index(struct Parse *parse, struct 
Token *token,
       */
      if (idx_type == SQLITE_IDXTYPE_APPDEF &&
          on_error != ON_CONFLICT_ACTION_NONE) {
-        pIndex->idxType = SQLITE_IDXTYPE_UNIQUE;
+        index->idxType = SQLITE_IDXTYPE_UNIQUE;
      } else {
-        pIndex->idxType = idx_type;
+        index->idxType = idx_type;
      }
-    pIndex->pSchema = db->pSchema;
-    pIndex->nColumn = col_list->nExpr;
+    index->pSchema = db->pSchema;
      /* Tarantool have access to each column by any index */
      if (where) {
-        sql_resolve_self_reference(parse, pTab, NC_PartIdx, where,
+        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");
-            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;
-        }
-        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;
+    uint32_t max_iid = 0;
+    for (Index *index = table->pIndex; index; index = index->pNext) {
+        max_iid = max_iid > index->def->iid ?
+              max_iid : index->def->iid + 1;
      }
-    if (pTab == parse->pNewTable) {
+
+    set_index_def(parse,  index, table, max_iid, name, name_len, on_error,
+              col_list, idx_type);
+
+    if (index->def == NULL ||
+        !index_def_is_valid(index->def, table->def->name))
+        goto exit_create_index;
+
+    if (table == 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.
@@ -2929,27 +2971,27 @@ sql_create_index(struct Parse *parse, struct 
Token *token,
           * considered distinct and both result in separate indices.
           */
          Index *pIdx;
-        for (pIdx = pTab->pIndex; pIdx; pIdx = pIdx->pNext) {
-            int k;
+        for (pIdx = table->pIndex; pIdx; pIdx = pIdx->pNext) {
+            uint32_t k;
              assert(IsUniqueIndex(pIdx));
              assert(pIdx->idxType != SQLITE_IDXTYPE_APPDEF);
-            assert(IsUniqueIndex(pIndex));
+            assert(IsUniqueIndex(index));

-            if (pIdx->nColumn != pIndex->nColumn)
+            if (pIdx->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 < pIdx->def->key_def->part_count; k++) {
+                if (pIdx->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 = pIdx->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) {
+            if (k == pIdx->def->key_def->part_count) {
+                if (pIdx->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
@@ -2959,15 +3001,14 @@ sql_create_index(struct Parse *parse, struct 
Token *token,
                       */
                      if (!
                          (pIdx->onError == ON_CONFLICT_ACTION_DEFAULT
-                         || pIndex->onError ==
+                         || 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;
-                    }
+                    if (pIdx->onError == ON_CONFLICT_ACTION_DEFAULT)
+                        pIdx->onError = index->onError;
                  }
                  if (idx_type == SQLITE_IDXTYPE_PRIMARYKEY)
                      pIdx->idxType = idx_type;
@@ -2982,14 +3023,14 @@ sql_create_index(struct Parse *parse, struct 
Token *token,
      assert(parse->nErr == 0);
      if (db->init.busy) {
          Index *p;
-        p = sqlite3HashInsert(&pTab->idxHash, pIndex->zName, pIndex);
+        p = sqlite3HashInsert(&table->idxHash, index->def->name, index);
          if (p) {
-            assert(p == pIndex);    /* Malloc must have failed */
+            assert(p == index);    /* Malloc must have failed */
              sqlite3OomFault(db);
              goto exit_create_index;
          }
          user_session->sql_flags |= SQLITE_InternChanges;
-        pIndex->tnum = db->init.newTnum;
+        index->tnum = db->init.newTnum;
      }

      /*
@@ -3040,14 +3081,14 @@ sql_create_index(struct Parse *parse, struct 
Token *token,
                         ON_CONFLICT_ACTION_NONE ? "" : " UNIQUE",
                         n, token->z);

-        iSpaceId = SQLITE_PAGENO_TO_SPACEID(pTab->tnum);
+        iSpaceId = SQLITE_PAGENO_TO_SPACEID(table->tnum);
          iIndexId = getNewIid(parse, iSpaceId, iCursor);
          sqlite3VdbeAddOp1(v, OP_Close, iCursor);
-        createIndex(parse, pIndex, iSpaceId, iIndexId, zStmt);
+        createIndex(parse, index, iSpaceId, iIndexId, zStmt);

          /* consumes zStmt */
          iFirstSchemaCol =
-            makeIndexSchemaRecord(parse, pIndex, iSpaceId, iIndexId,
+            makeIndexSchemaRecord(parse, index, iSpaceId, iIndexId,
                        zStmt);

          /* Reparse the schema. Code an OP_Expire
@@ -3070,54 +3111,17 @@ sql_create_index(struct Parse *parse, struct 
Token *token,

      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 */
   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
@@ -3743,9 +3747,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);
@@ -3766,11 +3770,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 8b13f6077..980891cb2 100644
--- a/src/box/sql/delete.c
+++ b/src/box/sql/delete.c
@@ -269,11 +269,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 {
@@ -569,13 +570,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 59e7cb4fd..a960c1cc1 100644
--- a/src/box/sql/expr.c
+++ b/src/box/sql/expr.c
@@ -2405,20 +2405,24 @@ 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) {
-                    if (pIdx->nColumn > nExpr
-                        || (pIdx->nColumn > nExpr
-                        && !index_is_unique(pIdx))) {
-                            continue;    /* This index is not unique 
over the IN RHS columns */
+                    if ((int)part_count > nExpr
+                        || !pIdx->def->opts.is_unique) {
+                        /* This index is not unique over the IN RHS 
columns */
+                        continue;
                      }
                  }

@@ -2432,12 +2436,13 @@ 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) {
                              continue;
@@ -2466,18 +2471,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 +2503,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);
                          }
                      }
@@ -3130,12 +3134,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
+            tab->def->fields[fieldno].affinity;
+        if (pk->def->key_def->part_count == 1
              && affinity == AFFINITY_INTEGER
-            && pk->aiColumn[0] < nVector) {
-            int reg_pk = rLhs + pk->aiColumn[0];
+            && (int) fieldno < nVector) { int reg_pk = rLhs + (int)fieldno;
              sqlite3VdbeAddOp2(v, OP_MustBeInt, reg_pk, destIfFalse);
          }
      }
@@ -3467,7 +3471,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 e3fff37fe..c14a70836 100644
--- a/src/box/sql/fkey.c
+++ b/src/box/sql/fkey.c
@@ -214,7 +214,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 */
@@ -256,21 +255,29 @@ 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.
+    Index *index = NULL;
+    for (index = pParent->pIndex; index; index = index->pNext) {
+        int part_count = index->def->key_def->part_count;
+        if (part_count == nCol && index->def->opts.is_unique
+            && index->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.
               */

              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 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 (IsPrimaryKeyIndex(index)) {
                      if (aiCol) {
                          int i;
                          for (i = 0; i < nCol; i++)
@@ -287,9 +294,16 @@ sqlite3FkLocateIndex(Parse * pParse,    /* Parse 
context to store any error in */
                   * the default collation sequences for each column.
                   */
                  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 */
+                struct key_part *part =
+                    index->def->key_def->parts;
+                for (i = 0; i < nCol; i++, part++) {
+                    /*
+                     * Index of column in
+                     * parent table.
+                     * */
+                    i16 iCol = (int) part->fieldno;
+                    /* Name of indexed column. */
+                    char *zIdxCol;

                      if (iCol < 0)
                          break;    /* No foreign keys against 
expression indexes */
@@ -303,9 +317,7 @@ sqlite3FkLocateIndex(Parse * pParse,    /* Parse 
context to store any error in */
                      def_coll = sql_column_collation(pParent->def,
                                      iCol,
                                      &id);
-                    struct coll *coll =
-                        sql_index_collation(pIdx, i,
-                                    &id);
+                    struct coll *coll = part->coll;
                      if (def_coll != coll)
                          break;

@@ -332,7 +344,7 @@ sqlite3FkLocateIndex(Parse * pParse,    /* Parse 
context to store any error in */
          }
      }

-    if (!pIdx) {
+    if (index == NULL) {
          if (!pParse->disableTriggers) {
              sqlite3ErrorMsg(pParse,
                      "foreign key mismatch - \"%w\" referencing \"%w\"",
@@ -342,7 +354,7 @@ sqlite3FkLocateIndex(Parse * pParse,    /* Parse 
context to store any error in */
          return 1;
      }

-    *ppIdx = pIdx;
+    *ppIdx = index;
      return 0;
  }

@@ -465,13 +477,15 @@ fkLookupParent(Parse * pParse,    /* Parse context */
                  for (i = 0; i < nCol; i++) {
                      int iChild = aiCol[i] + 1 + regData;
                      int iParent =
-                        pIdx->aiColumn[i] + 1 + regData;
-                    assert(pIdx->aiColumn[i] >= 0);
+                        (int) pIdx->def->key_def->parts[i].fieldno
+                        + 1 + regData;
                      assert(aiCol[i] != pTab->iPKey);
-                    if (pIdx->aiColumn[i] == pTab->iPKey) {
+                    if ((int)pIdx->def->key_def->
+                        parts[i].fieldno == pTab->iPKey) {
                          /* The parent key is a composite key that 
includes the IPK column */
                          iParent = regData;
                      }
+
                      sqlite3VdbeAddOp3(v, OP_Ne, iChild,
                                iJump, iParent);
                      VdbeCoverage(v);
@@ -615,7 +629,6 @@ 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() */
@@ -623,7 +636,7 @@ fkScanChildren(Parse * pParse,    /* Parse context */
      Vdbe *v = sqlite3GetVdbe(pParse);

      assert(pIdx == 0 || pIdx->pTable == pTab);
-    assert(pIdx == 0 || (int)index_column_count(pIdx) == pFKey->nCol);
+    assert(pIdx == 0 || (int) pIdx->def->key_def->part_count == 
pFKey->nCol);
      assert(pIdx != 0);

      if (nIncr < 0) {
@@ -640,19 +653,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);
      }
@@ -671,15 +685,16 @@ 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);
+        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 =
+                (int) pIdx->def->key_def->parts[i].fieldno;
+            pLeft = exprTableRegister(pParse, pTab, regData,
+                          fieldno);
              pRight =
                  exprTableColumn(db, pTab->def,
-                        pSrc->a[0].iCursor, iCol);
+                        pSrc->a[0].iCursor, fieldno);
              pEq = sqlite3PExpr(pParse, TK_EQ, pLeft, pRight);
              pAll = sqlite3ExprAnd(db, pAll, pEq);
          }
@@ -982,7 +997,6 @@ sqlite3FkCheck(Parse * pParse,    /* Parse context */
              if (aiCol[i] == pTab->iPKey) {
                  aiCol[i] = -1;
              }
-            assert(pIdx == 0 || pIdx->aiColumn[i] >= 0);
          }

          pParse->nTab++;
@@ -1107,19 +1121,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);
                  }
              }
          }
@@ -1254,11 +1268,12 @@ fkActionTrigger(Parse * pParse,    /* Parse 
context */
                     || (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 a43f7b596..d3e7cfb93 100644
--- a/src/box/sql/insert.c
+++ b/src/box/sql/insert.c
@@ -90,14 +90,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,8 +1093,8 @@ 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) {
@@ -1109,7 +1103,7 @@ sqlite3GenerateConstraintChecks(Parse * pParse,    
     /* The parser context */
          }
          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 +1173,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 +1200,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; 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 +1250,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 = (int) 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 +1265,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 +1280,13 @@ 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) {
+                reg_pk = regNewData + 1 +
+ pIdx->def->key_def->parts[0].fieldno;
+
+                int fieldno = (int) pIdx->def->key_def->
+                    parts[0].fieldno;
+                if (pTab->zColAff[fieldno] ==
                      AFFINITY_INTEGER) {
                      int skip_if_null = sqlite3VdbeMakeLabel(v);
                      if ((pTab->tabFlags & TF_Autoincrement) != 0) {
@@ -1303,7 +1304,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
@@ -1391,24 +1392,23 @@ 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);

+        const uint32_t pk_part_count = pPk->def->key_def->part_count;
          /* Generate code to handle collisions */
          regR =
              (pIdx == pPk) ? regIdx : sqlite3GetTempRange(pParse,
-                                 nPkField);
+                                 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,
@@ -1424,17 +1424,18 @@ sqlite3GenerateConstraintChecks(Parse * 
pParse,        /* The parser context */
                   * KEY values of this row before the update.
                   */
                  int addrJump =
-                    sqlite3VdbeCurrentAddr(v) + nPkCol;
+                    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)) {
+                for (uint32_t i = 0; i < pk_part_count; i++) {
+                    char *p4 = (char *) pPk->def->key_def->parts[i].coll;
+                    x = pPk->def->key_def->parts[i].fieldno;
+                    if (pPk->tnum==0)
+                        x = -1;
+                    if (i == (pk_part_count - 1)) {
                          addrJump = addrUniqueOk;
                          op = OP_Eq;
                      }
@@ -1482,7 +1483,8 @@ sqlite3GenerateConstraintChecks(Parse * pParse,    
     /* The parser context */
                                  NULL, NULL);
              }
              sql_generate_row_delete(pParse, pTab, pTrigger,
-                        iDataCur, regR, nPkField, false,
+                        iDataCur, regR, pk_part_count,
+                        false,
                          ON_CONFLICT_ACTION_REPLACE,
                          pIdx == pPk ? ONEPASS_SINGLE :
                          ONEPASS_OFF, -1);
@@ -1492,7 +1494,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;
@@ -1610,8 +1612,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 */
@@ -1629,7 +1631,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));
              }
          }
      }
@@ -1663,30 +1665,24 @@ 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 */
@@ -1858,16 +1854,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(
@@ -1943,11 +1938,11 @@ 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);
diff --git a/src/box/sql/pragma.c b/src/box/sql/pragma.c
index 5fb29c75c..7067a5ab1 100644
--- a/src/box/sql/pragma.c
+++ b/src/box/sql/pragma.c
@@ -370,7 +370,8 @@ sqlite3Pragma(Parse * pParse, Token * pId, /* First 
part of [schema.]id field */
                  k = 1;
              } else {
                  for (k = 1; k <= def->field_count &&
-                     pk->aiColumn[k - 1] != (int) i; ++k) {
+                     pk->def->key_def->parts[k - 1].fieldno
+                     != i; ++k) {
                  }
              }
              bool is_nullable = def->fields[i].is_nullable;
@@ -414,7 +415,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,
@@ -443,11 +444,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,
@@ -461,19 +464,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 <
@@ -503,10 +505,8 @@ sqlite3Pragma(Parse * pParse, Token * pId, /* First 
part of [schema.]id field */
                              { "c", "u", "pk" };
                          sqlite3VdbeMultiLoad(v, 1,
                                       "isisi", i,
-                                     pIdx->
-                                     zName,
-                                     index_is_unique
-                                     (pIdx),
+                                     pIdx->def->name,
+ pIdx->def->opts.is_unique,
                                       azOrigin
                                       [pIdx->
                                        idxType],
diff --git a/src/box/sql/select.c b/src/box/sql/select.c
index 368bcd6f0..c7c186d9d 100644
--- a/src/box/sql/select.c
+++ b/src/box/sql/select.c
@@ -4367,7 +4367,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 e7a02dc1d..fcedf4358 100644
--- a/src/box/sql/sqliteInt.h
+++ b/src/box/sql/sqliteInt.h
@@ -2069,27 +2069,17 @@ struct UnpackedRecord {
   * 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.
+ * Indexes name, corresponding space_id, type (in tarantool
+ * sense - HASH, TREE, etc) are stored in index definition - in
+ * Index.def.
+ * SQL statement which created the index and 'is_unique' flag are
+ * stored in Index.def.opts. Information about index parts (part
+ * count, corresponding space fields' numbers, parts' collations
+ * and sort orders, etc) are stored in Index.def.key_def.parts
+ *
+ * Index.onError indicate the which conflict resolution algorithm
+ * to employ whenever an attempt is made to insert a non-unique
+ * element in unique index.
   *
   * While parsing a CREATE TABLE or CREATE INDEX statement in order to
   * generate VDBE code (as opposed to reading from Tarantool's _space
@@ -2100,26 +2090,18 @@ 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 */
+    struct index_def *def;
  };

  /**
@@ -3541,34 +3523,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 *);

@@ -3655,9 +3609,7 @@ 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 *);
+Index *sqlite3AllocateIndexObject(sqlite3 *, i16);

  /**
   * Create a new index for an SQL table.  name is the name of the
@@ -4373,8 +4325,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);
diff --git a/src/box/sql/update.c b/src/box/sql/update.c
index 10385eb78..d45672228 100644
--- a/src/box/sql/update.c
+++ b/src/box/sql/update.c
@@ -238,17 +238,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;
                  }
              }
@@ -300,17 +301,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);
@@ -319,7 +321,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);
      }

@@ -329,27 +332,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 a29d0a3ae..9372ba360 100644
--- a/src/box/sql/vdbeaux.c
+++ b/src/box/sql/vdbeaux.c
@@ -1159,7 +1159,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 f408b7701..baf3df48b 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;
              }
@@ -1650,10 +1650,10 @@ sqlite3Stat4ProbeFree(UnpackedRecord * pRec)
  {
      if (pRec) {
          int i;
-        int nCol = pRec->key_def->part_count;
+        int part_count = pRec->key_def->part_count;
          Mem *aMem = pRec->aMem;
          sqlite3 *db = aMem[0].db;
-        for (i = 0; i < nCol; i++) {
+        for (i = 0; i < part_count; i++) {
              sqlite3VdbeMemRelease(&aMem[i]);
          }
          sqlite3DbFree(db, pRec);
diff --git a/src/box/sql/where.c b/src/box/sql/where.c
index c0c26ce29..599863041 100644
--- a/src/box/sql/where.c
+++ b/src/box/sql/where.c
@@ -372,13 +372,19 @@ whereScanInit(WhereScan * pScan,    /* The 
WhereScan object being initialized */
      pScan->is_column_seen = false;
      if (pIdx) {
          int j = iColumn;
-        iColumn = pIdx->aiColumn[j];
+        iColumn = pIdx->def->key_def->parts[j].fieldno;
+        /*
+         * pIdx->tnum == 0 means that pIdx is a fake
+         * integer primary key index
+         */
+        if (pIdx->tnum == 0)
+            iColumn = -1;
+
          if (iColumn >= 0) {
              char affinity =
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;
          }
      }
@@ -541,47 +547,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 +616,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 +626,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;
          }
@@ -1184,7 +1168,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 +1230,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 +1256,12 @@ whereRangeSkipScanEst(Parse * pParse,     /* 
Parsing & code generating context */
              rc = sqlite3Stat4Column(db, samples[i].sample_key,
                          samples[i].key_size, 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 +1431,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 +1581,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 +1602,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,7 +1734,7 @@ 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 (p->pIndex && (zName = p->pIndex->def->name) != 0) {
          if (strncmp(zName, "sqlite_autoindex_", 17) == 0) {
              int i = sqlite3Strlen30(zName) - 1;
              while (zName[i] != '_')
@@ -2314,7 +2297,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 +2318,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)) {
+        if (pLhs->op != TK_COLUMN || pLhs->iTable != iCur
+            || pLhs->iColumn != (int)pIdx->def->key_def->parts[i + 
nEq].fieldno
+            || pIdx->def->key_def->parts[i + nEq].sort_order !=
+ pIdx->def->key_def->parts[nEq].sort_order)
              break;
-        }

          aff = sqlite3CompareAffinity(pRhs, sqlite3ExprAffinity(pLhs));
          idxaff =
@@ -2353,7 +2334,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 +2377,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 +2412,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,8 +2433,9 @@ whereLoopAddBtreeIndex(WhereLoopBuilder * 
pBuilder,    /* The WhereLoop factory */
          LogEst nOutUnadjusted;    /* nOut before IN() and WHERE 
adjustments */
          int nIn = 0;
          int nRecValid = pBuilder->nRecValid;
+        uint32_t j = pProbe->def->key_def->parts[saved_nEq].fieldno;
          if ((eOp == WO_ISNULL || (pTerm->wtFlags & TERM_VNULL) != 0)
-            && indexColumnNotNull(pProbe, saved_nEq)
+            && !pProbe->pTable->def->fields[j].is_nullable
              ) {
              continue;    /* ignore IS [NOT] NULL constraints on NOT 
NULL columns */
          }
@@ -2523,14 +2505,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 +2576,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;
@@ -2696,7 +2679,7 @@ whereLoopAddBtreeIndex(WhereLoopBuilder * 
pBuilder,    /* The WhereLoop factory */
          }

          if ((pNew->wsFlags & WHERE_TOP_LIMIT) == 0
-            && pNew->nEq < nProbeCol) {
+            && pNew->nEq < probe_part_count) {
              whereLoopAddBtreeIndex(pBuilder, pSrc, pProbe,
                             nInMul + nIn);
          }
@@ -2724,7 +2707,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 +2732,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 +2775,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 +2785,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;
              }
          }
@@ -2882,7 +2866,6 @@ whereLoopAddBtree(WhereLoopBuilder * pBuilder,    
/* WHERE clause information */
      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 */
      SrcList *pTabList;    /* The FROM clause */
      struct SrcList_item *pSrc;    /* The FROM clause btree term to add */
      WhereLoop *pNew;    /* Template WhereLoop object */
@@ -2913,11 +2896,28 @@ whereLoopAddBtree(WhereLoopBuilder * 
pBuilder,    /* WHERE clause information */
           */
          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;
+
+        struct key_def *key_def = key_def_new(1);
+        if (key_def == NULL)
+            return SQLITE_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 index_opts = index_opts_default;
+
+        sPk.def = index_def_new(pTab->def->id, 0, "primary",
+                    sizeof("primary") - 1, TREE, &index_opts,
+                    key_def, NULL);
+        key_def_delete(key_def);
+
+        if (sPk.def == NULL)
+            return SQLITE_ERROR;
+
          aiRowEstPk[0] = sql_space_tuple_log_count(pTab);
          aiRowEstPk[1] = 0;
          pFirst = pSrc->pTab->pIndex;
@@ -3392,8 +3392,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 +3454,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);
+                    iColumn = pIndex->def->key_def->
+                        parts[j].fieldno;
+                    revIdx = pIndex->def->key_def->
+                        parts[j].sort_order;
                      if (iColumn == pIndex->pTable->iPKey)
                          iColumn = -1;
                  } else {
@@ -3506,8 +3507,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 +4777,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 +4910,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..88ae0773d 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;
+            part_count = idx_def->key_def->part_count;
          if ((pWInfo->wctrlFlags & WHERE_ORDERBY_MIN) != 0
-            && pWInfo->nOBSat > 0 && (nIdxCol > nEq)) {
-            j = pIdx->aiColumn[nEq];
+            && 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;
@@ -1095,14 +1095,14 @@ sqlite3WhereCodeOneLoopStart(WhereInfo * 
pWInfo,    /* Complete information about t
                  assert((bRev & ~1) == 0);
                  pLevel->iLikeRepCntr <<= 1;
                  pLevel->iLikeRepCntr |=
-                    bRev ^ (sql_index_column_sort_order(pIdx, nEq) ==
+                    bRev ^ (pIdx->def->key_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,10 @@ 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) ==
+        if ((nEq < part_count &&
+             bRev == (pIdx->def->key_def->parts[nEq].sort_order ==
                    SORT_ORDER_ASC)) ||
-            (bRev && nIdxCol == nEq)) {
+            (bRev && part_count == nEq)) {
              SWAP(pRangeEnd, pRangeStart);
              SWAP(bSeekPastNull, bStopAtNull);
              SWAP(nBtm, nTop);
@@ -1196,16 +1196,17 @@ sqlite3WhereCodeOneLoopStart(WhereInfo * 
pWInfo,    /* Complete information about t
              }
          } else {
              pk = sqlite3PrimaryKeyIndex(pIdx->pTable);
+            uint32_t fieldno = pk->def->key_def->parts[0].fieldno;
              affinity =
- pIdx->pTable->def->fields[pk->aiColumn[0]].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 +1214,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 +1328,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 +1436,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 +1540,26 @@ 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;
+                        uint32_t part_count =
+                            pPk->def->key_def->
+                                part_count;

                          /* 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,
+                                    part_count);
+                        for (uint32_t iPk = 0;
+                             iPk < part_count;
+                             iPk++) {
+                            uint32_t fieldno =
+                                pPk->def->
+                                key_def->
+                                parts[iPk].
+                                fieldno;
                              sqlite3ExprCodeGetColumnToReg
-                                (pParse, pTab->def,
-                                 iCol, iCur,
+                                (pParse,
+                                 pTab->def,
+                                 fieldno,
+                                 iCur,
                                   r + iPk);
                          }

@@ -1563,24 +1575,28 @@ sqlite3WhereCodeOneLoopStart(WhereInfo * 
pWInfo,    /* Complete information about t
                           * need to insert the key into the temp table, 
as it will never
                           * be tested for.
                           */
+                        uint32_t pk_part_count =
+                            pPk->def->key_def->
+                                part_count;
                          if (iSet) {
                              jmp1 = sqlite3VdbeAddOp4Int
                                  (v, OP_Found,
                                   regRowset, 0,
-                                 r, nPk);
+                                 r,
+                                 pk_part_count);
                              VdbeCoverage(v);
                          }
                          if (iSet >= 0) {
                              sqlite3VdbeAddOp3
                                  (v, OP_MakeRecord,
-                                 r, nPk, regPk);
+                                 r, pk_part_count, regPk);
                              sqlite3VdbeAddOp2
                                  (v, OP_IdxInsert,
                                   regRowset, regPk);
                          }

                          /* Release the array of temp registers */
-                        sqlite3ReleaseTempRange(pParse, r, nPk);
+                        sqlite3ReleaseTempRange(pParse, r, pk_part_count);
                      }

                      /* Invoke the main loop body as a subroutine */
-- 

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

* [tarantool-patches] Re: [PATCH v5] sql: add index_def to struct Index
  2018-06-27 17:46       ` [tarantool-patches] Re: [PATCH v5] " Ivan Koptelov
@ 2018-06-27 17:57         ` Kirill Shcherbatov
  2018-06-28 18:49           ` Vladislav Shpilevoy
  0 siblings, 1 reply; 23+ messages in thread
From: Kirill Shcherbatov @ 2018-06-27 17:57 UTC (permalink / raw)
  To: tarantool-patches, Vladislav Shpilevoy; +Cc: Ivan Koptelov

LDTM.
Vlad, please take a look. 
Kirill Y. asked to merge this patch somewhat quicker.

On 27.06.2018 20:46, Ivan Koptelov wrote:
> Thank for the review! The new ver. of the patch is at the end of mail.
>> Hi! Thank you for your patch. I'd like to tell that personally I hate netpicks like "same as 17"(to change variable name to tarantool codetyle on minor refactoring changes - you'll understand what I mean a bit later) and strongly recoment to ask Vlad, is this netpick really nessesary to fix.
>> As for me, I've refactored code this way so many times... So, I've wrote comments "Same as 17", but don't like them, really). Keep it in mind, You could use autorenaming feature of your IDE for this purpose.
>>
>> The biggest problem now is writing in NULL ptr with malloc on region_alloc failure in append_string_part.
>>
>> Here we go: 1. Take a look into commit history: you have picked an extra commit 'sql: remove zName and nColumn from SQL' on rebase.
>> As you see, it contains not too much changes; and they are wrong (compilation error).
>> +++ b/src/box/sql/trigger.c
>> @@ -873,6 +873,8 @@ codeRowTrigger(Parse * pParse,      /* Current parse context */
>>          pSubParse->pToplevel = pTop;
>>          pSubParse->eTriggerOp = pTrigger->op;
>>          pSubParse->nQueryLoop = pParse->nQueryLoop;
>> +       struct region *region = &fiber()->gc;
>> +       pSubParse->region_initial_size = region_used(region);
>>
>> Use interactive rebase to drop this commit.
> I don't remember, why it was needed. Rebased onto 2.0.
>> 2. I still have compilation error on gcc build:
>> /home/kir/tarantool/src/box/sql/fkey.c: In function ‘fkScanChildren’:
>> /home/kir/tarantool/src/box/sql/fkey.c:653:47: error: signed and unsigned type in conditional expression [-Werror=sign-compare]
>>            pIdx->def->key_def->parts[i].fieldno : -1;
>>                                                 ^
>> /home/kir/tarantool/src/box/sql/fkey.c: At top level:
>> cc1: error: unrecognized command line option ‘-Wno-format-truncation’ [-Werror]
>> cc1: all warnings being treated as errors
>> src/box/sql/CMakeFiles/sql.dir/build.make:374: recipe for target 'src/box/sql/CMakeFiles/sql.dir/fkey.c.o' failed
>> make[2]: *** [src/box/sql/CMakeFiles/sql.dir/fkey.c.o] Error 1
>> make[2]: *** Waiting for unfinished jobs....
>>
>> You need to cast fieldno to int.
> Fixed.
>>> Here is the patch:
>>> --
>>> 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
>>> and zName are removed from Index. All usages of this
>>> fields changed to usage of corresponding index_def
>>> fields.
>>> index_is_unique(), sql_index_collation() and
>>> index_column_count() are removed with calls of
>>> index_def corresponding fields.
>>>
>>> Closes: #3369
>>> ---
>>> Branch:
>>> https://github.com/tarantool/tarantool/tree/sb/gh-3369-use-index-def-in-select-and-where
>>> Issue: https://github.com/tarantool/tarantool/issues/3369
>>>    src/box/sql.c           |  54 +++---
>>>    src/box/sql/analyze.c   |  40 ++--
>>>    src/box/sql/build.c     | 488
>>> ++++++++++++++++++++++++------------------------
>>>    src/box/sql/delete.c    |  10 +-
>>>    src/box/sql/expr.c      |  48 ++---
>>>    src/box/sql/fkey.c      |  48 ++---
>>>    src/box/sql/insert.c    |  76 ++++----
>>>    src/box/sql/pragma.c    |  28 +--
>>>    src/box/sql/select.c    |   2 +-
>>>    src/box/sql/sqliteInt.h |  74 ++------
>>>    src/box/sql/trigger.c   |   2 -
>>>    src/box/sql/update.c    |  10 +-
>>>    src/box/sql/vdbeaux.c   |   2 +-
>>>    src/box/sql/vdbemem.c   |   4 +-
>>>    src/box/sql/where.c     | 151 +++++++--------
>>>    src/box/sql/wherecode.c |  51 ++---
>>>    16 files changed, 517 insertions(+), 571 deletions(-)
>>>
>>> diff --git a/src/box/sql.c b/src/box/sql.c
>>> index 82f3d6d52..a24812c65 100644
>>> --- a/src/box/sql.c
>>> +++ b/src/box/sql.c
>>> @@ -1452,8 +1452,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;
>>>        }
>>> @@ -1564,20 +1564,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;
>>>        }
>>>
>>> @@ -1587,46 +1586,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..ca699ecd9 100644
>>> --- a/src/box/sql/analyze.c
>>> +++ b/src/box/sql/analyze.c
>>> @@ -849,7 +849,6 @@ analyzeOneTable(Parse * pParse,    /* Parser context */
>>>            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 */
>>>
>>>            if (pOnlyIdx && pOnlyIdx != pIdx)
>>>                continue;
>>> @@ -860,9 +859,9 @@ analyzeOneTable(Parse * pParse,    /* Parser context */
>>>            if (IsPrimaryKeyIndex(pIdx)) {
>>>                zIdxName = pTab->def->name;
>>>            } else {
>>> -            zIdxName = pIdx->zName;
>>> +            zIdxName = pIdx->def->name;
>>>            }
>> 3. Oh, extra braces here. Hate such diffs too, dude...
>>   > -        nColTest = index_column_count(pIdx);
> Fxd.
>>> +        int nColTest = pIdx->def->key_def->part_count;
>>>
>>>            /* Populate the register containing the index name. */
>>>            sqlite3VdbeLoadString(v, regIdxname, zIdxName);
>>> @@ -917,7 +916,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:
>>>             *
>>> @@ -969,7 +968,7 @@ analyzeOneTable(Parse * pParse,    /* Parser context */
>>>                 */
>>>                sqlite3VdbeAddOp0(v, OP_Goto);
>>>                addrNextRow = sqlite3VdbeCurrentAddr(v);
>>> -            if (nColTest == 1 && index_is_unique(pIdx)) {
>>> +            if (nColTest == 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 +977,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 < nColTest; ++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,
>>> @@ -1006,7 +1004,8 @@ analyzeOneTable(Parse * pParse,    /* Parser
>>> context */
>>>                for (i = 0; i < nColTest; i++) {
>>>                    sqlite3VdbeJumpHere(v, aGotoChng[i]);
>>>                    sqlite3VdbeAddOp3(v, OP_Column, iIdxCur,
>>> -                          pIdx->aiColumn[i],
>>> +                          pIdx->def->key_def->
>>> +                              parts[i].fieldno,
>>>                              regPrev + i);
>>>                }
>>>                sqlite3VdbeResolveLabel(v, endDistinctTest);
>>> @@ -1022,15 +1021,14 @@ 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));
>>> +        int nPkColumn = (int) pPk->def->key_def->part_count;
>>> +        int regKeyStat = sqlite3GetTempRange(pParse, nPkColumn);
>> 4. Perhaps, Vlad would intend on tarantool-style names for new variables...Please, change.
> Discussed with Vlad verbally, fixed where necessary.
>>> +        for (int j = 0; j < nPkColumn; ++j) {
>>> +            int k = pPk->def->key_def->parts[j].fieldno;
>>> +            assert(k >= 0 && k < (int) 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);
>>> @@ -1146,7 +1144,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");
>>>        }
>>> diff --git a/src/box/sql/build.c b/src/box/sql/build.c
>>> index 592c9a6fa..84e481de3 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)
>> 5. Extra space before tab.
> Fxd.
>>
>>> +        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;
>>> @@ -376,7 +379,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);
>>> @@ -1041,7 +1044,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)
>>> @@ -1049,22 +1052,20 @@ 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; pIdx = pIdx->pNext) {
>>> +            assert(pIdx->def->key_def->part_count == 1);
>>> +            if (pIdx->def->key_def->parts[0].fieldno == i) {
>>> +                pIdx->def->key_def->parts[0].coll_id = *id;
>>> +                pIdx->def->key_def->parts[0].coll =
>>>                        sql_column_collation(p->def, i, id);
>>>                }
>>>            }
>>> -    } else {
>>> -        sqlite3DbFree(db, zColl);
>>>        }
>>> +    sqlite3DbFree(db, zColl);
>>>    }
>>>
>>>    struct coll *
>>> @@ -1094,66 +1095,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)
>>>    {
>>> @@ -1337,14 +1278,16 @@ 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)
>>> +hasColumn(const struct key_part *key_parts, int nCol, uint32_t fieldno)
>> 6. Please, find a better name for this function and make small new doxygen-stryle comment.
>>   >   {
> Done. Also changed return type to 'bool'
>>> -    while (nCol-- > 0)
>>> -        if (x == *(aiCol++))
>>> +    int i = 0;
>>> +    while (i < nCol) {
>>> +        if (fieldno == key_parts->fieldno)
>>>                return 1;
>>> +        key_parts++;
>>> +        i++;
>>> +    }
>>>        return 0;
>>>    }
>>>
>>> @@ -1364,13 +1307,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;
>>> @@ -1408,14 +1350,28 @@ convertToWithoutRowidTable(Parse * pParse, Table
>>> * pTab)
>>>             * "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];
>>> +
>>> +        struct key_part *parts = pPk->def->key_def->parts;
>>> +        uint32_t part_count = pPk->def->key_def->part_count;
>>> +        uint32_t new_part_count = part_count;
>>> +
>>> +        for (uint32_t i = 1; i < part_count; i++) {
>>> +            if (hasColumn(parts, i, parts[i].fieldno)){
>> 7. Please, compound directly with 0 via != operator.
> Not actual.
>>
>>> +                new_part_count--;
>>> +                bool is_found = false;
>>> +                for (uint32_t j = i + 1; j < part_count; j++){
>>> +                    if (!(hasColumn(parts, j,
>> 8. same
> Not actual, function returns bool now
>>
>>> +                            parts[j].fieldno))) {
>>> +                        parts[i] = parts[j];
>>> +                        is_found = true;
>>> +                        break;
>>> +                    }
>>> +                }
>>> +                if (!(is_found))
>> 9. extra braces?
> Fixed.
>>
>>> +                    break;
>>>                }
>>>            }
>>> -        pPk->nColumn = j;
>>> +        pPk->def->key_def->part_count = new_part_count;
>>>        }
>>>        assert(pPk != 0);
>>>    }
>>> @@ -1497,7 +1453,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);
>>> @@ -1534,7 +1490,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) {
>>> @@ -2463,15 +2419,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.
>>> @@ -2504,7 +2461,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);
>>> @@ -2542,24 +2500,13 @@ sqlite3AllocateIndexObject(sqlite3 * db,    /*
>>> Database connection */
>>>        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 */
>>> +        ROUND8(sizeof(LogEst) * (nCol + 1));    /* Index.aiRowLogEst */
>>>        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;
>>>        }
>>>        return p;
>>> @@ -2648,18 +2595,136 @@ addIndexToTable(Index * pIndex, Table * pTab)
>>>        }
>>>    }
>>>
>>> -bool
>>> -index_is_unique(Index *idx)
>>> +static void
>>> +append_string_part(struct region *r, const char *str,
>>> +          size_t *total_sql_size, Parse *parse)
>>>    {
>>> -    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);
>> 10. please, calculate size_t strlen and reuse it.
>>
>>> +    char * str_part = region_alloc(r, strlen(str> +    if (str_part == NULL){
>>> +        diag_set(OutOfMemory, strlen(str),
>>> +             "region_alloc", "str_part");
>>> +        parse->rc = SQL_TARANTOOL_ERROR;
>>> +        parse->nErr++;
>>> +    }
>> 11. You'll write to NULL ptr on region_alloc failure.
>> 12. I also don't like function interface: struct Parse already contain region that you specify as a first argument.
>> You better change it's signature to have only STRUCT Parser as a first argument or to  return -1 on failure (later, rc |= append_string_part... rc |= append_string_part.. if (rc != 0) ..)
>> 13. I also suggest to make a good doxygen-style comment.
>>
> Fixed, added comment.
>>> +    memcpy(str_part, str, strlen(str));
>>> +    *total_sql_size += strlen(str);
>>> +}
>>> +
>>> +static void
>>> +set_index_def(Parse *parse, Index *index, Table *table, uint32_t iid,
>>> +          const char *name, uint32_t name_len, int on_error,
>>> +          struct ExprList *expr_list, u8 idx_type)
>>> +{
>>> +    struct space_def *space_def = table->def;
>>> +    struct index_opts opts;
>>> +    index_opts_create(&opts);
>>> +    opts.is_unique = on_error != ON_CONFLICT_ACTION_NONE;
>>> +
>>> +    struct key_def *key_def = key_def_new(expr_list->nExpr);
>>> +    if (key_def == NULL) {
>>> +        parse->rc = SQL_TARANTOOL_ERROR;
>>> +        parse->nErr++;
>>> +        goto cleanup;
>>> +    }
>>> +
>>> +    /*
>>> +     * Build initial parts of SQL statement.
>>> +     */
>> 14. Please, make it a single line.
> Done.
>>
>>> +
>>> +    struct region *r = &parse->region;
>>> +    size_t total_sql_size = 0;
>>> +
>>> +    if (idx_type == SQLITE_IDXTYPE_APPDEF) {
>>> +        append_string_part(r, "CREATE INDEX ", &total_sql_size,
>>> +                   parse);
>>> +        append_string_part(r, name, &total_sql_size, parse);
>>> +        append_string_part(r, " ON ", &total_sql_size, parse);
>>> +        append_string_part(r, space_def->name, &total_sql_size,
>>> +                   parse);
>>> +        append_string_part(r, " (", &total_sql_size, parse);
>>> +    }
>>> +
>>> +    for (int i = 0; i < expr_list->nExpr; i++) {
>>> +        Expr *expr = expr_list->a[i].pExpr;
>>> +        sql_resolve_self_reference(parse, table, NC_IdxExpr, expr, 0);
>>> +        if (parse->nErr > 0)
>>> +            goto cleanup;
>>> +
>>> +        Expr *column_expr = sqlite3ExprSkipCollate(expr);
>>> +        if (column_expr->op != TK_COLUMN) {
>>> +            sqlite3ErrorMsg(parse,
>>> +                    "functional indexes aren't supported "
>>> +                    "in the current version");
>>> +            goto cleanup;
>>> +        }
>>> +
>>> +        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 (idx_type == SQLITE_IDXTYPE_APPDEF) {
>>> +                append_string_part(r, name,
>>> +                           &total_sql_size, parse);
>>> +                append_string_part(r, " COLLATE ",
>>> +                           &total_sql_size, parse);
>>> +                const char *coll_name = expr->u.zToken;
>>> +                append_string_part(r, coll_name,
>>> +                           &total_sql_size, parse);
>>> +                append_string_part(r, ", ",
>>> +                           &total_sql_size, parse);
>>> +            }
>>> +        } else {
>>> +            coll = sql_column_collation(space_def, fieldno,
>>> +                            &coll_id);
>>> +            if (idx_type == SQLITE_IDXTYPE_APPDEF) {
>>> +                append_string_part(r, name,
>>> +                           &total_sql_size, parse);
>>> +                append_string_part(r, ", ",
>>> +                           &total_sql_size, parse);
>>> +            }
>>> +        }
>>>
>>> -    return tnt_index->def->opts.is_unique;
>>> +        /*
>>> +        * Tarantool: DESC indexes are not supported so far.
>>> +        * See gh-3016.
>>> +        */
>>> +        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) {
>>> +        index->def = NULL;
>>> +        goto cleanup;
>>> +    }
>>> +
>>> +    if (idx_type == SQLITE_IDXTYPE_APPDEF) {
>>> +        memcpy(region_alloc(r, 1), "\0", 1);
>>> +        total_sql_size += 1;
>>> +        opts.sql = region_join(r, total_sql_size);
>>> +
>>> +        /*
>>> +         * fix last ", " with ")\0" to finish the statement.
>> 14, Please, start with capital letter. And also out-of-comment-type-margin.
> Fxd.
>>> +         */
>>> +        opts.sql[total_sql_size - 3] = ')';
>>> +        opts.sql[total_sql_size - 2] = '\0';
>> 15. Why don't you memcpy(&opts.sql[total_sql_size - 3], ")\0", 2); ? And why do you need two null-terminators? Could the string be shotter?
> Sorry, fixed it all.
>>> +    }
>>> +
>>> +    struct key_def *pk_key_def;
>>> +    if (idx_type == SQLITE_IDXTYPE_APPDEF)
>>> +        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);
>>> +    cleanup:
>>> +        if (key_def != NULL)
>>> +            key_def_delete(key_def);
>> 16. Invalid indent. cleanup shoud be at the beginnig of the line.
> Fxd.
>>
>>>    }
>>>
>>>    void
>>> @@ -2668,16 +2733,14 @@ sql_create_index(struct Parse *parse, struct
>>> Token *token,
>>>             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;
>>> +    Table *pTab = NULL;    /* Table to be indexed */
>>> +    Index *pIndex = NULL;    /* The index to be created */
>>> +    char *name = NULL;    /* Name of the index */
>>> +    int name_len;        /* Number of characters in zName */
>> 17. Please, write new code in Tarantool style. I mean snake_style for variables names and comments on the line before terminated with dot.  /* Table to be indexed. */
>> struct Table *table = NULL;
>>    >       DbFixer sFix;        /* For assigning database names to pTable */
> Fixed.
>>>        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 */
>>> +    char *zExtra = NULL;    /* Extra space after the Index object */
>>>        struct session *user_session = current_session();
>>>
>>>        if (db->mallocFailed || parse->nErr > 0) {
>>> @@ -2749,24 +2812,24 @@ sql_create_index(struct Parse *parse, struct
>>> Token *token,
>>>         * our own name.
>>>         */
>>>        if (token) {
>>> -        zName = sqlite3NameFromToken(db, token);
>>> -        if (zName == 0)
>>> +        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) !=
>>> +            if (sqlite3HashFind(&db->pSchema->tblHash, name) !=
>>>                    NULL) {
>>>                    sqlite3ErrorMsg(parse,
>>>                            "there is already a table named %s",
>>> -                        zName);
>>> +                        name);
>>>                    goto exit_create_index;
>>>                }
>>>            }
>>> -        if (sqlite3HashFind(&pTab->idxHash, zName) != NULL) {
>>> +        if (sqlite3HashFind(&pTab->idxHash, name) != NULL) {
>>>                if (!if_not_exist) {
>>>                    sqlite3ErrorMsg(parse,
>>>                            "index %s.%s already exists",
>>> -                        pTab->def->name, zName);
>>> +                        pTab->def->name, name);
>>>                } else {
>>>                    assert(!db->init.busy);
>>>                }
>>> @@ -2778,10 +2841,9 @@ sql_create_index(struct Parse *parse, struct
>>> Token *token,
>>>            for (pLoop = pTab->pIndex, n = 1; pLoop;
>>>                 pLoop = pLoop->pNext, n++) {
>>>            }
>>> -        zName =
>>> -            sqlite3MPrintf(db, "sqlite_autoindex_%s_%d", pTab->def->name,
>>> -                   n);
>>> -        if (zName == 0) {
>>> +        name = sqlite3MPrintf(db, "sqlite_autoindex_%s_%d",
>>> +                      pTab->def->name, n);
>>> +        if (name == NULL) {
>>>                goto exit_create_index;
>>>            }
>> 18. Extra braces.
> Fixed.
>>>        }
>>> @@ -2807,31 +2869,27 @@ 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));
>>> -        }
>>> -    }
>>> -
>>>        /*
>>>         * Allocate the index structure.
>>>         */
>>> -    nName = sqlite3Strlen30(zName);
>>> +    name_len = sqlite3Strlen30(name);
>>> +
>>> +    if (name_len > BOX_NAME_MAX) {
>>> +        sqlite3ErrorMsg(parse,
>>> +                "%s.%s exceeds indexes' names length limit",
>>> +                pTab->def->name, name);
>>> +        goto exit_create_index;
>>> +    }
>>> +
>>> +    if (sqlite3CheckIdentifierName(parse, name) != SQLITE_OK)
>>> +        goto exit_create_index;
>>> +
>>>        pIndex = sqlite3AllocateIndexObject(db, col_list->nExpr,
>>> -                        nName + nExtra + 1, &zExtra);
>>> +                        name_len + nExtra + 1, &zExtra);
>>>        if (db->mallocFailed) {
>>>            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;
>>>        /*
>>> @@ -2846,7 +2904,6 @@ sql_create_index(struct Parse *parse, struct Token
>>> *token,
>>>            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,
>>> @@ -2855,59 +2912,27 @@ sql_create_index(struct Parse *parse, struct
>>> Token *token,
>>>            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.
>>>         */
>>> -    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");
>>> -            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;
>>> -        }
>>> -        pIndex->coll_array[i] = coll;
>>> -        pIndex->coll_id_array[i] = id;
>>> +    uint32_t max_iid = 0;
>>> +    for (Index *index = pTab->pIndex; index; index = index->pNext) {
>>> +        max_iid = max_iid > index->def->iid ?
>>> +              max_iid :
>>> +              index->def->iid + 1;
>> 19. Not like this...
> Fixed.
>>
>>> +    }
>>>
>>> -        /* Tarantool: DESC indexes are not supported so far.
>>> -         * See gh-3016.
>>> -         */
>>> -        pIndex->sort_order[i] = SORT_ORDER_ASC;
>>> +    set_index_def(parse, pIndex, pTab, max_iid, name, name_len, on_error,
>>> +              col_list, idx_type);
>>> +
>>> +    if (pIndex->def == NULL ||
>>> +        !index_def_is_valid(pIndex->def, pTab->def->name)) {
>>> +        goto exit_create_index;
>>>        }
>> 20. Extra braces.
> Fixed.
>>
>>> +
>>>        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
>>> @@ -2932,25 +2957,27 @@ sql_create_index(struct Parse *parse, struct
>>> Token *token,
>>>             */
>>>            Index *pIdx;
>>>            for (pIdx = pTab->pIndex; pIdx; pIdx = pIdx->pNext) {
>>> -            int k;
>>> +            uint32_t k;
>>>                assert(IsUniqueIndex(pIdx));
>>>                assert(pIdx->idxType != SQLITE_IDXTYPE_APPDEF);
>>>                assert(IsUniqueIndex(pIndex));
>>>
>>> -            if (pIdx->nColumn != pIndex->nColumn)
>>> +            if (pIdx->def->key_def->part_count !=
>>> +                pIndex->def->key_def->part_count) {
>>>                    continue;
>> 21. same
> Fixed.
>>> -            for (k = 0; k < pIdx->nColumn; k++) {
>>> -                assert(pIdx->aiColumn[k] >= 0);
>>> -                if (pIdx->aiColumn[k] != pIndex->aiColumn[k])
>>> +            }
>>> +            for (k = 0; k < pIdx->def->key_def->part_count; k++) {
>>> +                if (pIdx->def->key_def->parts[k].fieldno !=
>>> + pIndex->def->key_def->parts[k].fieldno) {
>>>                        break;
>>> +                }
>> 22. same
> Fixed.
>>
>>>                    struct coll *coll1, *coll2;
>>> -                uint32_t id;
>>> -                coll1 = sql_index_collation(pIdx, k, &id);
>>> -                coll2 = sql_index_collation(pIndex, k, &id);
>>> +                coll1 = pIdx->def->key_def->parts[k].coll;
>>> +                coll2 = pIndex->def->key_def->parts[k].coll;
>>>                    if (coll1 != coll2)
>>>                        break;
>>>                }
>>> -            if (k == pIdx->nColumn) {
>>> +            if (k == pIdx->def->key_def->part_count) {
>>>                    if (pIdx->onError != pIndex->onError) {
>>>                        /* This constraint creates the same index as a
>>> previous
>>>                         * constraint specified somewhere in the CREATE
>>> TABLE statement.
>>> @@ -2984,7 +3011,7 @@ sql_create_index(struct Parse *parse, struct Token
>>> *token,
>>>        assert(parse->nErr == 0);
>>>        if (db->init.busy) {
>>>            Index *p;
>>> -        p = sqlite3HashInsert(&pTab->idxHash, pIndex->zName, pIndex);
>>> +        p = sqlite3HashInsert(&pTab->idxHash, pIndex->def->name, pIndex);
>>>            if (p) {
>>>                assert(p == pIndex);    /* Malloc must have failed */
>>>                sqlite3OomFault(db);
>>> @@ -3082,44 +3109,7 @@ sql_create_index(struct Parse *parse, struct
>>> Token *token,
>>>        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
>>> @@ -3745,9 +3735,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);
>>> @@ -3768,11 +3758,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 8b13f6077..931a15a60 100644
>>> --- a/src/box/sql/delete.c
>>> +++ b/src/box/sql/delete.c
>>> @@ -269,11 +269,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 {
>>> @@ -569,13 +570,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) {
>> 23. Extra whitespace in tab area (visible in git diff).
> Fixed.
>>>                /*
>>>                 * This column was already computed by the
>>>                 * previous index.
>>> diff --git a/src/box/sql/expr.c b/src/box/sql/expr.c
>>> index f03c7a3cd..b752084d4 100644
>>> --- a/src/box/sql/expr.c
>>> +++ b/src/box/sql/expr.c
>>> @@ -2423,20 +2423,24 @@ 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);
>> 24. What the testcase is? Why ">part_count" is a valid construction? Drop them all.
> Fxd.
>>
>>> +                if (part_count >= BMS - 1)
>>>                        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 */
>>> +                    if ((int)part_count > nExpr
>>> +                        || !pIdx->def->opts.is_unique) {
>>> +                        /* This index is not unique over the IN RHS
>>> columns */
>>> +                        continue;
>>>                        }
>>>                    }
>>>
>>> @@ -2450,12 +2454,13 @@ 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) {
>>>                                continue;
>>> @@ -2484,18 +2489,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
>>> @@ -2517,7 +2521,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);
>>>                            }
>>>                        }
>>> @@ -3148,12 +3152,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
>>> +            tab->def->fields[fieldno].affinity;
>>> +        if (pk->def->key_def->part_count == 1
>>>                && affinity == AFFINITY_INTEGER
>>> -            && pk->aiColumn[0] < nVector) {
>>> -            int reg_pk = rLhs + pk->aiColumn[0];
>>> +            && (int) fieldno < nVector) { int reg_pk = rLhs + (int)fieldno;
>>>                sqlite3VdbeAddOp2(v, OP_MustBeInt, reg_pk, destIfFalse);
>>>            }
>>>        }
>>> @@ -3485,7 +3489,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 e3fff37fe..c5fec3161 100644
>>> --- a/src/box/sql/fkey.c
>>> +++ b/src/box/sql/fkey.c
>>> @@ -257,8 +257,8 @@ sqlite3FkLocateIndex(Parse * pParse,    /* Parse
>>> context to store any error in */
>>>        }
>>>
>>>        for (pIdx = pParent->pIndex; pIdx; pIdx = pIdx->pNext) {
>>> -        int nIdxCol = index_column_count(pIdx);
>>> -        if (nIdxCol == nCol && index_is_unique(pIdx)
>>> +        int nIdxCol = pIdx->def->key_def->part_count;
>> 25. same as 17
> Fxd.
>>
>>> +        if (nIdxCol == nCol && pIdx->def->opts.is_unique
>>>                && 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
>>> @@ -287,8 +287,10 @@ sqlite3FkLocateIndex(Parse * pParse,    /* Parse
>>> context to store any error in */
>>>                     * the default collation sequences for each column.
>>>                     */
>>>                    int i, j;
>>> -                for (i = 0; i < nCol; i++) {
>>> -                    i16 iCol = pIdx->aiColumn[i];    /* Index of column
>>> in parent tbl */
>> 26. Move comment to prev. line, terminate it with dot.
> Fixed.
>>
>>> +                struct key_part *part =
>>> +                    pIdx->def->key_def->parts;
>>> +                for (i = 0; i < nCol; i++, part++) {
>>> +                    i16 iCol = (int) part->fieldno;    /* Index of
>>> column in parent tbl */
>>>                        char *zIdxCol;    /* Name of indexed column */
>>>
>>>                        if (iCol < 0)
>>> @@ -303,9 +305,7 @@ sqlite3FkLocateIndex(Parse * pParse,    /* Parse
>>> context to store any error in */
>>>                        def_coll = sql_column_collation(pParent->def,
>>>                                        iCol,
>>>                                        &id);
>>> -                    struct coll *coll =
>>> -                        sql_index_collation(pIdx, i,
>>> -                                    &id);
>>> +                    struct coll *coll = part->coll;
>>>                        if (def_coll != coll)
>>>                            break;
>>>
>>> @@ -465,13 +465,15 @@ fkLookupParent(Parse * pParse,    /* Parse context */
>>>                    for (i = 0; i < nCol; i++) {
>>>                        int iChild = aiCol[i] + 1 + regData;
>>>                        int iParent =
>>> -                        pIdx->aiColumn[i] + 1 + regData;
>>> -                    assert(pIdx->aiColumn[i] >= 0);
>>> +                        (int) pIdx->def->key_def->parts[i].fieldno
>>> +                        + 1 + regData;
>>>                        assert(aiCol[i] != pTab->iPKey);
>>> -                    if (pIdx->aiColumn[i] == pTab->iPKey) {
>>> +                    if ((int)pIdx->def->key_def->
>>> +                        parts[i].fieldno == pTab->iPKey) {
>>>                            /* The parent key is a composite key that
>>> includes the IPK column */
>>>                            iParent = regData;
>>>                        }
>>> +
>>>                        sqlite3VdbeAddOp3(v, OP_Ne, iChild,
>>>                                  iJump, iParent);
>>>                        VdbeCoverage(v);
>>> @@ -623,7 +625,7 @@ fkScanChildren(Parse * pParse,    /* Parse context */
>>>        Vdbe *v = sqlite3GetVdbe(pParse);
>>>
>>>        assert(pIdx == 0 || pIdx->pTable == pTab);
>>> -    assert(pIdx == 0 || (int)index_column_count(pIdx) == pFKey->nCol);
>>> +    assert(pIdx == 0 || (int) pIdx->def->key_def->part_count ==
>>> pFKey->nCol);
>> 27. pIdx == NULL
> Fxd.
>>
>>>        assert(pIdx != 0);
>>>
>>>        if (nIncr < 0) {
>>> @@ -647,7 +649,8 @@ fkScanChildren(Parse * pParse,    /* Parse context */
>>>            i16 iCol;    /* Index of column in child table */
>>>            const char *zCol;    /* Name of column in child table */
>>>
>>> -        iCol = pIdx ? pIdx->aiColumn[i] : -1;
>>> +        iCol = pIdx != NULL ?
>>> +               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);
>>> @@ -672,10 +675,9 @@ fkScanChildren(Parse * pParse,    /* Parse context */
>>>            Expr *pEq, *pAll = 0;
>>>            Index *pPk = sqlite3PrimaryKeyIndex(pTab);
>>>            assert(pIdx != 0);
>>> -        int col_count = index_column_count(pPk);
>>> +        int col_count = pPk->def->key_def->part_count;
>>>            for (i = 0; i < col_count; i++) {
>>> -            i16 iCol = pIdx->aiColumn[i];
>>> -            assert(iCol >= 0);
>>> +            i16 iCol = (int) pIdx->def->key_def->parts[i].fieldno;
>> 28. same as 17.
> Fixed.
>>>                pLeft = exprTableRegister(pParse, pTab, regData, iCol);
>>>                pRight =
>>>                    exprTableColumn(db, pTab->def,
>>> @@ -982,7 +984,6 @@ sqlite3FkCheck(Parse * pParse,    /* Parse context */
>>>                if (aiCol[i] == pTab->iPKey) {
>>>                    aiCol[i] = -1;
>>>                }
>>> -            assert(pIdx == 0 || pIdx->aiColumn[i] >= 0);
>>>            }
>>>
>>>            pParse->nTab++;
>>> @@ -1116,10 +1117,10 @@ sqlite3FkOldmask(Parse * pParse,    /* Parse
>>> context */
>>>                Index *pIdx = 0;
>>>                sqlite3FkLocateIndex(pParse, pTab, p, &pIdx, 0);
>>>                if (pIdx) {
>>> -                int nIdxCol = index_column_count(pIdx);
>>> +                int nIdxCol = pIdx->def->key_def->part_count;
>> 29. same as 17.
> Fixed.
>>>                    for (i = 0; i < nIdxCol; i++) {
>>> -                    assert(pIdx->aiColumn[i] >= 0);
>>> -                    mask |= COLUMN_MASK(pIdx->aiColumn[i]);
>>> +                    mask |= COLUMN_MASK(pIdx->def->
>>> +                        key_def->parts[i].fieldno);
>>>                    }
>>>                }
>>>            }
>>> @@ -1254,11 +1255,12 @@ fkActionTrigger(Parse * pParse,    /* Parse
>>> context */
>>>                       || (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 70555c3ec..b535763e9 100644
>>> --- a/src/box/sql/insert.c
>>> +++ b/src/box/sql/insert.c
>>> @@ -90,14 +90,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;
>>>            }
>>>        }
>>>
>>> @@ -1089,7 +1089,7 @@ sqlite3GenerateConstraintChecks(Parse * pParse,
>>>       /* The parser context */
>>>        nCol = def->field_count;
>>>
>>>        pPk = sqlite3PrimaryKeyIndex(pTab);
>>> -    nPkField = index_column_count(pPk);
>>> +    nPkField = pPk->def->key_def->part_count;
>>>
>>>        /* Record that this module has started */
>>>        VdbeModuleComment((v, "BEGIN: GenCnstCks(%d,%d,%d,%d,%d)",
>>> @@ -1253,10 +1253,10 @@ 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);
>>> +        int nIdxCol = (int) pIdx->def->key_def->part_count;
>> 30. same as 17
> Fixed.
>>
>>>            if (uniqueByteCodeNeeded) {
>>>                for (i = 0; i < nIdxCol; ++i) {
>>> -                int fieldno = pIdx->aiColumn[i];
>>> +                int fieldno = pIdx->def->key_def->parts[i].fieldno;
>>>                    int reg;
>>>                    /*
>>>                     * OP_SCopy copies value in
>>> @@ -1284,8 +1284,12 @@ sqlite3GenerateConstraintChecks(Parse *
>>> pParse,        /* The parser context */
>>>                /* 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]] ==
>>> +                reg_pk = regNewData + 1 +
>>> + pIdx->def->key_def->parts[0].fieldno;
>>> +
>>> +                int fieldno = (int) pIdx->def->key_def->
>>> +                    parts[0].fieldno;
>>> +                if (pTab->zColAff[fieldno] ==
>>>                        AFFINITY_INTEGER) {
>>>                        int skip_if_null = sqlite3VdbeMakeLabel(v);
>>>                        if ((pTab->tabFlags & TF_Autoincrement) != 0) {
>>> @@ -1303,7 +1307,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
>>> @@ -1391,7 +1395,7 @@ 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);
>>>
>>> @@ -1401,14 +1405,13 @@ sqlite3GenerateConstraintChecks(Parse *
>>> pParse,        /* The parser context */
>>>                                     nPkField);
>>>            if (isUpdate || on_error == ON_CONFLICT_ACTION_REPLACE) {
>>>                int x;
>>> -            int nPkCol = index_column_count(pPk);
>>> +            int nPkCol = pPk->def->key_def->part_count;
>> 31. same as 17
> Fixed.
>>
>>>                /* 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];
>>> +                    x = pPk->def->key_def->parts[i].fieldno;
>>>                        sqlite3VdbeAddOp3(v, OP_Column,
>>>                                  iThisCur, x, regR + i);
>>>                        VdbeComment((v, "%s.%s", def->name,
>>> @@ -1430,10 +1433,10 @@ sqlite3GenerateConstraintChecks(Parse *
>>> pParse,        /* The parser context */
>>>                              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);
>>> +                    char *p4 = (char *) pPk->def->key_def->parts[i].coll;
>>> +                    x = pPk->def->key_def->parts[i].fieldno;
>>> +                    if (pPk->tnum==0)
>>> +                        x = -1;
>>>                        if (i == (nPkCol - 1)) {
>>>                            addrJump = addrUniqueOk;
>>>                            op = OP_Eq;
>>> @@ -1610,8 +1613,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 */
>>> @@ -1629,7 +1632,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));
>>>                }
>>>            }
>>>        }
>>> @@ -1666,27 +1669,23 @@ 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);
>>> +    uint32_t nDestCol = pDest->def->key_def->part_count;
>>> +    uint32_t nSrcCol = pSrc->def->key_def->part_count;
>> 32. same as 17
> Fixed.
>>
>>>        if (nDestCol != nSrcCol) {
>>>            return 0;    /* Different number of columns */
>>>        }
>>>        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 (i = 0; i < nSrcCol; 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 */
>>> @@ -1858,16 +1857,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 == 0)
>> 33. Terminate comment with dot, pSrcIdx == NULL
> Fixed.
>>> +            return 0;
>>>        }
>>>        /* Get server checks. */
>>>        ExprList *pCheck_src = space_checks_expr_list(
>>> @@ -1943,12 +1941,12 @@ 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);
>>>            sqlite3VdbeChangeP5(v, OPFLAG_BULKCSR);
>>> -        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);
>>> diff --git a/src/box/sql/pragma.c b/src/box/sql/pragma.c
>>> index 5fb29c75c..7067a5ab1 100644
>>> --- a/src/box/sql/pragma.c
>>> +++ b/src/box/sql/pragma.c
>>> @@ -370,7 +370,8 @@ sqlite3Pragma(Parse * pParse, Token * pId, /* First
>>> part of [schema.]id field */
>>>                    k = 1;
>>>                } else {
>>>                    for (k = 1; k <= def->field_count &&
>>> -                     pk->aiColumn[k - 1] != (int) i; ++k) {
>>> +                     pk->def->key_def->parts[k - 1].fieldno
>>> +                     != i; ++k) {
>>>                    }
>>>                }
>>>                bool is_nullable = def->fields[i].is_nullable;
>>> @@ -414,7 +415,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,
>>> @@ -443,11 +444,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,
>>> @@ -461,19 +464,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 <
>>> @@ -503,10 +505,8 @@ sqlite3Pragma(Parse * pParse, Token * pId, /* First
>>> part of [schema.]id field */
>>>                                { "c", "u", "pk" };
>>>                            sqlite3VdbeMultiLoad(v, 1,
>>>                                         "isisi", i,
>>> -                                     pIdx->
>>> -                                     zName,
>>> -                                     index_is_unique
>>> -                                     (pIdx),
>>> +                                     pIdx->def->name,
>>> + pIdx->def->opts.is_unique,
>>>                                         azOrigin
>>>                                         [pIdx->
>>>                                          idxType],
>>> diff --git a/src/box/sql/select.c b/src/box/sql/select.c
>>> index 368bcd6f0..c7c186d9d 100644
>>> --- a/src/box/sql/select.c
>>> +++ b/src/box/sql/select.c
>>> @@ -4367,7 +4367,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 47360fa5b..f696591fd 100644
>>> --- a/src/box/sql/sqliteInt.h
>>> +++ b/src/box/sql/sqliteInt.h
>>> @@ -2070,27 +2070,17 @@ struct UnpackedRecord {
>>>     * 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.
>>> + * Indexes name, corresponding space_id, type (in tarantool
>>> + * sense - HASH, TREE, etc) are stored in index definition - in
>>> + * Index.def.
>>> + * SQL statement which created the index and 'is_unique' flag are
>>> + * stored in Index.def.opts. Information about index parts (part
>>> + * count, corresponding space fields' numbers, parts' collations
>>> + * and sort orders, etc) are stored in Index.def.key_def.parts
>>> + *
>>> + * Index.onError indicate the which conflict resolution algorithm
>>> + * to employ whenever an attempt is made to insert a non-unique
>>> + * element in unique index.
>>>     *
>>>     * While parsing a CREATE TABLE or CREATE INDEX statement in order to
>>>     * generate VDBE code (as opposed to reading from Tarantool's _space
>>> @@ -2101,26 +2091,18 @@ 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 */
>>> +    struct index_def *def;
>> 34.
>>>>>> + struct index_def *def;
>>>>>> };>> 10. Will you keep an informative tarantool-style comment here?
>>> Ok, added comment.
>> I still can't see a comment for index_def field in Index struct in your patch.
>> /** Smth like this. */
> All the information about index_def are in comment before struct Index 
> definiton.
> I am not sure, that something must be added before 'index_def' line.
>>>    };
>>>
>>>    /**
>>> @@ -3546,34 +3528,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 *);
>>>
>>> @@ -3661,8 +3615,6 @@ void sqlite3SrcListAssignCursors(Parse *, SrcList *);
>>>    void sqlite3IdListDelete(sqlite3 *, IdList *);
>>>    void sqlite3SrcListDelete(sqlite3 *, SrcList *);
>>>    Index *sqlite3AllocateIndexObject(sqlite3 *, i16, int, char **);
>>> -bool
>>> -index_is_unique(Index *);
>>>
>>>    /**
>>>     * Create a new index for an SQL table.  name is the name of the
>>> @@ -4381,8 +4333,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);
>>> diff --git a/src/box/sql/trigger.c b/src/box/sql/trigger.c
>>> index 042226cde..ad8e2438f 100644
>>> --- a/src/box/sql/trigger.c
>>> +++ b/src/box/sql/trigger.c
>>> @@ -873,8 +873,6 @@ codeRowTrigger(Parse * pParse,    /* Current parse
>>> context */
>>>        pSubParse->pToplevel = pTop;
>>>        pSubParse->eTriggerOp = pTrigger->op;
>>>        pSubParse->nQueryLoop = pParse->nQueryLoop;
>>> -    struct region *region = &fiber()->gc;
>>> -    pSubParse->region_initial_size = region_used(region);
>>>
>>>        v = sqlite3GetVdbe(pSubParse);
>>>        if (v) {
>>> diff --git a/src/box/sql/update.c b/src/box/sql/update.c
>>> index 10385eb78..fc479fb05 100644
>>> --- a/src/box/sql/update.c
>>> +++ b/src/box/sql/update.c
>>> @@ -238,14 +238,14 @@ 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);
>>> +        int nIdxCol = pIdx->def->key_def->part_count;
>> 35. Same as 17
> Fxd.
>>
>>>            if (chngPk || hasFK || pIdx->pPartIdxWhere || pIdx == pPk) {
>>>                reg = ++pParse->nMem;
>>>                pParse->nMem += nIdxCol;
>>>            } else {
>>>                reg = 0;
>>>                for (i = 0; i < nIdxCol; i++) {
>>> -                i16 iIdxCol = pIdx->aiColumn[i];
>>> +                i16 iIdxCol = pIdx->def->key_def->parts[i].fieldno;
>> 36. Same as 17
> Fxd.
>>
>>>                    if (iIdxCol < 0 || aXRef[iIdxCol] >= 0) {
>>>                        reg = ++pParse->nMem;
>>>                        pParse->nMem += nIdxCol;
>>> @@ -307,7 +307,7 @@ sqlite3Update(Parse * pParse,        /* The parser
>>> context */
>>>            nPk = nKey;
>>>        } else {
>>>            assert(pPk != 0);
>>> -        nPk = index_column_count(pPk);
>>> +        nPk = pPk->def->key_def->part_count;
>> 37. Same as 17
> Fxd.
>>>        }
>>>        iPk = pParse->nMem + 1;
>>>        pParse->nMem += nPk;
>>> @@ -334,9 +334,9 @@ sqlite3Update(Parse * pParse,        /* The parser
>>> context */
>>>            }
>>>        } else {
>>>            for (i = 0; i < nPk; i++) {
>>> -            assert(pPk->aiColumn[i] >= 0);
>>>                sqlite3ExprCodeGetColumnOfTable(v, def, iDataCur,
>>> -                            pPk->aiColumn[i],
>>> +                            pPk->def->key_def->
>>> +                                parts[i].fieldno,
>>>                                iPk + i);
>>>            }
>>>        }
>>> diff --git a/src/box/sql/vdbeaux.c b/src/box/sql/vdbeaux.c
>>> index 679bd0bc1..520b309d9 100644
>>> --- a/src/box/sql/vdbeaux.c
>>> +++ b/src/box/sql/vdbeaux.c
>>> @@ -1165,7 +1165,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 f408b7701..51b5d516e 100644
>>> --- a/src/box/sql/vdbemem.c
>>> +++ b/src/box/sql/vdbemem.c
>>> @@ -1087,7 +1087,7 @@ 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 nCol = pIdx->def->key_def->part_count;
>> 38. Same as 17..
> Fxd.
>>>                nByte = sizeof(Mem) * nCol +
>>>                    ROUND8(sizeof(UnpackedRecord));
>>> @@ -1095,7 +1095,7 @@ valueNew(sqlite3 * db, struct ValueNewStat4Ctx *p)
>>>                    (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);
>>> diff --git a/src/box/sql/where.c b/src/box/sql/where.c
>>> index e6c34f34a..3f95c4243 100644
>>> --- a/src/box/sql/where.c
>>> +++ b/src/box/sql/where.c
>>> @@ -372,13 +372,19 @@ whereScanInit(WhereScan * pScan,    /* The
>>> WhereScan object being initialized */
>>>        pScan->is_column_seen = false;
>>>        if (pIdx) {
>>>            int j = iColumn;
>>> -        iColumn = pIdx->aiColumn[j];
>>> +        iColumn = pIdx->def->key_def->parts[j].fieldno;
>>> +        /*
>>> +         * pIdx->tnum == 0 means that pIdx is a fake
>>> +         * integer primary key index
>>> +         */
>>> +        if (pIdx->tnum == 0)
>>> +            iColumn = -1;
>>> +
>>>            if (iColumn >= 0) {
>>>                char affinity =
>>> 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;
>>>            }
>>>        }
>>> @@ -541,47 +547,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 +616,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 +626,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;
>>>            }
>>> @@ -1184,7 +1168,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 +1230,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 *pColl = p->def->key_def->parts[nEq].coll;
>> 39. Same as 17
> Fxd.
>>>        if (pLower) {
>>>            rc = sqlite3Stat4ValueFromExpr(pParse, pLower->pExpr->pRight,
>>>                               aff, &p1);
>>> @@ -1448,7 +1431,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 +1581,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 +1602,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,7 +1734,7 @@ 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 (p->pIndex && (zName = p->pIndex->def->name) != 0) {
>>>            if (strncmp(zName, "sqlite_autoindex_", 17) == 0) {
>>>                int i = sqlite3Strlen30(zName) - 1;
>>>                while (zName[i] != '_')
>>> @@ -2314,7 +2297,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 +2318,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)) {
>>> +        if (pLhs->op != TK_COLUMN || pLhs->iTable != iCur
>>> +            || pLhs->iColumn != (int)pIdx->def->key_def->parts[i +
>>> nEq].fieldno
>>> +            || pIdx->def->key_def->parts[i + nEq].sort_order !=
>>> + pIdx->def->key_def->parts[nEq].sort_order)
>>>                break;
>>> -        }
>>>
>>>            aff = sqlite3CompareAffinity(pRhs, sqlite3ExprAffinity(pLhs));
>>>            idxaff =
>>> @@ -2353,7 +2334,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 +2377,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 nProbeCol = 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) {
>>> @@ -2452,8 +2433,9 @@ whereLoopAddBtreeIndex(WhereLoopBuilder *
>>> pBuilder,    /* The WhereLoop factory */
>>>            LogEst nOutUnadjusted;    /* nOut before IN() and WHERE
>>> adjustments */
>>>            int nIn = 0;
>>>            int nRecValid = pBuilder->nRecValid;
>>> +        uint32_t j = pProbe->def->key_def->parts[saved_nEq].fieldno;
>>>            if ((eOp == WO_ISNULL || (pTerm->wtFlags & TERM_VNULL) != 0)
>>> -            && indexColumnNotNull(pProbe, saved_nEq)
>>> +            && !pProbe->pTable->def->fields[j].is_nullable
>>>                ) {
>>>                continue;    /* ignore IS [NOT] NULL constraints on NOT
>>> NULL columns */
>>>            }
>>> @@ -2523,14 +2505,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 == nProbeCol - 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 +2576,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;
>>> @@ -2749,7 +2732,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 +2775,7 @@ indexMightHelpWithOrderBy(WhereLoopBuilder * pBuilder,
>>>    {
>>>        ExprList *pOB;
>>>        int ii, jj;
>>> -    int nIdxCol = index_column_count(pIndex);
>>> +    int nIdxCol = pIndex->def->key_def->part_count;
>>>        if (index_is_unordered(pIndex))
>>>            return 0;
>>>        if ((pOB = pBuilder->pWInfo->pOrderBy) == 0)
>>> @@ -2803,7 +2786,8 @@ indexMightHelpWithOrderBy(WhereLoopBuilder * pBuilder,
>>>                if (pExpr->iColumn < 0)
>>>                    return 1;
>>>                for (jj = 0; jj < nIdxCol; jj++) {
>>> -                if (pExpr->iColumn == pIndex->aiColumn[jj])
>>> +                if (pExpr->iColumn == (int)
>>> + pIndex->def->key_def->parts[jj].fieldno)
>>>                        return 1;
>>>                }
>>>            }
>>> @@ -2882,7 +2866,6 @@ whereLoopAddBtree(WhereLoopBuilder * pBuilder,
>>> /* WHERE clause information */
>>>        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 */
>>>        SrcList *pTabList;    /* The FROM clause */
>>>        struct SrcList_item *pSrc;    /* The FROM clause btree term to add */
>>>        WhereLoop *pNew;    /* Template WhereLoop object */
>>> @@ -2913,11 +2896,29 @@ whereLoopAddBtree(WhereLoopBuilder *
>>> pBuilder,    /* WHERE clause information */
>>>             */
>>>            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;
>>> +
>>> +        struct key_def *key_def = key_def_new(1);
>>> +        if (key_def == NULL)
>>> +            return SQLITE_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 index_opts = index_opts_default;
>>> +
>>> +        sPk.def = index_def_new(pTab->def->id, 0, "primary",
>>> +                    sizeof("primary") - 1, TREE, &index_opts,
>>> +                    key_def, NULL);
>>> +        key_def_delete(key_def);
>>> +
>>> +        if (sPk.def == NULL) {
>>> +            return SQLITE_ERROR;
>>> +        }
>> 39. Extra braces.
> Fixed.
>>> +
>>>            aiRowEstPk[0] = sql_space_tuple_log_count(pTab);
>>>            aiRowEstPk[1] = 0;
>>>            pFirst = pSrc->pTab->pIndex;
>>> @@ -3392,8 +3393,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 +3455,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);
>>> +                    iColumn = pIndex->def->key_def->
>>> +                        parts[j].fieldno;
>>> +                    revIdx = pIndex->def->key_def->
>>> +                        parts[j].sort_order;
>>>                        if (iColumn == pIndex->pTable->iPKey)
>>>                            iColumn = -1;
>>>                    } else {
>>> @@ -3506,8 +3508,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;
>>> @@ -4785,7 +4786,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
>>> @@ -4918,7 +4919,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 eaab0b657..a04013835 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)));
>>>            }
>>>        }
>>> @@ -1275,12 +1276,12 @@ sqlite3WhereCodeOneLoopStart(WhereInfo *
>>> pWInfo,    /* Complete information about t
>>>                   || (pWInfo->wctrlFlags & WHERE_ORDERBY_MIN) == 0);
>>>            int nIdxCol;
>>>            if (pIdx != NULL)
>>> -            nIdxCol = index_column_count(pIdx);
>>> +            nIdxCol = 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];
>>> +            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.
>>> @@ -1291,8 +1292,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;
>>> @@ -1331,14 +1331,14 @@ sqlite3WhereCodeOneLoopStart(WhereInfo *
>>> pWInfo,    /* Complete information about t
>>>                    assert((bRev & ~1) == 0);
>>>                    pLevel->iLikeRepCntr <<= 1;
>>>                    pLevel->iLikeRepCntr |=
>>> -                    bRev ^ (sql_index_column_sort_order(pIdx, nEq) ==
>>> +                    bRev ^ (pIdx->def->key_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;
>>>                }
>>>            }
>>> @@ -1350,7 +1350,7 @@ sqlite3WhereCodeOneLoopStart(WhereInfo *
>>> pWInfo,    /* Complete information about t
>>>             * start and end terms (pRangeStart and pRangeEnd).
>>>             */
>>>            if ((nEq < nIdxCol &&
>>> -             bRev == (sql_index_column_sort_order(pIdx, nEq) ==
>>> +             bRev == (pIdx->def->key_def->parts[nEq].sort_order ==
>>>                      SORT_ORDER_ASC)) ||
>>>                (bRev && nIdxCol == nEq)) {
>>>                SWAP(pRangeEnd, pRangeStart);
>>> @@ -1433,13 +1433,14 @@ sqlite3WhereCodeOneLoopStart(WhereInfo *
>>> pWInfo,    /* Complete information about t
>>>                }
>>>            } else {
>>>                pk = sqlite3PrimaryKeyIndex(pIdx->pTable);
>>> +            uint32_t fieldno = pk->def->key_def->parts[0].fieldno;
>>>                affinity =
>>> - pIdx->pTable->def->fields[pk->aiColumn[0]].affinity;
>>> + pIdx->pTable->def->fields[fieldno].affinity;
>>>            }
>>>
>>>            int nPkCol;
>>>            if (pk != NULL)
>>> -            nPkCol = index_column_count(pk);
>>> +            nPkCol = pk->def->key_def->part_count;
>>>            else
>>>                nPkCol = idx_pk->key_def->part_count;
>>>            if (nPkCol == 1 && affinity == AFFINITY_INTEGER) {
>>> @@ -1450,8 +1451,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)) {
>>> @@ -1563,10 +1565,10 @@ 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 nPkCol = pPk->def->key_def->part_count;
>> 41. Same as 17
> Fixed.
>>
>>>                int iKeyReg = sqlite3GetTempRange(pParse, nPkCol);
>>>                for (j = 0; j < nPkCol; j++) {
>>> -                k = pPk->aiColumn[j];
>>> +                k = pPk->def->key_def->parts[j].fieldno;
>>>                    sqlite3VdbeAddOp3(v, OP_Column, iIdxCur, k,
>>>                              iKeyReg + j);
>>>                }
>>> @@ -1671,7 +1673,7 @@ 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 nPkCol = pPk->def->key_def->part_count;
>> 42. Same as 17
> Fxd.
>>
>>>                regRowset = pParse->nTab++;
>>>                sqlite3VdbeAddOp2(v, OP_OpenTEphemeral,
>>>                          regRowset, nPkCol);
>>> @@ -1775,13 +1777,16 @@ 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 nPk = pPk->def->key_def->part_count;
>>>                            int iPk;
>>>
>>>                            /* Read the PK into an array of temp registers. */
>>>                            r = sqlite3GetTempRange(pParse, nPk);
>>>                            for (iPk = 0; iPk < nPk; iPk++) {
>>> -                            int iCol = pPk->aiColumn[iPk];
>>> +                            int iCol = pPk->def->
>> 43. Same as 17
> Fixed.
>>> +                                key_def->
>>> +                                parts[iPk].
>>> +                                fieldno;
>>>                                sqlite3ExprCodeGetColumnToReg
>>>                                    (pParse, pTab->def,
>>>                                     iCol, iCur,
>>>
> Here is the patch:
> ---
> Branch:https://github.com/tarantool/tarantool/tree/sb/gh-3369-use-index-def-in-select-and-where 
> 
> Issue:https://github.com/tarantool/tarantool/issues/3369
> 
> 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
> and zName are removed from Index. All usages of this
> fields changed to usage of corresponding index_def
> fields.
> index_is_unique(), sql_index_collation() and
> index_column_count() are removed with calls of
> index_def corresponding fields.
> 
> Closes: #3369
> ---
>   src/box/sql.c           |  54 ++--
>   src/box/sql/analyze.c   |  79 +++---
>   src/box/sql/build.c     | 642 
> ++++++++++++++++++++++++------------------------
>   src/box/sql/delete.c    |  10 +-
>   src/box/sql/expr.c      |  48 ++--
>   src/box/sql/fkey.c      | 113 +++++----
>   src/box/sql/insert.c    | 129 +++++-----
>   src/box/sql/pragma.c    |  28 +--
>   src/box/sql/select.c    |   2 +-
>   src/box/sql/sqliteInt.h |  76 +-----
>   src/box/sql/update.c    |  39 +--
>   src/box/sql/vdbeaux.c   |   2 +-
>   src/box/sql/vdbemem.c   |  12 +-
>   src/box/sql/where.c     | 162 ++++++------
>   src/box/sql/wherecode.c | 104 ++++----
>   15 files changed, 742 insertions(+), 758 deletions(-)
> 
> diff --git a/src/box/sql.c b/src/box/sql.c
> index 11353150e..24e37652e 100644
> --- a/src/box/sql.c
> +++ b/src/box/sql.c
> @@ -1452,8 +1452,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;
>       }
> @@ -1564,20 +1564,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;
>       }
> 
> @@ -1587,46 +1586,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..1e9d8ea36 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,10 +1000,11 @@ analyzeOneTable(Parse * pParse,    /* Parser 
> context */
>                *  ...
>                */
>               sqlite3VdbeJumpHere(v, addrNextRow - 1);
> -            for (i = 0; i < nColTest; i++) {
> +            for (i = 0; i < part_count; i++) {
>                   sqlite3VdbeJumpHere(v, aGotoChng[i]);
>                   sqlite3VdbeAddOp3(v, OP_Column, iIdxCur,
> -                          pIdx->aiColumn[i],
> +                          pIdx->def->key_def->
> +                              parts[i].fieldno,
>                             regPrev + i);
>               }
>               sqlite3VdbeResolveLabel(v, endDistinctTest);
> @@ -1022,19 +1020,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));
> +        int pk_part_count = (int) pPk->def->key_def->part_count;
> +        int regKeyStat = sqlite3GetTempRange(pParse, pk_part_count);
> +        for (int j = 0; j < pk_part_count; ++j) {
> +            int k = pPk->def->key_def->parts[j].fieldno;
> +            assert(k >= 0 && k < (int) 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 +1054,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 +1074,12 @@ analyzeOneTable(Parse * pParse,    /* Parser 
> context */
>            * be taken
>            */
>           VdbeCoverageNeverTaken(v);
> -        for (i = 0; i < nColTest; 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 +1143,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");
>       }
> diff --git a/src/box/sql/build.c b/src/box/sql/build.c
> index 0da7d805b..2c82644e6 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;
> @@ -376,7 +379,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);
> @@ -1041,7 +1044,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)
> @@ -1049,22 +1052,20 @@ 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; pIdx = pIdx->pNext) {
> +            assert(pIdx->def->key_def->part_count == 1);
> +            if (pIdx->def->key_def->parts[0].fieldno == i) {
> +                pIdx->def->key_def->parts[0].coll_id = *id;
> +                pIdx->def->key_def->parts[0].coll =
>                       sql_column_collation(p->def, i, id);
>               }
>           }
> -    } else {
> -        sqlite3DbFree(db, zColl);
>       }
> +    sqlite3DbFree(db, zColl);
>   }
> 
>   struct coll *
> @@ -1094,66 +1095,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)
>   {
> @@ -1337,15 +1278,26 @@ createTableStmt(sqlite3 * db, Table * p)
>       return zStmt;
>   }
> 
> -/* Return true if value x is found any of the first nCol entries of aiCol[]
> +/**
> + * Look for duplicates for the given index key part in index key
> + * prefix. Prefix is formed the following way:
> + * (first_key_part, ..., key_part_just_before_given). Index key
> + * parts are duplicates of each other if their 'fieldno' are the
> + * same.
> + *
> + * @param key_parts index key parts.
> + * @param part_index index of part, for which we look for
> + *       duplicates.
> + * @retval is there a duplicate part in prefix or not.
>    */
> -static int
> -hasColumn(const i16 * aiCol, int nCol, int x)
> +static bool
> +is_part_duplicated(const struct key_part *key_parts, uint32_t part_index)
>   {
> -    while (nCol-- > 0)
> -        if (x == *(aiCol++))
> -            return 1;
> -    return 0;
> +    for (uint32_t i = 0; i < part_index; i++) {
> +         if (key_parts[i].fieldno == key_parts[part_index].fieldno)
> +             return true;
> +    }
> +    return false;
>   }
> 
>   /*
> @@ -1364,13 +1316,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;
> @@ -1404,18 +1355,33 @@ convertToWithoutRowidTable(Parse * pParse, Table 
> * pTab)
>           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.
> +         * 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];
> +
> +        struct key_part *parts = pPk->def->key_def->parts;
> +        uint32_t part_count = pPk->def->key_def->part_count;
> +        uint32_t new_part_count = part_count;
> +
> +        for (uint32_t i = 1; i < part_count; i++) {
> +            if (is_part_duplicated(parts, i)) {
> +                new_part_count--;
> +                bool is_found = false;
> +                for (uint32_t j = i + 1; j < part_count; j++) {
> +                    if (!is_part_duplicated(parts, j)) {
> +                        parts[i] = parts[j];
> +                        is_found = true;
> +                        break;
> +                    }
> +                }
> +                if (!is_found)
> +                    break;
>               }
>           }
> -        pPk->nColumn = j;
> +        pPk->def->key_def->part_count = new_part_count;
>       }
>       assert(pPk != 0);
>   }
> @@ -1497,7 +1463,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);
> @@ -1534,7 +1500,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) {
> @@ -2463,15 +2429,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.
> @@ -2502,7 +2469,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);
> @@ -2531,34 +2499,20 @@ sqlite3RefillIndex(Parse * pParse, Index * 
> pIndex, int memRootPage)
>    */
>   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 */
> +               i16 nCol    /* Total number of columns in the index */
>       )
>   {
>       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);
> +        ROUND8(sizeof(LogEst) * (nCol + 1));    /* Index.aiRowLogEst */
> +    p = sqlite3DbMallocZero(db, nByte);
>       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;
>       }
>       return p;
>   }
> @@ -2646,18 +2600,142 @@ 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);
> +/**
> + * Allocate memory on parser region and copy given string (part of the
> + * sql statement) into the allocated memory. Add size of the
> + * given string to 'total_sql_size' in order to keep size of sql
> + * statement string being hold.
> + *
> + * @param parse Parse context.
> + * @param str String (a part of sql statement) to be copied.
> + * @param total_sql_size Current size of the whole sql statement.
> + */
> +static void
> +append_string_part(Parse *parse, const char *str, size_t *total_sql_size)
> +{
> +    const size_t str_len = strlen(str);
> +    char * str_part = region_alloc(&parse->region, str_len);
> +    if (str_part == NULL) {
> +        diag_set(OutOfMemory, str_len,
> +             "region_alloc", "str_part");
> +        parse->rc = SQL_TARANTOOL_ERROR;
> +        parse->nErr++;
> +        return;
> +    }
> +    memcpy(str_part, str, str_len);
> +    *total_sql_size += str_len;
> +}
> +
> +static void
> +set_index_def(Parse *parse, Index *index, Table *table, uint32_t iid,
> +          const char *name, uint32_t name_len, int on_error,
> +          struct ExprList *expr_list, u8 idx_type)
> +{
> +    struct space_def *space_def = table->def;
> +    struct index_opts opts;
> +    index_opts_create(&opts);
> +    opts.is_unique = on_error != ON_CONFLICT_ACTION_NONE;
> +
> +    struct key_def *key_def = key_def_new(expr_list->nExpr);
> +    if (key_def == NULL) {
> +        parse->rc = SQL_TARANTOOL_ERROR;
> +        parse->nErr++;
> +        goto cleanup;
> +    }
> +
> +    /* Build initial parts of SQL statement.  */
> 
> -    return tnt_index->def->opts.is_unique;
> +    size_t total_sql_size = 0;
> +
> +    if (idx_type == SQLITE_IDXTYPE_APPDEF) {
> +        append_string_part(parse, "CREATE INDEX ", &total_sql_size);
> +        append_string_part(parse, name, &total_sql_size);
> +        append_string_part(parse, " ON ", &total_sql_size);
> +        append_string_part(parse, space_def->name, &total_sql_size);
> +        append_string_part(parse, " (", &total_sql_size);
> +    }
> +
> +    for (int i = 0; i < expr_list->nExpr; i++) {
> +        Expr *expr = expr_list->a[i].pExpr;
> +        sql_resolve_self_reference(parse, table, NC_IdxExpr, expr, 0);
> +        if (parse->nErr > 0)
> +            goto cleanup;
> +
> +        Expr *column_expr = sqlite3ExprSkipCollate(expr);
> +        if (column_expr->op != TK_COLUMN) {
> +            sqlite3ErrorMsg(parse,
> +                    "functional indexes aren't supported "
> +                    "in the current version");
> +            goto cleanup;
> +        }
> +
> +        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 (idx_type == SQLITE_IDXTYPE_APPDEF) {
> +                const char *column_name =
> +                    column_expr->u.zToken;
> +                append_string_part(parse, column_name,
> +                           &total_sql_size);
> +                append_string_part(parse, " COLLATE ",
> +                           &total_sql_size);
> +                const char *coll_name = expr->u.zToken;
> +                append_string_part(parse, coll_name,
> +                           &total_sql_size);
> +                append_string_part(parse, ", ",
> +                           &total_sql_size);
> +            }
> +        } else {
> +            coll = sql_column_collation(space_def, fieldno,
> +                            &coll_id);
> +            if (idx_type == SQLITE_IDXTYPE_APPDEF) {
> +                const char *column_name =
> +                    column_expr->u.zToken;
> +                append_string_part(parse, column_name,
> +                           &total_sql_size);
> +                append_string_part(parse, ", ",
> +                           &total_sql_size);
> +            }
> +        }
> +
> +        /*
> +        * Tarantool: DESC indexes are not supported so far.
> +        * See gh-3016.
> +        */
> +        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) {
> +        index->def = NULL;
> +        goto cleanup;
> +    }
> +
> +    if (idx_type == SQLITE_IDXTYPE_APPDEF) {
> +        opts.sql = region_join(&parse->region, total_sql_size);
> +        /*
> +         * fix last ", " with ")\0" to finish statement.
> +         */
> +        memcpy(&opts.sql[total_sql_size - 2], ")\0", 2);
> +    }
> +
> +    struct key_def *pk_key_def;
> +    if (idx_type == SQLITE_IDXTYPE_APPDEF)
> +        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);
> + cleanup:
> +    if (key_def != NULL)
> +        key_def_delete(key_def);
>   }
> 
>   void
> @@ -2666,26 +2744,26 @@ sql_create_index(struct Parse *parse, struct 
> Token *token,
>            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 */
> +    /* Table to be indexed.  */
> +    Table *table = NULL;
> +    /* The index to be created.  */
> +    Index *index = NULL;
> +    /* Name of the index  */
> +    char *name = NULL;
> +    int name_len;
>       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 */
>       struct session *user_session = current_session();
> 
>       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) {
>           Vdbe *v = sqlite3GetVdbe(parse);
> @@ -2696,39 +2774,46 @@ 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.
>        */
>       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,
> +        DbFixer db_fixer;
> +        sqlite3FixInit(&db_fixer, parse, "index", token);
> +        if (sqlite3FixSrcList(&db_fixer, 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)
> +        table = sqlite3LocateTable(parse, 0, tbl_name->a[0].zName);
> +        assert(db->mallocFailed == 0 || table == NULL);
> +        if (table == NULL)
>               goto exit_create_index;
> -        sqlite3PrimaryKeyIndex(pTab);
> +        sqlite3PrimaryKeyIndex(table);
>       } else {
>           assert(token == NULL);
>           assert(start == NULL);
> -        pTab = parse->pNewTable;
> -        if (!pTab)
> +        table = parse->pNewTable;
> +        if (table == NULL)
>               goto exit_create_index;
>       }
> 
> -    assert(pTab != 0);
> +    assert(table != NULL);
>       assert(parse->nErr == 0);
> -    if (pTab->def->opts.is_view) {
> +    if (table->def->opts.is_view) {
>           sqlite3ErrorMsg(parse, "views may not be indexed");
>           goto exit_create_index;
>       }
> @@ -2747,24 +2832,24 @@ sql_create_index(struct Parse *parse, struct 
> Token *token,
>        * our own name.
>        */
>       if (token) {
> -        zName = sqlite3NameFromToken(db, token);
> -        if (zName == 0)
> +        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) !=
> +            if (sqlite3HashFind(&db->pSchema->tblHash, name) !=
>                   NULL) {
>                   sqlite3ErrorMsg(parse,
>                           "there is already a table named %s",
> -                        zName);
> +                        name);
>                   goto exit_create_index;
>               }
>           }
> -        if (sqlite3HashFind(&pTab->idxHash, zName) != 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);
>               }
> @@ -2773,15 +2858,13 @@ sql_create_index(struct Parse *parse, struct 
> Token *token,
>       } else {
>           int n;
>           Index *pLoop;
> -        for (pLoop = pTab->pIndex, n = 1; pLoop;
> +        for (pLoop = table->pIndex, n = 1; pLoop;
>                pLoop = pLoop->pNext, n++) {
>           }
> -        zName =
> -            sqlite3MPrintf(db, "sqlite_autoindex_%s_%d", pTab->def->name,
> -                   n);
> -        if (zName == 0) {
> +        name = sqlite3MPrintf(db, "sqlite_autoindex_%s_%d",
> +                      table->def->name, n);
> +        if (name == NULL)
>               goto exit_create_index;
> -        }
>       }
> 
>       /*
> @@ -2792,8 +2875,8 @@ sql_create_index(struct Parse *parse, struct Token 
> *token,
>        */
>       if (col_list == NULL) {
>           Token prevCol;
> -        uint32_t last_field = pTab->def->field_count - 1;
> -        sqlite3TokenInit(&prevCol, pTab->def->fields[last_field].name);
> +        uint32_t last_field = table->def->field_count - 1;
> +        sqlite3TokenInit(&prevCol, table->def->fields[last_field].name);
>           col_list = sql_expr_list_append(parse->db, NULL,
>                           sqlite3ExprAlloc(db, TK_ID,
>                                    &prevCol, 0));
> @@ -2805,33 +2888,26 @@ 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));
> -        }
> -    }
> +    /* Allocate the index structure.  */
> +    name_len = sqlite3Strlen30(name);
> 
> -    /*
> -     * Allocate the index structure.
> -     */
> -    nName = sqlite3Strlen30(zName);
> -    pIndex = sqlite3AllocateIndexObject(db, col_list->nExpr,
> -                        nName + nExtra + 1, &zExtra);
> -    if (db->mallocFailed) {
> +    if (name_len > BOX_NAME_MAX) {
> +        sqlite3ErrorMsg(parse,
> +                "%s.%s exceeds indexes' names length limit",
> +                table->def->name, name);
>           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;
> +
> +    if (sqlite3CheckIdentifierName(parse, name) != SQLITE_OK)
> +        goto exit_create_index;
> +
> +    index = sqlite3AllocateIndexObject(db, col_list->nExpr);
> +    if (db->mallocFailed)
> +        goto exit_create_index;
> +
> +    assert(EIGHT_BYTE_ALIGNMENT(index->aiRowLogEst));
> +    index->pTable = table;
> +    index->onError = (u8) on_error;
>       /*
>        * Don't make difference between UNIQUE indexes made by user
>        * using CREATE INDEX statement and those created during
> @@ -2839,74 +2915,40 @@ sql_create_index(struct Parse *parse, struct 
> Token *token,
>        */
>       if (idx_type == SQLITE_IDXTYPE_APPDEF &&
>           on_error != ON_CONFLICT_ACTION_NONE) {
> -        pIndex->idxType = SQLITE_IDXTYPE_UNIQUE;
> +        index->idxType = SQLITE_IDXTYPE_UNIQUE;
>       } else {
> -        pIndex->idxType = idx_type;
> +        index->idxType = idx_type;
>       }
> -    pIndex->pSchema = db->pSchema;
> -    pIndex->nColumn = col_list->nExpr;
> +    index->pSchema = db->pSchema;
>       /* Tarantool have access to each column by any index */
>       if (where) {
> -        sql_resolve_self_reference(parse, pTab, NC_PartIdx, where,
> +        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");
> -            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;
> -        }
> -        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;
> +    uint32_t max_iid = 0;
> +    for (Index *index = table->pIndex; index; index = index->pNext) {
> +        max_iid = max_iid > index->def->iid ?
> +              max_iid : index->def->iid + 1;
>       }
> -    if (pTab == parse->pNewTable) {
> +
> +    set_index_def(parse,  index, table, max_iid, name, name_len, on_error,
> +              col_list, idx_type);
> +
> +    if (index->def == NULL ||
> +        !index_def_is_valid(index->def, table->def->name))
> +        goto exit_create_index;
> +
> +    if (table == 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.
> @@ -2929,27 +2971,27 @@ sql_create_index(struct Parse *parse, struct 
> Token *token,
>            * considered distinct and both result in separate indices.
>            */
>           Index *pIdx;
> -        for (pIdx = pTab->pIndex; pIdx; pIdx = pIdx->pNext) {
> -            int k;
> +        for (pIdx = table->pIndex; pIdx; pIdx = pIdx->pNext) {
> +            uint32_t k;
>               assert(IsUniqueIndex(pIdx));
>               assert(pIdx->idxType != SQLITE_IDXTYPE_APPDEF);
> -            assert(IsUniqueIndex(pIndex));
> +            assert(IsUniqueIndex(index));
> 
> -            if (pIdx->nColumn != pIndex->nColumn)
> +            if (pIdx->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 < pIdx->def->key_def->part_count; k++) {
> +                if (pIdx->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 = pIdx->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) {
> +            if (k == pIdx->def->key_def->part_count) {
> +                if (pIdx->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
> @@ -2959,15 +3001,14 @@ sql_create_index(struct Parse *parse, struct 
> Token *token,
>                        */
>                       if (!
>                           (pIdx->onError == ON_CONFLICT_ACTION_DEFAULT
> -                         || pIndex->onError ==
> +                         || 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;
> -                    }
> +                    if (pIdx->onError == ON_CONFLICT_ACTION_DEFAULT)
> +                        pIdx->onError = index->onError;
>                   }
>                   if (idx_type == SQLITE_IDXTYPE_PRIMARYKEY)
>                       pIdx->idxType = idx_type;
> @@ -2982,14 +3023,14 @@ sql_create_index(struct Parse *parse, struct 
> Token *token,
>       assert(parse->nErr == 0);
>       if (db->init.busy) {
>           Index *p;
> -        p = sqlite3HashInsert(&pTab->idxHash, pIndex->zName, pIndex);
> +        p = sqlite3HashInsert(&table->idxHash, index->def->name, index);
>           if (p) {
> -            assert(p == pIndex);    /* Malloc must have failed */
> +            assert(p == index);    /* Malloc must have failed */
>               sqlite3OomFault(db);
>               goto exit_create_index;
>           }
>           user_session->sql_flags |= SQLITE_InternChanges;
> -        pIndex->tnum = db->init.newTnum;
> +        index->tnum = db->init.newTnum;
>       }
> 
>       /*
> @@ -3040,14 +3081,14 @@ sql_create_index(struct Parse *parse, struct 
> Token *token,
>                          ON_CONFLICT_ACTION_NONE ? "" : " UNIQUE",
>                          n, token->z);
> 
> -        iSpaceId = SQLITE_PAGENO_TO_SPACEID(pTab->tnum);
> +        iSpaceId = SQLITE_PAGENO_TO_SPACEID(table->tnum);
>           iIndexId = getNewIid(parse, iSpaceId, iCursor);
>           sqlite3VdbeAddOp1(v, OP_Close, iCursor);
> -        createIndex(parse, pIndex, iSpaceId, iIndexId, zStmt);
> +        createIndex(parse, index, iSpaceId, iIndexId, zStmt);
> 
>           /* consumes zStmt */
>           iFirstSchemaCol =
> -            makeIndexSchemaRecord(parse, pIndex, iSpaceId, iIndexId,
> +            makeIndexSchemaRecord(parse, index, iSpaceId, iIndexId,
>                         zStmt);
> 
>           /* Reparse the schema. Code an OP_Expire
> @@ -3070,54 +3111,17 @@ sql_create_index(struct Parse *parse, struct 
> Token *token,
> 
>       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 */
>    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
> @@ -3743,9 +3747,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);
> @@ -3766,11 +3770,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 8b13f6077..980891cb2 100644
> --- a/src/box/sql/delete.c
> +++ b/src/box/sql/delete.c
> @@ -269,11 +269,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 {
> @@ -569,13 +570,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 59e7cb4fd..a960c1cc1 100644
> --- a/src/box/sql/expr.c
> +++ b/src/box/sql/expr.c
> @@ -2405,20 +2405,24 @@ 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) {
> -                    if (pIdx->nColumn > nExpr
> -                        || (pIdx->nColumn > nExpr
> -                        && !index_is_unique(pIdx))) {
> -                            continue;    /* This index is not unique 
> over the IN RHS columns */
> +                    if ((int)part_count > nExpr
> +                        || !pIdx->def->opts.is_unique) {
> +                        /* This index is not unique over the IN RHS 
> columns */
> +                        continue;
>                       }
>                   }
> 
> @@ -2432,12 +2436,13 @@ 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) {
>                               continue;
> @@ -2466,18 +2471,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 +2503,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);
>                           }
>                       }
> @@ -3130,12 +3134,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
> +            tab->def->fields[fieldno].affinity;
> +        if (pk->def->key_def->part_count == 1
>               && affinity == AFFINITY_INTEGER
> -            && pk->aiColumn[0] < nVector) {
> -            int reg_pk = rLhs + pk->aiColumn[0];
> +            && (int) fieldno < nVector) { int reg_pk = rLhs + (int)fieldno;
>               sqlite3VdbeAddOp2(v, OP_MustBeInt, reg_pk, destIfFalse);
>           }
>       }
> @@ -3467,7 +3471,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 e3fff37fe..c14a70836 100644
> --- a/src/box/sql/fkey.c
> +++ b/src/box/sql/fkey.c
> @@ -214,7 +214,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 */
> @@ -256,21 +255,29 @@ 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.
> +    Index *index = NULL;
> +    for (index = pParent->pIndex; index; index = index->pNext) {
> +        int part_count = index->def->key_def->part_count;
> +        if (part_count == nCol && index->def->opts.is_unique
> +            && index->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.
>                */
> 
>               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 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 (IsPrimaryKeyIndex(index)) {
>                       if (aiCol) {
>                           int i;
>                           for (i = 0; i < nCol; i++)
> @@ -287,9 +294,16 @@ sqlite3FkLocateIndex(Parse * pParse,    /* Parse 
> context to store any error in */
>                    * the default collation sequences for each column.
>                    */
>                   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 */
> +                struct key_part *part =
> +                    index->def->key_def->parts;
> +                for (i = 0; i < nCol; i++, part++) {
> +                    /*
> +                     * Index of column in
> +                     * parent table.
> +                     * */
> +                    i16 iCol = (int) part->fieldno;
> +                    /* Name of indexed column. */
> +                    char *zIdxCol;
> 
>                       if (iCol < 0)
>                           break;    /* No foreign keys against 
> expression indexes */
> @@ -303,9 +317,7 @@ sqlite3FkLocateIndex(Parse * pParse,    /* Parse 
> context to store any error in */
>                       def_coll = sql_column_collation(pParent->def,
>                                       iCol,
>                                       &id);
> -                    struct coll *coll =
> -                        sql_index_collation(pIdx, i,
> -                                    &id);
> +                    struct coll *coll = part->coll;
>                       if (def_coll != coll)
>                           break;
> 
> @@ -332,7 +344,7 @@ sqlite3FkLocateIndex(Parse * pParse,    /* Parse 
> context to store any error in */
>           }
>       }
> 
> -    if (!pIdx) {
> +    if (index == NULL) {
>           if (!pParse->disableTriggers) {
>               sqlite3ErrorMsg(pParse,
>                       "foreign key mismatch - \"%w\" referencing \"%w\"",
> @@ -342,7 +354,7 @@ sqlite3FkLocateIndex(Parse * pParse,    /* Parse 
> context to store any error in */
>           return 1;
>       }
> 
> -    *ppIdx = pIdx;
> +    *ppIdx = index;
>       return 0;
>   }
> 
> @@ -465,13 +477,15 @@ fkLookupParent(Parse * pParse,    /* Parse context */
>                   for (i = 0; i < nCol; i++) {
>                       int iChild = aiCol[i] + 1 + regData;
>                       int iParent =
> -                        pIdx->aiColumn[i] + 1 + regData;
> -                    assert(pIdx->aiColumn[i] >= 0);
> +                        (int) pIdx->def->key_def->parts[i].fieldno
> +                        + 1 + regData;
>                       assert(aiCol[i] != pTab->iPKey);
> -                    if (pIdx->aiColumn[i] == pTab->iPKey) {
> +                    if ((int)pIdx->def->key_def->
> +                        parts[i].fieldno == pTab->iPKey) {
>                           /* The parent key is a composite key that 
> includes the IPK column */
>                           iParent = regData;
>                       }
> +
>                       sqlite3VdbeAddOp3(v, OP_Ne, iChild,
>                                 iJump, iParent);
>                       VdbeCoverage(v);
> @@ -615,7 +629,6 @@ 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() */
> @@ -623,7 +636,7 @@ fkScanChildren(Parse * pParse,    /* Parse context */
>       Vdbe *v = sqlite3GetVdbe(pParse);
> 
>       assert(pIdx == 0 || pIdx->pTable == pTab);
> -    assert(pIdx == 0 || (int)index_column_count(pIdx) == pFKey->nCol);
> +    assert(pIdx == 0 || (int) pIdx->def->key_def->part_count == 
> pFKey->nCol);
>       assert(pIdx != 0);
> 
>       if (nIncr < 0) {
> @@ -640,19 +653,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);
>       }
> @@ -671,15 +685,16 @@ 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);
> +        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 =
> +                (int) pIdx->def->key_def->parts[i].fieldno;
> +            pLeft = exprTableRegister(pParse, pTab, regData,
> +                          fieldno);
>               pRight =
>                   exprTableColumn(db, pTab->def,
> -                        pSrc->a[0].iCursor, iCol);
> +                        pSrc->a[0].iCursor, fieldno);
>               pEq = sqlite3PExpr(pParse, TK_EQ, pLeft, pRight);
>               pAll = sqlite3ExprAnd(db, pAll, pEq);
>           }
> @@ -982,7 +997,6 @@ sqlite3FkCheck(Parse * pParse,    /* Parse context */
>               if (aiCol[i] == pTab->iPKey) {
>                   aiCol[i] = -1;
>               }
> -            assert(pIdx == 0 || pIdx->aiColumn[i] >= 0);
>           }
> 
>           pParse->nTab++;
> @@ -1107,19 +1121,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);
>                   }
>               }
>           }
> @@ -1254,11 +1268,12 @@ fkActionTrigger(Parse * pParse,    /* Parse 
> context */
>                      || (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 a43f7b596..d3e7cfb93 100644
> --- a/src/box/sql/insert.c
> +++ b/src/box/sql/insert.c
> @@ -90,14 +90,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,8 +1093,8 @@ 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) {
> @@ -1109,7 +1103,7 @@ sqlite3GenerateConstraintChecks(Parse * pParse,    
>      /* The parser context */
>           }
>           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 +1173,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 +1200,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; 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 +1250,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 = (int) 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 +1265,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 +1280,13 @@ 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) {
> +                reg_pk = regNewData + 1 +
> + pIdx->def->key_def->parts[0].fieldno;
> +
> +                int fieldno = (int) pIdx->def->key_def->
> +                    parts[0].fieldno;
> +                if (pTab->zColAff[fieldno] ==
>                       AFFINITY_INTEGER) {
>                       int skip_if_null = sqlite3VdbeMakeLabel(v);
>                       if ((pTab->tabFlags & TF_Autoincrement) != 0) {
> @@ -1303,7 +1304,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
> @@ -1391,24 +1392,23 @@ 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);
> 
> +        const uint32_t pk_part_count = pPk->def->key_def->part_count;
>           /* Generate code to handle collisions */
>           regR =
>               (pIdx == pPk) ? regIdx : sqlite3GetTempRange(pParse,
> -                                 nPkField);
> +                                 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,
> @@ -1424,17 +1424,18 @@ sqlite3GenerateConstraintChecks(Parse * 
> pParse,        /* The parser context */
>                    * KEY values of this row before the update.
>                    */
>                   int addrJump =
> -                    sqlite3VdbeCurrentAddr(v) + nPkCol;
> +                    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)) {
> +                for (uint32_t i = 0; i < pk_part_count; i++) {
> +                    char *p4 = (char *) pPk->def->key_def->parts[i].coll;
> +                    x = pPk->def->key_def->parts[i].fieldno;
> +                    if (pPk->tnum==0)
> +                        x = -1;
> +                    if (i == (pk_part_count - 1)) {
>                           addrJump = addrUniqueOk;
>                           op = OP_Eq;
>                       }
> @@ -1482,7 +1483,8 @@ sqlite3GenerateConstraintChecks(Parse * pParse,    
>      /* The parser context */
>                                   NULL, NULL);
>               }
>               sql_generate_row_delete(pParse, pTab, pTrigger,
> -                        iDataCur, regR, nPkField, false,
> +                        iDataCur, regR, pk_part_count,
> +                        false,
>                           ON_CONFLICT_ACTION_REPLACE,
>                           pIdx == pPk ? ONEPASS_SINGLE :
>                           ONEPASS_OFF, -1);
> @@ -1492,7 +1494,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;
> @@ -1610,8 +1612,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 */
> @@ -1629,7 +1631,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));
>               }
>           }
>       }
> @@ -1663,30 +1665,24 @@ 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 */
> @@ -1858,16 +1854,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(
> @@ -1943,11 +1938,11 @@ 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);
> diff --git a/src/box/sql/pragma.c b/src/box/sql/pragma.c
> index 5fb29c75c..7067a5ab1 100644
> --- a/src/box/sql/pragma.c
> +++ b/src/box/sql/pragma.c
> @@ -370,7 +370,8 @@ sqlite3Pragma(Parse * pParse, Token * pId, /* First 
> part of [schema.]id field */
>                   k = 1;
>               } else {
>                   for (k = 1; k <= def->field_count &&
> -                     pk->aiColumn[k - 1] != (int) i; ++k) {
> +                     pk->def->key_def->parts[k - 1].fieldno
> +                     != i; ++k) {
>                   }
>               }
>               bool is_nullable = def->fields[i].is_nullable;
> @@ -414,7 +415,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,
> @@ -443,11 +444,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,
> @@ -461,19 +464,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 <
> @@ -503,10 +505,8 @@ sqlite3Pragma(Parse * pParse, Token * pId, /* First 
> part of [schema.]id field */
>                               { "c", "u", "pk" };
>                           sqlite3VdbeMultiLoad(v, 1,
>                                        "isisi", i,
> -                                     pIdx->
> -                                     zName,
> -                                     index_is_unique
> -                                     (pIdx),
> +                                     pIdx->def->name,
> + pIdx->def->opts.is_unique,
>                                        azOrigin
>                                        [pIdx->
>                                         idxType],
> diff --git a/src/box/sql/select.c b/src/box/sql/select.c
> index 368bcd6f0..c7c186d9d 100644
> --- a/src/box/sql/select.c
> +++ b/src/box/sql/select.c
> @@ -4367,7 +4367,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 e7a02dc1d..fcedf4358 100644
> --- a/src/box/sql/sqliteInt.h
> +++ b/src/box/sql/sqliteInt.h
> @@ -2069,27 +2069,17 @@ struct UnpackedRecord {
>    * 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.
> + * Indexes name, corresponding space_id, type (in tarantool
> + * sense - HASH, TREE, etc) are stored in index definition - in
> + * Index.def.
> + * SQL statement which created the index and 'is_unique' flag are
> + * stored in Index.def.opts. Information about index parts (part
> + * count, corresponding space fields' numbers, parts' collations
> + * and sort orders, etc) are stored in Index.def.key_def.parts
> + *
> + * Index.onError indicate the which conflict resolution algorithm
> + * to employ whenever an attempt is made to insert a non-unique
> + * element in unique index.
>    *
>    * While parsing a CREATE TABLE or CREATE INDEX statement in order to
>    * generate VDBE code (as opposed to reading from Tarantool's _space
> @@ -2100,26 +2090,18 @@ 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 */
> +    struct index_def *def;
>   };
> 
>   /**
> @@ -3541,34 +3523,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 *);
> 
> @@ -3655,9 +3609,7 @@ 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 *);
> +Index *sqlite3AllocateIndexObject(sqlite3 *, i16);
> 
>   /**
>    * Create a new index for an SQL table.  name is the name of the
> @@ -4373,8 +4325,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);
> diff --git a/src/box/sql/update.c b/src/box/sql/update.c
> index 10385eb78..d45672228 100644
> --- a/src/box/sql/update.c
> +++ b/src/box/sql/update.c
> @@ -238,17 +238,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;
>                   }
>               }
> @@ -300,17 +301,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);
> @@ -319,7 +321,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);
>       }
> 
> @@ -329,27 +332,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 a29d0a3ae..9372ba360 100644
> --- a/src/box/sql/vdbeaux.c
> +++ b/src/box/sql/vdbeaux.c
> @@ -1159,7 +1159,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 f408b7701..baf3df48b 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;
>               }
> @@ -1650,10 +1650,10 @@ sqlite3Stat4ProbeFree(UnpackedRecord * pRec)
>   {
>       if (pRec) {
>           int i;
> -        int nCol = pRec->key_def->part_count;
> +        int part_count = pRec->key_def->part_count;
>           Mem *aMem = pRec->aMem;
>           sqlite3 *db = aMem[0].db;
> -        for (i = 0; i < nCol; i++) {
> +        for (i = 0; i < part_count; i++) {
>               sqlite3VdbeMemRelease(&aMem[i]);
>           }
>           sqlite3DbFree(db, pRec);
> diff --git a/src/box/sql/where.c b/src/box/sql/where.c
> index c0c26ce29..599863041 100644
> --- a/src/box/sql/where.c
> +++ b/src/box/sql/where.c
> @@ -372,13 +372,19 @@ whereScanInit(WhereScan * pScan,    /* The 
> WhereScan object being initialized */
>       pScan->is_column_seen = false;
>       if (pIdx) {
>           int j = iColumn;
> -        iColumn = pIdx->aiColumn[j];
> +        iColumn = pIdx->def->key_def->parts[j].fieldno;
> +        /*
> +         * pIdx->tnum == 0 means that pIdx is a fake
> +         * integer primary key index
> +         */
> +        if (pIdx->tnum == 0)
> +            iColumn = -1;
> +
>           if (iColumn >= 0) {
>               char affinity =
> 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;
>           }
>       }
> @@ -541,47 +547,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 +616,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 +626,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;
>           }
> @@ -1184,7 +1168,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 +1230,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 +1256,12 @@ whereRangeSkipScanEst(Parse * pParse,     /* 
> Parsing & code generating context */
>               rc = sqlite3Stat4Column(db, samples[i].sample_key,
>                           samples[i].key_size, 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 +1431,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 +1581,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 +1602,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,7 +1734,7 @@ 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 (p->pIndex && (zName = p->pIndex->def->name) != 0) {
>           if (strncmp(zName, "sqlite_autoindex_", 17) == 0) {
>               int i = sqlite3Strlen30(zName) - 1;
>               while (zName[i] != '_')
> @@ -2314,7 +2297,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 +2318,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)) {
> +        if (pLhs->op != TK_COLUMN || pLhs->iTable != iCur
> +            || pLhs->iColumn != (int)pIdx->def->key_def->parts[i + 
> nEq].fieldno
> +            || pIdx->def->key_def->parts[i + nEq].sort_order !=
> + pIdx->def->key_def->parts[nEq].sort_order)
>               break;
> -        }
> 
>           aff = sqlite3CompareAffinity(pRhs, sqlite3ExprAffinity(pLhs));
>           idxaff =
> @@ -2353,7 +2334,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 +2377,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 +2412,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,8 +2433,9 @@ whereLoopAddBtreeIndex(WhereLoopBuilder * 
> pBuilder,    /* The WhereLoop factory */
>           LogEst nOutUnadjusted;    /* nOut before IN() and WHERE 
> adjustments */
>           int nIn = 0;
>           int nRecValid = pBuilder->nRecValid;
> +        uint32_t j = pProbe->def->key_def->parts[saved_nEq].fieldno;
>           if ((eOp == WO_ISNULL || (pTerm->wtFlags & TERM_VNULL) != 0)
> -            && indexColumnNotNull(pProbe, saved_nEq)
> +            && !pProbe->pTable->def->fields[j].is_nullable
>               ) {
>               continue;    /* ignore IS [NOT] NULL constraints on NOT 
> NULL columns */
>           }
> @@ -2523,14 +2505,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 +2576,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;
> @@ -2696,7 +2679,7 @@ whereLoopAddBtreeIndex(WhereLoopBuilder * 
> pBuilder,    /* The WhereLoop factory */
>           }
> 
>           if ((pNew->wsFlags & WHERE_TOP_LIMIT) == 0
> -            && pNew->nEq < nProbeCol) {
> +            && pNew->nEq < probe_part_count) {
>               whereLoopAddBtreeIndex(pBuilder, pSrc, pProbe,
>                              nInMul + nIn);
>           }
> @@ -2724,7 +2707,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 +2732,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 +2775,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 +2785,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;
>               }
>           }
> @@ -2882,7 +2866,6 @@ whereLoopAddBtree(WhereLoopBuilder * pBuilder,    
> /* WHERE clause information */
>       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 */
>       SrcList *pTabList;    /* The FROM clause */
>       struct SrcList_item *pSrc;    /* The FROM clause btree term to add */
>       WhereLoop *pNew;    /* Template WhereLoop object */
> @@ -2913,11 +2896,28 @@ whereLoopAddBtree(WhereLoopBuilder * 
> pBuilder,    /* WHERE clause information */
>            */
>           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;
> +
> +        struct key_def *key_def = key_def_new(1);
> +        if (key_def == NULL)
> +            return SQLITE_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 index_opts = index_opts_default;
> +
> +        sPk.def = index_def_new(pTab->def->id, 0, "primary",
> +                    sizeof("primary") - 1, TREE, &index_opts,
> +                    key_def, NULL);
> +        key_def_delete(key_def);
> +
> +        if (sPk.def == NULL)
> +            return SQLITE_ERROR;
> +
>           aiRowEstPk[0] = sql_space_tuple_log_count(pTab);
>           aiRowEstPk[1] = 0;
>           pFirst = pSrc->pTab->pIndex;
> @@ -3392,8 +3392,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 +3454,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);
> +                    iColumn = pIndex->def->key_def->
> +                        parts[j].fieldno;
> +                    revIdx = pIndex->def->key_def->
> +                        parts[j].sort_order;
>                       if (iColumn == pIndex->pTable->iPKey)
>                           iColumn = -1;
>                   } else {
> @@ -3506,8 +3507,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 +4777,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 +4910,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..88ae0773d 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;
> +            part_count = idx_def->key_def->part_count;
>           if ((pWInfo->wctrlFlags & WHERE_ORDERBY_MIN) != 0
> -            && pWInfo->nOBSat > 0 && (nIdxCol > nEq)) {
> -            j = pIdx->aiColumn[nEq];
> +            && 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;
> @@ -1095,14 +1095,14 @@ sqlite3WhereCodeOneLoopStart(WhereInfo * 
> pWInfo,    /* Complete information about t
>                   assert((bRev & ~1) == 0);
>                   pLevel->iLikeRepCntr <<= 1;
>                   pLevel->iLikeRepCntr |=
> -                    bRev ^ (sql_index_column_sort_order(pIdx, nEq) ==
> +                    bRev ^ (pIdx->def->key_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,10 @@ 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) ==
> +        if ((nEq < part_count &&
> +             bRev == (pIdx->def->key_def->parts[nEq].sort_order ==
>                     SORT_ORDER_ASC)) ||
> -            (bRev && nIdxCol == nEq)) {
> +            (bRev && part_count == nEq)) {
>               SWAP(pRangeEnd, pRangeStart);
>               SWAP(bSeekPastNull, bStopAtNull);
>               SWAP(nBtm, nTop);
> @@ -1196,16 +1196,17 @@ sqlite3WhereCodeOneLoopStart(WhereInfo * 
> pWInfo,    /* Complete information about t
>               }
>           } else {
>               pk = sqlite3PrimaryKeyIndex(pIdx->pTable);
> +            uint32_t fieldno = pk->def->key_def->parts[0].fieldno;
>               affinity =
> - pIdx->pTable->def->fields[pk->aiColumn[0]].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 +1214,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 +1328,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 +1436,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 +1540,26 @@ 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;
> +                        uint32_t part_count =
> +                            pPk->def->key_def->
> +                                part_count;
> 
>                           /* 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,
> +                                    part_count);
> +                        for (uint32_t iPk = 0;
> +                             iPk < part_count;
> +                             iPk++) {
> +                            uint32_t fieldno =
> +                                pPk->def->
> +                                key_def->
> +                                parts[iPk].
> +                                fieldno;
>                               sqlite3ExprCodeGetColumnToReg
> -                                (pParse, pTab->def,
> -                                 iCol, iCur,
> +                                (pParse,
> +                                 pTab->def,
> +                                 fieldno,
> +                                 iCur,
>                                    r + iPk);
>                           }
> 
> @@ -1563,24 +1575,28 @@ sqlite3WhereCodeOneLoopStart(WhereInfo * 
> pWInfo,    /* Complete information about t
>                            * need to insert the key into the temp table, 
> as it will never
>                            * be tested for.
>                            */
> +                        uint32_t pk_part_count =
> +                            pPk->def->key_def->
> +                                part_count;
>                           if (iSet) {
>                               jmp1 = sqlite3VdbeAddOp4Int
>                                   (v, OP_Found,
>                                    regRowset, 0,
> -                                 r, nPk);
> +                                 r,
> +                                 pk_part_count);
>                               VdbeCoverage(v);
>                           }
>                           if (iSet >= 0) {
>                               sqlite3VdbeAddOp3
>                                   (v, OP_MakeRecord,
> -                                 r, nPk, regPk);
> +                                 r, pk_part_count, regPk);
>                               sqlite3VdbeAddOp2
>                                   (v, OP_IdxInsert,
>                                    regRowset, regPk);
>                           }
> 
>                           /* Release the array of temp registers */
> -                        sqlite3ReleaseTempRange(pParse, r, nPk);
> +                        sqlite3ReleaseTempRange(pParse, r, pk_part_count);
>                       }
> 
>                       /* Invoke the main loop body as a subroutine */
> 

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

* [tarantool-patches] Re: [PATCH v5] sql: add index_def to struct Index
  2018-06-27 17:57         ` Kirill Shcherbatov
@ 2018-06-28 18:49           ` Vladislav Shpilevoy
  2018-06-29 13:49             ` [tarantool-patches] Re: [PATCH v6] " Ivan Koptelov
  0 siblings, 1 reply; 23+ messages in thread
From: Vladislav Shpilevoy @ 2018-06-28 18:49 UTC (permalink / raw)
  To: Kirill Shcherbatov, tarantool-patches; +Cc: Ivan Koptelov

Hello. Thanks for the patch!

See 11 comments below. Besides, see some of them and others
in the separate commit on the branch. Please, look at it and
squash. Note: I did not run the tests, so please, repair them
if they fail.

> commit cba31099432e47a4f65d5d48280da2608d6e615d
> Author: Ivan Koptelov <ivan.koptelov@tarantool.org>
> Date:   Fri Jun 8 10:32:01 2018 +0300
> 
>     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
>     and zName are removed from Index. All usages of this
>     fields changed to usage of corresponding index_def
>     fields.
>     index_is_unique(), sql_index_collation() and
>     index_column_count() are removed with calls of
>     index_def corresponding fields.
>     
>     Closes: #3369
> 
> diff --git a/src/box/sql/build.c b/src/box/sql/build.c
> index 0da7d805b..2c82644e6 100644
> --- a/src/box/sql/build.c
> +++ b/src/box/sql/build.c
> @@ -1049,22 +1052,20 @@ 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; pIdx = pIdx->pNext) {
> +			assert(pIdx->def->key_def->part_count == 1);
> +			if (pIdx->def->key_def->parts[0].fieldno == i) {
> +				pIdx->def->key_def->parts[0].coll_id = *id;

1. Here you should set id to address of parts[0].coll_id so that it will
be initialized in sql_column_collation(). Now you set coll_id to id before
initialization of id (fixed by me on the branch).

> +				pIdx->def->key_def->parts[0].coll =
>  					sql_column_collation(p->def, i, id);
>  			}
>  		}
> -	} else {
> -		sqlite3DbFree(db, zColl);
>  	}
> +	sqlite3DbFree(db, zColl);
>  }

2. I caught a crash:

     box.cfg{}
     box.sql.execute('CREATE TABLE test (a int, b int, c int, PRIMARY KEY (a, a, b, c))')

Process 85357 stopped
* thread #1, queue = 'com.apple.main-thread', stop reason = EXC_BAD_ACCESS (code=1, address=0x38)
     frame #0: 0x000000010036122f tarantool`convertToWithoutRowidTable(pParse=0x000000010401f8b0, pTab=0x0000000104600218) at build.c:1365
    1362			 * PRIMARY KEY contains no repeated columns.
    1363			 */
    1364	
-> 1365			struct key_part *parts = pPk->def->key_def->parts;
    1366			uint32_t part_count = pPk->def->key_def->part_count;
    1367			uint32_t new_part_count = part_count;
    1368	
Target 0: (tarantool) stopped


> @@ -1404,18 +1355,33 @@ convertToWithoutRowidTable(Parse * pParse, Table * pTab)
>  		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.
> +		 * 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];
> +
> +		struct key_part *parts = pPk->def->key_def->parts;
> +		uint32_t part_count = pPk->def->key_def->part_count;
> +		uint32_t new_part_count = part_count;
> +
> +		for (uint32_t i = 1; i < part_count; i++) {
> +			if (is_part_duplicated(parts, i)) {
> +				new_part_count--;
> +				bool is_found = false;
> +				for (uint32_t j = i + 1; j < part_count; j++) {
> +					if (!is_part_duplicated(parts, j)) {
> +						parts[i] = parts[j];
> +						is_found = true;
> +						break;
> +					}
> +				}
> +				if (!is_found)
> +					break;
>  			}
>  		}

3. This cycle and is_part_duplicated still are useless non working things.

Example:
CREATE TABLE test (a int, b int, c int, PRIMARY KEY (a, a, a, a))

Here the primary index has duplicate 'a'. Its key_def has these
fieldnos: [0, 0, 0, 0].

In your cycle you will update this def to [0, 0, 0], it is not? So there
is still 3 duplicates.

And please, remove is_part_duplicated and use key_def_find.


> -		pPk->nColumn = j;
> +		pPk->def->key_def->part_count = new_part_count;
>  	}
>  	assert(pPk != 0);
>  }
> @@ -2531,34 +2499,20 @@ sqlite3RefillIndex(Parse * pParse, Index * pIndex, int memRootPage)
>   */
>  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 */
> +			   i16 nCol	/* Total number of columns in the index */

4. You have removed more than half of the function, so it is time to
convert it to Tarantool code style.
(fixed by me on the branch).

>      )
>  {
>  	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);
> +	    ROUND8(sizeof(LogEst) * (nCol + 1));    /* Index.aiRowLogEst */
> +	p = sqlite3DbMallocZero(db, nByte);
>  	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;

5. You do not need pExtra propagation. It is not used below.
(fixed by me on the branch).

> -		p->sort_order = (enum sort_order *) pExtra;
> -		p->nColumn = nCol;
> -		*ppExtra = ((char *)p) + nByte;
>  	}
>  	return p;
>  }
> @@ -2646,18 +2600,142 @@ addIndexToTable(Index * pIndex, Table * pTab)

6. Please, add a comment here.

> +static void
> +set_index_def(Parse *parse, Index *index, Table *table, uint32_t iid,
> +	      const char *name, uint32_t name_len, int on_error,
> +	      struct ExprList *expr_list, u8 idx_type)
> diff --git a/src/box/sql/fkey.c b/src/box/sql/fkey.c
> index e3fff37fe..c14a70836 100644
> --- a/src/box/sql/fkey.c
> +++ b/src/box/sql/fkey.c
> @@ -287,9 +294,16 @@ sqlite3FkLocateIndex(Parse * pParse,	/* Parse context to store any error in */
>  				 * the default collation sequences for each column.
>  				 */
>  				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 */
> +				struct key_part *part =
> +					index->def->key_def->parts;
> +				for (i = 0; i < nCol; i++, part++) {
> +					/*
> +					 * Index of column in
> +					 * parent table.
> +					 * */
> +					i16 iCol = (int) part->fieldno;
> +					/* Name of indexed column. */
> +					char *zIdxCol;
>  
>  					if (iCol < 0)

7. How can iCol be < 0, if it was get from uint fieldno?

> diff --git a/src/box/sql/where.c b/src/box/sql/where.c
> index c0c26ce29..599863041 100644
> --- a/src/box/sql/where.c
> +++ b/src/box/sql/where.c
> @@ -2523,14 +2505,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)) {

8. This function is still declared (with not implementation)
and is even used in a couple of places.

> @@ -2913,11 +2896,28 @@ whereLoopAddBtree(WhereLoopBuilder * pBuilder,	/* WHERE clause information */
>  		 */
>  		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;
> +
> +		struct key_def *key_def = key_def_new(1);
> +		if (key_def == NULL)
> +			return SQLITE_ERROR;

9. Why SQLITE_ERROR. AFAIR we have decided to use SQL_TARANTOOL_ERROR +
nErr + rc setting.

> +
> +		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 index_opts = index_opts_default;
> +
> +		sPk.def = index_def_new(pTab->def->id, 0, "primary",
> +					sizeof("primary") - 1, TREE, &index_opts,
> +					key_def, NULL);
> +		key_def_delete(key_def);
> +
> +		if (sPk.def == NULL)
> +			return SQLITE_ERROR;

10. Same.

11. Where is sPk.def is deleted?

> +
>  		aiRowEstPk[0] = sql_space_tuple_log_count(pTab);
>  		aiRowEstPk[1] = 0;
>  		pFirst = pSrc->pTab->pIndex;

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

* [tarantool-patches] Re: [PATCH v6] sql: add index_def to struct Index
  2018-06-28 18:49           ` Vladislav Shpilevoy
@ 2018-06-29 13:49             ` Ivan Koptelov
  2018-06-29 20:46               ` Vladislav Shpilevoy
  0 siblings, 1 reply; 23+ messages in thread
From: Ivan Koptelov @ 2018-06-29 13:49 UTC (permalink / raw)
  To: Vladislav Shpilevoy, tarantool-patches

Thank you for the review and for the fixes! New ver. of patch is at the 
end of the mail.
> Hello. Thanks for the patch!
>
> See 11 comments below. Besides, see some of them and others
> in the separate commit on the branch. Please, look at it and
> squash. Note: I did not run the tests, so please, repair them
> if they fail.
>
>> commit cba31099432e47a4f65d5d48280da2608d6e615d
>> Author: Ivan Koptelov <ivan.koptelov@tarantool.org>
>> Date:   Fri Jun 8 10:32:01 2018 +0300
>>
>>     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
>>     and zName are removed from Index. All usages of this
>>     fields changed to usage of corresponding index_def
>>     fields.
>>     index_is_unique(), sql_index_collation() and
>>     index_column_count() are removed with calls of
>>     index_def corresponding fields.
>>         Closes: #3369
>>
>> diff --git a/src/box/sql/build.c b/src/box/sql/build.c
>> index 0da7d805b..2c82644e6 100644
>> --- a/src/box/sql/build.c
>> +++ b/src/box/sql/build.c
>> @@ -1049,22 +1052,20 @@ 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; pIdx = 
>> pIdx->pNext) {
>> +            assert(pIdx->def->key_def->part_count == 1);
>> +            if (pIdx->def->key_def->parts[0].fieldno == i) {
>> +                pIdx->def->key_def->parts[0].coll_id = *id;
>
> 1. Here you should set id to address of parts[0].coll_id so that it will
> be initialized in sql_column_collation(). Now you set coll_id to id 
> before
> initialization of id (fixed by me on the branch).

>
>> + pIdx->def->key_def->parts[0].coll =
>>                      sql_column_collation(p->def, i, id);
>>              }
>>          }
>> -    } else {
>> -        sqlite3DbFree(db, zColl);
>>      }
>> +    sqlite3DbFree(db, zColl);
>>  }
>
> 2. I caught a crash:
>
>     box.cfg{}
>     box.sql.execute('CREATE TABLE test (a int, b int, c int, PRIMARY 
> KEY (a, a, b, c))')
>
> Process 85357 stopped
> * thread #1, queue = 'com.apple.main-thread', stop reason = 
> EXC_BAD_ACCESS (code=1, address=0x38)
>     frame #0: 0x000000010036122f 
> tarantool`convertToWithoutRowidTable(pParse=0x000000010401f8b0, 
> pTab=0x0000000104600218) at build.c:1365
>    1362             * PRIMARY KEY contains no repeated columns.
>    1363             */
>    1364
> -> 1365            struct key_part *parts = pPk->def->key_def->parts;
>    1366            uint32_t part_count = pPk->def->key_def->part_count;
>    1367            uint32_t new_part_count = part_count;
>    1368
> Target 0: (tarantool) stopped
Fixed.

>
>> @@ -1404,18 +1355,33 @@ convertToWithoutRowidTable(Parse * pParse, 
>> Table * pTab)
>>          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.
>> +         * 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];
>> +
>> +        struct key_part *parts = pPk->def->key_def->parts;
>> +        uint32_t part_count = pPk->def->key_def->part_count;
>> +        uint32_t new_part_count = part_count;
>> +
>> +        for (uint32_t i = 1; i < part_count; i++) {
>> +            if (is_part_duplicated(parts, i)) {
>> +                new_part_count--;
>> +                bool is_found = false;
>> +                for (uint32_t j = i + 1; j < part_count; j++) {
>> +                    if (!is_part_duplicated(parts, j)) {
>> +                        parts[i] = parts[j];
>> +                        is_found = true;
>> +                        break;
>> +                    }
>> +                }
>> +                if (!is_found)
>> +                    break;
>>              }
>>          }
>
> 3. This cycle and is_part_duplicated still are useless non working 
> things.
>
> Example:
> CREATE TABLE test (a int, b int, c int, PRIMARY KEY (a, a, a, a))
>
> Here the primary index has duplicate 'a'. Its key_def has these
> fieldnos: [0, 0, 0, 0].
>
> In your cycle you will update this def to [0, 0, 0], it is not? So there
> is still 3 duplicates.
>
> And please, remove is_part_duplicated and use key_def_find.
Sorry, now fxd.
>
>
>> -        pPk->nColumn = j;
>> +        pPk->def->key_def->part_count = new_part_count;
>>      }
>>      assert(pPk != 0);
>>  }
>> @@ -2531,34 +2499,20 @@ sqlite3RefillIndex(Parse * pParse, Index * 
>> pIndex, int memRootPage)
>>   */
>>  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 */
>> +               i16 nCol    /* Total number of columns in the index */
>
> 4. You have removed more than half of the function, so it is time to
> convert it to Tarantool code style.
> (fixed by me on the branch).
>
>>      )
>>  {
>>      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);
>> +        ROUND8(sizeof(LogEst) * (nCol + 1));    /* Index.aiRowLogEst */
>> +    p = sqlite3DbMallocZero(db, nByte);
>>      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;
>
> 5. You do not need pExtra propagation. It is not used below.
> (fixed by me on the branch).
>
>> -        p->sort_order = (enum sort_order *) pExtra;
>> -        p->nColumn = nCol;
>> -        *ppExtra = ((char *)p) + nByte;
>>      }
>>      return p;
>>  }
>> @@ -2646,18 +2600,142 @@ addIndexToTable(Index * pIndex, Table * pTab)
>
> 6. Please, add a comment here.
Added.
>
>> +static void
>> +set_index_def(Parse *parse, Index *index, Table *table, uint32_t iid,
>> +          const char *name, uint32_t name_len, int on_error,
>> +          struct ExprList *expr_list, u8 idx_type)
>> diff --git a/src/box/sql/fkey.c b/src/box/sql/fkey.c
>> index e3fff37fe..c14a70836 100644
>> --- a/src/box/sql/fkey.c
>> +++ b/src/box/sql/fkey.c
>> @@ -287,9 +294,16 @@ sqlite3FkLocateIndex(Parse * pParse,    /* Parse 
>> context to store any error in */
>>                   * the default collation sequences for each column.
>>                   */
>>                  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 */
>> +                struct key_part *part =
>> +                    index->def->key_def->parts;
>> +                for (i = 0; i < nCol; i++, part++) {
>> +                    /*
>> +                     * Index of column in
>> +                     * parent table.
>> +                     * */
>> +                    i16 iCol = (int) part->fieldno;
>> +                    /* Name of indexed column. */
>> +                    char *zIdxCol;
>>
>>                      if (iCol < 0)
>
> 7. How can iCol be < 0, if it was get from uint fieldno?
Fixed.
>
>> diff --git a/src/box/sql/where.c b/src/box/sql/where.c
>> index c0c26ce29..599863041 100644
>> --- a/src/box/sql/where.c
>> +++ b/src/box/sql/where.c
>> @@ -2523,14 +2505,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)) {
>
> 8. This function is still declared (with not implementation)
> and is even used in a couple of places.
Fxd.
>> @@ -2913,11 +2896,28 @@ whereLoopAddBtree(WhereLoopBuilder * 
>> pBuilder,    /* WHERE clause information */
>>           */
>>          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;
>> +
>> +        struct key_def *key_def = key_def_new(1);
>> +        if (key_def == NULL)
>> +            return SQLITE_ERROR;
>
> 9. Why SQLITE_ERROR. AFAIR we have decided to use SQL_TARANTOOL_ERROR +
> nErr + rc setting.
>
Sorry, fixed.
>> +
>> +        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 index_opts = index_opts_default;
>> +
>> +        sPk.def = index_def_new(pTab->def->id, 0, "primary",
>> +                    sizeof("primary") - 1, TREE, &index_opts,
>> +                    key_def, NULL);
>> +        key_def_delete(key_def);
>> +
>> +        if (sPk.def == NULL)
>> +            return SQLITE_ERROR;
>
> 10. Same.
Fixed as well.
>
> 11. Where is sPk.def is deleted?
It is deleted in freeIndex() with sPk
>
>> +
>>          aiRowEstPk[0] = sql_space_tuple_log_count(pTab);
>>          aiRowEstPk[1] = 0;
>>          pFirst = pSrc->pTab->pIndex;
>

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
and zName are removed from Index. All usages of this
fields changed to usage of corresponding index_def
fields.
index_is_unique(), sql_index_collation() and
index_column_count() are removed with calls of
index_def corresponding fields.

Closes: #3369

---
Branch:
https://github.com/tarantool/tarantool/tree/sb/gh-3369-use-index-def-in-select-and-where
Issue:https://github.com/tarantool/tarantool/issues/3369

  src/box/sql.c                        |  54 ++-
  src/box/sql/analyze.c                |  85 ++---
  src/box/sql/build.c                  | 713 +++++++++++++++++------------------
  src/box/sql/delete.c                 |  10 +-
  src/box/sql/expr.c                   |  61 +--
  src/box/sql/fkey.c                   | 132 +++----
  src/box/sql/insert.c                 | 145 ++++---
  src/box/sql/pragma.c                 |  30 +-
  src/box/sql/select.c                 |   2 +-
  src/box/sql/sqliteInt.h              | 111 ++----
  src/box/sql/update.c                 |  39 +-
  src/box/sql/vdbeaux.c                |   2 +-
  src/box/sql/vdbemem.c                |  21 +-
  src/box/sql/where.c                  | 180 ++++-----
  src/box/sql/wherecode.c              | 102 ++---
  test/sql-tap/colname.test.lua        |   4 +-
  test/sql/message-func-indexes.result |   8 +-
  17 files changed, 821 insertions(+), 878 deletions(-)

diff --git a/src/box/sql.c b/src/box/sql.c
index 11353150e..24e37652e 100644
--- a/src/box/sql.c
+++ b/src/box/sql.c
@@ -1452,8 +1452,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;
  	}
@@ -1564,20 +1564,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;
  	}
  
@@ -1587,46 +1586,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..c5afff214 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));
+		int pk_part_count = (int) pPk->def->key_def->part_count;
+		int regKeyStat = sqlite3GetTempRange(pParse, pk_part_count);
+		for (int j = 0; j < pk_part_count; ++j) {
+			int k = pPk->def->key_def->parts[j].fieldno;
+			assert(k >= 0 && k < (int) 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");
  	}
diff --git a/src/box/sql/build.c b/src/box/sql/build.c
index 0da7d805b..662fc698e 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;
@@ -376,7 +379,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);
@@ -1041,7 +1044,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)
@@ -1049,22 +1052,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 *
@@ -1094,66 +1096,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)
  {
@@ -1337,17 +1279,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
@@ -1364,13 +1295,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;
@@ -1402,20 +1332,6 @@ convertToWithoutRowidTable(Parse * pParse, Table * 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);
  }
@@ -1497,7 +1413,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);
@@ -1534,7 +1450,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) {
@@ -2463,15 +2379,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.
@@ -2502,7 +2419,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);
@@ -2522,44 +2440,15 @@ 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, uint32_t part_count)
  {
-	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;
-	}
+	/* Size of struct Index and aiRowLogEst. */
+	int nByte = ROUND8(sizeof(struct Index)) +
+		    ROUND8(sizeof(LogEst) * (part_count + 1));
+	struct Index *p = sqlite3DbMallocZero(db, nByte);
+	if (p != NULL)
+		p->aiRowLogEst = (LogEst *) ((char *)p + ROUND8(sizeof(*p)));
  	return p;
  }
  
@@ -2646,18 +2535,154 @@ 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);
+/**
+ * Allocate memory on parser region and copy given string (part of
+ * the sql statement) into the allocated memory.
+ * @param parse Parse context.
+ * @param str String (a part of sql statement) to be copied.
+ *
+ * @retval size Appended size.
+ */
+static int
+sql_append(struct Parse *parse, const char *str)
+{
+	const size_t str_len = strlen(str);
+	char *str_part = region_alloc(&parse->region, str_len);
+	if (str_part == NULL) {
+		diag_set(OutOfMemory, str_len, "region_alloc", "str_part");
+		parse->rc = SQL_TARANTOOL_ERROR;
+		parse->nErr++;
+		return 0;
+	}
+	memcpy(str_part, str, str_len);
+	return str_len;
+}
+
+/**
+ * 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 is_unique Is given 'index' unique or not.
+ * @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, one of the following:
+ *                 SQLITE_IDXTYPE_APPDEF 0 (Index is created with
+ *                 CREATE INDEX statement)
+ *                 SQLITE_IDXTYPE_UNIQUE 1 (Index is created
+ *                 automatically to implement a UNIQUE constraint)
+ *                 SQLITE_IDXTYPE_PRIMARYKEY 2 (Index is a PRIMARY
+ *                 KEY).
+ */
+static void
+set_index_def(Parse *parse, Index *index, Table *table, uint32_t iid,
+	      const char *name, uint32_t name_len, bool is_unique,
+	      struct ExprList *expr_list, u8 idx_type)
+{
+	struct space_def *space_def = table->def;
+	size_t sql_size = 0;
+	struct index_opts opts;
+	index_opts_create(&opts);
+	index->def = NULL;
+	opts.is_unique = is_unique;
+
+	struct key_def *key_def = key_def_new(expr_list->nExpr);
+	if (key_def == NULL)
+		goto tnt_error;
+
+	/* Build initial parts of SQL statement.  */
+	if (idx_type == SQLITE_IDXTYPE_APPDEF) {
+		sql_size += sql_append(parse, "CREATE INDEX ");
+		sql_size += sql_append(parse, name);
+		sql_size += sql_append(parse, " ON ");
+		sql_size += sql_append(parse, space_def->name);
+		sql_size += sql_append(parse, " (");
+	}
+
+	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) {
+			sqlite3ErrorMsg(parse, tnt_errcode_desc(ER_UNSUPPORTED),
+				        "Tarantool", "functional indexes");
+			goto tnt_error;
+		}
+
+		uint32_t fieldno = column_expr->iColumn;
+		const char *column_name = column_expr->u.zToken;
+		uint32_t coll_id;
+		struct coll *coll;
+		if (expr->op == TK_COLLATE) {
+			coll = sql_get_coll_seq(parse, expr->u.zToken,
+						&coll_id);
+			if (idx_type == SQLITE_IDXTYPE_APPDEF) {
+				sql_size += sql_append(parse, column_name);
+				sql_size += sql_append(parse, " COLLATE ");
+				sql_size += sql_append(parse, expr->u.zToken);
+				sql_size += sql_append(parse, ", ");
+			}
+		} else {
+			coll = sql_column_collation(space_def, fieldno,
+						    &coll_id);
+			if (idx_type == SQLITE_IDXTYPE_APPDEF) {
+				sql_size += sql_append(parse, column_name);
+				sql_size += sql_append(parse, ", ");
+			}
+		}
+		/*
+		 * 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;
+
+	if (idx_type == SQLITE_IDXTYPE_APPDEF) {
+		opts.sql = region_join(&parse->region, sql_size);
+		if (opts.sql == NULL) {
+			diag_set(OutOfMemory, sql_size, "region_join",
+				 "opts.sql");
+			goto tnt_error;
+		}
+		/*
+		 * fix last ", " with ")\0" to finish statement.
+		 */
+		memcpy(&opts.sql[sql_size - 2], ")\0", 2);
+	}
  
-	return tnt_index->def->opts.is_unique;
+	struct key_def *pk_key_def;
+	if (idx_type == SQLITE_IDXTYPE_APPDEF)
+		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;
+cleanup:
+	if (key_def != NULL)
+		key_def_delete(key_def);
+	return;
+tnt_error:
+	parse->rc = SQL_TARANTOOL_ERROR;
+	++parse->nErr;
+	goto cleanup;
  }
  
  void
@@ -2666,26 +2691,24 @@ sql_create_index(struct Parse *parse, struct Token *token,
  		 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 */
+	/* Table to be indexed.  */
+	struct Table *table = NULL;
+	/* The index to be created.  */
+	struct Index *index = NULL;
+	/* Name of the index.  */
+	char *name = NULL;
+	int name_len;
+	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) {
  		Vdbe *v = sqlite3GetVdbe(parse);
@@ -2696,39 +2719,43 @@ 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.
  	 */
  	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,
+		DbFixer db_fixer;
+		sqlite3FixInit(&db_fixer, parse, "index", token);
+		if (sqlite3FixSrcList(&db_fixer, tbl_name)) {
+			/*
+			 * Because the parser constructs tbl_name
+			 * from a single identifier,
  			 * sqlite3FixSrcList can never fail.
  			 */
-			assert(0);
+			unreachable();
  		}
-		pTab = sqlite3LocateTable(parse, 0, tbl_name->a[0].zName);
-		assert(db->mallocFailed == 0 || pTab == 0);
-		if (pTab == 0)
+		table = sqlite3LocateTable(parse, 0, tbl_name->a[0].zName);
+		assert(db->mallocFailed == 0 || table == NULL);
+		if (table == NULL)
  			goto exit_create_index;
-		sqlite3PrimaryKeyIndex(pTab);
+		sqlite3PrimaryKeyIndex(table);
  	} else {
  		assert(token == NULL);
  		assert(start == NULL);
-		pTab = parse->pNewTable;
-		if (!pTab)
+		table = parse->pNewTable;
+		if (table == NULL)
  			goto exit_create_index;
  	}
  
-	assert(pTab != 0);
+	assert(table != NULL);
  	assert(parse->nErr == 0);
-	if (pTab->def->opts.is_view) {
+	if (table->def->opts.is_view) {
  		sqlite3ErrorMsg(parse, "views may not be indexed");
  		goto exit_create_index;
  	}
@@ -2746,42 +2773,38 @@ sql_create_index(struct Parse *parse, struct Token *token,
  	 * primary key or UNIQUE constraint.  We have to invent
  	 * our own name.
  	 */
-	if (token) {
-		zName = sqlite3NameFromToken(db, token);
-		if (zName == 0)
+	if (token != NULL) {
+		name = sqlite3NameFromToken(db, token);
+		if (name == NULL)
  			goto exit_create_index;
-		assert(token->z != 0);
+		assert(token->z != NULL);
  		if (!db->init.busy) {
-			if (sqlite3HashFind(&db->pSchema->tblHash, zName) !=
+			if (sqlite3HashFind(&db->pSchema->tblHash, name) !=
  			    NULL) {
-				sqlite3ErrorMsg(parse,
-						"there is already a table named %s",
-						zName);
+				sqlite3ErrorMsg(parse, "there is already a "\
+						"table named %s", name);
  				goto exit_create_index;
  			}
  		}
-		if (sqlite3HashFind(&pTab->idxHash, zName) != 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;
+		int n = 1;
+		for (struct Index *pLoop = table->pIndex; pLoop != NULL;
  		     pLoop = pLoop->pNext, n++) {
  		}
-		zName =
-		    sqlite3MPrintf(db, "sqlite_autoindex_%s_%d", pTab->def->name,
-				   n);
-		if (zName == 0) {
+		name = sqlite3MPrintf(db, "sqlite_autoindex_%s_%d",
+				      table->def->name, n);
+		if (name == NULL)
  			goto exit_create_index;
-		}
  	}
  
  	/*
@@ -2791,9 +2814,9 @@ 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 prevCol;
+		uint32_t last_field = table->def->field_count - 1;
+		sqlite3TokenInit(&prevCol, table->def->fields[last_field].name);
  		col_list = sql_expr_list_append(parse->db, NULL,
  						sqlite3ExprAlloc(db, TK_ID,
  								 &prevCol, 0));
@@ -2805,108 +2828,92 @@ 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));
-		}
-	}
+	/* Allocate the index structure.  */
+	name_len = sqlite3Strlen30(name);
  
-	/*
-	 * Allocate the index structure.
-	 */
-	nName = sqlite3Strlen30(zName);
-	pIndex = sqlite3AllocateIndexObject(db, col_list->nExpr,
-					    nName + nExtra + 1, &zExtra);
-	if (db->mallocFailed) {
+	if (name_len > BOX_NAME_MAX) {
+		sqlite3ErrorMsg(parse, "%s.%s exceeds indexes' names length "\
+				"limit", table->def->name, name);
  		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;
+
+	if (sqlite3CheckIdentifierName(parse, name) != SQLITE_OK)
+		goto exit_create_index;
+
+	index = sql_index_alloc(db, col_list->nExpr);
+	if (db->mallocFailed)
+		goto exit_create_index;
+
+	assert(EIGHT_BYTE_ALIGNMENT(index->aiRowLogEst));
+	index->pTable = table;
+	index->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;
+	    on_error != ON_CONFLICT_ACTION_NONE)
+		index->idxType = SQLITE_IDXTYPE_UNIQUE;
+	else
+		index->idxType = idx_type;
+	index->pSchema = db->pSchema;
  	/* Tarantool have access to each column by any index */
-	if (where) {
-		sql_resolve_self_reference(parse, pTab, NC_PartIdx, where,
+	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");
-			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;
+	uint32_t max_iid = 0;
+	for (struct Index *index = table->pIndex; index != NULL;
+	     index = index->pNext) {
+		max_iid = max_iid > index->def->iid ?
+			  max_iid : index->def->iid + 1;
+	}
+
+	bool is_unique = on_error != ON_CONFLICT_ACTION_NONE;
+	set_index_def(parse,  index, table, max_iid, name, name_len,
+		      is_unique, col_list, idx_type);
+
+	/*
+	* 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.
+	*/
+	if (IsPrimaryKeyIndex(index)) {
+		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;
  			}
-		} else if (j >= 0) {
-			coll = sql_column_collation(pTab->def, j, &id);
-		} else {
-			id = COLL_NONE;
-			coll = NULL;
+
+			if (j == new_part_count)
+				parts[new_part_count++] = parts[i];
  		}
-		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;
+		index->def->key_def->part_count = new_part_count;
  	}
-	if (pTab == parse->pNewTable) {
+
+	if (index->def == NULL ||
+	    !index_def_is_valid(index->def, table->def->name))
+		goto exit_create_index;
+
+	if (table == 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.
@@ -2928,28 +2935,28 @@ sql_create_index(struct Parse *parse, struct Token *token,
  		 * 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;
+		for (struct Index *pIdx = table->pIndex; pIdx != NULL;
+		     pIdx = pIdx->pNext) {
+			uint32_t k;
  			assert(IsUniqueIndex(pIdx));
  			assert(pIdx->idxType != SQLITE_IDXTYPE_APPDEF);
-			assert(IsUniqueIndex(pIndex));
+			assert(IsUniqueIndex(index));
  
-			if (pIdx->nColumn != pIndex->nColumn)
+			if (pIdx->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 < pIdx->def->key_def->part_count; k++) {
+				if (pIdx->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 = pIdx->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) {
+			if (k == pIdx->def->key_def->part_count) {
+				if (pIdx->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
@@ -2957,17 +2964,19 @@ sql_create_index(struct Parse *parse, struct Token *token,
  					 * 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 (pIdx->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 (pIdx->onError ==
+					    ON_CONFLICT_ACTION_DEFAULT)
+						pIdx->onError = index->onError;
  				}
  				if (idx_type == SQLITE_IDXTYPE_PRIMARYKEY)
  					pIdx->idxType = idx_type;
@@ -2981,15 +2990,16 @@ sql_create_index(struct Parse *parse, struct Token *token,
  	 */
  	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;
  	}
  
  	/*
@@ -3040,14 +3050,14 @@ sql_create_index(struct Parse *parse, struct Token *token,
  				       ON_CONFLICT_ACTION_NONE ? "" : " UNIQUE",
  				       n, token->z);
  
-		iSpaceId = SQLITE_PAGENO_TO_SPACEID(pTab->tnum);
+		iSpaceId = SQLITE_PAGENO_TO_SPACEID(table->tnum);
  		iIndexId = getNewIid(parse, iSpaceId, iCursor);
  		sqlite3VdbeAddOp1(v, OP_Close, iCursor);
-		createIndex(parse, pIndex, iSpaceId, iIndexId, zStmt);
+		createIndex(parse, index, iSpaceId, iIndexId, zStmt);
  
  		/* consumes zStmt */
  		iFirstSchemaCol =
-		    makeIndexSchemaRecord(parse, pIndex, iSpaceId, iIndexId,
+		    makeIndexSchemaRecord(parse, index, iSpaceId, iIndexId,
  					  zStmt);
  
  		/* Reparse the schema. Code an OP_Expire
@@ -3070,54 +3080,17 @@ sql_create_index(struct Parse *parse, struct Token *token,
  
  	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 */
   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
@@ -3743,9 +3716,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);
@@ -3766,11 +3739,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 8b13f6077..980891cb2 100644
--- a/src/box/sql/delete.c
+++ b/src/box/sql/delete.c
@@ -269,11 +269,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 {
@@ -569,13 +570,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 59e7cb4fd..e7c61fca5 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);
  						}
  					}
@@ -3130,12 +3135,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);
  		}
  	}
@@ -3467,7 +3472,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 e3fff37fe..278adf76d 100644
--- a/src/box/sql/fkey.c
+++ b/src/box/sql/fkey.c
@@ -214,7 +214,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 */
@@ -256,24 +255,30 @@ 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) {
+			/*
+			 * 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.
  			 */
-
-			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 (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 (IsPrimaryKeyIndex(pIdx)) {
-					if (aiCol) {
-						int i;
-						for (i = 0; i < nCol; i++)
+				if (IsPrimaryKeyIndex(index)) {
+					if (aiCol != NULL) {
+						for (int i = 0; i < nCol; i++)
  							aiCol[i] =
  							    pFKey->aCol[i].
  							    iFrom;
@@ -287,12 +292,14 @@ sqlite3FkLocateIndex(Parse * pParse,	/* Parse context to store any error in */
  				 * the default collation sequences for each column.
  				 */
  				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 */
+				struct key_part *part =
+					index->def->key_def->parts;
+				for (i = 0; i < nCol; i++, part++) {
+					/*
+					 * Index of column in
+					 * parent table.
+					 */
+					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
@@ -303,13 +310,11 @@ sqlite3FkLocateIndex(Parse * pParse,	/* Parse context to store any error in */
  					def_coll = sql_column_collation(pParent->def,
  									iCol,
  									&id);
-					struct coll *coll =
-						sql_index_collation(pIdx, i,
-								    &id);
+					struct coll *coll = part->coll;
  					if (def_coll != coll)
  						break;
  
-					zIdxCol =
+					char *zIdxCol =
  						pParent->def->fields[iCol].name;
  					for (j = 0; j < nCol; j++) {
  						if (strcmp
@@ -332,7 +337,7 @@ sqlite3FkLocateIndex(Parse * pParse,	/* Parse context to store any error in */
  		}
  	}
  
-	if (!pIdx) {
+	if (index == NULL) {
  		if (!pParse->disableTriggers) {
  			sqlite3ErrorMsg(pParse,
  					"foreign key mismatch - \"%w\" referencing \"%w\"",
@@ -342,7 +347,7 @@ sqlite3FkLocateIndex(Parse * pParse,	/* Parse context to store any error in */
  		return 1;
  	}
  
-	*ppIdx = pIdx;
+	*ppIdx = index;
  	return 0;
  }
  
@@ -461,17 +466,19 @@ 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;
  					}
+
  					sqlite3VdbeAddOp3(v, OP_Ne, iChild,
  							  iJump, iParent);
  					VdbeCoverage(v);
@@ -615,7 +622,6 @@ 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() */
@@ -623,7 +629,7 @@ fkScanChildren(Parse * pParse,	/* Parse context */
  	Vdbe *v = sqlite3GetVdbe(pParse);
  
  	assert(pIdx == 0 || pIdx->pTable == pTab);
-	assert(pIdx == 0 || (int)index_column_count(pIdx) == pFKey->nCol);
+	assert(pIdx == 0 || (int) pIdx->def->key_def->part_count == pFKey->nCol);
  	assert(pIdx != 0);
  
  	if (nIncr < 0) {
@@ -640,19 +646,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);
  	}
@@ -671,15 +678,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);
  		}
@@ -982,7 +988,6 @@ sqlite3FkCheck(Parse * pParse,	/* Parse context */
  			if (aiCol[i] == pTab->iPKey) {
  				aiCol[i] = -1;
  			}
-			assert(pIdx == 0 || pIdx->aiColumn[i] >= 0);
  		}
  
  		pParse->nTab++;
@@ -1107,19 +1112,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);
  				}
  			}
  		}
@@ -1254,11 +1259,12 @@ fkActionTrigger(Parse * pParse,	/* Parse context */
  			       || (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 a43f7b596..1d3f974a9 100644
--- a/src/box/sql/insert.c
+++ b/src/box/sql/insert.c
@@ -90,14 +90,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
@@ -1391,24 +1390,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);
  
+		const 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,
@@ -1423,22 +1420,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);
@@ -1482,7 +1482,8 @@ sqlite3GenerateConstraintChecks(Parse * pParse,		/* The parser context */
  								NULL, NULL);
  			}
  			sql_generate_row_delete(pParse, pTab, pTrigger,
-						iDataCur, regR, nPkField, false,
+						iDataCur, regR, pk_part_count,
+						false,
  						ON_CONFLICT_ACTION_REPLACE,
  						pIdx == pPk ? ONEPASS_SINGLE :
  						ONEPASS_OFF, -1);
@@ -1492,7 +1493,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;
@@ -1610,8 +1611,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 */
@@ -1629,7 +1630,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));
  			}
  		}
  	}
@@ -1663,30 +1664,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 */
@@ -1858,16 +1854,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(
@@ -1943,11 +1938,11 @@ 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);
diff --git a/src/box/sql/pragma.c b/src/box/sql/pragma.c
index 5fb29c75c..b6d3b096d 100644
--- a/src/box/sql/pragma.c
+++ b/src/box/sql/pragma.c
@@ -369,9 +369,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;
@@ -414,7 +413,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,
@@ -443,11 +442,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,
@@ -461,19 +462,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 <
@@ -503,10 +503,8 @@ sqlite3Pragma(Parse * pParse, Token * pId,	/* First part of [schema.]id field */
  						    { "c", "u", "pk" };
  						sqlite3VdbeMultiLoad(v, 1,
  								     "isisi", i,
-								     pIdx->
-								     zName,
-								     index_is_unique
-								     (pIdx),
+								     pIdx->def->name,
+								     pIdx->def->opts.is_unique,
  								     azOrigin
  								     [pIdx->
  								      idxType],
diff --git a/src/box/sql/select.c b/src/box/sql/select.c
index 368bcd6f0..c7c186d9d 100644
--- a/src/box/sql/select.c
+++ b/src/box/sql/select.c
@@ -4367,7 +4367,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 e7a02dc1d..29051ecc5 100644
--- a/src/box/sql/sqliteInt.h
+++ b/src/box/sql/sqliteInt.h
@@ -2069,28 +2069,6 @@ struct UnpackedRecord {
   * 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 +2078,30 @@ 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 */
+	/** From ANALYZE: Est. rows selected by each column. */
+	LogEst *aiRowLogEst;
+	/** 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;
+	/** 1==UNIQUE, 2==PRIMARY KEY, 0==CREATE INDEX. */
+	unsigned idxType:2;
+	/** Index definition. */
+	struct index_def *def;
  };
  
  /**
@@ -3541,34 +3523,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 *);
  
@@ -3655,9 +3609,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, uint32_t part_count);
  
  /**
   * Create a new index for an SQL table.  name is the name of the
@@ -4373,10 +4334,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 10385eb78..d45672228 100644
--- a/src/box/sql/update.c
+++ b/src/box/sql/update.c
@@ -238,17 +238,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;
  				}
  			}
@@ -300,17 +301,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);
@@ -319,7 +321,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);
  	}
  
@@ -329,27 +332,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 a29d0a3ae..9372ba360 100644
--- a/src/box/sql/vdbeaux.c
+++ b/src/box/sql/vdbeaux.c
@@ -1159,7 +1159,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 f408b7701..41dd93272 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;
  			}
@@ -1648,15 +1648,12 @@ sqlite3Stat4Column(sqlite3 * db,	/* Database handle */
  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 c0c26ce29..225fddc23 100644
--- a/src/box/sql/where.c
+++ b/src/box/sql/where.c
@@ -372,13 +372,19 @@ whereScanInit(WhereScan * pScan,	/* The WhereScan object being initialized */
  	pScan->is_column_seen = false;
  	if (pIdx) {
  		int j = iColumn;
-		iColumn = pIdx->aiColumn[j];
+		iColumn = pIdx->def->key_def->parts[j].fieldno;
+		/*
+		 * pIdx->tnum == 0 means that pIdx is a fake
+		 * integer primary key index.
+		 */
+		if (pIdx->tnum == 0)
+			iColumn = -1;
+
  		if (iColumn >= 0) {
  			char affinity =
  				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;
  		}
  	}
@@ -541,47 +547,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 +616,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 +626,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 +819,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 +1167,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 +1229,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 +1255,12 @@ whereRangeSkipScanEst(Parse * pParse,		/* Parsing & code generating context */
  			rc = sqlite3Stat4Column(db, samples[i].sample_key,
  						samples[i].key_size, 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 +1430,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 +1580,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 +1601,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,7 +1733,7 @@ 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 (p->pIndex != NULL && (zName = p->pIndex->def->name) != NULL) {
  		if (strncmp(zName, "sqlite_autoindex_", 17) == 0) {
  			int i = sqlite3Strlen30(zName) - 1;
  			while (zName[i] != '_')
@@ -2314,7 +2296,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 +2317,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 +2333,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 +2376,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 +2411,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 +2432,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 +2507,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 +2578,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 +2680,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 +2709,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 +2734,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 +2777,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 +2787,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;
  			}
  		}
@@ -2882,7 +2868,6 @@ whereLoopAddBtree(WhereLoopBuilder * pBuilder,	/* WHERE clause information */
  	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 */
  	SrcList *pTabList;	/* The FROM clause */
  	struct SrcList_item *pSrc;	/* The FROM clause btree term to add */
  	WhereLoop *pNew;	/* Template WhereLoop object */
@@ -2913,11 +2898,32 @@ whereLoopAddBtree(WhereLoopBuilder * pBuilder,	/* WHERE clause information */
  		 */
  		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;
+
+		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);
+
+		sPk.def = index_def_new(pTab->def->id, 0, "primary",
+					sizeof("primary") - 1, TREE,
+					&index_opts_default, key_def, NULL);
+		key_def_delete(key_def);
+
+		if (sPk.def == NULL) {
+			pWInfo->pParse->nErr++;
+			pWInfo->pParse->rc = SQL_TARANTOOL_ERROR;
+			return SQL_TARANTOOL_ERROR;
+		}
+
  		aiRowEstPk[0] = sql_space_tuple_log_count(pTab);
  		aiRowEstPk[1] = 0;
  		pFirst = pSrc->pTab->pIndex;
@@ -3392,8 +3398,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 +3460,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 +3513,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 +4783,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 +4916,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/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/message-func-indexes.result b/test/sql/message-func-indexes.result
index 4bf1cab49..dbcb12f03 100644
--- a/test/sql/message-func-indexes.result
+++ b/test/sql/message-func-indexes.result
@@ -12,25 +12,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")
-- 

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

* [tarantool-patches] Re: [PATCH v6] sql: add index_def to struct Index
  2018-06-29 13:49             ` [tarantool-patches] Re: [PATCH v6] " Ivan Koptelov
@ 2018-06-29 20:46               ` Vladislav Shpilevoy
       [not found]                 ` <146c3bd4-e9e6-f943-5a42-c6db966d1c9c@tarantool.org>
                                   ` (2 more replies)
  0 siblings, 3 replies; 23+ messages in thread
From: Vladislav Shpilevoy @ 2018-06-29 20:46 UTC (permalink / raw)
  To: Ivan Koptelov, tarantool-patches

Hello. Thanks for the fixes! See my 6 comments below.

And I have pushed more fixes on the branch. Please,
look and squash.

> 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
> and zName are removed from Index. All usages of this
> fields changed to usage of corresponding index_def
> fields.
> index_is_unique(), sql_index_collation() and
> index_column_count() are removed with calls of
> index_def corresponding fields.
> 
> Closes: #3369
> 
> ---
> Branch:
> https://github.com/tarantool/tarantool/tree/sb/gh-3369-use-index-def-in-select-and-where
> Issue:https://github.com/tarantool/tarantool/issues/3369
> 
>   src/box/sql.c                        |  54 ++-
>   src/box/sql/analyze.c                |  85 ++---
>   src/box/sql/build.c                  | 713 +++++++++++++++++------------------
>   src/box/sql/delete.c                 |  10 +-
>   src/box/sql/expr.c                   |  61 +--
>   src/box/sql/fkey.c                   | 132 +++----
>   src/box/sql/insert.c                 | 145 ++++---
>   src/box/sql/pragma.c                 |  30 +-
>   src/box/sql/select.c                 |   2 +-
>   src/box/sql/sqliteInt.h              | 111 ++----
>   src/box/sql/update.c                 |  39 +-
>   src/box/sql/vdbeaux.c                |   2 +-
>   src/box/sql/vdbemem.c                |  21 +-
>   src/box/sql/where.c                  | 180 ++++-----
>   src/box/sql/wherecode.c              | 102 ++---
>   test/sql-tap/colname.test.lua        |   4 +-
>   test/sql/message-func-indexes.result |   8 +-
>   17 files changed, 821 insertions(+), 878 deletions(-)
> 
> diff --git a/src/box/sql.c b/src/box/sql.c
> index 11353150e..24e37652e 100644
> --- a/src/box/sql.c
> +++ b/src/box/sql.c
> @@ -1452,8 +1452,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;

1. You again sent the patch with spaces instead of tabs. Please, cope
with it. Looks like you copied 'git diff/show' output. Either use format-patch
or use 'git --no-pager diff/show'.

>           if (def->fields[pk].type == FIELD_TYPE_INTEGER)
>               pk_forced_int = pk;
>       }
> diff --git a/src/box/sql/build.c b/src/box/sql/build.c
> index 0da7d805b..662fc698e 100644
> --- a/src/box/sql/build.c
> +++ b/src/box/sql/build.c> @@ -2646,18 +2535,154 @@ addIndexToTable(Index * pIndex, Table * pTab)> +        struct Expr *column_expr = sqlite3ExprSkipCollate(expr);
> +        if (column_expr->op != TK_COLUMN) {
> +            sqlite3ErrorMsg(parse, tnt_errcode_desc(ER_UNSUPPORTED),
> +                        "Tarantool", "functional indexes");

2. Patch to allow SQL_TARANTOOL_ERROR has been pushed today, so you can use
here diag_set again.

> +            goto tnt_error;
> +        }
> +
> @@ -2805,108 +2828,92 @@ sql_create_index(struct Parse *parse, struct Token *token,

3. Crash:

box.cfg{}
box.sql.execute('CREATE TABLE test (a int, b int, c int, PRIMARY KEY (a, a COLLATE kek, b, c))')
Process 15886 stopped
* thread #1, queue = 'com.apple.main-thread', stop reason = EXC_BAD_ACCESS (code=1, address=0x80)
     frame #0: 0x000000010035f77b tarantool`sql_create_index(parse=0x000000010481f8b0, token=0x0000000000000000, tbl_name=0x0000000000000000, col_list=0x00000001029039d8, on_error=ON_CONFLICT_ACTION_DEFAULT, start=0x0000000000000000, where=0x0000000000000000, sort_order=SORT_ORDER_ASC, if_not_exist=false, idx_type='\x02') at build.c:2895
    2892		 * PRIMARY KEY contains no repeated columns.
    2893		 */
    2894		if (IsPrimaryKeyIndex(index)) {
-> 2895			struct key_part *parts = index->def->key_def->parts;
    2896			uint32_t part_count = index->def->key_def->part_count;
    2897			uint32_t new_part_count = 1;
    2898	
Target 0: (tarantool) stopped.

> @@ -3070,54 +3080,17 @@ sql_create_index(struct Parse *parse, struct Token *token,
> -/**
> - * 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)

4. Same as on the previous review. Still is used in a pair of places.

> -{
> -    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);
>   }
> diff --git a/src/box/sql/where.c b/src/box/sql/where.c
> index c0c26ce29..225fddc23 100644
> --- a/src/box/sql/where.c
> +++ b/src/box/sql/where.c
> @@ -2913,11 +2898,32 @@ whereLoopAddBtree(WhereLoopBuilder * pBuilder,    /* WHERE clause information */
>            */
>           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;
> +
> +        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);
> +
> +        sPk.def = index_def_new(pTab->def->id, 0, "primary",
> +                    sizeof("primary") - 1, TREE,
> +                    &index_opts_default, key_def, NULL);

>> 11. Where is sPk.def is deleted?
> It is deleted in freeIndex() with sPk 

5. I do not see any mention of freeIndex() in where.c. Where is it deleted?

sPk is declared on stack. If it was deleted with freeIndex, Tarantool
would crash.

> +        key_def_delete(key_def);
> +
> +        if (sPk.def == NULL) {
> +            pWInfo->pParse->nErr++;
> +            pWInfo->pParse->rc = SQL_TARANTOOL_ERROR;
> +            return SQL_TARANTOOL_ERROR;
> +        }
> +
>           aiRowEstPk[0] = sql_space_tuple_log_count(pTab);
>           aiRowEstPk[1] = 0;
>           pFirst = pSrc->pTab->pIndex;

6. Where is the test, that I gave you on the previous review and that
lead to crash? Please, add it to the test suite. And the new test in
this review too.

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

* [tarantool-patches] Re: [PATCH v8] sql: add index_def to struct Index
       [not found]                 ` <146c3bd4-e9e6-f943-5a42-c6db966d1c9c@tarantool.org>
@ 2018-07-03  9:00                   ` Ivan Koptelov
  0 siblings, 0 replies; 23+ messages in thread
From: Ivan Koptelov @ 2018-07-03  9:00 UTC (permalink / raw)
  To: Vladislav Shpilevoy, tarantool-patches

[-- Attachment #1: Type: text/html, Size: 143530 bytes --]

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

* [tarantool-patches] Re: [PATCH v8.5] sql: add index_def to struct Index
  2018-06-29 20:46               ` Vladislav Shpilevoy
       [not found]                 ` <146c3bd4-e9e6-f943-5a42-c6db966d1c9c@tarantool.org>
@ 2018-07-03  9:46                 ` Ivan Koptelov
  2018-07-03 12:13                   ` Vladislav Shpilevoy
  2018-07-03 11:37                 ` [tarantool-patches] Re: [PATCH v9] " Ivan Koptelov
  2 siblings, 1 reply; 23+ messages in thread
From: Ivan Koptelov @ 2018-07-03  9:46 UTC (permalink / raw)
  To: Vladislav Shpilevoy, tarantool-patches

[-- Attachment #1: Type: text/html, Size: 141514 bytes --]

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

* [tarantool-patches] Re: [PATCH v9] sql: add index_def to struct Index
  2018-06-29 20:46               ` Vladislav Shpilevoy
       [not found]                 ` <146c3bd4-e9e6-f943-5a42-c6db966d1c9c@tarantool.org>
  2018-07-03  9:46                 ` [tarantool-patches] Re: [PATCH v8.5] " Ivan Koptelov
@ 2018-07-03 11:37                 ` Ivan Koptelov
  2018-07-03 23:54                   ` n.pettik
  2018-07-04 10:46                   ` [tarantool-patches] Re: [PATCH v9] " Kirill Yukhin
  2 siblings, 2 replies; 23+ messages in thread
From: Ivan Koptelov @ 2018-07-03 11:37 UTC (permalink / raw)
  To: Vladislav Shpilevoy, tarantool-patches

In previous patch versions I didn't fix one test, which prints all error 
codes.
At failed, because I added new error code. Now the test is fixed.
>> 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
>> and zName are removed from Index. All usages of this
>> fields changed to usage of corresponding index_def
>> fields.
>> index_is_unique(), sql_index_collation() and
>> index_column_count() are removed with calls of
>> index_def corresponding fields.
>>
>> Closes: #3369
>>
>> ---
>> Branch:
>> https://github.com/tarantool/tarantool/tree/sb/gh-3369-use-index-def-in-select-and-where 
>>
>> Issue:https://github.com/tarantool/tarantool/issues/3369
>>
>>   src/box/sql.c                        |  54 ++-
>>   src/box/sql/analyze.c                |  85 ++---
>>   src/box/sql/build.c                  | 713 
>> +++++++++++++++++------------------
>>   src/box/sql/delete.c                 |  10 +-
>>   src/box/sql/expr.c                   |  61 +--
>>   src/box/sql/fkey.c                   | 132 +++----
>>   src/box/sql/insert.c                 | 145 ++++---
>>   src/box/sql/pragma.c                 |  30 +-
>>   src/box/sql/select.c                 |   2 +-
>>   src/box/sql/sqliteInt.h              | 111 ++----
>>   src/box/sql/update.c                 |  39 +-
>>   src/box/sql/vdbeaux.c                |   2 +-
>>   src/box/sql/vdbemem.c                |  21 +-
>>   src/box/sql/where.c                  | 180 ++++-----
>>   src/box/sql/wherecode.c              | 102 ++---
>>   test/sql-tap/colname.test.lua        |   4 +-
>>   test/sql/message-func-indexes.result |   8 +-
>>   17 files changed, 821 insertions(+), 878 deletions(-)
>>
>> diff --git a/src/box/sql.c b/src/box/sql.c
>> index 11353150e..24e37652e 100644
>> --- a/src/box/sql.c
>> +++ b/src/box/sql.c
>> @@ -1452,8 +1452,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;
>
> 1. You again sent the patch with spaces instead of tabs. Please, cope
> with it. Looks like you copied 'git diff/show' output. Either use 
> format-patch
> or use 'git --no-pager diff/show'.
>
>>           if (def->fields[pk].type == FIELD_TYPE_INTEGER)
>>               pk_forced_int = pk;
>>       }
>> diff --git a/src/box/sql/build.c b/src/box/sql/build.c
>> index 0da7d805b..662fc698e 100644
>> --- a/src/box/sql/build.c
>> +++ b/src/box/sql/build.c> @@ -2646,18 +2535,154 @@ 
>> addIndexToTable(Index * pIndex, Table * pTab)> + struct Expr 
>> *column_expr = sqlite3ExprSkipCollate(expr);
>> +        if (column_expr->op != TK_COLUMN) {
>> +            sqlite3ErrorMsg(parse, tnt_errcode_desc(ER_UNSUPPORTED),
>> +                        "Tarantool", "functional indexes");
>
> 2. Patch to allow SQL_TARANTOOL_ERROR has been pushed today, so you 
> can use
> here diag_set again.
>
>> +            goto tnt_error;
>> +        }
>> +
>> @@ -2805,108 +2828,92 @@ sql_create_index(struct Parse *parse, struct 
>> Token *token,
>
> 3. Crash:
>
> box.cfg{}
> box.sql.execute('CREATE TABLE test (a int, b int, c int, PRIMARY KEY 
> (a, a COLLATE kek, b, c))')
> Process 15886 stopped
> * thread #1, queue = 'com.apple.main-thread', stop reason = 
> EXC_BAD_ACCESS (code=1, address=0x80)
>     frame #0: 0x000000010035f77b 
> tarantool`sql_create_index(parse=0x000000010481f8b0, 
> token=0x0000000000000000, tbl_name=0x0000000000000000, 
> col_list=0x00000001029039d8, on_error=ON_CONFLICT_ACTION_DEFAULT, 
> start=0x0000000000000000, where=0x0000000000000000, 
> sort_order=SORT_ORDER_ASC, if_not_exist=false, idx_type='\x02') at 
> build.c:2895
>    2892         * PRIMARY KEY contains no repeated columns.
>    2893         */
>    2894        if (IsPrimaryKeyIndex(index)) {
> -> 2895            struct key_part *parts = index->def->key_def->parts;
>    2896            uint32_t part_count = index->def->key_def->part_count;
>    2897            uint32_t new_part_count = 1;
>    2898
> Target 0: (tarantool) stopped.
>
>> @@ -3070,54 +3080,17 @@ sql_create_index(struct Parse *parse, struct 
>> Token *token,
>> -/**
>> - * 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)
>
> 4. Same as on the previous review. Still is used in a pair of places.
>
Are you sure? I searched through the whole project and didn't find it.
There is only a variable with the same name in one place
>> -{
>> -    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);
>>   }
>> diff --git a/src/box/sql/where.c b/src/box/sql/where.c
>> index c0c26ce29..225fddc23 100644
>> --- a/src/box/sql/where.c
>> +++ b/src/box/sql/where.c
>> @@ -2913,11 +2898,32 @@ whereLoopAddBtree(WhereLoopBuilder * 
>> pBuilder,    /* WHERE clause information */
>>            */
>>           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;
>> +
>> +        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);
>> +
>> +        sPk.def = index_def_new(pTab->def->id, 0, "primary",
>> +                    sizeof("primary") - 1, TREE,
>> +                    &index_opts_default, key_def, NULL);
>
>>> 11. Where is sPk.def is deleted?
>> It is deleted in freeIndex() with sPk 
>
> 5. I do not see any mention of freeIndex() in where.c. Where is it 
> deleted?
>
> sPk is declared on stack. If it was deleted with freeIndex, Tarantool
> would crash.
>
>> +        key_def_delete(key_def);
>> +
>> +        if (sPk.def == NULL) {
>> +            pWInfo->pParse->nErr++;
>> +            pWInfo->pParse->rc = SQL_TARANTOOL_ERROR;
>> +            return SQL_TARANTOOL_ERROR;
>> +        }
>> +
>>           aiRowEstPk[0] = sql_space_tuple_log_count(pTab);
>>           aiRowEstPk[1] = 0;
>>           pFirst = pSrc->pTab->pIndex;
>
> 6. Where is the test, that I gave you on the previous review and that
> lead to crash? Please, add it to the test suite. And the new test in
> this review too.
>

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
and zName are removed from Index. All usages of this
fields changed to usage of corresponding index_def
fields.
index_is_unique(), sql_index_collation() and
index_column_count() are removed with calls of
index_def corresponding fields.

Closes: #3369
---
Branch:
https://github.com/tarantool/tarantool/tree/sb/gh-3369-use-index-def-in-select-and-where
Issue: https://github.com/tarantool/tarantool/issues/3369

  src/box/errcode.h                                  |   1 +
  src/box/sql.c                                      |  54 +-
  src/box/sql/analyze.c                              |  85 ++-
  src/box/sql/build.c                                | 727 ++++++++++-----------
  src/box/sql/delete.c                               |  10 +-
  src/box/sql/expr.c                                 |  61 +-
  src/box/sql/fkey.c                                 | 213 +++---
  src/box/sql/insert.c                               | 145 ++--
  src/box/sql/pragma.c                               |  30 +-
  src/box/sql/select.c                               |   2 +-
  src/box/sql/sqliteInt.h                            | 116 ++--
  src/box/sql/update.c                               |  39 +-
  src/box/sql/vdbeaux.c                              |   2 +-
  src/box/sql/vdbemem.c                              |  21 +-
  src/box/sql/where.c                                | 182 +++---
  src/box/sql/wherecode.c                            | 102 +--
  test/box/misc.result                               |   1 +
  .../{collation.test.lua => collation1.test.lua}    |   0
  test/sql-tap/collation2.test.lua                   |  20 +
  test/sql-tap/colname.test.lua                      |   4 +-
  test/sql-tap/identifier_case.test.lua              |   4 +-
  test/sql/message-func-indexes.result               |   8 +-
  22 files changed, 900 insertions(+), 927 deletions(-)
  rename test/sql-tap/{collation.test.lua => collation1.test.lua} (100%)
  create mode 100755 test/sql-tap/collation2.test.lua

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 03b4f156a..b00e8655d 100644
--- a/src/box/sql.c
+++ b/src/box/sql.c
@@ -1418,8 +1418,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;
  	}
@@ -1530,20 +1530,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;
  	}
  
@@ -1553,46 +1552,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..c5afff214 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));
+		int pk_part_count = (int) pPk->def->key_def->part_count;
+		int regKeyStat = sqlite3GetTempRange(pParse, pk_part_count);
+		for (int j = 0; j < pk_part_count; ++j) {
+			int k = pPk->def->key_def->parts[j].fieldno;
+			assert(k >= 0 && k < (int) 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");
  	}
diff --git a/src/box/sql/build.c b/src/box/sql/build.c
index 3737a119f..48e0b8d5e 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);
@@ -1026,7 +1029,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)
@@ -1034,22 +1037,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 *
@@ -1079,66 +1081,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)
  {
@@ -1322,17 +1264,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
@@ -1349,13 +1280,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;
@@ -1387,20 +1317,6 @@ convertToWithoutRowidTable(Parse * pParse, Table * 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);
  }
@@ -1482,7 +1398,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);
@@ -1519,7 +1435,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) {
@@ -2448,15 +2364,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.
@@ -2487,7 +2404,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);
@@ -2507,44 +2425,15 @@ 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, uint32_t part_count)
  {
-	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;
-	}
+	/* Size of struct Index and aiRowLogEst. */
+	int nByte = ROUND8(sizeof(struct Index)) +
+		    ROUND8(sizeof(LogEst) * (part_count + 1));
+	struct Index *p = sqlite3DbMallocZero(db, nByte);
+	if (p != NULL)
+		p->aiRowLogEst = (LogEst *) ((char *)p + ROUND8(sizeof(*p)));
  	return p;
  }
  
@@ -2631,46 +2520,187 @@ 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);
+/**
+ * Allocate memory on parser region and copy given string (part of
+ * the sql statement) into the allocated memory.
+ * @param parse Parse context.
+ * @param str String (a part of sql statement) to be copied.
+ *
+ * @retval size Appended size.
+ */
+static int
+sql_append(struct Parse *parse, const char *str)
+{
+	const size_t str_len = strlen(str);
+	char *str_part = region_alloc(&parse->region, str_len);
+	if (str_part == NULL) {
+		diag_set(OutOfMemory, str_len, "region_alloc", "str_part");
+		parse->rc = SQL_TARANTOOL_ERROR;
+		parse->nErr++;
+		return 0;
+	}
+	memcpy(str_part, str, str_len);
+	return str_len;
+}
+
+/**
+ * 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 is_unique Is given 'index' unique or not.
+ * @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, one of the following:
+ *                 SQLITE_IDXTYPE_APPDEF 0 (Index is created with
+ *                 CREATE INDEX statement)
+ *                 SQLITE_IDXTYPE_UNIQUE 1 (Index is created
+ *                 automatically to implement a UNIQUE constraint)
+ *                 SQLITE_IDXTYPE_PRIMARYKEY 2 (Index is a PRIMARY
+ *                 KEY).
+ */
+static void
+set_index_def(struct Parse *parse, struct Index *index, struct Table *table,
+	      uint32_t iid, const char *name, uint32_t name_len, bool is_unique,
+	      struct ExprList *expr_list, u8 idx_type)
+{
+	struct space_def *space_def = table->def;
+	size_t sql_size = 0;
+	struct index_opts opts;
+	index_opts_create(&opts);
+	index->def = NULL;
+	opts.is_unique = is_unique;
+
+	struct key_def *key_def = key_def_new(expr_list->nExpr);
+	if (key_def == NULL)
+		goto tnt_error;
+
+	/* Build initial parts of SQL statement.  */
+	if (idx_type == SQLITE_IDXTYPE_APPDEF) {
+		sql_size += sql_append(parse, "CREATE INDEX ");
+		sql_size += sql_append(parse, name);
+		sql_size += sql_append(parse, " ON ");
+		sql_size += sql_append(parse, space_def->name);
+		sql_size += sql_append(parse, " (");
+	}
+
+	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;
+		const char *column_name = column_expr->u.zToken;
+		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;
+			}
+			if (idx_type == SQLITE_IDXTYPE_APPDEF) {
+				sql_size += sql_append(parse, column_name);
+				sql_size += sql_append(parse, " COLLATE ");
+				sql_size += sql_append(parse, expr->u.zToken);
+				sql_size += sql_append(parse, ", ");
+			}
+		} else {
+			coll = sql_column_collation(space_def, fieldno,
+						    &coll_id);
+			if (idx_type == SQLITE_IDXTYPE_APPDEF) {
+				sql_size += sql_append(parse, column_name);
+				sql_size += sql_append(parse, ", ");
+			}
+		}
+		/*
+		 * 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;
+	if (idx_type == SQLITE_IDXTYPE_APPDEF) {
+		opts.sql = region_join(&parse->region, sql_size);
+		if (opts.sql == NULL) {
+			diag_set(OutOfMemory, sql_size, "region_join",
+				 "opts.sql");
+			goto tnt_error;
+		}
+		/*
+		 * fix last ", " with ")\0" to finish statement.
+		 */
+		memcpy(&opts.sql[sql_size - 2], ")\0", 2);
+	}
+
+	struct key_def *pk_key_def;
+	if (idx_type == SQLITE_IDXTYPE_APPDEF)
+		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;
+cleanup:
+	if (key_def != NULL)
+		key_def_delete(key_def);
+	return;
+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, u8 idx_type)
+{
+	/* Table to be indexed.  */
+	struct Table *table = NULL;
+	/* The index to be created.  */
+	struct Index *index = NULL;
+	/* Name of the index.  */
+	char *name = NULL;
+	int name_len;
+	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) {
  		Vdbe *v = sqlite3GetVdbe(parse);
@@ -2681,39 +2711,43 @@ 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.
  	 */
  	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,
+		DbFixer db_fixer;
+		sqlite3FixInit(&db_fixer, parse, "index", token);
+		if (sqlite3FixSrcList(&db_fixer, tbl_name)) {
+			/*
+			 * Because the parser constructs tbl_name
+			 * from a single identifier,
  			 * sqlite3FixSrcList can never fail.
  			 */
-			assert(0);
+			unreachable();
  		}
-		pTab = sqlite3LocateTable(parse, 0, tbl_name->a[0].zName);
-		assert(db->mallocFailed == 0 || pTab == 0);
-		if (pTab == 0)
+		table = sqlite3LocateTable(parse, 0, tbl_name->a[0].zName);
+		assert(db->mallocFailed == 0 || table == NULL);
+		if (table == NULL)
  			goto exit_create_index;
-		sqlite3PrimaryKeyIndex(pTab);
+		sqlite3PrimaryKeyIndex(table);
  	} else {
  		assert(token == NULL);
  		assert(start == NULL);
-		pTab = parse->pNewTable;
-		if (!pTab)
+		table = parse->pNewTable;
+		if (table == NULL)
  			goto exit_create_index;
  	}
  
-	assert(pTab != 0);
+	assert(table != NULL);
  	assert(parse->nErr == 0);
-	if (pTab->def->opts.is_view) {
+	if (table->def->opts.is_view) {
  		sqlite3ErrorMsg(parse, "views may not be indexed");
  		goto exit_create_index;
  	}
@@ -2731,42 +2765,38 @@ sql_create_index(struct Parse *parse, struct Token *token,
  	 * primary key or UNIQUE constraint.  We have to invent
  	 * our own name.
  	 */
-	if (token) {
-		zName = sqlite3NameFromToken(db, token);
-		if (zName == 0)
+	if (token != NULL) {
+		name = sqlite3NameFromToken(db, token);
+		if (name == NULL)
  			goto exit_create_index;
-		assert(token->z != 0);
+		assert(token->z != NULL);
  		if (!db->init.busy) {
-			if (sqlite3HashFind(&db->pSchema->tblHash, zName) !=
+			if (sqlite3HashFind(&db->pSchema->tblHash, name) !=
  			    NULL) {
-				sqlite3ErrorMsg(parse,
-						"there is already a table named %s",
-						zName);
+				sqlite3ErrorMsg(parse, "there is already a "\
+						"table named %s", name);
  				goto exit_create_index;
  			}
  		}
-		if (sqlite3HashFind(&pTab->idxHash, zName) != 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;
+		int n = 1;
+		for (struct Index *pLoop = table->pIndex; pLoop != NULL;
  		     pLoop = pLoop->pNext, n++) {
  		}
-		zName =
-		    sqlite3MPrintf(db, "sqlite_autoindex_%s_%d", pTab->def->name,
-				   n);
-		if (zName == 0) {
+		name = sqlite3MPrintf(db, "sqlite_autoindex_%s_%d",
+				      table->def->name, n);
+		if (name == NULL)
  			goto exit_create_index;
-		}
  	}
  
  	/*
@@ -2776,9 +2806,9 @@ 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 prevCol;
+		uint32_t last_field = table->def->field_count - 1;
+		sqlite3TokenInit(&prevCol, table->def->fields[last_field].name);
  		col_list = sql_expr_list_append(parse->db, NULL,
  						sqlite3ExprAlloc(db, TK_ID,
  								 &prevCol, 0));
@@ -2790,108 +2820,93 @@ 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));
-		}
-	}
+	/* Allocate the index structure.  */
+	name_len = sqlite3Strlen30(name);
  
-	/*
-	 * Allocate the index structure.
-	 */
-	nName = sqlite3Strlen30(zName);
-	pIndex = sqlite3AllocateIndexObject(db, col_list->nExpr,
-					    nName + nExtra + 1, &zExtra);
-	if (db->mallocFailed) {
+	if (name_len > BOX_NAME_MAX) {
+		sqlite3ErrorMsg(parse, "%s.%s exceeds indexes' names length "\
+				"limit", table->def->name, name);
  		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;
+
+	if (sqlite3CheckIdentifierName(parse, name) != SQLITE_OK)
+		goto exit_create_index;
+
+	index = sql_index_alloc(db, col_list->nExpr);
+	if (db->mallocFailed)
+		goto exit_create_index;
+
+	assert(EIGHT_BYTE_ALIGNMENT(index->aiRowLogEst));
+	index->pTable = table;
+	index->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;
+	    on_error != ON_CONFLICT_ACTION_NONE)
+		index->idxType = SQLITE_IDXTYPE_UNIQUE;
+	else
+		index->idxType = idx_type;
+	index->pSchema = db->pSchema;
  	/* Tarantool have access to each column by any index */
-	if (where) {
-		sql_resolve_self_reference(parse, pTab, NC_PartIdx, where,
+	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");
-			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;
+	uint32_t max_iid = 0;
+	for (struct Index *index = table->pIndex; index != NULL;
+	     index = index->pNext) {
+		max_iid = max_iid > index->def->iid ?
+			  max_iid : index->def->iid + 1;
+	}
+
+	bool is_unique = on_error != ON_CONFLICT_ACTION_NONE;
+	set_index_def(parse,  index, table, max_iid, name, name_len,
+		      is_unique, col_list, idx_type);
+
+	if (index->def == NULL)
+		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.
+	 */
+	if (IsPrimaryKeyIndex(index)) {
+		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;
  			}
-		} else if (j >= 0) {
-			coll = sql_column_collation(pTab->def, j, &id);
-		} else {
-			id = COLL_NONE;
-			coll = NULL;
+
+			if (j == new_part_count)
+				parts[new_part_count++] = parts[i];
  		}
-		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;
+		index->def->key_def->part_count = new_part_count;
  	}
-	if (pTab == parse->pNewTable) {
+
+	if (!index_def_is_valid(index->def, table->def->name))
+		goto exit_create_index;
+
+	if (table == 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.
@@ -2913,28 +2928,28 @@ sql_create_index(struct Parse *parse, struct Token *token,
  		 * 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;
+		for (struct Index *pIdx = table->pIndex; pIdx != NULL;
+		     pIdx = pIdx->pNext) {
+			uint32_t k;
  			assert(IsUniqueIndex(pIdx));
  			assert(pIdx->idxType != SQLITE_IDXTYPE_APPDEF);
-			assert(IsUniqueIndex(pIndex));
+			assert(IsUniqueIndex(index));
  
-			if (pIdx->nColumn != pIndex->nColumn)
+			if (pIdx->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 < pIdx->def->key_def->part_count; k++) {
+				if (pIdx->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 = pIdx->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) {
+			if (k == pIdx->def->key_def->part_count) {
+				if (pIdx->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
@@ -2942,17 +2957,19 @@ sql_create_index(struct Parse *parse, struct Token *token,
  					 * 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 (pIdx->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 (pIdx->onError ==
+					    ON_CONFLICT_ACTION_DEFAULT)
+						pIdx->onError = index->onError;
  				}
  				if (idx_type == SQLITE_IDXTYPE_PRIMARYKEY)
  					pIdx->idxType = idx_type;
@@ -2966,15 +2983,16 @@ sql_create_index(struct Parse *parse, struct Token *token,
  	 */
  	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;
  	}
  
  	/*
@@ -3025,14 +3043,14 @@ sql_create_index(struct Parse *parse, struct Token *token,
  				       ON_CONFLICT_ACTION_NONE ? "" : " UNIQUE",
  				       n, token->z);
  
-		iSpaceId = SQLITE_PAGENO_TO_SPACEID(pTab->tnum);
+		iSpaceId = SQLITE_PAGENO_TO_SPACEID(table->tnum);
  		iIndexId = getNewIid(parse, iSpaceId, iCursor);
  		sqlite3VdbeAddOp1(v, OP_Close, iCursor);
-		createIndex(parse, pIndex, iSpaceId, iIndexId, zStmt);
+		createIndex(parse, index, iSpaceId, iIndexId, zStmt);
  
  		/* consumes zStmt */
  		iFirstSchemaCol =
-		    makeIndexSchemaRecord(parse, pIndex, iSpaceId, iIndexId,
+		    makeIndexSchemaRecord(parse, index, iSpaceId, iIndexId,
  					  zStmt);
  
  		/* Reparse the schema. Code an OP_Expire
@@ -3055,54 +3073,17 @@ sql_create_index(struct Parse *parse, struct Token *token,
  
  	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 */
   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
@@ -3728,9 +3709,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);
@@ -3751,11 +3732,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 70e134f21..00d8dedd8 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);
  						}
  					}
@@ -3130,12 +3135,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);
  		}
  	}
@@ -3467,7 +3472,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..2b96bde7e 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,17 +462,19 @@ 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;
  					}
+
  					sqlite3VdbeAddOp3(v, OP_Ne, iChild,
  							  iJump, iParent);
  					VdbeCoverage(v);
@@ -614,7 +618,6 @@ 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() */
@@ -622,7 +625,7 @@ fkScanChildren(Parse * pParse,	/* Parse context */
  	Vdbe *v = sqlite3GetVdbe(pParse);
  
  	assert(pIdx == 0 || pIdx->pTable == pTab);
-	assert(pIdx == 0 || (int)index_column_count(pIdx) == pFKey->nCol);
+	assert(pIdx == 0 || (int) pIdx->def->key_def->part_count == pFKey->nCol);
  	assert(pIdx != 0);
  
  	if (nIncr < 0) {
@@ -639,19 +642,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 +674,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 +986,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 +1110,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 +1266,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..762caaee5 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);
  
+		const 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,11 +1936,11 @@ 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);
diff --git a/src/box/sql/pragma.c b/src/box/sql/pragma.c
index be6a01c23..db0b9a482 100644
--- a/src/box/sql/pragma.c
+++ b/src/box/sql/pragma.c
@@ -369,9 +369,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;
@@ -414,7 +413,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,
@@ -443,11 +442,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,
@@ -461,19 +462,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 <
@@ -503,10 +503,8 @@ sqlite3Pragma(Parse * pParse, Token * pId,	/* First part of [schema.]id field */
  						    { "c", "u", "pk" };
  						sqlite3VdbeMultiLoad(v, 1,
  								     "isisi", i,
-								     pIdx->
-								     zName,
-								     index_is_unique
-								     (pIdx),
+								     pIdx->def->name,
+								     pIdx->def->opts.is_unique,
  								     azOrigin
  								     [pIdx->
  								      idxType],
diff --git a/src/box/sql/select.c b/src/box/sql/select.c
index 54f78a9de..adf10feca 100644
--- a/src/box/sql/select.c
+++ b/src/box/sql/select.c
@@ -4366,7 +4366,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 e939663b6..3a560f1db 100644
--- a/src/box/sql/sqliteInt.h
+++ b/src/box/sql/sqliteInt.h
@@ -2051,28 +2051,6 @@ struct UnpackedRecord {
   * 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
@@ -2082,26 +2060,30 @@ 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 */
+	/** From ANALYZE: Est. rows selected by each column. */
+	LogEst *aiRowLogEst;
+	/** 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;
+	/** 1==UNIQUE, 2==PRIMARY KEY, 0==CREATE INDEX. */
+	unsigned idxType:2;
+	/** Index definition. */
+	struct index_def *def;
  };
  
  /**
@@ -3532,34 +3514,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 *);
  
@@ -3646,9 +3600,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, uint32_t part_count);
  
  /**
   * Create a new index for an SQL table.  name is the name of the
@@ -3678,8 +3639,9 @@ index_is_unique(Index *);
  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, u8 idx_type);
  
  /**
   * This routine will drop an existing named index.  This routine
@@ -4531,10 +4493,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 a8bd1e582..e50a23b3d 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..7ca02095f 100644
--- a/src/box/sql/where.c
+++ b/src/box/sql/where.c
@@ -372,13 +372,19 @@ whereScanInit(WhereScan * pScan,	/* The WhereScan object being initialized */
  	pScan->is_column_seen = false;
  	if (pIdx) {
  		int j = iColumn;
-		iColumn = pIdx->aiColumn[j];
+		iColumn = pIdx->def->key_def->parts[j].fieldno;
+		/*
+		 * pIdx->tnum == 0 means that pIdx is a fake
+		 * integer primary key index.
+		 */
+		if (pIdx->tnum == 0)
+			iColumn = -1;
+
  		if (iColumn >= 0) {
  			char affinity =
  				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;
  		}
  	}
@@ -541,47 +547,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 +616,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 +626,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 +819,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 +1167,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 +1229,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 +1255,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 +1430,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 +1580,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 +1601,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,7 +1733,7 @@ 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 (p->pIndex != NULL && (zName = p->pIndex->def->name) != NULL) {
  		if (strncmp(zName, "sqlite_autoindex_", 17) == 0) {
  			int i = sqlite3Strlen30(zName) - 1;
  			while (zName[i] != '_')
@@ -2314,7 +2296,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 +2317,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 +2333,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 +2376,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 +2411,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 +2432,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 +2507,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 +2578,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 +2680,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 +2709,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 +2734,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 +2777,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 +2787,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;
  			}
  		}
@@ -2882,7 +2868,6 @@ whereLoopAddBtree(WhereLoopBuilder * pBuilder,	/* WHERE clause information */
  	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 */
  	SrcList *pTabList;	/* The FROM clause */
  	struct SrcList_item *pSrc;	/* The FROM clause btree term to add */
  	WhereLoop *pNew;	/* Template WhereLoop object */
@@ -2913,11 +2898,32 @@ whereLoopAddBtree(WhereLoopBuilder * pBuilder,	/* WHERE clause information */
  		 */
  		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;
+
+		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);
+
+		sPk.def = index_def_new(pTab->def->id, 0, "primary",
+					sizeof("primary") - 1, TREE,
+					&index_opts_default, key_def, NULL);
+		key_def_delete(key_def);
+
+		if (sPk.def == NULL) {
+			pWInfo->pParse->nErr++;
+			pWInfo->pParse->rc = SQL_TARANTOOL_ERROR;
+			return SQL_TARANTOOL_ERROR;
+		}
+
  		aiRowEstPk[0] = sql_space_tuple_log_count(pTab);
  		aiRowEstPk[1] = 0;
  		pFirst = pSrc->pTab->pIndex;
@@ -3058,6 +3064,8 @@ whereLoopAddBtree(WhereLoopBuilder * pBuilder,	/* WHERE clause information */
  		if (pSrc->pIBIndex)
  			break;
  	}
+	if (&sPk == pProbe && sPk.def != NULL)
+		index_def_delete(sPk.def);
  	return rc;
  }
  
@@ -3392,8 +3400,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 +3462,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 +3515,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 +4785,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 +4918,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/collation.test.lua b/test/sql-tap/collation1.test.lua
similarity index 100%
rename from test/sql-tap/collation.test.lua
rename to test/sql-tap/collation1.test.lua
diff --git a/test/sql-tap/collation2.test.lua b/test/sql-tap/collation2.test.lua
new file mode 100755
index 000000000..64296b0be
--- /dev/null
+++ b/test/sql-tap/collation2.test.lua
@@ -0,0 +1,20 @@
+#!/usr/bin/env tarantool
+test = require("sqltester")
+test:plan(3)
+
+test:do_catchsql_test(
+        "collation-2.1",
+        'CREATE TABLE test1 (a int, b int, c int, PRIMARY KEY (a, a, a, b, c))',
+        nil)
+
+test:do_catchsql_test(
+        "collation-2.2",
+        'CREATE TABLE test2 (a int, b int, c int, PRIMARY KEY (a, a, a, b, b, a, c))',
+        nil)
+
+test:do_catchsql_test(
+        "collation-2.3",
+        '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/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/message-func-indexes.result b/test/sql/message-func-indexes.result
index 4bf1cab49..dbcb12f03 100644
--- a/test/sql/message-func-indexes.result
+++ b/test/sql/message-func-indexes.result
@@ -12,25 +12,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")
-- 

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

* [tarantool-patches] Re: [PATCH v8.5] sql: add index_def to struct Index
  2018-07-03  9:46                 ` [tarantool-patches] Re: [PATCH v8.5] " Ivan Koptelov
@ 2018-07-03 12:13                   ` Vladislav Shpilevoy
  0 siblings, 0 replies; 23+ messages in thread
From: Vladislav Shpilevoy @ 2018-07-03 12:13 UTC (permalink / raw)
  To: tarantool-patches, Ivan Koptelov, Nikita Pettik

Hello. Thanks for the fixes!

On 03/07/2018 12:46, Ivan Koptelov wrote:
> Thank for the review. Previous mail contains patch with an error. Here it is fixed.
>> Hello. Thanks for the fixes! See my 6 comments below.
>>
>> And I have pushed more fixes on the branch. Please,
>> look and squash.
>>
>>> 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
>>> and zName are removed from Index. All usages of this
>>> fields changed to usage of corresponding index_def
>>> fields.
>>> index_is_unique(), sql_index_collation() and
>>> index_column_count() are removed with calls of
>>> index_def corresponding fields.
>>>
>>> Closes: #3369
>>>
>>> ---
>>> Branch:
>>> https://github.com/tarantool/tarantool/tree/sb/gh-3369-use-index-def-in-select-and-where
>>> Issue:https://github.com/tarantool/tarantool/issues/3369
>>>
>>>   src/box/sql.c                        |  54 ++-
>>>   src/box/sql/analyze.c                |  85 ++---
>>>   src/box/sql/build.c                  | 713 +++++++++++++++++------------------
>>>   src/box/sql/delete.c                 |  10 +-
>>>   src/box/sql/expr.c                   |  61 +--
>>>   src/box/sql/fkey.c                   | 132 +++----
>>>   src/box/sql/insert.c                 | 145 ++++---
>>>   src/box/sql/pragma.c                 |  30 +-
>>>   src/box/sql/select.c                 |   2 +-
>>>   src/box/sql/sqliteInt.h              | 111 ++----
>>>   src/box/sql/update.c                 |  39 +-
>>>   src/box/sql/vdbeaux.c                |   2 +-
>>>   src/box/sql/vdbemem.c                |  21 +-
>>>   src/box/sql/where.c                  | 180 ++++-----
>>>   src/box/sql/wherecode.c              | 102 ++---
>>>   test/sql-tap/colname.test.lua        |   4 +-
>>>   test/sql/message-func-indexes.result |   8 +-
>>>   17 files changed, 821 insertions(+), 878 deletions(-)
>>>
>>> -
>>> -/** Return true if given index is unique and not nullable. */
>>> -bool
>>> -index_is_unique_not_null(const Index *idx)
>>
>> 4. Same as on the previous review. Still is used in a pair of places.
> Are you sure? I searched through the whole project and didn't find it.
> There is only a variable with the same name in one place.

Sorry, my fault. It is just a variable.

> diff --git a/src/box/sql/where.c b/src/box/sql/where.c
> index 85143ed20..7ca02095f 100644
> --- a/src/box/sql/where.c
> +++ b/src/box/sql/where.c
> @@ -3058,6 +3064,8 @@ whereLoopAddBtree(WhereLoopBuilder * pBuilder,	/* WHERE clause information */
>   		if (pSrc->pIBIndex)
>   			break;
>   	}
> +	if (&sPk == pProbe && sPk.def != NULL)
> +		index_def_delete(sPk.def);

It will not be deleted since pProbe is changed in the cycle above.
I have checked it by putting assert(false) under this 'if'.
And I have already fixed it on the branch.

>   	return rc;
>   }
>   

I have pushed my last fixes on the branch. Please, look, squash and then
LGTM. Nikita, please, review after the squash.

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

* [tarantool-patches] Re: [PATCH v9] sql: add index_def to struct Index
  2018-07-03 11:37                 ` [tarantool-patches] Re: [PATCH v9] " Ivan Koptelov
@ 2018-07-03 23:54                   ` n.pettik
  2018-07-04  0:08                     ` Vladislav Shpilevoy
  2018-07-04 15:55                     ` [tarantool-patches] Re: [PATCH v11] " Ivan Koptelov
  2018-07-04 10:46                   ` [tarantool-patches] Re: [PATCH v9] " Kirill Yukhin
  1 sibling, 2 replies; 23+ messages in thread
From: n.pettik @ 2018-07-03 23:54 UTC (permalink / raw)
  To: tarantool-patches; +Cc: Ivan Koptelov

Firstly, I have found some bugs:

tarantool> create table t1(a,b,c,d, primary key(a,a,a,b,c));
---
...

It is OK, but when I do this:

tarantool> create index i1 on t1(b,c,a,c)
---
...

tarantool> create index i1 on t1(b,c,a,c)
---
...

tarantool> create index i1 on t1(b,c,a,c)
---
…

No error is raised and index isn’t created.

After you find and fix this bug, add also test on this case.

Then:

CREATE TABLE t11 (s1 INT, a, constraint c1 UNIQUE(s1) on conflict replace, PRIMARY KEY(s1));

In this case creation of unique constraint c1 is omitted, but no errors or warnings are shown.
It is not a problem now, but when ALTER TABLE DROP CONSTRAINT is implemented,
it will be possible to drop c1 constraint. Eventually, user would be disappointed if tried to drop
this constraint but got an error.

> @@ -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));
> +		int pk_part_count = (int) pPk->def->key_def->part_count;
> +		int regKeyStat = sqlite3GetTempRange(pParse, pk_part_count);
> +		for (int j = 0; j < pk_part_count; ++j) {

Use uint32_t instead of int:

-               int pk_part_count = (int) pPk->def->key_def->part_count;
+               uint32_t pk_part_count = pPk->def->key_def->part_count;
                int regKeyStat = sqlite3GetTempRange(pParse, pk_part_count);
-               for (int j = 0; j < pk_part_count; ++j) {
-                       int k = pPk->def->key_def->parts[j].fieldno;
-                       assert(k >= 0 && k < (int) pTab->def->field_count);
+               for (uint32_t j = 0; j < pk_part_count; ++j) {
+                       uint32_t k = pPk->def->key_def->parts[j].fieldno;
+                       assert(k >= 0 && k < pTab->def->field_count);

> +struct Index *
> +sql_index_alloc(struct sqlite3 *db, uint32_t part_count)
> {
> -	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;
> -	}
> +	/* Size of struct Index and aiRowLogEst. */
> +	int nByte = ROUND8(sizeof(struct Index)) +
> +		    ROUND8(sizeof(LogEst) * (part_count + 1));

Do we really need this alignment?

> +	struct Index *p = sqlite3DbMallocZero(db, nByte);
> +	if (p != NULL)
> +		p->aiRowLogEst = (LogEst *) ((char *)p + ROUND8(sizeof(*p)));
> 	return p;
> }
> @@ -2631,46 +2520,187 @@ addIndexToTable(Index * pIndex, Table * pTab)
> +/**
> + * Allocate memory on parser region and copy given string (part of
> + * the sql statement) into the allocated memory.
> + * @param parse Parse context.
> + * @param str String (a part of sql statement) to be copied.
> + *
> + * @retval size Appended size.
> + */
> +static int
> +sql_append(struct Parse *parse, const char *str)

Such strange function...Lets rename it to sql(or str)_copy_to_region() at least.

> +{
> +	const size_t str_len = strlen(str);
> +	char *str_part = region_alloc(&parse->region, str_len);
> +	if (str_part == NULL) {
> +		diag_set(OutOfMemory, str_len, "region_alloc", "str_part");
> +		parse->rc = SQL_TARANTOOL_ERROR;
> +		parse->nErr++;
> +		return 0;
> +	}
> +	memcpy(str_part, str, str_len);
> +	return str_len;
> +}
> +
> +/**
> + * 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 is_unique Is given 'index' unique or not.
> + * @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, one of the following:
> + *                 SQLITE_IDXTYPE_APPDEF 0 (Index is created with
> + *                 CREATE INDEX statement)
> + *                 SQLITE_IDXTYPE_UNIQUE 1 (Index is created
> + *                 automatically to implement a UNIQUE constraint)
> + *                 SQLITE_IDXTYPE_PRIMARYKEY 2 (Index is a PRIMARY
> + *                 KEY).

Yo can skip this description - it is almost copy of existing one at these macroses definition.

> + */
> +static void
> +set_index_def(struct Parse *parse, struct Index *index, struct Table *table,

Lets use Tarantool naming convention, sort of: index_fill_def() or index_create_def().

> +	      uint32_t iid, const char *name, uint32_t name_len, bool is_unique,
> +	      struct ExprList *expr_list, u8 idx_type)
> +{
> +	struct space_def *space_def = table->def;
> +	size_t sql_size = 0;
> +	struct index_opts opts;
> +	index_opts_create(&opts);
> +	index->def = NULL;
> +	opts.is_unique = is_unique;
> +	int rc = -1;

You don’t use this variable and in the end just reassign it:

+	if (index->def == NULL)
+		goto tnt_error;
+	rc = 0;
+cleanup:
+	if (key_def != NULL)
+		key_def_delete(key_def);
+	return rc;

> +
> +	struct key_def *key_def = key_def_new(expr_list->nExpr);
> +	if (key_def == NULL)
> +		goto tnt_error;
> +
> +	/* Build initial parts of SQL statement.  */
> +	if (idx_type == SQLITE_IDXTYPE_APPDEF) {
> +		sql_size += sql_append(parse, "CREATE INDEX ");
> +		sql_size += sql_append(parse, name);
> +		sql_size += sql_append(parse, " ON ");
> +		sql_size += sql_append(parse, space_def->name);
> +		sql_size += sql_append(parse, " (“);

Why do you need to construct “CREATE INDEX” statement from scratch?
This function is only called from sql_create_index() - there you already have
this string:

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

>  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, u8 idx_type)
> +{
> +	/* Table to be indexed.  */

Extra space after dot.

> +	struct Table *table = NULL;
> +	/* The index to be created.  */
> +	struct Index *index = NULL;
> +	/* Name of the index.  */
> +	char *name = NULL;
> +	int name_len;

You don’t need to declare variables so beforehand.
Its first usage occurs at 130+ lines below.

> +	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) {
> 		Vdbe *v = sqlite3GetVdbe(parse);
> @@ -2681,39 +2711,43 @@ 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.
> 	 */
> 	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,
> +		DbFixer db_fixer;
> +		sqlite3FixInit(&db_fixer, parse, "index", token);
> +		if (sqlite3FixSrcList(&db_fixer, tbl_name)) {

This ‘Fix’ routine seems to be useless now, lets remove it.

> +			/*
> +			 * Because the parser constructs tbl_name
> +			 * from a single identifier,
> 			 * sqlite3FixSrcList can never fail.
> 			 */
> -			assert(0);
> +			unreachable();
> 		}
> -		pTab = sqlite3LocateTable(parse, 0, tbl_name->a[0].zName);
> -		assert(db->mallocFailed == 0 || pTab == 0);
> -		if (pTab == 0)
> +		table = sqlite3LocateTable(parse, 0, tbl_name->a[0].zName);
> +		assert(db->mallocFailed == 0 || table == NULL);
> +		if (table == NULL)
> 			goto exit_create_index;
> -		sqlite3PrimaryKeyIndex(pTab);
> +		sqlite3PrimaryKeyIndex(table);

Why do you call this function? It returns PK, but you don’t use it.

> 	} else {
> 		assert(token == NULL);
> 		assert(start == NULL);
> -		pTab = parse->pNewTable;
> -		if (!pTab)
> +		table = parse->pNewTable;
> +		if (table == NULL)
> 			goto exit_create_index;
> 	}

Instead of checking table on NULL in each branch and after that using assert(table != NULL),
it is better to replace that assert() with check:

-               if (table == NULL)
-                       goto exit_create_index;
-               sqlite3PrimaryKeyIndex(table);
        } else {
                assert(token == NULL);
                assert(start == NULL);
                table = parse->pNewTable;
-               if (table == NULL)
-                       goto exit_create_index;
        }
 
-       assert(table != NULL);
+       if (table == NULL)
+               goto exit_create_index;

> -	assert(pTab != 0);
> +	assert(table != NULL);
> 	assert(parse->nErr == 0);
> -	if (pTab->def->opts.is_view) {
> +	if (table->def->opts.is_view) {
> 		sqlite3ErrorMsg(parse, "views may not be indexed");
> 		goto exit_create_index;
> 	}

Lets also prohibit creation of indexes on system spaces.

> @@ -2731,42 +2765,38 @@ sql_create_index(struct Parse *parse, struct Token *token,
> 	 * primary key or UNIQUE constraint.  We have to invent
> 	 * our own name.
> 	 */
> -	if (token) {
> -		zName = sqlite3NameFromToken(db, token);
> -		if (zName == 0)
> +	if (token != NULL) {
> +		name = sqlite3NameFromToken(db, token);
> +		if (name == NULL)
> 			goto exit_create_index;
> -		assert(token->z != 0);
> +		assert(token->z != NULL);
> 		if (!db->init.busy) {
> -			if (sqlite3HashFind(&db->pSchema->tblHash, zName) !=
> +			if (sqlite3HashFind(&db->pSchema->tblHash, name) !=
> 			    NULL) {
> -				sqlite3ErrorMsg(parse,
> -						"there is already a table named %s",
> -						zName);
> +				sqlite3ErrorMsg(parse, "there is already a "\
> +						"table named %s", name);
> 				goto exit_create_index;
> 			}
> 		}
> -		if (sqlite3HashFind(&pTab->idxHash, zName) != 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;
> +		int n = 1;
> +		for (struct Index *pLoop = table->pIndex; pLoop != NULL;

Lets use Tarantool naming convention.

> 		     pLoop = pLoop->pNext, n++) {
> 		}
> -		zName =
> -		    sqlite3MPrintf(db, "sqlite_autoindex_%s_%d", pTab->def->name,
> -				   n);
> -		if (zName == 0) {
> +		name = sqlite3MPrintf(db, "sqlite_autoindex_%s_%d”,

Lets remove ’sqlite_’ prefix and use just ’sql_’.

> +				      table->def->name, n);
> +		if (name == NULL)
> 			goto exit_create_index;
> -		}
> 	}
>  	/*
> @@ -2776,9 +2806,9 @@ 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 prevCol;

Lets use Tarantool naming convention.

> +		uint32_t last_field = table->def->field_count - 1;
> +		sqlite3TokenInit(&prevCol, table->def->fields[last_field].name);
> 		col_list = sql_expr_list_append(parse->db, NULL,
> 						sqlite3ExprAlloc(db, TK_ID,
> 								 &prevCol, 0));
> @@ -2790,108 +2820,93 @@ 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));
> -		}
> -	}
> +	/* Allocate the index structure.  */

Extra space after dot.

> +	name_len = sqlite3Strlen30(name);

Lets use traditional strlen() instead of SQLite analogs.

> -	/*
> -	 * Allocate the index structure.
> -	 */
> -	nName = sqlite3Strlen30(zName);
> -	pIndex = sqlite3AllocateIndexObject(db, col_list->nExpr,
> -					    nName + nExtra + 1, &zExtra);
> -	if (db->mallocFailed) {
> +	if (name_len > BOX_NAME_MAX) {
> +		sqlite3ErrorMsg(parse, "%s.%s exceeds indexes' names length "\
> +				"limit", table->def->name, name);

But sqlite3CheckIndentifier() also provides this check.

> +	uint32_t max_iid = 0;
> +	for (struct Index *index = table->pIndex; index != NULL;
> +	     index = index->pNext) {
> +		max_iid = max_iid > index->def->iid ?
> +			  max_iid : index->def->iid + 1;
> +	}

Look, you don’t need to find max_iid: if it is parsing stage, then you can
set it to any meaningful value (since in the end of function it is going to be destroyed);
if it is executing step, you can use db->init.newTnum.

> +
> +	bool is_unique = on_error != ON_CONFLICT_ACTION_NONE;

It seems to be so messy defining uniqueness by ON_CONFLICT_ACTION.
Lets refactor it somehow.

> @@ -460,17 +462,19 @@ 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;
> 					}
> +

Extra empty line.

> @@ -622,7 +625,7 @@ fkScanChildren(Parse * pParse,	/* Parse context */
> 	Vdbe *v = sqlite3GetVdbe(pParse);
>  	assert(pIdx == 0 || pIdx->pTable == pTab);
> -	assert(pIdx == 0 || (int)index_column_count(pIdx) == pFKey->nCol);
> +	assert(pIdx == 0 || (int) pIdx->def->key_def->part_count == pFKey->nCol);

Lets use == NULL comparison on pointers.

> 
> @@ -1108,19 +1110,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++)

Is this change related to patch?

> 
> @@ -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);
> +		const uint32_t pk_part_count = pPk->def->key_def->part_count;

Why do you use here const? In other places AFAIK we/you don’t use
const modifier (when it comes to simple numeric variables).


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

Is this refactoring related to patch? I mean, refactoring is always appreciated,
but don’t mess it with main goal of patch.

> diff --git a/src/box/sql/where.c b/src/box/sql/where.c
> index 85143ed20..7ca02095f 100644
> --- a/src/box/sql/where.c
> +++ b/src/box/sql/where.c
> @@ -372,13 +372,19 @@ whereScanInit(WhereScan * pScan,	/* The WhereScan object being initialized */
> 	pScan->is_column_seen = false;
> 	if (pIdx) {
> 		int j = iColumn;
> -		iColumn = pIdx->aiColumn[j];
> +		iColumn = pIdx->def->key_def->parts[j].fieldno;
> +		/*
> +		 * pIdx->tnum == 0 means that pIdx is a fake
> +		 * integer primary key index.
> +		 */
> +		if (pIdx->tnum == 0)
> +			iColumn = -1;

We are going to remove tnum from struct Index and struct Table.
So, if it is possible, use index->def->iid instead (or smth else).

> 
> @@ -2882,7 +2868,6 @@ whereLoopAddBtree(WhereLoopBuilder * pBuilder,	/* WHERE clause information */
> 	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 */
> 	SrcList *pTabList;	/* The FROM clause */
> 	struct SrcList_item *pSrc;	/* The FROM clause btree term to add */
> 	WhereLoop *pNew;	/* Template WhereLoop object */
> @@ -2913,11 +2898,32 @@ whereLoopAddBtree(WhereLoopBuilder * pBuilder,	/* WHERE clause information */
> 		 */
> 		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;
> +
> +		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);
> +
> +		sPk.def = index_def_new(pTab->def->id, 0, "primary”,

Lets name if like ‘fake_autoindex’ to easily tell it from the rest.

> diff --git a/test/sql-tap/collation.test.lua b/test/sql-tap/collation1.test.lua
> similarity index 100%
> rename from test/sql-tap/collation.test.lua
> rename to test/sql-tap/collation1.test.lua
> diff --git a/test/sql-tap/collation2.test.lua b/test/sql-tap/collation2.test.lua
> new file mode 100755
> index 000000000..64296b0be
> --- /dev/null
> +++ b/test/sql-tap/collation2.test.lua
> @@ -0,0 +1,20 @@
> +#!/usr/bin/env tarantool
> +test = require("sqltester")
> +test:plan(3)
> +
> +test:do_catchsql_test(
> +        "collation-2.1",
> +        'CREATE TABLE test1 (a int, b int, c int, PRIMARY KEY (a, a, a, b, c))',
> +        nil)
> +
> +test:do_catchsql_test(
> +        "collation-2.2",
> +        'CREATE TABLE test2 (a int, b int, c int, PRIMARY KEY (a, a, a, b, b, a, c))',
> +        nil)
> +
> +test:do_catchsql_test(
> +        "collation-2.3",
> +        '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()

I wouldn’t create separate test file for these simple tests.
Lets put them to existing one.

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

* [tarantool-patches] Re: [PATCH v9] sql: add index_def to struct Index
  2018-07-03 23:54                   ` n.pettik
@ 2018-07-04  0:08                     ` Vladislav Shpilevoy
  2018-07-04  9:17                       ` n.pettik
  2018-07-04 15:55                     ` [tarantool-patches] Re: [PATCH v11] " Ivan Koptelov
  1 sibling, 1 reply; 23+ messages in thread
From: Vladislav Shpilevoy @ 2018-07-04  0:08 UTC (permalink / raw)
  To: tarantool-patches, n.pettik; +Cc: Ivan Koptelov


>> +	      uint32_t iid, const char *name, uint32_t name_len, bool is_unique,
>> +	      struct ExprList *expr_list, u8 idx_type)
>> +{
>> +	struct space_def *space_def = table->def;
>> +	size_t sql_size = 0;
>> +	struct index_opts opts;
>> +	index_opts_create(&opts);
>> +	index->def = NULL;
>> +	opts.is_unique = is_unique;
>> +	int rc = -1;
> 
> You don’t use this variable and in the end just reassign it:

This variable is reassigned on success only. On any error we
go to cleanup or tnt_error and rc remains -1.

> 
> +	if (index->def == NULL)
> +		goto tnt_error;
> +	rc = 0;
> +cleanup:
> +	if (key_def != NULL)
> +		key_def_delete(key_def);
> +	return rc;
> 

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

* [tarantool-patches] Re: [PATCH v9] sql: add index_def to struct Index
  2018-07-04  0:08                     ` Vladislav Shpilevoy
@ 2018-07-04  9:17                       ` n.pettik
  0 siblings, 0 replies; 23+ messages in thread
From: n.pettik @ 2018-07-04  9:17 UTC (permalink / raw)
  To: tarantool-patches; +Cc: Ivan Koptelov, Vladislav Shpilevoy

> 
>>> +	      uint32_t iid, const char *name, uint32_t name_len, bool is_unique,
>>> +	      struct ExprList *expr_list, u8 idx_type)
>>> +{
>>> +	struct space_def *space_def = table->def;
>>> +	size_t sql_size = 0;
>>> +	struct index_opts opts;
>>> +	index_opts_create(&opts);
>>> +	index->def = NULL;
>>> +	opts.is_unique = is_unique;
>>> +	int rc = -1;
>> You don’t use this variable and in the end just reassign it:
> 
> This variable is reassigned on success only. On any error we
> go to cleanup or tnt_error and rc remains -1.

Oh, well, then it is OK.

Another one remark (after got approve from team):

@@ -2731,42 +2765,38 @@ sql_create_index(struct Parse *parse, struct Token *token,
	 * primary key or UNIQUE constraint.  We have to invent
	 * our own name.
	 */
-	if (token) {
-		zName = sqlite3NameFromToken(db, token);
-		if (zName == 0)
+	if (token != NULL) {
+		name = sqlite3NameFromToken(db, token);
+		if (name == NULL)
			goto exit_create_index;
-		assert(token->z != 0);
+		assert(token->z != NULL);
		if (!db->init.busy) {
-			if (sqlite3HashFind(&db->pSchema->tblHash, zName) !=
+			if (sqlite3HashFind(&db->pSchema->tblHash, name) !=
			    NULL) {
-				sqlite3ErrorMsg(parse,
-						"there is already a table named %s",
-						zName);
+				sqlite3ErrorMsg(parse, "there is already a "\
+						"table named %s", name);
				goto exit_create_index;

There is no need to prohibit creating index with table name, since
index name is local for given table. And most of other DBs also
allow to create index with table name.

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

* [tarantool-patches] Re: [PATCH v9] sql: add index_def to struct Index
  2018-07-03 11:37                 ` [tarantool-patches] Re: [PATCH v9] " Ivan Koptelov
  2018-07-03 23:54                   ` n.pettik
@ 2018-07-04 10:46                   ` Kirill Yukhin
  2018-07-04 12:10                     ` Kirill Yukhin
  1 sibling, 1 reply; 23+ messages in thread
From: Kirill Yukhin @ 2018-07-04 10:46 UTC (permalink / raw)
  To: tarantool-patches; +Cc: Vladislav Shpilevoy

Hello,
On 03 июл 14:37, Ivan Koptelov wrote:
> In previous patch versions I didn't fix one test, which prints all error
> codes.
> At failed, because I added new error code. Now the test is fixed.
> > > 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
> > > and zName are removed from Index. All usages of this
> > > fields changed to usage of corresponding index_def
> > > fields.
> > > index_is_unique(), sql_index_collation() and
> > > index_column_count() are removed with calls of
> > > index_def corresponding fields.
> > > 
> > > Closes: #3369
> > > 
> > > ---
> > > Branch:
> > > https://github.com/tarantool/tarantool/tree/sb/gh-3369-use-index-def-in-select-and-where
> > > 
> > > Issue:https://github.com/tarantool/tarantool/issues/3369
I've checked the patch into 2.0 branch.

--
Regards, Kirill Yukhin

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

* [tarantool-patches] Re: [PATCH v9] sql: add index_def to struct Index
  2018-07-04 10:46                   ` [tarantool-patches] Re: [PATCH v9] " Kirill Yukhin
@ 2018-07-04 12:10                     ` Kirill Yukhin
  0 siblings, 0 replies; 23+ messages in thread
From: Kirill Yukhin @ 2018-07-04 12:10 UTC (permalink / raw)
  To: tarantool-patches; +Cc: Vladislav Shpilevoy

On 04 июл 13:46, Kirill Yukhin wrote:
> Hello,
> On 03 июл 14:37, Ivan Koptelov wrote:
> > > > Branch:
> > > > https://github.com/tarantool/tarantool/tree/sb/gh-3369-use-index-def-in-select-and-where
> > > > 
> > > > Issue:https://github.com/tarantool/tarantool/issues/3369
> I've checked the patch into 2.0 branch.
Disregard. Patch reverted.

> --
> Regards, Kirill Yukhin

--
Regards, Kirill Yukhin

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

* [tarantool-patches] Re: [PATCH v11] sql: add index_def to struct Index
  2018-07-03 23:54                   ` n.pettik
  2018-07-04  0:08                     ` Vladislav Shpilevoy
@ 2018-07-04 15:55                     ` Ivan Koptelov
  2018-07-04 19:28                       ` n.pettik
  1 sibling, 1 reply; 23+ messages in thread
From: Ivan Koptelov @ 2018-07-04 15:55 UTC (permalink / raw)
  To: n.pettik, tarantool-patches

Thank you for the review!
> Firstly, I have found some bugs:
>
> tarantool> create table t1(a,b,c,d, primary key(a,a,a,b,c));
> ---
> ...
>
> It is OK, but when I do this:
>
> tarantool> create index i1 on t1(b,c,a,c)
> ---
> ...
>
> tarantool> create index i1 on t1(b,c,a,c)
> ---
> ...
>
> tarantool> create index i1 on t1(b,c,a,c)
> ---
> …
>
> No error is raised and index isn’t created.
>
> After you find and fix this bug, add also test on this case.
  Fixed, added test.
> Then:
>
> CREATE TABLE t11 (s1 INT, a, constraint c1 UNIQUE(s1) on conflict replace, PRIMARY KEY(s1));
>
> In this case creation of unique constraint c1 is omitted, but no errors or warnings are shown.
> It is not a problem now, but when ALTER TABLE DROP CONSTRAINT is implemented,
> it will be possible to drop c1 constraint. Eventually, user would be disappointed if tried to drop
> this constraint but got an error.
It seems to be out of the scope of the patch. Appropriate ticket:
https://github.com/tarantool/tarantool/issues/3498

>> @@ -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));
>> +		int pk_part_count = (int) pPk->def->key_def->part_count;
>> +		int regKeyStat = sqlite3GetTempRange(pParse, pk_part_count);
>> +		for (int j = 0; j < pk_part_count; ++j) {
> Use uint32_t instead of int:
>
> -               int pk_part_count = (int) pPk->def->key_def->part_count;
> +               uint32_t pk_part_count = pPk->def->key_def->part_count;
>                  int regKeyStat = sqlite3GetTempRange(pParse, pk_part_count);
> -               for (int j = 0; j < pk_part_count; ++j) {
> -                       int k = pPk->def->key_def->parts[j].fieldno;
> -                       assert(k >= 0 && k < (int) pTab->def->field_count);
> +               for (uint32_t j = 0; j < pk_part_count; ++j) {
> +                       uint32_t k = pPk->def->key_def->parts[j].fieldno;
> +                       assert(k >= 0 && k < pTab->def->field_count);
>
>> +struct Index *
>> +sql_index_alloc(struct sqlite3 *db, uint32_t part_count)
>> {
>> -	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;
>> -	}
>> +	/* Size of struct Index and aiRowLogEst. */
>> +	int nByte = ROUND8(sizeof(struct Index)) +
>> +		    ROUND8(sizeof(LogEst) * (part_count + 1));
> Do we really need this alignment?
No. Removed.
>
>> +	struct Index *p = sqlite3DbMallocZero(db, nByte);
>> +	if (p != NULL)
>> +		p->aiRowLogEst = (LogEst *) ((char *)p + ROUND8(sizeof(*p)));
>> 	return p;
>> }
>> @@ -2631,46 +2520,187 @@ addIndexToTable(Index * pIndex, Table * pTab)
>> +/**
>> + * Allocate memory on parser region and copy given string (part of
>> + * the sql statement) into the allocated memory.
>> + * @param parse Parse context.
>> + * @param str String (a part of sql statement) to be copied.
>> + *
>> + * @retval size Appended size.
>> + */
>> +static int
>> +sql_append(struct Parse *parse, const char *str)
> Such strange function...Lets rename it to sql(or str)_copy_to_region() at least.
Ok, renamed.
>
>> +{
>> +	const size_t str_len = strlen(str);
>> +	char *str_part = region_alloc(&parse->region, str_len);
>> +	if (str_part == NULL) {
>> +		diag_set(OutOfMemory, str_len, "region_alloc", "str_part");
>> +		parse->rc = SQL_TARANTOOL_ERROR;
>> +		parse->nErr++;
>> +		return 0;
>> +	}
>> +	memcpy(str_part, str, str_len);
>> +	return str_len;
>> +}
>> +
>> +/**
>> + * 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 is_unique Is given 'index' unique or not.
>> + * @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, one of the following:
>> + *                 SQLITE_IDXTYPE_APPDEF 0 (Index is created with
>> + *                 CREATE INDEX statement)
>> + *                 SQLITE_IDXTYPE_UNIQUE 1 (Index is created
>> + *                 automatically to implement a UNIQUE constraint)
>> + *                 SQLITE_IDXTYPE_PRIMARYKEY 2 (Index is a PRIMARY
>> + *                 KEY).
> Yo can skip this description - it is almost copy of existing one at these macroses definition.
Ok, removed detailed description, left a short one.
>
>> + */
>> +static void
>> +set_index_def(struct Parse *parse, struct Index *index, struct Table *table,
> Lets use Tarantool naming convention, sort of: index_fill_def() or index_create_def().
Ok, renamed.
>
>> +	      uint32_t iid, const char *name, uint32_t name_len, bool is_unique,
>> +	      struct ExprList *expr_list, u8 idx_type)
>> +{
>> +	struct space_def *space_def = table->def;
>> +	size_t sql_size = 0;
>> +	struct index_opts opts;
>> +	index_opts_create(&opts);
>> +	index->def = NULL;
>> +	opts.is_unique = is_unique;
>> +	int rc = -1;
> You don’t use this variable and in the end just reassign it:
>
> +	if (index->def == NULL)
> +		goto tnt_error;
> +	rc = 0;
> +cleanup:
> +	if (key_def != NULL)
> +		key_def_delete(key_def);
> +	return rc;
>
>> +
>> +	struct key_def *key_def = key_def_new(expr_list->nExpr);
>> +	if (key_def == NULL)
>> +		goto tnt_error;
>> +
>> +	/* Build initial parts of SQL statement.  */
>> +	if (idx_type == SQLITE_IDXTYPE_APPDEF) {
>> +		sql_size += sql_append(parse, "CREATE INDEX ");
>> +		sql_size += sql_append(parse, name);
>> +		sql_size += sql_append(parse, " ON ");
>> +		sql_size += sql_append(parse, space_def->name);
>> +		sql_size += sql_append(parse, " (“);
> Why do you need to construct “CREATE INDEX” statement from scratch?
> This function is only called from sql_create_index() - there you already have
> this string:
>
> /*
>   * 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--;
Ok, now use this string.
>
>>   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, u8 idx_type)
>> +{
>> +	/* Table to be indexed.  */
> Extra space after dot.
Removed.
>
>> +	struct Table *table = NULL;
>> +	/* The index to be created.  */
>> +	struct Index *index = NULL;
>> +	/* Name of the index.  */
>> +	char *name = NULL;
>> +	int name_len;
> You don’t need to declare variables so beforehand.
> Its first usage occurs at 130+ lines below.
Variables name and index should be declared at the
very beginig, because they are used in exit_create_index
section and 'goto's to this section starts from the very
beginig of create_index_fucntion.
>
>> +	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) {
>> 		Vdbe *v = sqlite3GetVdbe(parse);
>> @@ -2681,39 +2711,43 @@ 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.
>> 	 */
>> 	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,
>> +		DbFixer db_fixer;
>> +		sqlite3FixInit(&db_fixer, parse, "index", token);
>> +		if (sqlite3FixSrcList(&db_fixer, tbl_name)) {
> This ‘Fix’ routine seems to be useless now, lets remove it.
Ok, removed.
>
>> +			/*
>> +			 * Because the parser constructs tbl_name
>> +			 * from a single identifier,
>> 			 * sqlite3FixSrcList can never fail.
>> 			 */
>> -			assert(0);
>> +			unreachable();
>> 		}
>> -		pTab = sqlite3LocateTable(parse, 0, tbl_name->a[0].zName);
>> -		assert(db->mallocFailed == 0 || pTab == 0);
>> -		if (pTab == 0)
>> +		table = sqlite3LocateTable(parse, 0, tbl_name->a[0].zName);
>> +		assert(db->mallocFailed == 0 || table == NULL);
>> +		if (table == NULL)
>> 			goto exit_create_index;
>> -		sqlite3PrimaryKeyIndex(pTab);
>> +		sqlite3PrimaryKeyIndex(table);
> Why do you call this function? It returns PK, but you don’t use it.
Some strange sqlite legacy. Removed.
>
>> 	} else {
>> 		assert(token == NULL);
>> 		assert(start == NULL);
>> -		pTab = parse->pNewTable;
>> -		if (!pTab)
>> +		table = parse->pNewTable;
>> +		if (table == NULL)
>> 			goto exit_create_index;
>> 	}
> Instead of checking table on NULL in each branch and after that using assert(table != NULL),
> it is better to replace that assert() with check:
>
> -               if (table == NULL)
> -                       goto exit_create_index;
> -               sqlite3PrimaryKeyIndex(table);
>          } else {
>                  assert(token == NULL);
>                  assert(start == NULL);
>                  table = parse->pNewTable;
> -               if (table == NULL)
> -                       goto exit_create_index;
>          }
>   
> -       assert(table != NULL);
> +       if (table == NULL)
> +               goto exit_create_index;
>
Sure, it's better. Fixed.
>> -	assert(pTab != 0);
>> +	assert(table != NULL);
>> 	assert(parse->nErr == 0);
>> -	if (pTab->def->opts.is_view) {
>> +	if (table->def->opts.is_view) {
>> 		sqlite3ErrorMsg(parse, "views may not be indexed");
>> 		goto exit_create_index;
>> 	}
> Lets also prohibit creation of indexes on system spaces.
Ok. Added.
>
>> @@ -2731,42 +2765,38 @@ sql_create_index(struct Parse *parse, struct Token *token,
>> 	 * primary key or UNIQUE constraint.  We have to invent
>> 	 * our own name.
>> 	 */
>> -	if (token) {
>> -		zName = sqlite3NameFromToken(db, token);
>> -		if (zName == 0)
>> +	if (token != NULL) {
>> +		name = sqlite3NameFromToken(db, token);
>> +		if (name == NULL)
>> 			goto exit_create_index;
>> -		assert(token->z != 0);
>> +		assert(token->z != NULL);
>> 		if (!db->init.busy) {
>> -			if (sqlite3HashFind(&db->pSchema->tblHash, zName) !=
>> +			if (sqlite3HashFind(&db->pSchema->tblHash, name) !=
>> 			    NULL) {
>> -				sqlite3ErrorMsg(parse,
>> -						"there is already a table named %s",
>> -						zName);
>> +				sqlite3ErrorMsg(parse, "there is already a "\
>> +						"table named %s", name);
>> 				goto exit_create_index;
>> 			}
>> 		}
>> -		if (sqlite3HashFind(&pTab->idxHash, zName) != 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;
>> +		int n = 1;
>> +		for (struct Index *pLoop = table->pIndex; pLoop != NULL;
> Lets use Tarantool naming convention.
Ok, renamed where possible.
>
>> 		     pLoop = pLoop->pNext, n++) {
>> 		}
>> -		zName =
>> -		    sqlite3MPrintf(db, "sqlite_autoindex_%s_%d", pTab->def->name,
>> -				   n);
>> -		if (zName == 0) {
>> +		name = sqlite3MPrintf(db, "sqlite_autoindex_%s_%d”,
> Lets remove ’sqlite_’ prefix and use just ’sql_’.
Ok.
>
>> +				      table->def->name, n);
>> +		if (name == NULL)
>> 			goto exit_create_index;
>> -		}
>> 	}
>>   	/*
>> @@ -2776,9 +2806,9 @@ 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 prevCol;
> Lets use Tarantool naming convention.
Renamed.
>
>> +		uint32_t last_field = table->def->field_count - 1;
>> +		sqlite3TokenInit(&prevCol, table->def->fields[last_field].name);
>> 		col_list = sql_expr_list_append(parse->db, NULL,
>> 						sqlite3ExprAlloc(db, TK_ID,
>> 								 &prevCol, 0));
>> @@ -2790,108 +2820,93 @@ 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));
>> -		}
>> -	}
>> +	/* Allocate the index structure.  */
> Extra space after dot.
Removed.
>
>> +	name_len = sqlite3Strlen30(name);
> Lets use traditional strlen() instead of SQLite analogs.
Ok.
>
>> -	/*
>> -	 * Allocate the index structure.
>> -	 */
>> -	nName = sqlite3Strlen30(zName);
>> -	pIndex = sqlite3AllocateIndexObject(db, col_list->nExpr,
>> -					    nName + nExtra + 1, &zExtra);
>> -	if (db->mallocFailed) {
>> +	if (name_len > BOX_NAME_MAX) {
>> +		sqlite3ErrorMsg(parse, "%s.%s exceeds indexes' names length "\
>> +				"limit", table->def->name, name);
> But sqlite3CheckIndentifier() also provides this check.
Removed duplicate check.
>
>> +	uint32_t max_iid = 0;
>> +	for (struct Index *index = table->pIndex; index != NULL;
>> +	     index = index->pNext) {
>> +		max_iid = max_iid > index->def->iid ?
>> +			  max_iid : index->def->iid + 1;
>> +	}
> Look, you don’t need to find max_iid: if it is parsing stage, then you can
> set it to any meaningful value (since in the end of function it is going to be destroyed);
> if it is executing step, you can use db->init.newTnum.
Ok, fixed.
>
>> +
>> +	bool is_unique = on_error != ON_CONFLICT_ACTION_NONE;
> It seems to be so messy defining uniqueness by ON_CONFLICT_ACTION.
> Lets refactor it somehow.
Not sure about this. It seems that information about uniqueness is
only in on_error parameter.
>> @@ -460,17 +462,19 @@ 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;
>> 					}
>> +
> Extra empty line.
Removed.
>
>> @@ -622,7 +625,7 @@ fkScanChildren(Parse * pParse,	/* Parse context */
>> 	Vdbe *v = sqlite3GetVdbe(pParse);
>>   	assert(pIdx == 0 || pIdx->pTable == pTab);
>> -	assert(pIdx == 0 || (int)index_column_count(pIdx) == pFKey->nCol);
>> +	assert(pIdx == 0 || (int) pIdx->def->key_def->part_count == pFKey->nCol);
> Lets use == NULL comparison on pointers.
Fixed.
>
>> @@ -1108,19 +1110,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++)
> Is this change related to patch?
No, just a side refactoring.
>
>> @@ -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);
>> +		const uint32_t pk_part_count = pPk->def->key_def->part_count;
> Why do you use here const? In other places AFAIK we/you don’t use
> const modifier (when it comes to simple numeric variables).
Removed.
>
>
>> @@ -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);
>> 	}
>> }
> Is this refactoring related to patch? I mean, refactoring is always appreciated,
> but don’t mess it with main goal of patch.
It is not related to the patch.
>
>> diff --git a/src/box/sql/where.c b/src/box/sql/where.c
>> index 85143ed20..7ca02095f 100644
>> --- a/src/box/sql/where.c
>> +++ b/src/box/sql/where.c
>> @@ -372,13 +372,19 @@ whereScanInit(WhereScan * pScan,	/* The WhereScan object being initialized */
>> 	pScan->is_column_seen = false;
>> 	if (pIdx) {
>> 		int j = iColumn;
>> -		iColumn = pIdx->aiColumn[j];
>> +		iColumn = pIdx->def->key_def->parts[j].fieldno;
>> +		/*
>> +		 * pIdx->tnum == 0 means that pIdx is a fake
>> +		 * integer primary key index.
>> +		 */
>> +		if (pIdx->tnum == 0)
>> +			iColumn = -1;
> We are going to remove tnum from struct Index and struct Table.
> So, if it is possible, use index->def->iid instead (or smth else).
Removed with 'fake_autoindex'
>
>> @@ -2882,7 +2868,6 @@ whereLoopAddBtree(WhereLoopBuilder * pBuilder,	/* WHERE clause information */
>> 	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 */
>> 	SrcList *pTabList;	/* The FROM clause */
>> 	struct SrcList_item *pSrc;	/* The FROM clause btree term to add */
>> 	WhereLoop *pNew;	/* Template WhereLoop object */
>> @@ -2913,11 +2898,32 @@ whereLoopAddBtree(WhereLoopBuilder * pBuilder,	/* WHERE clause information */
>> 		 */
>> 		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;
>> +
>> +		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);
>> +
>> +		sPk.def = index_def_new(pTab->def->id, 0, "primary”,
> Lets name if like ‘fake_autoindex’ to easily tell it from the rest.
Ok.
>
>> diff --git a/test/sql-tap/collation.test.lua b/test/sql-tap/collation1.test.lua
>> similarity index 100%
>> rename from test/sql-tap/collation.test.lua
>> rename to test/sql-tap/collation1.test.lua
>> diff --git a/test/sql-tap/collation2.test.lua b/test/sql-tap/collation2.test.lua
>> new file mode 100755
>> index 000000000..64296b0be
>> --- /dev/null
>> +++ b/test/sql-tap/collation2.test.lua
>> @@ -0,0 +1,20 @@
>> +#!/usr/bin/env tarantool
>> +test = require("sqltester")
>> +test:plan(3)
>> +
>> +test:do_catchsql_test(
>> +        "collation-2.1",
>> +        'CREATE TABLE test1 (a int, b int, c int, PRIMARY KEY (a, a, a, b, c))',
>> +        nil)
>> +
>> +test:do_catchsql_test(
>> +        "collation-2.2",
>> +        'CREATE TABLE test2 (a int, b int, c int, PRIMARY KEY (a, a, a, b, b, a, c))',
>> +        nil)
>> +
>> +test:do_catchsql_test(
>> +        "collation-2.3",
>> +        '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()
> I wouldn’t create separate test file for these simple tests.
> Lets put them to existing one.
Moved this testcases to existing tests.

---
Branch:
https://github.com/tarantool/tarantool/tree/sb/gh-3369-use-index-def-in-select-and-where

Issue:https://github.com/tarantool/tarantool/issues/3369

  src/box/errcode.h                                  |   1 +
  src/box/sql.c                                      |  54 +-
  src/box/sql/analyze.c                              |  85 +--
  src/box/sql/build.c                                | 816 ++++++++++-----------
  src/box/sql/delete.c                               |  10 +-
  src/box/sql/expr.c                                 |  61 +-
  src/box/sql/fkey.c                                 | 216 +++---
  src/box/sql/insert.c                               | 145 ++--
  src/box/sql/pragma.c                               |  30 +-
  src/box/sql/select.c                               |   2 +-
  src/box/sql/sqliteInt.h                            | 116 +--
  src/box/sql/update.c                               |  39 +-
  src/box/sql/vdbeaux.c                              |   2 +-
  src/box/sql/vdbemem.c                              |  21 +-
  src/box/sql/where.c                                | 192 ++---
  src/box/sql/wherecode.c                            | 102 +--
  test/box/misc.result                               |   1 +
  test/sql-tap/analyze6.test.lua                     |   6 +-
  .../{collation.test.lua => collation1.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                       |  14 +-
  test/sql-tap/index7.test.lua                       |  21 +-
  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 +-
  38 files changed, 965 insertions(+), 1047 deletions(-)
  rename test/sql-tap/{collation.test.lua => collation1.test.lua} (97%)

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..cd7a642d2 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");
  	}
diff --git a/src/box/sql/build.c b/src/box/sql/build.c
index 0072f842e..d66777f73 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);
@@ -1029,7 +1032,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 +1040,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 +1084,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 +1267,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 +1283,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;
@@ -1390,20 +1320,6 @@ convertToWithoutRowidTable(Parse * pParse, Table * 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 +1401,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);
@@ -1522,7 +1438,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 +2368,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 +2408,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 +2429,14 @@ 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;
-	}
+	/* Size of struct Index and aiRowLogEst. */
+	int index_size = ROUND8(sizeof(struct Index));
+	struct Index *p = sqlite3DbMallocZero(db, index_size);
+	if (p != NULL)
+		p->aiRowLogEst = (LogEst *) ((char *)p + ROUND8(sizeof(*p)));
  	return p;
  }
  
@@ -2635,46 +2523,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 is_unique Is given 'index' unique or not.
+ * @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, one of the following:
+ *                 SQLITE_IDXTYPE_APPDEF     0
+ *                 SQLITE_IDXTYPE_UNIQUE     1
+ *                 SQLITE_IDXTYPE_PRIMARYKEY 2.
+ * @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, bool is_unique,
+	       struct ExprList *expr_list, u8 idx_type,
+	       char *sql_stmt)
+{
+	struct space_def *space_def = table->def;
+	struct index_opts opts;
+	index_opts_create(&opts);
+	opts.is_unique = is_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;
  
-	return tnt_index->def->opts.is_unique;
+		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;
+
+	struct key_def *pk_key_def;
+	if (idx_type == SQLITE_IDXTYPE_APPDEF)
+		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, u8 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) {
  		Vdbe *v = sqlite3GetVdbe(parse);
@@ -2685,39 +2659,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;
  	}
@@ -2735,42 +2700,49 @@ sql_create_index(struct Parse *parse, struct Token *token,
  	 * primary key or UNIQUE constraint.  We have to invent
  	 * our own name.
  	 */
-	if (token) {
-		zName = sqlite3NameFromToken(db, token);
-		if (zName == 0)
+	if (token != NULL) {
+		name = sqlite3NameFromToken(db, token);
+		if (name == NULL)
  			goto exit_create_index;
-		assert(token->z != 0);
+		assert(token->z != NULL);
  		if (!db->init.busy) {
-			if (sqlite3HashFind(&db->pSchema->tblHash, zName) !=
+			if (sqlite3HashFind(&db->pSchema->tblHash, name) !=
  			    NULL) {
-				sqlite3ErrorMsg(parse,
-						"there is already a table named %s",
-						zName);
+				sqlite3ErrorMsg(parse, "there is already a "\
+						"table named %s", name);
  				goto exit_create_index;
  			}
  		}
-		if (sqlite3HashFind(&pTab->idxHash, zName) != 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++) {
+		int n = 1;
+		for (struct Index *idx = table->pIndex; idx != NULL;
+		     idx = idx->pNext, n++) {
  		}
-		zName =
-		    sqlite3MPrintf(db, "sqlite_autoindex_%s_%d", pTab->def->name,
-				   n);
-		if (zName == 0) {
+		name = sqlite3MPrintf(db, "sql_autoindex_%s_%d",
+				      table->def->name, n);
+		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 == SQLITE_IDXTYPE_APPDEF) {
+		diag_set(ClientError, ER_MODIFY_INDEX, name,
+			 table->def->name, "creating indexes on system "
+					   "spaces are prohibited");
+		parse->nErr++;
+		parse->rc = SQL_TARANTOOL_ERROR;
+		goto exit_create_index;
  	}
  
  	/*
@@ -2780,12 +2752,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 +2766,194 @@ 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;
+
+	assert(EIGHT_BYTE_ALIGNMENT(index->aiRowLogEst));
+	index->pTable = table;
+	index->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,
+	    on_error != ON_CONFLICT_ACTION_NONE)
+		index->idxType = SQLITE_IDXTYPE_UNIQUE;
+	else
+		index->idxType = 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",
+				       on_error == ON_CONFLICT_ACTION_NONE ?
+				       "" : " 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);
+
+	bool is_unique = on_error != ON_CONFLICT_ACTION_NONE;
+	if (index_fill_def(parse, index, table, iid, name, strlen(name),
+			   is_unique, 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:
+	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) {
+		/*
+		 * 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
+		 * 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
+		 * 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.
+		 * 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)
+		for (struct Index *idx = table->pIndex; idx != NULL;
+		     idx = idx->pNext) {
+			uint32_t k;
+			assert(IsUniqueIndex(idx));
+			assert(idx->idxType != SQLITE_IDXTYPE_APPDEF);
+			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 == SQLITE_IDXTYPE_PRIMARYKEY)
-					pIdx->idxType = idx_type;
+					idx->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 +2971,72 @@ 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
+		 * statement into the sql_stmt 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 +3226,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 +3662,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 +3685,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..50c2e8634 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,11 +1936,11 @@ 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);
diff --git a/src/box/sql/pragma.c b/src/box/sql/pragma.c
index 31581b17f..90a728065 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 <
@@ -545,10 +545,8 @@ sqlite3Pragma(Parse * pParse, Token * pId,	/* First part of [schema.]id field */
  						    { "c", "u", "pk" };
  						sqlite3VdbeMultiLoad(v, 1,
  								     "isisi", i,
-								     pIdx->
-								     zName,
-								     index_is_unique
-								     (pIdx),
+								     pIdx->def->name,
+								     pIdx->def->opts.is_unique,
  								     azOrigin
  								     [pIdx->
  								      idxType],
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..ae31dfae5 100644
--- a/src/box/sql/sqliteInt.h
+++ b/src/box/sql/sqliteInt.h
@@ -2069,28 +2069,6 @@ struct UnpackedRecord {
   * 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 +2078,30 @@ 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 */
+	/** From ANALYZE: Est. rows selected by each column. */
+	LogEst *aiRowLogEst;
+	/** 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;
+	/** 1==UNIQUE, 2==PRIMARY KEY, 0==CREATE INDEX. */
+	unsigned idxType:2;
+	/** Index definition. */
+	struct index_def *def;
  };
  
  /**
@@ -3554,34 +3536,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 +3622,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
@@ -3700,8 +3661,9 @@ index_is_unique(Index *);
  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, u8 idx_type);
  
  /**
   * This routine will drop an existing named index.  This routine
@@ -4553,10 +4515,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..092869ee8 100644
--- a/src/box/sql/where.c
+++ b/src/box/sql/where.c
@@ -370,16 +370,20 @@ 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->name == "fake_autoindex" means that
+		 * pIdx is a fake integer primary key index.
+		 */
+		if (strcmp(pIdx->def->name, "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 +545,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 +614,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 +624,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 +817,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 +1165,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 +1227,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 +1253,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 +1428,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 +1578,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 +1599,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 +1731,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 +2294,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 +2315,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 +2331,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 +2374,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 +2409,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 +2430,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 +2505,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 +2576,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 +2678,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 +2707,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 +2732,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 +2775,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 +2785,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;
  			}
  		}
@@ -2882,7 +2866,6 @@ whereLoopAddBtree(WhereLoopBuilder * pBuilder,	/* WHERE clause information */
  	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 */
  	SrcList *pTabList;	/* The FROM clause */
  	struct SrcList_item *pSrc;	/* The FROM clause btree term to add */
  	WhereLoop *pNew;	/* Template WhereLoop object */
@@ -2903,8 +2886,10 @@ whereLoopAddBtree(WhereLoopBuilder * pBuilder,	/* WHERE clause information */
  	if (pSrc->pIBIndex) {
  		/* An INDEXED BY clause specifies a particular index to use */
  		pProbe = pSrc->pIBIndex;
+		sPk.def = NULL;
  	} else if (pTab->pIndex) {
  		pProbe = pTab->pIndex;
+		sPk.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
@@ -2913,11 +2898,32 @@ whereLoopAddBtree(WhereLoopBuilder * pBuilder,	/* WHERE clause information */
  		 */
  		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;
+
+		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);
+
+		sPk.def = index_def_new(pTab->def->id, 0, "fake_autoindex",
+					sizeof("fake_autoindex") - 1, TREE,
+					&index_opts_default, key_def, NULL);
+		key_def_delete(key_def);
+
+		if (sPk.def == NULL) {
+			pWInfo->pParse->nErr++;
+			pWInfo->pParse->rc = SQL_TARANTOOL_ERROR;
+			return SQL_TARANTOOL_ERROR;
+		}
+
  		aiRowEstPk[0] = sql_space_tuple_log_count(pTab);
  		aiRowEstPk[1] = 0;
  		pFirst = pSrc->pTab->pIndex;
@@ -3058,6 +3064,8 @@ whereLoopAddBtree(WhereLoopBuilder * pBuilder,	/* WHERE clause information */
  		if (pSrc->pIBIndex)
  			break;
  	}
+	if (sPk.def != NULL)
+		index_def_delete(sPk.def);
  	return rc;
  }
  
@@ -3392,8 +3400,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 +3462,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 +3515,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 +4785,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 +4918,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/collation1.test.lua
similarity index 97%
rename from test/sql-tap/collation.test.lua
rename to test/sql-tap/collation1.test.lua
index 8a98de987..4d33d0c9d 100755
--- a/test/sql-tap/collation.test.lua
+++ b/test/sql-tap/collation1.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..a3405a3e7 100755
--- a/test/sql-tap/index1.test.lua
+++ b/test/sql-tap/index1.test.lua
@@ -454,10 +454,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 +1015,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 +1027,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 +1081,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..4bd01b8b3 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(7)
  
  --!./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,21 @@ test:do_catchsql_test(
          1, "keyword \"WHERE\" is reserved"
      })
  
+test:do_catchsql_test(
+        "index7-6.6",
+        'CREATE TABLE test2 (a int, b int, c int, PRIMARY KEY (a, a, a, b, b, a, c))',
+        nil)
+
+test:do_catchsql_test(
+        "index7-6.7",
+        [[
+            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'}})
+
  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")
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("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")
@@ -209,8 +208,7 @@ box.sql.execute("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")
@@ -142,8 +141,7 @@ box.sql.execute("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")
  ---
-- 

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

* [tarantool-patches] Re: [PATCH v11] sql: add index_def to struct Index
  2018-07-04 15:55                     ` [tarantool-patches] Re: [PATCH v11] " Ivan Koptelov
@ 2018-07-04 19:28                       ` n.pettik
  2018-07-05 14:50                         ` Ivan Koptelov
  0 siblings, 1 reply; 23+ messages in thread
From: n.pettik @ 2018-07-04 19:28 UTC (permalink / raw)
  To: tarantool-patches; +Cc: Ivan Koptelov

Please, follow our SOP convention: if you send next patch version,
then resend it as a separate patch (git format-patch -1 --subject-prefix='PATCH v2’), 
and add changelog. If you simply want to send diff, then add it to each pace of
provided changes. In your case, it is better to send only diff AFTER changes
(not the whole patch), it makes review process MUCH easier.

>> Then:
>> 
>> CREATE TABLE t11 (s1 INT, a, constraint c1 UNIQUE(s1) on conflict replace, PRIMARY KEY(s1));
>> 
>> In this case creation of unique constraint c1 is omitted, but no errors or warnings are shown.
>> It is not a problem now, but when ALTER TABLE DROP CONSTRAINT is implemented,
>> it will be possible to drop c1 constraint. Eventually, user would be disappointed if tried to drop
>> this constraint but got an error.
> It seems to be out of the scope of the patch. Appropriate ticket:
> https://github.com/tarantool/tarantool/issues/3498

I don’t understand how that ticket is related to given issue. Again, problem lies in
silent absorption of unique constraint by primary index.

>> 
>>> +struct Index *
>>> +sql_index_alloc(struct sqlite3 *db, uint32_t part_count)
>>> {
>>> -	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;
>>> -	}
>>> +	/* Size of struct Index and aiRowLogEst. */
>>> +	int nByte = ROUND8(sizeof(struct Index)) +
>>> +		    ROUND8(sizeof(LogEst) * (part_count + 1));
>> Do we really need this alignment?
> No. Removed.

No, you don’t. Apply this diff:

diff --git a/src/box/sql/build.c b/src/box/sql/build.c
index d66777f73..d2fed97eb 100644
--- a/src/box/sql/build.c
+++ b/src/box/sql/build.c
@@ -2430,13 +2430,14 @@ sqlite3RefillIndex(Parse * pParse, Index * pIndex, int memRootPage)
 }
 
 struct Index *
-sql_index_alloc(struct sqlite3 *db)
+sql_index_alloc(struct sqlite3 *db, uint32_t part_count)
 {
        /* Size of struct Index and aiRowLogEst. */
-       int index_size = ROUND8(sizeof(struct Index));
+       int index_size = sizeof(struct Index) +
+                        sizeof(LogEst) * (part_count + 1);
        struct Index *p = sqlite3DbMallocZero(db, index_size);
        if (p != NULL)
-               p->aiRowLogEst = (LogEst *) ((char *)p + ROUND8(sizeof(*p)));
+               p->aiRowLogEst = (LogEst *) ((char *)p + sizeof(*p));
        return p;
 }

@@ -2769,11 +2757,10 @@ sql_create_index(struct Parse *parse, struct Token *token,
        if (sqlite3CheckIdentifierName(parse, name) != SQLITE_OK)
                goto exit_create_index;
 
-       index = sql_index_alloc(db);
+       index = sql_index_alloc(db, col_list->nExpr);

@@ -2769,11 +2757,10 @@ sql_create_index(struct Parse *parse, struct Token *token,
        if (sqlite3CheckIdentifierName(parse, name) != SQLITE_OK)
                goto exit_create_index;
 
-       index = sql_index_alloc(db);
+       index = sql_index_alloc(db, col_list->nExpr);
        if (index == NULL)
                goto exit_create_index;
 
-       assert(EIGHT_BYTE_ALIGNMENT(index->aiRowLogEst));

diff --git a/src/box/sql/sqliteInt.h b/src/box/sql/sqliteInt.h
index ae31dfae5..2082d6ca9 100644
--- a/src/box/sql/sqliteInt.h
+++ b/src/box/sql/sqliteInt.h
@@ -3631,7 +3631,7 @@ void sqlite3SrcListDelete(sqlite3 *, SrcList *);
  * @retval not NULL Index object.
  */
 struct Index *
-sql_index_alloc(struct sqlite3 *db);
+sql_index_alloc(struct sqlite3 *db, uint32_t part_count);

> Ok, removed detailed description, left a short one.

@@ -2538,19 +2539,15 @@ addIndexToTable(Index * pIndex, Table * pTab)
  * @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, one of the following:
- *                 SQLITE_IDXTYPE_APPDEF     0
- *                 SQLITE_IDXTYPE_UNIQUE     1
- *                 SQLITE_IDXTYPE_PRIMARYKEY 2.
+ * @param idx_type Index type: UNIQUE constraint, PK constraint,
+ *                 or created by <CREATE INDEX ...> stmt.

>> 
>>> @@ -2731,42 +2765,38 @@ sql_create_index(struct Parse *parse, struct Token *token,
>>> 	 * primary key or UNIQUE constraint.  We have to invent
>>> 	 * our own name.
>>> 	 */
>>> -	if (token) {
>>> -		zName = sqlite3NameFromToken(db, token);
>>> -		if (zName == 0)
>>> +	if (token != NULL) {
>>> +		name = sqlite3NameFromToken(db, token);
>>> +		if (name == NULL)
>>> 			goto exit_create_index;
>>> -		assert(token->z != 0);
>>> +		assert(token->z != NULL);
>>> 		if (!db->init.busy) {
>>> -			if (sqlite3HashFind(&db->pSchema->tblHash, zName) !=
>>> +			if (sqlite3HashFind(&db->pSchema->tblHash, name) !=
>>> 			    NULL) {
>>> -				sqlite3ErrorMsg(parse,
>>> -						"there is already a table named %s",
>>> -						zName);
>>> +				sqlite3ErrorMsg(parse, "there is already a "\
>>> +						"table named %s", name);
>>> 				goto exit_create_index;
>>> 			}
>>> 		}

You forgot about my comment:

>There is no need to prohibit creating index with table name, since
>index name is local for given table. And most of other DBs also
>allow to create index with table name.

Apply diff:

@@ -2705,14 +2702,6 @@ sql_create_index(struct Parse *parse, struct Token *token,
                if (name == NULL)
                        goto exit_create_index;
                assert(token->z != NULL);
-               if (!db->init.busy) {
-                       if (sqlite3HashFind(&db->pSchema->tblHash, name) !=
-                           NULL) {
-                               sqlite3ErrorMsg(parse, "there is already a "\
-                                               "table named %s", name);
-                               goto exit_create_index;
-                       }
-               }

>>> +
>>> +	bool is_unique = on_error != ON_CONFLICT_ACTION_NONE;
>> It seems to be so messy defining uniqueness by ON_CONFLICT_ACTION.
>> Lets refactor it somehow.
> Not sure about this. It seems that information about uniqueness is
> only in on_error parameter.

What? Lets then change signature and from parser pass additional parameter
which would tell if index is unique or not.

>>> diff --git a/src/box/sql/where.c b/src/box/sql/where.c
>>> index 85143ed20..7ca02095f 100644
>>> --- a/src/box/sql/where.c
>>> +++ b/src/box/sql/where.c
>>> @@ -372,13 +372,19 @@ whereScanInit(WhereScan * pScan,	/* The WhereScan object being initialized */
>>> 	pScan->is_column_seen = false;
>>> 	if (pIdx) {
>>> 		int j = iColumn;
>>> -		iColumn = pIdx->aiColumn[j];
>>> +		iColumn = pIdx->def->key_def->parts[j].fieldno;
>>> +		/*
>>> +		 * pIdx->tnum == 0 means that pIdx is a fake
>>> +		 * integer primary key index.
>>> +		 */
>>> +		if (pIdx->tnum == 0)
>>> +			iColumn = -1;
>> We are going to remove tnum from struct Index and struct Table.
>> So, if it is possible, use index->def->iid instead (or smth else).
> Removed with ‘fake_autoindex'

Well, if smb called index ‘fake_autoindex’, I guess strange things would happen.
Could we use for instance def->space_id == 0 as a sign of ‘fake_index'?.

> src/box/errcode.h                                  |   1 +
> src/box/sql.c                                      |  54 +-
> src/box/sql/analyze.c                              |  85 +--
> src/box/sql/build.c                                | 816 ++++++++++-----------
> src/box/sql/delete.c                               |  10 +-
> src/box/sql/expr.c                                 |  61 +-
> src/box/sql/fkey.c                                 | 216 +++---
> src/box/sql/insert.c                               | 145 ++--
> src/box/sql/pragma.c                               |  30 +-
> src/box/sql/select.c                               |   2 +-
> src/box/sql/sqliteInt.h                            | 116 +--
> src/box/sql/update.c                               |  39 +-
> src/box/sql/vdbeaux.c                              |   2 +-
> src/box/sql/vdbemem.c                              |  21 +-
> src/box/sql/where.c                                | 192 ++---
> src/box/sql/wherecode.c                            | 102 +--
> test/box/misc.result                               |   1 +
> test/sql-tap/analyze6.test.lua                     |   6 +-
> .../{collation.test.lua => collation1.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                       |  14 +-
> test/sql-tap/index7.test.lua                       |  21 +-
> 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 +-
> 38 files changed, 965 insertions(+), 1047 deletions(-)
> rename test/sql-tap/{collation.test.lua => collation1.test.lua} (97%)

Why have you renamed this test?

> +	if (is_system_space && idx_type == SQLITE_IDXTYPE_APPDEF) {
> +		diag_set(ClientError, ER_MODIFY_INDEX, name,
> +			 table->def->name, "creating indexes on system "
> +					   "spaces are prohibited”);

+               diag_set(ClientError, ER_MODIFY_INDEX, name, table->def->name,
+                        "can't create index on system space");

> +	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",
> +				       on_error == ON_CONFLICT_ACTION_NONE ?
> +				       "" : " UNIQUE", n, token->z);

Wrong alignment:

@@ -2809,8 +2796,8 @@ sql_create_index(struct Parse *parse, struct Token *token,
                if (token->z[n - 1] == ';')
                        n--;
                sql_stmt = sqlite3MPrintf(db, "CREATE%s INDEX %.*s",
-                                      on_error == ON_CONFLICT_ACTION_NONE ?
-                                      "" : " UNIQUE", n, token->z);
+                                         on_error == ON_CONFLICT_ACTION_NONE ?
+                                         "" : " UNIQUE", n, token->z);


Moreover, sql_stmt is obtained from sqlite3Malloc() and assigned to index->opts,
which in turn is released by common free().

> diff --git a/test/sql-tap/index7.test.lua b/test/sql-tap/index7.test.lua
> index 336f42796..4bd01b8b3 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(7)
>  --!./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,21 @@ test:do_catchsql_test(
>         1, "keyword \"WHERE\" is reserved"
>     })
> +test:do_catchsql_test(
> +        "index7-6.6",
> +        'CREATE TABLE test2 (a int, b int, c int, PRIMARY KEY (a, a, a, b, b, a, c))',
> +        nil)

Why nil? What does this test check at all?

> +
> +test:do_catchsql_test(
> +        "index7-6.7",
> +        [[
> +            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’;

Use indentation for nested select clause to make it more readable.
Moreover, add comment to the test which would explain what exactly this test checks.

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

* [tarantool-patches] Re: [PATCH v11] sql: add index_def to struct Index
  2018-07-04 19:28                       ` n.pettik
@ 2018-07-05 14:50                         ` Ivan Koptelov
  2018-07-06  0:51                           ` n.pettik
  0 siblings, 1 reply; 23+ messages in thread
From: Ivan Koptelov @ 2018-07-05 14:50 UTC (permalink / raw)
  To: n.pettik, tarantool-patches


> Please, follow our SOP convention: if you send next patch version,
> then resend it as a separate patch (git format-patch -1 --subject-prefix='PATCH v2’),
> and add changelog. If you simply want to send diff, then add it to each pace of
> provided changes. In your case, it is better to send only diff AFTER changes
> (not the whole patch), it makes review process MUCH easier.
Sorry. Would send only diff this time.
>>> Then:
>>>
>>> CREATE TABLE t11 (s1 INT, a, constraint c1 UNIQUE(s1) on conflict replace, PRIMARY KEY(s1));
>>>
>>> In this case creation of unique constraint c1 is omitted, but no errors or warnings are shown.
>>> It is not a problem now, but when ALTER TABLE DROP CONSTRAINT is implemented,
>>> it will be possible to drop c1 constraint. Eventually, user would be disappointed if tried to drop
>>> this constraint but got an error.
>> It seems to be out of the scope of the patch. Appropriate ticket:
>> https://github.com/tarantool/tarantool/issues/3498
> I don’t understand how that ticket is related to given issue. Again, problem lies in
> silent absorption of unique constraint by primary index.
I am not sure what to do with this. In Postgresql these two commands 
work silently, no UNIQUE
constraint is created and ALTER TABLE works silently and do nothing. In 
MySQL both constraints
will be created with no warnings or errors. What do you propose to do?

>
>>>> +struct Index *
>>>> +sql_index_alloc(struct sqlite3 *db, uint32_t part_count)
>>>> {
>>>> -	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;
>>>> -	}
>>>> +	/* Size of struct Index and aiRowLogEst. */
>>>> +	int nByte = ROUND8(sizeof(struct Index)) +
>>>> +		    ROUND8(sizeof(LogEst) * (part_count + 1));
>>> Do we really need this alignment?
>> No. Removed.
> No, you don’t. Apply this diff:
>
> diff --git a/src/box/sql/build.c b/src/box/sql/build.c
> index d66777f73..d2fed97eb 100644
> --- a/src/box/sql/build.c
> +++ b/src/box/sql/build.c
> @@ -2430,13 +2430,14 @@ sqlite3RefillIndex(Parse * pParse, Index * pIndex, int memRootPage)
>   }
>   
>   struct Index *
> -sql_index_alloc(struct sqlite3 *db)
> +sql_index_alloc(struct sqlite3 *db, uint32_t part_count)
>   {
>          /* Size of struct Index and aiRowLogEst. */
> -       int index_size = ROUND8(sizeof(struct Index));
> +       int index_size = sizeof(struct Index) +
> +                        sizeof(LogEst) * (part_count + 1);
>          struct Index *p = sqlite3DbMallocZero(db, index_size);
>          if (p != NULL)
> -               p->aiRowLogEst = (LogEst *) ((char *)p + ROUND8(sizeof(*p)));
> +               p->aiRowLogEst = (LogEst *) ((char *)p + sizeof(*p));
>          return p;
>   }
>
> @@ -2769,11 +2757,10 @@ sql_create_index(struct Parse *parse, struct Token *token,
>          if (sqlite3CheckIdentifierName(parse, name) != SQLITE_OK)
>                  goto exit_create_index;
>   
> -       index = sql_index_alloc(db);
> +       index = sql_index_alloc(db, col_list->nExpr);
>
> @@ -2769,11 +2757,10 @@ sql_create_index(struct Parse *parse, struct Token *token,
>          if (sqlite3CheckIdentifierName(parse, name) != SQLITE_OK)
>                  goto exit_create_index;
>   
> -       index = sql_index_alloc(db);
> +       index = sql_index_alloc(db, col_list->nExpr);
>          if (index == NULL)
>                  goto exit_create_index;
>   
> -       assert(EIGHT_BYTE_ALIGNMENT(index->aiRowLogEst));
>
> diff --git a/src/box/sql/sqliteInt.h b/src/box/sql/sqliteInt.h
> index ae31dfae5..2082d6ca9 100644
> --- a/src/box/sql/sqliteInt.h
> +++ b/src/box/sql/sqliteInt.h
> @@ -3631,7 +3631,7 @@ void sqlite3SrcListDelete(sqlite3 *, SrcList *);
>    * @retval not NULL Index object.
>    */
>   struct Index *
> -sql_index_alloc(struct sqlite3 *db);
> +sql_index_alloc(struct sqlite3 *db, uint32_t part_count);
Why do we need to allocate 'part_count' part of memory? It does not seem
to be used for anything.
>
>> Ok, removed detailed description, left a short one.
> @@ -2538,19 +2539,15 @@ addIndexToTable(Index * pIndex, Table * pTab)
>    * @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, one of the following:
> - *                 SQLITE_IDXTYPE_APPDEF     0
> - *                 SQLITE_IDXTYPE_UNIQUE     1
> - *                 SQLITE_IDXTYPE_PRIMARYKEY 2.
> + * @param idx_type Index type: UNIQUE constraint, PK constraint,
> + *                 or created by <CREATE INDEX ...> stmt.
Ok.
>>>> @@ -2731,42 +2765,38 @@ sql_create_index(struct Parse *parse, struct Token *token,
>>>> 	 * primary key or UNIQUE constraint.  We have to invent
>>>> 	 * our own name.
>>>> 	 */
>>>> -	if (token) {
>>>> -		zName = sqlite3NameFromToken(db, token);
>>>> -		if (zName == 0)
>>>> +	if (token != NULL) {
>>>> +		name = sqlite3NameFromToken(db, token);
>>>> +		if (name == NULL)
>>>> 			goto exit_create_index;
>>>> -		assert(token->z != 0);
>>>> +		assert(token->z != NULL);
>>>> 		if (!db->init.busy) {
>>>> -			if (sqlite3HashFind(&db->pSchema->tblHash, zName) !=
>>>> +			if (sqlite3HashFind(&db->pSchema->tblHash, name) !=
>>>> 			    NULL) {
>>>> -				sqlite3ErrorMsg(parse,
>>>> -						"there is already a table named %s",
>>>> -						zName);
>>>> +				sqlite3ErrorMsg(parse, "there is already a "\
>>>> +						"table named %s", name);
>>>> 				goto exit_create_index;
>>>> 			}
>>>> 		}
> You forgot about my comment:
>
>> There is no need to prohibit creating index with table name, since
>> index name is local for given table. And most of other DBs also
>> allow to create index with table name.
> Apply diff:
>
> @@ -2705,14 +2702,6 @@ sql_create_index(struct Parse *parse, struct Token *token,
>                  if (name == NULL)
>                          goto exit_create_index;
>                  assert(token->z != NULL);
> -               if (!db->init.busy) {
> -                       if (sqlite3HashFind(&db->pSchema->tblHash, name) !=
> -                           NULL) {
> -                               sqlite3ErrorMsg(parse, "there is already a "\
> -                                               "table named %s", name);
> -                               goto exit_create_index;
> -                       }
> -               }
Ok.
>>>> +
>>>> +	bool is_unique = on_error != ON_CONFLICT_ACTION_NONE;
>>> It seems to be so messy defining uniqueness by ON_CONFLICT_ACTION.
>>> Lets refactor it somehow.
>> Not sure about this. It seems that information about uniqueness is
>> only in on_error parameter.
> What? Lets then change signature and from parser pass additional parameter
> which would tell if index is unique or not.
Ok, done. Also perform small refactoring in parser.
>
>>>> diff --git a/src/box/sql/where.c b/src/box/sql/where.c
>>>> index 85143ed20..7ca02095f 100644
>>>> --- a/src/box/sql/where.c
>>>> +++ b/src/box/sql/where.c
>>>> @@ -372,13 +372,19 @@ whereScanInit(WhereScan * pScan,	/* The WhereScan object being initialized */
>>>> 	pScan->is_column_seen = false;
>>>> 	if (pIdx) {
>>>> 		int j = iColumn;
>>>> -		iColumn = pIdx->aiColumn[j];
>>>> +		iColumn = pIdx->def->key_def->parts[j].fieldno;
>>>> +		/*
>>>> +		 * pIdx->tnum == 0 means that pIdx is a fake
>>>> +		 * integer primary key index.
>>>> +		 */
>>>> +		if (pIdx->tnum == 0)
>>>> +			iColumn = -1;
>>> We are going to remove tnum from struct Index and struct Table.
>>> So, if it is possible, use index->def->iid instead (or smth else).
>> Removed with ‘fake_autoindex'
> Well, if smb called index ‘fake_autoindex’, I guess strange things would happen.
> Could we use for instance def->space_id == 0 as a sign of ‘fake_index'?.
Yes, it seems to be a better solution. What do you think about adding a 
field to
index_def.opts - index_def.opts.is_fake_pk?
>
>> src/box/errcode.h                                  |   1 +
>> src/box/sql.c                                      |  54 +-
>> src/box/sql/analyze.c                              |  85 +--
>> src/box/sql/build.c                                | 816 ++++++++++-----------
>> src/box/sql/delete.c                               |  10 +-
>> src/box/sql/expr.c                                 |  61 +-
>> src/box/sql/fkey.c                                 | 216 +++---
>> src/box/sql/insert.c                               | 145 ++--
>> src/box/sql/pragma.c                               |  30 +-
>> src/box/sql/select.c                               |   2 +-
>> src/box/sql/sqliteInt.h                            | 116 +--
>> src/box/sql/update.c                               |  39 +-
>> src/box/sql/vdbeaux.c                              |   2 +-
>> src/box/sql/vdbemem.c                              |  21 +-
>> src/box/sql/where.c                                | 192 ++---
>> src/box/sql/wherecode.c                            | 102 +--
>> test/box/misc.result                               |   1 +
>> test/sql-tap/analyze6.test.lua                     |   6 +-
>> .../{collation.test.lua => collation1.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                       |  14 +-
>> test/sql-tap/index7.test.lua                       |  21 +-
>> 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 +-
>> 38 files changed, 965 insertions(+), 1047 deletions(-)
>> rename test/sql-tap/{collation.test.lua => collation1.test.lua} (97%)
> Why have you renamed this test?
Renamed back.
>
>> +	if (is_system_space && idx_type == SQLITE_IDXTYPE_APPDEF) {
>> +		diag_set(ClientError, ER_MODIFY_INDEX, name,
>> +			 table->def->name, "creating indexes on system "
>> +					   "spaces are prohibited”);
> +               diag_set(ClientError, ER_MODIFY_INDEX, name, table->def->name,
> +                        "can't create index on system space");
Ok.
>> +	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",
>> +				       on_error == ON_CONFLICT_ACTION_NONE ?
>> +				       "" : " UNIQUE", n, token->z);
> Wrong alignment:
>
> @@ -2809,8 +2796,8 @@ sql_create_index(struct Parse *parse, struct Token *token,
>                  if (token->z[n - 1] == ';')
>                          n--;
>                  sql_stmt = sqlite3MPrintf(db, "CREATE%s INDEX %.*s",
> -                                      on_error == ON_CONFLICT_ACTION_NONE ?
> -                                      "" : " UNIQUE", n, token->z);
> +                                         on_error == ON_CONFLICT_ACTION_NONE ?
> +                                         "" : " UNIQUE", n, token->z);
Sorry, fixed.
> Moreover, sql_stmt is obtained from sqlite3Malloc() and assigned to index->opts,
> which in turn is released by common free().
In index_def_new() (where we pass opts) given sql_stmt is copied using 
strdup, so
it's okay to free() it, isn't it?
>
>> diff --git a/test/sql-tap/index7.test.lua b/test/sql-tap/index7.test.lua
>> index 336f42796..4bd01b8b3 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(7)
>>   --!./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,21 @@ test:do_catchsql_test(
>>          1, "keyword \"WHERE\" is reserved"
>>      })
>> +test:do_catchsql_test(
>> +        "index7-6.6",
>> +        'CREATE TABLE test2 (a int, b int, c int, PRIMARY KEY (a, a, a, b, b, a, c))',
>> +        nil)
> Why nil? What does this test check at all?
The idea was to check that removing duplicates works fine. Fixed the 
test to check names of
resulting pk columns with expected.
>
>> +
>> +test:do_catchsql_test(
>> +        "index7-6.7",
>> +        [[
>> +            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’;
> Use indentation for nested select clause to make it more readable.
> Moreover, add comment to the test which would explain what exactly this test checks.
Ok, done.

Here is diff:
--

diff --git a/src/box/sql/build.c b/src/box/sql/build.c
index d66777f73..0cb0b8e08 100644
--- a/src/box/sql/build.c
+++ b/src/box/sql/build.c
@@ -984,8 +984,10 @@ sqlite3AddPrimaryKey(Parse * pParse,	/* Parsing context */
  		sqlite3ErrorMsg(pParse, "AUTOINCREMENT is only allowed on an "
  				"INTEGER PRIMARY KEY or INT PRIMARY KEY");
  	} else {
+		/* Since this index implements PK, it is unique. */
  		sql_create_index(pParse, 0, 0, pList, onError, 0,
-				 0, sortOrder, false, SQLITE_IDXTYPE_PRIMARYKEY);
+				 0, sortOrder, false, SQLITE_IDXTYPE_PRIMARYKEY,
+				 true);
  		pList = 0;
  	}
  
@@ -1311,9 +1313,10 @@ convertToWithoutRowidTable(Parse * pParse, Table * pTab)
  			return;
  		pList->a[0].sort_order = pParse->iPkSortOrder;
  		assert(pParse->pNewTable == pTab);
+		/* Since this index implements PK, it is unique. */
  		sql_create_index(pParse, 0, 0, pList, pTab->keyConf, 0, 0,
  				 SORT_ORDER_ASC, false,
-				 SQLITE_IDXTYPE_PRIMARYKEY);
+				 SQLITE_IDXTYPE_PRIMARYKEY, true);
  		if (db->mallocFailed)
  			return;
  		pPk = sqlite3PrimaryKeyIndex(pTab);
@@ -2538,10 +2541,8 @@ addIndexToTable(Index * pIndex, Table * pTab)
   * @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, one of the following:
- *                 SQLITE_IDXTYPE_APPDEF     0
- *                 SQLITE_IDXTYPE_UNIQUE     1
- *                 SQLITE_IDXTYPE_PRIMARYKEY 2.
+ * @param idx_type Index type: UNIQUE constraint, PK constraint,
+ *                 or created by <CREATE INDEX ...> stmt.
   * @param sql_stmt SQL statement, which creates the index.
   * @retval 0 Success.
   * @retval -1 Error.
@@ -2632,7 +2633,7 @@ sql_create_index(struct Parse *parse, struct Token *token,
  		 struct SrcList *tbl_name, struct ExprList *col_list,
  		 enum on_conflict_action on_error, struct Token *start,
  		 struct Expr *where, enum sort_order sort_order,
-		 bool if_not_exist, u8 idx_type)
+		 bool if_not_exist, u8 idx_type, bool is_unique)
  {
  	/* The index to be created. */
  	struct Index *index = NULL;
@@ -2705,14 +2706,6 @@ sql_create_index(struct Parse *parse, struct Token *token,
  		if (name == NULL)
  			goto exit_create_index;
  		assert(token->z != NULL);
-		if (!db->init.busy) {
-			if (sqlite3HashFind(&db->pSchema->tblHash, name) !=
-			    NULL) {
-				sqlite3ErrorMsg(parse, "there is already a "\
-						"table named %s", name);
-				goto exit_create_index;
-			}
-		}
  		if (sqlite3HashFind(&table->idxHash, name) != NULL) {
  			if (!if_not_exist) {
  				sqlite3ErrorMsg(parse,
@@ -2737,9 +2730,8 @@ sql_create_index(struct Parse *parse, struct Token *token,
  	bool is_system_space = BOX_SYSTEM_ID_MIN < table->def->id &&
  			       table->def->id < BOX_SYSTEM_ID_MAX;
  	if (is_system_space && idx_type == SQLITE_IDXTYPE_APPDEF) {
-		diag_set(ClientError, ER_MODIFY_INDEX, name,
-			 table->def->name, "creating indexes on system "
-					   "spaces are prohibited");
+		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;
@@ -2809,8 +2801,8 @@ sql_create_index(struct Parse *parse, struct Token *token,
  		if (token->z[n - 1] == ';')
  			n--;
  		sql_stmt = sqlite3MPrintf(db, "CREATE%s INDEX %.*s",
-				       on_error == ON_CONFLICT_ACTION_NONE ?
-				       "" : " UNIQUE", n, token->z);
+					  on_error == ON_CONFLICT_ACTION_NONE ?
+					  "" : " UNIQUE", n, token->z);
  		if (db->mallocFailed || sql_stmt == NULL)
  			goto exit_create_index;
  	}
@@ -2820,9 +2812,8 @@ sql_create_index(struct Parse *parse, struct Token *token,
  	if (db->init.busy)
  		iid = SQLITE_PAGENO_TO_INDEXID(db->init.newTnum);
  
-	bool is_unique = on_error != ON_CONFLICT_ACTION_NONE;
  	if (index_fill_def(parse, index, table, iid, name, strlen(name),
-			   is_unique, col_list, idx_type, sql_stmt) != 0)
+			   is_unique != 0, col_list, idx_type, sql_stmt) != 0)
  		goto exit_create_index;
  	/*
  	 * Remove all redundant columns from the PRIMARY KEY.
diff --git a/src/box/sql/parse.y b/src/box/sql/parse.y
index b2940b7c4..ed3d82f37 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);}
+						   SQLITE_IDXTYPE_UNIQUE, true);}
  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,8 @@ 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);}
+						   SQLITE_IDXTYPE_UNIQUE,
+						   true);}
  tcons ::= CHECK LP expr(E) RP onconf.
                                   {sql_add_check_constraint(pParse,&E);}
  tcons ::= FOREIGN KEY LP eidlist(FA) RP
@@ -1194,13 +1195,16 @@ 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,
+                   SQLITE_IDXTYPE_APPDEF, U);
  }
  
  %type uniqueflag {int}
-uniqueflag(A) ::= UNIQUE.  {A = ON_CONFLICT_ACTION_ABORT;}
-uniqueflag(A) ::= .        {A = ON_CONFLICT_ACTION_NONE;}
+uniqueflag(A) ::= UNIQUE.  {A = true;}
+uniqueflag(A) ::= .        {A = false;}
  
  
  // The eidlist non-terminal (Expression Id List) generates an ExprList
diff --git a/src/box/sql/sqliteInt.h b/src/box/sql/sqliteInt.h
index ae31dfae5..e3d568153 100644
--- a/src/box/sql/sqliteInt.h
+++ b/src/box/sql/sqliteInt.h
@@ -3657,13 +3657,14 @@ sql_index_alloc(struct sqlite3 *db);
   * @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,
  		 enum on_conflict_action on_error, struct Token *start,
  		 struct Expr *pi_where, enum sort_order sort_order,
-		 bool if_not_exist, u8 idx_type);
+		 bool if_not_exist, u8 idx_type, bool is_unique);
  
  /**
   * This routine will drop an existing named index.  This routine
diff --git a/test/sql-tap/collation1.test.lua b/test/sql-tap/collation.test.lua
similarity index 100%
rename from test/sql-tap/collation1.test.lua
rename to test/sql-tap/collation.test.lua
diff --git a/test/sql-tap/index1.test.lua b/test/sql-tap/index1.test.lua
index a3405a3e7..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(
diff --git a/test/sql-tap/index7.test.lua b/test/sql-tap/index7.test.lua
index 4bd01b8b3..29eca386b 100755
--- a/test/sql-tap/index7.test.lua
+++ b/test/sql-tap/index7.test.lua
@@ -303,20 +303,42 @@ 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-6.6",
-        'CREATE TABLE test2 (a int, b int, c int, PRIMARY KEY (a, a, a, b, b, a, c))',
-        nil)
+        "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-6.7",
+        "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';
+            SELECT "_index"."name"
+            FROM "_index" JOIN "_space" WHERE
+                "_index"."id" = "_space"."id" AND
+                "_space"."name"='TEST4'       AND
+                "_index"."name"='INDEX1';
          ]],
          {0, {'INDEX1'}})
  
-- 

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

* [tarantool-patches] Re: [PATCH v11] sql: add index_def to struct Index
  2018-07-05 14:50                         ` Ivan Koptelov
@ 2018-07-06  0:51                           ` n.pettik
  2018-07-08 14:17                             ` [tarantool-patches] Re: [PATCH v2] " Ivan Koptelov
  0 siblings, 1 reply; 23+ messages in thread
From: n.pettik @ 2018-07-06  0:51 UTC (permalink / raw)
  To: tarantool-patches; +Cc: Ivan Koptelov


>>>> Then:
>>>> 
>>>> CREATE TABLE t11 (s1 INT, a, constraint c1 UNIQUE(s1) on conflict replace, PRIMARY KEY(s1));
>>>> 
>>>> In this case creation of unique constraint c1 is omitted, but no errors or warnings are shown.
>>>> It is not a problem now, but when ALTER TABLE DROP CONSTRAINT is implemented,
>>>> it will be possible to drop c1 constraint. Eventually, user would be disappointed if tried to drop
>>>> this constraint but got an error.
>>> It seems to be out of the scope of the patch. Appropriate ticket:
>>> https://github.com/tarantool/tarantool/issues/3498
>> I don’t understand how that ticket is related to given issue. Again, problem lies in
>> silent absorption of unique constraint by primary index.
> I am not sure what to do with this. In Postgresql these two commands work silently, no UNIQUE
> constraint is created

Really? I tried it on PostgreSQL 9.6.2 and got this:

CREATE TABLE t1(id INT PRIMARY KEY, CONSTRAINT c1 UNIQUE(id));

SELECT conname FROM pg_constraint WHERE conrelid = (SELECT oid  FROM pg_class WHERE relname LIKE 't1’);

    conname
1	c1

As we can see, this unique constraint has been successfully created.

> and ALTER TABLE works silently and do nothing.

And after drop:

ALTER TABLE t1 DROP CONSTRAINT c1;

SELECT conname FROM pg_constraint WHERE conrelid = (SELECT oid  FROM pg_class WHERE relname LIKE 't1’);

I got nothing.

> In MySQL both constraints
> will be created with no warnings or errors. What do you propose to do?

Lets dive into ANSI: if there is nothing about that (i.e. explicit contradictions), then
we shouldn’t omit creation of unique constraint over PK (at least in case it is named constraint).

>> diff --git a/src/box/sql/build.c b/src/box/sql/build.c
>> index d66777f73..d2fed97eb 100644
>> --- a/src/box/sql/build.c
>> +++ b/src/box/sql/build.c
>> @@ -2430,13 +2430,14 @@ sqlite3RefillIndex(Parse * pParse, Index * pIndex, int memRootPage)
>>  }
>>    struct Index *
>> -sql_index_alloc(struct sqlite3 *db)
>> +sql_index_alloc(struct sqlite3 *db, uint32_t part_count)
>>  {
>>         /* Size of struct Index and aiRowLogEst. */
>> -       int index_size = ROUND8(sizeof(struct Index));
>> +       int index_size = sizeof(struct Index) +
>> +                        sizeof(LogEst) * (part_count + 1);
>>         struct Index *p = sqlite3DbMallocZero(db, index_size);
>>         if (p != NULL)
>> -               p->aiRowLogEst = (LogEst *) ((char *)p + ROUND8(sizeof(*p)));
>> +               p->aiRowLogEst = (LogEst *) ((char *)p + sizeof(*p));
>>         return p;
>>  }
>> 
>> @@ -2769,11 +2757,10 @@ sql_create_index(struct Parse *parse, struct Token *token,
>>         if (sqlite3CheckIdentifierName(parse, name) != SQLITE_OK)
>>                 goto exit_create_index;
>>  -       index = sql_index_alloc(db);
>> +       index = sql_index_alloc(db, col_list->nExpr);
>> 
>> @@ -2769,11 +2757,10 @@ sql_create_index(struct Parse *parse, struct Token *token,
>>         if (sqlite3CheckIdentifierName(parse, name) != SQLITE_OK)
>>                 goto exit_create_index;
>>  -       index = sql_index_alloc(db);
>> +       index = sql_index_alloc(db, col_list->nExpr);
>>         if (index == NULL)
>>                 goto exit_create_index;
>>  -       assert(EIGHT_BYTE_ALIGNMENT(index->aiRowLogEst));
>> 
>> diff --git a/src/box/sql/sqliteInt.h b/src/box/sql/sqliteInt.h
>> index ae31dfae5..2082d6ca9 100644
>> --- a/src/box/sql/sqliteInt.h
>> +++ b/src/box/sql/sqliteInt.h
>> @@ -3631,7 +3631,7 @@ void sqlite3SrcListDelete(sqlite3 *, SrcList *);
>>   * @retval not NULL Index object.
>>   */
>>  struct Index *
>> -sql_index_alloc(struct sqlite3 *db);
>> +sql_index_alloc(struct sqlite3 *db, uint32_t part_count);
> Why do we need to allocate 'part_count' part of memory? It does not seem
> to be used for anything.

It is used for array aiRowLogEst. Btw, you really can remove and now use
index_def->opts.stat->tuple_log_est;  for fake indexes - then you are able to
avoid allocating this memory.

>>>>> diff --git a/src/box/sql/where.c b/src/box/sql/where.c
>>>>> index 85143ed20..7ca02095f 100644
>>>>> --- a/src/box/sql/where.c
>>>>> +++ b/src/box/sql/where.c
>>>>> @@ -372,13 +372,19 @@ whereScanInit(WhereScan * pScan,	/* The WhereScan object being initialized */
>>>>> 	pScan->is_column_seen = false;
>>>>> 	if (pIdx) {
>>>>> 		int j = iColumn;
>>>>> -		iColumn = pIdx->aiColumn[j];
>>>>> +		iColumn = pIdx->def->key_def->parts[j].fieldno;
>>>>> +		/*
>>>>> +		 * pIdx->tnum == 0 means that pIdx is a fake
>>>>> +		 * integer primary key index.
>>>>> +		 */
>>>>> +		if (pIdx->tnum == 0)
>>>>> +			iColumn = -1;
>>>> We are going to remove tnum from struct Index and struct Table.
>>>> So, if it is possible, use index->def->iid instead (or smth else).
>>> Removed with ‘fake_autoindex'
>> Well, if smb called index ‘fake_autoindex’, I guess strange things would happen.
>> Could we use for instance def->space_id == 0 as a sign of ‘fake_index'?.
> Yes, it seems to be a better solution. What do you think about adding a field to
> index_def.opts - index_def.opts.is_fake_pk?

I am strictly against - it sounds like over-engineering. In parser, for example,
space_def->opts.is_temporary is used as a sing of space_def allocated on a region
(but initially this property is set to be true if space is temporary and temporary != allocated on region).
So, since index is in fact surrogate, lets just use one of its fields.

>> 
>> Moreover, sql_stmt is obtained from sqlite3Malloc() and assigned to index->opts,
>> which in turn is released by common free().
> In index_def_new() (where we pass opts) given sql_stmt is copied using strdup, so
> it's okay to free() it, isn't it?

Ok, then you don’t release original sql_stmt: fix it.

> 
> diff --git a/src/box/sql/build.c b/src/box/sql/build.c
> index d66777f73..0cb0b8e08 100644
> --- a/src/box/sql/build.c
> +++ b/src/box/sql/build.c
> @@ -984,8 +984,10 @@ sqlite3AddPrimaryKey(Parse * pParse,	/* Parsing context */
> 		sqlite3ErrorMsg(pParse, "AUTOINCREMENT is only allowed on an "
> 				"INTEGER PRIMARY KEY or INT PRIMARY KEY");
> 	} else {
> +		/* Since this index implements PK, it is unique. */
> 		sql_create_index(pParse, 0, 0, pList, onError, 0,
> -				 0, sortOrder, false, SQLITE_IDXTYPE_PRIMARYKEY);
> +				 0, sortOrder, false, SQLITE_IDXTYPE_PRIMARYKEY,
> +				 true);
> 		pList = 0;
> 	}
> @@ -1311,9 +1313,10 @@ convertToWithoutRowidTable(Parse * pParse, Table * pTab)
> 			return;
> 		pList->a[0].sort_order = pParse->iPkSortOrder;
> 		assert(pParse->pNewTable == pTab);
> +		/* Since this index implements PK, it is unique. */
> 		sql_create_index(pParse, 0, 0, pList, pTab->keyConf, 0, 0,
> 				 SORT_ORDER_ASC, false,
> -				 SQLITE_IDXTYPE_PRIMARYKEY);
> +				 SQLITE_IDXTYPE_PRIMARYKEY, true);
> 		if (db->mallocFailed)
> 			return;
> 		pPk = sqlite3PrimaryKeyIndex(pTab);
> @@ -2538,10 +2541,8 @@ addIndexToTable(Index * pIndex, Table * pTab)
>  * @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, one of the following:
> - *                 SQLITE_IDXTYPE_APPDEF     0
> - *                 SQLITE_IDXTYPE_UNIQUE     1
> - *                 SQLITE_IDXTYPE_PRIMARYKEY 2.
> + * @param idx_type Index type: UNIQUE constraint, PK constraint,
> + *                 or created by <CREATE INDEX ...> stmt.
>  * @param sql_stmt SQL statement, which creates the index.
>  * @retval 0 Success.
>  * @retval -1 Error.
> @@ -2632,7 +2633,7 @@ sql_create_index(struct Parse *parse, struct Token *token,
> 		 struct SrcList *tbl_name, struct ExprList *col_list,
> 		 enum on_conflict_action on_error, struct Token *start,
> 		 struct Expr *where, enum sort_order sort_order,
> -		 bool if_not_exist, u8 idx_type)
> +		 bool if_not_exist, u8 idx_type, bool is_unique)

Wait, you already have idx_type, you don’t need another one argument.
Lets simply turn defines into appropriate enum
(probably you should pick better names, but follow code style):

-/*
- * 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 */
+/**
+ * There are some differences between explicitly created indexes
+ * and unique table constraints (despite the fact that they are
+ * the same in implementation). For instance, to drop index user
+ * must use <DROP INDEX ...> statement, but to drop constraint -
+ * <ALTER TABLE DROP CONSTRAINT ...> syntax.
+ */
+enum sql_index_type {
+       SQL_INDEX_USER_DEFINED_UINIQUE = 0,
+       SQL_INDEX_USER_DEFINED = 1,
+       SQL_INDEX_CONSTRAINT_UNIQUE = 2,
+       SQL_INDEX_CONSTRAINT_PK = 3,
+};

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

* [tarantool-patches] Re: [PATCH v2] sql: add index_def to struct Index
  2018-07-06  0:51                           ` n.pettik
@ 2018-07-08 14:17                             ` Ivan Koptelov
  0 siblings, 0 replies; 23+ messages in thread
From: Ivan Koptelov @ 2018-07-08 14:17 UTC (permalink / raw)
  To: n.pettik, tarantool-patches

>>>>> Then:
>>>>>
>>>>> CREATE TABLE t11 (s1 INT, a, constraint c1 UNIQUE(s1) on conflict replace, PRIMARY KEY(s1));
>>>>>
>>>>> In this case creation of unique constraint c1 is omitted, but no errors or warnings are shown.
>>>>> It is not a problem now, but when ALTER TABLE DROP CONSTRAINT is implemented,
>>>>> it will be possible to drop c1 constraint. Eventually, user would be disappointed if tried to drop
>>>>> this constraint but got an error.
>>>> It seems to be out of the scope of the patch. Appropriate ticket:
>>>> https://github.com/tarantool/tarantool/issues/3498
>>> I don’t understand how that ticket is related to given issue. Again, problem lies in
>>> silent absorption of unique constraint by primary index.
>> I am not sure what to do with this. In Postgresql these two commands work silently, no UNIQUE
>> constraint is created
> Really? I tried it on PostgreSQL 9.6.2 and got this:
>
> CREATE TABLE t1(id INT PRIMARY KEY, CONSTRAINT c1 UNIQUE(id));
>
> SELECT conname FROM pg_constraint WHERE conrelid = (SELECT oid  FROM pg_class WHERE relname LIKE 't1’);
>
>      conname
> 1	c1
>
> As we can see, this unique constraint has been successfully created.
>
>> and ALTER TABLE works silently and do nothing.
> And after drop:
>
> ALTER TABLE t1 DROP CONSTRAINT c1;
>
> SELECT conname FROM pg_constraint WHERE conrelid = (SELECT oid  FROM pg_class WHERE relname LIKE 't1’);
>
> I got nothing.
>
>> In MySQL both constraints
>> will be created with no warnings or errors. What do you propose to do?
> Lets dive into ANSI: if there is nothing about that (i.e. explicit contradictions), then
> we shouldn’t omit creation of unique constraint over PK (at least in case it is named constraint).
There is no explicit contradiction. I looked in 4.18.3.2 section (Unique 
constraints) in Foundation part of the standard (2011).
According to the general logic of the standard, PRIMARY KEY is a just a 
particular case of UNIQUE constraint (with non-null
addition).
After verbal discussion we decided the following: if UNIQUE constraint 
is named and it's columns are the same as the columns of
PK constraint then create index for this constraint. If the UNIQUE 
constraint is non-named, then create no index.
>>> diff --git a/src/box/sql/build.c b/src/box/sql/build.c
>>> index d66777f73..d2fed97eb 100644
>>> --- a/src/box/sql/build.c
>>> +++ b/src/box/sql/build.c
>>> @@ -2430,13 +2430,14 @@ sqlite3RefillIndex(Parse * pParse, Index * pIndex, int memRootPage)
>>>   }
>>>     struct Index *
>>> -sql_index_alloc(struct sqlite3 *db)
>>> +sql_index_alloc(struct sqlite3 *db, uint32_t part_count)
>>>   {
>>>          /* Size of struct Index and aiRowLogEst. */
>>> -       int index_size = ROUND8(sizeof(struct Index));
>>> +       int index_size = sizeof(struct Index) +
>>> +                        sizeof(LogEst) * (part_count + 1);
>>>          struct Index *p = sqlite3DbMallocZero(db, index_size);
>>>          if (p != NULL)
>>> -               p->aiRowLogEst = (LogEst *) ((char *)p + ROUND8(sizeof(*p)));
>>> +               p->aiRowLogEst = (LogEst *) ((char *)p + sizeof(*p));
>>>          return p;
>>>   }
>>>
>>> @@ -2769,11 +2757,10 @@ sql_create_index(struct Parse *parse, struct Token *token,
>>>          if (sqlite3CheckIdentifierName(parse, name) != SQLITE_OK)
>>>                  goto exit_create_index;
>>>   -       index = sql_index_alloc(db);
>>> +       index = sql_index_alloc(db, col_list->nExpr);
>>>
>>> @@ -2769,11 +2757,10 @@ sql_create_index(struct Parse *parse, struct Token *token,
>>>          if (sqlite3CheckIdentifierName(parse, name) != SQLITE_OK)
>>>                  goto exit_create_index;
>>>   -       index = sql_index_alloc(db);
>>> +       index = sql_index_alloc(db, col_list->nExpr);
>>>          if (index == NULL)
>>>                  goto exit_create_index;
>>>   -       assert(EIGHT_BYTE_ALIGNMENT(index->aiRowLogEst));
>>>
>>> diff --git a/src/box/sql/sqliteInt.h b/src/box/sql/sqliteInt.h
>>> index ae31dfae5..2082d6ca9 100644
>>> --- a/src/box/sql/sqliteInt.h
>>> +++ b/src/box/sql/sqliteInt.h
>>> @@ -3631,7 +3631,7 @@ void sqlite3SrcListDelete(sqlite3 *, SrcList *);
>>>    * @retval not NULL Index object.
>>>    */
>>>   struct Index *
>>> -sql_index_alloc(struct sqlite3 *db);
>>> +sql_index_alloc(struct sqlite3 *db, uint32_t part_count);
>> Why do we need to allocate 'part_count' part of memory? It does not seem
>> to be used for anything.
> It is used for array aiRowLogEst. Btw, you really can remove and now use
> index_def->opts.stat->tuple_log_est;  for fake indexes - then you are able to
> avoid allocating this memory.
Removed aiRowLogEst, fix it's usage with tuple_log_est.
>>>>>> diff --git a/src/box/sql/where.c b/src/box/sql/where.c
>>>>>> index 85143ed20..7ca02095f 100644
>>>>>> --- a/src/box/sql/where.c
>>>>>> +++ b/src/box/sql/where.c
>>>>>> @@ -372,13 +372,19 @@ whereScanInit(WhereScan * pScan,	/* The WhereScan object being initialized */
>>>>>> 	pScan->is_column_seen = false;
>>>>>> 	if (pIdx) {
>>>>>> 		int j = iColumn;
>>>>>> -		iColumn = pIdx->aiColumn[j];
>>>>>> +		iColumn = pIdx->def->key_def->parts[j].fieldno;
>>>>>> +		/*
>>>>>> +		 * pIdx->tnum == 0 means that pIdx is a fake
>>>>>> +		 * integer primary key index.
>>>>>> +		 */
>>>>>> +		if (pIdx->tnum == 0)
>>>>>> +			iColumn = -1;
>>>>> We are going to remove tnum from struct Index and struct Table.
>>>>> So, if it is possible, use index->def->iid instead (or smth else).
>>>> Removed with ‘fake_autoindex'
>>> Well, if smb called index ‘fake_autoindex’, I guess strange things would happen.
>>> Could we use for instance def->space_id == 0 as a sign of ‘fake_index'?.
>> Yes, it seems to be a better solution. What do you think about adding a field to
>> index_def.opts - index_def.opts.is_fake_pk?
> I am strictly against - it sounds like over-engineering. In parser, for example,
> space_def->opts.is_temporary is used as a sing of space_def allocated on a region
> (but initially this property is set to be true if space is temporary and temporary != allocated on region).
> So, since index is in fact surrogate, lets just use one of its fields.
I decided to use opts->sql. Since this fake index is PK, it was empty. 
But now
there is a "fake_autoindex" value to show that it is fake index.
>>> Moreover, sql_stmt is obtained from sqlite3Malloc() and assigned to index->opts,
>>> which in turn is released by common free().
>> In index_def_new() (where we pass opts) given sql_stmt is copied using strdup, so
>> it's okay to free() it, isn't it?
> Ok, then you don’t release original sql_stmt: fix it.
It is released somewhere out of create_index. I tried to add sqlite3DbFree
to the 'exit_create_index' section (with proper check that it's not NULL).
sqlite3DbFree was called succefuly and seemed to done it's work. But later
tarantool crashed with '0x7f839cc089f0: pointer being freed was not 
allocated'
This adress is the adress of 'sql_stmt'. BTW my patch currently does not
affect the way how this variable is created and freed.
>> diff --git a/src/box/sql/build.c b/src/box/sql/build.c
>> index d66777f73..0cb0b8e08 100644
>> --- a/src/box/sql/build.c
>> +++ b/src/box/sql/build.c
>> @@ -984,8 +984,10 @@ sqlite3AddPrimaryKey(Parse * pParse,	/* Parsing context */
>> 		sqlite3ErrorMsg(pParse, "AUTOINCREMENT is only allowed on an "
>> 				"INTEGER PRIMARY KEY or INT PRIMARY KEY");
>> 	} else {
>> +		/* Since this index implements PK, it is unique. */
>> 		sql_create_index(pParse, 0, 0, pList, onError, 0,
>> -				 0, sortOrder, false, SQLITE_IDXTYPE_PRIMARYKEY);
>> +				 0, sortOrder, false, SQLITE_IDXTYPE_PRIMARYKEY,
>> +				 true);
>> 		pList = 0;
>> 	}
>> @@ -1311,9 +1313,10 @@ convertToWithoutRowidTable(Parse * pParse, Table * pTab)
>> 			return;
>> 		pList->a[0].sort_order = pParse->iPkSortOrder;
>> 		assert(pParse->pNewTable == pTab);
>> +		/* Since this index implements PK, it is unique. */
>> 		sql_create_index(pParse, 0, 0, pList, pTab->keyConf, 0, 0,
>> 				 SORT_ORDER_ASC, false,
>> -				 SQLITE_IDXTYPE_PRIMARYKEY);
>> +				 SQLITE_IDXTYPE_PRIMARYKEY, true);
>> 		if (db->mallocFailed)
>> 			return;
>> 		pPk = sqlite3PrimaryKeyIndex(pTab);
>> @@ -2538,10 +2541,8 @@ addIndexToTable(Index * pIndex, Table * pTab)
>>   * @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, one of the following:
>> - *                 SQLITE_IDXTYPE_APPDEF     0
>> - *                 SQLITE_IDXTYPE_UNIQUE     1
>> - *                 SQLITE_IDXTYPE_PRIMARYKEY 2.
>> + * @param idx_type Index type: UNIQUE constraint, PK constraint,
>> + *                 or created by <CREATE INDEX ...> stmt.
>>   * @param sql_stmt SQL statement, which creates the index.
>>   * @retval 0 Success.
>>   * @retval -1 Error.
>> @@ -2632,7 +2633,7 @@ sql_create_index(struct Parse *parse, struct Token *token,
>> 		 struct SrcList *tbl_name, struct ExprList *col_list,
>> 		 enum on_conflict_action on_error, struct Token *start,
>> 		 struct Expr *where, enum sort_order sort_order,
>> -		 bool if_not_exist, u8 idx_type)
>> +		 bool if_not_exist, u8 idx_type, bool is_unique)
> Wait, you already have idx_type, you don’t need another one argument.
> Lets simply turn defines into appropriate enum
> (probably you should pick better names, but follow code style):
>
> -/*
> - * 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 */
> +/**
> + * There are some differences between explicitly created indexes
> + * and unique table constraints (despite the fact that they are
> + * the same in implementation). For instance, to drop index user
> + * must use <DROP INDEX ...> statement, but to drop constraint -
> + * <ALTER TABLE DROP CONSTRAINT ...> syntax.
> + */
> +enum sql_index_type {
> +       SQL_INDEX_USER_DEFINED_UINIQUE = 0,
> +       SQL_INDEX_USER_DEFINED = 1,
> +       SQL_INDEX_CONSTRAINT_UNIQUE = 2,
> +       SQL_INDEX_CONSTRAINT_PK = 3,
> +};
Done.
--

[PATCH v2] 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
---
  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 <
@@ -542,16 +542,14 @@ sqlite3Pragma(Parse * pParse, Token * pId,	/* First part of [schema.]id field */
  					for (pIdx = pTab->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")
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("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")
@@ -209,8 +208,7 @@ box.sql.execute("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")
@@ -142,8 +141,7 @@ box.sql.execute("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")
  ---
-- 

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

end of thread, other threads:[~2018-07-08 14:17 UTC | newest]

Thread overview: 23+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2018-06-13  7:30 [tarantool-patches] Re: [PATCH v3] sql: add index_def to struct Index Ivan Koptelov
2018-06-18 18:45 ` Kirill Shcherbatov
2018-06-21 12:57   ` [tarantool-patches] Re: [PATCH v4] " Ivan Koptelov
2018-06-22  8:46     ` Kirill Shcherbatov
2018-06-27 17:46       ` [tarantool-patches] Re: [PATCH v5] " Ivan Koptelov
2018-06-27 17:57         ` Kirill Shcherbatov
2018-06-28 18:49           ` Vladislav Shpilevoy
2018-06-29 13:49             ` [tarantool-patches] Re: [PATCH v6] " Ivan Koptelov
2018-06-29 20:46               ` Vladislav Shpilevoy
     [not found]                 ` <146c3bd4-e9e6-f943-5a42-c6db966d1c9c@tarantool.org>
2018-07-03  9:00                   ` [tarantool-patches] Re: [PATCH v8] " Ivan Koptelov
2018-07-03  9:46                 ` [tarantool-patches] Re: [PATCH v8.5] " Ivan Koptelov
2018-07-03 12:13                   ` Vladislav Shpilevoy
2018-07-03 11:37                 ` [tarantool-patches] Re: [PATCH v9] " Ivan Koptelov
2018-07-03 23:54                   ` n.pettik
2018-07-04  0:08                     ` Vladislav Shpilevoy
2018-07-04  9:17                       ` n.pettik
2018-07-04 15:55                     ` [tarantool-patches] Re: [PATCH v11] " Ivan Koptelov
2018-07-04 19:28                       ` n.pettik
2018-07-05 14:50                         ` Ivan Koptelov
2018-07-06  0:51                           ` n.pettik
2018-07-08 14:17                             ` [tarantool-patches] Re: [PATCH v2] " Ivan Koptelov
2018-07-04 10:46                   ` [tarantool-patches] Re: [PATCH v9] " Kirill Yukhin
2018-07-04 12:10                     ` Kirill Yukhin

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