diff --git a/src/box/sql.c b/src/box/sql.c
index 433264abe610872bd34e250f807331f0b818665b..a6a7864f1cc64bedbccb53aeef06e4c3a04c7ed5 100644
--- a/src/box/sql.c
+++ b/src/box/sql.c
@@ -204,75 +204,20 @@ int tarantoolsqlPrevious(BtCursor *pCur, int *pRes)
 	return cursor_advance(pCur, pRes);
 }
 
-int tarantoolsqlMovetoUnpacked(BtCursor *pCur, UnpackedRecord *pIdxKey,
-				   int *pRes)
+int
+sql_cursor_seek(struct BtCursor *cur, struct Mem *mems, uint32_t len, int *res)
 {
 	struct region *region = &fiber()->gc;
 	size_t used = region_used(region);
-	uint32_t tuple_size;
-	const char *tuple =
-		sql_vdbe_mem_encode_tuple(pIdxKey->aMem, pIdxKey->nField,
-					  &tuple_size, region);
+	uint32_t size;
+	const char *tuple = sql_vdbe_mem_encode_tuple(mems, len, &size, region);
 	if (tuple == NULL)
 		return -1;
-	if (key_alloc(pCur, tuple_size) != 0)
+	if (key_alloc(cur, size) != 0)
 		return -1;
-	memcpy(pCur->key, tuple, tuple_size);
+	memcpy(cur->key, tuple, size);
 	region_truncate(region, used);
-
-	int rc, res_success;
-	switch (pIdxKey->opcode) {
-	default:
-	  /*  "Unexpected opcode" */
-		assert(0);
-	case 255:
-	/* Restore saved state. Just re-seek cursor.
-	   TODO: replace w/ named constant.  */
-		res_success = 0;
-		break;
-	case OP_SeekLT:
-		pCur->iter_type = ITER_LT;
-		res_success = -1; /* item<key */
-		break;
-	case OP_SeekLE:
-		pCur->iter_type = (pCur->hints & OPFLAG_SEEKEQ) != 0 ?
-				  ITER_REQ : ITER_LE;
-		res_success = 0; /* item==key */
-		break;
-	case OP_SeekGE:
-		pCur->iter_type = (pCur->hints & OPFLAG_SEEKEQ) != 0 ?
-				  ITER_EQ : ITER_GE;
-		res_success = 0; /* item==key */
-		break;
-	case OP_SeekGT:
-		pCur->iter_type = ITER_GT;
-		res_success = 1; /* item>key */
-		break;
-	case OP_NoConflict:
-	case OP_NotFound:
-	case OP_Found:
-	case OP_IdxDelete:
-		pCur->iter_type = ITER_EQ;
-		res_success = 0;
-		break;
-	}
-	rc = cursor_seek(pCur, pRes);
-	if (*pRes == 0) {
-		*pRes = res_success;
-		/*
-		 * To select the first item in a row of equal items
-		 * (last item), sql comparator is configured to
-		 * return +1 (-1) if an item equals the key making it
-		 * impossible to distinguish from an item>key (item<key)
-		 * from comparator output alone.
-		 * To make it possible to learn if the current item
-		 * equals the key, the comparator sets eqSeen.
-		 */
-		pIdxKey->eqSeen = 1;
-	} else {
-		*pRes = -1; /* -1 also means EOF */
-	}
-	return rc;
+	return cursor_seek(cur, res);
 }
 
 /*
diff --git a/src/box/sql/cursor.c b/src/box/sql/cursor.c
index bb2dae898f4423a87a4c2a26ac9a49c9be483987..694fd9a7fb9101ceaf9376e56c6fcf6114d6156f 100644
--- a/src/box/sql/cursor.c
+++ b/src/box/sql/cursor.c
@@ -132,20 +132,6 @@ sqlCursorPayload(BtCursor *pCur, u32 offset, u32 amt, void *pBuf)
  *                  is larger than pIdxKey.
  */
 
-int
-sqlCursorMovetoUnpacked(BtCursor * pCur,	/* The cursor to be moved */
-			   UnpackedRecord * pIdxKey,	/* Unpacked index key */
-			   int *pRes	/* Write search results here */
-    )
-{
-	assert(pRes);
-	assert(pIdxKey);
-	assert((pCur->curFlags & BTCF_TaCursor) ||
-	       (pCur->curFlags & BTCF_TEphemCursor));
-
-	return tarantoolsqlMovetoUnpacked(pCur, pIdxKey, pRes);
-}
-
 int
 sqlCursorNext(BtCursor *pCur, int *pRes)
 {
diff --git a/src/box/sql/cursor.h b/src/box/sql/cursor.h
index 88e544191c0d228329e3733afa2d13fe7e1aa786..b82d69e9c69a95888e487858da6d684936951536 100644
--- a/src/box/sql/cursor.h
+++ b/src/box/sql/cursor.h
@@ -60,7 +60,6 @@ void sqlCursorZero(BtCursor *);
  */
 void
 sql_cursor_close(struct BtCursor *cursor);
-int sqlCursorMovetoUnpacked(BtCursor *, UnpackedRecord * pUnKey, int *pRes);
 
 int sqlCursorNext(BtCursor *, int *pRes);
 int sqlCursorPrevious(BtCursor *, int *pRes);
diff --git a/src/box/sql/mem.c b/src/box/sql/mem.c
index 32fdd9adddcb39529470c79653eb0f84ff06a6a1..e654cac41190be7941d51556fc59e4800490239b 100644
--- a/src/box/sql/mem.c
+++ b/src/box/sql/mem.c
@@ -45,6 +45,8 @@
 #include "uuid/mp_uuid.h"
 #include "mp_decimal.h"
 
+#define CMP_OLD_NEW(a, b, type) (((a) > (type)(b)) - ((a) < (type)(b)))
+
 /*
  * Make sure pMem->z points to a writable allocation of at least
  * min(n,32) bytes.
@@ -662,6 +664,23 @@ int_to_double_precise(struct Mem *mem)
 	return 0;
 }
 
+/**
+ * Cast MEM with negative INTEGER to DOUBLE. Doesn't fail. The return value
+ * is < 0 if the original value is less than the result, > 0 if the original
+ * value is greater than the result, and 0 if the cast is precise.
+ */
+static inline int
+int_to_double_forced(struct Mem *mem)
+{
+	assert(mem->type == MEM_TYPE_INT);
+	int64_t i = mem->u.i;
+	double d = (double)i;
+	mem->u.r = d;
+	mem->type = MEM_TYPE_DOUBLE;
+	mem->field_type = FIELD_TYPE_DOUBLE;
+	return CMP_OLD_NEW(i, d, int64_t);
+}
+
 static inline int
 uint_to_double_precise(struct Mem *mem)
 {
@@ -676,6 +695,23 @@ uint_to_double_precise(struct Mem *mem)
 	return 0;
 }
 
+/**
+ * Cast MEM with positive INTEGER to DOUBLE. Doesn't fail. The return value
+ * is < 0 if the original value is less than the result, > 0 if the original
+ * value is greater than the result, and 0 if the cast is precise.
+ */
+static inline int
+uint_to_double_forced(struct Mem *mem)
+{
+	assert(mem->type == MEM_TYPE_UINT);
+	uint64_t u = mem->u.u;
+	double d = (double)u;
+	mem->u.r = d;
+	mem->type = MEM_TYPE_DOUBLE;
+	mem->field_type = FIELD_TYPE_DOUBLE;
+	return CMP_OLD_NEW(u, d, uint64_t);
+}
+
 static inline int
 int_to_str0(struct Mem *mem)
 {
@@ -868,6 +904,43 @@ double_to_int_precise(struct Mem *mem)
 	return -1;
 }
 
+/**
+ * Cast MEM with DOUBLE to INTEGER. Doesn't fail. The return value is < 0 if the
+ * original value is less than the result, > 0 if the original value is greater
+ * than the result, and 0 if the cast is precise.
+ */
+static inline int
+double_to_int_forced(struct Mem *mem)
+{
+	assert(mem->type == MEM_TYPE_DOUBLE);
+	double d = mem->u.r;
+	int64_t i;
+	enum mem_type type;
+	int res;
+	if (d < (double)INT64_MIN) {
+		i = INT64_MIN;
+		type = MEM_TYPE_INT;
+		res = -1;
+	} else if (d >= (double)UINT64_MAX) {
+		i = (int64_t)UINT64_MAX;
+		type = MEM_TYPE_UINT;
+		res = 1;
+	} else if (d <= -1.0) {
+		i = (int64_t)d;
+		type = MEM_TYPE_INT;
+		res = CMP_OLD_NEW(d, i, double);
+	} else {
+		uint64_t u = (uint64_t)d;
+		i = (int64_t)u;
+		type = MEM_TYPE_UINT;
+		res = CMP_OLD_NEW(d, u, double);
+	}
+	mem->u.i = i;
+	mem->type = type;
+	mem->field_type = FIELD_TYPE_INTEGER;
+	return res;
+}
+
 static inline int
 double_to_uint(struct Mem *mem)
 {
@@ -898,6 +971,34 @@ double_to_uint_precise(struct Mem *mem)
 	return -1;
 }
 
+/**
+ * Cast MEM with DOUBLE to UNSIGNED. Doesn't fail. The return value is < 0 if
+ * the original value is less than the result, > 0 if the original value is
+ * greater than the result, and 0 if the cast is precise.
+ */
+static inline int
+double_to_uint_forced(struct Mem *mem)
+{
+	assert(mem->type == MEM_TYPE_DOUBLE);
+	double d = mem->u.r;
+	uint64_t u;
+	int res;
+	if (d < 0.0) {
+		u = 0;
+		res = -1;
+	} else if (d >= (double)UINT64_MAX) {
+		u = UINT64_MAX;
+		res = 1;
+	} else {
+		u = (uint64_t)d;
+		res = CMP_OLD_NEW(d, u, double);
+	}
+	mem->u.u = u;
+	mem->type = MEM_TYPE_UINT;
+	mem->field_type = FIELD_TYPE_UNSIGNED;
+	return res;
+}
+
 static inline int
 double_to_str0(struct Mem *mem)
 {
@@ -1274,6 +1375,57 @@ mem_cast_implicit_old(struct Mem *mem, enum field_type type)
 	return -1;
 }
 
+int
+mem_cast_implicit_number(struct Mem *mem, enum field_type type)
+{
+	assert(mem_is_num(mem) && sql_type_is_numeric(type));
+	switch (type) {
+	case FIELD_TYPE_UNSIGNED:
+		switch (mem->type) {
+		case MEM_TYPE_UINT:
+			mem->field_type = FIELD_TYPE_UNSIGNED;
+			return 0;
+		case MEM_TYPE_INT:
+			mem->u.u = 0;
+			mem->type = MEM_TYPE_UINT;
+			mem->field_type = FIELD_TYPE_UNSIGNED;
+			return -1;
+		case MEM_TYPE_DOUBLE:
+			return double_to_uint_forced(mem);
+		default:
+			unreachable();
+		}
+		break;
+	case FIELD_TYPE_DOUBLE:
+		switch (mem->type) {
+		case MEM_TYPE_INT:
+			return int_to_double_forced(mem);
+		case MEM_TYPE_UINT:
+			return uint_to_double_forced(mem);
+		case MEM_TYPE_DOUBLE:
+			return 0;
+		default:
+			unreachable();
+		}
+		break;
+	case FIELD_TYPE_INTEGER:
+		switch (mem->type) {
+		case MEM_TYPE_UINT:
+		case MEM_TYPE_INT:
+			mem->field_type = FIELD_TYPE_INTEGER;
+			return 0;
+		case MEM_TYPE_DOUBLE:
+			return double_to_int_forced(mem);
+		default:
+			unreachable();
+		}
+		break;
+	default:
+		unreachable();
+	}
+	return 0;
+}
+
 int
 mem_get_int(const struct Mem *mem, int64_t *i, bool *is_neg)
 {
@@ -2520,8 +2672,6 @@ sqlVdbeRecordCompareMsgpack(const void *key1,
 			return -rc;
 		}
 	}
-
-	key2->eqSeen = 1;
 	return key2->default_rc;
 }
 
diff --git a/src/box/sql/mem.h b/src/box/sql/mem.h
index 70bbe557b58a05a8d844b89486ce220a275b9dbc..4f61cbab080e7e0fd949f453e11a1c94f6e930bb 100644
--- a/src/box/sql/mem.h
+++ b/src/box/sql/mem.h
@@ -746,6 +746,14 @@ mem_cast_implicit(struct Mem *mem, enum field_type type);
 int
 mem_cast_implicit_old(struct Mem *mem, enum field_type type);
 
+/**
+ * Cast MEM with numeric value to given numeric type. Doesn't fail. The return
+ * value is < 0 if the original value is less than the result, > 0 if the
+ * original value is greater than the result, and 0 if the cast is precise.
+ */
+int
+mem_cast_implicit_number(struct Mem *mem, enum field_type type);
+
 /**
  * Return value for MEM of INTEGER type. For MEM of all other types convert
  * value of the MEM to INTEGER if possible and return converted value. Original
diff --git a/src/box/sql/sqlInt.h b/src/box/sql/sqlInt.h
index 115c52f96b7f307a6069a621c3952fa51d54ec40..e2e550978d80f88a57fea50e55b2cbe823c94e2d 100644
--- a/src/box/sql/sqlInt.h
+++ b/src/box/sql/sqlInt.h
@@ -1312,13 +1312,6 @@ sql_space_tuple_log_count(struct space *space);
  * 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.
- *
- * The key comparison functions will set eqSeen to true if they ever
- * get and equal results when comparing this structure to a b-tree record.
- * When default_rc!=0, the search might end up on the record immediately
- * before the first match or immediately after the last match.  The
- * eqSeen field will indicate whether or not an exact match exists in the
- * b-tree.
  */
 struct UnpackedRecord {
 	/** Collation and sort-order information. */
@@ -1328,7 +1321,6 @@ struct UnpackedRecord {
 	i8 default_rc;		/* Comparison result if keys are equal */
 	i8 r1;			/* Value to return if (lhs > rhs) */
 	i8 r2;			/* Value to return if (rhs < lhs) */
-	u8 eqSeen;		/* True if an equality comparison has been seen */
 	u8 opcode;		/* Currently executing opcode that invoked
 				 * movetoUnpacked, used by Tarantool storage layer.
 				 */
diff --git a/src/box/sql/tarantoolInt.h b/src/box/sql/tarantoolInt.h
index 1ded6c70906eae5904a53df7156864392fbefbfc..8fdc50432b6b8a38879e54e1ac18e3fadb0d36ef 100644
--- a/src/box/sql/tarantoolInt.h
+++ b/src/box/sql/tarantoolInt.h
@@ -27,6 +27,9 @@ int tarantoolsqlReplace(struct space *space, const char *tuple,
 			    const char *tuple_end);
 int tarantoolsqlDelete(BtCursor * pCur, u8 flags);
 
+int
+sql_cursor_seek(struct BtCursor *cur, struct Mem *mems, uint32_t len, int *res);
+
 /**
  * Delete entry from space by its key.
  *
diff --git a/src/box/sql/vdbe.c b/src/box/sql/vdbe.c
index 2e147ec0b4bda29fb64e3b4ef511d2ea8f34ab50..371be205ea30947840b448f1fa58c34d0bcd8a2c 100644
--- a/src/box/sql/vdbe.c
+++ b/src/box/sql/vdbe.c
@@ -2504,35 +2504,25 @@ case OP_Close: {
 	break;
 }
 
-/* Opcode: SeekGE P1 P2 P3 P4 P5
+/* Opcode: SeekLT P1 P2 P3 P4 P5
  * Synopsis: key=r[P3@P4]
  *
  * If cursor P1 refers to an SQL table (B-Tree that uses integer keys),
- * use the value in register P3 as the key.  If cursor P1 refers
+ * use the value in register P3 as a key. If cursor P1 refers
  * to an SQL index, then P3 is the first in an array of P4 registers
  * that are used as an unpacked index key.
  *
- * Reposition cursor P1 so that  it points to the smallest entry that
- * is greater than or equal to the key value. If there are no records
- * greater than or equal to the key and P2 is not zero, then jump to P2.
- *
- * If the cursor P1 was opened using the OPFLAG_SEEKEQ flag, then this
- * opcode will always land on a record that equally equals the key, or
- * else jump immediately to P2.  When the cursor is OPFLAG_SEEKEQ, this
- * opcode must be followed by an IdxLE opcode with the same arguments.
- * The IdxLE opcode will be skipped if this opcode succeeds, but the
- * IdxLE opcode will be used on subsequent loop iterations.
+ * Reposition cursor P1 so that  it points to the largest entry that
+ * is less than the key value. If there are no records less than
+ * the key and P2 is not zero, then jump to P2.
  *
- * This opcode leaves the cursor configured to move in forward order,
- * from the beginning toward the end.  In other words, the cursor is
- * configured to use Next, not Prev.
+ * This opcode leaves the cursor configured to move in reverse order,
+ * from the end toward the beginning.  In other words, the cursor is
+ * configured to use Prev, not Next.
  *
- * If P5 is not zero, than it is offset of integer fields in input
- * vector. Force corresponding value to be INTEGER, in case it
- * is floating point value. Alongside with that, type of
- * iterator may be changed: a > 1.5 -> a >= 2.
+ * P5 has the same meaning as for SeekGE.
  *
- * See also: Found, NotFound, SeekLt, SeekGt, SeekLe
+ * See also: Found, NotFound, SeekGt, SeekGe, SeekLe
  */
 /* Opcode: SeekGT P1 P2 P3 P4 P5
  * Synopsis: key=r[P3@P4]
@@ -2555,7 +2545,54 @@ case OP_Close: {
  *
  * P5 has the same meaning as for SeekGE.
  */
-/* Opcode: SeekLT P1 P2 P3 P4 P5
+case OP_SeekLT:         /* jump, in3 */
+case OP_SeekGT: {       /* jump, in3 */
+	bool is_lt = pOp->opcode == OP_SeekLT;
+	struct VdbeCursor *cur = p->apCsr[pOp->p1];
+#ifdef SQL_DEBUG
+	cur->seekOp = pOp->opcode;
+#endif
+	cur->nullRow = 0;
+	cur->uc.pCursor->iter_type = is_lt ? ITER_LT : ITER_GT;
+
+	uint32_t len = pOp->p4.i;
+	assert(pOp->p4type == P4_INT32);
+	assert(len <= cur->key_def->part_count);
+	struct Mem *mems = &aMem[pOp->p3];
+	bool is_op_change = false;
+	for (uint32_t i = 0; i < len; ++i) {
+		enum field_type type = cur->key_def->parts[i].type;
+		struct Mem *mem = &mems[i];
+		if (mem_is_field_compatible(mem, type))
+			continue;
+		if (!sql_type_is_numeric(type) || !mem_is_num(mem)) {
+			diag_set(ClientError, ER_SQL_TYPE_MISMATCH,
+				 mem_str(mem), field_type_strs[type]);
+			goto abort_due_to_error;
+		}
+		int cmp = mem_cast_implicit_number(mem, type);
+		is_op_change = is_op_change || (is_lt && cmp > 0) ||
+			       (!is_lt && cmp < 0);
+	}
+	if (is_op_change)
+		cur->uc.pCursor->iter_type = is_lt ? ITER_LE : ITER_GE;
+
+	int res;
+	if (sql_cursor_seek(cur->uc.pCursor, mems, len, &res) != 0)
+		goto abort_due_to_error;
+	assert((res != 0) == (cur->uc.pCursor->eState == CURSOR_INVALID));
+	cur->cacheStatus = CACHE_STALE;
+#ifdef SQL_TEST
+	sql_search_count++;
+#endif
+	assert(pOp->p2 > 0);
+	VdbeBranchTaken(res, 2);
+	if (res != 0)
+		goto jump_to_p2;
+	break;
+}
+
+/* Opcode: SeekLE P1 P2 P3 P4 P5
  * Synopsis: key=r[P3@P4]
  *
  * If cursor P1 refers to an SQL table (B-Tree that uses integer keys),
@@ -2563,227 +2600,119 @@ case OP_Close: {
  * to an SQL index, then P3 is the first in an array of P4 registers
  * that are used as an unpacked index key.
  *
- * Reposition cursor P1 so that  it points to the largest entry that
- * is less than the key value. If there are no records less than
- * the key and P2 is not zero, then jump to P2.
+ * Reposition cursor P1 so that it points to the largest entry that
+ * is less than or equal to the key value. If there are no records
+ * less than or equal to the key and P2 is not zero, then jump to P2.
  *
  * This opcode leaves the cursor configured to move in reverse order,
  * from the end toward the beginning.  In other words, the cursor is
  * configured to use Prev, not Next.
  *
+ * If the cursor P1 was opened using the OPFLAG_SEEKEQ flag, then this
+ * opcode will always land on a record that equally equals the key, or
+ * else jump immediately to P2.  When the cursor is OPFLAG_SEEKEQ, this
+ * opcode must be followed by an IdxGE opcode with the same arguments.
+ * The IdxGE opcode will be skipped if this opcode succeeds, but the
+ * IdxGE opcode will be used on subsequent loop iterations.
+ *
  * P5 has the same meaning as for SeekGE.
  *
- * See also: Found, NotFound, SeekGt, SeekGe, SeekLe
+ * See also: Found, NotFound, SeekGt, SeekGe, SeekLt
  */
-/* Opcode: SeekLE P1 P2 P3 P4 P5
+/* Opcode: SeekGE P1 P2 P3 P4 P5
  * Synopsis: key=r[P3@P4]
  *
  * If cursor P1 refers to an SQL table (B-Tree that uses integer keys),
- * use the value in register P3 as a key. If cursor P1 refers
+ * use the value in register P3 as the key.  If cursor P1 refers
  * to an SQL index, then P3 is the first in an array of P4 registers
  * that are used as an unpacked index key.
  *
- * Reposition cursor P1 so that it points to the largest entry that
- * is less than or equal to the key value. If there are no records
- * less than or equal to the key and P2 is not zero, then jump to P2.
- *
- * This opcode leaves the cursor configured to move in reverse order,
- * from the end toward the beginning.  In other words, the cursor is
- * configured to use Prev, not Next.
+ * Reposition cursor P1 so that  it points to the smallest entry that
+ * is greater than or equal to the key value. If there are no records
+ * greater than or equal to the key and P2 is not zero, then jump to P2.
  *
  * If the cursor P1 was opened using the OPFLAG_SEEKEQ flag, then this
  * opcode will always land on a record that equally equals the key, or
  * else jump immediately to P2.  When the cursor is OPFLAG_SEEKEQ, this
- * opcode must be followed by an IdxGE opcode with the same arguments.
- * The IdxGE opcode will be skipped if this opcode succeeds, but the
- * IdxGE opcode will be used on subsequent loop iterations.
+ * opcode must be followed by an IdxLE opcode with the same arguments.
+ * The IdxLE opcode will be skipped if this opcode succeeds, but the
+ * IdxLE opcode will be used on subsequent loop iterations.
  *
- * P5 has the same meaning as for SeekGE.
+ * This opcode leaves the cursor configured to move in forward order,
+ * from the beginning toward the end.  In other words, the cursor is
+ * configured to use Next, not Prev.
  *
- * See also: Found, NotFound, SeekGt, SeekGe, SeekLt
+ * If P5 is not zero, than it is offset of integer fields in input
+ * vector. Force corresponding value to be INTEGER, in case it
+ * is floating point value. Alongside with that, type of
+ * iterator may be changed: a > 1.5 -> a >= 2.
+ *
+ * See also: Found, NotFound, SeekLt, SeekGt, SeekLe
  */
-case OP_SeekLT:         /* jump, in3 */
 case OP_SeekLE:         /* jump, in3 */
-case OP_SeekGE:         /* jump, in3 */
-case OP_SeekGT: {       /* jump, in3 */
-	int res;           /* Comparison result */
-	int oc;            /* Opcode */
-	VdbeCursor *pC;    /* The cursor to seek */
-	UnpackedRecord r;  /* The key to seek for */
-	int nField;        /* Number of columns or fields in the key */
-	i64 iKey;          /* The id we are to seek to */
-	int eqOnly;        /* Only interested in == results */
-
-	assert(pOp->p1>=0 && pOp->p1<p->nCursor);
-	assert(pOp->p2!=0);
-	pC = p->apCsr[pOp->p1];
-	assert(pC!=0);
-	assert(pC->eCurType==CURTYPE_TARANTOOL);
-	assert(OP_SeekLE == OP_SeekLT+1);
-	assert(OP_SeekGE == OP_SeekLT+2);
-	assert(OP_SeekGT == OP_SeekLT+3);
-	assert(pC->uc.pCursor!=0);
-	oc = pOp->opcode;
-	eqOnly = 0;
-	pC->nullRow = 0;
+case OP_SeekGE: {       /* jump, in3 */
+	bool is_le = pOp->opcode == OP_SeekLE;
+	struct VdbeCursor *cur = p->apCsr[pOp->p1];
 #ifdef SQL_DEBUG
-	pC->seekOp = pOp->opcode;
+	cur->seekOp = pOp->opcode;
 #endif
-	iKey = 0;
-	/*
-	 * In case floating value is intended to be passed to
-	 * iterator over integer field, we must truncate it to
-	 * integer value and change type of iterator:
-	 * a > 1.5 -> a >= 2
-	 */
-	int int_field = pOp->p5;
-	bool is_neg = false;
-
-	if (int_field > 0) {
-		/* The input value in P3 might be of any type: integer, real, string,
-		 * blob, or NULL.  But it needs to be an integer before we can do
-		 * the seek, so convert it.
-		 */
-		pIn3 = &aMem[int_field];
-		if (mem_is_null(pIn3))
-			goto skip_truncate;
-		if (mem_is_str(pIn3))
-			mem_to_number(pIn3);
-		int64_t i;
-		if (mem_get_int(pIn3, &i, &is_neg) != 0) {
-			if (!mem_is_double(pIn3)) {
-				diag_set(ClientError, ER_SQL_TYPE_MISMATCH,
-					 mem_str(pIn3), "integer");
-				goto abort_due_to_error;
-			}
-			double d = pIn3->u.r;
-			assert(d >= (double)INT64_MAX || d < (double)INT64_MIN);
-			/* TODO: add [INT64_MAX, UINT64_MAX) here. */
-			if (d > (double)INT64_MAX)
-				i = INT64_MAX;
-			else if (d < (double)INT64_MIN)
-				i = INT64_MIN;
-			else
-				i = d;
-			is_neg = i < 0;
+	cur->nullRow = 0;
+	bool is_eq = (cur->uc.pCursor->hints & OPFLAG_SEEKEQ) != 0;
+	if (is_le)
+		cur->uc.pCursor->iter_type = is_eq ? ITER_REQ : ITER_LE;
+	else
+		cur->uc.pCursor->iter_type = is_eq ? ITER_EQ : ITER_GE;
+	assert(!is_eq || pOp[1].opcode == OP_IdxLT ||
+	       pOp[1].opcode == OP_IdxGT);
+
+	uint32_t len = pOp->p4.i;
+	assert(pOp->p4type == P4_INT32);
+	assert(len <= cur->key_def->part_count);
+	struct Mem *mems = &aMem[pOp->p3];
+	bool is_op_change = false;
+	bool is_zero = false;
+	for (uint32_t i = 0; i < len; ++i) {
+		enum field_type type = cur->key_def->parts[i].type;
+		struct Mem *mem = &mems[i];
+		if (mem_is_field_compatible(mem, type))
+			continue;
+		if (!sql_type_is_numeric(type) || !mem_is_num(mem)) {
+			diag_set(ClientError, ER_SQL_TYPE_MISMATCH,
+				 mem_str(mem), field_type_strs[type]);
+			goto abort_due_to_error;
 		}
-		iKey = i;
-
-		/* If the P3 value could not be converted into an integer without
-		 * loss of information, then special processing is required...
+		int cmp = mem_cast_implicit_number(mem, type);
+		is_op_change = is_op_change || (is_le && cmp < 0) ||
+			       (!is_le && cmp > 0);
+		/*
+		 * In case search using EQ or REQ, we will not find anything if
+		 * conversion cannot be precise.
 		 */
-		if (!mem_is_int(pIn3)) {
-			if (!mem_is_double(pIn3)) {
-				/* If the P3 value cannot be converted into any kind of a number,
-				 * then the seek is not possible, so jump to P2
-				 */
-				VdbeBranchTaken(1,2); goto jump_to_p2;
-				break;
-			}
-
-			/* If the approximation iKey is larger than the actual real search
-			 * term, substitute >= for > and < for <=. e.g. if the search term
-			 * is 4.9 and the integer approximation 5:
-			 *
-			 *        (x >  4.9)    ->     (x >= 5)
-			 *        (x <= 4.9)    ->     (x <  5)
-			 */
-			if (pIn3->u.r<(double)iKey) {
-				assert(OP_SeekGE==(OP_SeekGT-1));
-				assert(OP_SeekLT==(OP_SeekLE-1));
-				assert((OP_SeekLE & 0x0001)==(OP_SeekGT & 0x0001));
-				if ((oc & 0x0001)==(OP_SeekGT & 0x0001)) oc--;
-			}
-
-			/* If the approximation iKey is smaller than the actual real search
-			 * term, substitute <= for < and > for >=.
-			 */
-			else if (pIn3->u.r>(double)iKey) {
-				assert(OP_SeekLE==(OP_SeekLT+1));
-				assert(OP_SeekGT==(OP_SeekGE+1));
-				assert((OP_SeekLT & 0x0001)==(OP_SeekGE & 0x0001));
-				if ((oc & 0x0001)==(OP_SeekLT & 0x0001)) oc++;
-			}
-		}
+		is_zero = is_zero || (is_eq && cmp != 0);
 	}
-skip_truncate:
-	/*
-	 * For a cursor with the OPFLAG_SEEKEQ hint, only the
-	 * OP_SeekGE and OP_SeekLE opcodes are allowed, and these
-	 * must be immediately followed by an OP_IdxGT or
-	 * OP_IdxLT opcode, respectively, with the same key.
-	 */
-	if ((pC->uc.pCursor->hints & OPFLAG_SEEKEQ) != 0) {
-		eqOnly = 1;
-		assert(pOp->opcode==OP_SeekGE || pOp->opcode==OP_SeekLE);
-		assert(pOp[1].opcode==OP_IdxLT || pOp[1].opcode==OP_IdxGT);
-		assert(pOp[1].p1==pOp[0].p1);
-		assert(pOp[1].p2==pOp[0].p2);
-		assert(pOp[1].p3==pOp[0].p3);
-		assert(pOp[1].p4.i==pOp[0].p4.i);
+	if (is_zero) {
+		assert(pOp->p2 > 0);
+		VdbeBranchTaken(1, 2);
+		goto jump_to_p2;
 	}
+	if (!is_eq && is_op_change)
+		cur->uc.pCursor->iter_type = is_le ? ITER_LT : ITER_GT;
 
-	nField = pOp->p4.i;
-	assert(pOp->p4type==P4_INT32);
-	assert(nField>0);
-	r.key_def = pC->key_def;
-	r.nField = (u16)nField;
-
-	if (int_field > 0)
-		mem_set_int(&aMem[int_field], iKey, is_neg);
-
-	r.default_rc = ((1 & (oc - OP_SeekLT)) ? -1 : +1);
-	assert(oc!=OP_SeekGT || r.default_rc==-1);
-	assert(oc!=OP_SeekLE || r.default_rc==-1);
-	assert(oc!=OP_SeekGE || r.default_rc==+1);
-	assert(oc!=OP_SeekLT || r.default_rc==+1);
-
-	r.aMem = &aMem[pOp->p3];
-#ifdef SQL_DEBUG
-	{ int i; for(i=0; i<r.nField; i++) assert(memIsValid(&r.aMem[i])); }
-#endif
-	r.eqSeen = 0;
-	r.opcode = oc;
-	if (sqlCursorMovetoUnpacked(pC->uc.pCursor, &r, &res) != 0)
+	int res;
+	if (sql_cursor_seek(cur->uc.pCursor, mems, len, &res) != 0)
 		goto abort_due_to_error;
-	if (eqOnly && r.eqSeen==0) {
-		assert(res!=0);
-		goto seek_not_found;
-	}
-	pC->cacheStatus = CACHE_STALE;
+	assert((res != 0) == (cur->uc.pCursor->eState == CURSOR_INVALID));
+	cur->cacheStatus = CACHE_STALE;
 #ifdef SQL_TEST
 	sql_search_count++;
 #endif
-	if (oc>=OP_SeekGE) {  assert(oc==OP_SeekGE || oc==OP_SeekGT);
-		if (res<0 || (res==0 && oc==OP_SeekGT)) {
-			res = 0;
-			if (sqlCursorNext(pC->uc.pCursor, &res) != 0)
-				goto abort_due_to_error;
-		} else {
-			res = 0;
-		}
-	} else {
-		assert(oc==OP_SeekLT || oc==OP_SeekLE);
-		if (res>0 || (res==0 && oc==OP_SeekLT)) {
-			res = 0;
-			if (sqlCursorPrevious(pC->uc.pCursor, &res) != 0)
-				goto abort_due_to_error;
-		} else {
-			/* res might be negative because the table is empty.  Check to
-			 * see if this is the case.
-			 */
-			res = (CURSOR_VALID != pC->uc.pCursor->eState);
-		}
-	}
-			seek_not_found:
-	assert(pOp->p2>0);
-	VdbeBranchTaken(res!=0,2);
-	if (res) {
+	assert(pOp->p2 > 0);
+	VdbeBranchTaken(res, 2);
+	if (res != 0)
 		goto jump_to_p2;
-	} else if (eqOnly) {
-		assert(pOp[1].opcode==OP_IdxLT || pOp[1].opcode==OP_IdxGT);
-		pOp++; /* Skip the OP_IdxLt or OP_IdxGT that follows */
-	}
+	/* Skip the OP_IdxLT/OP_IdxGT that follows if we have EQ. */
+	if (is_eq)
+		pOp++;
 	break;
 }
 
@@ -2910,7 +2839,9 @@ case OP_Found: {        /* jump, in3 */
 			}
 		}
 	}
-	rc = sqlCursorMovetoUnpacked(pC->uc.pCursor, pIdxKey, &res);
+	pC->uc.pCursor->iter_type = ITER_EQ;
+	rc = sql_cursor_seek(pC->uc.pCursor, pIdxKey->aMem, pIdxKey->nField,
+			     &res);
 	if (pFree != NULL)
 		sqlDbFree(db, pFree);
 	if (rc != 0)
@@ -3768,7 +3699,6 @@ case OP_IdxDelete: {
 	VdbeCursor *pC;
 	BtCursor *pCrsr;
 	int res;
-	UnpackedRecord r;
 
 	assert(pOp->p3>0);
 	assert(pOp->p2>0 && pOp->p2+pOp->p3<=(p->nMem+1 - p->nCursor)+1);
@@ -3779,12 +3709,7 @@ case OP_IdxDelete: {
 	pCrsr = pC->uc.pCursor;
 	assert(pCrsr!=0);
 	assert(pOp->p5==0);
-	r.key_def = pC->key_def;
-	r.nField = (u16)pOp->p3;
-	r.default_rc = 0;
-	r.aMem = &aMem[pOp->p2];
-	r.opcode = OP_IdxDelete;
-	if (sqlCursorMovetoUnpacked(pCrsr, &r, &res) != 0)
+	if (sql_cursor_seek(pCrsr, &aMem[pOp->p2], (u16)pOp->p3, &res) != 0)
 		goto abort_due_to_error;
 	if (res==0) {
 		assert(pCrsr->eState == CURSOR_VALID);