[tarantool-patches] Re: [PATCH v4] sql: add index_def to struct Index

Ivan Koptelov ivan.koptelov at tarantool.org
Thu Jun 21 15:57:05 MSK 2018


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,
-- 







More information about the Tarantool-patches mailing list