Tarantool development patches archive
 help / color / mirror / Atom feed
* [tarantool-patches] [PATCH] sql: use collation pointers instead of names
@ 2018-04-13  8:05 Kirill Yukhin
  2018-04-16 13:43 ` [tarantool-patches] " Vladislav Shpilevoy
  0 siblings, 1 reply; 17+ messages in thread
From: Kirill Yukhin @ 2018-04-13  8:05 UTC (permalink / raw)
  To: v.shpilevoy; +Cc: tarantool-patches, Kirill Yukhin

Before the change SQL FE used collation names to refer to
collations. Main data structures were using names as well.
The patch fixes that and uses explicit pointers to Tarantool's
`struct coll` during code gen and inside data structures.

Closes #3205
---
 src/box/sql.c           |  21 ++------
 src/box/sql/alter.c     |   2 +-
 src/box/sql/analyze.c   |   9 +---
 src/box/sql/build.c     | 133 ++++++++++++++++++++++--------------------------
 src/box/sql/callback.c  |  53 ++-----------------
 src/box/sql/expr.c      |  54 +++++++++++---------
 src/box/sql/fkey.c      |  23 +++++----
 src/box/sql/func.c      |   3 +-
 src/box/sql/insert.c    |  12 ++---
 src/box/sql/pragma.c    |   9 +++-
 src/box/sql/select.c    |  84 +++++++++++++++++-------------
 src/box/sql/sqliteInt.h |  22 +++++---
 src/box/sql/vdbe.c      |   2 +-
 src/box/sql/vdbeaux.c   |   7 +++
 src/box/sql/vdbesort.c  |   4 +-
 src/box/sql/where.c     |  98 ++++++++++++++++-------------------
 src/box/sql/whereInt.h  |   7 ++-
 src/box/sql/whereexpr.c |  24 ++++++---
 18 files changed, 272 insertions(+), 295 deletions(-)

diff --git a/src/box/sql.c b/src/box/sql.c
index a6713f1..614917d 100644
--- a/src/box/sql.c
+++ b/src/box/sql.c
@@ -1465,12 +1465,8 @@ int tarantoolSqlite3MakeTableFormat(Table *pTable, void *buf)
 
 	for (i = 0; i < n; i++) {
 		const char *t;
-		struct coll *coll = NULL;
+		struct coll *coll = aCol[i].coll;
 		struct Expr *def = aCol[i].pDflt;
-		if (aCol[i].zColl != NULL &&
-		    strcasecmp(aCol[i].zColl, "binary") != 0) {
-			coll = sqlite3FindCollSeq(aCol[i].zColl);
-		}
 		int base_len = 4;
 		if (coll != NULL)
 			base_len += 1;
@@ -1571,28 +1567,19 @@ int tarantoolSqlite3MakeIdxParts(SqliteIndex *pIndex, void *buf)
 	for (i = 0; i < n; i++) {
 		int col = pIndex->aiColumn[i];
 		const char *t;
-		struct coll * collation = NULL;
 		if (pk_forced_int == col)
 			t = "integer";
 		else
 			t = convertSqliteAffinity(aCol[col].affinity, aCol[col].notNull == 0);
 		/* do not decode default collation */
-		if (sqlite3StrICmp(pIndex->azColl[i], "binary") != 0){
-			collation = sqlite3FindCollSeq(pIndex->azColl[i]);
-			/* 
-			 * At this point, the collation has already been found 
-			 * once and the assert should not fire.
-			 */
-			assert(collation);
-		}
-		p = enc->encode_map(p, collation == NULL ? 4 : 5);
+		p = enc->encode_map(p, pIndex->coll_array[i] == NULL ? 4 : 5);
 		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 (collation != NULL){
+		if (pIndex->coll_array[i] != NULL) {
 			p = enc->encode_str(p, "collation", sizeof("collation")-1);
-			p = enc->encode_uint(p, collation->id);
+			p = enc->encode_uint(p, pIndex->coll_array[i]->id);
 		}
 		p = enc->encode_str(p, "is_nullable", 11);
 		p = enc->encode_bool(p, aCol[col].notNull == ON_CONFLICT_ACTION_NONE);
diff --git a/src/box/sql/alter.c b/src/box/sql/alter.c
index 129ef82..b30a973 100644
--- a/src/box/sql/alter.c
+++ b/src/box/sql/alter.c
@@ -296,7 +296,7 @@ sqlite3AlterBeginAddColumn(Parse * pParse, SrcList * pSrc)
 	for (i = 0; i < pNew->nCol; i++) {
 		Column *pCol = &pNew->aCol[i];
 		pCol->zName = sqlite3DbStrDup(db, pCol->zName);
-		pCol->zColl = 0;
+		pCol->coll = NULL;
 		pCol->pDflt = 0;
 	}
 	pNew->pSchema = db->pSchema;
diff --git a/src/box/sql/analyze.c b/src/box/sql/analyze.c
index 665bfbc..f0054c5 100644
--- a/src/box/sql/analyze.c
+++ b/src/box/sql/analyze.c
@@ -977,18 +977,13 @@ analyzeOneTable(Parse * pParse,	/* Parser context */
 				VdbeCoverage(v);
 			}
 			for (i = 0; i < nColTest; i++) {
-				const char *zCollName =
-					index_collation_name(pIdx, i);
-				char *pColl =
-				    (char *)sqlite3LocateCollSeq(pParse,
-								 pParse->db,
-								 zCollName);
+				struct coll *coll = sql_index_collation(pIdx, i);
 				sqlite3VdbeAddOp2(v, OP_Integer, i, regChng);
 				sqlite3VdbeAddOp3(v, OP_Column, iIdxCur,
 						  pIdx->aiColumn[i], regTemp);
 				aGotoChng[i] =
 				    sqlite3VdbeAddOp4(v, OP_Ne, regTemp, 0,
-						      regPrev + i, pColl,
+						      regPrev + i, (char *)coll,
 						      P4_COLLSEQ);
 				sqlite3VdbeChangeP5(v, SQLITE_NULLEQ);
 				VdbeCoverage(v);
diff --git a/src/box/sql/build.c b/src/box/sql/build.c
index 92f3cb6..fffa65f 100644
--- a/src/box/sql/build.c
+++ b/src/box/sql/build.c
@@ -300,7 +300,6 @@ sqlite3DeleteColumnNames(sqlite3 * db, Table * pTable)
 		for (i = 0; i < pTable->nCol; i++, pCol++) {
 			sqlite3DbFree(db, pCol->zName);
 			sql_expr_free(db, pCol->pDflt, false);
-			sqlite3DbFree(db, pCol->zColl);
 		}
 		sqlite3DbFree(db, pTable->aCol);
 	}
@@ -952,6 +951,7 @@ sqlite3AddCollateType(Parse * pParse, Token * pToken)
 	Table *p;
 	int i;
 	char *zColl;		/* Dequoted name of collation sequence */
+	struct coll *coll;
 	sqlite3 *db;
 
 	if ((p = pParse->pNewTable) == 0)
@@ -962,10 +962,10 @@ sqlite3AddCollateType(Parse * pParse, Token * pToken)
 	if (!zColl)
 		return;
 
-	if (sqlite3LocateCollSeq(pParse, db, zColl)) {
+	coll =  sqlite3LocateCollSeq(pParse, db, zColl);
+	if (coll) {
 		Index *pIdx;
-		sqlite3DbFree(db, p->aCol[i].zColl);
-		p->aCol[i].zColl = zColl;
+		p->aCol[i].coll = coll;
 
 		/* If the column is declared as "<name> PRIMARY KEY COLLATE <type>",
 		 * then an index may have been created on this column before the
@@ -973,9 +973,8 @@ sqlite3AddCollateType(Parse * pParse, Token * pToken)
 		 */
 		for (pIdx = p->pIndex; pIdx; pIdx = pIdx->pNext) {
 			assert(pIdx->nColumn == 1);
-			if (pIdx->aiColumn[0] == i) {
-				pIdx->azColl[0] = column_collation_name(p, i);
-			}
+			if (pIdx->aiColumn[0] == i)
+				pIdx->coll_array[0] = sql_column_collation(p, i);
 		}
 	} else {
 		sqlite3DbFree(db, zColl);
@@ -983,14 +982,14 @@ sqlite3AddCollateType(Parse * pParse, Token * pToken)
 }
 
 /**
- * Return name of given column collation from table.
+ * Return collation of given column from table.
  *
  * @param table Table which is used to fetch column.
  * @param column Number of column.
- * @retval Pointer to collation's name.
+ * @retval Pointer to collation.
  */
-const char *
-column_collation_name(Table *table, uint32_t column)
+struct coll *
+sql_column_collation(Table *table, uint32_t column)
 {
 	assert(table != NULL);
 	uint32_t space_id = SQLITE_PAGENO_TO_SPACEID(table->tnum);
@@ -1007,15 +1006,12 @@ column_collation_name(Table *table, uint32_t column)
 	 * In cases mentioned above collation is fetched from
 	 * SQL specific structures.
 	 */
-	if (space == NULL || space_index(space, 0) == NULL)
-		return table->aCol[column].zColl;
-
-	/* "BINARY" is a name for default collation in SQL. */
-	char *coll_name = (char *)sqlite3StrBINARY;
-	if (space->format->fields[column].coll != NULL) {
-		coll_name = space->format->fields[column].coll->name;
+	if (space == NULL || space_index(space, 0) == NULL) {
+		assert(column < (uint32_t)table->nCol);
+		return table->aCol[column].coll;
 	}
-	return coll_name;
+
+	return space->format->fields[column].coll;
 }
 
 /**
@@ -1023,30 +1019,30 @@ column_collation_name(Table *table, uint32_t column)
  *
  * @param idx Index which is used to fetch column.
  * @param column Number of column.
- * @retval Pointer to collation's name.
+ * @retval Pointer to collation.
  */
-const char *
-index_collation_name(Index *idx, uint32_t column)
+struct coll *
+sql_index_collation(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)
-		return (char *)idx->azColl[column];
+	if (space == NULL) {
+		assert(column < idx->nColumn);
+		return idx->coll_array[column];
+	}
 
 	uint32_t index_id = SQLITE_PAGENO_TO_INDEXID(idx->tnum);
 	struct index *index = space_index(space, index_id);
 	assert(index != NULL && index->def->key_def->part_count >= column);
-	struct coll *coll = index->def->key_def->parts[column].coll;
-	/* "BINARY" is a name for default collation in SQL. */
-	if (coll == NULL)
-		return (char *)sqlite3StrBINARY;
-	return index->def->key_def->parts[column].coll->name;
+	return index->def->key_def->parts[column].coll;
 }
 
 /**
@@ -1117,6 +1113,9 @@ sqlite3LocateCollSeq(Parse * pParse, sqlite3 * db, const char *zName)
 	struct coll *pColl;
 	initbusy = db->init.busy;
 
+	if (sqlite3StrICmp(zName, "binary") == 0)
+		return NULL;
+
 	pColl = sqlite3FindCollSeq(zName);
 	if (!initbusy && (!pColl)) {
 		pColl = sqlite3GetCollSeq(pParse, pColl, zName);
@@ -2605,7 +2604,7 @@ sqlite3AllocateIndexObject(sqlite3 * db,	/* Database connection */
 	p = sqlite3DbMallocZero(db, nByte + nExtra);
 	if (p) {
 		char *pExtra = ((char *)p) + ROUND8(sizeof(Index));
-		p->azColl = (const char **)pExtra;
+		p->coll_array = (struct coll **)pExtra;
 		pExtra += ROUND8(sizeof(char *) * nCol);
 		p->aiRowLogEst = (LogEst *) pExtra;
 		pExtra += sizeof(LogEst) * (nCol + 1);
@@ -2902,7 +2901,7 @@ sqlite3CreateIndex(Parse * pParse,	/* All information about this parse */
 		goto exit_create_index;
 	}
 	assert(EIGHT_BYTE_ALIGNMENT(pIndex->aiRowLogEst));
-	assert(EIGHT_BYTE_ALIGNMENT(pIndex->azColl));
+	assert(EIGHT_BYTE_ALIGNMENT(pIndex->coll_array));
 	pIndex->zName = zExtra;
 	zExtra += nName + 1;
 	memcpy(pIndex->zName, zName, nName + 1);
@@ -2941,7 +2940,7 @@ sqlite3CreateIndex(Parse * pParse,	/* All information about this parse */
 	for (i = 0, pListItem = pList->a; i < pList->nExpr; i++, pListItem++) {
 		Expr *pCExpr;	/* The i-th index expression */
 		int requestedSortOrder;	/* ASC or DESC on the i-th expression */
-		const char *zColl;	/* Collation sequence name */
+		struct coll *coll;
 		sqlite3ResolveSelfReference(pParse, pTab, NC_IdxExpr,
 					    pListItem->pExpr, 0);
 		if (pParse->nErr)
@@ -2960,25 +2959,21 @@ sqlite3CreateIndex(Parse * pParse,	/* All information about this parse */
 			}
 			pIndex->aiColumn[i] = (i16) j;
 		}
-		zColl = 0;
+		coll = NULL;
+		const char *coll_name;
 		if (pListItem->pExpr->op == TK_COLLATE) {
-			int nColl;
-			zColl = pListItem->pExpr->u.zToken;
-			nColl = sqlite3Strlen30(zColl) + 1;
-			assert(nExtra >= nColl);
-			memcpy(zExtra, zColl, nColl);
-			zColl = zExtra;
-			zExtra += nColl;
-			nExtra -= nColl;
+			coll_name = pListItem->pExpr->u.zToken;
+			coll = sqlite3GetCollSeq(pParse, 0, coll_name);
+
+			if (coll == NULL &&
+			    sqlite3StrICmp(coll_name, "binary") != 0) {
+				goto exit_create_index;
+			}
 		} else if (j >= 0) {
-			zColl = column_collation_name(pTab, j);
-		}
-		if (!zColl)
-			zColl = sqlite3StrBINARY;
-		if (!db->init.busy && !sqlite3LocateCollSeq(pParse, db, zColl)) {
-			goto exit_create_index;
+			coll = sql_column_collation(pTab, j);
 		}
-		pIndex->azColl[i] = zColl;
+		pIndex->coll_array[i] = coll;
+
 		/* Tarantool: DESC indexes are not supported so far.
 		 * See gh-3016.
 		 */
@@ -3022,14 +3017,13 @@ sqlite3CreateIndex(Parse * pParse,	/* All information about this parse */
 			if (pIdx->nColumn != pIndex->nColumn)
 				continue;
 			for (k = 0; k < pIdx->nColumn; k++) {
-				const char *z1;
-				const char *z2;
 				assert(pIdx->aiColumn[k] >= 0);
 				if (pIdx->aiColumn[k] != pIndex->aiColumn[k])
 					break;
-				z1 = index_collation_name(pIdx, k);
-				z2 = index_collation_name(pIndex, k);
-				if (strcmp(z1, z2))
+				struct coll *coll1, *coll2;
+				coll1 = sql_index_collation(pIdx, k);
+				coll2 = sql_index_collation(pIndex, k);
+				if (coll1 != coll2)
 					break;
 			}
 			if (k == pIdx->nColumn) {
@@ -3945,19 +3939,18 @@ sqlite3UniqueConstraint(Parse * pParse,	/* Parsing context */
  * true if it does and false if it does not.
  */
 #ifndef SQLITE_OMIT_REINDEX
-static int
-collationMatch(const char *zColl, Index * pIndex)
+static bool
+collationMatch(struct coll *coll, struct Index *index)
 {
 	int i;
-	assert(zColl != 0);
-	for (i = 0; i < pIndex->nColumn; i++) {
-		const char *z = index_collation_name(pIndex, i);
-		assert(z != 0 || pIndex->aiColumn[i] < 0);
-		if (pIndex->aiColumn[i] >= 0 && 0 == sqlite3StrICmp(z, zColl)) {
-			return 1;
-		}
+	assert(coll != 0);
+	for (i = 0; i < index->nColumn; i++) {
+		struct coll *idx_coll = sql_index_collation(index, i);
+		assert(idx_coll != 0 || index->aiColumn[i] < 0);
+		if (index->aiColumn[i] >= 0 && coll == idx_coll)
+			return true;
 	}
-	return 0;
+	return false;
 }
 #endif
 
@@ -3967,12 +3960,12 @@ collationMatch(const char *zColl, Index * pIndex)
  */
 #ifndef SQLITE_OMIT_REINDEX
 static void
-reindexTable(Parse * pParse, Table * pTab, char const *zColl)
+reindexTable(Parse * pParse, Table * pTab, struct coll *coll)
 {
 	Index *pIndex;		/* An index associated with pTab */
 
 	for (pIndex = pTab->pIndex; pIndex; pIndex = pIndex->pNext) {
-		if (zColl == 0 || collationMatch(zColl, pIndex)) {
+		if (coll == 0 || collationMatch(coll, pIndex)) {
 			sql_set_multi_write(pParse, false);
 			sqlite3RefillIndex(pParse, pIndex, -1);
 		}
@@ -3987,7 +3980,7 @@ reindexTable(Parse * pParse, Table * pTab, char const *zColl)
  */
 #ifndef SQLITE_OMIT_REINDEX
 static void
-reindexDatabases(Parse * pParse, char const *zColl)
+reindexDatabases(Parse * pParse, struct coll *coll)
 {
 	sqlite3 *db = pParse->db;	/* The database connection */
 	HashElem *k;		/* For looping over tables in pSchema */
@@ -3997,7 +3990,7 @@ reindexDatabases(Parse * pParse, char const *zColl)
 	for (k = sqliteHashFirst(&db->pSchema->tblHash); k;
 	     k = sqliteHashNext(k)) {
 		pTab = (Table *) sqliteHashData(k);
-		reindexTable(pParse, pTab, zColl);
+		reindexTable(pParse, pTab, coll);
 	}
 }
 #endif
@@ -4039,7 +4032,7 @@ sqlite3Reindex(Parse * pParse, Token * pName1, Token * pName2)
 			return;
 		pColl = sqlite3FindCollSeq(zColl);
 		if (pColl) {
-			reindexDatabases(pParse, zColl);
+			reindexDatabases(pParse, pColl);
 			sqlite3DbFree(db, zColl);
 			return;
 		}
@@ -4108,9 +4101,7 @@ sqlite3KeyInfoOfIndex(Parse * pParse, sqlite3 * db, Index * pIdx)
 	if (pKey) {
 		assert(sqlite3KeyInfoIsWriteable(pKey));
 		for (i = 0; i < nCol; i++) {
-			const char *zColl = index_collation_name(pIdx, i);
-			pKey->aColl[i] = zColl == sqlite3StrBINARY ? 0 :
-			    sqlite3LocateCollSeq(pParse, db, zColl);
+			pKey->aColl[i] = sql_index_collation(pIdx, i);
 			pKey->aSortOrder[i] = pIdx->aSortOrder[i];
 		}
 		if (pParse && pParse->nErr) {
diff --git a/src/box/sql/callback.c b/src/box/sql/callback.c
index b176931..0d34e6a 100644
--- a/src/box/sql/callback.c
+++ b/src/box/sql/callback.c
@@ -62,10 +62,9 @@ sqlite3GetCollSeq(Parse * pParse,	/* Parsing context */
 	struct coll *p;
 
 	p = pColl;
-	if (!p) {
+	if (p == NULL)
 		p = sqlite3FindCollSeq(zName);
-	}
-	if (p == 0) {
+	if (p == NULL && (sqlite3StrICmp(zName, "binary") != 0)) {
 		if (pParse)
 			sqlite3ErrorMsg(pParse,
 					"no such collation sequence: %s",
@@ -102,49 +101,6 @@ sqlite3CheckCollSeq(Parse * pParse, struct coll * pColl)
 	return SQLITE_OK;
 }
 
-/*
- * This is the default collating function named "BINARY" which is always
- * available.
- * It is hardcoded to support Tarantool's collation interface.
- */
-static int
-binCollFunc(const char *pKey1, size_t nKey1, const char *pKey2, size_t nKey2, const struct coll * collation)
-{
-	int rc;
-	size_t n;
-	(void) collation;
-	n = nKey1 < nKey2 ? nKey1 : nKey2;
-	/* EVIDENCE-OF: R-65033-28449 The built-in BINARY collation compares
-	 * strings byte by byte using the memcmp() function from the standard C
-	 * library.
-	 */
-	rc = memcmp(pKey1, pKey2, n);
-	if (rc == 0) {
-		rc = (int)nKey1 - (int)nKey2;
-	}
-	return rc;
-}
-
-/*
- * This hardcoded structure created just to be called the same way
- * as collations in Tarantool, to support binary collation easily.
- */
-
-struct coll_plus_name_struct{
-    struct coll collation;
-    char name[20]; /* max of possible name lengths */
-};
-static struct coll_plus_name_struct binary_coll_with_name =
-	{{0, 0, COLL_TYPE_ICU, {0}, binCollFunc, 0, sizeof("BINARY"), {}},
-		"BINARY"};
-static struct coll * binary_coll = (struct coll*)&binary_coll_with_name;
-
-struct coll *
-sql_default_coll()
-{
-	return binary_coll;
-}
-
 /**
  * Return the coll* pointer for the collation sequence named zName.
  *
@@ -158,9 +114,8 @@ sql_default_coll()
 struct coll *
 sqlite3FindCollSeq(const char *zName)
 {
-	if (zName == NULL || sqlite3StrICmp(zName, "binary")==0){
-		return binary_coll;
-	}
+	if (zName == NULL || sqlite3StrICmp(zName, "binary")==0)
+		return 0;
 	return coll_by_name(zName, strlen(zName));
 }
 
diff --git a/src/box/sql/expr.c b/src/box/sql/expr.c
index 8071314..7c1cec4 100644
--- a/src/box/sql/expr.c
+++ b/src/box/sql/expr.c
@@ -168,10 +168,11 @@ sqlite3ExprSkipCollate(Expr * pExpr)
  * precedence over right operands.
  */
 struct coll *
-sqlite3ExprCollSeq(Parse * pParse, Expr * pExpr)
+sqlite3ExprCollSeq(Parse * pParse, Expr * pExpr, bool *found)
 {
 	struct coll *pColl = 0;
 	Expr *p = pExpr;
+	*found = false;
 	while (p) {
 		int op = p->op;
 		if (p->flags & EP_Generic)
@@ -183,7 +184,8 @@ sqlite3ExprCollSeq(Parse * pParse, Expr * pExpr)
 		if (op == TK_COLLATE
 		    || (op == TK_REGISTER && p->op2 == TK_COLLATE)) {
 			pColl =
-			    sqlite3GetCollSeq(pParse, 0, p->u.zToken);
+			    sqlite3GetCollSeq(pParse, NULL, p->u.zToken);
+			*found = true;
 			break;
 		}
 		if ((op == TK_AGG_COLUMN || op == TK_COLUMN
@@ -194,9 +196,8 @@ sqlite3ExprCollSeq(Parse * pParse, Expr * pExpr)
 			 */
 			int j = p->iColumn;
 			if (j >= 0) {
-				const char *zColl =
-					column_collation_name(p->pTab, j);
-				pColl = sqlite3FindCollSeq(zColl);
+				pColl = sql_column_collation(p->pTab, j);
+				*found = true;
 			}
 			break;
 		}
@@ -345,16 +346,16 @@ struct coll *
 sqlite3BinaryCompareCollSeq(Parse * pParse, Expr * pLeft, Expr * pRight)
 {
 	struct coll *pColl;
+	bool found;
 	assert(pLeft);
 	if (pLeft->flags & EP_Collate) {
-		pColl = sqlite3ExprCollSeq(pParse, pLeft);
+		pColl = sqlite3ExprCollSeq(pParse, pLeft, &found);
 	} else if (pRight && (pRight->flags & EP_Collate) != 0) {
-		pColl = sqlite3ExprCollSeq(pParse, pRight);
+		pColl = sqlite3ExprCollSeq(pParse, pRight, &found);
 	} else {
-		pColl = sqlite3ExprCollSeq(pParse, pLeft);
-		if (!pColl) {
-			pColl = sqlite3ExprCollSeq(pParse, pRight);
-		}
+		pColl = sqlite3ExprCollSeq(pParse, pLeft, &found);
+		if (!found)
+			pColl = sqlite3ExprCollSeq(pParse, pRight, &found);
 	}
 	return pColl;
 }
@@ -2520,14 +2521,15 @@ sqlite3FindInIndex(Parse * pParse,	/* Parsing context */
 						    (pParse, pLhs, pRhs);
 					int j;
 
-					assert(pReq != 0 || pParse->nErr);
 					for (j = 0; j < nExpr; j++) {
-						if (pIdx->aiColumn[j]
-						    != pRhs->iColumn)
+						if (pIdx->aiColumn[j] !=
+						    pRhs->iColumn) {
 							continue;
-						if (pReq != 0 && strcmp
-						    (pReq->name,
-						     index_collation_name(pIdx, j)) != 0) {
+						}
+						struct coll *idx_coll;
+						idx_coll = sql_index_collation(pIdx, j);
+						if (pReq != NULL &&
+						    pReq != idx_coll) {
 							continue;
 						}
 						break;
@@ -2879,11 +2881,13 @@ sqlite3CodeSubselect(Parse * pParse,	/* Parsing context */
 					affinity = SQLITE_AFF_BLOB;
 				}
 				if (pKeyInfo) {
+					bool found; /* Not Used.  */
 					assert(sqlite3KeyInfoIsWriteable
 					       (pKeyInfo));
 					pKeyInfo->aColl[0] =
 					    sqlite3ExprCollSeq(pParse,
-							       pExpr->pLeft);
+							       pExpr->pLeft,
+							       &found);
 				}
 
 				/* Loop through each expression in <exprlist>. */
@@ -3140,8 +3144,11 @@ sqlite3ExprCodeIN(Parse * pParse,	/* Parsing and code generating context */
 	 * This is step (1) in the in-operator.md optimized algorithm.
 	 */
 	if (eType == IN_INDEX_NOOP) {
+		bool found; /* Not used. */
 		ExprList *pList = pExpr->x.pList;
-		struct coll *pColl = sqlite3ExprCollSeq(pParse, pExpr->pLeft);
+		struct coll *pColl = sqlite3ExprCollSeq(pParse,
+							pExpr->pLeft,
+							&found);
 		int labelOk = sqlite3VdbeMakeLabel(v);
 		int r2, regToFree;
 		int regCkNull = 0;
@@ -3277,9 +3284,10 @@ sqlite3ExprCodeIN(Parse * pParse,	/* Parsing and code generating context */
 	for (i = 0; i < nVector; i++) {
 		Expr *p;
 		struct coll *pColl;
+		bool found;
 		int r3 = sqlite3GetTempReg(pParse);
 		p = sqlite3VectorFieldSubexpr(pLeft, i);
-		pColl = sqlite3ExprCollSeq(pParse, p);
+		pColl = sqlite3ExprCollSeq(pParse, p, &found);
 		/* Tarantool: Replace i -> aiMap [i], since original order of columns
 		 * is preserved.
 		 */
@@ -4134,10 +4142,12 @@ sqlite3ExprCodeTarget(Parse * pParse, Expr * pExpr, int target)
 				}
 				if ((pDef->funcFlags & SQLITE_FUNC_NEEDCOLL) !=
 				    0 && !pColl) {
+					bool found; /* Not used.  */
 					pColl =
 					    sqlite3ExprCollSeq(pParse,
 							       pFarg->a[i].
-							       pExpr);
+							       pExpr,
+							       &found);
 				}
 			}
 			if (pFarg) {
@@ -4186,8 +4196,6 @@ sqlite3ExprCodeTarget(Parse * pParse, Expr * pExpr, int target)
 				r1 = 0;
 			}
 			if (pDef->funcFlags & SQLITE_FUNC_NEEDCOLL) {
-				if (!pColl)
-					pColl = sql_default_coll();
 				sqlite3VdbeAddOp4(v, OP_CollSeq, 0, 0, 0,
 						  (char *)pColl, P4_COLLSEQ);
 			}
diff --git a/src/box/sql/fkey.c b/src/box/sql/fkey.c
index f56b6d9..a0a35e0 100644
--- a/src/box/sql/fkey.c
+++ b/src/box/sql/fkey.c
@@ -287,7 +287,6 @@ sqlite3FkLocateIndex(Parse * pParse,	/* Parse context to store any error in */
 				int i, j;
 				for (i = 0; i < nCol; i++) {
 					i16 iCol = pIdx->aiColumn[i];	/* Index of column in parent tbl */
-					const char *zDfltColl;	/* Def. collation for column */
 					char *zIdxCol;	/* Name of indexed column */
 
 					if (iCol < 0)
@@ -297,11 +296,12 @@ sqlite3FkLocateIndex(Parse * pParse,	/* Parse context to store any error in */
 					 * the default collation sequence for the column, this index is
 					 * unusable. Bail out early in this case.
 					 */
-					zDfltColl =
-						column_collation_name(pParent,
-								      iCol);
-					if (strcmp
-					    (index_collation_name(pIdx, i), zDfltColl))
+					struct coll *def_coll;
+					def_coll = sql_column_collation(pParent,
+									iCol);
+					struct coll *coll;
+					coll = sql_index_collation(pIdx, i);
+					if (def_coll != coll)
 						break;
 
 					zIdxCol = pParent->aCol[iCol].zName;
@@ -526,7 +526,6 @@ exprTableRegister(Parse * pParse,	/* Parsing and code generating context */
 {
 	Expr *pExpr;
 	Column *pCol;
-	const char *zColl;
 	sqlite3 *db = pParse->db;
 
 	pExpr = sqlite3Expr(db, TK_REGISTER, 0);
@@ -535,9 +534,13 @@ exprTableRegister(Parse * pParse,	/* Parsing and code generating context */
 			pCol = &pTab->aCol[iCol];
 			pExpr->iTable = regBase + iCol + 1;
 			pExpr->affinity = pCol->affinity;
-			zColl = column_collation_name(pTab, iCol);
-			pExpr =
-			    sqlite3ExprAddCollateString(pParse, pExpr, zColl);
+			const char *coll_name;
+			if (pCol->coll == NULL && pCol->coll != NULL)
+				coll_name = pCol->coll->name;
+			else
+				coll_name = "binary";
+			pExpr = sqlite3ExprAddCollateString(pParse, pExpr,
+							    coll_name);
 		} else {
 			pExpr->iTable = regBase;
 			pExpr->affinity = SQLITE_AFF_INTEGER;
diff --git a/src/box/sql/func.c b/src/box/sql/func.c
index 47b45de..dcac22c 100644
--- a/src/box/sql/func.c
+++ b/src/box/sql/func.c
@@ -54,7 +54,7 @@ sqlite3GetFuncCollSeq(sqlite3_context * context)
 	assert(context->pVdbe != 0);
 	pOp = &context->pVdbe->aOp[context->iOp - 1];
 	assert(pOp->opcode == OP_CollSeq);
-	assert(pOp->p4type == P4_COLLSEQ);
+	assert(pOp->p4type == P4_COLLSEQ || pOp->p4.pColl == NULL);
 	return pOp->p4.pColl;
 }
 
@@ -82,7 +82,6 @@ minmaxFunc(sqlite3_context * context, int argc, sqlite3_value ** argv)
 	assert(argc > 1);
 	mask = sqlite3_user_data(context) == 0 ? 0 : -1;
 	pColl = sqlite3GetFuncCollSeq(context);
-	assert(pColl);
 	assert(mask == -1 || mask == 0);
 	iBest = 0;
 	if (sqlite3_value_type(argv[0]) == SQLITE_NULL)
diff --git a/src/box/sql/insert.c b/src/box/sql/insert.c
index b24d55b..e74cad8 100644
--- a/src/box/sql/insert.c
+++ b/src/box/sql/insert.c
@@ -1404,9 +1404,7 @@ sqlite3GenerateConstraintChecks(Parse * pParse,		/* The parser context */
 					      regIdx : regR);
 
 				for (i = 0; i < nPkCol; i++) {
-					char *p4 = (char *)
-						sqlite3LocateCollSeq(pParse, db,
-								     index_collation_name(pPk, i));
+					char *p4 = (char *)sql_index_collation(pPk, i);
 					x = pPk->aiColumn[i];
 					assert(x >= 0);
 					if (i == (nPkCol - 1)) {
@@ -1651,8 +1649,8 @@ xferCompatibleIndex(Index * pDest, Index * pSrc)
 		if (pSrc->aSortOrder[i] != pDest->aSortOrder[i]) {
 			return 0;	/* Different sort orders */
 		}
-		if (strcasecmp(index_collation_name(pSrc, i),
-			       index_collation_name(pDest, i)) != 0) {
+		if (sql_index_collation(pSrc, i) !=
+		    sql_index_collation(pDest, i)) {
 			return 0;	/* Different collating sequences */
 		}
 	}
@@ -1791,8 +1789,8 @@ xferOptimization(Parse * pParse,	/* Parser context */
 		if (pDestCol->affinity != pSrcCol->affinity) {
 			return 0;	/* Affinity must be the same on all columns */
 		}
-		if (strcasecmp(column_collation_name(pDest, i),
-			       column_collation_name(pSrc, i)) != 0) {
+		if (sql_column_collation(pDest, i) !=
+		    sql_column_collation(pSrc, i)) {
 			return 0;	/* Collating sequence must be the same on all columns */
 		}
 		if (!table_column_is_nullable(pDest, i)
diff --git a/src/box/sql/pragma.c b/src/box/sql/pragma.c
index b724c98..a2a6391 100644
--- a/src/box/sql/pragma.c
+++ b/src/box/sql/pragma.c
@@ -452,13 +452,20 @@ sqlite3Pragma(Parse * pParse, Token * pId,	/* First part of [schema.]id field */
 								     aCol[cnum].
 								     zName);
 						if (pPragma->iArg) {
+							const char *c_n;
+							struct coll *coll;
+							coll = sql_index_collation(pIdx, i);
+							if (coll != NULL)
+								c_n = coll->name;
+							else
+								c_n = "BINARY";
 							sqlite3VdbeMultiLoad(v,
 									     4,
 									     "isi",
 									     pIdx->
 									     aSortOrder
 									     [i],
-									     index_collation_name(pIdx, i),
+									     c_n,
 									     i <
 									     mx);
 						}
diff --git a/src/box/sql/select.c b/src/box/sql/select.c
index d97e466..3195dcc 100644
--- a/src/box/sql/select.c
+++ b/src/box/sql/select.c
@@ -908,10 +908,12 @@ selectInnerLoop(Parse * pParse,		/* The parser context */
 
 				iJump = sqlite3VdbeCurrentAddr(v) + nResultCol;
 				for (i = 0; i < nResultCol; i++) {
+					bool found;
 					struct coll *pColl =
 					    sqlite3ExprCollSeq(pParse,
 							       pEList->a[i].
-							       pExpr);
+							       pExpr,
+							       &found);
 					if (i < nResultCol - 1) {
 						sqlite3VdbeAddOp3(v, OP_Ne,
 								  regResult + i,
@@ -925,9 +927,11 @@ selectInnerLoop(Parse * pParse,		/* The parser context */
 								  regPrev + i);
 						VdbeCoverage(v);
 					}
-					sqlite3VdbeChangeP4(v, -1,
-							    (const char *)pColl,
-							    P4_COLLSEQ);
+					if (found) {
+						sqlite3VdbeChangeP4(v, -1,
+								    (const char *)pColl,
+								    P4_COLLSEQ);
+					}
 					sqlite3VdbeChangeP5(v, SQLITE_NULLEQ);
 				}
 				assert(sqlite3VdbeCurrentAddr(v) == iJump
@@ -1288,10 +1292,11 @@ keyInfoFromExprList(Parse * pParse,	/* Parsing context */
 		assert(sqlite3KeyInfoIsWriteable(pInfo));
 		for (i = iStart, pItem = pList->a + iStart; i < nExpr;
 		     i++, pItem++) {
+			bool found; /* Not used.  */
 			struct coll *pColl;
-			pColl = sqlite3ExprCollSeq(pParse, pItem->pExpr);
-			if (!pColl)
-				pColl = sql_default_coll();
+			pColl = sqlite3ExprCollSeq(pParse,
+						   pItem->pExpr,
+						   &found);
 			pInfo->aColl[i - iStart] = pColl;
 			pInfo->aSortOrder[i - iStart] = pItem->sortOrder;
 		}
@@ -1935,7 +1940,6 @@ sqlite3SelectAddColumnTypeAndCollation(Parse * pParse,		/* Parsing contexts */
 	sqlite3 *db = pParse->db;
 	NameContext sNC;
 	Column *pCol;
-	struct coll *pColl;
 	int i;
 	Expr *p;
 	struct ExprList_item *a;
@@ -1950,6 +1954,7 @@ sqlite3SelectAddColumnTypeAndCollation(Parse * pParse,		/* Parsing contexts */
 	sNC.pSrcList = pSelect->pSrc;
 	a = pSelect->pEList->a;
 	for (i = 0, pCol = pTab->aCol; i < pTab->nCol; i++, pCol++) {
+		struct coll *coll;
 		enum field_type type;
 		p = a[i].pExpr;
 		type = columnType(&sNC, p, 0, 0, 0, &pCol->szEst);
@@ -1959,10 +1964,10 @@ sqlite3SelectAddColumnTypeAndCollation(Parse * pParse,		/* Parsing contexts */
 
 		if (pCol->affinity == 0)
 			pCol->affinity = SQLITE_AFF_BLOB;
-		pColl = sqlite3ExprCollSeq(pParse, p);
-		if (pColl && pCol->zColl == 0) {
-			pCol->zColl = sqlite3DbStrDup(db, pColl->name);
-		}
+		bool found;
+		coll = sqlite3ExprCollSeq(pParse, p, &found);
+		if (coll && pCol->coll == 0)
+			pCol->coll = coll;
 	}
 	pTab->szTabRow = sqlite3LogEst(szAll * 4);
 }
@@ -2123,11 +2128,11 @@ computeLimitRegisters(Parse * pParse, Select * p, int iBreak)
  * left-most term of the select that has a collating sequence.
  */
 static struct coll *
-multiSelectCollSeq(Parse * pParse, Select * p, int iCol)
+multiSelectCollSeq(Parse * pParse, Select * p, int iCol, bool *found)
 {
 	struct coll *pRet;
 	if (p->pPrior) {
-		pRet = multiSelectCollSeq(pParse, p->pPrior, iCol);
+		pRet = multiSelectCollSeq(pParse, p->pPrior, iCol, found);
 	} else {
 		pRet = 0;
 	}
@@ -2136,8 +2141,10 @@ multiSelectCollSeq(Parse * pParse, Select * p, int iCol)
 	 * have been thrown during name resolution and we would not have gotten
 	 * this far
 	 */
-	if (pRet == 0 && ALWAYS(iCol < p->pEList->nExpr)) {
-		pRet = sqlite3ExprCollSeq(pParse, p->pEList->a[iCol].pExpr);
+	if (!(*found) && ALWAYS(iCol < p->pEList->nExpr)) {
+		pRet = sqlite3ExprCollSeq(pParse,
+					  p->pEList->a[iCol].pExpr,
+					  found);
 	}
 	return pRet;
 }
@@ -2166,16 +2173,25 @@ multiSelectOrderByKeyInfo(Parse * pParse, Select * p, int nExtra)
 			struct coll *pColl;
 
 			if (pTerm->flags & EP_Collate) {
-				pColl = sqlite3ExprCollSeq(pParse, pTerm);
+				bool found; /* Not used.  */
+				pColl = sqlite3ExprCollSeq(pParse,
+							   pTerm,
+							   &found);
 			} else {
+				bool found = false;
 				pColl =
 				    multiSelectCollSeq(pParse, p,
-						       pItem->u.x.iOrderByCol - 1);
-				if (pColl == 0)
-					pColl = sql_default_coll();
-				pOrderBy->a[i].pExpr =
-				    sqlite3ExprAddCollateString(pParse, pTerm,
-								pColl->name);
+						       pItem->u.x.iOrderByCol - 1,
+						       &found);
+				if (pColl != NULL) {
+					pOrderBy->a[i].pExpr =
+						sqlite3ExprAddCollateString(pParse, pTerm,
+									    pColl->name);
+				} else {
+					pOrderBy->a[i].pExpr =
+						sqlite3ExprAddCollateString(pParse, pTerm,
+									    "BINARY");
+				}
 			}
 			assert(sqlite3KeyInfoIsWriteable(pRet));
 			pRet->aColl[i] = pColl;
@@ -2827,10 +2843,8 @@ multiSelect(Parse * pParse,	/* Parsing context */
 			goto multi_select_end;
 		}
 		for (i = 0, apColl = pKeyInfo->aColl; i < nCol; i++, apColl++) {
-			*apColl = multiSelectCollSeq(pParse, p, i);
-			if (0 == *apColl) {
-				*apColl = sql_default_coll();
-			}
+			bool found = false; /* Not used.  */
+			*apColl = multiSelectCollSeq(pParse, p, i, &found);
 		}
 
 		for (pLoop = p; pLoop; pLoop = pLoop->pPrior) {
@@ -3260,8 +3274,9 @@ multiSelectOrderBy(Parse * pParse,	/* Parsing context */
 		if (pKeyDup) {
 			assert(sqlite3KeyInfoIsWriteable(pKeyDup));
 			for (i = 0; i < nExpr; i++) {
+				bool found = false; /* Not used. */
 				pKeyDup->aColl[i] =
-				    multiSelectCollSeq(pParse, p, i);
+					multiSelectCollSeq(pParse, p, i, &found);
 				pKeyDup->aSortOrder[i] = 0;
 			}
 		}
@@ -5241,17 +5256,16 @@ updateAccumulator(Parse * pParse, AggInfo * pAggInfo)
 				     regAgg);
 		}
 		if (pF->pFunc->funcFlags & SQLITE_FUNC_NEEDCOLL) {
-			struct coll *pColl = 0;
+			struct coll *pColl = NULL;
 			struct ExprList_item *pItem;
 			int j;
 			assert(pList != 0);	/* pList!=0 if pF->pFunc has NEEDCOLL */
-			for (j = 0, pItem = pList->a; !pColl && j < nArg;
+			bool found = false;
+			for (j = 0, pItem = pList->a; !found && j < nArg;
 			     j++, pItem++) {
-				pColl =
-				    sqlite3ExprCollSeq(pParse, pItem->pExpr);
-			}
-			if (!pColl) {
-				pColl = sql_default_coll();
+				pColl = sqlite3ExprCollSeq(pParse,
+							   pItem->pExpr,
+							   &found);
 			}
 			if (regHit == 0 && pAggInfo->nAccumulator)
 				regHit = ++pParse->nMem;
diff --git a/src/box/sql/sqliteInt.h b/src/box/sql/sqliteInt.h
index 59662cf..35c9dae 100644
--- a/src/box/sql/sqliteInt.h
+++ b/src/box/sql/sqliteInt.h
@@ -1878,10 +1878,13 @@ struct Column {
 	char *zName;		/* Name of this column */
 	enum field_type type;	/* Column type. */
 	Expr *pDflt;		/* Default value of this column */
-	char *zColl;		/* Collating sequence.  If NULL, use the default */
-	enum on_conflict_action notNull;  /* An ON_CONFLICT_ACTION code for
-					   * handling a NOT NULL constraint
-					   */
+	/** Collating sequence. */
+	struct coll *coll;
+	/**
+	 * An ON_CONFLICT_ACTION code for handling a NOT NULL
+	 * constraint.
+	 */
+	enum on_conflict_action notNull;
 	char affinity;		/* One of the SQLITE_AFF_... values */
 	u8 szEst;		/* Estimated size of value in this column. sizeof(INT)==1 */
 	u8 is_primkey;		/* Boolean propertie for being PK */
@@ -2148,7 +2151,8 @@ struct Index {
 	Index *pNext;		/* The next index associated with the same table */
 	Schema *pSchema;	/* Schema containing this index */
 	u8 *aSortOrder;		/* for each column: True==DESC, False==ASC */
-	const char **azColl;	/* Array of collation sequence names for index */
+	/**  Array of collation sequences for index. */
+	struct coll **coll_array;
 	Expr *pPartIdxWhere;	/* WHERE clause for partial indices */
 	ExprList *aColExpr;	/* Column expressions */
 	int tnum;		/* DB Page containing root of this index */
@@ -3548,14 +3552,20 @@ void sqlite3AddPrimaryKey(Parse *, ExprList *, int, int, int);
 void sqlite3AddCheckConstraint(Parse *, Expr *);
 void sqlite3AddDefaultValue(Parse *, ExprSpan *);
 void sqlite3AddCollateType(Parse *, Token *);
+
 const char *
 column_collation_name(Table *, uint32_t);
+struct coll *
+sql_column_collation(Table *, uint32_t);
 const char *
 index_collation_name(Index *, uint32_t);
 struct coll *
+sql_index_collation(Index *idx, uint32_t column);
+struct coll *
 sql_default_coll();
 bool
 space_is_view(Table *);
+
 void sqlite3EndTable(Parse *, Token *, Token *, Select *);
 int
 emit_open_cursor(Parse *, int, int);
@@ -3845,7 +3855,7 @@ const char *sqlite3ErrName(int);
 const char *sqlite3ErrStr(int);
 struct coll *sqlite3FindCollSeq(const char *);
 struct coll *sqlite3LocateCollSeq(Parse * pParse, sqlite3 * db, const char *zName);
-struct coll *sqlite3ExprCollSeq(Parse * pParse, Expr * pExpr);
+struct coll *sqlite3ExprCollSeq(Parse * pParse, Expr * pExpr, bool *found);
 Expr *sqlite3ExprAddCollateToken(Parse * pParse, Expr *, const Token *, int);
 Expr *sqlite3ExprAddCollateString(Parse *, Expr *, const char *);
 Expr *sqlite3ExprSkipCollate(Expr *);
diff --git a/src/box/sql/vdbe.c b/src/box/sql/vdbe.c
index 8237183..321ce2f 100644
--- a/src/box/sql/vdbe.c
+++ b/src/box/sql/vdbe.c
@@ -1660,7 +1660,7 @@ case OP_Remainder: {           /* same as TK_REM, in1, in2, out3 */
  * publicly.  Only built-in functions have access to this feature.
  */
 case OP_CollSeq: {
-	assert(pOp->p4type==P4_COLLSEQ);
+	assert(pOp->p4type==P4_COLLSEQ || pOp->p4.pColl == NULL);
 	if (pOp->p1) {
 		sqlite3VdbeMemSetInt64(&aMem[pOp->p1], 0);
 	}
diff --git a/src/box/sql/vdbeaux.c b/src/box/sql/vdbeaux.c
index bb121a3..e8a0917 100644
--- a/src/box/sql/vdbeaux.c
+++ b/src/box/sql/vdbeaux.c
@@ -3840,6 +3840,13 @@ sqlite3MemCompare(const Mem * pMem1, const Mem * pMem2, const struct coll * pCol
 		 */
 		if (pColl) {
 			return vdbeCompareMemString(pMem1, pMem2, pColl, 0);
+		} else {
+			size_t n = pMem1->n < pMem2->n ? pMem1->n : pMem2->n;
+			int res;
+			res = memcmp(pMem1->z, pMem2->z, n);
+			if (res == 0)
+				res = (int)pMem1->n - (int)pMem2->n;
+			return res;
 		}
 		/* If a NULL pointer was passed as the collate function, fall through
 		 * to the blob case and use memcmp().
diff --git a/src/box/sql/vdbesort.c b/src/box/sql/vdbesort.c
index fc10ef6..be3cc4c 100644
--- a/src/box/sql/vdbesort.c
+++ b/src/box/sql/vdbesort.c
@@ -930,9 +930,7 @@ sqlite3VdbeSorterInit(sqlite3 * db,	/* Database connection (for malloc()) */
 		}
 
 		if ((pKeyInfo->nField + pKeyInfo->nXField) < 13
-		    && (pKeyInfo->aColl[0] == 0
-			|| pKeyInfo->aColl[0] == sql_default_coll())
-		    ) {
+		    && (pKeyInfo->aColl[0] == NULL)) {
 			pSorter->typeMask =
 			    SORTER_TYPE_INTEGER | SORTER_TYPE_TEXT;
 		}
diff --git a/src/box/sql/where.c b/src/box/sql/where.c
index 2a26302..abbf9eb 100644
--- a/src/box/sql/where.c
+++ b/src/box/sql/where.c
@@ -298,26 +298,20 @@ whereScanNext(WhereScan * pScan)
 					if ((pTerm->eOperator & pScan->
 					     opMask) != 0) {
 						/* Verify the affinity and collating sequence match */
-						if (pScan->zCollName
-						    && (pTerm->eOperator & WO_ISNULL) == 0) {
-							struct coll *pColl;
-							Parse *pParse =
-							    pWC->pWInfo->pParse;
+						if ((pTerm->eOperator & WO_ISNULL) == 0) {
 							pX = pTerm->pExpr;
-							if (!sqlite3IndexAffinityOk(pX, pScan->idxaff)) {
-								continue;
-							}
-							assert(pX->pLeft);
-							pColl =
-							    sqlite3BinaryCompareCollSeq
-							    (pParse, pX->pLeft,
-							     pX->pRight);
-							if (pColl == 0)
-								pColl =
-									sql_default_coll();
-							if (strcmp(pColl->name,
-									   pScan->zCollName)) {
+							if (!sqlite3IndexAffinityOk(pX, pScan->idxaff))
 								continue;
+							if (pScan->column_seen) {
+								Parse *pParse =
+									pWC->pWInfo->pParse;
+								struct coll *coll;
+								assert(pX->pLeft);
+								coll = sqlite3BinaryCompareCollSeq
+									(pParse, pX->pLeft,
+									 pX->pRight);
+								if (coll != pScan->coll)
+									continue;
 							}
 						}
 						if ((pTerm->eOperator & (WO_EQ | WO_IS)) != 0
@@ -376,7 +370,8 @@ whereScanInit(WhereScan * pScan,	/* The WhereScan object being initialized */
 	pScan->pWC = pWC;
 	pScan->pIdxExpr = 0;
 	pScan->idxaff = 0;
-	pScan->zCollName = 0;
+	pScan->coll = NULL;
+	pScan->column_seen = false;
 	if (pIdx) {
 		int j = iColumn;
 		iColumn = pIdx->aiColumn[j];
@@ -384,7 +379,8 @@ whereScanInit(WhereScan * pScan,	/* The WhereScan object being initialized */
 			pScan->pIdxExpr = pIdx->aColExpr->a[j].pExpr;
 		} else if (iColumn >= 0) {
 			pScan->idxaff = pIdx->pTable->aCol[iColumn].affinity;
-			pScan->zCollName = index_collation_name(pIdx, j);
+			pScan->coll = sql_index_collation(pIdx, j);
+			pScan->column_seen = true;
 		}
 	} else if (iColumn == XN_EXPR) {
 		return 0;
@@ -465,16 +461,18 @@ findIndexCol(Parse * pParse,	/* Parse context */
 	     Index * pIdx,	/* Index to match column of */
 	     int iCol)		/* Column of index to match */
 {
-	int i;
-	const char *zColl = index_collation_name(pIdx, iCol);
-
-	for (i = 0; i < pList->nExpr; i++) {
+	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) {
-			struct coll *pColl =
-			    sqlite3ExprCollSeq(pParse, pList->a[i].pExpr);
-			if (pColl && 0 == strcmp(pColl->name, zColl)) {
+		if (p->op == TK_COLUMN &&
+		    p->iColumn == pIdx->aiColumn[iCol] &&
+		    p->iTable == iBase) {
+			bool found;
+			struct coll *coll =
+				sqlite3ExprCollSeq(pParse,
+						   pList->a[i].pExpr,
+						   &found);
+			if (found &&
+			    coll == sql_index_collation(pIdx, iCol)) {
 				return i;
 			}
 		}
@@ -1174,7 +1172,7 @@ whereRangeSkipScanEst(Parse * pParse,		/* Parsing & code generating context */
 	sqlite3_value *p2 = 0;	/* Value extracted from pUpper */
 	sqlite3_value *pVal = 0;	/* Value extracted from record */
 
-	pColl = sqlite3LocateCollSeq(pParse, db, index_collation_name(p, nEq));
+	pColl = sql_index_collation(p, nEq);
 	if (pLower) {
 		rc = sqlite3Stat4ValueFromExpr(pParse, pLower->pExpr->pRight,
 					       aff, &p1);
@@ -2246,8 +2244,7 @@ whereRangeVectorLen(Parse * pParse,	/* Parsing context */
 		pColl = sqlite3BinaryCompareCollSeq(pParse, pLhs, pRhs);
 		if (pColl == 0)
 			break;
-		const char *zCollName = index_collation_name(pIdx, i + nEq);
-		if (zCollName && strcmp(pColl->name, zCollName))
+	        if (sql_index_collation(pIdx, i + nEq) != pColl)
 			break;
 	}
 	return i;
@@ -3235,20 +3232,15 @@ wherePathSatisfiesOrderBy(WhereInfo * pWInfo,	/* The WHERE clause */
 			}
 			if ((pTerm->eOperator & (WO_EQ | WO_IS)) != 0
 			    && pOBExpr->iColumn >= 0) {
-				const char *z1, *z2;
-				pColl =
-				    sqlite3ExprCollSeq(pWInfo->pParse,
-						       pOrderBy->a[i].pExpr);
-				if (!pColl)
-					pColl = sql_default_coll();
-				z1 = pColl->name;
-				pColl =
-				    sqlite3ExprCollSeq(pWInfo->pParse,
-						       pTerm->pExpr);
-				if (!pColl)
-					pColl = sql_default_coll();
-				z2 = pColl->name;
-				if (strcmp(z1, z2) != 0)
+				struct coll *coll1, *coll2;
+				bool found; /* Not used. */
+				coll1 = sqlite3ExprCollSeq(pWInfo->pParse,
+							   pOrderBy->a[i].pExpr,
+							   &found);
+				coll2 = sqlite3ExprCollSeq(pWInfo->pParse,
+							   pTerm->pExpr,
+							   &found);
+				if (coll1 != coll2)
 					continue;
 				testcase(pTerm->pExpr->op == TK_IS);
 			}
@@ -3372,15 +3364,15 @@ wherePathSatisfiesOrderBy(WhereInfo * pWInfo,	/* The WHERE clause */
 						}
 					}
 					if (iColumn >= 0) {
+						bool found;
 						pColl =
 						    sqlite3ExprCollSeq(pWInfo->pParse,
-								       pOrderBy->a[i].pExpr);
-						if (!pColl)
-							pColl = sql_default_coll();
-						const char *zCollName =
-							index_collation_name(pIndex, j);
-						if (strcmp(pColl->name,
-							   zCollName) != 0)
+								       pOrderBy->a[i].pExpr,
+								       &found);
+						struct coll *idx_coll;
+						idx_coll = sql_index_collation(pIndex,
+									       j);
+						if (found && pColl != idx_coll)
 							continue;
 					}
 					isMatch = 1;
diff --git a/src/box/sql/whereInt.h b/src/box/sql/whereInt.h
index 381a1d2..b85cd73 100644
--- a/src/box/sql/whereInt.h
+++ b/src/box/sql/whereInt.h
@@ -293,7 +293,12 @@ struct WhereTerm {
 struct WhereScan {
 	WhereClause *pOrigWC;	/* Original, innermost WhereClause */
 	WhereClause *pWC;	/* WhereClause currently being scanned */
-	const char *zCollName;	/* Required collating sequence, if not NULL */
+	/** Required collating sequence. */
+	struct coll *coll;
+	/** Explicitly specified BINARY collation. */
+	bool cool_binary;
+	/** Flag is set if actual column was encountered. */
+	bool column_seen;
 	Expr *pIdxExpr;		/* Search for this index expression */
 	char idxaff;		/* Must match this affinity, if zCollName!=NULL */
 	unsigned char nEquiv;	/* Number of entries in aEquiv[] */
diff --git a/src/box/sql/whereexpr.c b/src/box/sql/whereexpr.c
index ccdff46..4b14324 100644
--- a/src/box/sql/whereexpr.c
+++ b/src/box/sql/whereexpr.c
@@ -165,12 +165,19 @@ exprCommute(Parse * pParse, Expr * pExpr)
 			 * used by clearing the EP_Collate flag from Y.
 			 */
 			pExpr->pRight->flags &= ~EP_Collate;
-		} else if (sqlite3ExprCollSeq(pParse, pExpr->pLeft) != 0) {
-			/* Neither X nor Y have COLLATE operators, but X has a non-default
-			 * collating sequence.  So add the EP_Collate marker on X to cause
-			 * it to be searched first.
-			 */
-			pExpr->pLeft->flags |= EP_Collate;
+		} else {
+			bool found;
+			sqlite3ExprCollSeq(pParse, pExpr->pLeft, &found);
+			if (found) {
+				/* Neither X nor Y have COLLATE
+				 * operators, but X has a
+				 * non-default collating sequence.
+				 * So add the EP_Collate marker on
+				 * X to cause it to be searched
+				 * first.
+				 */
+				pExpr->pLeft->flags |= EP_Collate;
+			}
 		}
 	}
 	SWAP(pExpr->pRight, pExpr->pLeft);
@@ -842,9 +849,10 @@ termIsEquivalence(Parse * pParse, Expr * pExpr)
 	    sqlite3BinaryCompareCollSeq(pParse, pExpr->pLeft, pExpr->pRight);
 	if (pColl == 0 || sqlite3StrICmp(pColl->name, "BINARY") == 0)
 		return 1;
-	pColl = sqlite3ExprCollSeq(pParse, pExpr->pLeft);
+	bool found;
+	pColl = sqlite3ExprCollSeq(pParse, pExpr->pLeft, &found);
 	zColl1 = pColl ? pColl->name : 0;
-	pColl = sqlite3ExprCollSeq(pParse, pExpr->pRight);
+	pColl = sqlite3ExprCollSeq(pParse, pExpr->pRight, &found);
 	zColl2 = pColl ? pColl->name : 0;
 	return sqlite3_stricmp(zColl1, zColl2) == 0;
 }
-- 
2.11.0

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

* [tarantool-patches] Re: [PATCH] sql: use collation pointers instead of names
  2018-04-13  8:05 [tarantool-patches] [PATCH] sql: use collation pointers instead of names Kirill Yukhin
@ 2018-04-16 13:43 ` Vladislav Shpilevoy
  0 siblings, 0 replies; 17+ messages in thread
From: Vladislav Shpilevoy @ 2018-04-16 13:43 UTC (permalink / raw)
  To: tarantool-patches, Kirill Yukhin

Hello. See below 6 comments.

1. Looks that you did not pushed this patch, because on the
branch I see 3 commits:
[WIP] sql: use collation pointers instead of names
sql: RAW1
sql: RAW2

git diff head~3 --stat shows not the same as the info in this
letter:
18 files changed, 270 insertions(+), 295 deletions(-)
instead of
18 files changed, 272 insertions(+), 295 deletions(-)

Please, push the latest version.

2. Please, specify links to branch and issue after '---' in
the letter.

On 13/04/2018 11:05, Kirill Yukhin wrote:
> Before the change SQL FE used collation names to refer to
> collations. Main data structures were using names as well.
> The patch fixes that and uses explicit pointers to Tarantool's
> `struct coll` during code gen and inside data structures.
> 
> Closes #3205
> ---
>   src/box/sql.c           |  21 ++------
>   src/box/sql/alter.c     |   2 +-
>   src/box/sql/analyze.c   |   9 +---
>   src/box/sql/build.c     | 133 ++++++++++++++++++++++--------------------------
>   src/box/sql/callback.c  |  53 ++-----------------
>   src/box/sql/expr.c      |  54 +++++++++++---------
>   src/box/sql/fkey.c      |  23 +++++----
>   src/box/sql/func.c      |   3 +-
>   src/box/sql/insert.c    |  12 ++---
>   src/box/sql/pragma.c    |   9 +++-
>   src/box/sql/select.c    |  84 +++++++++++++++++-------------
>   src/box/sql/sqliteInt.h |  22 +++++---
>   src/box/sql/vdbe.c      |   2 +-
>   src/box/sql/vdbeaux.c   |   7 +++
>   src/box/sql/vdbesort.c  |   4 +-
>   src/box/sql/where.c     |  98 ++++++++++++++++-------------------
>   src/box/sql/whereInt.h  |   7 ++-
>   src/box/sql/whereexpr.c |  24 ++++++---
>   18 files changed, 272 insertions(+), 295 deletions(-)
> 
> @@ -952,6 +951,7 @@ sqlite3AddCollateType(Parse * pParse, Token * pToken)
>   	Table *p;
>   	int i;
>   	char *zColl;		/* Dequoted name of collation sequence */
> +	struct coll *coll;
>   	sqlite3 *db;
>   
>   	if ((p = pParse->pNewTable) == 0)
> @@ -962,10 +962,10 @@ sqlite3AddCollateType(Parse * pParse, Token * pToken)
>   	if (!zColl)
>   		return;
>   
> -	if (sqlite3LocateCollSeq(pParse, db, zColl)) {
> +	coll =  sqlite3LocateCollSeq(pParse, db, zColl);
> +	if (coll) {

3. Please, use explicit != NULL.

> diff --git a/src/box/sql/sqliteInt.h b/src/box/sql/sqliteInt.h
> index 59662cf..35c9dae 100644
> --- a/src/box/sql/sqliteInt.h
> +++ b/src/box/sql/sqliteInt.h
> @@ -1878,10 +1878,13 @@ struct Column {
>   	char *zName;		/* Name of this column */
>   	enum field_type type;	/* Column type. */
>   	Expr *pDflt;		/* Default value of this column */
> -	char *zColl;		/* Collating sequence.  If NULL, use the default */
> -	enum on_conflict_action notNull;  /* An ON_CONFLICT_ACTION code for
> -					   * handling a NOT NULL constraint
> -					   */
> +	/** Collating sequence. */
> +	struct coll *coll;
> +	/**
> +	 * An ON_CONFLICT_ACTION code for handling a NOT NULL
> +	 * constraint.
> +	 */
> +	enum on_conflict_action notNull;
>   	char affinity;		/* One of the SQLITE_AFF_... values */
>   	u8 szEst;		/* Estimated size of value in this column. sizeof(INT)==1 */
>   	u8 is_primkey;		/* Boolean propertie for being PK */
> @@ -2148,7 +2151,8 @@ struct Index {
>   	Index *pNext;		/* The next index associated with the same table */
>   	Schema *pSchema;	/* Schema containing this index */
>   	u8 *aSortOrder;		/* for each column: True==DESC, False==ASC */
> -	const char **azColl;	/* Array of collation sequence names for index */
> +	/**  Array of collation sequences for index. */
> +	struct coll **coll_array;
>   	Expr *pPartIdxWhere;	/* WHERE clause for partial indices */
>   	ExprList *aColExpr;	/* Column expressions */
>   	int tnum;		/* DB Page containing root of this index */
> @@ -3548,14 +3552,20 @@ void sqlite3AddPrimaryKey(Parse *, ExprList *, int, int, int);
>   void sqlite3AddCheckConstraint(Parse *, Expr *);
>   void sqlite3AddDefaultValue(Parse *, ExprSpan *);
>   void sqlite3AddCollateType(Parse *, Token *);
> + >   const char *
>   column_collation_name(Table *, uint32_t);
> +struct coll *
> +sql_column_collation(Table *, uint32_t);
>   const char *
>   index_collation_name(Index *, uint32_t);
>   struct coll *
> +sql_index_collation(Index *idx, uint32_t column);
> +struct coll *
>   sql_default_coll();
>   bool
>   space_is_view(Table *);
> +
>   void sqlite3EndTable(Parse *, Token *, Token *, Select *);
>   int
>   emit_open_cursor(Parse *, int, int);
> @@ -3845,7 +3855,7 @@ const char *sqlite3ErrName(int);
>   const char *sqlite3ErrStr(int);
>   struct coll *sqlite3FindCollSeq(const char *);
>   struct coll *sqlite3LocateCollSeq(Parse * pParse, sqlite3 * db, const char *zName);
> -struct coll *sqlite3ExprCollSeq(Parse * pParse, Expr * pExpr);
> +struct coll *sqlite3ExprCollSeq(Parse * pParse, Expr * pExpr, bool *found);

4. Please, move a comment into the header, and describe parameters. In
particular 'found' one.

> diff --git a/src/box/sql/whereInt.h b/src/box/sql/whereInt.h
> index 381a1d2..b85cd73 100644
> --- a/src/box/sql/whereInt.h
> +++ b/src/box/sql/whereInt.h
> @@ -293,7 +293,12 @@ struct WhereTerm {
>   struct WhereScan {
>   	WhereClause *pOrigWC;	/* Original, innermost WhereClause */
>   	WhereClause *pWC;	/* WhereClause currently being scanned */
> -	const char *zCollName;	/* Required collating sequence, if not NULL */
> +	/** Required collating sequence. */
> +	struct coll *coll;
> +	/** Explicitly specified BINARY collation. */
> +	bool cool_binary;

5. is_col_binary.

> +	/** Flag is set if actual column was encountered. */
> +	bool column_seen;

6. is_column_seen.

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

* [tarantool-patches] Re: [PATCH] sql: use collation pointers instead of names
  2018-05-08  7:56 ` [tarantool-patches] [PATCH] sql: use collation pointers instead of names Kirill Yukhin
@ 2018-04-17 18:06   ` Vladislav Shpilevoy
  2018-04-18  5:42     ` Kirill Yukhin
  2018-05-08  7:59   ` Kirill Yukhin
  1 sibling, 1 reply; 17+ messages in thread
From: Vladislav Shpilevoy @ 2018-04-17 18:06 UTC (permalink / raw)
  To: tarantool-patches, Kirill Yukhin

Hello. Thanks for fixes, but you did not all of them. I
pushed my fixes as a separate commit on your branch. Please,
look at them, squash into your commit, and then the patch
will be ok for me.

On 17/04/2018 08:35, Kirill Yukhin wrote:
> Before the change SQL FE used collation names to refer to
> collations. Main data structures were using names as well.
> The patch fixes that and uses explicit pointers to Tarantool's
> `struct coll` during code gen and inside data structures.
> 
> Closes #3205
> ---
> Hello Vlad,
> I've fixed all your inputs, thanks!
> 
> Branch: https://github.com/tarantool/tarantool/tree/kyukhin/gh-3205-use-coll-pointers
> Issue: https://github.com/tarantool/tarantool/issues/3205
> 
>   src/box/sql.c           |  21 ++------
>   src/box/sql/alter.c     |   2 +-
>   src/box/sql/analyze.c   |   9 +---
>   src/box/sql/build.c     | 133 ++++++++++++++++++++++--------------------------
>   src/box/sql/callback.c  |  53 ++-----------------
>   src/box/sql/expr.c      | 129 ++++++++++++++++++++++------------------------
>   src/box/sql/fkey.c      |  23 +++++----
>   src/box/sql/func.c      |   3 +-
>   src/box/sql/insert.c    |  12 ++---
>   src/box/sql/pragma.c    |   9 +++-
>   src/box/sql/select.c    | 111 ++++++++++++++++++++++------------------
>   src/box/sql/sqliteInt.h |  40 ++++++++++++---
>   src/box/sql/vdbe.c      |   2 +-
>   src/box/sql/vdbeaux.c   |   7 +++
>   src/box/sql/vdbesort.c  |   4 +-
>   src/box/sql/where.c     | 102 +++++++++++++++++--------------------
>   src/box/sql/whereInt.h  |   7 ++-
>   src/box/sql/whereexpr.c |  34 ++++++++-----
>   18 files changed, 338 insertions(+), 363 deletions(-)
> 
> diff --git a/src/box/sql.c b/src/box/sql.c
> index a6713f1..614917d 100644
> --- a/src/box/sql.c
> +++ b/src/box/sql.c
> @@ -1465,12 +1465,8 @@ int tarantoolSqlite3MakeTableFormat(Table *pTable, void *buf)
>   
>   	for (i = 0; i < n; i++) {
>   		const char *t;
> -		struct coll *coll = NULL;
> +		struct coll *coll = aCol[i].coll;
>   		struct Expr *def = aCol[i].pDflt;
> -		if (aCol[i].zColl != NULL &&
> -		    strcasecmp(aCol[i].zColl, "binary") != 0) {
> -			coll = sqlite3FindCollSeq(aCol[i].zColl);
> -		}
>   		int base_len = 4;
>   		if (coll != NULL)
>   			base_len += 1;
> @@ -1571,28 +1567,19 @@ int tarantoolSqlite3MakeIdxParts(SqliteIndex *pIndex, void *buf)
>   	for (i = 0; i < n; i++) {
>   		int col = pIndex->aiColumn[i];
>   		const char *t;
> -		struct coll * collation = NULL;
>   		if (pk_forced_int == col)
>   			t = "integer";
>   		else
>   			t = convertSqliteAffinity(aCol[col].affinity, aCol[col].notNull == 0);
>   		/* do not decode default collation */
> -		if (sqlite3StrICmp(pIndex->azColl[i], "binary") != 0){
> -			collation = sqlite3FindCollSeq(pIndex->azColl[i]);
> -			/*
> -			 * At this point, the collation has already been found
> -			 * once and the assert should not fire.
> -			 */
> -			assert(collation);
> -		}
> -		p = enc->encode_map(p, collation == NULL ? 4 : 5);
> +		p = enc->encode_map(p, pIndex->coll_array[i] == NULL ? 4 : 5);
>   		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 (collation != NULL){
> +		if (pIndex->coll_array[i] != NULL) {
>   			p = enc->encode_str(p, "collation", sizeof("collation")-1);
> -			p = enc->encode_uint(p, collation->id);
> +			p = enc->encode_uint(p, pIndex->coll_array[i]->id);
>   		}
>   		p = enc->encode_str(p, "is_nullable", 11);
>   		p = enc->encode_bool(p, aCol[col].notNull == ON_CONFLICT_ACTION_NONE);
> diff --git a/src/box/sql/alter.c b/src/box/sql/alter.c
> index 129ef82..b30a973 100644
> --- a/src/box/sql/alter.c
> +++ b/src/box/sql/alter.c
> @@ -296,7 +296,7 @@ sqlite3AlterBeginAddColumn(Parse * pParse, SrcList * pSrc)
>   	for (i = 0; i < pNew->nCol; i++) {
>   		Column *pCol = &pNew->aCol[i];
>   		pCol->zName = sqlite3DbStrDup(db, pCol->zName);
> -		pCol->zColl = 0;
> +		pCol->coll = NULL;
>   		pCol->pDflt = 0;
>   	}
>   	pNew->pSchema = db->pSchema;
> diff --git a/src/box/sql/analyze.c b/src/box/sql/analyze.c
> index 665bfbc..f0054c5 100644
> --- a/src/box/sql/analyze.c
> +++ b/src/box/sql/analyze.c
> @@ -977,18 +977,13 @@ analyzeOneTable(Parse * pParse,	/* Parser context */
>   				VdbeCoverage(v);
>   			}
>   			for (i = 0; i < nColTest; i++) {
> -				const char *zCollName =
> -					index_collation_name(pIdx, i);
> -				char *pColl =
> -				    (char *)sqlite3LocateCollSeq(pParse,
> -								 pParse->db,
> -								 zCollName);
> +				struct coll *coll = sql_index_collation(pIdx, i);
>   				sqlite3VdbeAddOp2(v, OP_Integer, i, regChng);
>   				sqlite3VdbeAddOp3(v, OP_Column, iIdxCur,
>   						  pIdx->aiColumn[i], regTemp);
>   				aGotoChng[i] =
>   				    sqlite3VdbeAddOp4(v, OP_Ne, regTemp, 0,
> -						      regPrev + i, pColl,
> +						      regPrev + i, (char *)coll,
>   						      P4_COLLSEQ);
>   				sqlite3VdbeChangeP5(v, SQLITE_NULLEQ);
>   				VdbeCoverage(v);
> diff --git a/src/box/sql/build.c b/src/box/sql/build.c
> index c6185e4..74b231a 100644
> --- a/src/box/sql/build.c
> +++ b/src/box/sql/build.c
> @@ -300,7 +300,6 @@ sqlite3DeleteColumnNames(sqlite3 * db, Table * pTable)
>   		for (i = 0; i < pTable->nCol; i++, pCol++) {
>   			sqlite3DbFree(db, pCol->zName);
>   			sql_expr_free(db, pCol->pDflt, false);
> -			sqlite3DbFree(db, pCol->zColl);
>   		}
>   		sqlite3DbFree(db, pTable->aCol);
>   	}
> @@ -952,6 +951,7 @@ sqlite3AddCollateType(Parse * pParse, Token * pToken)
>   	Table *p;
>   	int i;
>   	char *zColl;		/* Dequoted name of collation sequence */
> +	struct coll *coll;
>   	sqlite3 *db;
>   
>   	if ((p = pParse->pNewTable) == 0)
> @@ -962,10 +962,10 @@ sqlite3AddCollateType(Parse * pParse, Token * pToken)
>   	if (!zColl)
>   		return;
>   
> -	if (sqlite3LocateCollSeq(pParse, db, zColl)) {
> +	coll =  sqlite3LocateCollSeq(pParse, db, zColl);
> +	if (coll != NULL) {
>   		Index *pIdx;
> -		sqlite3DbFree(db, p->aCol[i].zColl);
> -		p->aCol[i].zColl = zColl;
> +		p->aCol[i].coll = coll;
>   
>   		/* If the column is declared as "<name> PRIMARY KEY COLLATE <type>",
>   		 * then an index may have been created on this column before the
> @@ -973,9 +973,8 @@ sqlite3AddCollateType(Parse * pParse, Token * pToken)
>   		 */
>   		for (pIdx = p->pIndex; pIdx; pIdx = pIdx->pNext) {
>   			assert(pIdx->nColumn == 1);
> -			if (pIdx->aiColumn[0] == i) {
> -				pIdx->azColl[0] = column_collation_name(p, i);
> -			}
> +			if (pIdx->aiColumn[0] == i)
> +				pIdx->coll_array[0] = sql_column_collation(p, i);
>   		}
>   	} else {
>   		sqlite3DbFree(db, zColl);
> @@ -983,14 +982,14 @@ sqlite3AddCollateType(Parse * pParse, Token * pToken)
>   }
>   
>   /**
> - * Return name of given column collation from table.
> + * Return collation of given column from table.
>    *
>    * @param table Table which is used to fetch column.
>    * @param column Number of column.
> - * @retval Pointer to collation's name.
> + * @retval Pointer to collation.
>    */
> -const char *
> -column_collation_name(Table *table, uint32_t column)
> +struct coll *
> +sql_column_collation(Table *table, uint32_t column)
>   {
>   	assert(table != NULL);
>   	uint32_t space_id = SQLITE_PAGENO_TO_SPACEID(table->tnum);
> @@ -1007,15 +1006,12 @@ column_collation_name(Table *table, uint32_t column)
>   	 * In cases mentioned above collation is fetched from
>   	 * SQL specific structures.
>   	 */
> -	if (space == NULL || space_index(space, 0) == NULL)
> -		return table->aCol[column].zColl;
> -
> -	/* "BINARY" is a name for default collation in SQL. */
> -	char *coll_name = (char *)sqlite3StrBINARY;
> -	if (space->format->fields[column].coll != NULL) {
> -		coll_name = space->format->fields[column].coll->name;
> +	if (space == NULL || space_index(space, 0) == NULL) {
> +		assert(column < (uint32_t)table->nCol);
> +		return table->aCol[column].coll;
>   	}
> -	return coll_name;
> +
> +	return space->format->fields[column].coll;
>   }
>   
>   /**
> @@ -1023,30 +1019,30 @@ column_collation_name(Table *table, uint32_t column)
>    *
>    * @param idx Index which is used to fetch column.
>    * @param column Number of column.
> - * @retval Pointer to collation's name.
> + * @retval Pointer to collation.
>    */
> -const char *
> -index_collation_name(Index *idx, uint32_t column)
> +struct coll *
> +sql_index_collation(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)
> -		return (char *)idx->azColl[column];
> +	if (space == NULL) {
> +		assert(column < idx->nColumn);
> +		return idx->coll_array[column];
> +	}
>   
>   	uint32_t index_id = SQLITE_PAGENO_TO_INDEXID(idx->tnum);
>   	struct index *index = space_index(space, index_id);
>   	assert(index != NULL && index->def->key_def->part_count >= column);
> -	struct coll *coll = index->def->key_def->parts[column].coll;
> -	/* "BINARY" is a name for default collation in SQL. */
> -	if (coll == NULL)
> -		return (char *)sqlite3StrBINARY;
> -	return index->def->key_def->parts[column].coll->name;
> +	return index->def->key_def->parts[column].coll;
>   }
>   
>   /**
> @@ -1117,6 +1113,9 @@ sqlite3LocateCollSeq(Parse * pParse, sqlite3 * db, const char *zName)
>   	struct coll *pColl;
>   	initbusy = db->init.busy;
>   
> +	if (sqlite3StrICmp(zName, "binary") == 0)
> +		return NULL;
> +
>   	pColl = sqlite3FindCollSeq(zName);
>   	if (!initbusy && (!pColl)) {
>   		pColl = sqlite3GetCollSeq(pParse, pColl, zName);
> @@ -2623,7 +2622,7 @@ sqlite3AllocateIndexObject(sqlite3 * db,	/* Database connection */
>   	p = sqlite3DbMallocZero(db, nByte + nExtra);
>   	if (p) {
>   		char *pExtra = ((char *)p) + ROUND8(sizeof(Index));
> -		p->azColl = (const char **)pExtra;
> +		p->coll_array = (struct coll **)pExtra;
>   		pExtra += ROUND8(sizeof(char *) * nCol);
>   		p->aiRowLogEst = (LogEst *) pExtra;
>   		pExtra += sizeof(LogEst) * (nCol + 1);
> @@ -2920,7 +2919,7 @@ sqlite3CreateIndex(Parse * pParse,	/* All information about this parse */
>   		goto exit_create_index;
>   	}
>   	assert(EIGHT_BYTE_ALIGNMENT(pIndex->aiRowLogEst));
> -	assert(EIGHT_BYTE_ALIGNMENT(pIndex->azColl));
> +	assert(EIGHT_BYTE_ALIGNMENT(pIndex->coll_array));
>   	pIndex->zName = zExtra;
>   	zExtra += nName + 1;
>   	memcpy(pIndex->zName, zName, nName + 1);
> @@ -2959,7 +2958,7 @@ sqlite3CreateIndex(Parse * pParse,	/* All information about this parse */
>   	for (i = 0, pListItem = pList->a; i < pList->nExpr; i++, pListItem++) {
>   		Expr *pCExpr;	/* The i-th index expression */
>   		int requestedSortOrder;	/* ASC or DESC on the i-th expression */
> -		const char *zColl;	/* Collation sequence name */
> +		struct coll *coll;
>   		sqlite3ResolveSelfReference(pParse, pTab, NC_IdxExpr,
>   					    pListItem->pExpr, 0);
>   		if (pParse->nErr)
> @@ -2978,25 +2977,21 @@ sqlite3CreateIndex(Parse * pParse,	/* All information about this parse */
>   			}
>   			pIndex->aiColumn[i] = (i16) j;
>   		}
> -		zColl = 0;
> +		coll = NULL;
> +		const char *coll_name;
>   		if (pListItem->pExpr->op == TK_COLLATE) {
> -			int nColl;
> -			zColl = pListItem->pExpr->u.zToken;
> -			nColl = sqlite3Strlen30(zColl) + 1;
> -			assert(nExtra >= nColl);
> -			memcpy(zExtra, zColl, nColl);
> -			zColl = zExtra;
> -			zExtra += nColl;
> -			nExtra -= nColl;
> +			coll_name = pListItem->pExpr->u.zToken;
> +			coll = sqlite3GetCollSeq(pParse, 0, coll_name);
> +
> +			if (coll == NULL &&
> +			    sqlite3StrICmp(coll_name, "binary") != 0) {
> +				goto exit_create_index;
> +			}
>   		} else if (j >= 0) {
> -			zColl = column_collation_name(pTab, j);
> -		}
> -		if (!zColl)
> -			zColl = sqlite3StrBINARY;
> -		if (!db->init.busy && !sqlite3LocateCollSeq(pParse, db, zColl)) {
> -			goto exit_create_index;
> +			coll = sql_column_collation(pTab, j);
>   		}
> -		pIndex->azColl[i] = zColl;
> +		pIndex->coll_array[i] = coll;
> +
>   		/* Tarantool: DESC indexes are not supported so far.
>   		 * See gh-3016.
>   		 */
> @@ -3040,14 +3035,13 @@ sqlite3CreateIndex(Parse * pParse,	/* All information about this parse */
>   			if (pIdx->nColumn != pIndex->nColumn)
>   				continue;
>   			for (k = 0; k < pIdx->nColumn; k++) {
> -				const char *z1;
> -				const char *z2;
>   				assert(pIdx->aiColumn[k] >= 0);
>   				if (pIdx->aiColumn[k] != pIndex->aiColumn[k])
>   					break;
> -				z1 = index_collation_name(pIdx, k);
> -				z2 = index_collation_name(pIndex, k);
> -				if (strcmp(z1, z2))
> +				struct coll *coll1, *coll2;
> +				coll1 = sql_index_collation(pIdx, k);
> +				coll2 = sql_index_collation(pIndex, k);
> +				if (coll1 != coll2)
>   					break;
>   			}
>   			if (k == pIdx->nColumn) {
> @@ -3963,19 +3957,18 @@ sqlite3UniqueConstraint(Parse * pParse,	/* Parsing context */
>    * true if it does and false if it does not.
>    */
>   #ifndef SQLITE_OMIT_REINDEX
> -static int
> -collationMatch(const char *zColl, Index * pIndex)
> +static bool
> +collationMatch(struct coll *coll, struct Index *index)
>   {
>   	int i;
> -	assert(zColl != 0);
> -	for (i = 0; i < pIndex->nColumn; i++) {
> -		const char *z = index_collation_name(pIndex, i);
> -		assert(z != 0 || pIndex->aiColumn[i] < 0);
> -		if (pIndex->aiColumn[i] >= 0 && 0 == sqlite3StrICmp(z, zColl)) {
> -			return 1;
> -		}
> +	assert(coll != 0);
> +	for (i = 0; i < index->nColumn; i++) {
> +		struct coll *idx_coll = sql_index_collation(index, i);
> +		assert(idx_coll != 0 || index->aiColumn[i] < 0);
> +		if (index->aiColumn[i] >= 0 && coll == idx_coll)
> +			return true;
>   	}
> -	return 0;
> +	return false;
>   }
>   #endif
>   
> @@ -3985,12 +3978,12 @@ collationMatch(const char *zColl, Index * pIndex)
>    */
>   #ifndef SQLITE_OMIT_REINDEX
>   static void
> -reindexTable(Parse * pParse, Table * pTab, char const *zColl)
> +reindexTable(Parse * pParse, Table * pTab, struct coll *coll)
>   {
>   	Index *pIndex;		/* An index associated with pTab */
>   
>   	for (pIndex = pTab->pIndex; pIndex; pIndex = pIndex->pNext) {
> -		if (zColl == 0 || collationMatch(zColl, pIndex)) {
> +		if (coll == 0 || collationMatch(coll, pIndex)) {
>   			sql_set_multi_write(pParse, false);
>   			sqlite3RefillIndex(pParse, pIndex, -1);
>   		}
> @@ -4005,7 +3998,7 @@ reindexTable(Parse * pParse, Table * pTab, char const *zColl)
>    */
>   #ifndef SQLITE_OMIT_REINDEX
>   static void
> -reindexDatabases(Parse * pParse, char const *zColl)
> +reindexDatabases(Parse * pParse, struct coll *coll)
>   {
>   	sqlite3 *db = pParse->db;	/* The database connection */
>   	HashElem *k;		/* For looping over tables in pSchema */
> @@ -4015,7 +4008,7 @@ reindexDatabases(Parse * pParse, char const *zColl)
>   	for (k = sqliteHashFirst(&db->pSchema->tblHash); k;
>   	     k = sqliteHashNext(k)) {
>   		pTab = (Table *) sqliteHashData(k);
> -		reindexTable(pParse, pTab, zColl);
> +		reindexTable(pParse, pTab, coll);
>   	}
>   }
>   #endif
> @@ -4057,7 +4050,7 @@ sqlite3Reindex(Parse * pParse, Token * pName1, Token * pName2)
>   			return;
>   		pColl = sqlite3FindCollSeq(zColl);
>   		if (pColl) {
> -			reindexDatabases(pParse, zColl);
> +			reindexDatabases(pParse, pColl);
>   			sqlite3DbFree(db, zColl);
>   			return;
>   		}
> @@ -4126,9 +4119,7 @@ sqlite3KeyInfoOfIndex(Parse * pParse, sqlite3 * db, Index * pIdx)
>   	if (pKey) {
>   		assert(sqlite3KeyInfoIsWriteable(pKey));
>   		for (i = 0; i < nCol; i++) {
> -			const char *zColl = index_collation_name(pIdx, i);
> -			pKey->aColl[i] = zColl == sqlite3StrBINARY ? 0 :
> -			    sqlite3LocateCollSeq(pParse, db, zColl);
> +			pKey->aColl[i] = sql_index_collation(pIdx, i);
>   			pKey->aSortOrder[i] = pIdx->aSortOrder[i];
>   		}
>   		if (pParse && pParse->nErr) {
> diff --git a/src/box/sql/callback.c b/src/box/sql/callback.c
> index b176931..0d34e6a 100644
> --- a/src/box/sql/callback.c
> +++ b/src/box/sql/callback.c
> @@ -62,10 +62,9 @@ sqlite3GetCollSeq(Parse * pParse,	/* Parsing context */
>   	struct coll *p;
>   
>   	p = pColl;
> -	if (!p) {
> +	if (p == NULL)
>   		p = sqlite3FindCollSeq(zName);
> -	}
> -	if (p == 0) {
> +	if (p == NULL && (sqlite3StrICmp(zName, "binary") != 0)) {
>   		if (pParse)
>   			sqlite3ErrorMsg(pParse,
>   					"no such collation sequence: %s",
> @@ -102,49 +101,6 @@ sqlite3CheckCollSeq(Parse * pParse, struct coll * pColl)
>   	return SQLITE_OK;
>   }
>   
> -/*
> - * This is the default collating function named "BINARY" which is always
> - * available.
> - * It is hardcoded to support Tarantool's collation interface.
> - */
> -static int
> -binCollFunc(const char *pKey1, size_t nKey1, const char *pKey2, size_t nKey2, const struct coll * collation)
> -{
> -	int rc;
> -	size_t n;
> -	(void) collation;
> -	n = nKey1 < nKey2 ? nKey1 : nKey2;
> -	/* EVIDENCE-OF: R-65033-28449 The built-in BINARY collation compares
> -	 * strings byte by byte using the memcmp() function from the standard C
> -	 * library.
> -	 */
> -	rc = memcmp(pKey1, pKey2, n);
> -	if (rc == 0) {
> -		rc = (int)nKey1 - (int)nKey2;
> -	}
> -	return rc;
> -}
> -
> -/*
> - * This hardcoded structure created just to be called the same way
> - * as collations in Tarantool, to support binary collation easily.
> - */
> -
> -struct coll_plus_name_struct{
> -    struct coll collation;
> -    char name[20]; /* max of possible name lengths */
> -};
> -static struct coll_plus_name_struct binary_coll_with_name =
> -	{{0, 0, COLL_TYPE_ICU, {0}, binCollFunc, 0, sizeof("BINARY"), {}},
> -		"BINARY"};
> -static struct coll * binary_coll = (struct coll*)&binary_coll_with_name;
> -
> -struct coll *
> -sql_default_coll()
> -{
> -	return binary_coll;
> -}
> -
>   /**
>    * Return the coll* pointer for the collation sequence named zName.
>    *
> @@ -158,9 +114,8 @@ sql_default_coll()
>   struct coll *
>   sqlite3FindCollSeq(const char *zName)
>   {
> -	if (zName == NULL || sqlite3StrICmp(zName, "binary")==0){
> -		return binary_coll;
> -	}
> +	if (zName == NULL || sqlite3StrICmp(zName, "binary")==0)
> +		return 0;
>   	return coll_by_name(zName, strlen(zName));
>   }
>   
> diff --git a/src/box/sql/expr.c b/src/box/sql/expr.c
> index 8071314..9ac67fb 100644
> --- a/src/box/sql/expr.c
> +++ b/src/box/sql/expr.c
> @@ -158,21 +158,13 @@ sqlite3ExprSkipCollate(Expr * pExpr)
>   	return pExpr;
>   }
>   
> -/*
> - * Return the collation sequence for the expression pExpr. If
> - * there is no defined collating sequence, return NULL.
> - *
> - * The collating sequence might be determined by a COLLATE operator
> - * or by the presence of a column with a defined collating sequence.
> - * COLLATE operators take first precedence.  Left operands take
> - * precedence over right operands.
> - */
>   struct coll *
> -sqlite3ExprCollSeq(Parse * pParse, Expr * pExpr)
> +sql_expr_coll(Parse *parse, Expr *expr, bool *found)
>   {
> -	struct coll *pColl = 0;
> -	Expr *p = pExpr;
> -	while (p) {
> +	struct coll *coll = 0;
> +	Expr *p = expr;
> +	*found = false;
> +	while (p != NULL) {
>   		int op = p->op;
>   		if (p->flags & EP_Generic)
>   			break;
> @@ -180,23 +172,22 @@ sqlite3ExprCollSeq(Parse * pParse, Expr * pExpr)
>   			p = p->pLeft;
>   			continue;
>   		}
> -		if (op == TK_COLLATE
> -		    || (op == TK_REGISTER && p->op2 == TK_COLLATE)) {
> -			pColl =
> -			    sqlite3GetCollSeq(pParse, 0, p->u.zToken);
> +		if (op == TK_COLLATE ||
> +		    (op == TK_REGISTER && p->op2 == TK_COLLATE)) {
> +			coll = sqlite3GetCollSeq(parse, NULL, p->u.zToken);
> +			*found = true;
>   			break;
>   		}
> -		if ((op == TK_AGG_COLUMN || op == TK_COLUMN
> -		     || op == TK_REGISTER || op == TK_TRIGGER)
> -		    && p->pTab != 0) {
> +		if ((op == TK_AGG_COLUMN || op == TK_COLUMN ||
> +		     op == TK_REGISTER || op == TK_TRIGGER) &&
> +		    p->pTab != 0) {
>   			/* op==TK_REGISTER && p->pTab!=0 happens when pExpr was originally
>   			 * a TK_COLUMN but was previously evaluated and cached in a register
>   			 */
>   			int j = p->iColumn;
>   			if (j >= 0) {
> -				const char *zColl =
> -					column_collation_name(p->pTab, j);
> -				pColl = sqlite3FindCollSeq(zColl);
> +				coll = sql_column_collation(p->pTab, j);
> +				*found = true;
>   			}
>   			break;
>   		}
> @@ -204,40 +195,37 @@ sqlite3ExprCollSeq(Parse * pParse, Expr * pExpr)
>   			if (p->pLeft && (p->pLeft->flags & EP_Collate) != 0) {
>   				p = p->pLeft;
>   			} else {
> -				Expr *pNext = p->pRight;
> +				Expr *next = p->pRight;
>   				/* The Expr.x union is never used at the same time as Expr.pRight */
>   				assert(p->x.pList == 0 || p->pRight == 0);
>   				/* p->flags holds EP_Collate and p->pLeft->flags does not.  And
>   				 * p->x.pSelect cannot.  So if p->x.pLeft exists, it must hold at
>   				 * least one EP_Collate. Thus the following two ALWAYS.
>   				 */
> -				if (p->x.pList != 0
> -				    &&
> +				if (p->x.pList != 0 &&
>   				    ALWAYS(!ExprHasProperty(p, EP_xIsSelect))) {
> -					int i;
> -					for (i = 0;
> +					for (int i = 0;
>   					     ALWAYS(i < p->x.pList->nExpr);
>   					     i++) {
> -						if (ExprHasProperty
> -						    (p->x.pList->a[i].pExpr,
> -						     EP_Collate)) {
> -							pNext =
> -							    p->x.pList->a[i].
> -							    pExpr;
> +						Expr *e;
> +						e = p->x.pList->a[i].pExpr;
> +						if (ExprHasProperty(e,
> +								    EP_Collate)) {
> +							next = e;
>   							break;
>   						}
>   					}
>   				}
> -				p = pNext;
> +				p = next;
>   			}
>   		} else {
>   			break;
>   		}
>   	}
> -	if (sqlite3CheckCollSeq(pParse, pColl)) {
> -		pColl = 0;
> -	}
> -	return pColl;
> +	if (sqlite3CheckCollSeq(parse, coll))
> +		parse = 0;
> +
> +	return coll;
>   }
>   
>   /*
> @@ -344,19 +332,19 @@ binaryCompareP5(Expr * pExpr1, Expr * pExpr2, int jumpIfNull)
>   struct coll *
>   sqlite3BinaryCompareCollSeq(Parse * pParse, Expr * pLeft, Expr * pRight)
>   {
> -	struct coll *pColl;
> +	struct coll *coll;
> +	bool found;
>   	assert(pLeft);
>   	if (pLeft->flags & EP_Collate) {
> -		pColl = sqlite3ExprCollSeq(pParse, pLeft);
> +		coll = sql_expr_coll(pParse, pLeft, &found);
>   	} else if (pRight && (pRight->flags & EP_Collate) != 0) {
> -		pColl = sqlite3ExprCollSeq(pParse, pRight);
> +		coll = sql_expr_coll(pParse, pRight, &found);
>   	} else {
> -		pColl = sqlite3ExprCollSeq(pParse, pLeft);
> -		if (!pColl) {
> -			pColl = sqlite3ExprCollSeq(pParse, pRight);
> -		}
> +		coll = sql_expr_coll(pParse, pLeft, &found);
> +		if (!found)
> +			coll = sql_expr_coll(pParse, pRight, &found);
>   	}
> -	return pColl;
> +	return coll;
>   }
>   
>   /*
> @@ -2520,14 +2508,15 @@ sqlite3FindInIndex(Parse * pParse,	/* Parsing context */
>   						    (pParse, pLhs, pRhs);
>   					int j;
>   
> -					assert(pReq != 0 || pParse->nErr);
>   					for (j = 0; j < nExpr; j++) {
> -						if (pIdx->aiColumn[j]
> -						    != pRhs->iColumn)
> +						if (pIdx->aiColumn[j] !=
> +						    pRhs->iColumn) {
>   							continue;
> -						if (pReq != 0 && strcmp
> -						    (pReq->name,
> -						     index_collation_name(pIdx, j)) != 0) {
> +						}
> +						struct coll *idx_coll;
> +						idx_coll = sql_index_collation(pIdx, j);
> +						if (pReq != NULL &&
> +						    pReq != idx_coll) {
>   							continue;
>   						}
>   						break;
> @@ -2879,11 +2868,13 @@ sqlite3CodeSubselect(Parse * pParse,	/* Parsing context */
>   					affinity = SQLITE_AFF_BLOB;
>   				}
>   				if (pKeyInfo) {
> +					bool found; /* Not Used.  */
>   					assert(sqlite3KeyInfoIsWriteable
>   					       (pKeyInfo));
>   					pKeyInfo->aColl[0] =
> -					    sqlite3ExprCollSeq(pParse,
> -							       pExpr->pLeft);
> +						sql_expr_coll(pParse,
> +							      pExpr->pLeft,
> +							      &found);
>   				}
>   
>   				/* Loop through each expression in <exprlist>. */
> @@ -3140,8 +3131,10 @@ sqlite3ExprCodeIN(Parse * pParse,	/* Parsing and code generating context */
>   	 * This is step (1) in the in-operator.md optimized algorithm.
>   	 */
>   	if (eType == IN_INDEX_NOOP) {
> +		bool found; /* Not used. */
>   		ExprList *pList = pExpr->x.pList;
> -		struct coll *pColl = sqlite3ExprCollSeq(pParse, pExpr->pLeft);
> +		struct coll *coll = sql_expr_coll(pParse, pExpr->pLeft,
> +						   &found);
>   		int labelOk = sqlite3VdbeMakeLabel(v);
>   		int r2, regToFree;
>   		int regCkNull = 0;
> @@ -3161,14 +3154,14 @@ sqlite3ExprCodeIN(Parse * pParse,	/* Parsing and code generating context */
>   			}
>   			if (ii < pList->nExpr - 1 || destIfNull != destIfFalse) {
>   				sqlite3VdbeAddOp4(v, OP_Eq, rLhs, labelOk, r2,
> -						  (void *)pColl, P4_COLLSEQ);
> +						  (void *)coll, P4_COLLSEQ);
>   				VdbeCoverageIf(v, ii < pList->nExpr - 1);
>   				VdbeCoverageIf(v, ii == pList->nExpr - 1);
>   				sqlite3VdbeChangeP5(v, zAff[0]);
>   			} else {
>   				assert(destIfNull == destIfFalse);
>   				sqlite3VdbeAddOp4(v, OP_Ne, rLhs, destIfFalse,
> -						  r2, (void *)pColl,
> +						  r2, (void *)coll,
>   						  P4_COLLSEQ);
>   				VdbeCoverage(v);
>   				sqlite3VdbeChangeP5(v,
> @@ -3277,9 +3270,10 @@ sqlite3ExprCodeIN(Parse * pParse,	/* Parsing and code generating context */
>   	for (i = 0; i < nVector; i++) {
>   		Expr *p;
>   		struct coll *pColl;
> +		bool found;
>   		int r3 = sqlite3GetTempReg(pParse);
>   		p = sqlite3VectorFieldSubexpr(pLeft, i);
> -		pColl = sqlite3ExprCollSeq(pParse, p);
> +		pColl = sql_expr_coll(pParse, p, &found);
>   		/* Tarantool: Replace i -> aiMap [i], since original order of columns
>   		 * is preserved.
>   		 */
> @@ -4066,7 +4060,7 @@ sqlite3ExprCodeTarget(Parse * pParse, Expr * pExpr, int target)
>   			u32 constMask = 0;	/* Mask of function arguments that are constant */
>   			int i;	/* Loop counter */
>   			sqlite3 *db = pParse->db;	/* The database connection */
> -			struct coll *pColl = 0;	/* A collating sequence */
> +			struct coll *coll = 0;	/* A collating sequence */
>   
>   			assert(!ExprHasProperty(pExpr, EP_xIsSelect));
>   			if (ExprHasProperty(pExpr, EP_TokenOnly)) {
> @@ -4133,11 +4127,12 @@ sqlite3ExprCodeTarget(Parse * pParse, Expr * pExpr, int target)
>   					constMask |= MASKBIT32(i);
>   				}
>   				if ((pDef->funcFlags & SQLITE_FUNC_NEEDCOLL) !=
> -				    0 && !pColl) {
> -					pColl =
> -					    sqlite3ExprCollSeq(pParse,
> -							       pFarg->a[i].
> -							       pExpr);
> +				    0 && coll == NULL) {
> +					bool found; /* Not used.  */
> +					coll = sql_expr_coll(pParse,
> +							      pFarg->a[i].
> +							      pExpr,
> +							      &found);
>   				}
>   			}
>   			if (pFarg) {
> @@ -4186,10 +4181,8 @@ sqlite3ExprCodeTarget(Parse * pParse, Expr * pExpr, int target)
>   				r1 = 0;
>   			}
>   			if (pDef->funcFlags & SQLITE_FUNC_NEEDCOLL) {
> -				if (!pColl)
> -					pColl = sql_default_coll();
>   				sqlite3VdbeAddOp4(v, OP_CollSeq, 0, 0, 0,
> -						  (char *)pColl, P4_COLLSEQ);
> +						  (char *)coll, P4_COLLSEQ);
>   			}
>   			sqlite3VdbeAddOp4(v, OP_Function0, constMask, r1,
>   					  target, (char *)pDef, P4_FUNCDEF);
> diff --git a/src/box/sql/fkey.c b/src/box/sql/fkey.c
> index f56b6d9..a0a35e0 100644
> --- a/src/box/sql/fkey.c
> +++ b/src/box/sql/fkey.c
> @@ -287,7 +287,6 @@ sqlite3FkLocateIndex(Parse * pParse,	/* Parse context to store any error in */
>   				int i, j;
>   				for (i = 0; i < nCol; i++) {
>   					i16 iCol = pIdx->aiColumn[i];	/* Index of column in parent tbl */
> -					const char *zDfltColl;	/* Def. collation for column */
>   					char *zIdxCol;	/* Name of indexed column */
>   
>   					if (iCol < 0)
> @@ -297,11 +296,12 @@ sqlite3FkLocateIndex(Parse * pParse,	/* Parse context to store any error in */
>   					 * the default collation sequence for the column, this index is
>   					 * unusable. Bail out early in this case.
>   					 */
> -					zDfltColl =
> -						column_collation_name(pParent,
> -								      iCol);
> -					if (strcmp
> -					    (index_collation_name(pIdx, i), zDfltColl))
> +					struct coll *def_coll;
> +					def_coll = sql_column_collation(pParent,
> +									iCol);
> +					struct coll *coll;
> +					coll = sql_index_collation(pIdx, i);
> +					if (def_coll != coll)
>   						break;
>   
>   					zIdxCol = pParent->aCol[iCol].zName;
> @@ -526,7 +526,6 @@ exprTableRegister(Parse * pParse,	/* Parsing and code generating context */
>   {
>   	Expr *pExpr;
>   	Column *pCol;
> -	const char *zColl;
>   	sqlite3 *db = pParse->db;
>   
>   	pExpr = sqlite3Expr(db, TK_REGISTER, 0);
> @@ -535,9 +534,13 @@ exprTableRegister(Parse * pParse,	/* Parsing and code generating context */
>   			pCol = &pTab->aCol[iCol];
>   			pExpr->iTable = regBase + iCol + 1;
>   			pExpr->affinity = pCol->affinity;
> -			zColl = column_collation_name(pTab, iCol);
> -			pExpr =
> -			    sqlite3ExprAddCollateString(pParse, pExpr, zColl);
> +			const char *coll_name;
> +			if (pCol->coll == NULL && pCol->coll != NULL)
> +				coll_name = pCol->coll->name;
> +			else
> +				coll_name = "binary";
> +			pExpr = sqlite3ExprAddCollateString(pParse, pExpr,
> +							    coll_name);
>   		} else {
>   			pExpr->iTable = regBase;
>   			pExpr->affinity = SQLITE_AFF_INTEGER;
> diff --git a/src/box/sql/func.c b/src/box/sql/func.c
> index 47b45de..dcac22c 100644
> --- a/src/box/sql/func.c
> +++ b/src/box/sql/func.c
> @@ -54,7 +54,7 @@ sqlite3GetFuncCollSeq(sqlite3_context * context)
>   	assert(context->pVdbe != 0);
>   	pOp = &context->pVdbe->aOp[context->iOp - 1];
>   	assert(pOp->opcode == OP_CollSeq);
> -	assert(pOp->p4type == P4_COLLSEQ);
> +	assert(pOp->p4type == P4_COLLSEQ || pOp->p4.pColl == NULL);
>   	return pOp->p4.pColl;
>   }
>   
> @@ -82,7 +82,6 @@ minmaxFunc(sqlite3_context * context, int argc, sqlite3_value ** argv)
>   	assert(argc > 1);
>   	mask = sqlite3_user_data(context) == 0 ? 0 : -1;
>   	pColl = sqlite3GetFuncCollSeq(context);
> -	assert(pColl);
>   	assert(mask == -1 || mask == 0);
>   	iBest = 0;
>   	if (sqlite3_value_type(argv[0]) == SQLITE_NULL)
> diff --git a/src/box/sql/insert.c b/src/box/sql/insert.c
> index ae8dafb..f04496a 100644
> --- a/src/box/sql/insert.c
> +++ b/src/box/sql/insert.c
> @@ -1419,9 +1419,7 @@ sqlite3GenerateConstraintChecks(Parse * pParse,		/* The parser context */
>   					      regIdx : regR);
>   
>   				for (i = 0; i < nPkCol; i++) {
> -					char *p4 = (char *)
> -						sqlite3LocateCollSeq(pParse, db,
> -								     index_collation_name(pPk, i));
> +					char *p4 = (char *)sql_index_collation(pPk, i);
>   					x = pPk->aiColumn[i];
>   					assert(x >= 0);
>   					if (i == (nPkCol - 1)) {
> @@ -1666,8 +1664,8 @@ xferCompatibleIndex(Index * pDest, Index * pSrc)
>   		if (pSrc->aSortOrder[i] != pDest->aSortOrder[i]) {
>   			return 0;	/* Different sort orders */
>   		}
> -		if (strcasecmp(index_collation_name(pSrc, i),
> -			       index_collation_name(pDest, i)) != 0) {
> +		if (sql_index_collation(pSrc, i) !=
> +		    sql_index_collation(pDest, i)) {
>   			return 0;	/* Different collating sequences */
>   		}
>   	}
> @@ -1806,8 +1804,8 @@ xferOptimization(Parse * pParse,	/* Parser context */
>   		if (pDestCol->affinity != pSrcCol->affinity) {
>   			return 0;	/* Affinity must be the same on all columns */
>   		}
> -		if (strcasecmp(column_collation_name(pDest, i),
> -			       column_collation_name(pSrc, i)) != 0) {
> +		if (sql_column_collation(pDest, i) !=
> +		    sql_column_collation(pSrc, i)) {
>   			return 0;	/* Collating sequence must be the same on all columns */
>   		}
>   		if (!table_column_is_nullable(pDest, i)
> diff --git a/src/box/sql/pragma.c b/src/box/sql/pragma.c
> index b724c98..a2a6391 100644
> --- a/src/box/sql/pragma.c
> +++ b/src/box/sql/pragma.c
> @@ -452,13 +452,20 @@ sqlite3Pragma(Parse * pParse, Token * pId,	/* First part of [schema.]id field */
>   								     aCol[cnum].
>   								     zName);
>   						if (pPragma->iArg) {
> +							const char *c_n;
> +							struct coll *coll;
> +							coll = sql_index_collation(pIdx, i);
> +							if (coll != NULL)
> +								c_n = coll->name;
> +							else
> +								c_n = "BINARY";
>   							sqlite3VdbeMultiLoad(v,
>   									     4,
>   									     "isi",
>   									     pIdx->
>   									     aSortOrder
>   									     [i],
> -									     index_collation_name(pIdx, i),
> +									     c_n,
>   									     i <
>   									     mx);
>   						}
> diff --git a/src/box/sql/select.c b/src/box/sql/select.c
> index d97e466..5a39435 100644
> --- a/src/box/sql/select.c
> +++ b/src/box/sql/select.c
> @@ -908,10 +908,12 @@ selectInnerLoop(Parse * pParse,		/* The parser context */
>   
>   				iJump = sqlite3VdbeCurrentAddr(v) + nResultCol;
>   				for (i = 0; i < nResultCol; i++) {
> -					struct coll *pColl =
> -					    sqlite3ExprCollSeq(pParse,
> -							       pEList->a[i].
> -							       pExpr);
> +					bool found;
> +					struct coll *coll =
> +					    sql_expr_coll(pParse,
> +							  pEList->a[i].
> +							  pExpr,
> +							  &found);
>   					if (i < nResultCol - 1) {
>   						sqlite3VdbeAddOp3(v, OP_Ne,
>   								  regResult + i,
> @@ -925,9 +927,11 @@ selectInnerLoop(Parse * pParse,		/* The parser context */
>   								  regPrev + i);
>   						VdbeCoverage(v);
>   					}
> -					sqlite3VdbeChangeP4(v, -1,
> -							    (const char *)pColl,
> -							    P4_COLLSEQ);
> +					if (found) {
> +						sqlite3VdbeChangeP4(v, -1,
> +								    (const char *)coll,
> +								    P4_COLLSEQ);
> +					}
>   					sqlite3VdbeChangeP5(v, SQLITE_NULLEQ);
>   				}
>   				assert(sqlite3VdbeCurrentAddr(v) == iJump
> @@ -1288,11 +1292,12 @@ keyInfoFromExprList(Parse * pParse,	/* Parsing context */
>   		assert(sqlite3KeyInfoIsWriteable(pInfo));
>   		for (i = iStart, pItem = pList->a + iStart; i < nExpr;
>   		     i++, pItem++) {
> -			struct coll *pColl;
> -			pColl = sqlite3ExprCollSeq(pParse, pItem->pExpr);
> -			if (!pColl)
> -				pColl = sql_default_coll();
> -			pInfo->aColl[i - iStart] = pColl;
> +			bool found; /* Not used.  */
> +			struct coll *coll;
> +			coll = sql_expr_coll(pParse,
> +					     pItem->pExpr,
> +					     &found);
> +			pInfo->aColl[i - iStart] = coll;
>   			pInfo->aSortOrder[i - iStart] = pItem->sortOrder;
>   		}
>   	}
> @@ -1935,7 +1940,6 @@ sqlite3SelectAddColumnTypeAndCollation(Parse * pParse,		/* Parsing contexts */
>   	sqlite3 *db = pParse->db;
>   	NameContext sNC;
>   	Column *pCol;
> -	struct coll *pColl;
>   	int i;
>   	Expr *p;
>   	struct ExprList_item *a;
> @@ -1950,6 +1954,7 @@ sqlite3SelectAddColumnTypeAndCollation(Parse * pParse,		/* Parsing contexts */
>   	sNC.pSrcList = pSelect->pSrc;
>   	a = pSelect->pEList->a;
>   	for (i = 0, pCol = pTab->aCol; i < pTab->nCol; i++, pCol++) {
> +		struct coll *coll;
>   		enum field_type type;
>   		p = a[i].pExpr;
>   		type = columnType(&sNC, p, 0, 0, 0, &pCol->szEst);
> @@ -1959,10 +1964,10 @@ sqlite3SelectAddColumnTypeAndCollation(Parse * pParse,		/* Parsing contexts */
>   
>   		if (pCol->affinity == 0)
>   			pCol->affinity = SQLITE_AFF_BLOB;
> -		pColl = sqlite3ExprCollSeq(pParse, p);
> -		if (pColl && pCol->zColl == 0) {
> -			pCol->zColl = sqlite3DbStrDup(db, pColl->name);
> -		}
> +		bool found;
> +		coll = sql_expr_coll(pParse, p, &found);
> +		if (coll && pCol->coll == 0)
> +			pCol->coll = coll;
>   	}
>   	pTab->szTabRow = sqlite3LogEst(szAll * 4);
>   }
> @@ -2123,23 +2128,24 @@ computeLimitRegisters(Parse * pParse, Select * p, int iBreak)
>    * left-most term of the select that has a collating sequence.
>    */
>   static struct coll *
> -multiSelectCollSeq(Parse * pParse, Select * p, int iCol)
> +multiSelectCollSeq(Parse * pParse, Select * p, int iCol, bool *found)
>   {
> -	struct coll *pRet;
> -	if (p->pPrior) {
> -		pRet = multiSelectCollSeq(pParse, p->pPrior, iCol);
> -	} else {
> -		pRet = 0;
> -	}
> +	struct coll *coll;
> +	if (p->pPrior)
> +		coll = multiSelectCollSeq(pParse, p->pPrior, iCol, found);
> +	else
> +		coll = NULL;
>   	assert(iCol >= 0);
>   	/* iCol must be less than p->pEList->nExpr.  Otherwise an error would
>   	 * have been thrown during name resolution and we would not have gotten
>   	 * this far
>   	 */
> -	if (pRet == 0 && ALWAYS(iCol < p->pEList->nExpr)) {
> -		pRet = sqlite3ExprCollSeq(pParse, p->pEList->a[iCol].pExpr);
> +	if (!(*found) && ALWAYS(iCol < p->pEList->nExpr)) {
> +		coll = sql_expr_coll(pParse,
> +				     p->pEList->a[iCol].pExpr,
> +				     found);
>   	}
> -	return pRet;
> +	return coll;
>   }
>   
>   /*
> @@ -2163,22 +2169,29 @@ multiSelectOrderByKeyInfo(Parse * pParse, Select * p, int nExtra)
>   		for (i = 0; i < nOrderBy; i++) {
>   			struct ExprList_item *pItem = &pOrderBy->a[i];
>   			Expr *pTerm = pItem->pExpr;
> -			struct coll *pColl;
> +			struct coll *coll;
>   
>   			if (pTerm->flags & EP_Collate) {
> -				pColl = sqlite3ExprCollSeq(pParse, pTerm);
> +				bool found; /* Not used.  */
> +				coll = sql_expr_coll(pParse, pTerm, &found);
>   			} else {
> -				pColl =
> +				bool found = false;
> +				coll =
>   				    multiSelectCollSeq(pParse, p,
> -						       pItem->u.x.iOrderByCol - 1);
> -				if (pColl == 0)
> -					pColl = sql_default_coll();
> -				pOrderBy->a[i].pExpr =
> -				    sqlite3ExprAddCollateString(pParse, pTerm,
> -								pColl->name);
> +						       pItem->u.x.iOrderByCol - 1,
> +						       &found);
> +				if (coll != NULL) {
> +					pOrderBy->a[i].pExpr =
> +						sqlite3ExprAddCollateString(pParse, pTerm,
> +									    coll->name);
> +				} else {
> +					pOrderBy->a[i].pExpr =
> +						sqlite3ExprAddCollateString(pParse, pTerm,
> +									    "BINARY");
> +				}
>   			}
>   			assert(sqlite3KeyInfoIsWriteable(pRet));
> -			pRet->aColl[i] = pColl;
> +			pRet->aColl[i] = coll;
>   			pRet->aSortOrder[i] = pOrderBy->a[i].sortOrder;
>   		}
>   	}
> @@ -2827,10 +2840,8 @@ multiSelect(Parse * pParse,	/* Parsing context */
>   			goto multi_select_end;
>   		}
>   		for (i = 0, apColl = pKeyInfo->aColl; i < nCol; i++, apColl++) {
> -			*apColl = multiSelectCollSeq(pParse, p, i);
> -			if (0 == *apColl) {
> -				*apColl = sql_default_coll();
> -			}
> +			bool found = false; /* Not used.  */
> +			*apColl = multiSelectCollSeq(pParse, p, i, &found);
>   		}
>   
>   		for (pLoop = p; pLoop; pLoop = pLoop->pPrior) {
> @@ -3260,8 +3271,9 @@ multiSelectOrderBy(Parse * pParse,	/* Parsing context */
>   		if (pKeyDup) {
>   			assert(sqlite3KeyInfoIsWriteable(pKeyDup));
>   			for (i = 0; i < nExpr; i++) {
> +				bool found = false; /* Not used. */
>   				pKeyDup->aColl[i] =
> -				    multiSelectCollSeq(pParse, p, i);
> +					multiSelectCollSeq(pParse, p, i, &found);
>   				pKeyDup->aSortOrder[i] = 0;
>   			}
>   		}
> @@ -5241,22 +5253,21 @@ updateAccumulator(Parse * pParse, AggInfo * pAggInfo)
>   				     regAgg);
>   		}
>   		if (pF->pFunc->funcFlags & SQLITE_FUNC_NEEDCOLL) {
> -			struct coll *pColl = 0;
> +			struct coll *coll = NULL;
>   			struct ExprList_item *pItem;
>   			int j;
>   			assert(pList != 0);	/* pList!=0 if pF->pFunc has NEEDCOLL */
> -			for (j = 0, pItem = pList->a; !pColl && j < nArg;
> +			bool found = false;
> +			for (j = 0, pItem = pList->a; !found && j < nArg;
>   			     j++, pItem++) {
> -				pColl =
> -				    sqlite3ExprCollSeq(pParse, pItem->pExpr);
> -			}
> -			if (!pColl) {
> -				pColl = sql_default_coll();
> +				coll = sql_expr_coll(pParse,
> +						     pItem->pExpr,
> +						     &found);
>   			}
>   			if (regHit == 0 && pAggInfo->nAccumulator)
>   				regHit = ++pParse->nMem;
>   			sqlite3VdbeAddOp4(v, OP_CollSeq, regHit, 0, 0,
> -					  (char *)pColl, P4_COLLSEQ);
> +					  (char *)coll, P4_COLLSEQ);
>   		}
>   		sqlite3VdbeAddOp3(v, OP_AggStep0, 0, regAgg, pF->iMem);
>   		sqlite3VdbeAppendP4(v, pF->pFunc, P4_FUNCDEF);
> diff --git a/src/box/sql/sqliteInt.h b/src/box/sql/sqliteInt.h
> index 59662cf..3c59589 100644
> --- a/src/box/sql/sqliteInt.h
> +++ b/src/box/sql/sqliteInt.h
> @@ -1878,10 +1878,13 @@ struct Column {
>   	char *zName;		/* Name of this column */
>   	enum field_type type;	/* Column type. */
>   	Expr *pDflt;		/* Default value of this column */
> -	char *zColl;		/* Collating sequence.  If NULL, use the default */
> -	enum on_conflict_action notNull;  /* An ON_CONFLICT_ACTION code for
> -					   * handling a NOT NULL constraint
> -					   */
> +	/** Collating sequence. */
> +	struct coll *coll;
> +	/**
> +	 * An ON_CONFLICT_ACTION code for handling a NOT NULL
> +	 * constraint.
> +	 */
> +	enum on_conflict_action notNull;
>   	char affinity;		/* One of the SQLITE_AFF_... values */
>   	u8 szEst;		/* Estimated size of value in this column. sizeof(INT)==1 */
>   	u8 is_primkey;		/* Boolean propertie for being PK */
> @@ -2148,7 +2151,8 @@ struct Index {
>   	Index *pNext;		/* The next index associated with the same table */
>   	Schema *pSchema;	/* Schema containing this index */
>   	u8 *aSortOrder;		/* for each column: True==DESC, False==ASC */
> -	const char **azColl;	/* Array of collation sequence names for index */
> +	/**  Array of collation sequences for index. */
> +	struct coll **coll_array;
>   	Expr *pPartIdxWhere;	/* WHERE clause for partial indices */
>   	ExprList *aColExpr;	/* Column expressions */
>   	int tnum;		/* DB Page containing root of this index */
> @@ -3548,14 +3552,20 @@ void sqlite3AddPrimaryKey(Parse *, ExprList *, int, int, int);
>   void sqlite3AddCheckConstraint(Parse *, Expr *);
>   void sqlite3AddDefaultValue(Parse *, ExprSpan *);
>   void sqlite3AddCollateType(Parse *, Token *);
> +
>   const char *
>   column_collation_name(Table *, uint32_t);
> +struct coll *
> +sql_column_collation(Table *, uint32_t);
>   const char *
>   index_collation_name(Index *, uint32_t);
>   struct coll *
> +sql_index_collation(Index *idx, uint32_t column);
> +struct coll *
>   sql_default_coll();
>   bool
>   space_is_view(Table *);
> +
>   void sqlite3EndTable(Parse *, Token *, Token *, Select *);
>   int
>   emit_open_cursor(Parse *, int, int);
> @@ -3845,7 +3855,25 @@ const char *sqlite3ErrName(int);
>   const char *sqlite3ErrStr(int);
>   struct coll *sqlite3FindCollSeq(const char *);
>   struct coll *sqlite3LocateCollSeq(Parse * pParse, sqlite3 * db, const char *zName);
> -struct coll *sqlite3ExprCollSeq(Parse * pParse, Expr * pExpr);
> +
> +/**
> + * Return the collation sequence for the expression pExpr. If
> + * there is no defined collating sequence, return NULL.
> + *
> + * The collating sequence might be determined by a COLLATE operator
> + * or by the presence of a column with a defined collating sequence.
> + * COLLATE operators take first precedence.  Left operands take
> + * precedence over right operands.
> + *
> + * @param parse Parsing context.
> + * @param expr Expression to scan
> + * @param found Flag set if collation was found
> + *
> + * @retval Pointer to collation.
> + */
> +struct coll *
> +sql_expr_coll(Parse * pParse, Expr * pExpr, bool *found);
> +
>   Expr *sqlite3ExprAddCollateToken(Parse * pParse, Expr *, const Token *, int);
>   Expr *sqlite3ExprAddCollateString(Parse *, Expr *, const char *);
>   Expr *sqlite3ExprSkipCollate(Expr *);
> diff --git a/src/box/sql/vdbe.c b/src/box/sql/vdbe.c
> index 8237183..321ce2f 100644
> --- a/src/box/sql/vdbe.c
> +++ b/src/box/sql/vdbe.c
> @@ -1660,7 +1660,7 @@ case OP_Remainder: {           /* same as TK_REM, in1, in2, out3 */
>    * publicly.  Only built-in functions have access to this feature.
>    */
>   case OP_CollSeq: {
> -	assert(pOp->p4type==P4_COLLSEQ);
> +	assert(pOp->p4type==P4_COLLSEQ || pOp->p4.pColl == NULL);
>   	if (pOp->p1) {
>   		sqlite3VdbeMemSetInt64(&aMem[pOp->p1], 0);
>   	}
> diff --git a/src/box/sql/vdbeaux.c b/src/box/sql/vdbeaux.c
> index bb121a3..e8a0917 100644
> --- a/src/box/sql/vdbeaux.c
> +++ b/src/box/sql/vdbeaux.c
> @@ -3840,6 +3840,13 @@ sqlite3MemCompare(const Mem * pMem1, const Mem * pMem2, const struct coll * pCol
>   		 */
>   		if (pColl) {
>   			return vdbeCompareMemString(pMem1, pMem2, pColl, 0);
> +		} else {
> +			size_t n = pMem1->n < pMem2->n ? pMem1->n : pMem2->n;
> +			int res;
> +			res = memcmp(pMem1->z, pMem2->z, n);
> +			if (res == 0)
> +				res = (int)pMem1->n - (int)pMem2->n;
> +			return res;
>   		}
>   		/* If a NULL pointer was passed as the collate function, fall through
>   		 * to the blob case and use memcmp().
> diff --git a/src/box/sql/vdbesort.c b/src/box/sql/vdbesort.c
> index fc10ef6..be3cc4c 100644
> --- a/src/box/sql/vdbesort.c
> +++ b/src/box/sql/vdbesort.c
> @@ -930,9 +930,7 @@ sqlite3VdbeSorterInit(sqlite3 * db,	/* Database connection (for malloc()) */
>   		}
>   
>   		if ((pKeyInfo->nField + pKeyInfo->nXField) < 13
> -		    && (pKeyInfo->aColl[0] == 0
> -			|| pKeyInfo->aColl[0] == sql_default_coll())
> -		    ) {
> +		    && (pKeyInfo->aColl[0] == NULL)) {
>   			pSorter->typeMask =
>   			    SORTER_TYPE_INTEGER | SORTER_TYPE_TEXT;
>   		}
> diff --git a/src/box/sql/where.c b/src/box/sql/where.c
> index 2a26302..0b4ab47 100644
> --- a/src/box/sql/where.c
> +++ b/src/box/sql/where.c
> @@ -298,26 +298,20 @@ whereScanNext(WhereScan * pScan)
>   					if ((pTerm->eOperator & pScan->
>   					     opMask) != 0) {
>   						/* Verify the affinity and collating sequence match */
> -						if (pScan->zCollName
> -						    && (pTerm->eOperator & WO_ISNULL) == 0) {
> -							struct coll *pColl;
> -							Parse *pParse =
> -							    pWC->pWInfo->pParse;
> +						if ((pTerm->eOperator & WO_ISNULL) == 0) {
>   							pX = pTerm->pExpr;
> -							if (!sqlite3IndexAffinityOk(pX, pScan->idxaff)) {
> -								continue;
> -							}
> -							assert(pX->pLeft);
> -							pColl =
> -							    sqlite3BinaryCompareCollSeq
> -							    (pParse, pX->pLeft,
> -							     pX->pRight);
> -							if (pColl == 0)
> -								pColl =
> -									sql_default_coll();
> -							if (strcmp(pColl->name,
> -									   pScan->zCollName)) {
> +							if (!sqlite3IndexAffinityOk(pX, pScan->idxaff))
>   								continue;
> +							if (pScan->column_seen) {
> +								Parse *pParse =
> +									pWC->pWInfo->pParse;
> +								struct coll *coll;
> +								assert(pX->pLeft);
> +								coll = sqlite3BinaryCompareCollSeq
> +									(pParse, pX->pLeft,
> +									 pX->pRight);
> +								if (coll != pScan->coll)
> +									continue;
>   							}
>   						}
>   						if ((pTerm->eOperator & (WO_EQ | WO_IS)) != 0
> @@ -376,7 +370,8 @@ whereScanInit(WhereScan * pScan,	/* The WhereScan object being initialized */
>   	pScan->pWC = pWC;
>   	pScan->pIdxExpr = 0;
>   	pScan->idxaff = 0;
> -	pScan->zCollName = 0;
> +	pScan->coll = NULL;
> +	pScan->column_seen = false;
>   	if (pIdx) {
>   		int j = iColumn;
>   		iColumn = pIdx->aiColumn[j];
> @@ -384,7 +379,8 @@ whereScanInit(WhereScan * pScan,	/* The WhereScan object being initialized */
>   			pScan->pIdxExpr = pIdx->aColExpr->a[j].pExpr;
>   		} else if (iColumn >= 0) {
>   			pScan->idxaff = pIdx->pTable->aCol[iColumn].affinity;
> -			pScan->zCollName = index_collation_name(pIdx, j);
> +			pScan->coll = sql_index_collation(pIdx, j);
> +			pScan->column_seen = true;
>   		}
>   	} else if (iColumn == XN_EXPR) {
>   		return 0;
> @@ -465,16 +461,17 @@ findIndexCol(Parse * pParse,	/* Parse context */
>   	     Index * pIdx,	/* Index to match column of */
>   	     int iCol)		/* Column of index to match */
>   {
> -	int i;
> -	const char *zColl = index_collation_name(pIdx, iCol);
> -
> -	for (i = 0; i < pList->nExpr; i++) {
> +	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) {
> -			struct coll *pColl =
> -			    sqlite3ExprCollSeq(pParse, pList->a[i].pExpr);
> -			if (pColl && 0 == strcmp(pColl->name, zColl)) {
> +		if (p->op == TK_COLUMN &&
> +		    p->iColumn == pIdx->aiColumn[iCol] &&
> +		    p->iTable == iBase) {
> +			bool found;
> +			struct coll *coll = sql_expr_coll(pParse,
> +							  pList->a[i].pExpr,
> +							  &found);
> +			if (found &&
> +			    coll == sql_index_collation(pIdx, iCol)) {
>   				return i;
>   			}
>   		}
> @@ -1174,7 +1171,7 @@ whereRangeSkipScanEst(Parse * pParse,		/* Parsing & code generating context */
>   	sqlite3_value *p2 = 0;	/* Value extracted from pUpper */
>   	sqlite3_value *pVal = 0;	/* Value extracted from record */
>   
> -	pColl = sqlite3LocateCollSeq(pParse, db, index_collation_name(p, nEq));
> +	pColl = sql_index_collation(p, nEq);
>   	if (pLower) {
>   		rc = sqlite3Stat4ValueFromExpr(pParse, pLower->pExpr->pRight,
>   					       aff, &p1);
> @@ -2246,8 +2243,7 @@ whereRangeVectorLen(Parse * pParse,	/* Parsing context */
>   		pColl = sqlite3BinaryCompareCollSeq(pParse, pLhs, pRhs);
>   		if (pColl == 0)
>   			break;
> -		const char *zCollName = index_collation_name(pIdx, i + nEq);
> -		if (zCollName && strcmp(pColl->name, zCollName))
> +	        if (sql_index_collation(pIdx, i + nEq) != pColl)
>   			break;
>   	}
>   	return i;
> @@ -3147,7 +3143,6 @@ wherePathSatisfiesOrderBy(WhereInfo * pWInfo,	/* The WHERE clause */
>   	WhereLoop *pLoop = 0;	/* Current WhereLoop being processed. */
>   	WhereTerm *pTerm;	/* A single term of the WHERE clause */
>   	Expr *pOBExpr;		/* An expression from the ORDER BY clause */
> -	struct coll *pColl;		/* COLLATE function from an ORDER BY clause term */
>   	Index *pIndex;		/* The index associated with pLoop */
>   	sqlite3 *db = pWInfo->pParse->db;	/* Database connection */
>   	Bitmask obSat = 0;	/* Mask of ORDER BY terms satisfied so far */
> @@ -3235,20 +3230,15 @@ wherePathSatisfiesOrderBy(WhereInfo * pWInfo,	/* The WHERE clause */
>   			}
>   			if ((pTerm->eOperator & (WO_EQ | WO_IS)) != 0
>   			    && pOBExpr->iColumn >= 0) {
> -				const char *z1, *z2;
> -				pColl =
> -				    sqlite3ExprCollSeq(pWInfo->pParse,
> -						       pOrderBy->a[i].pExpr);
> -				if (!pColl)
> -					pColl = sql_default_coll();
> -				z1 = pColl->name;
> -				pColl =
> -				    sqlite3ExprCollSeq(pWInfo->pParse,
> -						       pTerm->pExpr);
> -				if (!pColl)
> -					pColl = sql_default_coll();
> -				z2 = pColl->name;
> -				if (strcmp(z1, z2) != 0)
> +				struct coll *coll1, *coll2;
> +				bool found; /* Not used. */
> +				coll1 = sql_expr_coll(pWInfo->pParse,
> +						      pOrderBy->a[i].pExpr,
> +						      &found);
> +				coll2 = sql_expr_coll(pWInfo->pParse,
> +						      pTerm->pExpr,
> +						      &found);
> +				if (coll1 != coll2)
>   					continue;
>   				testcase(pTerm->pExpr->op == TK_IS);
>   			}
> @@ -3372,15 +3362,15 @@ wherePathSatisfiesOrderBy(WhereInfo * pWInfo,	/* The WHERE clause */
>   						}
>   					}
>   					if (iColumn >= 0) {
> -						pColl =
> -						    sqlite3ExprCollSeq(pWInfo->pParse,
> -								       pOrderBy->a[i].pExpr);
> -						if (!pColl)
> -							pColl = sql_default_coll();
> -						const char *zCollName =
> -							index_collation_name(pIndex, j);
> -						if (strcmp(pColl->name,
> -							   zCollName) != 0)
> +						bool found;
> +						struct coll *coll;
> +						coll = sql_expr_coll(pWInfo->pParse,
> +								     pOrderBy->a[i].pExpr,
> +								     &found);
> +						struct coll *idx_coll;
> +						idx_coll = sql_index_collation(pIndex,
> +									       j);
> +						if (found && coll != idx_coll)
>   							continue;
>   					}
>   					isMatch = 1;
> diff --git a/src/box/sql/whereInt.h b/src/box/sql/whereInt.h
> index 381a1d2..b85cd73 100644
> --- a/src/box/sql/whereInt.h
> +++ b/src/box/sql/whereInt.h
> @@ -293,7 +293,12 @@ struct WhereTerm {
>   struct WhereScan {
>   	WhereClause *pOrigWC;	/* Original, innermost WhereClause */
>   	WhereClause *pWC;	/* WhereClause currently being scanned */
> -	const char *zCollName;	/* Required collating sequence, if not NULL */
> +	/** Required collating sequence. */
> +	struct coll *coll;
> +	/** Explicitly specified BINARY collation. */
> +	bool cool_binary;
> +	/** Flag is set if actual column was encountered. */
> +	bool column_seen;
>   	Expr *pIdxExpr;		/* Search for this index expression */
>   	char idxaff;		/* Must match this affinity, if zCollName!=NULL */
>   	unsigned char nEquiv;	/* Number of entries in aEquiv[] */
> diff --git a/src/box/sql/whereexpr.c b/src/box/sql/whereexpr.c
> index ccdff46..917c2dc 100644
> --- a/src/box/sql/whereexpr.c
> +++ b/src/box/sql/whereexpr.c
> @@ -165,12 +165,19 @@ exprCommute(Parse * pParse, Expr * pExpr)
>   			 * used by clearing the EP_Collate flag from Y.
>   			 */
>   			pExpr->pRight->flags &= ~EP_Collate;
> -		} else if (sqlite3ExprCollSeq(pParse, pExpr->pLeft) != 0) {
> -			/* Neither X nor Y have COLLATE operators, but X has a non-default
> -			 * collating sequence.  So add the EP_Collate marker on X to cause
> -			 * it to be searched first.
> -			 */
> -			pExpr->pLeft->flags |= EP_Collate;
> +		} else {
> +			bool found;
> +			sql_expr_coll(pParse, pExpr->pLeft, &found);
> +			if (found) {
> +				/* Neither X nor Y have COLLATE
> +				 * operators, but X has a
> +				 * non-default collating sequence.
> +				 * So add the EP_Collate marker on
> +				 * X to cause it to be searched
> +				 * first.
> +				 */
> +				pExpr->pLeft->flags |= EP_Collate;
> +			}
>   		}
>   	}
>   	SWAP(pExpr->pRight, pExpr->pLeft);
> @@ -822,7 +829,6 @@ static int
>   termIsEquivalence(Parse * pParse, Expr * pExpr)
>   {
>   	char aff1, aff2;
> -	struct coll *pColl;
>   	const char *zColl1, *zColl2;
>   	if (!OptimizationEnabled(pParse->db, SQLITE_Transitive))
>   		return 0;
> @@ -838,14 +844,16 @@ termIsEquivalence(Parse * pParse, Expr * pExpr)
>   	    ) {
>   		return 0;
>   	}
> -	pColl =
> +	struct coll *coll;
> +	coll =
>   	    sqlite3BinaryCompareCollSeq(pParse, pExpr->pLeft, pExpr->pRight);
> -	if (pColl == 0 || sqlite3StrICmp(pColl->name, "BINARY") == 0)
> +	if (coll == NULL || sqlite3StrICmp(coll->name, "BINARY") == 0)
>   		return 1;
> -	pColl = sqlite3ExprCollSeq(pParse, pExpr->pLeft);
> -	zColl1 = pColl ? pColl->name : 0;
> -	pColl = sqlite3ExprCollSeq(pParse, pExpr->pRight);
> -	zColl2 = pColl ? pColl->name : 0;
> +	bool found;
> +	coll = sql_expr_coll(pParse, pExpr->pLeft, &found);
> +	zColl1 = coll ? coll->name : NULL;
> +	coll = sql_expr_coll(pParse, pExpr->pRight, &found);
> +	zColl2 = coll ? coll->name : NULL;
>   	return sqlite3_stricmp(zColl1, zColl2) == 0;
>   }
>   
> 

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

* [tarantool-patches] Re: [PATCH] sql: use collation pointers instead of names
  2018-04-17 18:06   ` [tarantool-patches] " Vladislav Shpilevoy
@ 2018-04-18  5:42     ` Kirill Yukhin
  0 siblings, 0 replies; 17+ messages in thread
From: Kirill Yukhin @ 2018-04-18  5:42 UTC (permalink / raw)
  To: Vladislav Shpilevoy; +Cc: tarantool-patches

Hello Vlad,
On 17 апр 21:06, Vladislav Shpilevoy wrote:
> Hello. Thanks for fixes, but you did not all of them. I
> pushed my fixes as a separate commit on your branch. Please,
> look at them, squash into your commit, and then the patch
> will be ok for me.
Thanks. I've squashed your changes in and committed
final patch to 2.1 branch.


--
Regards, Kirill Yukhin

> On 17/04/2018 08:35, Kirill Yukhin wrote:
> > Before the change SQL FE used collation names to refer to
> > collations. Main data structures were using names as well.
> > The patch fixes that and uses explicit pointers to Tarantool's
> > `struct coll` during code gen and inside data structures.
> > 
> > Closes #3205

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

* [tarantool-patches] [PATCH 0/2] sql: replace KeyInfo w/ key_def in SQL front-end
@ 2018-05-08  7:56 Kirill Yukhin
  2018-05-08  7:56 ` [tarantool-patches] [PATCH 1/2] sql: introduce sort order to key_part/key_part_def Kirill Yukhin
                   ` (2 more replies)
  0 siblings, 3 replies; 17+ messages in thread
From: Kirill Yukhin @ 2018-05-08  7:56 UTC (permalink / raw)
  To: v.shpilevoy; +Cc: tarantool-patches, Kirill Yukhin

Branch: https://github.com/tarantool/tarantool/issues/3235
Issue: https://github.com/tarantool/tarantool/tree/kyukhin/gh-3235-remove-keyinfo

This patch-set was originally aimed to move sort order column from
SQL front-end legacy structures to Tarantool's core structures.
So, first patch introduces this new field to key_part/key_def
structures.
After half of work was done, I've realized, that KeyInfo structure
is completely redundant and might be superseded by Tarantool's
key_def structure. Second patch is doing that.
It should be mentioned, that KeyInfo memory management was based
on reference counting and many memory copies were avoided. Right
not, key_def is always duplicated. This will be fixed in future,
since this is obvious performance issue.

Kirill Yukhin (2):
  sql: introduce sort order to key_part/key_part_def
  sql: replace KeyInfo with key_def

 src/box/key_def.cc            |  28 +++-
 src/box/key_def.h             |  16 +-
 src/box/schema.cc             |  30 ++--
 src/box/sql.c                 |  28 +++-
 src/box/sql/analyze.c         |   6 +-
 src/box/sql/build.c           | 114 ++++++-------
 src/box/sql/delete.c          |   6 +-
 src/box/sql/expr.c            |  61 ++++---
 src/box/sql/fkey.c            |   2 +-
 src/box/sql/insert.c          |  15 +-
 src/box/sql/parse.y           |  10 +-
 src/box/sql/pragma.c          |  11 +-
 src/box/sql/select.c          | 381 ++++++++++++++++++------------------------
 src/box/sql/sqliteInt.h       |  70 ++++----
 src/box/sql/tarantoolInt.h    |   5 +-
 src/box/sql/update.c          |   6 +-
 src/box/sql/vdbe.c            |  80 +++++----
 src/box/sql/vdbe.h            |  35 ++--
 src/box/sql/vdbeInt.h         |  13 +-
 src/box/sql/vdbeapi.c         | 183 --------------------
 src/box/sql/vdbeaux.c         | 247 ++++++++++-----------------
 src/box/sql/vdbemem.c         |  14 +-
 src/box/sql/vdbesort.c        |  54 +++---
 src/box/sql/where.c           |  32 ++--
 src/box/sql/wherecode.c       |  12 +-
 src/box/tuple.c               |   9 +
 src/box/tuple.h               |   9 +
 test/sql-tap/index1.test.lua  |   2 +
 test/sql-tap/index4.test.lua  |   1 +
 test/sql-tap/selectA.test.lua |   1 +
 30 files changed, 631 insertions(+), 850 deletions(-)

-- 
2.16.2

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

* [tarantool-patches] [PATCH 1/2] sql: introduce sort order to key_part/key_part_def
  2018-05-08  7:56 [tarantool-patches] [PATCH 0/2] sql: replace KeyInfo w/ key_def in SQL front-end Kirill Yukhin
@ 2018-05-08  7:56 ` Kirill Yukhin
  2018-05-08 16:02   ` [tarantool-patches] " Vladislav Shpilevoy
  2018-05-08  7:56 ` [tarantool-patches] [PATCH] sql: use collation pointers instead of names Kirill Yukhin
  2018-05-08  7:56 ` [tarantool-patches] [PATCH 2/2] sql: replace KeyInfo with key_def Kirill Yukhin
  2 siblings, 1 reply; 17+ messages in thread
From: Kirill Yukhin @ 2018-05-08  7:56 UTC (permalink / raw)
  To: v.shpilevoy; +Cc: tarantool-patches, Kirill Yukhin

Legacy SQL DD structs contained sort_order, defined per
index column. During integration, those structs are to be
vanished. So, introduce new field to part entity of Tarantool.
This field states for sorting order of given part in give index.
This field is ignored by Tarantool everywhere excpept for
some of nested queries in SQL.

Patch also replaces usages of SQL's stored sort order w/ this new
field.

Part of #3235
---
 src/box/key_def.cc      | 28 ++++++++++++++++++++++------
 src/box/key_def.h       | 16 +++++++++++++++-
 src/box/schema.cc       | 30 ++++++++++++++++++++----------
 src/box/sql.c           | 11 +++++++++--
 src/box/sql/build.c     | 47 ++++++++++++++++++++++++++++++++++++-----------
 src/box/sql/expr.c      |  9 ++++-----
 src/box/sql/insert.c    |  3 ++-
 src/box/sql/parse.y     | 10 +++++-----
 src/box/sql/pragma.c    |  7 ++++---
 src/box/sql/select.c    |  2 +-
 src/box/sql/sqliteInt.h | 20 ++++++++++++--------
 src/box/sql/vdbe.h      |  1 +
 src/box/sql/where.c     | 11 ++++++-----
 src/box/sql/wherecode.c | 10 ++++++----
 14 files changed, 143 insertions(+), 62 deletions(-)

diff --git a/src/box/key_def.cc b/src/box/key_def.cc
index 98719c2..9f1a373 100644
--- a/src/box/key_def.cc
+++ b/src/box/key_def.cc
@@ -36,12 +36,15 @@
 #include "schema_def.h"
 #include "coll_cache.h"
 
+const char *sort_order_strs[] = { "asc", "desc", "undef" };
+
 static const struct key_part_def key_part_def_default = {
 	0,
 	field_type_MAX,
 	COLL_NONE,
 	false,
-	ON_CONFLICT_ACTION_ABORT
+	ON_CONFLICT_ACTION_ABORT,
+	SORT_ORDER_ASC
 };
 
 static int64_t
@@ -55,6 +58,7 @@ part_type_by_name_wrapper(const char *str, uint32_t len)
 #define PART_OPT_COLLATION	 "collation"
 #define PART_OPT_NULLABILITY	 "is_nullable"
 #define PART_OPT_NULLABLE_ACTION "nullable_action"
+#define PART_OPT_SORT_ORDER	 "sort_order"
 
 const struct opt_def part_def_reg[] = {
 	OPT_DEF_ENUM(PART_OPT_TYPE, field_type, struct key_part_def, type,
@@ -65,6 +69,8 @@ const struct opt_def part_def_reg[] = {
 		is_nullable),
 	OPT_DEF_ENUM(PART_OPT_NULLABLE_ACTION, on_conflict_action,
 		     struct key_part_def, nullable_action, NULL),
+	OPT_DEF_ENUM(PART_OPT_SORT_ORDER, sort_order, struct key_part_def,
+		     sort_order, NULL),
 	OPT_END,
 };
 
@@ -169,7 +175,7 @@ key_def_new_with_parts(struct key_part_def *parts, uint32_t part_count)
 			}
 		}
 		key_def_set_part(def, i, part->fieldno, part->type,
-				 part->nullable_action, coll);
+				 part->nullable_action, coll, part->sort_order);
 	}
 	return def;
 }
@@ -199,7 +205,8 @@ box_key_def_new(uint32_t *fields, uint32_t *types, uint32_t part_count)
 	for (uint32_t item = 0; item < part_count; ++item) {
 		key_def_set_part(key_def, item, fields[item],
 				 (enum field_type)types[item],
-				 key_part_def_default.nullable_action, NULL);
+				 key_part_def_default.nullable_action,
+				 NULL, SORT_ORDER_ASC);
 	}
 	return key_def;
 }
@@ -252,7 +259,7 @@ key_part_cmp(const struct key_part *parts1, uint32_t part_count1,
 void
 key_def_set_part(struct key_def *def, uint32_t part_no, uint32_t fieldno,
 		 enum field_type type, enum on_conflict_action nullable_action,
-		 struct coll *coll)
+		 struct coll *coll, enum sort_order sort_order)
 {
 	assert(part_no < def->part_count);
 	assert(type < field_type_MAX);
@@ -261,6 +268,7 @@ key_def_set_part(struct key_def *def, uint32_t part_no, uint32_t fieldno,
 	def->parts[part_no].fieldno = fieldno;
 	def->parts[part_no].type = type;
 	def->parts[part_no].coll = coll;
+	def->parts[part_no].sort_order = sort_order;
 	column_mask_set_fieldno(&def->column_mask, fieldno);
 	/**
 	 * When all parts are set, initialize the tuple
@@ -510,6 +518,12 @@ key_def_decode_parts(struct key_part_def *parts, uint32_t part_count,
 				 "nullable action properties");
 			return -1;
 		}
+		if (part->sort_order == sort_order_MAX) {
+			diag_set(ClientError, ER_WRONG_INDEX_OPTIONS,
+				 i + TUPLE_INDEX_BASE,
+				 "index part: unknown sort order");
+			return -1;
+		}
 	}
 	return 0;
 }
@@ -572,7 +586,8 @@ key_def_merge(const struct key_def *first, const struct key_def *second)
 	end = part + first->part_count;
 	for (; part != end; part++) {
 		key_def_set_part(new_def, pos++, part->fieldno, part->type,
-				 part->nullable_action, part->coll);
+				 part->nullable_action, part->coll,
+				 part->sort_order);
 	}
 
 	/* Set-append second key def's part to the new key def. */
@@ -582,7 +597,8 @@ key_def_merge(const struct key_def *first, const struct key_def *second)
 		if (key_def_find(first, part->fieldno))
 			continue;
 		key_def_set_part(new_def, pos++, part->fieldno, part->type,
-				 part->nullable_action, part->coll);
+				 part->nullable_action, part->coll,
+				 part->sort_order);
 	}
 	return new_def;
 }
diff --git a/src/box/key_def.h b/src/box/key_def.h
index a02bfa1..4b18175 100644
--- a/src/box/key_def.h
+++ b/src/box/key_def.h
@@ -45,6 +45,16 @@ extern "C" {
 /* MsgPack type names */
 extern const char *mp_type_strs[];
 
+/* Sorting order of a part. */
+extern const char *sort_order_strs[];
+
+enum sort_order {
+	SORT_ORDER_ASC = 0,
+	SORT_ORDER_DESC,
+	SORT_ORDER_UNDEF,
+	sort_order_MAX
+};
+
 struct key_part_def {
 	/** Tuple field index for this part. */
 	uint32_t fieldno;
@@ -56,6 +66,8 @@ struct key_part_def {
 	bool is_nullable;
 	/** Action to perform if NULL constraint failed. */
 	enum on_conflict_action nullable_action;
+	/** Part sort order. */
+	enum sort_order sort_order;
 };
 
 /**
@@ -74,6 +86,8 @@ struct key_part {
 	struct coll *coll;
 	/** Action to perform if NULL constraint failed. */
 	enum on_conflict_action nullable_action;
+	/** Part sort order. */
+	enum sort_order sort_order;
 };
 
 struct key_def;
@@ -264,7 +278,7 @@ key_def_dump_parts(const struct key_def *def, struct key_part_def *parts);
 void
 key_def_set_part(struct key_def *def, uint32_t part_no, uint32_t fieldno,
 		 enum field_type type, enum on_conflict_action nullable_action,
-		 struct coll *coll);
+		 struct coll *coll, enum sort_order sort_order);
 
 /**
  * Update 'has_optional_parts' of @a key_def with correspondence
diff --git a/src/box/schema.cc b/src/box/schema.cc
index 2e14ec2..c3a8f93 100644
--- a/src/box/schema.cc
+++ b/src/box/schema.cc
@@ -279,13 +279,15 @@ schema_init()
 	auto key_def_guard = make_scoped_guard([&] { key_def_delete(key_def); });
 
 	key_def_set_part(key_def, 0 /* part no */, 0 /* field no */,
-			 FIELD_TYPE_STRING, ON_CONFLICT_ACTION_ABORT, NULL);
+			 FIELD_TYPE_STRING, ON_CONFLICT_ACTION_ABORT, NULL,
+			 SORT_ORDER_ASC);
 	sc_space_new(BOX_SCHEMA_ID, "_schema", key_def, &on_replace_schema,
 		     NULL);
 
 	/* _space - home for all spaces. */
 	key_def_set_part(key_def, 0 /* part no */, 0 /* field no */,
-			 FIELD_TYPE_UNSIGNED, ON_CONFLICT_ACTION_ABORT, NULL);
+			 FIELD_TYPE_UNSIGNED, ON_CONFLICT_ACTION_ABORT, NULL,
+			 SORT_ORDER_ASC);
 
 	/* _collation - collation description. */
 	sc_space_new(BOX_COLLATION_ID, "_collation", key_def,
@@ -329,7 +331,8 @@ schema_init()
 
 	/* _trigger - all existing SQL triggers. */
 	key_def_set_part(key_def, 0 /* part no */, 0 /* field no */,
-			 FIELD_TYPE_STRING, ON_CONFLICT_ACTION_ABORT, NULL);
+			 FIELD_TYPE_STRING, ON_CONFLICT_ACTION_ABORT, NULL,
+			 SORT_ORDER_ASC);
 	sc_space_new(BOX_TRIGGER_ID, "_trigger", key_def, &on_replace_trigger, NULL);
 
 	key_def_delete(key_def);
@@ -338,19 +341,23 @@ schema_init()
 		diag_raise();
 	/* space no */
 	key_def_set_part(key_def, 0 /* part no */, 0 /* field no */,
-			 FIELD_TYPE_UNSIGNED, ON_CONFLICT_ACTION_ABORT, NULL);
+			 FIELD_TYPE_UNSIGNED, ON_CONFLICT_ACTION_ABORT, NULL,
+			 SORT_ORDER_ASC);
 	/* index no */
 	key_def_set_part(key_def, 1 /* part no */, 1 /* field no */,
-			 FIELD_TYPE_UNSIGNED, ON_CONFLICT_ACTION_ABORT, NULL);
+			 FIELD_TYPE_UNSIGNED, ON_CONFLICT_ACTION_ABORT, NULL,
+			 SORT_ORDER_ASC);
 	sc_space_new(BOX_INDEX_ID, "_index", key_def,
 		     &alter_space_on_replace_index, &on_stmt_begin_index);
 
 	/* space name */
 	key_def_set_part(key_def, 0 /* part no */, 0 /* field no */,
-			 FIELD_TYPE_STRING, ON_CONFLICT_ACTION_ABORT, NULL);
+			 FIELD_TYPE_STRING, ON_CONFLICT_ACTION_ABORT, NULL,
+			 SORT_ORDER_ASC);
 	/* index name */
 	key_def_set_part(key_def, 1 /* part no */, 1 /* field no */,
-			 FIELD_TYPE_STRING, ON_CONFLICT_ACTION_ABORT, NULL);
+			 FIELD_TYPE_STRING, ON_CONFLICT_ACTION_ABORT, NULL,
+			 SORT_ORDER_ASC);
 	/* _sql_stat1 - a simpler statistics on space, seen in SQL. */
 	sc_space_new(BOX_SQL_STAT1_ID, "_sql_stat1", key_def, NULL, NULL);
 
@@ -361,13 +368,16 @@ schema_init()
 
 	/* space name */
 	key_def_set_part(key_def, 0 /* part no */, 0 /* field no */,
-			 FIELD_TYPE_STRING, ON_CONFLICT_ACTION_ABORT, NULL);
+			 FIELD_TYPE_STRING, ON_CONFLICT_ACTION_ABORT, NULL,
+			 SORT_ORDER_ASC);
 	/* index name */
 	key_def_set_part(key_def, 1 /* part no */, 1 /* field no */,
-			 FIELD_TYPE_STRING, ON_CONFLICT_ACTION_ABORT, NULL);
+			 FIELD_TYPE_STRING, ON_CONFLICT_ACTION_ABORT, NULL,
+			 SORT_ORDER_ASC);
 	/* sample */
 	key_def_set_part(key_def, 2 /* part no */, 5 /* field no */,
-			 FIELD_TYPE_SCALAR, ON_CONFLICT_ACTION_ABORT, NULL);
+			 FIELD_TYPE_SCALAR, ON_CONFLICT_ACTION_ABORT, NULL,
+			 SORT_ORDER_ASC);
 	/* _sql_stat4 - extensive statistics on space, seen in SQL. */
 	sc_space_new(BOX_SQL_STAT4_ID, "_sql_stat4", key_def, NULL, NULL);
 }
diff --git a/src/box/sql.c b/src/box/sql.c
index 166bb71..838fcf6 100644
--- a/src/box/sql.c
+++ b/src/box/sql.c
@@ -384,7 +384,8 @@ int tarantoolSqlite3EphemeralCreate(BtCursor *pCur, uint32_t field_count,
 				 part /* filed no */,
 				 FIELD_TYPE_SCALAR,
 				 ON_CONFLICT_ACTION_NONE /* nullable_action */,
-				 aColl /* coll */);
+				 aColl /* coll */,
+				 SORT_ORDER_ASC);
 	}
 
 	struct index_def *ephemer_index_def =
@@ -1555,7 +1556,7 @@ int tarantoolSqlite3MakeIdxParts(SqliteIndex *pIndex, void *buf)
 		else
 			t = convertSqliteAffinity(aCol[col].affinity, aCol[col].notNull == 0);
 		/* do not decode default collation */
-		p = enc->encode_map(p, pIndex->coll_array[i] == NULL ? 4 : 5);
+		p = enc->encode_map(p, pIndex->coll_array[i] == NULL ? 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);
@@ -1569,6 +1570,12 @@ int tarantoolSqlite3MakeIdxParts(SqliteIndex *pIndex, void *buf)
 		p = enc->encode_str(p, "nullable_action", 15);
 		const char *action_str = on_conflict_action_strs[aCol[col].notNull];
 		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];
+		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);
 }
diff --git a/src/box/sql/build.c b/src/box/sql/build.c
index a2b712a..ae662fb 100644
--- a/src/box/sql/build.c
+++ b/src/box/sql/build.c
@@ -976,7 +976,7 @@ sqlite3AddPrimaryKey(Parse * pParse,	/* Parsing context */
 	if (nTerm == 1
 	    && pCol
 	    && (sqlite3ColumnType(pCol) == FIELD_TYPE_INTEGER)
-	    && sortOrder != SQLITE_SO_DESC) {
+	    && sortOrder != SORT_ORDER_DESC) {
 		assert(autoInc == 0 || autoInc == 1);
 		pTab->iPKey = iCol;
 		pTab->keyConf = (u8) onError;
@@ -1125,6 +1125,30 @@ sql_index_collation(Index *idx, uint32_t column)
 	return index->def->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];
+	}
+
+	uint32_t index_id = SQLITE_PAGENO_TO_INDEXID(idx->tnum);
+	struct index *index = space_index(space, index_id);
+	assert(index != NULL && index->def->key_def->part_count >= column);
+	return index->def->key_def->parts[column].sort_order;
+}
+
 /**
  * Return true if space which corresponds to
  * given table has view option.
@@ -2694,11 +2718,11 @@ sqlite3AllocateIndexObject(sqlite3 * db,	/* Database connection */
 	Index *p;		/* Allocated index object */
 	int nByte;		/* Bytes of space for Index object + arrays */
 
-	nByte = ROUND8(sizeof(Index)) +	/* Index structure  */
-	    ROUND8(sizeof(char *) * nCol) +	/* Index.azColl     */
-	    ROUND8(sizeof(LogEst) * (nCol + 1) +	/* Index.aiRowLogEst   */
-		   sizeof(i16) * nCol +	/* Index.aiColumn   */
-		   sizeof(u8) * nCol);	/* Index.aSortOrder */
+	nByte = ROUND8(sizeof(Index)) +		    /* Index structure  */
+	    ROUND8(sizeof(char *) * nCol) +	    /* Index.azColl     */
+	    ROUND8(sizeof(LogEst) * (nCol + 1) +    /* Index.aiRowLogEst   */
+		   sizeof(i16) * nCol +		    /* Index.aiColumn   */
+		   sizeof(enum sort_order) * nCol); /* Index.sort_order */
 	p = sqlite3DbMallocZero(db, nByte + nExtra);
 	if (p) {
 		char *pExtra = ((char *)p) + ROUND8(sizeof(Index));
@@ -2708,7 +2732,7 @@ sqlite3AllocateIndexObject(sqlite3 * db,	/* Database connection */
 		pExtra += sizeof(LogEst) * (nCol + 1);
 		p->aiColumn = (i16 *) pExtra;
 		pExtra += sizeof(i16) * nCol;
-		p->aSortOrder = (u8 *) pExtra;
+		p->sort_order = (enum sort_order *) pExtra;
 		p->nColumn = nCol;
 		*ppExtra = ((char *)p) + nByte;
 	}
@@ -3037,7 +3061,7 @@ sqlite3CreateIndex(Parse * pParse,	/* All information about this parse */
 	 */
 	for (i = 0, pListItem = pList->a; i < pList->nExpr; i++, pListItem++) {
 		Expr *pCExpr;	/* The i-th index expression */
-		int requestedSortOrder;	/* ASC or DESC on the i-th expression */
+		enum sort_order requested_so;	/* ASC or DESC on the i-th expression */
 		sqlite3ResolveSelfReference(pParse, pTab, NC_IdxExpr,
 					    pListItem->pExpr, 0);
 		if (pParse->nErr)
@@ -3075,8 +3099,8 @@ sqlite3CreateIndex(Parse * pParse,	/* All information about this parse */
 		/* Tarantool: DESC indexes are not supported so far.
 		 * See gh-3016.
 		 */
-		requestedSortOrder = pListItem->sortOrder & 0;
-		pIndex->aSortOrder[i] = (u8) requestedSortOrder;
+		requested_so = pListItem->sortOrder & 0;
+		pIndex->sort_order[i] = requested_so;
 	}
 
 	sqlite3DefaultRowEst(pIndex);
@@ -4199,7 +4223,8 @@ sqlite3KeyInfoOfIndex(Parse * pParse, sqlite3 * db, Index * pIdx)
 		assert(sqlite3KeyInfoIsWriteable(pKey));
 		for (i = 0; i < nCol; i++) {
 			pKey->aColl[i] = sql_index_collation(pIdx, i);
-			pKey->aSortOrder[i] = pIdx->aSortOrder[i];
+			pKey->aSortOrder[i] = sql_index_column_sort_order(pIdx,
+									  i);
 		}
 		if (pParse && pParse->nErr) {
 			sqlite3KeyInfoUnref(pKey);
diff --git a/src/box/sql/expr.c b/src/box/sql/expr.c
index 0c86761..cc969ca 100644
--- a/src/box/sql/expr.c
+++ b/src/box/sql/expr.c
@@ -1764,11 +1764,9 @@ sqlite3ExprListSetSortOrder(ExprList * p, int iSortOrder)
 {
 	if (p == 0)
 		return;
-	assert(SQLITE_SO_UNDEFINED < 0 && SQLITE_SO_ASC >= 0
-	       && SQLITE_SO_DESC > 0);
 	assert(p->nExpr > 0);
-	if (iSortOrder < 0) {
-		assert(p->a[p->nExpr - 1].sortOrder == SQLITE_SO_ASC);
+	if (iSortOrder == SORT_ORDER_UNDEF) {
+		assert(p->a[p->nExpr - 1].sortOrder == SORT_ORDER_ASC);
 		return;
 	}
 	p->a[p->nExpr - 1].sortOrder = (u8) iSortOrder;
@@ -2529,7 +2527,8 @@ sqlite3FindInIndex(Parse * pParse,	/* Parsing context */
 					assert(IN_INDEX_INDEX_DESC ==
 					       IN_INDEX_INDEX_ASC + 1);
 					eType = IN_INDEX_INDEX_ASC +
-						pIdx->aSortOrder[0];
+						sql_index_column_sort_order(pIdx,
+									    0);
 
 					if (prRhsHasNull) {
 #ifdef SQLITE_ENABLE_COLUMN_USED_MASK
diff --git a/src/box/sql/insert.c b/src/box/sql/insert.c
index 939b5e3..1a34f71 100644
--- a/src/box/sql/insert.c
+++ b/src/box/sql/insert.c
@@ -1653,7 +1653,8 @@ xferCompatibleIndex(Index * pDest, Index * pSrc)
 				return 0;	/* Different expressions in the index */
 			}
 		}
-		if (pSrc->aSortOrder[i] != pDest->aSortOrder[i]) {
+		if (sql_index_column_sort_order(pSrc, i) !=
+		    sql_index_column_sort_order(pDest, i)) {
 			return 0;	/* Different sort orders */
 		}
 		if (sql_index_collation(pSrc, i) !=
diff --git a/src/box/sql/parse.y b/src/box/sql/parse.y
index b078e20..249d20d 100644
--- a/src/box/sql/parse.y
+++ b/src/box/sql/parse.y
@@ -684,9 +684,9 @@ sortlist(A) ::= expr(Y) sortorder(Z). {
 
 %type sortorder {int}
 
-sortorder(A) ::= ASC.           {A = SQLITE_SO_ASC;}
-sortorder(A) ::= DESC.          {A = SQLITE_SO_DESC;}
-sortorder(A) ::= .              {A = SQLITE_SO_UNDEFINED;}
+sortorder(A) ::= ASC.           {A = SORT_ORDER_ASC;}
+sortorder(A) ::= DESC.          {A = SORT_ORDER_DESC;}
+sortorder(A) ::= .              {A = SORT_ORDER_UNDEF;}
 
 %type groupby_opt {ExprList*}
 %destructor groupby_opt {sqlite3ExprListDelete(pParse->db, $$);}
@@ -1244,7 +1244,7 @@ cmd ::= createkw(S) uniqueflag(U) INDEX ifnotexists(NE) nm(X)
         ON nm(Y) LP sortlist(Z) RP where_opt(W). {
   sqlite3CreateIndex(pParse, &X, 
                      sqlite3SrcListAppend(pParse->db,0,&Y), Z, U,
-                      &S, W, SQLITE_SO_ASC, NE, SQLITE_IDXTYPE_APPDEF);
+                      &S, W, SORT_ORDER_ASC, NE, SQLITE_IDXTYPE_APPDEF);
 }
 
 %type uniqueflag {int}
@@ -1279,7 +1279,7 @@ uniqueflag(A) ::= .        {A = ON_CONFLICT_ACTION_NONE;}
     int sortOrder
   ){
     ExprList *p = sqlite3ExprListAppend(pParse, pPrior, 0);
-    if( (hasCollate || sortOrder!=SQLITE_SO_UNDEFINED)
+    if( (hasCollate || sortOrder != SORT_ORDER_UNDEF)
         && pParse->db->init.busy==0
     ){
       sqlite3ErrorMsg(pParse, "syntax error after column name \"%.*s\"",
diff --git a/src/box/sql/pragma.c b/src/box/sql/pragma.c
index e41f69b..738c254 100644
--- a/src/box/sql/pragma.c
+++ b/src/box/sql/pragma.c
@@ -463,12 +463,13 @@ sqlite3Pragma(Parse * pParse, Token * pId,	/* First part of [schema.]id field */
 								c_n = coll->name;
 							else
 								c_n = "BINARY";
+							enum sort_order sort_order;
+							sort_order = sql_index_column_sort_order(pIdx,
+												 i);
 							sqlite3VdbeMultiLoad(v,
 									     4,
 									     "isi",
-									     pIdx->
-									     aSortOrder
-									     [i],
+									     sort_order,
 									     c_n,
 									     i <
 									     mx);
diff --git a/src/box/sql/select.c b/src/box/sql/select.c
index 5a50413..aff534d3 100644
--- a/src/box/sql/select.c
+++ b/src/box/sql/select.c
@@ -5620,7 +5620,7 @@ sqlite3Select(Parse * pParse,		/* The parser context */
 		 * and plus one column for ID.
 		 */
 		int nCols = pEList->nExpr + sSort.pOrderBy->nExpr + 1;
-		if (pKeyInfo->aSortOrder[0] == SQLITE_SO_DESC) {
+		if (pKeyInfo->aSortOrder[0] == SORT_ORDER_DESC) {
 			sSort.sortFlags |= SORTFLAG_DESC;
 		}
 		sSort.addrSortIndex =
diff --git a/src/box/sql/sqliteInt.h b/src/box/sql/sqliteInt.h
index 8bb45c9..a811932 100644
--- a/src/box/sql/sqliteInt.h
+++ b/src/box/sql/sqliteInt.h
@@ -1881,13 +1881,6 @@ struct Column {
 	u8 is_primkey;		/* Boolean propertie for being PK */
 };
 
-/*
- * A sort order can be either ASC or DESC.
- */
-#define SQLITE_SO_ASC       0	/* Sort in ascending order */
-#define SQLITE_SO_DESC      1	/* Sort in ascending order */
-#define SQLITE_SO_UNDEFINED -1	/* No sort order specified */
-
 /*
  * Column affinity types.
  *
@@ -2143,7 +2136,8 @@ struct Index {
 	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 */
-	u8 *aSortOrder;		/* for each column: True==DESC, False==ASC */
+	/** Sorting order for each column. */
+	enum sort_order *sort_order;
 	/** Array of collation sequences for index. */
 	struct coll **coll_array;
 	Expr *pPartIdxWhere;	/* WHERE clause for partial indices */
@@ -3540,6 +3534,16 @@ sql_default_coll();
 bool
 space_is_view(Table *);
 
+/**
+ * Return name of given column collation 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);
diff --git a/src/box/sql/vdbe.h b/src/box/sql/vdbe.h
index 340ddc7..e244606 100644
--- a/src/box/sql/vdbe.h
+++ b/src/box/sql/vdbe.h
@@ -145,6 +145,7 @@ typedef struct VdbeOpList VdbeOpList;
 #define P4_FUNCCTX  (-16)	/* P4 is a pointer to an sqlite3_context object */
 #define P4_BOOL     (-17)	/* P4 is a bool value */
 #define P4_PTR      (-18)	/* P4 is a generic pointer */
+#define P4_KEYDEF   (-19)       /* P4 is a pointer to key_def structure. */
 
 
 /* Error message codes for OP_Halt */
diff --git a/src/box/sql/where.c b/src/box/sql/where.c
index 7a7103c..bad964a 100644
--- a/src/box/sql/where.c
+++ b/src/box/sql/where.c
@@ -1336,8 +1336,8 @@ whereRangeScanEst(Parse * pParse,	/* Parsing & code generating context */
 			       || (pLower->eOperator & (WO_GT | WO_GE)) != 0);
 			assert(pUpper == 0
 			       || (pUpper->eOperator & (WO_LT | WO_LE)) != 0);
-			assert(p->aSortOrder != 0);
-			if (p->aSortOrder[nEq]) {
+			if (sql_index_column_sort_order(p, nEq) !=
+			    SORT_ORDER_ASC) {
 				/* The roles of pLower and pUpper are swapped for a DESC index */
 				SWAP(pLower, pUpper);
 				SWAP(nBtm, nTop);
@@ -2229,8 +2229,8 @@ whereRangeVectorLen(Parse * pParse,	/* Parsing context */
 		if (pLhs->op != TK_COLUMN
 		    || pLhs->iTable != iCur
 		    || pLhs->iColumn != pIdx->aiColumn[i + nEq]
-		    || pIdx->aSortOrder[i + nEq] != pIdx->aSortOrder[nEq]
-		    ) {
+		    || sql_index_column_sort_order(pIdx, i + nEq) !=
+		       sql_index_column_sort_order(pIdx, nEq)) {
 			break;
 		}
 
@@ -3316,7 +3316,8 @@ wherePathSatisfiesOrderBy(WhereInfo * pWInfo,	/* The WHERE clause */
 				 */
 				if (pIndex) {
 					iColumn = pIndex->aiColumn[j];
-					revIdx = pIndex->aSortOrder[j];
+					revIdx = sql_index_column_sort_order(pIndex,
+									     j);
 					if (iColumn == pIndex->pTable->iPKey)
 						iColumn = -1;
 				} else {
diff --git a/src/box/sql/wherecode.c b/src/box/sql/wherecode.c
index f1112f2..9610f76 100644
--- a/src/box/sql/wherecode.c
+++ b/src/box/sql/wherecode.c
@@ -462,7 +462,8 @@ codeEqualityTerm(Parse * pParse,	/* The parsing context */
 		int nEq = 0;
 		int *aiMap = 0;
 
-		if (pLoop->pIndex != 0 && pLoop->pIndex->aSortOrder[iEq]) {
+		if (pLoop->pIndex != 0 &&
+		    sql_index_column_sort_order(pLoop->pIndex, iEq)) {
 			testcase(iEq == 0);
 			testcase(bRev);
 			bRev = !bRev;
@@ -1300,8 +1301,8 @@ sqlite3WhereCodeOneLoopStart(WhereInfo * pWInfo,	/* Complete information about t
 				assert((bRev & ~1) == 0);
 				pLevel->iLikeRepCntr <<= 1;
 				pLevel->iLikeRepCntr |=
-				    bRev ^ (pIdx->aSortOrder[nEq] ==
-					    SQLITE_SO_DESC);
+					bRev ^ (sql_index_column_sort_order(pIdx, nEq) ==
+						SORT_ORDER_DESC);
 			}
 #endif
 			if (pRangeStart == 0) {
@@ -1320,7 +1321,8 @@ sqlite3WhereCodeOneLoopStart(WhereInfo * pWInfo,	/* Complete information about t
 		 * start and end terms (pRangeStart and pRangeEnd).
 		 */
 		if ((nEq < nIdxCol &&
-		     bRev == (pIdx->aSortOrder[nEq] == SQLITE_SO_ASC)) ||
+		     bRev == (sql_index_column_sort_order(pIdx, nEq) ==
+			      SORT_ORDER_ASC)) ||
 		    (bRev && nIdxCol == nEq)) {
 			SWAP(pRangeEnd, pRangeStart);
 			SWAP(bSeekPastNull, bStopAtNull);
-- 
2.16.2

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

* [tarantool-patches] [PATCH] sql: use collation pointers instead of names
  2018-05-08  7:56 [tarantool-patches] [PATCH 0/2] sql: replace KeyInfo w/ key_def in SQL front-end Kirill Yukhin
  2018-05-08  7:56 ` [tarantool-patches] [PATCH 1/2] sql: introduce sort order to key_part/key_part_def Kirill Yukhin
@ 2018-05-08  7:56 ` Kirill Yukhin
  2018-04-17 18:06   ` [tarantool-patches] " Vladislav Shpilevoy
  2018-05-08  7:59   ` Kirill Yukhin
  2018-05-08  7:56 ` [tarantool-patches] [PATCH 2/2] sql: replace KeyInfo with key_def Kirill Yukhin
  2 siblings, 2 replies; 17+ messages in thread
From: Kirill Yukhin @ 2018-05-08  7:56 UTC (permalink / raw)
  To: v.shpilevoy; +Cc: tarantool-patches, Kirill Yukhin

Before the change SQL FE used collation names to refer to
collations. Main data structures were using names as well.
The patch fixes that and uses explicit pointers to Tarantool's
`struct coll` during code gen and inside data structures.

Closes #3205
---
Hello Vlad,
I've fixed all your inputs, thanks!

Branch: https://github.com/tarantool/tarantool/tree/kyukhin/gh-3205-use-coll-pointers
Issue: https://github.com/tarantool/tarantool/issues/3205

 src/box/sql.c           |  21 ++------
 src/box/sql/alter.c     |   2 +-
 src/box/sql/analyze.c   |   9 +---
 src/box/sql/build.c     | 133 ++++++++++++++++++++++--------------------------
 src/box/sql/callback.c  |  53 ++-----------------
 src/box/sql/expr.c      | 129 ++++++++++++++++++++++------------------------
 src/box/sql/fkey.c      |  23 +++++----
 src/box/sql/func.c      |   3 +-
 src/box/sql/insert.c    |  12 ++---
 src/box/sql/pragma.c    |   9 +++-
 src/box/sql/select.c    | 111 ++++++++++++++++++++++------------------
 src/box/sql/sqliteInt.h |  40 ++++++++++++---
 src/box/sql/vdbe.c      |   2 +-
 src/box/sql/vdbeaux.c   |   7 +++
 src/box/sql/vdbesort.c  |   4 +-
 src/box/sql/where.c     | 102 +++++++++++++++++--------------------
 src/box/sql/whereInt.h  |   7 ++-
 src/box/sql/whereexpr.c |  34 ++++++++-----
 18 files changed, 338 insertions(+), 363 deletions(-)

diff --git a/src/box/sql.c b/src/box/sql.c
index a6713f1..614917d 100644
--- a/src/box/sql.c
+++ b/src/box/sql.c
@@ -1465,12 +1465,8 @@ int tarantoolSqlite3MakeTableFormat(Table *pTable, void *buf)
 
 	for (i = 0; i < n; i++) {
 		const char *t;
-		struct coll *coll = NULL;
+		struct coll *coll = aCol[i].coll;
 		struct Expr *def = aCol[i].pDflt;
-		if (aCol[i].zColl != NULL &&
-		    strcasecmp(aCol[i].zColl, "binary") != 0) {
-			coll = sqlite3FindCollSeq(aCol[i].zColl);
-		}
 		int base_len = 4;
 		if (coll != NULL)
 			base_len += 1;
@@ -1571,28 +1567,19 @@ int tarantoolSqlite3MakeIdxParts(SqliteIndex *pIndex, void *buf)
 	for (i = 0; i < n; i++) {
 		int col = pIndex->aiColumn[i];
 		const char *t;
-		struct coll * collation = NULL;
 		if (pk_forced_int == col)
 			t = "integer";
 		else
 			t = convertSqliteAffinity(aCol[col].affinity, aCol[col].notNull == 0);
 		/* do not decode default collation */
-		if (sqlite3StrICmp(pIndex->azColl[i], "binary") != 0){
-			collation = sqlite3FindCollSeq(pIndex->azColl[i]);
-			/* 
-			 * At this point, the collation has already been found 
-			 * once and the assert should not fire.
-			 */
-			assert(collation);
-		}
-		p = enc->encode_map(p, collation == NULL ? 4 : 5);
+		p = enc->encode_map(p, pIndex->coll_array[i] == NULL ? 4 : 5);
 		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 (collation != NULL){
+		if (pIndex->coll_array[i] != NULL) {
 			p = enc->encode_str(p, "collation", sizeof("collation")-1);
-			p = enc->encode_uint(p, collation->id);
+			p = enc->encode_uint(p, pIndex->coll_array[i]->id);
 		}
 		p = enc->encode_str(p, "is_nullable", 11);
 		p = enc->encode_bool(p, aCol[col].notNull == ON_CONFLICT_ACTION_NONE);
diff --git a/src/box/sql/alter.c b/src/box/sql/alter.c
index 129ef82..b30a973 100644
--- a/src/box/sql/alter.c
+++ b/src/box/sql/alter.c
@@ -296,7 +296,7 @@ sqlite3AlterBeginAddColumn(Parse * pParse, SrcList * pSrc)
 	for (i = 0; i < pNew->nCol; i++) {
 		Column *pCol = &pNew->aCol[i];
 		pCol->zName = sqlite3DbStrDup(db, pCol->zName);
-		pCol->zColl = 0;
+		pCol->coll = NULL;
 		pCol->pDflt = 0;
 	}
 	pNew->pSchema = db->pSchema;
diff --git a/src/box/sql/analyze.c b/src/box/sql/analyze.c
index 665bfbc..f0054c5 100644
--- a/src/box/sql/analyze.c
+++ b/src/box/sql/analyze.c
@@ -977,18 +977,13 @@ analyzeOneTable(Parse * pParse,	/* Parser context */
 				VdbeCoverage(v);
 			}
 			for (i = 0; i < nColTest; i++) {
-				const char *zCollName =
-					index_collation_name(pIdx, i);
-				char *pColl =
-				    (char *)sqlite3LocateCollSeq(pParse,
-								 pParse->db,
-								 zCollName);
+				struct coll *coll = sql_index_collation(pIdx, i);
 				sqlite3VdbeAddOp2(v, OP_Integer, i, regChng);
 				sqlite3VdbeAddOp3(v, OP_Column, iIdxCur,
 						  pIdx->aiColumn[i], regTemp);
 				aGotoChng[i] =
 				    sqlite3VdbeAddOp4(v, OP_Ne, regTemp, 0,
-						      regPrev + i, pColl,
+						      regPrev + i, (char *)coll,
 						      P4_COLLSEQ);
 				sqlite3VdbeChangeP5(v, SQLITE_NULLEQ);
 				VdbeCoverage(v);
diff --git a/src/box/sql/build.c b/src/box/sql/build.c
index c6185e4..74b231a 100644
--- a/src/box/sql/build.c
+++ b/src/box/sql/build.c
@@ -300,7 +300,6 @@ sqlite3DeleteColumnNames(sqlite3 * db, Table * pTable)
 		for (i = 0; i < pTable->nCol; i++, pCol++) {
 			sqlite3DbFree(db, pCol->zName);
 			sql_expr_free(db, pCol->pDflt, false);
-			sqlite3DbFree(db, pCol->zColl);
 		}
 		sqlite3DbFree(db, pTable->aCol);
 	}
@@ -952,6 +951,7 @@ sqlite3AddCollateType(Parse * pParse, Token * pToken)
 	Table *p;
 	int i;
 	char *zColl;		/* Dequoted name of collation sequence */
+	struct coll *coll;
 	sqlite3 *db;
 
 	if ((p = pParse->pNewTable) == 0)
@@ -962,10 +962,10 @@ sqlite3AddCollateType(Parse * pParse, Token * pToken)
 	if (!zColl)
 		return;
 
-	if (sqlite3LocateCollSeq(pParse, db, zColl)) {
+	coll =  sqlite3LocateCollSeq(pParse, db, zColl);
+	if (coll != NULL) {
 		Index *pIdx;
-		sqlite3DbFree(db, p->aCol[i].zColl);
-		p->aCol[i].zColl = zColl;
+		p->aCol[i].coll = coll;
 
 		/* If the column is declared as "<name> PRIMARY KEY COLLATE <type>",
 		 * then an index may have been created on this column before the
@@ -973,9 +973,8 @@ sqlite3AddCollateType(Parse * pParse, Token * pToken)
 		 */
 		for (pIdx = p->pIndex; pIdx; pIdx = pIdx->pNext) {
 			assert(pIdx->nColumn == 1);
-			if (pIdx->aiColumn[0] == i) {
-				pIdx->azColl[0] = column_collation_name(p, i);
-			}
+			if (pIdx->aiColumn[0] == i)
+				pIdx->coll_array[0] = sql_column_collation(p, i);
 		}
 	} else {
 		sqlite3DbFree(db, zColl);
@@ -983,14 +982,14 @@ sqlite3AddCollateType(Parse * pParse, Token * pToken)
 }
 
 /**
- * Return name of given column collation from table.
+ * Return collation of given column from table.
  *
  * @param table Table which is used to fetch column.
  * @param column Number of column.
- * @retval Pointer to collation's name.
+ * @retval Pointer to collation.
  */
-const char *
-column_collation_name(Table *table, uint32_t column)
+struct coll *
+sql_column_collation(Table *table, uint32_t column)
 {
 	assert(table != NULL);
 	uint32_t space_id = SQLITE_PAGENO_TO_SPACEID(table->tnum);
@@ -1007,15 +1006,12 @@ column_collation_name(Table *table, uint32_t column)
 	 * In cases mentioned above collation is fetched from
 	 * SQL specific structures.
 	 */
-	if (space == NULL || space_index(space, 0) == NULL)
-		return table->aCol[column].zColl;
-
-	/* "BINARY" is a name for default collation in SQL. */
-	char *coll_name = (char *)sqlite3StrBINARY;
-	if (space->format->fields[column].coll != NULL) {
-		coll_name = space->format->fields[column].coll->name;
+	if (space == NULL || space_index(space, 0) == NULL) {
+		assert(column < (uint32_t)table->nCol);
+		return table->aCol[column].coll;
 	}
-	return coll_name;
+
+	return space->format->fields[column].coll;
 }
 
 /**
@@ -1023,30 +1019,30 @@ column_collation_name(Table *table, uint32_t column)
  *
  * @param idx Index which is used to fetch column.
  * @param column Number of column.
- * @retval Pointer to collation's name.
+ * @retval Pointer to collation.
  */
-const char *
-index_collation_name(Index *idx, uint32_t column)
+struct coll *
+sql_index_collation(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)
-		return (char *)idx->azColl[column];
+	if (space == NULL) {
+		assert(column < idx->nColumn);
+		return idx->coll_array[column];
+	}
 
 	uint32_t index_id = SQLITE_PAGENO_TO_INDEXID(idx->tnum);
 	struct index *index = space_index(space, index_id);
 	assert(index != NULL && index->def->key_def->part_count >= column);
-	struct coll *coll = index->def->key_def->parts[column].coll;
-	/* "BINARY" is a name for default collation in SQL. */
-	if (coll == NULL)
-		return (char *)sqlite3StrBINARY;
-	return index->def->key_def->parts[column].coll->name;
+	return index->def->key_def->parts[column].coll;
 }
 
 /**
@@ -1117,6 +1113,9 @@ sqlite3LocateCollSeq(Parse * pParse, sqlite3 * db, const char *zName)
 	struct coll *pColl;
 	initbusy = db->init.busy;
 
+	if (sqlite3StrICmp(zName, "binary") == 0)
+		return NULL;
+
 	pColl = sqlite3FindCollSeq(zName);
 	if (!initbusy && (!pColl)) {
 		pColl = sqlite3GetCollSeq(pParse, pColl, zName);
@@ -2623,7 +2622,7 @@ sqlite3AllocateIndexObject(sqlite3 * db,	/* Database connection */
 	p = sqlite3DbMallocZero(db, nByte + nExtra);
 	if (p) {
 		char *pExtra = ((char *)p) + ROUND8(sizeof(Index));
-		p->azColl = (const char **)pExtra;
+		p->coll_array = (struct coll **)pExtra;
 		pExtra += ROUND8(sizeof(char *) * nCol);
 		p->aiRowLogEst = (LogEst *) pExtra;
 		pExtra += sizeof(LogEst) * (nCol + 1);
@@ -2920,7 +2919,7 @@ sqlite3CreateIndex(Parse * pParse,	/* All information about this parse */
 		goto exit_create_index;
 	}
 	assert(EIGHT_BYTE_ALIGNMENT(pIndex->aiRowLogEst));
-	assert(EIGHT_BYTE_ALIGNMENT(pIndex->azColl));
+	assert(EIGHT_BYTE_ALIGNMENT(pIndex->coll_array));
 	pIndex->zName = zExtra;
 	zExtra += nName + 1;
 	memcpy(pIndex->zName, zName, nName + 1);
@@ -2959,7 +2958,7 @@ sqlite3CreateIndex(Parse * pParse,	/* All information about this parse */
 	for (i = 0, pListItem = pList->a; i < pList->nExpr; i++, pListItem++) {
 		Expr *pCExpr;	/* The i-th index expression */
 		int requestedSortOrder;	/* ASC or DESC on the i-th expression */
-		const char *zColl;	/* Collation sequence name */
+		struct coll *coll;
 		sqlite3ResolveSelfReference(pParse, pTab, NC_IdxExpr,
 					    pListItem->pExpr, 0);
 		if (pParse->nErr)
@@ -2978,25 +2977,21 @@ sqlite3CreateIndex(Parse * pParse,	/* All information about this parse */
 			}
 			pIndex->aiColumn[i] = (i16) j;
 		}
-		zColl = 0;
+		coll = NULL;
+		const char *coll_name;
 		if (pListItem->pExpr->op == TK_COLLATE) {
-			int nColl;
-			zColl = pListItem->pExpr->u.zToken;
-			nColl = sqlite3Strlen30(zColl) + 1;
-			assert(nExtra >= nColl);
-			memcpy(zExtra, zColl, nColl);
-			zColl = zExtra;
-			zExtra += nColl;
-			nExtra -= nColl;
+			coll_name = pListItem->pExpr->u.zToken;
+			coll = sqlite3GetCollSeq(pParse, 0, coll_name);
+
+			if (coll == NULL &&
+			    sqlite3StrICmp(coll_name, "binary") != 0) {
+				goto exit_create_index;
+			}
 		} else if (j >= 0) {
-			zColl = column_collation_name(pTab, j);
-		}
-		if (!zColl)
-			zColl = sqlite3StrBINARY;
-		if (!db->init.busy && !sqlite3LocateCollSeq(pParse, db, zColl)) {
-			goto exit_create_index;
+			coll = sql_column_collation(pTab, j);
 		}
-		pIndex->azColl[i] = zColl;
+		pIndex->coll_array[i] = coll;
+
 		/* Tarantool: DESC indexes are not supported so far.
 		 * See gh-3016.
 		 */
@@ -3040,14 +3035,13 @@ sqlite3CreateIndex(Parse * pParse,	/* All information about this parse */
 			if (pIdx->nColumn != pIndex->nColumn)
 				continue;
 			for (k = 0; k < pIdx->nColumn; k++) {
-				const char *z1;
-				const char *z2;
 				assert(pIdx->aiColumn[k] >= 0);
 				if (pIdx->aiColumn[k] != pIndex->aiColumn[k])
 					break;
-				z1 = index_collation_name(pIdx, k);
-				z2 = index_collation_name(pIndex, k);
-				if (strcmp(z1, z2))
+				struct coll *coll1, *coll2;
+				coll1 = sql_index_collation(pIdx, k);
+				coll2 = sql_index_collation(pIndex, k);
+				if (coll1 != coll2)
 					break;
 			}
 			if (k == pIdx->nColumn) {
@@ -3963,19 +3957,18 @@ sqlite3UniqueConstraint(Parse * pParse,	/* Parsing context */
  * true if it does and false if it does not.
  */
 #ifndef SQLITE_OMIT_REINDEX
-static int
-collationMatch(const char *zColl, Index * pIndex)
+static bool
+collationMatch(struct coll *coll, struct Index *index)
 {
 	int i;
-	assert(zColl != 0);
-	for (i = 0; i < pIndex->nColumn; i++) {
-		const char *z = index_collation_name(pIndex, i);
-		assert(z != 0 || pIndex->aiColumn[i] < 0);
-		if (pIndex->aiColumn[i] >= 0 && 0 == sqlite3StrICmp(z, zColl)) {
-			return 1;
-		}
+	assert(coll != 0);
+	for (i = 0; i < index->nColumn; i++) {
+		struct coll *idx_coll = sql_index_collation(index, i);
+		assert(idx_coll != 0 || index->aiColumn[i] < 0);
+		if (index->aiColumn[i] >= 0 && coll == idx_coll)
+			return true;
 	}
-	return 0;
+	return false;
 }
 #endif
 
@@ -3985,12 +3978,12 @@ collationMatch(const char *zColl, Index * pIndex)
  */
 #ifndef SQLITE_OMIT_REINDEX
 static void
-reindexTable(Parse * pParse, Table * pTab, char const *zColl)
+reindexTable(Parse * pParse, Table * pTab, struct coll *coll)
 {
 	Index *pIndex;		/* An index associated with pTab */
 
 	for (pIndex = pTab->pIndex; pIndex; pIndex = pIndex->pNext) {
-		if (zColl == 0 || collationMatch(zColl, pIndex)) {
+		if (coll == 0 || collationMatch(coll, pIndex)) {
 			sql_set_multi_write(pParse, false);
 			sqlite3RefillIndex(pParse, pIndex, -1);
 		}
@@ -4005,7 +3998,7 @@ reindexTable(Parse * pParse, Table * pTab, char const *zColl)
  */
 #ifndef SQLITE_OMIT_REINDEX
 static void
-reindexDatabases(Parse * pParse, char const *zColl)
+reindexDatabases(Parse * pParse, struct coll *coll)
 {
 	sqlite3 *db = pParse->db;	/* The database connection */
 	HashElem *k;		/* For looping over tables in pSchema */
@@ -4015,7 +4008,7 @@ reindexDatabases(Parse * pParse, char const *zColl)
 	for (k = sqliteHashFirst(&db->pSchema->tblHash); k;
 	     k = sqliteHashNext(k)) {
 		pTab = (Table *) sqliteHashData(k);
-		reindexTable(pParse, pTab, zColl);
+		reindexTable(pParse, pTab, coll);
 	}
 }
 #endif
@@ -4057,7 +4050,7 @@ sqlite3Reindex(Parse * pParse, Token * pName1, Token * pName2)
 			return;
 		pColl = sqlite3FindCollSeq(zColl);
 		if (pColl) {
-			reindexDatabases(pParse, zColl);
+			reindexDatabases(pParse, pColl);
 			sqlite3DbFree(db, zColl);
 			return;
 		}
@@ -4126,9 +4119,7 @@ sqlite3KeyInfoOfIndex(Parse * pParse, sqlite3 * db, Index * pIdx)
 	if (pKey) {
 		assert(sqlite3KeyInfoIsWriteable(pKey));
 		for (i = 0; i < nCol; i++) {
-			const char *zColl = index_collation_name(pIdx, i);
-			pKey->aColl[i] = zColl == sqlite3StrBINARY ? 0 :
-			    sqlite3LocateCollSeq(pParse, db, zColl);
+			pKey->aColl[i] = sql_index_collation(pIdx, i);
 			pKey->aSortOrder[i] = pIdx->aSortOrder[i];
 		}
 		if (pParse && pParse->nErr) {
diff --git a/src/box/sql/callback.c b/src/box/sql/callback.c
index b176931..0d34e6a 100644
--- a/src/box/sql/callback.c
+++ b/src/box/sql/callback.c
@@ -62,10 +62,9 @@ sqlite3GetCollSeq(Parse * pParse,	/* Parsing context */
 	struct coll *p;
 
 	p = pColl;
-	if (!p) {
+	if (p == NULL)
 		p = sqlite3FindCollSeq(zName);
-	}
-	if (p == 0) {
+	if (p == NULL && (sqlite3StrICmp(zName, "binary") != 0)) {
 		if (pParse)
 			sqlite3ErrorMsg(pParse,
 					"no such collation sequence: %s",
@@ -102,49 +101,6 @@ sqlite3CheckCollSeq(Parse * pParse, struct coll * pColl)
 	return SQLITE_OK;
 }
 
-/*
- * This is the default collating function named "BINARY" which is always
- * available.
- * It is hardcoded to support Tarantool's collation interface.
- */
-static int
-binCollFunc(const char *pKey1, size_t nKey1, const char *pKey2, size_t nKey2, const struct coll * collation)
-{
-	int rc;
-	size_t n;
-	(void) collation;
-	n = nKey1 < nKey2 ? nKey1 : nKey2;
-	/* EVIDENCE-OF: R-65033-28449 The built-in BINARY collation compares
-	 * strings byte by byte using the memcmp() function from the standard C
-	 * library.
-	 */
-	rc = memcmp(pKey1, pKey2, n);
-	if (rc == 0) {
-		rc = (int)nKey1 - (int)nKey2;
-	}
-	return rc;
-}
-
-/*
- * This hardcoded structure created just to be called the same way
- * as collations in Tarantool, to support binary collation easily.
- */
-
-struct coll_plus_name_struct{
-    struct coll collation;
-    char name[20]; /* max of possible name lengths */
-};
-static struct coll_plus_name_struct binary_coll_with_name =
-	{{0, 0, COLL_TYPE_ICU, {0}, binCollFunc, 0, sizeof("BINARY"), {}},
-		"BINARY"};
-static struct coll * binary_coll = (struct coll*)&binary_coll_with_name;
-
-struct coll *
-sql_default_coll()
-{
-	return binary_coll;
-}
-
 /**
  * Return the coll* pointer for the collation sequence named zName.
  *
@@ -158,9 +114,8 @@ sql_default_coll()
 struct coll *
 sqlite3FindCollSeq(const char *zName)
 {
-	if (zName == NULL || sqlite3StrICmp(zName, "binary")==0){
-		return binary_coll;
-	}
+	if (zName == NULL || sqlite3StrICmp(zName, "binary")==0)
+		return 0;
 	return coll_by_name(zName, strlen(zName));
 }
 
diff --git a/src/box/sql/expr.c b/src/box/sql/expr.c
index 8071314..9ac67fb 100644
--- a/src/box/sql/expr.c
+++ b/src/box/sql/expr.c
@@ -158,21 +158,13 @@ sqlite3ExprSkipCollate(Expr * pExpr)
 	return pExpr;
 }
 
-/*
- * Return the collation sequence for the expression pExpr. If
- * there is no defined collating sequence, return NULL.
- *
- * The collating sequence might be determined by a COLLATE operator
- * or by the presence of a column with a defined collating sequence.
- * COLLATE operators take first precedence.  Left operands take
- * precedence over right operands.
- */
 struct coll *
-sqlite3ExprCollSeq(Parse * pParse, Expr * pExpr)
+sql_expr_coll(Parse *parse, Expr *expr, bool *found)
 {
-	struct coll *pColl = 0;
-	Expr *p = pExpr;
-	while (p) {
+	struct coll *coll = 0;
+	Expr *p = expr;
+	*found = false;
+	while (p != NULL) {
 		int op = p->op;
 		if (p->flags & EP_Generic)
 			break;
@@ -180,23 +172,22 @@ sqlite3ExprCollSeq(Parse * pParse, Expr * pExpr)
 			p = p->pLeft;
 			continue;
 		}
-		if (op == TK_COLLATE
-		    || (op == TK_REGISTER && p->op2 == TK_COLLATE)) {
-			pColl =
-			    sqlite3GetCollSeq(pParse, 0, p->u.zToken);
+		if (op == TK_COLLATE ||
+		    (op == TK_REGISTER && p->op2 == TK_COLLATE)) {
+			coll = sqlite3GetCollSeq(parse, NULL, p->u.zToken);
+			*found = true;
 			break;
 		}
-		if ((op == TK_AGG_COLUMN || op == TK_COLUMN
-		     || op == TK_REGISTER || op == TK_TRIGGER)
-		    && p->pTab != 0) {
+		if ((op == TK_AGG_COLUMN || op == TK_COLUMN ||
+		     op == TK_REGISTER || op == TK_TRIGGER) &&
+		    p->pTab != 0) {
 			/* op==TK_REGISTER && p->pTab!=0 happens when pExpr was originally
 			 * a TK_COLUMN but was previously evaluated and cached in a register
 			 */
 			int j = p->iColumn;
 			if (j >= 0) {
-				const char *zColl =
-					column_collation_name(p->pTab, j);
-				pColl = sqlite3FindCollSeq(zColl);
+				coll = sql_column_collation(p->pTab, j);
+				*found = true;
 			}
 			break;
 		}
@@ -204,40 +195,37 @@ sqlite3ExprCollSeq(Parse * pParse, Expr * pExpr)
 			if (p->pLeft && (p->pLeft->flags & EP_Collate) != 0) {
 				p = p->pLeft;
 			} else {
-				Expr *pNext = p->pRight;
+				Expr *next = p->pRight;
 				/* The Expr.x union is never used at the same time as Expr.pRight */
 				assert(p->x.pList == 0 || p->pRight == 0);
 				/* p->flags holds EP_Collate and p->pLeft->flags does not.  And
 				 * p->x.pSelect cannot.  So if p->x.pLeft exists, it must hold at
 				 * least one EP_Collate. Thus the following two ALWAYS.
 				 */
-				if (p->x.pList != 0
-				    &&
+				if (p->x.pList != 0 &&
 				    ALWAYS(!ExprHasProperty(p, EP_xIsSelect))) {
-					int i;
-					for (i = 0;
+					for (int i = 0;
 					     ALWAYS(i < p->x.pList->nExpr);
 					     i++) {
-						if (ExprHasProperty
-						    (p->x.pList->a[i].pExpr,
-						     EP_Collate)) {
-							pNext =
-							    p->x.pList->a[i].
-							    pExpr;
+						Expr *e;
+						e = p->x.pList->a[i].pExpr;
+						if (ExprHasProperty(e,
+								    EP_Collate)) {
+							next = e;
 							break;
 						}
 					}
 				}
-				p = pNext;
+				p = next;
 			}
 		} else {
 			break;
 		}
 	}
-	if (sqlite3CheckCollSeq(pParse, pColl)) {
-		pColl = 0;
-	}
-	return pColl;
+	if (sqlite3CheckCollSeq(parse, coll))
+		parse = 0;
+
+	return coll;
 }
 
 /*
@@ -344,19 +332,19 @@ binaryCompareP5(Expr * pExpr1, Expr * pExpr2, int jumpIfNull)
 struct coll *
 sqlite3BinaryCompareCollSeq(Parse * pParse, Expr * pLeft, Expr * pRight)
 {
-	struct coll *pColl;
+	struct coll *coll;
+	bool found;
 	assert(pLeft);
 	if (pLeft->flags & EP_Collate) {
-		pColl = sqlite3ExprCollSeq(pParse, pLeft);
+		coll = sql_expr_coll(pParse, pLeft, &found);
 	} else if (pRight && (pRight->flags & EP_Collate) != 0) {
-		pColl = sqlite3ExprCollSeq(pParse, pRight);
+		coll = sql_expr_coll(pParse, pRight, &found);
 	} else {
-		pColl = sqlite3ExprCollSeq(pParse, pLeft);
-		if (!pColl) {
-			pColl = sqlite3ExprCollSeq(pParse, pRight);
-		}
+		coll = sql_expr_coll(pParse, pLeft, &found);
+		if (!found)
+			coll = sql_expr_coll(pParse, pRight, &found);
 	}
-	return pColl;
+	return coll;
 }
 
 /*
@@ -2520,14 +2508,15 @@ sqlite3FindInIndex(Parse * pParse,	/* Parsing context */
 						    (pParse, pLhs, pRhs);
 					int j;
 
-					assert(pReq != 0 || pParse->nErr);
 					for (j = 0; j < nExpr; j++) {
-						if (pIdx->aiColumn[j]
-						    != pRhs->iColumn)
+						if (pIdx->aiColumn[j] !=
+						    pRhs->iColumn) {
 							continue;
-						if (pReq != 0 && strcmp
-						    (pReq->name,
-						     index_collation_name(pIdx, j)) != 0) {
+						}
+						struct coll *idx_coll;
+						idx_coll = sql_index_collation(pIdx, j);
+						if (pReq != NULL &&
+						    pReq != idx_coll) {
 							continue;
 						}
 						break;
@@ -2879,11 +2868,13 @@ sqlite3CodeSubselect(Parse * pParse,	/* Parsing context */
 					affinity = SQLITE_AFF_BLOB;
 				}
 				if (pKeyInfo) {
+					bool found; /* Not Used.  */
 					assert(sqlite3KeyInfoIsWriteable
 					       (pKeyInfo));
 					pKeyInfo->aColl[0] =
-					    sqlite3ExprCollSeq(pParse,
-							       pExpr->pLeft);
+						sql_expr_coll(pParse,
+							      pExpr->pLeft,
+							      &found);
 				}
 
 				/* Loop through each expression in <exprlist>. */
@@ -3140,8 +3131,10 @@ sqlite3ExprCodeIN(Parse * pParse,	/* Parsing and code generating context */
 	 * This is step (1) in the in-operator.md optimized algorithm.
 	 */
 	if (eType == IN_INDEX_NOOP) {
+		bool found; /* Not used. */
 		ExprList *pList = pExpr->x.pList;
-		struct coll *pColl = sqlite3ExprCollSeq(pParse, pExpr->pLeft);
+		struct coll *coll = sql_expr_coll(pParse, pExpr->pLeft,
+						   &found);
 		int labelOk = sqlite3VdbeMakeLabel(v);
 		int r2, regToFree;
 		int regCkNull = 0;
@@ -3161,14 +3154,14 @@ sqlite3ExprCodeIN(Parse * pParse,	/* Parsing and code generating context */
 			}
 			if (ii < pList->nExpr - 1 || destIfNull != destIfFalse) {
 				sqlite3VdbeAddOp4(v, OP_Eq, rLhs, labelOk, r2,
-						  (void *)pColl, P4_COLLSEQ);
+						  (void *)coll, P4_COLLSEQ);
 				VdbeCoverageIf(v, ii < pList->nExpr - 1);
 				VdbeCoverageIf(v, ii == pList->nExpr - 1);
 				sqlite3VdbeChangeP5(v, zAff[0]);
 			} else {
 				assert(destIfNull == destIfFalse);
 				sqlite3VdbeAddOp4(v, OP_Ne, rLhs, destIfFalse,
-						  r2, (void *)pColl,
+						  r2, (void *)coll,
 						  P4_COLLSEQ);
 				VdbeCoverage(v);
 				sqlite3VdbeChangeP5(v,
@@ -3277,9 +3270,10 @@ sqlite3ExprCodeIN(Parse * pParse,	/* Parsing and code generating context */
 	for (i = 0; i < nVector; i++) {
 		Expr *p;
 		struct coll *pColl;
+		bool found;
 		int r3 = sqlite3GetTempReg(pParse);
 		p = sqlite3VectorFieldSubexpr(pLeft, i);
-		pColl = sqlite3ExprCollSeq(pParse, p);
+		pColl = sql_expr_coll(pParse, p, &found);
 		/* Tarantool: Replace i -> aiMap [i], since original order of columns
 		 * is preserved.
 		 */
@@ -4066,7 +4060,7 @@ sqlite3ExprCodeTarget(Parse * pParse, Expr * pExpr, int target)
 			u32 constMask = 0;	/* Mask of function arguments that are constant */
 			int i;	/* Loop counter */
 			sqlite3 *db = pParse->db;	/* The database connection */
-			struct coll *pColl = 0;	/* A collating sequence */
+			struct coll *coll = 0;	/* A collating sequence */
 
 			assert(!ExprHasProperty(pExpr, EP_xIsSelect));
 			if (ExprHasProperty(pExpr, EP_TokenOnly)) {
@@ -4133,11 +4127,12 @@ sqlite3ExprCodeTarget(Parse * pParse, Expr * pExpr, int target)
 					constMask |= MASKBIT32(i);
 				}
 				if ((pDef->funcFlags & SQLITE_FUNC_NEEDCOLL) !=
-				    0 && !pColl) {
-					pColl =
-					    sqlite3ExprCollSeq(pParse,
-							       pFarg->a[i].
-							       pExpr);
+				    0 && coll == NULL) {
+					bool found; /* Not used.  */
+					coll = sql_expr_coll(pParse,
+							      pFarg->a[i].
+							      pExpr,
+							      &found);
 				}
 			}
 			if (pFarg) {
@@ -4186,10 +4181,8 @@ sqlite3ExprCodeTarget(Parse * pParse, Expr * pExpr, int target)
 				r1 = 0;
 			}
 			if (pDef->funcFlags & SQLITE_FUNC_NEEDCOLL) {
-				if (!pColl)
-					pColl = sql_default_coll();
 				sqlite3VdbeAddOp4(v, OP_CollSeq, 0, 0, 0,
-						  (char *)pColl, P4_COLLSEQ);
+						  (char *)coll, P4_COLLSEQ);
 			}
 			sqlite3VdbeAddOp4(v, OP_Function0, constMask, r1,
 					  target, (char *)pDef, P4_FUNCDEF);
diff --git a/src/box/sql/fkey.c b/src/box/sql/fkey.c
index f56b6d9..a0a35e0 100644
--- a/src/box/sql/fkey.c
+++ b/src/box/sql/fkey.c
@@ -287,7 +287,6 @@ sqlite3FkLocateIndex(Parse * pParse,	/* Parse context to store any error in */
 				int i, j;
 				for (i = 0; i < nCol; i++) {
 					i16 iCol = pIdx->aiColumn[i];	/* Index of column in parent tbl */
-					const char *zDfltColl;	/* Def. collation for column */
 					char *zIdxCol;	/* Name of indexed column */
 
 					if (iCol < 0)
@@ -297,11 +296,12 @@ sqlite3FkLocateIndex(Parse * pParse,	/* Parse context to store any error in */
 					 * the default collation sequence for the column, this index is
 					 * unusable. Bail out early in this case.
 					 */
-					zDfltColl =
-						column_collation_name(pParent,
-								      iCol);
-					if (strcmp
-					    (index_collation_name(pIdx, i), zDfltColl))
+					struct coll *def_coll;
+					def_coll = sql_column_collation(pParent,
+									iCol);
+					struct coll *coll;
+					coll = sql_index_collation(pIdx, i);
+					if (def_coll != coll)
 						break;
 
 					zIdxCol = pParent->aCol[iCol].zName;
@@ -526,7 +526,6 @@ exprTableRegister(Parse * pParse,	/* Parsing and code generating context */
 {
 	Expr *pExpr;
 	Column *pCol;
-	const char *zColl;
 	sqlite3 *db = pParse->db;
 
 	pExpr = sqlite3Expr(db, TK_REGISTER, 0);
@@ -535,9 +534,13 @@ exprTableRegister(Parse * pParse,	/* Parsing and code generating context */
 			pCol = &pTab->aCol[iCol];
 			pExpr->iTable = regBase + iCol + 1;
 			pExpr->affinity = pCol->affinity;
-			zColl = column_collation_name(pTab, iCol);
-			pExpr =
-			    sqlite3ExprAddCollateString(pParse, pExpr, zColl);
+			const char *coll_name;
+			if (pCol->coll == NULL && pCol->coll != NULL)
+				coll_name = pCol->coll->name;
+			else
+				coll_name = "binary";
+			pExpr = sqlite3ExprAddCollateString(pParse, pExpr,
+							    coll_name);
 		} else {
 			pExpr->iTable = regBase;
 			pExpr->affinity = SQLITE_AFF_INTEGER;
diff --git a/src/box/sql/func.c b/src/box/sql/func.c
index 47b45de..dcac22c 100644
--- a/src/box/sql/func.c
+++ b/src/box/sql/func.c
@@ -54,7 +54,7 @@ sqlite3GetFuncCollSeq(sqlite3_context * context)
 	assert(context->pVdbe != 0);
 	pOp = &context->pVdbe->aOp[context->iOp - 1];
 	assert(pOp->opcode == OP_CollSeq);
-	assert(pOp->p4type == P4_COLLSEQ);
+	assert(pOp->p4type == P4_COLLSEQ || pOp->p4.pColl == NULL);
 	return pOp->p4.pColl;
 }
 
@@ -82,7 +82,6 @@ minmaxFunc(sqlite3_context * context, int argc, sqlite3_value ** argv)
 	assert(argc > 1);
 	mask = sqlite3_user_data(context) == 0 ? 0 : -1;
 	pColl = sqlite3GetFuncCollSeq(context);
-	assert(pColl);
 	assert(mask == -1 || mask == 0);
 	iBest = 0;
 	if (sqlite3_value_type(argv[0]) == SQLITE_NULL)
diff --git a/src/box/sql/insert.c b/src/box/sql/insert.c
index ae8dafb..f04496a 100644
--- a/src/box/sql/insert.c
+++ b/src/box/sql/insert.c
@@ -1419,9 +1419,7 @@ sqlite3GenerateConstraintChecks(Parse * pParse,		/* The parser context */
 					      regIdx : regR);
 
 				for (i = 0; i < nPkCol; i++) {
-					char *p4 = (char *)
-						sqlite3LocateCollSeq(pParse, db,
-								     index_collation_name(pPk, i));
+					char *p4 = (char *)sql_index_collation(pPk, i);
 					x = pPk->aiColumn[i];
 					assert(x >= 0);
 					if (i == (nPkCol - 1)) {
@@ -1666,8 +1664,8 @@ xferCompatibleIndex(Index * pDest, Index * pSrc)
 		if (pSrc->aSortOrder[i] != pDest->aSortOrder[i]) {
 			return 0;	/* Different sort orders */
 		}
-		if (strcasecmp(index_collation_name(pSrc, i),
-			       index_collation_name(pDest, i)) != 0) {
+		if (sql_index_collation(pSrc, i) !=
+		    sql_index_collation(pDest, i)) {
 			return 0;	/* Different collating sequences */
 		}
 	}
@@ -1806,8 +1804,8 @@ xferOptimization(Parse * pParse,	/* Parser context */
 		if (pDestCol->affinity != pSrcCol->affinity) {
 			return 0;	/* Affinity must be the same on all columns */
 		}
-		if (strcasecmp(column_collation_name(pDest, i),
-			       column_collation_name(pSrc, i)) != 0) {
+		if (sql_column_collation(pDest, i) !=
+		    sql_column_collation(pSrc, i)) {
 			return 0;	/* Collating sequence must be the same on all columns */
 		}
 		if (!table_column_is_nullable(pDest, i)
diff --git a/src/box/sql/pragma.c b/src/box/sql/pragma.c
index b724c98..a2a6391 100644
--- a/src/box/sql/pragma.c
+++ b/src/box/sql/pragma.c
@@ -452,13 +452,20 @@ sqlite3Pragma(Parse * pParse, Token * pId,	/* First part of [schema.]id field */
 								     aCol[cnum].
 								     zName);
 						if (pPragma->iArg) {
+							const char *c_n;
+							struct coll *coll;
+							coll = sql_index_collation(pIdx, i);
+							if (coll != NULL)
+								c_n = coll->name;
+							else
+								c_n = "BINARY";
 							sqlite3VdbeMultiLoad(v,
 									     4,
 									     "isi",
 									     pIdx->
 									     aSortOrder
 									     [i],
-									     index_collation_name(pIdx, i),
+									     c_n,
 									     i <
 									     mx);
 						}
diff --git a/src/box/sql/select.c b/src/box/sql/select.c
index d97e466..5a39435 100644
--- a/src/box/sql/select.c
+++ b/src/box/sql/select.c
@@ -908,10 +908,12 @@ selectInnerLoop(Parse * pParse,		/* The parser context */
 
 				iJump = sqlite3VdbeCurrentAddr(v) + nResultCol;
 				for (i = 0; i < nResultCol; i++) {
-					struct coll *pColl =
-					    sqlite3ExprCollSeq(pParse,
-							       pEList->a[i].
-							       pExpr);
+					bool found;
+					struct coll *coll =
+					    sql_expr_coll(pParse,
+							  pEList->a[i].
+							  pExpr,
+							  &found);
 					if (i < nResultCol - 1) {
 						sqlite3VdbeAddOp3(v, OP_Ne,
 								  regResult + i,
@@ -925,9 +927,11 @@ selectInnerLoop(Parse * pParse,		/* The parser context */
 								  regPrev + i);
 						VdbeCoverage(v);
 					}
-					sqlite3VdbeChangeP4(v, -1,
-							    (const char *)pColl,
-							    P4_COLLSEQ);
+					if (found) {
+						sqlite3VdbeChangeP4(v, -1,
+								    (const char *)coll,
+								    P4_COLLSEQ);
+					}
 					sqlite3VdbeChangeP5(v, SQLITE_NULLEQ);
 				}
 				assert(sqlite3VdbeCurrentAddr(v) == iJump
@@ -1288,11 +1292,12 @@ keyInfoFromExprList(Parse * pParse,	/* Parsing context */
 		assert(sqlite3KeyInfoIsWriteable(pInfo));
 		for (i = iStart, pItem = pList->a + iStart; i < nExpr;
 		     i++, pItem++) {
-			struct coll *pColl;
-			pColl = sqlite3ExprCollSeq(pParse, pItem->pExpr);
-			if (!pColl)
-				pColl = sql_default_coll();
-			pInfo->aColl[i - iStart] = pColl;
+			bool found; /* Not used.  */
+			struct coll *coll;
+			coll = sql_expr_coll(pParse,
+					     pItem->pExpr,
+					     &found);
+			pInfo->aColl[i - iStart] = coll;
 			pInfo->aSortOrder[i - iStart] = pItem->sortOrder;
 		}
 	}
@@ -1935,7 +1940,6 @@ sqlite3SelectAddColumnTypeAndCollation(Parse * pParse,		/* Parsing contexts */
 	sqlite3 *db = pParse->db;
 	NameContext sNC;
 	Column *pCol;
-	struct coll *pColl;
 	int i;
 	Expr *p;
 	struct ExprList_item *a;
@@ -1950,6 +1954,7 @@ sqlite3SelectAddColumnTypeAndCollation(Parse * pParse,		/* Parsing contexts */
 	sNC.pSrcList = pSelect->pSrc;
 	a = pSelect->pEList->a;
 	for (i = 0, pCol = pTab->aCol; i < pTab->nCol; i++, pCol++) {
+		struct coll *coll;
 		enum field_type type;
 		p = a[i].pExpr;
 		type = columnType(&sNC, p, 0, 0, 0, &pCol->szEst);
@@ -1959,10 +1964,10 @@ sqlite3SelectAddColumnTypeAndCollation(Parse * pParse,		/* Parsing contexts */
 
 		if (pCol->affinity == 0)
 			pCol->affinity = SQLITE_AFF_BLOB;
-		pColl = sqlite3ExprCollSeq(pParse, p);
-		if (pColl && pCol->zColl == 0) {
-			pCol->zColl = sqlite3DbStrDup(db, pColl->name);
-		}
+		bool found;
+		coll = sql_expr_coll(pParse, p, &found);
+		if (coll && pCol->coll == 0)
+			pCol->coll = coll;
 	}
 	pTab->szTabRow = sqlite3LogEst(szAll * 4);
 }
@@ -2123,23 +2128,24 @@ computeLimitRegisters(Parse * pParse, Select * p, int iBreak)
  * left-most term of the select that has a collating sequence.
  */
 static struct coll *
-multiSelectCollSeq(Parse * pParse, Select * p, int iCol)
+multiSelectCollSeq(Parse * pParse, Select * p, int iCol, bool *found)
 {
-	struct coll *pRet;
-	if (p->pPrior) {
-		pRet = multiSelectCollSeq(pParse, p->pPrior, iCol);
-	} else {
-		pRet = 0;
-	}
+	struct coll *coll;
+	if (p->pPrior)
+		coll = multiSelectCollSeq(pParse, p->pPrior, iCol, found);
+	else
+		coll = NULL;
 	assert(iCol >= 0);
 	/* iCol must be less than p->pEList->nExpr.  Otherwise an error would
 	 * have been thrown during name resolution and we would not have gotten
 	 * this far
 	 */
-	if (pRet == 0 && ALWAYS(iCol < p->pEList->nExpr)) {
-		pRet = sqlite3ExprCollSeq(pParse, p->pEList->a[iCol].pExpr);
+	if (!(*found) && ALWAYS(iCol < p->pEList->nExpr)) {
+		coll = sql_expr_coll(pParse,
+				     p->pEList->a[iCol].pExpr,
+				     found);
 	}
-	return pRet;
+	return coll;
 }
 
 /*
@@ -2163,22 +2169,29 @@ multiSelectOrderByKeyInfo(Parse * pParse, Select * p, int nExtra)
 		for (i = 0; i < nOrderBy; i++) {
 			struct ExprList_item *pItem = &pOrderBy->a[i];
 			Expr *pTerm = pItem->pExpr;
-			struct coll *pColl;
+			struct coll *coll;
 
 			if (pTerm->flags & EP_Collate) {
-				pColl = sqlite3ExprCollSeq(pParse, pTerm);
+				bool found; /* Not used.  */
+				coll = sql_expr_coll(pParse, pTerm, &found);
 			} else {
-				pColl =
+				bool found = false;
+				coll =
 				    multiSelectCollSeq(pParse, p,
-						       pItem->u.x.iOrderByCol - 1);
-				if (pColl == 0)
-					pColl = sql_default_coll();
-				pOrderBy->a[i].pExpr =
-				    sqlite3ExprAddCollateString(pParse, pTerm,
-								pColl->name);
+						       pItem->u.x.iOrderByCol - 1,
+						       &found);
+				if (coll != NULL) {
+					pOrderBy->a[i].pExpr =
+						sqlite3ExprAddCollateString(pParse, pTerm,
+									    coll->name);
+				} else {
+					pOrderBy->a[i].pExpr =
+						sqlite3ExprAddCollateString(pParse, pTerm,
+									    "BINARY");
+				}
 			}
 			assert(sqlite3KeyInfoIsWriteable(pRet));
-			pRet->aColl[i] = pColl;
+			pRet->aColl[i] = coll;
 			pRet->aSortOrder[i] = pOrderBy->a[i].sortOrder;
 		}
 	}
@@ -2827,10 +2840,8 @@ multiSelect(Parse * pParse,	/* Parsing context */
 			goto multi_select_end;
 		}
 		for (i = 0, apColl = pKeyInfo->aColl; i < nCol; i++, apColl++) {
-			*apColl = multiSelectCollSeq(pParse, p, i);
-			if (0 == *apColl) {
-				*apColl = sql_default_coll();
-			}
+			bool found = false; /* Not used.  */
+			*apColl = multiSelectCollSeq(pParse, p, i, &found);
 		}
 
 		for (pLoop = p; pLoop; pLoop = pLoop->pPrior) {
@@ -3260,8 +3271,9 @@ multiSelectOrderBy(Parse * pParse,	/* Parsing context */
 		if (pKeyDup) {
 			assert(sqlite3KeyInfoIsWriteable(pKeyDup));
 			for (i = 0; i < nExpr; i++) {
+				bool found = false; /* Not used. */
 				pKeyDup->aColl[i] =
-				    multiSelectCollSeq(pParse, p, i);
+					multiSelectCollSeq(pParse, p, i, &found);
 				pKeyDup->aSortOrder[i] = 0;
 			}
 		}
@@ -5241,22 +5253,21 @@ updateAccumulator(Parse * pParse, AggInfo * pAggInfo)
 				     regAgg);
 		}
 		if (pF->pFunc->funcFlags & SQLITE_FUNC_NEEDCOLL) {
-			struct coll *pColl = 0;
+			struct coll *coll = NULL;
 			struct ExprList_item *pItem;
 			int j;
 			assert(pList != 0);	/* pList!=0 if pF->pFunc has NEEDCOLL */
-			for (j = 0, pItem = pList->a; !pColl && j < nArg;
+			bool found = false;
+			for (j = 0, pItem = pList->a; !found && j < nArg;
 			     j++, pItem++) {
-				pColl =
-				    sqlite3ExprCollSeq(pParse, pItem->pExpr);
-			}
-			if (!pColl) {
-				pColl = sql_default_coll();
+				coll = sql_expr_coll(pParse,
+						     pItem->pExpr,
+						     &found);
 			}
 			if (regHit == 0 && pAggInfo->nAccumulator)
 				regHit = ++pParse->nMem;
 			sqlite3VdbeAddOp4(v, OP_CollSeq, regHit, 0, 0,
-					  (char *)pColl, P4_COLLSEQ);
+					  (char *)coll, P4_COLLSEQ);
 		}
 		sqlite3VdbeAddOp3(v, OP_AggStep0, 0, regAgg, pF->iMem);
 		sqlite3VdbeAppendP4(v, pF->pFunc, P4_FUNCDEF);
diff --git a/src/box/sql/sqliteInt.h b/src/box/sql/sqliteInt.h
index 59662cf..3c59589 100644
--- a/src/box/sql/sqliteInt.h
+++ b/src/box/sql/sqliteInt.h
@@ -1878,10 +1878,13 @@ struct Column {
 	char *zName;		/* Name of this column */
 	enum field_type type;	/* Column type. */
 	Expr *pDflt;		/* Default value of this column */
-	char *zColl;		/* Collating sequence.  If NULL, use the default */
-	enum on_conflict_action notNull;  /* An ON_CONFLICT_ACTION code for
-					   * handling a NOT NULL constraint
-					   */
+	/** Collating sequence. */
+	struct coll *coll;
+	/**
+	 * An ON_CONFLICT_ACTION code for handling a NOT NULL
+	 * constraint.
+	 */
+	enum on_conflict_action notNull;
 	char affinity;		/* One of the SQLITE_AFF_... values */
 	u8 szEst;		/* Estimated size of value in this column. sizeof(INT)==1 */
 	u8 is_primkey;		/* Boolean propertie for being PK */
@@ -2148,7 +2151,8 @@ struct Index {
 	Index *pNext;		/* The next index associated with the same table */
 	Schema *pSchema;	/* Schema containing this index */
 	u8 *aSortOrder;		/* for each column: True==DESC, False==ASC */
-	const char **azColl;	/* Array of collation sequence names for index */
+	/**  Array of collation sequences for index. */
+	struct coll **coll_array;
 	Expr *pPartIdxWhere;	/* WHERE clause for partial indices */
 	ExprList *aColExpr;	/* Column expressions */
 	int tnum;		/* DB Page containing root of this index */
@@ -3548,14 +3552,20 @@ void sqlite3AddPrimaryKey(Parse *, ExprList *, int, int, int);
 void sqlite3AddCheckConstraint(Parse *, Expr *);
 void sqlite3AddDefaultValue(Parse *, ExprSpan *);
 void sqlite3AddCollateType(Parse *, Token *);
+
 const char *
 column_collation_name(Table *, uint32_t);
+struct coll *
+sql_column_collation(Table *, uint32_t);
 const char *
 index_collation_name(Index *, uint32_t);
 struct coll *
+sql_index_collation(Index *idx, uint32_t column);
+struct coll *
 sql_default_coll();
 bool
 space_is_view(Table *);
+
 void sqlite3EndTable(Parse *, Token *, Token *, Select *);
 int
 emit_open_cursor(Parse *, int, int);
@@ -3845,7 +3855,25 @@ const char *sqlite3ErrName(int);
 const char *sqlite3ErrStr(int);
 struct coll *sqlite3FindCollSeq(const char *);
 struct coll *sqlite3LocateCollSeq(Parse * pParse, sqlite3 * db, const char *zName);
-struct coll *sqlite3ExprCollSeq(Parse * pParse, Expr * pExpr);
+
+/**
+ * Return the collation sequence for the expression pExpr. If
+ * there is no defined collating sequence, return NULL.
+ *
+ * The collating sequence might be determined by a COLLATE operator
+ * or by the presence of a column with a defined collating sequence.
+ * COLLATE operators take first precedence.  Left operands take
+ * precedence over right operands.
+ *
+ * @param parse Parsing context.
+ * @param expr Expression to scan
+ * @param found Flag set if collation was found
+ *
+ * @retval Pointer to collation.
+ */
+struct coll *
+sql_expr_coll(Parse * pParse, Expr * pExpr, bool *found);
+
 Expr *sqlite3ExprAddCollateToken(Parse * pParse, Expr *, const Token *, int);
 Expr *sqlite3ExprAddCollateString(Parse *, Expr *, const char *);
 Expr *sqlite3ExprSkipCollate(Expr *);
diff --git a/src/box/sql/vdbe.c b/src/box/sql/vdbe.c
index 8237183..321ce2f 100644
--- a/src/box/sql/vdbe.c
+++ b/src/box/sql/vdbe.c
@@ -1660,7 +1660,7 @@ case OP_Remainder: {           /* same as TK_REM, in1, in2, out3 */
  * publicly.  Only built-in functions have access to this feature.
  */
 case OP_CollSeq: {
-	assert(pOp->p4type==P4_COLLSEQ);
+	assert(pOp->p4type==P4_COLLSEQ || pOp->p4.pColl == NULL);
 	if (pOp->p1) {
 		sqlite3VdbeMemSetInt64(&aMem[pOp->p1], 0);
 	}
diff --git a/src/box/sql/vdbeaux.c b/src/box/sql/vdbeaux.c
index bb121a3..e8a0917 100644
--- a/src/box/sql/vdbeaux.c
+++ b/src/box/sql/vdbeaux.c
@@ -3840,6 +3840,13 @@ sqlite3MemCompare(const Mem * pMem1, const Mem * pMem2, const struct coll * pCol
 		 */
 		if (pColl) {
 			return vdbeCompareMemString(pMem1, pMem2, pColl, 0);
+		} else {
+			size_t n = pMem1->n < pMem2->n ? pMem1->n : pMem2->n;
+			int res;
+			res = memcmp(pMem1->z, pMem2->z, n);
+			if (res == 0)
+				res = (int)pMem1->n - (int)pMem2->n;
+			return res;
 		}
 		/* If a NULL pointer was passed as the collate function, fall through
 		 * to the blob case and use memcmp().
diff --git a/src/box/sql/vdbesort.c b/src/box/sql/vdbesort.c
index fc10ef6..be3cc4c 100644
--- a/src/box/sql/vdbesort.c
+++ b/src/box/sql/vdbesort.c
@@ -930,9 +930,7 @@ sqlite3VdbeSorterInit(sqlite3 * db,	/* Database connection (for malloc()) */
 		}
 
 		if ((pKeyInfo->nField + pKeyInfo->nXField) < 13
-		    && (pKeyInfo->aColl[0] == 0
-			|| pKeyInfo->aColl[0] == sql_default_coll())
-		    ) {
+		    && (pKeyInfo->aColl[0] == NULL)) {
 			pSorter->typeMask =
 			    SORTER_TYPE_INTEGER | SORTER_TYPE_TEXT;
 		}
diff --git a/src/box/sql/where.c b/src/box/sql/where.c
index 2a26302..0b4ab47 100644
--- a/src/box/sql/where.c
+++ b/src/box/sql/where.c
@@ -298,26 +298,20 @@ whereScanNext(WhereScan * pScan)
 					if ((pTerm->eOperator & pScan->
 					     opMask) != 0) {
 						/* Verify the affinity and collating sequence match */
-						if (pScan->zCollName
-						    && (pTerm->eOperator & WO_ISNULL) == 0) {
-							struct coll *pColl;
-							Parse *pParse =
-							    pWC->pWInfo->pParse;
+						if ((pTerm->eOperator & WO_ISNULL) == 0) {
 							pX = pTerm->pExpr;
-							if (!sqlite3IndexAffinityOk(pX, pScan->idxaff)) {
-								continue;
-							}
-							assert(pX->pLeft);
-							pColl =
-							    sqlite3BinaryCompareCollSeq
-							    (pParse, pX->pLeft,
-							     pX->pRight);
-							if (pColl == 0)
-								pColl =
-									sql_default_coll();
-							if (strcmp(pColl->name,
-									   pScan->zCollName)) {
+							if (!sqlite3IndexAffinityOk(pX, pScan->idxaff))
 								continue;
+							if (pScan->column_seen) {
+								Parse *pParse =
+									pWC->pWInfo->pParse;
+								struct coll *coll;
+								assert(pX->pLeft);
+								coll = sqlite3BinaryCompareCollSeq
+									(pParse, pX->pLeft,
+									 pX->pRight);
+								if (coll != pScan->coll)
+									continue;
 							}
 						}
 						if ((pTerm->eOperator & (WO_EQ | WO_IS)) != 0
@@ -376,7 +370,8 @@ whereScanInit(WhereScan * pScan,	/* The WhereScan object being initialized */
 	pScan->pWC = pWC;
 	pScan->pIdxExpr = 0;
 	pScan->idxaff = 0;
-	pScan->zCollName = 0;
+	pScan->coll = NULL;
+	pScan->column_seen = false;
 	if (pIdx) {
 		int j = iColumn;
 		iColumn = pIdx->aiColumn[j];
@@ -384,7 +379,8 @@ whereScanInit(WhereScan * pScan,	/* The WhereScan object being initialized */
 			pScan->pIdxExpr = pIdx->aColExpr->a[j].pExpr;
 		} else if (iColumn >= 0) {
 			pScan->idxaff = pIdx->pTable->aCol[iColumn].affinity;
-			pScan->zCollName = index_collation_name(pIdx, j);
+			pScan->coll = sql_index_collation(pIdx, j);
+			pScan->column_seen = true;
 		}
 	} else if (iColumn == XN_EXPR) {
 		return 0;
@@ -465,16 +461,17 @@ findIndexCol(Parse * pParse,	/* Parse context */
 	     Index * pIdx,	/* Index to match column of */
 	     int iCol)		/* Column of index to match */
 {
-	int i;
-	const char *zColl = index_collation_name(pIdx, iCol);
-
-	for (i = 0; i < pList->nExpr; i++) {
+	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) {
-			struct coll *pColl =
-			    sqlite3ExprCollSeq(pParse, pList->a[i].pExpr);
-			if (pColl && 0 == strcmp(pColl->name, zColl)) {
+		if (p->op == TK_COLUMN &&
+		    p->iColumn == pIdx->aiColumn[iCol] &&
+		    p->iTable == iBase) {
+			bool found;
+			struct coll *coll = sql_expr_coll(pParse,
+							  pList->a[i].pExpr,
+							  &found);
+			if (found &&
+			    coll == sql_index_collation(pIdx, iCol)) {
 				return i;
 			}
 		}
@@ -1174,7 +1171,7 @@ whereRangeSkipScanEst(Parse * pParse,		/* Parsing & code generating context */
 	sqlite3_value *p2 = 0;	/* Value extracted from pUpper */
 	sqlite3_value *pVal = 0;	/* Value extracted from record */
 
-	pColl = sqlite3LocateCollSeq(pParse, db, index_collation_name(p, nEq));
+	pColl = sql_index_collation(p, nEq);
 	if (pLower) {
 		rc = sqlite3Stat4ValueFromExpr(pParse, pLower->pExpr->pRight,
 					       aff, &p1);
@@ -2246,8 +2243,7 @@ whereRangeVectorLen(Parse * pParse,	/* Parsing context */
 		pColl = sqlite3BinaryCompareCollSeq(pParse, pLhs, pRhs);
 		if (pColl == 0)
 			break;
-		const char *zCollName = index_collation_name(pIdx, i + nEq);
-		if (zCollName && strcmp(pColl->name, zCollName))
+	        if (sql_index_collation(pIdx, i + nEq) != pColl)
 			break;
 	}
 	return i;
@@ -3147,7 +3143,6 @@ wherePathSatisfiesOrderBy(WhereInfo * pWInfo,	/* The WHERE clause */
 	WhereLoop *pLoop = 0;	/* Current WhereLoop being processed. */
 	WhereTerm *pTerm;	/* A single term of the WHERE clause */
 	Expr *pOBExpr;		/* An expression from the ORDER BY clause */
-	struct coll *pColl;		/* COLLATE function from an ORDER BY clause term */
 	Index *pIndex;		/* The index associated with pLoop */
 	sqlite3 *db = pWInfo->pParse->db;	/* Database connection */
 	Bitmask obSat = 0;	/* Mask of ORDER BY terms satisfied so far */
@@ -3235,20 +3230,15 @@ wherePathSatisfiesOrderBy(WhereInfo * pWInfo,	/* The WHERE clause */
 			}
 			if ((pTerm->eOperator & (WO_EQ | WO_IS)) != 0
 			    && pOBExpr->iColumn >= 0) {
-				const char *z1, *z2;
-				pColl =
-				    sqlite3ExprCollSeq(pWInfo->pParse,
-						       pOrderBy->a[i].pExpr);
-				if (!pColl)
-					pColl = sql_default_coll();
-				z1 = pColl->name;
-				pColl =
-				    sqlite3ExprCollSeq(pWInfo->pParse,
-						       pTerm->pExpr);
-				if (!pColl)
-					pColl = sql_default_coll();
-				z2 = pColl->name;
-				if (strcmp(z1, z2) != 0)
+				struct coll *coll1, *coll2;
+				bool found; /* Not used. */
+				coll1 = sql_expr_coll(pWInfo->pParse,
+						      pOrderBy->a[i].pExpr,
+						      &found);
+				coll2 = sql_expr_coll(pWInfo->pParse,
+						      pTerm->pExpr,
+						      &found);
+				if (coll1 != coll2)
 					continue;
 				testcase(pTerm->pExpr->op == TK_IS);
 			}
@@ -3372,15 +3362,15 @@ wherePathSatisfiesOrderBy(WhereInfo * pWInfo,	/* The WHERE clause */
 						}
 					}
 					if (iColumn >= 0) {
-						pColl =
-						    sqlite3ExprCollSeq(pWInfo->pParse,
-								       pOrderBy->a[i].pExpr);
-						if (!pColl)
-							pColl = sql_default_coll();
-						const char *zCollName =
-							index_collation_name(pIndex, j);
-						if (strcmp(pColl->name,
-							   zCollName) != 0)
+						bool found;
+						struct coll *coll;
+						coll = sql_expr_coll(pWInfo->pParse,
+								     pOrderBy->a[i].pExpr,
+								     &found);
+						struct coll *idx_coll;
+						idx_coll = sql_index_collation(pIndex,
+									       j);
+						if (found && coll != idx_coll)
 							continue;
 					}
 					isMatch = 1;
diff --git a/src/box/sql/whereInt.h b/src/box/sql/whereInt.h
index 381a1d2..b85cd73 100644
--- a/src/box/sql/whereInt.h
+++ b/src/box/sql/whereInt.h
@@ -293,7 +293,12 @@ struct WhereTerm {
 struct WhereScan {
 	WhereClause *pOrigWC;	/* Original, innermost WhereClause */
 	WhereClause *pWC;	/* WhereClause currently being scanned */
-	const char *zCollName;	/* Required collating sequence, if not NULL */
+	/** Required collating sequence. */
+	struct coll *coll;
+	/** Explicitly specified BINARY collation. */
+	bool cool_binary;
+	/** Flag is set if actual column was encountered. */
+	bool column_seen;
 	Expr *pIdxExpr;		/* Search for this index expression */
 	char idxaff;		/* Must match this affinity, if zCollName!=NULL */
 	unsigned char nEquiv;	/* Number of entries in aEquiv[] */
diff --git a/src/box/sql/whereexpr.c b/src/box/sql/whereexpr.c
index ccdff46..917c2dc 100644
--- a/src/box/sql/whereexpr.c
+++ b/src/box/sql/whereexpr.c
@@ -165,12 +165,19 @@ exprCommute(Parse * pParse, Expr * pExpr)
 			 * used by clearing the EP_Collate flag from Y.
 			 */
 			pExpr->pRight->flags &= ~EP_Collate;
-		} else if (sqlite3ExprCollSeq(pParse, pExpr->pLeft) != 0) {
-			/* Neither X nor Y have COLLATE operators, but X has a non-default
-			 * collating sequence.  So add the EP_Collate marker on X to cause
-			 * it to be searched first.
-			 */
-			pExpr->pLeft->flags |= EP_Collate;
+		} else {
+			bool found;
+			sql_expr_coll(pParse, pExpr->pLeft, &found);
+			if (found) {
+				/* Neither X nor Y have COLLATE
+				 * operators, but X has a
+				 * non-default collating sequence.
+				 * So add the EP_Collate marker on
+				 * X to cause it to be searched
+				 * first.
+				 */
+				pExpr->pLeft->flags |= EP_Collate;
+			}
 		}
 	}
 	SWAP(pExpr->pRight, pExpr->pLeft);
@@ -822,7 +829,6 @@ static int
 termIsEquivalence(Parse * pParse, Expr * pExpr)
 {
 	char aff1, aff2;
-	struct coll *pColl;
 	const char *zColl1, *zColl2;
 	if (!OptimizationEnabled(pParse->db, SQLITE_Transitive))
 		return 0;
@@ -838,14 +844,16 @@ termIsEquivalence(Parse * pParse, Expr * pExpr)
 	    ) {
 		return 0;
 	}
-	pColl =
+	struct coll *coll;
+	coll =
 	    sqlite3BinaryCompareCollSeq(pParse, pExpr->pLeft, pExpr->pRight);
-	if (pColl == 0 || sqlite3StrICmp(pColl->name, "BINARY") == 0)
+	if (coll == NULL || sqlite3StrICmp(coll->name, "BINARY") == 0)
 		return 1;
-	pColl = sqlite3ExprCollSeq(pParse, pExpr->pLeft);
-	zColl1 = pColl ? pColl->name : 0;
-	pColl = sqlite3ExprCollSeq(pParse, pExpr->pRight);
-	zColl2 = pColl ? pColl->name : 0;
+	bool found;
+	coll = sql_expr_coll(pParse, pExpr->pLeft, &found);
+	zColl1 = coll ? coll->name : NULL;
+	coll = sql_expr_coll(pParse, pExpr->pRight, &found);
+	zColl2 = coll ? coll->name : NULL;
 	return sqlite3_stricmp(zColl1, zColl2) == 0;
 }
 
-- 
2.11.0

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

* [tarantool-patches] [PATCH 2/2] sql: replace KeyInfo with key_def
  2018-05-08  7:56 [tarantool-patches] [PATCH 0/2] sql: replace KeyInfo w/ key_def in SQL front-end Kirill Yukhin
  2018-05-08  7:56 ` [tarantool-patches] [PATCH 1/2] sql: introduce sort order to key_part/key_part_def Kirill Yukhin
  2018-05-08  7:56 ` [tarantool-patches] [PATCH] sql: use collation pointers instead of names Kirill Yukhin
@ 2018-05-08  7:56 ` Kirill Yukhin
  2018-05-08 16:02   ` [tarantool-patches] " Vladislav Shpilevoy
  2 siblings, 1 reply; 17+ messages in thread
From: Kirill Yukhin @ 2018-05-08  7:56 UTC (permalink / raw)
  To: v.shpilevoy; +Cc: tarantool-patches, Kirill Yukhin

KeyInfo is a legacy struct which was heavily used in SQL
front-end. This patch replaces all its usages w/ Tarantool's
natural key description structure called key_def.

This change is a prt of data dictionary intagration effort:
Tarantool indexes don't aware of KeyInfo, that is why it was
evicted.

Legacy KeyInfo memory handling was ref-counting based and now
is replaced w/ memory duplication. This state of affairs should
be improved in future.

Part of #3235
---
 src/box/sql.c                 |  19 ++-
 src/box/sql/analyze.c         |   6 +-
 src/box/sql/build.c           |  79 +++------
 src/box/sql/delete.c          |   6 +-
 src/box/sql/expr.c            |  52 +++---
 src/box/sql/fkey.c            |   2 +-
 src/box/sql/insert.c          |  12 +-
 src/box/sql/pragma.c          |   4 +-
 src/box/sql/select.c          | 381 ++++++++++++++++++------------------------
 src/box/sql/sqliteInt.h       |  50 ++----
 src/box/sql/tarantoolInt.h    |   5 +-
 src/box/sql/update.c          |   6 +-
 src/box/sql/vdbe.c            |  80 +++++----
 src/box/sql/vdbe.h            |  34 ++--
 src/box/sql/vdbeInt.h         |  13 +-
 src/box/sql/vdbeapi.c         | 183 --------------------
 src/box/sql/vdbeaux.c         | 247 ++++++++++-----------------
 src/box/sql/vdbemem.c         |  14 +-
 src/box/sql/vdbesort.c        |  54 +++---
 src/box/sql/where.c           |  21 ++-
 src/box/sql/wherecode.c       |   2 +-
 src/box/tuple.c               |   9 +
 src/box/tuple.h               |   9 +
 test/sql-tap/index1.test.lua  |   2 +
 test/sql-tap/index4.test.lua  |   1 +
 test/sql-tap/selectA.test.lua |   1 +
 26 files changed, 496 insertions(+), 796 deletions(-)

diff --git a/src/box/sql.c b/src/box/sql.c
index 838fcf6..42372ea 100644
--- a/src/box/sql.c
+++ b/src/box/sql.c
@@ -371,7 +371,7 @@ int tarantoolSqlite3Count(BtCursor *pCur, i64 *pnEntry)
  * @retval SQLITE_OK on success, SQLITE_TARANTOOL_ERROR otherwise.
  */
 int tarantoolSqlite3EphemeralCreate(BtCursor *pCur, uint32_t field_count,
-				    struct coll *aColl)
+				    struct key_def *def)
 {
 	assert(pCur);
 	assert(pCur->curFlags & BTCF_TEphemCursor);
@@ -380,11 +380,16 @@ int tarantoolSqlite3EphemeralCreate(BtCursor *pCur, uint32_t field_count,
 	if (ephemer_key_def == NULL)
 		return SQL_TARANTOOL_ERROR;
 	for (uint32_t part = 0; part < field_count; ++part) {
+		struct coll *coll;
+		if (part < def->part_count)
+			coll = def->parts[part].coll;
+		else
+			coll = NULL;
 		key_def_set_part(ephemer_key_def, part /* part no */,
 				 part /* filed no */,
 				 FIELD_TYPE_SCALAR,
 				 ON_CONFLICT_ACTION_NONE /* nullable_action */,
-				 aColl /* coll */,
+				 coll /* coll */,
 				 SORT_ORDER_ASC);
 	}
 
@@ -933,7 +938,8 @@ rename_fail:
  * Performs exactly as extract_key + sqlite3VdbeCompareMsgpack,
  * only faster.
  */
-int tarantoolSqlite3IdxKeyCompare(BtCursor *pCur, UnpackedRecord *pUnpacked,
+int tarantoolSqlite3IdxKeyCompare(struct sqlite3 *db,
+				  BtCursor *pCur, UnpackedRecord *pUnpacked,
 				  int *res)
 {
 	assert(pCur->curFlags & BTCF_TaCursor);
@@ -995,9 +1001,10 @@ int tarantoolSqlite3IdxKeyCompare(BtCursor *pCur, UnpackedRecord *pUnpacked,
 			}
 		}
 		next_fieldno = fieldno + 1;
-		rc = sqlite3VdbeCompareMsgpack(&p, pUnpacked, i);
+		rc = sqlite3VdbeCompareMsgpack(db, &p, pUnpacked, i);
 		if (rc != 0) {
-			if (pUnpacked->pKeyInfo->aSortOrder[i]) {
+			if (pUnpacked->key_def->parts[i].sort_order !=
+			    SORT_ORDER_ASC) {
 				rc = -rc;
 			}
 			*res = rc;
@@ -1011,7 +1018,7 @@ out:
 	original_size = region_used(&fiber()->gc);
 	key = tuple_extract_key(tuple, key_def, &key_size);
 	if (key != NULL) {
-		rc = sqlite3VdbeRecordCompareMsgpack((int)key_size, key,
+		rc = sqlite3VdbeRecordCompareMsgpack(db, (int)key_size, key,
 						     pUnpacked);
 		region_truncate(&fiber()->gc, original_size);
 		assert(rc == *res);
diff --git a/src/box/sql/analyze.c b/src/box/sql/analyze.c
index f0054c5..ec23481 100644
--- a/src/box/sql/analyze.c
+++ b/src/box/sql/analyze.c
@@ -176,8 +176,8 @@ openStatTable(Parse * pParse,	/* Parsing context */
 	/* Open the sql_stat[134] tables for writing. */
 	for (i = 0; aTable[i]; i++) {
 		int addr = emit_open_cursor(pParse, iStatCur + i, aRoot[i]);
-		v->aOp[addr].p4.pKeyInfo = 0;
-		v->aOp[addr].p4type = P4_KEYINFO;
+		v->aOp[addr].p4.key_def = NULL;
+		v->aOp[addr].p4type = P4_KEYDEF;
 		sqlite3VdbeChangeP5(v, aCreateTbl[i]);
 		VdbeComment((v, aTable[i]));
 	}
@@ -914,7 +914,7 @@ analyzeOneTable(Parse * pParse,	/* Parser context */
 				     (void *) space);
 		sqlite3VdbeAddOp3(v, OP_OpenRead, iIdxCur, pIdx->tnum,
 				  space_ptr_reg);
-		sqlite3VdbeSetP4KeyInfo(pParse, pIdx);
+		sql_vdbe_set_p4_key_def(pParse, pIdx);
 		VdbeComment((v, "%s", pIdx->zName));
 
 		/* Invoke the stat_init() function. The arguments are:
diff --git a/src/box/sql/build.c b/src/box/sql/build.c
index ae662fb..f9b54ef 100644
--- a/src/box/sql/build.c
+++ b/src/box/sql/build.c
@@ -1094,6 +1094,18 @@ sql_column_collation(Table *table, uint32_t column)
 	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->pTable->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 key_def_dup(index->def->key_def);
+}
+
 /**
  * Return name of given column collation from index.
  *
@@ -1119,10 +1131,9 @@ sql_index_collation(Index *idx, uint32_t column)
 		return idx->coll_array[column];
 	}
 
-	uint32_t index_id = SQLITE_PAGENO_TO_INDEXID(idx->tnum);
-	struct index *index = space_index(space, index_id);
-	assert(index != NULL && index->def->key_def->part_count >= column);
-	return index->def->key_def->parts[column].coll;
+	struct key_def *key_def = sql_index_key_def(idx);
+	assert(key_def != NULL && key_def->part_count >= column);
+	return key_def->parts[column].coll;
 }
 
 enum sort_order
@@ -1143,10 +1154,9 @@ sql_index_column_sort_order(Index *idx, uint32_t column)
 		return idx->sort_order[column];
 	}
 
-	uint32_t index_id = SQLITE_PAGENO_TO_INDEXID(idx->tnum);
-	struct index *index = space_index(space, index_id);
-	assert(index != NULL && index->def->key_def->part_count >= column);
-	return index->def->key_def->parts[column].sort_order;
+	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;
 }
 
 /**
@@ -1443,9 +1453,7 @@ hasColumn(const i16 * aiCol, int nCol, int x)
  *     (2)  Set the Index.tnum of the PRIMARY KEY Index object in the
  *          schema to the rootpage from the main table.
  *     (3)  Add all table columns to the PRIMARY KEY Index object
- *          so that the PRIMARY KEY is a covering index.  The surplus
- *          columns are part of KeyInfo.nXField and are not used for
- *          sorting or lookup or uniqueness checks.
+ *          so that the PRIMARY KEY is a covering index.
  */
 static void
 convertToWithoutRowidTable(Parse * pParse, Table * pTab)
@@ -2632,7 +2640,6 @@ sqlite3RefillIndex(Parse * pParse, Index * pIndex, int memRootPage)
 	int tnum;		/* Root page of index */
 	int iPartIdxLabel;	/* Jump to this label to skip a row */
 	Vdbe *v;		/* Generate code into this virtual machine */
-	KeyInfo *pKey;		/* KeyInfo for index */
 	int regRecord;		/* Register holding assembled index record */
 	sqlite3 *db = pParse->db;	/* The database connection */
 	v = sqlite3GetVdbe(pParse);
@@ -2643,14 +2650,14 @@ sqlite3RefillIndex(Parse * pParse, Index * pIndex, int memRootPage)
 	} else {
 		tnum = pIndex->tnum;
 	}
-	pKey = sqlite3KeyInfoOfIndex(pParse, db, pIndex);
-	assert(pKey != 0 || db->mallocFailed || pParse->nErr);
+	struct key_def *def = sql_index_key_def(pIndex);
+	assert(def != NULL || db->mallocFailed || pParse->nErr);
 
 	/* Open the sorter cursor if we are to use one. */
 	iSorter = pParse->nTab++;
 	sqlite3VdbeAddOp4(v, OP_SorterOpen, iSorter, 0, pIndex->nColumn,
-			  (char *)
-			  sqlite3KeyInfoRef(pKey), P4_KEYINFO);
+			  (char *)def, P4_KEYDEF);
+	assert(0);
 
 	/* Open the table. Loop through all rows of the table, inserting index
 	 * records into the sorter.
@@ -4194,46 +4201,6 @@ sqlite3Reindex(Parse * pParse, Token * pName1, Token * pName2)
 }
 #endif
 
-/*
- * Return a KeyInfo structure that is appropriate for the given Index.
- *
- * The caller should invoke sqlite3KeyInfoUnref() on the returned object
- * when it has finished using it.
- */
-KeyInfo *
-sqlite3KeyInfoOfIndex(Parse * pParse, sqlite3 * db, Index * pIdx)
-{
-	int i;
-	int nCol = pIdx->nColumn;
-	int nTableCol = pIdx->pTable->nCol;
-	KeyInfo *pKey;
-
-	if (pParse && pParse->nErr)
-		return 0;
-
-	/*
-	 * KeyInfo describes the index (i.e. the number of key columns,
-	 * comparator options, and the number of columns beyond the key).
-	 * Since Tarantool iterator yields the full tuple, we need a KeyInfo
-	 * as wide as the table itself.  Otherwize, not enough slots
-	 * for row parser cache are allocated in VdbeCursor object.
-	 */
-	pKey = sqlite3KeyInfoAlloc(db, nCol, nTableCol - nCol);
-	if (pKey) {
-		assert(sqlite3KeyInfoIsWriteable(pKey));
-		for (i = 0; i < nCol; i++) {
-			pKey->aColl[i] = sql_index_collation(pIdx, i);
-			pKey->aSortOrder[i] = sql_index_column_sort_order(pIdx,
-									  i);
-		}
-		if (pParse && pParse->nErr) {
-			sqlite3KeyInfoUnref(pKey);
-			pKey = 0;
-		}
-	}
-	return pKey;
-}
-
 #ifndef SQLITE_OMIT_CTE
 /*
  * This routine is invoked once per CTE by the parser while parsing a
diff --git a/src/box/sql/delete.c b/src/box/sql/delete.c
index 3f74b93..b6fc135 100644
--- a/src/box/sql/delete.c
+++ b/src/box/sql/delete.c
@@ -386,10 +386,10 @@ sqlite3DeleteFrom(Parse * pParse,	/* The parser context */
 			iPk = pParse->nMem + 1;
 			pParse->nMem += nPk;
 			iEphCur = pParse->nTab++;
-			KeyInfo *pKeyInfo = sqlite3KeyInfoAlloc(pParse->db, nPk, 0);
+			struct key_def *def = key_def_new(nPk);
 			addrEphOpen =
 				sqlite3VdbeAddOp4(v, OP_OpenTEphemeral, iEphCur,
-						  nPk, 0, (char*) pKeyInfo, P4_KEYINFO);
+						  nPk, 0, (char*)def, P4_KEYDEF);
 		} else {
 			pPk = sqlite3PrimaryKeyIndex(pTab);
 			assert(pPk != 0);
@@ -400,7 +400,7 @@ sqlite3DeleteFrom(Parse * pParse,	/* The parser context */
 			addrEphOpen =
 			    sqlite3VdbeAddOp2(v, OP_OpenTEphemeral, iEphCur,
 					      nPk);
-			sqlite3VdbeSetP4KeyInfo(pParse, pPk);
+			sql_vdbe_set_p4_key_def(pParse, pPk);
 		}
 
 		/* Construct a query to find the primary key for every row
diff --git a/src/box/sql/expr.c b/src/box/sql/expr.c
index cc969ca..e5405dd 100644
--- a/src/box/sql/expr.c
+++ b/src/box/sql/expr.c
@@ -2522,7 +2522,7 @@ sqlite3FindInIndex(Parse * pParse,	/* Parsing context */
 							  P4_DYNAMIC);
 					emit_open_cursor(pParse, iTab,
 							 pIdx->tnum);
-					sqlite3VdbeSetP4KeyInfo(pParse, pIdx);
+					sql_vdbe_set_p4_key_def(pParse, pIdx);
 					VdbeComment((v, "%s", pIdx->zName));
 					assert(IN_INDEX_INDEX_DESC ==
 					       IN_INDEX_INDEX_ASC + 1);
@@ -2739,7 +2739,6 @@ sqlite3CodeSubselect(Parse * pParse,	/* Parsing context */
 	case TK_IN:{
 			int addr;	/* Address of OP_OpenEphemeral instruction */
 			Expr *pLeft = pExpr->pLeft;	/* the LHS of the IN operator */
-			KeyInfo *pKeyInfo = 0;	/* Key information */
 			int nVal;	/* Size of vector pLeft */
 
 			nVal = sqlite3ExprVectorSize(pLeft);
@@ -2761,7 +2760,7 @@ sqlite3CodeSubselect(Parse * pParse,	/* Parsing context */
 			pExpr->is_ephemeral = 1;
 			addr = sqlite3VdbeAddOp2(v, OP_OpenTEphemeral,
 						 pExpr->iTable, nVal);
-			pKeyInfo = sqlite3KeyInfoAlloc(pParse->db, nVal, 1);
+			struct key_def *key_def = key_def_new(nVal);
 
 			if (ExprHasProperty(pExpr, EP_xIsSelect)) {
 				/* Case 1:     expr IN (SELECT ...)
@@ -2787,29 +2786,34 @@ sqlite3CodeSubselect(Parse * pParse,	/* Parsing context */
 					pSelect->iLimit = 0;
 					testcase(pSelect->
 						 selFlags & SF_Distinct);
-					testcase(pKeyInfo == 0);	/* Caused by OOM in sqlite3KeyInfoAlloc() */
 					if (sqlite3Select
 					    (pParse, pSelect, &dest)) {
 						sqlite3DbFree(pParse->db,
 							      dest.zAffSdst);
-						sqlite3KeyInfoUnref(pKeyInfo);
+						if (key_def != NULL)
+							free(key_def);
 						return 0;
 					}
 					sqlite3DbFree(pParse->db,
 						      dest.zAffSdst);
-					assert(pKeyInfo != 0);	/* OOM will cause exit after sqlite3Select() */
+					assert(key_def != NULL);
 					assert(pEList != 0);
 					assert(pEList->nExpr > 0);
-					assert(sqlite3KeyInfoIsWriteable
-					       (pKeyInfo));
 					for (i = 0; i < nVal; i++) {
 						Expr *p =
 						    sqlite3VectorFieldSubexpr
 						    (pLeft, i);
-						pKeyInfo->aColl[i] =
-						    sqlite3BinaryCompareCollSeq
-						    (pParse, p,
-						     pEList->a[i].pExpr);
+
+						struct coll *coll;
+						coll = sqlite3BinaryCompareCollSeq
+							(pParse, p,
+							 pEList->a[i].pExpr);
+
+						key_def_set_part(key_def, i, i,
+								 FIELD_TYPE_SCALAR,
+								 ON_CONFLICT_ACTION_ABORT,
+								 coll,
+								 SORT_ORDER_ASC);
 					}
 				}
 			} else if (ALWAYS(pExpr->x.pList != 0)) {
@@ -2830,14 +2834,18 @@ sqlite3CodeSubselect(Parse * pParse,	/* Parsing context */
 				if (!affinity) {
 					affinity = SQLITE_AFF_BLOB;
 				}
-				if (pKeyInfo) {
+				if (key_def != NULL) {
 					bool unused;
-					assert(sqlite3KeyInfoIsWriteable
-					       (pKeyInfo));
-					pKeyInfo->aColl[0] =
-						sql_expr_coll(pParse,
-							      pExpr->pLeft,
-							      &unused);
+					struct coll *coll;
+					coll = sql_expr_coll(pParse,
+							     pExpr->pLeft,
+							     &unused);
+
+					key_def_set_part(key_def, 0, 0,
+							 FIELD_TYPE_SCALAR,
+							 ON_CONFLICT_ACTION_ABORT,
+							 coll,
+							 SORT_ORDER_ASC);
 				}
 
 				/* Loop through each expression in <exprlist>. */
@@ -2868,9 +2876,9 @@ sqlite3CodeSubselect(Parse * pParse,	/* Parsing context */
 				sqlite3ReleaseTempReg(pParse, r1);
 				sqlite3ReleaseTempReg(pParse, r2);
 			}
-			if (pKeyInfo) {
-				sqlite3VdbeChangeP4(v, addr, (void *)pKeyInfo,
-						    P4_KEYINFO);
+			if (key_def != NULL) {
+				sqlite3VdbeChangeP4(v, addr, (void *)key_def,
+						    P4_KEYDEF);
 			}
 			break;
 		}
diff --git a/src/box/sql/fkey.c b/src/box/sql/fkey.c
index fb9a310..4b9d3ed 100644
--- a/src/box/sql/fkey.c
+++ b/src/box/sql/fkey.c
@@ -437,7 +437,7 @@ fkLookupParent(Parse * pParse,	/* Parse context */
 			int regRec = sqlite3GetTempReg(pParse);
 
 			emit_open_cursor(pParse, iCur, pIdx->tnum);
-			sqlite3VdbeSetP4KeyInfo(pParse, pIdx);
+			sql_vdbe_set_p4_key_def(pParse, pIdx);
 			for (i = 0; i < nCol; i++) {
 				sqlite3VdbeAddOp2(v, OP_Copy,
 						  aiCol[i] + 1 + regData,
diff --git a/src/box/sql/insert.c b/src/box/sql/insert.c
index 1a34f71..95a2610 100644
--- a/src/box/sql/insert.c
+++ b/src/box/sql/insert.c
@@ -55,7 +55,7 @@ sqlite3OpenTable(Parse * pParse,	/* Generate code into this VDBE */
 	assert(pPk != 0);
 	assert(pPk->tnum == pTab->tnum);
 	emit_open_cursor(pParse, iCur, pPk->tnum);
-	sqlite3VdbeSetP4KeyInfo(pParse, pPk);
+	sql_vdbe_set_p4_key_def(pParse, pPk);
 	VdbeComment((v, "%s", pTab->zName));
 }
 
@@ -565,9 +565,9 @@ sqlite3Insert(Parse * pParse,	/* Parser context */
 			regRec = sqlite3GetTempReg(pParse);
 			regCopy = sqlite3GetTempRange(pParse, nColumn);
 			regTempId = sqlite3GetTempReg(pParse);
-			KeyInfo *pKeyInfo = sqlite3KeyInfoAlloc(pParse->db, 1+nColumn, 0);
+			struct key_def *def = key_def_new(nColumn + 1);
 			sqlite3VdbeAddOp4(v, OP_OpenTEphemeral, srcTab, nColumn+1,
-					  0, (char*)pKeyInfo, P4_KEYINFO);
+					  0, (char*)def, P4_KEYDEF);
 			addrL = sqlite3VdbeAddOp1(v, OP_Yield, dest.iSDParm);
 			VdbeCoverage(v);
 			sqlite3VdbeAddOp3(v, OP_NextIdEphemeral, srcTab, 2, regTempId);
@@ -1594,7 +1594,7 @@ sqlite3OpenTableAndIndices(Parse * pParse,	/* Parsing context */
 			if (aToOpen == 0 || aToOpen[i + 1]) {
 				sqlite3VdbeAddOp3(v, op, iIdxCur, pIdx->tnum,
 						  space_ptr_reg);
-				sqlite3VdbeSetP4KeyInfo(pParse, pIdx);
+				sql_vdbe_set_p4_key_def(pParse, pIdx);
 				sqlite3VdbeChangeP5(v, p5);
 				VdbeComment((v, "%s", pIdx->zName));
 			}
@@ -1908,10 +1908,10 @@ xferOptimization(Parse * pParse,	/* Parser context */
 		}
 		assert(pSrcIdx);
 		emit_open_cursor(pParse, iSrc, pSrcIdx->tnum);
-		sqlite3VdbeSetP4KeyInfo(pParse, pSrcIdx);
+		sql_vdbe_set_p4_key_def(pParse, pSrcIdx);
 		VdbeComment((v, "%s", pSrcIdx->zName));
 		emit_open_cursor(pParse, iDest, pDestIdx->tnum);
-		sqlite3VdbeSetP4KeyInfo(pParse, pDestIdx);
+		sql_vdbe_set_p4_key_def(pParse, pDestIdx);
 		sqlite3VdbeChangeP5(v, OPFLAG_BULKCSR);
 		VdbeComment((v, "%s", pDestIdx->zName));
 		addr1 = sqlite3VdbeAddOp2(v, OP_Rewind, iSrc, 0);
diff --git a/src/box/sql/pragma.c b/src/box/sql/pragma.c
index 738c254..0f1c64d 100644
--- a/src/box/sql/pragma.c
+++ b/src/box/sql/pragma.c
@@ -646,8 +646,8 @@ sqlite3Pragma(Parse * pParse, Token * pId,	/* First part of [schema.]id field */
 									  pIdx->
 									  tnum,
 									  0);
-							sqlite3VdbeSetP4KeyInfo
-							    (pParse, pIdx);
+							sql_vdbe_set_p4_key_def(pParse,
+										pIdx);
 						}
 					} else {
 						k = 0;
diff --git a/src/box/sql/select.c b/src/box/sql/select.c
index aff534d3..6729eef 100644
--- a/src/box/sql/select.c
+++ b/src/box/sql/select.c
@@ -562,12 +562,11 @@ sqliteProcessJoin(Parse * pParse, Select * p)
 	return 0;
 }
 
-/* Forward reference */
-static KeyInfo *
-keyInfoFromExprList(Parse * pParse,	/* Parsing context */
-		    ExprList * pList,	/* Form the KeyInfo object from this ExprList */
-		    int iStart,		/* Begin with this column of pList */
-		    int nExtra);	/* Add this many extra columns to the end */
+static struct key_def *
+sql_expr_list_to_key_def(struct Parse *parse,
+			 struct ExprList *list,
+			 int start);
+
 
 /*
  * Generate code that will push the record in registers regData
@@ -623,7 +622,6 @@ pushOntoSorter(Parse * pParse,		/* Parser context */
 		int addrJmp;	/* Address of the OP_Jump opcode */
 		VdbeOp *pOp;	/* Opcode that opens the sorter */
 		int nKey;	/* Number of sorting key columns, including OP_Sequence */
-		KeyInfo *pKI;	/* Original KeyInfo on the sorter table */
 
 		regPrevKey = pParse->nMem + 1;
 		pParse->nMem += pSort->nOBSat;
@@ -643,13 +641,13 @@ pushOntoSorter(Parse * pParse,		/* Parser context */
 		if (pParse->db->mallocFailed)
 			return;
 		pOp->p2 = nKey + nData;
-		pKI = pOp->p4.pKeyInfo;
-		memset(pKI->aSortOrder, 0, pKI->nField);	/* Makes OP_Jump below testable */
-		sqlite3VdbeChangeP4(v, -1, (char *)pKI, P4_KEYINFO);
-		testcase(pKI->nXField > 2);
-		pOp->p4.pKeyInfo =
-		    keyInfoFromExprList(pParse, pSort->pOrderBy, nOBSat,
-					pKI->nXField - 1);
+		struct key_def *def = key_def_dup(pOp->p4.key_def);
+		for (uint32_t i = 0; i < def->part_count; ++i)
+			pOp->p4.key_def->parts[i].sort_order = SORT_ORDER_ASC;
+		sqlite3VdbeChangeP4(v, -1, (char *)def, P4_KEYDEF);
+		pOp->p4.key_def = sql_expr_list_to_key_def(pParse,
+							   pSort->pOrderBy,
+							   nOBSat);
 		addrJmp = sqlite3VdbeCurrentAddr(v);
 		sqlite3VdbeAddOp3(v, OP_Jump, addrJmp + 1, 0, addrJmp + 1);
 		VdbeCoverage(v);
@@ -1193,109 +1191,51 @@ selectInnerLoop(Parse * pParse,		/* The parser context */
 	}
 }
 
-/*
- * Allocate a KeyInfo object sufficient for an index of N key columns and
- * X extra columns.
- */
-KeyInfo *
-sqlite3KeyInfoAlloc(sqlite3 * db, int N, int X)
-{
-	int nExtra = (N + X) * (sizeof(struct coll *) + 1);
-	KeyInfo *p = sqlite3DbMallocRawNN(db, sizeof(KeyInfo) + nExtra);
-	if (p) {
-		p->aSortOrder = (u8 *) & p->aColl[N + X];
-		p->nField = (u16) N;
-		p->nXField = (u16) X;
-		p->db = db;
-		p->nRef = 1;
-		p->aColl[0] = NULL;
-		memset(&p[1], 0, nExtra);
-	} else {
-		sqlite3OomFault(db);
-	}
-	return p;
-}
-
-/*
- * Deallocate a KeyInfo object
- */
-void
-sqlite3KeyInfoUnref(KeyInfo * p)
-{
-	if (p) {
-		assert(p->nRef > 0);
-		p->nRef--;
-		if (p->nRef == 0)
-			sqlite3DbFree(p->db, p);
-	}
-}
-
-/*
- * Make a new pointer to a KeyInfo object
- */
-KeyInfo *
-sqlite3KeyInfoRef(KeyInfo * p)
-{
-	if (p) {
-		assert(p->nRef > 0);
-		p->nRef++;
-	}
-	return p;
-}
-
-#ifdef SQLITE_DEBUG
-/*
- * Return TRUE if a KeyInfo object can be change.  The KeyInfo object
- * can only be changed if this is just a single reference to the object.
- *
- * This routine is used only inside of assert() statements.
- */
-int
-sqlite3KeyInfoIsWriteable(KeyInfo * p)
-{
-	return p->nRef == 1;
-}
-#endif				/* SQLITE_DEBUG */
-
-/*
- * Given an expression list, generate a KeyInfo structure that records
+/**
+ * Given an expression list, generate a key_defd structure that records
  * the collating sequence for each expression in that expression list.
  *
  * If the ExprList is an ORDER BY or GROUP BY clause then the resulting
- * KeyInfo structure is appropriate for initializing a virtual index to
+ * key_def structure is appropriate for initializing a virtual index to
  * implement that clause.  If the ExprList is the result set of a SELECT
- * then the KeyInfo structure is appropriate for initializing a virtual
+ * then the key_info structure is appropriate for initializing a virtual
  * index to implement a DISTINCT test.
  *
- * Space to hold the KeyInfo structure is obtained from malloc.  The calling
- * function is responsible for seeing that this structure is eventually
- * freed.
+ * Space to hold the key_info structure is obtained from malloc. 
+ * The calling function is responsible for seeing that this
+ * structure is eventually freed.
+ *
+ * @param Parsing context.
+ * @param Expression list.
+ * @param No of leading parts to skip.
+ *
+ * @retval Allocated key_def, NULL in case of OOM.
  */
-static KeyInfo *
-keyInfoFromExprList(Parse * pParse,	/* Parsing context */
-		    ExprList * pList,	/* Form the KeyInfo object from this ExprList */
-		    int iStart,		/* Begin with this column of pList */
-		    int nExtra)		/* Add this many extra columns to the end */
+static struct key_def *
+sql_expr_list_to_key_def(struct Parse *parse,
+			 struct ExprList *list,
+			 int start)
 {
-	int nExpr;
-	KeyInfo *pInfo;
-	struct ExprList_item *pItem;
-	sqlite3 *db = pParse->db;
-	int i;
-
-	nExpr = pList->nExpr;
-	pInfo = sqlite3KeyInfoAlloc(db, nExpr - iStart, nExtra + 1);
-	if (pInfo) {
-		assert(sqlite3KeyInfoIsWriteable(pInfo));
-		for (i = iStart, pItem = pList->a + iStart; i < nExpr;
-		     i++, pItem++) {
+	struct key_def *def;
+	int expr_count = list->nExpr;
+	def = key_def_new(expr_count);
+	if (def != NULL) {
+		int i;
+		struct ExprList_item *item;
+		for (i = start, item = list->a + start; i < expr_count;
+		     ++i, ++item) {
 			bool unused;
-			pInfo->aColl[i - iStart] =
-				sql_expr_coll(pParse, pItem->pExpr, &unused);
-			pInfo->aSortOrder[i - iStart] = pItem->sortOrder;
+			struct coll *coll = sql_expr_coll(parse, item->pExpr,
+							  &unused);
+			enum sort_order sort_order = item->sortOrder;
+			key_def_set_part(def, i-start, i-start,
+					 FIELD_TYPE_SCALAR,
+					 ON_CONFLICT_ACTION_ABORT,
+					 coll, sort_order);
 		}
 	}
-	return pInfo;
+
+	return def;
 }
 
 /*
@@ -2118,54 +2058,62 @@ multiSelectCollSeq(Parse * pParse, Select * p, int iCol, bool *is_found)
 	return coll;
 }
 
-/*
+/**
  * The select statement passed as the second parameter is a compound SELECT
- * with an ORDER BY clause. This function allocates and returns a KeyInfo
+ * with an ORDER BY clause. This function allocates and returns a key_def
  * structure suitable for implementing the ORDER BY.
  *
- * Space to hold the KeyInfo structure is obtained from malloc. The calling
+ * Space to hold the key_def structure is obtained from malloc. The calling
  * function is responsible for ensuring that this structure is eventually
  * freed.
+ *
+ * @param Parsing context.
+ * @param Select struct to analyze.
+ * @param No of extra slots to allocate.
+ *
+ * @retval Allocated key_def, NULL in case of OOM.
  */
-static KeyInfo *
-multiSelectOrderByKeyInfo(Parse * pParse, Select * p, int nExtra)
+static struct key_def *
+sql_multiselect_orderby_to_key_def(struct Parse *parse,
+				   struct Select *s,
+				   int extra)
 {
-	ExprList *pOrderBy = p->pOrderBy;
-	int nOrderBy = p->pOrderBy->nExpr;
-	sqlite3 *db = pParse->db;
-	KeyInfo *pRet = sqlite3KeyInfoAlloc(db, nOrderBy + nExtra, 1);
-	if (pRet) {
-		int i;
-		for (i = 0; i < nOrderBy; i++) {
-			struct ExprList_item *pItem = &pOrderBy->a[i];
-			Expr *pTerm = pItem->pExpr;
+	int ob_count = s->pOrderBy->nExpr;
+	struct key_def *key_def = key_def_new(ob_count + extra);
+	if (key_def != NULL) {
+		ExprList *order_by = s->pOrderBy;
+		for (int i = 0; i < ob_count; i++) {
+			struct ExprList_item *item = &order_by->a[i];
+			struct Expr *term = item->pExpr;
 			struct coll *coll;
 			bool is_found = false;
 
-			if (pTerm->flags & EP_Collate) {
-				coll = sql_expr_coll(pParse, pTerm, &is_found);
+			if (term->flags & EP_Collate) {
+				coll = sql_expr_coll(parse, term, &is_found);
 			} else {
 				coll =
-				    multiSelectCollSeq(pParse, p,
-						       pItem->u.x.iOrderByCol - 1,
+				    multiSelectCollSeq(parse, s,
+						       item->u.x.iOrderByCol - 1,
 						       &is_found);
 				if (coll != NULL) {
-					pOrderBy->a[i].pExpr =
-						sqlite3ExprAddCollateString(pParse, pTerm,
+					order_by->a[i].pExpr =
+						sqlite3ExprAddCollateString(parse, term,
 									    coll->name);
 				} else {
-					pOrderBy->a[i].pExpr =
-						sqlite3ExprAddCollateString(pParse, pTerm,
+					order_by->a[i].pExpr =
+						sqlite3ExprAddCollateString(parse, term,
 									    "BINARY");
 				}
 			}
-			assert(sqlite3KeyInfoIsWriteable(pRet));
-			pRet->aColl[i] = coll;
-			pRet->aSortOrder[i] = pOrderBy->a[i].sortOrder;
+			key_def_set_part(key_def, i, i,
+					 FIELD_TYPE_SCALAR,
+					 ON_CONFLICT_ACTION_ABORT,
+					 coll,
+					 order_by->a[i].sortOrder);
 		}
 	}
 
-	return pRet;
+	return key_def;
 }
 
 #ifndef SQLITE_OMIT_CTE
@@ -2265,16 +2213,17 @@ generateWithRecursiveQuery(Parse * pParse,	/* Parsing context */
 	regCurrent = ++pParse->nMem;
 	sqlite3VdbeAddOp3(v, OP_OpenPseudo, iCurrent, regCurrent, nCol);
 	if (pOrderBy) {
-		KeyInfo *pKeyInfo = multiSelectOrderByKeyInfo(pParse, p, 1);
+		struct key_def *def = sql_multiselect_orderby_to_key_def(pParse,
+									 p, 1);
 		sqlite3VdbeAddOp4(v, OP_OpenTEphemeral, iQueue,
-				  pOrderBy->nExpr + 2, 0, (char *)pKeyInfo,
-				  P4_KEYINFO);
+				  pOrderBy->nExpr + 2, 0, (char *)def,
+				  P4_KEYDEF);
 		VdbeComment((v, "Orderby table"));
 		destQueue.pOrderBy = pOrderBy;
 	} else {
-		KeyInfo *pKeyInfo = sqlite3KeyInfoAlloc(pParse->db, nCol + 1, 0);
+		struct key_def *def = key_def_new(nCol + 1);
 		sqlite3VdbeAddOp4(v, OP_OpenTEphemeral, iQueue, nCol + 1, 0,
-				  (char*)pKeyInfo, P4_KEYINFO);
+				  (char*)def, P4_KEYDEF);
 		VdbeComment((v, "Queue table"));
 	}
 	if (iDistinct) {
@@ -2475,9 +2424,10 @@ multiSelect(Parse * pParse,	/* Parsing context */
 	if (dest.eDest == SRT_EphemTab) {
 		assert(p->pEList);
 		int nCols = p->pEList->nExpr;
-		KeyInfo *pKeyInfo = sqlite3KeyInfoAlloc(pParse->db, nCols + 1, 0);
+		struct key_def *def;
+		def = key_def_new(nCols + 1);
 		sqlite3VdbeAddOp4(v, OP_OpenTEphemeral, dest.iSDParm, nCols + 1,
-				  0, (char*)pKeyInfo, P4_KEYINFO);
+				  0, (char*)def, P4_KEYDEF);
 		VdbeComment((v, "Destination temp"));
 		dest.eDest = SRT_Table;
 	}
@@ -2785,7 +2735,7 @@ multiSelect(Parse * pParse,	/* Parsing context */
 
 	/* Compute collating sequences used by
 	 * temporary tables needed to implement the compound select.
-	 * Attach the KeyInfo structure to all temporary tables.
+	 * Attach the key_def structure to all temporary tables.
 	 *
 	 * This section is run by the right-most SELECT statement only.
 	 * SELECT statements to the left always skip this part.  The right-most
@@ -2793,26 +2743,28 @@ multiSelect(Parse * pParse,	/* Parsing context */
 	 * no temp tables are required.
 	 */
 	if (p->selFlags & SF_UsesEphemeral) {
-		int i;		/* Loop counter */
-		KeyInfo *pKeyInfo;	/* Collating sequence for the result set */
-		Select *pLoop;	/* For looping through SELECT statements */
-		struct coll **apColl;	/* For looping through pKeyInfo->aColl[] */
-		int nCol;	/* Number of columns in result set */
+		int nCol;
+		struct key_def *key_def;
 
 		assert(p->pNext == 0);
 		nCol = p->pEList->nExpr;
-		pKeyInfo = sqlite3KeyInfoAlloc(db, nCol, 1);
-		if (!pKeyInfo) {
+		key_def = key_def_new(nCol);
+		if (key_def == NULL) {
 			rc = SQLITE_NOMEM_BKPT;
 			goto multi_select_end;
 		}
-		for (i = 0, apColl = pKeyInfo->aColl; i < nCol; i++, apColl++) {
-			bool is_found = false;
-			*apColl = multiSelectCollSeq(pParse, p, i, &is_found);
+		for (int i = 0; i < nCol; i++) {
+			bool unused;
+			key_def_set_part(key_def, i, i,
+					 FIELD_TYPE_SCALAR,
+					 ON_CONFLICT_ACTION_ABORT,
+					 multiSelectCollSeq(pParse, p, i,
+							    &unused),
+					 SORT_ORDER_ASC);
 		}
 
-		for (pLoop = p; pLoop; pLoop = pLoop->pPrior) {
-			for (i = 0; i < 2; i++) {
+		for (struct Select *pLoop = p; pLoop; pLoop = pLoop->pPrior) {
+			for (int i = 0; i < 2; i++) {
 				int addr = pLoop->addrOpenEphm[i];
 				if (addr < 0) {
 					/* If [0] is unused then [1] is also unused.  So we can
@@ -2823,13 +2775,12 @@ multiSelect(Parse * pParse,	/* Parsing context */
 				}
 				sqlite3VdbeChangeP2(v, addr, nCol);
 				sqlite3VdbeChangeP4(v, addr,
-						    (char *)
-						    sqlite3KeyInfoRef(pKeyInfo),
-						    P4_KEYINFO);
+						    (char *)key_def_dup(key_def),
+						    P4_KEYDEF);
 				pLoop->addrOpenEphm[i] = -1;
 			}
 		}
-		sqlite3KeyInfoUnref(pKeyInfo);
+		free(key_def);
 	}
 
  multi_select_end:
@@ -2871,7 +2822,7 @@ sqlite3SelectWrongNumTermsError(Parse * pParse, Select * p)
  * If regPrev>0 then it is the first register in a vector that
  * records the previous output.  mem[regPrev] is a flag that is false
  * if there has been no previous output.  If regPrev>0 then code is
- * generated to suppress duplicates.  pKeyInfo is used for comparing
+ * generated to suppress duplicates.  def is used for comparing
  * keys.
  *
  * If the LIMIT found in p->iLimit is reached, jump immediately to
@@ -2884,7 +2835,7 @@ generateOutputSubroutine(Parse * pParse,	/* Parsing context */
 			 SelectDest * pDest,	/* Where to send the data */
 			 int regReturn,		/* The return address register */
 			 int regPrev,		/* Previous result register.  No uniqueness if 0 */
-			 KeyInfo * pKeyInfo,	/* For comparing with previous entry */
+			 struct key_def *def,	/* For comparing with previous entry */
 			 int iBreak)		/* Jump here if we hit the LIMIT */
 {
 	Vdbe *v = pParse->pVdbe;
@@ -2903,8 +2854,8 @@ generateOutputSubroutine(Parse * pParse,	/* Parsing context */
 		addr2 =
 		    sqlite3VdbeAddOp4(v, OP_Compare, pIn->iSdst, regPrev + 1,
 				      pIn->nSdst,
-				      (char *)sqlite3KeyInfoRef(pKeyInfo),
-				      P4_KEYINFO);
+				      (char *)key_def_dup(def),
+				      P4_KEYDEF);
 		sqlite3VdbeAddOp3(v, OP_Jump, addr2 + 2, iContinue, addr2 + 2);
 		VdbeCoverage(v);
 		sqlite3VdbeJumpHere(v, addr1);
@@ -3135,8 +3086,10 @@ multiSelectOrderBy(Parse * pParse,	/* Parsing context */
 	int labelEnd;		/* Label for the end of the overall SELECT stmt */
 	int addr1;		/* Jump instructions that get retargetted */
 	int op;			/* One of TK_ALL, TK_UNION, TK_EXCEPT, TK_INTERSECT */
-	KeyInfo *pKeyDup = 0;	/* Comparison information for duplicate removal */
-	KeyInfo *pKeyMerge;	/* Comparison information for merging rows */
+	/* Comparison information for duplicate removal */
+	struct key_def *def_dup = NULL;
+	/* Comparison information for merging rows */
+	struct key_def *def_merge;
 	sqlite3 *db;		/* Database connection */
 	ExprList *pOrderBy;	/* The ORDER BY clause */
 	int nOrderBy;		/* Number of terms in the ORDER BY clause */
@@ -3145,7 +3098,8 @@ multiSelectOrderBy(Parse * pParse,	/* Parsing context */
 	int iSub2;		/* EQP id of right-hand query */
 
 	assert(p->pOrderBy != 0);
-	assert(pKeyDup == 0);	/* "Managed" code needs this.  Ticket #3382. */
+	/* "Managed" code needs this.  Ticket #3382. */
+	assert(def_dup == NULL);
 	db = pParse->db;
 	v = pParse->pVdbe;
 	assert(v != 0);		/* Already thrown the error if VDBE alloc failed */
@@ -3190,7 +3144,7 @@ multiSelectOrderBy(Parse * pParse,	/* Parsing context */
 		}
 	}
 
-	/* Compute the comparison permutation and keyinfo that is used with
+	/* Compute the comparison permutation and key_def that is used with
 	 * the permutation used to determine if the next
 	 * row of results comes from selectA or selectB.  Also add explicit
 	 * collations to the ORDER BY clause terms so that when the subqueries
@@ -3206,9 +3160,9 @@ multiSelectOrderBy(Parse * pParse,	/* Parsing context */
 			assert(pItem->u.x.iOrderByCol <= p->pEList->nExpr);
 			aPermute[i] = pItem->u.x.iOrderByCol - 1;
 		}
-		pKeyMerge = multiSelectOrderByKeyInfo(pParse, p, 1);
+		def_merge = sql_multiselect_orderby_to_key_def(pParse, p, 1);
 	} else {
-		pKeyMerge = 0;
+		def_merge = NULL;
 	}
 
 	/* Reattach the ORDER BY clause to the query.
@@ -3216,27 +3170,30 @@ multiSelectOrderBy(Parse * pParse,	/* Parsing context */
 	p->pOrderBy = pOrderBy;
 	pPrior->pOrderBy = sqlite3ExprListDup(pParse->db, pOrderBy, 0);
 
-	/* Allocate a range of temporary registers and the KeyInfo needed
+	/* Allocate a range of temporary registers and the key_def needed
 	 * for the logic that removes duplicate result rows when the
 	 * operator is UNION, EXCEPT, or INTERSECT (but not UNION ALL).
 	 */
 	if (op == TK_ALL) {
 		regPrev = 0;
 	} else {
-		int nExpr = p->pEList->nExpr;
-		assert(nOrderBy >= nExpr || db->mallocFailed);
+		int expr_count = p->pEList->nExpr;
+		assert(nOrderBy >= expr_count || db->mallocFailed);
 		regPrev = pParse->nMem + 1;
-		pParse->nMem += nExpr + 1;
+		pParse->nMem += expr_count + 1;
 		sqlite3VdbeAddOp2(v, OP_Integer, 0, regPrev);
-		pKeyDup = sqlite3KeyInfoAlloc(db, nExpr, 1);
-		if (pKeyDup) {
-			assert(sqlite3KeyInfoIsWriteable(pKeyDup));
-			for (i = 0; i < nExpr; i++) {
+		def_dup = key_def_new(expr_count);
+		if (def_dup != NULL) {
+			for (int i = 0; i < expr_count; i++) {
 				bool is_found = false;
-				pKeyDup->aColl[i] =
-					multiSelectCollSeq(pParse, p, i,
-							   &is_found);
-				pKeyDup->aSortOrder[i] = 0;
+				struct coll *coll;
+				coll = multiSelectCollSeq(pParse, p, i,
+							  &is_found);
+				key_def_set_part(def_dup, i, i,
+						 FIELD_TYPE_SCALAR,
+						 ON_CONFLICT_ACTION_ABORT,
+						 coll,
+						 SORT_ORDER_ASC);
 			}
 		}
 	}
@@ -3311,7 +3268,7 @@ multiSelectOrderBy(Parse * pParse,	/* Parsing context */
 	VdbeNoopComment((v, "Output routine for A"));
 	addrOutA = generateOutputSubroutine(pParse,
 					    p, &destA, pDest, regOutA,
-					    regPrev, pKeyDup, labelEnd);
+					    regPrev, def_dup, labelEnd);
 
 	/* Generate a subroutine that outputs the current row of the B
 	 * select as the next output row of the compound select.
@@ -3320,9 +3277,8 @@ multiSelectOrderBy(Parse * pParse,	/* Parsing context */
 		VdbeNoopComment((v, "Output routine for B"));
 		addrOutB = generateOutputSubroutine(pParse,
 						    p, &destB, pDest, regOutB,
-						    regPrev, pKeyDup, labelEnd);
+						    regPrev, def_dup, labelEnd);
 	}
-	sqlite3KeyInfoUnref(pKeyDup);
 
 	/* Generate a subroutine to run when the results from select A
 	 * are exhausted and only data in select B remains.
@@ -3402,7 +3358,7 @@ multiSelectOrderBy(Parse * pParse,	/* Parsing context */
 	sqlite3VdbeAddOp4(v, OP_Permutation, 0, 0, 0, (char *)aPermute,
 			  P4_INTARRAY);
 	sqlite3VdbeAddOp4(v, OP_Compare, destA.iSdst, destB.iSdst, nOrderBy,
-			  (char *)pKeyMerge, P4_KEYINFO);
+			  (char *)def_merge, P4_KEYDEF);
 	sqlite3VdbeChangeP5(v, OPFLAG_PERMUTE);
 	sqlite3VdbeAddOp3(v, OP_Jump, addrAltB, addrAeqB, addrAgtB);
 	VdbeCoverage(v);
@@ -5135,12 +5091,13 @@ resetAccumulator(Parse * pParse, AggInfo * pAggInfo)
 						"argument");
 				pFunc->iDistinct = -1;
 			} else {
-				KeyInfo *pKeyInfo =
-				    keyInfoFromExprList(pParse, pE->x.pList, 0,
-							0);
+				struct key_def *def;
+				def = sql_expr_list_to_key_def(pParse,
+							       pE->x.pList,
+							       0);
 				sqlite3VdbeAddOp4(v, OP_OpenTEphemeral,
 						  pFunc->iDistinct, 1, 0,
-						  (char *)pKeyInfo, P4_KEYINFO);
+						  (char *)def, P4_KEYDEF);
 			}
 		}
 	}
@@ -5611,23 +5568,22 @@ sqlite3Select(Parse * pParse,		/* The parser context */
 	 * that change.
 	 */
 	if (sSort.pOrderBy) {
-		KeyInfo *pKeyInfo;
-		pKeyInfo =
-		    keyInfoFromExprList(pParse, sSort.pOrderBy, 0, pEList->nExpr);
+		struct key_def *def;
+		def = sql_expr_list_to_key_def(pParse, sSort.pOrderBy, 0);
 		sSort.iECursor = pParse->nTab++;
 		/* Number of columns in transient table equals to number of columns in
 		 * SELECT statement plus number of columns in ORDER BY statement
 		 * and plus one column for ID.
 		 */
 		int nCols = pEList->nExpr + sSort.pOrderBy->nExpr + 1;
-		if (pKeyInfo->aSortOrder[0] == SORT_ORDER_DESC) {
+		if (def->parts[0].sort_order == SORT_ORDER_DESC) {
 			sSort.sortFlags |= SORTFLAG_DESC;
 		}
 		sSort.addrSortIndex =
 		    sqlite3VdbeAddOp4(v, OP_OpenTEphemeral,
 				      sSort.iECursor,
 				      nCols,
-				      0, (char *)pKeyInfo, P4_KEYINFO);
+				      0, (char *)def, P4_KEYDEF);
 		VdbeComment((v, "Sort table"));
 	} else {
 		sSort.addrSortIndex = -1;
@@ -5636,10 +5592,10 @@ sqlite3Select(Parse * pParse,		/* The parser context */
 	/* If the output is destined for a temporary table, open that table.
 	 */
 	if (pDest->eDest == SRT_EphemTab) {
-		KeyInfo *pKeyInfo = sqlite3KeyInfoAlloc(pParse->db,
-							pEList->nExpr + 1, 0);
+		struct key_def *def;
+		def = key_def_new(pEList->nExpr + 1);
 		sqlite3VdbeAddOp4(v, OP_OpenTEphemeral, pDest->iSDParm,
-				  pEList->nExpr + 1, 0, (char*)pKeyInfo, P4_KEYINFO);
+				  pEList->nExpr + 1, 0, (char*)def, P4_KEYDEF);
 
 		VdbeComment((v, "Output table"));
 	}
@@ -5660,12 +5616,12 @@ sqlite3Select(Parse * pParse,		/* The parser context */
 	 */
 	if (p->selFlags & SF_Distinct) {
 		sDistinct.tabTnct = pParse->nTab++;
-		KeyInfo *pKeyInfo = keyInfoFromExprList(pParse, p->pEList, 0, 0);
+		struct key_def *def = sql_expr_list_to_key_def(pParse, p->pEList, 0);
 		sDistinct.addrTnct = sqlite3VdbeAddOp4(v, OP_OpenTEphemeral,
 						       sDistinct.tabTnct,
-						       pKeyInfo->nField,
-						       0, (char *)pKeyInfo,
-						       P4_KEYINFO);
+						       def->part_count,
+						       0, (char *)def,
+						       P4_KEYDEF);
 		VdbeComment((v, "Distinct table"));
 		sDistinct.eTnctType = WHERE_DISTINCT_UNORDERED;
 	} else {
@@ -5806,7 +5762,6 @@ sqlite3Select(Parse * pParse,		/* The parser context */
 		 * much more complex than aggregates without a GROUP BY.
 		 */
 		if (pGroupBy) {
-			KeyInfo *pKeyInfo;	/* Keying information for the group by clause */
 			int addr1;	/* A-vs-B comparision jump */
 			int addrOutputRow;	/* Start of subroutine that outputs a result row */
 			int regOutputRow;	/* Return address register for output subroutine */
@@ -5822,14 +5777,12 @@ sqlite3Select(Parse * pParse,		/* The parser context */
 			 * will be converted into a Noop.
 			 */
 			sAggInfo.sortingIdx = pParse->nTab++;
-			pKeyInfo =
-			    keyInfoFromExprList(pParse, pGroupBy, 0,
-						sAggInfo.nColumn);
+			struct key_def *def = sql_expr_list_to_key_def(pParse, pGroupBy, 0);
 			addrSortingIdx =
 			    sqlite3VdbeAddOp4(v, OP_SorterOpen,
 					      sAggInfo.sortingIdx,
 					      sAggInfo.nSortingColumn, 0,
-					      (char *)pKeyInfo, P4_KEYINFO);
+					      (char *)def, P4_KEYDEF);
 
 			/* Initialize memory locations used by GROUP BY aggregate processing
 			 */
@@ -5867,7 +5820,7 @@ sqlite3Select(Parse * pParse,		/* The parser context */
 			if (sqlite3WhereIsOrdered(pWInfo) == pGroupBy->nExpr) {
 				/* The optimizer is able to deliver rows in group by order so
 				 * we do not have to sort.  The OP_OpenEphemeral table will be
-				 * cancelled later because we still need to use the pKeyInfo
+				 * cancelled later because we still need to use the key_def
 				 */
 				groupBySort = 0;
 			} else {
@@ -5979,8 +5932,8 @@ sqlite3Select(Parse * pParse,		/* The parser context */
 			}
 			sqlite3VdbeAddOp4(v, OP_Compare, iAMem, iBMem,
 					  pGroupBy->nExpr,
-					  (char *)sqlite3KeyInfoRef(pKeyInfo),
-					  P4_KEYINFO);
+					  (char*) key_def_dup(def),
+					  P4_KEYDEF);
 			addr1 = sqlite3VdbeCurrentAddr(v);
 			sqlite3VdbeAddOp3(v, OP_Jump, addr1 + 1, 0, addr1 + 1);
 			VdbeCoverage(v);
@@ -6090,7 +6043,7 @@ sqlite3Select(Parse * pParse,		/* The parser context */
 				 */
 				const int iCsr = pParse->nTab++;	/* Cursor to scan b-tree */
 				Index *pIdx;	/* Iterator variable */
-				KeyInfo *pKeyInfo = 0;	/* Keyinfo for scanned index */
+				struct key_def *def = NULL;
 				Index *pBest;	/* Best index found so far */
 				int iRoot = pTab->tnum;	/* Root page of scanned b-tree */
 
@@ -6100,7 +6053,7 @@ sqlite3Select(Parse * pParse,		/* The parser context */
 				 *
 				 * (2013-10-03) Do not count the entries in a partial index.
 				 *
-				 * In practice the KeyInfo structure will not be used. It is only
+				 * In practice the key_def structure will not be used. It is only
 				 * passed to keep OP_OpenRead happy.
 				 */
 				pBest = sqlite3PrimaryKeyIndex(pTab);
@@ -6116,19 +6069,17 @@ sqlite3Select(Parse * pParse,		/* The parser context */
 						pBest = pIdx;
 					}
 				}
-				if (pBest) {
+				if (pBest != NULL) {
 					iRoot = pBest->tnum;
-					pKeyInfo =
-					    sqlite3KeyInfoOfIndex(pParse, db,
-								  pBest);
+					def = sql_index_key_def(pBest);
 				}
 
 				/* Open a read-only cursor, execute the OP_Count, close the cursor. */
 				emit_open_cursor(pParse, iCsr, iRoot);
-				if (pKeyInfo) {
+				if (def != NULL) {
 					sqlite3VdbeChangeP4(v, -1,
-							    (char *)pKeyInfo,
-							    P4_KEYINFO);
+							    (char *)def,
+							    P4_KEYDEF);
 				}
 				sqlite3VdbeAddOp2(v, OP_Count, iCsr,
 						  sAggInfo.aFunc[0].iMem);
diff --git a/src/box/sql/sqliteInt.h b/src/box/sql/sqliteInt.h
index a811932..7d32556 100644
--- a/src/box/sql/sqliteInt.h
+++ b/src/box/sql/sqliteInt.h
@@ -1462,7 +1462,6 @@ typedef struct IdList IdList;
 typedef struct Index Index;
 typedef struct IndexSample IndexSample;
 typedef struct KeyClass KeyClass;
-typedef struct KeyInfo KeyInfo;
 typedef struct Lookaside Lookaside;
 typedef struct LookasideSlot LookasideSlot;
 typedef struct NameContext NameContext;
@@ -2027,24 +2026,6 @@ struct FKey {
 #define OE_SetDflt  8		/* Set the foreign key value to its default */
 #define OE_Cascade  9		/* Cascade the changes */
 
-/*
- * An instance of the following structure is passed as the first
- * argument to sqlite3VdbeKeyCompare and is used to control the
- * comparison of the two index keys.
- *
- * Note that aSortOrder[] and aColl[] have nField+1 slots.  There
- * are nField slots for the columns of an index.
- */
-struct KeyInfo {
-	u32 nRef;		/* Number of references to this KeyInfo object */
-	u8 enc;			/* Text encoding - one of the SQLITE_UTF* values */
-	u16 nField;		/* Number of key columns in the index */
-	u16 nXField;		/* Number of columns beyond the key columns */
-	sqlite3 *db;		/* The database connection */
-	u8 *aSortOrder;		/* Sort order for each column. */
-	struct coll *aColl[1];	/* Collating sequence for each term of the key */
-};
-
 /*
  * This object holds a record which has been parsed out into individual
  * fields, for the purposes of doing a comparison.
@@ -2059,7 +2040,7 @@ struct KeyInfo {
  * an index b+tree. The goal of the search is to find the entry that
  * is closed to the key described by this object.  This object might hold
  * just a prefix of the key.  The number of fields is given by
- * pKeyInfo->nField.
+ * key_def->part_count.
  *
  * The r1 and r2 fields are the values to return if this key is less than
  * or greater than a key in the btree, respectively.  These are normally
@@ -2069,7 +2050,7 @@ struct KeyInfo {
  * The key comparison functions actually return default_rc when they find
  * an equals comparison.  default_rc can be -1, 0, or +1.  If there are
  * multiple entries in the b-tree with the same key (when only looking
- * at the first pKeyInfo->nFields,) then default_rc can be set to -1 to
+ * at the first key_def->part_count) then default_rc can be set to -1 to
  * cause the search to find the last match, or +1 to cause the search to
  * find the first match.
  *
@@ -2081,7 +2062,8 @@ struct KeyInfo {
  * b-tree.
  */
 struct UnpackedRecord {
-	KeyInfo *pKeyInfo;	/* Collation and sort-order information */
+	/* Collation and sort-order information */
+	struct key_def* key_def;
 	Mem *aMem;		/* Values */
 	u16 nField;		/* Number of entries in apMem[] */
 	i8 default_rc;		/* Comparison result if keys are equal */
@@ -2690,12 +2672,12 @@ struct NameContext {
  *
  * addrOpenEphm[] entries contain the address of OP_OpenEphemeral opcodes.
  * These addresses must be stored so that we can go back and fill in
- * the P4_KEYINFO and P2 parameters later.  Neither the KeyInfo nor
+ * the P4_KEYDEF and P2 parameters later.  Neither the key_def nor
  * the number of columns in P2 can be computed at the same time
  * as the OP_OpenEphm instruction is coded because not
  * enough information about the compound query is known at that point.
- * The KeyInfo for addrOpenTran[0] and [1] contains collating sequences
- * for the result set.  The KeyInfo for addrOpenEphm[2] contains collating
+ * The key_def for addrOpenTran[0] and [1] contains collating sequences
+ * for the result set.  The key_def for addrOpenEphm[2] contains collating
  * sequences for the ORDER BY clause.
  */
 struct Select {
@@ -3529,11 +3511,18 @@ const char *
 index_collation_name(Index *, uint32_t);
 struct coll *
 sql_index_collation(Index *idx, uint32_t column);
-struct coll *
-sql_default_coll();
 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 name of given column collation from index.
  *
@@ -3926,13 +3915,6 @@ void sqlite3RegisterLikeFunctions(sqlite3 *, int);
 int sqlite3IsLikeFunction(sqlite3 *, Expr *, int *, char *);
 void sqlite3SchemaClear(sqlite3 *);
 Schema *sqlite3SchemaCreate(sqlite3 *);
-KeyInfo *sqlite3KeyInfoAlloc(sqlite3 *, int, int);
-void sqlite3KeyInfoUnref(KeyInfo *);
-KeyInfo *sqlite3KeyInfoRef(KeyInfo *);
-KeyInfo *sqlite3KeyInfoOfIndex(Parse *, sqlite3 *, Index *);
-#ifdef SQLITE_DEBUG
-int sqlite3KeyInfoIsWriteable(KeyInfo *);
-#endif
 int sqlite3CreateFunc(sqlite3 *, const char *, int, int, void *,
 		      void (*)(sqlite3_context *, int, sqlite3_value **),
 		      void (*)(sqlite3_context *, int, sqlite3_value **),
diff --git a/src/box/sql/tarantoolInt.h b/src/box/sql/tarantoolInt.h
index 57fc4a1..af774fc 100644
--- a/src/box/sql/tarantoolInt.h
+++ b/src/box/sql/tarantoolInt.h
@@ -98,7 +98,7 @@ int tarantoolSqlite3RenameParentTable(int iTab, const char *zOldParentName,
 
 /* Interface for ephemeral tables. */
 int tarantoolSqlite3EphemeralCreate(BtCursor * pCur, uint32_t filed_count,
-				    struct coll *aColl);
+				    struct key_def *def);
 /**
  * Insert tuple into ephemeral space.
  * In contrast to ordinary spaces, there is no need to create and
@@ -123,7 +123,8 @@ int tarantoolSqlite3EphemeralGetMaxId(BtCursor * pCur, uint32_t fieldno,
  * the key may span non-adjacent fields in a random order,
  * ex: [4]-[1]-[2]
  */
-int tarantoolSqlite3IdxKeyCompare(BtCursor * pCur, UnpackedRecord * pUnpacked,
+int tarantoolSqlite3IdxKeyCompare(struct sqlite3 *db,
+				  BtCursor * pCur, UnpackedRecord * pUnpacked,
 				  int *res);
 
 /**
diff --git a/src/box/sql/update.c b/src/box/sql/update.c
index f3bd0b7..d380b8c 100644
--- a/src/box/sql/update.c
+++ b/src/box/sql/update.c
@@ -358,12 +358,12 @@ sqlite3Update(Parse * pParse,		/* The parser context */
 	sqlite3VdbeAddOp2(v, OP_Null, 0, iPk);
 
 	if (isView) {
-		KeyInfo *pKeyInfo = sqlite3KeyInfoAlloc(pParse->db, nKey, 0);
+		struct key_def *def = key_def_new(nKey);
 		addrOpen = sqlite3VdbeAddOp4(v, OP_OpenTEphemeral, iEph,
-					     nKey, 0, (char*)pKeyInfo, P4_KEYINFO);
+					     nKey, 0, (char*)def, P4_KEYDEF);
 	} else {
 		addrOpen = sqlite3VdbeAddOp2(v, OP_OpenTEphemeral, iEph, nPk);
-		sqlite3VdbeSetP4KeyInfo(pParse, pPk);
+		sql_vdbe_set_p4_key_def(pParse, pPk);
 	}
 
 	pWInfo = sqlite3WhereBegin(pParse, pTabList, pWhere, 0, 0,
diff --git a/src/box/sql/vdbe.c b/src/box/sql/vdbe.c
index 013460f..7f82c3c 100644
--- a/src/box/sql/vdbe.c
+++ b/src/box/sql/vdbe.c
@@ -2243,35 +2243,36 @@ case OP_Permutation: {
  * OPFLAG_PERMUTE bit is clear, then register are compared in sequential
  * order.
  *
- * P4 is a KeyInfo structure that defines collating sequences and sort
+ * P4 is a key_def structure that defines collating sequences and sort
  * orders for the comparison.  The permutation applies to registers
- * only.  The KeyInfo elements are used sequentially.
+ * only.  The key_def elements are used sequentially.
  *
  * The comparison is a sort comparison, so NULLs compare equal,
  * NULLs are less than numbers, numbers are less than strings,
  * and strings are less than blobs.
  */
 case OP_Compare: {
-	int n;
-	int i;
 	int p1;
 	int p2;
-	const KeyInfo *pKeyInfo;
 	int idx;
-	struct coll *pColl;    /* Collating sequence to use on this term */
-	int bRev;          /* True for DESCENDING sort order */
 
-	if ((pOp->p5 & OPFLAG_PERMUTE)==0) aPermute = 0;
-	n = pOp->p3;
-	pKeyInfo = pOp->p4.pKeyInfo;
+	if ((pOp->p5 & OPFLAG_PERMUTE) == 0)
+		aPermute = 0;
+
+	int n = pOp->p3;
+
+	assert(pOp->p4type == P4_KEYDEF);
 	assert(n>0);
-	assert(pKeyInfo!=0);
 	p1 = pOp->p1;
 	p2 = pOp->p2;
+
+	struct key_def *def = pOp->p4.key_def;
 #if SQLITE_DEBUG
 	if (aPermute) {
-		int k, mx = 0;
-		for(k=0; k<n; k++) if (aPermute[k]>mx) mx = aPermute[k];
+		int mx = 0;
+		for(uint32_t k = 0; k < (uint32_t)n; k++)
+			if (aPermute[k] > mx)
+				mx = aPermute[k];
 		assert(p1>0 && p1+mx<=(p->nMem+1 - p->nCursor)+1);
 		assert(p2>0 && p2+mx<=(p->nMem+1 - p->nCursor)+1);
 	} else {
@@ -2279,18 +2280,19 @@ case OP_Compare: {
 		assert(p2>0 && p2+n<=(p->nMem+1 - p->nCursor)+1);
 	}
 #endif /* SQLITE_DEBUG */
-	for(i=0; i<n; i++) {
+	for(int i = 0; i < n; i++) {
 		idx = aPermute ? aPermute[i] : i;
 		assert(memIsValid(&aMem[p1+idx]));
 		assert(memIsValid(&aMem[p2+idx]));
 		REGISTER_TRACE(p1+idx, &aMem[p1+idx]);
 		REGISTER_TRACE(p2+idx, &aMem[p2+idx]);
-		assert(i<pKeyInfo->nField);
-		pColl = pKeyInfo->aColl[i];
-		bRev = pKeyInfo->aSortOrder[i];
-		iCompare = sqlite3MemCompare(&aMem[p1+idx], &aMem[p2+idx], pColl);
+		assert(i < (int)def->part_count);
+		struct coll *coll = def->parts[i].coll;
+		bool is_rev = def->parts[i].sort_order == SORT_ORDER_DESC;
+		iCompare = sqlite3MemCompare(&aMem[p1+idx], &aMem[p2+idx], coll);
 		if (iCompare) {
-			if (bRev) iCompare = -iCompare;
+			if (is_rev)
+				iCompare = -iCompare;
 			break;
 		}
 	}
@@ -3119,8 +3121,8 @@ case OP_SetCookie: {
  * values need not be contiguous but all P1 values should be
  * small integers. It is an error for P1 to be negative.
  *
- * The P4 value may be a pointer to a KeyInfo structure.
- * If it is a pointer to a KeyInfo structure, then said structure
+ * The P4 value may be a pointer to a key_def structure.
+ * If it is a pointer to a key_def structure, then said structure
  * defines the content and collatining sequence of the index
  * being opened. Otherwise, P4 is NULL.
  *
@@ -3208,7 +3210,7 @@ case OP_OpenWrite:
 	pBtCur->index = index;
 	pBtCur->eState = CURSOR_INVALID;
 	/* Key info still contains sorter order and collation. */
-	pCur->pKeyInfo = pOp->p4.pKeyInfo;
+	pCur->key_def = index->def->key_def;
 
 open_cursor_set_hints:
 	assert(OPFLAG_BULKCSR==BTREE_BULKLOAD);
@@ -3223,8 +3225,12 @@ open_cursor_set_hints:
 	break;
 }
 
-/* Opcode: OpenTEphemeral P1 P2 * * *
- * Synopsis: nColumn = P2
+/**
+ * Opcode: OpenTEphemeral P1 P2 * P4 *
+ * Synopsis:
+ * @param P1 index of new cursor to be created
+ * @param P2 number of columns in a new table
+ * @param P4 key def for new table
  *
  * This opcode creates Tarantool's ephemeral table and sets cursor P1 to it.
  */
@@ -3233,21 +3239,21 @@ case OP_OpenTEphemeral: {
 	BtCursor *pBtCur;
 	assert(pOp->p1 >= 0);
 	assert(pOp->p2 > 0);
-	assert(pOp->p4.pKeyInfo != 0);
-	assert(pOp->p4type == P4_KEYINFO);
+	assert(pOp->p4.key_def != NULL);
+	assert(pOp->p4type == P4_KEYDEF);
 
 	pCx = allocateCursor(p, pOp->p1, pOp->p2, CURTYPE_TARANTOOL);
 	if (pCx == 0) goto no_mem;
 	pCx->nullRow = 1;
 
-	pCx->pKeyInfo  = pOp->p4.pKeyInfo;
+	pCx->key_def  = pOp->p4.key_def;
 	pBtCur = pCx->uc.pCursor;
 	/* Ephemeral spaces don't have space_id */
 	pBtCur->eState = CURSOR_INVALID;
 	pBtCur->curFlags = BTCF_TEphemCursor;
 
 	rc = tarantoolSqlite3EphemeralCreate(pCx->uc.pCursor, pOp->p2,
-					     pOp->p4.pKeyInfo->aColl[0]);
+					     pCx->key_def);
 	if (rc) goto abort_due_to_error;
 	break;
 }
@@ -3269,9 +3275,8 @@ case OP_SorterOpen: {
 	assert(pOp->p2>=0);
 	pCx = allocateCursor(p, pOp->p1, pOp->p2, CURTYPE_SORTER);
 	if (pCx==0) goto no_mem;
-	pCx->pKeyInfo = pOp->p4.pKeyInfo;
-	assert(pCx->pKeyInfo->db==db);
-	rc = sqlite3VdbeSorterInit(db, pOp->p3, pCx);
+	pCx->key_def = pOp->p4.key_def;
+	rc = sqlite3VdbeSorterInit(db, pCx);
 	if (rc) goto abort_due_to_error;
 	break;
 }
@@ -3547,7 +3552,7 @@ case OP_SeekGT: {       /* jump, in3 */
 	nField = pOp->p4.i;
 	assert(pOp->p4type==P4_INT32);
 	assert(nField>0);
-	r.pKeyInfo = pC->pKeyInfo;
+	r.key_def = pC->key_def;
 	r.nField = (u16)nField;
 
 	if (reg_ipk > 0) {
@@ -3699,7 +3704,7 @@ case OP_Found: {        /* jump, in3 */
 	assert(pC->eCurType==CURTYPE_TARANTOOL);
 	assert(pC->uc.pCursor!=0);
 	if (pOp->p4.i>0) {
-		r.pKeyInfo = pC->pKeyInfo;
+		r.key_def = pC->key_def;
 		r.nField = (u16)pOp->p4.i;
 		r.aMem = pIn3;
 #ifdef SQLITE_DEBUG
@@ -3712,11 +3717,12 @@ case OP_Found: {        /* jump, in3 */
 		pIdxKey = &r;
 		pFree = 0;
 	} else {
-		pFree = pIdxKey = sqlite3VdbeAllocUnpackedRecord(pC->pKeyInfo);
+		pFree = pIdxKey = sqlite3VdbeAllocUnpackedRecord(db, pC->key_def);
 		if (pIdxKey==0) goto no_mem;
 		assert(pIn3->flags & MEM_Blob );
 		(void)ExpandBlob(pIn3);
-		sqlite3VdbeRecordUnpackMsgpack(pC->pKeyInfo, pIn3->n, pIn3->z, pIdxKey);
+		sqlite3VdbeRecordUnpackMsgpack(db, pC->key_def,
+					       pIn3->n, pIn3->z, pIdxKey);
 	}
 	pIdxKey->default_rc = 0;
 	pIdxKey->opcode = pOp->opcode;
@@ -4492,7 +4498,7 @@ case OP_IdxDelete: {
 	pCrsr = pC->uc.pCursor;
 	assert(pCrsr!=0);
 	assert(pOp->p5==0);
-	r.pKeyInfo = pC->pKeyInfo;
+	r.key_def = pC->key_def;
 	r.nField = (u16)pOp->p3;
 	r.default_rc = 0;
 	r.aMem = &aMem[pOp->p2];
@@ -4576,7 +4582,7 @@ case OP_IdxGE:  {       /* jump */
 	assert(pC->deferredMoveto==0);
 	assert(pOp->p5==0 || pOp->p5==1);
 	assert(pOp->p4type==P4_INT32);
-	r.pKeyInfo = pC->pKeyInfo;
+	r.key_def = pC->key_def;
 	r.nField = (u16)pOp->p4.i;
 	if (pOp->opcode<OP_IdxLT) {
 		assert(pOp->opcode==OP_IdxLE || pOp->opcode==OP_IdxGT);
diff --git a/src/box/sql/vdbe.h b/src/box/sql/vdbe.h
index e244606..4bd48d5 100644
--- a/src/box/sql/vdbe.h
+++ b/src/box/sql/vdbe.h
@@ -77,7 +77,6 @@ struct VdbeOp {
 		struct coll *pColl;	/* Used when p4type is P4_COLLSEQ */
 		Mem *pMem;	/* Used when p4type is P4_MEM */
 		bool b;         /* Used when p4type is P4_BOOL */
-		KeyInfo *pKeyInfo;	/* Used when p4type is P4_KEYINFO */
 		int *ai;	/* Used when p4type is P4_INTARRAY */
 		SubProgram *pProgram;	/* Used when p4type is P4_SUBPROGRAM */
 		Index *pIndex;	/* Used when p4type is P4_INDEX */
@@ -85,6 +84,8 @@ struct VdbeOp {
 		Expr *pExpr;	/* Used when p4type is P4_EXPR */
 #endif
 		int (*xAdvance) (BtCursor *, int *);
+		/* Used when p4type is P4_KEYDEF. */
+		struct key_def *key_def;
 	} p4;
 #ifdef SQLITE_ENABLE_EXPLAIN_COMMENTS
 	char *zComment;		/* Comment to improve readability */
@@ -131,7 +132,6 @@ typedef struct VdbeOpList VdbeOpList;
 #define P4_STATIC   (-2)	/* Pointer to a static string */
 #define P4_COLLSEQ  (-3)	/* P4 is a pointer to a CollSeq structure */
 #define P4_FUNCDEF  (-4)	/* P4 is a pointer to a FuncDef structure */
-#define P4_KEYINFO  (-5)	/* P4 is a pointer to a KeyInfo structure */
 #define P4_EXPR     (-6)	/* P4 is a pointer to an Expr tree */
 #define P4_MEM      (-7)	/* P4 is a pointer to a Mem*    structure */
 #define P4_TRANSIENT  0		/* P4 is a pointer to a transient string */
@@ -147,7 +147,6 @@ typedef struct VdbeOpList VdbeOpList;
 #define P4_PTR      (-18)	/* P4 is a generic pointer */
 #define P4_KEYDEF   (-19)       /* P4 is a pointer to key_def structure. */
 
-
 /* Error message codes for OP_Halt */
 #define P5_ConstraintNotNull 1
 #define P5_ConstraintUnique  2
@@ -226,7 +225,16 @@ int sqlite3VdbeChangeToNoop(Vdbe *, int addr);
 int sqlite3VdbeDeletePriorOpcode(Vdbe *, u8 op);
 void sqlite3VdbeChangeP4(Vdbe *, int addr, const char *zP4, int N);
 void sqlite3VdbeAppendP4(Vdbe *, void *pP4, int p4type);
-void sqlite3VdbeSetP4KeyInfo(Parse *, Index *);
+
+/**
+ * Set the P4 on the most recently added opcode to the key_def for the
+ * index given.
+ * @param Parse context, for error reporting.
+ * @param Index to get key_def from.
+ */
+void
+sql_vdbe_set_p4_key_def(struct Parse *parse, struct Index *index);
+
 VdbeOp *sqlite3VdbeGetOp(Vdbe *, int);
 int sqlite3VdbeMakeLabel(Vdbe *);
 void sqlite3VdbeRunOnlyOnce(Vdbe *);
@@ -257,14 +265,20 @@ char *sqlite3VdbeExpandSql(Vdbe *, const char *);
 #endif
 int sqlite3MemCompare(const Mem *, const Mem *, const struct coll *);
 
-void sqlite3VdbeRecordUnpackMsgpack(KeyInfo *, int, const void *,
-				    UnpackedRecord *);
-int sqlite3VdbeRecordCompare(int, const void *, UnpackedRecord *);
-int sqlite3VdbeRecordCompareWithSkip(int, const void *, UnpackedRecord *, int);
-UnpackedRecord *sqlite3VdbeAllocUnpackedRecord(KeyInfo *);
+void sqlite3VdbeRecordUnpackMsgpack(struct sqlite3 *db,
+				    struct key_def *key_def,
+				    int key_count, const void * msgpack,
+				    UnpackedRecord *dest);
+int sqlite3VdbeRecordCompare(struct sqlite3 *db, int key_count,
+			     const void *key1, UnpackedRecord *key2);
+int sqlite3VdbeRecordCompareWithSkip(struct sqlite3 *db,
+				     int key_count, const void *key1,
+				     struct UnpackedRecord *key2, bool is_skip);
+UnpackedRecord *sqlite3VdbeAllocUnpackedRecord(struct sqlite3 *,
+					       struct key_def *);
 int sql_vdbe_mem_alloc_region(Mem *, uint32_t);
 
-typedef int (*RecordCompare) (int, const void *, UnpackedRecord *);
+typedef int (*RecordCompare) (struct sqlite3 *db, int, const void *, UnpackedRecord *);
 RecordCompare sqlite3VdbeFindCompare(UnpackedRecord *);
 
 #ifndef SQLITE_OMIT_TRIGGER
diff --git a/src/box/sql/vdbeInt.h b/src/box/sql/vdbeInt.h
index ab9147c..044df10 100644
--- a/src/box/sql/vdbeInt.h
+++ b/src/box/sql/vdbeInt.h
@@ -110,7 +110,8 @@ struct VdbeCursor {
 		int pseudoTableReg;	/* CURTYPE_PSEUDO. Reg holding content. */
 		VdbeSorter *pSorter;	/* CURTYPE_SORTER. Sorter object */
 	} uc;
-	KeyInfo *pKeyInfo;	/* Info about index keys needed by index cursors */
+	/* Info about keys needed by index cursors. */
+	struct key_def *key_def;
 	i16 nField;		/* Number of fields in the header */
 	u16 nHdrParsed;		/* Number of header fields parsed so far */
 	const u8 *aRow;		/* Data for the current row, if all on one page */
@@ -451,7 +452,6 @@ struct PreUpdate {
 	VdbeCursor *pCsr;	/* Cursor to read old values from */
 	int op;			/* One of SQLITE_INSERT, UPDATE, DELETE */
 	u8 *aRecord;		/* old.* database record */
-	KeyInfo keyinfo;
 	UnpackedRecord *pUnpacked;	/* Unpacked version of aRecord[] */
 	UnpackedRecord *pNewUnpacked;	/* Unpacked version of new.* record */
 	int iNewReg;		/* Register for new.* values */
@@ -527,7 +527,7 @@ void sqlite3VdbePreUpdateHook(Vdbe *, VdbeCursor *, int, const char *, Table *,
 #endif
 int sqlite3VdbeTransferError(Vdbe * p);
 
-int sqlite3VdbeSorterInit(sqlite3 *, int, VdbeCursor *);
+int sqlite3VdbeSorterInit(struct sqlite3 *db, struct VdbeCursor *cursor);
 void sqlite3VdbeSorterReset(sqlite3 *, VdbeSorter *);
 void sqlite3VdbeSorterClose(sqlite3 *, VdbeCursor *);
 int sqlite3VdbeSorterRowkey(const VdbeCursor *, Mem *);
@@ -564,9 +564,10 @@ int sqlite3VdbeMemExpandBlob(Mem *);
 
 i64 sqlite3VdbeMsgpackRecordLen(Mem * pMem, u32 n);
 u32 sqlite3VdbeMsgpackRecordPut(u8 * pBuf, Mem * pMem, u32 n);
-int sqlite3VdbeCompareMsgpack(const char **pKey1,
-			      UnpackedRecord * pUnpacked, int iKey2);
-int sqlite3VdbeRecordCompareMsgpack(int nKey1, const void *pKey1,
+int sqlite3VdbeCompareMsgpack(struct sqlite3 *db,
+			      const char **key1,
+			      UnpackedRecord *pUnpacked, int iKey2);
+int sqlite3VdbeRecordCompareMsgpack(struct sqlite3 *db, int nKey1, const void *pKey1,
 				    UnpackedRecord * pPKey2);
 u32 sqlite3VdbeMsgpackGet(const unsigned char *buf, Mem * pMem);
 
diff --git a/src/box/sql/vdbeapi.c b/src/box/sql/vdbeapi.c
index 6e41859..c781fd4 100644
--- a/src/box/sql/vdbeapi.c
+++ b/src/box/sql/vdbeapi.c
@@ -1581,103 +1581,6 @@ sqlite3_expanded_sql(sqlite3_stmt * pStmt)
 #endif
 }
 
-#ifdef SQLITE_ENABLE_PREUPDATE_HOOK
-/*
- * Allocate and populate an UnpackedRecord structure based on the serialized
- * record in nKey/pKey. Return a pointer to the new UnpackedRecord structure
- * if successful, or a NULL pointer if an OOM error is encountered.
- */
-static UnpackedRecord *
-vdbeUnpackRecord(KeyInfo * pKeyInfo, int nKey, const void *pKey)
-{
-	UnpackedRecord *pRet;	/* Return value */
-
-	pRet = sqlite3VdbeAllocUnpackedRecord(pKeyInfo);
-	if (pRet) {
-		memset(pRet->aMem, 0, sizeof(Mem) * (pKeyInfo->nField + 1));
-		sqlite3VdbeRecordUnpack(pKeyInfo, nKey, pKey, pRet);
-	}
-	return pRet;
-}
-
-/*
- * This function is called from within a pre-update callback to retrieve
- * a field of the row currently being updated or deleted.
- */
-int
-sqlite3_preupdate_old(sqlite3 * db, int iIdx, sqlite3_value ** ppValue)
-{
-	PreUpdate *p = db->pPreUpdate;
-	int rc = SQLITE_OK;
-
-	/* Test that this call is being made from within an SQLITE_DELETE or
-	 * SQLITE_UPDATE pre-update callback, and that iIdx is within range.
-	 */
-	if (!p || p->op == SQLITE_INSERT) {
-		rc = SQLITE_MISUSE_BKPT;
-		goto preupdate_old_out;
-	}
-	if (iIdx >= p->pCsr->nField || iIdx < 0) {
-		rc = SQLITE_RANGE;
-		goto preupdate_old_out;
-	}
-
-	/* If the old.* record has not yet been loaded into memory, do so now. */
-	if (p->pUnpacked == 0) {
-		u32 nRec;
-		u8 *aRec;
-
-		nRec = sqlite3BtreePayloadSize(p->pCsr->uc.pCursor);
-		aRec = sqlite3DbMallocRaw(db, nRec);
-		if (!aRec)
-			goto preupdate_old_out;
-		rc = sqlite3BtreePayload(p->pCsr->uc.pCursor, 0, nRec, aRec);
-		if (rc == SQLITE_OK) {
-			p->pUnpacked =
-			    vdbeUnpackRecord(&p->keyinfo, nRec, aRec);
-			if (!p->pUnpacked)
-				rc = SQLITE_NOMEM;
-		}
-		if (rc != SQLITE_OK) {
-			sqlite3DbFree(db, aRec);
-			goto preupdate_old_out;
-		}
-		p->aRecord = aRec;
-	}
-
-	if (iIdx >= p->pUnpacked->nField) {
-		*ppValue = (sqlite3_value *) columnNullValue();
-	} else {
-		Mem *pMem = *ppValue = &p->pUnpacked->aMem[iIdx];
-		*ppValue = &p->pUnpacked->aMem[iIdx];
-		if (iIdx == p->pTab->iPKey) {
-			sqlite3VdbeMemSetInt64(pMem, p->iKey1);
-		} else if (p->pTab->aCol[iIdx].affinity == SQLITE_AFF_REAL) {
-			if (pMem->flags & MEM_Int) {
-				sqlite3VdbeMemRealify(pMem);
-			}
-		}
-	}
-
- preupdate_old_out:
-	sqlite3Error(db, rc);
-	return sqlite3ApiExit(db, rc);
-}
-#endif				/* SQLITE_ENABLE_PREUPDATE_HOOK */
-
-#ifdef SQLITE_ENABLE_PREUPDATE_HOOK
-/*
- * This function is called from within a pre-update callback to retrieve
- * the number of columns in the row being updated, deleted or inserted.
- */
-int
-sqlite3_preupdate_count(sqlite3 * db)
-{
-	PreUpdate *p = db->pPreUpdate;
-	return (p ? p->keyinfo.nField : 0);
-}
-#endif				/* SQLITE_ENABLE_PREUPDATE_HOOK */
-
 #ifdef SQLITE_ENABLE_PREUPDATE_HOOK
 /*
  * This function is designed to be called from within a pre-update callback
@@ -1698,92 +1601,6 @@ sqlite3_preupdate_depth(sqlite3 * db)
 }
 #endif				/* SQLITE_ENABLE_PREUPDATE_HOOK */
 
-#ifdef SQLITE_ENABLE_PREUPDATE_HOOK
-/*
- * This function is called from within a pre-update callback to retrieve
- * a field of the row currently being updated or inserted.
- */
-int
-sqlite3_preupdate_new(sqlite3 * db, int iIdx, sqlite3_value ** ppValue)
-{
-	PreUpdate *p = db->pPreUpdate;
-	int rc = SQLITE_OK;
-	Mem *pMem;
-
-	if (!p || p->op == SQLITE_DELETE) {
-		rc = SQLITE_MISUSE_BKPT;
-		goto preupdate_new_out;
-	}
-	if (iIdx >= p->pCsr->nField || iIdx < 0) {
-		rc = SQLITE_RANGE;
-		goto preupdate_new_out;
-	}
-
-	if (p->op == SQLITE_INSERT) {
-		/* For an INSERT, memory cell p->iNewReg contains the serialized record
-		 * that is being inserted. Deserialize it.
-		 */
-		UnpackedRecord *pUnpack = p->pNewUnpacked;
-		if (!pUnpack) {
-			Mem *pData = &p->v->aMem[p->iNewReg];
-			rc = ExpandBlob(pData);
-			if (rc != SQLITE_OK)
-				goto preupdate_new_out;
-			pUnpack =
-			    vdbeUnpackRecord(&p->keyinfo, pData->n, pData->z);
-			if (!pUnpack) {
-				rc = SQLITE_NOMEM;
-				goto preupdate_new_out;
-			}
-			p->pNewUnpacked = pUnpack;
-		}
-		if (iIdx >= pUnpack->nField) {
-			pMem = (sqlite3_value *) columnNullValue();
-		} else {
-			pMem = &pUnpack->aMem[iIdx];
-			if (iIdx == p->pTab->iPKey) {
-				sqlite3VdbeMemSetInt64(pMem, p->iKey2);
-			}
-		}
-	} else {
-		/* For an UPDATE, memory cell (p->iNewReg+1+iIdx) contains the required
-		 * value. Make a copy of the cell contents and return a pointer to it.
-		 * It is not safe to return a pointer to the memory cell itself as the
-		 * caller may modify the value text encoding.
-		 */
-		assert(p->op == SQLITE_UPDATE);
-		if (!p->aNew) {
-			p->aNew =
-			    (Mem *) sqlite3DbMallocZero(db,
-							sizeof(Mem) *
-							p->pCsr->nField);
-			if (!p->aNew) {
-				rc = SQLITE_NOMEM;
-				goto preupdate_new_out;
-			}
-		}
-		assert(iIdx >= 0 && iIdx < p->pCsr->nField);
-		pMem = &p->aNew[iIdx];
-		if (pMem->flags == 0) {
-			if (iIdx == p->pTab->iPKey) {
-				sqlite3VdbeMemSetInt64(pMem, p->iKey2);
-			} else {
-				rc = sqlite3VdbeMemCopy(pMem,
-							&p->v->aMem[p->iNewReg +
-								    1 + iIdx]);
-				if (rc != SQLITE_OK)
-					goto preupdate_new_out;
-			}
-		}
-	}
-	*ppValue = pMem;
-
- preupdate_new_out:
-	sqlite3Error(db, rc);
-	return sqlite3ApiExit(db, rc);
-}
-#endif				/* SQLITE_ENABLE_PREUPDATE_HOOK */
-
 #ifdef SQLITE_ENABLE_STMT_SCANSTATUS
 /*
  * Return status data for a single loop within query pStmt.
diff --git a/src/box/sql/vdbeaux.c b/src/box/sql/vdbeaux.c
index b3998ea..c2352e7 100644
--- a/src/box/sql/vdbeaux.c
+++ b/src/box/sql/vdbeaux.c
@@ -948,11 +948,9 @@ freeP4(sqlite3 * db, int p4type, void *p4)
 			sqlite3DbFree(db, p4);
 			break;
 		}
-	case P4_KEYINFO:{
-			if (db->pnBytesFreed == 0)
-				sqlite3KeyInfoUnref((KeyInfo *) p4);
-			break;
-		}
+	case P4_KEYDEF:
+		free(p4);
+		break;
 #ifdef SQLITE_ENABLE_CURSOR_HINTS
 	case P4_EXPR:{
 			sqlite3ExprDelete(db, (Expr *) p4);
@@ -1140,20 +1138,19 @@ sqlite3VdbeAppendP4(Vdbe * p, void *pP4, int n)
 	}
 }
 
-/*
- * Set the P4 on the most recently added opcode to the KeyInfo for the
- * index given.
- */
 void
-sqlite3VdbeSetP4KeyInfo(Parse * pParse, Index * pIdx)
-{
-	Vdbe *v = pParse->pVdbe;
-	KeyInfo *pKeyInfo;
-	assert(v != 0);
-	assert(pIdx != 0);
-	pKeyInfo = sqlite3KeyInfoOfIndex(pParse, pParse->db, pIdx);
-	if (pKeyInfo)
-		sqlite3VdbeAppendP4(v, pKeyInfo, P4_KEYINFO);
+sql_vdbe_set_p4_key_def(struct Parse *parse, struct Index *idx)
+{
+	struct Vdbe *v = parse->pVdbe;
+	assert(v != NULL);
+	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 *index = index_find(space, index_id);
+	assert(index != NULL);
+	sqlite3VdbeAppendP4(v, key_def_dup(index->def->key_def), P4_KEYDEF);
 }
 
 #ifdef SQLITE_ENABLE_EXPLAIN_COMMENTS
@@ -1512,26 +1509,28 @@ displayP4(Op * pOp, char *zTemp, int nTemp)
 	assert(nTemp >= 20);
 	sqlite3StrAccumInit(&x, 0, zTemp, nTemp, 0);
 	switch (pOp->p4type) {
-	case P4_KEYINFO:{
-			int j;
-			KeyInfo *pKeyInfo;
+	case P4_KEYDEF:{
+			struct key_def *def;
 
-			if (pOp->p4.pKeyInfo == NULL) {
+			if (pOp->p4.key_def == NULL) {
 				sqlite3XPrintf(&x, "k[NULL]");
 			} else {
-				pKeyInfo = pOp->p4.pKeyInfo;
-				assert(pKeyInfo->aSortOrder != 0);
-				sqlite3XPrintf(&x, "k(%d", pKeyInfo->nField);
-				for (j = 0; j < pKeyInfo->nField; j++) {
-					struct coll *pColl = pKeyInfo->aColl[j];
-					const char *zColl =
-					    pColl ? pColl->name : "";
-					if (strcmp(zColl, "BINARY") == 0)
-						zColl = "B";
+				def = pOp->p4.key_def;
+				sqlite3XPrintf(&x, "k(%d", def->part_count);
+				for (int j = 0; j < (int)def->part_count; j++) {
+					struct coll *coll = def->parts[j].coll;
+					const char *coll_str =
+					    coll != NULL ? coll->name : "";
+					if (strcmp(coll_str, "BINARY") == 0)
+						coll_str = "B";
+					const char *sort_order = "";
+					if (def->parts[j].sort_order ==
+					    SORT_ORDER_DESC) {
+						sort_order = "-";
+					}
 					sqlite3XPrintf(&x, ",%s%s",
-						       pKeyInfo->
-						       aSortOrder[j] ? "-" : "",
-						       zColl);
+						       sort_order,
+						       coll_str);
 				}
 				sqlite3StrAccumAppend(&x, ")", 1);
 			}
@@ -3491,7 +3490,7 @@ sqlite3VdbeSerialGet(const unsigned char *buf,	/* Buffer to deserialize from */
 /*
  * This routine is used to allocate sufficient space for an UnpackedRecord
  * structure large enough to be used with sqlite3VdbeRecordUnpack() if
- * the first argument is a pointer to KeyInfo structure pKeyInfo.
+ * the first argument is a pointer to key_def structure.
  *
  * The space is either allocated using sqlite3DbMallocRaw() or from within
  * the unaligned buffer passed via the second and third arguments (presumably
@@ -3503,20 +3502,19 @@ sqlite3VdbeSerialGet(const unsigned char *buf,	/* Buffer to deserialize from */
  * If an OOM error occurs, NULL is returned.
  */
 UnpackedRecord *
-sqlite3VdbeAllocUnpackedRecord(KeyInfo * pKeyInfo)
+sqlite3VdbeAllocUnpackedRecord(struct sqlite3 *db, struct key_def *key_def)
 {
 	UnpackedRecord *p;	/* Unpacked record to return */
 	int nByte;		/* Number of bytes required for *p */
 	nByte =
-	    ROUND8(sizeof(UnpackedRecord)) + sizeof(Mem) * (pKeyInfo->nField +
+	    ROUND8(sizeof(UnpackedRecord)) + sizeof(Mem) * (key_def->part_count +
 							    1);
-	p = (UnpackedRecord *) sqlite3DbMallocRaw(pKeyInfo->db, nByte);
+	p = (UnpackedRecord *) sqlite3DbMallocRaw(db, nByte);
 	if (!p)
 		return 0;
 	p->aMem = (Mem *) & ((char *)p)[ROUND8(sizeof(UnpackedRecord))];
-	assert(pKeyInfo->aSortOrder != 0);
-	p->pKeyInfo = pKeyInfo;
-	p->nField = pKeyInfo->nField + 1;
+	p->key_def = key_def;
+	p->nField = key_def->part_count + 1;
 	return p;
 }
 
@@ -3545,7 +3543,8 @@ sql_vdbe_mem_alloc_region(Mem *vdbe_mem, uint32_t size)
  * Return false if there is a disagreement.
  */
 static int
-vdbeRecordCompareDebug(int nKey1, const void *pKey1,	/* Left key */
+vdbeRecordCompareDebug(struct sqlite3 *db,
+		       int nKey1, const void *pKey1,	/* Left key */
 		       const UnpackedRecord * pPKey2,	/* Right key */
 		       int desiredResult)		/* Correct answer */
 {
@@ -3555,13 +3554,11 @@ vdbeRecordCompareDebug(int nKey1, const void *pKey1,	/* Left key */
 	int i = 0;
 	int rc = 0;
 	const unsigned char *aKey1 = (const unsigned char *)pKey1;
-	KeyInfo *pKeyInfo;
+	struct key_def *key_def;
 	Mem mem1;
 
-	pKeyInfo = pPKey2->pKeyInfo;
-	if (pKeyInfo->db == 0)
-		return 1;
-	mem1.db = pKeyInfo->db;
+	key_def = pPKey2->key_def;
+	mem1.db = db;
 	/* mem1.flags = 0;  // Will be initialized by sqlite3VdbeSerialGet() */
 	VVA_ONLY(mem1.szMalloc = 0;
 	    )
@@ -3579,10 +3576,7 @@ vdbeRecordCompareDebug(int nKey1, const void *pKey1,	/* Left key */
 	if (szHdr1 > 98307)
 		return SQLITE_CORRUPT;
 	d1 = szHdr1;
-	assert(pKeyInfo->nField + pKeyInfo->nXField >= pPKey2->nField
-	       || CORRUPT_DB);
-	assert(pKeyInfo->aSortOrder != 0);
-	assert(pKeyInfo->nField > 0);
+	assert(key_def->part_count > 0);
 	assert(idx1 <= szHdr1 || CORRUPT_DB);
 	do {
 		u32 serial_type1;
@@ -3609,10 +3603,10 @@ vdbeRecordCompareDebug(int nKey1, const void *pKey1,	/* Left key */
 		/* Do the comparison
 		 */
 		rc = sqlite3MemCompare(&mem1, &pPKey2->aMem[i],
-				       pKeyInfo->aColl[i]);
+				       key_def->parts[i].coll);
 		if (rc != 0) {
 			assert(mem1.szMalloc == 0);	/* See comment below */
-			if (pKeyInfo->aSortOrder[i]) {
+			if (key_def->parts[i].sort_order != SORT_ORDER_ASC) {
 				rc = -rc;	/* Invert the result for DESC sort order. */
 			}
 			goto debugCompareEnd;
@@ -3641,7 +3635,7 @@ vdbeRecordCompareDebug(int nKey1, const void *pKey1,	/* Left key */
 		return 1;
 	if (CORRUPT_DB)
 		return 1;
-	if (pKeyInfo->db->mallocFailed)
+	if (db->mallocFailed)
 		return 1;
 	return 0;
 }
@@ -3917,12 +3911,13 @@ vdbeRecordDecodeInt(u32 serial_type, const u8 * aKey)
  * If database corruption is discovered, set pPKey2->errCode to
  * SQLITE_CORRUPT and return 0. If an OOM error is encountered,
  * pPKey2->errCode is set to SQLITE_NOMEM and, if it is not NULL, the
- * malloc-failed flag set on database handle (pPKey2->pKeyInfo->db).
+ * malloc-failed flag set on database handle.
  */
 int
-sqlite3VdbeRecordCompareWithSkip(int nKey1, const void *pKey1,	/* Left key */
+sqlite3VdbeRecordCompareWithSkip(struct sqlite3 *db,
+				 int nKey1, const void *pKey1,	/* Left key */
 				 UnpackedRecord * pPKey2,	/* Right key */
-				 int bSkip)			/* If true, skip the first field */
+				 bool bSkip)			/* If true, skip the first field */
 {
 	u32 d1;			/* Offset into aKey[] of next data element */
 	int i;			/* Index of next field to compare */
@@ -3930,7 +3925,7 @@ sqlite3VdbeRecordCompareWithSkip(int nKey1, const void *pKey1,	/* Left key */
 	u32 idx1;		/* Offset of first type in header */
 	int rc = 0;		/* Return value */
 	Mem *pRhs = pPKey2->aMem;	/* Next field of pPKey2 to compare */
-	KeyInfo *pKeyInfo = pPKey2->pKeyInfo;
+	struct key_def *key_def = pPKey2->key_def;
 	const unsigned char *aKey1 = (const unsigned char *)pKey1;
 	Mem mem1;
 
@@ -3957,10 +3952,7 @@ sqlite3VdbeRecordCompareWithSkip(int nKey1, const void *pKey1,	/* Left key */
 
 	VVA_ONLY(mem1.szMalloc = 0;
 	    )			/* Only needed by assert() statements */
-	    assert(pPKey2->pKeyInfo->nField + pPKey2->pKeyInfo->nXField >=
-		   pPKey2->nField || CORRUPT_DB);
-	assert(pPKey2->pKeyInfo->aSortOrder != 0);
-	assert(pPKey2->pKeyInfo->nField > 0);
+	assert(pPKey2->key_def->part_count > 0);
 	assert(idx1 <= szHdr1 || CORRUPT_DB);
 	do {
 		u32 serial_type;
@@ -4035,13 +4027,14 @@ sqlite3VdbeRecordCompareWithSkip(int nKey1, const void *pKey1,	/* Left key */
 					pPKey2->errCode =
 					    (u8) SQLITE_CORRUPT_BKPT;
 					return 0;	/* Corruption */
-				} else if (pKeyInfo->aColl[i]) {
-					mem1.db = pKeyInfo->db;
+				} else if (key_def->parts[i].coll !=NULL) {
+					mem1.db = db;
 					mem1.flags = MEM_Str;
 					mem1.z = (char *)&aKey1[d1];
+					struct coll* coll;
+					coll = key_def->parts[i].coll;
 					rc = vdbeCompareMemString(&mem1, pRhs,
-								  pKeyInfo->
-								  aColl[i],
+								  coll,
 								  &pPKey2->
 								  errCode);
 				} else {
@@ -4091,11 +4084,10 @@ sqlite3VdbeRecordCompareWithSkip(int nKey1, const void *pKey1,	/* Left key */
 		}
 
 		if (rc != 0) {
-			if (pKeyInfo->aSortOrder[i]) {
+			if (key_def->parts[i].sort_order != SORT_ORDER_ASC)
 				rc = -rc;
-			}
 			assert(vdbeRecordCompareDebug
-			       (nKey1, pKey1, pPKey2, rc));
+			       (db, nKey1, pKey1, pPKey2, rc));
 			assert(mem1.szMalloc == 0);	/* See comment below */
 			return rc;
 		}
@@ -4118,18 +4110,19 @@ sqlite3VdbeRecordCompareWithSkip(int nKey1, const void *pKey1,	/* Left key */
 	 * value.
 	 */
 	assert(CORRUPT_DB
-	       || vdbeRecordCompareDebug(nKey1, pKey1, pPKey2,
+	       || vdbeRecordCompareDebug(db, nKey1, pKey1, pPKey2,
 					 pPKey2->default_rc)
-	       || pKeyInfo->db->mallocFailed);
+	       || db->mallocFailed);
 	pPKey2->eqSeen = 1;
 	return pPKey2->default_rc;
 }
 
 int
-sqlite3VdbeRecordCompare(int nKey1, const void *pKey1,	/* Left key */
-			 UnpackedRecord * pPKey2)	/* Right key */
+sqlite3VdbeRecordCompare(struct sqlite3 *db,
+			 int key_count, const void *key1,	/* Left key */
+			 UnpackedRecord *key2)	/* Right key */
 {
-	return sqlite3VdbeRecordCompareWithSkip(nKey1, pKey1, pPKey2, 0);
+	return sqlite3VdbeRecordCompareWithSkip(db, key_count, key1, key2, false);
 }
 
 /*
@@ -4164,7 +4157,7 @@ sqlite3VdbeIdxKeyCompare(sqlite3 * db,			/* Database connection */
 	assert(sqlite3CursorIsValid(pCur));
 	if (pCur->curFlags & BTCF_TaCursor ||
 	    pCur->curFlags & BTCF_TEphemCursor) {
-		return tarantoolSqlite3IdxKeyCompare(pCur, pUnpacked, res);
+		return tarantoolSqlite3IdxKeyCompare(db, pCur, pUnpacked, res);
 	}
 	unreachable();
 	return SQLITE_OK;
@@ -4286,68 +4279,6 @@ vdbeFreeUnpacked(sqlite3 * db, UnpackedRecord * p)
 }
 #endif				/* SQLITE_ENABLE_PREUPDATE_HOOK */
 
-#ifdef SQLITE_ENABLE_PREUPDATE_HOOK
-/*
- * Invoke the pre-update hook. If this is an UPDATE or DELETE pre-update call,
- * then cursor passed as the second argument should point to the row about
- * to be update or deleted. If the application calls sqlite3_preupdate_old(),
- * the required value will be read from the row the cursor points to.
- */
-void
-sqlite3VdbePreUpdateHook(Vdbe * v,		/* Vdbe pre-update hook is invoked by */
-			 VdbeCursor * pCsr,	/* Cursor to grab old.* values from */
-			 int op,		/* SQLITE_INSERT, UPDATE or DELETE */
-			 Table * pTab,		/* Modified table */
-			 i64 iKey1,		/* Initial key value */
-			 int iReg)		/* Register for new.* record */
-{
-	sqlite3 *db = v->db;
-	i64 iKey2;
-	PreUpdate preupdate;
-	const char *zTbl = pTab->zName;
-	static const u8 fakeSortOrder = 0;
-
-	assert(db->pPreUpdate == 0);
-	memset(&preupdate, 0, sizeof(PreUpdate));
-	if (op == SQLITE_UPDATE) {
-		iKey2 = v->aMem[iReg].u.i;
-	} else {
-		iKey2 = iKey1;
-	}
-
-	assert(pCsr->nField == pTab->nCol
-	       || (pCsr->nField == pTab->nCol + 1 && op == SQLITE_DELETE
-		   && iReg == -1)
-	    );
-
-	preupdate.v = v;
-	preupdate.pCsr = pCsr;
-	preupdate.op = op;
-	preupdate.iNewReg = iReg;
-	preupdate.keyinfo.db = db;
-	preupdate.keyinfo.nField = pTab->nCol;
-	preupdate.keyinfo.aSortOrder = (u8 *) & fakeSortOrder;
-	preupdate.iKey1 = iKey1;
-	preupdate.iKey2 = iKey2;
-	preupdate.pTab = pTab;
-
-	db->pPreUpdate = &preupdate;
-	db->xPreUpdateCallback(db->pPreUpdateArg, db, op, zTbl, iKey1,
-			       iKey2);
-	db->pPreUpdate = 0;
-	sqlite3DbFree(db, preupdate.aRecord);
-	vdbeFreeUnpacked(db, preupdate.pUnpacked);
-	vdbeFreeUnpacked(db, preupdate.pNewUnpacked);
-	if (preupdate.aNew) {
-		int i;
-		for (i = 0; i < pCsr->nField; i++) {
-			sqlite3VdbeMemRelease(&preupdate.aNew[i]);
-		}
-		sqlite3DbFree(db, preupdate.aNew);
-	}
-}
-#endif				/* SQLITE_ENABLE_PREUPDATE_HOOK */
-
 i64
 sqlite3VdbeMsgpackRecordLen(Mem * pRec, u32 n)
 {
@@ -4420,7 +4351,7 @@ sqlite3VdbeMsgpackRecordPut(u8 * pBuf, Mem * pRec, u32 n)
 }
 
 int
-sqlite3VdbeCompareMsgpack(const char **pKey1,
+sqlite3VdbeCompareMsgpack(struct sqlite3 *db, const char **pKey1,
 			  UnpackedRecord * pUnpacked, int iKey2)
 {
 	const char *aKey1 = *pKey1;
@@ -4493,16 +4424,17 @@ sqlite3VdbeCompareMsgpack(const char **pKey1,
 		}
 	case MP_STR:{
 			if (pKey2->flags & MEM_Str) {
-				KeyInfo *pKeyInfo = pUnpacked->pKeyInfo;
+				struct key_def *key_def = pUnpacked->key_def;
 				mem1.n = mp_decode_strl(&aKey1);
 				mem1.z = (char *)aKey1;
 				aKey1 += mem1.n;
-				if (pKeyInfo->aColl[iKey2]) {
-					mem1.db = pKeyInfo->db;
+				struct coll *coll;
+				coll = key_def->parts[iKey2].coll;
+				if (coll != NULL) {
+					mem1.db = db;
 					mem1.flags = MEM_Str;
 					rc = vdbeCompareMemString(&mem1, pKey2,
-								  pKeyInfo->
-								  aColl[iKey2],
+								  coll,
 								  &pUnpacked->
 								  errCode);
 				} else {
@@ -4553,29 +4485,30 @@ sqlite3VdbeCompareMsgpack(const char **pKey1,
 }
 
 int
-sqlite3VdbeRecordCompareMsgpack(int nKey1, const void *pKey1,	/* Left key */
-				UnpackedRecord * pPKey2)	/* Right key */
+sqlite3VdbeRecordCompareMsgpack(struct sqlite3 *db,
+				int key_count, const void *key1,	/* Left key */
+				UnpackedRecord * key2)	/* Right key */
 {
-	(void)nKey1;		/* assume valid data */
+	(void)key_count;		/* assume valid data */
 
 	int rc = 0;		/* Return value */
-	const char *aKey1 = (const char *)pKey1;
-	u32 i, n = mp_decode_array(&aKey1);
+	u32 i, n = mp_decode_array((const char**)&key1);
 
-	n = MIN(n, pPKey2->nField);
+	n = MIN(n, key2->nField);
 
 	for (i = 0; i != n; i++) {
-		rc = sqlite3VdbeCompareMsgpack(&aKey1, pPKey2, i);
+		rc = sqlite3VdbeCompareMsgpack(db, (const char**)&key1, key2, i);
 		if (rc != 0) {
-			if (pPKey2->pKeyInfo->aSortOrder[i]) {
+			if (key2->key_def->parts[i].sort_order !=
+			    SORT_ORDER_ASC) {
 				rc = -rc;
 			}
 			return rc;
 		}
 	}
 
-	pPKey2->eqSeen = 1;
-	return pPKey2->default_rc;
+	key2->eqSeen = 1;
+	return key2->default_rc;
 }
 
 u32
@@ -4654,7 +4587,8 @@ sqlite3VdbeMsgpackGet(const unsigned char *buf,	/* Buffer to deserialize from */
 }
 
 void
-sqlite3VdbeRecordUnpackMsgpack(KeyInfo * pKeyInfo,	/* Information about the record format */
+sqlite3VdbeRecordUnpackMsgpack(struct sqlite3 *db,
+			       struct key_def *key_def,	/* Information about the record format */
 			       int nKey,		/* Size of the binary record */
 			       const void *pKey,	/* The binary record */
 			       UnpackedRecord * p)	/* Populate this structure before returning. */
@@ -4664,10 +4598,11 @@ sqlite3VdbeRecordUnpackMsgpack(KeyInfo * pKeyInfo,	/* Information about the reco
 	(void)nKey;
 	Mem *pMem = p->aMem;
 	n = mp_decode_array(&zParse);
-	n = p->nField = MIN(n, pKeyInfo->nField);
+	n = p->nField = MIN(n, key_def->part_count);
 	p->default_rc = 0;
+	p->key_def = key_def;
 	while (n--) {
-		pMem->db = pKeyInfo->db;
+		pMem->db = db;
 		/* pMem->flags = 0; // sqlite3VdbeSerialGet() will set this for us */
 		pMem->szMalloc = 0;
 		pMem->z = 0;
diff --git a/src/box/sql/vdbemem.c b/src/box/sql/vdbemem.c
index 9dd254f..93f451d 100644
--- a/src/box/sql/vdbemem.c
+++ b/src/box/sql/vdbemem.c
@@ -1089,14 +1089,13 @@ valueNew(sqlite3 * db, struct ValueNewStat4Ctx *p)
 			int i;	/* Counter variable */
 			int nCol = index_column_count(pIdx);
 
-			nByte =
-			    sizeof(Mem) * nCol + ROUND8(sizeof(UnpackedRecord));
+			nByte = sizeof(Mem) * nCol +
+				ROUND8(sizeof(UnpackedRecord));
 			pRec =
 			    (UnpackedRecord *) sqlite3DbMallocZero(db, nByte);
-			if (pRec) {
-				pRec->pKeyInfo =
-				    sqlite3KeyInfoOfIndex(p->pParse, db, pIdx);
-				if (pRec->pKeyInfo) {
+			if (pRec != NULL) {
+				pRec->key_def = sql_index_key_def(pIdx);
+				if (pRec->key_def != NULL) {
 					pRec->aMem =
 					    (Mem *) ((u8 *) pRec +
 						     ROUND8(sizeof
@@ -1655,13 +1654,12 @@ sqlite3Stat4ProbeFree(UnpackedRecord * pRec)
 {
 	if (pRec) {
 		int i;
-		int nCol = pRec->pKeyInfo->nField;
+		int nCol = pRec->key_def->part_count;
 		Mem *aMem = pRec->aMem;
 		sqlite3 *db = aMem[0].db;
 		for (i = 0; i < nCol; i++) {
 			sqlite3VdbeMemRelease(&aMem[i]);
 		}
-		sqlite3KeyInfoUnref(pRec->pKeyInfo);
 		sqlite3DbFree(db, pRec);
 	}
 }
diff --git a/src/box/sql/vdbesort.c b/src/box/sql/vdbesort.c
index be3cc4c..9651fae 100644
--- a/src/box/sql/vdbesort.c
+++ b/src/box/sql/vdbesort.c
@@ -343,7 +343,7 @@ struct VdbeSorter {
 	PmaReader *pReader;	/* Readr data from here after Rewind() */
 	MergeEngine *pMerger;	/* Or here, if bUseThreads==0 */
 	sqlite3 *db;		/* Database connection */
-	KeyInfo *pKeyInfo;	/* How to compare records */
+	struct key_def *key_def;
 	UnpackedRecord *pUnpacked;	/* Used by VdbeSorterCompare() */
 	SorterList list;	/* List of in-memory records */
 	int iMemory;		/* Offset of free space in list.aMemory */
@@ -796,7 +796,7 @@ vdbePmaReaderInit(SortSubtask * pTask,	/* Task context */
 
 /*
  * Compare key1 (buffer pKey1, size nKey1 bytes) with key2 (buffer pKey2,
- * size nKey2 bytes). Use (pTask->pKeyInfo) for the collation sequences
+ * size nKey2 bytes). Use (pTask->key_def) for the collation sequences
  * used by the comparison. Return the result of the comparison.
  *
  * If IN/OUT parameter *pbKey2Cached is true when this function is called,
@@ -808,7 +808,7 @@ vdbePmaReaderInit(SortSubtask * pTask,	/* Task context */
  * to SQLITE_NOMEM.
  */
 static int
-vdbeSorterCompare(SortSubtask * pTask,	/* Subtask context (for pKeyInfo) */
+vdbeSorterCompare(SortSubtask * pTask,	/* Subtask context (for key_def) */
 		  int *pbKey2Cached,	/* True if pTask->pUnpacked is pKey2 */
 		  const void *pKey1, int nKey1,	/* Left side of comparison */
 		  const void *pKey2, int nKey2	/* Right side of comparison */
@@ -816,17 +816,19 @@ vdbeSorterCompare(SortSubtask * pTask,	/* Subtask context (for pKeyInfo) */
 {
 	UnpackedRecord *r2 = pTask->pUnpacked;
 	if (!*pbKey2Cached) {
-		sqlite3VdbeRecordUnpackMsgpack(pTask->pSorter->pKeyInfo, nKey2,
-					       pKey2, r2);
+		sqlite3VdbeRecordUnpackMsgpack(pTask->pSorter->db,
+					       pTask->pSorter->key_def,
+					       nKey2, pKey2, r2);
 		*pbKey2Cached = 1;
 	}
-	return sqlite3VdbeRecordCompareMsgpack(nKey1, pKey1, r2);
+	return sqlite3VdbeRecordCompareMsgpack(pTask->pSorter->db,
+					       nKey1, pKey1, r2);
 }
 
 /*
  * Initialize the temporary index cursor just opened as a sorter cursor.
  *
- * Usually, the sorter module uses the value of (pCsr->pKeyInfo->nField)
+ * Usually, the sorter module uses the value of (pCsr->key_def->part_count)
  * to determine the number of fields that should be compared from the
  * records being sorted. However, if the value passed as argument nField
  * is non-zero and the sorter is able to guarantee a stable sort, nField
@@ -844,16 +846,12 @@ vdbeSorterCompare(SortSubtask * pTask,	/* Subtask context (for pKeyInfo) */
  */
 int
 sqlite3VdbeSorterInit(sqlite3 * db,	/* Database connection (for malloc()) */
-		      int nField,	/* Number of key fields in each record */
 		      VdbeCursor * pCsr	/* Cursor that holds the new sorter */
     )
 {
 	int pgsz;		/* Page size of main database */
 	int i;			/* Used to iterate through aTask[] */
 	VdbeSorter *pSorter;	/* The new sorter */
-	KeyInfo *pKeyInfo;	/* Copy of pCsr->pKeyInfo with db==0 */
-	int szKeyInfo;		/* Size of pCsr->pKeyInfo in bytes */
-	int sz;			/* Size of pSorter in bytes */
 	int rc = SQLITE_OK;
 #if SQLITE_MAX_WORKER_THREADS==0
 #define nWorker 0
@@ -875,25 +873,15 @@ sqlite3VdbeSorterInit(sqlite3 * db,	/* Database connection (for malloc()) */
 	}
 #endif
 
-	assert(pCsr->pKeyInfo);
+	assert(pCsr->key_def != NULL);
 	assert(pCsr->eCurType == CURTYPE_SORTER);
-	szKeyInfo =
-	    sizeof(KeyInfo) + (pCsr->pKeyInfo->nField - 1) * sizeof(struct coll *);
-	sz = sizeof(VdbeSorter) + nWorker * sizeof(SortSubtask);
 
-	pSorter = (VdbeSorter *) sqlite3DbMallocZero(db, sz + szKeyInfo);
+	pSorter = (VdbeSorter *) sqlite3DbMallocZero(db, sizeof(VdbeSorter));
 	pCsr->uc.pSorter = pSorter;
 	if (pSorter == 0) {
 		rc = SQLITE_NOMEM_BKPT;
 	} else {
-		pSorter->pKeyInfo = pKeyInfo =
-		    (KeyInfo *) ((u8 *) pSorter + sz);
-		memcpy(pKeyInfo, pCsr->pKeyInfo, szKeyInfo);
-		pKeyInfo->db = 0;
-		if (nField && nWorker == 0) {
-			pKeyInfo->nXField += (pKeyInfo->nField - nField);
-			pKeyInfo->nField = nField;
-		}
+		pSorter->key_def = pCsr->key_def;
 		pSorter->pgsz = pgsz = 1024;
 		pSorter->nTask = nWorker + 1;
 		pSorter->iPrev = (u8) (nWorker - 1);
@@ -929,8 +917,8 @@ sqlite3VdbeSorterInit(sqlite3 * db,	/* Database connection (for malloc()) */
 			}
 		}
 
-		if ((pKeyInfo->nField + pKeyInfo->nXField) < 13
-		    && (pKeyInfo->aColl[0] == NULL)) {
+		if (pCsr->key_def->part_count < 13
+		    && (pCsr->key_def->parts[0].coll == NULL)) {
 			pSorter->typeMask =
 			    SORTER_TYPE_INTEGER | SORTER_TYPE_TEXT;
 		}
@@ -1280,10 +1268,11 @@ vdbeSortAllocUnpacked(SortSubtask * pTask)
 {
 	if (pTask->pUnpacked == 0) {
 		pTask->pUnpacked =
-		    sqlite3VdbeAllocUnpackedRecord(pTask->pSorter->pKeyInfo);
+			sqlite3VdbeAllocUnpackedRecord(pTask->pSorter->db,
+						       pTask->pSorter->key_def);
 		if (pTask->pUnpacked == 0)
 			return SQLITE_NOMEM_BKPT;
-		pTask->pUnpacked->nField = pTask->pSorter->pKeyInfo->nField;
+		pTask->pUnpacked->nField = pTask->pSorter->key_def->part_count;
 		pTask->pUnpacked->errCode = 0;
 	}
 	return SQLITE_OK;
@@ -2824,7 +2813,6 @@ sqlite3VdbeSorterCompare(const VdbeCursor * pCsr,	/* Sorter cursor */
 {
 	VdbeSorter *pSorter;
 	UnpackedRecord *r2;
-	KeyInfo *pKeyInfo;
 	int i;
 	void *pKey;
 	int nKey;		/* Sorter key to compare pVal with */
@@ -2832,10 +2820,9 @@ sqlite3VdbeSorterCompare(const VdbeCursor * pCsr,	/* Sorter cursor */
 	assert(pCsr->eCurType == CURTYPE_SORTER);
 	pSorter = pCsr->uc.pSorter;
 	r2 = pSorter->pUnpacked;
-	pKeyInfo = pCsr->pKeyInfo;
 	if (r2 == 0) {
 		r2 = pSorter->pUnpacked =
-		    sqlite3VdbeAllocUnpackedRecord(pKeyInfo);
+			sqlite3VdbeAllocUnpackedRecord(pSorter->db,  pCsr->key_def);
 		if (r2 == 0)
 			return SQLITE_NOMEM_BKPT;
 		r2->nField = nKeyCol;
@@ -2843,7 +2830,8 @@ sqlite3VdbeSorterCompare(const VdbeCursor * pCsr,	/* Sorter cursor */
 	assert(r2->nField == nKeyCol);
 
 	pKey = vdbeSorterRowkey(pSorter, &nKey);
-	sqlite3VdbeRecordUnpackMsgpack(pKeyInfo, nKey, pKey, r2);
+	sqlite3VdbeRecordUnpackMsgpack(pSorter->db, pCsr->key_def,
+				       nKey, pKey, r2);
 	for (i = 0; i < nKeyCol; i++) {
 		if (r2->aMem[i].flags & MEM_Null) {
 			*pRes = -1;
@@ -2851,6 +2839,6 @@ sqlite3VdbeSorterCompare(const VdbeCursor * pCsr,	/* Sorter cursor */
 		}
 	}
 
-	*pRes = sqlite3VdbeRecordCompareMsgpack(pVal->n, pVal->z, r2);
+	*pRes = sqlite3VdbeRecordCompareMsgpack(pSorter->db, pVal->n, pVal->z, r2);
 	return SQLITE_OK;
 }
diff --git a/src/box/sql/where.c b/src/box/sql/where.c
index bad964a..fb40f76 100644
--- a/src/box/sql/where.c
+++ b/src/box/sql/where.c
@@ -812,7 +812,7 @@ constructAutomaticIndex(Parse * pParse,			/* The parsing context */
 	assert(pLevel->iIdxCur >= 0);
 	pLevel->iIdxCur = pParse->nTab++;
 	sqlite3VdbeAddOp2(v, OP_OpenAutoindex, pLevel->iIdxCur, nKeyCol + 1);
-	sqlite3VdbeSetP4KeyInfo(pParse, pIdx);
+	sql_vdbe_set_p4_key_def(pParse, pIdx);
 	VdbeComment((v, "for %s", pTable->zName));
 
 	/* Fill the automatic index with content */
@@ -971,9 +971,9 @@ whereKeyStats(Parse * pParse,	/* Database connection */
 		}
 
 		pRec->nField = n;
-		res =
-		    sqlite3VdbeRecordCompareMsgpack(aSample[iSamp].n,
-						    aSample[iSamp].p, pRec);
+		res = sqlite3VdbeRecordCompareMsgpack(pParse->db,
+						      aSample[iSamp].n,
+						      aSample[iSamp].p, pRec);
 		if (res < 0) {
 			iLower =
 			    aSample[iSamp].anLt[n - 1] + aSample[iSamp].anEq[n -
@@ -1002,7 +1002,8 @@ whereKeyStats(Parse * pParse,	/* Database connection */
 			assert(iCol == nField - 1);
 			pRec->nField = nField;
 			assert(0 ==
-			       sqlite3VdbeRecordCompareMsgpack(aSample[i].n,
+			       sqlite3VdbeRecordCompareMsgpack(pParse->db,
+							       aSample[i].n,
 							       aSample[i].p,
 							       pRec)
 			       || pParse->db->mallocFailed);
@@ -1014,7 +1015,8 @@ whereKeyStats(Parse * pParse,	/* Database connection */
 			assert(i <= pIdx->nSample && i >= 0);
 			pRec->nField = iCol + 1;
 			assert(i == pIdx->nSample
-			       || sqlite3VdbeRecordCompareMsgpack(aSample[i].n,
+			       || sqlite3VdbeRecordCompareMsgpack(pParse->db,
+								  aSample[i].n,
 								  aSample[i].p,
 								  pRec) > 0
 			       || pParse->db->mallocFailed);
@@ -1027,13 +1029,14 @@ whereKeyStats(Parse * pParse,	/* Database connection */
 			if (iCol > 0) {
 				pRec->nField = iCol;
 				assert(sqlite3VdbeRecordCompareMsgpack
-				       (aSample[i].n, aSample[i].p, pRec) <= 0
+				       (pParse->db, aSample[i].n, aSample[i].p,
+					pRec) <= 0
 				       || pParse->db->mallocFailed);
 			}
 			if (i > 0) {
 				pRec->nField = nField;
 				assert(sqlite3VdbeRecordCompareMsgpack
-				       (aSample[i - 1].n, aSample[i - 1].p,
+				       (pParse->db, aSample[i - 1].n, aSample[i - 1].p,
 					pRec) < 0 || pParse->db->mallocFailed);
 			}
 		}
@@ -4571,7 +4574,7 @@ sqlite3WhereBegin(Parse * pParse,	/* The parser context */
 			assert(iIndexCur >= 0);
 			if (op) {
 				emit_open_cursor(pParse, iIndexCur, pIx->tnum);
-				sqlite3VdbeSetP4KeyInfo(pParse, pIx);
+				sql_vdbe_set_p4_key_def(pParse, pIx);
 				if ((pLoop->wsFlags & WHERE_CONSTRAINT) != 0
 				    && (pLoop->
 					wsFlags & (WHERE_COLUMN_RANGE |
diff --git a/src/box/sql/wherecode.c b/src/box/sql/wherecode.c
index 9610f76..a295578 100644
--- a/src/box/sql/wherecode.c
+++ b/src/box/sql/wherecode.c
@@ -1618,7 +1618,7 @@ sqlite3WhereCodeOneLoopStart(WhereInfo * pWInfo,	/* Complete information about t
 			regRowset = pParse->nTab++;
 			sqlite3VdbeAddOp2(v, OP_OpenTEphemeral,
 					  regRowset, nPkCol);
-			sqlite3VdbeSetP4KeyInfo(pParse, pPk);
+			sql_vdbe_set_p4_key_def(pParse, pPk);
 			regPk = ++pParse->nMem;
 		}
 		iRetInit = sqlite3VdbeAddOp2(v, OP_Integer, 0, regReturn);
diff --git a/src/box/tuple.c b/src/box/tuple.c
index df2686e..7bcd9f8 100644
--- a/src/box/tuple.c
+++ b/src/box/tuple.c
@@ -474,3 +474,12 @@ tuple_str(const struct tuple *tuple)
 		return "<failed to format tuple>";
 	return buf;
 }
+
+const char *
+mp_str(const char *data)
+{
+	char *buf = tt_static_buf();
+	if (mp_snprint(buf, TT_STATIC_BUF_LEN, data) < 0)
+		return "<failed to format message pack>";
+	return buf;
+}
diff --git a/src/box/tuple.h b/src/box/tuple.h
index 97b81cf..9a459d9 100644
--- a/src/box/tuple.h
+++ b/src/box/tuple.h
@@ -395,6 +395,15 @@ tuple_snprint(char *buf, int size, const struct tuple *tuple);
 const char *
 tuple_str(const struct tuple *tuple);
 
+/**
+ * Format msgpack into string using a static buffer.
+ * Useful for debugger. Example: [1, 2, "string"]
+ * @param msgpack to format
+ * @return formatted null-terminated string
+ */
+const char *
+mp_str(const char *data);
+
 /**
  * Get the format of the tuple.
  * @param tuple Tuple.
diff --git a/test/sql-tap/index1.test.lua b/test/sql-tap/index1.test.lua
index 201838d..c0177c8 100755
--- a/test/sql-tap/index1.test.lua
+++ b/test/sql-tap/index1.test.lua
@@ -528,7 +528,9 @@ test:do_execsql_test(
         INSERT INTO t1 VALUES(2, 2,4);
         INSERT INTO t1 VALUES(3, 3,8);
         INSERT INTO t1 VALUES(4, 1,12);
+	pragma vdbe_debug=1;
         SELECT b FROM t1 WHERE a=1 ORDER BY b;
+	pragma vdbe_debug=0;
     ]], {
         -- <index-10.0>
         2, 12
diff --git a/test/sql-tap/index4.test.lua b/test/sql-tap/index4.test.lua
index 22e5066..85d3b3c 100755
--- a/test/sql-tap/index4.test.lua
+++ b/test/sql-tap/index4.test.lua
@@ -22,6 +22,7 @@ testprefix = "index4"
 test:do_execsql_test(
     1.1,
     [[
+    pragma vdbe_debug=1;
           CREATE TABLE t1(x primary key);
         BEGIN;
           INSERT INTO t1 VALUES(randomblob(102));
diff --git a/test/sql-tap/selectA.test.lua b/test/sql-tap/selectA.test.lua
index fc482e9..fa3a025 100755
--- a/test/sql-tap/selectA.test.lua
+++ b/test/sql-tap/selectA.test.lua
@@ -1155,6 +1155,7 @@ test:do_execsql_test(
 test:do_execsql_test(
     "selectA-2.94",
     [[
+    pragma vdbe_debug=1;
         SELECT lower((SELECT c FROM t1 UNION ALL SELECT z FROM t2 ORDER BY 1));
     ]], {
         -- <selectA-2.94>
-- 
2.16.2

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

* [tarantool-patches] Re: [PATCH] sql: use collation pointers instead of names
  2018-05-08  7:56 ` [tarantool-patches] [PATCH] sql: use collation pointers instead of names Kirill Yukhin
  2018-04-17 18:06   ` [tarantool-patches] " Vladislav Shpilevoy
@ 2018-05-08  7:59   ` Kirill Yukhin
  1 sibling, 0 replies; 17+ messages in thread
From: Kirill Yukhin @ 2018-05-08  7:59 UTC (permalink / raw)
  To: v.shpilevoy; +Cc: tarantool-patches

On 08 мая 10:56, Kirill Yukhin wrote:
> Hello Vlad,
> I've fixed all your inputs, thanks!

Disregard, please.

--
Regards, Kirill Yukhin

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

* [tarantool-patches] Re: [PATCH 1/2] sql: introduce sort order to key_part/key_part_def
  2018-05-08  7:56 ` [tarantool-patches] [PATCH 1/2] sql: introduce sort order to key_part/key_part_def Kirill Yukhin
@ 2018-05-08 16:02   ` Vladislav Shpilevoy
  2018-05-10 13:01     ` Kirill Yukhin
  0 siblings, 1 reply; 17+ messages in thread
From: Vladislav Shpilevoy @ 2018-05-08 16:02 UTC (permalink / raw)
  To: Kirill Yukhin; +Cc: tarantool-patches

Hello. Thanks for contributing! See 2 comments below.

1. I still can grep SQLITE_SO_ASC/DESC in build.c

On 08/05/2018 10:56, Kirill Yukhin wrote:
> Legacy SQL DD structs contained sort_order, defined per
> index column. During integration, those structs are to be
> vanished. So, introduce new field to part entity of Tarantool.
> This field states for sorting order of given part in give index.
> This field is ignored by Tarantool everywhere excpept for
> some of nested queries in SQL.
> 
> Patch also replaces usages of SQL's stored sort order w/ this new
> field.
> 
> Part of #3235
> ---
>   src/box/key_def.cc      | 28 ++++++++++++++++++++++------
>   src/box/key_def.h       | 16 +++++++++++++++-
>   src/box/schema.cc       | 30 ++++++++++++++++++++----------
>   src/box/sql.c           | 11 +++++++++--
>   src/box/sql/build.c     | 47 ++++++++++++++++++++++++++++++++++++-----------
>   src/box/sql/expr.c      |  9 ++++-----
>   src/box/sql/insert.c    |  3 ++-
>   src/box/sql/parse.y     | 10 +++++-----
>   src/box/sql/pragma.c    |  7 ++++---
>   src/box/sql/select.c    |  2 +-
>   src/box/sql/sqliteInt.h | 20 ++++++++++++--------
>   src/box/sql/vdbe.h      |  1 +
>   src/box/sql/where.c     | 11 ++++++-----
>   src/box/sql/wherecode.c | 10 ++++++----
>   14 files changed, 143 insertions(+), 62 deletions(-)
> 
> diff --git a/src/box/sql/sqliteInt.h b/src/box/sql/sqliteInt.h
> index 8bb45c9..a811932 100644
> --- a/src/box/sql/sqliteInt.h
> +++ b/src/box/sql/sqliteInt.h
> @@ -3540,6 +3534,16 @@ sql_default_coll();
>   bool
>   space_is_view(Table *);
>   
> +/**
> + * Return name of given column collation from index.

2. Irrelevant comment.

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

* [tarantool-patches] Re: [PATCH 2/2] sql: replace KeyInfo with key_def
  2018-05-08  7:56 ` [tarantool-patches] [PATCH 2/2] sql: replace KeyInfo with key_def Kirill Yukhin
@ 2018-05-08 16:02   ` Vladislav Shpilevoy
  2018-05-10 12:59     ` Kirill Yukhin
  0 siblings, 1 reply; 17+ messages in thread
From: Vladislav Shpilevoy @ 2018-05-08 16:02 UTC (permalink / raw)
  To: tarantool-patches, Kirill Yukhin

Hello. Thanks for contribution! See 38 comments below.

On 08/05/2018 10:56, Kirill Yukhin wrote:
> KeyInfo is a legacy struct which was heavily used in SQL
> front-end. This patch replaces all its usages w/ Tarantool's
> natural key description structure called key_def.
> 
> This change is a prt of data dictionary intagration effort:

1. prt?
2. intagration?

> Tarantool indexes don't aware of KeyInfo, that is why it was
> evicted.
> 
> Legacy KeyInfo memory handling was ref-counting based and now
> is replaced w/ memory duplication. This state of affairs should
> be improved in future.
> 
> Part of #3235
> ---
>   src/box/sql.c                 |  19 ++-
>   src/box/sql/analyze.c         |   6 +-
>   src/box/sql/build.c           |  79 +++------
>   src/box/sql/delete.c          |   6 +-
>   src/box/sql/expr.c            |  52 +++---
>   src/box/sql/fkey.c            |   2 +-
>   src/box/sql/insert.c          |  12 +-
>   src/box/sql/pragma.c          |   4 +-
>   src/box/sql/select.c          | 381 ++++++++++++++++++------------------------
>   src/box/sql/sqliteInt.h       |  50 ++----
>   src/box/sql/tarantoolInt.h    |   5 +-
>   src/box/sql/update.c          |   6 +-
>   src/box/sql/vdbe.c            |  80 +++++----
>   src/box/sql/vdbe.h            |  34 ++--
>   src/box/sql/vdbeInt.h         |  13 +-
>   src/box/sql/vdbeapi.c         | 183 --------------------
>   src/box/sql/vdbeaux.c         | 247 ++++++++++-----------------
>   src/box/sql/vdbemem.c         |  14 +-
>   src/box/sql/vdbesort.c        |  54 +++---
>   src/box/sql/where.c           |  21 ++-
>   src/box/sql/wherecode.c       |   2 +-
>   src/box/tuple.c               |   9 +
>   src/box/tuple.h               |   9 +
>   test/sql-tap/index1.test.lua  |   2 +
>   test/sql-tap/index4.test.lua  |   1 +
>   test/sql-tap/selectA.test.lua |   1 +
>   26 files changed, 496 insertions(+), 796 deletions(-)
> 
> diff --git a/src/box/sql.c b/src/box/sql.c
> index 838fcf6..42372ea 100644
> --- a/src/box/sql.c
> +++ b/src/box/sql.c
> @@ -371,7 +371,7 @@ int tarantoolSqlite3Count(BtCursor *pCur, i64 *pnEntry)
>    * @retval SQLITE_OK on success, SQLITE_TARANTOOL_ERROR otherwise.
>    */
>   int tarantoolSqlite3EphemeralCreate(BtCursor *pCur, uint32_t field_count,
> -				    struct coll *aColl)
> +				    struct key_def *def)

3. Please, update the comment as well.
> @@ -380,11 +380,16 @@ int tarantoolSqlite3EphemeralCreate(BtCursor *pCur, uint32_t field_count,
>   	if (ephemer_key_def == NULL)
>   		return SQL_TARANTOOL_ERROR;
>   	for (uint32_t part = 0; part < field_count; ++part) {
> +		struct coll *coll;
> +		if (part < def->part_count)
> +			coll = def->parts[part].coll;
> +		else
> +			coll = NULL;
>   		key_def_set_part(ephemer_key_def, part /* part no */,
>   				 part /* filed no */,
>   				 FIELD_TYPE_SCALAR,
>   				 ON_CONFLICT_ACTION_NONE /* nullable_action */,
> -				 aColl /* coll */,
> +				 coll /* coll */,

4. Lets remove these annotations for each argument. It makes no sense.
> @@ -933,7 +938,8 @@ rename_fail:
>    * Performs exactly as extract_key + sqlite3VdbeCompareMsgpack,
>    * only faster.
>    */
> -int tarantoolSqlite3IdxKeyCompare(BtCursor *pCur, UnpackedRecord *pUnpacked,
> +int tarantoolSqlite3IdxKeyCompare(struct sqlite3 *db,
> +				  BtCursor *pCur, UnpackedRecord *pUnpacked,
>   				  int *res)

5. I see, that db is used only to store it in Mem.db in sqlite3VdbeCompareMsgpack.
This mem is then passed to vdbeCompareMemString, which do not use db. So this
argument can be removed as well as res. You can simply return comparison result.

Same about sqlite3VdbeRecordCompareMsgpack.
> diff --git a/src/box/sql/build.c b/src/box/sql/build.c
> index ae662fb..f9b54ef 100644
> --- a/src/box/sql/build.c
> +++ b/src/box/sql/build.c
> @@ -2643,14 +2650,14 @@ sqlite3RefillIndex(Parse * pParse, Index * pIndex, int memRootPage)
>   	} else {
>   		tnum = pIndex->tnum;
>   	}
> -	pKey = sqlite3KeyInfoOfIndex(pParse, db, pIndex);
> -	assert(pKey != 0 || db->mallocFailed || pParse->nErr);
> +	struct key_def *def = sql_index_key_def(pIndex);
> +	assert(def != NULL || db->mallocFailed || pParse->nErr);
>   
>   	/* Open the sorter cursor if we are to use one. */
>   	iSorter = pParse->nTab++;
>   	sqlite3VdbeAddOp4(v, OP_SorterOpen, iSorter, 0, pIndex->nColumn,
> -			  (char *)
> -			  sqlite3KeyInfoRef(pKey), P4_KEYINFO);
> +			  (char *)def, P4_KEYDEF);
> +	assert(0);

6. Please, use unreachable() macro.
> diff --git a/src/box/sql/select.c b/src/box/sql/select.c
> index aff534d3..6729eef 100644
> --- a/src/box/sql/select.c
> +++ b/src/box/sql/select.c
> @@ -562,12 +562,11 @@ sqliteProcessJoin(Parse * pParse, Select * p)
>   	return 0;
>   }
>   
> -/* Forward reference */
> -static KeyInfo *
> -keyInfoFromExprList(Parse * pParse,	/* Parsing context */
> -		    ExprList * pList,	/* Form the KeyInfo object from this ExprList */
> -		    int iStart,		/* Begin with this column of pList */
> -		    int nExtra);	/* Add this many extra columns to the end */
> +static struct key_def *
> +sql_expr_list_to_key_def(struct Parse *parse,
> +			 struct ExprList *list,
> +			 int start);

7. It would be good to see the comment on the first function declaration.
> -/*
> - * Given an expression list, generate a KeyInfo structure that records
> +/**
> + * Given an expression list, generate a key_defd structure that records

8. key_defd -> key_def.

9. Please, wrap the comment on 66 symbols.

>    * the collating sequence for each expression in that expression list.
>    *
>    * If the ExprList is an ORDER BY or GROUP BY clause then the resulting
> - * KeyInfo structure is appropriate for initializing a virtual index to
> + * key_def structure is appropriate for initializing a virtual index to
>    * implement that clause.  If the ExprList is the result set of a SELECT
> - * then the KeyInfo structure is appropriate for initializing a virtual
> + * then the key_info structure is appropriate for initializing a virtual
>    * index to implement a DISTINCT test.
>    *
> - * Space to hold the KeyInfo structure is obtained from malloc.  The calling
> - * function is responsible for seeing that this structure is eventually
> - * freed.
> + * Space to hold the key_info structure is obtained from malloc.

10. Leading white space.

> + * The calling function is responsible for seeing that this
> + * structure is eventually freed.
> + *
> + * @param Parsing context.
> + * @param Expression list.
> + * @param No of leading parts to skip.
> + *
> + * @retval Allocated key_def, NULL in case of OOM.

11. Please, specify parameter names.

>    */
> -static KeyInfo *
> -keyInfoFromExprList(Parse * pParse,	/* Parsing context */
> -		    ExprList * pList,	/* Form the KeyInfo object from this ExprList */
> -		    int iStart,		/* Begin with this column of pList */
> -		    int nExtra)		/* Add this many extra columns to the end */
> +static struct key_def *
> +sql_expr_list_to_key_def(struct Parse *parse,
> +			 struct ExprList *list,
> +			 int start)

12. Lets put arguments into the line.

>   {
> -	int nExpr;
> -	KeyInfo *pInfo;
> -	struct ExprList_item *pItem;
> -	sqlite3 *db = pParse->db;
> -	int i;
> -
> -	nExpr = pList->nExpr;
> -	pInfo = sqlite3KeyInfoAlloc(db, nExpr - iStart, nExtra + 1);
> -	if (pInfo) {
> -		assert(sqlite3KeyInfoIsWriteable(pInfo));
> -		for (i = iStart, pItem = pList->a + iStart; i < nExpr;
> -		     i++, pItem++) {
> +	struct key_def *def;
> +	int expr_count = list->nExpr;
> +	def = key_def_new(expr_count);

13. How about struct key_def *def = key_def_new(expr_count); ?

> +	if (def != NULL) {
> +		int i;
> +		struct ExprList_item *item;
> +		for (i = start, item = list->a + start; i < expr_count;

14. How about 'for (int i...' instead of 'int i; for (...' ?

> +		     ++i, ++item) {
>   			bool unused;
> -			pInfo->aColl[i - iStart] =
> -				sql_expr_coll(pParse, pItem->pExpr, &unused);
> -			pInfo->aSortOrder[i - iStart] = pItem->sortOrder;
> +			struct coll *coll = sql_expr_coll(parse, item->pExpr,
> +							  &unused);
> +			enum sort_order sort_order = item->sortOrder;

15. How about inline it into the call below?

16. Please, set db->mallocFailed if key_def_new returns NULL.

> @@ -2118,54 +2058,62 @@ multiSelectCollSeq(Parse * pParse, Select * p, int iCol, bool *is_found)
>   	return coll;
>   }
>   
> -/*
> +/**
>    * The select statement passed as the second parameter is a compound SELECT
> - * with an ORDER BY clause. This function allocates and returns a KeyInfo
> + * with an ORDER BY clause. This function allocates and returns a key_def
>    * structure suitable for implementing the ORDER BY.
>    *
> - * Space to hold the KeyInfo structure is obtained from malloc. The calling
> + * Space to hold the key_def structure is obtained from malloc. The calling
>    * function is responsible for ensuring that this structure is eventually
>    * freed.
> + *
> + * @param Parsing context.
> + * @param Select struct to analyze.
> + * @param No of extra slots to allocate.
> + *
> + * @retval Allocated key_def, NULL in case of OOM.
>    */
> -static KeyInfo *
> -multiSelectOrderByKeyInfo(Parse * pParse, Select * p, int nExtra)
> +static struct key_def *
> +sql_multiselect_orderby_to_key_def(struct Parse *parse,
> +				   struct Select *s,
> +				   int extra)

17. All the same about this function.

>   {
> -	ExprList *pOrderBy = p->pOrderBy;
> -	int nOrderBy = p->pOrderBy->nExpr;
> -	sqlite3 *db = pParse->db;
> -	KeyInfo *pRet = sqlite3KeyInfoAlloc(db, nOrderBy + nExtra, 1);
> -	if (pRet) {
> -		int i;
> -		for (i = 0; i < nOrderBy; i++) {
> -			struct ExprList_item *pItem = &pOrderBy->a[i];
> -			Expr *pTerm = pItem->pExpr;
> +	int ob_count = s->pOrderBy->nExpr;
> +	struct key_def *key_def = key_def_new(ob_count + extra);
> +	if (key_def != NULL) {

18. Lets return if key_def == NULL and reduce indentation level. The code
will be more compact.
> @@ -2475,9 +2424,10 @@ multiSelect(Parse * pParse,	/* Parsing context */
>   	if (dest.eDest == SRT_EphemTab) {
>   		assert(p->pEList);
>   		int nCols = p->pEList->nExpr;
> -		KeyInfo *pKeyInfo = sqlite3KeyInfoAlloc(pParse->db, nCols + 1, 0);
> +		struct key_def *def;
> +		def = key_def_new(nCols + 1);

19. Lets avoid this style: 'declare, new line, assign'. Here it is ok to do
struct key_def *def = key_def_new(nCols + 1);
> @@ -2793,26 +2743,28 @@ multiSelect(Parse * pParse,	/* Parsing context */
>   	 * no temp tables are required.
>   	 */
>   	if (p->selFlags & SF_UsesEphemeral) {
> -		int i;		/* Loop counter */
> -		KeyInfo *pKeyInfo;	/* Collating sequence for the result set */
> -		Select *pLoop;	/* For looping through SELECT statements */
> -		struct coll **apColl;	/* For looping through pKeyInfo->aColl[] */
> -		int nCol;	/* Number of columns in result set */
> +		int nCol;
> +		struct key_def *key_def;

20. Same.
> @@ -2823,13 +2775,12 @@ multiSelect(Parse * pParse,	/* Parsing context */
>   				}
>   				sqlite3VdbeChangeP2(v, addr, nCol);
>   				sqlite3VdbeChangeP4(v, addr,
> -						    (char *)
> -						    sqlite3KeyInfoRef(pKeyInfo),
> -						    P4_KEYINFO);
> +						    (char *)key_def_dup(key_def),

21. What if dup is failed?
> @@ -2871,7 +2822,7 @@ sqlite3SelectWrongNumTermsError(Parse * pParse, Select * p)
>    * If regPrev>0 then it is the first register in a vector that
>    * records the previous output.  mem[regPrev] is a flag that is false
>    * if there has been no previous output.  If regPrev>0 then code is
> - * generated to suppress duplicates.  pKeyInfo is used for comparing
> + * generated to suppress duplicates.  def is used for comparing

22. Lets rewrite this comment in Tarantool style.
> @@ -2884,7 +2835,7 @@ generateOutputSubroutine(Parse * pParse,	/* Parsing context */
>   			 SelectDest * pDest,	/* Where to send the data */
>   			 int regReturn,		/* The return address register */
>   			 int regPrev,		/* Previous result register.  No uniqueness if 0 */
> -			 KeyInfo * pKeyInfo,	/* For comparing with previous entry */
> +			 struct key_def *def,	/* For comparing with previous entry */

23. And remove these annotations. And def here can be const key_def *.
> @@ -3145,7 +3098,8 @@ multiSelectOrderBy(Parse * pParse,	/* Parsing context */
>   	int iSub2;		/* EQP id of right-hand query */
>   
>   	assert(p->pOrderBy != 0);
> -	assert(pKeyDup == 0);	/* "Managed" code needs this.  Ticket #3382. */
> +	/* "Managed" code needs this.  Ticket #3382. */
> +	assert(def_dup == NULL);

24. This assertion makes no sense - obviously dup here is NULL, it is declared
as NULL few lines above.
> @@ -3216,27 +3170,30 @@ multiSelectOrderBy(Parse * pParse,	/* Parsing context */
>   	p->pOrderBy = pOrderBy;
>   	pPrior->pOrderBy = sqlite3ExprListDup(pParse->db, pOrderBy, 0);
>   
> -	/* Allocate a range of temporary registers and the KeyInfo needed
> +	/* Allocate a range of temporary registers and the key_def needed
>   	 * for the logic that removes duplicate result rows when the
>   	 * operator is UNION, EXCEPT, or INTERSECT (but not UNION ALL).
>   	 */
>   	if (op == TK_ALL) {
>   		regPrev = 0;
>   	} else {
> -		int nExpr = p->pEList->nExpr;
> -		assert(nOrderBy >= nExpr || db->mallocFailed);
> +		int expr_count = p->pEList->nExpr;
> +		assert(nOrderBy >= expr_count || db->mallocFailed);
>   		regPrev = pParse->nMem + 1;
> -		pParse->nMem += nExpr + 1;
> +		pParse->nMem += expr_count + 1;
>   		sqlite3VdbeAddOp2(v, OP_Integer, 0, regPrev);
> -		pKeyDup = sqlite3KeyInfoAlloc(db, nExpr, 1);
> -		if (pKeyDup) {
> -			assert(sqlite3KeyInfoIsWriteable(pKeyDup));
> -			for (i = 0; i < nExpr; i++) {
> +		def_dup = key_def_new(expr_count);

25. I do not see, where do you free it. So this key_def leaks. In two
functions below (generateOutputSubroutine) it is used only to create
more duplicates.
> @@ -3320,9 +3277,8 @@ multiSelectOrderBy(Parse * pParse,	/* Parsing context */
>   		VdbeNoopComment((v, "Output routine for B"));
>   		addrOutB = generateOutputSubroutine(pParse,
>   						    p, &destB, pDest, regOutB,
> -						    regPrev, pKeyDup, labelEnd);
> +						    regPrev, def_dup, labelEnd);
>   	}
> -	sqlite3KeyInfoUnref(pKeyDup);

This is where def_dup leaks.
> @@ -5979,8 +5932,8 @@ sqlite3Select(Parse * pParse,		/* The parser context */
>   			}
>   			sqlite3VdbeAddOp4(v, OP_Compare, iAMem, iBMem,
>   					  pGroupBy->nExpr,
> -					  (char *)sqlite3KeyInfoRef(pKeyInfo),
> -					  P4_KEYINFO);
> +					  (char*) key_def_dup(def),
> +					  P4_KEYDEF);

26. What if key_def_dup returned NULL? db->mallocFailed is not set. Or you can
reuse diag and set here pParse->rc into TARANTOOL_ERROR. In other places too.

> diff --git a/src/box/sql/sqliteInt.h b/src/box/sql/sqliteInt.h
> index a811932..7d32556 100644
> --- a/src/box/sql/sqliteInt.h
> +++ b/src/box/sql/sqliteInt.h
> @@ -2081,7 +2062,8 @@ struct KeyInfo {
>    * b-tree.
>    */
>   struct UnpackedRecord {
> -	KeyInfo *pKeyInfo;	/* Collation and sort-order information */
> +	/* Collation and sort-order information */

27. Please, put a dot at the end of sentence. And use /** prefix for
comments out of function body.
> @@ -3529,11 +3511,18 @@ const char *
>   index_collation_name(Index *, uint32_t);
>   struct coll *
>   sql_index_collation(Index *idx, uint32_t column);
> -struct coll *
> -sql_default_coll();
>   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`

28. Please, say here that this function makes duplicate of original key_def,
and can return NULL.
> diff --git a/src/box/sql/update.c b/src/box/sql/update.c
> index f3bd0b7..d380b8c 100644
> --- a/src/box/sql/update.c
> +++ b/src/box/sql/update.c
> @@ -358,12 +358,12 @@ sqlite3Update(Parse * pParse,		/* The parser context */
>   	sqlite3VdbeAddOp2(v, OP_Null, 0, iPk);
>   
>   	if (isView) {
> -		KeyInfo *pKeyInfo = sqlite3KeyInfoAlloc(pParse->db, nKey, 0);
> +		struct key_def *def = key_def_new(nKey);

29. Here and in other places alloc() == NULL was ignored, because it set
db->mallocFailed. With key_def we can not ignore OOM.
> diff --git a/src/box/sql/vdbe.h b/src/box/sql/vdbe.h
> index e244606..4bd48d5 100644
> --- a/src/box/sql/vdbe.h
> +++ b/src/box/sql/vdbe.h
> @@ -257,14 +265,20 @@ char *sqlite3VdbeExpandSql(Vdbe *, const char *);
>   #endif
>   int sqlite3MemCompare(const Mem *, const Mem *, const struct coll *);
>   
> -void sqlite3VdbeRecordUnpackMsgpack(KeyInfo *, int, const void *,
> -				    UnpackedRecord *);
> -int sqlite3VdbeRecordCompare(int, const void *, UnpackedRecord *);
> -int sqlite3VdbeRecordCompareWithSkip(int, const void *, UnpackedRecord *, int);
> -UnpackedRecord *sqlite3VdbeAllocUnpackedRecord(KeyInfo *);
> +void sqlite3VdbeRecordUnpackMsgpack(struct sqlite3 *db,
> +				    struct key_def *key_def,
> +				    int key_count, const void * msgpack,
> +				    UnpackedRecord *dest);
> +int sqlite3VdbeRecordCompare(struct sqlite3 *db, int key_count,
> +			     const void *key1, UnpackedRecord *key2);
> +int sqlite3VdbeRecordCompareWithSkip(struct sqlite3 *db,
> +				     int key_count, const void *key1,
> +				     struct UnpackedRecord *key2, bool is_skip);

30. Db argument here is not needed. CompareWithSkip allocs nothing.

> +UnpackedRecord *sqlite3VdbeAllocUnpackedRecord(struct sqlite3 *,
> +					       struct key_def *);
>   int sql_vdbe_mem_alloc_region(Mem *, uint32_t);
>   
> -typedef int (*RecordCompare) (int, const void *, UnpackedRecord *);
> +typedef int (*RecordCompare) (struct sqlite3 *db, int, const void *, UnpackedRecord *);

31. Same.
> diff --git a/src/box/sql/vdbeaux.c b/src/box/sql/vdbeaux.c
> index b3998ea..c2352e7 100644
> --- a/src/box/sql/vdbeaux.c
> +++ b/src/box/sql/vdbeaux.c
> @@ -948,11 +948,9 @@ freeP4(sqlite3 * db, int p4type, void *p4)
>   			sqlite3DbFree(db, p4);
>   			break;
>   		}
> -	case P4_KEYINFO:{
> -			if (db->pnBytesFreed == 0)
> -				sqlite3KeyInfoUnref((KeyInfo *) p4);
> -			break;
> -		}
> +	case P4_KEYDEF:
> +		free(p4);

32. Please use key_def_delete. It will be hard to find all free() and
replace them to key_def_delete, when key_def will consists of more than
one memory blocks.

In other places too.
>   #ifdef SQLITE_ENABLE_EXPLAIN_COMMENTS
> @@ -1512,26 +1509,28 @@ displayP4(Op * pOp, char *zTemp, int nTemp)
>   	assert(nTemp >= 20);
>   	sqlite3StrAccumInit(&x, 0, zTemp, nTemp, 0);
>   	switch (pOp->p4type) {
> -	case P4_KEYINFO:{
> -			int j;
> -			KeyInfo *pKeyInfo;
> +	case P4_KEYDEF:{
> +			struct key_def *def;
>   
> -			if (pOp->p4.pKeyInfo == NULL) {
> +			if (pOp->p4.key_def == NULL) {
>   				sqlite3XPrintf(&x, "k[NULL]");
>   			} else {
> -				pKeyInfo = pOp->p4.pKeyInfo;
> -				assert(pKeyInfo->aSortOrder != 0);
> -				sqlite3XPrintf(&x, "k(%d", pKeyInfo->nField);
> -				for (j = 0; j < pKeyInfo->nField; j++) {
> -					struct coll *pColl = pKeyInfo->aColl[j];
> -					const char *zColl =
> -					    pColl ? pColl->name : "";
> -					if (strcmp(zColl, "BINARY") == 0)
> -						zColl = "B";
> +				def = pOp->p4.key_def;
> +				sqlite3XPrintf(&x, "k(%d", def->part_count);
> +				for (int j = 0; j < (int)def->part_count; j++) {
> +					struct coll *coll = def->parts[j].coll;
> +					const char *coll_str =
> +					    coll != NULL ? coll->name : "";
> +					if (strcmp(coll_str, "BINARY") == 0)
> +						coll_str = "B";
> +					const char *sort_order = "";
> +					if (def->parts[j].sort_order ==
> +					    SORT_ORDER_DESC) {
> +						sort_order = "-";
> +					}
>   					sqlite3XPrintf(&x, ",%s%s",
> -						       pKeyInfo->
> -						       aSortOrder[j] ? "-" : "",
> -						       zColl);
> +						       sort_order,
> +						       coll_str);

33. How about to move it into key_def.h/.cc and name key_def_str() or something?
> @@ -3545,7 +3543,8 @@ sql_vdbe_mem_alloc_region(Mem *vdbe_mem, uint32_t size)
>    * Return false if there is a disagreement.
>    */
>   static int
> -vdbeRecordCompareDebug(int nKey1, const void *pKey1,	/* Left key */
> +vdbeRecordCompareDebug(struct sqlite3 *db,
> +		       int nKey1, const void *pKey1,	/* Left key */
>   		       const UnpackedRecord * pPKey2,	/* Right key */
>   		       int desiredResult)		/* Correct answer */
>   {

34. It does not require db - this function and all nested ones do not use it
for anything except useless saving into struct Mem on stack.
> @@ -4286,68 +4279,6 @@ vdbeFreeUnpacked(sqlite3 * db, UnpackedRecord * p)
>   }
>   #endif				/* SQLITE_ENABLE_PREUPDATE_HOOK */
>   
> -#ifdef SQLITE_ENABLE_PREUPDATE_HOOK
> -/*
> - * Invoke the pre-update hook. If this is an UPDATE or DELETE pre-update call,
> - * then cursor passed as the second argument should point to the row about
> - * to be update or deleted. If the application calls sqlite3_preupdate_old(),
> - * the required value will be read from the row the cursor points to.
> - */
> -void
> -sqlite3VdbePreUpdateHook(Vdbe * v,		/* Vdbe pre-update hook is invoked by */

35. This function still is declared in vdbeInt.h.
> diff --git a/test/sql-tap/index1.test.lua b/test/sql-tap/index1.test.lua
> index 201838d..c0177c8 100755
> --- a/test/sql-tap/index1.test.lua
> +++ b/test/sql-tap/index1.test.lua
> @@ -528,7 +528,9 @@ test:do_execsql_test(
>           INSERT INTO t1 VALUES(2, 2,4);
>           INSERT INTO t1 VALUES(3, 3,8);
>           INSERT INTO t1 VALUES(4, 1,12);
> +	pragma vdbe_debug=1;
>           SELECT b FROM t1 WHERE a=1 ORDER BY b;
> +	pragma vdbe_debug=0;

36. Why?
> diff --git a/test/sql-tap/index4.test.lua b/test/sql-tap/index4.test.lua
> index 22e5066..85d3b3c 100755
> --- a/test/sql-tap/index4.test.lua
> +++ b/test/sql-tap/index4.test.lua
> @@ -22,6 +22,7 @@ testprefix = "index4"
>   test:do_execsql_test(
>       1.1,
>       [[
> +    pragma vdbe_debug=1;

37. Same.
> diff --git a/test/sql-tap/selectA.test.lua b/test/sql-tap/selectA.test.lua
> index fc482e9..fa3a025 100755
> --- a/test/sql-tap/selectA.test.lua
> +++ b/test/sql-tap/selectA.test.lua
> @@ -1155,6 +1155,7 @@ test:do_execsql_test(
>   test:do_execsql_test(
>       "selectA-2.94",
>       [[
> +    pragma vdbe_debug=1;

38. Same.

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

* [tarantool-patches] Re: [PATCH 2/2] sql: replace KeyInfo with key_def
  2018-05-08 16:02   ` [tarantool-patches] " Vladislav Shpilevoy
@ 2018-05-10 12:59     ` Kirill Yukhin
  2018-05-11 11:22       ` Vladislav Shpilevoy
  0 siblings, 1 reply; 17+ messages in thread
From: Kirill Yukhin @ 2018-05-10 12:59 UTC (permalink / raw)
  To: Vladislav Shpilevoy; +Cc: tarantool-patches

Hello Vlad,
Thanks for detailed review. My answers inlined.
Updated patch in the bottom.

On 08 мая 19:02, Vladislav Shpilevoy wrote:
> Hello. Thanks for contribution! See 38 comments below.
> 
> On 08/05/2018 10:56, Kirill Yukhin wrote:
> > KeyInfo is a legacy struct which was heavily used in SQL
> > front-end. This patch replaces all its usages w/ Tarantool's
> > natural key description structure called key_def.
> > 
> > This change is a prt of data dictionary intagration effort:
> 
> 1. prt?
> 2. intagration?
Both typos fixed.

> > diff --git a/src/box/sql.c b/src/box/sql.c
> > index 838fcf6..42372ea 100644
> > --- a/src/box/sql.c
> > +++ b/src/box/sql.c
> > @@ -371,7 +371,7 @@ int tarantoolSqlite3Count(BtCursor *pCur, i64 *pnEntry)
> >    * @retval SQLITE_OK on success, SQLITE_TARANTOOL_ERROR otherwise.
> >    */
> >   int tarantoolSqlite3EphemeralCreate(BtCursor *pCur, uint32_t field_count,
> > -				    struct coll *aColl)
> > +				    struct key_def *def)
> 
> 3. Please, update the comment as well.
Fixed.

> > @@ -380,11 +380,16 @@ int tarantoolSqlite3EphemeralCreate(BtCursor *pCur, uint32_t field_count,
> >   	if (ephemer_key_def == NULL)
> >   		return SQL_TARANTOOL_ERROR;
> >   	for (uint32_t part = 0; part < field_count; ++part) {
> > +		struct coll *coll;
> > +		if (part < def->part_count)
> > +			coll = def->parts[part].coll;
> > +		else
> > +			coll = NULL;
> >   		key_def_set_part(ephemer_key_def, part /* part no */,
> >   				 part /* filed no */,
> >   				 FIELD_TYPE_SCALAR,
> >   				 ON_CONFLICT_ACTION_NONE /* nullable_action */,
> > -				 aColl /* coll */,
> > +				 coll /* coll */,
> 
> 4. Lets remove these annotations for each argument. It makes no sense.
Done.

> > @@ -933,7 +938,8 @@ rename_fail:
> >    * Performs exactly as extract_key + sqlite3VdbeCompareMsgpack,
> >    * only faster.
> >    */
> > -int tarantoolSqlite3IdxKeyCompare(BtCursor *pCur, UnpackedRecord *pUnpacked,
> > +int tarantoolSqlite3IdxKeyCompare(struct sqlite3 *db,
> > +				  BtCursor *pCur, UnpackedRecord *pUnpacked,
> >   				  int *res)
> 
> 5. I see, that db is used only to store it in Mem.db in sqlite3VdbeCompareMsgpack.
> This mem is then passed to vdbeCompareMemString, which do not use db. So this
> argument can be removed as well as res. You can simply return comparison result.
> Same about sqlite3VdbeRecordCompareMsgpack.
I've fixed this and dozen of similar cases in other functions.

> > diff --git a/src/box/sql/build.c b/src/box/sql/build.c
> > index ae662fb..f9b54ef 100644
> > --- a/src/box/sql/build.c
> > +++ b/src/box/sql/build.c
> > @@ -2643,14 +2650,14 @@ sqlite3RefillIndex(Parse * pParse, Index * pIndex, int memRootPage)
> >   	} else {
> >   		tnum = pIndex->tnum;
> >   	}
> > -	pKey = sqlite3KeyInfoOfIndex(pParse, db, pIndex);
> > -	assert(pKey != 0 || db->mallocFailed || pParse->nErr);
> > +	struct key_def *def = sql_index_key_def(pIndex);
> > +	assert(def != NULL || db->mallocFailed || pParse->nErr);
> >   	/* Open the sorter cursor if we are to use one. */
> >   	iSorter = pParse->nTab++;
> >   	sqlite3VdbeAddOp4(v, OP_SorterOpen, iSorter, 0, pIndex->nColumn,
> > -			  (char *)
> > -			  sqlite3KeyInfoRef(pKey), P4_KEYINFO);
> > +			  (char *)def, P4_KEYDEF);
> > +	assert(0);
> 
> 6. Please, use unreachable() macro.
Fixed.

> > diff --git a/src/box/sql/select.c b/src/box/sql/select.c
> > index aff534d3..6729eef 100644
> > --- a/src/box/sql/select.c
> > +++ b/src/box/sql/select.c
> > @@ -562,12 +562,11 @@ sqliteProcessJoin(Parse * pParse, Select * p)
> >   	return 0;
> >   }
> > -/* Forward reference */
> > -static KeyInfo *
> > -keyInfoFromExprList(Parse * pParse,	/* Parsing context */
> > -		    ExprList * pList,	/* Form the KeyInfo object from this ExprList */
> > -		    int iStart,		/* Begin with this column of pList */
> > -		    int nExtra);	/* Add this many extra columns to the end */
> > +static struct key_def *
> > +sql_expr_list_to_key_def(struct Parse *parse,
> > +			 struct ExprList *list,
> > +			 int start);
> 
> 7. It would be good to see the comment on the first function declaration.
Moved.

> > -/*
> > - * Given an expression list, generate a KeyInfo structure that records
> > +/**
> > + * Given an expression list, generate a key_defd structure that records
> 
> 8. key_defd -> key_def.
Fixed.

> 9. Please, wrap the comment on 66 symbols.
Done.

> >    * the collating sequence for each expression in that expression list.
> >    *
> >    * If the ExprList is an ORDER BY or GROUP BY clause then the resulting
> > - * KeyInfo structure is appropriate for initializing a virtual index to
> > + * key_def structure is appropriate for initializing a virtual index to
> >    * implement that clause.  If the ExprList is the result set of a SELECT
> > - * then the KeyInfo structure is appropriate for initializing a virtual
> > + * then the key_info structure is appropriate for initializing a virtual
> >    * index to implement a DISTINCT test.
> >    *
> > - * Space to hold the KeyInfo structure is obtained from malloc.  The calling
> > - * function is responsible for seeing that this structure is eventually
> > - * freed.
> > + * Space to hold the key_info structure is obtained from malloc.
> 
> 10. Leading white space.
Trailing white-space removed.

> > + * The calling function is responsible for seeing that this
> > + * structure is eventually freed.
> > + *
> > + * @param Parsing context.
> > + * @param Expression list.
> > + * @param No of leading parts to skip.
> > + *
> > + * @retval Allocated key_def, NULL in case of OOM.
> 
> 11. Please, specify parameter names.
Done.

> >    */
> > -static KeyInfo *
> > -keyInfoFromExprList(Parse * pParse,	/* Parsing context */
> > -		    ExprList * pList,	/* Form the KeyInfo object from this ExprList */
> > -		    int iStart,		/* Begin with this column of pList */
> > -		    int nExtra)		/* Add this many extra columns to the end */
> > +static struct key_def *
> > +sql_expr_list_to_key_def(struct Parse *parse,
> > +			 struct ExprList *list,
> > +			 int start)
> 
> 12. Lets put arguments into the line.
Done.

> >   {
> > -	int nExpr;
> > -	KeyInfo *pInfo;
> > -	struct ExprList_item *pItem;
> > -	sqlite3 *db = pParse->db;
> > -	int i;
> > -
> > -	nExpr = pList->nExpr;
> > -	pInfo = sqlite3KeyInfoAlloc(db, nExpr - iStart, nExtra + 1);
> > -	if (pInfo) {
> > -		assert(sqlite3KeyInfoIsWriteable(pInfo));
> > -		for (i = iStart, pItem = pList->a + iStart; i < nExpr;
> > -		     i++, pItem++) {
> > +	struct key_def *def;
> > +	int expr_count = list->nExpr;
> > +	def = key_def_new(expr_count);
> 
> 13. How about struct key_def *def = key_def_new(expr_count); ?
Done.

> > +	if (def != NULL) {
> > +		int i;
> > +		struct ExprList_item *item;
> > +		for (i = start, item = list->a + start; i < expr_count;
> 
> 14. How about 'for (int i...' instead of 'int i; for (...' ?
Done.

> > +		     ++i, ++item) {
> >   			bool unused;
> > -			pInfo->aColl[i - iStart] =
> > -				sql_expr_coll(pParse, pItem->pExpr, &unused);
> > -			pInfo->aSortOrder[i - iStart] = pItem->sortOrder;
> > +			struct coll *coll = sql_expr_coll(parse, item->pExpr,
> > +							  &unused);
> > +			enum sort_order sort_order = item->sortOrder;
> 
> 15. How about inline it into the call below?
Done.

> 16. Please, set db->mallocFailed if key_def_new returns NULL.
Done.

> > @@ -2118,54 +2058,62 @@ multiSelectCollSeq(Parse * pParse, Select * p, int iCol, bool *is_found)
> >   	return coll;
> >   }
> > -/*
> > +/**
> >    * The select statement passed as the second parameter is a compound SELECT
> > - * with an ORDER BY clause. This function allocates and returns a KeyInfo
> > + * with an ORDER BY clause. This function allocates and returns a key_def
> >    * structure suitable for implementing the ORDER BY.
> >    *
> > - * Space to hold the KeyInfo structure is obtained from malloc. The calling
> > + * Space to hold the key_def structure is obtained from malloc. The calling
> >    * function is responsible for ensuring that this structure is eventually
> >    * freed.
> > + *
> > + * @param Parsing context.
> > + * @param Select struct to analyze.
> > + * @param No of extra slots to allocate.
> > + *
> > + * @retval Allocated key_def, NULL in case of OOM.
> >    */
> > -static KeyInfo *
> > -multiSelectOrderByKeyInfo(Parse * pParse, Select * p, int nExtra)
> > +static struct key_def *
> > +sql_multiselect_orderby_to_key_def(struct Parse *parse,
> > +				   struct Select *s,
> > +				   int extra)
> 
> 17. All the same about this function.
Done.

> >   {
> > -	ExprList *pOrderBy = p->pOrderBy;
> > -	int nOrderBy = p->pOrderBy->nExpr;
> > -	sqlite3 *db = pParse->db;
> > -	KeyInfo *pRet = sqlite3KeyInfoAlloc(db, nOrderBy + nExtra, 1);
> > -	if (pRet) {
> > -		int i;
> > -		for (i = 0; i < nOrderBy; i++) {
> > -			struct ExprList_item *pItem = &pOrderBy->a[i];
> > -			Expr *pTerm = pItem->pExpr;
> > +	int ob_count = s->pOrderBy->nExpr;
> > +	struct key_def *key_def = key_def_new(ob_count + extra);
> > +	if (key_def != NULL) {
> 
> 18. Lets return if key_def == NULL and reduce indentation level. The code
> will be more compact.
Done.

> > @@ -2475,9 +2424,10 @@ multiSelect(Parse * pParse,	/* Parsing context */
> >   	if (dest.eDest == SRT_EphemTab) {
> >   		assert(p->pEList);
> >   		int nCols = p->pEList->nExpr;
> > -		KeyInfo *pKeyInfo = sqlite3KeyInfoAlloc(pParse->db, nCols + 1, 0);
> > +		struct key_def *def;
> > +		def = key_def_new(nCols + 1);
> 
> 19. Lets avoid this style: 'declare, new line, assign'. Here it is ok to do
> struct key_def *def = key_def_new(nCols + 1);
Done.

> > @@ -2793,26 +2743,28 @@ multiSelect(Parse * pParse,	/* Parsing context */
> >   	 * no temp tables are required.
> >   	 */
> >   	if (p->selFlags & SF_UsesEphemeral) {
> > -		int i;		/* Loop counter */
> > -		KeyInfo *pKeyInfo;	/* Collating sequence for the result set */
> > -		Select *pLoop;	/* For looping through SELECT statements */
> > -		struct coll **apColl;	/* For looping through pKeyInfo->aColl[] */
> > -		int nCol;	/* Number of columns in result set */
> > +		int nCol;
> > +		struct key_def *key_def;
> 
> 20. Same.
Done.

> > @@ -2823,13 +2775,12 @@ multiSelect(Parse * pParse,	/* Parsing context */
> >   				}
> >   				sqlite3VdbeChangeP2(v, addr, nCol);
> >   				sqlite3VdbeChangeP4(v, addr,
> > -						    (char *)
> > -						    sqlite3KeyInfoRef(pKeyInfo),
> > -						    P4_KEYINFO);
> > +						    (char *)key_def_dup(key_def),
> 
> 21. What if dup is failed?
I've added check for OOM.

> > @@ -2871,7 +2822,7 @@ sqlite3SelectWrongNumTermsError(Parse * pParse, Select * p)
> >    * If regPrev>0 then it is the first register in a vector that
> >    * records the previous output.  mem[regPrev] is a flag that is false
> >    * if there has been no previous output.  If regPrev>0 then code is
> > - * generated to suppress duplicates.  pKeyInfo is used for comparing
> > + * generated to suppress duplicates.  def is used for comparing
> 
> 22. Lets rewrite this comment in Tarantool style.
Done.

> > @@ -2884,7 +2835,7 @@ generateOutputSubroutine(Parse * pParse,	/* Parsing context */
> >   			 SelectDest * pDest,	/* Where to send the data */
> >   			 int regReturn,		/* The return address register */
> >   			 int regPrev,		/* Previous result register.  No uniqueness if 0 */
> > -			 KeyInfo * pKeyInfo,	/* For comparing with previous entry */
> > +			 struct key_def *def,	/* For comparing with previous entry */
> 
> 23. And remove these annotations. And def here can be const key_def *.
Done.

> > @@ -3145,7 +3098,8 @@ multiSelectOrderBy(Parse * pParse,	/* Parsing context */
> >   	int iSub2;		/* EQP id of right-hand query */
> >   	assert(p->pOrderBy != 0);
> > -	assert(pKeyDup == 0);	/* "Managed" code needs this.  Ticket #3382. */
> > +	/* "Managed" code needs this.  Ticket #3382. */
> > +	assert(def_dup == NULL);
> 
> 24. This assertion makes no sense - obviously dup here is NULL, it is declared
> as NULL few lines above.
Fixed.

> > @@ -3216,27 +3170,30 @@ multiSelectOrderBy(Parse * pParse,	/* Parsing context */
> >   	p->pOrderBy = pOrderBy;
> >   	pPrior->pOrderBy = sqlite3ExprListDup(pParse->db, pOrderBy, 0);
> > -	/* Allocate a range of temporary registers and the KeyInfo needed
> > +	/* Allocate a range of temporary registers and the key_def needed
> >   	 * for the logic that removes duplicate result rows when the
> >   	 * operator is UNION, EXCEPT, or INTERSECT (but not UNION ALL).
> >   	 */
> >   	if (op == TK_ALL) {
> >   		regPrev = 0;
> >   	} else {
> > -		int nExpr = p->pEList->nExpr;
> > -		assert(nOrderBy >= nExpr || db->mallocFailed);
> > +		int expr_count = p->pEList->nExpr;
> > +		assert(nOrderBy >= expr_count || db->mallocFailed);
> >   		regPrev = pParse->nMem + 1;
> > -		pParse->nMem += nExpr + 1;
> > +		pParse->nMem += expr_count + 1;
> >   		sqlite3VdbeAddOp2(v, OP_Integer, 0, regPrev);
> > -		pKeyDup = sqlite3KeyInfoAlloc(db, nExpr, 1);
> > -		if (pKeyDup) {
> > -			assert(sqlite3KeyInfoIsWriteable(pKeyDup));
> > -			for (i = 0; i < nExpr; i++) {
> > +		def_dup = key_def_new(expr_count);
> 
> 25. I do not see, where do you free it. So this key_def leaks. In two
> functions below (generateOutputSubroutine) it is used only to create
> more duplicates.
Good catch. I've added free() after use of the def_dup.

> > @@ -3320,9 +3277,8 @@ multiSelectOrderBy(Parse * pParse,	/* Parsing context */
> >   		VdbeNoopComment((v, "Output routine for B"));
> >   		addrOutB = generateOutputSubroutine(pParse,
> >   						    p, &destB, pDest, regOutB,
> > -						    regPrev, pKeyDup, labelEnd);
> > +						    regPrev, def_dup, labelEnd);
> >   	}
> > -	sqlite3KeyInfoUnref(pKeyDup);
> 
> This is where def_dup leaks.
> > @@ -5979,8 +5932,8 @@ sqlite3Select(Parse * pParse,		/* The parser context */
> >   			}
> >   			sqlite3VdbeAddOp4(v, OP_Compare, iAMem, iBMem,
> >   					  pGroupBy->nExpr,
> > -					  (char *)sqlite3KeyInfoRef(pKeyInfo),
> > -					  P4_KEYINFO);
> > +					  (char*) key_def_dup(def),
> > +					  P4_KEYDEF);
> 
> 26. What if key_def_dup returned NULL? db->mallocFailed is not set. Or you can
> reuse diag and set here pParse->rc into TARANTOOL_ERROR. In other places too.
I've set mallocFailed and returned void.

> > diff --git a/src/box/sql/sqliteInt.h b/src/box/sql/sqliteInt.h
> > index a811932..7d32556 100644
> > --- a/src/box/sql/sqliteInt.h
> > +++ b/src/box/sql/sqliteInt.h
> > @@ -2081,7 +2062,8 @@ struct KeyInfo {
> >    * b-tree.
> >    */
> >   struct UnpackedRecord {
> > -	KeyInfo *pKeyInfo;	/* Collation and sort-order information */
> > +	/* Collation and sort-order information */
> 
> 27. Please, put a dot at the end of sentence. And use /** prefix for
> comments out of function body.
Done.

> > @@ -3529,11 +3511,18 @@ const char *
> >   index_collation_name(Index *, uint32_t);
> >   struct coll *
> >   sql_index_collation(Index *idx, uint32_t column);
> > -struct coll *
> > -sql_default_coll();
> >   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`
> 
> 28. Please, say here that this function makes duplicate of original key_def,
> and can return NULL.
Done.

> > diff --git a/src/box/sql/update.c b/src/box/sql/update.c
> > index f3bd0b7..d380b8c 100644
> > --- a/src/box/sql/update.c
> > +++ b/src/box/sql/update.c
> > @@ -358,12 +358,12 @@ sqlite3Update(Parse * pParse,		/* The parser context */
> >   	sqlite3VdbeAddOp2(v, OP_Null, 0, iPk);
> >   	if (isView) {
> > -		KeyInfo *pKeyInfo = sqlite3KeyInfoAlloc(pParse->db, nKey, 0);
> > +		struct key_def *def = key_def_new(nKey);
> 
> 29. Here and in other places alloc() == NULL was ignored, because it set
> db->mallocFailed. With key_def we can not ignore OOM.
I've added the check.

> > diff --git a/src/box/sql/vdbe.h b/src/box/sql/vdbe.h
> > index e244606..4bd48d5 100644
> > --- a/src/box/sql/vdbe.h
> > +++ b/src/box/sql/vdbe.h
> > @@ -257,14 +265,20 @@ char *sqlite3VdbeExpandSql(Vdbe *, const char *);
> >   #endif
> >   int sqlite3MemCompare(const Mem *, const Mem *, const struct coll *);
> > -void sqlite3VdbeRecordUnpackMsgpack(KeyInfo *, int, const void *,
> > -				    UnpackedRecord *);
> > -int sqlite3VdbeRecordCompare(int, const void *, UnpackedRecord *);
> > -int sqlite3VdbeRecordCompareWithSkip(int, const void *, UnpackedRecord *, int);
> > -UnpackedRecord *sqlite3VdbeAllocUnpackedRecord(KeyInfo *);
> > +void sqlite3VdbeRecordUnpackMsgpack(struct sqlite3 *db,
> > +				    struct key_def *key_def,
> > +				    int key_count, const void * msgpack,
> > +				    UnpackedRecord *dest);
> > +int sqlite3VdbeRecordCompare(struct sqlite3 *db, int key_count,
> > +			     const void *key1, UnpackedRecord *key2);
> > +int sqlite3VdbeRecordCompareWithSkip(struct sqlite3 *db,
> > +				     int key_count, const void *key1,
> > +				     struct UnpackedRecord *key2, bool is_skip);
> 
> 30. Db argument here is not needed. CompareWithSkip allocs nothing.
It is used by vdbeRecordCompareDebug(), which performs OOM check. We'll refactor
this generically in future patches.
 
> > +UnpackedRecord *sqlite3VdbeAllocUnpackedRecord(struct sqlite3 *,
> > +					       struct key_def *);
> >   int sql_vdbe_mem_alloc_region(Mem *, uint32_t);
> > -typedef int (*RecordCompare) (int, const void *, UnpackedRecord *);
> > +typedef int (*RecordCompare) (struct sqlite3 *db, int, const void *, UnpackedRecord *);
> 
> 31. Same.
Fixed.

> > diff --git a/src/box/sql/vdbeaux.c b/src/box/sql/vdbeaux.c
> > index b3998ea..c2352e7 100644
> > --- a/src/box/sql/vdbeaux.c
> > +++ b/src/box/sql/vdbeaux.c
> > @@ -948,11 +948,9 @@ freeP4(sqlite3 * db, int p4type, void *p4)
> >   			sqlite3DbFree(db, p4);
> >   			break;
> >   		}
> > -	case P4_KEYINFO:{
> > -			if (db->pnBytesFreed == 0)
> > -				sqlite3KeyInfoUnref((KeyInfo *) p4);
> > -			break;
> > -		}
> > +	case P4_KEYDEF:
> > +		free(p4);
> 
> 32. Please use key_def_delete. It will be hard to find all free() and
> replace them to key_def_delete, when key_def will consists of more than
> one memory blocks.
Done.

> In other places too.
> >   #ifdef SQLITE_ENABLE_EXPLAIN_COMMENTS
> > @@ -1512,26 +1509,28 @@ displayP4(Op * pOp, char *zTemp, int nTemp)
> >   	assert(nTemp >= 20);
> >   	sqlite3StrAccumInit(&x, 0, zTemp, nTemp, 0);
> >   	switch (pOp->p4type) {
> > -	case P4_KEYINFO:{
> > -			int j;
> > -			KeyInfo *pKeyInfo;
> > +	case P4_KEYDEF:{
> > +			struct key_def *def;
> > -			if (pOp->p4.pKeyInfo == NULL) {
> > +			if (pOp->p4.key_def == NULL) {
> >   				sqlite3XPrintf(&x, "k[NULL]");
> >   			} else {
> > -				pKeyInfo = pOp->p4.pKeyInfo;
> > -				assert(pKeyInfo->aSortOrder != 0);
> > -				sqlite3XPrintf(&x, "k(%d", pKeyInfo->nField);
> > -				for (j = 0; j < pKeyInfo->nField; j++) {
> > -					struct coll *pColl = pKeyInfo->aColl[j];
> > -					const char *zColl =
> > -					    pColl ? pColl->name : "";
> > -					if (strcmp(zColl, "BINARY") == 0)
> > -						zColl = "B";
> > +				def = pOp->p4.key_def;
> > +				sqlite3XPrintf(&x, "k(%d", def->part_count);
> > +				for (int j = 0; j < (int)def->part_count; j++) {
> > +					struct coll *coll = def->parts[j].coll;
> > +					const char *coll_str =
> > +					    coll != NULL ? coll->name : "";
> > +					if (strcmp(coll_str, "BINARY") == 0)
> > +						coll_str = "B";
> > +					const char *sort_order = "";
> > +					if (def->parts[j].sort_order ==
> > +					    SORT_ORDER_DESC) {
> > +						sort_order = "-";
> > +					}
> >   					sqlite3XPrintf(&x, ",%s%s",
> > -						       pKeyInfo->
> > -						       aSortOrder[j] ? "-" : "",
> > -						       zColl);
> > +						       sort_order,
> > +						       coll_str);
> 
> 33. How about to move it into key_def.h/.cc and name key_def_str() or something?
Will do in separate patch.

> > @@ -3545,7 +3543,8 @@ sql_vdbe_mem_alloc_region(Mem *vdbe_mem, uint32_t size)
> >    * Return false if there is a disagreement.
> >    */
> >   static int
> > -vdbeRecordCompareDebug(int nKey1, const void *pKey1,	/* Left key */
> > +vdbeRecordCompareDebug(struct sqlite3 *db,
> > +		       int nKey1, const void *pKey1,	/* Left key */
> >   		       const UnpackedRecord * pPKey2,	/* Right key */
> >   		       int desiredResult)		/* Correct answer */
> >   {
> 
> 34. It does not require db - this function and all nested ones do not use it
> for anything except useless saving into struct Mem on stack.
See comment to p.30.

> > @@ -4286,68 +4279,6 @@ vdbeFreeUnpacked(sqlite3 * db, UnpackedRecord * p)
> >   }
> >   #endif				/* SQLITE_ENABLE_PREUPDATE_HOOK */
> > -#ifdef SQLITE_ENABLE_PREUPDATE_HOOK
> > -/*
> > - * Invoke the pre-update hook. If this is an UPDATE or DELETE pre-update call,
> > - * then cursor passed as the second argument should point to the row about
> > - * to be update or deleted. If the application calls sqlite3_preupdate_old(),
> > - * the required value will be read from the row the cursor points to.
> > - */
> > -void
> > -sqlite3VdbePreUpdateHook(Vdbe * v,		/* Vdbe pre-update hook is invoked by */
> 
> 35. This function still is declared in vdbeInt.h.
Removed.

> > diff --git a/test/sql-tap/index1.test.lua b/test/sql-tap/index1.test.lua
> > index 201838d..c0177c8 100755
> > --- a/test/sql-tap/index1.test.lua
> > +++ b/test/sql-tap/index1.test.lua
> > @@ -528,7 +528,9 @@ test:do_execsql_test(
> >           INSERT INTO t1 VALUES(2, 2,4);
> >           INSERT INTO t1 VALUES(3, 3,8);
> >           INSERT INTO t1 VALUES(4, 1,12);
> > +	pragma vdbe_debug=1;
> >           SELECT b FROM t1 WHERE a=1 ORDER BY b;
> > +	pragma vdbe_debug=0;
> 
> 36. Why?
Garbage. Removed.

> > diff --git a/test/sql-tap/index4.test.lua b/test/sql-tap/index4.test.lua
> > index 22e5066..85d3b3c 100755
> > --- a/test/sql-tap/index4.test.lua
> > +++ b/test/sql-tap/index4.test.lua
> > @@ -22,6 +22,7 @@ testprefix = "index4"
> >   test:do_execsql_test(
> >       1.1,
> >       [[
> > +    pragma vdbe_debug=1;
> 
> 37. Same.
Ditto.

> > diff --git a/test/sql-tap/selectA.test.lua b/test/sql-tap/selectA.test.lua
> > index fc482e9..fa3a025 100755
> > --- a/test/sql-tap/selectA.test.lua
> > +++ b/test/sql-tap/selectA.test.lua
> > @@ -1155,6 +1155,7 @@ test:do_execsql_test(
> >   test:do_execsql_test(
> >       "selectA-2.94",
> >       [[
> > +    pragma vdbe_debug=1;
> 
> 38. Same.
Ditto.

--
Regards, Kirill Yukhin


diff --git a/src/box/sql.c b/src/box/sql.c
index 838fcf6..2070e66 100644
--- a/src/box/sql.c
+++ b/src/box/sql.c
@@ -357,7 +357,7 @@ int tarantoolSqlite3Count(BtCursor *pCur, i64 *pnEntry)
 	return SQLITE_OK;
 }
 
-/*
+/**
  * Create ephemeral space and set cursor to the first entry. Features of
  * ephemeral spaces: id == 0, name == "ephemeral", memtx engine (in future it
  * can be changed, but now only memtx engine is supported), primary index
@@ -366,12 +366,12 @@ int tarantoolSqlite3Count(BtCursor *pCur, i64 *pnEntry)
  *
  * @param pCur Cursor which will point to the new ephemeral space.
  * @param field_count Number of fields in ephemeral space.
- * @param aColl Collation sequence of ephemeral space.
+ * @param def Keys description for new ephemeral space.
  *
  * @retval SQLITE_OK on success, SQLITE_TARANTOOL_ERROR otherwise.
  */
 int tarantoolSqlite3EphemeralCreate(BtCursor *pCur, uint32_t field_count,
-				    struct coll *aColl)
+				    struct key_def *def)
 {
 	assert(pCur);
 	assert(pCur->curFlags & BTCF_TEphemCursor);
@@ -380,11 +380,16 @@ int tarantoolSqlite3EphemeralCreate(BtCursor *pCur, uint32_t field_count,
 	if (ephemer_key_def == NULL)
 		return SQL_TARANTOOL_ERROR;
 	for (uint32_t part = 0; part < field_count; ++part) {
-		key_def_set_part(ephemer_key_def, part /* part no */,
-				 part /* filed no */,
+		struct coll *coll;
+		if (part < def->part_count)
+			coll = def->parts[part].coll;
+		else
+			coll = NULL;
+		key_def_set_part(ephemer_key_def, part,
+				 part,
 				 FIELD_TYPE_SCALAR,
-				 ON_CONFLICT_ACTION_NONE /* nullable_action */,
-				 aColl /* coll */,
+				 ON_CONFLICT_ACTION_NONE,
+				 coll,
 				 SORT_ORDER_ASC);
 	}
 
@@ -929,16 +934,14 @@ rename_fail:
 	return SQL_TARANTOOL_ERROR;
 }
 
-/*
- * Performs exactly as extract_key + sqlite3VdbeCompareMsgpack,
- * only faster.
- */
-int tarantoolSqlite3IdxKeyCompare(BtCursor *pCur, UnpackedRecord *pUnpacked,
-				  int *res)
+int
+tarantoolSqlite3IdxKeyCompare(struct BtCursor *cursor,
+			      struct UnpackedRecord *unpacked,
+			      int *res)
 {
-	assert(pCur->curFlags & BTCF_TaCursor);
-	assert(pCur->iter != NULL);
-	assert(pCur->last_tuple != NULL);
+	assert(cursor->curFlags & BTCF_TaCursor);
+	assert(cursor->iter != NULL);
+	assert(cursor->last_tuple != NULL);
 
 	const box_key_def_t *key_def;
 	const struct tuple *tuple;
@@ -955,9 +958,9 @@ int tarantoolSqlite3IdxKeyCompare(BtCursor *pCur, UnpackedRecord *pUnpacked,
 	uint32_t key_size;
 #endif
 
-	key_def = box_iterator_key_def(pCur->iter);
-	n = MIN(pUnpacked->nField, key_def->part_count);
-	tuple = pCur->last_tuple;
+	key_def = box_iterator_key_def(cursor->iter);
+	n = MIN(unpacked->nField, key_def->part_count);
+	tuple = cursor->last_tuple;
 	base = tuple_data(tuple);
 	format = tuple_format(tuple);
 	field_map = tuple_field_map(tuple);
@@ -991,28 +994,28 @@ int tarantoolSqlite3IdxKeyCompare(BtCursor *pCur, UnpackedRecord *pUnpacked,
 			} else {
 				p = base + field_map[
 					format->fields[fieldno].offset_slot
-];
+					];
 			}
 		}
 		next_fieldno = fieldno + 1;
-		rc = sqlite3VdbeCompareMsgpack(&p, pUnpacked, i);
+		rc = sqlite3VdbeCompareMsgpack(&p, unpacked, i);
 		if (rc != 0) {
-			if (pUnpacked->pKeyInfo->aSortOrder[i]) {
+			if (unpacked->key_def->parts[i].sort_order !=
+			    SORT_ORDER_ASC) {
 				rc = -rc;
 			}
 			*res = rc;
 			goto out;
 		}
 	}
-	*res = pUnpacked->default_rc;
+	*res = unpacked->default_rc;
 out:
 #ifndef NDEBUG
 	/* Sanity check. */
 	original_size = region_used(&fiber()->gc);
 	key = tuple_extract_key(tuple, key_def, &key_size);
 	if (key != NULL) {
-		rc = sqlite3VdbeRecordCompareMsgpack((int)key_size, key,
-						     pUnpacked);
+		rc = sqlite3VdbeRecordCompareMsgpack(key, unpacked);
 		region_truncate(&fiber()->gc, original_size);
 		assert(rc == *res);
 	}
diff --git a/src/box/sql/analyze.c b/src/box/sql/analyze.c
index f0054c5..ec23481 100644
--- a/src/box/sql/analyze.c
+++ b/src/box/sql/analyze.c
@@ -176,8 +176,8 @@ openStatTable(Parse * pParse,	/* Parsing context */
 	/* Open the sql_stat[134] tables for writing. */
 	for (i = 0; aTable[i]; i++) {
 		int addr = emit_open_cursor(pParse, iStatCur + i, aRoot[i]);
-		v->aOp[addr].p4.pKeyInfo = 0;
-		v->aOp[addr].p4type = P4_KEYINFO;
+		v->aOp[addr].p4.key_def = NULL;
+		v->aOp[addr].p4type = P4_KEYDEF;
 		sqlite3VdbeChangeP5(v, aCreateTbl[i]);
 		VdbeComment((v, aTable[i]));
 	}
@@ -914,7 +914,7 @@ analyzeOneTable(Parse * pParse,	/* Parser context */
 				     (void *) space);
 		sqlite3VdbeAddOp3(v, OP_OpenRead, iIdxCur, pIdx->tnum,
 				  space_ptr_reg);
-		sqlite3VdbeSetP4KeyInfo(pParse, pIdx);
+		sql_vdbe_set_p4_key_def(pParse, pIdx);
 		VdbeComment((v, "%s", pIdx->zName));
 
 		/* Invoke the stat_init() function. The arguments are:
diff --git a/src/box/sql/build.c b/src/box/sql/build.c
index 029c71e..bfaf3af 100644
--- a/src/box/sql/build.c
+++ b/src/box/sql/build.c
@@ -985,7 +985,7 @@ sqlite3AddPrimaryKey(Parse * pParse,	/* Parsing context */
 			pTab->tabFlags |= TF_Autoincrement;
 		}
 		if (pList)
-			pParse->iPkSortOrder = pList->a[0].sortOrder;
+			pParse->iPkSortOrder = pList->a[0].sort_order;
 	} else if (autoInc) {
 		sqlite3ErrorMsg(pParse, "AUTOINCREMENT is only allowed on an "
 				"INTEGER PRIMARY KEY or INT PRIMARY KEY");
@@ -1094,6 +1094,18 @@ sql_column_collation(Table *table, uint32_t column)
 	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->pTable->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 key_def_dup(index->def->key_def);
+}
+
 /**
  * Return name of given column collation from index.
  *
@@ -1119,10 +1131,9 @@ sql_index_collation(Index *idx, uint32_t column)
 		return idx->coll_array[column];
 	}
 
-	uint32_t index_id = SQLITE_PAGENO_TO_INDEXID(idx->tnum);
-	struct index *index = space_index(space, index_id);
-	assert(index != NULL && index->def->key_def->part_count >= column);
-	return index->def->key_def->parts[column].coll;
+	struct key_def *key_def = sql_index_key_def(idx);
+	assert(key_def != NULL && key_def->part_count >= column);
+	return key_def->parts[column].coll;
 }
 
 enum sort_order
@@ -1143,10 +1154,9 @@ sql_index_column_sort_order(Index *idx, uint32_t column)
 		return idx->sort_order[column];
 	}
 
-	uint32_t index_id = SQLITE_PAGENO_TO_INDEXID(idx->tnum);
-	struct index *index = space_index(space, index_id);
-	assert(index != NULL && index->def->key_def->part_count >= column);
-	return index->def->key_def->parts[column].sort_order;
+	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;
 }
 
 /**
@@ -1443,9 +1453,7 @@ hasColumn(const i16 * aiCol, int nCol, int x)
  *     (2)  Set the Index.tnum of the PRIMARY KEY Index object in the
  *          schema to the rootpage from the main table.
  *     (3)  Add all table columns to the PRIMARY KEY Index object
- *          so that the PRIMARY KEY is a covering index.  The surplus
- *          columns are part of KeyInfo.nXField and are not used for
- *          sorting or lookup or uniqueness checks.
+ *          so that the PRIMARY KEY is a covering index.
  */
 static void
 convertToWithoutRowidTable(Parse * pParse, Table * pTab)
@@ -1476,7 +1484,7 @@ convertToWithoutRowidTable(Parse * pParse, Table * pTab)
 							       &ipkToken, 0));
 		if (pList == 0)
 			return;
-		pList->a[0].sortOrder = pParse->iPkSortOrder;
+		pList->a[0].sort_order = pParse->iPkSortOrder;
 		assert(pParse->pNewTable == pTab);
 		sqlite3CreateIndex(pParse, 0, 0, pList, pTab->keyConf, 0, 0, 0,
 				   0, SQLITE_IDXTYPE_PRIMARYKEY);
@@ -2632,7 +2640,6 @@ sqlite3RefillIndex(Parse * pParse, Index * pIndex, int memRootPage)
 	int tnum;		/* Root page of index */
 	int iPartIdxLabel;	/* Jump to this label to skip a row */
 	Vdbe *v;		/* Generate code into this virtual machine */
-	KeyInfo *pKey;		/* KeyInfo for index */
 	int regRecord;		/* Register holding assembled index record */
 	sqlite3 *db = pParse->db;	/* The database connection */
 	v = sqlite3GetVdbe(pParse);
@@ -2643,14 +2650,13 @@ sqlite3RefillIndex(Parse * pParse, Index * pIndex, int memRootPage)
 	} else {
 		tnum = pIndex->tnum;
 	}
-	pKey = sqlite3KeyInfoOfIndex(pParse, db, pIndex);
-	assert(pKey != 0 || db->mallocFailed || pParse->nErr);
+	struct key_def *def = sql_index_key_def(pIndex);
+	assert(def != NULL || db->mallocFailed || pParse->nErr);
 
 	/* Open the sorter cursor if we are to use one. */
 	iSorter = pParse->nTab++;
 	sqlite3VdbeAddOp4(v, OP_SorterOpen, iSorter, 0, pIndex->nColumn,
-			  (char *)
-			  sqlite3KeyInfoRef(pKey), P4_KEYINFO);
+			  (char *)def, P4_KEYDEF);
 
 	/* Open the table. Loop through all rows of the table, inserting index
 	 * records into the sorter.
@@ -3099,7 +3105,7 @@ sqlite3CreateIndex(Parse * pParse,	/* All information about this parse */
 		/* Tarantool: DESC indexes are not supported so far.
 		 * See gh-3016.
 		 */
-		requested_so = pListItem->sortOrder & 0;
+		requested_so = pListItem->sort_order & 0;
 		pIndex->sort_order[i] = requested_so;
 	}
 
@@ -4194,46 +4200,6 @@ sqlite3Reindex(Parse * pParse, Token * pName1, Token * pName2)
 }
 #endif
 
-/*
- * Return a KeyInfo structure that is appropriate for the given Index.
- *
- * The caller should invoke sqlite3KeyInfoUnref() on the returned object
- * when it has finished using it.
- */
-KeyInfo *
-sqlite3KeyInfoOfIndex(Parse * pParse, sqlite3 * db, Index * pIdx)
-{
-	int i;
-	int nCol = pIdx->nColumn;
-	int nTableCol = pIdx->pTable->nCol;
-	KeyInfo *pKey;
-
-	if (pParse && pParse->nErr)
-		return 0;
-
-	/*
-	 * KeyInfo describes the index (i.e. the number of key columns,
-	 * comparator options, and the number of columns beyond the key).
-	 * Since Tarantool iterator yields the full tuple, we need a KeyInfo
-	 * as wide as the table itself.  Otherwize, not enough slots
-	 * for row parser cache are allocated in VdbeCursor object.
-	 */
-	pKey = sqlite3KeyInfoAlloc(db, nCol, nTableCol - nCol);
-	if (pKey) {
-		assert(sqlite3KeyInfoIsWriteable(pKey));
-		for (i = 0; i < nCol; i++) {
-			pKey->aColl[i] = sql_index_collation(pIdx, i);
-			pKey->aSortOrder[i] = sql_index_column_sort_order(pIdx,
-									  i);
-		}
-		if (pParse && pParse->nErr) {
-			sqlite3KeyInfoUnref(pKey);
-			pKey = 0;
-		}
-	}
-	return pKey;
-}
-
 #ifndef SQLITE_OMIT_CTE
 /*
  * This routine is invoked once per CTE by the parser while parsing a
diff --git a/src/box/sql/delete.c b/src/box/sql/delete.c
index 3f74b93..b6fc135 100644
--- a/src/box/sql/delete.c
+++ b/src/box/sql/delete.c
@@ -386,10 +386,10 @@ sqlite3DeleteFrom(Parse * pParse,	/* The parser context */
 			iPk = pParse->nMem + 1;
 			pParse->nMem += nPk;
 			iEphCur = pParse->nTab++;
-			KeyInfo *pKeyInfo = sqlite3KeyInfoAlloc(pParse->db, nPk, 0);
+			struct key_def *def = key_def_new(nPk);
 			addrEphOpen =
 				sqlite3VdbeAddOp4(v, OP_OpenTEphemeral, iEphCur,
-						  nPk, 0, (char*) pKeyInfo, P4_KEYINFO);
+						  nPk, 0, (char*)def, P4_KEYDEF);
 		} else {
 			pPk = sqlite3PrimaryKeyIndex(pTab);
 			assert(pPk != 0);
@@ -400,7 +400,7 @@ sqlite3DeleteFrom(Parse * pParse,	/* The parser context */
 			addrEphOpen =
 			    sqlite3VdbeAddOp2(v, OP_OpenTEphemeral, iEphCur,
 					      nPk);
-			sqlite3VdbeSetP4KeyInfo(pParse, pPk);
+			sql_vdbe_set_p4_key_def(pParse, pPk);
 		}
 
 		/* Construct a query to find the primary key for every row
diff --git a/src/box/sql/expr.c b/src/box/sql/expr.c
index cc969ca..635357b 100644
--- a/src/box/sql/expr.c
+++ b/src/box/sql/expr.c
@@ -1505,7 +1505,7 @@ sqlite3ExprListDup(sqlite3 * db, ExprList * p, int flags)
 		}
 		pItem->zName = sqlite3DbStrDup(db, pOldItem->zName);
 		pItem->zSpan = sqlite3DbStrDup(db, pOldItem->zSpan);
-		pItem->sortOrder = pOldItem->sortOrder;
+		pItem->sort_order = pOldItem->sort_order;
 		pItem->done = 0;
 		pItem->bSpanIsTab = pOldItem->bSpanIsTab;
 		pItem->u = pOldItem->u;
@@ -1766,10 +1766,13 @@ sqlite3ExprListSetSortOrder(ExprList * p, int iSortOrder)
 		return;
 	assert(p->nExpr > 0);
 	if (iSortOrder == SORT_ORDER_UNDEF) {
-		assert(p->a[p->nExpr - 1].sortOrder == SORT_ORDER_ASC);
+		assert(p->a[p->nExpr - 1].sort_order == SORT_ORDER_ASC);
 		return;
 	}
-	p->a[p->nExpr - 1].sortOrder = (u8) iSortOrder;
+	if (iSortOrder == 0)
+		p->a[p->nExpr - 1].sort_order = SORT_ORDER_ASC;
+	else
+		p->a[p->nExpr - 1].sort_order = SORT_ORDER_DESC;
 }
 
 /*
@@ -2522,7 +2525,7 @@ sqlite3FindInIndex(Parse * pParse,	/* Parsing context */
 							  P4_DYNAMIC);
 					emit_open_cursor(pParse, iTab,
 							 pIdx->tnum);
-					sqlite3VdbeSetP4KeyInfo(pParse, pIdx);
+					sql_vdbe_set_p4_key_def(pParse, pIdx);
 					VdbeComment((v, "%s", pIdx->zName));
 					assert(IN_INDEX_INDEX_DESC ==
 					       IN_INDEX_INDEX_ASC + 1);
@@ -2739,7 +2742,6 @@ sqlite3CodeSubselect(Parse * pParse,	/* Parsing context */
 	case TK_IN:{
 			int addr;	/* Address of OP_OpenEphemeral instruction */
 			Expr *pLeft = pExpr->pLeft;	/* the LHS of the IN operator */
-			KeyInfo *pKeyInfo = 0;	/* Key information */
 			int nVal;	/* Size of vector pLeft */
 
 			nVal = sqlite3ExprVectorSize(pLeft);
@@ -2761,7 +2763,7 @@ sqlite3CodeSubselect(Parse * pParse,	/* Parsing context */
 			pExpr->is_ephemeral = 1;
 			addr = sqlite3VdbeAddOp2(v, OP_OpenTEphemeral,
 						 pExpr->iTable, nVal);
-			pKeyInfo = sqlite3KeyInfoAlloc(pParse->db, nVal, 1);
+			struct key_def *key_def = key_def_new(nVal);
 
 			if (ExprHasProperty(pExpr, EP_xIsSelect)) {
 				/* Case 1:     expr IN (SELECT ...)
@@ -2787,29 +2789,34 @@ sqlite3CodeSubselect(Parse * pParse,	/* Parsing context */
 					pSelect->iLimit = 0;
 					testcase(pSelect->
 						 selFlags & SF_Distinct);
-					testcase(pKeyInfo == 0);	/* Caused by OOM in sqlite3KeyInfoAlloc() */
 					if (sqlite3Select
 					    (pParse, pSelect, &dest)) {
 						sqlite3DbFree(pParse->db,
 							      dest.zAffSdst);
-						sqlite3KeyInfoUnref(pKeyInfo);
+						if (key_def != NULL)
+							free(key_def);
 						return 0;
 					}
 					sqlite3DbFree(pParse->db,
 						      dest.zAffSdst);
-					assert(pKeyInfo != 0);	/* OOM will cause exit after sqlite3Select() */
+					assert(key_def != NULL);
 					assert(pEList != 0);
 					assert(pEList->nExpr > 0);
-					assert(sqlite3KeyInfoIsWriteable
-					       (pKeyInfo));
 					for (i = 0; i < nVal; i++) {
 						Expr *p =
 						    sqlite3VectorFieldSubexpr
 						    (pLeft, i);
-						pKeyInfo->aColl[i] =
-						    sqlite3BinaryCompareCollSeq
-						    (pParse, p,
-						     pEList->a[i].pExpr);
+
+						struct coll *coll;
+						coll = sqlite3BinaryCompareCollSeq
+							(pParse, p,
+							 pEList->a[i].pExpr);
+
+						key_def_set_part(key_def, i, i,
+								 FIELD_TYPE_SCALAR,
+								 ON_CONFLICT_ACTION_ABORT,
+								 coll,
+								 SORT_ORDER_ASC);
 					}
 				}
 			} else if (ALWAYS(pExpr->x.pList != 0)) {
@@ -2830,14 +2837,18 @@ sqlite3CodeSubselect(Parse * pParse,	/* Parsing context */
 				if (!affinity) {
 					affinity = SQLITE_AFF_BLOB;
 				}
-				if (pKeyInfo) {
+				if (key_def != NULL) {
 					bool unused;
-					assert(sqlite3KeyInfoIsWriteable
-					       (pKeyInfo));
-					pKeyInfo->aColl[0] =
-						sql_expr_coll(pParse,
-							      pExpr->pLeft,
-							      &unused);
+					struct coll *coll;
+					coll = sql_expr_coll(pParse,
+							     pExpr->pLeft,
+							     &unused);
+
+					key_def_set_part(key_def, 0, 0,
+							 FIELD_TYPE_SCALAR,
+							 ON_CONFLICT_ACTION_ABORT,
+							 coll,
+							 SORT_ORDER_ASC);
 				}
 
 				/* Loop through each expression in <exprlist>. */
@@ -2868,9 +2879,9 @@ sqlite3CodeSubselect(Parse * pParse,	/* Parsing context */
 				sqlite3ReleaseTempReg(pParse, r1);
 				sqlite3ReleaseTempReg(pParse, r2);
 			}
-			if (pKeyInfo) {
-				sqlite3VdbeChangeP4(v, addr, (void *)pKeyInfo,
-						    P4_KEYINFO);
+			if (key_def != NULL) {
+				sqlite3VdbeChangeP4(v, addr, (void *)key_def,
+						    P4_KEYDEF);
 			}
 			break;
 		}
@@ -5183,7 +5194,7 @@ sqlite3ExprListCompare(ExprList * pA, ExprList * pB, int iTab)
 	for (i = 0; i < pA->nExpr; i++) {
 		Expr *pExprA = pA->a[i].pExpr;
 		Expr *pExprB = pB->a[i].pExpr;
-		if (pA->a[i].sortOrder != pB->a[i].sortOrder)
+		if (pA->a[i].sort_order != pB->a[i].sort_order)
 			return 1;
 		if (sqlite3ExprCompare(pExprA, pExprB, iTab))
 			return 1;
diff --git a/src/box/sql/fkey.c b/src/box/sql/fkey.c
index fb9a310..4b9d3ed 100644
--- a/src/box/sql/fkey.c
+++ b/src/box/sql/fkey.c
@@ -437,7 +437,7 @@ fkLookupParent(Parse * pParse,	/* Parse context */
 			int regRec = sqlite3GetTempReg(pParse);
 
 			emit_open_cursor(pParse, iCur, pIdx->tnum);
-			sqlite3VdbeSetP4KeyInfo(pParse, pIdx);
+			sql_vdbe_set_p4_key_def(pParse, pIdx);
 			for (i = 0; i < nCol; i++) {
 				sqlite3VdbeAddOp2(v, OP_Copy,
 						  aiCol[i] + 1 + regData,
diff --git a/src/box/sql/insert.c b/src/box/sql/insert.c
index 1a34f71..95a2610 100644
--- a/src/box/sql/insert.c
+++ b/src/box/sql/insert.c
@@ -55,7 +55,7 @@ sqlite3OpenTable(Parse * pParse,	/* Generate code into this VDBE */
 	assert(pPk != 0);
 	assert(pPk->tnum == pTab->tnum);
 	emit_open_cursor(pParse, iCur, pPk->tnum);
-	sqlite3VdbeSetP4KeyInfo(pParse, pPk);
+	sql_vdbe_set_p4_key_def(pParse, pPk);
 	VdbeComment((v, "%s", pTab->zName));
 }
 
@@ -565,9 +565,9 @@ sqlite3Insert(Parse * pParse,	/* Parser context */
 			regRec = sqlite3GetTempReg(pParse);
 			regCopy = sqlite3GetTempRange(pParse, nColumn);
 			regTempId = sqlite3GetTempReg(pParse);
-			KeyInfo *pKeyInfo = sqlite3KeyInfoAlloc(pParse->db, 1+nColumn, 0);
+			struct key_def *def = key_def_new(nColumn + 1);
 			sqlite3VdbeAddOp4(v, OP_OpenTEphemeral, srcTab, nColumn+1,
-					  0, (char*)pKeyInfo, P4_KEYINFO);
+					  0, (char*)def, P4_KEYDEF);
 			addrL = sqlite3VdbeAddOp1(v, OP_Yield, dest.iSDParm);
 			VdbeCoverage(v);
 			sqlite3VdbeAddOp3(v, OP_NextIdEphemeral, srcTab, 2, regTempId);
@@ -1594,7 +1594,7 @@ sqlite3OpenTableAndIndices(Parse * pParse,	/* Parsing context */
 			if (aToOpen == 0 || aToOpen[i + 1]) {
 				sqlite3VdbeAddOp3(v, op, iIdxCur, pIdx->tnum,
 						  space_ptr_reg);
-				sqlite3VdbeSetP4KeyInfo(pParse, pIdx);
+				sql_vdbe_set_p4_key_def(pParse, pIdx);
 				sqlite3VdbeChangeP5(v, p5);
 				VdbeComment((v, "%s", pIdx->zName));
 			}
@@ -1908,10 +1908,10 @@ xferOptimization(Parse * pParse,	/* Parser context */
 		}
 		assert(pSrcIdx);
 		emit_open_cursor(pParse, iSrc, pSrcIdx->tnum);
-		sqlite3VdbeSetP4KeyInfo(pParse, pSrcIdx);
+		sql_vdbe_set_p4_key_def(pParse, pSrcIdx);
 		VdbeComment((v, "%s", pSrcIdx->zName));
 		emit_open_cursor(pParse, iDest, pDestIdx->tnum);
-		sqlite3VdbeSetP4KeyInfo(pParse, pDestIdx);
+		sql_vdbe_set_p4_key_def(pParse, pDestIdx);
 		sqlite3VdbeChangeP5(v, OPFLAG_BULKCSR);
 		VdbeComment((v, "%s", pDestIdx->zName));
 		addr1 = sqlite3VdbeAddOp2(v, OP_Rewind, iSrc, 0);
diff --git a/src/box/sql/pragma.c b/src/box/sql/pragma.c
index 738c254..0f1c64d 100644
--- a/src/box/sql/pragma.c
+++ b/src/box/sql/pragma.c
@@ -646,8 +646,8 @@ sqlite3Pragma(Parse * pParse, Token * pId,	/* First part of [schema.]id field */
 									  pIdx->
 									  tnum,
 									  0);
-							sqlite3VdbeSetP4KeyInfo
-							    (pParse, pIdx);
+							sql_vdbe_set_p4_key_def(pParse,
+										pIdx);
 						}
 					} else {
 						k = 0;
diff --git a/src/box/sql/select.c b/src/box/sql/select.c
index aff534d3..2e9d86f 100644
--- a/src/box/sql/select.c
+++ b/src/box/sql/select.c
@@ -562,12 +562,31 @@ sqliteProcessJoin(Parse * pParse, Select * p)
 	return 0;
 }
 
-/* Forward reference */
-static KeyInfo *
-keyInfoFromExprList(Parse * pParse,	/* Parsing context */
-		    ExprList * pList,	/* Form the KeyInfo object from this ExprList */
-		    int iStart,		/* Begin with this column of pList */
-		    int nExtra);	/* Add this many extra columns to the end */
+/**
+ * Given an expression list, generate a key_def structure that
+ * records the collating sequence for each expression in that
+ * expression list.
+ *
+ * If the ExprList is an ORDER BY or GROUP BY clause then the
+ * resulting key_def structure is appropriate for initializing
+ * a virtual index to implement that clause.  If the ExprList is
+ * the result set of a SELECT then the key_info structure is
+ * appropriate for initializing a virtual index to implement a
+ * DISTINCT test.
+ *
+ * Space to hold the key_info structure is obtained from malloc.
+ * The calling function is responsible for seeing that this
+ * structure is eventually freed.
+ *
+ * @param parse Parsing context.
+ * @param list Expression list.
+ * @param start No of leading parts to skip.
+ *
+ * @retval Allocated key_def, NULL in case of OOM.
+ */
+static struct key_def *
+sql_expr_list_to_key_def(struct Parse *parse, struct ExprList *list, int start);
+
 
 /*
  * Generate code that will push the record in registers regData
@@ -623,7 +642,6 @@ pushOntoSorter(Parse * pParse,		/* Parser context */
 		int addrJmp;	/* Address of the OP_Jump opcode */
 		VdbeOp *pOp;	/* Opcode that opens the sorter */
 		int nKey;	/* Number of sorting key columns, including OP_Sequence */
-		KeyInfo *pKI;	/* Original KeyInfo on the sorter table */
 
 		regPrevKey = pParse->nMem + 1;
 		pParse->nMem += pSort->nOBSat;
@@ -643,13 +661,17 @@ pushOntoSorter(Parse * pParse,		/* Parser context */
 		if (pParse->db->mallocFailed)
 			return;
 		pOp->p2 = nKey + nData;
-		pKI = pOp->p4.pKeyInfo;
-		memset(pKI->aSortOrder, 0, pKI->nField);	/* Makes OP_Jump below testable */
-		sqlite3VdbeChangeP4(v, -1, (char *)pKI, P4_KEYINFO);
-		testcase(pKI->nXField > 2);
-		pOp->p4.pKeyInfo =
-		    keyInfoFromExprList(pParse, pSort->pOrderBy, nOBSat,
-					pKI->nXField - 1);
+		struct key_def *def = key_def_dup(pOp->p4.key_def);
+		if (def == NULL) {
+			pParse->db->mallocFailed = 1;
+			return;
+		}
+		for (uint32_t i = 0; i < def->part_count; ++i)
+			pOp->p4.key_def->parts[i].sort_order = SORT_ORDER_ASC;
+		sqlite3VdbeChangeP4(v, -1, (char *)def, P4_KEYDEF);
+		pOp->p4.key_def = sql_expr_list_to_key_def(pParse,
+							   pSort->pOrderBy,
+							   nOBSat);
 		addrJmp = sqlite3VdbeCurrentAddr(v);
 		sqlite3VdbeAddOp3(v, OP_Jump, addrJmp + 1, 0, addrJmp + 1);
 		VdbeCoverage(v);
@@ -1193,109 +1215,27 @@ selectInnerLoop(Parse * pParse,		/* The parser context */
 	}
 }
 
-/*
- * Allocate a KeyInfo object sufficient for an index of N key columns and
- * X extra columns.
- */
-KeyInfo *
-sqlite3KeyInfoAlloc(sqlite3 * db, int N, int X)
-{
-	int nExtra = (N + X) * (sizeof(struct coll *) + 1);
-	KeyInfo *p = sqlite3DbMallocRawNN(db, sizeof(KeyInfo) + nExtra);
-	if (p) {
-		p->aSortOrder = (u8 *) & p->aColl[N + X];
-		p->nField = (u16) N;
-		p->nXField = (u16) X;
-		p->db = db;
-		p->nRef = 1;
-		p->aColl[0] = NULL;
-		memset(&p[1], 0, nExtra);
-	} else {
-		sqlite3OomFault(db);
-	}
-	return p;
-}
-
-/*
- * Deallocate a KeyInfo object
- */
-void
-sqlite3KeyInfoUnref(KeyInfo * p)
-{
-	if (p) {
-		assert(p->nRef > 0);
-		p->nRef--;
-		if (p->nRef == 0)
-			sqlite3DbFree(p->db, p);
-	}
-}
-
-/*
- * Make a new pointer to a KeyInfo object
- */
-KeyInfo *
-sqlite3KeyInfoRef(KeyInfo * p)
-{
-	if (p) {
-		assert(p->nRef > 0);
-		p->nRef++;
-	}
-	return p;
-}
-
-#ifdef SQLITE_DEBUG
-/*
- * Return TRUE if a KeyInfo object can be change.  The KeyInfo object
- * can only be changed if this is just a single reference to the object.
- *
- * This routine is used only inside of assert() statements.
- */
-int
-sqlite3KeyInfoIsWriteable(KeyInfo * p)
-{
-	return p->nRef == 1;
-}
-#endif				/* SQLITE_DEBUG */
-
-/*
- * Given an expression list, generate a KeyInfo structure that records
- * the collating sequence for each expression in that expression list.
- *
- * If the ExprList is an ORDER BY or GROUP BY clause then the resulting
- * KeyInfo structure is appropriate for initializing a virtual index to
- * implement that clause.  If the ExprList is the result set of a SELECT
- * then the KeyInfo structure is appropriate for initializing a virtual
- * index to implement a DISTINCT test.
- *
- * Space to hold the KeyInfo structure is obtained from malloc.  The calling
- * function is responsible for seeing that this structure is eventually
- * freed.
- */
-static KeyInfo *
-keyInfoFromExprList(Parse * pParse,	/* Parsing context */
-		    ExprList * pList,	/* Form the KeyInfo object from this ExprList */
-		    int iStart,		/* Begin with this column of pList */
-		    int nExtra)		/* Add this many extra columns to the end */
+static struct key_def *
+sql_expr_list_to_key_def(struct Parse *parse, struct ExprList *list, int start)
 {
-	int nExpr;
-	KeyInfo *pInfo;
-	struct ExprList_item *pItem;
-	sqlite3 *db = pParse->db;
-	int i;
-
-	nExpr = pList->nExpr;
-	pInfo = sqlite3KeyInfoAlloc(db, nExpr - iStart, nExtra + 1);
-	if (pInfo) {
-		assert(sqlite3KeyInfoIsWriteable(pInfo));
-		for (i = iStart, pItem = pList->a + iStart; i < nExpr;
-		     i++, pItem++) {
+	int expr_count = list->nExpr;
+	struct key_def *def = key_def_new(expr_count);
+	if (def != NULL) {
+		struct ExprList_item *item = list->a + start;
+		for (int i = start; i < expr_count; ++i, ++item) {
 			bool unused;
-			pInfo->aColl[i - iStart] =
-				sql_expr_coll(pParse, pItem->pExpr, &unused);
-			pInfo->aSortOrder[i - iStart] = pItem->sortOrder;
+			struct coll *coll = sql_expr_coll(parse, item->pExpr,
+							  &unused);
+			key_def_set_part(def, i-start, i-start,
+					 FIELD_TYPE_SCALAR,
+					 ON_CONFLICT_ACTION_ABORT,
+					 coll, item->sort_order);
 		}
+	} else {
+		parse->db->mallocFailed = 1;
 	}
-	return pInfo;
+
+	return def;
 }
 
 /*
@@ -2118,54 +2058,65 @@ multiSelectCollSeq(Parse * pParse, Select * p, int iCol, bool *is_found)
 	return coll;
 }
 
-/*
- * The select statement passed as the second parameter is a compound SELECT
- * with an ORDER BY clause. This function allocates and returns a KeyInfo
- * structure suitable for implementing the ORDER BY.
+/**
+ * The select statement passed as the second parameter is a
+ * compound SELECT with an ORDER BY clause. This function
+ * allocates and returns a key_def structure suitable for
+ * implementing the ORDER BY.
+ *
+ * Space to hold the key_def structure is obtained from malloc.
+ * The calling function is responsible for ensuring that this
+ * structure is eventually freed.
+ *
+ * @param parse Parsing context.
+ * @param s Select struct to analyze.
+ * @param extra No of extra slots to allocate.
  *
- * Space to hold the KeyInfo structure is obtained from malloc. The calling
- * function is responsible for ensuring that this structure is eventually
- * freed.
+ * @retval Allocated key_def, NULL in case of OOM.
  */
-static KeyInfo *
-multiSelectOrderByKeyInfo(Parse * pParse, Select * p, int nExtra)
+static struct key_def *
+sql_multiselect_orderby_to_key_def(struct Parse *parse,
+				   struct Select *s,
+				   int extra)
 {
-	ExprList *pOrderBy = p->pOrderBy;
-	int nOrderBy = p->pOrderBy->nExpr;
-	sqlite3 *db = pParse->db;
-	KeyInfo *pRet = sqlite3KeyInfoAlloc(db, nOrderBy + nExtra, 1);
-	if (pRet) {
-		int i;
-		for (i = 0; i < nOrderBy; i++) {
-			struct ExprList_item *pItem = &pOrderBy->a[i];
-			Expr *pTerm = pItem->pExpr;
-			struct coll *coll;
-			bool is_found = false;
-
-			if (pTerm->flags & EP_Collate) {
-				coll = sql_expr_coll(pParse, pTerm, &is_found);
+	int ob_count = s->pOrderBy->nExpr;
+	struct key_def *key_def = key_def_new(ob_count + extra);
+	if (key_def == NULL) {
+		parse->db->mallocFailed = 1;
+		return NULL;
+	}
+
+	ExprList *order_by = s->pOrderBy;
+	for (int i = 0; i < ob_count; i++) {
+		struct ExprList_item *item = &order_by->a[i];
+		struct Expr *term = item->pExpr;
+		struct coll *coll;
+		bool is_found = false;
+
+		if (term->flags & EP_Collate) {
+			coll = sql_expr_coll(parse, term, &is_found);
+		} else {
+			coll =multiSelectCollSeq(parse, s,
+						 item->u.x.iOrderByCol - 1,
+						 &is_found);
+			if (coll != NULL) {
+				order_by->a[i].pExpr =
+					sqlite3ExprAddCollateString(parse, term,
+								    coll->name);
 			} else {
-				coll =
-				    multiSelectCollSeq(pParse, p,
-						       pItem->u.x.iOrderByCol - 1,
-						       &is_found);
-				if (coll != NULL) {
-					pOrderBy->a[i].pExpr =
-						sqlite3ExprAddCollateString(pParse, pTerm,
-									    coll->name);
-				} else {
-					pOrderBy->a[i].pExpr =
-						sqlite3ExprAddCollateString(pParse, pTerm,
-									    "BINARY");
-				}
+				order_by->a[i].pExpr =
+					sqlite3ExprAddCollateString(parse, term,
+								    "BINARY");
 			}
-			assert(sqlite3KeyInfoIsWriteable(pRet));
-			pRet->aColl[i] = coll;
-			pRet->aSortOrder[i] = pOrderBy->a[i].sortOrder;
 		}
+		key_def_set_part(key_def, i, i,
+				 FIELD_TYPE_SCALAR,
+				 ON_CONFLICT_ACTION_ABORT,
+				 coll,
+				 order_by->a[i].sort_order);
 	}
 
-	return pRet;
+	return key_def;
 }
 
 #ifndef SQLITE_OMIT_CTE
@@ -2265,16 +2216,17 @@ generateWithRecursiveQuery(Parse * pParse,	/* Parsing context */
 	regCurrent = ++pParse->nMem;
 	sqlite3VdbeAddOp3(v, OP_OpenPseudo, iCurrent, regCurrent, nCol);
 	if (pOrderBy) {
-		KeyInfo *pKeyInfo = multiSelectOrderByKeyInfo(pParse, p, 1);
+		struct key_def *def = sql_multiselect_orderby_to_key_def(pParse,
+									 p, 1);
 		sqlite3VdbeAddOp4(v, OP_OpenTEphemeral, iQueue,
-				  pOrderBy->nExpr + 2, 0, (char *)pKeyInfo,
-				  P4_KEYINFO);
+				  pOrderBy->nExpr + 2, 0, (char *)def,
+				  P4_KEYDEF);
 		VdbeComment((v, "Orderby table"));
 		destQueue.pOrderBy = pOrderBy;
 	} else {
-		KeyInfo *pKeyInfo = sqlite3KeyInfoAlloc(pParse->db, nCol + 1, 0);
+		struct key_def *def = key_def_new(nCol + 1);
 		sqlite3VdbeAddOp4(v, OP_OpenTEphemeral, iQueue, nCol + 1, 0,
-				  (char*)pKeyInfo, P4_KEYINFO);
+				  (char*)def, P4_KEYDEF);
 		VdbeComment((v, "Queue table"));
 	}
 	if (iDistinct) {
@@ -2475,9 +2427,9 @@ multiSelect(Parse * pParse,	/* Parsing context */
 	if (dest.eDest == SRT_EphemTab) {
 		assert(p->pEList);
 		int nCols = p->pEList->nExpr;
-		KeyInfo *pKeyInfo = sqlite3KeyInfoAlloc(pParse->db, nCols + 1, 0);
+		struct key_def *def = key_def_new(nCols + 1);
 		sqlite3VdbeAddOp4(v, OP_OpenTEphemeral, dest.iSDParm, nCols + 1,
-				  0, (char*)pKeyInfo, P4_KEYINFO);
+				  0, (char*)def, P4_KEYDEF);
 		VdbeComment((v, "Destination temp"));
 		dest.eDest = SRT_Table;
 	}
@@ -2785,7 +2737,7 @@ multiSelect(Parse * pParse,	/* Parsing context */
 
 	/* Compute collating sequences used by
 	 * temporary tables needed to implement the compound select.
-	 * Attach the KeyInfo structure to all temporary tables.
+	 * Attach the key_def structure to all temporary tables.
 	 *
 	 * This section is run by the right-most SELECT statement only.
 	 * SELECT statements to the left always skip this part.  The right-most
@@ -2793,26 +2745,25 @@ multiSelect(Parse * pParse,	/* Parsing context */
 	 * no temp tables are required.
 	 */
 	if (p->selFlags & SF_UsesEphemeral) {
-		int i;		/* Loop counter */
-		KeyInfo *pKeyInfo;	/* Collating sequence for the result set */
-		Select *pLoop;	/* For looping through SELECT statements */
-		struct coll **apColl;	/* For looping through pKeyInfo->aColl[] */
-		int nCol;	/* Number of columns in result set */
-
-		assert(p->pNext == 0);
-		nCol = p->pEList->nExpr;
-		pKeyInfo = sqlite3KeyInfoAlloc(db, nCol, 1);
-		if (!pKeyInfo) {
+		assert(p->pNext == NULL);
+		int nCol = p->pEList->nExpr;
+		struct key_def *key_def = key_def_new(nCol);
+		if (key_def == NULL) {
 			rc = SQLITE_NOMEM_BKPT;
 			goto multi_select_end;
 		}
-		for (i = 0, apColl = pKeyInfo->aColl; i < nCol; i++, apColl++) {
-			bool is_found = false;
-			*apColl = multiSelectCollSeq(pParse, p, i, &is_found);
+		for (int i = 0; i < nCol; i++) {
+			bool unused;
+			key_def_set_part(key_def, i, i,
+					 FIELD_TYPE_SCALAR,
+					 ON_CONFLICT_ACTION_ABORT,
+					 multiSelectCollSeq(pParse, p, i,
+							    &unused),
+					 SORT_ORDER_ASC);
 		}
 
-		for (pLoop = p; pLoop; pLoop = pLoop->pPrior) {
-			for (i = 0; i < 2; i++) {
+		for (struct Select *pLoop = p; pLoop; pLoop = pLoop->pPrior) {
+			for (int i = 0; i < 2; i++) {
 				int addr = pLoop->addrOpenEphm[i];
 				if (addr < 0) {
 					/* If [0] is unused then [1] is also unused.  So we can
@@ -2822,14 +2773,19 @@ multiSelect(Parse * pParse,	/* Parsing context */
 					break;
 				}
 				sqlite3VdbeChangeP2(v, addr, nCol);
+				struct key_def *dup_def = key_def_dup(key_def);
+				if (dup_def == NULL) {
+					rc = SQLITE_NOMEM_BKPT;
+					goto multi_select_end;
+				}
+				
 				sqlite3VdbeChangeP4(v, addr,
-						    (char *)
-						    sqlite3KeyInfoRef(pKeyInfo),
-						    P4_KEYINFO);
+						    (char *)dup_def,
+						    P4_KEYDEF);
 				pLoop->addrOpenEphm[i] = -1;
 			}
 		}
-		sqlite3KeyInfoUnref(pKeyInfo);
+		free(key_def);
 	}
 
  multi_select_end:
@@ -2840,54 +2796,57 @@ multiSelect(Parse * pParse,	/* Parsing context */
 }
 #endif				/* SQLITE_OMIT_COMPOUND_SELECT */
 
-/*
- * Error message for when two or more terms of a compound select have different
- * size result sets.
- */
 void
-sqlite3SelectWrongNumTermsError(Parse * pParse, Select * p)
+sqlite3SelectWrongNumTermsError(struct Parse *parse, struct Select * p)
 {
 	if (p->selFlags & SF_Values) {
-		sqlite3ErrorMsg(pParse,
+		sqlite3ErrorMsg(parse,
 				"all VALUES must have the same number of terms");
 	} else {
-		sqlite3ErrorMsg(pParse, "SELECTs to the left and right of %s"
+		sqlite3ErrorMsg(parse, "SELECTs to the left and right of %s"
 				" do not have the same number of result columns",
 				selectOpName(p->op));
 	}
 }
 
-/*
+/**
  * Code an output subroutine for a coroutine implementation of a
  * SELECT statment.
  *
  * The data to be output is contained in pIn->iSdst.  There are
- * pIn->nSdst columns to be output.  pDest is where the output should
- * be sent.
+ * pIn->nSdst columns to be output.  pDest is where the output
+ * should be sent.
  *
  * regReturn is the number of the register holding the subroutine
  * return address.
  *
  * If regPrev>0 then it is the first register in a vector that
- * records the previous output.  mem[regPrev] is a flag that is false
- * if there has been no previous output.  If regPrev>0 then code is
- * generated to suppress duplicates.  pKeyInfo is used for comparing
- * keys.
+ * records the previous output.  mem[regPrev] is a flag that is
+ * false if there has been no previous output.  If regPrev>0 then
+ * code is generated to suppress duplicates.  def is used for
+ * comparing keys.
  *
  * If the LIMIT found in p->iLimit is reached, jump immediately to
  * iBreak.
+ *
+ * @param parse Parsing context.
+ * @param p The SELECT statement.
+ * @param in Coroutine supplying data.
+ * @param dest Where to send the data.
+ * @param reg_ret The return address register.
+ * @param reg_prev Previous result register.  No uniqueness if 0.
+ * @param def For comparing with previous entry.
+ * @param break_addr Jump here if we hit the LIMIT.
+ *
+ * @retval Address of generated routine.
  */
 static int
-generateOutputSubroutine(Parse * pParse,	/* Parsing context */
-			 Select * p,		/* The SELECT statement */
-			 SelectDest * pIn,	/* Coroutine supplying data */
-			 SelectDest * pDest,	/* Where to send the data */
-			 int regReturn,		/* The return address register */
-			 int regPrev,		/* Previous result register.  No uniqueness if 0 */
-			 KeyInfo * pKeyInfo,	/* For comparing with previous entry */
-			 int iBreak)		/* Jump here if we hit the LIMIT */
+generateOutputSubroutine(struct Parse *parse, struct Select *p,
+			 struct SelectDest *in, struct SelectDest *dest,
+			 int reg_ret, int reg_prev, const struct key_def *def,
+			 int break_addr)
 {
-	Vdbe *v = pParse->pVdbe;
+	Vdbe *v = parse->pVdbe;
 	int iContinue;
 	int addr;
 
@@ -2896,63 +2855,68 @@ generateOutputSubroutine(Parse * pParse,	/* Parsing context */
 
 	/* Suppress duplicates for UNION, EXCEPT, and INTERSECT
 	 */
-	if (regPrev) {
+	if (reg_prev) {
 		int addr1, addr2;
-		addr1 = sqlite3VdbeAddOp1(v, OP_IfNot, regPrev);
+		addr1 = sqlite3VdbeAddOp1(v, OP_IfNot, reg_prev);
 		VdbeCoverage(v);
+		struct key_def *dup_def = key_def_dup(def);
+		if (dup_def == NULL) {
+			parse->db->mallocFailed = 1;
+			return 0;
+		}
 		addr2 =
-		    sqlite3VdbeAddOp4(v, OP_Compare, pIn->iSdst, regPrev + 1,
-				      pIn->nSdst,
-				      (char *)sqlite3KeyInfoRef(pKeyInfo),
-				      P4_KEYINFO);
+		    sqlite3VdbeAddOp4(v, OP_Compare, in->iSdst, reg_prev + 1,
+				      in->nSdst,
+				      (char *)dup_def,
+				      P4_KEYDEF);
 		sqlite3VdbeAddOp3(v, OP_Jump, addr2 + 2, iContinue, addr2 + 2);
 		VdbeCoverage(v);
 		sqlite3VdbeJumpHere(v, addr1);
-		sqlite3VdbeAddOp3(v, OP_Copy, pIn->iSdst, regPrev + 1,
-				  pIn->nSdst - 1);
-		sqlite3VdbeAddOp2(v, OP_Integer, 1, regPrev);
+		sqlite3VdbeAddOp3(v, OP_Copy, in->iSdst, reg_prev + 1,
+				  in->nSdst - 1);
+		sqlite3VdbeAddOp2(v, OP_Integer, 1, reg_prev);
 	}
-	if (pParse->db->mallocFailed)
+	if (parse->db->mallocFailed)
 		return 0;
 
 	/* Suppress the first OFFSET entries if there is an OFFSET clause
 	 */
 	codeOffset(v, p->iOffset, iContinue);
 
-	assert(pDest->eDest != SRT_Exists);
-	assert(pDest->eDest != SRT_Table);
-	switch (pDest->eDest) {
+	assert(dest->eDest != SRT_Exists);
+	assert(dest->eDest != SRT_Table);
+	switch (dest->eDest) {
 		/* Store the result as data using a unique key.
 		 */
 	case SRT_EphemTab:{
-			int regRec = sqlite3GetTempReg(pParse);
-			int regCopy = sqlite3GetTempRange(pParse, pIn->nSdst + 1);
-			sqlite3VdbeAddOp3(v, OP_NextIdEphemeral, pDest->iSDParm,
-					  0, regCopy + pIn->nSdst);
-			sqlite3VdbeAddOp3(v, OP_Copy, pIn->iSdst, regCopy,
-					  pIn->nSdst - 1);
+			int regRec = sqlite3GetTempReg(parse);
+			int regCopy = sqlite3GetTempRange(parse, in->nSdst + 1);
+			sqlite3VdbeAddOp3(v, OP_NextIdEphemeral, dest->iSDParm,
+					  0, regCopy + in->nSdst);
+			sqlite3VdbeAddOp3(v, OP_Copy, in->iSdst, regCopy,
+					  in->nSdst - 1);
 			sqlite3VdbeAddOp3(v, OP_MakeRecord, regCopy,
-					  pIn->nSdst + 1, regRec);
+					  in->nSdst + 1, regRec);
 			/* Set flag to save memory allocating one by malloc. */
 			sqlite3VdbeChangeP5(v, 1);
-			sqlite3VdbeAddOp2(v, OP_IdxInsert, pDest->iSDParm, regRec);
-			sqlite3ReleaseTempRange(pParse, regCopy, pIn->nSdst + 1);
-			sqlite3ReleaseTempReg(pParse, regRec);
+			sqlite3VdbeAddOp2(v, OP_IdxInsert, dest->iSDParm, regRec);
+			sqlite3ReleaseTempRange(parse, regCopy, in->nSdst + 1);
+			sqlite3ReleaseTempReg(parse, regRec);
 			break;
 		}
 		/* If we are creating a set for an "expr IN (SELECT ...)".
 		 */
 	case SRT_Set:{
 			int r1;
-			testcase(pIn->nSdst > 1);
-			r1 = sqlite3GetTempReg(pParse);
-			sqlite3VdbeAddOp4(v, OP_MakeRecord, pIn->iSdst,
-					  pIn->nSdst, r1, pDest->zAffSdst,
-					  pIn->nSdst);
-			sqlite3ExprCacheAffinityChange(pParse, pIn->iSdst,
-						       pIn->nSdst);
-			sqlite3VdbeAddOp2(v, OP_IdxInsert, pDest->iSDParm, r1);
-			sqlite3ReleaseTempReg(pParse, r1);
+			testcase(in->nSdst > 1);
+			r1 = sqlite3GetTempReg(parse);
+			sqlite3VdbeAddOp4(v, OP_MakeRecord, in->iSdst,
+					  in->nSdst, r1, dest->zAffSdst,
+					  in->nSdst);
+			sqlite3ExprCacheAffinityChange(parse, in->iSdst,
+						       in->nSdst);
+			sqlite3VdbeAddOp2(v, OP_IdxInsert, dest->iSDParm, r1);
+			sqlite3ReleaseTempReg(parse, r1);
 			break;
 		}
 
@@ -2961,25 +2925,25 @@ generateOutputSubroutine(Parse * pParse,	/* Parsing context */
 		 * of the scan loop.
 		 */
 	case SRT_Mem:{
-			assert(pIn->nSdst == 1 || pParse->nErr > 0);
-			testcase(pIn->nSdst != 1);
-			sqlite3ExprCodeMove(pParse, pIn->iSdst, pDest->iSDParm,
+			assert(in->nSdst == 1 || parse->nErr > 0);
+			testcase(in->nSdst != 1);
+			sqlite3ExprCodeMove(parse, in->iSdst, dest->iSDParm,
 					    1);
 			/* The LIMIT clause will jump out of the loop for us */
 			break;
 		}
 		/* The results are stored in a sequence of registers
-		 * starting at pDest->iSdst.  Then the co-routine yields.
+		 * starting at dest->iSdst.  Then the co-routine yields.
 		 */
 	case SRT_Coroutine:{
-			if (pDest->iSdst == 0) {
-				pDest->iSdst =
-				    sqlite3GetTempRange(pParse, pIn->nSdst);
-				pDest->nSdst = pIn->nSdst;
+			if (dest->iSdst == 0) {
+				dest->iSdst =
+				    sqlite3GetTempRange(parse, in->nSdst);
+				dest->nSdst = in->nSdst;
 			}
-			sqlite3ExprCodeMove(pParse, pIn->iSdst, pDest->iSdst,
-					    pIn->nSdst);
-			sqlite3VdbeAddOp1(v, OP_Yield, pDest->iSDParm);
+			sqlite3ExprCodeMove(parse, in->iSdst, dest->iSdst,
+					    in->nSdst);
+			sqlite3VdbeAddOp1(v, OP_Yield, dest->iSDParm);
 			break;
 		}
 
@@ -2992,11 +2956,11 @@ generateOutputSubroutine(Parse * pParse,	/* Parsing context */
 		 * return the next row of result.
 		 */
 	default:{
-			assert(pDest->eDest == SRT_Output);
-			sqlite3VdbeAddOp2(v, OP_ResultRow, pIn->iSdst,
-					  pIn->nSdst);
-			sqlite3ExprCacheAffinityChange(pParse, pIn->iSdst,
-						       pIn->nSdst);
+			assert(dest->eDest == SRT_Output);
+			sqlite3VdbeAddOp2(v, OP_ResultRow, in->iSdst,
+					  in->nSdst);
+			sqlite3ExprCacheAffinityChange(parse, in->iSdst,
+						       in->nSdst);
 			break;
 		}
 	}
@@ -3004,14 +2968,14 @@ generateOutputSubroutine(Parse * pParse,	/* Parsing context */
 	/* Jump to the end of the loop if the LIMIT is reached.
 	 */
 	if (p->iLimit) {
-		sqlite3VdbeAddOp2(v, OP_DecrJumpZero, p->iLimit, iBreak);
+		sqlite3VdbeAddOp2(v, OP_DecrJumpZero, p->iLimit, break_addr);
 		VdbeCoverage(v);
 	}
 
 	/* Generate the subroutine return
 	 */
 	sqlite3VdbeResolveLabel(v, iContinue);
-	sqlite3VdbeAddOp1(v, OP_Return, regReturn);
+	sqlite3VdbeAddOp1(v, OP_Return, reg_ret);
 
 	return addr;
 }
@@ -3135,8 +3099,10 @@ multiSelectOrderBy(Parse * pParse,	/* Parsing context */
 	int labelEnd;		/* Label for the end of the overall SELECT stmt */
 	int addr1;		/* Jump instructions that get retargetted */
 	int op;			/* One of TK_ALL, TK_UNION, TK_EXCEPT, TK_INTERSECT */
-	KeyInfo *pKeyDup = 0;	/* Comparison information for duplicate removal */
-	KeyInfo *pKeyMerge;	/* Comparison information for merging rows */
+	/* Comparison information for duplicate removal */
+	struct key_def *def_dup = NULL;
+	/* Comparison information for merging rows */
+	struct key_def *def_merge;
 	sqlite3 *db;		/* Database connection */
 	ExprList *pOrderBy;	/* The ORDER BY clause */
 	int nOrderBy;		/* Number of terms in the ORDER BY clause */
@@ -3145,7 +3111,6 @@ multiSelectOrderBy(Parse * pParse,	/* Parsing context */
 	int iSub2;		/* EQP id of right-hand query */
 
 	assert(p->pOrderBy != 0);
-	assert(pKeyDup == 0);	/* "Managed" code needs this.  Ticket #3382. */
 	db = pParse->db;
 	v = pParse->pVdbe;
 	assert(v != 0);		/* Already thrown the error if VDBE alloc failed */
@@ -3190,7 +3155,7 @@ multiSelectOrderBy(Parse * pParse,	/* Parsing context */
 		}
 	}
 
-	/* Compute the comparison permutation and keyinfo that is used with
+	/* Compute the comparison permutation and key_def that is used with
 	 * the permutation used to determine if the next
 	 * row of results comes from selectA or selectB.  Also add explicit
 	 * collations to the ORDER BY clause terms so that when the subqueries
@@ -3206,9 +3171,9 @@ multiSelectOrderBy(Parse * pParse,	/* Parsing context */
 			assert(pItem->u.x.iOrderByCol <= p->pEList->nExpr);
 			aPermute[i] = pItem->u.x.iOrderByCol - 1;
 		}
-		pKeyMerge = multiSelectOrderByKeyInfo(pParse, p, 1);
+		def_merge = sql_multiselect_orderby_to_key_def(pParse, p, 1);
 	} else {
-		pKeyMerge = 0;
+		def_merge = NULL;
 	}
 
 	/* Reattach the ORDER BY clause to the query.
@@ -3216,27 +3181,30 @@ multiSelectOrderBy(Parse * pParse,	/* Parsing context */
 	p->pOrderBy = pOrderBy;
 	pPrior->pOrderBy = sqlite3ExprListDup(pParse->db, pOrderBy, 0);
 
-	/* Allocate a range of temporary registers and the KeyInfo needed
+	/* Allocate a range of temporary registers and the key_def needed
 	 * for the logic that removes duplicate result rows when the
 	 * operator is UNION, EXCEPT, or INTERSECT (but not UNION ALL).
 	 */
 	if (op == TK_ALL) {
 		regPrev = 0;
 	} else {
-		int nExpr = p->pEList->nExpr;
-		assert(nOrderBy >= nExpr || db->mallocFailed);
+		int expr_count = p->pEList->nExpr;
+		assert(nOrderBy >= expr_count || db->mallocFailed);
 		regPrev = pParse->nMem + 1;
-		pParse->nMem += nExpr + 1;
+		pParse->nMem += expr_count + 1;
 		sqlite3VdbeAddOp2(v, OP_Integer, 0, regPrev);
-		pKeyDup = sqlite3KeyInfoAlloc(db, nExpr, 1);
-		if (pKeyDup) {
-			assert(sqlite3KeyInfoIsWriteable(pKeyDup));
-			for (i = 0; i < nExpr; i++) {
+		def_dup = key_def_new(expr_count);
+		if (def_dup != NULL) {
+			for (int i = 0; i < expr_count; i++) {
 				bool is_found = false;
-				pKeyDup->aColl[i] =
-					multiSelectCollSeq(pParse, p, i,
-							   &is_found);
-				pKeyDup->aSortOrder[i] = 0;
+				struct coll *coll;
+				coll = multiSelectCollSeq(pParse, p, i,
+							  &is_found);
+				key_def_set_part(def_dup, i, i,
+						 FIELD_TYPE_SCALAR,
+						 ON_CONFLICT_ACTION_ABORT,
+						 coll,
+						 SORT_ORDER_ASC);
 			}
 		}
 	}
@@ -3311,7 +3279,7 @@ multiSelectOrderBy(Parse * pParse,	/* Parsing context */
 	VdbeNoopComment((v, "Output routine for A"));
 	addrOutA = generateOutputSubroutine(pParse,
 					    p, &destA, pDest, regOutA,
-					    regPrev, pKeyDup, labelEnd);
+					    regPrev, def_dup, labelEnd);
 
 	/* Generate a subroutine that outputs the current row of the B
 	 * select as the next output row of the compound select.
@@ -3320,9 +3288,10 @@ multiSelectOrderBy(Parse * pParse,	/* Parsing context */
 		VdbeNoopComment((v, "Output routine for B"));
 		addrOutB = generateOutputSubroutine(pParse,
 						    p, &destB, pDest, regOutB,
-						    regPrev, pKeyDup, labelEnd);
+						    regPrev, def_dup, labelEnd);
 	}
-	sqlite3KeyInfoUnref(pKeyDup);
+
+	key_def_delete(def_dup);
 
 	/* Generate a subroutine to run when the results from select A
 	 * are exhausted and only data in select B remains.
@@ -3402,7 +3371,7 @@ multiSelectOrderBy(Parse * pParse,	/* Parsing context */
 	sqlite3VdbeAddOp4(v, OP_Permutation, 0, 0, 0, (char *)aPermute,
 			  P4_INTARRAY);
 	sqlite3VdbeAddOp4(v, OP_Compare, destA.iSdst, destB.iSdst, nOrderBy,
-			  (char *)pKeyMerge, P4_KEYINFO);
+			  (char *)def_merge, P4_KEYDEF);
 	sqlite3VdbeChangeP5(v, OPFLAG_PERMUTE);
 	sqlite3VdbeAddOp3(v, OP_Jump, addrAltB, addrAeqB, addrAgtB);
 	VdbeCoverage(v);
@@ -5135,12 +5104,13 @@ resetAccumulator(Parse * pParse, AggInfo * pAggInfo)
 						"argument");
 				pFunc->iDistinct = -1;
 			} else {
-				KeyInfo *pKeyInfo =
-				    keyInfoFromExprList(pParse, pE->x.pList, 0,
-							0);
+				struct key_def *def;
+				def = sql_expr_list_to_key_def(pParse,
+							       pE->x.pList,
+							       0);
 				sqlite3VdbeAddOp4(v, OP_OpenTEphemeral,
 						  pFunc->iDistinct, 1, 0,
-						  (char *)pKeyInfo, P4_KEYINFO);
+						  (char *)def, P4_KEYDEF);
 			}
 		}
 	}
@@ -5611,23 +5581,22 @@ sqlite3Select(Parse * pParse,		/* The parser context */
 	 * that change.
 	 */
 	if (sSort.pOrderBy) {
-		KeyInfo *pKeyInfo;
-		pKeyInfo =
-		    keyInfoFromExprList(pParse, sSort.pOrderBy, 0, pEList->nExpr);
+		struct key_def *def;
+		def = sql_expr_list_to_key_def(pParse, sSort.pOrderBy, 0);
 		sSort.iECursor = pParse->nTab++;
 		/* Number of columns in transient table equals to number of columns in
 		 * SELECT statement plus number of columns in ORDER BY statement
 		 * and plus one column for ID.
 		 */
 		int nCols = pEList->nExpr + sSort.pOrderBy->nExpr + 1;
-		if (pKeyInfo->aSortOrder[0] == SORT_ORDER_DESC) {
+		if (def->parts[0].sort_order == SORT_ORDER_DESC) {
 			sSort.sortFlags |= SORTFLAG_DESC;
 		}
 		sSort.addrSortIndex =
 		    sqlite3VdbeAddOp4(v, OP_OpenTEphemeral,
 				      sSort.iECursor,
 				      nCols,
-				      0, (char *)pKeyInfo, P4_KEYINFO);
+				      0, (char *)def, P4_KEYDEF);
 		VdbeComment((v, "Sort table"));
 	} else {
 		sSort.addrSortIndex = -1;
@@ -5636,10 +5605,10 @@ sqlite3Select(Parse * pParse,		/* The parser context */
 	/* If the output is destined for a temporary table, open that table.
 	 */
 	if (pDest->eDest == SRT_EphemTab) {
-		KeyInfo *pKeyInfo = sqlite3KeyInfoAlloc(pParse->db,
-							pEList->nExpr + 1, 0);
+		struct key_def *def;
+		def = key_def_new(pEList->nExpr + 1);
 		sqlite3VdbeAddOp4(v, OP_OpenTEphemeral, pDest->iSDParm,
-				  pEList->nExpr + 1, 0, (char*)pKeyInfo, P4_KEYINFO);
+				  pEList->nExpr + 1, 0, (char*)def, P4_KEYDEF);
 
 		VdbeComment((v, "Output table"));
 	}
@@ -5660,12 +5629,12 @@ sqlite3Select(Parse * pParse,		/* The parser context */
 	 */
 	if (p->selFlags & SF_Distinct) {
 		sDistinct.tabTnct = pParse->nTab++;
-		KeyInfo *pKeyInfo = keyInfoFromExprList(pParse, p->pEList, 0, 0);
+		struct key_def *def = sql_expr_list_to_key_def(pParse, p->pEList, 0);
 		sDistinct.addrTnct = sqlite3VdbeAddOp4(v, OP_OpenTEphemeral,
 						       sDistinct.tabTnct,
-						       pKeyInfo->nField,
-						       0, (char *)pKeyInfo,
-						       P4_KEYINFO);
+						       def->part_count,
+						       0, (char *)def,
+						       P4_KEYDEF);
 		VdbeComment((v, "Distinct table"));
 		sDistinct.eTnctType = WHERE_DISTINCT_UNORDERED;
 	} else {
@@ -5806,7 +5775,6 @@ sqlite3Select(Parse * pParse,		/* The parser context */
 		 * much more complex than aggregates without a GROUP BY.
 		 */
 		if (pGroupBy) {
-			KeyInfo *pKeyInfo;	/* Keying information for the group by clause */
 			int addr1;	/* A-vs-B comparision jump */
 			int addrOutputRow;	/* Start of subroutine that outputs a result row */
 			int regOutputRow;	/* Return address register for output subroutine */
@@ -5822,14 +5790,12 @@ sqlite3Select(Parse * pParse,		/* The parser context */
 			 * will be converted into a Noop.
 			 */
 			sAggInfo.sortingIdx = pParse->nTab++;
-			pKeyInfo =
-			    keyInfoFromExprList(pParse, pGroupBy, 0,
-						sAggInfo.nColumn);
+			struct key_def *def = sql_expr_list_to_key_def(pParse, pGroupBy, 0);
 			addrSortingIdx =
 			    sqlite3VdbeAddOp4(v, OP_SorterOpen,
 					      sAggInfo.sortingIdx,
 					      sAggInfo.nSortingColumn, 0,
-					      (char *)pKeyInfo, P4_KEYINFO);
+					      (char *)def, P4_KEYDEF);
 
 			/* Initialize memory locations used by GROUP BY aggregate processing
 			 */
@@ -5867,7 +5833,7 @@ sqlite3Select(Parse * pParse,		/* The parser context */
 			if (sqlite3WhereIsOrdered(pWInfo) == pGroupBy->nExpr) {
 				/* The optimizer is able to deliver rows in group by order so
 				 * we do not have to sort.  The OP_OpenEphemeral table will be
-				 * cancelled later because we still need to use the pKeyInfo
+				 * cancelled later because we still need to use the key_def
 				 */
 				groupBySort = 0;
 			} else {
@@ -5977,10 +5943,15 @@ sqlite3Select(Parse * pParse,		/* The parser context */
 							iBMem + j);
 				}
 			}
+			struct key_def *dup_def = key_def_dup(def);
+			if (dup_def == NULL) {
+				db->mallocFailed = 1;
+				goto select_end;
+			}
 			sqlite3VdbeAddOp4(v, OP_Compare, iAMem, iBMem,
 					  pGroupBy->nExpr,
-					  (char *)sqlite3KeyInfoRef(pKeyInfo),
-					  P4_KEYINFO);
+					  (char*)dup_def,
+					  P4_KEYDEF);
 			addr1 = sqlite3VdbeCurrentAddr(v);
 			sqlite3VdbeAddOp3(v, OP_Jump, addr1 + 1, 0, addr1 + 1);
 			VdbeCoverage(v);
@@ -6090,7 +6061,7 @@ sqlite3Select(Parse * pParse,		/* The parser context */
 				 */
 				const int iCsr = pParse->nTab++;	/* Cursor to scan b-tree */
 				Index *pIdx;	/* Iterator variable */
-				KeyInfo *pKeyInfo = 0;	/* Keyinfo for scanned index */
+				struct key_def *def = NULL;
 				Index *pBest;	/* Best index found so far */
 				int iRoot = pTab->tnum;	/* Root page of scanned b-tree */
 
@@ -6100,7 +6071,7 @@ sqlite3Select(Parse * pParse,		/* The parser context */
 				 *
 				 * (2013-10-03) Do not count the entries in a partial index.
 				 *
-				 * In practice the KeyInfo structure will not be used. It is only
+				 * In practice the key_def structure will not be used. It is only
 				 * passed to keep OP_OpenRead happy.
 				 */
 				pBest = sqlite3PrimaryKeyIndex(pTab);
@@ -6116,19 +6087,17 @@ sqlite3Select(Parse * pParse,		/* The parser context */
 						pBest = pIdx;
 					}
 				}
-				if (pBest) {
+				if (pBest != NULL) {
 					iRoot = pBest->tnum;
-					pKeyInfo =
-					    sqlite3KeyInfoOfIndex(pParse, db,
-								  pBest);
+					def = sql_index_key_def(pBest);
 				}
 
 				/* Open a read-only cursor, execute the OP_Count, close the cursor. */
 				emit_open_cursor(pParse, iCsr, iRoot);
-				if (pKeyInfo) {
+				if (def != NULL) {
 					sqlite3VdbeChangeP4(v, -1,
-							    (char *)pKeyInfo,
-							    P4_KEYINFO);
+							    (char *)def,
+							    P4_KEYDEF);
 				}
 				sqlite3VdbeAddOp2(v, OP_Count, iCsr,
 						  sAggInfo.aFunc[0].iMem);
@@ -6181,7 +6150,7 @@ sqlite3Select(Parse * pParse,		/* The parser context */
 					assert(db->mallocFailed
 					       || pMinMax != 0);
 					if (!db->mallocFailed) {
-						pMinMax->a[0].sortOrder =
+						pMinMax->a[0].sort_order =
 						    flag !=
 						    WHERE_ORDERBY_MIN ? 1 : 0;
 						pMinMax->a[0].pExpr->op =
diff --git a/src/box/sql/sqliteInt.h b/src/box/sql/sqliteInt.h
index c2c1090..e1a3f59 100644
--- a/src/box/sql/sqliteInt.h
+++ b/src/box/sql/sqliteInt.h
@@ -1462,7 +1462,6 @@ typedef struct IdList IdList;
 typedef struct Index Index;
 typedef struct IndexSample IndexSample;
 typedef struct KeyClass KeyClass;
-typedef struct KeyInfo KeyInfo;
 typedef struct Lookaside Lookaside;
 typedef struct LookasideSlot LookasideSlot;
 typedef struct NameContext NameContext;
@@ -2027,24 +2026,6 @@ struct FKey {
 #define OE_SetDflt  8		/* Set the foreign key value to its default */
 #define OE_Cascade  9		/* Cascade the changes */
 
-/*
- * An instance of the following structure is passed as the first
- * argument to sqlite3VdbeKeyCompare and is used to control the
- * comparison of the two index keys.
- *
- * Note that aSortOrder[] and aColl[] have nField+1 slots.  There
- * are nField slots for the columns of an index.
- */
-struct KeyInfo {
-	u32 nRef;		/* Number of references to this KeyInfo object */
-	u8 enc;			/* Text encoding - one of the SQLITE_UTF* values */
-	u16 nField;		/* Number of key columns in the index */
-	u16 nXField;		/* Number of columns beyond the key columns */
-	sqlite3 *db;		/* The database connection */
-	u8 *aSortOrder;		/* Sort order for each column. */
-	struct coll *aColl[1];	/* Collating sequence for each term of the key */
-};
-
 /*
  * This object holds a record which has been parsed out into individual
  * fields, for the purposes of doing a comparison.
@@ -2059,7 +2040,7 @@ struct KeyInfo {
  * an index b+tree. The goal of the search is to find the entry that
  * is closed to the key described by this object.  This object might hold
  * just a prefix of the key.  The number of fields is given by
- * pKeyInfo->nField.
+ * key_def->part_count.
  *
  * The r1 and r2 fields are the values to return if this key is less than
  * or greater than a key in the btree, respectively.  These are normally
@@ -2069,7 +2050,7 @@ struct KeyInfo {
  * The key comparison functions actually return default_rc when they find
  * an equals comparison.  default_rc can be -1, 0, or +1.  If there are
  * multiple entries in the b-tree with the same key (when only looking
- * at the first pKeyInfo->nFields,) then default_rc can be set to -1 to
+ * at the first key_def->part_count) then default_rc can be set to -1 to
  * cause the search to find the last match, or +1 to cause the search to
  * find the first match.
  *
@@ -2081,7 +2062,8 @@ struct KeyInfo {
  * b-tree.
  */
 struct UnpackedRecord {
-	KeyInfo *pKeyInfo;	/* Collation and sort-order information */
+	/** Collation and sort-order information. */
+	struct key_def* key_def;
 	Mem *aMem;		/* Values */
 	u16 nField;		/* Number of entries in apMem[] */
 	i8 default_rc;		/* Comparison result if keys are equal */
@@ -2458,7 +2440,7 @@ struct ExprList {
 		Expr *pExpr;	/* The list of expressions */
 		char *zName;	/* Token associated with this expression */
 		char *zSpan;	/* Original text of the expression */
-		u8 sortOrder;	/* 1 for DESC or 0 for ASC */
+		enum sort_order sort_order;
 		unsigned done:1;	/* A flag to indicate when processing is finished */
 		unsigned bSpanIsTab:1;	/* zSpan holds DB.TABLE.COLUMN */
 		unsigned reusable:1;	/* Constant expression is reusable */
@@ -2690,12 +2672,12 @@ struct NameContext {
  *
  * addrOpenEphm[] entries contain the address of OP_OpenEphemeral opcodes.
  * These addresses must be stored so that we can go back and fill in
- * the P4_KEYINFO and P2 parameters later.  Neither the KeyInfo nor
+ * the P4_KEYDEF and P2 parameters later.  Neither the key_def nor
  * the number of columns in P2 can be computed at the same time
  * as the OP_OpenEphm instruction is coded because not
  * enough information about the compound query is known at that point.
- * The KeyInfo for addrOpenTran[0] and [1] contains collating sequences
- * for the result set.  The KeyInfo for addrOpenEphm[2] contains collating
+ * The key_def for addrOpenTran[0] and [1] contains collating sequences
+ * for the result set.  The key_def for addrOpenEphm[2] contains collating
  * sequences for the ORDER BY clause.
  */
 struct Select {
@@ -3529,11 +3511,20 @@ const char *
 index_collation_name(Index *, uint32_t);
 struct coll *
 sql_index_collation(Index *idx, uint32_t column);
-struct coll *
-sql_default_coll();
 bool
 space_is_view(Table *);
 
+/**
+ * Return key_def of provided struct Index. This routine
+ * actually performs key duplication.
+ *
+ * @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.
  *
@@ -3897,7 +3888,17 @@ void sqlite3NestedParse(Parse *, const char *, ...);
 void sqlite3ExpirePreparedStatements(sqlite3 *);
 int sqlite3CodeSubselect(Parse *, Expr *, int);
 void sqlite3SelectPrep(Parse *, Select *, NameContext *);
-void sqlite3SelectWrongNumTermsError(Parse * pParse, Select * p);
+
+/**
+ * Error message for when two or more terms of a compound select
+ * have different size result sets.
+ *
+ * @param parse Parsing context.
+ * @param p Select struct to analyze.
+ */
+void
+sqlite3SelectWrongNumTermsError(struct Parse *parse, struct Select *p);
+
 int sqlite3MatchSpanName(const char *, const char *, const char *);
 int sqlite3ResolveExprNames(NameContext *, Expr *);
 int sqlite3ResolveExprListNames(NameContext *, ExprList *);
@@ -3926,13 +3927,6 @@ void sqlite3RegisterLikeFunctions(sqlite3 *, int);
 int sqlite3IsLikeFunction(sqlite3 *, Expr *, int *, char *);
 void sqlite3SchemaClear(sqlite3 *);
 Schema *sqlite3SchemaCreate(sqlite3 *);
-KeyInfo *sqlite3KeyInfoAlloc(sqlite3 *, int, int);
-void sqlite3KeyInfoUnref(KeyInfo *);
-KeyInfo *sqlite3KeyInfoRef(KeyInfo *);
-KeyInfo *sqlite3KeyInfoOfIndex(Parse *, sqlite3 *, Index *);
-#ifdef SQLITE_DEBUG
-int sqlite3KeyInfoIsWriteable(KeyInfo *);
-#endif
 int sqlite3CreateFunc(sqlite3 *, const char *, int, int, void *,
 		      void (*)(sqlite3_context *, int, sqlite3_value **),
 		      void (*)(sqlite3_context *, int, sqlite3_value **),
diff --git a/src/box/sql/tarantoolInt.h b/src/box/sql/tarantoolInt.h
index 57fc4a1..ef36f5a 100644
--- a/src/box/sql/tarantoolInt.h
+++ b/src/box/sql/tarantoolInt.h
@@ -98,7 +98,7 @@ int tarantoolSqlite3RenameParentTable(int iTab, const char *zOldParentName,
 
 /* Interface for ephemeral tables. */
 int tarantoolSqlite3EphemeralCreate(BtCursor * pCur, uint32_t filed_count,
-				    struct coll *aColl);
+				    struct key_def *def);
 /**
  * Insert tuple into ephemeral space.
  * In contrast to ordinary spaces, there is no need to create and
@@ -119,12 +119,20 @@ int tarantoolSqlite3EphemeralClearTable(BtCursor * pCur);
 int tarantoolSqlite3EphemeralGetMaxId(BtCursor * pCur, uint32_t fieldno,
 				       uint64_t * max_id);
 
-/* Compare against the index key under a cursor -
- * the key may span non-adjacent fields in a random order,
- * ex: [4]-[1]-[2]
+/**
+ * Performs exactly as extract_key + sqlite3VdbeCompareMsgpack,
+ * only faster.
+ *
+ * @param pCur cursor which point to tuple to compare.
+ * @param pUnpacked Unpacked record to compare with.
+ * @param[out] res Comparison result.
+ *
+ * @retval Error code.
  */
-int tarantoolSqlite3IdxKeyCompare(BtCursor * pCur, UnpackedRecord * pUnpacked,
-				  int *res);
+int
+tarantoolSqlite3IdxKeyCompare(struct BtCursor *cursor,
+			      struct UnpackedRecord *unpacked,
+			      int *res);
 
 /**
  * The function assumes the cursor is open on _schema.
diff --git a/src/box/sql/update.c b/src/box/sql/update.c
index f3bd0b7..31d07d5 100644
--- a/src/box/sql/update.c
+++ b/src/box/sql/update.c
@@ -358,12 +358,16 @@ sqlite3Update(Parse * pParse,		/* The parser context */
 	sqlite3VdbeAddOp2(v, OP_Null, 0, iPk);
 
 	if (isView) {
-		KeyInfo *pKeyInfo = sqlite3KeyInfoAlloc(pParse->db, nKey, 0);
+		struct key_def *def = key_def_new(nKey);
+		if (def == NULL) {
+			pParse->db->mallocFailed = 1;
+			goto update_cleanup;
+		}
 		addrOpen = sqlite3VdbeAddOp4(v, OP_OpenTEphemeral, iEph,
-					     nKey, 0, (char*)pKeyInfo, P4_KEYINFO);
+					     nKey, 0, (char*)def, P4_KEYDEF);
 	} else {
 		addrOpen = sqlite3VdbeAddOp2(v, OP_OpenTEphemeral, iEph, nPk);
-		sqlite3VdbeSetP4KeyInfo(pParse, pPk);
+		sql_vdbe_set_p4_key_def(pParse, pPk);
 	}
 
 	pWInfo = sqlite3WhereBegin(pParse, pTabList, pWhere, 0, 0,
diff --git a/src/box/sql/vdbe.c b/src/box/sql/vdbe.c
index 013460f..08e364b3 100644
--- a/src/box/sql/vdbe.c
+++ b/src/box/sql/vdbe.c
@@ -2243,35 +2243,36 @@ case OP_Permutation: {
  * OPFLAG_PERMUTE bit is clear, then register are compared in sequential
  * order.
  *
- * P4 is a KeyInfo structure that defines collating sequences and sort
+ * P4 is a key_def structure that defines collating sequences and sort
  * orders for the comparison.  The permutation applies to registers
- * only.  The KeyInfo elements are used sequentially.
+ * only.  The key_def elements are used sequentially.
  *
  * The comparison is a sort comparison, so NULLs compare equal,
  * NULLs are less than numbers, numbers are less than strings,
  * and strings are less than blobs.
  */
 case OP_Compare: {
-	int n;
-	int i;
 	int p1;
 	int p2;
-	const KeyInfo *pKeyInfo;
 	int idx;
-	struct coll *pColl;    /* Collating sequence to use on this term */
-	int bRev;          /* True for DESCENDING sort order */
 
-	if ((pOp->p5 & OPFLAG_PERMUTE)==0) aPermute = 0;
-	n = pOp->p3;
-	pKeyInfo = pOp->p4.pKeyInfo;
+	if ((pOp->p5 & OPFLAG_PERMUTE) == 0)
+		aPermute = 0;
+
+	int n = pOp->p3;
+
+	assert(pOp->p4type == P4_KEYDEF);
 	assert(n>0);
-	assert(pKeyInfo!=0);
 	p1 = pOp->p1;
 	p2 = pOp->p2;
+
+	struct key_def *def = pOp->p4.key_def;
 #if SQLITE_DEBUG
 	if (aPermute) {
-		int k, mx = 0;
-		for(k=0; k<n; k++) if (aPermute[k]>mx) mx = aPermute[k];
+		int mx = 0;
+		for(uint32_t k = 0; k < (uint32_t)n; k++)
+			if (aPermute[k] > mx)
+				mx = aPermute[k];
 		assert(p1>0 && p1+mx<=(p->nMem+1 - p->nCursor)+1);
 		assert(p2>0 && p2+mx<=(p->nMem+1 - p->nCursor)+1);
 	} else {
@@ -2279,18 +2280,19 @@ case OP_Compare: {
 		assert(p2>0 && p2+n<=(p->nMem+1 - p->nCursor)+1);
 	}
 #endif /* SQLITE_DEBUG */
-	for(i=0; i<n; i++) {
+	for(int i = 0; i < n; i++) {
 		idx = aPermute ? aPermute[i] : i;
 		assert(memIsValid(&aMem[p1+idx]));
 		assert(memIsValid(&aMem[p2+idx]));
 		REGISTER_TRACE(p1+idx, &aMem[p1+idx]);
 		REGISTER_TRACE(p2+idx, &aMem[p2+idx]);
-		assert(i<pKeyInfo->nField);
-		pColl = pKeyInfo->aColl[i];
-		bRev = pKeyInfo->aSortOrder[i];
-		iCompare = sqlite3MemCompare(&aMem[p1+idx], &aMem[p2+idx], pColl);
+		assert(i < (int)def->part_count);
+		struct coll *coll = def->parts[i].coll;
+		bool is_rev = def->parts[i].sort_order == SORT_ORDER_DESC;
+		iCompare = sqlite3MemCompare(&aMem[p1+idx], &aMem[p2+idx], coll);
 		if (iCompare) {
-			if (bRev) iCompare = -iCompare;
+			if (is_rev)
+				iCompare = -iCompare;
 			break;
 		}
 	}
@@ -3119,8 +3121,8 @@ case OP_SetCookie: {
  * values need not be contiguous but all P1 values should be
  * small integers. It is an error for P1 to be negative.
  *
- * The P4 value may be a pointer to a KeyInfo structure.
- * If it is a pointer to a KeyInfo structure, then said structure
+ * The P4 value may be a pointer to a key_def structure.
+ * If it is a pointer to a key_def structure, then said structure
  * defines the content and collatining sequence of the index
  * being opened. Otherwise, P4 is NULL.
  *
@@ -3208,7 +3210,7 @@ case OP_OpenWrite:
 	pBtCur->index = index;
 	pBtCur->eState = CURSOR_INVALID;
 	/* Key info still contains sorter order and collation. */
-	pCur->pKeyInfo = pOp->p4.pKeyInfo;
+	pCur->key_def = index->def->key_def;
 
 open_cursor_set_hints:
 	assert(OPFLAG_BULKCSR==BTREE_BULKLOAD);
@@ -3223,8 +3225,12 @@ open_cursor_set_hints:
 	break;
 }
 
-/* Opcode: OpenTEphemeral P1 P2 * * *
- * Synopsis: nColumn = P2
+/**
+ * Opcode: OpenTEphemeral P1 P2 * P4 *
+ * Synopsis:
+ * @param P1 index of new cursor to be created
+ * @param P2 number of columns in a new table
+ * @param P4 key def for new table
  *
  * This opcode creates Tarantool's ephemeral table and sets cursor P1 to it.
  */
@@ -3233,21 +3239,21 @@ case OP_OpenTEphemeral: {
 	BtCursor *pBtCur;
 	assert(pOp->p1 >= 0);
 	assert(pOp->p2 > 0);
-	assert(pOp->p4.pKeyInfo != 0);
-	assert(pOp->p4type == P4_KEYINFO);
+	assert(pOp->p4.key_def != NULL);
+	assert(pOp->p4type == P4_KEYDEF);
 
 	pCx = allocateCursor(p, pOp->p1, pOp->p2, CURTYPE_TARANTOOL);
 	if (pCx == 0) goto no_mem;
 	pCx->nullRow = 1;
 
-	pCx->pKeyInfo  = pOp->p4.pKeyInfo;
+	pCx->key_def  = pOp->p4.key_def;
 	pBtCur = pCx->uc.pCursor;
 	/* Ephemeral spaces don't have space_id */
 	pBtCur->eState = CURSOR_INVALID;
 	pBtCur->curFlags = BTCF_TEphemCursor;
 
 	rc = tarantoolSqlite3EphemeralCreate(pCx->uc.pCursor, pOp->p2,
-					     pOp->p4.pKeyInfo->aColl[0]);
+					     pCx->key_def);
 	if (rc) goto abort_due_to_error;
 	break;
 }
@@ -3269,9 +3275,8 @@ case OP_SorterOpen: {
 	assert(pOp->p2>=0);
 	pCx = allocateCursor(p, pOp->p1, pOp->p2, CURTYPE_SORTER);
 	if (pCx==0) goto no_mem;
-	pCx->pKeyInfo = pOp->p4.pKeyInfo;
-	assert(pCx->pKeyInfo->db==db);
-	rc = sqlite3VdbeSorterInit(db, pOp->p3, pCx);
+	pCx->key_def = pOp->p4.key_def;
+	rc = sqlite3VdbeSorterInit(db, pCx);
 	if (rc) goto abort_due_to_error;
 	break;
 }
@@ -3547,7 +3552,7 @@ case OP_SeekGT: {       /* jump, in3 */
 	nField = pOp->p4.i;
 	assert(pOp->p4type==P4_INT32);
 	assert(nField>0);
-	r.pKeyInfo = pC->pKeyInfo;
+	r.key_def = pC->key_def;
 	r.nField = (u16)nField;
 
 	if (reg_ipk > 0) {
@@ -3699,7 +3704,7 @@ case OP_Found: {        /* jump, in3 */
 	assert(pC->eCurType==CURTYPE_TARANTOOL);
 	assert(pC->uc.pCursor!=0);
 	if (pOp->p4.i>0) {
-		r.pKeyInfo = pC->pKeyInfo;
+		r.key_def = pC->key_def;
 		r.nField = (u16)pOp->p4.i;
 		r.aMem = pIn3;
 #ifdef SQLITE_DEBUG
@@ -3712,11 +3717,12 @@ case OP_Found: {        /* jump, in3 */
 		pIdxKey = &r;
 		pFree = 0;
 	} else {
-		pFree = pIdxKey = sqlite3VdbeAllocUnpackedRecord(pC->pKeyInfo);
+		pFree = pIdxKey = sqlite3VdbeAllocUnpackedRecord(db, pC->key_def);
 		if (pIdxKey==0) goto no_mem;
 		assert(pIn3->flags & MEM_Blob );
 		(void)ExpandBlob(pIn3);
-		sqlite3VdbeRecordUnpackMsgpack(pC->pKeyInfo, pIn3->n, pIn3->z, pIdxKey);
+		sqlite3VdbeRecordUnpackMsgpack(pC->key_def,
+					       pIn3->z, pIdxKey);
 	}
 	pIdxKey->default_rc = 0;
 	pIdxKey->opcode = pOp->opcode;
@@ -4492,7 +4498,7 @@ case OP_IdxDelete: {
 	pCrsr = pC->uc.pCursor;
 	assert(pCrsr!=0);
 	assert(pOp->p5==0);
-	r.pKeyInfo = pC->pKeyInfo;
+	r.key_def = pC->key_def;
 	r.nField = (u16)pOp->p3;
 	r.default_rc = 0;
 	r.aMem = &aMem[pOp->p2];
@@ -4576,7 +4582,7 @@ case OP_IdxGE:  {       /* jump */
 	assert(pC->deferredMoveto==0);
 	assert(pOp->p5==0 || pOp->p5==1);
 	assert(pOp->p4type==P4_INT32);
-	r.pKeyInfo = pC->pKeyInfo;
+	r.key_def = pC->key_def;
 	r.nField = (u16)pOp->p4.i;
 	if (pOp->opcode<OP_IdxLT) {
 		assert(pOp->opcode==OP_IdxLE || pOp->opcode==OP_IdxGT);
@@ -4590,7 +4596,7 @@ case OP_IdxGE:  {       /* jump */
 	{ int i; for(i=0; i<r.nField; i++) assert(memIsValid(&r.aMem[i])); }
 #endif
 	res = 0;  /* Not needed.  Only used to silence a warning. */
-	rc = sqlite3VdbeIdxKeyCompare(db, pC, &r, &res);
+	rc = sqlite3VdbeIdxKeyCompare(pC, &r, &res);
 	assert((OP_IdxLE&1)==(OP_IdxLT&1) && (OP_IdxGE&1)==(OP_IdxGT&1));
 	if ((pOp->opcode&1)==(OP_IdxLT&1)) {
 		assert(pOp->opcode==OP_IdxLE || pOp->opcode==OP_IdxLT);
diff --git a/src/box/sql/vdbe.h b/src/box/sql/vdbe.h
index e244606..645d1cd 100644
--- a/src/box/sql/vdbe.h
+++ b/src/box/sql/vdbe.h
@@ -77,7 +77,6 @@ struct VdbeOp {
 		struct coll *pColl;	/* Used when p4type is P4_COLLSEQ */
 		Mem *pMem;	/* Used when p4type is P4_MEM */
 		bool b;         /* Used when p4type is P4_BOOL */
-		KeyInfo *pKeyInfo;	/* Used when p4type is P4_KEYINFO */
 		int *ai;	/* Used when p4type is P4_INTARRAY */
 		SubProgram *pProgram;	/* Used when p4type is P4_SUBPROGRAM */
 		Index *pIndex;	/* Used when p4type is P4_INDEX */
@@ -85,6 +84,8 @@ struct VdbeOp {
 		Expr *pExpr;	/* Used when p4type is P4_EXPR */
 #endif
 		int (*xAdvance) (BtCursor *, int *);
+		/* Used when p4type is P4_KEYDEF. */
+		struct key_def *key_def;
 	} p4;
 #ifdef SQLITE_ENABLE_EXPLAIN_COMMENTS
 	char *zComment;		/* Comment to improve readability */
@@ -131,7 +132,6 @@ typedef struct VdbeOpList VdbeOpList;
 #define P4_STATIC   (-2)	/* Pointer to a static string */
 #define P4_COLLSEQ  (-3)	/* P4 is a pointer to a CollSeq structure */
 #define P4_FUNCDEF  (-4)	/* P4 is a pointer to a FuncDef structure */
-#define P4_KEYINFO  (-5)	/* P4 is a pointer to a KeyInfo structure */
 #define P4_EXPR     (-6)	/* P4 is a pointer to an Expr tree */
 #define P4_MEM      (-7)	/* P4 is a pointer to a Mem*    structure */
 #define P4_TRANSIENT  0		/* P4 is a pointer to a transient string */
@@ -147,7 +147,6 @@ typedef struct VdbeOpList VdbeOpList;
 #define P4_PTR      (-18)	/* P4 is a generic pointer */
 #define P4_KEYDEF   (-19)       /* P4 is a pointer to key_def structure. */
 
-
 /* Error message codes for OP_Halt */
 #define P5_ConstraintNotNull 1
 #define P5_ConstraintUnique  2
@@ -226,7 +225,16 @@ int sqlite3VdbeChangeToNoop(Vdbe *, int addr);
 int sqlite3VdbeDeletePriorOpcode(Vdbe *, u8 op);
 void sqlite3VdbeChangeP4(Vdbe *, int addr, const char *zP4, int N);
 void sqlite3VdbeAppendP4(Vdbe *, void *pP4, int p4type);
-void sqlite3VdbeSetP4KeyInfo(Parse *, Index *);
+
+/**
+ * Set the P4 on the most recently added opcode to the key_def for the
+ * index given.
+ * @param Parse context, for error reporting.
+ * @param Index to get key_def from.
+ */
+void
+sql_vdbe_set_p4_key_def(struct Parse *parse, struct Index *index);
+
 VdbeOp *sqlite3VdbeGetOp(Vdbe *, int);
 int sqlite3VdbeMakeLabel(Vdbe *);
 void sqlite3VdbeRunOnlyOnce(Vdbe *);
@@ -257,14 +265,27 @@ char *sqlite3VdbeExpandSql(Vdbe *, const char *);
 #endif
 int sqlite3MemCompare(const Mem *, const Mem *, const struct coll *);
 
-void sqlite3VdbeRecordUnpackMsgpack(KeyInfo *, int, const void *,
-				    UnpackedRecord *);
-int sqlite3VdbeRecordCompare(int, const void *, UnpackedRecord *);
-int sqlite3VdbeRecordCompareWithSkip(int, const void *, UnpackedRecord *, int);
-UnpackedRecord *sqlite3VdbeAllocUnpackedRecord(KeyInfo *);
+/**
+ * Perform unpacking of provided message pack.
+ *
+ * @param key_def Information about the record format
+ * @param key The binary record
+ * @param dest Populate this structure before returning.
+ */
+void sqlite3VdbeRecordUnpackMsgpack(struct key_def *key_def,
+				    const void *msgpack,
+				    struct UnpackedRecord *dest);
+
+int sqlite3VdbeRecordCompare(struct sqlite3 *db, int key_count,
+			     const void *key1, UnpackedRecord *key2);
+int sqlite3VdbeRecordCompareWithSkip(struct sqlite3 *db,
+				     int key_count, const void *key1,
+				     struct UnpackedRecord *key2, bool is_skip);
+UnpackedRecord *sqlite3VdbeAllocUnpackedRecord(struct sqlite3 *,
+					       struct key_def *);
 int sql_vdbe_mem_alloc_region(Mem *, uint32_t);
 
-typedef int (*RecordCompare) (int, const void *, UnpackedRecord *);
+typedef int (*RecordCompare) (const void *, UnpackedRecord *);
 RecordCompare sqlite3VdbeFindCompare(UnpackedRecord *);
 
 #ifndef SQLITE_OMIT_TRIGGER
diff --git a/src/box/sql/vdbeInt.h b/src/box/sql/vdbeInt.h
index ab9147c..c9ee64e 100644
--- a/src/box/sql/vdbeInt.h
+++ b/src/box/sql/vdbeInt.h
@@ -110,7 +110,8 @@ struct VdbeCursor {
 		int pseudoTableReg;	/* CURTYPE_PSEUDO. Reg holding content. */
 		VdbeSorter *pSorter;	/* CURTYPE_SORTER. Sorter object */
 	} uc;
-	KeyInfo *pKeyInfo;	/* Info about index keys needed by index cursors */
+	/* Info about keys needed by index cursors. */
+	struct key_def *key_def;
 	i16 nField;		/* Number of fields in the header */
 	u16 nHdrParsed;		/* Number of header fields parsed so far */
 	const u8 *aRow;		/* Data for the current row, if all on one page */
@@ -451,7 +452,6 @@ struct PreUpdate {
 	VdbeCursor *pCsr;	/* Cursor to read old values from */
 	int op;			/* One of SQLITE_INSERT, UPDATE, DELETE */
 	u8 *aRecord;		/* old.* database record */
-	KeyInfo keyinfo;
 	UnpackedRecord *pUnpacked;	/* Unpacked version of aRecord[] */
 	UnpackedRecord *pNewUnpacked;	/* Unpacked version of new.* record */
 	int iNewReg;		/* Register for new.* values */
@@ -479,7 +479,24 @@ u32 sqlite3VdbeSerialPut(unsigned char *, Mem *, u32);
 u32 sqlite3VdbeSerialGet(const unsigned char *, u32, Mem *);
 void sqlite3VdbeDeleteAuxData(sqlite3 *, AuxData **, int, int);
 
-int sqlite3VdbeIdxKeyCompare(sqlite3 *, VdbeCursor *, UnpackedRecord *, int *);
+/**
+ * Compare the key of the index entry that cursor vdbe_cursor is
+ * pointing to against the key string in unpacked.  Write into
+ * *res a number that is negative, zero, or positive if
+ * vdbe_cursor is less than, equal to, or greater than unpacked.
+ * Return SQLITE_OK on success.
+ *
+ * @param vdbe_cursor Cursor, which points to tuple to compare.
+ * @param unpacked Unpacked version of key.
+ * @param[out] Write the comparison result here.
+ *
+ * @retval Error status code.
+ */
+int
+sqlite3VdbeIdxKeyCompare(struct VdbeCursor *vdbe_cursor,
+			 struct UnpackedRecord *unpacked,
+			 int * res);
+
 int sqlite3VdbeExec(Vdbe *);
 int sqlite3VdbeList(Vdbe *);
 int
@@ -521,13 +538,9 @@ int sqlite3VdbeMemClearAndResize(Mem * pMem, int n);
 int sqlite3VdbeCloseStatement(Vdbe *, int);
 void sqlite3VdbeFrameDelete(VdbeFrame *);
 int sqlite3VdbeFrameRestore(VdbeFrame *);
-#ifdef SQLITE_ENABLE_PREUPDATE_HOOK
-void sqlite3VdbePreUpdateHook(Vdbe *, VdbeCursor *, int, const char *, Table *,
-			      i64, int);
-#endif
 int sqlite3VdbeTransferError(Vdbe * p);
 
-int sqlite3VdbeSorterInit(sqlite3 *, int, VdbeCursor *);
+int sqlite3VdbeSorterInit(struct sqlite3 *db, struct VdbeCursor *cursor);
 void sqlite3VdbeSorterReset(sqlite3 *, VdbeSorter *);
 void sqlite3VdbeSorterClose(sqlite3 *, VdbeCursor *);
 int sqlite3VdbeSorterRowkey(const VdbeCursor *, Mem *);
@@ -564,10 +577,28 @@ int sqlite3VdbeMemExpandBlob(Mem *);
 
 i64 sqlite3VdbeMsgpackRecordLen(Mem * pMem, u32 n);
 u32 sqlite3VdbeMsgpackRecordPut(u8 * pBuf, Mem * pMem, u32 n);
-int sqlite3VdbeCompareMsgpack(const char **pKey1,
-			      UnpackedRecord * pUnpacked, int iKey2);
-int sqlite3VdbeRecordCompareMsgpack(int nKey1, const void *pKey1,
-				    UnpackedRecord * pPKey2);
+/**
+ * Perform comparison of two keys: one is packed and one is not.
+ *
+ * @param key1 Pointer to pointer to first key.
+ * @param unpacked Pointer to unpacked tuple.
+ * @param key2_idx index of key in umpacked record to compare.
+ *
+ * @retval +1 if key1 > pUnpacked[iKey2], -1 ptherwise.
+ */
+int sqlite3VdbeCompareMsgpack(const char **key1,
+			      struct UnpackedRecord *unpacked, int key2_idx);
+
+/**
+ * Perform comparison of two tuples: unpacked (key1) and packed (key2)
+ *
+ * @param key1 Packed key.
+ * @param unpacked Unpacked key.
+ *
+ * @retval +1 if key1 > unpacked, -1 otherwise.
+ */
+int sqlite3VdbeRecordCompareMsgpack(const void *key1,
+				    struct UnpackedRecord *key2);
 u32 sqlite3VdbeMsgpackGet(const unsigned char *buf, Mem * pMem);
 
 #endif				/* !defined(SQLITE_VDBEINT_H) */
diff --git a/src/box/sql/vdbeapi.c b/src/box/sql/vdbeapi.c
index 6e41859..c781fd4 100644
--- a/src/box/sql/vdbeapi.c
+++ b/src/box/sql/vdbeapi.c
@@ -1581,103 +1581,6 @@ sqlite3_expanded_sql(sqlite3_stmt * pStmt)
 #endif
 }
 
-#ifdef SQLITE_ENABLE_PREUPDATE_HOOK
-/*
- * Allocate and populate an UnpackedRecord structure based on the serialized
- * record in nKey/pKey. Return a pointer to the new UnpackedRecord structure
- * if successful, or a NULL pointer if an OOM error is encountered.
- */
-static UnpackedRecord *
-vdbeUnpackRecord(KeyInfo * pKeyInfo, int nKey, const void *pKey)
-{
-	UnpackedRecord *pRet;	/* Return value */
-
-	pRet = sqlite3VdbeAllocUnpackedRecord(pKeyInfo);
-	if (pRet) {
-		memset(pRet->aMem, 0, sizeof(Mem) * (pKeyInfo->nField + 1));
-		sqlite3VdbeRecordUnpack(pKeyInfo, nKey, pKey, pRet);
-	}
-	return pRet;
-}
-
-/*
- * This function is called from within a pre-update callback to retrieve
- * a field of the row currently being updated or deleted.
- */
-int
-sqlite3_preupdate_old(sqlite3 * db, int iIdx, sqlite3_value ** ppValue)
-{
-	PreUpdate *p = db->pPreUpdate;
-	int rc = SQLITE_OK;
-
-	/* Test that this call is being made from within an SQLITE_DELETE or
-	 * SQLITE_UPDATE pre-update callback, and that iIdx is within range.
-	 */
-	if (!p || p->op == SQLITE_INSERT) {
-		rc = SQLITE_MISUSE_BKPT;
-		goto preupdate_old_out;
-	}
-	if (iIdx >= p->pCsr->nField || iIdx < 0) {
-		rc = SQLITE_RANGE;
-		goto preupdate_old_out;
-	}
-
-	/* If the old.* record has not yet been loaded into memory, do so now. */
-	if (p->pUnpacked == 0) {
-		u32 nRec;
-		u8 *aRec;
-
-		nRec = sqlite3BtreePayloadSize(p->pCsr->uc.pCursor);
-		aRec = sqlite3DbMallocRaw(db, nRec);
-		if (!aRec)
-			goto preupdate_old_out;
-		rc = sqlite3BtreePayload(p->pCsr->uc.pCursor, 0, nRec, aRec);
-		if (rc == SQLITE_OK) {
-			p->pUnpacked =
-			    vdbeUnpackRecord(&p->keyinfo, nRec, aRec);
-			if (!p->pUnpacked)
-				rc = SQLITE_NOMEM;
-		}
-		if (rc != SQLITE_OK) {
-			sqlite3DbFree(db, aRec);
-			goto preupdate_old_out;
-		}
-		p->aRecord = aRec;
-	}
-
-	if (iIdx >= p->pUnpacked->nField) {
-		*ppValue = (sqlite3_value *) columnNullValue();
-	} else {
-		Mem *pMem = *ppValue = &p->pUnpacked->aMem[iIdx];
-		*ppValue = &p->pUnpacked->aMem[iIdx];
-		if (iIdx == p->pTab->iPKey) {
-			sqlite3VdbeMemSetInt64(pMem, p->iKey1);
-		} else if (p->pTab->aCol[iIdx].affinity == SQLITE_AFF_REAL) {
-			if (pMem->flags & MEM_Int) {
-				sqlite3VdbeMemRealify(pMem);
-			}
-		}
-	}
-
- preupdate_old_out:
-	sqlite3Error(db, rc);
-	return sqlite3ApiExit(db, rc);
-}
-#endif				/* SQLITE_ENABLE_PREUPDATE_HOOK */
-
-#ifdef SQLITE_ENABLE_PREUPDATE_HOOK
-/*
- * This function is called from within a pre-update callback to retrieve
- * the number of columns in the row being updated, deleted or inserted.
- */
-int
-sqlite3_preupdate_count(sqlite3 * db)
-{
-	PreUpdate *p = db->pPreUpdate;
-	return (p ? p->keyinfo.nField : 0);
-}
-#endif				/* SQLITE_ENABLE_PREUPDATE_HOOK */
-
 #ifdef SQLITE_ENABLE_PREUPDATE_HOOK
 /*
  * This function is designed to be called from within a pre-update callback
@@ -1698,92 +1601,6 @@ sqlite3_preupdate_depth(sqlite3 * db)
 }
 #endif				/* SQLITE_ENABLE_PREUPDATE_HOOK */
 
-#ifdef SQLITE_ENABLE_PREUPDATE_HOOK
-/*
- * This function is called from within a pre-update callback to retrieve
- * a field of the row currently being updated or inserted.
- */
-int
-sqlite3_preupdate_new(sqlite3 * db, int iIdx, sqlite3_value ** ppValue)
-{
-	PreUpdate *p = db->pPreUpdate;
-	int rc = SQLITE_OK;
-	Mem *pMem;
-
-	if (!p || p->op == SQLITE_DELETE) {
-		rc = SQLITE_MISUSE_BKPT;
-		goto preupdate_new_out;
-	}
-	if (iIdx >= p->pCsr->nField || iIdx < 0) {
-		rc = SQLITE_RANGE;
-		goto preupdate_new_out;
-	}
-
-	if (p->op == SQLITE_INSERT) {
-		/* For an INSERT, memory cell p->iNewReg contains the serialized record
-		 * that is being inserted. Deserialize it.
-		 */
-		UnpackedRecord *pUnpack = p->pNewUnpacked;
-		if (!pUnpack) {
-			Mem *pData = &p->v->aMem[p->iNewReg];
-			rc = ExpandBlob(pData);
-			if (rc != SQLITE_OK)
-				goto preupdate_new_out;
-			pUnpack =
-			    vdbeUnpackRecord(&p->keyinfo, pData->n, pData->z);
-			if (!pUnpack) {
-				rc = SQLITE_NOMEM;
-				goto preupdate_new_out;
-			}
-			p->pNewUnpacked = pUnpack;
-		}
-		if (iIdx >= pUnpack->nField) {
-			pMem = (sqlite3_value *) columnNullValue();
-		} else {
-			pMem = &pUnpack->aMem[iIdx];
-			if (iIdx == p->pTab->iPKey) {
-				sqlite3VdbeMemSetInt64(pMem, p->iKey2);
-			}
-		}
-	} else {
-		/* For an UPDATE, memory cell (p->iNewReg+1+iIdx) contains the required
-		 * value. Make a copy of the cell contents and return a pointer to it.
-		 * It is not safe to return a pointer to the memory cell itself as the
-		 * caller may modify the value text encoding.
-		 */
-		assert(p->op == SQLITE_UPDATE);
-		if (!p->aNew) {
-			p->aNew =
-			    (Mem *) sqlite3DbMallocZero(db,
-							sizeof(Mem) *
-							p->pCsr->nField);
-			if (!p->aNew) {
-				rc = SQLITE_NOMEM;
-				goto preupdate_new_out;
-			}
-		}
-		assert(iIdx >= 0 && iIdx < p->pCsr->nField);
-		pMem = &p->aNew[iIdx];
-		if (pMem->flags == 0) {
-			if (iIdx == p->pTab->iPKey) {
-				sqlite3VdbeMemSetInt64(pMem, p->iKey2);
-			} else {
-				rc = sqlite3VdbeMemCopy(pMem,
-							&p->v->aMem[p->iNewReg +
-								    1 + iIdx]);
-				if (rc != SQLITE_OK)
-					goto preupdate_new_out;
-			}
-		}
-	}
-	*ppValue = pMem;
-
- preupdate_new_out:
-	sqlite3Error(db, rc);
-	return sqlite3ApiExit(db, rc);
-}
-#endif				/* SQLITE_ENABLE_PREUPDATE_HOOK */
-
 #ifdef SQLITE_ENABLE_STMT_SCANSTATUS
 /*
  * Return status data for a single loop within query pStmt.
diff --git a/src/box/sql/vdbeaux.c b/src/box/sql/vdbeaux.c
index b3998ea..e7dc4f6 100644
--- a/src/box/sql/vdbeaux.c
+++ b/src/box/sql/vdbeaux.c
@@ -948,11 +948,9 @@ freeP4(sqlite3 * db, int p4type, void *p4)
 			sqlite3DbFree(db, p4);
 			break;
 		}
-	case P4_KEYINFO:{
-			if (db->pnBytesFreed == 0)
-				sqlite3KeyInfoUnref((KeyInfo *) p4);
-			break;
-		}
+	case P4_KEYDEF:
+		key_def_delete(p4);
+		break;
 #ifdef SQLITE_ENABLE_CURSOR_HINTS
 	case P4_EXPR:{
 			sqlite3ExprDelete(db, (Expr *) p4);
@@ -1140,20 +1138,19 @@ sqlite3VdbeAppendP4(Vdbe * p, void *pP4, int n)
 	}
 }
 
-/*
- * Set the P4 on the most recently added opcode to the KeyInfo for the
- * index given.
- */
 void
-sqlite3VdbeSetP4KeyInfo(Parse * pParse, Index * pIdx)
-{
-	Vdbe *v = pParse->pVdbe;
-	KeyInfo *pKeyInfo;
-	assert(v != 0);
-	assert(pIdx != 0);
-	pKeyInfo = sqlite3KeyInfoOfIndex(pParse, pParse->db, pIdx);
-	if (pKeyInfo)
-		sqlite3VdbeAppendP4(v, pKeyInfo, P4_KEYINFO);
+sql_vdbe_set_p4_key_def(struct Parse *parse, struct Index *idx)
+{
+	struct Vdbe *v = parse->pVdbe;
+	assert(v != NULL);
+	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 *index = index_find(space, index_id);
+	assert(index != NULL);
+	sqlite3VdbeAppendP4(v, key_def_dup(index->def->key_def), P4_KEYDEF);
 }
 
 #ifdef SQLITE_ENABLE_EXPLAIN_COMMENTS
@@ -1512,26 +1509,28 @@ displayP4(Op * pOp, char *zTemp, int nTemp)
 	assert(nTemp >= 20);
 	sqlite3StrAccumInit(&x, 0, zTemp, nTemp, 0);
 	switch (pOp->p4type) {
-	case P4_KEYINFO:{
-			int j;
-			KeyInfo *pKeyInfo;
+	case P4_KEYDEF:{
+			struct key_def *def;
 
-			if (pOp->p4.pKeyInfo == NULL) {
+			if (pOp->p4.key_def == NULL) {
 				sqlite3XPrintf(&x, "k[NULL]");
 			} else {
-				pKeyInfo = pOp->p4.pKeyInfo;
-				assert(pKeyInfo->aSortOrder != 0);
-				sqlite3XPrintf(&x, "k(%d", pKeyInfo->nField);
-				for (j = 0; j < pKeyInfo->nField; j++) {
-					struct coll *pColl = pKeyInfo->aColl[j];
-					const char *zColl =
-					    pColl ? pColl->name : "";
-					if (strcmp(zColl, "BINARY") == 0)
-						zColl = "B";
+				def = pOp->p4.key_def;
+				sqlite3XPrintf(&x, "k(%d", def->part_count);
+				for (int j = 0; j < (int)def->part_count; j++) {
+					struct coll *coll = def->parts[j].coll;
+					const char *coll_str =
+					    coll != NULL ? coll->name : "";
+					if (strcmp(coll_str, "BINARY") == 0)
+						coll_str = "B";
+					const char *sort_order = "";
+					if (def->parts[j].sort_order ==
+					    SORT_ORDER_DESC) {
+						sort_order = "-";
+					}
 					sqlite3XPrintf(&x, ",%s%s",
-						       pKeyInfo->
-						       aSortOrder[j] ? "-" : "",
-						       zColl);
+						       sort_order,
+						       coll_str);
 				}
 				sqlite3StrAccumAppend(&x, ")", 1);
 			}
@@ -3491,7 +3490,7 @@ sqlite3VdbeSerialGet(const unsigned char *buf,	/* Buffer to deserialize from */
 /*
  * This routine is used to allocate sufficient space for an UnpackedRecord
  * structure large enough to be used with sqlite3VdbeRecordUnpack() if
- * the first argument is a pointer to KeyInfo structure pKeyInfo.
+ * the first argument is a pointer to key_def structure.
  *
  * The space is either allocated using sqlite3DbMallocRaw() or from within
  * the unaligned buffer passed via the second and third arguments (presumably
@@ -3503,20 +3502,19 @@ sqlite3VdbeSerialGet(const unsigned char *buf,	/* Buffer to deserialize from */
  * If an OOM error occurs, NULL is returned.
  */
 UnpackedRecord *
-sqlite3VdbeAllocUnpackedRecord(KeyInfo * pKeyInfo)
+sqlite3VdbeAllocUnpackedRecord(struct sqlite3 *db, struct key_def *key_def)
 {
 	UnpackedRecord *p;	/* Unpacked record to return */
 	int nByte;		/* Number of bytes required for *p */
 	nByte =
-	    ROUND8(sizeof(UnpackedRecord)) + sizeof(Mem) * (pKeyInfo->nField +
+	    ROUND8(sizeof(UnpackedRecord)) + sizeof(Mem) * (key_def->part_count +
 							    1);
-	p = (UnpackedRecord *) sqlite3DbMallocRaw(pKeyInfo->db, nByte);
+	p = (UnpackedRecord *) sqlite3DbMallocRaw(db, nByte);
 	if (!p)
 		return 0;
 	p->aMem = (Mem *) & ((char *)p)[ROUND8(sizeof(UnpackedRecord))];
-	assert(pKeyInfo->aSortOrder != 0);
-	p->pKeyInfo = pKeyInfo;
-	p->nField = pKeyInfo->nField + 1;
+	p->key_def = key_def;
+	p->nField = key_def->part_count + 1;
 	return p;
 }
 
@@ -3545,7 +3543,8 @@ sql_vdbe_mem_alloc_region(Mem *vdbe_mem, uint32_t size)
  * Return false if there is a disagreement.
  */
 static int
-vdbeRecordCompareDebug(int nKey1, const void *pKey1,	/* Left key */
+vdbeRecordCompareDebug(struct sqlite3 *db,
+		       int nKey1, const void *pKey1,	/* Left key */
 		       const UnpackedRecord * pPKey2,	/* Right key */
 		       int desiredResult)		/* Correct answer */
 {
@@ -3555,13 +3554,11 @@ vdbeRecordCompareDebug(int nKey1, const void *pKey1,	/* Left key */
 	int i = 0;
 	int rc = 0;
 	const unsigned char *aKey1 = (const unsigned char *)pKey1;
-	KeyInfo *pKeyInfo;
+	struct key_def *key_def;
 	Mem mem1;
 
-	pKeyInfo = pPKey2->pKeyInfo;
-	if (pKeyInfo->db == 0)
-		return 1;
-	mem1.db = pKeyInfo->db;
+	key_def = pPKey2->key_def;
+	mem1.db = db;
 	/* mem1.flags = 0;  // Will be initialized by sqlite3VdbeSerialGet() */
 	VVA_ONLY(mem1.szMalloc = 0;
 	    )
@@ -3579,10 +3576,7 @@ vdbeRecordCompareDebug(int nKey1, const void *pKey1,	/* Left key */
 	if (szHdr1 > 98307)
 		return SQLITE_CORRUPT;
 	d1 = szHdr1;
-	assert(pKeyInfo->nField + pKeyInfo->nXField >= pPKey2->nField
-	       || CORRUPT_DB);
-	assert(pKeyInfo->aSortOrder != 0);
-	assert(pKeyInfo->nField > 0);
+	assert(key_def->part_count > 0);
 	assert(idx1 <= szHdr1 || CORRUPT_DB);
 	do {
 		u32 serial_type1;
@@ -3609,10 +3603,10 @@ vdbeRecordCompareDebug(int nKey1, const void *pKey1,	/* Left key */
 		/* Do the comparison
 		 */
 		rc = sqlite3MemCompare(&mem1, &pPKey2->aMem[i],
-				       pKeyInfo->aColl[i]);
+				       key_def->parts[i].coll);
 		if (rc != 0) {
 			assert(mem1.szMalloc == 0);	/* See comment below */
-			if (pKeyInfo->aSortOrder[i]) {
+			if (key_def->parts[i].sort_order != SORT_ORDER_ASC) {
 				rc = -rc;	/* Invert the result for DESC sort order. */
 			}
 			goto debugCompareEnd;
@@ -3641,7 +3635,7 @@ vdbeRecordCompareDebug(int nKey1, const void *pKey1,	/* Left key */
 		return 1;
 	if (CORRUPT_DB)
 		return 1;
-	if (pKeyInfo->db->mallocFailed)
+	if (db->mallocFailed)
 		return 1;
 	return 0;
 }
@@ -3917,12 +3911,13 @@ vdbeRecordDecodeInt(u32 serial_type, const u8 * aKey)
  * If database corruption is discovered, set pPKey2->errCode to
  * SQLITE_CORRUPT and return 0. If an OOM error is encountered,
  * pPKey2->errCode is set to SQLITE_NOMEM and, if it is not NULL, the
- * malloc-failed flag set on database handle (pPKey2->pKeyInfo->db).
+ * malloc-failed flag set on database handle.
  */
 int
-sqlite3VdbeRecordCompareWithSkip(int nKey1, const void *pKey1,	/* Left key */
+sqlite3VdbeRecordCompareWithSkip(struct sqlite3 *db,
+				 int nKey1, const void *pKey1,	/* Left key */
 				 UnpackedRecord * pPKey2,	/* Right key */
-				 int bSkip)			/* If true, skip the first field */
+				 bool bSkip)			/* If true, skip the first field */
 {
 	u32 d1;			/* Offset into aKey[] of next data element */
 	int i;			/* Index of next field to compare */
@@ -3930,7 +3925,7 @@ sqlite3VdbeRecordCompareWithSkip(int nKey1, const void *pKey1,	/* Left key */
 	u32 idx1;		/* Offset of first type in header */
 	int rc = 0;		/* Return value */
 	Mem *pRhs = pPKey2->aMem;	/* Next field of pPKey2 to compare */
-	KeyInfo *pKeyInfo = pPKey2->pKeyInfo;
+	struct key_def *key_def = pPKey2->key_def;
 	const unsigned char *aKey1 = (const unsigned char *)pKey1;
 	Mem mem1;
 
@@ -3957,10 +3952,7 @@ sqlite3VdbeRecordCompareWithSkip(int nKey1, const void *pKey1,	/* Left key */
 
 	VVA_ONLY(mem1.szMalloc = 0;
 	    )			/* Only needed by assert() statements */
-	    assert(pPKey2->pKeyInfo->nField + pPKey2->pKeyInfo->nXField >=
-		   pPKey2->nField || CORRUPT_DB);
-	assert(pPKey2->pKeyInfo->aSortOrder != 0);
-	assert(pPKey2->pKeyInfo->nField > 0);
+	assert(pPKey2->key_def->part_count > 0);
 	assert(idx1 <= szHdr1 || CORRUPT_DB);
 	do {
 		u32 serial_type;
@@ -4035,13 +4027,14 @@ sqlite3VdbeRecordCompareWithSkip(int nKey1, const void *pKey1,	/* Left key */
 					pPKey2->errCode =
 					    (u8) SQLITE_CORRUPT_BKPT;
 					return 0;	/* Corruption */
-				} else if (pKeyInfo->aColl[i]) {
-					mem1.db = pKeyInfo->db;
+				} else if (key_def->parts[i].coll !=NULL) {
+					mem1.db = db;
 					mem1.flags = MEM_Str;
 					mem1.z = (char *)&aKey1[d1];
+					struct coll* coll;
+					coll = key_def->parts[i].coll;
 					rc = vdbeCompareMemString(&mem1, pRhs,
-								  pKeyInfo->
-								  aColl[i],
+								  coll,
 								  &pPKey2->
 								  errCode);
 				} else {
@@ -4091,11 +4084,10 @@ sqlite3VdbeRecordCompareWithSkip(int nKey1, const void *pKey1,	/* Left key */
 		}
 
 		if (rc != 0) {
-			if (pKeyInfo->aSortOrder[i]) {
+			if (key_def->parts[i].sort_order != SORT_ORDER_ASC)
 				rc = -rc;
-			}
 			assert(vdbeRecordCompareDebug
-			       (nKey1, pKey1, pPKey2, rc));
+			       (db, nKey1, pKey1, pPKey2, rc));
 			assert(mem1.szMalloc == 0);	/* See comment below */
 			return rc;
 		}
@@ -4118,18 +4110,19 @@ sqlite3VdbeRecordCompareWithSkip(int nKey1, const void *pKey1,	/* Left key */
 	 * value.
 	 */
 	assert(CORRUPT_DB
-	       || vdbeRecordCompareDebug(nKey1, pKey1, pPKey2,
+	       || vdbeRecordCompareDebug(db, nKey1, pKey1, pPKey2,
 					 pPKey2->default_rc)
-	       || pKeyInfo->db->mallocFailed);
+	       || db->mallocFailed);
 	pPKey2->eqSeen = 1;
 	return pPKey2->default_rc;
 }
 
 int
-sqlite3VdbeRecordCompare(int nKey1, const void *pKey1,	/* Left key */
-			 UnpackedRecord * pPKey2)	/* Right key */
+sqlite3VdbeRecordCompare(struct sqlite3 *db,
+			 int key_count, const void *key1,	/* Left key */
+			 UnpackedRecord *key2)	/* Right key */
 {
-	return sqlite3VdbeRecordCompareWithSkip(nKey1, pKey1, pPKey2, 0);
+	return sqlite3VdbeRecordCompareWithSkip(db, key_count, key1, key2, false);
 }
 
 /*
@@ -4144,27 +4137,19 @@ sqlite3VdbeFindCompare(UnpackedRecord * p)
 	return sqlite3VdbeRecordCompareMsgpack;
 }
 
-/*
- * Compare the key of the index entry that cursor pC is pointing to against
- * the key string in pUnpacked.  Write into *pRes a number
- * that is negative, zero, or positive if pC is less than, equal to,
- * or greater than pUnpacked.  Return SQLITE_OK on success.
- */
 int
-sqlite3VdbeIdxKeyCompare(sqlite3 * db,			/* Database connection */
-			 VdbeCursor * pC,		/* The cursor to compare against */
-			 UnpackedRecord * pUnpacked,	/* Unpacked version of key */
-			 int *res)			/* Write the comparison result here */
-{
-	(void)db;
-	BtCursor *pCur;
-
-	assert(pC->eCurType == CURTYPE_TARANTOOL);
-	pCur = pC->uc.pCursor;
-	assert(sqlite3CursorIsValid(pCur));
-	if (pCur->curFlags & BTCF_TaCursor ||
-	    pCur->curFlags & BTCF_TEphemCursor) {
-		return tarantoolSqlite3IdxKeyCompare(pCur, pUnpacked, res);
+sqlite3VdbeIdxKeyCompare(struct VdbeCursor *vdbe_cursor,
+			 struct UnpackedRecord *unpacked,
+			 int *res)
+{
+	struct BtCursor *cursor;
+
+	assert(vdbe_cursor->eCurType == CURTYPE_TARANTOOL);
+	cursor = vdbe_cursor->uc.pCursor;
+	assert(sqlite3CursorIsValid(cursor));
+	if (cursor->curFlags & BTCF_TaCursor ||
+	    cursor->curFlags & BTCF_TEphemCursor) {
+		return tarantoolSqlite3IdxKeyCompare(cursor, unpacked, res);
 	}
 	unreachable();
 	return SQLITE_OK;
@@ -4286,68 +4271,6 @@ vdbeFreeUnpacked(sqlite3 * db, UnpackedRecord * p)
 }
 #endif				/* SQLITE_ENABLE_PREUPDATE_HOOK */
 
-#ifdef SQLITE_ENABLE_PREUPDATE_HOOK
-/*
- * Invoke the pre-update hook. If this is an UPDATE or DELETE pre-update call,
- * then cursor passed as the second argument should point to the row about
- * to be update or deleted. If the application calls sqlite3_preupdate_old(),
- * the required value will be read from the row the cursor points to.
- */
-void
-sqlite3VdbePreUpdateHook(Vdbe * v,		/* Vdbe pre-update hook is invoked by */
-			 VdbeCursor * pCsr,	/* Cursor to grab old.* values from */
-			 int op,		/* SQLITE_INSERT, UPDATE or DELETE */
-			 Table * pTab,		/* Modified table */
-			 i64 iKey1,		/* Initial key value */
-			 int iReg)		/* Register for new.* record */
-{
-	sqlite3 *db = v->db;
-	i64 iKey2;
-	PreUpdate preupdate;
-	const char *zTbl = pTab->zName;
-	static const u8 fakeSortOrder = 0;
-
-	assert(db->pPreUpdate == 0);
-	memset(&preupdate, 0, sizeof(PreUpdate));
-	if (op == SQLITE_UPDATE) {
-		iKey2 = v->aMem[iReg].u.i;
-	} else {
-		iKey2 = iKey1;
-	}
-
-	assert(pCsr->nField == pTab->nCol
-	       || (pCsr->nField == pTab->nCol + 1 && op == SQLITE_DELETE
-		   && iReg == -1)
-	    );
-
-	preupdate.v = v;
-	preupdate.pCsr = pCsr;
-	preupdate.op = op;
-	preupdate.iNewReg = iReg;
-	preupdate.keyinfo.db = db;
-	preupdate.keyinfo.nField = pTab->nCol;
-	preupdate.keyinfo.aSortOrder = (u8 *) & fakeSortOrder;
-	preupdate.iKey1 = iKey1;
-	preupdate.iKey2 = iKey2;
-	preupdate.pTab = pTab;
-
-	db->pPreUpdate = &preupdate;
-	db->xPreUpdateCallback(db->pPreUpdateArg, db, op, zTbl, iKey1,
-			       iKey2);
-	db->pPreUpdate = 0;
-	sqlite3DbFree(db, preupdate.aRecord);
-	vdbeFreeUnpacked(db, preupdate.pUnpacked);
-	vdbeFreeUnpacked(db, preupdate.pNewUnpacked);
-	if (preupdate.aNew) {
-		int i;
-		for (i = 0; i < pCsr->nField; i++) {
-			sqlite3VdbeMemRelease(&preupdate.aNew[i]);
-		}
-		sqlite3DbFree(db, preupdate.aNew);
-	}
-}
-#endif				/* SQLITE_ENABLE_PREUPDATE_HOOK */
-
 i64
 sqlite3VdbeMsgpackRecordLen(Mem * pRec, u32 n)
 {
@@ -4420,11 +4343,11 @@ sqlite3VdbeMsgpackRecordPut(u8 * pBuf, Mem * pRec, u32 n)
 }
 
 int
-sqlite3VdbeCompareMsgpack(const char **pKey1,
-			  UnpackedRecord * pUnpacked, int iKey2)
+sqlite3VdbeCompareMsgpack(const char **key1,
+			  struct UnpackedRecord *unpacked, int key2_idx)
 {
-	const char *aKey1 = *pKey1;
-	Mem *pKey2 = pUnpacked->aMem + iKey2;
+	const char *aKey1 = *key1;
+	Mem *pKey2 = unpacked->aMem + key2_idx;
 	Mem mem1;
 	int rc = 0;
 	switch (mp_typeof(*aKey1)) {
@@ -4493,17 +4416,17 @@ sqlite3VdbeCompareMsgpack(const char **pKey1,
 		}
 	case MP_STR:{
 			if (pKey2->flags & MEM_Str) {
-				KeyInfo *pKeyInfo = pUnpacked->pKeyInfo;
+				struct key_def *key_def = unpacked->key_def;
 				mem1.n = mp_decode_strl(&aKey1);
 				mem1.z = (char *)aKey1;
 				aKey1 += mem1.n;
-				if (pKeyInfo->aColl[iKey2]) {
-					mem1.db = pKeyInfo->db;
+				struct coll *coll;
+				coll = key_def->parts[key2_idx].coll;
+				if (coll != NULL) {
 					mem1.flags = MEM_Str;
 					rc = vdbeCompareMemString(&mem1, pKey2,
-								  pKeyInfo->
-								  aColl[iKey2],
-								  &pUnpacked->
+								  coll,
+								  &unpacked->
 								  errCode);
 				} else {
 					goto do_bin_cmp;
@@ -4548,34 +4471,32 @@ sqlite3VdbeCompareMsgpack(const char **pKey1,
 			goto do_blob;
 		}
 	}
-	*pKey1 = aKey1;
+	*key1 = aKey1;
 	return rc;
 }
 
 int
-sqlite3VdbeRecordCompareMsgpack(int nKey1, const void *pKey1,	/* Left key */
-				UnpackedRecord * pPKey2)	/* Right key */
+sqlite3VdbeRecordCompareMsgpack(const void *key1,
+				struct UnpackedRecord *key2)
 {
-	(void)nKey1;		/* assume valid data */
-
-	int rc = 0;		/* Return value */
-	const char *aKey1 = (const char *)pKey1;
-	u32 i, n = mp_decode_array(&aKey1);
+	int rc = 0;
+	u32 i, n = mp_decode_array((const char**)&key1);
 
-	n = MIN(n, pPKey2->nField);
+	n = MIN(n, key2->nField);
 
 	for (i = 0; i != n; i++) {
-		rc = sqlite3VdbeCompareMsgpack(&aKey1, pPKey2, i);
+		rc = sqlite3VdbeCompareMsgpack((const char**)&key1, key2, i);
 		if (rc != 0) {
-			if (pPKey2->pKeyInfo->aSortOrder[i]) {
+			if (key2->key_def->parts[i].sort_order !=
+			    SORT_ORDER_ASC) {
 				rc = -rc;
 			}
 			return rc;
 		}
 	}
 
-	pPKey2->eqSeen = 1;
-	return pPKey2->default_rc;
+	key2->eqSeen = 1;
+	return key2->default_rc;
 }
 
 u32
@@ -4654,21 +4575,18 @@ sqlite3VdbeMsgpackGet(const unsigned char *buf,	/* Buffer to deserialize from */
 }
 
 void
-sqlite3VdbeRecordUnpackMsgpack(KeyInfo * pKeyInfo,	/* Information about the record format */
-			       int nKey,		/* Size of the binary record */
+sqlite3VdbeRecordUnpackMsgpack(struct key_def *key_def,	/* Information about the record format */
 			       const void *pKey,	/* The binary record */
 			       UnpackedRecord * p)	/* Populate this structure before returning. */
 {
 	uint32_t n;
 	const char *zParse = pKey;
-	(void)nKey;
 	Mem *pMem = p->aMem;
 	n = mp_decode_array(&zParse);
-	n = p->nField = MIN(n, pKeyInfo->nField);
+	n = p->nField = MIN(n, key_def->part_count);
 	p->default_rc = 0;
+	p->key_def = key_def;
 	while (n--) {
-		pMem->db = pKeyInfo->db;
-		/* pMem->flags = 0; // sqlite3VdbeSerialGet() will set this for us */
 		pMem->szMalloc = 0;
 		pMem->z = 0;
 		u32 sz = sqlite3VdbeMsgpackGet((u8 *) zParse, pMem);
diff --git a/src/box/sql/vdbemem.c b/src/box/sql/vdbemem.c
index 9dd254f..93f451d 100644
--- a/src/box/sql/vdbemem.c
+++ b/src/box/sql/vdbemem.c
@@ -1089,14 +1089,13 @@ valueNew(sqlite3 * db, struct ValueNewStat4Ctx *p)
 			int i;	/* Counter variable */
 			int nCol = index_column_count(pIdx);
 
-			nByte =
-			    sizeof(Mem) * nCol + ROUND8(sizeof(UnpackedRecord));
+			nByte = sizeof(Mem) * nCol +
+				ROUND8(sizeof(UnpackedRecord));
 			pRec =
 			    (UnpackedRecord *) sqlite3DbMallocZero(db, nByte);
-			if (pRec) {
-				pRec->pKeyInfo =
-				    sqlite3KeyInfoOfIndex(p->pParse, db, pIdx);
-				if (pRec->pKeyInfo) {
+			if (pRec != NULL) {
+				pRec->key_def = sql_index_key_def(pIdx);
+				if (pRec->key_def != NULL) {
 					pRec->aMem =
 					    (Mem *) ((u8 *) pRec +
 						     ROUND8(sizeof
@@ -1655,13 +1654,12 @@ sqlite3Stat4ProbeFree(UnpackedRecord * pRec)
 {
 	if (pRec) {
 		int i;
-		int nCol = pRec->pKeyInfo->nField;
+		int nCol = pRec->key_def->part_count;
 		Mem *aMem = pRec->aMem;
 		sqlite3 *db = aMem[0].db;
 		for (i = 0; i < nCol; i++) {
 			sqlite3VdbeMemRelease(&aMem[i]);
 		}
-		sqlite3KeyInfoUnref(pRec->pKeyInfo);
 		sqlite3DbFree(db, pRec);
 	}
 }
diff --git a/src/box/sql/vdbesort.c b/src/box/sql/vdbesort.c
index be3cc4c..7c45553 100644
--- a/src/box/sql/vdbesort.c
+++ b/src/box/sql/vdbesort.c
@@ -312,8 +312,8 @@ struct MergeEngine {
  * after the thread has finished are not dire. So we don't worry about
  * memory barriers and such here.
  */
-typedef int (*SorterCompare) (SortSubtask *, int *, const void *, int,
-			      const void *, int);
+typedef int (*SorterCompare) (SortSubtask *, bool *, const void *,
+			      const void *);
 struct SortSubtask {
 	SQLiteThread *pThread;	/* Background thread, if any */
 	int bDone;		/* Set if thread is finished but not joined */
@@ -343,7 +343,7 @@ struct VdbeSorter {
 	PmaReader *pReader;	/* Readr data from here after Rewind() */
 	MergeEngine *pMerger;	/* Or here, if bUseThreads==0 */
 	sqlite3 *db;		/* Database connection */
-	KeyInfo *pKeyInfo;	/* How to compare records */
+	struct key_def *key_def;
 	UnpackedRecord *pUnpacked;	/* Used by VdbeSorterCompare() */
 	SorterList list;	/* List of in-memory records */
 	int iMemory;		/* Offset of free space in list.aMemory */
@@ -794,10 +794,10 @@ vdbePmaReaderInit(SortSubtask * pTask,	/* Task context */
 	return rc;
 }
 
-/*
- * Compare key1 (buffer pKey1, size nKey1 bytes) with key2 (buffer pKey2,
- * size nKey2 bytes). Use (pTask->pKeyInfo) for the collation sequences
- * used by the comparison. Return the result of the comparison.
+/**
+ * Compare key1 with key2. Use (pTask->key_def) for the collation
+ * sequences used by the comparison. Return the result of the
+ * comparison.
  *
  * If IN/OUT parameter *pbKey2Cached is true when this function is called,
  * it is assumed that (pTask->pUnpacked) contains the unpacked version
@@ -806,27 +806,31 @@ vdbePmaReaderInit(SortSubtask * pTask,	/* Task context */
  *
  * If an OOM error is encountered, (pTask->pUnpacked->error_rc) is set
  * to SQLITE_NOMEM.
+ *
+ * @param task Subtask context (for key_def).
+ * @param key2_cached True if pTask->pUnpacked is key2.
+ * @param key1 Left side of comparison.
+ * @param key2 Right side of comparison.
+ *
+ * @retval +1 if key1 > key2, -1 otherwise.
  */
 static int
-vdbeSorterCompare(SortSubtask * pTask,	/* Subtask context (for pKeyInfo) */
-		  int *pbKey2Cached,	/* True if pTask->pUnpacked is pKey2 */
-		  const void *pKey1, int nKey1,	/* Left side of comparison */
-		  const void *pKey2, int nKey2	/* Right side of comparison */
-    )
+vdbeSorterCompare(struct SortSubtask *task, bool *key2_cached,
+		  const void *key1, const void *key2)
 {
-	UnpackedRecord *r2 = pTask->pUnpacked;
-	if (!*pbKey2Cached) {
-		sqlite3VdbeRecordUnpackMsgpack(pTask->pSorter->pKeyInfo, nKey2,
-					       pKey2, r2);
-		*pbKey2Cached = 1;
+	struct UnpackedRecord *r2 = task->pUnpacked;
+	if (!*key2_cached) {
+		sqlite3VdbeRecordUnpackMsgpack(task->pSorter->key_def,
+					       key2, r2);
+		*key2_cached = 1;
 	}
-	return sqlite3VdbeRecordCompareMsgpack(nKey1, pKey1, r2);
+	return sqlite3VdbeRecordCompareMsgpack(key1, r2);
 }
 
 /*
  * Initialize the temporary index cursor just opened as a sorter cursor.
  *
- * Usually, the sorter module uses the value of (pCsr->pKeyInfo->nField)
+ * Usually, the sorter module uses the value of (pCsr->key_def->part_count)
  * to determine the number of fields that should be compared from the
  * records being sorted. However, if the value passed as argument nField
  * is non-zero and the sorter is able to guarantee a stable sort, nField
@@ -844,16 +848,12 @@ vdbeSorterCompare(SortSubtask * pTask,	/* Subtask context (for pKeyInfo) */
  */
 int
 sqlite3VdbeSorterInit(sqlite3 * db,	/* Database connection (for malloc()) */
-		      int nField,	/* Number of key fields in each record */
 		      VdbeCursor * pCsr	/* Cursor that holds the new sorter */
     )
 {
 	int pgsz;		/* Page size of main database */
 	int i;			/* Used to iterate through aTask[] */
 	VdbeSorter *pSorter;	/* The new sorter */
-	KeyInfo *pKeyInfo;	/* Copy of pCsr->pKeyInfo with db==0 */
-	int szKeyInfo;		/* Size of pCsr->pKeyInfo in bytes */
-	int sz;			/* Size of pSorter in bytes */
 	int rc = SQLITE_OK;
 #if SQLITE_MAX_WORKER_THREADS==0
 #define nWorker 0
@@ -875,25 +875,15 @@ sqlite3VdbeSorterInit(sqlite3 * db,	/* Database connection (for malloc()) */
 	}
 #endif
 
-	assert(pCsr->pKeyInfo);
+	assert(pCsr->key_def != NULL);
 	assert(pCsr->eCurType == CURTYPE_SORTER);
-	szKeyInfo =
-	    sizeof(KeyInfo) + (pCsr->pKeyInfo->nField - 1) * sizeof(struct coll *);
-	sz = sizeof(VdbeSorter) + nWorker * sizeof(SortSubtask);
 
-	pSorter = (VdbeSorter *) sqlite3DbMallocZero(db, sz + szKeyInfo);
+	pSorter = (VdbeSorter *) sqlite3DbMallocZero(db, sizeof(VdbeSorter));
 	pCsr->uc.pSorter = pSorter;
 	if (pSorter == 0) {
 		rc = SQLITE_NOMEM_BKPT;
 	} else {
-		pSorter->pKeyInfo = pKeyInfo =
-		    (KeyInfo *) ((u8 *) pSorter + sz);
-		memcpy(pKeyInfo, pCsr->pKeyInfo, szKeyInfo);
-		pKeyInfo->db = 0;
-		if (nField && nWorker == 0) {
-			pKeyInfo->nXField += (pKeyInfo->nField - nField);
-			pKeyInfo->nField = nField;
-		}
+		pSorter->key_def = pCsr->key_def;
 		pSorter->pgsz = pgsz = 1024;
 		pSorter->nTask = nWorker + 1;
 		pSorter->iPrev = (u8) (nWorker - 1);
@@ -929,8 +919,8 @@ sqlite3VdbeSorterInit(sqlite3 * db,	/* Database connection (for malloc()) */
 			}
 		}
 
-		if ((pKeyInfo->nField + pKeyInfo->nXField) < 13
-		    && (pKeyInfo->aColl[0] == NULL)) {
+		if (pCsr->key_def->part_count < 13
+		    && (pCsr->key_def->parts[0].coll == NULL)) {
 			pSorter->typeMask =
 			    SORTER_TYPE_INTEGER | SORTER_TYPE_TEXT;
 		}
@@ -1280,10 +1270,11 @@ vdbeSortAllocUnpacked(SortSubtask * pTask)
 {
 	if (pTask->pUnpacked == 0) {
 		pTask->pUnpacked =
-		    sqlite3VdbeAllocUnpackedRecord(pTask->pSorter->pKeyInfo);
+			sqlite3VdbeAllocUnpackedRecord(pTask->pSorter->db,
+						       pTask->pSorter->key_def);
 		if (pTask->pUnpacked == 0)
 			return SQLITE_NOMEM_BKPT;
-		pTask->pUnpacked->nField = pTask->pSorter->pKeyInfo->nField;
+		pTask->pUnpacked->nField = pTask->pSorter->key_def->part_count;
 		pTask->pUnpacked->errCode = 0;
 	}
 	return SQLITE_OK;
@@ -1300,14 +1291,14 @@ vdbeSorterMerge(SortSubtask * pTask,	/* Calling thread context */
 {
 	SorterRecord *pFinal = 0;
 	SorterRecord **pp = &pFinal;
-	int bCached = 0;
+	bool bCached = false;
 
 	assert(p1 != 0 && p2 != 0);
 	for (;;) {
 		int res;
 		res =
-		    pTask->xCompare(pTask, &bCached, SRVAL(p1), p1->nVal,
-				    SRVAL(p2), p2->nVal);
+		    pTask->xCompare(pTask, &bCached, SRVAL(p1),
+				    SRVAL(p2));
 
 		if (res <= 0) {
 			*pp = p1;
@@ -1599,7 +1590,7 @@ vdbeMergeEngineStep(MergeEngine * pMerger,	/* The merge engine to advance to the
 		int i;		/* Index of aTree[] to recalculate */
 		PmaReader *pReadr1;	/* First PmaReader to compare */
 		PmaReader *pReadr2;	/* Second PmaReader to compare */
-		int bCached = 0;
+		bool bCached = false;
 
 		/* Find the first two PmaReaders to compare. The one that was just
 		 * advanced (iPrev) and the one next to it in the array.
@@ -1617,9 +1608,7 @@ vdbeMergeEngineStep(MergeEngine * pMerger,	/* The merge engine to advance to the
 			} else {
 				iRes = pTask->xCompare(pTask, &bCached,
 						       pReadr1->aKey,
-						       pReadr1->nKey,
-						       pReadr2->aKey,
-						       pReadr2->nKey);
+						       pReadr2->aKey);
 			}
 
 			/* If pReadr1 contained the smaller value, set aTree[i] to its index.
@@ -2075,12 +2064,11 @@ vdbeMergeEngineCompare(MergeEngine * pMerger,	/* Merge engine containing PmaRead
 		iRes = i1;
 	} else {
 		SortSubtask *pTask = pMerger->pTask;
-		int bCached = 0;
+		bool cached = false;
 		int res;
 		assert(pTask->pUnpacked != 0);	/* from vdbeSortSubtaskMain() */
 		res =
-		    pTask->xCompare(pTask, &bCached, p1->aKey, p1->nKey,
-				    p2->aKey, p2->nKey);
+		    pTask->xCompare(pTask, &cached, p1->aKey, p2->aKey);
 		if (res <= 0) {
 			iRes = i1;
 		} else {
@@ -2824,7 +2812,6 @@ sqlite3VdbeSorterCompare(const VdbeCursor * pCsr,	/* Sorter cursor */
 {
 	VdbeSorter *pSorter;
 	UnpackedRecord *r2;
-	KeyInfo *pKeyInfo;
 	int i;
 	void *pKey;
 	int nKey;		/* Sorter key to compare pVal with */
@@ -2832,10 +2819,9 @@ sqlite3VdbeSorterCompare(const VdbeCursor * pCsr,	/* Sorter cursor */
 	assert(pCsr->eCurType == CURTYPE_SORTER);
 	pSorter = pCsr->uc.pSorter;
 	r2 = pSorter->pUnpacked;
-	pKeyInfo = pCsr->pKeyInfo;
 	if (r2 == 0) {
 		r2 = pSorter->pUnpacked =
-		    sqlite3VdbeAllocUnpackedRecord(pKeyInfo);
+			sqlite3VdbeAllocUnpackedRecord(pSorter->db,  pCsr->key_def);
 		if (r2 == 0)
 			return SQLITE_NOMEM_BKPT;
 		r2->nField = nKeyCol;
@@ -2843,7 +2829,7 @@ sqlite3VdbeSorterCompare(const VdbeCursor * pCsr,	/* Sorter cursor */
 	assert(r2->nField == nKeyCol);
 
 	pKey = vdbeSorterRowkey(pSorter, &nKey);
-	sqlite3VdbeRecordUnpackMsgpack(pKeyInfo, nKey, pKey, r2);
+	sqlite3VdbeRecordUnpackMsgpack(pCsr->key_def, pKey, r2);
 	for (i = 0; i < nKeyCol; i++) {
 		if (r2->aMem[i].flags & MEM_Null) {
 			*pRes = -1;
@@ -2851,6 +2837,6 @@ sqlite3VdbeSorterCompare(const VdbeCursor * pCsr,	/* Sorter cursor */
 		}
 	}
 
-	*pRes = sqlite3VdbeRecordCompareMsgpack(pVal->n, pVal->z, r2);
+	*pRes = sqlite3VdbeRecordCompareMsgpack(pVal->z, r2);
 	return SQLITE_OK;
 }
diff --git a/src/box/sql/where.c b/src/box/sql/where.c
index bad964a..cde3815 100644
--- a/src/box/sql/where.c
+++ b/src/box/sql/where.c
@@ -812,7 +812,7 @@ constructAutomaticIndex(Parse * pParse,			/* The parsing context */
 	assert(pLevel->iIdxCur >= 0);
 	pLevel->iIdxCur = pParse->nTab++;
 	sqlite3VdbeAddOp2(v, OP_OpenAutoindex, pLevel->iIdxCur, nKeyCol + 1);
-	sqlite3VdbeSetP4KeyInfo(pParse, pIdx);
+	sql_vdbe_set_p4_key_def(pParse, pIdx);
 	VdbeComment((v, "for %s", pTable->zName));
 
 	/* Fill the automatic index with content */
@@ -971,9 +971,7 @@ whereKeyStats(Parse * pParse,	/* Database connection */
 		}
 
 		pRec->nField = n;
-		res =
-		    sqlite3VdbeRecordCompareMsgpack(aSample[iSamp].n,
-						    aSample[iSamp].p, pRec);
+		res = sqlite3VdbeRecordCompareMsgpack(aSample[iSamp].p, pRec);
 		if (res < 0) {
 			iLower =
 			    aSample[iSamp].anLt[n - 1] + aSample[iSamp].anEq[n -
@@ -1002,8 +1000,7 @@ whereKeyStats(Parse * pParse,	/* Database connection */
 			assert(iCol == nField - 1);
 			pRec->nField = nField;
 			assert(0 ==
-			       sqlite3VdbeRecordCompareMsgpack(aSample[i].n,
-							       aSample[i].p,
+			       sqlite3VdbeRecordCompareMsgpack(aSample[i].p,
 							       pRec)
 			       || pParse->db->mallocFailed);
 		} else {
@@ -1014,8 +1011,7 @@ whereKeyStats(Parse * pParse,	/* Database connection */
 			assert(i <= pIdx->nSample && i >= 0);
 			pRec->nField = iCol + 1;
 			assert(i == pIdx->nSample
-			       || sqlite3VdbeRecordCompareMsgpack(aSample[i].n,
-								  aSample[i].p,
+			       || sqlite3VdbeRecordCompareMsgpack(aSample[i].p,
 								  pRec) > 0
 			       || pParse->db->mallocFailed);
 
@@ -1027,14 +1023,14 @@ whereKeyStats(Parse * pParse,	/* Database connection */
 			if (iCol > 0) {
 				pRec->nField = iCol;
 				assert(sqlite3VdbeRecordCompareMsgpack
-				       (aSample[i].n, aSample[i].p, pRec) <= 0
+				       (aSample[i].p, pRec) <= 0
 				       || pParse->db->mallocFailed);
 			}
 			if (i > 0) {
 				pRec->nField = nField;
 				assert(sqlite3VdbeRecordCompareMsgpack
-				       (aSample[i - 1].n, aSample[i - 1].p,
-					pRec) < 0 || pParse->db->mallocFailed);
+				       (aSample[i - 1].p, pRec) < 0 ||
+				       pParse->db->mallocFailed);
 			}
 		}
 	}
@@ -3385,12 +3381,12 @@ wherePathSatisfiesOrderBy(WhereInfo * pWInfo,	/* The WHERE clause */
 					 */
 					if (revSet) {
 						if ((rev ^ revIdx) !=
-						    pOrderBy->a[i].sortOrder)
+						    pOrderBy->a[i].sort_order)
 							isMatch = 0;
 					} else {
 						rev =
 						    revIdx ^ pOrderBy->a[i].
-						    sortOrder;
+						    sort_order;
 						if (rev)
 							*pRevMask |=
 							    MASKBIT(iLoop);
@@ -4571,7 +4567,7 @@ sqlite3WhereBegin(Parse * pParse,	/* The parser context */
 			assert(iIndexCur >= 0);
 			if (op) {
 				emit_open_cursor(pParse, iIndexCur, pIx->tnum);
-				sqlite3VdbeSetP4KeyInfo(pParse, pIx);
+				sql_vdbe_set_p4_key_def(pParse, pIx);
 				if ((pLoop->wsFlags & WHERE_CONSTRAINT) != 0
 				    && (pLoop->
 					wsFlags & (WHERE_COLUMN_RANGE |
diff --git a/src/box/sql/wherecode.c b/src/box/sql/wherecode.c
index 231d690..135db0c 100644
--- a/src/box/sql/wherecode.c
+++ b/src/box/sql/wherecode.c
@@ -1618,7 +1618,7 @@ sqlite3WhereCodeOneLoopStart(WhereInfo * pWInfo,	/* Complete information about t
 			regRowset = pParse->nTab++;
 			sqlite3VdbeAddOp2(v, OP_OpenTEphemeral,
 					  regRowset, nPkCol);
-			sqlite3VdbeSetP4KeyInfo(pParse, pPk);
+			sql_vdbe_set_p4_key_def(pParse, pPk);
 			regPk = ++pParse->nMem;
 		}
 		iRetInit = sqlite3VdbeAddOp2(v, OP_Integer, 0, regReturn);
diff --git a/src/box/tuple.c b/src/box/tuple.c
index df2686e..7bcd9f8 100644
--- a/src/box/tuple.c
+++ b/src/box/tuple.c
@@ -474,3 +474,12 @@ tuple_str(const struct tuple *tuple)
 		return "<failed to format tuple>";
 	return buf;
 }
+
+const char *
+mp_str(const char *data)
+{
+	char *buf = tt_static_buf();
+	if (mp_snprint(buf, TT_STATIC_BUF_LEN, data) < 0)
+		return "<failed to format message pack>";
+	return buf;
+}
diff --git a/src/box/tuple.h b/src/box/tuple.h
index 97b81cf..9a459d9 100644
--- a/src/box/tuple.h
+++ b/src/box/tuple.h
@@ -395,6 +395,15 @@ tuple_snprint(char *buf, int size, const struct tuple *tuple);
 const char *
 tuple_str(const struct tuple *tuple);
 
+/**
+ * Format msgpack into string using a static buffer.
+ * Useful for debugger. Example: [1, 2, "string"]
+ * @param msgpack to format
+ * @return formatted null-terminated string
+ */
+const char *
+mp_str(const char *data);
+
 /**
  * Get the format of the tuple.
  * @param tuple Tuple.

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

* [tarantool-patches] Re: [PATCH 1/2] sql: introduce sort order to key_part/key_part_def
  2018-05-08 16:02   ` [tarantool-patches] " Vladislav Shpilevoy
@ 2018-05-10 13:01     ` Kirill Yukhin
  0 siblings, 0 replies; 17+ messages in thread
From: Kirill Yukhin @ 2018-05-10 13:01 UTC (permalink / raw)
  To: Vladislav Shpilevoy; +Cc: tarantool-patches

Hello Vlad,
Thanks for review. Updated patch in the bottom.

On 08 мая 19:02, Vladislav Shpilevoy wrote:
> Hello. Thanks for contributing! See 2 comments below.
> 
> 1. I still can grep SQLITE_SO_ASC/DESC in build.c
Fixed.
> 
> On 08/05/2018 10:56, Kirill Yukhin wrote:
> > Legacy SQL DD structs contained sort_order, defined per
> > index column. During integration, those structs are to be
> > vanished. So, introduce new field to part entity of Tarantool.
> > This field states for sorting order of given part in give index.
> > This field is ignored by Tarantool everywhere excpept for
> > some of nested queries in SQL.
> > 
> > Patch also replaces usages of SQL's stored sort order w/ this new
> > field.
> > 
> > Part of #3235
> > ---
> >   src/box/key_def.cc      | 28 ++++++++++++++++++++++------
> >   src/box/key_def.h       | 16 +++++++++++++++-
> >   src/box/schema.cc       | 30 ++++++++++++++++++++----------
> >   src/box/sql.c           | 11 +++++++++--
> >   src/box/sql/build.c     | 47 ++++++++++++++++++++++++++++++++++++-----------
> >   src/box/sql/expr.c      |  9 ++++-----
> >   src/box/sql/insert.c    |  3 ++-
> >   src/box/sql/parse.y     | 10 +++++-----
> >   src/box/sql/pragma.c    |  7 ++++---
> >   src/box/sql/select.c    |  2 +-
> >   src/box/sql/sqliteInt.h | 20 ++++++++++++--------
> >   src/box/sql/vdbe.h      |  1 +
> >   src/box/sql/where.c     | 11 ++++++-----
> >   src/box/sql/wherecode.c | 10 ++++++----
> >   14 files changed, 143 insertions(+), 62 deletions(-)
> > 
> > diff --git a/src/box/sql/sqliteInt.h b/src/box/sql/sqliteInt.h
> > index 8bb45c9..a811932 100644
> > --- a/src/box/sql/sqliteInt.h
> > +++ b/src/box/sql/sqliteInt.h
> > @@ -3540,6 +3534,16 @@ sql_default_coll();
> >   bool
> >   space_is_view(Table *);
> > +/**
> > + * Return name of given column collation from index.
> 
> 2. Irrelevant comment.
Fixed.

--
Regards, Kirill Yukhin

diff --git a/src/box/key_def.cc b/src/box/key_def.cc
index 98719c2..9f1a373 100644
--- a/src/box/key_def.cc
+++ b/src/box/key_def.cc
@@ -36,12 +36,15 @@
 #include "schema_def.h"
 #include "coll_cache.h"
 
+const char *sort_order_strs[] = { "asc", "desc", "undef" };
+
 static const struct key_part_def key_part_def_default = {
 	0,
 	field_type_MAX,
 	COLL_NONE,
 	false,
-	ON_CONFLICT_ACTION_ABORT
+	ON_CONFLICT_ACTION_ABORT,
+	SORT_ORDER_ASC
 };
 
 static int64_t
@@ -55,6 +58,7 @@ part_type_by_name_wrapper(const char *str, uint32_t len)
 #define PART_OPT_COLLATION	 "collation"
 #define PART_OPT_NULLABILITY	 "is_nullable"
 #define PART_OPT_NULLABLE_ACTION "nullable_action"
+#define PART_OPT_SORT_ORDER	 "sort_order"
 
 const struct opt_def part_def_reg[] = {
 	OPT_DEF_ENUM(PART_OPT_TYPE, field_type, struct key_part_def, type,
@@ -65,6 +69,8 @@ const struct opt_def part_def_reg[] = {
 		is_nullable),
 	OPT_DEF_ENUM(PART_OPT_NULLABLE_ACTION, on_conflict_action,
 		     struct key_part_def, nullable_action, NULL),
+	OPT_DEF_ENUM(PART_OPT_SORT_ORDER, sort_order, struct key_part_def,
+		     sort_order, NULL),
 	OPT_END,
 };
 
@@ -169,7 +175,7 @@ key_def_new_with_parts(struct key_part_def *parts, uint32_t part_count)
 			}
 		}
 		key_def_set_part(def, i, part->fieldno, part->type,
-				 part->nullable_action, coll);
+				 part->nullable_action, coll, part->sort_order);
 	}
 	return def;
 }
@@ -199,7 +205,8 @@ box_key_def_new(uint32_t *fields, uint32_t *types, uint32_t part_count)
 	for (uint32_t item = 0; item < part_count; ++item) {
 		key_def_set_part(key_def, item, fields[item],
 				 (enum field_type)types[item],
-				 key_part_def_default.nullable_action, NULL);
+				 key_part_def_default.nullable_action,
+				 NULL, SORT_ORDER_ASC);
 	}
 	return key_def;
 }
@@ -252,7 +259,7 @@ key_part_cmp(const struct key_part *parts1, uint32_t part_count1,
 void
 key_def_set_part(struct key_def *def, uint32_t part_no, uint32_t fieldno,
 		 enum field_type type, enum on_conflict_action nullable_action,
-		 struct coll *coll)
+		 struct coll *coll, enum sort_order sort_order)
 {
 	assert(part_no < def->part_count);
 	assert(type < field_type_MAX);
@@ -261,6 +268,7 @@ key_def_set_part(struct key_def *def, uint32_t part_no, uint32_t fieldno,
 	def->parts[part_no].fieldno = fieldno;
 	def->parts[part_no].type = type;
 	def->parts[part_no].coll = coll;
+	def->parts[part_no].sort_order = sort_order;
 	column_mask_set_fieldno(&def->column_mask, fieldno);
 	/**
 	 * When all parts are set, initialize the tuple
@@ -510,6 +518,12 @@ key_def_decode_parts(struct key_part_def *parts, uint32_t part_count,
 				 "nullable action properties");
 			return -1;
 		}
+		if (part->sort_order == sort_order_MAX) {
+			diag_set(ClientError, ER_WRONG_INDEX_OPTIONS,
+				 i + TUPLE_INDEX_BASE,
+				 "index part: unknown sort order");
+			return -1;
+		}
 	}
 	return 0;
 }
@@ -572,7 +586,8 @@ key_def_merge(const struct key_def *first, const struct key_def *second)
 	end = part + first->part_count;
 	for (; part != end; part++) {
 		key_def_set_part(new_def, pos++, part->fieldno, part->type,
-				 part->nullable_action, part->coll);
+				 part->nullable_action, part->coll,
+				 part->sort_order);
 	}
 
 	/* Set-append second key def's part to the new key def. */
@@ -582,7 +597,8 @@ key_def_merge(const struct key_def *first, const struct key_def *second)
 		if (key_def_find(first, part->fieldno))
 			continue;
 		key_def_set_part(new_def, pos++, part->fieldno, part->type,
-				 part->nullable_action, part->coll);
+				 part->nullable_action, part->coll,
+				 part->sort_order);
 	}
 	return new_def;
 }
diff --git a/src/box/key_def.h b/src/box/key_def.h
index a02bfa1..4b18175 100644
--- a/src/box/key_def.h
+++ b/src/box/key_def.h
@@ -45,6 +45,16 @@ extern "C" {
 /* MsgPack type names */
 extern const char *mp_type_strs[];
 
+/* Sorting order of a part. */
+extern const char *sort_order_strs[];
+
+enum sort_order {
+	SORT_ORDER_ASC = 0,
+	SORT_ORDER_DESC,
+	SORT_ORDER_UNDEF,
+	sort_order_MAX
+};
+
 struct key_part_def {
 	/** Tuple field index for this part. */
 	uint32_t fieldno;
@@ -56,6 +66,8 @@ struct key_part_def {
 	bool is_nullable;
 	/** Action to perform if NULL constraint failed. */
 	enum on_conflict_action nullable_action;
+	/** Part sort order. */
+	enum sort_order sort_order;
 };
 
 /**
@@ -74,6 +86,8 @@ struct key_part {
 	struct coll *coll;
 	/** Action to perform if NULL constraint failed. */
 	enum on_conflict_action nullable_action;
+	/** Part sort order. */
+	enum sort_order sort_order;
 };
 
 struct key_def;
@@ -264,7 +278,7 @@ key_def_dump_parts(const struct key_def *def, struct key_part_def *parts);
 void
 key_def_set_part(struct key_def *def, uint32_t part_no, uint32_t fieldno,
 		 enum field_type type, enum on_conflict_action nullable_action,
-		 struct coll *coll);
+		 struct coll *coll, enum sort_order sort_order);
 
 /**
  * Update 'has_optional_parts' of @a key_def with correspondence
diff --git a/src/box/schema.cc b/src/box/schema.cc
index 2e14ec2..c3a8f93 100644
--- a/src/box/schema.cc
+++ b/src/box/schema.cc
@@ -279,13 +279,15 @@ schema_init()
 	auto key_def_guard = make_scoped_guard([&] { key_def_delete(key_def); });
 
 	key_def_set_part(key_def, 0 /* part no */, 0 /* field no */,
-			 FIELD_TYPE_STRING, ON_CONFLICT_ACTION_ABORT, NULL);
+			 FIELD_TYPE_STRING, ON_CONFLICT_ACTION_ABORT, NULL,
+			 SORT_ORDER_ASC);
 	sc_space_new(BOX_SCHEMA_ID, "_schema", key_def, &on_replace_schema,
 		     NULL);
 
 	/* _space - home for all spaces. */
 	key_def_set_part(key_def, 0 /* part no */, 0 /* field no */,
-			 FIELD_TYPE_UNSIGNED, ON_CONFLICT_ACTION_ABORT, NULL);
+			 FIELD_TYPE_UNSIGNED, ON_CONFLICT_ACTION_ABORT, NULL,
+			 SORT_ORDER_ASC);
 
 	/* _collation - collation description. */
 	sc_space_new(BOX_COLLATION_ID, "_collation", key_def,
@@ -329,7 +331,8 @@ schema_init()
 
 	/* _trigger - all existing SQL triggers. */
 	key_def_set_part(key_def, 0 /* part no */, 0 /* field no */,
-			 FIELD_TYPE_STRING, ON_CONFLICT_ACTION_ABORT, NULL);
+			 FIELD_TYPE_STRING, ON_CONFLICT_ACTION_ABORT, NULL,
+			 SORT_ORDER_ASC);
 	sc_space_new(BOX_TRIGGER_ID, "_trigger", key_def, &on_replace_trigger, NULL);
 
 	key_def_delete(key_def);
@@ -338,19 +341,23 @@ schema_init()
 		diag_raise();
 	/* space no */
 	key_def_set_part(key_def, 0 /* part no */, 0 /* field no */,
-			 FIELD_TYPE_UNSIGNED, ON_CONFLICT_ACTION_ABORT, NULL);
+			 FIELD_TYPE_UNSIGNED, ON_CONFLICT_ACTION_ABORT, NULL,
+			 SORT_ORDER_ASC);
 	/* index no */
 	key_def_set_part(key_def, 1 /* part no */, 1 /* field no */,
-			 FIELD_TYPE_UNSIGNED, ON_CONFLICT_ACTION_ABORT, NULL);
+			 FIELD_TYPE_UNSIGNED, ON_CONFLICT_ACTION_ABORT, NULL,
+			 SORT_ORDER_ASC);
 	sc_space_new(BOX_INDEX_ID, "_index", key_def,
 		     &alter_space_on_replace_index, &on_stmt_begin_index);
 
 	/* space name */
 	key_def_set_part(key_def, 0 /* part no */, 0 /* field no */,
-			 FIELD_TYPE_STRING, ON_CONFLICT_ACTION_ABORT, NULL);
+			 FIELD_TYPE_STRING, ON_CONFLICT_ACTION_ABORT, NULL,
+			 SORT_ORDER_ASC);
 	/* index name */
 	key_def_set_part(key_def, 1 /* part no */, 1 /* field no */,
-			 FIELD_TYPE_STRING, ON_CONFLICT_ACTION_ABORT, NULL);
+			 FIELD_TYPE_STRING, ON_CONFLICT_ACTION_ABORT, NULL,
+			 SORT_ORDER_ASC);
 	/* _sql_stat1 - a simpler statistics on space, seen in SQL. */
 	sc_space_new(BOX_SQL_STAT1_ID, "_sql_stat1", key_def, NULL, NULL);
 
@@ -361,13 +368,16 @@ schema_init()
 
 	/* space name */
 	key_def_set_part(key_def, 0 /* part no */, 0 /* field no */,
-			 FIELD_TYPE_STRING, ON_CONFLICT_ACTION_ABORT, NULL);
+			 FIELD_TYPE_STRING, ON_CONFLICT_ACTION_ABORT, NULL,
+			 SORT_ORDER_ASC);
 	/* index name */
 	key_def_set_part(key_def, 1 /* part no */, 1 /* field no */,
-			 FIELD_TYPE_STRING, ON_CONFLICT_ACTION_ABORT, NULL);
+			 FIELD_TYPE_STRING, ON_CONFLICT_ACTION_ABORT, NULL,
+			 SORT_ORDER_ASC);
 	/* sample */
 	key_def_set_part(key_def, 2 /* part no */, 5 /* field no */,
-			 FIELD_TYPE_SCALAR, ON_CONFLICT_ACTION_ABORT, NULL);
+			 FIELD_TYPE_SCALAR, ON_CONFLICT_ACTION_ABORT, NULL,
+			 SORT_ORDER_ASC);
 	/* _sql_stat4 - extensive statistics on space, seen in SQL. */
 	sc_space_new(BOX_SQL_STAT4_ID, "_sql_stat4", key_def, NULL, NULL);
 }
diff --git a/src/box/sql.c b/src/box/sql.c
index 166bb71..838fcf6 100644
--- a/src/box/sql.c
+++ b/src/box/sql.c
@@ -384,7 +384,8 @@ int tarantoolSqlite3EphemeralCreate(BtCursor *pCur, uint32_t field_count,
 				 part /* filed no */,
 				 FIELD_TYPE_SCALAR,
 				 ON_CONFLICT_ACTION_NONE /* nullable_action */,
-				 aColl /* coll */);
+				 aColl /* coll */,
+				 SORT_ORDER_ASC);
 	}
 
 	struct index_def *ephemer_index_def =
@@ -1555,7 +1556,7 @@ int tarantoolSqlite3MakeIdxParts(SqliteIndex *pIndex, void *buf)
 		else
 			t = convertSqliteAffinity(aCol[col].affinity, aCol[col].notNull == 0);
 		/* do not decode default collation */
-		p = enc->encode_map(p, pIndex->coll_array[i] == NULL ? 4 : 5);
+		p = enc->encode_map(p, pIndex->coll_array[i] == NULL ? 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);
@@ -1569,6 +1570,12 @@ int tarantoolSqlite3MakeIdxParts(SqliteIndex *pIndex, void *buf)
 		p = enc->encode_str(p, "nullable_action", 15);
 		const char *action_str = on_conflict_action_strs[aCol[col].notNull];
 		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];
+		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);
 }
diff --git a/src/box/sql/build.c b/src/box/sql/build.c
index a2b712a..029c71e 100644
--- a/src/box/sql/build.c
+++ b/src/box/sql/build.c
@@ -930,7 +930,7 @@ sqlite3AddPrimaryKey(Parse * pParse,	/* Parsing context */
 		     ExprList * pList,	/* List of field names to be indexed */
 		     int onError,	/* What to do with a uniqueness conflict */
 		     int autoInc,	/* True if the AUTOINCREMENT keyword is present */
-		     int sortOrder	/* SQLITE_SO_ASC or SQLITE_SO_DESC */
+		     enum sort_order sortOrder
     )
 {
 	Table *pTab = pParse->pNewTable;
@@ -976,7 +976,7 @@ sqlite3AddPrimaryKey(Parse * pParse,	/* Parsing context */
 	if (nTerm == 1
 	    && pCol
 	    && (sqlite3ColumnType(pCol) == FIELD_TYPE_INTEGER)
-	    && sortOrder != SQLITE_SO_DESC) {
+	    && sortOrder != SORT_ORDER_DESC) {
 		assert(autoInc == 0 || autoInc == 1);
 		pTab->iPKey = iCol;
 		pTab->keyConf = (u8) onError;
@@ -1125,6 +1125,30 @@ sql_index_collation(Index *idx, uint32_t column)
 	return index->def->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];
+	}
+
+	uint32_t index_id = SQLITE_PAGENO_TO_INDEXID(idx->tnum);
+	struct index *index = space_index(space, index_id);
+	assert(index != NULL && index->def->key_def->part_count >= column);
+	return index->def->key_def->parts[column].sort_order;
+}
+
 /**
  * Return true if space which corresponds to
  * given table has view option.
@@ -2694,11 +2718,11 @@ sqlite3AllocateIndexObject(sqlite3 * db,	/* Database connection */
 	Index *p;		/* Allocated index object */
 	int nByte;		/* Bytes of space for Index object + arrays */
 
-	nByte = ROUND8(sizeof(Index)) +	/* Index structure  */
-	    ROUND8(sizeof(char *) * nCol) +	/* Index.azColl     */
-	    ROUND8(sizeof(LogEst) * (nCol + 1) +	/* Index.aiRowLogEst   */
-		   sizeof(i16) * nCol +	/* Index.aiColumn   */
-		   sizeof(u8) * nCol);	/* Index.aSortOrder */
+	nByte = ROUND8(sizeof(Index)) +		    /* Index structure  */
+	    ROUND8(sizeof(char *) * nCol) +	    /* Index.azColl     */
+	    ROUND8(sizeof(LogEst) * (nCol + 1) +    /* Index.aiRowLogEst   */
+		   sizeof(i16) * nCol +		    /* Index.aiColumn   */
+		   sizeof(enum sort_order) * nCol); /* Index.sort_order */
 	p = sqlite3DbMallocZero(db, nByte + nExtra);
 	if (p) {
 		char *pExtra = ((char *)p) + ROUND8(sizeof(Index));
@@ -2708,7 +2732,7 @@ sqlite3AllocateIndexObject(sqlite3 * db,	/* Database connection */
 		pExtra += sizeof(LogEst) * (nCol + 1);
 		p->aiColumn = (i16 *) pExtra;
 		pExtra += sizeof(i16) * nCol;
-		p->aSortOrder = (u8 *) pExtra;
+		p->sort_order = (enum sort_order *) pExtra;
 		p->nColumn = nCol;
 		*ppExtra = ((char *)p) + nByte;
 	}
@@ -3037,7 +3061,7 @@ sqlite3CreateIndex(Parse * pParse,	/* All information about this parse */
 	 */
 	for (i = 0, pListItem = pList->a; i < pList->nExpr; i++, pListItem++) {
 		Expr *pCExpr;	/* The i-th index expression */
-		int requestedSortOrder;	/* ASC or DESC on the i-th expression */
+		enum sort_order requested_so;	/* ASC or DESC on the i-th expression */
 		sqlite3ResolveSelfReference(pParse, pTab, NC_IdxExpr,
 					    pListItem->pExpr, 0);
 		if (pParse->nErr)
@@ -3075,8 +3099,8 @@ sqlite3CreateIndex(Parse * pParse,	/* All information about this parse */
 		/* Tarantool: DESC indexes are not supported so far.
 		 * See gh-3016.
 		 */
-		requestedSortOrder = pListItem->sortOrder & 0;
-		pIndex->aSortOrder[i] = (u8) requestedSortOrder;
+		requested_so = pListItem->sortOrder & 0;
+		pIndex->sort_order[i] = requested_so;
 	}
 
 	sqlite3DefaultRowEst(pIndex);
@@ -4199,7 +4223,8 @@ sqlite3KeyInfoOfIndex(Parse * pParse, sqlite3 * db, Index * pIdx)
 		assert(sqlite3KeyInfoIsWriteable(pKey));
 		for (i = 0; i < nCol; i++) {
 			pKey->aColl[i] = sql_index_collation(pIdx, i);
-			pKey->aSortOrder[i] = pIdx->aSortOrder[i];
+			pKey->aSortOrder[i] = sql_index_column_sort_order(pIdx,
+									  i);
 		}
 		if (pParse && pParse->nErr) {
 			sqlite3KeyInfoUnref(pKey);
diff --git a/src/box/sql/expr.c b/src/box/sql/expr.c
index 0c86761..cc969ca 100644
--- a/src/box/sql/expr.c
+++ b/src/box/sql/expr.c
@@ -1764,11 +1764,9 @@ sqlite3ExprListSetSortOrder(ExprList * p, int iSortOrder)
 {
 	if (p == 0)
 		return;
-	assert(SQLITE_SO_UNDEFINED < 0 && SQLITE_SO_ASC >= 0
-	       && SQLITE_SO_DESC > 0);
 	assert(p->nExpr > 0);
-	if (iSortOrder < 0) {
-		assert(p->a[p->nExpr - 1].sortOrder == SQLITE_SO_ASC);
+	if (iSortOrder == SORT_ORDER_UNDEF) {
+		assert(p->a[p->nExpr - 1].sortOrder == SORT_ORDER_ASC);
 		return;
 	}
 	p->a[p->nExpr - 1].sortOrder = (u8) iSortOrder;
@@ -2529,7 +2527,8 @@ sqlite3FindInIndex(Parse * pParse,	/* Parsing context */
 					assert(IN_INDEX_INDEX_DESC ==
 					       IN_INDEX_INDEX_ASC + 1);
 					eType = IN_INDEX_INDEX_ASC +
-						pIdx->aSortOrder[0];
+						sql_index_column_sort_order(pIdx,
+									    0);
 
 					if (prRhsHasNull) {
 #ifdef SQLITE_ENABLE_COLUMN_USED_MASK
diff --git a/src/box/sql/insert.c b/src/box/sql/insert.c
index 939b5e3..1a34f71 100644
--- a/src/box/sql/insert.c
+++ b/src/box/sql/insert.c
@@ -1653,7 +1653,8 @@ xferCompatibleIndex(Index * pDest, Index * pSrc)
 				return 0;	/* Different expressions in the index */
 			}
 		}
-		if (pSrc->aSortOrder[i] != pDest->aSortOrder[i]) {
+		if (sql_index_column_sort_order(pSrc, i) !=
+		    sql_index_column_sort_order(pDest, i)) {
 			return 0;	/* Different sort orders */
 		}
 		if (sql_index_collation(pSrc, i) !=
diff --git a/src/box/sql/parse.y b/src/box/sql/parse.y
index b078e20..249d20d 100644
--- a/src/box/sql/parse.y
+++ b/src/box/sql/parse.y
@@ -684,9 +684,9 @@ sortlist(A) ::= expr(Y) sortorder(Z). {
 
 %type sortorder {int}
 
-sortorder(A) ::= ASC.           {A = SQLITE_SO_ASC;}
-sortorder(A) ::= DESC.          {A = SQLITE_SO_DESC;}
-sortorder(A) ::= .              {A = SQLITE_SO_UNDEFINED;}
+sortorder(A) ::= ASC.           {A = SORT_ORDER_ASC;}
+sortorder(A) ::= DESC.          {A = SORT_ORDER_DESC;}
+sortorder(A) ::= .              {A = SORT_ORDER_UNDEF;}
 
 %type groupby_opt {ExprList*}
 %destructor groupby_opt {sqlite3ExprListDelete(pParse->db, $$);}
@@ -1244,7 +1244,7 @@ cmd ::= createkw(S) uniqueflag(U) INDEX ifnotexists(NE) nm(X)
         ON nm(Y) LP sortlist(Z) RP where_opt(W). {
   sqlite3CreateIndex(pParse, &X, 
                      sqlite3SrcListAppend(pParse->db,0,&Y), Z, U,
-                      &S, W, SQLITE_SO_ASC, NE, SQLITE_IDXTYPE_APPDEF);
+                      &S, W, SORT_ORDER_ASC, NE, SQLITE_IDXTYPE_APPDEF);
 }
 
 %type uniqueflag {int}
@@ -1279,7 +1279,7 @@ uniqueflag(A) ::= .        {A = ON_CONFLICT_ACTION_NONE;}
     int sortOrder
   ){
     ExprList *p = sqlite3ExprListAppend(pParse, pPrior, 0);
-    if( (hasCollate || sortOrder!=SQLITE_SO_UNDEFINED)
+    if( (hasCollate || sortOrder != SORT_ORDER_UNDEF)
         && pParse->db->init.busy==0
     ){
       sqlite3ErrorMsg(pParse, "syntax error after column name \"%.*s\"",
diff --git a/src/box/sql/pragma.c b/src/box/sql/pragma.c
index e41f69b..738c254 100644
--- a/src/box/sql/pragma.c
+++ b/src/box/sql/pragma.c
@@ -463,12 +463,13 @@ sqlite3Pragma(Parse * pParse, Token * pId,	/* First part of [schema.]id field */
 								c_n = coll->name;
 							else
 								c_n = "BINARY";
+							enum sort_order sort_order;
+							sort_order = sql_index_column_sort_order(pIdx,
+												 i);
 							sqlite3VdbeMultiLoad(v,
 									     4,
 									     "isi",
-									     pIdx->
-									     aSortOrder
-									     [i],
+									     sort_order,
 									     c_n,
 									     i <
 									     mx);
diff --git a/src/box/sql/select.c b/src/box/sql/select.c
index 5a50413..aff534d3 100644
--- a/src/box/sql/select.c
+++ b/src/box/sql/select.c
@@ -5620,7 +5620,7 @@ sqlite3Select(Parse * pParse,		/* The parser context */
 		 * and plus one column for ID.
 		 */
 		int nCols = pEList->nExpr + sSort.pOrderBy->nExpr + 1;
-		if (pKeyInfo->aSortOrder[0] == SQLITE_SO_DESC) {
+		if (pKeyInfo->aSortOrder[0] == SORT_ORDER_DESC) {
 			sSort.sortFlags |= SORTFLAG_DESC;
 		}
 		sSort.addrSortIndex =
diff --git a/src/box/sql/sqliteInt.h b/src/box/sql/sqliteInt.h
index 8bb45c9..c2c1090 100644
--- a/src/box/sql/sqliteInt.h
+++ b/src/box/sql/sqliteInt.h
@@ -1881,13 +1881,6 @@ struct Column {
 	u8 is_primkey;		/* Boolean propertie for being PK */
 };
 
-/*
- * A sort order can be either ASC or DESC.
- */
-#define SQLITE_SO_ASC       0	/* Sort in ascending order */
-#define SQLITE_SO_DESC      1	/* Sort in ascending order */
-#define SQLITE_SO_UNDEFINED -1	/* No sort order specified */
-
 /*
  * Column affinity types.
  *
@@ -2143,7 +2136,8 @@ struct Index {
 	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 */
-	u8 *aSortOrder;		/* for each column: True==DESC, False==ASC */
+	/** Sorting order for each column. */
+	enum sort_order *sort_order;
 	/** Array of collation sequences for index. */
 	struct coll **coll_array;
 	Expr *pPartIdxWhere;	/* WHERE clause for partial indices */
@@ -3522,7 +3516,7 @@ Index *sqlite3PrimaryKeyIndex(Table *);
 void sqlite3StartTable(Parse *, Token *, int);
 void sqlite3AddColumn(Parse *, Token *, Token *);
 void sqlite3AddNotNull(Parse *, int);
-void sqlite3AddPrimaryKey(Parse *, ExprList *, int, int, int);
+void sqlite3AddPrimaryKey(Parse *, ExprList *, int, int, enum sort_order);
 void sqlite3AddCheckConstraint(Parse *, Expr *);
 void sqlite3AddDefaultValue(Parse *, ExprSpan *);
 void sqlite3AddCollateType(Parse *, Token *);
@@ -3540,6 +3534,16 @@ sql_default_coll();
 bool
 space_is_view(Table *);
 
+/**
+ * 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);
diff --git a/src/box/sql/vdbe.h b/src/box/sql/vdbe.h
index 340ddc7..e244606 100644
--- a/src/box/sql/vdbe.h
+++ b/src/box/sql/vdbe.h
@@ -145,6 +145,7 @@ typedef struct VdbeOpList VdbeOpList;
 #define P4_FUNCCTX  (-16)	/* P4 is a pointer to an sqlite3_context object */
 #define P4_BOOL     (-17)	/* P4 is a bool value */
 #define P4_PTR      (-18)	/* P4 is a generic pointer */
+#define P4_KEYDEF   (-19)       /* P4 is a pointer to key_def structure. */
 
 
 /* Error message codes for OP_Halt */
diff --git a/src/box/sql/where.c b/src/box/sql/where.c
index 7a7103c..bad964a 100644
--- a/src/box/sql/where.c
+++ b/src/box/sql/where.c
@@ -1336,8 +1336,8 @@ whereRangeScanEst(Parse * pParse,	/* Parsing & code generating context */
 			       || (pLower->eOperator & (WO_GT | WO_GE)) != 0);
 			assert(pUpper == 0
 			       || (pUpper->eOperator & (WO_LT | WO_LE)) != 0);
-			assert(p->aSortOrder != 0);
-			if (p->aSortOrder[nEq]) {
+			if (sql_index_column_sort_order(p, nEq) !=
+			    SORT_ORDER_ASC) {
 				/* The roles of pLower and pUpper are swapped for a DESC index */
 				SWAP(pLower, pUpper);
 				SWAP(nBtm, nTop);
@@ -2229,8 +2229,8 @@ whereRangeVectorLen(Parse * pParse,	/* Parsing context */
 		if (pLhs->op != TK_COLUMN
 		    || pLhs->iTable != iCur
 		    || pLhs->iColumn != pIdx->aiColumn[i + nEq]
-		    || pIdx->aSortOrder[i + nEq] != pIdx->aSortOrder[nEq]
-		    ) {
+		    || sql_index_column_sort_order(pIdx, i + nEq) !=
+		       sql_index_column_sort_order(pIdx, nEq)) {
 			break;
 		}
 
@@ -3316,7 +3316,8 @@ wherePathSatisfiesOrderBy(WhereInfo * pWInfo,	/* The WHERE clause */
 				 */
 				if (pIndex) {
 					iColumn = pIndex->aiColumn[j];
-					revIdx = pIndex->aSortOrder[j];
+					revIdx = sql_index_column_sort_order(pIndex,
+									     j);
 					if (iColumn == pIndex->pTable->iPKey)
 						iColumn = -1;
 				} else {
diff --git a/src/box/sql/wherecode.c b/src/box/sql/wherecode.c
index f1112f2..231d690 100644
--- a/src/box/sql/wherecode.c
+++ b/src/box/sql/wherecode.c
@@ -462,7 +462,8 @@ codeEqualityTerm(Parse * pParse,	/* The parsing context */
 		int nEq = 0;
 		int *aiMap = 0;
 
-		if (pLoop->pIndex != 0 && pLoop->pIndex->aSortOrder[iEq]) {
+		if (pLoop->pIndex != 0 &&
+		    sql_index_column_sort_order(pLoop->pIndex, iEq)) {
 			testcase(iEq == 0);
 			testcase(bRev);
 			bRev = !bRev;
@@ -1296,12 +1297,12 @@ sqlite3WhereCodeOneLoopStart(WhereInfo * pWInfo,	/* Complete information about t
 				 */
 				testcase(bRev);
 				testcase(pIdx->aSortOrder[nEq] ==
-					 SQLITE_SO_DESC);
+					 SORT_ORDER_DESC);
 				assert((bRev & ~1) == 0);
 				pLevel->iLikeRepCntr <<= 1;
 				pLevel->iLikeRepCntr |=
-				    bRev ^ (pIdx->aSortOrder[nEq] ==
-					    SQLITE_SO_DESC);
+					bRev ^ (sql_index_column_sort_order(pIdx, nEq) ==
+						SORT_ORDER_DESC);
 			}
 #endif
 			if (pRangeStart == 0) {
@@ -1320,7 +1321,8 @@ sqlite3WhereCodeOneLoopStart(WhereInfo * pWInfo,	/* Complete information about t
 		 * start and end terms (pRangeStart and pRangeEnd).
 		 */
 		if ((nEq < nIdxCol &&
-		     bRev == (pIdx->aSortOrder[nEq] == SQLITE_SO_ASC)) ||
+		     bRev == (sql_index_column_sort_order(pIdx, nEq) ==
+			      SORT_ORDER_ASC)) ||
 		    (bRev && nIdxCol == nEq)) {
 			SWAP(pRangeEnd, pRangeStart);
 			SWAP(bSeekPastNull, bStopAtNull);

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

* [tarantool-patches] Re: [PATCH 2/2] sql: replace KeyInfo with key_def
  2018-05-10 12:59     ` Kirill Yukhin
@ 2018-05-11 11:22       ` Vladislav Shpilevoy
  2018-05-11 12:56         ` Kirill Yukhin
  0 siblings, 1 reply; 17+ messages in thread
From: Vladislav Shpilevoy @ 2018-05-11 11:22 UTC (permalink / raw)
  To: tarantool-patches, Kirill Yukhin


Hello. Thanks for fixes. See travis: https://travis-ci.org/tarantool/tarantool/jobs/377267310.

See 10 comments below.

> diff --git a/src/box/sql/build.c b/src/box/sql/build.c
> index 029c71e..bfaf3af 100644
> --- a/src/box/sql/build.c
> +++ b/src/box/sql/build.c
> @@ -1119,10 +1131,9 @@ sql_index_collation(Index *idx, uint32_t column)
>   		return idx->coll_array[column];
>   	}
>   
> -	uint32_t index_id = SQLITE_PAGENO_TO_INDEXID(idx->tnum);
> -	struct index *index = space_index(space, index_id);
> -	assert(index != NULL && index->def->key_def->part_count >= column);
> -	return index->def->key_def->parts[column].coll;
> +	struct key_def *key_def = sql_index_key_def(idx);

1. Sql_index_key_def makes dup, that is not needed to get the collation.

2. It leaks.

> +	assert(key_def != NULL && key_def->part_count >= column);

3. Assertion can fail on OOM.
> @@ -1143,10 +1154,9 @@ sql_index_column_sort_order(Index *idx, uint32_t column)
>   		return idx->sort_order[column];
>   	}
>   
> -	uint32_t index_id = SQLITE_PAGENO_TO_INDEXID(idx->tnum);
> -	struct index *index = space_index(space, index_id);
> -	assert(index != NULL && index->def->key_def->part_count >= column);
> -	return index->def->key_def->parts[column].sort_order;
> +	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;

4. All the same. Maybe it is better to do not dup key_def in sql_index_key_def
and just return it as is. And do dup() in caller code.


> @@ -2643,14 +2650,13 @@ sqlite3RefillIndex(Parse * pParse, Index * pIndex, int memRootPage)
>   	} else {
>   		tnum = pIndex->tnum;
>   	}
> -	pKey = sqlite3KeyInfoOfIndex(pParse, db, pIndex);
> -	assert(pKey != 0 || db->mallocFailed || pParse->nErr);
> +	struct key_def *def = sql_index_key_def(pIndex);
> +	assert(def != NULL || db->mallocFailed || pParse->nErr);

5. Assertion can fail - when sql_index_key_def fails to do dup(),
it does not set mallocFailed and nErr.
> diff --git a/src/box/sql/delete.c b/src/box/sql/delete.c
> index 3f74b93..b6fc135 100644
> --- a/src/box/sql/delete.c
> +++ b/src/box/sql/delete.c
> @@ -386,10 +386,10 @@ sqlite3DeleteFrom(Parse * pParse,	/* The parser context */
>   			iPk = pParse->nMem + 1;
>   			pParse->nMem += nPk;
>   			iEphCur = pParse->nTab++;
> -			KeyInfo *pKeyInfo = sqlite3KeyInfoAlloc(pParse->db, nPk, 0);
> +			struct key_def *def = key_def_new(nPk);

6. Nobody sets pParse->rc. Maybe it is better to declare a function like
parser_key_def_new, that takes all the same as key_def + struct Parse, and sets
rc = SQL_TARANTOOL_ERROR to get OOM from diag.

> diff --git a/src/box/sql/expr.c b/src/box/sql/expr.c
> index cc969ca..635357b 100644
> --- a/src/box/sql/expr.c
> +++ b/src/box/sql/expr.c
> @@ -1505,7 +1505,7 @@ sqlite3ExprListDup(sqlite3 * db, ExprList * p, int flags)
>   		}
>   		pItem->zName = sqlite3DbStrDup(db, pOldItem->zName);
>   		pItem->zSpan = sqlite3DbStrDup(db, pOldItem->zSpan);
> -		pItem->sortOrder = pOldItem->sortOrder;
> +		pItem->sort_order = pOldItem->sort_order;
>   		pItem->done = 0;
>   		pItem->bSpanIsTab = pOldItem->bSpanIsTab;
>   		pItem->u = pOldItem->u;
> @@ -1766,10 +1766,13 @@ sqlite3ExprListSetSortOrder(ExprList * p, int iSortOrder)
>   		return;
>   	assert(p->nExpr > 0);
>   	if (iSortOrder == SORT_ORDER_UNDEF) {
> -		assert(p->a[p->nExpr - 1].sortOrder == SORT_ORDER_ASC);
> +		assert(p->a[p->nExpr - 1].sort_order == SORT_ORDER_ASC);
>   		return;
>   	}
> -	p->a[p->nExpr - 1].sortOrder = (u8) iSortOrder;
> +	if (iSortOrder == 0)
> +		p->a[p->nExpr - 1].sort_order = SORT_ORDER_ASC;
> +	else
> +		p->a[p->nExpr - 1].sort_order = SORT_ORDER_DESC;

7. Lets make iSortOrder be enum sort_order and remove this 'if's.

> @@ -2761,7 +2763,7 @@ sqlite3CodeSubselect(Parse * pParse,	/* Parsing context */
>   			pExpr->is_ephemeral = 1;
>   			addr = sqlite3VdbeAddOp2(v, OP_OpenTEphemeral,
>   						 pExpr->iTable, nVal);
> -			pKeyInfo = sqlite3KeyInfoAlloc(pParse->db, nVal, 1);
> +			struct key_def *key_def = key_def_new(nVal);

8. pParse->rc is not set to error.
> @@ -2787,29 +2789,34 @@ sqlite3CodeSubselect(Parse * pParse,	/* Parsing context */
>   					pSelect->iLimit = 0;
>   					testcase(pSelect->
>   						 selFlags & SF_Distinct);
> -					testcase(pKeyInfo == 0);	/* Caused by OOM in sqlite3KeyInfoAlloc() */
>   					if (sqlite3Select
>   					    (pParse, pSelect, &dest)) {
>   						sqlite3DbFree(pParse->db,
>   							      dest.zAffSdst);
> -						sqlite3KeyInfoUnref(pKeyInfo);
> +						if (key_def != NULL)
> +							free(key_def);
>   						return 0;
>   					}
>   					sqlite3DbFree(pParse->db,
>   						      dest.zAffSdst);
> -					assert(pKeyInfo != 0);	/* OOM will cause exit after sqlite3Select() */
> +					assert(key_def != NULL);

9. Assertion fails, if key_def == NULL. Maybe it is better to check key_def == NULL once right
after key_def_new, return on error, and remove this 'if (key_def != NULL)' everywhere.
> diff --git a/src/box/sql/insert.c b/src/box/sql/insert.c
> index 1a34f71..95a2610 100644
> --- a/src/box/sql/insert.c
> +++ b/src/box/sql/insert.c
> @@ -565,9 +565,9 @@ sqlite3Insert(Parse * pParse,	/* Parser context */
>   			regRec = sqlite3GetTempReg(pParse);
>   			regCopy = sqlite3GetTempRange(pParse, nColumn);
>   			regTempId = sqlite3GetTempReg(pParse);
> -			KeyInfo *pKeyInfo = sqlite3KeyInfoAlloc(pParse->db, 1+nColumn, 0);
> +			struct key_def *def = key_def_new(nColumn + 1);

10. No error code is set. I will not repeat this comment below, but the error code is not
set in more places.

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

* [tarantool-patches] Re: [PATCH 2/2] sql: replace KeyInfo with key_def
  2018-05-11 11:22       ` Vladislav Shpilevoy
@ 2018-05-11 12:56         ` Kirill Yukhin
  2018-05-11 19:05           ` Vladislav Shpilevoy
  0 siblings, 1 reply; 17+ messages in thread
From: Kirill Yukhin @ 2018-05-11 12:56 UTC (permalink / raw)
  To: Vladislav Shpilevoy; +Cc: tarantool-patches

Hi Vlad,
On 11 мая 14:22, Vladislav Shpilevoy wrote:
> 
> Hello. Thanks for fixes. See travis: https://travis-ci.org/tarantool/tarantool/jobs/377267310.
> 
> See 10 comments below.
Thanks, I've updated the patch (in the bottom). Answers inlined.

> > diff --git a/src/box/sql/build.c b/src/box/sql/build.c
> > index 029c71e..bfaf3af 100644
> > --- a/src/box/sql/build.c
> > +++ b/src/box/sql/build.c
> > @@ -1119,10 +1131,9 @@ sql_index_collation(Index *idx, uint32_t column)
> >   		return idx->coll_array[column];
> >   	}
> > -	uint32_t index_id = SQLITE_PAGENO_TO_INDEXID(idx->tnum);
> > -	struct index *index = space_index(space, index_id);
> > -	assert(index != NULL && index->def->key_def->part_count >= column);
> > -	return index->def->key_def->parts[column].coll;
> > +	struct key_def *key_def = sql_index_key_def(idx);
> 
> 1. Sql_index_key_def makes dup, that is not needed to get the collation.
> 
> 2. It leaks.
I've added a flag which signals if duplication is needed to sql_index_key_def.

> > +	assert(key_def != NULL && key_def->part_count >= column);
> 
> 3. Assertion can fail on OOM.
No more mallocs, see p 1, 2.

> > @@ -1143,10 +1154,9 @@ sql_index_column_sort_order(Index *idx, uint32_t column)
> >   		return idx->sort_order[column];
> >   	}
> > -	uint32_t index_id = SQLITE_PAGENO_TO_INDEXID(idx->tnum);
> > -	struct index *index = space_index(space, index_id);
> > -	assert(index != NULL && index->def->key_def->part_count >= column);
> > -	return index->def->key_def->parts[column].sort_order;
> > +	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;
> 
> 4. All the same. Maybe it is better to do not dup key_def in sql_index_key_def
> and just return it as is. And do dup() in caller code.
Ditto.

> > @@ -2643,14 +2650,13 @@ sqlite3RefillIndex(Parse * pParse, Index * pIndex, int memRootPage)
> >   	} else {
> >   		tnum = pIndex->tnum;
> >   	}
> > -	pKey = sqlite3KeyInfoOfIndex(pParse, db, pIndex);
> > -	assert(pKey != 0 || db->mallocFailed || pParse->nErr);
> > +	struct key_def *def = sql_index_key_def(pIndex);
> > +	assert(def != NULL || db->mallocFailed || pParse->nErr);
> 
> 5. Assertion can fail - when sql_index_key_def fails to do dup(),
> it does not set mallocFailed and nErr.
Ditto.

> > diff --git a/src/box/sql/delete.c b/src/box/sql/delete.c
> > index 3f74b93..b6fc135 100644
> > --- a/src/box/sql/delete.c
> > +++ b/src/box/sql/delete.c
> > @@ -386,10 +386,10 @@ sqlite3DeleteFrom(Parse * pParse,	/* The parser context */
> >   			iPk = pParse->nMem + 1;
> >   			pParse->nMem += nPk;
> >   			iEphCur = pParse->nTab++;
> > -			KeyInfo *pKeyInfo = sqlite3KeyInfoAlloc(pParse->db, nPk, 0);
> > +			struct key_def *def = key_def_new(nPk);
> 
> 6. Nobody sets pParse->rc. Maybe it is better to declare a function like
> parser_key_def_new, that takes all the same as key_def + struct Parse, and sets
> rc = SQL_TARANTOOL_ERROR to get OOM from diag.
I think we should refactor all error reporting, so no need to do that right now.
I've added check for OOM.

> > diff --git a/src/box/sql/expr.c b/src/box/sql/expr.c
> > index cc969ca..635357b 100644
> > --- a/src/box/sql/expr.c
> > +++ b/src/box/sql/expr.c
> > @@ -1766,10 +1766,13 @@ sqlite3ExprListSetSortOrder(ExprList * p, int iSortOrder)
> >   		return;
> >   	assert(p->nExpr > 0);
> >   	if (iSortOrder == SORT_ORDER_UNDEF) {
> > -		assert(p->a[p->nExpr - 1].sortOrder == SORT_ORDER_ASC);
> > +		assert(p->a[p->nExpr - 1].sort_order == SORT_ORDER_ASC);
> >   		return;
> >   	}
> > -	p->a[p->nExpr - 1].sortOrder = (u8) iSortOrder;
> > +	if (iSortOrder == 0)
> > +		p->a[p->nExpr - 1].sort_order = SORT_ORDER_ASC;
> > +	else
> > +		p->a[p->nExpr - 1].sort_order = SORT_ORDER_DESC;
> 
> 7. Lets make iSortOrder be enum sort_order and remove this 'if's.
Done. Refactored sqlite3CreateIndex() as well

> > @@ -2761,7 +2763,7 @@ sqlite3CodeSubselect(Parse * pParse,	/* Parsing context */
> >   			pExpr->is_ephemeral = 1;
> >   			addr = sqlite3VdbeAddOp2(v, OP_OpenTEphemeral,
> >   						 pExpr->iTable, nVal);
> > -			pKeyInfo = sqlite3KeyInfoAlloc(pParse->db, nVal, 1);
> > +			struct key_def *key_def = key_def_new(nVal);
> 
> 8. pParse->rc is not set to error.
Fixed.

> > @@ -2787,29 +2789,34 @@ sqlite3CodeSubselect(Parse * pParse,	/* Parsing context */
> >   					pSelect->iLimit = 0;
> >   					testcase(pSelect->
> >   						 selFlags & SF_Distinct);
> > -					testcase(pKeyInfo == 0);	/* Caused by OOM in sqlite3KeyInfoAlloc() */
> >   					if (sqlite3Select
> >   					    (pParse, pSelect, &dest)) {
> >   						sqlite3DbFree(pParse->db,
> >   							      dest.zAffSdst);
> > -						sqlite3KeyInfoUnref(pKeyInfo);
> > +						if (key_def != NULL)
> > +							free(key_def);
> >   						return 0;
> >   					}
> >   					sqlite3DbFree(pParse->db,
> >   						      dest.zAffSdst);
> > -					assert(pKeyInfo != 0);	/* OOM will cause exit after sqlite3Select() */
> > +					assert(key_def != NULL);
> 
> 9. Assertion fails, if key_def == NULL. Maybe it is better to check key_def == NULL once right
> after key_def_new, return on error, and remove this 'if (key_def != NULL)' everywhere.
Done.

> > diff --git a/src/box/sql/insert.c b/src/box/sql/insert.c
> > index 1a34f71..95a2610 100644
> > --- a/src/box/sql/insert.c
> > +++ b/src/box/sql/insert.c
> > @@ -565,9 +565,9 @@ sqlite3Insert(Parse * pParse,	/* Parser context */
> >   			regRec = sqlite3GetTempReg(pParse);
> >   			regCopy = sqlite3GetTempRange(pParse, nColumn);
> >   			regTempId = sqlite3GetTempReg(pParse);
> > -			KeyInfo *pKeyInfo = sqlite3KeyInfoAlloc(pParse->db, 1+nColumn, 0);
> > +			struct key_def *def = key_def_new(nColumn + 1);
> 
> 10. No error code is set. I will not repeat this comment below, but the error code is not
> set in more places.
Fixed.

--
Regards, Kirill Yukhin

commit db01ff515830b422ee7a874f5c0d34e6814222c5
Author: Kirill Yukhin <kyukhin@tarantool.org>
Date:   Fri May 4 16:11:02 2018 +0300

    sql: replace KeyInfo with key_def
    
    KeyInfo is a legacy struct which was heavily used in SQL
    front-end. This patch replaces all its usages w/ Tarantool's
    natural key description structure called key_def.
    
    This change is a part of data dictionary integration effort:
    Tarantool indexes don't aware of KeyInfo, that is why it was
    evicted.
    
    Legacy KeyInfo memory handling was ref-counting based and now
    is replaced w/ memory duplication. This state of affairs should
    be improved in future.
    
    Part of #3235

diff --git a/src/box/sql.c b/src/box/sql.c
index e312d03..79da550 100644
--- a/src/box/sql.c
+++ b/src/box/sql.c
@@ -357,7 +357,7 @@ int tarantoolSqlite3Count(BtCursor *pCur, i64 *pnEntry)
 	return SQLITE_OK;
 }
 
-/*
+/**
  * Create ephemeral space and set cursor to the first entry. Features of
  * ephemeral spaces: id == 0, name == "ephemeral", memtx engine (in future it
  * can be changed, but now only memtx engine is supported), primary index
@@ -366,12 +366,12 @@ int tarantoolSqlite3Count(BtCursor *pCur, i64 *pnEntry)
  *
  * @param pCur Cursor which will point to the new ephemeral space.
  * @param field_count Number of fields in ephemeral space.
- * @param aColl Collation sequence of ephemeral space.
+ * @param def Keys description for new ephemeral space.
  *
  * @retval SQLITE_OK on success, SQLITE_TARANTOOL_ERROR otherwise.
  */
 int tarantoolSqlite3EphemeralCreate(BtCursor *pCur, uint32_t field_count,
-				    struct coll *aColl)
+				    struct key_def *def)
 {
 	assert(pCur);
 	assert(pCur->curFlags & BTCF_TEphemCursor);
@@ -380,12 +380,13 @@ int tarantoolSqlite3EphemeralCreate(BtCursor *pCur, uint32_t field_count,
 	if (ephemer_key_def == NULL)
 		return SQL_TARANTOOL_ERROR;
 	for (uint32_t part = 0; part < field_count; ++part) {
-		key_def_set_part(ephemer_key_def, part /* part no */,
-				 part /* filed no */,
-				 FIELD_TYPE_SCALAR,
-				 ON_CONFLICT_ACTION_NONE /* nullable_action */,
-				 aColl /* coll */,
-				 SORT_ORDER_ASC);
+		struct coll *coll;
+		if (part < def->part_count)
+			coll = def->parts[part].coll;
+		else
+			coll = NULL;
+		key_def_set_part(ephemer_key_def, part, part, FIELD_TYPE_SCALAR,
+				 ON_CONFLICT_ACTION_NONE, coll, SORT_ORDER_ASC);
 	}
 
 	struct index_def *ephemer_index_def =
@@ -929,16 +930,13 @@ rename_fail:
 	return SQL_TARANTOOL_ERROR;
 }
 
-/*
- * Performs exactly as extract_key + sqlite3VdbeCompareMsgpack,
- * only faster.
- */
-int tarantoolSqlite3IdxKeyCompare(BtCursor *pCur, UnpackedRecord *pUnpacked,
-				  int *res)
+int
+tarantoolSqlite3IdxKeyCompare(struct BtCursor *cursor,
+			      struct UnpackedRecord *unpacked, int *res)
 {
-	assert(pCur->curFlags & BTCF_TaCursor);
-	assert(pCur->iter != NULL);
-	assert(pCur->last_tuple != NULL);
+	assert(cursor->curFlags & BTCF_TaCursor);
+	assert(cursor->iter != NULL);
+	assert(cursor->last_tuple != NULL);
 
 	const box_key_def_t *key_def;
 	const struct tuple *tuple;
@@ -955,9 +953,9 @@ int tarantoolSqlite3IdxKeyCompare(BtCursor *pCur, UnpackedRecord *pUnpacked,
 	uint32_t key_size;
 #endif
 
-	key_def = box_iterator_key_def(pCur->iter);
-	n = MIN(pUnpacked->nField, key_def->part_count);
-	tuple = pCur->last_tuple;
+	key_def = box_iterator_key_def(cursor->iter);
+	n = MIN(unpacked->nField, key_def->part_count);
+	tuple = cursor->last_tuple;
 	base = tuple_data(tuple);
 	format = tuple_format(tuple);
 	field_map = tuple_field_map(tuple);
@@ -991,28 +989,28 @@ int tarantoolSqlite3IdxKeyCompare(BtCursor *pCur, UnpackedRecord *pUnpacked,
 			} else {
 				p = base + field_map[
 					format->fields[fieldno].offset_slot
-];
+					];
 			}
 		}
 		next_fieldno = fieldno + 1;
-		rc = sqlite3VdbeCompareMsgpack(&p, pUnpacked, i);
+		rc = sqlite3VdbeCompareMsgpack(&p, unpacked, i);
 		if (rc != 0) {
-			if (pUnpacked->pKeyInfo->aSortOrder[i]) {
+			if (unpacked->key_def->parts[i].sort_order !=
+			    SORT_ORDER_ASC) {
 				rc = -rc;
 			}
 			*res = rc;
 			goto out;
 		}
 	}
-	*res = pUnpacked->default_rc;
+	*res = unpacked->default_rc;
 out:
 #ifndef NDEBUG
 	/* Sanity check. */
 	original_size = region_used(&fiber()->gc);
 	key = tuple_extract_key(tuple, key_def, &key_size);
 	if (key != NULL) {
-		rc = sqlite3VdbeRecordCompareMsgpack((int)key_size, key,
-						     pUnpacked);
+		rc = sqlite3VdbeRecordCompareMsgpack(key, unpacked);
 		region_truncate(&fiber()->gc, original_size);
 		assert(rc == *res);
 	}
diff --git a/src/box/sql/analyze.c b/src/box/sql/analyze.c
index f0054c5..ec23481 100644
--- a/src/box/sql/analyze.c
+++ b/src/box/sql/analyze.c
@@ -176,8 +176,8 @@ openStatTable(Parse * pParse,	/* Parsing context */
 	/* Open the sql_stat[134] tables for writing. */
 	for (i = 0; aTable[i]; i++) {
 		int addr = emit_open_cursor(pParse, iStatCur + i, aRoot[i]);
-		v->aOp[addr].p4.pKeyInfo = 0;
-		v->aOp[addr].p4type = P4_KEYINFO;
+		v->aOp[addr].p4.key_def = NULL;
+		v->aOp[addr].p4type = P4_KEYDEF;
 		sqlite3VdbeChangeP5(v, aCreateTbl[i]);
 		VdbeComment((v, aTable[i]));
 	}
@@ -914,7 +914,7 @@ analyzeOneTable(Parse * pParse,	/* Parser context */
 				     (void *) space);
 		sqlite3VdbeAddOp3(v, OP_OpenRead, iIdxCur, pIdx->tnum,
 				  space_ptr_reg);
-		sqlite3VdbeSetP4KeyInfo(pParse, pIdx);
+		sql_vdbe_set_p4_key_def(pParse, pIdx);
 		VdbeComment((v, "%s", pIdx->zName));
 
 		/* Invoke the stat_init() function. The arguments are:
diff --git a/src/box/sql/build.c b/src/box/sql/build.c
index 49ac1fa..be40982 100644
--- a/src/box/sql/build.c
+++ b/src/box/sql/build.c
@@ -985,13 +985,13 @@ sqlite3AddPrimaryKey(Parse * pParse,	/* Parsing context */
 			pTab->tabFlags |= TF_Autoincrement;
 		}
 		if (pList)
-			pParse->iPkSortOrder = pList->a[0].sortOrder;
+			pParse->iPkSortOrder = pList->a[0].sort_order;
 	} else if (autoInc) {
 		sqlite3ErrorMsg(pParse, "AUTOINCREMENT is only allowed on an "
 				"INTEGER PRIMARY KEY or INT PRIMARY KEY");
 	} else {
-		sqlite3CreateIndex(pParse, 0, 0, pList, onError, 0,
-				   0, sortOrder, 0, SQLITE_IDXTYPE_PRIMARYKEY);
+		sql_create_index(pParse, 0, 0, pList, onError, 0,
+				 0, sortOrder, false, SQLITE_IDXTYPE_PRIMARYKEY);
 		pList = 0;
 	}
 
@@ -1094,6 +1094,21 @@ sql_column_collation(Table *table, uint32_t column)
 	return space->format->fields[column].coll;
 }
 
+struct key_def*
+sql_index_key_def(struct Index *idx, bool is_dup)
+{
+	uint32_t space_id = SQLITE_PAGENO_TO_SPACEID(idx->pTable->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);
+	if (is_dup)
+		return key_def_dup(index->def->key_def);
+	else
+		return index->def->key_def;
+}
+
 /**
  * Return name of given column collation from index.
  *
@@ -1119,10 +1134,9 @@ sql_index_collation(Index *idx, uint32_t column)
 		return idx->coll_array[column];
 	}
 
-	uint32_t index_id = SQLITE_PAGENO_TO_INDEXID(idx->tnum);
-	struct index *index = space_index(space, index_id);
-	assert(index != NULL && index->def->key_def->part_count >= column);
-	return index->def->key_def->parts[column].coll;
+	struct key_def *key_def = sql_index_key_def(idx, false);
+	assert(key_def != NULL && key_def->part_count >= column);
+	return key_def->parts[column].coll;
 }
 
 enum sort_order
@@ -1143,10 +1157,9 @@ sql_index_column_sort_order(Index *idx, uint32_t column)
 		return idx->sort_order[column];
 	}
 
-	uint32_t index_id = SQLITE_PAGENO_TO_INDEXID(idx->tnum);
-	struct index *index = space_index(space, index_id);
-	assert(index != NULL && index->def->key_def->part_count >= column);
-	return index->def->key_def->parts[column].sort_order;
+	struct key_def *key_def = sql_index_key_def(idx, false);
+	assert(key_def != NULL && key_def->part_count >= column);
+	return key_def->parts[column].sort_order;
 }
 
 /**
@@ -1443,9 +1456,7 @@ hasColumn(const i16 * aiCol, int nCol, int x)
  *     (2)  Set the Index.tnum of the PRIMARY KEY Index object in the
  *          schema to the rootpage from the main table.
  *     (3)  Add all table columns to the PRIMARY KEY Index object
- *          so that the PRIMARY KEY is a covering index.  The surplus
- *          columns are part of KeyInfo.nXField and are not used for
- *          sorting or lookup or uniqueness checks.
+ *          so that the PRIMARY KEY is a covering index.
  */
 static void
 convertToWithoutRowidTable(Parse * pParse, Table * pTab)
@@ -1476,10 +1487,11 @@ convertToWithoutRowidTable(Parse * pParse, Table * pTab)
 							       &ipkToken, 0));
 		if (pList == 0)
 			return;
-		pList->a[0].sortOrder = pParse->iPkSortOrder;
+		pList->a[0].sort_order = pParse->iPkSortOrder;
 		assert(pParse->pNewTable == pTab);
-		sqlite3CreateIndex(pParse, 0, 0, pList, pTab->keyConf, 0, 0, 0,
-				   0, SQLITE_IDXTYPE_PRIMARYKEY);
+		sql_create_index(pParse, 0, 0, pList, pTab->keyConf, 0, 0,
+				 SORT_ORDER_ASC, false,
+				 SQLITE_IDXTYPE_PRIMARYKEY);
 		if (db->mallocFailed)
 			return;
 		pPk = sqlite3PrimaryKeyIndex(pTab);
@@ -2632,7 +2644,6 @@ sqlite3RefillIndex(Parse * pParse, Index * pIndex, int memRootPage)
 	int tnum;		/* Root page of index */
 	int iPartIdxLabel;	/* Jump to this label to skip a row */
 	Vdbe *v;		/* Generate code into this virtual machine */
-	KeyInfo *pKey;		/* KeyInfo for index */
 	int regRecord;		/* Register holding assembled index record */
 	sqlite3 *db = pParse->db;	/* The database connection */
 	v = sqlite3GetVdbe(pParse);
@@ -2643,14 +2654,13 @@ sqlite3RefillIndex(Parse * pParse, Index * pIndex, int memRootPage)
 	} else {
 		tnum = pIndex->tnum;
 	}
-	pKey = sqlite3KeyInfoOfIndex(pParse, db, pIndex);
-	assert(pKey != 0 || db->mallocFailed || pParse->nErr);
+	struct key_def *def = sql_index_key_def(pIndex, false);
+	assert(def != NULL || db->mallocFailed || pParse->nErr);
 
 	/* Open the sorter cursor if we are to use one. */
 	iSorter = pParse->nTab++;
 	sqlite3VdbeAddOp4(v, OP_SorterOpen, iSorter, 0, pIndex->nColumn,
-			  (char *)
-			  sqlite3KeyInfoRef(pKey), P4_KEYINFO);
+			  (char *)def, P4_KEYDEF);
 
 	/* Open the table. Loop through all rows of the table, inserting index
 	 * records into the sorter.
@@ -2836,34 +2846,11 @@ index_is_unique(Index *idx)
 	return tnt_index->def->opts.is_unique;
 }
 
-/*
- * Create a new index for an SQL table.  pName1.pName2 is the name of the index
- * and pTblList is the name of the table that is to be indexed.  Both will
- * be NULL for a primary key or an index that is created to satisfy a
- * UNIQUE constraint.  If pTable and pIndex are NULL, use pParse->pNewTable
- * as the table to be indexed.  pParse->pNewTable is a table that is
- * currently being constructed by a CREATE TABLE statement.
- *
- * pList is a list of columns to be indexed.  pList will be NULL if this
- * is a primary key or unique-constraint on the most recent column added
- * to the table currently under construction.
- */
 void
-sqlite3CreateIndex(Parse * pParse,	/* All information about this parse */
-		   Token * pName,	/* Index name. May be NULL */
-		   SrcList * pTblName,	/* Table to index. Use pParse->pNewTable if 0 */
-		   ExprList * pList,	/* A list of columns to be indexed */
-		   int onError,	        /* ON_CONFLICT_ACTION_ABORT, _IGNORE,
-					 * _REPLACE, or _NONE.
-					 */
-		   Token MAYBE_UNUSED * pStart,	/* The CREATE token that begins
-						 * this statement
-						 */
-		   Expr * pPIWhere,	/* WHERE clause for partial indices */
-		   int sortOrder,	/* Sort order of primary key when pList==NULL */
-		   int ifNotExist,	/* Omit error if index already exists */
-		   u8 idxType	/* The index type */
-    )
+sql_create_index(struct Parse *parse, struct Token *token,
+		 struct SrcList *tbl_name, struct ExprList *col_list,
+		 int on_error, struct Token *start, struct Expr *pi_where,
+		 enum sort_order sort_order, bool if_not_exist, u8 idx_type)
 {
 	Table *pTab = 0;	/* Table to be indexed */
 	Index *pIndex = 0;	/* The index to be created */
@@ -2871,13 +2858,13 @@ sqlite3CreateIndex(Parse * pParse,	/* All information about this parse */
 	int nName;		/* Number of characters in zName */
 	int i, j;
 	DbFixer sFix;		/* For assigning database names to pTable */
-	sqlite3 *db = pParse->db;
-	struct ExprList_item *pListItem;	/* For looping over pList */
+	sqlite3 *db = parse->db;
+	struct ExprList_item *col_listItem;	/* For looping over col_list */
 	int nExtra = 0;		/* Space allocated for zExtra[] */
 	char *zExtra = 0;	/* Extra space after the Index object */
 	struct session *user_session = current_session();
 
-	if (db->mallocFailed || pParse->nErr > 0) {
+	if (db->mallocFailed || parse->nErr > 0) {
 		goto exit_create_index;
 	}
 	/* Do not account nested operations: the count of such
@@ -2886,8 +2873,8 @@ sqlite3CreateIndex(Parse * pParse,	/* All information about this parse */
 	 * PRIMARY KEY and UNIQUE constraint - they had been accounted
 	 * in CREATE TABLE already.
 	 */
-	if (!pParse->nested && idxType == SQLITE_IDXTYPE_APPDEF) {
-		Vdbe *v = sqlite3GetVdbe(pParse);
+	if (!parse->nested && idx_type == SQLITE_IDXTYPE_APPDEF) {
+		Vdbe *v = sqlite3GetVdbe(parse);
 		if (v == NULL)
 			goto exit_create_index;
 		sqlite3VdbeCountChanges(v);
@@ -2897,39 +2884,39 @@ sqlite3CreateIndex(Parse * pParse,	/* All information about this parse */
 	/*
 	 * Find the table that is to be indexed.  Return early if not found.
 	 */
-	if (pTblName != 0) {
+	if (tbl_name != 0) {
 
 		/* Use the two-part index name to determine the database
 		 * to search for the table. 'Fix' the table name to this db
 		 * before looking up the table.
 		 */
-		assert(pName && pName->z);
+		assert(token && token->z);
 
-		sqlite3FixInit(&sFix, pParse, "index", pName);
-		if (sqlite3FixSrcList(&sFix, pTblName)) {
-			/* Because the parser constructs pTblName from a single identifier,
+		sqlite3FixInit(&sFix, parse, "index", token);
+		if (sqlite3FixSrcList(&sFix, tbl_name)) {
+			/* Because the parser constructs tbl_name from a single identifier,
 			 * sqlite3FixSrcList can never fail.
 			 */
 			assert(0);
 		}
-		pTab = sqlite3LocateTable(pParse, 0, pTblName->a[0].zName);
+		pTab = sqlite3LocateTable(parse, 0, tbl_name->a[0].zName);
 		assert(db->mallocFailed == 0 || pTab == 0);
 		if (pTab == 0)
 			goto exit_create_index;
 		sqlite3PrimaryKeyIndex(pTab);
 	} else {
-		assert(pName == 0);
-		assert(pStart == 0);
-		pTab = pParse->pNewTable;
+		assert(token == 0);
+		assert(start == 0);
+		pTab = parse->pNewTable;
 		if (!pTab)
 			goto exit_create_index;
 	}
 
 	assert(pTab != 0);
-	assert(pParse->nErr == 0);
+	assert(parse->nErr == 0);
 #ifndef SQLITE_OMIT_VIEW
 	if (pTab->pSelect) {
-		sqlite3ErrorMsg(pParse, "views may not be indexed");
+		sqlite3ErrorMsg(parse, "views may not be indexed");
 		goto exit_create_index;
 	}
 #endif
@@ -2942,27 +2929,27 @@ sqlite3CreateIndex(Parse * pParse,	/* All information about this parse */
 	 * one of the index names collides with the name of a temporary table or
 	 * index, then we will continue to process this index.
 	 *
-	 * If pName==0 it means that we are
+	 * If token==0 it means that we are
 	 * dealing with a primary key or UNIQUE constraint.  We have to invent our
 	 * own name.
 	 */
-	if (pName) {
-		zName = sqlite3NameFromToken(db, pName);
+	if (token) {
+		zName = sqlite3NameFromToken(db, token);
 		if (zName == 0)
 			goto exit_create_index;
-		assert(pName->z != 0);
+		assert(token->z != 0);
 		if (!db->init.busy) {
 			if (sqlite3HashFind(&db->pSchema->tblHash, zName) !=
 			    NULL) {
-				sqlite3ErrorMsg(pParse,
+				sqlite3ErrorMsg(parse,
 						"there is already a table named %s",
 						zName);
 				goto exit_create_index;
 			}
 		}
 		if (sqlite3HashFind(&pTab->idxHash, zName) != NULL) {
-			if (!ifNotExist) {
-				sqlite3ErrorMsg(pParse,
+			if (!if_not_exist) {
+				sqlite3ErrorMsg(parse,
 						"index %s.%s already exists",
 						pTab->zName, zName);
 			} else {
@@ -2984,29 +2971,29 @@ sqlite3CreateIndex(Parse * pParse,	/* All information about this parse */
 		}
 	}
 
-	/* If pList==0, it means this routine was called to make a primary
+	/* If col_list==0, it means this routine was called to make a primary
 	 * key out of the last column added to the table under construction.
 	 * So create a fake list to simulate this.
 	 */
-	if (pList == 0) {
+	if (col_list == 0) {
 		Token prevCol;
 		sqlite3TokenInit(&prevCol, pTab->aCol[pTab->nCol - 1].zName);
-		pList = sqlite3ExprListAppend(pParse, 0,
+		col_list = sqlite3ExprListAppend(parse, 0,
 					      sqlite3ExprAlloc(db, TK_ID,
 							       &prevCol, 0));
-		if (pList == 0)
+		if (col_list == 0)
 			goto exit_create_index;
-		assert(pList->nExpr == 1);
-		sqlite3ExprListSetSortOrder(pList, sortOrder);
+		assert(col_list->nExpr == 1);
+		sqlite3ExprListSetSortOrder(col_list, sort_order);
 	} else {
-		sqlite3ExprListCheckLength(pParse, pList, "index");
+		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 < pList->nExpr; i++) {
-		Expr *pExpr = pList->a[i].pExpr;
+	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));
@@ -3017,7 +3004,7 @@ sqlite3CreateIndex(Parse * pParse,	/* All information about this parse */
 	 * Allocate the index structure.
 	 */
 	nName = sqlite3Strlen30(zName);
-	pIndex = sqlite3AllocateIndexObject(db, pList->nExpr,
+	pIndex = sqlite3AllocateIndexObject(db, col_list->nExpr,
 					    nName + nExtra + 1, &zExtra);
 	if (db->mallocFailed) {
 		goto exit_create_index;
@@ -3028,26 +3015,26 @@ sqlite3CreateIndex(Parse * pParse,	/* All information about this parse */
 	zExtra += nName + 1;
 	memcpy(pIndex->zName, zName, nName + 1);
 	pIndex->pTable = pTab;
-	pIndex->onError = (u8) onError;
+	pIndex->onError = (u8) on_error;
 	/*
 	 * Don't make difference between UNIQUE indexes made by user
 	 * using CREATE INDEX statement and those created during
 	 * CREATE TABLE processing.
 	 */
-	if (idxType == SQLITE_IDXTYPE_APPDEF &&
-	    onError != ON_CONFLICT_ACTION_NONE) {
+	if (idx_type == SQLITE_IDXTYPE_APPDEF &&
+	    on_error != ON_CONFLICT_ACTION_NONE) {
 		pIndex->idxType = SQLITE_IDXTYPE_UNIQUE;
 	} else {
-		pIndex->idxType = idxType;
+		pIndex->idxType = idx_type;
 	}
 	pIndex->pSchema = db->pSchema;
-	pIndex->nColumn = pList->nExpr;
+	pIndex->nColumn = col_list->nExpr;
 	/* Tarantool have access to each column by any index */
-	if (pPIWhere) {
-		sqlite3ResolveSelfReference(pParse, pTab, NC_PartIdx, pPIWhere,
+	if (pi_where) {
+		sqlite3ResolveSelfReference(parse, pTab, NC_PartIdx, pi_where,
 					    0);
-		pIndex->pPartIdxWhere = pPIWhere;
-		pPIWhere = 0;
+		pIndex->pPartIdxWhere = pi_where;
+		pi_where = 0;
 	}
 
 	/* Analyze the list of expressions that form the terms of the index and
@@ -3059,16 +3046,16 @@ sqlite3CreateIndex(Parse * pParse,	/* All information about this parse */
 	 * TODO: Issue a warning if the table primary key is used as part of the
 	 * index key.
 	 */
-	for (i = 0, pListItem = pList->a; i < pList->nExpr; i++, pListItem++) {
+	for (i = 0, col_listItem = col_list->a; i < col_list->nExpr; i++, col_listItem++) {
 		Expr *pCExpr;	/* The i-th index expression */
 		enum sort_order requested_so;	/* ASC or DESC on the i-th expression */
-		sqlite3ResolveSelfReference(pParse, pTab, NC_IdxExpr,
-					    pListItem->pExpr, 0);
-		if (pParse->nErr)
+		sqlite3ResolveSelfReference(parse, pTab, NC_IdxExpr,
+					    col_listItem->pExpr, 0);
+		if (parse->nErr)
 			goto exit_create_index;
-		pCExpr = sqlite3ExprSkipCollate(pListItem->pExpr);
+		pCExpr = sqlite3ExprSkipCollate(col_listItem->pExpr);
 		if (pCExpr->op != TK_COLUMN) {
-			sqlite3ErrorMsg(pParse,
+			sqlite3ErrorMsg(parse,
 					"functional indexes aren't supported "
 					"in the current version");
 			goto exit_create_index;
@@ -3081,9 +3068,9 @@ sqlite3CreateIndex(Parse * pParse,	/* All information about this parse */
 			pIndex->aiColumn[i] = (i16) j;
 		}
 		struct coll *coll;
-		if (pListItem->pExpr->op == TK_COLLATE) {
-			const char *coll_name = pListItem->pExpr->u.zToken;
-			coll = sqlite3GetCollSeq(pParse, 0, coll_name);
+		if (col_listItem->pExpr->op == TK_COLLATE) {
+			const char *coll_name = col_listItem->pExpr->u.zToken;
+			coll = sqlite3GetCollSeq(parse, 0, coll_name);
 
 			if (coll == NULL &&
 			    sqlite3StrICmp(coll_name, "binary") != 0) {
@@ -3099,15 +3086,15 @@ sqlite3CreateIndex(Parse * pParse,	/* All information about this parse */
 		/* Tarantool: DESC indexes are not supported so far.
 		 * See gh-3016.
 		 */
-		requested_so = pListItem->sortOrder & 0;
+		requested_so = col_listItem->sort_order & 0;
 		pIndex->sort_order[i] = requested_so;
 	}
 
 	sqlite3DefaultRowEst(pIndex);
-	if (pParse->pNewTable == 0)
+	if (parse->pNewTable == 0)
 		estimateIndexWidth(pIndex);
 
-	if (pTab == pParse->pNewTable) {
+	if (pTab == parse->pNewTable) {
 		/* This routine has been called to create an automatic index as a
 		 * result of a PRIMARY KEY or UNIQUE clause on a column definition, or
 		 * a PRIMARY KEY or UNIQUE clause following the column definitions.
@@ -3161,7 +3148,7 @@ sqlite3CreateIndex(Parse * pParse,	/* All information about this parse */
 					    (pIdx->onError == ON_CONFLICT_ACTION_DEFAULT
 					     || pIndex->onError ==
 					     ON_CONFLICT_ACTION_DEFAULT)) {
-						sqlite3ErrorMsg(pParse,
+						sqlite3ErrorMsg(parse,
 								"conflicting ON CONFLICT clauses specified",
 								0);
 					}
@@ -3169,8 +3156,8 @@ sqlite3CreateIndex(Parse * pParse,	/* All information about this parse */
 						pIdx->onError = pIndex->onError;
 					}
 				}
-				if (idxType == SQLITE_IDXTYPE_PRIMARYKEY)
-					pIdx->idxType = idxType;
+				if (idx_type == SQLITE_IDXTYPE_PRIMARYKEY)
+					pIdx->idxType = idx_type;
 				goto exit_create_index;
 			}
 		}
@@ -3179,7 +3166,7 @@ sqlite3CreateIndex(Parse * pParse,	/* All information about this parse */
 	/* Link the new Index structure to its table and to the other
 	 * in-memory database structures.
 	 */
-	assert(pParse->nErr == 0);
+	assert(parse->nErr == 0);
 	if (db->init.busy) {
 		Index *p;
 		p = sqlite3HashInsert(&pTab->idxHash, pIndex->zName, pIndex);
@@ -3198,23 +3185,23 @@ sqlite3CreateIndex(Parse * pParse,	/* All information about this parse */
 	 * But, do not do this if we are simply parsing the schema, or if this
 	 * index is the PRIMARY KEY index.
 	 *
-	 * If pTblName==0 it means this index is generated as an implied PRIMARY KEY
+	 * If tbl_name==0 it means this index is generated as an implied PRIMARY KEY
 	 * or UNIQUE index in a CREATE TABLE statement.  Since the table
 	 * has just been created, it contains no data and the index initialization
 	 * step can be skipped.
 	 */
-	else if (pTblName) {
+	else if (tbl_name) {
 		Vdbe *v;
 		char *zStmt;
-		int iCursor = pParse->nTab++;
-		int index_space_ptr_reg = pParse->nTab++;
+		int iCursor = parse->nTab++;
+		int index_space_ptr_reg = parse->nTab++;
 		int iSpaceId, iIndexId, iFirstSchemaCol;
 
-		v = sqlite3GetVdbe(pParse);
+		v = sqlite3GetVdbe(parse);
 		if (v == 0)
 			goto exit_create_index;
 
-		sql_set_multi_write(pParse, true);
+		sql_set_multi_write(parse, true);
 
 
 		sqlite3VdbeAddOp2(v, OP_SIDtoPtr, BOX_INDEX_ID,
@@ -3226,34 +3213,34 @@ sqlite3CreateIndex(Parse * pParse,	/* All information about this parse */
 		/* Gather the complete text of the CREATE INDEX statement into
 		 * the zStmt variable
 		 */
-		assert(pStart); {
+		assert(start); {
 			int n =
-			    (int)(pParse->sLastToken.z - pName->z) +
-			    pParse->sLastToken.n;
-			if (pName->z[n - 1] == ';')
+			    (int)(parse->sLastToken.z - token->z) +
+			    parse->sLastToken.n;
+			if (token->z[n - 1] == ';')
 				n--;
 			/* A named index with an explicit CREATE INDEX statement */
 			zStmt = sqlite3MPrintf(db, "CREATE%s INDEX %.*s",
-					       onError ==
+					       on_error ==
 					       ON_CONFLICT_ACTION_NONE
 					       ? "" : " UNIQUE", n,
-					       pName->z);
+					       token->z);
 		}
 
 		iSpaceId = SQLITE_PAGENO_TO_SPACEID(pTab->tnum);
-		iIndexId = getNewIid(pParse, iSpaceId, iCursor);
+		iIndexId = getNewIid(parse, iSpaceId, iCursor);
 		sqlite3VdbeAddOp1(v, OP_Close, iCursor);
-		createIndex(pParse, pIndex, iSpaceId, iIndexId, zStmt);
+		createIndex(parse, pIndex, iSpaceId, iIndexId, zStmt);
 
 		/* consumes zStmt */
 		iFirstSchemaCol =
-		    makeIndexSchemaRecord(pParse, pIndex, iSpaceId, iIndexId,
+		    makeIndexSchemaRecord(parse, pIndex, iSpaceId, iIndexId,
 					  zStmt);
 
 		/* Reparse the schema. Code an OP_Expire
 		 * to invalidate all pre-compiled statements.
 		 */
-		sqlite3ChangeCookie(pParse);
+		sqlite3ChangeCookie(parse);
 		sqlite3VdbeAddParseSchema2Op(v, iFirstSchemaCol, 4);
 		sqlite3VdbeAddOp0(v, OP_Expire);
 	}
@@ -3268,7 +3255,7 @@ sqlite3CreateIndex(Parse * pParse,	/* All information about this parse */
 	 * UPDATE and INSERT statements.
 	 */
 
-	if (!(db->init.busy || pTblName == 0))
+	if (!(db->init.busy || tbl_name == 0))
 		goto exit_create_index;
 	addIndexToTable(pIndex, pTab);
 	pIndex = 0;
@@ -3277,9 +3264,9 @@ sqlite3CreateIndex(Parse * pParse,	/* All information about this parse */
  exit_create_index:
 	if (pIndex)
 		freeIndex(db, pIndex);
-	sql_expr_free(db, pPIWhere, false);
-	sqlite3ExprListDelete(db, pList);
-	sqlite3SrcListDelete(db, pTblName);
+	sql_expr_free(db, pi_where, false);
+	sqlite3ExprListDelete(db, col_list);
+	sqlite3SrcListDelete(db, tbl_name);
 	sqlite3DbFree(db, zName);
 }
 
@@ -3374,15 +3361,6 @@ index_is_unique_not_null(const Index *idx)
 		!index->def->key_def->is_nullable);
 }
 
-/**
- * This routine will drop an existing named index.  This routine
- * implements the DROP INDEX statement.
- *
- * @param parse_context Current parsing context.
- * @param index_name_list List containing index name.
- * @param table_token Token representing table name.
- * @param if_exists True, if statement contains 'IF EXISTS' clause.
- */
 void
 sql_drop_index(struct Parse *parse_context, struct SrcList *index_name_list,
 	       struct Token *table_token, bool if_exists)
@@ -4171,46 +4149,6 @@ sqlite3Reindex(Parse * pParse, Token * pName1, Token * pName2)
 }
 #endif
 
-/*
- * Return a KeyInfo structure that is appropriate for the given Index.
- *
- * The caller should invoke sqlite3KeyInfoUnref() on the returned object
- * when it has finished using it.
- */
-KeyInfo *
-sqlite3KeyInfoOfIndex(Parse * pParse, sqlite3 * db, Index * pIdx)
-{
-	int i;
-	int nCol = pIdx->nColumn;
-	int nTableCol = pIdx->pTable->nCol;
-	KeyInfo *pKey;
-
-	if (pParse && pParse->nErr)
-		return 0;
-
-	/*
-	 * KeyInfo describes the index (i.e. the number of key columns,
-	 * comparator options, and the number of columns beyond the key).
-	 * Since Tarantool iterator yields the full tuple, we need a KeyInfo
-	 * as wide as the table itself.  Otherwize, not enough slots
-	 * for row parser cache are allocated in VdbeCursor object.
-	 */
-	pKey = sqlite3KeyInfoAlloc(db, nCol, nTableCol - nCol);
-	if (pKey) {
-		assert(sqlite3KeyInfoIsWriteable(pKey));
-		for (i = 0; i < nCol; i++) {
-			pKey->aColl[i] = sql_index_collation(pIdx, i);
-			pKey->aSortOrder[i] = sql_index_column_sort_order(pIdx,
-									  i);
-		}
-		if (pParse && pParse->nErr) {
-			sqlite3KeyInfoUnref(pKey);
-			pKey = 0;
-		}
-	}
-	return pKey;
-}
-
 #ifndef SQLITE_OMIT_CTE
 /*
  * This routine is invoked once per CTE by the parser while parsing a
diff --git a/src/box/sql/delete.c b/src/box/sql/delete.c
index 3f74b93..e229d45 100644
--- a/src/box/sql/delete.c
+++ b/src/box/sql/delete.c
@@ -386,10 +386,14 @@ sqlite3DeleteFrom(Parse * pParse,	/* The parser context */
 			iPk = pParse->nMem + 1;
 			pParse->nMem += nPk;
 			iEphCur = pParse->nTab++;
-			KeyInfo *pKeyInfo = sqlite3KeyInfoAlloc(pParse->db, nPk, 0);
+			struct key_def *def = key_def_new(nPk);
+			if (def == NULL) {
+				pParse->rc = SQL_TARANTOOL_ERROR;
+				goto delete_from_cleanup;
+			}
 			addrEphOpen =
 				sqlite3VdbeAddOp4(v, OP_OpenTEphemeral, iEphCur,
-						  nPk, 0, (char*) pKeyInfo, P4_KEYINFO);
+						  nPk, 0, (char*)def, P4_KEYDEF);
 		} else {
 			pPk = sqlite3PrimaryKeyIndex(pTab);
 			assert(pPk != 0);
@@ -400,7 +404,7 @@ sqlite3DeleteFrom(Parse * pParse,	/* The parser context */
 			addrEphOpen =
 			    sqlite3VdbeAddOp2(v, OP_OpenTEphemeral, iEphCur,
 					      nPk);
-			sqlite3VdbeSetP4KeyInfo(pParse, pPk);
+			sql_vdbe_set_p4_key_def(pParse, pPk);
 		}
 
 		/* Construct a query to find the primary key for every row
diff --git a/src/box/sql/expr.c b/src/box/sql/expr.c
index cc969ca..83cbe60 100644
--- a/src/box/sql/expr.c
+++ b/src/box/sql/expr.c
@@ -1505,7 +1505,7 @@ sqlite3ExprListDup(sqlite3 * db, ExprList * p, int flags)
 		}
 		pItem->zName = sqlite3DbStrDup(db, pOldItem->zName);
 		pItem->zSpan = sqlite3DbStrDup(db, pOldItem->zSpan);
-		pItem->sortOrder = pOldItem->sortOrder;
+		pItem->sort_order = pOldItem->sort_order;
 		pItem->done = 0;
 		pItem->bSpanIsTab = pOldItem->bSpanIsTab;
 		pItem->u = pOldItem->u;
@@ -1756,20 +1756,17 @@ sqlite3ExprListAppendVector(Parse * pParse,	/* Parsing context */
 	return pList;
 }
 
-/*
- * Set the sort order for the last element on the given ExprList.
- */
 void
-sqlite3ExprListSetSortOrder(ExprList * p, int iSortOrder)
+sqlite3ExprListSetSortOrder(struct ExprList *p, enum sort_order sort_order)
 {
 	if (p == 0)
 		return;
 	assert(p->nExpr > 0);
-	if (iSortOrder == SORT_ORDER_UNDEF) {
-		assert(p->a[p->nExpr - 1].sortOrder == SORT_ORDER_ASC);
+	if (sort_order == SORT_ORDER_UNDEF) {
+		assert(p->a[p->nExpr - 1].sort_order == SORT_ORDER_ASC);
 		return;
 	}
-	p->a[p->nExpr - 1].sortOrder = (u8) iSortOrder;
+	p->a[p->nExpr - 1].sort_order = sort_order;
 }
 
 /*
@@ -2522,7 +2519,7 @@ sqlite3FindInIndex(Parse * pParse,	/* Parsing context */
 							  P4_DYNAMIC);
 					emit_open_cursor(pParse, iTab,
 							 pIdx->tnum);
-					sqlite3VdbeSetP4KeyInfo(pParse, pIdx);
+					sql_vdbe_set_p4_key_def(pParse, pIdx);
 					VdbeComment((v, "%s", pIdx->zName));
 					assert(IN_INDEX_INDEX_DESC ==
 					       IN_INDEX_INDEX_ASC + 1);
@@ -2739,7 +2736,6 @@ sqlite3CodeSubselect(Parse * pParse,	/* Parsing context */
 	case TK_IN:{
 			int addr;	/* Address of OP_OpenEphemeral instruction */
 			Expr *pLeft = pExpr->pLeft;	/* the LHS of the IN operator */
-			KeyInfo *pKeyInfo = 0;	/* Key information */
 			int nVal;	/* Size of vector pLeft */
 
 			nVal = sqlite3ExprVectorSize(pLeft);
@@ -2761,7 +2757,11 @@ sqlite3CodeSubselect(Parse * pParse,	/* Parsing context */
 			pExpr->is_ephemeral = 1;
 			addr = sqlite3VdbeAddOp2(v, OP_OpenTEphemeral,
 						 pExpr->iTable, nVal);
-			pKeyInfo = sqlite3KeyInfoAlloc(pParse->db, nVal, 1);
+			struct key_def *key_def = key_def_new(nVal);
+			if (key_def == NULL) {
+				pParse->rc = SQL_TARANTOOL_ERROR;
+				return 0;
+			}
 
 			if (ExprHasProperty(pExpr, EP_xIsSelect)) {
 				/* Case 1:     expr IN (SELECT ...)
@@ -2787,29 +2787,33 @@ sqlite3CodeSubselect(Parse * pParse,	/* Parsing context */
 					pSelect->iLimit = 0;
 					testcase(pSelect->
 						 selFlags & SF_Distinct);
-					testcase(pKeyInfo == 0);	/* Caused by OOM in sqlite3KeyInfoAlloc() */
 					if (sqlite3Select
 					    (pParse, pSelect, &dest)) {
 						sqlite3DbFree(pParse->db,
 							      dest.zAffSdst);
-						sqlite3KeyInfoUnref(pKeyInfo);
+						if (key_def != NULL)
+							key_def_delete(key_def);
 						return 0;
 					}
 					sqlite3DbFree(pParse->db,
 						      dest.zAffSdst);
-					assert(pKeyInfo != 0);	/* OOM will cause exit after sqlite3Select() */
 					assert(pEList != 0);
 					assert(pEList->nExpr > 0);
-					assert(sqlite3KeyInfoIsWriteable
-					       (pKeyInfo));
 					for (i = 0; i < nVal; i++) {
 						Expr *p =
 						    sqlite3VectorFieldSubexpr
 						    (pLeft, i);
-						pKeyInfo->aColl[i] =
-						    sqlite3BinaryCompareCollSeq
-						    (pParse, p,
-						     pEList->a[i].pExpr);
+
+						struct coll *coll;
+						coll = sqlite3BinaryCompareCollSeq
+							(pParse, p,
+							 pEList->a[i].pExpr);
+
+						key_def_set_part(key_def, i, i,
+								 FIELD_TYPE_SCALAR,
+								 ON_CONFLICT_ACTION_ABORT,
+								 coll,
+								 SORT_ORDER_ASC);
 					}
 				}
 			} else if (ALWAYS(pExpr->x.pList != 0)) {
@@ -2830,14 +2834,18 @@ sqlite3CodeSubselect(Parse * pParse,	/* Parsing context */
 				if (!affinity) {
 					affinity = SQLITE_AFF_BLOB;
 				}
-				if (pKeyInfo) {
+				if (key_def != NULL) {
 					bool unused;
-					assert(sqlite3KeyInfoIsWriteable
-					       (pKeyInfo));
-					pKeyInfo->aColl[0] =
-						sql_expr_coll(pParse,
-							      pExpr->pLeft,
-							      &unused);
+					struct coll *coll;
+					coll = sql_expr_coll(pParse,
+							     pExpr->pLeft,
+							     &unused);
+
+					key_def_set_part(key_def, 0, 0,
+							 FIELD_TYPE_SCALAR,
+							 ON_CONFLICT_ACTION_ABORT,
+							 coll,
+							 SORT_ORDER_ASC);
 				}
 
 				/* Loop through each expression in <exprlist>. */
@@ -2868,9 +2876,9 @@ sqlite3CodeSubselect(Parse * pParse,	/* Parsing context */
 				sqlite3ReleaseTempReg(pParse, r1);
 				sqlite3ReleaseTempReg(pParse, r2);
 			}
-			if (pKeyInfo) {
-				sqlite3VdbeChangeP4(v, addr, (void *)pKeyInfo,
-						    P4_KEYINFO);
+			if (key_def != NULL) {
+				sqlite3VdbeChangeP4(v, addr, (void *)key_def,
+						    P4_KEYDEF);
 			}
 			break;
 		}
@@ -5183,7 +5191,7 @@ sqlite3ExprListCompare(ExprList * pA, ExprList * pB, int iTab)
 	for (i = 0; i < pA->nExpr; i++) {
 		Expr *pExprA = pA->a[i].pExpr;
 		Expr *pExprB = pB->a[i].pExpr;
-		if (pA->a[i].sortOrder != pB->a[i].sortOrder)
+		if (pA->a[i].sort_order != pB->a[i].sort_order)
 			return 1;
 		if (sqlite3ExprCompare(pExprA, pExprB, iTab))
 			return 1;
diff --git a/src/box/sql/fkey.c b/src/box/sql/fkey.c
index 55edf6b..60b4786 100644
--- a/src/box/sql/fkey.c
+++ b/src/box/sql/fkey.c
@@ -437,7 +437,7 @@ fkLookupParent(Parse * pParse,	/* Parse context */
 			int regRec = sqlite3GetTempReg(pParse);
 
 			emit_open_cursor(pParse, iCur, pIdx->tnum);
-			sqlite3VdbeSetP4KeyInfo(pParse, pIdx);
+			sql_vdbe_set_p4_key_def(pParse, pIdx);
 			for (i = 0; i < nCol; i++) {
 				sqlite3VdbeAddOp2(v, OP_Copy,
 						  aiCol[i] + 1 + regData,
diff --git a/src/box/sql/insert.c b/src/box/sql/insert.c
index 3ad3fc7..f6a93ce 100644
--- a/src/box/sql/insert.c
+++ b/src/box/sql/insert.c
@@ -55,7 +55,7 @@ sqlite3OpenTable(Parse * pParse,	/* Generate code into this VDBE */
 	assert(pPk != 0);
 	assert(pPk->tnum == pTab->tnum);
 	emit_open_cursor(pParse, iCur, pPk->tnum);
-	sqlite3VdbeSetP4KeyInfo(pParse, pPk);
+	sql_vdbe_set_p4_key_def(pParse, pPk);
 	VdbeComment((v, "%s", pTab->zName));
 }
 
@@ -566,9 +566,13 @@ sqlite3Insert(Parse * pParse,	/* Parser context */
 			regRec = sqlite3GetTempReg(pParse);
 			regCopy = sqlite3GetTempRange(pParse, nColumn);
 			regTempId = sqlite3GetTempReg(pParse);
-			KeyInfo *pKeyInfo = sqlite3KeyInfoAlloc(pParse->db, 1+nColumn, 0);
+			struct key_def *def = key_def_new(nColumn + 1);
+			if (def == NULL) {
+				pParse->rc = SQL_TARANTOOL_ERROR;
+				goto insert_cleanup;
+			}
 			sqlite3VdbeAddOp4(v, OP_OpenTEphemeral, srcTab, nColumn+1,
-					  0, (char*)pKeyInfo, P4_KEYINFO);
+					  0, (char*)def, P4_KEYDEF);
 			/* Create counter for rowid */
 			sqlite3VdbeAddOp4Dup8(v, OP_Int64,
 					      0 /* unused */,
@@ -1602,7 +1606,7 @@ sqlite3OpenTableAndIndices(Parse * pParse,	/* Parsing context */
 			if (aToOpen == 0 || aToOpen[i + 1]) {
 				sqlite3VdbeAddOp3(v, op, iIdxCur, pIdx->tnum,
 						  space_ptr_reg);
-				sqlite3VdbeSetP4KeyInfo(pParse, pIdx);
+				sql_vdbe_set_p4_key_def(pParse, pIdx);
 				sqlite3VdbeChangeP5(v, p5);
 				VdbeComment((v, "%s", pIdx->zName));
 			}
@@ -1916,10 +1920,10 @@ xferOptimization(Parse * pParse,	/* Parser context */
 		}
 		assert(pSrcIdx);
 		emit_open_cursor(pParse, iSrc, pSrcIdx->tnum);
-		sqlite3VdbeSetP4KeyInfo(pParse, pSrcIdx);
+		sql_vdbe_set_p4_key_def(pParse, pSrcIdx);
 		VdbeComment((v, "%s", pSrcIdx->zName));
 		emit_open_cursor(pParse, iDest, pDestIdx->tnum);
-		sqlite3VdbeSetP4KeyInfo(pParse, pDestIdx);
+		sql_vdbe_set_p4_key_def(pParse, pDestIdx);
 		sqlite3VdbeChangeP5(v, OPFLAG_BULKCSR);
 		VdbeComment((v, "%s", pDestIdx->zName));
 		addr1 = sqlite3VdbeAddOp2(v, OP_Rewind, iSrc, 0);
diff --git a/src/box/sql/parse.y b/src/box/sql/parse.y
index f548b4d..a40009d 100644
--- a/src/box/sql/parse.y
+++ b/src/box/sql/parse.y
@@ -281,8 +281,9 @@ ccons ::= NULL onconf.
 ccons ::= NOT NULL onconf(R).    {sqlite3AddNotNull(pParse, R);}
 ccons ::= PRIMARY KEY sortorder(Z) onconf(R) autoinc(I).
                                  {sqlite3AddPrimaryKey(pParse,0,R,I,Z);}
-ccons ::= UNIQUE onconf(R).      {sqlite3CreateIndex(pParse,0,0,0,R,0,0,0,0,
-                                   SQLITE_IDXTYPE_UNIQUE);}
+ccons ::= UNIQUE onconf(R).      {sql_create_index(pParse,0,0,0,R,0,0,
+						   SORT_ORDER_ASC, false,
+						   SQLITE_IDXTYPE_UNIQUE);}
 ccons ::= CHECK LP expr(X) RP.   {sqlite3AddCheckConstraint(pParse,X.pExpr);}
 ccons ::= REFERENCES nm(T) eidlist_opt(TA) refargs(R).
                                  {sqlite3CreateForeignKey(pParse,0,&T,TA,R);}
@@ -331,8 +332,9 @@ tcons ::= CONSTRAINT nm(X).      {pParse->constraintName = X;}
 tcons ::= PRIMARY KEY LP sortlist(X) autoinc(I) RP onconf(R).
                                  {sqlite3AddPrimaryKey(pParse,X,R,I,0);}
 tcons ::= UNIQUE LP sortlist(X) RP onconf(R).
-                                 {sqlite3CreateIndex(pParse,0,0,X,R,0,0,0,0,
-                                       SQLITE_IDXTYPE_UNIQUE);}
+                                 {sql_create_index(pParse,0,0,X,R,0,0,
+						   SORT_ORDER_ASC,false,
+						   SQLITE_IDXTYPE_UNIQUE);}
 tcons ::= CHECK LP expr(E) RP onconf.
                                  {sqlite3AddCheckConstraint(pParse,E.pExpr);}
 tcons ::= FOREIGN KEY LP eidlist(FA) RP
@@ -1239,9 +1241,9 @@ paren_exprlist(A) ::= LP exprlist(X) RP.  {A = X;}
 //
 cmd ::= createkw(S) uniqueflag(U) INDEX ifnotexists(NE) nm(X)
         ON nm(Y) LP sortlist(Z) RP where_opt(W). {
-  sqlite3CreateIndex(pParse, &X, 
-                     sqlite3SrcListAppend(pParse->db,0,&Y), Z, U,
-                      &S, W, SORT_ORDER_ASC, NE, SQLITE_IDXTYPE_APPDEF);
+  sql_create_index(pParse, &X, 
+		   sqlite3SrcListAppend(pParse->db,0,&Y), Z, U,
+		   &S, W, SORT_ORDER_ASC, NE, SQLITE_IDXTYPE_APPDEF);
 }
 
 %type uniqueflag {int}
diff --git a/src/box/sql/pragma.c b/src/box/sql/pragma.c
index 20a1587..1e4ae81 100644
--- a/src/box/sql/pragma.c
+++ b/src/box/sql/pragma.c
@@ -643,8 +643,8 @@ sqlite3Pragma(Parse * pParse, Token * pId,	/* First part of [schema.]id field */
 									  pIdx->
 									  tnum,
 									  0);
-							sqlite3VdbeSetP4KeyInfo
-							    (pParse, pIdx);
+							sql_vdbe_set_p4_key_def(pParse,
+										pIdx);
 						}
 					} else {
 						k = 0;
diff --git a/src/box/sql/select.c b/src/box/sql/select.c
index a5e6563..e49b90b 100644
--- a/src/box/sql/select.c
+++ b/src/box/sql/select.c
@@ -562,12 +562,31 @@ sqliteProcessJoin(Parse * pParse, Select * p)
 	return 0;
 }
 
-/* Forward reference */
-static KeyInfo *
-keyInfoFromExprList(Parse * pParse,	/* Parsing context */
-		    ExprList * pList,	/* Form the KeyInfo object from this ExprList */
-		    int iStart,		/* Begin with this column of pList */
-		    int nExtra);	/* Add this many extra columns to the end */
+/**
+ * Given an expression list, generate a key_def structure that
+ * records the collating sequence for each expression in that
+ * expression list.
+ *
+ * If the ExprList is an ORDER BY or GROUP BY clause then the
+ * resulting key_def structure is appropriate for initializing
+ * a virtual index to implement that clause.  If the ExprList is
+ * the result set of a SELECT then the key_info structure is
+ * appropriate for initializing a virtual index to implement a
+ * DISTINCT test.
+ *
+ * Space to hold the key_info structure is obtained from malloc.
+ * The calling function is responsible for seeing that this
+ * structure is eventually freed.
+ *
+ * @param parse Parsing context.
+ * @param list Expression list.
+ * @param start No of leading parts to skip.
+ *
+ * @retval Allocated key_def, NULL in case of OOM.
+ */
+static struct key_def *
+sql_expr_list_to_key_def(struct Parse *parse, struct ExprList *list, int start);
+
 
 /*
  * Generate code that will push the record in registers regData
@@ -623,7 +642,6 @@ pushOntoSorter(Parse * pParse,		/* Parser context */
 		int addrJmp;	/* Address of the OP_Jump opcode */
 		VdbeOp *pOp;	/* Opcode that opens the sorter */
 		int nKey;	/* Number of sorting key columns, including OP_Sequence */
-		KeyInfo *pKI;	/* Original KeyInfo on the sorter table */
 
 		regPrevKey = pParse->nMem + 1;
 		pParse->nMem += pSort->nOBSat;
@@ -643,13 +661,17 @@ pushOntoSorter(Parse * pParse,		/* Parser context */
 		if (pParse->db->mallocFailed)
 			return;
 		pOp->p2 = nKey + nData;
-		pKI = pOp->p4.pKeyInfo;
-		memset(pKI->aSortOrder, 0, pKI->nField);	/* Makes OP_Jump below testable */
-		sqlite3VdbeChangeP4(v, -1, (char *)pKI, P4_KEYINFO);
-		testcase(pKI->nXField > 2);
-		pOp->p4.pKeyInfo =
-		    keyInfoFromExprList(pParse, pSort->pOrderBy, nOBSat,
-					pKI->nXField - 1);
+		struct key_def *def = key_def_dup(pOp->p4.key_def);
+		if (def == NULL) {
+			pParse->rc = SQL_TARANTOOL_ERROR;
+			return;
+		}
+		for (uint32_t i = 0; i < def->part_count; ++i)
+			pOp->p4.key_def->parts[i].sort_order = SORT_ORDER_ASC;
+		sqlite3VdbeChangeP4(v, -1, (char *)def, P4_KEYDEF);
+		pOp->p4.key_def = sql_expr_list_to_key_def(pParse,
+							   pSort->pOrderBy,
+							   nOBSat);
 		addrJmp = sqlite3VdbeCurrentAddr(v);
 		sqlite3VdbeAddOp3(v, OP_Jump, addrJmp + 1, 0, addrJmp + 1);
 		VdbeCoverage(v);
@@ -1193,109 +1215,24 @@ selectInnerLoop(Parse * pParse,		/* The parser context */
 	}
 }
 
-/*
- * Allocate a KeyInfo object sufficient for an index of N key columns and
- * X extra columns.
- */
-KeyInfo *
-sqlite3KeyInfoAlloc(sqlite3 * db, int N, int X)
+static struct key_def *
+sql_expr_list_to_key_def(struct Parse *parse, struct ExprList *list, int start)
 {
-	int nExtra = (N + X) * (sizeof(struct coll *) + 1);
-	KeyInfo *p = sqlite3DbMallocRawNN(db, sizeof(KeyInfo) + nExtra);
-	if (p) {
-		p->aSortOrder = (u8 *) & p->aColl[N + X];
-		p->nField = (u16) N;
-		p->nXField = (u16) X;
-		p->db = db;
-		p->nRef = 1;
-		p->aColl[0] = NULL;
-		memset(&p[1], 0, nExtra);
-	} else {
-		sqlite3OomFault(db);
-	}
-	return p;
-}
-
-/*
- * Deallocate a KeyInfo object
- */
-void
-sqlite3KeyInfoUnref(KeyInfo * p)
-{
-	if (p) {
-		assert(p->nRef > 0);
-		p->nRef--;
-		if (p->nRef == 0)
-			sqlite3DbFree(p->db, p);
-	}
-}
-
-/*
- * Make a new pointer to a KeyInfo object
- */
-KeyInfo *
-sqlite3KeyInfoRef(KeyInfo * p)
-{
-	if (p) {
-		assert(p->nRef > 0);
-		p->nRef++;
+	int expr_count = list->nExpr;
+	struct key_def *def = key_def_new(expr_count);
+	if (def == NULL) {
+		parse->rc = SQL_TARANTOOL_ERROR;
+		return NULL;
 	}
-	return p;
-}
-
-#ifdef SQLITE_DEBUG
-/*
- * Return TRUE if a KeyInfo object can be change.  The KeyInfo object
- * can only be changed if this is just a single reference to the object.
- *
- * This routine is used only inside of assert() statements.
- */
-int
-sqlite3KeyInfoIsWriteable(KeyInfo * p)
-{
-	return p->nRef == 1;
-}
-#endif				/* SQLITE_DEBUG */
-
-/*
- * Given an expression list, generate a KeyInfo structure that records
- * the collating sequence for each expression in that expression list.
- *
- * If the ExprList is an ORDER BY or GROUP BY clause then the resulting
- * KeyInfo structure is appropriate for initializing a virtual index to
- * implement that clause.  If the ExprList is the result set of a SELECT
- * then the KeyInfo structure is appropriate for initializing a virtual
- * index to implement a DISTINCT test.
- *
- * Space to hold the KeyInfo structure is obtained from malloc.  The calling
- * function is responsible for seeing that this structure is eventually
- * freed.
- */
-static KeyInfo *
-keyInfoFromExprList(Parse * pParse,	/* Parsing context */
-		    ExprList * pList,	/* Form the KeyInfo object from this ExprList */
-		    int iStart,		/* Begin with this column of pList */
-		    int nExtra)		/* Add this many extra columns to the end */
-{
-	int nExpr;
-	KeyInfo *pInfo;
-	struct ExprList_item *pItem;
-	sqlite3 *db = pParse->db;
-	int i;
-
-	nExpr = pList->nExpr;
-	pInfo = sqlite3KeyInfoAlloc(db, nExpr - iStart, nExtra + 1);
-	if (pInfo) {
-		assert(sqlite3KeyInfoIsWriteable(pInfo));
-		for (i = iStart, pItem = pList->a + iStart; i < nExpr;
-		     i++, pItem++) {
-			bool unused;
-			pInfo->aColl[i - iStart] =
-				sql_expr_coll(pParse, pItem->pExpr, &unused);
-			pInfo->aSortOrder[i - iStart] = pItem->sortOrder;
-		}
+	struct ExprList_item *item = list->a + start;
+	for (int i = start; i < expr_count; ++i, ++item) {
+		bool unused;
+		struct coll *coll = sql_expr_coll(parse, item->pExpr, &unused);
+		key_def_set_part(def, i-start, i-start, FIELD_TYPE_SCALAR,
+				 ON_CONFLICT_ACTION_ABORT, coll,
+				 item->sort_order);
 	}
-	return pInfo;
+	return def;
 }
 
 /*
@@ -2123,54 +2060,62 @@ multiSelectCollSeq(Parse * pParse, Select * p, int iCol, bool *is_found)
 	return coll;
 }
 
-/*
- * The select statement passed as the second parameter is a compound SELECT
- * with an ORDER BY clause. This function allocates and returns a KeyInfo
- * structure suitable for implementing the ORDER BY.
+/**
+ * The select statement passed as the second parameter is a
+ * compound SELECT with an ORDER BY clause. This function
+ * allocates and returns a key_def structure suitable for
+ * implementing the ORDER BY.
+ *
+ * Space to hold the key_def structure is obtained from malloc.
+ * The calling function is responsible for ensuring that this
+ * structure is eventually freed.
  *
- * Space to hold the KeyInfo structure is obtained from malloc. The calling
- * function is responsible for ensuring that this structure is eventually
- * freed.
+ * @param parse Parsing context.
+ * @param s Select struct to analyze.
+ * @param extra No of extra slots to allocate.
+ *
+ * @retval Allocated key_def, NULL in case of OOM.
  */
-static KeyInfo *
-multiSelectOrderByKeyInfo(Parse * pParse, Select * p, int nExtra)
+static struct key_def *
+sql_multiselect_orderby_to_key_def(struct Parse *parse, struct Select *s,
+				   int extra)
 {
-	ExprList *pOrderBy = p->pOrderBy;
-	int nOrderBy = p->pOrderBy->nExpr;
-	sqlite3 *db = pParse->db;
-	KeyInfo *pRet = sqlite3KeyInfoAlloc(db, nOrderBy + nExtra, 1);
-	if (pRet) {
-		int i;
-		for (i = 0; i < nOrderBy; i++) {
-			struct ExprList_item *pItem = &pOrderBy->a[i];
-			Expr *pTerm = pItem->pExpr;
-			struct coll *coll;
-			bool is_found = false;
+	int ob_count = s->pOrderBy->nExpr;
+	struct key_def *key_def = key_def_new(ob_count + extra);
+	if (key_def == NULL) {
+		parse->rc = SQL_TARANTOOL_ERROR;
+		return NULL;
+	}
 
-			if (pTerm->flags & EP_Collate) {
-				coll = sql_expr_coll(pParse, pTerm, &is_found);
+	ExprList *order_by = s->pOrderBy;
+	for (int i = 0; i < ob_count; i++) {
+		struct ExprList_item *item = &order_by->a[i];
+		struct Expr *term = item->pExpr;
+		struct coll *coll;
+		bool is_found = false;
+
+		if (term->flags & EP_Collate) {
+			coll = sql_expr_coll(parse, term, &is_found);
+		} else {
+			coll = multiSelectCollSeq(parse, s,
+						  item->u.x.iOrderByCol - 1,
+						  &is_found);
+			if (coll != NULL) {
+				order_by->a[i].pExpr =
+					sqlite3ExprAddCollateString(parse, term,
+								    coll->name);
 			} else {
-				coll =
-				    multiSelectCollSeq(pParse, p,
-						       pItem->u.x.iOrderByCol - 1,
-						       &is_found);
-				if (coll != NULL) {
-					pOrderBy->a[i].pExpr =
-						sqlite3ExprAddCollateString(pParse, pTerm,
-									    coll->name);
-				} else {
-					pOrderBy->a[i].pExpr =
-						sqlite3ExprAddCollateString(pParse, pTerm,
-									    "BINARY");
-				}
+				order_by->a[i].pExpr =
+					sqlite3ExprAddCollateString(parse, term,
+								    "BINARY");
 			}
-			assert(sqlite3KeyInfoIsWriteable(pRet));
-			pRet->aColl[i] = coll;
-			pRet->aSortOrder[i] = pOrderBy->a[i].sortOrder;
 		}
+		key_def_set_part(key_def, i, i, FIELD_TYPE_SCALAR,
+				 ON_CONFLICT_ACTION_ABORT, coll,
+				 order_by->a[i].sort_order);
 	}
 
-	return pRet;
+	return key_def;
 }
 
 #ifndef SQLITE_OMIT_CTE
@@ -2270,16 +2215,17 @@ generateWithRecursiveQuery(Parse * pParse,	/* Parsing context */
 	regCurrent = ++pParse->nMem;
 	sqlite3VdbeAddOp3(v, OP_OpenPseudo, iCurrent, regCurrent, nCol);
 	if (pOrderBy) {
-		KeyInfo *pKeyInfo = multiSelectOrderByKeyInfo(pParse, p, 1);
+		struct key_def *def = sql_multiselect_orderby_to_key_def(pParse,
+									 p, 1);
 		sqlite3VdbeAddOp4(v, OP_OpenTEphemeral, iQueue,
-				  pOrderBy->nExpr + 2, 0, (char *)pKeyInfo,
-				  P4_KEYINFO);
+				  pOrderBy->nExpr + 2, 0, (char *)def,
+				  P4_KEYDEF);
 		VdbeComment((v, "Orderby table"));
 		destQueue.pOrderBy = pOrderBy;
 	} else {
-		KeyInfo *pKeyInfo = sqlite3KeyInfoAlloc(pParse->db, nCol + 1, 0);
+		struct key_def *def = key_def_new(nCol + 1);
 		sqlite3VdbeAddOp4(v, OP_OpenTEphemeral, iQueue, nCol + 1, 0,
-				  (char*)pKeyInfo, P4_KEYINFO);
+				  (char*)def, P4_KEYDEF);
 		VdbeComment((v, "Queue table"));
 	}
 	if (iDistinct) {
@@ -2480,9 +2426,9 @@ multiSelect(Parse * pParse,	/* Parsing context */
 	if (dest.eDest == SRT_EphemTab) {
 		assert(p->pEList);
 		int nCols = p->pEList->nExpr;
-		KeyInfo *pKeyInfo = sqlite3KeyInfoAlloc(pParse->db, nCols + 1, 0);
+		struct key_def *def = key_def_new(nCols + 1);
 		sqlite3VdbeAddOp4(v, OP_OpenTEphemeral, dest.iSDParm, nCols + 1,
-				  0, (char*)pKeyInfo, P4_KEYINFO);
+				  0, (char*)def, P4_KEYDEF);
 		VdbeComment((v, "Destination temp"));
 		dest.eDest = SRT_Table;
 	}
@@ -2790,7 +2736,7 @@ multiSelect(Parse * pParse,	/* Parsing context */
 
 	/* Compute collating sequences used by
 	 * temporary tables needed to implement the compound select.
-	 * Attach the KeyInfo structure to all temporary tables.
+	 * Attach the key_def structure to all temporary tables.
 	 *
 	 * This section is run by the right-most SELECT statement only.
 	 * SELECT statements to the left always skip this part.  The right-most
@@ -2798,26 +2744,25 @@ multiSelect(Parse * pParse,	/* Parsing context */
 	 * no temp tables are required.
 	 */
 	if (p->selFlags & SF_UsesEphemeral) {
-		int i;		/* Loop counter */
-		KeyInfo *pKeyInfo;	/* Collating sequence for the result set */
-		Select *pLoop;	/* For looping through SELECT statements */
-		struct coll **apColl;	/* For looping through pKeyInfo->aColl[] */
-		int nCol;	/* Number of columns in result set */
-
-		assert(p->pNext == 0);
-		nCol = p->pEList->nExpr;
-		pKeyInfo = sqlite3KeyInfoAlloc(db, nCol, 1);
-		if (!pKeyInfo) {
+		assert(p->pNext == NULL);
+		int nCol = p->pEList->nExpr;
+		struct key_def *key_def = key_def_new(nCol);
+		if (key_def == NULL) {
 			rc = SQLITE_NOMEM_BKPT;
 			goto multi_select_end;
 		}
-		for (i = 0, apColl = pKeyInfo->aColl; i < nCol; i++, apColl++) {
-			bool is_found = false;
-			*apColl = multiSelectCollSeq(pParse, p, i, &is_found);
+		for (int i = 0; i < nCol; i++) {
+			bool unused;
+			key_def_set_part(key_def, i, i,
+					 FIELD_TYPE_SCALAR,
+					 ON_CONFLICT_ACTION_ABORT,
+					 multiSelectCollSeq(pParse, p, i,
+							    &unused),
+					 SORT_ORDER_ASC);
 		}
 
-		for (pLoop = p; pLoop; pLoop = pLoop->pPrior) {
-			for (i = 0; i < 2; i++) {
+		for (struct Select *pLoop = p; pLoop; pLoop = pLoop->pPrior) {
+			for (int i = 0; i < 2; i++) {
 				int addr = pLoop->addrOpenEphm[i];
 				if (addr < 0) {
 					/* If [0] is unused then [1] is also unused.  So we can
@@ -2827,14 +2772,19 @@ multiSelect(Parse * pParse,	/* Parsing context */
 					break;
 				}
 				sqlite3VdbeChangeP2(v, addr, nCol);
+				struct key_def *dup_def = key_def_dup(key_def);
+				if (dup_def == NULL) {
+					rc = SQLITE_NOMEM_BKPT;
+					goto multi_select_end;
+				}
+				
 				sqlite3VdbeChangeP4(v, addr,
-						    (char *)
-						    sqlite3KeyInfoRef(pKeyInfo),
-						    P4_KEYINFO);
+						    (char *)dup_def,
+						    P4_KEYDEF);
 				pLoop->addrOpenEphm[i] = -1;
 			}
 		}
-		sqlite3KeyInfoUnref(pKeyInfo);
+		free(key_def);
 	}
 
  multi_select_end:
@@ -2845,54 +2795,57 @@ multiSelect(Parse * pParse,	/* Parsing context */
 }
 #endif				/* SQLITE_OMIT_COMPOUND_SELECT */
 
-/*
- * Error message for when two or more terms of a compound select have different
- * size result sets.
- */
 void
-sqlite3SelectWrongNumTermsError(Parse * pParse, Select * p)
+sqlite3SelectWrongNumTermsError(struct Parse *parse, struct Select * p)
 {
 	if (p->selFlags & SF_Values) {
-		sqlite3ErrorMsg(pParse,
+		sqlite3ErrorMsg(parse,
 				"all VALUES must have the same number of terms");
 	} else {
-		sqlite3ErrorMsg(pParse, "SELECTs to the left and right of %s"
+		sqlite3ErrorMsg(parse, "SELECTs to the left and right of %s"
 				" do not have the same number of result columns",
 				selectOpName(p->op));
 	}
 }
 
-/*
+/**
  * Code an output subroutine for a coroutine implementation of a
  * SELECT statment.
  *
  * The data to be output is contained in pIn->iSdst.  There are
- * pIn->nSdst columns to be output.  pDest is where the output should
- * be sent.
+ * pIn->nSdst columns to be output.  pDest is where the output
+ * should be sent.
  *
  * regReturn is the number of the register holding the subroutine
  * return address.
  *
  * If regPrev>0 then it is the first register in a vector that
- * records the previous output.  mem[regPrev] is a flag that is false
- * if there has been no previous output.  If regPrev>0 then code is
- * generated to suppress duplicates.  pKeyInfo is used for comparing
- * keys.
+ * records the previous output.  mem[regPrev] is a flag that is
+ * false if there has been no previous output.  If regPrev>0 then
+ * code is generated to suppress duplicates.  def is used for
+ * comparing keys.
  *
  * If the LIMIT found in p->iLimit is reached, jump immediately to
  * iBreak.
+ *
+ * @param parse Parsing context.
+ * @param p The SELECT statement.
+ * @param in Coroutine supplying data.
+ * @param dest Where to send the data.
+ * @param reg_ret The return address register.
+ * @param reg_prev Previous result register.  No uniqueness if 0.
+ * @param def For comparing with previous entry.
+ * @param break_addr Jump here if we hit the LIMIT.
+ *
+ * @retval Address of generated routine.
  */
 static int
-generateOutputSubroutine(Parse * pParse,	/* Parsing context */
-			 Select * p,		/* The SELECT statement */
-			 SelectDest * pIn,	/* Coroutine supplying data */
-			 SelectDest * pDest,	/* Where to send the data */
-			 int regReturn,		/* The return address register */
-			 int regPrev,		/* Previous result register.  No uniqueness if 0 */
-			 KeyInfo * pKeyInfo,	/* For comparing with previous entry */
-			 int iBreak)		/* Jump here if we hit the LIMIT */
+generateOutputSubroutine(struct Parse *parse, struct Select *p,
+			 struct SelectDest *in, struct SelectDest *dest,
+			 int reg_ret, int reg_prev, const struct key_def *def,
+			 int break_addr)
 {
-	Vdbe *v = pParse->pVdbe;
+	Vdbe *v = parse->pVdbe;
 	int iContinue;
 	int addr;
 
@@ -2901,63 +2854,68 @@ generateOutputSubroutine(Parse * pParse,	/* Parsing context */
 
 	/* Suppress duplicates for UNION, EXCEPT, and INTERSECT
 	 */
-	if (regPrev) {
+	if (reg_prev) {
 		int addr1, addr2;
-		addr1 = sqlite3VdbeAddOp1(v, OP_IfNot, regPrev);
+		addr1 = sqlite3VdbeAddOp1(v, OP_IfNot, reg_prev);
 		VdbeCoverage(v);
+		struct key_def *dup_def = key_def_dup(def);
+		if (dup_def == NULL) {
+			parse->db->mallocFailed = 1;
+			return 0;
+		}
 		addr2 =
-		    sqlite3VdbeAddOp4(v, OP_Compare, pIn->iSdst, regPrev + 1,
-				      pIn->nSdst,
-				      (char *)sqlite3KeyInfoRef(pKeyInfo),
-				      P4_KEYINFO);
+		    sqlite3VdbeAddOp4(v, OP_Compare, in->iSdst, reg_prev + 1,
+				      in->nSdst,
+				      (char *)dup_def,
+				      P4_KEYDEF);
 		sqlite3VdbeAddOp3(v, OP_Jump, addr2 + 2, iContinue, addr2 + 2);
 		VdbeCoverage(v);
 		sqlite3VdbeJumpHere(v, addr1);
-		sqlite3VdbeAddOp3(v, OP_Copy, pIn->iSdst, regPrev + 1,
-				  pIn->nSdst - 1);
-		sqlite3VdbeAddOp2(v, OP_Integer, 1, regPrev);
+		sqlite3VdbeAddOp3(v, OP_Copy, in->iSdst, reg_prev + 1,
+				  in->nSdst - 1);
+		sqlite3VdbeAddOp2(v, OP_Integer, 1, reg_prev);
 	}
-	if (pParse->db->mallocFailed)
+	if (parse->db->mallocFailed)
 		return 0;
 
 	/* Suppress the first OFFSET entries if there is an OFFSET clause
 	 */
 	codeOffset(v, p->iOffset, iContinue);
 
-	assert(pDest->eDest != SRT_Exists);
-	assert(pDest->eDest != SRT_Table);
-	switch (pDest->eDest) {
+	assert(dest->eDest != SRT_Exists);
+	assert(dest->eDest != SRT_Table);
+	switch (dest->eDest) {
 		/* Store the result as data using a unique key.
 		 */
 	case SRT_EphemTab:{
-			int regRec = sqlite3GetTempReg(pParse);
-			int regCopy = sqlite3GetTempRange(pParse, pIn->nSdst + 1);
-			sqlite3VdbeAddOp3(v, OP_NextIdEphemeral, pDest->iSDParm,
-					  pIn->nSdst, regCopy + pIn->nSdst);
-			sqlite3VdbeAddOp3(v, OP_Copy, pIn->iSdst, regCopy,
-					  pIn->nSdst - 1);
+			int regRec = sqlite3GetTempReg(parse);
+			int regCopy = sqlite3GetTempRange(parse, in->nSdst + 1);
+			sqlite3VdbeAddOp3(v, OP_NextIdEphemeral, dest->iSDParm,
+					  in->nSdst, regCopy + in->nSdst);
+			sqlite3VdbeAddOp3(v, OP_Copy, in->iSdst, regCopy,
+					  in->nSdst - 1);
 			sqlite3VdbeAddOp3(v, OP_MakeRecord, regCopy,
-					  pIn->nSdst + 1, regRec);
+					  in->nSdst + 1, regRec);
 			/* Set flag to save memory allocating one by malloc. */
 			sqlite3VdbeChangeP5(v, 1);
-			sqlite3VdbeAddOp2(v, OP_IdxInsert, pDest->iSDParm, regRec);
-			sqlite3ReleaseTempRange(pParse, regCopy, pIn->nSdst + 1);
-			sqlite3ReleaseTempReg(pParse, regRec);
+			sqlite3VdbeAddOp2(v, OP_IdxInsert, dest->iSDParm, regRec);
+			sqlite3ReleaseTempRange(parse, regCopy, in->nSdst + 1);
+			sqlite3ReleaseTempReg(parse, regRec);
 			break;
 		}
 		/* If we are creating a set for an "expr IN (SELECT ...)".
 		 */
 	case SRT_Set:{
 			int r1;
-			testcase(pIn->nSdst > 1);
-			r1 = sqlite3GetTempReg(pParse);
-			sqlite3VdbeAddOp4(v, OP_MakeRecord, pIn->iSdst,
-					  pIn->nSdst, r1, pDest->zAffSdst,
-					  pIn->nSdst);
-			sqlite3ExprCacheAffinityChange(pParse, pIn->iSdst,
-						       pIn->nSdst);
-			sqlite3VdbeAddOp2(v, OP_IdxInsert, pDest->iSDParm, r1);
-			sqlite3ReleaseTempReg(pParse, r1);
+			testcase(in->nSdst > 1);
+			r1 = sqlite3GetTempReg(parse);
+			sqlite3VdbeAddOp4(v, OP_MakeRecord, in->iSdst,
+					  in->nSdst, r1, dest->zAffSdst,
+					  in->nSdst);
+			sqlite3ExprCacheAffinityChange(parse, in->iSdst,
+						       in->nSdst);
+			sqlite3VdbeAddOp2(v, OP_IdxInsert, dest->iSDParm, r1);
+			sqlite3ReleaseTempReg(parse, r1);
 			break;
 		}
 
@@ -2966,25 +2924,25 @@ generateOutputSubroutine(Parse * pParse,	/* Parsing context */
 		 * of the scan loop.
 		 */
 	case SRT_Mem:{
-			assert(pIn->nSdst == 1 || pParse->nErr > 0);
-			testcase(pIn->nSdst != 1);
-			sqlite3ExprCodeMove(pParse, pIn->iSdst, pDest->iSDParm,
+			assert(in->nSdst == 1 || parse->nErr > 0);
+			testcase(in->nSdst != 1);
+			sqlite3ExprCodeMove(parse, in->iSdst, dest->iSDParm,
 					    1);
 			/* The LIMIT clause will jump out of the loop for us */
 			break;
 		}
 		/* The results are stored in a sequence of registers
-		 * starting at pDest->iSdst.  Then the co-routine yields.
+		 * starting at dest->iSdst.  Then the co-routine yields.
 		 */
 	case SRT_Coroutine:{
-			if (pDest->iSdst == 0) {
-				pDest->iSdst =
-				    sqlite3GetTempRange(pParse, pIn->nSdst);
-				pDest->nSdst = pIn->nSdst;
+			if (dest->iSdst == 0) {
+				dest->iSdst =
+				    sqlite3GetTempRange(parse, in->nSdst);
+				dest->nSdst = in->nSdst;
 			}
-			sqlite3ExprCodeMove(pParse, pIn->iSdst, pDest->iSdst,
-					    pIn->nSdst);
-			sqlite3VdbeAddOp1(v, OP_Yield, pDest->iSDParm);
+			sqlite3ExprCodeMove(parse, in->iSdst, dest->iSdst,
+					    in->nSdst);
+			sqlite3VdbeAddOp1(v, OP_Yield, dest->iSDParm);
 			break;
 		}
 
@@ -2997,11 +2955,11 @@ generateOutputSubroutine(Parse * pParse,	/* Parsing context */
 		 * return the next row of result.
 		 */
 	default:{
-			assert(pDest->eDest == SRT_Output);
-			sqlite3VdbeAddOp2(v, OP_ResultRow, pIn->iSdst,
-					  pIn->nSdst);
-			sqlite3ExprCacheAffinityChange(pParse, pIn->iSdst,
-						       pIn->nSdst);
+			assert(dest->eDest == SRT_Output);
+			sqlite3VdbeAddOp2(v, OP_ResultRow, in->iSdst,
+					  in->nSdst);
+			sqlite3ExprCacheAffinityChange(parse, in->iSdst,
+						       in->nSdst);
 			break;
 		}
 	}
@@ -3009,14 +2967,14 @@ generateOutputSubroutine(Parse * pParse,	/* Parsing context */
 	/* Jump to the end of the loop if the LIMIT is reached.
 	 */
 	if (p->iLimit) {
-		sqlite3VdbeAddOp2(v, OP_DecrJumpZero, p->iLimit, iBreak);
+		sqlite3VdbeAddOp2(v, OP_DecrJumpZero, p->iLimit, break_addr);
 		VdbeCoverage(v);
 	}
 
 	/* Generate the subroutine return
 	 */
 	sqlite3VdbeResolveLabel(v, iContinue);
-	sqlite3VdbeAddOp1(v, OP_Return, regReturn);
+	sqlite3VdbeAddOp1(v, OP_Return, reg_ret);
 
 	return addr;
 }
@@ -3140,8 +3098,10 @@ multiSelectOrderBy(Parse * pParse,	/* Parsing context */
 	int labelEnd;		/* Label for the end of the overall SELECT stmt */
 	int addr1;		/* Jump instructions that get retargetted */
 	int op;			/* One of TK_ALL, TK_UNION, TK_EXCEPT, TK_INTERSECT */
-	KeyInfo *pKeyDup = 0;	/* Comparison information for duplicate removal */
-	KeyInfo *pKeyMerge;	/* Comparison information for merging rows */
+	/* Comparison information for duplicate removal */
+	struct key_def *def_dup = NULL;
+	/* Comparison information for merging rows */
+	struct key_def *def_merge;
 	sqlite3 *db;		/* Database connection */
 	ExprList *pOrderBy;	/* The ORDER BY clause */
 	int nOrderBy;		/* Number of terms in the ORDER BY clause */
@@ -3150,7 +3110,6 @@ multiSelectOrderBy(Parse * pParse,	/* Parsing context */
 	int iSub2;		/* EQP id of right-hand query */
 
 	assert(p->pOrderBy != 0);
-	assert(pKeyDup == 0);	/* "Managed" code needs this.  Ticket #3382. */
 	db = pParse->db;
 	v = pParse->pVdbe;
 	assert(v != 0);		/* Already thrown the error if VDBE alloc failed */
@@ -3195,7 +3154,7 @@ multiSelectOrderBy(Parse * pParse,	/* Parsing context */
 		}
 	}
 
-	/* Compute the comparison permutation and keyinfo that is used with
+	/* Compute the comparison permutation and key_def that is used with
 	 * the permutation used to determine if the next
 	 * row of results comes from selectA or selectB.  Also add explicit
 	 * collations to the ORDER BY clause terms so that when the subqueries
@@ -3211,9 +3170,9 @@ multiSelectOrderBy(Parse * pParse,	/* Parsing context */
 			assert(pItem->u.x.iOrderByCol <= p->pEList->nExpr);
 			aPermute[i] = pItem->u.x.iOrderByCol - 1;
 		}
-		pKeyMerge = multiSelectOrderByKeyInfo(pParse, p, 1);
+		def_merge = sql_multiselect_orderby_to_key_def(pParse, p, 1);
 	} else {
-		pKeyMerge = 0;
+		def_merge = NULL;
 	}
 
 	/* Reattach the ORDER BY clause to the query.
@@ -3221,27 +3180,30 @@ multiSelectOrderBy(Parse * pParse,	/* Parsing context */
 	p->pOrderBy = pOrderBy;
 	pPrior->pOrderBy = sqlite3ExprListDup(pParse->db, pOrderBy, 0);
 
-	/* Allocate a range of temporary registers and the KeyInfo needed
+	/* Allocate a range of temporary registers and the key_def needed
 	 * for the logic that removes duplicate result rows when the
 	 * operator is UNION, EXCEPT, or INTERSECT (but not UNION ALL).
 	 */
 	if (op == TK_ALL) {
 		regPrev = 0;
 	} else {
-		int nExpr = p->pEList->nExpr;
-		assert(nOrderBy >= nExpr || db->mallocFailed);
+		int expr_count = p->pEList->nExpr;
+		assert(nOrderBy >= expr_count || db->mallocFailed);
 		regPrev = pParse->nMem + 1;
-		pParse->nMem += nExpr + 1;
+		pParse->nMem += expr_count + 1;
 		sqlite3VdbeAddOp2(v, OP_Integer, 0, regPrev);
-		pKeyDup = sqlite3KeyInfoAlloc(db, nExpr, 1);
-		if (pKeyDup) {
-			assert(sqlite3KeyInfoIsWriteable(pKeyDup));
-			for (i = 0; i < nExpr; i++) {
+		def_dup = key_def_new(expr_count);
+		if (def_dup != NULL) {
+			for (int i = 0; i < expr_count; i++) {
 				bool is_found = false;
-				pKeyDup->aColl[i] =
-					multiSelectCollSeq(pParse, p, i,
-							   &is_found);
-				pKeyDup->aSortOrder[i] = 0;
+				struct coll *coll;
+				coll = multiSelectCollSeq(pParse, p, i,
+							  &is_found);
+				key_def_set_part(def_dup, i, i,
+						 FIELD_TYPE_SCALAR,
+						 ON_CONFLICT_ACTION_ABORT,
+						 coll,
+						 SORT_ORDER_ASC);
 			}
 		}
 	}
@@ -3316,7 +3278,7 @@ multiSelectOrderBy(Parse * pParse,	/* Parsing context */
 	VdbeNoopComment((v, "Output routine for A"));
 	addrOutA = generateOutputSubroutine(pParse,
 					    p, &destA, pDest, regOutA,
-					    regPrev, pKeyDup, labelEnd);
+					    regPrev, def_dup, labelEnd);
 
 	/* Generate a subroutine that outputs the current row of the B
 	 * select as the next output row of the compound select.
@@ -3325,9 +3287,10 @@ multiSelectOrderBy(Parse * pParse,	/* Parsing context */
 		VdbeNoopComment((v, "Output routine for B"));
 		addrOutB = generateOutputSubroutine(pParse,
 						    p, &destB, pDest, regOutB,
-						    regPrev, pKeyDup, labelEnd);
+						    regPrev, def_dup, labelEnd);
 	}
-	sqlite3KeyInfoUnref(pKeyDup);
+
+	key_def_delete(def_dup);
 
 	/* Generate a subroutine to run when the results from select A
 	 * are exhausted and only data in select B remains.
@@ -3407,7 +3370,7 @@ multiSelectOrderBy(Parse * pParse,	/* Parsing context */
 	sqlite3VdbeAddOp4(v, OP_Permutation, 0, 0, 0, (char *)aPermute,
 			  P4_INTARRAY);
 	sqlite3VdbeAddOp4(v, OP_Compare, destA.iSdst, destB.iSdst, nOrderBy,
-			  (char *)pKeyMerge, P4_KEYINFO);
+			  (char *)def_merge, P4_KEYDEF);
 	sqlite3VdbeChangeP5(v, OPFLAG_PERMUTE);
 	sqlite3VdbeAddOp3(v, OP_Jump, addrAltB, addrAeqB, addrAgtB);
 	VdbeCoverage(v);
@@ -5140,12 +5103,13 @@ resetAccumulator(Parse * pParse, AggInfo * pAggInfo)
 						"argument");
 				pFunc->iDistinct = -1;
 			} else {
-				KeyInfo *pKeyInfo =
-				    keyInfoFromExprList(pParse, pE->x.pList, 0,
-							0);
+				struct key_def *def;
+				def = sql_expr_list_to_key_def(pParse,
+							       pE->x.pList,
+							       0);
 				sqlite3VdbeAddOp4(v, OP_OpenTEphemeral,
 						  pFunc->iDistinct, 1, 0,
-						  (char *)pKeyInfo, P4_KEYINFO);
+						  (char *)def, P4_KEYDEF);
 			}
 		}
 	}
@@ -5616,23 +5580,22 @@ sqlite3Select(Parse * pParse,		/* The parser context */
 	 * that change.
 	 */
 	if (sSort.pOrderBy) {
-		KeyInfo *pKeyInfo;
-		pKeyInfo =
-		    keyInfoFromExprList(pParse, sSort.pOrderBy, 0, pEList->nExpr);
+		struct key_def *def;
+		def = sql_expr_list_to_key_def(pParse, sSort.pOrderBy, 0);
 		sSort.iECursor = pParse->nTab++;
 		/* Number of columns in transient table equals to number of columns in
 		 * SELECT statement plus number of columns in ORDER BY statement
 		 * and plus one column for ID.
 		 */
 		int nCols = pEList->nExpr + sSort.pOrderBy->nExpr + 1;
-		if (pKeyInfo->aSortOrder[0] == SORT_ORDER_DESC) {
+		if (def->parts[0].sort_order == SORT_ORDER_DESC) {
 			sSort.sortFlags |= SORTFLAG_DESC;
 		}
 		sSort.addrSortIndex =
 		    sqlite3VdbeAddOp4(v, OP_OpenTEphemeral,
 				      sSort.iECursor,
 				      nCols,
-				      0, (char *)pKeyInfo, P4_KEYINFO);
+				      0, (char *)def, P4_KEYDEF);
 		VdbeComment((v, "Sort table"));
 	} else {
 		sSort.addrSortIndex = -1;
@@ -5641,10 +5604,10 @@ sqlite3Select(Parse * pParse,		/* The parser context */
 	/* If the output is destined for a temporary table, open that table.
 	 */
 	if (pDest->eDest == SRT_EphemTab) {
-		KeyInfo *pKeyInfo = sqlite3KeyInfoAlloc(pParse->db,
-							pEList->nExpr + 1, 0);
+		struct key_def *def;
+		def = key_def_new(pEList->nExpr + 1);
 		sqlite3VdbeAddOp4(v, OP_OpenTEphemeral, pDest->iSDParm,
-				  pEList->nExpr + 1, 0, (char*)pKeyInfo, P4_KEYINFO);
+				  pEList->nExpr + 1, 0, (char*)def, P4_KEYDEF);
 
 		VdbeComment((v, "Output table"));
 	}
@@ -5665,12 +5628,12 @@ sqlite3Select(Parse * pParse,		/* The parser context */
 	 */
 	if (p->selFlags & SF_Distinct) {
 		sDistinct.tabTnct = pParse->nTab++;
-		KeyInfo *pKeyInfo = keyInfoFromExprList(pParse, p->pEList, 0, 0);
+		struct key_def *def = sql_expr_list_to_key_def(pParse, p->pEList, 0);
 		sDistinct.addrTnct = sqlite3VdbeAddOp4(v, OP_OpenTEphemeral,
 						       sDistinct.tabTnct,
-						       pKeyInfo->nField,
-						       0, (char *)pKeyInfo,
-						       P4_KEYINFO);
+						       def->part_count,
+						       0, (char *)def,
+						       P4_KEYDEF);
 		VdbeComment((v, "Distinct table"));
 		sDistinct.eTnctType = WHERE_DISTINCT_UNORDERED;
 	} else {
@@ -5811,7 +5774,6 @@ sqlite3Select(Parse * pParse,		/* The parser context */
 		 * much more complex than aggregates without a GROUP BY.
 		 */
 		if (pGroupBy) {
-			KeyInfo *pKeyInfo;	/* Keying information for the group by clause */
 			int addr1;	/* A-vs-B comparision jump */
 			int addrOutputRow;	/* Start of subroutine that outputs a result row */
 			int regOutputRow;	/* Return address register for output subroutine */
@@ -5827,14 +5789,12 @@ sqlite3Select(Parse * pParse,		/* The parser context */
 			 * will be converted into a Noop.
 			 */
 			sAggInfo.sortingIdx = pParse->nTab++;
-			pKeyInfo =
-			    keyInfoFromExprList(pParse, pGroupBy, 0,
-						sAggInfo.nColumn);
+			struct key_def *def = sql_expr_list_to_key_def(pParse, pGroupBy, 0);
 			addrSortingIdx =
 			    sqlite3VdbeAddOp4(v, OP_SorterOpen,
 					      sAggInfo.sortingIdx,
 					      sAggInfo.nSortingColumn, 0,
-					      (char *)pKeyInfo, P4_KEYINFO);
+					      (char *)def, P4_KEYDEF);
 
 			/* Initialize memory locations used by GROUP BY aggregate processing
 			 */
@@ -5872,7 +5832,7 @@ sqlite3Select(Parse * pParse,		/* The parser context */
 			if (sqlite3WhereIsOrdered(pWInfo) == pGroupBy->nExpr) {
 				/* The optimizer is able to deliver rows in group by order so
 				 * we do not have to sort.  The OP_OpenEphemeral table will be
-				 * cancelled later because we still need to use the pKeyInfo
+				 * cancelled later because we still need to use the key_def
 				 */
 				groupBySort = 0;
 			} else {
@@ -5982,10 +5942,15 @@ sqlite3Select(Parse * pParse,		/* The parser context */
 							iBMem + j);
 				}
 			}
+			struct key_def *dup_def = key_def_dup(def);
+			if (dup_def == NULL) {
+				db->mallocFailed = 1;
+				goto select_end;
+			}
 			sqlite3VdbeAddOp4(v, OP_Compare, iAMem, iBMem,
 					  pGroupBy->nExpr,
-					  (char *)sqlite3KeyInfoRef(pKeyInfo),
-					  P4_KEYINFO);
+					  (char*)dup_def,
+					  P4_KEYDEF);
 			addr1 = sqlite3VdbeCurrentAddr(v);
 			sqlite3VdbeAddOp3(v, OP_Jump, addr1 + 1, 0, addr1 + 1);
 			VdbeCoverage(v);
@@ -6095,7 +6060,7 @@ sqlite3Select(Parse * pParse,		/* The parser context */
 				 */
 				const int iCsr = pParse->nTab++;	/* Cursor to scan b-tree */
 				Index *pIdx;	/* Iterator variable */
-				KeyInfo *pKeyInfo = 0;	/* Keyinfo for scanned index */
+				struct key_def *def = NULL;
 				Index *pBest;	/* Best index found so far */
 				int iRoot = pTab->tnum;	/* Root page of scanned b-tree */
 
@@ -6105,7 +6070,7 @@ sqlite3Select(Parse * pParse,		/* The parser context */
 				 *
 				 * (2013-10-03) Do not count the entries in a partial index.
 				 *
-				 * In practice the KeyInfo structure will not be used. It is only
+				 * In practice the key_def structure will not be used. It is only
 				 * passed to keep OP_OpenRead happy.
 				 */
 				pBest = sqlite3PrimaryKeyIndex(pTab);
@@ -6121,19 +6086,17 @@ sqlite3Select(Parse * pParse,		/* The parser context */
 						pBest = pIdx;
 					}
 				}
-				if (pBest) {
+				if (pBest != NULL) {
 					iRoot = pBest->tnum;
-					pKeyInfo =
-					    sqlite3KeyInfoOfIndex(pParse, db,
-								  pBest);
+					def = sql_index_key_def(pBest, true);
 				}
 
 				/* Open a read-only cursor, execute the OP_Count, close the cursor. */
 				emit_open_cursor(pParse, iCsr, iRoot);
-				if (pKeyInfo) {
+				if (def != NULL) {
 					sqlite3VdbeChangeP4(v, -1,
-							    (char *)pKeyInfo,
-							    P4_KEYINFO);
+							    (char *)def,
+							    P4_KEYDEF);
 				}
 				sqlite3VdbeAddOp2(v, OP_Count, iCsr,
 						  sAggInfo.aFunc[0].iMem);
@@ -6186,7 +6149,7 @@ sqlite3Select(Parse * pParse,		/* The parser context */
 					assert(db->mallocFailed
 					       || pMinMax != 0);
 					if (!db->mallocFailed) {
-						pMinMax->a[0].sortOrder =
+						pMinMax->a[0].sort_order =
 						    flag !=
 						    WHERE_ORDERBY_MIN ? 1 : 0;
 						pMinMax->a[0].pExpr->op =
diff --git a/src/box/sql/sqliteInt.h b/src/box/sql/sqliteInt.h
index 2a801a4..7fa8d6f 100644
--- a/src/box/sql/sqliteInt.h
+++ b/src/box/sql/sqliteInt.h
@@ -1462,7 +1462,6 @@ typedef struct IdList IdList;
 typedef struct Index Index;
 typedef struct IndexSample IndexSample;
 typedef struct KeyClass KeyClass;
-typedef struct KeyInfo KeyInfo;
 typedef struct Lookaside Lookaside;
 typedef struct LookasideSlot LookasideSlot;
 typedef struct NameContext NameContext;
@@ -2025,24 +2024,6 @@ struct FKey {
 #define OE_SetDflt  8		/* Set the foreign key value to its default */
 #define OE_Cascade  9		/* Cascade the changes */
 
-/*
- * An instance of the following structure is passed as the first
- * argument to sqlite3VdbeKeyCompare and is used to control the
- * comparison of the two index keys.
- *
- * Note that aSortOrder[] and aColl[] have nField+1 slots.  There
- * are nField slots for the columns of an index.
- */
-struct KeyInfo {
-	u32 nRef;		/* Number of references to this KeyInfo object */
-	u8 enc;			/* Text encoding - one of the SQLITE_UTF* values */
-	u16 nField;		/* Number of key columns in the index */
-	u16 nXField;		/* Number of columns beyond the key columns */
-	sqlite3 *db;		/* The database connection */
-	u8 *aSortOrder;		/* Sort order for each column. */
-	struct coll *aColl[1];	/* Collating sequence for each term of the key */
-};
-
 /*
  * This object holds a record which has been parsed out into individual
  * fields, for the purposes of doing a comparison.
@@ -2057,7 +2038,7 @@ struct KeyInfo {
  * an index b+tree. The goal of the search is to find the entry that
  * is closed to the key described by this object.  This object might hold
  * just a prefix of the key.  The number of fields is given by
- * pKeyInfo->nField.
+ * key_def->part_count.
  *
  * The r1 and r2 fields are the values to return if this key is less than
  * or greater than a key in the btree, respectively.  These are normally
@@ -2067,7 +2048,7 @@ struct KeyInfo {
  * The key comparison functions actually return default_rc when they find
  * an equals comparison.  default_rc can be -1, 0, or +1.  If there are
  * multiple entries in the b-tree with the same key (when only looking
- * at the first pKeyInfo->nFields,) then default_rc can be set to -1 to
+ * at the first key_def->part_count) then default_rc can be set to -1 to
  * cause the search to find the last match, or +1 to cause the search to
  * find the first match.
  *
@@ -2079,7 +2060,8 @@ struct KeyInfo {
  * b-tree.
  */
 struct UnpackedRecord {
-	KeyInfo *pKeyInfo;	/* Collation and sort-order information */
+	/** Collation and sort-order information. */
+	struct key_def* key_def;
 	Mem *aMem;		/* Values */
 	u16 nField;		/* Number of entries in apMem[] */
 	i8 default_rc;		/* Comparison result if keys are equal */
@@ -2456,7 +2438,7 @@ struct ExprList {
 		Expr *pExpr;	/* The list of expressions */
 		char *zName;	/* Token associated with this expression */
 		char *zSpan;	/* Original text of the expression */
-		u8 sortOrder;	/* 1 for DESC or 0 for ASC */
+		enum sort_order sort_order;
 		unsigned done:1;	/* A flag to indicate when processing is finished */
 		unsigned bSpanIsTab:1;	/* zSpan holds DB.TABLE.COLUMN */
 		unsigned reusable:1;	/* Constant expression is reusable */
@@ -2688,12 +2670,12 @@ struct NameContext {
  *
  * addrOpenEphm[] entries contain the address of OP_OpenEphemeral opcodes.
  * These addresses must be stored so that we can go back and fill in
- * the P4_KEYINFO and P2 parameters later.  Neither the KeyInfo nor
+ * the P4_KEYDEF and P2 parameters later.  Neither the key_def nor
  * the number of columns in P2 can be computed at the same time
  * as the OP_OpenEphm instruction is coded because not
  * enough information about the compound query is known at that point.
- * The KeyInfo for addrOpenTran[0] and [1] contains collating sequences
- * for the result set.  The KeyInfo for addrOpenEphm[2] contains collating
+ * The key_def for addrOpenTran[0] and [1] contains collating sequences
+ * for the result set.  The key_def for addrOpenEphm[2] contains collating
  * sequences for the ORDER BY clause.
  */
 struct Select {
@@ -3495,7 +3477,15 @@ Expr *sqlite3ExprFunction(Parse *, ExprList *, Token *);
 void sqlite3ExprAssignVarNumber(Parse *, Expr *, u32);
 ExprList *sqlite3ExprListAppend(Parse *, ExprList *, Expr *);
 ExprList *sqlite3ExprListAppendVector(Parse *, ExprList *, IdList *, Expr *);
-void sqlite3ExprListSetSortOrder(ExprList *, int);
+
+/**
+ * Set the sort order for the last element on the given ExprList.
+ *
+ * @param p Expression list.
+ * @param sort_order Sort order to set.
+ */
+void sqlite3ExprListSetSortOrder(ExprList *, enum sort_order sort_order);
+
 void sqlite3ExprListSetName(Parse *, ExprList *, Token *, int);
 void sqlite3ExprListSetSpan(Parse *, ExprList *, ExprSpan *);
 void sqlite3ExprListDelete(sqlite3 *, ExprList *);
@@ -3527,11 +3517,22 @@ const char *
 index_collation_name(Index *, uint32_t);
 struct coll *
 sql_index_collation(Index *idx, uint32_t column);
-struct coll *
-sql_default_coll();
 bool
 space_is_view(Table *);
 
+/**
+ * Return key_def of provided struct Index. This routine
+ * actually performs key duplication.
+ *
+ * @param idx Pointer to `struct Index` object.
+ * @param is_dup Flag which is set if key_def should be
+ * duplicated.
+ *
+ * @retval Pointer to `struct key_def`.
+ */
+struct key_def*
+sql_index_key_def(struct Index *idx, bool is_dup);
+
 /**
  * Return sort order of given column from index.
  *
@@ -3586,10 +3587,50 @@ void sqlite3SrcListDelete(sqlite3 *, SrcList *);
 Index *sqlite3AllocateIndexObject(sqlite3 *, i16, int, char **);
 bool
 index_is_unique(Index *);
-void sqlite3CreateIndex(Parse *, Token *, SrcList *, ExprList *, int, Token *,
-			Expr *, int, int, u8);
+
+/**
+ * Create a new index for an SQL table.  name is the name of the
+ * index and tbl_name is the name of the table that is to be
+ * indexed.  Both will be NULL for a primary key or an index that
+ * is created to satisfy a UNIQUE constraint.  If tbl_name and
+ * name are NULL, use parse->pNewTable as the table to be indexed.
+ * parse->pNewTable is a table that is currently being
+ * constructed by a CREATE TABLE statement.
+ *
+ * col_list is a list of columns to be indexed.  col_list will be
+ * NULL if this is a primary key or unique-constraint on the most
+ * recent column added to the table currently under construction.
+ *
+ * @param parse All information about this parse.
+ * @param name Index name. May be NULL.
+ * @param tbl_name Table to index. Use pParse->pNewTable ifNULL.
+ * @param col_list A list of columns to be indexed.
+ * @param on_error One of ON_CONFLICT_ACTION_ABORT, _IGNORE,
+ *        _REPLACE, or _NONE.
+ * @param start The CREATE token that begins this statement.
+ * @param pi_where WHERE clause for partial indices.
+ * @param sort_order Sort order of primary key when pList==NULL.
+ * @param if_not_exist Omit error if index already exists.
+ * @param idx_type The index type.
+ */
+void
+sql_create_index(struct Parse *parse, struct Token *token,
+		 struct SrcList *tbl_name, struct ExprList *col_list,
+		 int on_error, struct Token *start, struct Expr *pi_where,
+		 enum sort_order sort_order, bool if_not_exist, u8 idx_type);
+
+/**
+ * This routine will drop an existing named index.  This routine
+ * implements the DROP INDEX statement.
+ *
+ * @param parse_context Current parsing context.
+ * @param index_name_list List containing index name.
+ * @param table_token Token representing table name.
+ * @param if_exists True, if statement contains 'IF EXISTS' clause.
+ */
 void
 sql_drop_index(struct Parse *, struct SrcList *, struct Token *, bool);
+
 int sqlite3Select(Parse *, Select *, SelectDest *);
 Select *sqlite3SelectNew(Parse *, ExprList *, SrcList *, Expr *, ExprList *,
 			 Expr *, ExprList *, u32, Expr *, Expr *);
@@ -3920,7 +3961,17 @@ void sqlite3NestedParse(Parse *, const char *, ...);
 void sqlite3ExpirePreparedStatements(sqlite3 *);
 int sqlite3CodeSubselect(Parse *, Expr *, int);
 void sqlite3SelectPrep(Parse *, Select *, NameContext *);
-void sqlite3SelectWrongNumTermsError(Parse * pParse, Select * p);
+
+/**
+ * Error message for when two or more terms of a compound select
+ * have different size result sets.
+ *
+ * @param parse Parsing context.
+ * @param p Select struct to analyze.
+ */
+void
+sqlite3SelectWrongNumTermsError(struct Parse *parse, struct Select *p);
+
 int sqlite3MatchSpanName(const char *, const char *, const char *);
 int sqlite3ResolveExprNames(NameContext *, Expr *);
 int sqlite3ResolveExprListNames(NameContext *, ExprList *);
@@ -3949,13 +4000,6 @@ void sqlite3RegisterLikeFunctions(sqlite3 *, int);
 int sqlite3IsLikeFunction(sqlite3 *, Expr *, int *, char *);
 void sqlite3SchemaClear(sqlite3 *);
 Schema *sqlite3SchemaCreate(sqlite3 *);
-KeyInfo *sqlite3KeyInfoAlloc(sqlite3 *, int, int);
-void sqlite3KeyInfoUnref(KeyInfo *);
-KeyInfo *sqlite3KeyInfoRef(KeyInfo *);
-KeyInfo *sqlite3KeyInfoOfIndex(Parse *, sqlite3 *, Index *);
-#ifdef SQLITE_DEBUG
-int sqlite3KeyInfoIsWriteable(KeyInfo *);
-#endif
 int sqlite3CreateFunc(sqlite3 *, const char *, int, int, void *,
 		      void (*)(sqlite3_context *, int, sqlite3_value **),
 		      void (*)(sqlite3_context *, int, sqlite3_value **),
diff --git a/src/box/sql/tarantoolInt.h b/src/box/sql/tarantoolInt.h
index 57fc4a1..8fc9dd6 100644
--- a/src/box/sql/tarantoolInt.h
+++ b/src/box/sql/tarantoolInt.h
@@ -98,7 +98,7 @@ int tarantoolSqlite3RenameParentTable(int iTab, const char *zOldParentName,
 
 /* Interface for ephemeral tables. */
 int tarantoolSqlite3EphemeralCreate(BtCursor * pCur, uint32_t filed_count,
-				    struct coll *aColl);
+				    struct key_def *def);
 /**
  * Insert tuple into ephemeral space.
  * In contrast to ordinary spaces, there is no need to create and
@@ -119,12 +119,19 @@ int tarantoolSqlite3EphemeralClearTable(BtCursor * pCur);
 int tarantoolSqlite3EphemeralGetMaxId(BtCursor * pCur, uint32_t fieldno,
 				       uint64_t * max_id);
 
-/* Compare against the index key under a cursor -
- * the key may span non-adjacent fields in a random order,
- * ex: [4]-[1]-[2]
+/**
+ * Performs exactly as extract_key + sqlite3VdbeCompareMsgpack,
+ * only faster.
+ *
+ * @param pCur cursor which point to tuple to compare.
+ * @param pUnpacked Unpacked record to compare with.
+ * @param[out] res Comparison result.
+ *
+ * @retval Error code.
  */
-int tarantoolSqlite3IdxKeyCompare(BtCursor * pCur, UnpackedRecord * pUnpacked,
-				  int *res);
+int
+tarantoolSqlite3IdxKeyCompare(struct BtCursor *cursor,
+			      struct UnpackedRecord *unpacked, int *res);
 
 /**
  * The function assumes the cursor is open on _schema.
diff --git a/src/box/sql/update.c b/src/box/sql/update.c
index f3bd0b7..31d07d5 100644
--- a/src/box/sql/update.c
+++ b/src/box/sql/update.c
@@ -358,12 +358,16 @@ sqlite3Update(Parse * pParse,		/* The parser context */
 	sqlite3VdbeAddOp2(v, OP_Null, 0, iPk);
 
 	if (isView) {
-		KeyInfo *pKeyInfo = sqlite3KeyInfoAlloc(pParse->db, nKey, 0);
+		struct key_def *def = key_def_new(nKey);
+		if (def == NULL) {
+			pParse->db->mallocFailed = 1;
+			goto update_cleanup;
+		}
 		addrOpen = sqlite3VdbeAddOp4(v, OP_OpenTEphemeral, iEph,
-					     nKey, 0, (char*)pKeyInfo, P4_KEYINFO);
+					     nKey, 0, (char*)def, P4_KEYDEF);
 	} else {
 		addrOpen = sqlite3VdbeAddOp2(v, OP_OpenTEphemeral, iEph, nPk);
-		sqlite3VdbeSetP4KeyInfo(pParse, pPk);
+		sql_vdbe_set_p4_key_def(pParse, pPk);
 	}
 
 	pWInfo = sqlite3WhereBegin(pParse, pTabList, pWhere, 0, 0,
diff --git a/src/box/sql/vdbe.c b/src/box/sql/vdbe.c
index f405ac0..127f320 100644
--- a/src/box/sql/vdbe.c
+++ b/src/box/sql/vdbe.c
@@ -2242,35 +2242,36 @@ case OP_Permutation: {
  * OPFLAG_PERMUTE bit is clear, then register are compared in sequential
  * order.
  *
- * P4 is a KeyInfo structure that defines collating sequences and sort
+ * P4 is a key_def structure that defines collating sequences and sort
  * orders for the comparison.  The permutation applies to registers
- * only.  The KeyInfo elements are used sequentially.
+ * only.  The key_def elements are used sequentially.
  *
  * The comparison is a sort comparison, so NULLs compare equal,
  * NULLs are less than numbers, numbers are less than strings,
  * and strings are less than blobs.
  */
 case OP_Compare: {
-	int n;
-	int i;
 	int p1;
 	int p2;
-	const KeyInfo *pKeyInfo;
 	int idx;
-	struct coll *pColl;    /* Collating sequence to use on this term */
-	int bRev;          /* True for DESCENDING sort order */
 
-	if ((pOp->p5 & OPFLAG_PERMUTE)==0) aPermute = 0;
-	n = pOp->p3;
-	pKeyInfo = pOp->p4.pKeyInfo;
+	if ((pOp->p5 & OPFLAG_PERMUTE) == 0)
+		aPermute = 0;
+
+	int n = pOp->p3;
+
+	assert(pOp->p4type == P4_KEYDEF);
 	assert(n>0);
-	assert(pKeyInfo!=0);
 	p1 = pOp->p1;
 	p2 = pOp->p2;
+
+	struct key_def *def = pOp->p4.key_def;
 #if SQLITE_DEBUG
 	if (aPermute) {
-		int k, mx = 0;
-		for(k=0; k<n; k++) if (aPermute[k]>mx) mx = aPermute[k];
+		int mx = 0;
+		for(uint32_t k = 0; k < (uint32_t)n; k++)
+			if (aPermute[k] > mx)
+				mx = aPermute[k];
 		assert(p1>0 && p1+mx<=(p->nMem+1 - p->nCursor)+1);
 		assert(p2>0 && p2+mx<=(p->nMem+1 - p->nCursor)+1);
 	} else {
@@ -2278,18 +2279,19 @@ case OP_Compare: {
 		assert(p2>0 && p2+n<=(p->nMem+1 - p->nCursor)+1);
 	}
 #endif /* SQLITE_DEBUG */
-	for(i=0; i<n; i++) {
+	for(int i = 0; i < n; i++) {
 		idx = aPermute ? aPermute[i] : i;
 		assert(memIsValid(&aMem[p1+idx]));
 		assert(memIsValid(&aMem[p2+idx]));
 		REGISTER_TRACE(p1+idx, &aMem[p1+idx]);
 		REGISTER_TRACE(p2+idx, &aMem[p2+idx]);
-		assert(i<pKeyInfo->nField);
-		pColl = pKeyInfo->aColl[i];
-		bRev = pKeyInfo->aSortOrder[i];
-		iCompare = sqlite3MemCompare(&aMem[p1+idx], &aMem[p2+idx], pColl);
+		assert(i < (int)def->part_count);
+		struct coll *coll = def->parts[i].coll;
+		bool is_rev = def->parts[i].sort_order == SORT_ORDER_DESC;
+		iCompare = sqlite3MemCompare(&aMem[p1+idx], &aMem[p2+idx], coll);
 		if (iCompare) {
-			if (bRev) iCompare = -iCompare;
+			if (is_rev)
+				iCompare = -iCompare;
 			break;
 		}
 	}
@@ -3118,8 +3120,8 @@ case OP_SetCookie: {
  * values need not be contiguous but all P1 values should be
  * small integers. It is an error for P1 to be negative.
  *
- * The P4 value may be a pointer to a KeyInfo structure.
- * If it is a pointer to a KeyInfo structure, then said structure
+ * The P4 value may be a pointer to a key_def structure.
+ * If it is a pointer to a key_def structure, then said structure
  * defines the content and collatining sequence of the index
  * being opened. Otherwise, P4 is NULL.
  *
@@ -3207,7 +3209,7 @@ case OP_OpenWrite:
 	pBtCur->index = index;
 	pBtCur->eState = CURSOR_INVALID;
 	/* Key info still contains sorter order and collation. */
-	pCur->pKeyInfo = pOp->p4.pKeyInfo;
+	pCur->key_def = index->def->key_def;
 
 open_cursor_set_hints:
 	assert(OPFLAG_BULKCSR==BTREE_BULKLOAD);
@@ -3222,8 +3224,12 @@ open_cursor_set_hints:
 	break;
 }
 
-/* Opcode: OpenTEphemeral P1 P2 * * *
- * Synopsis: nColumn = P2
+/**
+ * Opcode: OpenTEphemeral P1 P2 * P4 *
+ * Synopsis:
+ * @param P1 index of new cursor to be created
+ * @param P2 number of columns in a new table
+ * @param P4 key def for new table
  *
  * This opcode creates Tarantool's ephemeral table and sets cursor P1 to it.
  */
@@ -3232,21 +3238,21 @@ case OP_OpenTEphemeral: {
 	BtCursor *pBtCur;
 	assert(pOp->p1 >= 0);
 	assert(pOp->p2 > 0);
-	assert(pOp->p4.pKeyInfo != 0);
-	assert(pOp->p4type == P4_KEYINFO);
+	assert(pOp->p4.key_def != NULL);
+	assert(pOp->p4type == P4_KEYDEF);
 
 	pCx = allocateCursor(p, pOp->p1, pOp->p2, CURTYPE_TARANTOOL);
 	if (pCx == 0) goto no_mem;
 	pCx->nullRow = 1;
 
-	pCx->pKeyInfo  = pOp->p4.pKeyInfo;
+	pCx->key_def  = pOp->p4.key_def;
 	pBtCur = pCx->uc.pCursor;
 	/* Ephemeral spaces don't have space_id */
 	pBtCur->eState = CURSOR_INVALID;
 	pBtCur->curFlags = BTCF_TEphemCursor;
 
 	rc = tarantoolSqlite3EphemeralCreate(pCx->uc.pCursor, pOp->p2,
-					     pOp->p4.pKeyInfo->aColl[0]);
+					     pCx->key_def);
 	if (rc) goto abort_due_to_error;
 	break;
 }
@@ -3268,9 +3274,8 @@ case OP_SorterOpen: {
 	assert(pOp->p2>=0);
 	pCx = allocateCursor(p, pOp->p1, pOp->p2, CURTYPE_SORTER);
 	if (pCx==0) goto no_mem;
-	pCx->pKeyInfo = pOp->p4.pKeyInfo;
-	assert(pCx->pKeyInfo->db==db);
-	rc = sqlite3VdbeSorterInit(db, pOp->p3, pCx);
+	pCx->key_def = pOp->p4.key_def;
+	rc = sqlite3VdbeSorterInit(db, pCx);
 	if (rc) goto abort_due_to_error;
 	break;
 }
@@ -3546,7 +3551,7 @@ case OP_SeekGT: {       /* jump, in3 */
 	nField = pOp->p4.i;
 	assert(pOp->p4type==P4_INT32);
 	assert(nField>0);
-	r.pKeyInfo = pC->pKeyInfo;
+	r.key_def = pC->key_def;
 	r.nField = (u16)nField;
 
 	if (reg_ipk > 0) {
@@ -3698,7 +3703,7 @@ case OP_Found: {        /* jump, in3 */
 	assert(pC->eCurType==CURTYPE_TARANTOOL);
 	assert(pC->uc.pCursor!=0);
 	if (pOp->p4.i>0) {
-		r.pKeyInfo = pC->pKeyInfo;
+		r.key_def = pC->key_def;
 		r.nField = (u16)pOp->p4.i;
 		r.aMem = pIn3;
 #ifdef SQLITE_DEBUG
@@ -3711,11 +3716,12 @@ case OP_Found: {        /* jump, in3 */
 		pIdxKey = &r;
 		pFree = 0;
 	} else {
-		pFree = pIdxKey = sqlite3VdbeAllocUnpackedRecord(pC->pKeyInfo);
+		pFree = pIdxKey = sqlite3VdbeAllocUnpackedRecord(db, pC->key_def);
 		if (pIdxKey==0) goto no_mem;
 		assert(pIn3->flags & MEM_Blob );
 		(void)ExpandBlob(pIn3);
-		sqlite3VdbeRecordUnpackMsgpack(pC->pKeyInfo, pIn3->n, pIn3->z, pIdxKey);
+		sqlite3VdbeRecordUnpackMsgpack(pC->key_def,
+					       pIn3->z, pIdxKey);
 	}
 	pIdxKey->default_rc = 0;
 	pIdxKey->opcode = pOp->opcode;
@@ -4491,7 +4497,7 @@ case OP_IdxDelete: {
 	pCrsr = pC->uc.pCursor;
 	assert(pCrsr!=0);
 	assert(pOp->p5==0);
-	r.pKeyInfo = pC->pKeyInfo;
+	r.key_def = pC->key_def;
 	r.nField = (u16)pOp->p3;
 	r.default_rc = 0;
 	r.aMem = &aMem[pOp->p2];
@@ -4575,7 +4581,7 @@ case OP_IdxGE:  {       /* jump */
 	assert(pC->deferredMoveto==0);
 	assert(pOp->p5==0 || pOp->p5==1);
 	assert(pOp->p4type==P4_INT32);
-	r.pKeyInfo = pC->pKeyInfo;
+	r.key_def = pC->key_def;
 	r.nField = (u16)pOp->p4.i;
 	if (pOp->opcode<OP_IdxLT) {
 		assert(pOp->opcode==OP_IdxLE || pOp->opcode==OP_IdxGT);
@@ -4589,7 +4595,7 @@ case OP_IdxGE:  {       /* jump */
 	{ int i; for(i=0; i<r.nField; i++) assert(memIsValid(&r.aMem[i])); }
 #endif
 	res = 0;  /* Not needed.  Only used to silence a warning. */
-	rc = sqlite3VdbeIdxKeyCompare(db, pC, &r, &res);
+	rc = sqlite3VdbeIdxKeyCompare(pC, &r, &res);
 	assert((OP_IdxLE&1)==(OP_IdxLT&1) && (OP_IdxGE&1)==(OP_IdxGT&1));
 	if ((pOp->opcode&1)==(OP_IdxLT&1)) {
 		assert(pOp->opcode==OP_IdxLE || pOp->opcode==OP_IdxLT);
diff --git a/src/box/sql/vdbe.h b/src/box/sql/vdbe.h
index d1f416b..e9ef693 100644
--- a/src/box/sql/vdbe.h
+++ b/src/box/sql/vdbe.h
@@ -77,7 +77,6 @@ struct VdbeOp {
 		struct coll *pColl;	/* Used when p4type is P4_COLLSEQ */
 		Mem *pMem;	/* Used when p4type is P4_MEM */
 		bool b;         /* Used when p4type is P4_BOOL */
-		KeyInfo *pKeyInfo;	/* Used when p4type is P4_KEYINFO */
 		int *ai;	/* Used when p4type is P4_INTARRAY */
 		SubProgram *pProgram;	/* Used when p4type is P4_SUBPROGRAM */
 		Index *pIndex;	/* Used when p4type is P4_INDEX */
@@ -85,6 +84,8 @@ struct VdbeOp {
 		Expr *pExpr;	/* Used when p4type is P4_EXPR */
 #endif
 		int (*xAdvance) (BtCursor *, int *);
+		/* Used when p4type is P4_KEYDEF. */
+		struct key_def *key_def;
 	} p4;
 #ifdef SQLITE_ENABLE_EXPLAIN_COMMENTS
 	char *zComment;		/* Comment to improve readability */
@@ -131,7 +132,6 @@ typedef struct VdbeOpList VdbeOpList;
 #define P4_STATIC   (-2)	/* Pointer to a static string */
 #define P4_COLLSEQ  (-3)	/* P4 is a pointer to a CollSeq structure */
 #define P4_FUNCDEF  (-4)	/* P4 is a pointer to a FuncDef structure */
-#define P4_KEYINFO  (-5)	/* P4 is a pointer to a KeyInfo structure */
 #define P4_EXPR     (-6)	/* P4 is a pointer to an Expr tree */
 #define P4_MEM      (-7)	/* P4 is a pointer to a Mem*    structure */
 #define P4_TRANSIENT  0		/* P4 is a pointer to a transient string */
@@ -147,7 +147,6 @@ typedef struct VdbeOpList VdbeOpList;
 #define P4_PTR      (-18)	/* P4 is a generic pointer */
 #define P4_KEYDEF   (-19)       /* P4 is a pointer to key_def structure. */
 
-
 /* Error message codes for OP_Halt */
 #define P5_ConstraintNotNull 1
 #define P5_ConstraintUnique  2
@@ -249,7 +248,16 @@ int sqlite3VdbeChangeToNoop(Vdbe *, int addr);
 int sqlite3VdbeDeletePriorOpcode(Vdbe *, u8 op);
 void sqlite3VdbeChangeP4(Vdbe *, int addr, const char *zP4, int N);
 void sqlite3VdbeAppendP4(Vdbe *, void *pP4, int p4type);
-void sqlite3VdbeSetP4KeyInfo(Parse *, Index *);
+
+/**
+ * Set the P4 on the most recently added opcode to the key_def for the
+ * index given.
+ * @param Parse context, for error reporting.
+ * @param Index to get key_def from.
+ */
+void
+sql_vdbe_set_p4_key_def(struct Parse *parse, struct Index *index);
+
 VdbeOp *sqlite3VdbeGetOp(Vdbe *, int);
 int sqlite3VdbeMakeLabel(Vdbe *);
 void sqlite3VdbeRunOnlyOnce(Vdbe *);
@@ -280,14 +288,27 @@ char *sqlite3VdbeExpandSql(Vdbe *, const char *);
 #endif
 int sqlite3MemCompare(const Mem *, const Mem *, const struct coll *);
 
-void sqlite3VdbeRecordUnpackMsgpack(KeyInfo *, int, const void *,
-				    UnpackedRecord *);
-int sqlite3VdbeRecordCompare(int, const void *, UnpackedRecord *);
-int sqlite3VdbeRecordCompareWithSkip(int, const void *, UnpackedRecord *, int);
-UnpackedRecord *sqlite3VdbeAllocUnpackedRecord(KeyInfo *);
+/**
+ * Perform unpacking of provided message pack.
+ *
+ * @param key_def Information about the record format
+ * @param key The binary record
+ * @param dest Populate this structure before returning.
+ */
+void sqlite3VdbeRecordUnpackMsgpack(struct key_def *key_def,
+				    const void *msgpack,
+				    struct UnpackedRecord *dest);
+
+int sqlite3VdbeRecordCompare(struct sqlite3 *db, int key_count,
+			     const void *key1, UnpackedRecord *key2);
+int sqlite3VdbeRecordCompareWithSkip(struct sqlite3 *db,
+				     int key_count, const void *key1,
+				     struct UnpackedRecord *key2, bool is_skip);
+UnpackedRecord *sqlite3VdbeAllocUnpackedRecord(struct sqlite3 *,
+					       struct key_def *);
 int sql_vdbe_mem_alloc_region(Mem *, uint32_t);
 
-typedef int (*RecordCompare) (int, const void *, UnpackedRecord *);
+typedef int (*RecordCompare) (const void *, UnpackedRecord *);
 RecordCompare sqlite3VdbeFindCompare(UnpackedRecord *);
 
 #ifndef SQLITE_OMIT_TRIGGER
diff --git a/src/box/sql/vdbeInt.h b/src/box/sql/vdbeInt.h
index 3a907cd..9779654 100644
--- a/src/box/sql/vdbeInt.h
+++ b/src/box/sql/vdbeInt.h
@@ -110,7 +110,8 @@ struct VdbeCursor {
 		int pseudoTableReg;	/* CURTYPE_PSEUDO. Reg holding content. */
 		VdbeSorter *pSorter;	/* CURTYPE_SORTER. Sorter object */
 	} uc;
-	KeyInfo *pKeyInfo;	/* Info about index keys needed by index cursors */
+	/* Info about keys needed by index cursors. */
+	struct key_def *key_def;
 	i16 nField;		/* Number of fields in the header */
 	u16 nHdrParsed;		/* Number of header fields parsed so far */
 	const u8 *aRow;		/* Data for the current row, if all on one page */
@@ -430,7 +431,6 @@ struct PreUpdate {
 	VdbeCursor *pCsr;	/* Cursor to read old values from */
 	int op;			/* One of SQLITE_INSERT, UPDATE, DELETE */
 	u8 *aRecord;		/* old.* database record */
-	KeyInfo keyinfo;
 	UnpackedRecord *pUnpacked;	/* Unpacked version of aRecord[] */
 	UnpackedRecord *pNewUnpacked;	/* Unpacked version of new.* record */
 	int iNewReg;		/* Register for new.* values */
@@ -458,7 +458,24 @@ u32 sqlite3VdbeSerialPut(unsigned char *, Mem *, u32);
 u32 sqlite3VdbeSerialGet(const unsigned char *, u32, Mem *);
 void sqlite3VdbeDeleteAuxData(sqlite3 *, AuxData **, int, int);
 
-int sqlite3VdbeIdxKeyCompare(sqlite3 *, VdbeCursor *, UnpackedRecord *, int *);
+/**
+ * Compare the key of the index entry that cursor vdbe_cursor is
+ * pointing to against the key string in unpacked.  Write into
+ * *res a number that is negative, zero, or positive if
+ * vdbe_cursor is less than, equal to, or greater than unpacked.
+ * Return SQLITE_OK on success.
+ *
+ * @param vdbe_cursor Cursor, which points to tuple to compare.
+ * @param unpacked Unpacked version of key.
+ * @param[out] Write the comparison result here.
+ *
+ * @retval Error status code.
+ */
+int
+sqlite3VdbeIdxKeyCompare(struct VdbeCursor *vdbe_cursor,
+			 struct UnpackedRecord *unpacked,
+			 int * res);
+
 int sqlite3VdbeExec(Vdbe *);
 int sqlite3VdbeList(Vdbe *);
 int
@@ -500,13 +517,9 @@ int sqlite3VdbeMemClearAndResize(Mem * pMem, int n);
 int sqlite3VdbeCloseStatement(Vdbe *, int);
 void sqlite3VdbeFrameDelete(VdbeFrame *);
 int sqlite3VdbeFrameRestore(VdbeFrame *);
-#ifdef SQLITE_ENABLE_PREUPDATE_HOOK
-void sqlite3VdbePreUpdateHook(Vdbe *, VdbeCursor *, int, const char *, Table *,
-			      i64, int);
-#endif
 int sqlite3VdbeTransferError(Vdbe * p);
 
-int sqlite3VdbeSorterInit(sqlite3 *, int, VdbeCursor *);
+int sqlite3VdbeSorterInit(struct sqlite3 *db, struct VdbeCursor *cursor);
 void sqlite3VdbeSorterReset(sqlite3 *, VdbeSorter *);
 void sqlite3VdbeSorterClose(sqlite3 *, VdbeCursor *);
 int sqlite3VdbeSorterRowkey(const VdbeCursor *, Mem *);
@@ -543,10 +556,28 @@ int sqlite3VdbeMemExpandBlob(Mem *);
 
 i64 sqlite3VdbeMsgpackRecordLen(Mem * pMem, u32 n);
 u32 sqlite3VdbeMsgpackRecordPut(u8 * pBuf, Mem * pMem, u32 n);
-int sqlite3VdbeCompareMsgpack(const char **pKey1,
-			      UnpackedRecord * pUnpacked, int iKey2);
-int sqlite3VdbeRecordCompareMsgpack(int nKey1, const void *pKey1,
-				    UnpackedRecord * pPKey2);
+/**
+ * Perform comparison of two keys: one is packed and one is not.
+ *
+ * @param key1 Pointer to pointer to first key.
+ * @param unpacked Pointer to unpacked tuple.
+ * @param key2_idx index of key in umpacked record to compare.
+ *
+ * @retval +1 if key1 > pUnpacked[iKey2], -1 ptherwise.
+ */
+int sqlite3VdbeCompareMsgpack(const char **key1,
+			      struct UnpackedRecord *unpacked, int key2_idx);
+
+/**
+ * Perform comparison of two tuples: unpacked (key1) and packed (key2)
+ *
+ * @param key1 Packed key.
+ * @param unpacked Unpacked key.
+ *
+ * @retval +1 if key1 > unpacked, -1 otherwise.
+ */
+int sqlite3VdbeRecordCompareMsgpack(const void *key1,
+				    struct UnpackedRecord *key2);
 u32 sqlite3VdbeMsgpackGet(const unsigned char *buf, Mem * pMem);
 
 #endif				/* !defined(SQLITE_VDBEINT_H) */
diff --git a/src/box/sql/vdbeapi.c b/src/box/sql/vdbeapi.c
index 32af463..d35338a 100644
--- a/src/box/sql/vdbeapi.c
+++ b/src/box/sql/vdbeapi.c
@@ -1577,103 +1577,6 @@ sqlite3_expanded_sql(sqlite3_stmt * pStmt)
 #endif
 }
 
-#ifdef SQLITE_ENABLE_PREUPDATE_HOOK
-/*
- * Allocate and populate an UnpackedRecord structure based on the serialized
- * record in nKey/pKey. Return a pointer to the new UnpackedRecord structure
- * if successful, or a NULL pointer if an OOM error is encountered.
- */
-static UnpackedRecord *
-vdbeUnpackRecord(KeyInfo * pKeyInfo, int nKey, const void *pKey)
-{
-	UnpackedRecord *pRet;	/* Return value */
-
-	pRet = sqlite3VdbeAllocUnpackedRecord(pKeyInfo);
-	if (pRet) {
-		memset(pRet->aMem, 0, sizeof(Mem) * (pKeyInfo->nField + 1));
-		sqlite3VdbeRecordUnpack(pKeyInfo, nKey, pKey, pRet);
-	}
-	return pRet;
-}
-
-/*
- * This function is called from within a pre-update callback to retrieve
- * a field of the row currently being updated or deleted.
- */
-int
-sqlite3_preupdate_old(sqlite3 * db, int iIdx, sqlite3_value ** ppValue)
-{
-	PreUpdate *p = db->pPreUpdate;
-	int rc = SQLITE_OK;
-
-	/* Test that this call is being made from within an SQLITE_DELETE or
-	 * SQLITE_UPDATE pre-update callback, and that iIdx is within range.
-	 */
-	if (!p || p->op == SQLITE_INSERT) {
-		rc = SQLITE_MISUSE_BKPT;
-		goto preupdate_old_out;
-	}
-	if (iIdx >= p->pCsr->nField || iIdx < 0) {
-		rc = SQLITE_RANGE;
-		goto preupdate_old_out;
-	}
-
-	/* If the old.* record has not yet been loaded into memory, do so now. */
-	if (p->pUnpacked == 0) {
-		u32 nRec;
-		u8 *aRec;
-
-		nRec = sqlite3BtreePayloadSize(p->pCsr->uc.pCursor);
-		aRec = sqlite3DbMallocRaw(db, nRec);
-		if (!aRec)
-			goto preupdate_old_out;
-		rc = sqlite3BtreePayload(p->pCsr->uc.pCursor, 0, nRec, aRec);
-		if (rc == SQLITE_OK) {
-			p->pUnpacked =
-			    vdbeUnpackRecord(&p->keyinfo, nRec, aRec);
-			if (!p->pUnpacked)
-				rc = SQLITE_NOMEM;
-		}
-		if (rc != SQLITE_OK) {
-			sqlite3DbFree(db, aRec);
-			goto preupdate_old_out;
-		}
-		p->aRecord = aRec;
-	}
-
-	if (iIdx >= p->pUnpacked->nField) {
-		*ppValue = (sqlite3_value *) columnNullValue();
-	} else {
-		Mem *pMem = *ppValue = &p->pUnpacked->aMem[iIdx];
-		*ppValue = &p->pUnpacked->aMem[iIdx];
-		if (iIdx == p->pTab->iPKey) {
-			sqlite3VdbeMemSetInt64(pMem, p->iKey1);
-		} else if (p->pTab->aCol[iIdx].affinity == SQLITE_AFF_REAL) {
-			if (pMem->flags & MEM_Int) {
-				sqlite3VdbeMemRealify(pMem);
-			}
-		}
-	}
-
- preupdate_old_out:
-	sqlite3Error(db, rc);
-	return sqlite3ApiExit(db, rc);
-}
-#endif				/* SQLITE_ENABLE_PREUPDATE_HOOK */
-
-#ifdef SQLITE_ENABLE_PREUPDATE_HOOK
-/*
- * This function is called from within a pre-update callback to retrieve
- * the number of columns in the row being updated, deleted or inserted.
- */
-int
-sqlite3_preupdate_count(sqlite3 * db)
-{
-	PreUpdate *p = db->pPreUpdate;
-	return (p ? p->keyinfo.nField : 0);
-}
-#endif				/* SQLITE_ENABLE_PREUPDATE_HOOK */
-
 #ifdef SQLITE_ENABLE_PREUPDATE_HOOK
 /*
  * This function is designed to be called from within a pre-update callback
@@ -1694,92 +1597,6 @@ sqlite3_preupdate_depth(sqlite3 * db)
 }
 #endif				/* SQLITE_ENABLE_PREUPDATE_HOOK */
 
-#ifdef SQLITE_ENABLE_PREUPDATE_HOOK
-/*
- * This function is called from within a pre-update callback to retrieve
- * a field of the row currently being updated or inserted.
- */
-int
-sqlite3_preupdate_new(sqlite3 * db, int iIdx, sqlite3_value ** ppValue)
-{
-	PreUpdate *p = db->pPreUpdate;
-	int rc = SQLITE_OK;
-	Mem *pMem;
-
-	if (!p || p->op == SQLITE_DELETE) {
-		rc = SQLITE_MISUSE_BKPT;
-		goto preupdate_new_out;
-	}
-	if (iIdx >= p->pCsr->nField || iIdx < 0) {
-		rc = SQLITE_RANGE;
-		goto preupdate_new_out;
-	}
-
-	if (p->op == SQLITE_INSERT) {
-		/* For an INSERT, memory cell p->iNewReg contains the serialized record
-		 * that is being inserted. Deserialize it.
-		 */
-		UnpackedRecord *pUnpack = p->pNewUnpacked;
-		if (!pUnpack) {
-			Mem *pData = &p->v->aMem[p->iNewReg];
-			rc = ExpandBlob(pData);
-			if (rc != SQLITE_OK)
-				goto preupdate_new_out;
-			pUnpack =
-			    vdbeUnpackRecord(&p->keyinfo, pData->n, pData->z);
-			if (!pUnpack) {
-				rc = SQLITE_NOMEM;
-				goto preupdate_new_out;
-			}
-			p->pNewUnpacked = pUnpack;
-		}
-		if (iIdx >= pUnpack->nField) {
-			pMem = (sqlite3_value *) columnNullValue();
-		} else {
-			pMem = &pUnpack->aMem[iIdx];
-			if (iIdx == p->pTab->iPKey) {
-				sqlite3VdbeMemSetInt64(pMem, p->iKey2);
-			}
-		}
-	} else {
-		/* For an UPDATE, memory cell (p->iNewReg+1+iIdx) contains the required
-		 * value. Make a copy of the cell contents and return a pointer to it.
-		 * It is not safe to return a pointer to the memory cell itself as the
-		 * caller may modify the value text encoding.
-		 */
-		assert(p->op == SQLITE_UPDATE);
-		if (!p->aNew) {
-			p->aNew =
-			    (Mem *) sqlite3DbMallocZero(db,
-							sizeof(Mem) *
-							p->pCsr->nField);
-			if (!p->aNew) {
-				rc = SQLITE_NOMEM;
-				goto preupdate_new_out;
-			}
-		}
-		assert(iIdx >= 0 && iIdx < p->pCsr->nField);
-		pMem = &p->aNew[iIdx];
-		if (pMem->flags == 0) {
-			if (iIdx == p->pTab->iPKey) {
-				sqlite3VdbeMemSetInt64(pMem, p->iKey2);
-			} else {
-				rc = sqlite3VdbeMemCopy(pMem,
-							&p->v->aMem[p->iNewReg +
-								    1 + iIdx]);
-				if (rc != SQLITE_OK)
-					goto preupdate_new_out;
-			}
-		}
-	}
-	*ppValue = pMem;
-
- preupdate_new_out:
-	sqlite3Error(db, rc);
-	return sqlite3ApiExit(db, rc);
-}
-#endif				/* SQLITE_ENABLE_PREUPDATE_HOOK */
-
 #ifdef SQLITE_ENABLE_STMT_SCANSTATUS
 /*
  * Return status data for a single loop within query pStmt.
diff --git a/src/box/sql/vdbeaux.c b/src/box/sql/vdbeaux.c
index ead9659..1fb609b 100644
--- a/src/box/sql/vdbeaux.c
+++ b/src/box/sql/vdbeaux.c
@@ -978,11 +978,9 @@ freeP4(sqlite3 * db, int p4type, void *p4)
 			sqlite3DbFree(db, p4);
 			break;
 		}
-	case P4_KEYINFO:{
-			if (db->pnBytesFreed == 0)
-				sqlite3KeyInfoUnref((KeyInfo *) p4);
-			break;
-		}
+	case P4_KEYDEF:
+		key_def_delete(p4);
+		break;
 #ifdef SQLITE_ENABLE_CURSOR_HINTS
 	case P4_EXPR:{
 			sqlite3ExprDelete(db, (Expr *) p4);
@@ -1170,20 +1168,19 @@ sqlite3VdbeAppendP4(Vdbe * p, void *pP4, int n)
 	}
 }
 
-/*
- * Set the P4 on the most recently added opcode to the KeyInfo for the
- * index given.
- */
 void
-sqlite3VdbeSetP4KeyInfo(Parse * pParse, Index * pIdx)
-{
-	Vdbe *v = pParse->pVdbe;
-	KeyInfo *pKeyInfo;
-	assert(v != 0);
-	assert(pIdx != 0);
-	pKeyInfo = sqlite3KeyInfoOfIndex(pParse, pParse->db, pIdx);
-	if (pKeyInfo)
-		sqlite3VdbeAppendP4(v, pKeyInfo, P4_KEYINFO);
+sql_vdbe_set_p4_key_def(struct Parse *parse, struct Index *idx)
+{
+	struct Vdbe *v = parse->pVdbe;
+	assert(v != NULL);
+	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 *index = index_find(space, index_id);
+	assert(index != NULL);
+	sqlite3VdbeAppendP4(v, key_def_dup(index->def->key_def), P4_KEYDEF);
 }
 
 #ifdef SQLITE_ENABLE_EXPLAIN_COMMENTS
@@ -1542,26 +1539,28 @@ displayP4(Op * pOp, char *zTemp, int nTemp)
 	assert(nTemp >= 20);
 	sqlite3StrAccumInit(&x, 0, zTemp, nTemp, 0);
 	switch (pOp->p4type) {
-	case P4_KEYINFO:{
-			int j;
-			KeyInfo *pKeyInfo;
+	case P4_KEYDEF:{
+			struct key_def *def;
 
-			if (pOp->p4.pKeyInfo == NULL) {
+			if (pOp->p4.key_def == NULL) {
 				sqlite3XPrintf(&x, "k[NULL]");
 			} else {
-				pKeyInfo = pOp->p4.pKeyInfo;
-				assert(pKeyInfo->aSortOrder != 0);
-				sqlite3XPrintf(&x, "k(%d", pKeyInfo->nField);
-				for (j = 0; j < pKeyInfo->nField; j++) {
-					struct coll *pColl = pKeyInfo->aColl[j];
-					const char *zColl =
-					    pColl ? pColl->name : "";
-					if (strcmp(zColl, "BINARY") == 0)
-						zColl = "B";
+				def = pOp->p4.key_def;
+				sqlite3XPrintf(&x, "k(%d", def->part_count);
+				for (int j = 0; j < (int)def->part_count; j++) {
+					struct coll *coll = def->parts[j].coll;
+					const char *coll_str =
+					    coll != NULL ? coll->name : "";
+					if (strcmp(coll_str, "BINARY") == 0)
+						coll_str = "B";
+					const char *sort_order = "";
+					if (def->parts[j].sort_order ==
+					    SORT_ORDER_DESC) {
+						sort_order = "-";
+					}
 					sqlite3XPrintf(&x, ",%s%s",
-						       pKeyInfo->
-						       aSortOrder[j] ? "-" : "",
-						       zColl);
+						       sort_order,
+						       coll_str);
 				}
 				sqlite3StrAccumAppend(&x, ")", 1);
 			}
@@ -3506,7 +3505,7 @@ sqlite3VdbeSerialGet(const unsigned char *buf,	/* Buffer to deserialize from */
 /*
  * This routine is used to allocate sufficient space for an UnpackedRecord
  * structure large enough to be used with sqlite3VdbeRecordUnpack() if
- * the first argument is a pointer to KeyInfo structure pKeyInfo.
+ * the first argument is a pointer to key_def structure.
  *
  * The space is either allocated using sqlite3DbMallocRaw() or from within
  * the unaligned buffer passed via the second and third arguments (presumably
@@ -3518,20 +3517,19 @@ sqlite3VdbeSerialGet(const unsigned char *buf,	/* Buffer to deserialize from */
  * If an OOM error occurs, NULL is returned.
  */
 UnpackedRecord *
-sqlite3VdbeAllocUnpackedRecord(KeyInfo * pKeyInfo)
+sqlite3VdbeAllocUnpackedRecord(struct sqlite3 *db, struct key_def *key_def)
 {
 	UnpackedRecord *p;	/* Unpacked record to return */
 	int nByte;		/* Number of bytes required for *p */
 	nByte =
-	    ROUND8(sizeof(UnpackedRecord)) + sizeof(Mem) * (pKeyInfo->nField +
+	    ROUND8(sizeof(UnpackedRecord)) + sizeof(Mem) * (key_def->part_count +
 							    1);
-	p = (UnpackedRecord *) sqlite3DbMallocRaw(pKeyInfo->db, nByte);
+	p = (UnpackedRecord *) sqlite3DbMallocRaw(db, nByte);
 	if (!p)
 		return 0;
 	p->aMem = (Mem *) & ((char *)p)[ROUND8(sizeof(UnpackedRecord))];
-	assert(pKeyInfo->aSortOrder != 0);
-	p->pKeyInfo = pKeyInfo;
-	p->nField = pKeyInfo->nField + 1;
+	p->key_def = key_def;
+	p->nField = key_def->part_count + 1;
 	return p;
 }
 
@@ -3560,7 +3558,8 @@ sql_vdbe_mem_alloc_region(Mem *vdbe_mem, uint32_t size)
  * Return false if there is a disagreement.
  */
 static int
-vdbeRecordCompareDebug(int nKey1, const void *pKey1,	/* Left key */
+vdbeRecordCompareDebug(struct sqlite3 *db,
+		       int nKey1, const void *pKey1,	/* Left key */
 		       const UnpackedRecord * pPKey2,	/* Right key */
 		       int desiredResult)		/* Correct answer */
 {
@@ -3570,13 +3569,11 @@ vdbeRecordCompareDebug(int nKey1, const void *pKey1,	/* Left key */
 	int i = 0;
 	int rc = 0;
 	const unsigned char *aKey1 = (const unsigned char *)pKey1;
-	KeyInfo *pKeyInfo;
+	struct key_def *key_def;
 	Mem mem1;
 
-	pKeyInfo = pPKey2->pKeyInfo;
-	if (pKeyInfo->db == 0)
-		return 1;
-	mem1.db = pKeyInfo->db;
+	key_def = pPKey2->key_def;
+	mem1.db = db;
 	/* mem1.flags = 0;  // Will be initialized by sqlite3VdbeSerialGet() */
 	VVA_ONLY(mem1.szMalloc = 0;
 	    )
@@ -3594,10 +3591,7 @@ vdbeRecordCompareDebug(int nKey1, const void *pKey1,	/* Left key */
 	if (szHdr1 > 98307)
 		return SQLITE_CORRUPT;
 	d1 = szHdr1;
-	assert(pKeyInfo->nField + pKeyInfo->nXField >= pPKey2->nField
-	       || CORRUPT_DB);
-	assert(pKeyInfo->aSortOrder != 0);
-	assert(pKeyInfo->nField > 0);
+	assert(key_def->part_count > 0);
 	assert(idx1 <= szHdr1 || CORRUPT_DB);
 	do {
 		u32 serial_type1;
@@ -3624,10 +3618,10 @@ vdbeRecordCompareDebug(int nKey1, const void *pKey1,	/* Left key */
 		/* Do the comparison
 		 */
 		rc = sqlite3MemCompare(&mem1, &pPKey2->aMem[i],
-				       pKeyInfo->aColl[i]);
+				       key_def->parts[i].coll);
 		if (rc != 0) {
 			assert(mem1.szMalloc == 0);	/* See comment below */
-			if (pKeyInfo->aSortOrder[i]) {
+			if (key_def->parts[i].sort_order != SORT_ORDER_ASC) {
 				rc = -rc;	/* Invert the result for DESC sort order. */
 			}
 			goto debugCompareEnd;
@@ -3656,7 +3650,7 @@ vdbeRecordCompareDebug(int nKey1, const void *pKey1,	/* Left key */
 		return 1;
 	if (CORRUPT_DB)
 		return 1;
-	if (pKeyInfo->db->mallocFailed)
+	if (db->mallocFailed)
 		return 1;
 	return 0;
 }
@@ -3932,12 +3926,13 @@ vdbeRecordDecodeInt(u32 serial_type, const u8 * aKey)
  * If database corruption is discovered, set pPKey2->errCode to
  * SQLITE_CORRUPT and return 0. If an OOM error is encountered,
  * pPKey2->errCode is set to SQLITE_NOMEM and, if it is not NULL, the
- * malloc-failed flag set on database handle (pPKey2->pKeyInfo->db).
+ * malloc-failed flag set on database handle.
  */
 int
-sqlite3VdbeRecordCompareWithSkip(int nKey1, const void *pKey1,	/* Left key */
+sqlite3VdbeRecordCompareWithSkip(struct sqlite3 *db,
+				 int nKey1, const void *pKey1,	/* Left key */
 				 UnpackedRecord * pPKey2,	/* Right key */
-				 int bSkip)			/* If true, skip the first field */
+				 bool bSkip)			/* If true, skip the first field */
 {
 	u32 d1;			/* Offset into aKey[] of next data element */
 	int i;			/* Index of next field to compare */
@@ -3945,7 +3940,7 @@ sqlite3VdbeRecordCompareWithSkip(int nKey1, const void *pKey1,	/* Left key */
 	u32 idx1;		/* Offset of first type in header */
 	int rc = 0;		/* Return value */
 	Mem *pRhs = pPKey2->aMem;	/* Next field of pPKey2 to compare */
-	KeyInfo *pKeyInfo = pPKey2->pKeyInfo;
+	struct key_def *key_def = pPKey2->key_def;
 	const unsigned char *aKey1 = (const unsigned char *)pKey1;
 	Mem mem1;
 
@@ -3972,10 +3967,7 @@ sqlite3VdbeRecordCompareWithSkip(int nKey1, const void *pKey1,	/* Left key */
 
 	VVA_ONLY(mem1.szMalloc = 0;
 	    )			/* Only needed by assert() statements */
-	    assert(pPKey2->pKeyInfo->nField + pPKey2->pKeyInfo->nXField >=
-		   pPKey2->nField || CORRUPT_DB);
-	assert(pPKey2->pKeyInfo->aSortOrder != 0);
-	assert(pPKey2->pKeyInfo->nField > 0);
+	assert(pPKey2->key_def->part_count > 0);
 	assert(idx1 <= szHdr1 || CORRUPT_DB);
 	do {
 		u32 serial_type;
@@ -4050,13 +4042,14 @@ sqlite3VdbeRecordCompareWithSkip(int nKey1, const void *pKey1,	/* Left key */
 					pPKey2->errCode =
 					    (u8) SQLITE_CORRUPT_BKPT;
 					return 0;	/* Corruption */
-				} else if (pKeyInfo->aColl[i]) {
-					mem1.db = pKeyInfo->db;
+				} else if (key_def->parts[i].coll !=NULL) {
+					mem1.db = db;
 					mem1.flags = MEM_Str;
 					mem1.z = (char *)&aKey1[d1];
+					struct coll* coll;
+					coll = key_def->parts[i].coll;
 					rc = vdbeCompareMemString(&mem1, pRhs,
-								  pKeyInfo->
-								  aColl[i],
+								  coll,
 								  &pPKey2->
 								  errCode);
 				} else {
@@ -4106,11 +4099,10 @@ sqlite3VdbeRecordCompareWithSkip(int nKey1, const void *pKey1,	/* Left key */
 		}
 
 		if (rc != 0) {
-			if (pKeyInfo->aSortOrder[i]) {
+			if (key_def->parts[i].sort_order != SORT_ORDER_ASC)
 				rc = -rc;
-			}
 			assert(vdbeRecordCompareDebug
-			       (nKey1, pKey1, pPKey2, rc));
+			       (db, nKey1, pKey1, pPKey2, rc));
 			assert(mem1.szMalloc == 0);	/* See comment below */
 			return rc;
 		}
@@ -4133,18 +4125,19 @@ sqlite3VdbeRecordCompareWithSkip(int nKey1, const void *pKey1,	/* Left key */
 	 * value.
 	 */
 	assert(CORRUPT_DB
-	       || vdbeRecordCompareDebug(nKey1, pKey1, pPKey2,
+	       || vdbeRecordCompareDebug(db, nKey1, pKey1, pPKey2,
 					 pPKey2->default_rc)
-	       || pKeyInfo->db->mallocFailed);
+	       || db->mallocFailed);
 	pPKey2->eqSeen = 1;
 	return pPKey2->default_rc;
 }
 
 int
-sqlite3VdbeRecordCompare(int nKey1, const void *pKey1,	/* Left key */
-			 UnpackedRecord * pPKey2)	/* Right key */
+sqlite3VdbeRecordCompare(struct sqlite3 *db,
+			 int key_count, const void *key1,	/* Left key */
+			 UnpackedRecord *key2)	/* Right key */
 {
-	return sqlite3VdbeRecordCompareWithSkip(nKey1, pKey1, pPKey2, 0);
+	return sqlite3VdbeRecordCompareWithSkip(db, key_count, key1, key2, false);
 }
 
 /*
@@ -4159,27 +4152,19 @@ sqlite3VdbeFindCompare(UnpackedRecord * p)
 	return sqlite3VdbeRecordCompareMsgpack;
 }
 
-/*
- * Compare the key of the index entry that cursor pC is pointing to against
- * the key string in pUnpacked.  Write into *pRes a number
- * that is negative, zero, or positive if pC is less than, equal to,
- * or greater than pUnpacked.  Return SQLITE_OK on success.
- */
 int
-sqlite3VdbeIdxKeyCompare(sqlite3 * db,			/* Database connection */
-			 VdbeCursor * pC,		/* The cursor to compare against */
-			 UnpackedRecord * pUnpacked,	/* Unpacked version of key */
-			 int *res)			/* Write the comparison result here */
-{
-	(void)db;
-	BtCursor *pCur;
-
-	assert(pC->eCurType == CURTYPE_TARANTOOL);
-	pCur = pC->uc.pCursor;
-	assert(sqlite3CursorIsValid(pCur));
-	if (pCur->curFlags & BTCF_TaCursor ||
-	    pCur->curFlags & BTCF_TEphemCursor) {
-		return tarantoolSqlite3IdxKeyCompare(pCur, pUnpacked, res);
+sqlite3VdbeIdxKeyCompare(struct VdbeCursor *vdbe_cursor,
+			 struct UnpackedRecord *unpacked,
+			 int *res)
+{
+	struct BtCursor *cursor;
+
+	assert(vdbe_cursor->eCurType == CURTYPE_TARANTOOL);
+	cursor = vdbe_cursor->uc.pCursor;
+	assert(sqlite3CursorIsValid(cursor));
+	if (cursor->curFlags & BTCF_TaCursor ||
+	    cursor->curFlags & BTCF_TEphemCursor) {
+		return tarantoolSqlite3IdxKeyCompare(cursor, unpacked, res);
 	}
 	unreachable();
 	return SQLITE_OK;
@@ -4301,68 +4286,6 @@ vdbeFreeUnpacked(sqlite3 * db, UnpackedRecord * p)
 }
 #endif				/* SQLITE_ENABLE_PREUPDATE_HOOK */
 
-#ifdef SQLITE_ENABLE_PREUPDATE_HOOK
-/*
- * Invoke the pre-update hook. If this is an UPDATE or DELETE pre-update call,
- * then cursor passed as the second argument should point to the row about
- * to be update or deleted. If the application calls sqlite3_preupdate_old(),
- * the required value will be read from the row the cursor points to.
- */
-void
-sqlite3VdbePreUpdateHook(Vdbe * v,		/* Vdbe pre-update hook is invoked by */
-			 VdbeCursor * pCsr,	/* Cursor to grab old.* values from */
-			 int op,		/* SQLITE_INSERT, UPDATE or DELETE */
-			 Table * pTab,		/* Modified table */
-			 i64 iKey1,		/* Initial key value */
-			 int iReg)		/* Register for new.* record */
-{
-	sqlite3 *db = v->db;
-	i64 iKey2;
-	PreUpdate preupdate;
-	const char *zTbl = pTab->zName;
-	static const u8 fakeSortOrder = 0;
-
-	assert(db->pPreUpdate == 0);
-	memset(&preupdate, 0, sizeof(PreUpdate));
-	if (op == SQLITE_UPDATE) {
-		iKey2 = v->aMem[iReg].u.i;
-	} else {
-		iKey2 = iKey1;
-	}
-
-	assert(pCsr->nField == pTab->nCol
-	       || (pCsr->nField == pTab->nCol + 1 && op == SQLITE_DELETE
-		   && iReg == -1)
-	    );
-
-	preupdate.v = v;
-	preupdate.pCsr = pCsr;
-	preupdate.op = op;
-	preupdate.iNewReg = iReg;
-	preupdate.keyinfo.db = db;
-	preupdate.keyinfo.nField = pTab->nCol;
-	preupdate.keyinfo.aSortOrder = (u8 *) & fakeSortOrder;
-	preupdate.iKey1 = iKey1;
-	preupdate.iKey2 = iKey2;
-	preupdate.pTab = pTab;
-
-	db->pPreUpdate = &preupdate;
-	db->xPreUpdateCallback(db->pPreUpdateArg, db, op, zTbl, iKey1,
-			       iKey2);
-	db->pPreUpdate = 0;
-	sqlite3DbFree(db, preupdate.aRecord);
-	vdbeFreeUnpacked(db, preupdate.pUnpacked);
-	vdbeFreeUnpacked(db, preupdate.pNewUnpacked);
-	if (preupdate.aNew) {
-		int i;
-		for (i = 0; i < pCsr->nField; i++) {
-			sqlite3VdbeMemRelease(&preupdate.aNew[i]);
-		}
-		sqlite3DbFree(db, preupdate.aNew);
-	}
-}
-#endif				/* SQLITE_ENABLE_PREUPDATE_HOOK */
-
 i64
 sqlite3VdbeMsgpackRecordLen(Mem * pRec, u32 n)
 {
@@ -4435,11 +4358,11 @@ sqlite3VdbeMsgpackRecordPut(u8 * pBuf, Mem * pRec, u32 n)
 }
 
 int
-sqlite3VdbeCompareMsgpack(const char **pKey1,
-			  UnpackedRecord * pUnpacked, int iKey2)
+sqlite3VdbeCompareMsgpack(const char **key1,
+			  struct UnpackedRecord *unpacked, int key2_idx)
 {
-	const char *aKey1 = *pKey1;
-	Mem *pKey2 = pUnpacked->aMem + iKey2;
+	const char *aKey1 = *key1;
+	Mem *pKey2 = unpacked->aMem + key2_idx;
 	Mem mem1;
 	int rc = 0;
 	switch (mp_typeof(*aKey1)) {
@@ -4508,17 +4431,17 @@ sqlite3VdbeCompareMsgpack(const char **pKey1,
 		}
 	case MP_STR:{
 			if (pKey2->flags & MEM_Str) {
-				KeyInfo *pKeyInfo = pUnpacked->pKeyInfo;
+				struct key_def *key_def = unpacked->key_def;
 				mem1.n = mp_decode_strl(&aKey1);
 				mem1.z = (char *)aKey1;
 				aKey1 += mem1.n;
-				if (pKeyInfo->aColl[iKey2]) {
-					mem1.db = pKeyInfo->db;
+				struct coll *coll;
+				coll = key_def->parts[key2_idx].coll;
+				if (coll != NULL) {
 					mem1.flags = MEM_Str;
 					rc = vdbeCompareMemString(&mem1, pKey2,
-								  pKeyInfo->
-								  aColl[iKey2],
-								  &pUnpacked->
+								  coll,
+								  &unpacked->
 								  errCode);
 				} else {
 					goto do_bin_cmp;
@@ -4563,34 +4486,32 @@ sqlite3VdbeCompareMsgpack(const char **pKey1,
 			goto do_blob;
 		}
 	}
-	*pKey1 = aKey1;
+	*key1 = aKey1;
 	return rc;
 }
 
 int
-sqlite3VdbeRecordCompareMsgpack(int nKey1, const void *pKey1,	/* Left key */
-				UnpackedRecord * pPKey2)	/* Right key */
+sqlite3VdbeRecordCompareMsgpack(const void *key1,
+				struct UnpackedRecord *key2)
 {
-	(void)nKey1;		/* assume valid data */
-
-	int rc = 0;		/* Return value */
-	const char *aKey1 = (const char *)pKey1;
-	u32 i, n = mp_decode_array(&aKey1);
+	int rc = 0;
+	u32 i, n = mp_decode_array((const char**)&key1);
 
-	n = MIN(n, pPKey2->nField);
+	n = MIN(n, key2->nField);
 
 	for (i = 0; i != n; i++) {
-		rc = sqlite3VdbeCompareMsgpack(&aKey1, pPKey2, i);
+		rc = sqlite3VdbeCompareMsgpack((const char**)&key1, key2, i);
 		if (rc != 0) {
-			if (pPKey2->pKeyInfo->aSortOrder[i]) {
+			if (key2->key_def->parts[i].sort_order !=
+			    SORT_ORDER_ASC) {
 				rc = -rc;
 			}
 			return rc;
 		}
 	}
 
-	pPKey2->eqSeen = 1;
-	return pPKey2->default_rc;
+	key2->eqSeen = 1;
+	return key2->default_rc;
 }
 
 u32
@@ -4669,21 +4590,18 @@ sqlite3VdbeMsgpackGet(const unsigned char *buf,	/* Buffer to deserialize from */
 }
 
 void
-sqlite3VdbeRecordUnpackMsgpack(KeyInfo * pKeyInfo,	/* Information about the record format */
-			       int nKey,		/* Size of the binary record */
+sqlite3VdbeRecordUnpackMsgpack(struct key_def *key_def,	/* Information about the record format */
 			       const void *pKey,	/* The binary record */
 			       UnpackedRecord * p)	/* Populate this structure before returning. */
 {
 	uint32_t n;
 	const char *zParse = pKey;
-	(void)nKey;
 	Mem *pMem = p->aMem;
 	n = mp_decode_array(&zParse);
-	n = p->nField = MIN(n, pKeyInfo->nField);
+	n = p->nField = MIN(n, key_def->part_count);
 	p->default_rc = 0;
+	p->key_def = key_def;
 	while (n--) {
-		pMem->db = pKeyInfo->db;
-		/* pMem->flags = 0; // sqlite3VdbeSerialGet() will set this for us */
 		pMem->szMalloc = 0;
 		pMem->z = 0;
 		u32 sz = sqlite3VdbeMsgpackGet((u8 *) zParse, pMem);
diff --git a/src/box/sql/vdbemem.c b/src/box/sql/vdbemem.c
index 9dd254f..3c23991 100644
--- a/src/box/sql/vdbemem.c
+++ b/src/box/sql/vdbemem.c
@@ -1089,14 +1089,13 @@ valueNew(sqlite3 * db, struct ValueNewStat4Ctx *p)
 			int i;	/* Counter variable */
 			int nCol = index_column_count(pIdx);
 
-			nByte =
-			    sizeof(Mem) * nCol + ROUND8(sizeof(UnpackedRecord));
+			nByte = sizeof(Mem) * nCol +
+				ROUND8(sizeof(UnpackedRecord));
 			pRec =
 			    (UnpackedRecord *) sqlite3DbMallocZero(db, nByte);
-			if (pRec) {
-				pRec->pKeyInfo =
-				    sqlite3KeyInfoOfIndex(p->pParse, db, pIdx);
-				if (pRec->pKeyInfo) {
+			if (pRec != NULL) {
+				pRec->key_def = sql_index_key_def(pIdx, true);
+				if (pRec->key_def != NULL) {
 					pRec->aMem =
 					    (Mem *) ((u8 *) pRec +
 						     ROUND8(sizeof
@@ -1655,13 +1654,12 @@ sqlite3Stat4ProbeFree(UnpackedRecord * pRec)
 {
 	if (pRec) {
 		int i;
-		int nCol = pRec->pKeyInfo->nField;
+		int nCol = pRec->key_def->part_count;
 		Mem *aMem = pRec->aMem;
 		sqlite3 *db = aMem[0].db;
 		for (i = 0; i < nCol; i++) {
 			sqlite3VdbeMemRelease(&aMem[i]);
 		}
-		sqlite3KeyInfoUnref(pRec->pKeyInfo);
 		sqlite3DbFree(db, pRec);
 	}
 }
diff --git a/src/box/sql/vdbesort.c b/src/box/sql/vdbesort.c
index be3cc4c..7c45553 100644
--- a/src/box/sql/vdbesort.c
+++ b/src/box/sql/vdbesort.c
@@ -312,8 +312,8 @@ struct MergeEngine {
  * after the thread has finished are not dire. So we don't worry about
  * memory barriers and such here.
  */
-typedef int (*SorterCompare) (SortSubtask *, int *, const void *, int,
-			      const void *, int);
+typedef int (*SorterCompare) (SortSubtask *, bool *, const void *,
+			      const void *);
 struct SortSubtask {
 	SQLiteThread *pThread;	/* Background thread, if any */
 	int bDone;		/* Set if thread is finished but not joined */
@@ -343,7 +343,7 @@ struct VdbeSorter {
 	PmaReader *pReader;	/* Readr data from here after Rewind() */
 	MergeEngine *pMerger;	/* Or here, if bUseThreads==0 */
 	sqlite3 *db;		/* Database connection */
-	KeyInfo *pKeyInfo;	/* How to compare records */
+	struct key_def *key_def;
 	UnpackedRecord *pUnpacked;	/* Used by VdbeSorterCompare() */
 	SorterList list;	/* List of in-memory records */
 	int iMemory;		/* Offset of free space in list.aMemory */
@@ -794,10 +794,10 @@ vdbePmaReaderInit(SortSubtask * pTask,	/* Task context */
 	return rc;
 }
 
-/*
- * Compare key1 (buffer pKey1, size nKey1 bytes) with key2 (buffer pKey2,
- * size nKey2 bytes). Use (pTask->pKeyInfo) for the collation sequences
- * used by the comparison. Return the result of the comparison.
+/**
+ * Compare key1 with key2. Use (pTask->key_def) for the collation
+ * sequences used by the comparison. Return the result of the
+ * comparison.
  *
  * If IN/OUT parameter *pbKey2Cached is true when this function is called,
  * it is assumed that (pTask->pUnpacked) contains the unpacked version
@@ -806,27 +806,31 @@ vdbePmaReaderInit(SortSubtask * pTask,	/* Task context */
  *
  * If an OOM error is encountered, (pTask->pUnpacked->error_rc) is set
  * to SQLITE_NOMEM.
+ *
+ * @param task Subtask context (for key_def).
+ * @param key2_cached True if pTask->pUnpacked is key2.
+ * @param key1 Left side of comparison.
+ * @param key2 Right side of comparison.
+ *
+ * @retval +1 if key1 > key2, -1 otherwise.
  */
 static int
-vdbeSorterCompare(SortSubtask * pTask,	/* Subtask context (for pKeyInfo) */
-		  int *pbKey2Cached,	/* True if pTask->pUnpacked is pKey2 */
-		  const void *pKey1, int nKey1,	/* Left side of comparison */
-		  const void *pKey2, int nKey2	/* Right side of comparison */
-    )
+vdbeSorterCompare(struct SortSubtask *task, bool *key2_cached,
+		  const void *key1, const void *key2)
 {
-	UnpackedRecord *r2 = pTask->pUnpacked;
-	if (!*pbKey2Cached) {
-		sqlite3VdbeRecordUnpackMsgpack(pTask->pSorter->pKeyInfo, nKey2,
-					       pKey2, r2);
-		*pbKey2Cached = 1;
+	struct UnpackedRecord *r2 = task->pUnpacked;
+	if (!*key2_cached) {
+		sqlite3VdbeRecordUnpackMsgpack(task->pSorter->key_def,
+					       key2, r2);
+		*key2_cached = 1;
 	}
-	return sqlite3VdbeRecordCompareMsgpack(nKey1, pKey1, r2);
+	return sqlite3VdbeRecordCompareMsgpack(key1, r2);
 }
 
 /*
  * Initialize the temporary index cursor just opened as a sorter cursor.
  *
- * Usually, the sorter module uses the value of (pCsr->pKeyInfo->nField)
+ * Usually, the sorter module uses the value of (pCsr->key_def->part_count)
  * to determine the number of fields that should be compared from the
  * records being sorted. However, if the value passed as argument nField
  * is non-zero and the sorter is able to guarantee a stable sort, nField
@@ -844,16 +848,12 @@ vdbeSorterCompare(SortSubtask * pTask,	/* Subtask context (for pKeyInfo) */
  */
 int
 sqlite3VdbeSorterInit(sqlite3 * db,	/* Database connection (for malloc()) */
-		      int nField,	/* Number of key fields in each record */
 		      VdbeCursor * pCsr	/* Cursor that holds the new sorter */
     )
 {
 	int pgsz;		/* Page size of main database */
 	int i;			/* Used to iterate through aTask[] */
 	VdbeSorter *pSorter;	/* The new sorter */
-	KeyInfo *pKeyInfo;	/* Copy of pCsr->pKeyInfo with db==0 */
-	int szKeyInfo;		/* Size of pCsr->pKeyInfo in bytes */
-	int sz;			/* Size of pSorter in bytes */
 	int rc = SQLITE_OK;
 #if SQLITE_MAX_WORKER_THREADS==0
 #define nWorker 0
@@ -875,25 +875,15 @@ sqlite3VdbeSorterInit(sqlite3 * db,	/* Database connection (for malloc()) */
 	}
 #endif
 
-	assert(pCsr->pKeyInfo);
+	assert(pCsr->key_def != NULL);
 	assert(pCsr->eCurType == CURTYPE_SORTER);
-	szKeyInfo =
-	    sizeof(KeyInfo) + (pCsr->pKeyInfo->nField - 1) * sizeof(struct coll *);
-	sz = sizeof(VdbeSorter) + nWorker * sizeof(SortSubtask);
 
-	pSorter = (VdbeSorter *) sqlite3DbMallocZero(db, sz + szKeyInfo);
+	pSorter = (VdbeSorter *) sqlite3DbMallocZero(db, sizeof(VdbeSorter));
 	pCsr->uc.pSorter = pSorter;
 	if (pSorter == 0) {
 		rc = SQLITE_NOMEM_BKPT;
 	} else {
-		pSorter->pKeyInfo = pKeyInfo =
-		    (KeyInfo *) ((u8 *) pSorter + sz);
-		memcpy(pKeyInfo, pCsr->pKeyInfo, szKeyInfo);
-		pKeyInfo->db = 0;
-		if (nField && nWorker == 0) {
-			pKeyInfo->nXField += (pKeyInfo->nField - nField);
-			pKeyInfo->nField = nField;
-		}
+		pSorter->key_def = pCsr->key_def;
 		pSorter->pgsz = pgsz = 1024;
 		pSorter->nTask = nWorker + 1;
 		pSorter->iPrev = (u8) (nWorker - 1);
@@ -929,8 +919,8 @@ sqlite3VdbeSorterInit(sqlite3 * db,	/* Database connection (for malloc()) */
 			}
 		}
 
-		if ((pKeyInfo->nField + pKeyInfo->nXField) < 13
-		    && (pKeyInfo->aColl[0] == NULL)) {
+		if (pCsr->key_def->part_count < 13
+		    && (pCsr->key_def->parts[0].coll == NULL)) {
 			pSorter->typeMask =
 			    SORTER_TYPE_INTEGER | SORTER_TYPE_TEXT;
 		}
@@ -1280,10 +1270,11 @@ vdbeSortAllocUnpacked(SortSubtask * pTask)
 {
 	if (pTask->pUnpacked == 0) {
 		pTask->pUnpacked =
-		    sqlite3VdbeAllocUnpackedRecord(pTask->pSorter->pKeyInfo);
+			sqlite3VdbeAllocUnpackedRecord(pTask->pSorter->db,
+						       pTask->pSorter->key_def);
 		if (pTask->pUnpacked == 0)
 			return SQLITE_NOMEM_BKPT;
-		pTask->pUnpacked->nField = pTask->pSorter->pKeyInfo->nField;
+		pTask->pUnpacked->nField = pTask->pSorter->key_def->part_count;
 		pTask->pUnpacked->errCode = 0;
 	}
 	return SQLITE_OK;
@@ -1300,14 +1291,14 @@ vdbeSorterMerge(SortSubtask * pTask,	/* Calling thread context */
 {
 	SorterRecord *pFinal = 0;
 	SorterRecord **pp = &pFinal;
-	int bCached = 0;
+	bool bCached = false;
 
 	assert(p1 != 0 && p2 != 0);
 	for (;;) {
 		int res;
 		res =
-		    pTask->xCompare(pTask, &bCached, SRVAL(p1), p1->nVal,
-				    SRVAL(p2), p2->nVal);
+		    pTask->xCompare(pTask, &bCached, SRVAL(p1),
+				    SRVAL(p2));
 
 		if (res <= 0) {
 			*pp = p1;
@@ -1599,7 +1590,7 @@ vdbeMergeEngineStep(MergeEngine * pMerger,	/* The merge engine to advance to the
 		int i;		/* Index of aTree[] to recalculate */
 		PmaReader *pReadr1;	/* First PmaReader to compare */
 		PmaReader *pReadr2;	/* Second PmaReader to compare */
-		int bCached = 0;
+		bool bCached = false;
 
 		/* Find the first two PmaReaders to compare. The one that was just
 		 * advanced (iPrev) and the one next to it in the array.
@@ -1617,9 +1608,7 @@ vdbeMergeEngineStep(MergeEngine * pMerger,	/* The merge engine to advance to the
 			} else {
 				iRes = pTask->xCompare(pTask, &bCached,
 						       pReadr1->aKey,
-						       pReadr1->nKey,
-						       pReadr2->aKey,
-						       pReadr2->nKey);
+						       pReadr2->aKey);
 			}
 
 			/* If pReadr1 contained the smaller value, set aTree[i] to its index.
@@ -2075,12 +2064,11 @@ vdbeMergeEngineCompare(MergeEngine * pMerger,	/* Merge engine containing PmaRead
 		iRes = i1;
 	} else {
 		SortSubtask *pTask = pMerger->pTask;
-		int bCached = 0;
+		bool cached = false;
 		int res;
 		assert(pTask->pUnpacked != 0);	/* from vdbeSortSubtaskMain() */
 		res =
-		    pTask->xCompare(pTask, &bCached, p1->aKey, p1->nKey,
-				    p2->aKey, p2->nKey);
+		    pTask->xCompare(pTask, &cached, p1->aKey, p2->aKey);
 		if (res <= 0) {
 			iRes = i1;
 		} else {
@@ -2824,7 +2812,6 @@ sqlite3VdbeSorterCompare(const VdbeCursor * pCsr,	/* Sorter cursor */
 {
 	VdbeSorter *pSorter;
 	UnpackedRecord *r2;
-	KeyInfo *pKeyInfo;
 	int i;
 	void *pKey;
 	int nKey;		/* Sorter key to compare pVal with */
@@ -2832,10 +2819,9 @@ sqlite3VdbeSorterCompare(const VdbeCursor * pCsr,	/* Sorter cursor */
 	assert(pCsr->eCurType == CURTYPE_SORTER);
 	pSorter = pCsr->uc.pSorter;
 	r2 = pSorter->pUnpacked;
-	pKeyInfo = pCsr->pKeyInfo;
 	if (r2 == 0) {
 		r2 = pSorter->pUnpacked =
-		    sqlite3VdbeAllocUnpackedRecord(pKeyInfo);
+			sqlite3VdbeAllocUnpackedRecord(pSorter->db,  pCsr->key_def);
 		if (r2 == 0)
 			return SQLITE_NOMEM_BKPT;
 		r2->nField = nKeyCol;
@@ -2843,7 +2829,7 @@ sqlite3VdbeSorterCompare(const VdbeCursor * pCsr,	/* Sorter cursor */
 	assert(r2->nField == nKeyCol);
 
 	pKey = vdbeSorterRowkey(pSorter, &nKey);
-	sqlite3VdbeRecordUnpackMsgpack(pKeyInfo, nKey, pKey, r2);
+	sqlite3VdbeRecordUnpackMsgpack(pCsr->key_def, pKey, r2);
 	for (i = 0; i < nKeyCol; i++) {
 		if (r2->aMem[i].flags & MEM_Null) {
 			*pRes = -1;
@@ -2851,6 +2837,6 @@ sqlite3VdbeSorterCompare(const VdbeCursor * pCsr,	/* Sorter cursor */
 		}
 	}
 
-	*pRes = sqlite3VdbeRecordCompareMsgpack(pVal->n, pVal->z, r2);
+	*pRes = sqlite3VdbeRecordCompareMsgpack(pVal->z, r2);
 	return SQLITE_OK;
 }
diff --git a/src/box/sql/where.c b/src/box/sql/where.c
index bad964a..cde3815 100644
--- a/src/box/sql/where.c
+++ b/src/box/sql/where.c
@@ -812,7 +812,7 @@ constructAutomaticIndex(Parse * pParse,			/* The parsing context */
 	assert(pLevel->iIdxCur >= 0);
 	pLevel->iIdxCur = pParse->nTab++;
 	sqlite3VdbeAddOp2(v, OP_OpenAutoindex, pLevel->iIdxCur, nKeyCol + 1);
-	sqlite3VdbeSetP4KeyInfo(pParse, pIdx);
+	sql_vdbe_set_p4_key_def(pParse, pIdx);
 	VdbeComment((v, "for %s", pTable->zName));
 
 	/* Fill the automatic index with content */
@@ -971,9 +971,7 @@ whereKeyStats(Parse * pParse,	/* Database connection */
 		}
 
 		pRec->nField = n;
-		res =
-		    sqlite3VdbeRecordCompareMsgpack(aSample[iSamp].n,
-						    aSample[iSamp].p, pRec);
+		res = sqlite3VdbeRecordCompareMsgpack(aSample[iSamp].p, pRec);
 		if (res < 0) {
 			iLower =
 			    aSample[iSamp].anLt[n - 1] + aSample[iSamp].anEq[n -
@@ -1002,8 +1000,7 @@ whereKeyStats(Parse * pParse,	/* Database connection */
 			assert(iCol == nField - 1);
 			pRec->nField = nField;
 			assert(0 ==
-			       sqlite3VdbeRecordCompareMsgpack(aSample[i].n,
-							       aSample[i].p,
+			       sqlite3VdbeRecordCompareMsgpack(aSample[i].p,
 							       pRec)
 			       || pParse->db->mallocFailed);
 		} else {
@@ -1014,8 +1011,7 @@ whereKeyStats(Parse * pParse,	/* Database connection */
 			assert(i <= pIdx->nSample && i >= 0);
 			pRec->nField = iCol + 1;
 			assert(i == pIdx->nSample
-			       || sqlite3VdbeRecordCompareMsgpack(aSample[i].n,
-								  aSample[i].p,
+			       || sqlite3VdbeRecordCompareMsgpack(aSample[i].p,
 								  pRec) > 0
 			       || pParse->db->mallocFailed);
 
@@ -1027,14 +1023,14 @@ whereKeyStats(Parse * pParse,	/* Database connection */
 			if (iCol > 0) {
 				pRec->nField = iCol;
 				assert(sqlite3VdbeRecordCompareMsgpack
-				       (aSample[i].n, aSample[i].p, pRec) <= 0
+				       (aSample[i].p, pRec) <= 0
 				       || pParse->db->mallocFailed);
 			}
 			if (i > 0) {
 				pRec->nField = nField;
 				assert(sqlite3VdbeRecordCompareMsgpack
-				       (aSample[i - 1].n, aSample[i - 1].p,
-					pRec) < 0 || pParse->db->mallocFailed);
+				       (aSample[i - 1].p, pRec) < 0 ||
+				       pParse->db->mallocFailed);
 			}
 		}
 	}
@@ -3385,12 +3381,12 @@ wherePathSatisfiesOrderBy(WhereInfo * pWInfo,	/* The WHERE clause */
 					 */
 					if (revSet) {
 						if ((rev ^ revIdx) !=
-						    pOrderBy->a[i].sortOrder)
+						    pOrderBy->a[i].sort_order)
 							isMatch = 0;
 					} else {
 						rev =
 						    revIdx ^ pOrderBy->a[i].
-						    sortOrder;
+						    sort_order;
 						if (rev)
 							*pRevMask |=
 							    MASKBIT(iLoop);
@@ -4571,7 +4567,7 @@ sqlite3WhereBegin(Parse * pParse,	/* The parser context */
 			assert(iIndexCur >= 0);
 			if (op) {
 				emit_open_cursor(pParse, iIndexCur, pIx->tnum);
-				sqlite3VdbeSetP4KeyInfo(pParse, pIx);
+				sql_vdbe_set_p4_key_def(pParse, pIx);
 				if ((pLoop->wsFlags & WHERE_CONSTRAINT) != 0
 				    && (pLoop->
 					wsFlags & (WHERE_COLUMN_RANGE |
diff --git a/src/box/sql/wherecode.c b/src/box/sql/wherecode.c
index b467bbe..878425c 100644
--- a/src/box/sql/wherecode.c
+++ b/src/box/sql/wherecode.c
@@ -1618,7 +1618,7 @@ sqlite3WhereCodeOneLoopStart(WhereInfo * pWInfo,	/* Complete information about t
 			regRowset = pParse->nTab++;
 			sqlite3VdbeAddOp2(v, OP_OpenTEphemeral,
 					  regRowset, nPkCol);
-			sqlite3VdbeSetP4KeyInfo(pParse, pPk);
+			sql_vdbe_set_p4_key_def(pParse, pPk);
 			regPk = ++pParse->nMem;
 		}
 		iRetInit = sqlite3VdbeAddOp2(v, OP_Integer, 0, regReturn);
diff --git a/src/box/tuple.c b/src/box/tuple.c
index df2686e..7bcd9f8 100644
--- a/src/box/tuple.c
+++ b/src/box/tuple.c
@@ -474,3 +474,12 @@ tuple_str(const struct tuple *tuple)
 		return "<failed to format tuple>";
 	return buf;
 }
+
+const char *
+mp_str(const char *data)
+{
+	char *buf = tt_static_buf();
+	if (mp_snprint(buf, TT_STATIC_BUF_LEN, data) < 0)
+		return "<failed to format message pack>";
+	return buf;
+}
diff --git a/src/box/tuple.h b/src/box/tuple.h
index 97b81cf..9a459d9 100644
--- a/src/box/tuple.h
+++ b/src/box/tuple.h
@@ -395,6 +395,15 @@ tuple_snprint(char *buf, int size, const struct tuple *tuple);
 const char *
 tuple_str(const struct tuple *tuple);
 
+/**
+ * Format msgpack into string using a static buffer.
+ * Useful for debugger. Example: [1, 2, "string"]
+ * @param msgpack to format
+ * @return formatted null-terminated string
+ */
+const char *
+mp_str(const char *data);
+
 /**
  * Get the format of the tuple.
  * @param tuple Tuple.

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

* [tarantool-patches] Re: [PATCH 2/2] sql: replace KeyInfo with key_def
  2018-05-11 12:56         ` Kirill Yukhin
@ 2018-05-11 19:05           ` Vladislav Shpilevoy
  2018-05-14 11:40             ` Kirill Yukhin
  0 siblings, 1 reply; 17+ messages in thread
From: Vladislav Shpilevoy @ 2018-05-11 19:05 UTC (permalink / raw)
  To: tarantool-patches, Kirill Yukhin

Hello. Thanks for fixes. I pushed more on the branch. Please, look at
them, squash if you agree, and them the patchset is LGTM.

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

* [tarantool-patches] Re: [PATCH 2/2] sql: replace KeyInfo with key_def
  2018-05-11 19:05           ` Vladislav Shpilevoy
@ 2018-05-14 11:40             ` Kirill Yukhin
  0 siblings, 0 replies; 17+ messages in thread
From: Kirill Yukhin @ 2018-05-14 11:40 UTC (permalink / raw)
  To: Vladislav Shpilevoy; +Cc: tarantool-patches

Hi Vlad,
On 11 мая 22:05, Vladislav Shpilevoy wrote:
> Hello. Thanks for fixes. I pushed more on the branch. Please, look at
> them, squash if you agree, and them the patchset is LGTM.
Thanks a lot for your inputs and fixes!

I've checked the patch-set into 2.1 branch.

--
Regards, Kirill Yukhin

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

end of thread, other threads:[~2018-05-14 11:40 UTC | newest]

Thread overview: 17+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2018-04-13  8:05 [tarantool-patches] [PATCH] sql: use collation pointers instead of names Kirill Yukhin
2018-04-16 13:43 ` [tarantool-patches] " Vladislav Shpilevoy
2018-05-08  7:56 [tarantool-patches] [PATCH 0/2] sql: replace KeyInfo w/ key_def in SQL front-end Kirill Yukhin
2018-05-08  7:56 ` [tarantool-patches] [PATCH 1/2] sql: introduce sort order to key_part/key_part_def Kirill Yukhin
2018-05-08 16:02   ` [tarantool-patches] " Vladislav Shpilevoy
2018-05-10 13:01     ` Kirill Yukhin
2018-05-08  7:56 ` [tarantool-patches] [PATCH] sql: use collation pointers instead of names Kirill Yukhin
2018-04-17 18:06   ` [tarantool-patches] " Vladislav Shpilevoy
2018-04-18  5:42     ` Kirill Yukhin
2018-05-08  7:59   ` Kirill Yukhin
2018-05-08  7:56 ` [tarantool-patches] [PATCH 2/2] sql: replace KeyInfo with key_def Kirill Yukhin
2018-05-08 16:02   ` [tarantool-patches] " Vladislav Shpilevoy
2018-05-10 12:59     ` Kirill Yukhin
2018-05-11 11:22       ` Vladislav Shpilevoy
2018-05-11 12:56         ` Kirill Yukhin
2018-05-11 19:05           ` Vladislav Shpilevoy
2018-05-14 11:40             ` Kirill Yukhin

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