Tarantool development patches archive
 help / color / mirror / Atom feed
* [Tarantool-patches] [PATCH v1 0/7] Rework implicit cast
@ 2021-07-28 20:51 Mergen Imeev via Tarantool-patches
  2021-07-28 20:51 ` [Tarantool-patches] [PATCH v1 1/7] sql: rework implicit cast fo assignment Mergen Imeev via Tarantool-patches
                   ` (6 more replies)
  0 siblings, 7 replies; 25+ messages in thread
From: Mergen Imeev via Tarantool-patches @ 2021-07-28 20:51 UTC (permalink / raw)
  To: v.shpilevoy; +Cc: tarantool-patches

This patch-set reworks implicit cast in SQL according to new rules. According to
these rules, all scalar values can be cast to SCALAR, all numeric values can be
cast to NUMBER, and any numeric value can be cast to another numeric type only
if the conversion is exact. No other implicit cast is allowed.

https://github.com/tarantool/tarantool/issues/4230
https://github.com/tarantool/tarantool/issues/4470
https://github.com/tarantool/tarantool/tree/imeevma/gh-4230-remove-implicit-cast-for-comparison-v2

Mergen Imeev (7):
  sql: rework implicit cast fo assignment
  sql: remove implicit cast from comparison opcodes
  sql: rework OP_Seek* opcodes
  sql: remove unnecessary calls of OP_ApplyType
  sql: remove implicit cast from OP_MakeRecord
  sql: remove implicit cast from OP_MustBeInt
  sql: remove unused MEM cast functions

 .../gh-4230-implicit-cast-for-comparison.md   |   3 +
 .../gh-4470-implicit-cast-for-assignment.md   |   3 +
 src/box/sql.c                                 |  69 +-
 src/box/sql/analyze.c                         |   7 +-
 src/box/sql/cursor.c                          |  14 -
 src/box/sql/cursor.h                          |   1 -
 src/box/sql/delete.c                          |   8 +-
 src/box/sql/expr.c                            |   9 +-
 src/box/sql/fk_constraint.c                   |   9 +-
 src/box/sql/func.c                            |  29 +-
 src/box/sql/insert.c                          |  14 -
 src/box/sql/mem.c                             | 618 ++++++--------
 src/box/sql/mem.h                             |  89 +-
 src/box/sql/sqlInt.h                          |   4 -
 src/box/sql/tarantoolInt.h                    |   3 +
 src/box/sql/update.c                          |  14 +-
 src/box/sql/vdbe.c                            | 789 ++++++++----------
 src/box/sql/vdbe.h                            |   2 +-
 src/box/sql/vdbeaux.c                         |   6 +-
 src/box/sql/where.c                           |  20 +-
 src/box/sql/wherecode.c                       | 217 +----
 src/box/sql/whereexpr.c                       |   4 +-
 test/sql-tap/cast.test.lua                    | 152 +++-
 test/sql-tap/func5.test.lua                   |   6 +-
 test/sql-tap/identifier_case.test.lua         |  10 +-
 test/sql-tap/in1.test.lua                     |  15 +-
 test/sql-tap/in3.test.lua                     |   4 +-
 test/sql-tap/in4.test.lua                     |   4 +-
 test/sql-tap/index1.test.lua                  |   4 +-
 test/sql-tap/insert3.test.lua                 |   2 +-
 test/sql-tap/join.test.lua                    |   8 +-
 test/sql-tap/misc1.test.lua                   |  45 +-
 test/sql-tap/numcast.test.lua                 |   4 +-
 test/sql-tap/select1.test.lua                 |   6 +-
 test/sql-tap/select7.test.lua                 |   2 +-
 test/sql-tap/sql-errors.test.lua              |   4 +-
 test/sql-tap/subquery.test.lua                |   4 +-
 test/sql-tap/tkt-9a8b09f8e6.test.lua          |  54 +-
 test/sql-tap/tkt3493.test.lua                 |  54 +-
 test/sql-tap/transitive1.test.lua             |  16 +-
 test/sql-tap/uuid.test.lua                    |  68 +-
 test/sql-tap/where2.test.lua                  | 143 +---
 test/sql-tap/where5.test.lua                  |  12 +-
 test/sql/boolean.result                       | 220 ++---
 test/sql/types.result                         |  10 +-
 45 files changed, 1076 insertions(+), 1703 deletions(-)
 create mode 100644 changelogs/unreleased/gh-4230-implicit-cast-for-comparison.md
 create mode 100644 changelogs/unreleased/gh-4470-implicit-cast-for-assignment.md

-- 
2.25.1


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

* [Tarantool-patches] [PATCH v1 1/7] sql: rework implicit cast fo assignment
  2021-07-28 20:51 [Tarantool-patches] [PATCH v1 0/7] Rework implicit cast Mergen Imeev via Tarantool-patches
@ 2021-07-28 20:51 ` Mergen Imeev via Tarantool-patches
  2021-07-30 21:55   ` Vladislav Shpilevoy via Tarantool-patches
  2021-07-28 20:51 ` [Tarantool-patches] [PATCH v1 2/7] sql: remove implicit cast from comparison opcodes Mergen Imeev via Tarantool-patches
                   ` (5 subsequent siblings)
  6 siblings, 1 reply; 25+ messages in thread
From: Mergen Imeev via Tarantool-patches @ 2021-07-28 20:51 UTC (permalink / raw)
  To: v.shpilevoy; +Cc: tarantool-patches

After this patch, the new rules will be applied to implicit cast during
assignment. According to these rules, all scalar values can be cast to
SCALAR, all numeric values can be cast to NUMBER, and any numeric value
can be cast to another numeric type only if the conversion is exact.
No other implicit cast is allowed.

Part of #4470
---
 .../gh-4470-implicit-cast-for-assignment.md   |   3 +
 src/box/sql/mem.c                             | 135 ++++++++++++++++++
 src/box/sql/mem.h                             |  10 ++
 src/box/sql/vdbe.c                            |  15 +-
 test/sql-tap/cast.test.lua                    |  94 +++++++++++-
 test/sql-tap/numcast.test.lua                 |   4 +-
 test/sql-tap/uuid.test.lua                    |  64 +++------
 test/sql/types.result                         |   3 +-
 8 files changed, 281 insertions(+), 47 deletions(-)
 create mode 100644 changelogs/unreleased/gh-4470-implicit-cast-for-assignment.md

diff --git a/changelogs/unreleased/gh-4470-implicit-cast-for-assignment.md b/changelogs/unreleased/gh-4470-implicit-cast-for-assignment.md
new file mode 100644
index 000000000..c758494eb
--- /dev/null
+++ b/changelogs/unreleased/gh-4470-implicit-cast-for-assignment.md
@@ -0,0 +1,3 @@
+## feature/sql
+
+* Implicit cast for assignment now works according to defined rules (gh-4470).
diff --git a/src/box/sql/mem.c b/src/box/sql/mem.c
index 351d80b76..e804dba67 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) ((int)(a > (type)b) - (int)(a < (type)b))
+
 /*
  * Make sure pMem->z points to a writable allocation of at least
  * min(n,32) bytes.
@@ -900,6 +902,92 @@ uuid_to_bin(struct Mem *mem)
 	return mem_copy_bin(mem, (char *)&mem->u.uuid, UUID_LEN);
 }
 
+static inline int
+forced_int_to_double(struct Mem *mem)
+{
+	assert(mem->type == MEM_TYPE_INT || mem->type == MEM_TYPE_UINT);
+	double d;
+	int res;
+	if (mem->type == MEM_TYPE_INT) {
+		d = (double)mem->u.i;
+		res = CMP_OLD_NEW(mem->u.i, d, int64_t);
+	} else {
+		d = (double)mem->u.u;
+		res = CMP_OLD_NEW(mem->u.u, d, uint64_t);
+	}
+	mem->u.r = d;
+	mem->type = MEM_TYPE_DOUBLE;
+	mem->field_type = FIELD_TYPE_DOUBLE;
+	return res;
+}
+
+static inline int
+forced_int_to_uint(struct Mem *mem)
+{
+	assert(mem->type == MEM_TYPE_INT || mem->type == MEM_TYPE_UINT);
+	if (mem->type == MEM_TYPE_UINT)
+		return 0;
+	mem->u.u = 0;
+	mem->type = MEM_TYPE_UINT;
+	mem->field_type = FIELD_TYPE_UNSIGNED;
+	return -1;
+}
+
+static inline int
+forced_double_to_uint(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
+forced_double_to_int(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;
+}
+
 int
 mem_to_int(struct Mem *mem)
 {
@@ -1228,6 +1316,53 @@ 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:
+			return forced_int_to_uint(mem);
+		case MEM_TYPE_DOUBLE:
+			return forced_double_to_uint(mem);
+		default:
+			unreachable();
+		}
+		break;
+	case FIELD_TYPE_DOUBLE:
+		switch (mem->type) {
+		case MEM_TYPE_INT:
+		case MEM_TYPE_UINT:
+			return forced_int_to_double(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 forced_double_to_int(mem);
+		default:
+			unreachable();
+		}
+		break;
+	default:
+		unreachable();
+	}
+	return 0;
+}
+
 int
 mem_get_int(const struct Mem *mem, int64_t *i, bool *is_neg)
 {
diff --git a/src/box/sql/mem.h b/src/box/sql/mem.h
index 645d0ee27..9766bb836 100644
--- a/src/box/sql/mem.h
+++ b/src/box/sql/mem.h
@@ -779,6 +779,16 @@ mem_cast_implicit(struct Mem *mem, enum field_type type);
 int
 mem_cast_implicit_old(struct Mem *mem, enum field_type type);
 
+/**
+ * Convert the given MEM that contains numeric value to given numeric type
+ * according to implicit cast rules. This function cannot fail. Returns:
+ *  -1 if before conversion value was more that after conversion;
+ *  +1 if before conversion value was more that after conversion;
+ *   0 if conversion 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/vdbe.c b/src/box/sql/vdbe.c
index 9e763ed85..d143ce364 100644
--- a/src/box/sql/vdbe.c
+++ b/src/box/sql/vdbe.c
@@ -2157,11 +2157,24 @@ case OP_ApplyType: {
 	while((type = *(types++)) != field_type_MAX) {
 		assert(pIn1 <= &p->aMem[(p->nMem+1 - p->nCursor)]);
 		assert(memIsValid(pIn1));
-		if (mem_cast_implicit(pIn1, type) != 0) {
+		if (mem_is_field_compatible(pIn1, type)) {
+			pIn1++;
+			continue;
+		}
+		if (!mem_is_num(pIn1) || !sql_type_is_numeric(type)) {
 			diag_set(ClientError, ER_SQL_TYPE_MISMATCH,
 				 mem_str(pIn1), field_type_strs[type]);
 			goto abort_due_to_error;
 		}
+		struct Mem mem;
+		mem.type = pIn1->type;
+		mem.u = pIn1->u;
+		mem.flags = 0;
+		if (mem_cast_implicit_number(pIn1, type) != 0) {
+			diag_set(ClientError, ER_SQL_TYPE_MISMATCH,
+				 mem_str(&mem), field_type_strs[type]);
+			goto abort_due_to_error;
+		}
 		pIn1++;
 	}
 	break;
diff --git a/test/sql-tap/cast.test.lua b/test/sql-tap/cast.test.lua
index 799bcc1a8..8af99dbde 100755
--- a/test/sql-tap/cast.test.lua
+++ b/test/sql-tap/cast.test.lua
@@ -1,6 +1,6 @@
 #!/usr/bin/env tarantool
 local test = require("sqltester")
-test:plan(95)
+test:plan(103)
 
 --!./tcltestrunner.lua
 -- 2005 June 25
@@ -875,7 +875,7 @@ test:do_test(
         -- </cast-4.4>
     })
 
--- gh-4470: Make explicit casts work according to our rules.
+-- gh-4470: Make explicit and implicit casts work according to our rules.
 
 -- Make sure that explicit cast from BOOLEAN to numeric types throws an error.
 test:do_catchsql_test(
@@ -1017,4 +1017,94 @@ test:do_execsql_test(
         true
     })
 
+-- Make sure that implicit conversion of numeric values is precise.
+test:execsql([[
+    CREATE TABLE t2 (i INTEGER PRIMARY KEY AUTOINCREMENT, a INTEGER, b DOUBLE);
+    CREATE TABLE t3 (i INTEGER PRIMARY KEY AUTOINCREMENT, s STRING);
+    CREATE TABLE t4 (i INTEGER PRIMARY KEY AUTOINCREMENT, v VARBINARY);
+    CREATE TABLE t5 (i INTEGER PRIMARY KEY AUTOINCREMENT, u UUID);
+]])
+
+test:do_execsql_test(
+    "cast-9.1.1",
+    [[
+        INSERT INTO t2(a) VALUES(1.0e0);
+        SELECT a FROM t2 WHERE i = 1;
+    ]], {
+        1
+    })
+
+test:do_catchsql_test(
+    "cast-9.1.2",
+    [[
+        INSERT INTO t2(a) VALUES(1.5e0);
+    ]], {
+        1, "Type mismatch: can not convert double(1.5) to integer"
+    })
+
+test:do_execsql_test(
+    "cast-9.1.3",
+    [[
+        INSERT INTO t2(b) VALUES(10000000000000000);
+        SELECT b FROM t2 WHERE i = 2;
+    ]], {
+        10000000000000000
+    })
+
+test:do_catchsql_test(
+    "cast-9.1.4",
+    [[
+        INSERT INTO t2(b) VALUES(10000000000000001);
+    ]], {
+        1, "Type mismatch: can not convert integer(10000000000000001) to double"
+    })
+
+-- Make sure that UUID cannot be implicitly cast to STRING.
+local uuid = "CAST('11111111-1111-1111-1111-111111111111' AS UUID)";
+test:do_catchsql_test(
+    "cast-9.2",
+    [[
+        INSERT INTO t3(s) VALUES(]]..uuid..[[);
+    ]], {
+        1, "Type mismatch: can not convert "..
+           "uuid('11111111-1111-1111-1111-111111111111') to string"
+    })
+
+-- Make sure that UUID cannot be implicitly cast to VARBINARY.
+test:do_catchsql_test(
+    "cast-9.3",
+    [[
+        INSERT INTO t4(v) VALUES(]]..uuid..[[);
+    ]], {
+        1, "Type mismatch: can not convert "..
+           "uuid('11111111-1111-1111-1111-111111111111') to varbinary"
+    })
+
+-- Make sure that STRING and VARBINARY cannot be implicitly cast to UUID.
+test:do_catchsql_test(
+    "cast-9.4.1",
+    [[
+        INSERT INTO t5(u) VALUES('11111111-1111-1111-1111-111111111111');
+    ]], {
+        1, "Type mismatch: can not convert "..
+           "string('11111111-1111-1111-1111-111111111111') to uuid"
+    })
+
+test:do_catchsql_test(
+    "cast-9.4.2",
+    [[
+        INSERT INTO t5(u) VALUES(x'11111111111111111111111111111111');
+    ]], {
+        1, "Type mismatch: can not convert "..
+           "varbinary(x'11111111111111111111111111111111') to uuid"
+    })
+
+test:execsql([[
+    DROP TABLE t1;
+    DROP TABLE t2;
+    DROP TABLE t3;
+    DROP TABLE t4;
+    DROP TABLE t5;
+]])
+
 test:finish_test()
diff --git a/test/sql-tap/numcast.test.lua b/test/sql-tap/numcast.test.lua
index 802fe712c..be1366260 100755
--- a/test/sql-tap/numcast.test.lua
+++ b/test/sql-tap/numcast.test.lua
@@ -136,13 +136,13 @@ test:do_catchsql_test(
         1,"Type mismatch: can not convert double(2.0e+19) to integer"
     })
 
-test:do_execsql_test(
+test:do_catchsql_test(
     "cast-2.9",
     [[
         INSERT INTO t VALUES(2.1);
         SELECT * FROM t;
     ]], {
-        2, 9223372036854775808ULL, 18000000000000000000ULL
+        1, "Type mismatch: can not convert double(2.1) to integer"
     })
 
 --
diff --git a/test/sql-tap/uuid.test.lua b/test/sql-tap/uuid.test.lua
index 70683a4fd..613f4c865 100755
--- a/test/sql-tap/uuid.test.lua
+++ b/test/sql-tap/uuid.test.lua
@@ -3,7 +3,7 @@ local build_path = os.getenv("BUILDDIR")
 package.cpath = build_path..'/test/sql-tap/?.so;'..build_path..'/test/sql-tap/?.dylib;'..package.cpath
 
 local test = require("sqltester")
-test:plan(147)
+test:plan(146)
 
 local uuid = require("uuid")
 local uuid1 = uuid.fromstr("11111111-1111-1111-1111-111111111111")
@@ -722,15 +722,12 @@ test:do_catchsql_test(
         1, "Type mismatch: can not convert uuid('11111111-1111-1111-1111-111111111111') to unsigned"
     })
 
-test:do_execsql_test(
+test:do_catchsql_test(
     "uuid-8.1.2",
     [[
         INSERT INTO ts(s) SELECT u FROM t2;
-        SELECT * FROM ts;
     ]], {
-        1, "11111111-1111-1111-1111-111111111111",
-        2, "11111111-3333-1111-1111-111111111111",
-        3, "22222222-1111-1111-1111-111111111111"
+        1, "Type mismatch: can not convert uuid('11111111-1111-1111-1111-111111111111') to string"
     })
 
 test:do_catchsql_test(
@@ -765,15 +762,12 @@ test:do_catchsql_test(
         1, "Type mismatch: can not convert uuid('11111111-1111-1111-1111-111111111111') to boolean"
     })
 
-test:do_execsql_test(
+test:do_catchsql_test(
     "uuid-8.1.7",
     [[
         INSERT INTO tv(v) SELECT u FROM t2;
-        SELECT id, hex(v) FROM tv;
     ]], {
-        1, "11111111111111111111111111111111",
-        2, "11111111333311111111111111111111",
-        3, "22222222111111111111111111111111"
+        1, "Type mismatch: can not convert uuid('11111111-1111-1111-1111-111111111111') to varbinary"
     })
 
 test:do_execsql_test(
@@ -803,13 +797,12 @@ test:do_catchsql_test(
         1, "Type mismatch: can not convert integer(1) to uuid"
     })
 
-test:do_execsql_test(
+test:do_catchsql_test(
     "uuid-8.2.2",
     [[
         INSERT INTO tsu VALUES ('2_string_right', '11111111-1111-1111-1111-111111111111');
-        SELECT * FROM tsu ORDER BY s DESC LIMIT 1;
     ]], {
-        '2_string_right', uuid1
+        1, "Type mismatch: can not convert string('11111111-1111-1111-1111-111111111111') to uuid"
     })
 
 test:do_catchsql_test(
@@ -844,13 +837,13 @@ test:do_catchsql_test(
         1, "Type mismatch: can not convert boolean(TRUE) to uuid"
     })
 
-test:do_execsql_test(
+test:do_catchsql_test(
     "uuid-8.2.7",
     [[
         INSERT INTO tsu SELECT '7_varbinary', x'11111111111111111111111111111111' FROM t2 LIMIT 1;
         SELECT * FROM tsu ORDER BY s DESC LIMIT 1;
     ]], {
-        '7_varbinary', uuid1
+        1, "Type mismatch: can not convert varbinary(x'11111111111111111111111111111111') to uuid"
     })
 
 test:do_catchsql_test(
@@ -882,59 +875,48 @@ test:execsql([[
 ]])
 
 -- Check that INSERT into UUID field works.
-test:do_execsql_test(
+test:do_catchsql_test(
     "uuid-10.1.1",
     [[
         INSERT INTO t10 VALUES (1, '22222222-1111-1111-1111-111111111111');
-        SELECT * FROM t10 WHERE i = 1;
     ]], {
-        1, uuid2
+        1, "Type mismatch: can not convert string('22222222-1111-1111-1111-111111111111') to uuid"
     })
 
-test:do_execsql_test(
+test:do_catchsql_test(
     "uuid-10.1.2",
     [[
         INSERT INTO t10 VALUES (2, x'22222222111111111111111111111111');
-        SELECT * FROM t10 WHERE i = 2;
     ]], {
-        2, uuid2
+        1, "Type mismatch: can not convert varbinary(x'22222222111111111111111111111111') to uuid"
     })
 
-test:do_execsql_test(
+test:do_catchsql_test(
     "uuid-10.1.3",
     [[
         INSERT INTO t10(i) VALUES (3);
-        SELECT * FROM t10 WHERE i = 3;
     ]], {
-        3, uuid1
+        1, "Type mismatch: can not convert string('11111111-1111-1111-1111-111111111111') to uuid"
     })
 
 test:do_execsql_test(
     "uuid-10.1.4",
     [[
-        INSERT INTO t10 VALUES (4, NULL);
-        SELECT * FROM t10 WHERE i = 4;
+        INSERT INTO t10 VALUES (1, CAST(']]..uuid2:str()..[[' AS UUID));
+        INSERT INTO t10 VALUES (2, NULL);
+        SELECT * FROM t10;
     ]], {
-        4, ''
+        1, uuid2, 2, ''
     })
 
 -- Check that UPDATE of UUID field works.
 test:do_execsql_test(
-    "uuid-10.2.1",
-    [[
-        UPDATE t10 SET u = '11111111-3333-1111-1111-111111111111' WHERE i = 1;
-        SELECT * FROM t10 WHERE i = 1;
-    ]], {
-        1, uuid3
-    })
-
-test:do_execsql_test(
-    "uuid-10.2.2",
+    "uuid-10.2",
     [[
-        UPDATE t10 SET u = x'11111111333311111111111111111111' WHERE i = 2;
-        SELECT * FROM t10 WHERE i = 2;
+        UPDATE t10 SET u = CAST(']]..uuid3:str()..[[' AS UUID) WHERE i = 1;
+        SELECT * FROM t10;
     ]], {
-        2, uuid3
+        1, uuid3, 2, ''
     })
 
 -- Check that JOIN by UUID field works.
diff --git a/test/sql/types.result b/test/sql/types.result
index 8da94d126..25d4dbefc 100644
--- a/test/sql/types.result
+++ b/test/sql/types.result
@@ -2580,7 +2580,8 @@ box.execute([[UPDATE td SET d = 11 WHERE a = 1;]])
 ...
 box.execute([[UPDATE td SET d = 100000000000000001 WHERE a = 1;]])
 ---
-- row_count: 1
+- null
+- 'Type mismatch: can not convert integer(100000000000000001) to double'
 ...
 box.execute([[UPDATE td SET d = 22.2 WHERE a = 1;]])
 ---
-- 
2.25.1


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

* [Tarantool-patches] [PATCH v1 2/7] sql: remove implicit cast from comparison opcodes
  2021-07-28 20:51 [Tarantool-patches] [PATCH v1 0/7] Rework implicit cast Mergen Imeev via Tarantool-patches
  2021-07-28 20:51 ` [Tarantool-patches] [PATCH v1 1/7] sql: rework implicit cast fo assignment Mergen Imeev via Tarantool-patches
@ 2021-07-28 20:51 ` Mergen Imeev via Tarantool-patches
  2021-08-04 22:24   ` Vladislav Shpilevoy via Tarantool-patches
  2021-07-28 20:51 ` [Tarantool-patches] [PATCH v1 3/7] sql: rework OP_Seek* opcodes Mergen Imeev via Tarantool-patches
                   ` (4 subsequent siblings)
  6 siblings, 1 reply; 25+ messages in thread
From: Mergen Imeev via Tarantool-patches @ 2021-07-28 20:51 UTC (permalink / raw)
  To: v.shpilevoy; +Cc: tarantool-patches

After this patch, the new rules will be applied to implicit cast during
comparison where index is not used. Essentially it means that implicit
cast from STRING to number during such comparisons was removed.

Part of #4230
Part of #4470
---
 src/box/sql/func.c                    |  27 +--
 src/box/sql/mem.c                     | 293 ++++++++++++--------------
 src/box/sql/mem.h                     |  51 +----
 src/box/sql/vdbe.c                    | 257 ++++++----------------
 src/box/sql/where.c                   |  20 +-
 test/sql-tap/cast.test.lua            |  39 +++-
 test/sql-tap/func5.test.lua           |   6 +-
 test/sql-tap/identifier_case.test.lua |  10 +-
 test/sql-tap/in1.test.lua             |  15 +-
 test/sql-tap/index1.test.lua          |   4 +-
 test/sql-tap/insert3.test.lua         |   2 +-
 test/sql-tap/join.test.lua            |   4 +-
 test/sql-tap/misc1.test.lua           |  45 ++--
 test/sql-tap/select1.test.lua         |   6 +-
 test/sql-tap/select7.test.lua         |   2 +-
 test/sql-tap/sql-errors.test.lua      |   4 +-
 test/sql-tap/subquery.test.lua        |   4 +-
 test/sql-tap/tkt-9a8b09f8e6.test.lua  |  32 +--
 test/sql-tap/tkt3493.test.lua         |  54 ++---
 test/sql-tap/transitive1.test.lua     |  16 +-
 test/sql-tap/uuid.test.lua            |   4 +-
 test/sql-tap/where2.test.lua          | 143 +------------
 test/sql-tap/where5.test.lua          |  12 +-
 test/sql/boolean.result               | 220 +++++++++----------
 test/sql/types.result                 |   7 +-
 25 files changed, 472 insertions(+), 805 deletions(-)

diff --git a/src/box/sql/func.c b/src/box/sql/func.c
index 6ca852dec..9a96e96ff 100644
--- a/src/box/sql/func.c
+++ b/src/box/sql/func.c
@@ -144,15 +144,7 @@ minmaxFunc(sql_context * context, int argc, sql_value ** argv)
 	for (i = 1; i < argc; i++) {
 		if (mem_is_null(argv[i]))
 			return;
-		int res;
-		if (mem_cmp_scalar(argv[iBest], argv[i], &res, pColl) != 0) {
-			diag_set(ClientError, ER_SQL_TYPE_MISMATCH,
-				 mem_str(argv[i]),
-				 mem_type_to_str(argv[iBest]));
-			context->is_aborted = true;
-			return;
-		}
-		if ((res ^ mask) >= 0)
+		if ((mem_cmp_scalar(argv[iBest], argv[i], pColl) ^ mask) >= 0)
 			iBest = i;
 	}
 	sql_result_value(context, argv[iBest]);
@@ -1059,14 +1051,7 @@ nullifFunc(sql_context * context, int NotUsed, sql_value ** argv)
 {
 	struct coll *pColl = sqlGetFuncCollSeq(context);
 	UNUSED_PARAMETER(NotUsed);
-	int res;
-	if (mem_cmp_scalar(argv[0], argv[1], &res, pColl) != 0) {
-		diag_set(ClientError, ER_SQL_TYPE_MISMATCH,
-			 mem_str(argv[1]), mem_type_to_str(argv[0]));
-		context->is_aborted = true;
-		return;
-	}
-	if (res != 0)
+	if (mem_cmp_scalar(argv[0], argv[1], pColl) != 0)
 		sql_result_value(context, argv[0]);
 }
 
@@ -1826,7 +1811,6 @@ minmaxStep(sql_context * context, int NotUsed, sql_value ** argv)
 		if (!mem_is_null(pBest))
 			sqlSkipAccumulatorLoad(context);
 	} else if (!mem_is_null(pBest)) {
-		int cmp;
 		struct coll *pColl = sqlGetFuncCollSeq(context);
 		/*
 		 * This step function is used for both the min()
@@ -1835,12 +1819,7 @@ minmaxStep(sql_context * context, int NotUsed, sql_value ** argv)
 		 * comparison is inverted.
 		 */
 		bool is_max = (func->flags & SQL_FUNC_MAX) != 0;
-		if (mem_cmp_scalar(pBest, pArg, &cmp, pColl) != 0) {
-			diag_set(ClientError, ER_SQL_TYPE_MISMATCH,
-				 mem_str(pArg), mem_type_to_str(pBest));
-			context->is_aborted = true;
-			return;
-		}
+		int cmp = mem_cmp_scalar(pBest, pArg, pColl);
 		if ((is_max && cmp < 0) || (!is_max && cmp > 0)) {
 			mem_copy(pBest, pArg);
 		} else {
diff --git a/src/box/sql/mem.c b/src/box/sql/mem.c
index e804dba67..b04303be2 100644
--- a/src/box/sql/mem.c
+++ b/src/box/sql/mem.c
@@ -164,6 +164,30 @@ mem_str(const struct Mem *mem)
 	}
 }
 
+static const char *
+mem_type_class_to_str(const struct Mem *mem)
+{
+	switch (mem->type) {
+	case MEM_TYPE_NULL:
+		return "NULL";
+	case MEM_TYPE_UINT:
+	case MEM_TYPE_INT:
+	case MEM_TYPE_DOUBLE:
+		return "number";
+	case MEM_TYPE_STR:
+		return "string";
+	case MEM_TYPE_BIN:
+		return "varbinary";
+	case MEM_TYPE_BOOL:
+		return "boolean";
+	case MEM_TYPE_UUID:
+		return "uuid";
+	default:
+		break;
+	}
+	return "unknown";
+}
+
 void
 mem_create(struct Mem *mem)
 {
@@ -1976,25 +2000,21 @@ mem_bit_not(const struct Mem *mem, struct Mem *result)
 	return 0;
 }
 
-int
-mem_cmp_bool(const struct Mem *a, const struct Mem *b, int *result)
+static int
+mem_cmp_bool(const struct Mem *a, const struct Mem *b)
 {
-	if ((a->type & b->type & MEM_TYPE_BOOL) == 0)
-		return -1;
+	assert((a->type & b->type & MEM_TYPE_BOOL) != 0);
 	if (a->u.b == b->u.b)
-		*result = 0;
-	else if (a->u.b)
-		*result = 1;
-	else
-		*result = -1;
-	return 0;
+		return 0;
+	if (a->u.b)
+		return 1;
+	return -1;
 }
 
-int
-mem_cmp_bin(const struct Mem *a, const struct Mem *b, int *result)
+static int
+mem_cmp_bin(const struct Mem *a, const struct Mem *b)
 {
-	if ((a->type & b->type & MEM_TYPE_BIN) == 0)
-		return -1;
+	assert((a->type & b->type & MEM_TYPE_BIN) != 0);
 	int an = a->n;
 	int bn = b->n;
 	int minlen = MIN(an, bn);
@@ -2008,181 +2028,105 @@ mem_cmp_bin(const struct Mem *a, const struct Mem *b, int *result)
 	assert((a->flags & MEM_Zero) == 0 || an == 0);
 	assert((b->flags & MEM_Zero) == 0 || bn == 0);
 
-	if ((a->flags & b->flags & MEM_Zero) != 0) {
-		*result = a->u.nZero - b->u.nZero;
-		return 0;
-	}
+	if ((a->flags & b->flags & MEM_Zero) != 0)
+		return a->u.nZero - b->u.nZero;
 	if ((a->flags & MEM_Zero) != 0) {
 		for (int i = 0; i < minlen; ++i) {
-			if (b->z[i] != 0) {
-				*result = -1;
-				return 0;
-			}
+			if (b->z[i] != 0)
+				return -1;
 		}
-		*result = a->u.nZero - bn;
-		return 0;
+		return a->u.nZero - bn;
 	}
 	if ((b->flags & MEM_Zero) != 0) {
 		for (int i = 0; i < minlen; ++i) {
-			if (a->z[i] != 0){
-				*result = 1;
-				return 0;
-			}
+			if (a->z[i] != 0)
+				return 1;
 		}
-		*result = b->u.nZero - an;
-		return 0;
+		return b->u.nZero - an;
 	}
-	*result = memcmp(a->z, b->z, minlen);
-	if (*result != 0)
-		return 0;
-	*result = an - bn;
-	return 0;
+	int res = memcmp(a->z, b->z, minlen);
+	return res != 0 ? res : an - bn;
 }
 
-int
-mem_cmp_num(const struct Mem *left, const struct Mem *right, int *result)
+static int
+mem_cmp_num(const struct Mem *a, const struct Mem *b)
 {
-	struct sql_num a, b;
-	/* TODO: Here should be check for right value type. */
-	if (get_number(right, &b) != 0) {
-		*result = -1;
+	assert(mem_is_num(a) && mem_is_num(b));
+	if ((a->type & b->type & MEM_TYPE_DOUBLE) != 0) {
+		if (a->u.r > b->u.r)
+			return 1;
+		if (a->u.r < b->u.r)
+			return -1;
 		return 0;
 	}
-	if (get_number(left, &a) != 0)
-		return -1;
-	if (a.type == MEM_TYPE_DOUBLE) {
-		if (b.type == MEM_TYPE_DOUBLE) {
-			if (a.d > b.d)
-				*result = 1;
-			else if (a.d < b.d)
-				*result = -1;
-			else
-				*result = 0;
-			return 0;
-		}
-		if (b.type == MEM_TYPE_INT)
-			*result = double_compare_nint64(a.d, b.i, 1);
-		else
-			*result = double_compare_uint64(a.d, b.u, 1);
+	if ((a->type & b->type & MEM_TYPE_INT) != 0) {
+		if (a->u.i > b->u.i)
+			return 1;
+		if (a->u.i < b->u.i)
+			return -1;
 		return 0;
 	}
-	if (a.type == MEM_TYPE_INT) {
-		if (b.type == MEM_TYPE_INT) {
-			if (a.i > b.i)
-				*result = 1;
-			else if (a.i < b.i)
-				*result = -1;
-			else
-				*result = 0;
-			return 0;
-		}
-		if (b.type == MEM_TYPE_UINT)
-			*result = -1;
-		else
-			*result = double_compare_nint64(b.d, a.i, -1);
+	if ((a->type & b->type & MEM_TYPE_UINT) != 0) {
+		if (a->u.u > b->u.u)
+			return 1;
+		if (a->u.u < b->u.u)
+			return -1;
 		return 0;
 	}
-	assert(a.type == MEM_TYPE_UINT);
-	if (b.type == MEM_TYPE_UINT) {
-		if (a.u > b.u)
-			*result = 1;
-		else if (a.u < b.u)
-			*result = -1;
-		else
-			*result = 0;
-		return 0;
+	if (a->type == MEM_TYPE_DOUBLE) {
+		if (b->type == MEM_TYPE_INT)
+			return double_compare_nint64(a->u.r, b->u.i, 1);
+		return double_compare_uint64(a->u.r, b->u.u, 1);
 	}
-	if (b.type == MEM_TYPE_INT)
-		*result = 1;
-	else
-		*result = double_compare_uint64(b.d, a.u, -1);
-	return 0;
+	if (b->type == MEM_TYPE_DOUBLE) {
+		if (a->type == MEM_TYPE_INT)
+			return double_compare_nint64(b->u.r, a->u.i, -1);
+		return double_compare_uint64(b->u.r, a->u.u, -1);
+	}
+	if (a->type == MEM_TYPE_INT)
+		return -1;
+	assert(a->type == MEM_TYPE_UINT && b->type == MEM_TYPE_INT);
+	return 1;
 }
 
-int
-mem_cmp_str(const struct Mem *left, const struct Mem *right, int *result,
-	    const struct coll *coll)
-{
-	char *a;
-	uint32_t an;
-	char bufl[BUF_SIZE];
-	if (left->type == MEM_TYPE_STR) {
-		a = left->z;
-		an = left->n;
-	} else {
-		assert(mem_is_num(left));
-		a = &bufl[0];
-		if (left->type == MEM_TYPE_INT)
-			sql_snprintf(BUF_SIZE, a, "%lld", left->u.i);
-		else if (left->type == MEM_TYPE_UINT)
-			sql_snprintf(BUF_SIZE, a, "%llu", left->u.u);
-		else
-			sql_snprintf(BUF_SIZE, a, "%!.15g", left->u.r);
-		an = strlen(a);
-	}
-
-	char *b;
-	uint32_t bn;
-	char bufr[BUF_SIZE];
-	if (right->type == MEM_TYPE_STR) {
-		b = right->z;
-		bn = right->n;
-	} else {
-		assert(mem_is_num(right));
-		b = &bufr[0];
-		if (right->type == MEM_TYPE_INT)
-			sql_snprintf(BUF_SIZE, b, "%lld", right->u.i);
-		else if (right->type == MEM_TYPE_UINT)
-			sql_snprintf(BUF_SIZE, b, "%llu", right->u.u);
-		else
-			sql_snprintf(BUF_SIZE, b, "%!.15g", right->u.r);
-		bn = strlen(b);
-	}
-	if (coll != NULL) {
-		*result = coll->cmp(a, an, b, bn, coll);
-		return 0;
-	}
-	uint32_t minlen = MIN(an, bn);
-	*result = memcmp(a, b, minlen);
-	if (*result != 0)
-		return 0;
-	*result = an - bn;
-	return 0;
+static int
+mem_cmp_str(const struct Mem *a, const struct Mem *b, const struct coll *coll)
+{
+	assert((a->type & b->type & MEM_TYPE_STR) != 0);
+	if (coll != NULL)
+		return coll->cmp(a->z, a->n, b->z, b->n, coll);
+	int res = memcmp(a->z, b->z, MIN(a->n, b->n));
+	return res != 0 ? res : a->n - b->n;
 }
 
-int
-mem_cmp_uuid(const struct Mem *a, const struct Mem *b, int *result)
+static int
+mem_cmp_uuid(const struct Mem *a, const struct Mem *b)
 {
-	if ((a->type & b->type & MEM_TYPE_UUID) == 0)
-		return -1;
-	*result = memcmp(&a->u.uuid, &b->u.uuid, UUID_LEN);
-	return 0;
+	assert((a->type & b->type & MEM_TYPE_UUID) != 0);
+	return memcmp(&a->u.uuid, &b->u.uuid, UUID_LEN);
 }
 
 int
-mem_cmp_scalar(const struct Mem *a, const struct Mem *b, int *result,
+mem_cmp_scalar(const struct Mem *a, const struct Mem *b,
 	       const struct coll *coll)
 {
 	enum mem_class class_a = mem_type_class(a->type);
 	enum mem_class class_b = mem_type_class(b->type);
-	if (class_a != class_b) {
-		*result = class_a - class_b;
-		return 0;
-	}
+	if (class_a != class_b)
+		return class_a - class_b;
 	switch (class_a) {
 	case MEM_CLASS_NULL:
-		*result = 0;
 		return 0;
 	case MEM_CLASS_BOOL:
-		return mem_cmp_bool(a, b, result);
+		return mem_cmp_bool(a, b);
 	case MEM_CLASS_NUMBER:
-		return mem_cmp_num(a, b, result);
+		return mem_cmp_num(a, b);
 	case MEM_CLASS_STR:
-		return mem_cmp_str(a, b, result, coll);
+		return mem_cmp_str(a, b, coll);
 	case MEM_CLASS_BIN:
-		return mem_cmp_bin(a, b, result);
+		return mem_cmp_bin(a, b);
 	case MEM_CLASS_UUID:
-		return mem_cmp_uuid(a, b, result);
+		return mem_cmp_uuid(a, b);
 	default:
 		unreachable();
 	}
@@ -2245,8 +2189,11 @@ mem_cmp_msgpack(const struct Mem *a, const char **b, int *result,
 		if (type == MP_UUID) {
 			assert(len == UUID_LEN);
 			mem.type = MEM_TYPE_UUID;
-			if (uuid_unpack(b, len, &mem.u.uuid) == NULL)
+			if (uuid_unpack(b, len, &mem.u.uuid) == NULL) {
+				diag_set(ClientError, ER_SQL_EXECUTE,
+					 "cannot parse UUID");
 				return -1;
+			}
 			break;
 		}
 		*b += len;
@@ -2259,7 +2206,47 @@ mem_cmp_msgpack(const struct Mem *a, const char **b, int *result,
 	default:
 		unreachable();
 	}
-	return mem_cmp_scalar(a, &mem, result, coll);
+	*result = mem_cmp_scalar(a, &mem, coll);
+	return 0;
+}
+
+int
+mem_cmp(const struct Mem *a, const struct Mem *b, int *result,
+	const struct coll *coll)
+{
+	enum mem_class class_a = mem_type_class(a->type);
+	enum mem_class class_b = mem_type_class(b->type);
+	if (mem_is_any_null(a, b)) {
+		*result = class_a - class_b;
+		if ((a->flags & b->flags & MEM_Cleared) != 0)
+			*result = 1;
+		return 0;
+	}
+	if (class_a != class_b) {
+		diag_set(ClientError, ER_SQL_TYPE_MISMATCH, mem_str(b),
+			 mem_type_class_to_str(a));
+		return -1;
+	}
+	switch (class_a) {
+	case MEM_CLASS_BOOL:
+		*result =  mem_cmp_bool(a, b);
+		break;
+	case MEM_CLASS_NUMBER:
+		*result = mem_cmp_num(a, b);
+		break;
+	case MEM_CLASS_STR:
+		*result = mem_cmp_str(a, b, coll);
+		break;
+	case MEM_CLASS_BIN:
+		*result = mem_cmp_bin(a, b);
+		break;
+	case MEM_CLASS_UUID:
+		*result = mem_cmp_uuid(a, b);
+		break;
+	default:
+		unreachable();
+	}
+	return 0;
 }
 
 char *
diff --git a/src/box/sql/mem.h b/src/box/sql/mem.h
index 9766bb836..47a940c56 100644
--- a/src/box/sql/mem.h
+++ b/src/box/sql/mem.h
@@ -657,53 +657,12 @@ mem_shift_right(const struct Mem *left, const struct Mem *right,
 int
 mem_bit_not(const struct Mem *mem, struct Mem *result);
 
-/**
- * Compare two MEMs and return the result of comparison. MEMs should be of
- * BOOLEAN type or their values are converted to VARBINARY according to implicit
- * cast rules. Original MEMs are not changed.
- */
-int
-mem_cmp_bool(const struct Mem *a, const struct Mem *b, int *result);
-
-/**
- * Compare two MEMs and return the result of comparison. MEMs should be of
- * VARBINARY type or their values are converted to VARBINARY according to
- * implicit cast rules. Original MEMs are not changed.
- */
-int
-mem_cmp_bin(const struct Mem *a, const struct Mem *b, int *result);
-
-/**
- * Compare two MEMs and return the result of comparison. MEMs should be of
- * STRING type or their values are converted to VARBINARY according to
- * implicit cast rules. Original MEMs are not changed.
- */
-int
-mem_cmp_str(const struct Mem *left, const struct Mem *right, int *result,
-	    const struct coll *coll);
-
-/**
- * Compare two MEMs and return the result of comparison. MEMs should be of
- * NUMBER type or their values are converted to NUMBER according to
- * implicit cast rules. Original MEMs are not changed.
- */
-int
-mem_cmp_num(const struct Mem *a, const struct Mem *b, int *result);
-
-/**
- * Compare two MEMs and return the result of comparison. MEMs should be of
- * UUID type or their values are converted to UUID according to
- * implicit cast rules. Original MEMs are not changed.
- */
-int
-mem_cmp_uuid(const struct Mem *left, const struct Mem *right, int *result);
-
 /**
  * Compare two MEMs using SCALAR rules and return the result of comparison. MEMs
  * should be scalars. Original MEMs are not changed.
  */
 int
-mem_cmp_scalar(const struct Mem *a, const struct Mem *b, int *result,
+mem_cmp_scalar(const struct Mem *a, const struct Mem *b,
 	       const struct coll *coll);
 
 /**
@@ -716,6 +675,14 @@ int
 mem_cmp_msgpack(const struct Mem *a, const char **b, int *result,
 		const struct coll *coll);
 
+/**
+ * Compare two MEMs using implicit cast rules and return the result of
+ * comparison. MEMs should be scalars. Original MEMs are not changed.
+ */
+int
+mem_cmp(const struct Mem *a, const struct Mem *b, int *result,
+	const struct coll *coll);
+
 /**
  * Convert the given MEM to INTEGER. This function and the function below define
  * the rules that are used to convert values of all other types to INTEGER. In
diff --git a/src/box/sql/vdbe.c b/src/box/sql/vdbe.c
index d143ce364..62f58def9 100644
--- a/src/box/sql/vdbe.c
+++ b/src/box/sql/vdbe.c
@@ -1500,86 +1500,42 @@ case OP_Cast: {                  /* in1 */
 /* Opcode: Eq P1 P2 P3 P4 P5
  * Synopsis: IF r[P3]==r[P1]
  *
- * Compare the values in register P1 and P3.  If reg(P3)==reg(P1) then
- * jump to address P2.  Or if the SQL_STOREP2 flag is set in P5, then
- * store the result of comparison in register P2.
- *
- * Once any conversions have taken place, and neither value is NULL,
- * the values are compared. If both values are blobs then memcmp() is
- * used to determine the results of the comparison.  If both values
- * are text, then the appropriate collating function specified in
- * P4 is used to do the comparison.  If P4 is not specified then
- * memcmp() is used to compare text string.  If both values are
- * numeric, then a numeric comparison is used. If the two values
- * are of different types, then numbers are considered less than
- * strings and strings are considered less than blobs.
- *
- * If SQL_NULLEQ is set in P5 then the result of comparison is always either
- * true or false and is never NULL.  If both operands are NULL then the result
- * of comparison is true.  If either operand is NULL then the result is false.
- * If neither operand is NULL the result is the same as it would be if
- * the SQL_NULLEQ flag were omitted from P5.
- * P5 also can contain type to be applied to operands. Note that
- * the type conversions are stored back into the input registers
- * P1 and P3.  So this opcode can cause persistent changes to
- * registers P1 and P3.
- *
- * If both SQL_STOREP2 and SQL_KEEPNULL flags are set then the
- * content of r[P2] is only changed if the new value is NULL or false.
- * In other words, a prior r[P2] value will not be overwritten by true.
+ * Compare the values in register P1 and P3. If r[P3] == r[P1], then the action
+ * is performed. The action is to jump to address P2 or store the comparison
+ * result in register P2 if the SQL_STOREP2 flag is set in P5. In case both
+ * values are STRINGs and collation is used for comparison, the collation is
+ * specified in P4. If SQL_NULLEQ is set in P5, then the result of the
+ * comparison is always either TRUE or FALSE and will never be NULL.
  */
 /* Opcode: Ne P1 P2 P3 P4 P5
  * Synopsis: IF r[P3]!=r[P1]
  *
- * This works just like the Eq opcode except that the jump is taken if
- * the operands in registers P1 and P3 are not equal.  See the Eq opcode for
- * additional information.
- *
- * If both SQL_STOREP2 and SQL_KEEPNULL flags are set then the
- * content of r[P2] is only changed if the new value is NULL or true.
- * In other words, a prior r[P2] value will not be overwritten by false.
+ * This works just like the Eq opcode except that the action is performed if
+ * r[P3] != r[P1]. See the Eq opcode for additional information.
  */
 /* Opcode: Lt P1 P2 P3 P4 P5
  * Synopsis: IF r[P3]<r[P1]
  *
- * Compare the values in register P1 and P3.  If reg(P3)<reg(P1) then
- * jump to address P2.  Or if the SQL_STOREP2 flag is set in P5 store
- * the result of comparison (false or true or NULL) into register P2.
- *
- * If the SQL_JUMPIFNULL bit of P5 is set and either reg(P1) or
- * reg(P3) is NULL then the take the jump.  If the SQL_JUMPIFNULL
- * bit is clear then fall through if either operand is NULL.
- *
- * Once any conversions have taken place, and neither value is NULL,
- * the values are compared. If both values are blobs then memcmp() is
- * used to determine the results of the comparison.  If both values
- * are text, then the appropriate collating function specified in
- * P4 is  used to do the comparison.  If P4 is not specified then
- * memcmp() is used to compare text string.  If both values are
- * numeric, then a numeric comparison is used. If the two values
- * are of different types, then numbers are considered less than
- * strings and strings are considered less than blobs.
+ * This works just like the Eq opcode except that the action is performed if
+ * r[P3] < r[P1]. See the Eq opcode for additional information.
  */
 /* Opcode: Le P1 P2 P3 P4 P5
  * Synopsis: IF r[P3]<=r[P1]
  *
- * This works just like the Lt opcode except that the jump is taken if
- * the content of register P3 is less than or equal to the content of
- * register P1.  See the Lt opcode for additional information.
+ * This works just like the Eq opcode except that the action is performed if
+ * r[P3] <= r[P1]. See the Eq opcode for additional information.
  */
 /* Opcode: Gt P1 P2 P3 P4 P5
  * Synopsis: IF r[P3]>r[P1]
  *
- * This works just like the Lt opcode except that the jump is taken if
- * the content of register P3 is greater than the content of
- * register P1.  See the Lt opcode for additional information.
+ * This works just like the Eq opcode except that the action is performed if
+ * r[P3] > r[P1]. See the Eq opcode for additional information.
  */
 /* Opcode: Ge P1 P2 P3 P4 P5
  * Synopsis: IF r[P3]>=r[P1]
  *
- * This works just like the Lt opcode except that the jump is taken if
- * the content of register P3 is greater than or equal to the content of
- * register P1.  See the Lt opcode for additional information.
+ * This works just like the Eq opcode except that the action is performed if
+ * r[P3] >= r[P1]. See the Eq opcode for additional information.
  */
 case OP_Eq:               /* same as TK_EQ, jump, in1, in3 */
 case OP_Ne:               /* same as TK_NE, jump, in1, in3 */
@@ -1587,144 +1543,63 @@ case OP_Lt:               /* same as TK_LT, jump, in1, in3 */
 case OP_Le:               /* same as TK_LE, jump, in1, in3 */
 case OP_Gt:               /* same as TK_GT, jump, in1, in3 */
 case OP_Ge: {             /* same as TK_GE, jump, in1, in3 */
-	int res, res2;      /* Result of the comparison of pIn1 against pIn3 */
-
 	pIn1 = &aMem[pOp->p1];
 	pIn3 = &aMem[pOp->p3];
-	enum field_type type = pOp->p5 & FIELD_TYPE_MASK;
-	if (mem_is_any_null(pIn1, pIn3)) {
-		/* One or both operands are NULL */
-		if (pOp->p5 & SQL_NULLEQ) {
-			/* If SQL_NULLEQ is set (which will only happen if the operator is
-			 * OP_Eq or OP_Ne) then take the jump or not depending on whether
-			 * or not both operands are null.
-			 */
-			assert(pOp->opcode==OP_Eq || pOp->opcode==OP_Ne);
-			assert(!mem_is_cleared(pIn1));
-			assert((pOp->p5 & SQL_JUMPIFNULL)==0);
-			if (mem_is_same_type(pIn1, pIn3) &&
-			    !mem_is_cleared(pIn3)) {
-				res = 0;  /* Operands are equal */
-			} else {
-				res = 1;  /* Operands are not equal */
-			}
-		} else {
-			/* SQL_NULLEQ is clear and at least one operand is NULL,
-			 * then the result is always NULL.
-			 * The jump is taken if the SQL_JUMPIFNULL bit is set.
-			 */
-			if (pOp->p5 & SQL_STOREP2) {
-				pOut = vdbe_prepare_null_out(p, pOp->p2);
-				iCompare = 1;    /* Operands are not equal */
-				REGISTER_TRACE(p, pOp->p2, pOut);
-			} else {
-				VdbeBranchTaken(2,3);
-				if (pOp->p5 & SQL_JUMPIFNULL) {
-					goto jump_to_p2;
-				}
-			}
+	if (mem_is_any_null(pIn1, pIn3) && (pOp->p5 & SQL_NULLEQ) == 0) {
+		/*
+		 * SQL_NULLEQ is clear and at least one operand is NULL, then
+		 * the result is always NULL. The jump is taken if the
+		 * SQL_JUMPIFNULL bit is set.
+		 */
+		if ((pOp->p5 & SQL_STOREP2) != 0) {
+			pOut = vdbe_prepare_null_out(p, pOp->p2);
+			iCompare = 1;
+			REGISTER_TRACE(p, pOp->p2, pOut);
 			break;
 		}
-	} else if (mem_is_bool(pIn3) || mem_is_bool(pIn1)) {
-		if (mem_cmp_bool(pIn3, pIn1, &res) != 0) {
-			const char *str = !mem_is_bool(pIn3) ?
-					  mem_str(pIn3) : mem_str(pIn1);
-			diag_set(ClientError, ER_SQL_TYPE_MISMATCH, str,
-				 "boolean");
-			goto abort_due_to_error;
-		}
-	} else if (((pIn3->type | pIn1->type) & MEM_TYPE_UUID) != 0) {
-		if (mem_cmp_uuid(pIn3, pIn1, &res) != 0) {
-			const char *str = pIn3->type != MEM_TYPE_UUID ?
-					  mem_str(pIn3) : mem_str(pIn1);
-			diag_set(ClientError, ER_SQL_TYPE_MISMATCH, str,
-				 "uuid");
-			goto abort_due_to_error;
-		}
-	} else if (mem_is_bin(pIn3) || mem_is_bin(pIn1)) {
-		if (mem_cmp_bin(pIn3, pIn1, &res) != 0) {
-			const char *str = !mem_is_bin(pIn3) ?
-					  mem_str(pIn3) : mem_str(pIn1);
-			diag_set(ClientError, ER_SQL_TYPE_MISMATCH, str,
-				 "varbinary");
-			goto abort_due_to_error;
-		}
-	} else if (mem_is_map(pIn3) || mem_is_map(pIn1) || mem_is_array(pIn3) ||
-		   mem_is_array(pIn1)) {
-		diag_set(ClientError, ER_SQL_TYPE_MISMATCH,
-			 mem_str(pIn3), mem_type_to_str(pIn1));
-		goto abort_due_to_error;
-	} else if (type == FIELD_TYPE_STRING) {
-		if (mem_cmp_str(pIn3, pIn1, &res, pOp->p4.pColl) != 0) {
-			const char *str =
-				mem_cast_implicit_old(pIn3, type) != 0 ?
-				mem_str(pIn3) : mem_str(pIn1);
-			diag_set(ClientError, ER_SQL_TYPE_MISMATCH, str,
-				 "string");
-			goto abort_due_to_error;
-		}
-	} else if (sql_type_is_numeric(type) || mem_is_num(pIn3) ||
-		   mem_is_num(pIn1)) {
-		type = FIELD_TYPE_NUMBER;
-		if (mem_cmp_num(pIn3, pIn1, &res) != 0) {
-			const char *str =
-				mem_cast_implicit_old(pIn3, type) != 0 ?
-				mem_str(pIn3) : mem_str(pIn1);
-			diag_set(ClientError, ER_SQL_TYPE_MISMATCH, str,
-				 "number");
-			goto abort_due_to_error;
-		}
-	} else {
-		type = FIELD_TYPE_STRING;
-		assert(mem_is_str(pIn3) && mem_is_same_type(pIn3, pIn1));
-		if (mem_cmp_str(pIn3, pIn1, &res, pOp->p4.pColl) != 0) {
-			const char *str =
-				mem_cast_implicit_old(pIn3, type) != 0 ?
-				mem_str(pIn3) : mem_str(pIn1);
-			diag_set(ClientError, ER_SQL_TYPE_MISMATCH, str,
-				 "string");
-			goto abort_due_to_error;
-		}
+		VdbeBranchTaken(2,3);
+		if ((pOp->p5 & SQL_JUMPIFNULL) != 0)
+			goto jump_to_p2;
+		break;
 	}
+	int cmp_res;
+	if (mem_cmp(pIn3, pIn1, &cmp_res, pOp->p4.pColl) != 0)
+		goto abort_due_to_error;
 
-	switch( pOp->opcode) {
-	case OP_Eq:    res2 = res==0;     break;
-	case OP_Ne:    res2 = res;        break;
-	case OP_Lt:    res2 = res<0;      break;
-	case OP_Le:    res2 = res<=0;     break;
-	case OP_Gt:    res2 = res>0;      break;
-	default:       res2 = res>=0;     break;
+	bool result;
+	switch(pOp->opcode) {
+	case OP_Eq:
+		result = cmp_res == 0;
+		break;
+	case OP_Ne:
+		result = cmp_res != 0;
+		break;
+	case OP_Lt:
+		result = cmp_res < 0;
+		break;
+	case OP_Le:
+		result = cmp_res <= 0;
+		break;
+	case OP_Gt:
+		result = cmp_res > 0;
+		break;
+	case OP_Ge:
+		result = cmp_res >= 0;
+		break;
+	default:
+		unreachable();
 	}
 
-	if (pOp->p5 & SQL_STOREP2) {
-		iCompare = res;
-		res2 = res2!=0;  /* For this path res2 must be exactly 0 or 1 */
-		if ((pOp->p5 & SQL_KEEPNULL)!=0) {
-			/* The KEEPNULL flag prevents OP_Eq from overwriting a NULL with true
-			 * and prevents OP_Ne from overwriting NULL with false.  This flag
-			 * is only used in contexts where either:
-			 *   (1) op==OP_Eq && (r[P2]==NULL || r[P2]==0)
-			 *   (2) op==OP_Ne && (r[P2]==NULL || r[P2]==1)
-			 * Therefore it is not necessary to check the content of r[P2] for
-			 * NULL.
-			 */
-			assert(pOp->opcode==OP_Ne || pOp->opcode==OP_Eq);
-			assert(res2==0 || res2==1);
-			testcase( res2==0 && pOp->opcode==OP_Eq);
-			testcase( res2==1 && pOp->opcode==OP_Eq);
-			testcase( res2==0 && pOp->opcode==OP_Ne);
-			testcase( res2==1 && pOp->opcode==OP_Ne);
-			if ((pOp->opcode==OP_Eq)==res2) break;
-		}
-		pOut = vdbe_prepare_null_out(p, pOp->p2);
-		mem_set_bool(pOut, res2);
+	if ((pOp->p5 & SQL_STOREP2) != 0) {
+		iCompare = cmp_res;
+		pOut = &aMem[pOp->p2];
+		mem_set_bool(pOut, result);
 		REGISTER_TRACE(p, pOp->p2, pOut);
-	} else {
-		VdbeBranchTaken(res!=0, (pOp->p5 & SQL_NULLEQ)?2:3);
-		if (res2) {
-			goto jump_to_p2;
-		}
+		break;
 	}
+	VdbeBranchTaken(result, (pOp->p5 & SQL_NULLEQ) != 0 ? 2 : 3);
+	if (result)
+		goto jump_to_p2;
 	break;
 }
 
@@ -1827,11 +1702,7 @@ case OP_Compare: {
 		bool is_rev = def->parts[i].sort_order == SORT_ORDER_DESC;
 		struct Mem *a = &aMem[p1+idx];
 		struct Mem *b = &aMem[p2+idx];
-		if (mem_cmp_scalar(a, b, &iCompare, coll) != 0) {
-			diag_set(ClientError, ER_SQL_TYPE_MISMATCH, mem_str(b),
-				 mem_type_to_str(a));
-			goto abort_due_to_error;
-		}
+		iCompare = mem_cmp_scalar(a, b, coll);
 		if (iCompare) {
 			if (is_rev)
 				iCompare = -iCompare;
diff --git a/src/box/sql/where.c b/src/box/sql/where.c
index e2eb153fb..16766f2f8 100644
--- a/src/box/sql/where.c
+++ b/src/box/sql/where.c
@@ -1272,27 +1272,11 @@ whereRangeSkipScanEst(Parse * pParse,		/* Parsing & code generating context */
 			rc = sql_stat4_column(db, samples[i].sample_key, nEq,
 					      &pVal);
 			if (rc == 0 && p1 != NULL) {
-				int res;
-				rc = mem_cmp_scalar(p1, pVal, &res, coll);
-				if (rc != 0) {
-					diag_set(ClientError,
-						 ER_SQL_TYPE_MISMATCH,
-						 mem_str(pVal),
-						 mem_type_to_str(p1));
-				}
-				if (rc == 0 && res >= 0)
+				if (mem_cmp_scalar(p1, pVal, coll) >= 0)
 					nLower++;
 			}
 			if (rc == 0 && p2 != NULL) {
-				int res;
-				rc = mem_cmp_scalar(p2, pVal, &res, coll);
-				if (rc != 0) {
-					diag_set(ClientError,
-						 ER_SQL_TYPE_MISMATCH,
-						 mem_str(pVal),
-						 mem_type_to_str(p2));
-				}
-				if (rc == 0 && res >= 0)
+				if (mem_cmp_scalar(p2, pVal, coll) >= 0)
 					nUpper++;
 			}
 		}
diff --git a/test/sql-tap/cast.test.lua b/test/sql-tap/cast.test.lua
index 8af99dbde..b8ad23317 100755
--- a/test/sql-tap/cast.test.lua
+++ b/test/sql-tap/cast.test.lua
@@ -1,6 +1,6 @@
 #!/usr/bin/env tarantool
 local test = require("sqltester")
-test:plan(103)
+test:plan(107)
 
 --!./tcltestrunner.lua
 -- 2005 June 25
@@ -1107,4 +1107,41 @@ test:execsql([[
     DROP TABLE t5;
 ]])
 
+--
+-- Make sure that implicit cast from STRING to number was removed during
+-- comparison where index is not used.
+--
+
+test:do_catchsql_test(
+    "cast-10.1",
+    [[
+        SELECT 1 < '2';
+    ]], {
+        1, "Type mismatch: can not convert string('2') to number"
+    })
+
+test:do_catchsql_test(
+    "cast-10.2",
+    [[
+        SELECT '1' < 2;
+    ]], {
+        1, "Type mismatch: can not convert integer(2) to string"
+    })
+
+test:do_catchsql_test(
+    "cast-10.3",
+    [[
+        SELECT 1.5 < '2';
+    ]], {
+        1, "Type mismatch: can not convert string('2') to number"
+    })
+
+test:do_catchsql_test(
+    "cast-10.4",
+    [[
+        SELECT '1' < 2.5;
+    ]], {
+        1, "Type mismatch: can not convert double(2.5) to string"
+    })
+
 test:finish_test()
diff --git a/test/sql-tap/func5.test.lua b/test/sql-tap/func5.test.lua
index 9b1526aaf..98a8b9ada 100755
--- a/test/sql-tap/func5.test.lua
+++ b/test/sql-tap/func5.test.lua
@@ -305,11 +305,13 @@ test:do_execsql_test(
         SELECT ifnull(null, 'qqq2') = 'qqq2';
     ]], { true } )
 
-test:do_execsql_test(
+test:do_catchsql_test(
     "func-6.3-ifnull",
     [[
         SELECT ifnull(null, 1) = 'qqq2';
-    ]], { false } )
+    ]], {
+        1, "Type mismatch: can not convert string('qqq2') to number"
+    })
 
 box.func.COUNTER1:drop()
 box.func.COUNTER2:drop()
diff --git a/test/sql-tap/identifier_case.test.lua b/test/sql-tap/identifier_case.test.lua
index 84b6078fd..097fa1b64 100755
--- a/test/sql-tap/identifier_case.test.lua
+++ b/test/sql-tap/identifier_case.test.lua
@@ -242,11 +242,11 @@ data = {
     { 2,  [[ 'a' < 'b' collate "binary" ]], {0, {true}}},
     { 3,  [[ 'a' < 'b' collate 'binary' ]], {1, [[Syntax error at line 1 near ''binary'']]}},
     { 4,  [[ 'a' < 'b' collate "unicode" ]], {0, {true}}},
-    { 5,  [[ 5 < 'b' collate "unicode" ]], {0, {true}}},
-    { 6,  [[ 5 < 'b' collate unicode ]], {1,"Collation 'UNICODE' does not exist"}},
-    { 7,  [[ 5 < 'b' collate "unicode_ci" ]], {0, {true}}},
-    { 8,  [[ 5 < 'b' collate NONE ]], {1, "Collation 'NONE' does not exist"}},
-    { 9,  [[ 5 < 'b' collate "none" ]], {0, {true}}},
+    { 5,  [[ '5' < 'b' collate "unicode" ]], {0, {true}}},
+    { 6,  [[ '5' < 'b' collate unicode ]], {1,"Collation 'UNICODE' does not exist"}},
+    { 7,  [[ '5' < 'b' collate "unicode_ci" ]], {0, {true}}},
+    { 8,  [[ '5' < 'b' collate NONE ]], {1, "Collation 'NONE' does not exist"}},
+    { 9,  [[ '5' < 'b' collate "none" ]], {0, {true}}},
 }
 
 for _, row in ipairs(data) do
diff --git a/test/sql-tap/in1.test.lua b/test/sql-tap/in1.test.lua
index bdf1f207a..14aefd15d 100755
--- a/test/sql-tap/in1.test.lua
+++ b/test/sql-tap/in1.test.lua
@@ -1,6 +1,6 @@
 #!/usr/bin/env tarantool
 local test = require("sqltester")
-test:plan(80)
+test:plan(79)
 
 --!./tcltestrunner.lua
 -- 2001 September 15
@@ -636,19 +636,6 @@ test:do_execsql_test(
         -- </in-11.1>
     })
 
-test:do_test(
-    "in-11.2",
-    function()
-        -- The '2' should be coerced into 2 because t6.b is NUMERIC
-        return test:execsql [[
-            SELECT * FROM t6 WHERE b IN ('2');
-        ]]
-    end, {
-        -- <in-11.2>
-        1, 2
-        -- </in-11.2>
-    })
-
 test:do_execsql_test(
     "in-11.5",
     [[
diff --git a/test/sql-tap/index1.test.lua b/test/sql-tap/index1.test.lua
index 6a7450bb7..3230556d1 100755
--- a/test/sql-tap/index1.test.lua
+++ b/test/sql-tap/index1.test.lua
@@ -778,7 +778,7 @@ test:do_catchsql_test(
         SELECT c FROM t6 WHERE a>123;
     ]], {
         -- <index-14.6>
-        1, "Type mismatch: can not convert string('') to number"
+        1, "Type mismatch: can not convert integer(123) to string"
         -- </index-14.6>
     })
 
@@ -788,7 +788,7 @@ test:do_catchsql_test(
         SELECT c FROM t6 WHERE a>=123;
     ]], {
         -- <index-14.7>
-        1, "Type mismatch: can not convert string('') to number"
+        1, "Type mismatch: can not convert integer(123) to string"
         -- </index-14.7>
     })
 
diff --git a/test/sql-tap/insert3.test.lua b/test/sql-tap/insert3.test.lua
index 5469fe06b..062fb1e33 100755
--- a/test/sql-tap/insert3.test.lua
+++ b/test/sql-tap/insert3.test.lua
@@ -59,7 +59,7 @@ test:do_execsql_test(
     [[
             CREATE TABLE log2(rowid INTEGER PRIMARY KEY AUTOINCREMENT, x TEXT UNIQUE,y INT );
             CREATE TRIGGER r2 BEFORE INSERT ON t1 FOR EACH ROW BEGIN
-              UPDATE log2 SET y=y+1 WHERE x=new.b;
+              UPDATE log2 SET y = y+1 WHERE x = CAST(new.b AS STRING);
               INSERT OR IGNORE INTO log2(x, y) VALUES(CAST(new.b AS STRING),1);
             END;
             INSERT INTO t1(a, b) VALUES('hi', 453);
diff --git a/test/sql-tap/join.test.lua b/test/sql-tap/join.test.lua
index 2eb884d32..48639a7b3 100755
--- a/test/sql-tap/join.test.lua
+++ b/test/sql-tap/join.test.lua
@@ -1038,13 +1038,13 @@ test:do_execsql_test(
         -- </join-11.9>
     })
 
-test:do_execsql_test(
+test:do_catchsql_test(
     "join-11.10",
     [[
         SELECT * FROM t2 NATURAL JOIN t1
     ]], {
         -- <join-11.10>
-        1, "one", 2, "two"
+        1, "Type mismatch: can not convert string('1') to number"
         -- </join-11.10>
     })
 
diff --git a/test/sql-tap/misc1.test.lua b/test/sql-tap/misc1.test.lua
index bcd8e665a..1aa55a869 100755
--- a/test/sql-tap/misc1.test.lua
+++ b/test/sql-tap/misc1.test.lua
@@ -1,6 +1,6 @@
 #!/usr/bin/env tarantool
 local test = require("sqltester")
-test:plan(59)
+test:plan(58)
 
 --!./tcltestrunner.lua
 -- 2001 September 15.
@@ -88,7 +88,7 @@ test:do_execsql_test(
 test:do_execsql_test(
     "misc1-1.4",
     [[
-        SELECT x75 FROM manycol WHERE x50=350
+        SELECT x75 FROM manycol WHERE x50 = '350'
     ]], {
         -- <misc1-1.4>
         "375"
@@ -98,7 +98,7 @@ test:do_execsql_test(
 test:do_execsql_test(
     "misc1-1.5",
     [[
-        SELECT x50 FROM manycol WHERE x99=599
+        SELECT x50 FROM manycol WHERE x99 = '599'
     ]], {
         -- <misc1-1.5>
         "550"
@@ -109,7 +109,7 @@ test:do_test(
     "misc1-1.6",
     function()
         test:execsql("CREATE INDEX manycol_idx1 ON manycol(x99)")
-        return test:execsql("SELECT x50 FROM manycol WHERE x99=899")
+        return test:execsql("SELECT x50 FROM manycol WHERE x99 = '899'")
     end, {
         -- <misc1-1.6>
         "850"
@@ -129,7 +129,7 @@ test:do_execsql_test(
 test:do_test(
     "misc1-1.8",
     function()
-        test:execsql("DELETE FROM manycol WHERE x98=1234")
+        test:execsql("DELETE FROM manycol WHERE x98 = '1234'")
         return test:execsql("SELECT count(*) FROM manycol")
     end, {
         -- <misc1-1.8>
@@ -140,7 +140,7 @@ test:do_test(
 test:do_test(
     "misc1-1.9",
     function()
-        test:execsql("DELETE FROM manycol WHERE x98=998")
+        test:execsql("DELETE FROM manycol WHERE x98 = '998'")
         return test:execsql("SELECT count(*) FROM manycol")
     end, {
         -- <misc1-1.9>
@@ -151,7 +151,7 @@ test:do_test(
 test:do_test(
     "misc1-1.10",
     function()
-        test:execsql("DELETE FROM manycol WHERE x99=500")
+        test:execsql("DELETE FROM manycol WHERE x99 = '500'")
         return test:execsql("SELECT count(*) FROM manycol")
     end, {
         -- <misc1-1.10>
@@ -162,7 +162,7 @@ test:do_test(
 test:do_test(
     "misc1-1.11",
     function()
-        test:execsql("DELETE FROM manycol WHERE x99=599")
+        test:execsql("DELETE FROM manycol WHERE x99 = '599'")
         return test:execsql("SELECT count(*) FROM manycol")
     end, {
         -- <misc1-1.11>
@@ -476,13 +476,14 @@ test:do_execsql_test(
         -- </misc1-10.0>
     })
 local where = ""
+local where_part = ""
+for i = 1, 99, 1 do
+    where_part = where_part .. " AND CAST(x"..i.." AS NUMBER) <> 0"
+end
 test:do_test(
     "misc1-10.1",
     function()
-        where = "WHERE x0>=0"
-        for i = 1, 99, 1 do
-            where = where .. " AND x"..i.."<>0"
-        end
+        where = "WHERE CAST(x0 AS NUMBER) >= 0" .. where_part
         return test:catchsql("SELECT count(*) FROM manycol "..where.."")
     end, {
         -- <misc1-10.1>
@@ -496,7 +497,7 @@ test:do_test(
 test:do_test(
     "misc1-10.3",
     function()
-        where = string.gsub(where,"x0>=0", "x0=0")
+        where = "WHERE CAST(x0 AS NUMBER) = 0" .. where_part
         return test:catchsql("DELETE FROM manycol "..where.."")
     end, {
         -- <misc1-10.3>
@@ -520,7 +521,7 @@ test:do_execsql_test(
 test:do_execsql_test(
     "misc1-10.6",
     [[
-        SELECT x1 FROM manycol WHERE x0=100
+        SELECT x1 FROM manycol WHERE x0 = '100'
     ]], {
         -- <misc1-10.6>
         "101"
@@ -530,7 +531,7 @@ test:do_execsql_test(
 test:do_test(
     "misc1-10.7",
     function()
-        where = string.gsub(where, "x0=0", "x0=100")
+        where = "WHERE CAST(x0 AS NUMBER) = 100" .. where_part
         return test:catchsql("UPDATE manycol SET x1=CAST(x1+1 AS STRING) "..where.."")
     end, {
         -- <misc1-10.7>
@@ -541,7 +542,7 @@ test:do_test(
 test:do_execsql_test(
     "misc1-10.8",
     [[
-        SELECT x1 FROM manycol WHERE x0=100
+        SELECT x1 FROM manycol WHERE x0 = '100'
     ]], {
         -- <misc1-10.8>
         "102"
@@ -563,7 +564,7 @@ test:do_execsql_test(
 test:do_execsql_test(
     "misc1-10.10",
     [[
-        SELECT x1 FROM manycol WHERE x0=100
+        SELECT x1 FROM manycol WHERE x0 = '100'
     ]], {
         -- <misc1-10.10>
         "103"
@@ -627,16 +628,6 @@ test:do_execsql_test(
         -- </misc1-12.1>
     })
 
-test:do_execsql_test(
-    "misc1-12.2",
-    [[
-        SELECT '0'==0.0
-    ]], {
-        -- <misc1-12.2>
-        true
-        -- </misc1-12.2>
-    })
-
 test:do_execsql_test(
     "misc1-12.3",
     [[
diff --git a/test/sql-tap/select1.test.lua b/test/sql-tap/select1.test.lua
index 06641a60e..dbc6e193d 100755
--- a/test/sql-tap/select1.test.lua
+++ b/test/sql-tap/select1.test.lua
@@ -320,7 +320,7 @@ test:do_catchsql_test(
         SELECT count(*),count(a),count(b) FROM t4 WHERE b=5
     ]], {
         -- <select1-2.5.3>
-        1, "Type mismatch: can not convert string('This is a string that is too big to fit inside a NBFS buffer') to number"
+        1, "Type mismatch: can not convert integer(5) to string"
         -- </select1-2.5.3>
     })
 
@@ -1916,7 +1916,7 @@ test:do_execsql_test(
 test:do_execsql_test(
         "select1-12.7",
         [[
-            SELECT * FROM t3 WHERE a=(SELECT 1);
+            SELECT * FROM t3 WHERE a = (SELECT '1');
         ]], {
             -- <select1-12.7>
             0, "1", "2"
@@ -1926,7 +1926,7 @@ test:do_execsql_test(
 test:do_execsql_test(
     "select1-12.8",
     [[
-        SELECT * FROM t3 WHERE a=(SELECT 2);
+        SELECT * FROM t3 WHERE a = (SELECT '2');
     ]], {
         -- <select1-12.8>
 
diff --git a/test/sql-tap/select7.test.lua b/test/sql-tap/select7.test.lua
index 6b8f385e9..815f9110b 100755
--- a/test/sql-tap/select7.test.lua
+++ b/test/sql-tap/select7.test.lua
@@ -255,7 +255,7 @@ test:do_execsql_test(
         DROP TABLE IF EXISTS t5;
         CREATE TABLE t5(a TEXT primary key, b INT);
         INSERT INTO t5 VALUES('123', 456);
-        SELECT typeof(a), a FROM t5 GROUP BY a HAVING a<b;
+        SELECT typeof(a), a FROM t5 GROUP BY a HAVING CAST(a AS NUMBER) < b;
     ]], {
         -- <select7-7.7>
         "string", "123"
diff --git a/test/sql-tap/sql-errors.test.lua b/test/sql-tap/sql-errors.test.lua
index 17fe4b5b2..409c97257 100755
--- a/test/sql-tap/sql-errors.test.lua
+++ b/test/sql-tap/sql-errors.test.lua
@@ -766,7 +766,7 @@ test:do_catchsql_test(
 		SELECT X'ff' >= false;
 	]], {
 		-- <sql-errors-2.8>
-		1, "Type mismatch: can not convert varbinary(x'FF') to boolean"
+		1, "Type mismatch: can not convert boolean(FALSE) to varbinary"
 		-- </sql-errors-2.8>
 	})
 
@@ -776,7 +776,7 @@ test:do_catchsql_test(
 		SELECT X'ff' <= false;
 	]], {
 		-- <sql-errors-2.9>
-		1, "Type mismatch: can not convert varbinary(x'FF') to boolean"
+		1, "Type mismatch: can not convert boolean(FALSE) to varbinary"
 		-- </sql-errors-2.9>
 	})
 
diff --git a/test/sql-tap/subquery.test.lua b/test/sql-tap/subquery.test.lua
index 44032548f..1c5b3d02e 100755
--- a/test/sql-tap/subquery.test.lua
+++ b/test/sql-tap/subquery.test.lua
@@ -284,13 +284,13 @@ test:do_execsql_test(
         -- </subquery-2.3.1>
     })
 
-test:do_execsql_test(
+test:do_catchsql_test(
     "subquery-2.3.2",
     [[
         SELECT a IN (10.0, 20) FROM t3;
     ]], {
         -- <subquery-2.3.2>
-        false
+        1, "Type mismatch: can not convert string('10') to number"
         -- </subquery-2.3.2>
     })
 
diff --git a/test/sql-tap/tkt-9a8b09f8e6.test.lua b/test/sql-tap/tkt-9a8b09f8e6.test.lua
index bee4fdf6c..43322468d 100755
--- a/test/sql-tap/tkt-9a8b09f8e6.test.lua
+++ b/test/sql-tap/tkt-9a8b09f8e6.test.lua
@@ -79,23 +79,23 @@ test:do_execsql_test(
         -- </1.5>
     })
 
-test:do_execsql_test(
+test:do_catchsql_test(
     2.1,
     [[
         SELECT x FROM t1 WHERE x IN (1);
     ]], {
         -- <2.1>
-        "1"
+        1, "Type mismatch: can not convert integer(1) to string"
         -- </2.1>
     })
 
-test:do_execsql_test(
+test:do_catchsql_test(
     2.2,
     [[
         SELECT x FROM t1 WHERE x IN (1.0);
     ]], {
         -- <2.2>
-        "1"
+        1, "Type mismatch: can not convert double(1.0) to string"
         -- </2.2>
     })
 
@@ -119,23 +119,23 @@ test:do_execsql_test(
         -- </2.4>
     })
 
-test:do_execsql_test(
+test:do_catchsql_test(
     2.5,
     [[
         SELECT x FROM t1 WHERE 1 IN (x);
     ]], {
         -- <2.5>
-        "1"
+        1, "Type mismatch: can not convert integer(1) to string"
         -- </2.5>
     })
 
-test:do_execsql_test(
+test:do_catchsql_test(
     2.6,
     [[
         SELECT x FROM t1 WHERE 1.0 IN (x);
     ]], {
         -- <2.6>
-        "1"
+        1, "Type mismatch: can not convert double(1.0) to string"
         -- </2.6>
     })
 
@@ -439,23 +439,23 @@ test:do_execsql_test(
         -- </6.2>
     })
 
-test:do_execsql_test(
+test:do_catchsql_test(
     6.3,
     [[
         SELECT x, y FROM t5 WHERE x IN ('1');
     ]], {
         -- <6.3>
-        1, "one", 1, "two", 1, "three", 1.0, "four"
+        1, "Type mismatch: can not convert string('1') to number"
         -- </6.3>
     })
 
-test:do_execsql_test(
+test:do_catchsql_test(
     6.4,
     [[
         SELECT x, y FROM t5 WHERE x IN ('1.0');
     ]], {
         -- <6.4>
-        1, "one", 1, "two", 1, "three", 1.0, "four"
+        1, "Type mismatch: can not convert string('1.0') to number"
         -- </6.4>
     })
 
@@ -479,23 +479,23 @@ test:do_execsql_test(
         -- </6.6>
     })
 
-test:do_execsql_test(
+test:do_catchsql_test(
     6.7,
     [[
         SELECT x, y FROM t5 WHERE '1' IN (x);
     ]], {
         -- <6.7>
-        1, "one", 1, "two", 1, "three", 1.0, "four"
+        1, "Type mismatch: can not convert string('1') to number"
         -- </6.7>
     })
 
-test:do_execsql_test(
+test:do_catchsql_test(
     6.8,
     [[
         SELECT x, y FROM t5 WHERE '1.0' IN (x);
     ]], {
         -- <6.8>
-        1, "one", 1, "two", 1, "three", 1, "four"
+        1, "Type mismatch: can not convert string('1.0') to number"
         -- </6.8>
     })
 
diff --git a/test/sql-tap/tkt3493.test.lua b/test/sql-tap/tkt3493.test.lua
index 0aac0ddb9..77c84a994 100755
--- a/test/sql-tap/tkt3493.test.lua
+++ b/test/sql-tap/tkt3493.test.lua
@@ -45,7 +45,7 @@ test:do_execsql_test(
     [[
         SELECT
           CASE
-             WHEN B.val = 1 THEN 'XYZ'
+             WHEN B.val = '1' THEN 'XYZ'
              ELSE A.val
           END AS Col1
         FROM B
@@ -63,7 +63,7 @@ test:do_execsql_test(
     [[
         SELECT DISTINCT
           CASE
-             WHEN B.val = 1 THEN 'XYZ'
+             WHEN B.val = '1' THEN 'XYZ'
              ELSE A.val
           END AS Col1
         FROM B
@@ -79,14 +79,14 @@ test:do_execsql_test(
 test:do_execsql_test(
     "tkt3493-1.4",
     [[
-        SELECT b.val, CASE WHEN b.val = 1 THEN 'xyz' ELSE b.val END AS col1 FROM b;
+        SELECT b.val, CASE WHEN b.val = '1' THEN 'xyz' ELSE b.val END AS col1 FROM b;
     ]], {
         -- <tkt3493-1.4>
         "1", "xyz", "2", "2"
         -- </tkt3493-1.4>
     })
 
-test:do_execsql_test(
+test:do_catchsql_test(
     "tkt3493-1.5",
     [[
         SELECT DISTINCT
@@ -95,7 +95,7 @@ test:do_execsql_test(
         FROM b;
     ]], {
         -- <tkt3493-1.5>
-        "1", "xyz", "2", "2"
+        1, "Type mismatch: can not convert integer(1) to string"
         -- </tkt3493-1.5>
     })
 
@@ -123,23 +123,23 @@ test:do_execsql_test(
         -- </tkt3493-2.1>
     })
 
-test:do_execsql_test(
+test:do_catchsql_test(
     "tkt3493-2.2.1",
     [[
         SELECT a=123 FROM t1 GROUP BY a
     ]], {
         -- <tkt3493-2.2.1>
-        true
+        1, "Type mismatch: can not convert integer(123) to string"
         -- </tkt3493-2.2.1>
     })
 
-test:do_execsql_test(
+test:do_catchsql_test(
     "tkt3493-2.2.2",
     [[
         SELECT a=123 FROM t1
     ]], {
         -- <tkt3493-2.2.2>
-        true
+        1, "Type mismatch: can not convert integer(123) to string"
         -- </tkt3493-2.2.2>
     })
 
@@ -153,93 +153,93 @@ test:do_execsql_test(
         -- </tkt3493-2.2.3>
     })
 
-test:do_execsql_test(
+test:do_catchsql_test(
     "tkt3493-2.2.4",
     [[
         SELECT count(*), a=123 FROM t1
     ]], {
         -- <tkt3493-2.2.4>
-        1, true
+        1, "Type mismatch: can not convert integer(123) to string"
         -- </tkt3493-2.2.4>
     })
 
-test:do_execsql_test(
+test:do_catchsql_test(
     "tkt3493-2.2.5",
     [[
         SELECT count(*), +a=123 FROM t1
     ]], {
         -- <tkt3493-2.2.5>
-        1, true
+        1, "Type mismatch: can not convert integer(123) to string"
         -- </tkt3493-2.2.5>
     })
 
-test:do_execsql_test(
+test:do_catchsql_test(
     "tkt3493-2.3.3",
     [[
         SELECT b='456' FROM t1 GROUP BY a
     ]], {
         -- <tkt3493-2.3.3>
-        true
+        1, "Type mismatch: can not convert string('456') to number"
         -- </tkt3493-2.3.3>
     })
 
-test:do_execsql_test(
+test:do_catchsql_test(
     "tkt3493-2.3.1",
     [[
         SELECT b='456' FROM t1 GROUP BY b
     ]], {
         -- <tkt3493-2.3.1>
-        true
+        1, "Type mismatch: can not convert string('456') to number"
         -- </tkt3493-2.3.1>
     })
 
-test:do_execsql_test(
+test:do_catchsql_test(
     "tkt3493-2.3.2",
     [[
         SELECT b='456' FROM t1
     ]], {
         -- <tkt3493-2.3.2>
-        true
+        1, "Type mismatch: can not convert string('456') to number"
         -- </tkt3493-2.3.2>
     })
 
-test:do_execsql_test(
+test:do_catchsql_test(
     "tkt3493-2.4.1",
     [[
         SELECT typeof(a), a FROM t1 GROUP BY a HAVING a=123
     ]], {
         -- <tkt3493-2.4.1>
-        "string", "123"
+        1, "Type mismatch: can not convert integer(123) to string"
         -- </tkt3493-2.4.1>
     })
 
-test:do_execsql_test(
+test:do_catchsql_test(
     "tkt3493-2.4.2",
     [[
         SELECT typeof(a), a FROM t1 GROUP BY b HAVING a=123
     ]], {
         -- <tkt3493-2.4.2>
-        "string", "123"
+        1, "Type mismatch: can not convert integer(123) to string"
         -- </tkt3493-2.4.2>
     })
 
-test:do_execsql_test(
+test:do_catchsql_test(
     "tkt3493-2.5.1",
     [[
         SELECT typeof(b), b FROM t1 GROUP BY a HAVING b='456'
     ]], {
         -- <tkt3493-2.5.1>
-        "integer", 456
+        1, "Type mismatch: can not convert string('456') to number"
         -- </tkt3493-2.5.1>
     })
 
-test:do_execsql_test(
+test:do_catchsql_test(
     "tkt3493-2.5.2",
     [[
         SELECT typeof(b), b FROM t1 GROUP BY b HAVING b='456'
     ]], {
         -- <tkt3493-2.5.2>
-        "integer", 456
+        1, "Type mismatch: can not convert string('456') to number"
         -- </tkt3493-2.5.2>
     })
 
diff --git a/test/sql-tap/transitive1.test.lua b/test/sql-tap/transitive1.test.lua
index dbc2559fa..2d502f5e8 100755
--- a/test/sql-tap/transitive1.test.lua
+++ b/test/sql-tap/transitive1.test.lua
@@ -63,7 +63,7 @@ test:do_execsql_test(
         INSERT INTO t2 VALUES(2, 20,20,'20');
         INSERT INTO t2 VALUES(3, 3,3,'3');
 
-        SELECT a,b,c FROM t2 WHERE a=b AND c=b AND c=20;
+        SELECT a, b, c FROM t2 WHERE a = b AND c = '20';
     ]], {
         -- <transitive1-200>
         20, 20, "20"
@@ -73,7 +73,7 @@ test:do_execsql_test(
 test:do_execsql_test(
     "transitive1-210",
     [[
-        SELECT a,b,c FROM t2 WHERE a=b AND c=b AND c>='20' ORDER BY +a;
+        SELECT a,b,c FROM t2 WHERE a = b AND c >= '20' ORDER BY +a;
     ]], {
         -- <transitive1-210>
         3, 3, "3", 20, 20, "20"
@@ -83,7 +83,7 @@ test:do_execsql_test(
 test:do_execsql_test(
     "transitive1-220",
     [[
-        SELECT a,b,c FROM t2 WHERE a=b AND c=b AND c<='20' ORDER BY +a;
+        SELECT a, b, c FROM t2 WHERE a = b AND c <= '20' ORDER BY +a;
     ]], {
         -- <transitive1-220>
         20, 20, "20", 100, 100, "100"
@@ -402,7 +402,7 @@ test:do_execsql_test(
     [[
         CREATE TABLE x(i INTEGER PRIMARY KEY, y TEXT);
         INSERT INTO x VALUES(10, '10');
-        SELECT * FROM x WHERE x.y>='1' AND x.y<'2' AND x.i=x.y;
+        SELECT * FROM x WHERE x.y >= '1' AND x.y < '2' AND x.i = CAST(y AS INT);
     ]], {
         -- <transitive1-500>
         10, "10"
@@ -430,23 +430,23 @@ test:do_execsql_test(
     [[
         CREATE TABLE t3(i INTEGER PRIMARY KEY, t TEXT);
         INSERT INTO t3 VALUES(10, '10');
-        SELECT * FROM t3 WHERE i=t AND t = '10 ';
+        SELECT * FROM t3 WHERE i = CAST(t AS NUMBER) AND t = '10 ';
     ]], {
         -- <transitive1-520>
 
         -- </transitive1-520>
     })
 
-test:do_execsql_test(
+test:do_catchsql_test(
     "transitive1-530",
     [[
         CREATE TABLE u1(x TEXT PRIMARY KEY, y INTEGER, z TEXT);
         CREATE INDEX i1 ON u1(x);
         INSERT INTO u1 VALUES('00013', 13, '013');
-        SELECT * FROM u1 WHERE x=y AND y=z AND z='013';
+        SELECT * FROM u1 WHERE x = y AND y = z AND z = '013';
     ]], {
         -- <transitive1-530>
-        "00013",13,"013"
+        1, "Type mismatch: can not convert integer(13) to string"
         -- </transitive1-530>
     })
 
diff --git a/test/sql-tap/uuid.test.lua b/test/sql-tap/uuid.test.lua
index 613f4c865..165125e51 100755
--- a/test/sql-tap/uuid.test.lua
+++ b/test/sql-tap/uuid.test.lua
@@ -1124,7 +1124,7 @@ test:do_catchsql_test(
     [[
         SELECT u > true FROM t2;
     ]], {
-        1, "Type mismatch: can not convert uuid('11111111-1111-1111-1111-111111111111') to boolean"
+        1, "Type mismatch: can not convert boolean(TRUE) to uuid"
     })
 
 test:do_execsql_test(
@@ -1188,7 +1188,7 @@ test:do_catchsql_test(
     [[
         SELECT u = true FROM t2;
     ]], {
-        1, "Type mismatch: can not convert uuid('11111111-1111-1111-1111-111111111111') to boolean"
+        1, "Type mismatch: can not convert boolean(TRUE) to uuid"
     })
 
 test:do_execsql_test(
diff --git a/test/sql-tap/where2.test.lua b/test/sql-tap/where2.test.lua
index 58b795cd2..de060c66d 100755
--- a/test/sql-tap/where2.test.lua
+++ b/test/sql-tap/where2.test.lua
@@ -1,7 +1,7 @@
 #!/usr/bin/env tarantool
 local test = require("sqltester")
 local ffi = require("ffi")
-test:plan(74)
+test:plan(64)
 
 ffi.cdef[[
        int dup(int oldfd);
@@ -617,7 +617,7 @@ test:do_test(
     test:do_test(
         "where2-6.7",
         function()
-            test:execsql [[
+            test:catchsql [[
                 CREATE TABLE t2249a(a TEXT PRIMARY KEY, x VARCHAR(100));
                 CREATE TABLE t2249b(b INTEGER PRIMARY KEY);
                 INSERT INTO t2249a(a) VALUES('0123');
@@ -628,7 +628,7 @@ test:do_test(
     -- will attempt to convert to NUMERIC before the comparison.
     -- They will thus compare equal.
     --
-    SELECT b,a FROM t2249b CROSS JOIN t2249a WHERE a=b;
+    SELECT b, a FROM t2249b CROSS JOIN t2249a WHERE CAST(a AS INT) = b;
   ]])
         end, {
             -- <where2-6.7>
@@ -642,7 +642,7 @@ test:do_test(
             return queryplan([[
     -- The + operator doesn't affect RHS.
     --
-    SELECT b,a FROM t2249b CROSS JOIN t2249a WHERE a=+b;
+    SELECT b, a FROM t2249b CROSS JOIN t2249a WHERE CAST(a AS INT) = +b;
   ]])
         end, {
             -- <where2-6.9>
@@ -650,141 +650,6 @@ test:do_test(
             -- </where2-6.9>
         })
 
-    test:do_test(
-        "where2-6.9.2",
-        function()
-            -- The same thing but with the expression flipped around.
-            return queryplan([[
-    SELECT b,a FROM t2249b CROSS JOIN t2249a WHERE +b=a
-  ]])
-        end, {
-            -- <where2-6.9.2>
-            123, "0123","nosort", "T2249B", "*", "T2249A", "*"
-            -- </where2-6.9.2>
-        })
-
-    test:do_test(
-        "where2-6.10",
-        function()
-            return queryplan([[
-    SELECT b,a FROM t2249b CROSS JOIN t2249a WHERE +a=+b;
-  ]])
-        end, {
-            -- <where2-6.10>
-            123, "0123", "nosort", "T2249B", "*", "T2249A", "*"
-            -- </where2-6.10>
-        })
-
-    test:do_test(
-        "where2-6.11",
-        function()
-            -- This will not attempt the OR optimization because of the a=b
-            -- comparison.
-            return queryplan([[
-    SELECT b,a FROM t2249b CROSS JOIN t2249a WHERE a=b OR a='hello';
-  ]])
-        end, {
-            -- <where2-6.11>
-            123, '0123', "nosort", "T2249B", "*", "T2249A", "*"
-            -- </where2-6.11>
-        })
-
-    test:do_test(
-        "where2-6.11.2",
-        function()
-            -- Permutations of the expression terms.
-            return queryplan([[
-    SELECT b,a FROM t2249b CROSS JOIN t2249a WHERE b=a OR a='hello';
-  ]])
-        end, {
-            -- <where2-6.11.2>
-            123, '0123', "nosort", "T2249B", "*", "T2249A", "*"
-            -- </where2-6.11.2>
-        })
-
-    test:do_test(
-        "where2-6.11.3",
-        function()
-            -- Permutations of the expression terms.
-            return queryplan([[
-    SELECT b,a FROM t2249b CROSS JOIN t2249a WHERE 'hello'=a OR b=a;
-  ]])
-        end, {
-            -- <where2-6.11.3>
-            123, '0123', "nosort", "T2249B", "*", "T2249A", "*"
-            -- </where2-6.11.3>
-        })
-
-    test:do_test(
-        "where2-6.11.4",
-        function()
-            -- Permutations of the expression terms.
-            return queryplan([[
-    SELECT b,a FROM t2249b CROSS JOIN t2249a WHERE a='hello' OR b=a;
-  ]])
-        end, {
-            -- <where2-6.11.4>
-            123, '0123', "nosort", "T2249B", "*", "T2249A", "*"
-            -- </where2-6.11.4>
-        })
-
-    -- These tests are not run if subquery support is not included in the
-    -- build. This is because these tests test the "a = 1 OR a = 2" to
-    -- "a IN (1, 2)" optimisation transformation, which is not enabled if
-    -- subqueries and the IN operator is not available.
-    --
-    test:do_test(
-        "where2-6.12",
-        function()
-            return queryplan([[
-      SELECT b,a FROM t2249b CROSS JOIN t2249a WHERE a=+b OR a='hello';
-    ]])
-        end, {
-            -- <where2-6.12>
-            123, "0123", "nosort", "T2249B", "*", "T2249A", "*"
-            -- </where2-6.12>
-        })
-
-    test:do_test(
-        "where2-6.12.2",
-        function()
-            return queryplan([[
-      SELECT b,a FROM t2249b CROSS JOIN t2249a WHERE a='hello' OR +b=a;
-    ]])
-        end, {
-            -- <where2-6.12.2>
-            123, "0123", "nosort", "T2249B", "*", "T2249A", "*"
-            -- </where2-6.12.2>
-        })
-
-    test:do_test(
-        "where2-6.12.3",
-        function()
-            return queryplan([[
-      SELECT b,a FROM t2249b CROSS JOIN t2249a WHERE +b=a OR a='hello';
-    ]])
-        end, {
-            -- <where2-6.12.3>
-            123, "0123", "nosort", "T2249B", "*", "T2249A", "*"
-            -- </where2-6.12.3>
-        })
-
-    test:do_test(
-        "where2-6.13",
-        function()
-            -- The addition of +a on the second term disabled the OR optimization.
-            -- But we should still get the same empty-set result as in where2-6.9.
-            return queryplan([[
-      SELECT b,a FROM t2249b CROSS JOIN t2249a WHERE a=+b OR +a='hello';
-    ]])
-        end, {
-            -- <where2-6.13>
-            123, "0123", "nosort", "T2249B", "*", "T2249A", "*"
-            -- </where2-6.13>
-        })
-
-
-
     -- Variations on the order of terms in a WHERE clause in order
     -- to make sure the OR optimizer can recognize them all.
     test:do_test(
diff --git a/test/sql-tap/where5.test.lua b/test/sql-tap/where5.test.lua
index 3d3c3a0ca..400597257 100755
--- a/test/sql-tap/where5.test.lua
+++ b/test/sql-tap/where5.test.lua
@@ -34,7 +34,7 @@ test:do_test("where5-1.0", function()
         INSERT INTO t3 SELECT CAST(x AS INTEGER) FROM t1;
     ]]
     return test:execsql [[
-        SELECT * FROM t1 WHERE x<0
+        SELECT * FROM t1 WHERE CAST(x AS INTEGER) < 0
     ]]
 end, {
     -- <where5-1.0>
@@ -43,7 +43,7 @@ end, {
 })
 
 test:do_execsql_test("where5-1.1", [[
-    SELECT * FROM t1 WHERE x<=0
+    SELECT * FROM t1 WHERE CAST(x AS INTEGER) <= 0
 ]], {
     -- <where5-1.1>
     '-1', '0'
@@ -51,7 +51,7 @@ test:do_execsql_test("where5-1.1", [[
 })
 
 test:do_execsql_test("where5-1.2", [[
-    SELECT * FROM t1 WHERE x=0
+    SELECT * FROM t1 WHERE CAST(x AS INTEGER) = 0
 ]], {
     -- <where5-1.2>
     '0'
@@ -59,7 +59,7 @@ test:do_execsql_test("where5-1.2", [[
 })
 
 test:do_execsql_test("where5-1.3", [[
-    SELECT * FROM t1 WHERE x>=0
+    SELECT * FROM t1 WHERE CAST(x AS INTEGER) >= 0
 ]], {
     -- <where5-1.3>
     '0', '1'
@@ -67,7 +67,7 @@ test:do_execsql_test("where5-1.3", [[
 })
 
 test:do_execsql_test("where5-1.4", [[
-    SELECT * FROM t1 WHERE x>0
+    SELECT * FROM t1 WHERE CAST(x AS INTEGER) > 0
 ]], {
     -- <where5-1.4>
     '1'
@@ -75,7 +75,7 @@ test:do_execsql_test("where5-1.4", [[
 })
 
 test:do_execsql_test("where5-1.5", [[
-    SELECT * FROM t1 WHERE x<>0
+    SELECT * FROM t1 WHERE CAST(x AS INTEGER) <> 0
 ]], {
     -- <where5-1.5>
     '-1', '1'
diff --git a/test/sql/boolean.result b/test/sql/boolean.result
index d54de8fe7..81d79ee78 100644
--- a/test/sql/boolean.result
+++ b/test/sql/boolean.result
@@ -138,12 +138,12 @@ INSERT INTO ts(s) VALUES ('abc'), (12.5);
 SELECT s FROM ts WHERE s = true;
  | ---
  | - null
- | - 'Type mismatch: can not convert string(''abc'') to boolean'
+ | - 'Type mismatch: can not convert boolean(TRUE) to string'
  | ...
 SELECT s FROM ts WHERE s < true;
  | ---
  | - null
- | - 'Type mismatch: can not convert string(''abc'') to boolean'
+ | - 'Type mismatch: can not convert boolean(TRUE) to string'
  | ...
 SELECT s FROM ts WHERE s IN (true, 1, 'abcd');
  | ---
@@ -3394,22 +3394,22 @@ SELECT false < 2;
 SELECT 2 > true;
  | ---
  | - null
- | - 'Type mismatch: can not convert integer(2) to boolean'
+ | - 'Type mismatch: can not convert boolean(TRUE) to number'
  | ...
 SELECT 2 > false;
  | ---
  | - null
- | - 'Type mismatch: can not convert integer(2) to boolean'
+ | - 'Type mismatch: can not convert boolean(FALSE) to number'
  | ...
 SELECT 2 < true;
  | ---
  | - null
- | - 'Type mismatch: can not convert integer(2) to boolean'
+ | - 'Type mismatch: can not convert boolean(TRUE) to number'
  | ...
 SELECT 2 < false;
  | ---
  | - null
- | - 'Type mismatch: can not convert integer(2) to boolean'
+ | - 'Type mismatch: can not convert boolean(FALSE) to number'
  | ...
 
 SELECT a1, a1 > 2 FROM t6
@@ -3425,12 +3425,12 @@ SELECT a1, a1 < 2 FROM t6
 SELECT a1, 2 > a1 FROM t6
  | ---
  | - null
- | - 'Type mismatch: can not convert integer(2) to boolean'
+ | - 'Type mismatch: can not convert boolean(FALSE) to number'
  | ...
 SELECT a1, 2 < a1 FROM t6
  | ---
  | - null
- | - 'Type mismatch: can not convert integer(2) to boolean'
+ | - 'Type mismatch: can not convert boolean(FALSE) to number'
  | ...
 SELECT a2, a2 > 2 FROM t6
  | ---
@@ -3445,12 +3445,12 @@ SELECT a2, a2 < 2 FROM t6
 SELECT a2, 2 > a2 FROM t6
  | ---
  | - null
- | - 'Type mismatch: can not convert integer(2) to boolean'
+ | - 'Type mismatch: can not convert boolean(TRUE) to number'
  | ...
 SELECT a2, 2 < a2 FROM t6
  | ---
  | - null
- | - 'Type mismatch: can not convert integer(2) to boolean'
+ | - 'Type mismatch: can not convert boolean(TRUE) to number'
  | ...
 
 SELECT b, true > b FROM t7;
@@ -3476,22 +3476,22 @@ SELECT b, false < b FROM t7;
 SELECT b, b > true FROM t7;
  | ---
  | - null
- | - 'Type mismatch: can not convert integer(123) to boolean'
+ | - 'Type mismatch: can not convert boolean(TRUE) to number'
  | ...
 SELECT b, b > false FROM t7;
  | ---
  | - null
- | - 'Type mismatch: can not convert integer(123) to boolean'
+ | - 'Type mismatch: can not convert boolean(FALSE) to number'
  | ...
 SELECT b, b < true FROM t7;
  | ---
  | - null
- | - 'Type mismatch: can not convert integer(123) to boolean'
+ | - 'Type mismatch: can not convert boolean(TRUE) to number'
  | ...
 SELECT b, b < false FROM t7;
  | ---
  | - null
- | - 'Type mismatch: can not convert integer(123) to boolean'
+ | - 'Type mismatch: can not convert boolean(FALSE) to number'
  | ...
 
 SELECT a1, b, a1 > b FROM t6, t7;
@@ -3507,12 +3507,12 @@ SELECT a1, b, a1 < b FROM t6, t7;
 SELECT a1, b, b > a1 FROM t6, t7;
  | ---
  | - null
- | - 'Type mismatch: can not convert integer(123) to boolean'
+ | - 'Type mismatch: can not convert boolean(FALSE) to number'
  | ...
 SELECT a1, b, b < a1 FROM t6, t7;
  | ---
  | - null
- | - 'Type mismatch: can not convert integer(123) to boolean'
+ | - 'Type mismatch: can not convert boolean(FALSE) to number'
  | ...
 SELECT a2, b, a2 > b FROM t6, t7;
  | ---
@@ -3527,12 +3527,12 @@ SELECT a2, b, a2 < b FROM t6, t7;
 SELECT a2, b, b > a2 FROM t6, t7;
  | ---
  | - null
- | - 'Type mismatch: can not convert integer(123) to boolean'
+ | - 'Type mismatch: can not convert boolean(TRUE) to number'
  | ...
 SELECT a2, b, b < a2 FROM t6, t7;
  | ---
  | - null
- | - 'Type mismatch: can not convert integer(123) to boolean'
+ | - 'Type mismatch: can not convert boolean(TRUE) to number'
  | ...
 
 SELECT true >= 2;
@@ -3558,22 +3558,22 @@ SELECT false <= 2;
 SELECT 2 >= true;
  | ---
  | - null
- | - 'Type mismatch: can not convert integer(2) to boolean'
+ | - 'Type mismatch: can not convert boolean(TRUE) to number'
  | ...
 SELECT 2 >= false;
  | ---
  | - null
- | - 'Type mismatch: can not convert integer(2) to boolean'
+ | - 'Type mismatch: can not convert boolean(FALSE) to number'
  | ...
 SELECT 2 <= true;
  | ---
  | - null
- | - 'Type mismatch: can not convert integer(2) to boolean'
+ | - 'Type mismatch: can not convert boolean(TRUE) to number'
  | ...
 SELECT 2 <= false;
  | ---
  | - null
- | - 'Type mismatch: can not convert integer(2) to boolean'
+ | - 'Type mismatch: can not convert boolean(FALSE) to number'
  | ...
 
 SELECT a1, a1 >= 2 FROM t6
@@ -3589,12 +3589,12 @@ SELECT a1, a1 <= 2 FROM t6
 SELECT a1, 2 >= a1 FROM t6
  | ---
  | - null
- | - 'Type mismatch: can not convert integer(2) to boolean'
+ | - 'Type mismatch: can not convert boolean(FALSE) to number'
  | ...
 SELECT a1, 2 <= a1 FROM t6
  | ---
  | - null
- | - 'Type mismatch: can not convert integer(2) to boolean'
+ | - 'Type mismatch: can not convert boolean(FALSE) to number'
  | ...
 SELECT a2, a2 >= 2 FROM t6
  | ---
@@ -3609,12 +3609,12 @@ SELECT a2, a2 <= 2 FROM t6
 SELECT a2, 2 >= a2 FROM t6
  | ---
  | - null
- | - 'Type mismatch: can not convert integer(2) to boolean'
+ | - 'Type mismatch: can not convert boolean(TRUE) to number'
  | ...
 SELECT a2, 2 <= a2 FROM t6
  | ---
  | - null
- | - 'Type mismatch: can not convert integer(2) to boolean'
+ | - 'Type mismatch: can not convert boolean(TRUE) to number'
  | ...
 
 SELECT b, true >= b FROM t7;
@@ -3640,22 +3640,22 @@ SELECT b, false <= b FROM t7;
 SELECT b, b >= true FROM t7;
  | ---
  | - null
- | - 'Type mismatch: can not convert integer(123) to boolean'
+ | - 'Type mismatch: can not convert boolean(TRUE) to number'
  | ...
 SELECT b, b >= false FROM t7;
  | ---
  | - null
- | - 'Type mismatch: can not convert integer(123) to boolean'
+ | - 'Type mismatch: can not convert boolean(FALSE) to number'
  | ...
 SELECT b, b <= true FROM t7;
  | ---
  | - null
- | - 'Type mismatch: can not convert integer(123) to boolean'
+ | - 'Type mismatch: can not convert boolean(TRUE) to number'
  | ...
 SELECT b, b <= false FROM t7;
  | ---
  | - null
- | - 'Type mismatch: can not convert integer(123) to boolean'
+ | - 'Type mismatch: can not convert boolean(FALSE) to number'
  | ...
 
 SELECT a1, b, a1 >= b FROM t6, t7;
@@ -3671,12 +3671,12 @@ SELECT a1, b, a1 <= b FROM t6, t7;
 SELECT a1, b, b >= a1 FROM t6, t7;
  | ---
  | - null
- | - 'Type mismatch: can not convert integer(123) to boolean'
+ | - 'Type mismatch: can not convert boolean(FALSE) to number'
  | ...
 SELECT a1, b, b <= a1 FROM t6, t7;
  | ---
  | - null
- | - 'Type mismatch: can not convert integer(123) to boolean'
+ | - 'Type mismatch: can not convert boolean(FALSE) to number'
  | ...
 SELECT a2, b, a2 >= b FROM t6, t7;
  | ---
@@ -3691,12 +3691,12 @@ SELECT a2, b, a2 <= b FROM t6, t7;
 SELECT a2, b, b >= a2 FROM t6, t7;
  | ---
  | - null
- | - 'Type mismatch: can not convert integer(123) to boolean'
+ | - 'Type mismatch: can not convert boolean(TRUE) to number'
  | ...
 SELECT a2, b, b <= a2 FROM t6, t7;
  | ---
  | - null
- | - 'Type mismatch: can not convert integer(123) to boolean'
+ | - 'Type mismatch: can not convert boolean(TRUE) to number'
  | ...
 
 SELECT true == 2;
@@ -3722,22 +3722,22 @@ SELECT false != 2;
 SELECT 2 == true;
  | ---
  | - null
- | - 'Type mismatch: can not convert integer(2) to boolean'
+ | - 'Type mismatch: can not convert boolean(TRUE) to number'
  | ...
 SELECT 2 == false;
  | ---
  | - null
- | - 'Type mismatch: can not convert integer(2) to boolean'
+ | - 'Type mismatch: can not convert boolean(FALSE) to number'
  | ...
 SELECT 2 != true;
  | ---
  | - null
- | - 'Type mismatch: can not convert integer(2) to boolean'
+ | - 'Type mismatch: can not convert boolean(TRUE) to number'
  | ...
 SELECT 2 != false;
  | ---
  | - null
- | - 'Type mismatch: can not convert integer(2) to boolean'
+ | - 'Type mismatch: can not convert boolean(FALSE) to number'
  | ...
 
 SELECT a1, a1 == 2 FROM t6
@@ -3753,12 +3753,12 @@ SELECT a1, a1 != 2 FROM t6
 SELECT a1, 2 == a1 FROM t6
  | ---
  | - null
- | - 'Type mismatch: can not convert integer(2) to boolean'
+ | - 'Type mismatch: can not convert boolean(FALSE) to number'
  | ...
 SELECT a1, 2 != a1 FROM t6
  | ---
  | - null
- | - 'Type mismatch: can not convert integer(2) to boolean'
+ | - 'Type mismatch: can not convert boolean(FALSE) to number'
  | ...
 SELECT a2, a2 == 2 FROM t6
  | ---
@@ -3773,12 +3773,12 @@ SELECT a2, a2 != 2 FROM t6
 SELECT a2, 2 == a2 FROM t6
  | ---
  | - null
- | - 'Type mismatch: can not convert integer(2) to boolean'
+ | - 'Type mismatch: can not convert boolean(TRUE) to number'
  | ...
 SELECT a2, 2 != a2 FROM t6
  | ---
  | - null
- | - 'Type mismatch: can not convert integer(2) to boolean'
+ | - 'Type mismatch: can not convert boolean(TRUE) to number'
  | ...
 
 SELECT b, true == b FROM t7;
@@ -3804,22 +3804,22 @@ SELECT b, false != b FROM t7;
 SELECT b, b == true FROM t7;
  | ---
  | - null
- | - 'Type mismatch: can not convert integer(123) to boolean'
+ | - 'Type mismatch: can not convert boolean(TRUE) to number'
  | ...
 SELECT b, b == false FROM t7;
  | ---
  | - null
- | - 'Type mismatch: can not convert integer(123) to boolean'
+ | - 'Type mismatch: can not convert boolean(FALSE) to number'
  | ...
 SELECT b, b != true FROM t7;
  | ---
  | - null
- | - 'Type mismatch: can not convert integer(123) to boolean'
+ | - 'Type mismatch: can not convert boolean(TRUE) to number'
  | ...
 SELECT b, b != false FROM t7;
  | ---
  | - null
- | - 'Type mismatch: can not convert integer(123) to boolean'
+ | - 'Type mismatch: can not convert boolean(FALSE) to number'
  | ...
 
 SELECT a1, b, a1 == b FROM t6, t7;
@@ -3835,12 +3835,12 @@ SELECT a1, b, a1 != b FROM t6, t7;
 SELECT a1, b, b == a1 FROM t6, t7;
  | ---
  | - null
- | - 'Type mismatch: can not convert integer(123) to boolean'
+ | - 'Type mismatch: can not convert boolean(FALSE) to number'
  | ...
 SELECT a1, b, b != a1 FROM t6, t7;
  | ---
  | - null
- | - 'Type mismatch: can not convert integer(123) to boolean'
+ | - 'Type mismatch: can not convert boolean(FALSE) to number'
  | ...
 SELECT a2, b, a2 == b FROM t6, t7;
  | ---
@@ -3855,12 +3855,12 @@ SELECT a2, b, a2 != b FROM t6, t7;
 SELECT a2, b, b == a2 FROM t6, t7;
  | ---
  | - null
- | - 'Type mismatch: can not convert integer(123) to boolean'
+ | - 'Type mismatch: can not convert boolean(TRUE) to number'
  | ...
 SELECT a2, b, b != a2 FROM t6, t7;
  | ---
  | - null
- | - 'Type mismatch: can not convert integer(123) to boolean'
+ | - 'Type mismatch: can not convert boolean(TRUE) to number'
  | ...
 
 SELECT true IN (0, 1, 2, 3);
@@ -4539,22 +4539,22 @@ SELECT false < 2.3;
 SELECT 2.3 > true;
  | ---
  | - null
- | - 'Type mismatch: can not convert double(2.3) to boolean'
+ | - 'Type mismatch: can not convert boolean(TRUE) to number'
  | ...
 SELECT 2.3 > false;
  | ---
  | - null
- | - 'Type mismatch: can not convert double(2.3) to boolean'
+ | - 'Type mismatch: can not convert boolean(FALSE) to number'
  | ...
 SELECT 2.3 < true;
  | ---
  | - null
- | - 'Type mismatch: can not convert double(2.3) to boolean'
+ | - 'Type mismatch: can not convert boolean(TRUE) to number'
  | ...
 SELECT 2.3 < false;
  | ---
  | - null
- | - 'Type mismatch: can not convert double(2.3) to boolean'
+ | - 'Type mismatch: can not convert boolean(FALSE) to number'
  | ...
 
 SELECT a1, a1 > 2.3 FROM t6
@@ -4570,12 +4570,12 @@ SELECT a1, a1 < 2.3 FROM t6
 SELECT a1, 2.3 > a1 FROM t6
  | ---
  | - null
- | - 'Type mismatch: can not convert double(2.3) to boolean'
+ | - 'Type mismatch: can not convert boolean(FALSE) to number'
  | ...
 SELECT a1, 2.3 < a1 FROM t6
  | ---
  | - null
- | - 'Type mismatch: can not convert double(2.3) to boolean'
+ | - 'Type mismatch: can not convert boolean(FALSE) to number'
  | ...
 SELECT a2, a2 > 2.3 FROM t6
  | ---
@@ -4590,12 +4590,12 @@ SELECT a2, a2 < 2.3 FROM t6
 SELECT a2, 2.3 > a2 FROM t6
  | ---
  | - null
- | - 'Type mismatch: can not convert double(2.3) to boolean'
+ | - 'Type mismatch: can not convert boolean(TRUE) to number'
  | ...
 SELECT a2, 2.3 < a2 FROM t6
  | ---
  | - null
- | - 'Type mismatch: can not convert double(2.3) to boolean'
+ | - 'Type mismatch: can not convert boolean(TRUE) to number'
  | ...
 
 SELECT c, true > c FROM t8;
@@ -4621,22 +4621,22 @@ SELECT c, false < c FROM t8;
 SELECT c, c > true FROM t8;
  | ---
  | - null
- | - 'Type mismatch: can not convert double(4.56) to boolean'
+ | - 'Type mismatch: can not convert boolean(TRUE) to number'
  | ...
 SELECT c, c > false FROM t8;
  | ---
  | - null
- | - 'Type mismatch: can not convert double(4.56) to boolean'
+ | - 'Type mismatch: can not convert boolean(FALSE) to number'
  | ...
 SELECT c, c < true FROM t8;
  | ---
  | - null
- | - 'Type mismatch: can not convert double(4.56) to boolean'
+ | - 'Type mismatch: can not convert boolean(TRUE) to number'
  | ...
 SELECT c, c < false FROM t8;
  | ---
  | - null
- | - 'Type mismatch: can not convert double(4.56) to boolean'
+ | - 'Type mismatch: can not convert boolean(FALSE) to number'
  | ...
 
 SELECT a1, c, a1 > c FROM t6, t8;
@@ -4652,12 +4652,12 @@ SELECT a1, c, a1 < c FROM t6, t8;
 SELECT a1, c, c > a1 FROM t6, t8;
  | ---
  | - null
- | - 'Type mismatch: can not convert double(4.56) to boolean'
+ | - 'Type mismatch: can not convert boolean(FALSE) to number'
  | ...
 SELECT a1, c, c < a1 FROM t6, t8;
  | ---
  | - null
- | - 'Type mismatch: can not convert double(4.56) to boolean'
+ | - 'Type mismatch: can not convert boolean(FALSE) to number'
  | ...
 SELECT a2, c, a2 > c FROM t6, t8;
  | ---
@@ -4672,12 +4672,12 @@ SELECT a2, c, a2 < c FROM t6, t8;
 SELECT a2, c, c > a2 FROM t6, t8;
  | ---
  | - null
- | - 'Type mismatch: can not convert double(4.56) to boolean'
+ | - 'Type mismatch: can not convert boolean(TRUE) to number'
  | ...
 SELECT a2, c, c < a2 FROM t6, t8;
  | ---
  | - null
- | - 'Type mismatch: can not convert double(4.56) to boolean'
+ | - 'Type mismatch: can not convert boolean(TRUE) to number'
  | ...
 
 SELECT true >= 2.3;
@@ -4703,22 +4703,22 @@ SELECT false <= 2.3;
 SELECT 2.3 >= true;
  | ---
  | - null
- | - 'Type mismatch: can not convert double(2.3) to boolean'
+ | - 'Type mismatch: can not convert boolean(TRUE) to number'
  | ...
 SELECT 2.3 >= false;
  | ---
  | - null
- | - 'Type mismatch: can not convert double(2.3) to boolean'
+ | - 'Type mismatch: can not convert boolean(FALSE) to number'
  | ...
 SELECT 2.3 <= true;
  | ---
  | - null
- | - 'Type mismatch: can not convert double(2.3) to boolean'
+ | - 'Type mismatch: can not convert boolean(TRUE) to number'
  | ...
 SELECT 2.3 <= false;
  | ---
  | - null
- | - 'Type mismatch: can not convert double(2.3) to boolean'
+ | - 'Type mismatch: can not convert boolean(FALSE) to number'
  | ...
 
 SELECT a1, a1 >= 2.3 FROM t6
@@ -4734,12 +4734,12 @@ SELECT a1, a1 <= 2.3 FROM t6
 SELECT a1, 2.3 >= a1 FROM t6
  | ---
  | - null
- | - 'Type mismatch: can not convert double(2.3) to boolean'
+ | - 'Type mismatch: can not convert boolean(FALSE) to number'
  | ...
 SELECT a1, 2.3 <= a1 FROM t6
  | ---
  | - null
- | - 'Type mismatch: can not convert double(2.3) to boolean'
+ | - 'Type mismatch: can not convert boolean(FALSE) to number'
  | ...
 SELECT a2, a2 >= 2.3 FROM t6
  | ---
@@ -4754,12 +4754,12 @@ SELECT a2, a2 <= 2.3 FROM t6
 SELECT a2, 2.3 >= a2 FROM t6
  | ---
  | - null
- | - 'Type mismatch: can not convert double(2.3) to boolean'
+ | - 'Type mismatch: can not convert boolean(TRUE) to number'
  | ...
 SELECT a2, 2.3 <= a2 FROM t6
  | ---
  | - null
- | - 'Type mismatch: can not convert double(2.3) to boolean'
+ | - 'Type mismatch: can not convert boolean(TRUE) to number'
  | ...
 
 SELECT c, true >= c FROM t8;
@@ -4785,22 +4785,22 @@ SELECT c, false <= c FROM t8;
 SELECT c, c >= true FROM t8;
  | ---
  | - null
- | - 'Type mismatch: can not convert double(4.56) to boolean'
+ | - 'Type mismatch: can not convert boolean(TRUE) to number'
  | ...
 SELECT c, c >= false FROM t8;
  | ---
  | - null
- | - 'Type mismatch: can not convert double(4.56) to boolean'
+ | - 'Type mismatch: can not convert boolean(FALSE) to number'
  | ...
 SELECT c, c <= true FROM t8;
  | ---
  | - null
- | - 'Type mismatch: can not convert double(4.56) to boolean'
+ | - 'Type mismatch: can not convert boolean(TRUE) to number'
  | ...
 SELECT c, c <= false FROM t8;
  | ---
  | - null
- | - 'Type mismatch: can not convert double(4.56) to boolean'
+ | - 'Type mismatch: can not convert boolean(FALSE) to number'
  | ...
 
 SELECT a1, c, a1 >= c FROM t6, t8;
@@ -4816,12 +4816,12 @@ SELECT a1, c, a1 <= c FROM t6, t8;
 SELECT a1, c, c >= a1 FROM t6, t8;
  | ---
  | - null
- | - 'Type mismatch: can not convert double(4.56) to boolean'
+ | - 'Type mismatch: can not convert boolean(FALSE) to number'
  | ...
 SELECT a1, c, c <= a1 FROM t6, t8;
  | ---
  | - null
- | - 'Type mismatch: can not convert double(4.56) to boolean'
+ | - 'Type mismatch: can not convert boolean(FALSE) to number'
  | ...
 SELECT a2, c, a2 >= c FROM t6, t8;
  | ---
@@ -4836,12 +4836,12 @@ SELECT a2, c, a2 <= c FROM t6, t8;
 SELECT a2, c, c >= a2 FROM t6, t8;
  | ---
  | - null
- | - 'Type mismatch: can not convert double(4.56) to boolean'
+ | - 'Type mismatch: can not convert boolean(TRUE) to number'
  | ...
 SELECT a2, c, c <= a2 FROM t6, t8;
  | ---
  | - null
- | - 'Type mismatch: can not convert double(4.56) to boolean'
+ | - 'Type mismatch: can not convert boolean(TRUE) to number'
  | ...
 
 SELECT true == 2.3;
@@ -4867,22 +4867,22 @@ SELECT false != 2.3;
 SELECT 2.3 == true;
  | ---
  | - null
- | - 'Type mismatch: can not convert double(2.3) to boolean'
+ | - 'Type mismatch: can not convert boolean(TRUE) to number'
  | ...
 SELECT 2.3 == false;
  | ---
  | - null
- | - 'Type mismatch: can not convert double(2.3) to boolean'
+ | - 'Type mismatch: can not convert boolean(FALSE) to number'
  | ...
 SELECT 2.3 != true;
  | ---
  | - null
- | - 'Type mismatch: can not convert double(2.3) to boolean'
+ | - 'Type mismatch: can not convert boolean(TRUE) to number'
  | ...
 SELECT 2.3 != false;
  | ---
  | - null
- | - 'Type mismatch: can not convert double(2.3) to boolean'
+ | - 'Type mismatch: can not convert boolean(FALSE) to number'
  | ...
 
 SELECT a1, a1 == 2.3 FROM t6
@@ -4898,12 +4898,12 @@ SELECT a1, a1 != 2.3 FROM t6
 SELECT a1, 2.3 == a1 FROM t6
  | ---
  | - null
- | - 'Type mismatch: can not convert double(2.3) to boolean'
+ | - 'Type mismatch: can not convert boolean(FALSE) to number'
  | ...
 SELECT a1, 2.3 != a1 FROM t6
  | ---
  | - null
- | - 'Type mismatch: can not convert double(2.3) to boolean'
+ | - 'Type mismatch: can not convert boolean(FALSE) to number'
  | ...
 SELECT a2, a2 == 2.3 FROM t6
  | ---
@@ -4918,12 +4918,12 @@ SELECT a2, a2 != 2.3 FROM t6
 SELECT a2, 2.3 == a2 FROM t6
  | ---
  | - null
- | - 'Type mismatch: can not convert double(2.3) to boolean'
+ | - 'Type mismatch: can not convert boolean(TRUE) to number'
  | ...
 SELECT a2, 2.3 != a2 FROM t6
  | ---
  | - null
- | - 'Type mismatch: can not convert double(2.3) to boolean'
+ | - 'Type mismatch: can not convert boolean(TRUE) to number'
  | ...
 
 SELECT c, true == c FROM t8;
@@ -4949,22 +4949,22 @@ SELECT c, false != c FROM t8;
 SELECT c, c == true FROM t8;
  | ---
  | - null
- | - 'Type mismatch: can not convert double(4.56) to boolean'
+ | - 'Type mismatch: can not convert boolean(TRUE) to number'
  | ...
 SELECT c, c == false FROM t8;
  | ---
  | - null
- | - 'Type mismatch: can not convert double(4.56) to boolean'
+ | - 'Type mismatch: can not convert boolean(FALSE) to number'
  | ...
 SELECT c, c != true FROM t8;
  | ---
  | - null
- | - 'Type mismatch: can not convert double(4.56) to boolean'
+ | - 'Type mismatch: can not convert boolean(TRUE) to number'
  | ...
 SELECT c, c != false FROM t8;
  | ---
  | - null
- | - 'Type mismatch: can not convert double(4.56) to boolean'
+ | - 'Type mismatch: can not convert boolean(FALSE) to number'
  | ...
 
 SELECT a1, c, a1 == c FROM t6, t8;
@@ -4980,12 +4980,12 @@ SELECT a1, c, a1 != c FROM t6, t8;
 SELECT a1, c, c == a1 FROM t6, t8;
  | ---
  | - null
- | - 'Type mismatch: can not convert double(4.56) to boolean'
+ | - 'Type mismatch: can not convert boolean(FALSE) to number'
  | ...
 SELECT a1, c, c != a1 FROM t6, t8;
  | ---
  | - null
- | - 'Type mismatch: can not convert double(4.56) to boolean'
+ | - 'Type mismatch: can not convert boolean(FALSE) to number'
  | ...
 SELECT a2, c, a2 == c FROM t6, t8;
  | ---
@@ -5000,12 +5000,12 @@ SELECT a2, c, a2 != c FROM t6, t8;
 SELECT a2, c, c == a2 FROM t6, t8;
  | ---
  | - null
- | - 'Type mismatch: can not convert double(4.56) to boolean'
+ | - 'Type mismatch: can not convert boolean(TRUE) to number'
  | ...
 SELECT a2, c, c != a2 FROM t6, t8;
  | ---
  | - null
- | - 'Type mismatch: can not convert double(4.56) to boolean'
+ | - 'Type mismatch: can not convert boolean(TRUE) to number'
  | ...
 
 SELECT true IN (0.1, 1.2, 2.3, 3.4);
@@ -5295,22 +5295,22 @@ SELECT false < 'abc';
 SELECT 'abc' > true;
  | ---
  | - null
- | - 'Type mismatch: can not convert string(''abc'') to boolean'
+ | - 'Type mismatch: can not convert boolean(TRUE) to string'
  | ...
 SELECT 'abc' > false;
  | ---
  | - null
- | - 'Type mismatch: can not convert string(''abc'') to boolean'
+ | - 'Type mismatch: can not convert boolean(FALSE) to string'
  | ...
 SELECT 'abc' < true;
  | ---
  | - null
- | - 'Type mismatch: can not convert string(''abc'') to boolean'
+ | - 'Type mismatch: can not convert boolean(TRUE) to string'
  | ...
 SELECT 'abc' < false;
  | ---
  | - null
- | - 'Type mismatch: can not convert string(''abc'') to boolean'
+ | - 'Type mismatch: can not convert boolean(FALSE) to string'
  | ...
 
 SELECT d, true > d FROM t9;
@@ -5336,22 +5336,22 @@ SELECT d, false < d FROM t9;
 SELECT d, d > true FROM t9;
  | ---
  | - null
- | - 'Type mismatch: can not convert string(''AsdF'') to boolean'
+ | - 'Type mismatch: can not convert boolean(TRUE) to string'
  | ...
 SELECT d, d > false FROM t9;
  | ---
  | - null
- | - 'Type mismatch: can not convert string(''AsdF'') to boolean'
+ | - 'Type mismatch: can not convert boolean(FALSE) to string'
  | ...
 SELECT d, d < true FROM t9;
  | ---
  | - null
- | - 'Type mismatch: can not convert string(''AsdF'') to boolean'
+ | - 'Type mismatch: can not convert boolean(TRUE) to string'
  | ...
 SELECT d, d < false FROM t9;
  | ---
  | - null
- | - 'Type mismatch: can not convert string(''AsdF'') to boolean'
+ | - 'Type mismatch: can not convert boolean(FALSE) to string'
  | ...
 
 SELECT a1, d, a1 > d FROM t6, t9;
@@ -5367,12 +5367,12 @@ SELECT a1, d, a1 < d FROM t6, t9;
 SELECT a1, d, d > a1 FROM t6, t9;
  | ---
  | - null
- | - 'Type mismatch: can not convert string(''AsdF'') to boolean'
+ | - 'Type mismatch: can not convert boolean(FALSE) to string'
  | ...
 SELECT a1, d, d < a1 FROM t6, t9;
  | ---
  | - null
- | - 'Type mismatch: can not convert string(''AsdF'') to boolean'
+ | - 'Type mismatch: can not convert boolean(FALSE) to string'
  | ...
 SELECT a2, d, a2 > d FROM t6, t9;
  | ---
@@ -5387,12 +5387,12 @@ SELECT a2, d, a2 < d FROM t6, t9;
 SELECT a2, d, d > a2 FROM t6, t9;
  | ---
  | - null
- | - 'Type mismatch: can not convert string(''AsdF'') to boolean'
+ | - 'Type mismatch: can not convert boolean(TRUE) to string'
  | ...
 SELECT a2, d, d < a2 FROM t6, t9;
  | ---
  | - null
- | - 'Type mismatch: can not convert string(''AsdF'') to boolean'
+ | - 'Type mismatch: can not convert boolean(TRUE) to string'
  | ...
 
 SELECT true || 'abc';
diff --git a/test/sql/types.result b/test/sql/types.result
index 25d4dbefc..7eea13db5 100644
--- a/test/sql/types.result
+++ b/test/sql/types.result
@@ -608,11 +608,8 @@ box.execute("SELECT 18446744073709551615.0 > 18446744073709551615")
 ...
 box.execute("SELECT 18446744073709551615 IN ('18446744073709551615', 18446744073709551615.0)")
 ---
-- metadata:
-  - name: COLUMN_1
-    type: boolean
-  rows:
-  - [true]
+- null
+- 'Type mismatch: can not convert integer(18446744073709551615) to string'
 ...
 box.execute("SELECT 1 LIMIT 18446744073709551615;")
 ---
-- 
2.25.1


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

* [Tarantool-patches] [PATCH v1 3/7] sql: rework OP_Seek* opcodes
  2021-07-28 20:51 [Tarantool-patches] [PATCH v1 0/7] Rework implicit cast Mergen Imeev via Tarantool-patches
  2021-07-28 20:51 ` [Tarantool-patches] [PATCH v1 1/7] sql: rework implicit cast fo assignment Mergen Imeev via Tarantool-patches
  2021-07-28 20:51 ` [Tarantool-patches] [PATCH v1 2/7] sql: remove implicit cast from comparison opcodes Mergen Imeev via Tarantool-patches
@ 2021-07-28 20:51 ` Mergen Imeev via Tarantool-patches
  2021-08-04 22:25   ` Vladislav Shpilevoy via Tarantool-patches
  2021-07-28 20:51 ` [Tarantool-patches] [PATCH v1 4/7] sql: remove unnecessary calls of OP_ApplyType Mergen Imeev via Tarantool-patches
                   ` (3 subsequent siblings)
  6 siblings, 1 reply; 25+ messages in thread
From: Mergen Imeev via Tarantool-patches @ 2021-07-28 20:51 UTC (permalink / raw)
  To: v.shpilevoy; +Cc: tarantool-patches

This patch changes the Seek* opcodes that are used to search in space
using index. After the redesign, searches using these opcodes work
according to the new implicit casting rules. However, currently implicit
cast in these opcodes is not invoked since there is OP_ApplyType before
them. Unnecessary OP_ApplyType calls will be removed in next patch.

Part of 4230
Part of 4470
---
 src/box/sql.c              |  69 +-----
 src/box/sql/cursor.c       |  14 --
 src/box/sql/cursor.h       |   1 -
 src/box/sql/mem.c          |   2 -
 src/box/sql/mem.h          |  17 ++
 src/box/sql/tarantoolInt.h |   3 +
 src/box/sql/vdbe.c         | 495 ++++++++++++++++++++-----------------
 7 files changed, 299 insertions(+), 302 deletions(-)

diff --git a/src/box/sql.c b/src/box/sql.c
index 433264abe..a6a7864f1 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 bb2dae898..694fd9a7f 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 88e544191..b82d69e9c 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 b04303be2..98b367054 100644
--- a/src/box/sql/mem.c
+++ b/src/box/sql/mem.c
@@ -2698,8 +2698,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 47a940c56..5d1d7592e 100644
--- a/src/box/sql/mem.h
+++ b/src/box/sql/mem.h
@@ -1028,3 +1028,20 @@ mpstream_encode_vdbe_mem(struct mpstream *stream, struct Mem *var);
 char *
 sql_vdbe_mem_encode_tuple(struct Mem *fields, uint32_t field_count,
 			  uint32_t *tuple_size, struct region *region);
+
+static inline bool
+is_mem_num_min(const struct Mem *mem)
+{
+	return (mem->field_type == FIELD_TYPE_INTEGER &&
+		mem->type == MEM_TYPE_INT && mem->u.i == INT64_MIN) ||
+	       (mem->field_type == FIELD_TYPE_UNSIGNED &&
+		mem->type == MEM_TYPE_UINT && mem->u.u == 0);
+}
+
+static inline bool
+is_mem_num_max(const struct Mem *mem)
+{
+	return (mem->field_type == FIELD_TYPE_INTEGER ||
+		mem->field_type == FIELD_TYPE_UNSIGNED) &&
+	       mem->type == MEM_TYPE_UINT && mem->u.u == 0;
+}
diff --git a/src/box/sql/tarantoolInt.h b/src/box/sql/tarantoolInt.h
index 1ded6c709..8fdc50432 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 62f58def9..a69402720 100644
--- a/src/box/sql/vdbe.c
+++ b/src/box/sql/vdbe.c
@@ -2506,57 +2506,6 @@ case OP_Close: {
 	break;
 }
 
-/* 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 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 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.
- *
- * 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.
- *
- * 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
- */
-/* Opcode: SeekGT 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
- * 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 the key value. If there are no records greater 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.
- *
- * If P5 is not zero, than it is offset of integer fields in input
- * vector. Force corresponding value to be INTEGER.
- *
- * P5 has the same meaning as for SeekGE.
- */
 /* Opcode: SeekLT P1 P2 P3 P4 P5
  * Synopsis: key=r[P3@P4]
  *
@@ -2577,6 +2526,62 @@ case OP_Close: {
  *
  * See also: Found, NotFound, SeekGt, SeekGe, SeekLe
  */
+case OP_SeekLT: {       /* jump, in3 */
+	struct VdbeCursor *cur = p->apCsr[pOp->p1];
+#ifdef SQL_DEBUG
+	cur->seekOp = pOp->opcode;
+#endif
+	cur->nullRow = 0;
+	cur->uc.pCursor->iter_type = ITER_LT;
+
+	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_le = 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;
+		}
+		int cmp = mem_cast_implicit_number(mem, type);
+		is_le = is_le || cmp > 0;
+		/*
+		 * If number before cast were less than min possible for given
+		 * field type, than there is no point to use iterator since we
+		 * won't find anything.
+		 */
+		is_zero = is_zero || (is_mem_num_min(mem) && cmp < 0);
+	}
+	if (is_zero) {
+		assert(pOp->p2 > 0);
+		VdbeBranchTaken(1, 2);
+		goto jump_to_p2;
+	}
+	if (is_le)
+		cur->uc.pCursor->iter_type = ITER_LE;
+
+	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]
  *
@@ -2604,188 +2609,236 @@ case OP_Close: {
  *
  * See also: Found, NotFound, SeekGt, SeekGe, SeekLt
  */
-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_SeekLE: {       /* jump, in3 */
+	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;
+	cur->uc.pCursor->iter_type = is_eq ? ITER_REQ : ITER_LE;
+	assert(!is_eq || pOp[1].opcode == OP_IdxLT);
+
+	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_lt = 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_lt = is_lt || cmp < 0;
+		/*
+		 * If number before cast were less than min possible for given
+		 * field type, than there is no point to use iterator since we
+		 * won't find anything. Also, in case search using EQ, 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) ||
+			(is_mem_num_min(mem) && 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_lt)
+		cur->uc.pCursor->iter_type = ITER_LT;
 
-	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);
+	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;
+	/* Skip the OP_IdxLT that follows if we have EQ. */
+	if (is_eq)
+		pOp++;
+	break;
+}
 
-	r.aMem = &aMem[pOp->p3];
+/* 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 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 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.
+ *
+ * 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.
+ *
+ * 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_SeekGE: {       /* jump, in3 */
+	struct VdbeCursor *cur = p->apCsr[pOp->p1];
 #ifdef SQL_DEBUG
-	{ int i; for(i=0; i<r.nField; i++) assert(memIsValid(&r.aMem[i])); }
+	cur->seekOp = pOp->opcode;
 #endif
-	r.eqSeen = 0;
-	r.opcode = oc;
-	if (sqlCursorMovetoUnpacked(pC->uc.pCursor, &r, &res) != 0)
-		goto abort_due_to_error;
-	if (eqOnly && r.eqSeen==0) {
-		assert(res!=0);
-		goto seek_not_found;
+	cur->nullRow = 0;
+	bool is_eq = (cur->uc.pCursor->hints & OPFLAG_SEEKEQ) != 0;
+	cur->uc.pCursor->iter_type = is_eq ? ITER_EQ : ITER_GE;
+	assert(!is_eq || 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_gt = 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;
+		}
+		int cmp = mem_cast_implicit_number(mem, type);
+		is_gt = is_gt || cmp > 0;
+		/*
+		 * If number before cast were more than max possible for given
+		 * field type, than there is no point to use iterator since we
+		 * won't find anything. Also, in case search using EQ, we will
+		 * not find anything if conversion cannot be precise.
+		 */
+		is_zero = is_zero || (is_eq && cmp != 0) ||
+			(is_mem_num_max(mem) && cmp > 0);
 	}
-	pC->cacheStatus = CACHE_STALE;
+	if (is_zero) {
+		assert(pOp->p2 > 0);
+		VdbeBranchTaken(1, 2);
+		goto jump_to_p2;
+	}
+	if (!is_eq && is_gt)
+		cur->uc.pCursor->iter_type = ITER_GT;
+
+	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
-	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);
+	assert(pOp->p2 > 0);
+	VdbeBranchTaken(res, 2);
+	if (res != 0)
+		goto jump_to_p2;
+	/* Skip the OP_IdxGT that follows if we have EQ. */
+	if (is_eq)
+		pOp++;
+	break;
+}
+
+/* Opcode: SeekGT 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
+ * 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 the key value. If there are no records greater 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.
+ *
+ * If P5 is not zero, than it is offset of integer fields in input
+ * vector. Force corresponding value to be INTEGER.
+ *
+ * P5 has the same meaning as for SeekGE.
+ */
+case OP_SeekGT: {       /* jump, in3 */
+	struct VdbeCursor *cur = p->apCsr[pOp->p1];
+#ifdef SQL_DEBUG
+	cur->seekOp = pOp->opcode;
+#endif
+	cur->nullRow = 0;
+	cur->uc.pCursor->iter_type = 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_ge = 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;
 		}
+		int cmp = mem_cast_implicit_number(mem, type);
+		is_ge = is_ge || cmp < 0;
+		/*
+		 * If number before cast were more than max possible for given
+		 * field type, than there is no point to use iterator since we
+		 * won't find anything.
+		 */
+		is_zero = is_zero || (is_mem_num_max(mem) && cmp > 0);
 	}
-			seek_not_found:
-	assert(pOp->p2>0);
-	VdbeBranchTaken(res!=0,2);
-	if (res) {
+	if (is_zero) {
+		assert(pOp->p2 > 0);
+		VdbeBranchTaken(1, 2);
 		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 */
 	}
+	if (is_ge)
+		cur->uc.pCursor->iter_type = 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;
 }
 
@@ -2912,7 +2965,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)
@@ -3770,7 +3825,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);
@@ -3781,12 +3835,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);
-- 
2.25.1


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

* [Tarantool-patches] [PATCH v1 4/7] sql: remove unnecessary calls of OP_ApplyType
  2021-07-28 20:51 [Tarantool-patches] [PATCH v1 0/7] Rework implicit cast Mergen Imeev via Tarantool-patches
                   ` (2 preceding siblings ...)
  2021-07-28 20:51 ` [Tarantool-patches] [PATCH v1 3/7] sql: rework OP_Seek* opcodes Mergen Imeev via Tarantool-patches
@ 2021-07-28 20:51 ` Mergen Imeev via Tarantool-patches
  2021-08-04 22:26   ` Vladislav Shpilevoy via Tarantool-patches
  2021-07-28 20:51 ` [Tarantool-patches] [PATCH v1 5/7] sql: remove implicit cast from OP_MakeRecord Mergen Imeev via Tarantool-patches
                   ` (2 subsequent siblings)
  6 siblings, 1 reply; 25+ messages in thread
From: Mergen Imeev via Tarantool-patches @ 2021-07-28 20:51 UTC (permalink / raw)
  To: v.shpilevoy; +Cc: tarantool-patches

Since the OP_Seek* opcodes now work using the new implicit casting
rules, we don't need to call OP_ApplyType before them. This patch
removes such calls.

Part of #4230
Part of #4470
---
 src/box/sql/vdbe.c                   |  20 +--
 src/box/sql/wherecode.c              | 202 +--------------------------
 test/sql-tap/cast.test.lua           |  14 +-
 test/sql-tap/in4.test.lua            |   4 +-
 test/sql-tap/join.test.lua           |   4 +-
 test/sql-tap/tkt-9a8b09f8e6.test.lua |  22 +--
 6 files changed, 24 insertions(+), 242 deletions(-)

diff --git a/src/box/sql/vdbe.c b/src/box/sql/vdbe.c
index a69402720..c24265a1d 100644
--- a/src/box/sql/vdbe.c
+++ b/src/box/sql/vdbe.c
@@ -2506,7 +2506,7 @@ case OP_Close: {
 	break;
 }
 
-/* Opcode: SeekLT P1 P2 P3 P4 P5
+/* Opcode: SeekLT P1 P2 P3 P4 *
  * Synopsis: key=r[P3@P4]
  *
  * If cursor P1 refers to an SQL table (B-Tree that uses integer keys),
@@ -2522,8 +2522,6 @@ case OP_Close: {
  * from the end toward the beginning.  In other words, the cursor is
  * configured to use Prev, not Next.
  *
- * P5 has the same meaning as for SeekGE.
- *
  * See also: Found, NotFound, SeekGt, SeekGe, SeekLe
  */
 case OP_SeekLT: {       /* jump, in3 */
@@ -2582,7 +2580,7 @@ case OP_SeekLT: {       /* jump, in3 */
 	break;
 }
 
-/* Opcode: SeekLE P1 P2 P3 P4 P5
+/* Opcode: SeekLE P1 P2 P3 P4 *
  * Synopsis: key=r[P3@P4]
  *
  * If cursor P1 refers to an SQL table (B-Tree that uses integer keys),
@@ -2605,8 +2603,6 @@ case OP_SeekLT: {       /* jump, in3 */
  * 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, SeekLt
  */
 case OP_SeekLE: {       /* jump, in3 */
@@ -2672,7 +2668,7 @@ case OP_SeekLE: {       /* jump, in3 */
 	break;
 }
 
-/* Opcode: SeekGE P1 P2 P3 P4 P5
+/* Opcode: SeekGE P1 P2 P3 P4 *
  * Synopsis: key=r[P3@P4]
  *
  * If cursor P1 refers to an SQL table (B-Tree that uses integer keys),
@@ -2695,11 +2691,6 @@ case OP_SeekLE: {       /* jump, in3 */
  * from the beginning toward the end.  In other words, the cursor is
  * configured to use Next, not Prev.
  *
- * 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_SeekGE: {       /* jump, in3 */
@@ -2765,7 +2756,7 @@ case OP_SeekGE: {       /* jump, in3 */
 	break;
 }
 
-/* Opcode: SeekGT P1 P2 P3 P4 P5
+/* Opcode: SeekGT P1 P2 P3 P4 *
  * Synopsis: key=r[P3@P4]
  *
  * If cursor P1 refers to an SQL table (B-Tree that uses integer keys),
@@ -2781,9 +2772,6 @@ case OP_SeekGE: {       /* jump, in3 */
  * from the beginning toward the end.  In other words, the cursor is
  * configured to use Next, not Prev.
  *
- * If P5 is not zero, than it is offset of integer fields in input
- * vector. Force corresponding value to be INTEGER.
- *
  * P5 has the same meaning as for SeekGE.
  */
 case OP_SeekGT: {       /* jump, in3 */
diff --git a/src/box/sql/wherecode.c b/src/box/sql/wherecode.c
index 96bcab110..92d374200 100644
--- a/src/box/sql/wherecode.c
+++ b/src/box/sql/wherecode.c
@@ -335,72 +335,6 @@ disableTerm(WhereLevel * pLevel, WhereTerm * pTerm)
 	}
 }
 
-/**
- * Code an OP_ApplyType opcode to apply the column type string
- * @types to the n registers starting at @base.
- *
- * As an optimization, SCALAR entries (which are no-ops) at the
- * beginning and end of @types are ignored.  If all entries in
- * @types are SCALAR, then no code gets generated.
- *
- * This routine makes its own copy of @types so that the caller is
- * free to modify @types after this routine returns.
- */
-static void
-emit_apply_type(Parse *pParse, int base, int n, enum field_type *types)
-{
-	Vdbe *v = pParse->pVdbe;
-	if (types == NULL) {
-		assert(pParse->db->mallocFailed);
-		return;
-	}
-	assert(v != 0);
-
-	/*
-	 * Adjust base and n to skip over SCALAR entries at the
-	 * beginning and end of the type sequence.
-	 */
-	while (n > 0 && types[0] == FIELD_TYPE_SCALAR) {
-		n--;
-		base++;
-		types++;
-	}
-	while (n > 1 && types[n - 1] == FIELD_TYPE_SCALAR) {
-		n--;
-	}
-
-	if (n > 0) {
-		enum field_type *types_dup = field_type_sequence_dup(pParse,
-								     types, n);
-		sqlVdbeAddOp4(v, OP_ApplyType, base, n, 0,
-				  (char *) types_dup, P4_DYNAMIC);
-		sql_expr_type_cache_change(pParse, base, n);
-	}
-}
-
-/**
- * Expression @rhs, which is the RHS of a comparison operation, is
- * either a vector of n elements or, if n==1, a scalar expression.
- * Before the comparison operation, types @types are to be applied
- * to the @rhs values. This function modifies entries within the
- * field sequence to SCALAR if either:
- *
- *   * the comparison will be performed with no type, or
- *   * the type change in @types is guaranteed not to change the value.
- */
-static void
-expr_cmp_update_rhs_type(struct Expr *rhs, int n, enum field_type *types)
-{
-	for (int i = 0; i < n; i++) {
-		Expr *p = sqlVectorFieldSubexpr(rhs, i);
-		enum field_type expr_type = sql_expr_type(p);
-		if (sql_type_result(expr_type, types[i]) == FIELD_TYPE_SCALAR ||
-		    sql_expr_needs_no_type_change(p, types[i])) {
-			types[i] = FIELD_TYPE_SCALAR;
-		}
-	}
-}
-
 /*
  * Generate code for a single equality term of the WHERE clause.  An equality
  * term can be either X=expr or X IN (...).   pTerm is the term to be
@@ -644,8 +578,7 @@ static int
 codeAllEqualityTerms(Parse * pParse,	/* Parsing context */
 		     WhereLevel * pLevel,	/* Which nested loop of the FROM we are coding */
 		     int bRev,		/* Reverse the order of IN operators */
-		     int nExtraReg,	/* Number of extra registers to allocate */
-		     enum field_type **res_type)
+		     int nExtraReg)	/* Number of extra registers to allocate */
 {
 	u16 nEq;		/* The number of == or IN constraints to code */
 	u16 nSkip;		/* Number of left-most columns to skip */
@@ -733,7 +666,6 @@ codeAllEqualityTerms(Parse * pParse,	/* Parsing context */
 			}
 		}
 	}
-	*res_type = type;
 	return regBase;
 }
 
@@ -905,16 +837,8 @@ sqlWhereCodeOneLoopStart(WhereInfo * pWInfo,	/* Complete information about the W
 		int iIdxCur;	/* The VDBE cursor for the index */
 		int nExtraReg = 0;	/* Number of extra registers needed */
 		int op;		/* Instruction opcode */
-		/* Types for start of range constraint. */
-		enum field_type *start_types;
-		/* Types for end of range constraint */
-		enum field_type *end_types = NULL;
 		u8 bSeekPastNull = 0;	/* True to seek past initial nulls */
 		u8 bStopAtNull = 0;	/* Add condition to terminate at NULLs */
-		int force_integer_reg = -1;  /* If non-negative: number of
-					      * column which must be converted
-					      * to integer type, used for IPK.
-					      */
 
 		struct index_def *idx_def = pLoop->index_def;
 		assert(idx_def != NULL);
@@ -996,16 +920,7 @@ sqlWhereCodeOneLoopStart(WhereInfo * pWInfo,	/* Complete information about the W
 		 * starting at regBase.
 		 */
 		regBase =
-		    codeAllEqualityTerms(pParse, pLevel, bRev, nExtraReg,
-					 &start_types);
-		if (start_types != NULL && nTop) {
-			uint32_t len = 0;
-			for (enum field_type *tmp = &start_types[nEq];
-			     *tmp != field_type_MAX; tmp++, len++);
-			uint32_t sz = len * sizeof(enum field_type);
-			end_types = sqlDbMallocRaw(db, sz);
-			memcpy(end_types, &start_types[nEq], sz);
-		}
+		    codeAllEqualityTerms(pParse, pLevel, bRev, nExtraReg);
 		addrNxt = pLevel->addrNxt;
 
 		testcase(pRangeStart && (pRangeStart->eOperator & WO_LE) != 0);
@@ -1030,10 +945,6 @@ sqlWhereCodeOneLoopStart(WhereInfo * pWInfo,	/* Complete information about the W
 				VdbeCoverage(v);
 			}
 
-			if (start_types) {
-				expr_cmp_update_rhs_type(pRight, nBtm,
-							 &start_types[nEq]);
-			}
 			nConstraint += nBtm;
 			testcase(pRangeStart->wtFlags & TERM_VIRTUAL);
 			if (sqlExprIsVector(pRight) == 0) {
@@ -1048,94 +959,6 @@ sqlWhereCodeOneLoopStart(WhereInfo * pWInfo,	/* Complete information about the W
 			startEq = 0;
 			start_constraints = 1;
 		}
-		/*
-		 * Tarantool's iterator over integer fields doesn't
-		 * tolerate floating point values. Hence, if term
-		 * is equality comparison and value of operand is
-		 * not integer, we can skip it since it always
-		 * results in false: INT a == 0.5 -> false;
-		 * It is done using OP_MustBeInt facilities.
-		 * In case term is greater comparison (a > ?), we
-		 * should notify OP_SeekGT to process truncation of
-		 * floating point value: a > 0.5 -> a >= 1;
-		 * It is done by setting P5 flag for OP_Seek*.
-		 * It is worth mentioning that we do not need
-		 * this step when it comes for less (<) comparison
-		 * of nullable field. Key is NULL in this case:
-		 * values are ordered as  NULL, ... NULL, min_value,
-		 * so to fetch min value we pass NULL to GT iterator.
-		 * The only exception is less comparison in
-		 * conjunction with ORDER BY DESC clause:
-		 * in such situation we use LE iterator and
-		 * truncated value to compare. But then
-		 * pRangeStart == NULL.
-		 * This procedure is correct for compound index:
-		 * only one comparison of less/greater type can be
-		 * used at the same time. For instance,
-		 * a < 1.5 AND b > 0.5 is handled by SeekGT using
-		 * column a and fetching column b from tuple and
-		 * OP_Le comparison.
-		 *
-		 * Note that OP_ApplyType, which is emitted before
-		 * OP_Seek** doesn't truncate floating point to
-		 * integer. That's why we need this routine.
-		 * Also, note that terms are separated by OR
-		 * predicates, so we consider term as sequence
-		 * of AND'ed predicates.
-		 */
-		size_t addrs_sz;
-		int *seek_addrs = region_alloc_array(&pParse->region,
-						     typeof(seek_addrs[0]), nEq,
-						     &addrs_sz);
-		if (seek_addrs == NULL) {
-			diag_set(OutOfMemory, addrs_sz, "region_alloc_array",
-				 "seek_addrs");
-			pParse->is_aborted = true;
-			return 0;
-		}
-		memset(seek_addrs, 0, addrs_sz);
-		for (int i = 0; i < nEq; i++) {
-			enum field_type type = idx_def->key_def->parts[i].type;
-			if (type == FIELD_TYPE_INTEGER ||
-			    type == FIELD_TYPE_UNSIGNED) {
-				/*
-				 * OP_MustBeInt consider NULLs as
-				 * non-integer values, so firstly
-				 * check whether value is NULL or not.
-				 */
-				seek_addrs[i] = sqlVdbeAddOp1(v, OP_IsNull,
-							      regBase);
-				sqlVdbeAddOp2(v, OP_MustBeInt, regBase + i,
-					      addrNxt);
-				start_types[i] = FIELD_TYPE_SCALAR;
-				/*
-				 * We need to notify column cache
-				 * that type of value may change
-				 * so we should fetch value from
-				 * tuple again rather then copy
-				 * from register.
-				 */
-				sql_expr_type_cache_change(pParse, regBase + i,
-							   1);
-			}
-		}
-		/* Inequality constraint comes always at the end of list. */
-		part_count = idx_def->key_def->part_count;
-		if (pRangeStart != NULL) {
-			/*
-			 * nEq == 0 means that filter condition
-			 * contains only inequality.
-			 */
-			uint32_t ineq_idx = nEq == 0 ? 0 : nEq - 1;
-			assert(ineq_idx < part_count);
-			enum field_type ineq_type =
-				idx_def->key_def->parts[ineq_idx].type;
-			if (ineq_type == FIELD_TYPE_INTEGER ||
-			    ineq_type == FIELD_TYPE_UNSIGNED)
-				force_integer_reg = regBase + nEq;
-		}
-		emit_apply_type(pParse, regBase, nConstraint - bSeekPastNull,
-				start_types);
 		if (pLoop->nSkip > 0 && nConstraint == pLoop->nSkip) {
 			/* The skip-scan logic inside the call to codeAllEqualityConstraints()
 			 * above has already left the cursor sitting on the correct row,
@@ -1145,20 +968,8 @@ sqlWhereCodeOneLoopStart(WhereInfo * pWInfo,	/* Complete information about the W
 			op = aStartOp[(start_constraints << 2) +
 				      (startEq << 1) + bRev];
 			assert(op != 0);
-			for (uint32_t i = 0; i < nEq; ++i) {
-				if (seek_addrs[i] != 0)
-					sqlVdbeJumpHere(v, seek_addrs[i]);
-			}
 			sqlVdbeAddOp4Int(v, op, iIdxCur, addrNxt, regBase,
 					     nConstraint);
-			/* If this is Seek* opcode, and IPK is detected in the
-			 * constraints vector: force it to be integer.
-			 */
-			if ((op == OP_SeekGE || op == OP_SeekGT
-			    || op == OP_SeekLE || op == OP_SeekLT)
-			    && force_integer_reg > 0) {
-				sqlVdbeChangeP5(v, force_integer_reg);
-			}
 			VdbeCoverage(v);
 			VdbeCoverageIf(v, op == OP_Rewind);
 			testcase(op == OP_Rewind);
@@ -1188,13 +999,6 @@ sqlWhereCodeOneLoopStart(WhereInfo * pWInfo,	/* Complete information about the W
 						  addrNxt);
 				VdbeCoverage(v);
 			}
-			if (end_types) {
-				expr_cmp_update_rhs_type(pRight, nTop, end_types);
-				emit_apply_type(pParse, regBase + nEq, nTop,
-						end_types);
-			} else {
-				assert(pParse->db->mallocFailed);
-			}
 			nConstraint += nTop;
 			testcase(pRangeEnd->wtFlags & TERM_VIRTUAL);
 
@@ -1208,8 +1012,6 @@ sqlWhereCodeOneLoopStart(WhereInfo * pWInfo,	/* Complete information about the W
 			endEq = 0;
 			nConstraint++;
 		}
-		sqlDbFree(db, start_types);
-		sqlDbFree(db, end_types);
 
 		/* Top of the loop body */
 		pLevel->p2 = sqlVdbeCurrentAddr(v);
diff --git a/test/sql-tap/cast.test.lua b/test/sql-tap/cast.test.lua
index b8ad23317..b570fc878 100755
--- a/test/sql-tap/cast.test.lua
+++ b/test/sql-tap/cast.test.lua
@@ -1,6 +1,6 @@
 #!/usr/bin/env tarantool
 local test = require("sqltester")
-test:plan(107)
+test:plan(108)
 
 --!./tcltestrunner.lua
 -- 2005 June 25
@@ -1144,4 +1144,16 @@ test:do_catchsql_test(
         1, "Type mismatch: can not convert double(2.5) to string"
     })
 
+-- Make sure that search using index in field type number work right.
+test:do_execsql_test(
+    "cast-11",
+    [[
+        CREATE TABLE t6(d DOUBLE PRIMARY KEY);
+        INSERT INTO t6 VALUES(10000000000000000);
+        SELECT d FROM t6 WHERE d < 10000000000000001 and d > 9999999999999999;
+        DROP TABLE t6;
+    ]], {
+        10000000000000000
+    })
+
 test:finish_test()
diff --git a/test/sql-tap/in4.test.lua b/test/sql-tap/in4.test.lua
index 8442944b9..aa6483697 100755
--- a/test/sql-tap/in4.test.lua
+++ b/test/sql-tap/in4.test.lua
@@ -147,13 +147,13 @@ test:do_execsql_test(
         -- </in4-2.7>
     })
 
-test:do_execsql_test(
+test:do_catchsql_test(
     "in4-2.8",
     [[
         SELECT b FROM t2 WHERE a IN ('', '0.0.0', '2')
     ]], {
         -- <in4-2.8>
-        "two"
+        1, "Type mismatch: can not convert string('') to integer"
         -- </in4-2.8>
     })
 
diff --git a/test/sql-tap/join.test.lua b/test/sql-tap/join.test.lua
index 48639a7b3..abfabfacf 100755
--- a/test/sql-tap/join.test.lua
+++ b/test/sql-tap/join.test.lua
@@ -1028,13 +1028,13 @@ test:do_test(
         -- </join-11.8>
     })
 
-test:do_execsql_test(
+test:do_catchsql_test(
     "join-11.9",
     [[
         SELECT * FROM t1 NATURAL JOIN t2
     ]], {
         -- <join-11.9>
-        "one", "1", "two", "2"
+        1, "Type mismatch: can not convert string('1') to integer"
         -- </join-11.9>
     })
 
diff --git a/test/sql-tap/tkt-9a8b09f8e6.test.lua b/test/sql-tap/tkt-9a8b09f8e6.test.lua
index 43322468d..cc321c2f6 100755
--- a/test/sql-tap/tkt-9a8b09f8e6.test.lua
+++ b/test/sql-tap/tkt-9a8b09f8e6.test.lua
@@ -1,6 +1,6 @@
 #!/usr/bin/env tarantool
 local test = require("sqltester")
-test:plan(47)
+test:plan(45)
 
 --!./tcltestrunner.lua
 -- 2014 June 26
@@ -179,16 +179,6 @@ test:do_execsql_test(
         -- </3.2>
     })
 
-test:do_execsql_test(
-    3.3,
-    [[
-        SELECT x FROM t2 WHERE x IN ('1');
-    ]], {
-        -- <3.3>
-        1
-        -- </3.3>
-    })
-
 test:do_execsql_test(
     3.5,
     [[
@@ -209,16 +199,6 @@ test:do_execsql_test(
         -- </3.6>
     })
 
-test:do_execsql_test(
-    3.7,
-    [[
-        SELECT x FROM t2 WHERE '1' IN (x);
-    ]], {
-        -- <3.7>
-        1
-        -- </3.7>
-    })
-
 test:do_execsql_test(
     4.1,
     [[
-- 
2.25.1


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

* [Tarantool-patches] [PATCH v1 5/7] sql: remove implicit cast from OP_MakeRecord
  2021-07-28 20:51 [Tarantool-patches] [PATCH v1 0/7] Rework implicit cast Mergen Imeev via Tarantool-patches
                   ` (3 preceding siblings ...)
  2021-07-28 20:51 ` [Tarantool-patches] [PATCH v1 4/7] sql: remove unnecessary calls of OP_ApplyType Mergen Imeev via Tarantool-patches
@ 2021-07-28 20:51 ` Mergen Imeev via Tarantool-patches
  2021-08-04 22:27   ` Vladislav Shpilevoy via Tarantool-patches
  2021-07-28 20:51 ` [Tarantool-patches] [PATCH v1 6/7] sql: remove implicit cast from OP_MustBeInt Mergen Imeev via Tarantool-patches
  2021-07-28 20:51 ` [Tarantool-patches] [PATCH v1 7/7] sql: remove unused MEM cast functions Mergen Imeev via Tarantool-patches
  6 siblings, 1 reply; 25+ messages in thread
From: Mergen Imeev via Tarantool-patches @ 2021-07-28 20:51 UTC (permalink / raw)
  To: v.shpilevoy; +Cc: tarantool-patches

This patch removes deprecated implicit cast from OP_MakeRecord opcode.
Not there will be no implicit cast in OP_MakeRecord opcode.

Closes #4230
Part of #4470
---
 .../gh-4230-implicit-cast-for-comparison.md   |  3 +++
 src/box/sql/analyze.c                         |  7 +------
 src/box/sql/delete.c                          |  8 ++------
 src/box/sql/expr.c                            |  9 ++-------
 src/box/sql/fk_constraint.c                   |  9 ++-------
 src/box/sql/insert.c                          | 14 --------------
 src/box/sql/sqlInt.h                          |  4 ----
 src/box/sql/update.c                          | 14 +++-----------
 src/box/sql/vdbe.c                            | 19 +------------------
 src/box/sql/wherecode.c                       | 15 +--------------
 test/sql-tap/cast.test.lua                    | 11 ++++++++++-
 test/sql-tap/in3.test.lua                     |  4 ++--
 12 files changed, 27 insertions(+), 90 deletions(-)
 create mode 100644 changelogs/unreleased/gh-4230-implicit-cast-for-comparison.md

diff --git a/changelogs/unreleased/gh-4230-implicit-cast-for-comparison.md b/changelogs/unreleased/gh-4230-implicit-cast-for-comparison.md
new file mode 100644
index 000000000..9f523d3ed
--- /dev/null
+++ b/changelogs/unreleased/gh-4230-implicit-cast-for-comparison.md
@@ -0,0 +1,3 @@
+## feature/sql
+
+* Implicit cast for comparison now works according to defined rules (gh-4230).
diff --git a/src/box/sql/analyze.c b/src/box/sql/analyze.c
index b87f69512..e97fefb3a 100644
--- a/src/box/sql/analyze.c
+++ b/src/box/sql/analyze.c
@@ -968,12 +968,7 @@ vdbe_emit_analyze_space(struct Parse *parse, struct space *space)
 		sqlVdbeAddOp2(v, OP_Next, idx_cursor, next_row_addr);
 		/* Add the entry to the stat1 table. */
 		callStatGet(v, stat4_reg, STAT_GET_STAT1, stat1_reg);
-		enum field_type types[4] = { FIELD_TYPE_STRING,
-					     FIELD_TYPE_STRING,
-					     FIELD_TYPE_STRING,
-					     field_type_MAX };
-		sqlVdbeAddOp4(v, OP_MakeRecord, tab_name_reg, 4, tmp_reg,
-				  (char *)types, sizeof(types));
+		sqlVdbeAddOp3(v, OP_MakeRecord, tab_name_reg, 4, tmp_reg);
 		sqlVdbeAddOp4(v, OP_IdxInsert, tmp_reg, 0, 0,
 				  (char *)stat1, P4_SPACEPTR);
 		/* Add the entries to the stat4 table. */
diff --git a/src/box/sql/delete.c b/src/box/sql/delete.c
index 62a726fdd..5226dd6ea 100644
--- a/src/box/sql/delete.c
+++ b/src/box/sql/delete.c
@@ -328,12 +328,8 @@ sql_table_delete_from(struct Parse *parse, struct SrcList *tab_list,
 			 * key.
 			 */
 			key_len = 0;
-			struct index *pk = space_index(space, 0);
-			enum field_type *types = is_view ? NULL :
-						 sql_index_type_str(parse->db,
-								    pk->def);
-			sqlVdbeAddOp4(v, OP_MakeRecord, reg_pk, pk_len,
-					  reg_key, (char *)types, P4_DYNAMIC);
+			sqlVdbeAddOp3(v, OP_MakeRecord, reg_pk, pk_len,
+				      reg_key);
 			/* Set flag to save memory allocating one
 			 * by malloc.
 			 */
diff --git a/src/box/sql/expr.c b/src/box/sql/expr.c
index 3772596d6..8635f2443 100644
--- a/src/box/sql/expr.c
+++ b/src/box/sql/expr.c
@@ -2859,8 +2859,6 @@ sqlCodeSubselect(Parse * pParse,	/* Parsing context */
 				struct ExprList_item *pItem;
 				int r1, r2, r3;
 
-				enum field_type lhs_type =
-					sql_expr_type(pLeft);
 				bool unused;
 				struct coll *unused_coll;
 				if (sql_expr_coll(pParse, pExpr->pLeft, &unused,
@@ -2886,11 +2884,8 @@ sqlCodeSubselect(Parse * pParse,	/* Parsing context */
 						jmpIfDynamic = -1;
 					}
 					r3 = sqlExprCodeTarget(pParse, pE2, r1);
-					enum field_type types[2] =
-						{ lhs_type, field_type_MAX };
-	 				sqlVdbeAddOp4(v, OP_MakeRecord, r3,
-							  1, r2, (char *)types,
-							  sizeof(types));
+					sqlVdbeAddOp3(v, OP_MakeRecord, r3, 1,
+						      r2);
 					sql_expr_type_cache_change(pParse,
 								   r3, 1);
 					sqlVdbeAddOp2(v, OP_IdxInsert, r2,
diff --git a/src/box/sql/fk_constraint.c b/src/box/sql/fk_constraint.c
index 0dd10c420..2a9399d78 100644
--- a/src/box/sql/fk_constraint.c
+++ b/src/box/sql/fk_constraint.c
@@ -262,13 +262,8 @@ fk_constraint_lookup_parent(struct Parse *parse_context, struct space *parent,
 					  link->child_field + 1 + reg_data,
 					  temp_regs + i);
 		}
-		struct index *idx = space_index(parent, referenced_idx);
-		assert(idx != NULL);
-		sqlVdbeAddOp4(v, OP_MakeRecord, temp_regs, field_count,
-				  rec_reg,
-				  (char *) sql_index_type_str(parse_context->db,
-							      idx->def),
-				  P4_DYNAMIC);
+		sqlVdbeAddOp3(v, OP_MakeRecord, temp_regs, field_count,
+			      rec_reg);
 		sqlVdbeAddOp4Int(v, OP_Found, cursor, ok_label, rec_reg, 0);
 		sqlReleaseTempReg(parse_context, rec_reg);
 		sqlReleaseTempRange(parse_context, temp_regs, field_count);
diff --git a/src/box/sql/insert.c b/src/box/sql/insert.c
index 02e9f9673..21b4f2407 100644
--- a/src/box/sql/insert.c
+++ b/src/box/sql/insert.c
@@ -42,20 +42,6 @@
 #include "box/box.h"
 #include "box/schema.h"
 
-enum field_type *
-sql_index_type_str(struct sql *db, const struct index_def *idx_def)
-{
-	uint32_t column_count = idx_def->key_def->part_count;
-	uint32_t sz = (column_count + 1) * sizeof(enum field_type);
-	enum field_type *types = (enum field_type *) sqlDbMallocRaw(db, sz);
-	if (types == NULL)
-		return NULL;
-	for (uint32_t i = 0; i < column_count; i++)
-		types[i] = idx_def->key_def->parts[i].type;
-	types[column_count] = field_type_MAX;
-	return types;
-}
-
 void
 sql_emit_table_types(struct Vdbe *v, struct space_def *def, int reg)
 {
diff --git a/src/box/sql/sqlInt.h b/src/box/sql/sqlInt.h
index 115c52f96..4f94afdf7 100644
--- a/src/box/sql/sqlInt.h
+++ b/src/box/sql/sqlInt.h
@@ -3792,10 +3792,6 @@ int sqlVarintLen(u64 v);
 #define getVarint    sqlGetVarint
 #define putVarint    sqlPutVarint
 
-/** Return string consisting of fields types of given index. */
-enum field_type *
-sql_index_type_str(struct sql *db, const struct index_def *idx_def);
-
 /**
  * Code an OP_ApplyType opcode that will force types
  * for given range of register starting from @reg.
diff --git a/src/box/sql/update.c b/src/box/sql/update.c
index 24c7cfa27..22f82390c 100644
--- a/src/box/sql/update.c
+++ b/src/box/sql/update.c
@@ -251,11 +251,7 @@ sqlUpdate(Parse * pParse,		/* The parser context */
 		nKey = pk_part_count;
 		regKey = iPk;
 	} else {
-		enum field_type *types = is_view ? NULL :
-					 sql_index_type_str(pParse->db,
-							    pPk->def);
-		sqlVdbeAddOp4(v, OP_MakeRecord, iPk, pk_part_count,
-				  regKey, (char *) types, P4_DYNAMIC);
+		sqlVdbeAddOp3(v, OP_MakeRecord, iPk, pk_part_count, regKey);
 		/*
 		 * Set flag to save memory allocating one by
 		 * malloc.
@@ -420,12 +416,8 @@ sqlUpdate(Parse * pParse,		/* The parser context */
 			int key_reg;
 			if (okOnePass) {
 				key_reg = sqlGetTempReg(pParse);
-				enum field_type *types =
-					sql_index_type_str(pParse->db,
-							   pPk->def);
-				sqlVdbeAddOp4(v, OP_MakeRecord, iPk,
-						  pk_part_count, key_reg,
-						  (char *) types, P4_DYNAMIC);
+				sqlVdbeAddOp3(v, OP_MakeRecord, iPk,
+					      pk_part_count, key_reg);
 			} else {
 				assert(nKey == 0);
 				key_reg = regKey;
diff --git a/src/box/sql/vdbe.c b/src/box/sql/vdbe.c
index c24265a1d..81551acf3 100644
--- a/src/box/sql/vdbe.c
+++ b/src/box/sql/vdbe.c
@@ -2051,24 +2051,17 @@ case OP_ApplyType: {
 	break;
 }
 
-/* Opcode: MakeRecord P1 P2 P3 P4 P5
+/* Opcode: MakeRecord P1 P2 P3 * P5
  * Synopsis: r[P3]=mkrec(r[P1@P2])
  *
  * Convert P2 registers beginning with P1 into the [record format]
  * use as a data record in a database table or as a key
  * in an index.  The OP_Column opcode can decode the record later.
  *
- * P4 may be a string that is P2 characters long.  The nth character of the
- * string indicates the column type that should be used for the nth
- * field of the index key.
- *
- * If P4 is NULL then all index fields have type SCALAR.
- *
  * If P5 is not NULL then record under construction is intended to be inserted
  * into ephemeral space. Thus, sort of memory optimization can be performed.
  */
 case OP_MakeRecord: {
-	Mem *pRec;             /* The new record */
 	Mem *pData0;           /* First field to be combined into the record */
 	Mem MAYBE_UNUSED *pLast;  /* Last field of the record */
 	int nField;            /* Number of fields in the record */
@@ -2089,7 +2082,6 @@ case OP_MakeRecord: {
 	 * is the offset from the beginning of the record to data0.
 	 */
 	nField = pOp->p1;
-	enum field_type *types = pOp->p4.types;
 	bIsEphemeral = pOp->p5;
 	assert(nField>0 && pOp->p2>0 && pOp->p2+nField<=(p->nMem+1 - p->nCursor)+1);
 	pData0 = &aMem[nField];
@@ -2100,15 +2092,6 @@ case OP_MakeRecord: {
 	assert(pOp->p3<pOp->p1 || pOp->p3>=pOp->p1+pOp->p2);
 	pOut = vdbe_prepare_null_out(p, pOp->p3);
 
-	/* Apply the requested types to all inputs */
-	assert(pData0<=pLast);
-	if (types != NULL) {
-		pRec = pData0;
-		do {
-			mem_cast_implicit_old(pRec++, *(types++));
-		} while(types[0] != field_type_MAX);
-	}
-
 	struct region *region = &fiber()->gc;
 	size_t used = region_used(region);
 	uint32_t tuple_size;
diff --git a/src/box/sql/wherecode.c b/src/box/sql/wherecode.c
index 92d374200..df6cc92e1 100644
--- a/src/box/sql/wherecode.c
+++ b/src/box/sql/wherecode.c
@@ -602,9 +602,6 @@ codeAllEqualityTerms(Parse * pParse,	/* Parsing context */
 	nReg = pLoop->nEq + nExtraReg;
 	pParse->nMem += nReg;
 
-	enum field_type *type = sql_index_type_str(pParse->db, idx_def);
-	assert(type != NULL || pParse->db->mallocFailed);
-
 	if (nSkip) {
 		int iIdxCur = pLevel->iIdxCur;
 		sqlVdbeAddOp1(v, (bRev ? OP_Last : OP_Rewind), iIdxCur);
@@ -647,17 +644,7 @@ codeAllEqualityTerms(Parse * pParse,	/* Parsing context */
 				sqlVdbeAddOp2(v, OP_SCopy, r1, regBase + j);
 			}
 		}
-		if (pTerm->eOperator & WO_IN) {
-			if (pTerm->pExpr->flags & EP_xIsSelect) {
-				/* No type ever needs to be (or should be) applied to a value
-				 * from the RHS of an "? IN (SELECT ...)" expression. The
-				 * sqlFindInIndex() routine has already ensured that the
-				 * type of the comparison has been applied to the value.
-				 */
-				if (type != NULL)
-					type[j] = FIELD_TYPE_SCALAR;
-			}
-		} else if ((pTerm->eOperator & WO_ISNULL) == 0) {
+		if ((pTerm->eOperator & (WO_IN | WO_ISNULL)) == 0) {
 			Expr *pRight = pTerm->pExpr->pRight;
 			if (sqlExprCanBeNull(pRight)) {
 				sqlVdbeAddOp2(v, OP_IsNull, regBase + j,
diff --git a/test/sql-tap/cast.test.lua b/test/sql-tap/cast.test.lua
index b570fc878..927114772 100755
--- a/test/sql-tap/cast.test.lua
+++ b/test/sql-tap/cast.test.lua
@@ -1,6 +1,6 @@
 #!/usr/bin/env tarantool
 local test = require("sqltester")
-test:plan(108)
+test:plan(109)
 
 --!./tcltestrunner.lua
 -- 2005 June 25
@@ -1156,4 +1156,13 @@ test:do_execsql_test(
         10000000000000000
     })
 
+-- Make sure that there is no unnecessary implicit casts in IN operator.
+test:do_catchsql_test(
+    "cast-12",
+    [[
+        SELECT 1 IN (SELECT '1');
+    ]], {
+        1, "Type mismatch: can not convert integer(1) to string"
+    })
+
 test:finish_test()
diff --git a/test/sql-tap/in3.test.lua b/test/sql-tap/in3.test.lua
index 5f3f543af..4536fb0d3 100755
--- a/test/sql-tap/in3.test.lua
+++ b/test/sql-tap/in3.test.lua
@@ -342,7 +342,7 @@ test:do_test(
         return exec_neph(" SELECT y IN (SELECT a FROM t1) FROM t2 ")
     end, {
         -- <in3-3.5>
-        1, true
+        1, false
         -- </in3-3.5>
     })
 
@@ -366,7 +366,7 @@ test:do_test(
         return exec_neph(" SELECT y IN (SELECT c FROM t1) FROM t2 ")
     end, {
         -- <in3-3.7>
-        1, true
+        1, false
         -- </in3-3.7>
     })
 
-- 
2.25.1


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

* [Tarantool-patches] [PATCH v1 6/7] sql: remove implicit cast from OP_MustBeInt
  2021-07-28 20:51 [Tarantool-patches] [PATCH v1 0/7] Rework implicit cast Mergen Imeev via Tarantool-patches
                   ` (4 preceding siblings ...)
  2021-07-28 20:51 ` [Tarantool-patches] [PATCH v1 5/7] sql: remove implicit cast from OP_MakeRecord Mergen Imeev via Tarantool-patches
@ 2021-07-28 20:51 ` Mergen Imeev via Tarantool-patches
  2021-08-05 23:47   ` Mergen Imeev via Tarantool-patches
  2021-07-28 20:51 ` [Tarantool-patches] [PATCH v1 7/7] sql: remove unused MEM cast functions Mergen Imeev via Tarantool-patches
  6 siblings, 1 reply; 25+ messages in thread
From: Mergen Imeev via Tarantool-patches @ 2021-07-28 20:51 UTC (permalink / raw)
  To: v.shpilevoy; +Cc: tarantool-patches

This patch removes implicit casting from STRING to number in
OP_MustBeInt opcode.

Part of #4470
---
 src/box/sql/vdbe.c | 3 ++-
 1 file changed, 2 insertions(+), 1 deletion(-)

diff --git a/src/box/sql/vdbe.c b/src/box/sql/vdbe.c
index 81551acf3..89e77baee 100644
--- a/src/box/sql/vdbe.c
+++ b/src/box/sql/vdbe.c
@@ -1435,7 +1435,8 @@ case OP_AddImm: {            /* in1 */
  */
 case OP_MustBeInt: {            /* jump, in1 */
 	pIn1 = &aMem[pOp->p1];
-	if (mem_to_int_precise(pIn1) != 0) {
+	enum field_type type = FIELD_TYPE_INTEGER;
+	if (!mem_is_num(pIn1) || mem_cast_implicit_number(pIn1, type) != 0) {
 		if (pOp->p2 != 0)
 			goto jump_to_p2;
 		diag_set(ClientError, ER_SQL_TYPE_MISMATCH,
-- 
2.25.1


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

* [Tarantool-patches] [PATCH v1 7/7] sql: remove unused MEM cast functions
  2021-07-28 20:51 [Tarantool-patches] [PATCH v1 0/7] Rework implicit cast Mergen Imeev via Tarantool-patches
                   ` (5 preceding siblings ...)
  2021-07-28 20:51 ` [Tarantool-patches] [PATCH v1 6/7] sql: remove implicit cast from OP_MustBeInt Mergen Imeev via Tarantool-patches
@ 2021-07-28 20:51 ` Mergen Imeev via Tarantool-patches
  2021-08-04 22:27   ` Vladislav Shpilevoy via Tarantool-patches
  6 siblings, 1 reply; 25+ messages in thread
From: Mergen Imeev via Tarantool-patches @ 2021-07-28 20:51 UTC (permalink / raw)
  To: v.shpilevoy; +Cc: tarantool-patches

This patch removes functions that become unused due to changes of
implicit and explicit cast rules.

Part of #4470
---
 src/box/sql/func.c      |   2 +-
 src/box/sql/mem.c       | 222 ----------------------------------------
 src/box/sql/mem.h       |  19 ----
 src/box/sql/vdbe.h      |   2 +-
 src/box/sql/vdbeaux.c   |   6 +-
 src/box/sql/whereexpr.c |   4 +-
 6 files changed, 5 insertions(+), 250 deletions(-)

diff --git a/src/box/sql/func.c b/src/box/sql/func.c
index 9a96e96ff..7dd3a4897 100644
--- a/src/box/sql/func.c
+++ b/src/box/sql/func.c
@@ -58,7 +58,7 @@ static const void *
 mem_as_bin(struct Mem *mem)
 {
 	const char *s;
-	if (mem_cast_implicit(mem, FIELD_TYPE_VARBINARY) != 0 &&
+	if (mem_cast_explicit(mem, FIELD_TYPE_VARBINARY) != 0 &&
 	    mem_to_str(mem) != 0)
 		return NULL;
 	if (mem_get_bin(mem, &s) != 0)
diff --git a/src/box/sql/mem.c b/src/box/sql/mem.c
index 98b367054..d965d327b 100644
--- a/src/box/sql/mem.c
+++ b/src/box/sql/mem.c
@@ -820,58 +820,6 @@ double_to_int(struct Mem *mem)
 	return -1;
 }
 
-static inline int
-double_to_int_precise(struct Mem *mem)
-{
-	assert(mem->type == MEM_TYPE_DOUBLE);
-	double d = mem->u.r;
-	if (d < 0 && d >= (double)INT64_MIN && (double)(int64_t)d == d) {
-		mem->u.i = (int64_t)d;
-		mem->type = MEM_TYPE_INT;
-		assert(mem->flags == 0);
-		mem->field_type = FIELD_TYPE_INTEGER;
-		return 0;
-	}
-	if (d >= 0 && d < (double)UINT64_MAX && (double)(uint64_t)d == d) {
-		mem->u.u = (uint64_t)d;
-		mem->type = MEM_TYPE_UINT;
-		assert(mem->flags == 0);
-		mem->field_type = FIELD_TYPE_UNSIGNED;
-		return 0;
-	}
-	return -1;
-}
-
-static inline int
-double_to_uint(struct Mem *mem)
-{
-	assert(mem->type == MEM_TYPE_DOUBLE);
-	double d = mem->u.r;
-	if (d >= 0 && d < (double)UINT64_MAX) {
-		mem->u.u = (uint64_t)d;
-		mem->type = MEM_TYPE_UINT;
-		assert(mem->flags == 0);
-		mem->field_type = FIELD_TYPE_UNSIGNED;
-		return 0;
-	}
-	return -1;
-}
-
-static inline int
-double_to_uint_precise(struct Mem *mem)
-{
-	assert(mem->type == MEM_TYPE_DOUBLE);
-	double d = mem->u.r;
-	if (d >= 0 && d < (double)UINT64_MAX && (double)(uint64_t)d == d) {
-		mem->u.u = (uint64_t)d;
-		mem->type = MEM_TYPE_UINT;
-		assert(mem->flags == 0);
-		mem->field_type = FIELD_TYPE_UNSIGNED;
-		return 0;
-	}
-	return -1;
-}
-
 static inline int
 double_to_str0(struct Mem *mem)
 {
@@ -1025,19 +973,6 @@ mem_to_int(struct Mem *mem)
 	return -1;
 }
 
-int
-mem_to_int_precise(struct Mem *mem)
-{
-	assert(mem->type < MEM_TYPE_INVALID);
-	if ((mem->type & (MEM_TYPE_INT | MEM_TYPE_UINT)) != 0)
-		return 0;
-	if (mem->type == MEM_TYPE_STR)
-		return str_to_int(mem);
-	if (mem->type == MEM_TYPE_DOUBLE)
-		return double_to_int_precise(mem);
-	return -1;
-}
-
 int
 mem_to_double(struct Mem *mem)
 {
@@ -1183,163 +1118,6 @@ mem_cast_explicit(struct Mem *mem, enum field_type type)
 	return -1;
 }
 
-int
-mem_cast_implicit(struct Mem *mem, enum field_type type)
-{
-	if (mem->type == MEM_TYPE_NULL) {
-		mem->field_type = type;
-		return 0;
-	}
-	switch (type) {
-	case FIELD_TYPE_UNSIGNED:
-		if (mem->type == MEM_TYPE_UINT)
-			return 0;
-		if (mem->type == MEM_TYPE_DOUBLE)
-			return double_to_uint(mem);
-		return -1;
-	case FIELD_TYPE_STRING:
-		if (mem->type == MEM_TYPE_STR)
-			return 0;
-		if (mem->type == MEM_TYPE_UUID)
-			return uuid_to_str0(mem);
-		return -1;
-	case FIELD_TYPE_DOUBLE:
-		if (mem->type == MEM_TYPE_DOUBLE)
-			return 0;
-		if ((mem->type & (MEM_TYPE_INT | MEM_TYPE_UINT)) != 0)
-			return int_to_double(mem);
-		return -1;
-	case FIELD_TYPE_INTEGER:
-		if ((mem->type & (MEM_TYPE_INT | MEM_TYPE_UINT)) != 0)
-			return 0;
-		if (mem->type == MEM_TYPE_DOUBLE)
-			return double_to_int(mem);
-		return -1;
-	case FIELD_TYPE_BOOLEAN:
-		if (mem->type == MEM_TYPE_BOOL)
-			return 0;
-		return -1;
-	case FIELD_TYPE_VARBINARY:
-		if ((mem->type & (MEM_TYPE_BIN | MEM_TYPE_MAP |
-				  MEM_TYPE_ARRAY)) != 0)
-			return 0;
-		if (mem->type == MEM_TYPE_UUID)
-			return uuid_to_bin(mem);
-		return -1;
-	case FIELD_TYPE_NUMBER:
-		if (mem_is_num(mem))
-			return 0;
-		return -1;
-	case FIELD_TYPE_MAP:
-		if (mem->type == MEM_TYPE_MAP)
-			return 0;
-		return -1;
-	case FIELD_TYPE_ARRAY:
-		if (mem->type == MEM_TYPE_ARRAY)
-			return 0;
-		return -1;
-	case FIELD_TYPE_SCALAR:
-		if ((mem->type & (MEM_TYPE_MAP | MEM_TYPE_ARRAY)) != 0)
-			return -1;
-		return 0;
-	case FIELD_TYPE_UUID:
-		if (mem->type == MEM_TYPE_UUID)
-			return 0;
-		if (mem->type == MEM_TYPE_STR)
-			return str_to_uuid(mem);
-		if (mem->type == MEM_TYPE_BIN)
-			return bin_to_uuid(mem);
-		return -1;
-	case FIELD_TYPE_ANY:
-		return 0;
-	default:
-		break;
-	}
-	return -1;
-}
-
-int
-mem_cast_implicit_old(struct Mem *mem, enum field_type type)
-{
-	if (mem->type == MEM_TYPE_NULL)
-		return 0;
-	switch (type) {
-	case FIELD_TYPE_UNSIGNED:
-		if (mem->type == MEM_TYPE_UINT)
-			return 0;
-		if (mem->type == MEM_TYPE_DOUBLE)
-			return double_to_uint_precise(mem);
-		if (mem->type == MEM_TYPE_STR)
-			return str_to_uint(mem);
-		return -1;
-	case FIELD_TYPE_STRING:
-		if ((mem->type & (MEM_TYPE_STR | MEM_TYPE_BIN)) != 0)
-			return 0;
-		if ((mem->type & (MEM_TYPE_INT | MEM_TYPE_UINT)) != 0)
-			return int_to_str0(mem);
-		if (mem->type == MEM_TYPE_DOUBLE)
-			return double_to_str0(mem);
-		if (mem->type == MEM_TYPE_UUID)
-			return uuid_to_str0(mem);
-		return -1;
-	case FIELD_TYPE_DOUBLE:
-		if (mem->type == MEM_TYPE_DOUBLE)
-			return 0;
-		if ((mem->type & (MEM_TYPE_INT | MEM_TYPE_UINT)) != 0)
-			return int_to_double(mem);
-		if (mem->type == MEM_TYPE_STR)
-			return bin_to_str(mem);
-		return -1;
-	case FIELD_TYPE_INTEGER:
-		if ((mem->type & (MEM_TYPE_INT | MEM_TYPE_UINT)) != 0)
-			return 0;
-		if (mem->type == MEM_TYPE_STR)
-			return str_to_int(mem);
-		if (mem->type == MEM_TYPE_DOUBLE)
-			return double_to_int_precise(mem);
-		return -1;
-	case FIELD_TYPE_BOOLEAN:
-		if (mem->type == MEM_TYPE_BOOL)
-			return 0;
-		return -1;
-	case FIELD_TYPE_VARBINARY:
-		if (mem->type == MEM_TYPE_BIN)
-			return 0;
-		if (mem->type == MEM_TYPE_UUID)
-			return uuid_to_bin(mem);
-		return -1;
-	case FIELD_TYPE_NUMBER:
-		if (mem_is_num(mem))
-			return 0;
-		if (mem->type == MEM_TYPE_STR)
-			return mem_to_number(mem);
-		return -1;
-	case FIELD_TYPE_MAP:
-		if (mem->type == MEM_TYPE_MAP)
-			return 0;
-		return -1;
-	case FIELD_TYPE_ARRAY:
-		if (mem->type == MEM_TYPE_ARRAY)
-			return 0;
-		return -1;
-	case FIELD_TYPE_SCALAR:
-		if ((mem->type & (MEM_TYPE_MAP | MEM_TYPE_ARRAY)) != 0)
-			return -1;
-		return 0;
-	case FIELD_TYPE_UUID:
-		if (mem->type == MEM_TYPE_UUID)
-			return 0;
-		if (mem->type == MEM_TYPE_STR)
-			return str_to_uuid(mem);
-		if (mem->type == MEM_TYPE_BIN)
-			return bin_to_uuid(mem);
-		return -1;
-	default:
-		break;
-	}
-	return -1;
-}
-
 int
 mem_cast_implicit_number(struct Mem *mem, enum field_type type)
 {
diff --git a/src/box/sql/mem.h b/src/box/sql/mem.h
index 5d1d7592e..de0558150 100644
--- a/src/box/sql/mem.h
+++ b/src/box/sql/mem.h
@@ -692,15 +692,6 @@ mem_cmp(const struct Mem *a, const struct Mem *b, int *result,
 int
 mem_to_int(struct Mem *mem);
 
-/**
- * Convert the given MEM to INTEGER. This function and the function above define
- * the rules that are used to convert values of all other types to INTEGER. In
- * this function, the conversion from double to integer is only possible if it
- * is lossless.
- */
-int
-mem_to_int_precise(struct Mem *mem);
-
 /**
  * Convert the given MEM to DOUBLE. This function defines the rules that are
  * used to convert values of all other types to DOUBLE.
@@ -736,16 +727,6 @@ mem_to_str0(struct Mem *mem);
 int
 mem_cast_explicit(struct Mem *mem, enum field_type type);
 
-/** Convert the given MEM to given type according to implicit cast rules. */
-int
-mem_cast_implicit(struct Mem *mem, enum field_type type);
-
-/**
- * Convert the given MEM to given type according to legacy implicit cast rules.
- */
-int
-mem_cast_implicit_old(struct Mem *mem, enum field_type type);
-
 /**
  * Convert the given MEM that contains numeric value to given numeric type
  * according to implicit cast rules. This function cannot fail. Returns:
diff --git a/src/box/sql/vdbe.h b/src/box/sql/vdbe.h
index 118f1cd83..be112c72d 100644
--- a/src/box/sql/vdbe.h
+++ b/src/box/sql/vdbe.h
@@ -266,7 +266,7 @@ sql *sqlVdbeDb(Vdbe *);
 void sqlVdbeSetSql(Vdbe *, const char *z, int n);
 void sqlVdbeSwap(Vdbe *, Vdbe *);
 VdbeOp *sqlVdbeTakeOpArray(Vdbe *, int *, int *);
-sql_value *sqlVdbeGetBoundValue(Vdbe *, int, u8);
+sql_value *sqlVdbeGetBoundValue(Vdbe *, int);
 char *sqlVdbeExpandSql(Vdbe *, const char *);
 
 /**
diff --git a/src/box/sql/vdbeaux.c b/src/box/sql/vdbeaux.c
index 4a1fdb637..fc06e502e 100644
--- a/src/box/sql/vdbeaux.c
+++ b/src/box/sql/vdbeaux.c
@@ -2323,17 +2323,15 @@ sqlVdbeDb(Vdbe * v)
  * The returned value must be freed by the caller using sqlValueFree().
  */
 sql_value *
-sqlVdbeGetBoundValue(Vdbe * v, int iVar, u8 aff)
+sqlVdbeGetBoundValue(Vdbe * v, int iVar)
 {
 	assert(iVar > 0);
 	if (v) {
 		Mem *pMem = &v->aVar[iVar - 1];
 		if (!mem_is_null(pMem)) {
 			sql_value *pRet = sqlValueNew(v->db);
-			if (pRet) {
+			if (pRet)
 				mem_copy(pRet, pMem);
-				mem_cast_implicit_old(pRet, aff);
-			}
 			return pRet;
 		}
 	}
diff --git a/src/box/sql/whereexpr.c b/src/box/sql/whereexpr.c
index fe7329ea8..6849f13ec 100644
--- a/src/box/sql/whereexpr.c
+++ b/src/box/sql/whereexpr.c
@@ -309,9 +309,7 @@ like_optimization_is_valid(Parse *pParse, Expr *pExpr, Expr **ppPrefix,
 	if (op == TK_VARIABLE) {
 		Vdbe *pReprepare = pParse->pReprepare;
 		int iCol = pRight->iColumn;
-		pVal =
-		    sqlVdbeGetBoundValue(pReprepare, iCol,
-					     FIELD_TYPE_SCALAR);
+		pVal = sqlVdbeGetBoundValue(pReprepare, iCol);
 		if (pVal != NULL && mem_is_str(pVal)) {
 			if (mem_as_str0(pVal) == NULL)
 				return -1;
-- 
2.25.1


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

* Re: [Tarantool-patches] [PATCH v1 1/7] sql: rework implicit cast fo assignment
  2021-07-28 20:51 ` [Tarantool-patches] [PATCH v1 1/7] sql: rework implicit cast fo assignment Mergen Imeev via Tarantool-patches
@ 2021-07-30 21:55   ` Vladislav Shpilevoy via Tarantool-patches
  2021-08-04 22:21     ` Vladislav Shpilevoy via Tarantool-patches
  2021-08-05 23:27     ` Mergen Imeev via Tarantool-patches
  0 siblings, 2 replies; 25+ messages in thread
From: Vladislav Shpilevoy via Tarantool-patches @ 2021-07-30 21:55 UTC (permalink / raw)
  To: imeevma; +Cc: tarantool-patches

Thanks for the patch!

I will return later to continue the review of the next commits.
Sending first comments so as I could switch to other patches.

See 10 comments below.

> diff --git a/changelogs/unreleased/gh-4470-implicit-cast-for-assignment.md b/changelogs/unreleased/gh-4470-implicit-cast-for-assignment.md
> new file mode 100644
> index 000000000..c758494eb
> --- /dev/null
> +++ b/changelogs/unreleased/gh-4470-implicit-cast-for-assignment.md
> @@ -0,0 +1,3 @@
> +## feature/sql
> +
> +* Implicit cast for assignment now works according to defined rules (gh-4470).

1. Please, be more specific about what exactly has changed.

> diff --git a/src/box/sql/mem.c b/src/box/sql/mem.c
> index 351d80b76..e804dba67 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) ((int)(a > (type)b) - (int)(a < (type)b))

2. Please, wrap 'a' and 'b' into parentheses. Otherwise the macro might
work in unexpected ways if these are expressions.

> @@ -900,6 +902,92 @@ uuid_to_bin(struct Mem *mem)
>  	return mem_copy_bin(mem, (char *)&mem->u.uuid, UUID_LEN);
>  }
>  
> +static inline int
> +forced_int_to_double(struct Mem *mem)
> +{
> +	assert(mem->type == MEM_TYPE_INT || mem->type == MEM_TYPE_UINT);
> +	double d;
> +	int res;
> +	if (mem->type == MEM_TYPE_INT) {
> +		d = (double)mem->u.i;
> +		res = CMP_OLD_NEW(mem->u.i, d, int64_t);
> +	} else {
> +		d = (double)mem->u.u;
> +		res = CMP_OLD_NEW(mem->u.u, d, uint64_t);
> +	}
> +	mem->u.r = d;
> +	mem->type = MEM_TYPE_DOUBLE;
> +	mem->field_type = FIELD_TYPE_DOUBLE;

3. Why do you need not to fail in case of imprecise conversion? It don't
see -1 and 1 values used. Only != 0 in the end where the implicit casts
are applied.

> +	return res;
> +}
> +
> +static inline int
> +forced_int_to_uint(struct Mem *mem)
> +{
> +	assert(mem->type == MEM_TYPE_INT || mem->type == MEM_TYPE_UINT);

4. It is never used with MEM_TYPE_UINT from what I see.

> +	if (mem->type == MEM_TYPE_UINT)
> +		return 0;
> +	mem->u.u = 0;
> +	mem->type = MEM_TYPE_UINT;
> +	mem->field_type = FIELD_TYPE_UNSIGNED;
> +	return -1;
> +}
> @@ -1228,6 +1316,53 @@ 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:
> +			return forced_int_to_uint(mem);
> +		case MEM_TYPE_DOUBLE:
> +			return forced_double_to_uint(mem);
> +		default:
> +			unreachable();
> +		}
> +		break;
> +	case FIELD_TYPE_DOUBLE:
> +		switch (mem->type) {
> +		case MEM_TYPE_INT:
> +		case MEM_TYPE_UINT:
> +			return forced_int_to_double(mem);

5. You already have the switch here and yet you have a branch
by MEM_TYPE_UINT vs MEM_TYPE_INT inside forced_int_to_double.
Maybe split it 2 for INT and UINT separately and use them
right under each 'case'?

> diff --git a/src/box/sql/mem.h b/src/box/sql/mem.h
> index 645d0ee27..9766bb836 100644
> --- a/src/box/sql/mem.h
> +++ b/src/box/sql/mem.h
> @@ -779,6 +779,16 @@ mem_cast_implicit(struct Mem *mem, enum field_type type);
>  int
>  mem_cast_implicit_old(struct Mem *mem, enum field_type type);
>  
> +/**
> + * Convert the given MEM that contains numeric value to given numeric type
> + * according to implicit cast rules. This function cannot fail. Returns:
> + *  -1 if before conversion value was more that after conversion;

6. 'more' -> 'greater', 'that' -> 'than'.

> + *  +1 if before conversion value was more that after conversion;
> + *   0 if conversion is precise.
> + */
> +int
> +mem_cast_implicit_number(struct Mem *mem, enum field_type type);
> diff --git a/src/box/sql/vdbe.c b/src/box/sql/vdbe.c
> index 9e763ed85..d143ce364 100644
> --- a/src/box/sql/vdbe.c
> +++ b/src/box/sql/vdbe.c
> @@ -2157,11 +2157,24 @@ case OP_ApplyType: {
>  	while((type = *(types++)) != field_type_MAX) {
>  		assert(pIn1 <= &p->aMem[(p->nMem+1 - p->nCursor)]);
>  		assert(memIsValid(pIn1));
> -		if (mem_cast_implicit(pIn1, type) != 0) {
> +		if (mem_is_field_compatible(pIn1, type)) {
> +			pIn1++;
> +			continue;
> +		}
> +		if (!mem_is_num(pIn1) || !sql_type_is_numeric(type)) {
>  			diag_set(ClientError, ER_SQL_TYPE_MISMATCH,
>  				 mem_str(pIn1), field_type_strs[type]);
>  			goto abort_due_to_error;
>  		}
> +		struct Mem mem;
> +		mem.type = pIn1->type;
> +		mem.u = pIn1->u;
> +		mem.flags = 0;

7. The mem is only used for the error message. Please, move it
under the 'if' right before diag_set so as not to create it for
success path. And why can't you use pIn1 in mem_str() below?

> +		if (mem_cast_implicit_number(pIn1, type) != 0) {
> +			diag_set(ClientError, ER_SQL_TYPE_MISMATCH,
> +				 mem_str(&mem), field_type_strs[type]);
> +			goto abort_due_to_error;
> +		}
>  		pIn1++;
>  	}
>  	break;
> diff --git a/test/sql-tap/numcast.test.lua b/test/sql-tap/numcast.test.lua
> index 802fe712c..be1366260 100755
> --- a/test/sql-tap/numcast.test.lua
> +++ b/test/sql-tap/numcast.test.lua
> @@ -136,13 +136,13 @@ test:do_catchsql_test(
>          1,"Type mismatch: can not convert double(2.0e+19) to integer"
>      })
>  
> -test:do_execsql_test(
> +test:do_catchsql_test(
>      "cast-2.9",
>      [[
>          INSERT INTO t VALUES(2.1);
>          SELECT * FROM t;

8. The select is not needed now.

>      ]], {
> -        2, 9223372036854775808ULL, 18000000000000000000ULL
> +        1, "Type mismatch: can not convert double(2.1) to integer"
>      })
>  
>  --
> diff --git a/test/sql-tap/uuid.test.lua b/test/sql-tap/uuid.test.lua
> index 70683a4fd..613f4c865 100755
> --- a/test/sql-tap/uuid.test.lua
> +++ b/test/sql-tap/uuid.test.lua
> @@ -844,13 +837,13 @@ test:do_catchsql_test(
>          1, "Type mismatch: can not convert boolean(TRUE) to uuid"
>      })
>  
> -test:do_execsql_test(
> +test:do_catchsql_test(
>      "uuid-8.2.7",
>      [[
>          INSERT INTO tsu SELECT '7_varbinary', x'11111111111111111111111111111111' FROM t2 LIMIT 1;
>          SELECT * FROM tsu ORDER BY s DESC LIMIT 1;

9. The select is unreachable.

>      ]], {
> -        '7_varbinary', uuid1
> +        1, "Type mismatch: can not convert varbinary(x'11111111111111111111111111111111') to uuid"
>      })
> @@ -882,59 +875,48 @@ test:execsql([[

<...>

> -test:do_execsql_test(
> +test:do_catchsql_test(
>      "uuid-10.1.3",
>      [[
>          INSERT INTO t10(i) VALUES (3);
> -        SELECT * FROM t10 WHERE i = 3;
>      ]], {
> -        3, uuid1
> +        1, "Type mismatch: can not convert string('11111111-1111-1111-1111-111111111111') to uuid"

10. I suspect this test was about checking if UUID DEFAULT works.
Maybe better fix its DEFAULT in the table definition so as not to
use a string. Is it possible to use CAST in DEFAULT?

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

* Re: [Tarantool-patches] [PATCH v1 1/7] sql: rework implicit cast fo assignment
  2021-07-30 21:55   ` Vladislav Shpilevoy via Tarantool-patches
@ 2021-08-04 22:21     ` Vladislav Shpilevoy via Tarantool-patches
  2021-08-05 23:27     ` Mergen Imeev via Tarantool-patches
  1 sibling, 0 replies; 25+ messages in thread
From: Vladislav Shpilevoy via Tarantool-patches @ 2021-08-04 22:21 UTC (permalink / raw)
  To: imeevma; +Cc: tarantool-patches

Hi! These comments still need attention.

On 30.07.2021 23:55, Vladislav Shpilevoy via Tarantool-patches wrote:
> Thanks for the patch!
> 
> I will return later to continue the review of the next commits.
> Sending first comments so as I could switch to other patches.
> 
> See 10 comments below.

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

* Re: [Tarantool-patches] [PATCH v1 2/7] sql: remove implicit cast from comparison opcodes
  2021-07-28 20:51 ` [Tarantool-patches] [PATCH v1 2/7] sql: remove implicit cast from comparison opcodes Mergen Imeev via Tarantool-patches
@ 2021-08-04 22:24   ` Vladislav Shpilevoy via Tarantool-patches
  2021-08-05 23:33     ` Mergen Imeev via Tarantool-patches
  0 siblings, 1 reply; 25+ messages in thread
From: Vladislav Shpilevoy via Tarantool-patches @ 2021-08-04 22:24 UTC (permalink / raw)
  To: imeevma; +Cc: tarantool-patches

Thanks for the patch!

See 5 comments below.

> diff --git a/src/box/sql/mem.c b/src/box/sql/mem.c
> index e804dba67..b04303be2 100644
> --- a/src/box/sql/mem.c
> +++ b/src/box/sql/mem.c
> @@ -1976,25 +2000,21 @@ mem_bit_not(const struct Mem *mem, struct Mem *result)
>  	return 0;
>  }
>  
> -int
> -mem_cmp_bool(const struct Mem *a, const struct Mem *b, int *result)
> +static int
> +mem_cmp_bool(const struct Mem *a, const struct Mem *b)
>  {
> -	if ((a->type & b->type & MEM_TYPE_BOOL) == 0)
> -		return -1;
> +	assert((a->type & b->type & MEM_TYPE_BOOL) != 0);
>  	if (a->u.b == b->u.b)
> -		*result = 0;
> -	else if (a->u.b)
> -		*result = 1;
> -	else
> -		*result = -1;
> -	return 0;
> +		return 0;
> +	if (a->u.b)
> +		return 1;
> +	return -1;

1. Could be simpler:

====================
@@ -2004,11 +2004,7 @@ static int
 mem_cmp_bool(const struct Mem *a, const struct Mem *b)
 {
 	assert((a->type & b->type & MEM_TYPE_BOOL) != 0);
-	if (a->u.b == b->u.b)
-		return 0;
-	if (a->u.b)
-		return 1;
-	return -1;
+	return a->u.b - b->u.b;
 }
====================

> @@ -2245,8 +2189,11 @@ mem_cmp_msgpack(const struct Mem *a, const char **b, int *result,
>  		if (type == MP_UUID) {
>  			assert(len == UUID_LEN);
>  			mem.type = MEM_TYPE_UUID;
> -			if (uuid_unpack(b, len, &mem.u.uuid) == NULL)
> +			if (uuid_unpack(b, len, &mem.u.uuid) == NULL) {
> +				diag_set(ClientError, ER_SQL_EXECUTE,
> +					 "cannot parse UUID");

2. Shouldn't this be a separate commit as a bugfix?

>  				return -1;
> +			}
>  			break;
>  		}
> diff --git a/src/box/sql/vdbe.c b/src/box/sql/vdbe.c
> index d143ce364..62f58def9 100644
> --- a/src/box/sql/vdbe.c
> +++ b/src/box/sql/vdbe.c
<...>

> -	switch( pOp->opcode) {
> -	case OP_Eq:    res2 = res==0;     break;
> -	case OP_Ne:    res2 = res;        break;
> -	case OP_Lt:    res2 = res<0;      break;
> -	case OP_Le:    res2 = res<=0;     break;
> -	case OP_Gt:    res2 = res>0;      break;
> -	default:       res2 = res>=0;     break;
> +	bool result;
> +	switch(pOp->opcode) {

3. It does not look good to have a second switch-case here. But I
can't find a better solution right away. We can't wrap this all
into a function, because need to make goto abort_due_to_error and
goto jump_to_p2 in some places. Up to you if you want to find a
way to fix it.

As a side note, the code now is incomparably simpler than it was.
It is getting actually understable, nice.

> +	case OP_Eq:
> +		result = cmp_res == 0;
> +		break;
> +	case OP_Ne:
> +		result = cmp_res != 0;
> +		break;
> +	case OP_Lt:
> +		result = cmp_res < 0;
> +		break;
> +	case OP_Le:
> +		result = cmp_res <= 0;
> +		break;
> +	case OP_Gt:
> +		result = cmp_res > 0;
> +		break;
> +	case OP_Ge:
> +		result = cmp_res >= 0;
> +		break;
> +	default:
> +		unreachable();
>  	}
> diff --git a/test/sql-tap/transitive1.test.lua b/test/sql-tap/transitive1.test.lua
> index dbc2559fa..2d502f5e8 100755
> --- a/test/sql-tap/transitive1.test.lua
> +++ b/test/sql-tap/transitive1.test.lua
> @@ -73,7 +73,7 @@ test:do_execsql_test(
>  test:do_execsql_test(
>      "transitive1-210",
>      [[
> -        SELECT a,b,c FROM t2 WHERE a=b AND c=b AND c>='20' ORDER BY +a;
> +        SELECT a,b,c FROM t2 WHERE a = b AND c >= '20' ORDER BY +a;

4. Might worth adding whitespaces after ',' in the select list.

>      ]], {
>          -- <transitive1-210>
>          3, 3, "3", 20, 20, "20"
> diff --git a/test/sql/boolean.result b/test/sql/boolean.result
> index d54de8fe7..81d79ee78 100644
> --- a/test/sql/boolean.result
> +++ b/test/sql/boolean.result
> @@ -4898,12 +4898,12 @@ SELECT a1, a1 != 2.3 FROM t6
>  SELECT a1, 2.3 == a1 FROM t6
>   | ---
>   | - null
> - | - 'Type mismatch: can not convert double(2.3) to boolean'
> + | - 'Type mismatch: can not convert boolean(FALSE) to number'

5. Is it possible to keep the old error messages? For the sake of
smaller diff.

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

* Re: [Tarantool-patches] [PATCH v1 3/7] sql: rework OP_Seek* opcodes
  2021-07-28 20:51 ` [Tarantool-patches] [PATCH v1 3/7] sql: rework OP_Seek* opcodes Mergen Imeev via Tarantool-patches
@ 2021-08-04 22:25   ` Vladislav Shpilevoy via Tarantool-patches
  2021-08-05 23:40     ` Mergen Imeev via Tarantool-patches
  0 siblings, 1 reply; 25+ messages in thread
From: Vladislav Shpilevoy via Tarantool-patches @ 2021-08-04 22:25 UTC (permalink / raw)
  To: imeevma; +Cc: tarantool-patches

Thanks for the patch!

See 3 comments below.

> diff --git a/src/box/sql/mem.h b/src/box/sql/mem.h
> index 47a940c56..5d1d7592e 100644
> --- a/src/box/sql/mem.h
> +++ b/src/box/sql/mem.h
> @@ -1028,3 +1028,20 @@ mpstream_encode_vdbe_mem(struct mpstream *stream, struct Mem *var);
>  char *
>  sql_vdbe_mem_encode_tuple(struct Mem *fields, uint32_t field_count,
>  			  uint32_t *tuple_size, struct region *region);
> +
> +static inline bool
> +is_mem_num_min(const struct Mem *mem)
> +{
> +	return (mem->field_type == FIELD_TYPE_INTEGER &&
> +		mem->type == MEM_TYPE_INT && mem->u.i == INT64_MIN) ||
> +	       (mem->field_type == FIELD_TYPE_UNSIGNED &&
> +		mem->type == MEM_TYPE_UINT && mem->u.u == 0);
> +}
> +
> +static inline bool
> +is_mem_num_max(const struct Mem *mem)

1. Could you please name them to mem_is_num_min() and mem_is_num_max()?
Also what if they are DOUBLE? Should't these functions be called then
mem_is_int_min() and mem_is_int_max()?

> +{
> +	return (mem->field_type == FIELD_TYPE_INTEGER ||
> +		mem->field_type == FIELD_TYPE_UNSIGNED) &&
> +	       mem->type == MEM_TYPE_UINT && mem->u.u == 0;
> +}
> diff --git a/src/box/sql/vdbe.c b/src/box/sql/vdbe.c
> index 62f58def9..a69402720 100644
> --- a/src/box/sql/vdbe.c
> +++ b/src/box/sql/vdbe.c
> @@ -2577,6 +2526,62 @@ case OP_Close: {
>   *
>   * See also: Found, NotFound, SeekGt, SeekGe, SeekLe
>   */
> +case OP_SeekLT: {       /* jump, in3 */
> +	struct VdbeCursor *cur = p->apCsr[pOp->p1];
> +#ifdef SQL_DEBUG
> +	cur->seekOp = pOp->opcode;
> +#endif
> +	cur->nullRow = 0;
> +	cur->uc.pCursor->iter_type = ITER_LT;
> +
> +	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_le = 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;
> +		}
> +		int cmp = mem_cast_implicit_number(mem, type);
> +		is_le = is_le || cmp > 0;
> +		/*
> +		 * If number before cast were less than min possible for given
> +		 * field type, than there is no point to use iterator since we
> +		 * won't find anything.
> +		 */
> +		is_zero = is_zero || (is_mem_num_min(mem) && cmp < 0);

2. Do you really need this optimization? Seems like a very rare case. The
same in the other places.

Also it looks like a lot of code duplication. But I see why, and again I
don't see a way to reuse all this code easily.

> +	}
> +	if (is_zero) {
> +		assert(pOp->p2 > 0);
> +		VdbeBranchTaken(1, 2);
> +		goto jump_to_p2;
> +	}

<...>

> +case OP_SeekLE: {       /* jump, in3 */

<...>

> +	cur->nullRow = 0;
> +	bool is_eq = (cur->uc.pCursor->hints & OPFLAG_SEEKEQ) != 0;
> +	cur->uc.pCursor->iter_type = is_eq ? ITER_REQ : ITER_LE;
> +	assert(!is_eq || pOp[1].opcode == OP_IdxLT);
> +
> +	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_lt = 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_lt = is_lt || cmp < 0;

3. You needed to find values <= mem. The mem during cast was
turned into mem2 < mem, correct? Then you need to find values
<= mem2. Otherwise you skip mem2 itself. Or am I mistaken
somewhere? Similar questions to all the other usages of
mem_cast_implicit_number for seeking.

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

* Re: [Tarantool-patches] [PATCH v1 4/7] sql: remove unnecessary calls of OP_ApplyType
  2021-07-28 20:51 ` [Tarantool-patches] [PATCH v1 4/7] sql: remove unnecessary calls of OP_ApplyType Mergen Imeev via Tarantool-patches
@ 2021-08-04 22:26   ` Vladislav Shpilevoy via Tarantool-patches
  2021-08-05 23:41     ` Mergen Imeev via Tarantool-patches
  0 siblings, 1 reply; 25+ messages in thread
From: Vladislav Shpilevoy via Tarantool-patches @ 2021-08-04 22:26 UTC (permalink / raw)
  To: imeevma; +Cc: tarantool-patches

Thanks for the patch!

> diff --git a/src/box/sql/wherecode.c b/src/box/sql/wherecode.c
> index 96bcab110..92d374200 100644
> --- a/src/box/sql/wherecode.c
> +++ b/src/box/sql/wherecode.c

<...>

> -/**
> - * Expression @rhs, which is the RHS of a comparison operation, is
> - * either a vector of n elements or, if n==1, a scalar expression.
> - * Before the comparison operation, types @types are to be applied
> - * to the @rhs values. This function modifies entries within the
> - * field sequence to SCALAR if either:
> - *
> - *   * the comparison will be performed with no type, or
> - *   * the type change in @types is guaranteed not to change the value.
> - */
> -static void
> -expr_cmp_update_rhs_type(struct Expr *rhs, int n, enum field_type *types)
> -{
> -	for (int i = 0; i < n; i++) {
> -		Expr *p = sqlVectorFieldSubexpr(rhs, i);
> -		enum field_type expr_type = sql_expr_type(p);
> -		if (sql_type_result(expr_type, types[i]) == FIELD_TYPE_SCALAR ||
> -		    sql_expr_needs_no_type_change(p, types[i])) {

sql_expr_needs_no_type_change is now unused.

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

* Re: [Tarantool-patches] [PATCH v1 5/7] sql: remove implicit cast from OP_MakeRecord
  2021-07-28 20:51 ` [Tarantool-patches] [PATCH v1 5/7] sql: remove implicit cast from OP_MakeRecord Mergen Imeev via Tarantool-patches
@ 2021-08-04 22:27   ` Vladislav Shpilevoy via Tarantool-patches
  2021-08-05 23:43     ` Mergen Imeev via Tarantool-patches
  0 siblings, 1 reply; 25+ messages in thread
From: Vladislav Shpilevoy via Tarantool-patches @ 2021-08-04 22:27 UTC (permalink / raw)
  To: imeevma; +Cc: tarantool-patches

Thanks for the patch!

On 28.07.2021 22:51, imeevma@tarantool.org wrote:
> This patch removes deprecated implicit cast from OP_MakeRecord opcode.
> Not there will be no implicit cast in OP_MakeRecord opcode.

1. Not -> Note? The commit seems strange. You basically said the same
thing 3 times: 1 in th title and 2 in the message.

> diff --git a/changelogs/unreleased/gh-4230-implicit-cast-for-comparison.md b/changelogs/unreleased/gh-4230-implicit-cast-for-comparison.md
> new file mode 100644
> index 000000000..9f523d3ed
> --- /dev/null
> +++ b/changelogs/unreleased/gh-4230-implicit-cast-for-comparison.md
> @@ -0,0 +1,3 @@
> +## feature/sql
> +
> +* Implicit cast for comparison now works according to defined rules (gh-4230).

2. Please, try to be more specific here. Users won't understand anything from
this sentence.

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

* Re: [Tarantool-patches] [PATCH v1 7/7] sql: remove unused MEM cast functions
  2021-07-28 20:51 ` [Tarantool-patches] [PATCH v1 7/7] sql: remove unused MEM cast functions Mergen Imeev via Tarantool-patches
@ 2021-08-04 22:27   ` Vladislav Shpilevoy via Tarantool-patches
  2021-08-05 23:45     ` Mergen Imeev via Tarantool-patches
  0 siblings, 1 reply; 25+ messages in thread
From: Vladislav Shpilevoy via Tarantool-patches @ 2021-08-04 22:27 UTC (permalink / raw)
  To: imeevma; +Cc: tarantool-patches

Thanks for the patch!

> diff --git a/src/box/sql/vdbeaux.c b/src/box/sql/vdbeaux.c
> index 4a1fdb637..fc06e502e 100644
> --- a/src/box/sql/vdbeaux.c
> +++ b/src/box/sql/vdbeaux.c
> @@ -2323,17 +2323,15 @@ sqlVdbeDb(Vdbe * v)
>   * The returned value must be freed by the caller using sqlValueFree().
>   */
>  sql_value *
> -sqlVdbeGetBoundValue(Vdbe * v, int iVar, u8 aff)
> +sqlVdbeGetBoundValue(Vdbe * v, int iVar)

1. No need for a whitespace after *.

>  {
>  	assert(iVar > 0);
>  	if (v) {
>  		Mem *pMem = &v->aVar[iVar - 1];
>  		if (!mem_is_null(pMem)) {
>  			sql_value *pRet = sqlValueNew(v->db);
> -			if (pRet) {
> +			if (pRet)

2. != NULL.

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

* Re: [Tarantool-patches] [PATCH v1 1/7] sql: rework implicit cast fo assignment
  2021-07-30 21:55   ` Vladislav Shpilevoy via Tarantool-patches
  2021-08-04 22:21     ` Vladislav Shpilevoy via Tarantool-patches
@ 2021-08-05 23:27     ` Mergen Imeev via Tarantool-patches
  2021-08-06  0:13       ` Vladislav Shpilevoy via Tarantool-patches
  1 sibling, 1 reply; 25+ messages in thread
From: Mergen Imeev via Tarantool-patches @ 2021-08-05 23:27 UTC (permalink / raw)
  To: Vladislav Shpilevoy; +Cc: tarantool-patches

Hi! Thank you for the review! My answer and new patch below. I didn't include
diffs in all my answers in this patch-set since these were some merge-conflicts
due to rebase on current master.

On Fri, Jul 30, 2021 at 11:55:05PM +0200, Vladislav Shpilevoy wrote:
> Thanks for the patch!
> 
> I will return later to continue the review of the next commits.
> Sending first comments so as I could switch to other patches.
> 
> See 10 comments below.
> 
> > diff --git a/changelogs/unreleased/gh-4470-implicit-cast-for-assignment.md b/changelogs/unreleased/gh-4470-implicit-cast-for-assignment.md
> > new file mode 100644
> > index 000000000..c758494eb
> > --- /dev/null
> > +++ b/changelogs/unreleased/gh-4470-implicit-cast-for-assignment.md
> > @@ -0,0 +1,3 @@
> > +## feature/sql
> > +
> > +* Implicit cast for assignment now works according to defined rules (gh-4470).
> 
> 1. Please, be more specific about what exactly has changed.
> 
Fixed.

> > diff --git a/src/box/sql/mem.c b/src/box/sql/mem.c
> > index 351d80b76..e804dba67 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) ((int)(a > (type)b) - (int)(a < (type)b))
> 
> 2. Please, wrap 'a' and 'b' into parentheses. Otherwise the macro might
> work in unexpected ways if these are expressions.
> 
Fixed. However, I moved this code to another patch.

> > @@ -900,6 +902,92 @@ uuid_to_bin(struct Mem *mem)
> >  	return mem_copy_bin(mem, (char *)&mem->u.uuid, UUID_LEN);
> >  }
> >  
> > +static inline int
> > +forced_int_to_double(struct Mem *mem)
> > +{
> > +	assert(mem->type == MEM_TYPE_INT || mem->type == MEM_TYPE_UINT);
> > +	double d;
> > +	int res;
> > +	if (mem->type == MEM_TYPE_INT) {
> > +		d = (double)mem->u.i;
> > +		res = CMP_OLD_NEW(mem->u.i, d, int64_t);
> > +	} else {
> > +		d = (double)mem->u.u;
> > +		res = CMP_OLD_NEW(mem->u.u, d, uint64_t);
> > +	}
> > +	mem->u.r = d;
> > +	mem->type = MEM_TYPE_DOUBLE;
> > +	mem->field_type = FIELD_TYPE_DOUBLE;
> 
> 3. Why do you need not to fail in case of imprecise conversion? It don't
> see -1 and 1 values used. Only != 0 in the end where the implicit casts
> are applied.
> 
True, however I need this functions to work with indexes. I moved this function
to patch that fixes work with indexes.

> > +	return res;
> > +}
> > +
> > +static inline int
> > +forced_int_to_uint(struct Mem *mem)
> > +{
> > +	assert(mem->type == MEM_TYPE_INT || mem->type == MEM_TYPE_UINT);
> 
> 4. It is never used with MEM_TYPE_UINT from what I see.
> 
True, in another patch already fixed function.

> > +	if (mem->type == MEM_TYPE_UINT)
> > +		return 0;
> > +	mem->u.u = 0;
> > +	mem->type = MEM_TYPE_UINT;
> > +	mem->field_type = FIELD_TYPE_UNSIGNED;
> > +	return -1;
> > +}
> > @@ -1228,6 +1316,53 @@ 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:
> > +			return forced_int_to_uint(mem);
> > +		case MEM_TYPE_DOUBLE:
> > +			return forced_double_to_uint(mem);
> > +		default:
> > +			unreachable();
> > +		}
> > +		break;
> > +	case FIELD_TYPE_DOUBLE:
> > +		switch (mem->type) {
> > +		case MEM_TYPE_INT:
> > +		case MEM_TYPE_UINT:
> > +			return forced_int_to_double(mem);
> 
> 5. You already have the switch here and yet you have a branch
> by MEM_TYPE_UINT vs MEM_TYPE_INT inside forced_int_to_double.
> Maybe split it 2 for INT and UINT separately and use them
> right under each 'case'?
> 
Done, in another patch.

> > diff --git a/src/box/sql/mem.h b/src/box/sql/mem.h
> > index 645d0ee27..9766bb836 100644
> > --- a/src/box/sql/mem.h
> > +++ b/src/box/sql/mem.h
> > @@ -779,6 +779,16 @@ mem_cast_implicit(struct Mem *mem, enum field_type type);
> >  int
> >  mem_cast_implicit_old(struct Mem *mem, enum field_type type);
> >  
> > +/**
> > + * Convert the given MEM that contains numeric value to given numeric type
> > + * according to implicit cast rules. This function cannot fail. Returns:
> > + *  -1 if before conversion value was more that after conversion;
> 
> 6. 'more' -> 'greater', 'that' -> 'than'.
> 
Fixed. Also added comments to all "forced" functions.

> > + *  +1 if before conversion value was more that after conversion;
> > + *   0 if conversion is precise.
> > + */
> > +int
> > +mem_cast_implicit_number(struct Mem *mem, enum field_type type);
> > diff --git a/src/box/sql/vdbe.c b/src/box/sql/vdbe.c
> > index 9e763ed85..d143ce364 100644
> > --- a/src/box/sql/vdbe.c
> > +++ b/src/box/sql/vdbe.c
> > @@ -2157,11 +2157,24 @@ case OP_ApplyType: {
> >  	while((type = *(types++)) != field_type_MAX) {
> >  		assert(pIn1 <= &p->aMem[(p->nMem+1 - p->nCursor)]);
> >  		assert(memIsValid(pIn1));
> > -		if (mem_cast_implicit(pIn1, type) != 0) {
> > +		if (mem_is_field_compatible(pIn1, type)) {
> > +			pIn1++;
> > +			continue;
> > +		}
> > +		if (!mem_is_num(pIn1) || !sql_type_is_numeric(type)) {
> >  			diag_set(ClientError, ER_SQL_TYPE_MISMATCH,
> >  				 mem_str(pIn1), field_type_strs[type]);
> >  			goto abort_due_to_error;
> >  		}
> > +		struct Mem mem;
> > +		mem.type = pIn1->type;
> > +		mem.u = pIn1->u;
> > +		mem.flags = 0;
> 
> 7. The mem is only used for the error message. Please, move it
> under the 'if' right before diag_set so as not to create it for
> success path. And why can't you use pIn1 in mem_str() below?
> 
I did this because after sql_type_is_numeric() MEM would change. However,
I dropped this diff since I decided that it is quite inconvenient not eaily
understandable change.

> > +		if (mem_cast_implicit_number(pIn1, type) != 0) {
> > +			diag_set(ClientError, ER_SQL_TYPE_MISMATCH,
> > +				 mem_str(&mem), field_type_strs[type]);
> > +			goto abort_due_to_error;
> > +		}
> >  		pIn1++;
> >  	}
> >  	break;
> > diff --git a/test/sql-tap/numcast.test.lua b/test/sql-tap/numcast.test.lua
> > index 802fe712c..be1366260 100755
> > --- a/test/sql-tap/numcast.test.lua
> > +++ b/test/sql-tap/numcast.test.lua
> > @@ -136,13 +136,13 @@ test:do_catchsql_test(
> >          1,"Type mismatch: can not convert double(2.0e+19) to integer"
> >      })
> >  
> > -test:do_execsql_test(
> > +test:do_catchsql_test(
> >      "cast-2.9",
> >      [[
> >          INSERT INTO t VALUES(2.1);
> >          SELECT * FROM t;
> 
> 8. The select is not needed now.
> 
Fixed.

> >      ]], {
> > -        2, 9223372036854775808ULL, 18000000000000000000ULL
> > +        1, "Type mismatch: can not convert double(2.1) to integer"
> >      })
> >  
> >  --
> > diff --git a/test/sql-tap/uuid.test.lua b/test/sql-tap/uuid.test.lua
> > index 70683a4fd..613f4c865 100755
> > --- a/test/sql-tap/uuid.test.lua
> > +++ b/test/sql-tap/uuid.test.lua
> > @@ -844,13 +837,13 @@ test:do_catchsql_test(
> >          1, "Type mismatch: can not convert boolean(TRUE) to uuid"
> >      })
> >  
> > -test:do_execsql_test(
> > +test:do_catchsql_test(
> >      "uuid-8.2.7",
> >      [[
> >          INSERT INTO tsu SELECT '7_varbinary', x'11111111111111111111111111111111' FROM t2 LIMIT 1;
> >          SELECT * FROM tsu ORDER BY s DESC LIMIT 1;
> 
> 9. The select is unreachable.
> 
Fixed.

> >      ]], {
> > -        '7_varbinary', uuid1
> > +        1, "Type mismatch: can not convert varbinary(x'11111111111111111111111111111111') to uuid"
> >      })
> > @@ -882,59 +875,48 @@ test:execsql([[
> 
> <...>
> 
> > -test:do_execsql_test(
> > +test:do_catchsql_test(
> >      "uuid-10.1.3",
> >      [[
> >          INSERT INTO t10(i) VALUES (3);
> > -        SELECT * FROM t10 WHERE i = 3;
> >      ]], {
> > -        3, uuid1
> > +        1, "Type mismatch: can not convert string('11111111-1111-1111-1111-111111111111') to uuid"
> 
> 10. I suspect this test was about checking if UUID DEFAULT works.
> Maybe better fix its DEFAULT in the table definition so as not to
> use a string. Is it possible to use CAST in DEFAULT?
Fixed. It is possible to work with functions using "()" for DEFAULT.


New patch:


commit 99407b480ec338bfb629e7ddd9ae168f76674c8d
Author: Mergen Imeev <imeevma@gmail.com>
Date:   Tue Jul 27 23:54:20 2021 +0300

    sql: rework implicit cast fo assignment
    
    After this patch, the new rules will be applied to implicit cast during
    assignment. According to these rules, all scalar values can be cast to
    SCALAR, all numeric values can be cast to NUMBER, and any numeric value
    can be cast to another numeric type only if the conversion is exact.
    No other implicit cast is allowed.
    
    Part of #4470

diff --git a/changelogs/unreleased/gh-4470-implicit-cast-for-assignment.md b/changelogs/unreleased/gh-4470-implicit-cast-for-assignment.md
new file mode 100644
index 000000000..6e3e78cf7
--- /dev/null
+++ b/changelogs/unreleased/gh-4470-implicit-cast-for-assignment.md
@@ -0,0 +1,6 @@
+## feature/sql
+
+* Now a numeric value can be cast to another numeric type only if the cast is
+  precise. In addition, a UUID value cannot be implicitly cast to
+  STRING/VARBINARY, and a STRING/VARBINARY value cannot be implicitly cast to
+  a UUID (gh-4470).
\ No newline at end of file
diff --git a/src/box/sql/func.c b/src/box/sql/func.c
index 6ca852dec..e153db24b 100644
--- a/src/box/sql/func.c
+++ b/src/box/sql/func.c
@@ -58,7 +58,7 @@ static const void *
 mem_as_bin(struct Mem *mem)
 {
 	const char *s;
-	if (mem_cast_implicit(mem, FIELD_TYPE_VARBINARY) != 0 &&
+	if (mem_cast_explicit(mem, FIELD_TYPE_VARBINARY) != 0 &&
 	    mem_to_str(mem) != 0)
 		return NULL;
 	if (mem_get_bin(mem, &s) != 0)
diff --git a/src/box/sql/mem.c b/src/box/sql/mem.c
index eebc47f4e..c0ceb98e9 100644
--- a/src/box/sql/mem.c
+++ b/src/box/sql/mem.c
@@ -624,6 +624,34 @@ int_to_double(struct Mem *mem)
 	return 0;
 }
 
+static inline int
+int_to_double_precise(struct Mem *mem)
+{
+	assert(mem->type == MEM_TYPE_INT);
+	double d;
+	d = (double)mem->u.i;
+	if (mem->u.i != (int64_t)d)
+		return -1;
+	mem->u.r = d;
+	mem->type = MEM_TYPE_DOUBLE;
+	mem->field_type = FIELD_TYPE_DOUBLE;
+	return 0;
+}
+
+static inline int
+uint_to_double_precise(struct Mem *mem)
+{
+	assert(mem->type == MEM_TYPE_UINT);
+	double d;
+	d = (double)mem->u.u;
+	if (mem->u.u != (uint64_t)d)
+		return -1;
+	mem->u.r = d;
+	mem->type = MEM_TYPE_DOUBLE;
+	mem->field_type = FIELD_TYPE_DOUBLE;
+	return 0;
+}
+
 static inline int
 int_to_str0(struct Mem *mem)
 {
@@ -1083,25 +1111,25 @@ mem_cast_implicit(struct Mem *mem, enum field_type type)
 		if (mem->type == MEM_TYPE_UINT)
 			return 0;
 		if (mem->type == MEM_TYPE_DOUBLE)
-			return double_to_uint(mem);
+			return double_to_uint_precise(mem);
 		return -1;
 	case FIELD_TYPE_STRING:
 		if (mem->type == MEM_TYPE_STR)
 			return 0;
-		if (mem->type == MEM_TYPE_UUID)
-			return uuid_to_str0(mem);
 		return -1;
 	case FIELD_TYPE_DOUBLE:
 		if (mem->type == MEM_TYPE_DOUBLE)
 			return 0;
-		if ((mem->type & (MEM_TYPE_INT | MEM_TYPE_UINT)) != 0)
-			return int_to_double(mem);
+		if (mem->type == MEM_TYPE_INT)
+			return int_to_double_precise(mem);
+		if (mem->type == MEM_TYPE_UINT)
+			return uint_to_double_precise(mem);
 		return -1;
 	case FIELD_TYPE_INTEGER:
 		if ((mem->type & (MEM_TYPE_INT | MEM_TYPE_UINT)) != 0)
 			return 0;
 		if (mem->type == MEM_TYPE_DOUBLE)
-			return double_to_int(mem);
+			return double_to_int_precise(mem);
 		return -1;
 	case FIELD_TYPE_BOOLEAN:
 		if (mem->type == MEM_TYPE_BOOL)
@@ -1111,8 +1139,6 @@ mem_cast_implicit(struct Mem *mem, enum field_type type)
 		if ((mem->type & (MEM_TYPE_BIN | MEM_TYPE_MAP |
 				  MEM_TYPE_ARRAY)) != 0)
 			return 0;
-		if (mem->type == MEM_TYPE_UUID)
-			return uuid_to_bin(mem);
 		return -1;
 	case FIELD_TYPE_NUMBER:
 		if (mem_is_num(mem))
@@ -1133,10 +1159,6 @@ mem_cast_implicit(struct Mem *mem, enum field_type type)
 	case FIELD_TYPE_UUID:
 		if (mem->type == MEM_TYPE_UUID)
 			return 0;
-		if (mem->type == MEM_TYPE_STR)
-			return str_to_uuid(mem);
-		if (mem->type == MEM_TYPE_BIN)
-			return bin_to_uuid(mem);
 		return -1;
 	case FIELD_TYPE_ANY:
 		return 0;
diff --git a/test/sql-tap/cast.test.lua b/test/sql-tap/cast.test.lua
index 799bcc1a8..8af99dbde 100755
--- a/test/sql-tap/cast.test.lua
+++ b/test/sql-tap/cast.test.lua
@@ -1,6 +1,6 @@
 #!/usr/bin/env tarantool
 local test = require("sqltester")
-test:plan(95)
+test:plan(103)
 
 --!./tcltestrunner.lua
 -- 2005 June 25
@@ -875,7 +875,7 @@ test:do_test(
         -- </cast-4.4>
     })
 
--- gh-4470: Make explicit casts work according to our rules.
+-- gh-4470: Make explicit and implicit casts work according to our rules.
 
 -- Make sure that explicit cast from BOOLEAN to numeric types throws an error.
 test:do_catchsql_test(
@@ -1017,4 +1017,94 @@ test:do_execsql_test(
         true
     })
 
+-- Make sure that implicit conversion of numeric values is precise.
+test:execsql([[
+    CREATE TABLE t2 (i INTEGER PRIMARY KEY AUTOINCREMENT, a INTEGER, b DOUBLE);
+    CREATE TABLE t3 (i INTEGER PRIMARY KEY AUTOINCREMENT, s STRING);
+    CREATE TABLE t4 (i INTEGER PRIMARY KEY AUTOINCREMENT, v VARBINARY);
+    CREATE TABLE t5 (i INTEGER PRIMARY KEY AUTOINCREMENT, u UUID);
+]])
+
+test:do_execsql_test(
+    "cast-9.1.1",
+    [[
+        INSERT INTO t2(a) VALUES(1.0e0);
+        SELECT a FROM t2 WHERE i = 1;
+    ]], {
+        1
+    })
+
+test:do_catchsql_test(
+    "cast-9.1.2",
+    [[
+        INSERT INTO t2(a) VALUES(1.5e0);
+    ]], {
+        1, "Type mismatch: can not convert double(1.5) to integer"
+    })
+
+test:do_execsql_test(
+    "cast-9.1.3",
+    [[
+        INSERT INTO t2(b) VALUES(10000000000000000);
+        SELECT b FROM t2 WHERE i = 2;
+    ]], {
+        10000000000000000
+    })
+
+test:do_catchsql_test(
+    "cast-9.1.4",
+    [[
+        INSERT INTO t2(b) VALUES(10000000000000001);
+    ]], {
+        1, "Type mismatch: can not convert integer(10000000000000001) to double"
+    })
+
+-- Make sure that UUID cannot be implicitly cast to STRING.
+local uuid = "CAST('11111111-1111-1111-1111-111111111111' AS UUID)";
+test:do_catchsql_test(
+    "cast-9.2",
+    [[
+        INSERT INTO t3(s) VALUES(]]..uuid..[[);
+    ]], {
+        1, "Type mismatch: can not convert "..
+           "uuid('11111111-1111-1111-1111-111111111111') to string"
+    })
+
+-- Make sure that UUID cannot be implicitly cast to VARBINARY.
+test:do_catchsql_test(
+    "cast-9.3",
+    [[
+        INSERT INTO t4(v) VALUES(]]..uuid..[[);
+    ]], {
+        1, "Type mismatch: can not convert "..
+           "uuid('11111111-1111-1111-1111-111111111111') to varbinary"
+    })
+
+-- Make sure that STRING and VARBINARY cannot be implicitly cast to UUID.
+test:do_catchsql_test(
+    "cast-9.4.1",
+    [[
+        INSERT INTO t5(u) VALUES('11111111-1111-1111-1111-111111111111');
+    ]], {
+        1, "Type mismatch: can not convert "..
+           "string('11111111-1111-1111-1111-111111111111') to uuid"
+    })
+
+test:do_catchsql_test(
+    "cast-9.4.2",
+    [[
+        INSERT INTO t5(u) VALUES(x'11111111111111111111111111111111');
+    ]], {
+        1, "Type mismatch: can not convert "..
+           "varbinary(x'11111111111111111111111111111111') to uuid"
+    })
+
+test:execsql([[
+    DROP TABLE t1;
+    DROP TABLE t2;
+    DROP TABLE t3;
+    DROP TABLE t4;
+    DROP TABLE t5;
+]])
+
 test:finish_test()
diff --git a/test/sql-tap/numcast.test.lua b/test/sql-tap/numcast.test.lua
index 9ecda6833..b172ca625 100755
--- a/test/sql-tap/numcast.test.lua
+++ b/test/sql-tap/numcast.test.lua
@@ -136,13 +136,12 @@ test:do_catchsql_test(
         1,"Type mismatch: can not convert double(2.0e+19) to integer"
     })
 
-test:do_execsql_test(
+test:do_catchsql_test(
     "cast-2.9",
     [[
         INSERT INTO t VALUES(2.1);
-        SELECT * FROM t;
     ]], {
-        2, 9223372036854775808ULL, 18000000000000000000ULL
+        1, "Type mismatch: can not convert double(2.1) to integer"
     })
 
 --
diff --git a/test/sql-tap/uuid.test.lua b/test/sql-tap/uuid.test.lua
index ad91c3e1c..abb5960a7 100755
--- a/test/sql-tap/uuid.test.lua
+++ b/test/sql-tap/uuid.test.lua
@@ -3,7 +3,7 @@ local build_path = os.getenv("BUILDDIR")
 package.cpath = build_path..'/test/sql-tap/?.so;'..build_path..'/test/sql-tap/?.dylib;'..package.cpath
 
 local test = require("sqltester")
-test:plan(147)
+test:plan(146)
 
 local uuid = require("uuid")
 local uuid1 = uuid.fromstr("11111111-1111-1111-1111-111111111111")
@@ -722,15 +722,12 @@ test:do_catchsql_test(
         1, "Type mismatch: can not convert uuid('11111111-1111-1111-1111-111111111111') to unsigned"
     })
 
-test:do_execsql_test(
+test:do_catchsql_test(
     "uuid-8.1.2",
     [[
         INSERT INTO ts(s) SELECT u FROM t2;
-        SELECT * FROM ts;
     ]], {
-        1, "11111111-1111-1111-1111-111111111111",
-        2, "11111111-3333-1111-1111-111111111111",
-        3, "22222222-1111-1111-1111-111111111111"
+        1, "Type mismatch: can not convert uuid('11111111-1111-1111-1111-111111111111') to string"
     })
 
 test:do_catchsql_test(
@@ -765,15 +762,12 @@ test:do_catchsql_test(
         1, "Type mismatch: can not convert uuid('11111111-1111-1111-1111-111111111111') to boolean"
     })
 
-test:do_execsql_test(
+test:do_catchsql_test(
     "uuid-8.1.7",
     [[
         INSERT INTO tv(v) SELECT u FROM t2;
-        SELECT id, hex(v) FROM tv;
     ]], {
-        1, "11111111111111111111111111111111",
-        2, "11111111333311111111111111111111",
-        3, "22222222111111111111111111111111"
+        1, "Type mismatch: can not convert uuid('11111111-1111-1111-1111-111111111111') to varbinary"
     })
 
 test:do_execsql_test(
@@ -803,13 +797,12 @@ test:do_catchsql_test(
         1, "Type mismatch: can not convert integer(1) to uuid"
     })
 
-test:do_execsql_test(
+test:do_catchsql_test(
     "uuid-8.2.2",
     [[
         INSERT INTO tsu VALUES ('2_string_right', '11111111-1111-1111-1111-111111111111');
-        SELECT * FROM tsu ORDER BY s DESC LIMIT 1;
     ]], {
-        '2_string_right', uuid1
+        1, "Type mismatch: can not convert string('11111111-1111-1111-1111-111111111111') to uuid"
     })
 
 test:do_catchsql_test(
@@ -844,13 +837,12 @@ test:do_catchsql_test(
         1, "Type mismatch: can not convert boolean(TRUE) to uuid"
     })
 
-test:do_execsql_test(
+test:do_catchsql_test(
     "uuid-8.2.7",
     [[
         INSERT INTO tsu SELECT '7_varbinary', x'11111111111111111111111111111111' FROM t2 LIMIT 1;
-        SELECT * FROM tsu ORDER BY s DESC LIMIT 1;
     ]], {
-        '7_varbinary', uuid1
+        1, "Type mismatch: can not convert varbinary(x'11111111111111111111111111111111') to uuid"
     })
 
 test:do_catchsql_test(
@@ -877,24 +869,27 @@ test:do_execsql_test(
         uuid1, uuid3, uuid2
     })
 
-test:execsql([[
-    CREATE TABLE t10 (i INT PRIMARY KEY AUTOINCREMENT, u UUID DEFAULT '11111111-1111-1111-1111-111111111111');
-]])
+local default = "DEFAULT(CAST('11111111-1111-1111-1111-111111111111' AS UUID))"
+test:execsql(
+    "CREATE TABLE t10 (i INT PRIMARY KEY AUTOINCREMENT, u UUID "..default..");"
+)
 
 -- Check that INSERT into UUID field works.
+local uuid2_str = "'22222222-1111-1111-1111-111111111111'";
 test:do_execsql_test(
     "uuid-10.1.1",
     [[
-        INSERT INTO t10 VALUES (1, '22222222-1111-1111-1111-111111111111');
+        INSERT INTO t10 VALUES (1, CAST(]]..uuid2_str..[[ AS UUID));
         SELECT * FROM t10 WHERE i = 1;
     ]], {
         1, uuid2
     })
 
+local uuid2_bin = "x'22222222111111111111111111111111'";
 test:do_execsql_test(
     "uuid-10.1.2",
     [[
-        INSERT INTO t10 VALUES (2, x'22222222111111111111111111111111');
+        INSERT INTO t10 VALUES (2, CAST(]]..uuid2_bin..[[ AS UUID));
         SELECT * FROM t10 WHERE i = 2;
     ]], {
         2, uuid2
@@ -920,21 +915,12 @@ test:do_execsql_test(
 
 -- Check that UPDATE of UUID field works.
 test:do_execsql_test(
-    "uuid-10.2.1",
+    "uuid-10",
     [[
-        UPDATE t10 SET u = '11111111-3333-1111-1111-111111111111' WHERE i = 1;
-        SELECT * FROM t10 WHERE i = 1;
-    ]], {
-        1, uuid3
-    })
-
-test:do_execsql_test(
-    "uuid-10.2.2",
-    [[
-        UPDATE t10 SET u = x'11111111333311111111111111111111' WHERE i = 2;
-        SELECT * FROM t10 WHERE i = 2;
+        UPDATE t10 SET u = CAST(]]..uuid2_str..[[ AS UUID) WHERE i = 3;
+        SELECT * FROM t10 WHERE i = 3;
     ]], {
-        2, uuid3
+        3, uuid2
     })
 
 -- Check that JOIN by UUID field works.
diff --git a/test/sql/types.result b/test/sql/types.result
index bb0109d6d..00c60cca9 100644
--- a/test/sql/types.result
+++ b/test/sql/types.result
@@ -2577,7 +2577,8 @@ box.execute([[UPDATE td SET d = 11 WHERE a = 1;]])
 ...
 box.execute([[UPDATE td SET d = 100000000000000001 WHERE a = 1;]])
 ---
-- row_count: 1
+- null
+- 'Type mismatch: can not convert integer(100000000000000001) to double'
 ...
 box.execute([[UPDATE td SET d = 22.2 WHERE a = 1;]])
 ---

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

* Re: [Tarantool-patches] [PATCH v1 2/7] sql: remove implicit cast from comparison opcodes
  2021-08-04 22:24   ` Vladislav Shpilevoy via Tarantool-patches
@ 2021-08-05 23:33     ` Mergen Imeev via Tarantool-patches
  2021-08-06  0:13       ` Vladislav Shpilevoy via Tarantool-patches
  0 siblings, 1 reply; 25+ messages in thread
From: Mergen Imeev via Tarantool-patches @ 2021-08-05 23:33 UTC (permalink / raw)
  To: Vladislav Shpilevoy; +Cc: tarantool-patches

Thank you for the review! My answers and new patch below.

On Thu, Aug 05, 2021 at 12:24:05AM +0200, Vladislav Shpilevoy wrote:
> Thanks for the patch!
> 
> See 5 comments below.
> 
> > diff --git a/src/box/sql/mem.c b/src/box/sql/mem.c
> > index e804dba67..b04303be2 100644
> > --- a/src/box/sql/mem.c
> > +++ b/src/box/sql/mem.c
> > @@ -1976,25 +2000,21 @@ mem_bit_not(const struct Mem *mem, struct Mem *result)
> >  	return 0;
> >  }
> >  
> > -int
> > -mem_cmp_bool(const struct Mem *a, const struct Mem *b, int *result)
> > +static int
> > +mem_cmp_bool(const struct Mem *a, const struct Mem *b)
> >  {
> > -	if ((a->type & b->type & MEM_TYPE_BOOL) == 0)
> > -		return -1;
> > +	assert((a->type & b->type & MEM_TYPE_BOOL) != 0);
> >  	if (a->u.b == b->u.b)
> > -		*result = 0;
> > -	else if (a->u.b)
> > -		*result = 1;
> > -	else
> > -		*result = -1;
> > -	return 0;
> > +		return 0;
> > +	if (a->u.b)
> > +		return 1;
> > +	return -1;
> 
> 1. Could be simpler:
> 
> ====================
> @@ -2004,11 +2004,7 @@ static int
>  mem_cmp_bool(const struct Mem *a, const struct Mem *b)
>  {
>  	assert((a->type & b->type & MEM_TYPE_BOOL) != 0);
> -	if (a->u.b == b->u.b)
> -		return 0;
> -	if (a->u.b)
> -		return 1;
> -	return -1;
> +	return a->u.b - b->u.b;
>  }
> ====================
> 
Thanks! Fixed.

> > @@ -2245,8 +2189,11 @@ mem_cmp_msgpack(const struct Mem *a, const char **b, int *result,
> >  		if (type == MP_UUID) {
> >  			assert(len == UUID_LEN);
> >  			mem.type = MEM_TYPE_UUID;
> > -			if (uuid_unpack(b, len, &mem.u.uuid) == NULL)
> > +			if (uuid_unpack(b, len, &mem.u.uuid) == NULL) {
> > +				diag_set(ClientError, ER_SQL_EXECUTE,
> > +					 "cannot parse UUID");
> 
> 2. Shouldn't this be a separate commit as a bugfix?
> 
True. I removed this diff. I will create an issue a bit later, when I think up
of some reproducer.

> >  				return -1;
> > +			}
> >  			break;
> >  		}
> > diff --git a/src/box/sql/vdbe.c b/src/box/sql/vdbe.c
> > index d143ce364..62f58def9 100644
> > --- a/src/box/sql/vdbe.c
> > +++ b/src/box/sql/vdbe.c
> <...>
> 
> > -	switch( pOp->opcode) {
> > -	case OP_Eq:    res2 = res==0;     break;
> > -	case OP_Ne:    res2 = res;        break;
> > -	case OP_Lt:    res2 = res<0;      break;
> > -	case OP_Le:    res2 = res<=0;     break;
> > -	case OP_Gt:    res2 = res>0;      break;
> > -	default:       res2 = res>=0;     break;
> > +	bool result;
> > +	switch(pOp->opcode) {
> 
> 3. It does not look good to have a second switch-case here. But I
> can't find a better solution right away. We can't wrap this all
> into a function, because need to make goto abort_due_to_error and
> goto jump_to_p2 in some places. Up to you if you want to find a
> way to fix it.
> 
> As a side note, the code now is incomparably simpler than it was.
> It is getting actually understable, nice.
> 
I divided these 6 opcode to two grops: EQ & NE and all others. ALso, I actually
can avoid branching in any groups using somethins like:

int op = pOp->opcode;
bool result = (op == OP_Lt && cmp_res < 0) || (op == OP_Gt && cmp_res > 0) || ...

But not sure that it will be easier to read. What do you think?

> > +	case OP_Eq:
> > +		result = cmp_res == 0;
> > +		break;
> > +	case OP_Ne:
> > +		result = cmp_res != 0;
> > +		break;
> > +	case OP_Lt:
> > +		result = cmp_res < 0;
> > +		break;
> > +	case OP_Le:
> > +		result = cmp_res <= 0;
> > +		break;
> > +	case OP_Gt:
> > +		result = cmp_res > 0;
> > +		break;
> > +	case OP_Ge:
> > +		result = cmp_res >= 0;
> > +		break;
> > +	default:
> > +		unreachable();
> >  	}
> > diff --git a/test/sql-tap/transitive1.test.lua b/test/sql-tap/transitive1.test.lua
> > index dbc2559fa..2d502f5e8 100755
> > --- a/test/sql-tap/transitive1.test.lua
> > +++ b/test/sql-tap/transitive1.test.lua
> > @@ -73,7 +73,7 @@ test:do_execsql_test(
> >  test:do_execsql_test(
> >      "transitive1-210",
> >      [[
> > -        SELECT a,b,c FROM t2 WHERE a=b AND c=b AND c>='20' ORDER BY +a;
> > +        SELECT a,b,c FROM t2 WHERE a = b AND c >= '20' ORDER BY +a;
> 
> 4. Might worth adding whitespaces after ',' in the select list.
> 
Fixed.

> >      ]], {
> >          -- <transitive1-210>
> >          3, 3, "3", 20, 20, "20"
> > diff --git a/test/sql/boolean.result b/test/sql/boolean.result
> > index d54de8fe7..81d79ee78 100644
> > --- a/test/sql/boolean.result
> > +++ b/test/sql/boolean.result
> > @@ -4898,12 +4898,12 @@ SELECT a1, a1 != 2.3 FROM t6
> >  SELECT a1, 2.3 == a1 FROM t6
> >   | ---
> >   | - null
> > - | - 'Type mismatch: can not convert double(2.3) to boolean'
> > + | - 'Type mismatch: can not convert boolean(FALSE) to number'
> 
> 5. Is it possible to keep the old error messages? For the sake of
> smaller diff.
It is possible but it will be very inconvenient since rules of defining value
and type for the error become quite complicated. Sorry, but I think it is better
to fix these rules now.

New patch:


commit 8bb2223cd9a50b47d4055a357825e4ea3007c0f0
Author: Mergen Imeev <imeevma@gmail.com>
Date:   Fri Jul 23 15:31:19 2021 +0300

    sql: remove implicit cast from comparison opcodes
    
    After this patch, the new rules will be applied to implicit cast during
    comparison where index is not used. Essentially it means that implicit
    cast from STRING to number during such comparisons was removed.
    
    Part of #4230
    Part of #4470

diff --git a/src/box/sql/func.c b/src/box/sql/func.c
index e153db24b..7dd3a4897 100644
--- a/src/box/sql/func.c
+++ b/src/box/sql/func.c
@@ -144,15 +144,7 @@ minmaxFunc(sql_context * context, int argc, sql_value ** argv)
 	for (i = 1; i < argc; i++) {
 		if (mem_is_null(argv[i]))
 			return;
-		int res;
-		if (mem_cmp_scalar(argv[iBest], argv[i], &res, pColl) != 0) {
-			diag_set(ClientError, ER_SQL_TYPE_MISMATCH,
-				 mem_str(argv[i]),
-				 mem_type_to_str(argv[iBest]));
-			context->is_aborted = true;
-			return;
-		}
-		if ((res ^ mask) >= 0)
+		if ((mem_cmp_scalar(argv[iBest], argv[i], pColl) ^ mask) >= 0)
 			iBest = i;
 	}
 	sql_result_value(context, argv[iBest]);
@@ -1059,14 +1051,7 @@ nullifFunc(sql_context * context, int NotUsed, sql_value ** argv)
 {
 	struct coll *pColl = sqlGetFuncCollSeq(context);
 	UNUSED_PARAMETER(NotUsed);
-	int res;
-	if (mem_cmp_scalar(argv[0], argv[1], &res, pColl) != 0) {
-		diag_set(ClientError, ER_SQL_TYPE_MISMATCH,
-			 mem_str(argv[1]), mem_type_to_str(argv[0]));
-		context->is_aborted = true;
-		return;
-	}
-	if (res != 0)
+	if (mem_cmp_scalar(argv[0], argv[1], pColl) != 0)
 		sql_result_value(context, argv[0]);
 }
 
@@ -1826,7 +1811,6 @@ minmaxStep(sql_context * context, int NotUsed, sql_value ** argv)
 		if (!mem_is_null(pBest))
 			sqlSkipAccumulatorLoad(context);
 	} else if (!mem_is_null(pBest)) {
-		int cmp;
 		struct coll *pColl = sqlGetFuncCollSeq(context);
 		/*
 		 * This step function is used for both the min()
@@ -1835,12 +1819,7 @@ minmaxStep(sql_context * context, int NotUsed, sql_value ** argv)
 		 * comparison is inverted.
 		 */
 		bool is_max = (func->flags & SQL_FUNC_MAX) != 0;
-		if (mem_cmp_scalar(pBest, pArg, &cmp, pColl) != 0) {
-			diag_set(ClientError, ER_SQL_TYPE_MISMATCH,
-				 mem_str(pArg), mem_type_to_str(pBest));
-			context->is_aborted = true;
-			return;
-		}
+		int cmp = mem_cmp_scalar(pBest, pArg, pColl);
 		if ((is_max && cmp < 0) || (!is_max && cmp > 0)) {
 			mem_copy(pBest, pArg);
 		} else {
diff --git a/src/box/sql/mem.c b/src/box/sql/mem.c
index c0ceb98e9..32fdd9add 100644
--- a/src/box/sql/mem.c
+++ b/src/box/sql/mem.c
@@ -162,6 +162,30 @@ mem_str(const struct Mem *mem)
 	}
 }
 
+static const char *
+mem_type_class_to_str(const struct Mem *mem)
+{
+	switch (mem->type) {
+	case MEM_TYPE_NULL:
+		return "NULL";
+	case MEM_TYPE_UINT:
+	case MEM_TYPE_INT:
+	case MEM_TYPE_DOUBLE:
+		return "number";
+	case MEM_TYPE_STR:
+		return "string";
+	case MEM_TYPE_BIN:
+		return "varbinary";
+	case MEM_TYPE_BOOL:
+		return "boolean";
+	case MEM_TYPE_UUID:
+		return "uuid";
+	default:
+		break;
+	}
+	return "unknown";
+}
+
 void
 mem_create(struct Mem *mem)
 {
@@ -1508,56 +1532,6 @@ mem_concat(struct Mem *a, struct Mem *b, struct Mem *result)
 	return 0;
 }
 
-struct sql_num {
-	union {
-		int64_t i;
-		uint64_t u;
-		double d;
-	};
-	enum mem_type type;
-	bool is_neg;
-};
-
-static int
-get_number(const struct Mem *mem, struct sql_num *number)
-{
-	if (mem->type == MEM_TYPE_DOUBLE) {
-		number->d = mem->u.r;
-		number->type = MEM_TYPE_DOUBLE;
-		return 0;
-	}
-	if (mem->type == MEM_TYPE_INT) {
-		number->i = mem->u.i;
-		number->type = MEM_TYPE_INT;
-		number->is_neg = true;
-		return 0;
-	}
-	if (mem->type == MEM_TYPE_UINT) {
-		number->u = mem->u.u;
-		number->type = MEM_TYPE_UINT;
-		number->is_neg = false;
-		return 0;
-	}
-	if ((mem->type & (MEM_TYPE_STR | MEM_TYPE_BIN)) == 0)
-		return -1;
-	if (sql_atoi64(mem->z, &number->i, &number->is_neg, mem->n) == 0) {
-		number->type = number->is_neg ? MEM_TYPE_INT : MEM_TYPE_UINT;
-		/*
-		 * The next line should be removed along with the is_neg field
-		 * of struct sql_num. The integer type tells us about the sign.
-		 * However, if it is removed, the behavior of arithmetic
-		 * operations will change.
-		 */
-		number->is_neg = false;
-		return 0;
-	}
-	if (sqlAtoF(mem->z, &number->d, mem->n) != 0) {
-		number->type = MEM_TYPE_DOUBLE;
-		return 0;
-	}
-	return -1;
-}
-
 int
 mem_add(const struct Mem *left, const struct Mem *right, struct Mem *result)
 {
@@ -1855,25 +1829,17 @@ mem_bit_not(const struct Mem *mem, struct Mem *result)
 	return 0;
 }
 
-int
-mem_cmp_bool(const struct Mem *a, const struct Mem *b, int *result)
+static int
+mem_cmp_bool(const struct Mem *a, const struct Mem *b)
 {
-	if ((a->type & b->type & MEM_TYPE_BOOL) == 0)
-		return -1;
-	if (a->u.b == b->u.b)
-		*result = 0;
-	else if (a->u.b)
-		*result = 1;
-	else
-		*result = -1;
-	return 0;
+	assert((a->type & b->type & MEM_TYPE_BOOL) != 0);
+	return a->u.b - b->u.b;
 }
 
-int
-mem_cmp_bin(const struct Mem *a, const struct Mem *b, int *result)
+static int
+mem_cmp_bin(const struct Mem *a, const struct Mem *b)
 {
-	if ((a->type & b->type & MEM_TYPE_BIN) == 0)
-		return -1;
+	assert((a->type & b->type & MEM_TYPE_BIN) != 0);
 	int an = a->n;
 	int bn = b->n;
 	int minlen = MIN(an, bn);
@@ -1887,181 +1853,105 @@ mem_cmp_bin(const struct Mem *a, const struct Mem *b, int *result)
 	assert((a->flags & MEM_Zero) == 0 || an == 0);
 	assert((b->flags & MEM_Zero) == 0 || bn == 0);
 
-	if ((a->flags & b->flags & MEM_Zero) != 0) {
-		*result = a->u.nZero - b->u.nZero;
-		return 0;
-	}
+	if ((a->flags & b->flags & MEM_Zero) != 0)
+		return a->u.nZero - b->u.nZero;
 	if ((a->flags & MEM_Zero) != 0) {
 		for (int i = 0; i < minlen; ++i) {
-			if (b->z[i] != 0) {
-				*result = -1;
-				return 0;
-			}
+			if (b->z[i] != 0)
+				return -1;
 		}
-		*result = a->u.nZero - bn;
-		return 0;
+		return a->u.nZero - bn;
 	}
 	if ((b->flags & MEM_Zero) != 0) {
 		for (int i = 0; i < minlen; ++i) {
-			if (a->z[i] != 0){
-				*result = 1;
-				return 0;
-			}
+			if (a->z[i] != 0)
+				return 1;
 		}
-		*result = b->u.nZero - an;
-		return 0;
+		return b->u.nZero - an;
 	}
-	*result = memcmp(a->z, b->z, minlen);
-	if (*result != 0)
-		return 0;
-	*result = an - bn;
-	return 0;
+	int res = memcmp(a->z, b->z, minlen);
+	return res != 0 ? res : an - bn;
 }
 
-int
-mem_cmp_num(const struct Mem *left, const struct Mem *right, int *result)
+static int
+mem_cmp_num(const struct Mem *a, const struct Mem *b)
 {
-	struct sql_num a, b;
-	/* TODO: Here should be check for right value type. */
-	if (get_number(right, &b) != 0) {
-		*result = -1;
+	assert(mem_is_num(a) && mem_is_num(b));
+	if ((a->type & b->type & MEM_TYPE_DOUBLE) != 0) {
+		if (a->u.r > b->u.r)
+			return 1;
+		if (a->u.r < b->u.r)
+			return -1;
 		return 0;
 	}
-	if (get_number(left, &a) != 0)
-		return -1;
-	if (a.type == MEM_TYPE_DOUBLE) {
-		if (b.type == MEM_TYPE_DOUBLE) {
-			if (a.d > b.d)
-				*result = 1;
-			else if (a.d < b.d)
-				*result = -1;
-			else
-				*result = 0;
-			return 0;
-		}
-		if (b.type == MEM_TYPE_INT)
-			*result = double_compare_nint64(a.d, b.i, 1);
-		else
-			*result = double_compare_uint64(a.d, b.u, 1);
+	if ((a->type & b->type & MEM_TYPE_INT) != 0) {
+		if (a->u.i > b->u.i)
+			return 1;
+		if (a->u.i < b->u.i)
+			return -1;
 		return 0;
 	}
-	if (a.type == MEM_TYPE_INT) {
-		if (b.type == MEM_TYPE_INT) {
-			if (a.i > b.i)
-				*result = 1;
-			else if (a.i < b.i)
-				*result = -1;
-			else
-				*result = 0;
-			return 0;
-		}
-		if (b.type == MEM_TYPE_UINT)
-			*result = -1;
-		else
-			*result = double_compare_nint64(b.d, a.i, -1);
+	if ((a->type & b->type & MEM_TYPE_UINT) != 0) {
+		if (a->u.u > b->u.u)
+			return 1;
+		if (a->u.u < b->u.u)
+			return -1;
 		return 0;
 	}
-	assert(a.type == MEM_TYPE_UINT);
-	if (b.type == MEM_TYPE_UINT) {
-		if (a.u > b.u)
-			*result = 1;
-		else if (a.u < b.u)
-			*result = -1;
-		else
-			*result = 0;
-		return 0;
+	if (a->type == MEM_TYPE_DOUBLE) {
+		if (b->type == MEM_TYPE_INT)
+			return double_compare_nint64(a->u.r, b->u.i, 1);
+		return double_compare_uint64(a->u.r, b->u.u, 1);
 	}
-	if (b.type == MEM_TYPE_INT)
-		*result = 1;
-	else
-		*result = double_compare_uint64(b.d, a.u, -1);
-	return 0;
+	if (b->type == MEM_TYPE_DOUBLE) {
+		if (a->type == MEM_TYPE_INT)
+			return double_compare_nint64(b->u.r, a->u.i, -1);
+		return double_compare_uint64(b->u.r, a->u.u, -1);
+	}
+	if (a->type == MEM_TYPE_INT)
+		return -1;
+	assert(a->type == MEM_TYPE_UINT && b->type == MEM_TYPE_INT);
+	return 1;
 }
 
-int
-mem_cmp_str(const struct Mem *left, const struct Mem *right, int *result,
-	    const struct coll *coll)
-{
-	char *a;
-	uint32_t an;
-	char bufl[BUF_SIZE];
-	if (left->type == MEM_TYPE_STR) {
-		a = left->z;
-		an = left->n;
-	} else {
-		assert(mem_is_num(left));
-		a = &bufl[0];
-		if (left->type == MEM_TYPE_INT)
-			sql_snprintf(BUF_SIZE, a, "%lld", left->u.i);
-		else if (left->type == MEM_TYPE_UINT)
-			sql_snprintf(BUF_SIZE, a, "%llu", left->u.u);
-		else
-			sql_snprintf(BUF_SIZE, a, "%!.15g", left->u.r);
-		an = strlen(a);
-	}
-
-	char *b;
-	uint32_t bn;
-	char bufr[BUF_SIZE];
-	if (right->type == MEM_TYPE_STR) {
-		b = right->z;
-		bn = right->n;
-	} else {
-		assert(mem_is_num(right));
-		b = &bufr[0];
-		if (right->type == MEM_TYPE_INT)
-			sql_snprintf(BUF_SIZE, b, "%lld", right->u.i);
-		else if (right->type == MEM_TYPE_UINT)
-			sql_snprintf(BUF_SIZE, b, "%llu", right->u.u);
-		else
-			sql_snprintf(BUF_SIZE, b, "%!.15g", right->u.r);
-		bn = strlen(b);
-	}
-	if (coll != NULL) {
-		*result = coll->cmp(a, an, b, bn, coll);
-		return 0;
-	}
-	uint32_t minlen = MIN(an, bn);
-	*result = memcmp(a, b, minlen);
-	if (*result != 0)
-		return 0;
-	*result = an - bn;
-	return 0;
+static int
+mem_cmp_str(const struct Mem *a, const struct Mem *b, const struct coll *coll)
+{
+	assert((a->type & b->type & MEM_TYPE_STR) != 0);
+	if (coll != NULL)
+		return coll->cmp(a->z, a->n, b->z, b->n, coll);
+	int res = memcmp(a->z, b->z, MIN(a->n, b->n));
+	return res != 0 ? res : a->n - b->n;
 }
 
-int
-mem_cmp_uuid(const struct Mem *a, const struct Mem *b, int *result)
+static int
+mem_cmp_uuid(const struct Mem *a, const struct Mem *b)
 {
-	if ((a->type & b->type & MEM_TYPE_UUID) == 0)
-		return -1;
-	*result = memcmp(&a->u.uuid, &b->u.uuid, UUID_LEN);
-	return 0;
+	assert((a->type & b->type & MEM_TYPE_UUID) != 0);
+	return memcmp(&a->u.uuid, &b->u.uuid, UUID_LEN);
 }
 
 int
-mem_cmp_scalar(const struct Mem *a, const struct Mem *b, int *result,
+mem_cmp_scalar(const struct Mem *a, const struct Mem *b,
 	       const struct coll *coll)
 {
 	enum mem_class class_a = mem_type_class(a->type);
 	enum mem_class class_b = mem_type_class(b->type);
-	if (class_a != class_b) {
-		*result = class_a - class_b;
-		return 0;
-	}
+	if (class_a != class_b)
+		return class_a - class_b;
 	switch (class_a) {
 	case MEM_CLASS_NULL:
-		*result = 0;
 		return 0;
 	case MEM_CLASS_BOOL:
-		return mem_cmp_bool(a, b, result);
+		return mem_cmp_bool(a, b);
 	case MEM_CLASS_NUMBER:
-		return mem_cmp_num(a, b, result);
+		return mem_cmp_num(a, b);
 	case MEM_CLASS_STR:
-		return mem_cmp_str(a, b, result, coll);
+		return mem_cmp_str(a, b, coll);
 	case MEM_CLASS_BIN:
-		return mem_cmp_bin(a, b, result);
+		return mem_cmp_bin(a, b);
 	case MEM_CLASS_UUID:
-		return mem_cmp_uuid(a, b, result);
+		return mem_cmp_uuid(a, b);
 	default:
 		unreachable();
 	}
@@ -2138,7 +2028,47 @@ mem_cmp_msgpack(const struct Mem *a, const char **b, int *result,
 	default:
 		unreachable();
 	}
-	return mem_cmp_scalar(a, &mem, result, coll);
+	*result = mem_cmp_scalar(a, &mem, coll);
+	return 0;
+}
+
+int
+mem_cmp(const struct Mem *a, const struct Mem *b, int *result,
+	const struct coll *coll)
+{
+	enum mem_class class_a = mem_type_class(a->type);
+	enum mem_class class_b = mem_type_class(b->type);
+	if (mem_is_any_null(a, b)) {
+		*result = class_a - class_b;
+		if ((a->flags & b->flags & MEM_Cleared) != 0)
+			*result = 1;
+		return 0;
+	}
+	if (class_a != class_b) {
+		diag_set(ClientError, ER_SQL_TYPE_MISMATCH, mem_str(b),
+			 mem_type_class_to_str(a));
+		return -1;
+	}
+	switch (class_a) {
+	case MEM_CLASS_BOOL:
+		*result =  mem_cmp_bool(a, b);
+		break;
+	case MEM_CLASS_NUMBER:
+		*result = mem_cmp_num(a, b);
+		break;
+	case MEM_CLASS_STR:
+		*result = mem_cmp_str(a, b, coll);
+		break;
+	case MEM_CLASS_BIN:
+		*result = mem_cmp_bin(a, b);
+		break;
+	case MEM_CLASS_UUID:
+		*result = mem_cmp_uuid(a, b);
+		break;
+	default:
+		unreachable();
+	}
+	return 0;
 }
 
 char *
diff --git a/src/box/sql/mem.h b/src/box/sql/mem.h
index 645d0ee27..70bbe557b 100644
--- a/src/box/sql/mem.h
+++ b/src/box/sql/mem.h
@@ -657,53 +657,12 @@ mem_shift_right(const struct Mem *left, const struct Mem *right,
 int
 mem_bit_not(const struct Mem *mem, struct Mem *result);
 
-/**
- * Compare two MEMs and return the result of comparison. MEMs should be of
- * BOOLEAN type or their values are converted to VARBINARY according to implicit
- * cast rules. Original MEMs are not changed.
- */
-int
-mem_cmp_bool(const struct Mem *a, const struct Mem *b, int *result);
-
-/**
- * Compare two MEMs and return the result of comparison. MEMs should be of
- * VARBINARY type or their values are converted to VARBINARY according to
- * implicit cast rules. Original MEMs are not changed.
- */
-int
-mem_cmp_bin(const struct Mem *a, const struct Mem *b, int *result);
-
-/**
- * Compare two MEMs and return the result of comparison. MEMs should be of
- * STRING type or their values are converted to VARBINARY according to
- * implicit cast rules. Original MEMs are not changed.
- */
-int
-mem_cmp_str(const struct Mem *left, const struct Mem *right, int *result,
-	    const struct coll *coll);
-
-/**
- * Compare two MEMs and return the result of comparison. MEMs should be of
- * NUMBER type or their values are converted to NUMBER according to
- * implicit cast rules. Original MEMs are not changed.
- */
-int
-mem_cmp_num(const struct Mem *a, const struct Mem *b, int *result);
-
-/**
- * Compare two MEMs and return the result of comparison. MEMs should be of
- * UUID type or their values are converted to UUID according to
- * implicit cast rules. Original MEMs are not changed.
- */
-int
-mem_cmp_uuid(const struct Mem *left, const struct Mem *right, int *result);
-
 /**
  * Compare two MEMs using SCALAR rules and return the result of comparison. MEMs
  * should be scalars. Original MEMs are not changed.
  */
 int
-mem_cmp_scalar(const struct Mem *a, const struct Mem *b, int *result,
+mem_cmp_scalar(const struct Mem *a, const struct Mem *b,
 	       const struct coll *coll);
 
 /**
@@ -716,6 +675,14 @@ int
 mem_cmp_msgpack(const struct Mem *a, const char **b, int *result,
 		const struct coll *coll);
 
+/**
+ * Compare two MEMs using implicit cast rules and return the result of
+ * comparison. MEMs should be scalars. Original MEMs are not changed.
+ */
+int
+mem_cmp(const struct Mem *a, const struct Mem *b, int *result,
+	const struct coll *coll);
+
 /**
  * Convert the given MEM to INTEGER. This function and the function below define
  * the rules that are used to convert values of all other types to INTEGER. In
diff --git a/src/box/sql/vdbe.c b/src/box/sql/vdbe.c
index f05da5051..2e147ec0b 100644
--- a/src/box/sql/vdbe.c
+++ b/src/box/sql/vdbe.c
@@ -1483,231 +1483,134 @@ case OP_Cast: {                  /* in1 */
 /* Opcode: Eq P1 P2 P3 P4 P5
  * Synopsis: IF r[P3]==r[P1]
  *
- * Compare the values in register P1 and P3.  If reg(P3)==reg(P1) then
- * jump to address P2.  Or if the SQL_STOREP2 flag is set in P5, then
- * store the result of comparison in register P2.
- *
- * Once any conversions have taken place, and neither value is NULL,
- * the values are compared. If both values are blobs then memcmp() is
- * used to determine the results of the comparison.  If both values
- * are text, then the appropriate collating function specified in
- * P4 is used to do the comparison.  If P4 is not specified then
- * memcmp() is used to compare text string.  If both values are
- * numeric, then a numeric comparison is used. If the two values
- * are of different types, then numbers are considered less than
- * strings and strings are considered less than blobs.
- *
- * If SQL_NULLEQ is set in P5 then the result of comparison is always either
- * true or false and is never NULL.  If both operands are NULL then the result
- * of comparison is true.  If either operand is NULL then the result is false.
- * If neither operand is NULL the result is the same as it would be if
- * the SQL_NULLEQ flag were omitted from P5.
- * P5 also can contain type to be applied to operands. Note that
- * the type conversions are stored back into the input registers
- * P1 and P3.  So this opcode can cause persistent changes to
- * registers P1 and P3.
- *
- * If both SQL_STOREP2 and SQL_KEEPNULL flags are set then the
- * content of r[P2] is only changed if the new value is NULL or false.
- * In other words, a prior r[P2] value will not be overwritten by true.
+ * Compare the values in register P1 and P3. If r[P3] == r[P1], then the action
+ * is performed. The action is to jump to address P2 or store the comparison
+ * result in register P2 if the SQL_STOREP2 flag is set in P5. In case both
+ * values are STRINGs and collation is used for comparison, the collation is
+ * specified in P4. If SQL_NULLEQ is set in P5, then the result of the
+ * comparison is always either TRUE or FALSE and will never be NULL.
  */
 /* Opcode: Ne P1 P2 P3 P4 P5
  * Synopsis: IF r[P3]!=r[P1]
  *
- * This works just like the Eq opcode except that the jump is taken if
- * the operands in registers P1 and P3 are not equal.  See the Eq opcode for
- * additional information.
- *
- * If both SQL_STOREP2 and SQL_KEEPNULL flags are set then the
- * content of r[P2] is only changed if the new value is NULL or true.
- * In other words, a prior r[P2] value will not be overwritten by false.
+ * This works just like the Eq opcode except that the action is performed if
+ * r[P3] != r[P1]. See the Eq opcode for additional information.
  */
+case OP_Eq:               /* same as TK_EQ, jump, in1, in3 */
+case OP_Ne: {             /* same as TK_NE, jump, in1, in3 */
+	pIn1 = &aMem[pOp->p1];
+	pIn3 = &aMem[pOp->p3];
+	if (mem_is_any_null(pIn1, pIn3) && (pOp->p5 & SQL_NULLEQ) == 0) {
+		/*
+		 * SQL_NULLEQ is clear and at least one operand is NULL, then
+		 * the result is always NULL. The jump is taken if the
+		 * SQL_JUMPIFNULL bit is set.
+		 */
+		if ((pOp->p5 & SQL_STOREP2) != 0) {
+			pOut = vdbe_prepare_null_out(p, pOp->p2);
+			iCompare = 1;
+			REGISTER_TRACE(p, pOp->p2, pOut);
+			break;
+		}
+		VdbeBranchTaken(2, 3);
+		if ((pOp->p5 & SQL_JUMPIFNULL) != 0)
+			goto jump_to_p2;
+		break;
+	}
+	int cmp_res;
+	if (mem_cmp(pIn3, pIn1, &cmp_res, pOp->p4.pColl) != 0)
+		goto abort_due_to_error;
+	bool result = pOp->opcode == OP_Eq ? cmp_res == 0 : cmp_res != 0;
+	if ((pOp->p5 & SQL_STOREP2) != 0) {
+		iCompare = cmp_res;
+		pOut = &aMem[pOp->p2];
+		mem_set_bool(pOut, result);
+		REGISTER_TRACE(p, pOp->p2, pOut);
+		break;
+	}
+	VdbeBranchTaken(result, (pOp->p5 & SQL_NULLEQ) != 0 ? 2 : 3);
+	if (result)
+		goto jump_to_p2;
+	break;
+}
+
 /* Opcode: Lt P1 P2 P3 P4 P5
  * Synopsis: IF r[P3]<r[P1]
  *
- * Compare the values in register P1 and P3.  If reg(P3)<reg(P1) then
- * jump to address P2.  Or if the SQL_STOREP2 flag is set in P5 store
- * the result of comparison (false or true or NULL) into register P2.
- *
- * If the SQL_JUMPIFNULL bit of P5 is set and either reg(P1) or
- * reg(P3) is NULL then the take the jump.  If the SQL_JUMPIFNULL
- * bit is clear then fall through if either operand is NULL.
- *
- * Once any conversions have taken place, and neither value is NULL,
- * the values are compared. If both values are blobs then memcmp() is
- * used to determine the results of the comparison.  If both values
- * are text, then the appropriate collating function specified in
- * P4 is  used to do the comparison.  If P4 is not specified then
- * memcmp() is used to compare text string.  If both values are
- * numeric, then a numeric comparison is used. If the two values
- * are of different types, then numbers are considered less than
- * strings and strings are considered less than blobs.
+ * Compare the values in register P1 and P3. If r[P3] < r[P1], then the action
+ * is performed. The action is to jump to address P2 or store the comparison
+ * result in register P2 if the SQL_STOREP2 flag is set in P5. In case both
+ * values are STRINGs and collation is used for comparison, the collation is
+ * specified in P4.
  */
 /* Opcode: Le P1 P2 P3 P4 P5
  * Synopsis: IF r[P3]<=r[P1]
  *
- * This works just like the Lt opcode except that the jump is taken if
- * the content of register P3 is less than or equal to the content of
- * register P1.  See the Lt opcode for additional information.
+ * This works just like the Lt opcode except that the action is performed if
+ * r[P3] <= r[P1]. See the Lt opcode for additional information.
  */
 /* Opcode: Gt P1 P2 P3 P4 P5
  * Synopsis: IF r[P3]>r[P1]
  *
- * This works just like the Lt opcode except that the jump is taken if
- * the content of register P3 is greater than the content of
- * register P1.  See the Lt opcode for additional information.
+ * This works just like the Lt opcode except that the action is performed if
+ * r[P3] > r[P1]. See the Lt opcode for additional information.
  */
 /* Opcode: Ge P1 P2 P3 P4 P5
  * Synopsis: IF r[P3]>=r[P1]
  *
- * This works just like the Lt opcode except that the jump is taken if
- * the content of register P3 is greater than or equal to the content of
- * register P1.  See the Lt opcode for additional information.
+ * This works just like the Lt opcode except that the action is performed if
+ * r[P3] >= r[P1]. See the Lt opcode for additional information.
  */
-case OP_Eq:               /* same as TK_EQ, jump, in1, in3 */
-case OP_Ne:               /* same as TK_NE, jump, in1, in3 */
 case OP_Lt:               /* same as TK_LT, jump, in1, in3 */
 case OP_Le:               /* same as TK_LE, jump, in1, in3 */
 case OP_Gt:               /* same as TK_GT, jump, in1, in3 */
 case OP_Ge: {             /* same as TK_GE, jump, in1, in3 */
-	int res, res2;      /* Result of the comparison of pIn1 against pIn3 */
-
 	pIn1 = &aMem[pOp->p1];
 	pIn3 = &aMem[pOp->p3];
-	enum field_type type = pOp->p5 & FIELD_TYPE_MASK;
 	if (mem_is_any_null(pIn1, pIn3)) {
-		/* One or both operands are NULL */
-		if (pOp->p5 & SQL_NULLEQ) {
-			/* If SQL_NULLEQ is set (which will only happen if the operator is
-			 * OP_Eq or OP_Ne) then take the jump or not depending on whether
-			 * or not both operands are null.
-			 */
-			assert(pOp->opcode==OP_Eq || pOp->opcode==OP_Ne);
-			assert(!mem_is_cleared(pIn1));
-			assert((pOp->p5 & SQL_JUMPIFNULL)==0);
-			if (mem_is_same_type(pIn1, pIn3) &&
-			    !mem_is_cleared(pIn3)) {
-				res = 0;  /* Operands are equal */
-			} else {
-				res = 1;  /* Operands are not equal */
-			}
-		} else {
-			/* SQL_NULLEQ is clear and at least one operand is NULL,
-			 * then the result is always NULL.
-			 * The jump is taken if the SQL_JUMPIFNULL bit is set.
-			 */
-			if (pOp->p5 & SQL_STOREP2) {
-				pOut = vdbe_prepare_null_out(p, pOp->p2);
-				iCompare = 1;    /* Operands are not equal */
-				REGISTER_TRACE(p, pOp->p2, pOut);
-			} else {
-				VdbeBranchTaken(2,3);
-				if (pOp->p5 & SQL_JUMPIFNULL) {
-					goto jump_to_p2;
-				}
-			}
+		if ((pOp->p5 & SQL_STOREP2) != 0) {
+			pOut = vdbe_prepare_null_out(p, pOp->p2);
+			iCompare = 1;
+			REGISTER_TRACE(p, pOp->p2, pOut);
 			break;
 		}
-	} else if (mem_is_bool(pIn3) || mem_is_bool(pIn1)) {
-		if (mem_cmp_bool(pIn3, pIn1, &res) != 0) {
-			const char *str = !mem_is_bool(pIn3) ?
-					  mem_str(pIn3) : mem_str(pIn1);
-			diag_set(ClientError, ER_SQL_TYPE_MISMATCH, str,
-				 "boolean");
-			goto abort_due_to_error;
-		}
-	} else if (((pIn3->type | pIn1->type) & MEM_TYPE_UUID) != 0) {
-		if (mem_cmp_uuid(pIn3, pIn1, &res) != 0) {
-			const char *str = pIn3->type != MEM_TYPE_UUID ?
-					  mem_str(pIn3) : mem_str(pIn1);
-			diag_set(ClientError, ER_SQL_TYPE_MISMATCH, str,
-				 "uuid");
-			goto abort_due_to_error;
-		}
-	} else if (mem_is_bin(pIn3) || mem_is_bin(pIn1)) {
-		if (mem_cmp_bin(pIn3, pIn1, &res) != 0) {
-			const char *str = !mem_is_bin(pIn3) ?
-					  mem_str(pIn3) : mem_str(pIn1);
-			diag_set(ClientError, ER_SQL_TYPE_MISMATCH, str,
-				 "varbinary");
-			goto abort_due_to_error;
-		}
-	} else if (mem_is_map(pIn3) || mem_is_map(pIn1) || mem_is_array(pIn3) ||
-		   mem_is_array(pIn1)) {
-		diag_set(ClientError, ER_SQL_TYPE_MISMATCH,
-			 mem_str(pIn3), mem_type_to_str(pIn1));
-		goto abort_due_to_error;
-	} else if (type == FIELD_TYPE_STRING) {
-		if (mem_cmp_str(pIn3, pIn1, &res, pOp->p4.pColl) != 0) {
-			const char *str =
-				mem_cast_implicit_old(pIn3, type) != 0 ?
-				mem_str(pIn3) : mem_str(pIn1);
-			diag_set(ClientError, ER_SQL_TYPE_MISMATCH, str,
-				 "string");
-			goto abort_due_to_error;
-		}
-	} else if (sql_type_is_numeric(type) || mem_is_num(pIn3) ||
-		   mem_is_num(pIn1)) {
-		type = FIELD_TYPE_NUMBER;
-		if (mem_cmp_num(pIn3, pIn1, &res) != 0) {
-			const char *str =
-				mem_cast_implicit_old(pIn3, type) != 0 ?
-				mem_str(pIn3) : mem_str(pIn1);
-			diag_set(ClientError, ER_SQL_TYPE_MISMATCH, str,
-				 "number");
-			goto abort_due_to_error;
-		}
-	} else {
-		type = FIELD_TYPE_STRING;
-		assert(mem_is_str(pIn3) && mem_is_same_type(pIn3, pIn1));
-		if (mem_cmp_str(pIn3, pIn1, &res, pOp->p4.pColl) != 0) {
-			const char *str =
-				mem_cast_implicit_old(pIn3, type) != 0 ?
-				mem_str(pIn3) : mem_str(pIn1);
-			diag_set(ClientError, ER_SQL_TYPE_MISMATCH, str,
-				 "string");
-			goto abort_due_to_error;
-		}
+		VdbeBranchTaken(2,3);
+		if ((pOp->p5 & SQL_JUMPIFNULL) != 0)
+			goto jump_to_p2;
+		break;
 	}
+	int cmp_res;
+	if (mem_cmp(pIn3, pIn1, &cmp_res, pOp->p4.pColl) != 0)
+		goto abort_due_to_error;
 
-	switch( pOp->opcode) {
-	case OP_Eq:    res2 = res==0;     break;
-	case OP_Ne:    res2 = res;        break;
-	case OP_Lt:    res2 = res<0;      break;
-	case OP_Le:    res2 = res<=0;     break;
-	case OP_Gt:    res2 = res>0;      break;
-	default:       res2 = res>=0;     break;
+	bool result;
+	switch(pOp->opcode) {
+	case OP_Lt:
+		result = cmp_res < 0;
+		break;
+	case OP_Le:
+		result = cmp_res <= 0;
+		break;
+	case OP_Gt:
+		result = cmp_res > 0;
+		break;
+	case OP_Ge:
+		result = cmp_res >= 0;
+		break;
+	default:
+		unreachable();
 	}
 
-	if (pOp->p5 & SQL_STOREP2) {
-		iCompare = res;
-		res2 = res2!=0;  /* For this path res2 must be exactly 0 or 1 */
-		if ((pOp->p5 & SQL_KEEPNULL)!=0) {
-			/* The KEEPNULL flag prevents OP_Eq from overwriting a NULL with true
-			 * and prevents OP_Ne from overwriting NULL with false.  This flag
-			 * is only used in contexts where either:
-			 *   (1) op==OP_Eq && (r[P2]==NULL || r[P2]==0)
-			 *   (2) op==OP_Ne && (r[P2]==NULL || r[P2]==1)
-			 * Therefore it is not necessary to check the content of r[P2] for
-			 * NULL.
-			 */
-			assert(pOp->opcode==OP_Ne || pOp->opcode==OP_Eq);
-			assert(res2==0 || res2==1);
-			testcase( res2==0 && pOp->opcode==OP_Eq);
-			testcase( res2==1 && pOp->opcode==OP_Eq);
-			testcase( res2==0 && pOp->opcode==OP_Ne);
-			testcase( res2==1 && pOp->opcode==OP_Ne);
-			if ((pOp->opcode==OP_Eq)==res2) break;
-		}
-		pOut = vdbe_prepare_null_out(p, pOp->p2);
-		mem_set_bool(pOut, res2);
+	if ((pOp->p5 & SQL_STOREP2) != 0) {
+		iCompare = cmp_res;
+		pOut = &aMem[pOp->p2];
+		mem_set_bool(pOut, result);
 		REGISTER_TRACE(p, pOp->p2, pOut);
-	} else {
-		VdbeBranchTaken(res!=0, (pOp->p5 & SQL_NULLEQ)?2:3);
-		if (res2) {
-			goto jump_to_p2;
-		}
+		break;
 	}
+	VdbeBranchTaken(result, 3);
+	if (result)
+		goto jump_to_p2;
 	break;
 }
 
@@ -1810,11 +1713,7 @@ case OP_Compare: {
 		bool is_rev = def->parts[i].sort_order == SORT_ORDER_DESC;
 		struct Mem *a = &aMem[p1+idx];
 		struct Mem *b = &aMem[p2+idx];
-		if (mem_cmp_scalar(a, b, &iCompare, coll) != 0) {
-			diag_set(ClientError, ER_SQL_TYPE_MISMATCH, mem_str(b),
-				 mem_type_to_str(a));
-			goto abort_due_to_error;
-		}
+		iCompare = mem_cmp_scalar(a, b, coll);
 		if (iCompare) {
 			if (is_rev)
 				iCompare = -iCompare;
diff --git a/src/box/sql/where.c b/src/box/sql/where.c
index e2eb153fb..16766f2f8 100644
--- a/src/box/sql/where.c
+++ b/src/box/sql/where.c
@@ -1272,27 +1272,11 @@ whereRangeSkipScanEst(Parse * pParse,		/* Parsing & code generating context */
 			rc = sql_stat4_column(db, samples[i].sample_key, nEq,
 					      &pVal);
 			if (rc == 0 && p1 != NULL) {
-				int res;
-				rc = mem_cmp_scalar(p1, pVal, &res, coll);
-				if (rc != 0) {
-					diag_set(ClientError,
-						 ER_SQL_TYPE_MISMATCH,
-						 mem_str(pVal),
-						 mem_type_to_str(p1));
-				}
-				if (rc == 0 && res >= 0)
+				if (mem_cmp_scalar(p1, pVal, coll) >= 0)
 					nLower++;
 			}
 			if (rc == 0 && p2 != NULL) {
-				int res;
-				rc = mem_cmp_scalar(p2, pVal, &res, coll);
-				if (rc != 0) {
-					diag_set(ClientError,
-						 ER_SQL_TYPE_MISMATCH,
-						 mem_str(pVal),
-						 mem_type_to_str(p2));
-				}
-				if (rc == 0 && res >= 0)
+				if (mem_cmp_scalar(p2, pVal, coll) >= 0)
 					nUpper++;
 			}
 		}
diff --git a/test/sql-tap/cast.test.lua b/test/sql-tap/cast.test.lua
index 8af99dbde..b8ad23317 100755
--- a/test/sql-tap/cast.test.lua
+++ b/test/sql-tap/cast.test.lua
@@ -1,6 +1,6 @@
 #!/usr/bin/env tarantool
 local test = require("sqltester")
-test:plan(103)
+test:plan(107)
 
 --!./tcltestrunner.lua
 -- 2005 June 25
@@ -1107,4 +1107,41 @@ test:execsql([[
     DROP TABLE t5;
 ]])
 
+--
+-- Make sure that implicit cast from STRING to number was removed during
+-- comparison where index is not used.
+--
+
+test:do_catchsql_test(
+    "cast-10.1",
+    [[
+        SELECT 1 < '2';
+    ]], {
+        1, "Type mismatch: can not convert string('2') to number"
+    })
+
+test:do_catchsql_test(
+    "cast-10.2",
+    [[
+        SELECT '1' < 2;
+    ]], {
+        1, "Type mismatch: can not convert integer(2) to string"
+    })
+
+test:do_catchsql_test(
+    "cast-10.3",
+    [[
+        SELECT 1.5 < '2';
+    ]], {
+        1, "Type mismatch: can not convert string('2') to number"
+    })
+
+test:do_catchsql_test(
+    "cast-10.4",
+    [[
+        SELECT '1' < 2.5;
+    ]], {
+        1, "Type mismatch: can not convert double(2.5) to string"
+    })
+
 test:finish_test()
diff --git a/test/sql-tap/func5.test.lua b/test/sql-tap/func5.test.lua
index 9b1526aaf..98a8b9ada 100755
--- a/test/sql-tap/func5.test.lua
+++ b/test/sql-tap/func5.test.lua
@@ -305,11 +305,13 @@ test:do_execsql_test(
         SELECT ifnull(null, 'qqq2') = 'qqq2';
     ]], { true } )
 
-test:do_execsql_test(
+test:do_catchsql_test(
     "func-6.3-ifnull",
     [[
         SELECT ifnull(null, 1) = 'qqq2';
-    ]], { false } )
+    ]], {
+        1, "Type mismatch: can not convert string('qqq2') to number"
+    })
 
 box.func.COUNTER1:drop()
 box.func.COUNTER2:drop()
diff --git a/test/sql-tap/identifier_case.test.lua b/test/sql-tap/identifier_case.test.lua
index 84b6078fd..097fa1b64 100755
--- a/test/sql-tap/identifier_case.test.lua
+++ b/test/sql-tap/identifier_case.test.lua
@@ -242,11 +242,11 @@ data = {
     { 2,  [[ 'a' < 'b' collate "binary" ]], {0, {true}}},
     { 3,  [[ 'a' < 'b' collate 'binary' ]], {1, [[Syntax error at line 1 near ''binary'']]}},
     { 4,  [[ 'a' < 'b' collate "unicode" ]], {0, {true}}},
-    { 5,  [[ 5 < 'b' collate "unicode" ]], {0, {true}}},
-    { 6,  [[ 5 < 'b' collate unicode ]], {1,"Collation 'UNICODE' does not exist"}},
-    { 7,  [[ 5 < 'b' collate "unicode_ci" ]], {0, {true}}},
-    { 8,  [[ 5 < 'b' collate NONE ]], {1, "Collation 'NONE' does not exist"}},
-    { 9,  [[ 5 < 'b' collate "none" ]], {0, {true}}},
+    { 5,  [[ '5' < 'b' collate "unicode" ]], {0, {true}}},
+    { 6,  [[ '5' < 'b' collate unicode ]], {1,"Collation 'UNICODE' does not exist"}},
+    { 7,  [[ '5' < 'b' collate "unicode_ci" ]], {0, {true}}},
+    { 8,  [[ '5' < 'b' collate NONE ]], {1, "Collation 'NONE' does not exist"}},
+    { 9,  [[ '5' < 'b' collate "none" ]], {0, {true}}},
 }
 
 for _, row in ipairs(data) do
diff --git a/test/sql-tap/in1.test.lua b/test/sql-tap/in1.test.lua
index bdf1f207a..14aefd15d 100755
--- a/test/sql-tap/in1.test.lua
+++ b/test/sql-tap/in1.test.lua
@@ -1,6 +1,6 @@
 #!/usr/bin/env tarantool
 local test = require("sqltester")
-test:plan(80)
+test:plan(79)
 
 --!./tcltestrunner.lua
 -- 2001 September 15
@@ -636,19 +636,6 @@ test:do_execsql_test(
         -- </in-11.1>
     })
 
-test:do_test(
-    "in-11.2",
-    function()
-        -- The '2' should be coerced into 2 because t6.b is NUMERIC
-        return test:execsql [[
-            SELECT * FROM t6 WHERE b IN ('2');
-        ]]
-    end, {
-        -- <in-11.2>
-        1, 2
-        -- </in-11.2>
-    })
-
 test:do_execsql_test(
     "in-11.5",
     [[
diff --git a/test/sql-tap/index1.test.lua b/test/sql-tap/index1.test.lua
index 6a7450bb7..3230556d1 100755
--- a/test/sql-tap/index1.test.lua
+++ b/test/sql-tap/index1.test.lua
@@ -778,7 +778,7 @@ test:do_catchsql_test(
         SELECT c FROM t6 WHERE a>123;
     ]], {
         -- <index-14.6>
-        1, "Type mismatch: can not convert string('') to number"
+        1, "Type mismatch: can not convert integer(123) to string"
         -- </index-14.6>
     })
 
@@ -788,7 +788,7 @@ test:do_catchsql_test(
         SELECT c FROM t6 WHERE a>=123;
     ]], {
         -- <index-14.7>
-        1, "Type mismatch: can not convert string('') to number"
+        1, "Type mismatch: can not convert integer(123) to string"
         -- </index-14.7>
     })
 
diff --git a/test/sql-tap/insert3.test.lua b/test/sql-tap/insert3.test.lua
index 5469fe06b..062fb1e33 100755
--- a/test/sql-tap/insert3.test.lua
+++ b/test/sql-tap/insert3.test.lua
@@ -59,7 +59,7 @@ test:do_execsql_test(
     [[
             CREATE TABLE log2(rowid INTEGER PRIMARY KEY AUTOINCREMENT, x TEXT UNIQUE,y INT );
             CREATE TRIGGER r2 BEFORE INSERT ON t1 FOR EACH ROW BEGIN
-              UPDATE log2 SET y=y+1 WHERE x=new.b;
+              UPDATE log2 SET y = y+1 WHERE x = CAST(new.b AS STRING);
               INSERT OR IGNORE INTO log2(x, y) VALUES(CAST(new.b AS STRING),1);
             END;
             INSERT INTO t1(a, b) VALUES('hi', 453);
diff --git a/test/sql-tap/join.test.lua b/test/sql-tap/join.test.lua
index 2eb884d32..48639a7b3 100755
--- a/test/sql-tap/join.test.lua
+++ b/test/sql-tap/join.test.lua
@@ -1038,13 +1038,13 @@ test:do_execsql_test(
         -- </join-11.9>
     })
 
-test:do_execsql_test(
+test:do_catchsql_test(
     "join-11.10",
     [[
         SELECT * FROM t2 NATURAL JOIN t1
     ]], {
         -- <join-11.10>
-        1, "one", 2, "two"
+        1, "Type mismatch: can not convert string('1') to number"
         -- </join-11.10>
     })
 
diff --git a/test/sql-tap/misc1.test.lua b/test/sql-tap/misc1.test.lua
index 52c7945fe..f207d3e92 100755
--- a/test/sql-tap/misc1.test.lua
+++ b/test/sql-tap/misc1.test.lua
@@ -1,6 +1,6 @@
 #!/usr/bin/env tarantool
 local test = require("sqltester")
-test:plan(59)
+test:plan(58)
 
 --!./tcltestrunner.lua
 -- 2001 September 15.
@@ -90,7 +90,7 @@ test:do_execsql_test(
 test:do_execsql_test(
     "misc1-1.4",
     [[
-        SELECT x75 FROM manycol WHERE x50=350
+        SELECT x75 FROM manycol WHERE x50 = '350'
     ]], {
         -- <misc1-1.4>
         "375"
@@ -100,7 +100,7 @@ test:do_execsql_test(
 test:do_execsql_test(
     "misc1-1.5",
     [[
-        SELECT x50 FROM manycol WHERE x99=599
+        SELECT x50 FROM manycol WHERE x99 = '599'
     ]], {
         -- <misc1-1.5>
         "550"
@@ -111,7 +111,7 @@ test:do_test(
     "misc1-1.6",
     function()
         test:execsql("CREATE INDEX manycol_idx1 ON manycol(x99)")
-        return test:execsql("SELECT x50 FROM manycol WHERE x99=899")
+        return test:execsql("SELECT x50 FROM manycol WHERE x99 = '899'")
     end, {
         -- <misc1-1.6>
         "850"
@@ -131,7 +131,7 @@ test:do_execsql_test(
 test:do_test(
     "misc1-1.8",
     function()
-        test:execsql("DELETE FROM manycol WHERE x98=1234")
+        test:execsql("DELETE FROM manycol WHERE x98 = '1234'")
         return test:execsql("SELECT count(*) FROM manycol")
     end, {
         -- <misc1-1.8>
@@ -142,7 +142,7 @@ test:do_test(
 test:do_test(
     "misc1-1.9",
     function()
-        test:execsql("DELETE FROM manycol WHERE x98=998")
+        test:execsql("DELETE FROM manycol WHERE x98 = '998'")
         return test:execsql("SELECT count(*) FROM manycol")
     end, {
         -- <misc1-1.9>
@@ -153,7 +153,7 @@ test:do_test(
 test:do_test(
     "misc1-1.10",
     function()
-        test:execsql("DELETE FROM manycol WHERE x99=500")
+        test:execsql("DELETE FROM manycol WHERE x99 = '500'")
         return test:execsql("SELECT count(*) FROM manycol")
     end, {
         -- <misc1-1.10>
@@ -164,7 +164,7 @@ test:do_test(
 test:do_test(
     "misc1-1.11",
     function()
-        test:execsql("DELETE FROM manycol WHERE x99=599")
+        test:execsql("DELETE FROM manycol WHERE x99 = '599'")
         return test:execsql("SELECT count(*) FROM manycol")
     end, {
         -- <misc1-1.11>
@@ -478,13 +478,14 @@ test:do_execsql_test(
         -- </misc1-10.0>
     })
 local where = ""
+local where_part = ""
+for i = 1, 99, 1 do
+    where_part = where_part .. " AND CAST(x"..i.." AS NUMBER) <> 0"
+end
 test:do_test(
     "misc1-10.1",
     function()
-        where = "WHERE x0>=0"
-        for i = 1, 99, 1 do
-            where = where .. " AND x"..i.."<>0"
-        end
+        where = "WHERE CAST(x0 AS NUMBER) >= 0" .. where_part
         return test:catchsql("SELECT count(*) FROM manycol "..where.."")
     end, {
         -- <misc1-10.1>
@@ -498,7 +499,7 @@ test:do_test(
 test:do_test(
     "misc1-10.3",
     function()
-        where = string.gsub(where,"x0>=0", "x0=0")
+        where = "WHERE CAST(x0 AS NUMBER) = 0" .. where_part
         return test:catchsql("DELETE FROM manycol "..where.."")
     end, {
         -- <misc1-10.3>
@@ -522,7 +523,7 @@ test:do_execsql_test(
 test:do_execsql_test(
     "misc1-10.6",
     [[
-        SELECT x1 FROM manycol WHERE x0=100
+        SELECT x1 FROM manycol WHERE x0 = '100'
     ]], {
         -- <misc1-10.6>
         "101"
@@ -533,7 +534,7 @@ local cast = "CAST(CAST(x1 AS INTEGER) + 1 AS STRING)"
 test:do_test(
     "misc1-10.7",
     function()
-        where = string.gsub(where, "x0=0", "x0=100")
+        where = "WHERE CAST(x0 AS NUMBER) = 100" .. where_part
         return test:catchsql("UPDATE manycol SET x1 = "..cast.." "..where..";")
     end, {
         -- <misc1-10.7>
@@ -544,7 +545,7 @@ test:do_test(
 test:do_execsql_test(
     "misc1-10.8",
     [[
-        SELECT x1 FROM manycol WHERE x0=100
+        SELECT x1 FROM manycol WHERE x0 = '100'
     ]], {
         -- <misc1-10.8>
         "102"
@@ -566,7 +567,7 @@ test:do_execsql_test(
 test:do_execsql_test(
     "misc1-10.10",
     [[
-        SELECT x1 FROM manycol WHERE x0=100
+        SELECT x1 FROM manycol WHERE x0 = '100'
     ]], {
         -- <misc1-10.10>
         "103"
@@ -630,16 +631,6 @@ test:do_execsql_test(
         -- </misc1-12.1>
     })
 
-test:do_execsql_test(
-    "misc1-12.2",
-    [[
-        SELECT '0'==0.0
-    ]], {
-        -- <misc1-12.2>
-        true
-        -- </misc1-12.2>
-    })
-
 test:do_execsql_test(
     "misc1-12.3",
     [[
diff --git a/test/sql-tap/select1.test.lua b/test/sql-tap/select1.test.lua
index 06641a60e..dbc6e193d 100755
--- a/test/sql-tap/select1.test.lua
+++ b/test/sql-tap/select1.test.lua
@@ -320,7 +320,7 @@ test:do_catchsql_test(
         SELECT count(*),count(a),count(b) FROM t4 WHERE b=5
     ]], {
         -- <select1-2.5.3>
-        1, "Type mismatch: can not convert string('This is a string that is too big to fit inside a NBFS buffer') to number"
+        1, "Type mismatch: can not convert integer(5) to string"
         -- </select1-2.5.3>
     })
 
@@ -1916,7 +1916,7 @@ test:do_execsql_test(
 test:do_execsql_test(
         "select1-12.7",
         [[
-            SELECT * FROM t3 WHERE a=(SELECT 1);
+            SELECT * FROM t3 WHERE a = (SELECT '1');
         ]], {
             -- <select1-12.7>
             0, "1", "2"
@@ -1926,7 +1926,7 @@ test:do_execsql_test(
 test:do_execsql_test(
     "select1-12.8",
     [[
-        SELECT * FROM t3 WHERE a=(SELECT 2);
+        SELECT * FROM t3 WHERE a = (SELECT '2');
     ]], {
         -- <select1-12.8>
 
diff --git a/test/sql-tap/select7.test.lua b/test/sql-tap/select7.test.lua
index 6b8f385e9..815f9110b 100755
--- a/test/sql-tap/select7.test.lua
+++ b/test/sql-tap/select7.test.lua
@@ -255,7 +255,7 @@ test:do_execsql_test(
         DROP TABLE IF EXISTS t5;
         CREATE TABLE t5(a TEXT primary key, b INT);
         INSERT INTO t5 VALUES('123', 456);
-        SELECT typeof(a), a FROM t5 GROUP BY a HAVING a<b;
+        SELECT typeof(a), a FROM t5 GROUP BY a HAVING CAST(a AS NUMBER) < b;
     ]], {
         -- <select7-7.7>
         "string", "123"
diff --git a/test/sql-tap/sql-errors.test.lua b/test/sql-tap/sql-errors.test.lua
index 17fe4b5b2..409c97257 100755
--- a/test/sql-tap/sql-errors.test.lua
+++ b/test/sql-tap/sql-errors.test.lua
@@ -766,7 +766,7 @@ test:do_catchsql_test(
 		SELECT X'ff' >= false;
 	]], {
 		-- <sql-errors-2.8>
-		1, "Type mismatch: can not convert varbinary(x'FF') to boolean"
+		1, "Type mismatch: can not convert boolean(FALSE) to varbinary"
 		-- </sql-errors-2.8>
 	})
 
@@ -776,7 +776,7 @@ test:do_catchsql_test(
 		SELECT X'ff' <= false;
 	]], {
 		-- <sql-errors-2.9>
-		1, "Type mismatch: can not convert varbinary(x'FF') to boolean"
+		1, "Type mismatch: can not convert boolean(FALSE) to varbinary"
 		-- </sql-errors-2.9>
 	})
 
diff --git a/test/sql-tap/subquery.test.lua b/test/sql-tap/subquery.test.lua
index 44032548f..1c5b3d02e 100755
--- a/test/sql-tap/subquery.test.lua
+++ b/test/sql-tap/subquery.test.lua
@@ -284,13 +284,13 @@ test:do_execsql_test(
         -- </subquery-2.3.1>
     })
 
-test:do_execsql_test(
+test:do_catchsql_test(
     "subquery-2.3.2",
     [[
         SELECT a IN (10.0, 20) FROM t3;
     ]], {
         -- <subquery-2.3.2>
-        false
+        1, "Type mismatch: can not convert string('10') to number"
         -- </subquery-2.3.2>
     })
 
diff --git a/test/sql-tap/tkt-9a8b09f8e6.test.lua b/test/sql-tap/tkt-9a8b09f8e6.test.lua
index bee4fdf6c..43322468d 100755
--- a/test/sql-tap/tkt-9a8b09f8e6.test.lua
+++ b/test/sql-tap/tkt-9a8b09f8e6.test.lua
@@ -79,23 +79,23 @@ test:do_execsql_test(
         -- </1.5>
     })
 
-test:do_execsql_test(
+test:do_catchsql_test(
     2.1,
     [[
         SELECT x FROM t1 WHERE x IN (1);
     ]], {
         -- <2.1>
-        "1"
+        1, "Type mismatch: can not convert integer(1) to string"
         -- </2.1>
     })
 
-test:do_execsql_test(
+test:do_catchsql_test(
     2.2,
     [[
         SELECT x FROM t1 WHERE x IN (1.0);
     ]], {
         -- <2.2>
-        "1"
+        1, "Type mismatch: can not convert double(1.0) to string"
         -- </2.2>
     })
 
@@ -119,23 +119,23 @@ test:do_execsql_test(
         -- </2.4>
     })
 
-test:do_execsql_test(
+test:do_catchsql_test(
     2.5,
     [[
         SELECT x FROM t1 WHERE 1 IN (x);
     ]], {
         -- <2.5>
-        "1"
+        1, "Type mismatch: can not convert integer(1) to string"
         -- </2.5>
     })
 
-test:do_execsql_test(
+test:do_catchsql_test(
     2.6,
     [[
         SELECT x FROM t1 WHERE 1.0 IN (x);
     ]], {
         -- <2.6>
-        "1"
+        1, "Type mismatch: can not convert double(1.0) to string"
         -- </2.6>
     })
 
@@ -439,23 +439,23 @@ test:do_execsql_test(
         -- </6.2>
     })
 
-test:do_execsql_test(
+test:do_catchsql_test(
     6.3,
     [[
         SELECT x, y FROM t5 WHERE x IN ('1');
     ]], {
         -- <6.3>
-        1, "one", 1, "two", 1, "three", 1.0, "four"
+        1, "Type mismatch: can not convert string('1') to number"
         -- </6.3>
     })
 
-test:do_execsql_test(
+test:do_catchsql_test(
     6.4,
     [[
         SELECT x, y FROM t5 WHERE x IN ('1.0');
     ]], {
         -- <6.4>
-        1, "one", 1, "two", 1, "three", 1.0, "four"
+        1, "Type mismatch: can not convert string('1.0') to number"
         -- </6.4>
     })
 
@@ -479,23 +479,23 @@ test:do_execsql_test(
         -- </6.6>
     })
 
-test:do_execsql_test(
+test:do_catchsql_test(
     6.7,
     [[
         SELECT x, y FROM t5 WHERE '1' IN (x);
     ]], {
         -- <6.7>
-        1, "one", 1, "two", 1, "three", 1.0, "four"
+        1, "Type mismatch: can not convert string('1') to number"
         -- </6.7>
     })
 
-test:do_execsql_test(
+test:do_catchsql_test(
     6.8,
     [[
         SELECT x, y FROM t5 WHERE '1.0' IN (x);
     ]], {
         -- <6.8>
-        1, "one", 1, "two", 1, "three", 1, "four"
+        1, "Type mismatch: can not convert string('1.0') to number"
         -- </6.8>
     })
 
diff --git a/test/sql-tap/tkt3493.test.lua b/test/sql-tap/tkt3493.test.lua
index 0aac0ddb9..77c84a994 100755
--- a/test/sql-tap/tkt3493.test.lua
+++ b/test/sql-tap/tkt3493.test.lua
@@ -45,7 +45,7 @@ test:do_execsql_test(
     [[
         SELECT
           CASE
-             WHEN B.val = 1 THEN 'XYZ'
+             WHEN B.val = '1' THEN 'XYZ'
              ELSE A.val
           END AS Col1
         FROM B
@@ -63,7 +63,7 @@ test:do_execsql_test(
     [[
         SELECT DISTINCT
           CASE
-             WHEN B.val = 1 THEN 'XYZ'
+             WHEN B.val = '1' THEN 'XYZ'
              ELSE A.val
           END AS Col1
         FROM B
@@ -79,14 +79,14 @@ test:do_execsql_test(
 test:do_execsql_test(
     "tkt3493-1.4",
     [[
-        SELECT b.val, CASE WHEN b.val = 1 THEN 'xyz' ELSE b.val END AS col1 FROM b;
+        SELECT b.val, CASE WHEN b.val = '1' THEN 'xyz' ELSE b.val END AS col1 FROM b;
     ]], {
         -- <tkt3493-1.4>
         "1", "xyz", "2", "2"
         -- </tkt3493-1.4>
     })
 
-test:do_execsql_test(
+test:do_catchsql_test(
     "tkt3493-1.5",
     [[
         SELECT DISTINCT
@@ -95,7 +95,7 @@ test:do_execsql_test(
         FROM b;
     ]], {
         -- <tkt3493-1.5>
-        "1", "xyz", "2", "2"
+        1, "Type mismatch: can not convert integer(1) to string"
         -- </tkt3493-1.5>
     })
 
@@ -123,23 +123,23 @@ test:do_execsql_test(
         -- </tkt3493-2.1>
     })
 
-test:do_execsql_test(
+test:do_catchsql_test(
     "tkt3493-2.2.1",
     [[
         SELECT a=123 FROM t1 GROUP BY a
     ]], {
         -- <tkt3493-2.2.1>
-        true
+        1, "Type mismatch: can not convert integer(123) to string"
         -- </tkt3493-2.2.1>
     })
 
-test:do_execsql_test(
+test:do_catchsql_test(
     "tkt3493-2.2.2",
     [[
         SELECT a=123 FROM t1
     ]], {
         -- <tkt3493-2.2.2>
-        true
+        1, "Type mismatch: can not convert integer(123) to string"
         -- </tkt3493-2.2.2>
     })
 
@@ -153,93 +153,93 @@ test:do_execsql_test(
         -- </tkt3493-2.2.3>
     })
 
-test:do_execsql_test(
+test:do_catchsql_test(
     "tkt3493-2.2.4",
     [[
         SELECT count(*), a=123 FROM t1
     ]], {
         -- <tkt3493-2.2.4>
-        1, true
+        1, "Type mismatch: can not convert integer(123) to string"
         -- </tkt3493-2.2.4>
     })
 
-test:do_execsql_test(
+test:do_catchsql_test(
     "tkt3493-2.2.5",
     [[
         SELECT count(*), +a=123 FROM t1
     ]], {
         -- <tkt3493-2.2.5>
-        1, true
+        1, "Type mismatch: can not convert integer(123) to string"
         -- </tkt3493-2.2.5>
     })
 
-test:do_execsql_test(
+test:do_catchsql_test(
     "tkt3493-2.3.3",
     [[
         SELECT b='456' FROM t1 GROUP BY a
     ]], {
         -- <tkt3493-2.3.3>
-        true
+        1, "Type mismatch: can not convert string('456') to number"
         -- </tkt3493-2.3.3>
     })
 
-test:do_execsql_test(
+test:do_catchsql_test(
     "tkt3493-2.3.1",
     [[
         SELECT b='456' FROM t1 GROUP BY b
     ]], {
         -- <tkt3493-2.3.1>
-        true
+        1, "Type mismatch: can not convert string('456') to number"
         -- </tkt3493-2.3.1>
     })
 
-test:do_execsql_test(
+test:do_catchsql_test(
     "tkt3493-2.3.2",
     [[
         SELECT b='456' FROM t1
     ]], {
         -- <tkt3493-2.3.2>
-        true
+        1, "Type mismatch: can not convert string('456') to number"
         -- </tkt3493-2.3.2>
     })
 
-test:do_execsql_test(
+test:do_catchsql_test(
     "tkt3493-2.4.1",
     [[
         SELECT typeof(a), a FROM t1 GROUP BY a HAVING a=123
     ]], {
         -- <tkt3493-2.4.1>
-        "string", "123"
+        1, "Type mismatch: can not convert integer(123) to string"
         -- </tkt3493-2.4.1>
     })
 
-test:do_execsql_test(
+test:do_catchsql_test(
     "tkt3493-2.4.2",
     [[
         SELECT typeof(a), a FROM t1 GROUP BY b HAVING a=123
     ]], {
         -- <tkt3493-2.4.2>
-        "string", "123"
+        1, "Type mismatch: can not convert integer(123) to string"
         -- </tkt3493-2.4.2>
     })
 
-test:do_execsql_test(
+test:do_catchsql_test(
     "tkt3493-2.5.1",
     [[
         SELECT typeof(b), b FROM t1 GROUP BY a HAVING b='456'
     ]], {
         -- <tkt3493-2.5.1>
-        "integer", 456
+        1, "Type mismatch: can not convert string('456') to number"
         -- </tkt3493-2.5.1>
     })
 
-test:do_execsql_test(
+test:do_catchsql_test(
     "tkt3493-2.5.2",
     [[
         SELECT typeof(b), b FROM t1 GROUP BY b HAVING b='456'
     ]], {
         -- <tkt3493-2.5.2>
-        "integer", 456
+        1, "Type mismatch: can not convert string('456') to number"
         -- </tkt3493-2.5.2>
     })
 
diff --git a/test/sql-tap/transitive1.test.lua b/test/sql-tap/transitive1.test.lua
index dbc2559fa..9250cdfc3 100755
--- a/test/sql-tap/transitive1.test.lua
+++ b/test/sql-tap/transitive1.test.lua
@@ -63,7 +63,7 @@ test:do_execsql_test(
         INSERT INTO t2 VALUES(2, 20,20,'20');
         INSERT INTO t2 VALUES(3, 3,3,'3');
 
-        SELECT a,b,c FROM t2 WHERE a=b AND c=b AND c=20;
+        SELECT a, b, c FROM t2 WHERE a = b AND c = '20';
     ]], {
         -- <transitive1-200>
         20, 20, "20"
@@ -73,7 +73,7 @@ test:do_execsql_test(
 test:do_execsql_test(
     "transitive1-210",
     [[
-        SELECT a,b,c FROM t2 WHERE a=b AND c=b AND c>='20' ORDER BY +a;
+        SELECT a, b, c FROM t2 WHERE a = b AND c >= '20' ORDER BY +a;
     ]], {
         -- <transitive1-210>
         3, 3, "3", 20, 20, "20"
@@ -83,7 +83,7 @@ test:do_execsql_test(
 test:do_execsql_test(
     "transitive1-220",
     [[
-        SELECT a,b,c FROM t2 WHERE a=b AND c=b AND c<='20' ORDER BY +a;
+        SELECT a, b, c FROM t2 WHERE a = b AND c <= '20' ORDER BY +a;
     ]], {
         -- <transitive1-220>
         20, 20, "20", 100, 100, "100"
@@ -402,7 +402,7 @@ test:do_execsql_test(
     [[
         CREATE TABLE x(i INTEGER PRIMARY KEY, y TEXT);
         INSERT INTO x VALUES(10, '10');
-        SELECT * FROM x WHERE x.y>='1' AND x.y<'2' AND x.i=x.y;
+        SELECT * FROM x WHERE x.y >= '1' AND x.y < '2' AND x.i = CAST(y AS INT);
     ]], {
         -- <transitive1-500>
         10, "10"
@@ -430,23 +430,23 @@ test:do_execsql_test(
     [[
         CREATE TABLE t3(i INTEGER PRIMARY KEY, t TEXT);
         INSERT INTO t3 VALUES(10, '10');
-        SELECT * FROM t3 WHERE i=t AND t = '10 ';
+        SELECT * FROM t3 WHERE i = CAST(t AS NUMBER) AND t = '10 ';
     ]], {
         -- <transitive1-520>
 
         -- </transitive1-520>
     })
 
-test:do_execsql_test(
+test:do_catchsql_test(
     "transitive1-530",
     [[
         CREATE TABLE u1(x TEXT PRIMARY KEY, y INTEGER, z TEXT);
         CREATE INDEX i1 ON u1(x);
         INSERT INTO u1 VALUES('00013', 13, '013');
-        SELECT * FROM u1 WHERE x=y AND y=z AND z='013';
+        SELECT * FROM u1 WHERE x = y AND y = z AND z = '013';
     ]], {
         -- <transitive1-530>
-        "00013",13,"013"
+        1, "Type mismatch: can not convert integer(13) to string"
         -- </transitive1-530>
     })
 
diff --git a/test/sql-tap/uuid.test.lua b/test/sql-tap/uuid.test.lua
index abb5960a7..bbe912c72 100755
--- a/test/sql-tap/uuid.test.lua
+++ b/test/sql-tap/uuid.test.lua
@@ -1128,7 +1128,7 @@ test:do_catchsql_test(
     [[
         SELECT u > true FROM t2;
     ]], {
-        1, "Type mismatch: can not convert uuid('11111111-1111-1111-1111-111111111111') to boolean"
+        1, "Type mismatch: can not convert boolean(TRUE) to uuid"
     })
 
 test:do_execsql_test(
@@ -1192,7 +1192,7 @@ test:do_catchsql_test(
     [[
         SELECT u = true FROM t2;
     ]], {
-        1, "Type mismatch: can not convert uuid('11111111-1111-1111-1111-111111111111') to boolean"
+        1, "Type mismatch: can not convert boolean(TRUE) to uuid"
     })
 
 test:do_execsql_test(
diff --git a/test/sql-tap/where2.test.lua b/test/sql-tap/where2.test.lua
index 58b795cd2..de060c66d 100755
--- a/test/sql-tap/where2.test.lua
+++ b/test/sql-tap/where2.test.lua
@@ -1,7 +1,7 @@
 #!/usr/bin/env tarantool
 local test = require("sqltester")
 local ffi = require("ffi")
-test:plan(74)
+test:plan(64)
 
 ffi.cdef[[
        int dup(int oldfd);
@@ -617,7 +617,7 @@ test:do_test(
     test:do_test(
         "where2-6.7",
         function()
-            test:execsql [[
+            test:catchsql [[
                 CREATE TABLE t2249a(a TEXT PRIMARY KEY, x VARCHAR(100));
                 CREATE TABLE t2249b(b INTEGER PRIMARY KEY);
                 INSERT INTO t2249a(a) VALUES('0123');
@@ -628,7 +628,7 @@ test:do_test(
     -- will attempt to convert to NUMERIC before the comparison.
     -- They will thus compare equal.
     --
-    SELECT b,a FROM t2249b CROSS JOIN t2249a WHERE a=b;
+    SELECT b, a FROM t2249b CROSS JOIN t2249a WHERE CAST(a AS INT) = b;
   ]])
         end, {
             -- <where2-6.7>
@@ -642,7 +642,7 @@ test:do_test(
             return queryplan([[
     -- The + operator doesn't affect RHS.
     --
-    SELECT b,a FROM t2249b CROSS JOIN t2249a WHERE a=+b;
+    SELECT b, a FROM t2249b CROSS JOIN t2249a WHERE CAST(a AS INT) = +b;
   ]])
         end, {
             -- <where2-6.9>
@@ -650,141 +650,6 @@ test:do_test(
             -- </where2-6.9>
         })
 
-    test:do_test(
-        "where2-6.9.2",
-        function()
-            -- The same thing but with the expression flipped around.
-            return queryplan([[
-    SELECT b,a FROM t2249b CROSS JOIN t2249a WHERE +b=a
-  ]])
-        end, {
-            -- <where2-6.9.2>
-            123, "0123","nosort", "T2249B", "*", "T2249A", "*"
-            -- </where2-6.9.2>
-        })
-
-    test:do_test(
-        "where2-6.10",
-        function()
-            return queryplan([[
-    SELECT b,a FROM t2249b CROSS JOIN t2249a WHERE +a=+b;
-  ]])
-        end, {
-            -- <where2-6.10>
-            123, "0123", "nosort", "T2249B", "*", "T2249A", "*"
-            -- </where2-6.10>
-        })
-
-    test:do_test(
-        "where2-6.11",
-        function()
-            -- This will not attempt the OR optimization because of the a=b
-            -- comparison.
-            return queryplan([[
-    SELECT b,a FROM t2249b CROSS JOIN t2249a WHERE a=b OR a='hello';
-  ]])
-        end, {
-            -- <where2-6.11>
-            123, '0123', "nosort", "T2249B", "*", "T2249A", "*"
-            -- </where2-6.11>
-        })
-
-    test:do_test(
-        "where2-6.11.2",
-        function()
-            -- Permutations of the expression terms.
-            return queryplan([[
-    SELECT b,a FROM t2249b CROSS JOIN t2249a WHERE b=a OR a='hello';
-  ]])
-        end, {
-            -- <where2-6.11.2>
-            123, '0123', "nosort", "T2249B", "*", "T2249A", "*"
-            -- </where2-6.11.2>
-        })
-
-    test:do_test(
-        "where2-6.11.3",
-        function()
-            -- Permutations of the expression terms.
-            return queryplan([[
-    SELECT b,a FROM t2249b CROSS JOIN t2249a WHERE 'hello'=a OR b=a;
-  ]])
-        end, {
-            -- <where2-6.11.3>
-            123, '0123', "nosort", "T2249B", "*", "T2249A", "*"
-            -- </where2-6.11.3>
-        })
-
-    test:do_test(
-        "where2-6.11.4",
-        function()
-            -- Permutations of the expression terms.
-            return queryplan([[
-    SELECT b,a FROM t2249b CROSS JOIN t2249a WHERE a='hello' OR b=a;
-  ]])
-        end, {
-            -- <where2-6.11.4>
-            123, '0123', "nosort", "T2249B", "*", "T2249A", "*"
-            -- </where2-6.11.4>
-        })
-
-    -- These tests are not run if subquery support is not included in the
-    -- build. This is because these tests test the "a = 1 OR a = 2" to
-    -- "a IN (1, 2)" optimisation transformation, which is not enabled if
-    -- subqueries and the IN operator is not available.
-    --
-    test:do_test(
-        "where2-6.12",
-        function()
-            return queryplan([[
-      SELECT b,a FROM t2249b CROSS JOIN t2249a WHERE a=+b OR a='hello';
-    ]])
-        end, {
-            -- <where2-6.12>
-            123, "0123", "nosort", "T2249B", "*", "T2249A", "*"
-            -- </where2-6.12>
-        })
-
-    test:do_test(
-        "where2-6.12.2",
-        function()
-            return queryplan([[
-      SELECT b,a FROM t2249b CROSS JOIN t2249a WHERE a='hello' OR +b=a;
-    ]])
-        end, {
-            -- <where2-6.12.2>
-            123, "0123", "nosort", "T2249B", "*", "T2249A", "*"
-            -- </where2-6.12.2>
-        })
-
-    test:do_test(
-        "where2-6.12.3",
-        function()
-            return queryplan([[
-      SELECT b,a FROM t2249b CROSS JOIN t2249a WHERE +b=a OR a='hello';
-    ]])
-        end, {
-            -- <where2-6.12.3>
-            123, "0123", "nosort", "T2249B", "*", "T2249A", "*"
-            -- </where2-6.12.3>
-        })
-
-    test:do_test(
-        "where2-6.13",
-        function()
-            -- The addition of +a on the second term disabled the OR optimization.
-            -- But we should still get the same empty-set result as in where2-6.9.
-            return queryplan([[
-      SELECT b,a FROM t2249b CROSS JOIN t2249a WHERE a=+b OR +a='hello';
-    ]])
-        end, {
-            -- <where2-6.13>
-            123, "0123", "nosort", "T2249B", "*", "T2249A", "*"
-            -- </where2-6.13>
-        })
-
-
-
     -- Variations on the order of terms in a WHERE clause in order
     -- to make sure the OR optimizer can recognize them all.
     test:do_test(
diff --git a/test/sql-tap/where5.test.lua b/test/sql-tap/where5.test.lua
index 3d3c3a0ca..400597257 100755
--- a/test/sql-tap/where5.test.lua
+++ b/test/sql-tap/where5.test.lua
@@ -34,7 +34,7 @@ test:do_test("where5-1.0", function()
         INSERT INTO t3 SELECT CAST(x AS INTEGER) FROM t1;
     ]]
     return test:execsql [[
-        SELECT * FROM t1 WHERE x<0
+        SELECT * FROM t1 WHERE CAST(x AS INTEGER) < 0
     ]]
 end, {
     -- <where5-1.0>
@@ -43,7 +43,7 @@ end, {
 })
 
 test:do_execsql_test("where5-1.1", [[
-    SELECT * FROM t1 WHERE x<=0
+    SELECT * FROM t1 WHERE CAST(x AS INTEGER) <= 0
 ]], {
     -- <where5-1.1>
     '-1', '0'
@@ -51,7 +51,7 @@ test:do_execsql_test("where5-1.1", [[
 })
 
 test:do_execsql_test("where5-1.2", [[
-    SELECT * FROM t1 WHERE x=0
+    SELECT * FROM t1 WHERE CAST(x AS INTEGER) = 0
 ]], {
     -- <where5-1.2>
     '0'
@@ -59,7 +59,7 @@ test:do_execsql_test("where5-1.2", [[
 })
 
 test:do_execsql_test("where5-1.3", [[
-    SELECT * FROM t1 WHERE x>=0
+    SELECT * FROM t1 WHERE CAST(x AS INTEGER) >= 0
 ]], {
     -- <where5-1.3>
     '0', '1'
@@ -67,7 +67,7 @@ test:do_execsql_test("where5-1.3", [[
 })
 
 test:do_execsql_test("where5-1.4", [[
-    SELECT * FROM t1 WHERE x>0
+    SELECT * FROM t1 WHERE CAST(x AS INTEGER) > 0
 ]], {
     -- <where5-1.4>
     '1'
@@ -75,7 +75,7 @@ test:do_execsql_test("where5-1.4", [[
 })
 
 test:do_execsql_test("where5-1.5", [[
-    SELECT * FROM t1 WHERE x<>0
+    SELECT * FROM t1 WHERE CAST(x AS INTEGER) <> 0
 ]], {
     -- <where5-1.5>
     '-1', '1'
diff --git a/test/sql/boolean.result b/test/sql/boolean.result
index 81acdb691..6e7ad179f 100644
--- a/test/sql/boolean.result
+++ b/test/sql/boolean.result
@@ -138,12 +138,12 @@ INSERT INTO ts(s) VALUES ('abc'), (12.5);
 SELECT s FROM ts WHERE s = true;
  | ---
  | - null
- | - 'Type mismatch: can not convert string(''abc'') to boolean'
+ | - 'Type mismatch: can not convert boolean(TRUE) to string'
  | ...
 SELECT s FROM ts WHERE s < true;
  | ---
  | - null
- | - 'Type mismatch: can not convert string(''abc'') to boolean'
+ | - 'Type mismatch: can not convert boolean(TRUE) to string'
  | ...
 SELECT s FROM ts WHERE s IN (true, 1, 'abcd');
  | ---
@@ -3394,22 +3394,22 @@ SELECT false < 2;
 SELECT 2 > true;
  | ---
  | - null
- | - 'Type mismatch: can not convert integer(2) to boolean'
+ | - 'Type mismatch: can not convert boolean(TRUE) to number'
  | ...
 SELECT 2 > false;
  | ---
  | - null
- | - 'Type mismatch: can not convert integer(2) to boolean'
+ | - 'Type mismatch: can not convert boolean(FALSE) to number'
  | ...
 SELECT 2 < true;
  | ---
  | - null
- | - 'Type mismatch: can not convert integer(2) to boolean'
+ | - 'Type mismatch: can not convert boolean(TRUE) to number'
  | ...
 SELECT 2 < false;
  | ---
  | - null
- | - 'Type mismatch: can not convert integer(2) to boolean'
+ | - 'Type mismatch: can not convert boolean(FALSE) to number'
  | ...
 
 SELECT a1, a1 > 2 FROM t6
@@ -3425,12 +3425,12 @@ SELECT a1, a1 < 2 FROM t6
 SELECT a1, 2 > a1 FROM t6
  | ---
  | - null
- | - 'Type mismatch: can not convert integer(2) to boolean'
+ | - 'Type mismatch: can not convert boolean(FALSE) to number'
  | ...
 SELECT a1, 2 < a1 FROM t6
  | ---
  | - null
- | - 'Type mismatch: can not convert integer(2) to boolean'
+ | - 'Type mismatch: can not convert boolean(FALSE) to number'
  | ...
 SELECT a2, a2 > 2 FROM t6
  | ---
@@ -3445,12 +3445,12 @@ SELECT a2, a2 < 2 FROM t6
 SELECT a2, 2 > a2 FROM t6
  | ---
  | - null
- | - 'Type mismatch: can not convert integer(2) to boolean'
+ | - 'Type mismatch: can not convert boolean(TRUE) to number'
  | ...
 SELECT a2, 2 < a2 FROM t6
  | ---
  | - null
- | - 'Type mismatch: can not convert integer(2) to boolean'
+ | - 'Type mismatch: can not convert boolean(TRUE) to number'
  | ...
 
 SELECT b, true > b FROM t7;
@@ -3476,22 +3476,22 @@ SELECT b, false < b FROM t7;
 SELECT b, b > true FROM t7;
  | ---
  | - null
- | - 'Type mismatch: can not convert integer(123) to boolean'
+ | - 'Type mismatch: can not convert boolean(TRUE) to number'
  | ...
 SELECT b, b > false FROM t7;
  | ---
  | - null
- | - 'Type mismatch: can not convert integer(123) to boolean'
+ | - 'Type mismatch: can not convert boolean(FALSE) to number'
  | ...
 SELECT b, b < true FROM t7;
  | ---
  | - null
- | - 'Type mismatch: can not convert integer(123) to boolean'
+ | - 'Type mismatch: can not convert boolean(TRUE) to number'
  | ...
 SELECT b, b < false FROM t7;
  | ---
  | - null
- | - 'Type mismatch: can not convert integer(123) to boolean'
+ | - 'Type mismatch: can not convert boolean(FALSE) to number'
  | ...
 
 SELECT a1, b, a1 > b FROM t6, t7;
@@ -3507,12 +3507,12 @@ SELECT a1, b, a1 < b FROM t6, t7;
 SELECT a1, b, b > a1 FROM t6, t7;
  | ---
  | - null
- | - 'Type mismatch: can not convert integer(123) to boolean'
+ | - 'Type mismatch: can not convert boolean(FALSE) to number'
  | ...
 SELECT a1, b, b < a1 FROM t6, t7;
  | ---
  | - null
- | - 'Type mismatch: can not convert integer(123) to boolean'
+ | - 'Type mismatch: can not convert boolean(FALSE) to number'
  | ...
 SELECT a2, b, a2 > b FROM t6, t7;
  | ---
@@ -3527,12 +3527,12 @@ SELECT a2, b, a2 < b FROM t6, t7;
 SELECT a2, b, b > a2 FROM t6, t7;
  | ---
  | - null
- | - 'Type mismatch: can not convert integer(123) to boolean'
+ | - 'Type mismatch: can not convert boolean(TRUE) to number'
  | ...
 SELECT a2, b, b < a2 FROM t6, t7;
  | ---
  | - null
- | - 'Type mismatch: can not convert integer(123) to boolean'
+ | - 'Type mismatch: can not convert boolean(TRUE) to number'
  | ...
 
 SELECT true >= 2;
@@ -3558,22 +3558,22 @@ SELECT false <= 2;
 SELECT 2 >= true;
  | ---
  | - null
- | - 'Type mismatch: can not convert integer(2) to boolean'
+ | - 'Type mismatch: can not convert boolean(TRUE) to number'
  | ...
 SELECT 2 >= false;
  | ---
  | - null
- | - 'Type mismatch: can not convert integer(2) to boolean'
+ | - 'Type mismatch: can not convert boolean(FALSE) to number'
  | ...
 SELECT 2 <= true;
  | ---
  | - null
- | - 'Type mismatch: can not convert integer(2) to boolean'
+ | - 'Type mismatch: can not convert boolean(TRUE) to number'
  | ...
 SELECT 2 <= false;
  | ---
  | - null
- | - 'Type mismatch: can not convert integer(2) to boolean'
+ | - 'Type mismatch: can not convert boolean(FALSE) to number'
  | ...
 
 SELECT a1, a1 >= 2 FROM t6
@@ -3589,12 +3589,12 @@ SELECT a1, a1 <= 2 FROM t6
 SELECT a1, 2 >= a1 FROM t6
  | ---
  | - null
- | - 'Type mismatch: can not convert integer(2) to boolean'
+ | - 'Type mismatch: can not convert boolean(FALSE) to number'
  | ...
 SELECT a1, 2 <= a1 FROM t6
  | ---
  | - null
- | - 'Type mismatch: can not convert integer(2) to boolean'
+ | - 'Type mismatch: can not convert boolean(FALSE) to number'
  | ...
 SELECT a2, a2 >= 2 FROM t6
  | ---
@@ -3609,12 +3609,12 @@ SELECT a2, a2 <= 2 FROM t6
 SELECT a2, 2 >= a2 FROM t6
  | ---
  | - null
- | - 'Type mismatch: can not convert integer(2) to boolean'
+ | - 'Type mismatch: can not convert boolean(TRUE) to number'
  | ...
 SELECT a2, 2 <= a2 FROM t6
  | ---
  | - null
- | - 'Type mismatch: can not convert integer(2) to boolean'
+ | - 'Type mismatch: can not convert boolean(TRUE) to number'
  | ...
 
 SELECT b, true >= b FROM t7;
@@ -3640,22 +3640,22 @@ SELECT b, false <= b FROM t7;
 SELECT b, b >= true FROM t7;
  | ---
  | - null
- | - 'Type mismatch: can not convert integer(123) to boolean'
+ | - 'Type mismatch: can not convert boolean(TRUE) to number'
  | ...
 SELECT b, b >= false FROM t7;
  | ---
  | - null
- | - 'Type mismatch: can not convert integer(123) to boolean'
+ | - 'Type mismatch: can not convert boolean(FALSE) to number'
  | ...
 SELECT b, b <= true FROM t7;
  | ---
  | - null
- | - 'Type mismatch: can not convert integer(123) to boolean'
+ | - 'Type mismatch: can not convert boolean(TRUE) to number'
  | ...
 SELECT b, b <= false FROM t7;
  | ---
  | - null
- | - 'Type mismatch: can not convert integer(123) to boolean'
+ | - 'Type mismatch: can not convert boolean(FALSE) to number'
  | ...
 
 SELECT a1, b, a1 >= b FROM t6, t7;
@@ -3671,12 +3671,12 @@ SELECT a1, b, a1 <= b FROM t6, t7;
 SELECT a1, b, b >= a1 FROM t6, t7;
  | ---
  | - null
- | - 'Type mismatch: can not convert integer(123) to boolean'
+ | - 'Type mismatch: can not convert boolean(FALSE) to number'
  | ...
 SELECT a1, b, b <= a1 FROM t6, t7;
  | ---
  | - null
- | - 'Type mismatch: can not convert integer(123) to boolean'
+ | - 'Type mismatch: can not convert boolean(FALSE) to number'
  | ...
 SELECT a2, b, a2 >= b FROM t6, t7;
  | ---
@@ -3691,12 +3691,12 @@ SELECT a2, b, a2 <= b FROM t6, t7;
 SELECT a2, b, b >= a2 FROM t6, t7;
  | ---
  | - null
- | - 'Type mismatch: can not convert integer(123) to boolean'
+ | - 'Type mismatch: can not convert boolean(TRUE) to number'
  | ...
 SELECT a2, b, b <= a2 FROM t6, t7;
  | ---
  | - null
- | - 'Type mismatch: can not convert integer(123) to boolean'
+ | - 'Type mismatch: can not convert boolean(TRUE) to number'
  | ...
 
 SELECT true == 2;
@@ -3722,22 +3722,22 @@ SELECT false != 2;
 SELECT 2 == true;
  | ---
  | - null
- | - 'Type mismatch: can not convert integer(2) to boolean'
+ | - 'Type mismatch: can not convert boolean(TRUE) to number'
  | ...
 SELECT 2 == false;
  | ---
  | - null
- | - 'Type mismatch: can not convert integer(2) to boolean'
+ | - 'Type mismatch: can not convert boolean(FALSE) to number'
  | ...
 SELECT 2 != true;
  | ---
  | - null
- | - 'Type mismatch: can not convert integer(2) to boolean'
+ | - 'Type mismatch: can not convert boolean(TRUE) to number'
  | ...
 SELECT 2 != false;
  | ---
  | - null
- | - 'Type mismatch: can not convert integer(2) to boolean'
+ | - 'Type mismatch: can not convert boolean(FALSE) to number'
  | ...
 
 SELECT a1, a1 == 2 FROM t6
@@ -3753,12 +3753,12 @@ SELECT a1, a1 != 2 FROM t6
 SELECT a1, 2 == a1 FROM t6
  | ---
  | - null
- | - 'Type mismatch: can not convert integer(2) to boolean'
+ | - 'Type mismatch: can not convert boolean(FALSE) to number'
  | ...
 SELECT a1, 2 != a1 FROM t6
  | ---
  | - null
- | - 'Type mismatch: can not convert integer(2) to boolean'
+ | - 'Type mismatch: can not convert boolean(FALSE) to number'
  | ...
 SELECT a2, a2 == 2 FROM t6
  | ---
@@ -3773,12 +3773,12 @@ SELECT a2, a2 != 2 FROM t6
 SELECT a2, 2 == a2 FROM t6
  | ---
  | - null
- | - 'Type mismatch: can not convert integer(2) to boolean'
+ | - 'Type mismatch: can not convert boolean(TRUE) to number'
  | ...
 SELECT a2, 2 != a2 FROM t6
  | ---
  | - null
- | - 'Type mismatch: can not convert integer(2) to boolean'
+ | - 'Type mismatch: can not convert boolean(TRUE) to number'
  | ...
 
 SELECT b, true == b FROM t7;
@@ -3804,22 +3804,22 @@ SELECT b, false != b FROM t7;
 SELECT b, b == true FROM t7;
  | ---
  | - null
- | - 'Type mismatch: can not convert integer(123) to boolean'
+ | - 'Type mismatch: can not convert boolean(TRUE) to number'
  | ...
 SELECT b, b == false FROM t7;
  | ---
  | - null
- | - 'Type mismatch: can not convert integer(123) to boolean'
+ | - 'Type mismatch: can not convert boolean(FALSE) to number'
  | ...
 SELECT b, b != true FROM t7;
  | ---
  | - null
- | - 'Type mismatch: can not convert integer(123) to boolean'
+ | - 'Type mismatch: can not convert boolean(TRUE) to number'
  | ...
 SELECT b, b != false FROM t7;
  | ---
  | - null
- | - 'Type mismatch: can not convert integer(123) to boolean'
+ | - 'Type mismatch: can not convert boolean(FALSE) to number'
  | ...
 
 SELECT a1, b, a1 == b FROM t6, t7;
@@ -3835,12 +3835,12 @@ SELECT a1, b, a1 != b FROM t6, t7;
 SELECT a1, b, b == a1 FROM t6, t7;
  | ---
  | - null
- | - 'Type mismatch: can not convert integer(123) to boolean'
+ | - 'Type mismatch: can not convert boolean(FALSE) to number'
  | ...
 SELECT a1, b, b != a1 FROM t6, t7;
  | ---
  | - null
- | - 'Type mismatch: can not convert integer(123) to boolean'
+ | - 'Type mismatch: can not convert boolean(FALSE) to number'
  | ...
 SELECT a2, b, a2 == b FROM t6, t7;
  | ---
@@ -3855,12 +3855,12 @@ SELECT a2, b, a2 != b FROM t6, t7;
 SELECT a2, b, b == a2 FROM t6, t7;
  | ---
  | - null
- | - 'Type mismatch: can not convert integer(123) to boolean'
+ | - 'Type mismatch: can not convert boolean(TRUE) to number'
  | ...
 SELECT a2, b, b != a2 FROM t6, t7;
  | ---
  | - null
- | - 'Type mismatch: can not convert integer(123) to boolean'
+ | - 'Type mismatch: can not convert boolean(TRUE) to number'
  | ...
 
 SELECT true IN (0, 1, 2, 3);
@@ -4539,22 +4539,22 @@ SELECT false < 2.3;
 SELECT 2.3 > true;
  | ---
  | - null
- | - 'Type mismatch: can not convert double(2.3) to boolean'
+ | - 'Type mismatch: can not convert boolean(TRUE) to number'
  | ...
 SELECT 2.3 > false;
  | ---
  | - null
- | - 'Type mismatch: can not convert double(2.3) to boolean'
+ | - 'Type mismatch: can not convert boolean(FALSE) to number'
  | ...
 SELECT 2.3 < true;
  | ---
  | - null
- | - 'Type mismatch: can not convert double(2.3) to boolean'
+ | - 'Type mismatch: can not convert boolean(TRUE) to number'
  | ...
 SELECT 2.3 < false;
  | ---
  | - null
- | - 'Type mismatch: can not convert double(2.3) to boolean'
+ | - 'Type mismatch: can not convert boolean(FALSE) to number'
  | ...
 
 SELECT a1, a1 > 2.3 FROM t6
@@ -4570,12 +4570,12 @@ SELECT a1, a1 < 2.3 FROM t6
 SELECT a1, 2.3 > a1 FROM t6
  | ---
  | - null
- | - 'Type mismatch: can not convert double(2.3) to boolean'
+ | - 'Type mismatch: can not convert boolean(FALSE) to number'
  | ...
 SELECT a1, 2.3 < a1 FROM t6
  | ---
  | - null
- | - 'Type mismatch: can not convert double(2.3) to boolean'
+ | - 'Type mismatch: can not convert boolean(FALSE) to number'
  | ...
 SELECT a2, a2 > 2.3 FROM t6
  | ---
@@ -4590,12 +4590,12 @@ SELECT a2, a2 < 2.3 FROM t6
 SELECT a2, 2.3 > a2 FROM t6
  | ---
  | - null
- | - 'Type mismatch: can not convert double(2.3) to boolean'
+ | - 'Type mismatch: can not convert boolean(TRUE) to number'
  | ...
 SELECT a2, 2.3 < a2 FROM t6
  | ---
  | - null
- | - 'Type mismatch: can not convert double(2.3) to boolean'
+ | - 'Type mismatch: can not convert boolean(TRUE) to number'
  | ...
 
 SELECT c, true > c FROM t8;
@@ -4621,22 +4621,22 @@ SELECT c, false < c FROM t8;
 SELECT c, c > true FROM t8;
  | ---
  | - null
- | - 'Type mismatch: can not convert double(4.56) to boolean'
+ | - 'Type mismatch: can not convert boolean(TRUE) to number'
  | ...
 SELECT c, c > false FROM t8;
  | ---
  | - null
- | - 'Type mismatch: can not convert double(4.56) to boolean'
+ | - 'Type mismatch: can not convert boolean(FALSE) to number'
  | ...
 SELECT c, c < true FROM t8;
  | ---
  | - null
- | - 'Type mismatch: can not convert double(4.56) to boolean'
+ | - 'Type mismatch: can not convert boolean(TRUE) to number'
  | ...
 SELECT c, c < false FROM t8;
  | ---
  | - null
- | - 'Type mismatch: can not convert double(4.56) to boolean'
+ | - 'Type mismatch: can not convert boolean(FALSE) to number'
  | ...
 
 SELECT a1, c, a1 > c FROM t6, t8;
@@ -4652,12 +4652,12 @@ SELECT a1, c, a1 < c FROM t6, t8;
 SELECT a1, c, c > a1 FROM t6, t8;
  | ---
  | - null
- | - 'Type mismatch: can not convert double(4.56) to boolean'
+ | - 'Type mismatch: can not convert boolean(FALSE) to number'
  | ...
 SELECT a1, c, c < a1 FROM t6, t8;
  | ---
  | - null
- | - 'Type mismatch: can not convert double(4.56) to boolean'
+ | - 'Type mismatch: can not convert boolean(FALSE) to number'
  | ...
 SELECT a2, c, a2 > c FROM t6, t8;
  | ---
@@ -4672,12 +4672,12 @@ SELECT a2, c, a2 < c FROM t6, t8;
 SELECT a2, c, c > a2 FROM t6, t8;
  | ---
  | - null
- | - 'Type mismatch: can not convert double(4.56) to boolean'
+ | - 'Type mismatch: can not convert boolean(TRUE) to number'
  | ...
 SELECT a2, c, c < a2 FROM t6, t8;
  | ---
  | - null
- | - 'Type mismatch: can not convert double(4.56) to boolean'
+ | - 'Type mismatch: can not convert boolean(TRUE) to number'
  | ...
 
 SELECT true >= 2.3;
@@ -4703,22 +4703,22 @@ SELECT false <= 2.3;
 SELECT 2.3 >= true;
  | ---
  | - null
- | - 'Type mismatch: can not convert double(2.3) to boolean'
+ | - 'Type mismatch: can not convert boolean(TRUE) to number'
  | ...
 SELECT 2.3 >= false;
  | ---
  | - null
- | - 'Type mismatch: can not convert double(2.3) to boolean'
+ | - 'Type mismatch: can not convert boolean(FALSE) to number'
  | ...
 SELECT 2.3 <= true;
  | ---
  | - null
- | - 'Type mismatch: can not convert double(2.3) to boolean'
+ | - 'Type mismatch: can not convert boolean(TRUE) to number'
  | ...
 SELECT 2.3 <= false;
  | ---
  | - null
- | - 'Type mismatch: can not convert double(2.3) to boolean'
+ | - 'Type mismatch: can not convert boolean(FALSE) to number'
  | ...
 
 SELECT a1, a1 >= 2.3 FROM t6
@@ -4734,12 +4734,12 @@ SELECT a1, a1 <= 2.3 FROM t6
 SELECT a1, 2.3 >= a1 FROM t6
  | ---
  | - null
- | - 'Type mismatch: can not convert double(2.3) to boolean'
+ | - 'Type mismatch: can not convert boolean(FALSE) to number'
  | ...
 SELECT a1, 2.3 <= a1 FROM t6
  | ---
  | - null
- | - 'Type mismatch: can not convert double(2.3) to boolean'
+ | - 'Type mismatch: can not convert boolean(FALSE) to number'
  | ...
 SELECT a2, a2 >= 2.3 FROM t6
  | ---
@@ -4754,12 +4754,12 @@ SELECT a2, a2 <= 2.3 FROM t6
 SELECT a2, 2.3 >= a2 FROM t6
  | ---
  | - null
- | - 'Type mismatch: can not convert double(2.3) to boolean'
+ | - 'Type mismatch: can not convert boolean(TRUE) to number'
  | ...
 SELECT a2, 2.3 <= a2 FROM t6
  | ---
  | - null
- | - 'Type mismatch: can not convert double(2.3) to boolean'
+ | - 'Type mismatch: can not convert boolean(TRUE) to number'
  | ...
 
 SELECT c, true >= c FROM t8;
@@ -4785,22 +4785,22 @@ SELECT c, false <= c FROM t8;
 SELECT c, c >= true FROM t8;
  | ---
  | - null
- | - 'Type mismatch: can not convert double(4.56) to boolean'
+ | - 'Type mismatch: can not convert boolean(TRUE) to number'
  | ...
 SELECT c, c >= false FROM t8;
  | ---
  | - null
- | - 'Type mismatch: can not convert double(4.56) to boolean'
+ | - 'Type mismatch: can not convert boolean(FALSE) to number'
  | ...
 SELECT c, c <= true FROM t8;
  | ---
  | - null
- | - 'Type mismatch: can not convert double(4.56) to boolean'
+ | - 'Type mismatch: can not convert boolean(TRUE) to number'
  | ...
 SELECT c, c <= false FROM t8;
  | ---
  | - null
- | - 'Type mismatch: can not convert double(4.56) to boolean'
+ | - 'Type mismatch: can not convert boolean(FALSE) to number'
  | ...
 
 SELECT a1, c, a1 >= c FROM t6, t8;
@@ -4816,12 +4816,12 @@ SELECT a1, c, a1 <= c FROM t6, t8;
 SELECT a1, c, c >= a1 FROM t6, t8;
  | ---
  | - null
- | - 'Type mismatch: can not convert double(4.56) to boolean'
+ | - 'Type mismatch: can not convert boolean(FALSE) to number'
  | ...
 SELECT a1, c, c <= a1 FROM t6, t8;
  | ---
  | - null
- | - 'Type mismatch: can not convert double(4.56) to boolean'
+ | - 'Type mismatch: can not convert boolean(FALSE) to number'
  | ...
 SELECT a2, c, a2 >= c FROM t6, t8;
  | ---
@@ -4836,12 +4836,12 @@ SELECT a2, c, a2 <= c FROM t6, t8;
 SELECT a2, c, c >= a2 FROM t6, t8;
  | ---
  | - null
- | - 'Type mismatch: can not convert double(4.56) to boolean'
+ | - 'Type mismatch: can not convert boolean(TRUE) to number'
  | ...
 SELECT a2, c, c <= a2 FROM t6, t8;
  | ---
  | - null
- | - 'Type mismatch: can not convert double(4.56) to boolean'
+ | - 'Type mismatch: can not convert boolean(TRUE) to number'
  | ...
 
 SELECT true == 2.3;
@@ -4867,22 +4867,22 @@ SELECT false != 2.3;
 SELECT 2.3 == true;
  | ---
  | - null
- | - 'Type mismatch: can not convert double(2.3) to boolean'
+ | - 'Type mismatch: can not convert boolean(TRUE) to number'
  | ...
 SELECT 2.3 == false;
  | ---
  | - null
- | - 'Type mismatch: can not convert double(2.3) to boolean'
+ | - 'Type mismatch: can not convert boolean(FALSE) to number'
  | ...
 SELECT 2.3 != true;
  | ---
  | - null
- | - 'Type mismatch: can not convert double(2.3) to boolean'
+ | - 'Type mismatch: can not convert boolean(TRUE) to number'
  | ...
 SELECT 2.3 != false;
  | ---
  | - null
- | - 'Type mismatch: can not convert double(2.3) to boolean'
+ | - 'Type mismatch: can not convert boolean(FALSE) to number'
  | ...
 
 SELECT a1, a1 == 2.3 FROM t6
@@ -4898,12 +4898,12 @@ SELECT a1, a1 != 2.3 FROM t6
 SELECT a1, 2.3 == a1 FROM t6
  | ---
  | - null
- | - 'Type mismatch: can not convert double(2.3) to boolean'
+ | - 'Type mismatch: can not convert boolean(FALSE) to number'
  | ...
 SELECT a1, 2.3 != a1 FROM t6
  | ---
  | - null
- | - 'Type mismatch: can not convert double(2.3) to boolean'
+ | - 'Type mismatch: can not convert boolean(FALSE) to number'
  | ...
 SELECT a2, a2 == 2.3 FROM t6
  | ---
@@ -4918,12 +4918,12 @@ SELECT a2, a2 != 2.3 FROM t6
 SELECT a2, 2.3 == a2 FROM t6
  | ---
  | - null
- | - 'Type mismatch: can not convert double(2.3) to boolean'
+ | - 'Type mismatch: can not convert boolean(TRUE) to number'
  | ...
 SELECT a2, 2.3 != a2 FROM t6
  | ---
  | - null
- | - 'Type mismatch: can not convert double(2.3) to boolean'
+ | - 'Type mismatch: can not convert boolean(TRUE) to number'
  | ...
 
 SELECT c, true == c FROM t8;
@@ -4949,22 +4949,22 @@ SELECT c, false != c FROM t8;
 SELECT c, c == true FROM t8;
  | ---
  | - null
- | - 'Type mismatch: can not convert double(4.56) to boolean'
+ | - 'Type mismatch: can not convert boolean(TRUE) to number'
  | ...
 SELECT c, c == false FROM t8;
  | ---
  | - null
- | - 'Type mismatch: can not convert double(4.56) to boolean'
+ | - 'Type mismatch: can not convert boolean(FALSE) to number'
  | ...
 SELECT c, c != true FROM t8;
  | ---
  | - null
- | - 'Type mismatch: can not convert double(4.56) to boolean'
+ | - 'Type mismatch: can not convert boolean(TRUE) to number'
  | ...
 SELECT c, c != false FROM t8;
  | ---
  | - null
- | - 'Type mismatch: can not convert double(4.56) to boolean'
+ | - 'Type mismatch: can not convert boolean(FALSE) to number'
  | ...
 
 SELECT a1, c, a1 == c FROM t6, t8;
@@ -4980,12 +4980,12 @@ SELECT a1, c, a1 != c FROM t6, t8;
 SELECT a1, c, c == a1 FROM t6, t8;
  | ---
  | - null
- | - 'Type mismatch: can not convert double(4.56) to boolean'
+ | - 'Type mismatch: can not convert boolean(FALSE) to number'
  | ...
 SELECT a1, c, c != a1 FROM t6, t8;
  | ---
  | - null
- | - 'Type mismatch: can not convert double(4.56) to boolean'
+ | - 'Type mismatch: can not convert boolean(FALSE) to number'
  | ...
 SELECT a2, c, a2 == c FROM t6, t8;
  | ---
@@ -5000,12 +5000,12 @@ SELECT a2, c, a2 != c FROM t6, t8;
 SELECT a2, c, c == a2 FROM t6, t8;
  | ---
  | - null
- | - 'Type mismatch: can not convert double(4.56) to boolean'
+ | - 'Type mismatch: can not convert boolean(TRUE) to number'
  | ...
 SELECT a2, c, c != a2 FROM t6, t8;
  | ---
  | - null
- | - 'Type mismatch: can not convert double(4.56) to boolean'
+ | - 'Type mismatch: can not convert boolean(TRUE) to number'
  | ...
 
 SELECT true IN (0.1, 1.2, 2.3, 3.4);
@@ -5295,22 +5295,22 @@ SELECT false < 'abc';
 SELECT 'abc' > true;
  | ---
  | - null
- | - 'Type mismatch: can not convert string(''abc'') to boolean'
+ | - 'Type mismatch: can not convert boolean(TRUE) to string'
  | ...
 SELECT 'abc' > false;
  | ---
  | - null
- | - 'Type mismatch: can not convert string(''abc'') to boolean'
+ | - 'Type mismatch: can not convert boolean(FALSE) to string'
  | ...
 SELECT 'abc' < true;
  | ---
  | - null
- | - 'Type mismatch: can not convert string(''abc'') to boolean'
+ | - 'Type mismatch: can not convert boolean(TRUE) to string'
  | ...
 SELECT 'abc' < false;
  | ---
  | - null
- | - 'Type mismatch: can not convert string(''abc'') to boolean'
+ | - 'Type mismatch: can not convert boolean(FALSE) to string'
  | ...
 
 SELECT d, true > d FROM t9;
@@ -5336,22 +5336,22 @@ SELECT d, false < d FROM t9;
 SELECT d, d > true FROM t9;
  | ---
  | - null
- | - 'Type mismatch: can not convert string(''AsdF'') to boolean'
+ | - 'Type mismatch: can not convert boolean(TRUE) to string'
  | ...
 SELECT d, d > false FROM t9;
  | ---
  | - null
- | - 'Type mismatch: can not convert string(''AsdF'') to boolean'
+ | - 'Type mismatch: can not convert boolean(FALSE) to string'
  | ...
 SELECT d, d < true FROM t9;
  | ---
  | - null
- | - 'Type mismatch: can not convert string(''AsdF'') to boolean'
+ | - 'Type mismatch: can not convert boolean(TRUE) to string'
  | ...
 SELECT d, d < false FROM t9;
  | ---
  | - null
- | - 'Type mismatch: can not convert string(''AsdF'') to boolean'
+ | - 'Type mismatch: can not convert boolean(FALSE) to string'
  | ...
 
 SELECT a1, d, a1 > d FROM t6, t9;
@@ -5367,12 +5367,12 @@ SELECT a1, d, a1 < d FROM t6, t9;
 SELECT a1, d, d > a1 FROM t6, t9;
  | ---
  | - null
- | - 'Type mismatch: can not convert string(''AsdF'') to boolean'
+ | - 'Type mismatch: can not convert boolean(FALSE) to string'
  | ...
 SELECT a1, d, d < a1 FROM t6, t9;
  | ---
  | - null
- | - 'Type mismatch: can not convert string(''AsdF'') to boolean'
+ | - 'Type mismatch: can not convert boolean(FALSE) to string'
  | ...
 SELECT a2, d, a2 > d FROM t6, t9;
  | ---
@@ -5387,12 +5387,12 @@ SELECT a2, d, a2 < d FROM t6, t9;
 SELECT a2, d, d > a2 FROM t6, t9;
  | ---
  | - null
- | - 'Type mismatch: can not convert string(''AsdF'') to boolean'
+ | - 'Type mismatch: can not convert boolean(TRUE) to string'
  | ...
 SELECT a2, d, d < a2 FROM t6, t9;
  | ---
  | - null
- | - 'Type mismatch: can not convert string(''AsdF'') to boolean'
+ | - 'Type mismatch: can not convert boolean(TRUE) to string'
  | ...
 
 SELECT true || 'abc';
diff --git a/test/sql/types.result b/test/sql/types.result
index 00c60cca9..07d5b46e4 100644
--- a/test/sql/types.result
+++ b/test/sql/types.result
@@ -605,11 +605,8 @@ box.execute("SELECT 18446744073709551615.0 > 18446744073709551615")
 ...
 box.execute("SELECT 18446744073709551615 IN ('18446744073709551615', 18446744073709551615.0)")
 ---
-- metadata:
-  - name: COLUMN_1
-    type: boolean
-  rows:
-  - [true]
+- null
+- 'Type mismatch: can not convert integer(18446744073709551615) to string'
 ...
 box.execute("SELECT 1 LIMIT 18446744073709551615;")
 ---

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

* Re: [Tarantool-patches] [PATCH v1 3/7] sql: rework OP_Seek* opcodes
  2021-08-04 22:25   ` Vladislav Shpilevoy via Tarantool-patches
@ 2021-08-05 23:40     ` Mergen Imeev via Tarantool-patches
  0 siblings, 0 replies; 25+ messages in thread
From: Mergen Imeev via Tarantool-patches @ 2021-08-05 23:40 UTC (permalink / raw)
  To: Vladislav Shpilevoy; +Cc: tarantool-patches

Thank you for the review! My answers and new patch below. Also, it become bigger
since I moved "forced" implicit cast functions here.

On Thu, Aug 05, 2021 at 12:25:49AM +0200, Vladislav Shpilevoy wrote:
> Thanks for the patch!
> 
> See 3 comments below.
> 
> > diff --git a/src/box/sql/mem.h b/src/box/sql/mem.h
> > index 47a940c56..5d1d7592e 100644
> > --- a/src/box/sql/mem.h
> > +++ b/src/box/sql/mem.h
> > @@ -1028,3 +1028,20 @@ mpstream_encode_vdbe_mem(struct mpstream *stream, struct Mem *var);
> >  char *
> >  sql_vdbe_mem_encode_tuple(struct Mem *fields, uint32_t field_count,
> >  			  uint32_t *tuple_size, struct region *region);
> > +
> > +static inline bool
> > +is_mem_num_min(const struct Mem *mem)
> > +{
> > +	return (mem->field_type == FIELD_TYPE_INTEGER &&
> > +		mem->type == MEM_TYPE_INT && mem->u.i == INT64_MIN) ||
> > +	       (mem->field_type == FIELD_TYPE_UNSIGNED &&
> > +		mem->type == MEM_TYPE_UINT && mem->u.u == 0);
> > +}
> > +
> > +static inline bool
> > +is_mem_num_max(const struct Mem *mem)
> 
> 1. Could you please name them to mem_is_num_min() and mem_is_num_max()?
> Also what if they are DOUBLE? Should't these functions be called then
> mem_is_int_min() and mem_is_int_max()?
> 
I dropped this function. However, at the beginning I also planned to use it
for DECIMAl values. DOUBLE has "widest" range, so there is no valuies less
or more than min and max DOUBLE values.

> > +{
> > +	return (mem->field_type == FIELD_TYPE_INTEGER ||
> > +		mem->field_type == FIELD_TYPE_UNSIGNED) &&
> > +	       mem->type == MEM_TYPE_UINT && mem->u.u == 0;
> > +}
> > diff --git a/src/box/sql/vdbe.c b/src/box/sql/vdbe.c
> > index 62f58def9..a69402720 100644
> > --- a/src/box/sql/vdbe.c
> > +++ b/src/box/sql/vdbe.c
> > @@ -2577,6 +2526,62 @@ case OP_Close: {
> >   *
> >   * See also: Found, NotFound, SeekGt, SeekGe, SeekLe
> >   */
> > +case OP_SeekLT: {       /* jump, in3 */
> > +	struct VdbeCursor *cur = p->apCsr[pOp->p1];
> > +#ifdef SQL_DEBUG
> > +	cur->seekOp = pOp->opcode;
> > +#endif
> > +	cur->nullRow = 0;
> > +	cur->uc.pCursor->iter_type = ITER_LT;
> > +
> > +	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_le = 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;
> > +		}
> > +		int cmp = mem_cast_implicit_number(mem, type);
> > +		is_le = is_le || cmp > 0;
> > +		/*
> > +		 * If number before cast were less than min possible for given
> > +		 * field type, than there is no point to use iterator since we
> > +		 * won't find anything.
> > +		 */
> > +		is_zero = is_zero || (is_mem_num_min(mem) && cmp < 0);
> 
> 2. Do you really need this optimization? Seems like a very rare case. The
> same in the other places.
> 
True, dropped most of it. For EQ/REQ I left this flag, since there is no another
way to implement them.

> Also it looks like a lot of code duplication. But I see why, and again I
> don't see a way to reuse all this code easily.
> 
True, however I found that I can divide these opcodes into two group: GE+LE
and GT+LT. And I did this.

> > +	}
> > +	if (is_zero) {
> > +		assert(pOp->p2 > 0);
> > +		VdbeBranchTaken(1, 2);
> > +		goto jump_to_p2;
> > +	}
> 
> <...>
> 
> > +case OP_SeekLE: {       /* jump, in3 */
> 
> <...>
> 
> > +	cur->nullRow = 0;
> > +	bool is_eq = (cur->uc.pCursor->hints & OPFLAG_SEEKEQ) != 0;
> > +	cur->uc.pCursor->iter_type = is_eq ? ITER_REQ : ITER_LE;
> > +	assert(!is_eq || pOp[1].opcode == OP_IdxLT);
> > +
> > +	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_lt = 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_lt = is_lt || cmp < 0;
> 
> 3. You needed to find values <= mem. The mem during cast was
> turned into mem2 < mem, correct? Then you need to find values
> <= mem2. Otherwise you skip mem2 itself. Or am I mistaken
> somewhere? Similar questions to all the other usages of
> mem_cast_implicit_number for seeking.
The logic is right, however cmp < 0 if value before less than value after
and vise versa.


New patch:


commit 46546623c76ca5d299cbff024adf63d0f38d3d40
Author: Mergen Imeev <imeevma@gmail.com>
Date:   Tue Jul 27 20:43:35 2021 +0300

    sql: rework OP_Seek* opcodes
    
    This patch changes the Seek* opcodes that are used to search in space
    using index. After the redesign, searches using these opcodes work
    according to the new implicit casting rules. However, currently implicit
    cast in these opcodes is not invoked since there is OP_ApplyType before
    them. Unnecessary OP_ApplyType calls will be removed in next patch.
    
    Part of 4230
    Part of 4470

diff --git a/src/box/sql.c b/src/box/sql.c
index 433264abe..a6a7864f1 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 bb2dae898..694fd9a7f 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 88e544191..b82d69e9c 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 32fdd9add..e654cac41 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 70bbe557b..4f61cbab0 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 115c52f96..e2e550978 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 1ded6c709..8fdc50432 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 2e147ec0b..371be205e 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);

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

* Re: [Tarantool-patches] [PATCH v1 4/7] sql: remove unnecessary calls of OP_ApplyType
  2021-08-04 22:26   ` Vladislav Shpilevoy via Tarantool-patches
@ 2021-08-05 23:41     ` Mergen Imeev via Tarantool-patches
  0 siblings, 0 replies; 25+ messages in thread
From: Mergen Imeev via Tarantool-patches @ 2021-08-05 23:41 UTC (permalink / raw)
  To: Vladislav Shpilevoy; +Cc: tarantool-patches

Thank you for ther review! My answer and new patch below.

On Thu, Aug 05, 2021 at 12:26:25AM +0200, Vladislav Shpilevoy wrote:
> Thanks for the patch!
> 
> > diff --git a/src/box/sql/wherecode.c b/src/box/sql/wherecode.c
> > index 96bcab110..92d374200 100644
> > --- a/src/box/sql/wherecode.c
> > +++ b/src/box/sql/wherecode.c
> 
> <...>
> 
> > -/**
> > - * Expression @rhs, which is the RHS of a comparison operation, is
> > - * either a vector of n elements or, if n==1, a scalar expression.
> > - * Before the comparison operation, types @types are to be applied
> > - * to the @rhs values. This function modifies entries within the
> > - * field sequence to SCALAR if either:
> > - *
> > - *   * the comparison will be performed with no type, or
> > - *   * the type change in @types is guaranteed not to change the value.
> > - */
> > -static void
> > -expr_cmp_update_rhs_type(struct Expr *rhs, int n, enum field_type *types)
> > -{
> > -	for (int i = 0; i < n; i++) {
> > -		Expr *p = sqlVectorFieldSubexpr(rhs, i);
> > -		enum field_type expr_type = sql_expr_type(p);
> > -		if (sql_type_result(expr_type, types[i]) == FIELD_TYPE_SCALAR ||
> > -		    sql_expr_needs_no_type_change(p, types[i])) {
> 
> sql_expr_needs_no_type_change is now unused.
Thanks, fixed.


New patch:


commit ecbc9842d5641e6746d909fe0c16fa6120b21e09
Author: Mergen Imeev <imeevma@gmail.com>
Date:   Tue Jul 27 22:51:34 2021 +0300

    sql: remove unnecessary calls of OP_ApplyType
    
    Since the OP_Seek* opcodes now work using the new implicit casting
    rules, we don't need to call OP_ApplyType before them. This patch
    removes such calls.
    
    Part of #4230
    Part of #4470

diff --git a/src/box/sql/expr.c b/src/box/sql/expr.c
index d2624516c..b9fc5bfc5 100644
--- a/src/box/sql/expr.c
+++ b/src/box/sql/expr.c
@@ -2246,36 +2246,6 @@ sqlExprCanBeNull(const Expr * p)
 	}
 }
 
-bool
-sql_expr_needs_no_type_change(const struct Expr *p, enum field_type type)
-{
-	u8 op;
-	if (type == FIELD_TYPE_SCALAR)
-		return true;
-	while (p->op == TK_UPLUS || p->op == TK_UMINUS) {
-		p = p->pLeft;
-	}
-	op = p->op;
-	if (op == TK_REGISTER)
-		op = p->op2;
-	switch (op) {
-	case TK_INTEGER:
-		return type == FIELD_TYPE_INTEGER;
-	case TK_FLOAT:
-		return type == FIELD_TYPE_DOUBLE;
-	case TK_STRING:
-		return type == FIELD_TYPE_STRING;
-	case TK_BLOB:
-		return type == FIELD_TYPE_VARBINARY;
-	case TK_COLUMN_REF:
-		/* p cannot be part of a CHECK constraint. */
-		assert(p->iTable >= 0);
-		return p->iColumn < 0 && sql_type_is_numeric(type);
-	default:
-		return false;
-	}
-}
-
 /*
  * pX is the RHS of an IN operator.  If pX is a SELECT statement
  * that can be simplified to a direct table access, then return
diff --git a/src/box/sql/sqlInt.h b/src/box/sql/sqlInt.h
index e2e550978..6b3444cf3 100644
--- a/src/box/sql/sqlInt.h
+++ b/src/box/sql/sqlInt.h
@@ -3182,19 +3182,6 @@ int sqlExprIsTableConstant(Expr *, int);
 int sqlExprIsInteger(Expr *, int *);
 int sqlExprCanBeNull(const Expr *);
 
-/**
- * Return TRUE if the given expression is a constant which would
- * be unchanged by OP_ApplyType with the type given in the second
- * argument.
- *
- * This routine is used to determine if the OP_ApplyType operation
- * can be omitted.  When in doubt return FALSE.  A false negative
- * is harmless. A false positive, however, can result in the wrong
- * answer.
- */
-bool
-sql_expr_needs_no_type_change(const struct Expr *expr, enum field_type type);
-
 /**
  * This routine generates VDBE code that causes a single row of a
  * single table to be deleted.  Both the original table entry and
diff --git a/src/box/sql/vdbe.c b/src/box/sql/vdbe.c
index 371be205e..aedff8b09 100644
--- a/src/box/sql/vdbe.c
+++ b/src/box/sql/vdbe.c
@@ -2504,7 +2504,7 @@ case OP_Close: {
 	break;
 }
 
-/* Opcode: SeekLT P1 P2 P3 P4 P5
+/* Opcode: SeekLT P1 P2 P3 P4 *
  * Synopsis: key=r[P3@P4]
  *
  * If cursor P1 refers to an SQL table (B-Tree that uses integer keys),
@@ -2520,11 +2520,9 @@ case OP_Close: {
  * from the end toward the beginning.  In other words, the cursor is
  * configured to use Prev, not Next.
  *
- * P5 has the same meaning as for SeekGE.
- *
  * See also: Found, NotFound, SeekGt, SeekGe, SeekLe
  */
-/* Opcode: SeekGT P1 P2 P3 P4 P5
+/* Opcode: SeekGT P1 P2 P3 P4 *
  * Synopsis: key=r[P3@P4]
  *
  * If cursor P1 refers to an SQL table (B-Tree that uses integer keys),
@@ -2539,11 +2537,6 @@ case OP_Close: {
  * 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.
- *
- * If P5 is not zero, than it is offset of integer fields in input
- * vector. Force corresponding value to be INTEGER.
- *
- * P5 has the same meaning as for SeekGE.
  */
 case OP_SeekLT:         /* jump, in3 */
 case OP_SeekGT: {       /* jump, in3 */
@@ -2592,7 +2585,7 @@ case OP_SeekGT: {       /* jump, in3 */
 	break;
 }
 
-/* Opcode: SeekLE P1 P2 P3 P4 P5
+/* Opcode: SeekLE P1 P2 P3 P4 *
  * Synopsis: key=r[P3@P4]
  *
  * If cursor P1 refers to an SQL table (B-Tree that uses integer keys),
@@ -2615,11 +2608,9 @@ case OP_SeekGT: {       /* jump, in3 */
  * 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, SeekLt
  */
-/* Opcode: SeekGE P1 P2 P3 P4 P5
+/* Opcode: SeekGE P1 P2 P3 P4 *
  * Synopsis: key=r[P3@P4]
  *
  * If cursor P1 refers to an SQL table (B-Tree that uses integer keys),
@@ -2642,11 +2633,6 @@ case OP_SeekGT: {       /* jump, in3 */
  * from the beginning toward the end.  In other words, the cursor is
  * configured to use Next, not Prev.
  *
- * 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_SeekLE:         /* jump, in3 */
diff --git a/src/box/sql/wherecode.c b/src/box/sql/wherecode.c
index 96bcab110..92d374200 100644
--- a/src/box/sql/wherecode.c
+++ b/src/box/sql/wherecode.c
@@ -335,72 +335,6 @@ disableTerm(WhereLevel * pLevel, WhereTerm * pTerm)
 	}
 }
 
-/**
- * Code an OP_ApplyType opcode to apply the column type string
- * @types to the n registers starting at @base.
- *
- * As an optimization, SCALAR entries (which are no-ops) at the
- * beginning and end of @types are ignored.  If all entries in
- * @types are SCALAR, then no code gets generated.
- *
- * This routine makes its own copy of @types so that the caller is
- * free to modify @types after this routine returns.
- */
-static void
-emit_apply_type(Parse *pParse, int base, int n, enum field_type *types)
-{
-	Vdbe *v = pParse->pVdbe;
-	if (types == NULL) {
-		assert(pParse->db->mallocFailed);
-		return;
-	}
-	assert(v != 0);
-
-	/*
-	 * Adjust base and n to skip over SCALAR entries at the
-	 * beginning and end of the type sequence.
-	 */
-	while (n > 0 && types[0] == FIELD_TYPE_SCALAR) {
-		n--;
-		base++;
-		types++;
-	}
-	while (n > 1 && types[n - 1] == FIELD_TYPE_SCALAR) {
-		n--;
-	}
-
-	if (n > 0) {
-		enum field_type *types_dup = field_type_sequence_dup(pParse,
-								     types, n);
-		sqlVdbeAddOp4(v, OP_ApplyType, base, n, 0,
-				  (char *) types_dup, P4_DYNAMIC);
-		sql_expr_type_cache_change(pParse, base, n);
-	}
-}
-
-/**
- * Expression @rhs, which is the RHS of a comparison operation, is
- * either a vector of n elements or, if n==1, a scalar expression.
- * Before the comparison operation, types @types are to be applied
- * to the @rhs values. This function modifies entries within the
- * field sequence to SCALAR if either:
- *
- *   * the comparison will be performed with no type, or
- *   * the type change in @types is guaranteed not to change the value.
- */
-static void
-expr_cmp_update_rhs_type(struct Expr *rhs, int n, enum field_type *types)
-{
-	for (int i = 0; i < n; i++) {
-		Expr *p = sqlVectorFieldSubexpr(rhs, i);
-		enum field_type expr_type = sql_expr_type(p);
-		if (sql_type_result(expr_type, types[i]) == FIELD_TYPE_SCALAR ||
-		    sql_expr_needs_no_type_change(p, types[i])) {
-			types[i] = FIELD_TYPE_SCALAR;
-		}
-	}
-}
-
 /*
  * Generate code for a single equality term of the WHERE clause.  An equality
  * term can be either X=expr or X IN (...).   pTerm is the term to be
@@ -644,8 +578,7 @@ static int
 codeAllEqualityTerms(Parse * pParse,	/* Parsing context */
 		     WhereLevel * pLevel,	/* Which nested loop of the FROM we are coding */
 		     int bRev,		/* Reverse the order of IN operators */
-		     int nExtraReg,	/* Number of extra registers to allocate */
-		     enum field_type **res_type)
+		     int nExtraReg)	/* Number of extra registers to allocate */
 {
 	u16 nEq;		/* The number of == or IN constraints to code */
 	u16 nSkip;		/* Number of left-most columns to skip */
@@ -733,7 +666,6 @@ codeAllEqualityTerms(Parse * pParse,	/* Parsing context */
 			}
 		}
 	}
-	*res_type = type;
 	return regBase;
 }
 
@@ -905,16 +837,8 @@ sqlWhereCodeOneLoopStart(WhereInfo * pWInfo,	/* Complete information about the W
 		int iIdxCur;	/* The VDBE cursor for the index */
 		int nExtraReg = 0;	/* Number of extra registers needed */
 		int op;		/* Instruction opcode */
-		/* Types for start of range constraint. */
-		enum field_type *start_types;
-		/* Types for end of range constraint */
-		enum field_type *end_types = NULL;
 		u8 bSeekPastNull = 0;	/* True to seek past initial nulls */
 		u8 bStopAtNull = 0;	/* Add condition to terminate at NULLs */
-		int force_integer_reg = -1;  /* If non-negative: number of
-					      * column which must be converted
-					      * to integer type, used for IPK.
-					      */
 
 		struct index_def *idx_def = pLoop->index_def;
 		assert(idx_def != NULL);
@@ -996,16 +920,7 @@ sqlWhereCodeOneLoopStart(WhereInfo * pWInfo,	/* Complete information about the W
 		 * starting at regBase.
 		 */
 		regBase =
-		    codeAllEqualityTerms(pParse, pLevel, bRev, nExtraReg,
-					 &start_types);
-		if (start_types != NULL && nTop) {
-			uint32_t len = 0;
-			for (enum field_type *tmp = &start_types[nEq];
-			     *tmp != field_type_MAX; tmp++, len++);
-			uint32_t sz = len * sizeof(enum field_type);
-			end_types = sqlDbMallocRaw(db, sz);
-			memcpy(end_types, &start_types[nEq], sz);
-		}
+		    codeAllEqualityTerms(pParse, pLevel, bRev, nExtraReg);
 		addrNxt = pLevel->addrNxt;
 
 		testcase(pRangeStart && (pRangeStart->eOperator & WO_LE) != 0);
@@ -1030,10 +945,6 @@ sqlWhereCodeOneLoopStart(WhereInfo * pWInfo,	/* Complete information about the W
 				VdbeCoverage(v);
 			}
 
-			if (start_types) {
-				expr_cmp_update_rhs_type(pRight, nBtm,
-							 &start_types[nEq]);
-			}
 			nConstraint += nBtm;
 			testcase(pRangeStart->wtFlags & TERM_VIRTUAL);
 			if (sqlExprIsVector(pRight) == 0) {
@@ -1048,94 +959,6 @@ sqlWhereCodeOneLoopStart(WhereInfo * pWInfo,	/* Complete information about the W
 			startEq = 0;
 			start_constraints = 1;
 		}
-		/*
-		 * Tarantool's iterator over integer fields doesn't
-		 * tolerate floating point values. Hence, if term
-		 * is equality comparison and value of operand is
-		 * not integer, we can skip it since it always
-		 * results in false: INT a == 0.5 -> false;
-		 * It is done using OP_MustBeInt facilities.
-		 * In case term is greater comparison (a > ?), we
-		 * should notify OP_SeekGT to process truncation of
-		 * floating point value: a > 0.5 -> a >= 1;
-		 * It is done by setting P5 flag for OP_Seek*.
-		 * It is worth mentioning that we do not need
-		 * this step when it comes for less (<) comparison
-		 * of nullable field. Key is NULL in this case:
-		 * values are ordered as  NULL, ... NULL, min_value,
-		 * so to fetch min value we pass NULL to GT iterator.
-		 * The only exception is less comparison in
-		 * conjunction with ORDER BY DESC clause:
-		 * in such situation we use LE iterator and
-		 * truncated value to compare. But then
-		 * pRangeStart == NULL.
-		 * This procedure is correct for compound index:
-		 * only one comparison of less/greater type can be
-		 * used at the same time. For instance,
-		 * a < 1.5 AND b > 0.5 is handled by SeekGT using
-		 * column a and fetching column b from tuple and
-		 * OP_Le comparison.
-		 *
-		 * Note that OP_ApplyType, which is emitted before
-		 * OP_Seek** doesn't truncate floating point to
-		 * integer. That's why we need this routine.
-		 * Also, note that terms are separated by OR
-		 * predicates, so we consider term as sequence
-		 * of AND'ed predicates.
-		 */
-		size_t addrs_sz;
-		int *seek_addrs = region_alloc_array(&pParse->region,
-						     typeof(seek_addrs[0]), nEq,
-						     &addrs_sz);
-		if (seek_addrs == NULL) {
-			diag_set(OutOfMemory, addrs_sz, "region_alloc_array",
-				 "seek_addrs");
-			pParse->is_aborted = true;
-			return 0;
-		}
-		memset(seek_addrs, 0, addrs_sz);
-		for (int i = 0; i < nEq; i++) {
-			enum field_type type = idx_def->key_def->parts[i].type;
-			if (type == FIELD_TYPE_INTEGER ||
-			    type == FIELD_TYPE_UNSIGNED) {
-				/*
-				 * OP_MustBeInt consider NULLs as
-				 * non-integer values, so firstly
-				 * check whether value is NULL or not.
-				 */
-				seek_addrs[i] = sqlVdbeAddOp1(v, OP_IsNull,
-							      regBase);
-				sqlVdbeAddOp2(v, OP_MustBeInt, regBase + i,
-					      addrNxt);
-				start_types[i] = FIELD_TYPE_SCALAR;
-				/*
-				 * We need to notify column cache
-				 * that type of value may change
-				 * so we should fetch value from
-				 * tuple again rather then copy
-				 * from register.
-				 */
-				sql_expr_type_cache_change(pParse, regBase + i,
-							   1);
-			}
-		}
-		/* Inequality constraint comes always at the end of list. */
-		part_count = idx_def->key_def->part_count;
-		if (pRangeStart != NULL) {
-			/*
-			 * nEq == 0 means that filter condition
-			 * contains only inequality.
-			 */
-			uint32_t ineq_idx = nEq == 0 ? 0 : nEq - 1;
-			assert(ineq_idx < part_count);
-			enum field_type ineq_type =
-				idx_def->key_def->parts[ineq_idx].type;
-			if (ineq_type == FIELD_TYPE_INTEGER ||
-			    ineq_type == FIELD_TYPE_UNSIGNED)
-				force_integer_reg = regBase + nEq;
-		}
-		emit_apply_type(pParse, regBase, nConstraint - bSeekPastNull,
-				start_types);
 		if (pLoop->nSkip > 0 && nConstraint == pLoop->nSkip) {
 			/* The skip-scan logic inside the call to codeAllEqualityConstraints()
 			 * above has already left the cursor sitting on the correct row,
@@ -1145,20 +968,8 @@ sqlWhereCodeOneLoopStart(WhereInfo * pWInfo,	/* Complete information about the W
 			op = aStartOp[(start_constraints << 2) +
 				      (startEq << 1) + bRev];
 			assert(op != 0);
-			for (uint32_t i = 0; i < nEq; ++i) {
-				if (seek_addrs[i] != 0)
-					sqlVdbeJumpHere(v, seek_addrs[i]);
-			}
 			sqlVdbeAddOp4Int(v, op, iIdxCur, addrNxt, regBase,
 					     nConstraint);
-			/* If this is Seek* opcode, and IPK is detected in the
-			 * constraints vector: force it to be integer.
-			 */
-			if ((op == OP_SeekGE || op == OP_SeekGT
-			    || op == OP_SeekLE || op == OP_SeekLT)
-			    && force_integer_reg > 0) {
-				sqlVdbeChangeP5(v, force_integer_reg);
-			}
 			VdbeCoverage(v);
 			VdbeCoverageIf(v, op == OP_Rewind);
 			testcase(op == OP_Rewind);
@@ -1188,13 +999,6 @@ sqlWhereCodeOneLoopStart(WhereInfo * pWInfo,	/* Complete information about the W
 						  addrNxt);
 				VdbeCoverage(v);
 			}
-			if (end_types) {
-				expr_cmp_update_rhs_type(pRight, nTop, end_types);
-				emit_apply_type(pParse, regBase + nEq, nTop,
-						end_types);
-			} else {
-				assert(pParse->db->mallocFailed);
-			}
 			nConstraint += nTop;
 			testcase(pRangeEnd->wtFlags & TERM_VIRTUAL);
 
@@ -1208,8 +1012,6 @@ sqlWhereCodeOneLoopStart(WhereInfo * pWInfo,	/* Complete information about the W
 			endEq = 0;
 			nConstraint++;
 		}
-		sqlDbFree(db, start_types);
-		sqlDbFree(db, end_types);
 
 		/* Top of the loop body */
 		pLevel->p2 = sqlVdbeCurrentAddr(v);
diff --git a/test/sql-tap/cast.test.lua b/test/sql-tap/cast.test.lua
index b8ad23317..b570fc878 100755
--- a/test/sql-tap/cast.test.lua
+++ b/test/sql-tap/cast.test.lua
@@ -1,6 +1,6 @@
 #!/usr/bin/env tarantool
 local test = require("sqltester")
-test:plan(107)
+test:plan(108)
 
 --!./tcltestrunner.lua
 -- 2005 June 25
@@ -1144,4 +1144,16 @@ test:do_catchsql_test(
         1, "Type mismatch: can not convert double(2.5) to string"
     })
 
+-- Make sure that search using index in field type number work right.
+test:do_execsql_test(
+    "cast-11",
+    [[
+        CREATE TABLE t6(d DOUBLE PRIMARY KEY);
+        INSERT INTO t6 VALUES(10000000000000000);
+        SELECT d FROM t6 WHERE d < 10000000000000001 and d > 9999999999999999;
+        DROP TABLE t6;
+    ]], {
+        10000000000000000
+    })
+
 test:finish_test()
diff --git a/test/sql-tap/in4.test.lua b/test/sql-tap/in4.test.lua
index 8442944b9..aa6483697 100755
--- a/test/sql-tap/in4.test.lua
+++ b/test/sql-tap/in4.test.lua
@@ -147,13 +147,13 @@ test:do_execsql_test(
         -- </in4-2.7>
     })
 
-test:do_execsql_test(
+test:do_catchsql_test(
     "in4-2.8",
     [[
         SELECT b FROM t2 WHERE a IN ('', '0.0.0', '2')
     ]], {
         -- <in4-2.8>
-        "two"
+        1, "Type mismatch: can not convert string('') to integer"
         -- </in4-2.8>
     })
 
diff --git a/test/sql-tap/join.test.lua b/test/sql-tap/join.test.lua
index 48639a7b3..abfabfacf 100755
--- a/test/sql-tap/join.test.lua
+++ b/test/sql-tap/join.test.lua
@@ -1028,13 +1028,13 @@ test:do_test(
         -- </join-11.8>
     })
 
-test:do_execsql_test(
+test:do_catchsql_test(
     "join-11.9",
     [[
         SELECT * FROM t1 NATURAL JOIN t2
     ]], {
         -- <join-11.9>
-        "one", "1", "two", "2"
+        1, "Type mismatch: can not convert string('1') to integer"
         -- </join-11.9>
     })
 
diff --git a/test/sql-tap/tkt-9a8b09f8e6.test.lua b/test/sql-tap/tkt-9a8b09f8e6.test.lua
index 43322468d..cc321c2f6 100755
--- a/test/sql-tap/tkt-9a8b09f8e6.test.lua
+++ b/test/sql-tap/tkt-9a8b09f8e6.test.lua
@@ -1,6 +1,6 @@
 #!/usr/bin/env tarantool
 local test = require("sqltester")
-test:plan(47)
+test:plan(45)
 
 --!./tcltestrunner.lua
 -- 2014 June 26
@@ -179,16 +179,6 @@ test:do_execsql_test(
         -- </3.2>
     })
 
-test:do_execsql_test(
-    3.3,
-    [[
-        SELECT x FROM t2 WHERE x IN ('1');
-    ]], {
-        -- <3.3>
-        1
-        -- </3.3>
-    })
-
 test:do_execsql_test(
     3.5,
     [[
@@ -209,16 +199,6 @@ test:do_execsql_test(
         -- </3.6>
     })
 
-test:do_execsql_test(
-    3.7,
-    [[
-        SELECT x FROM t2 WHERE '1' IN (x);
-    ]], {
-        -- <3.7>
-        1
-        -- </3.7>
-    })
-
 test:do_execsql_test(
     4.1,
     [[

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

* Re: [Tarantool-patches] [PATCH v1 5/7] sql: remove implicit cast from OP_MakeRecord
  2021-08-04 22:27   ` Vladislav Shpilevoy via Tarantool-patches
@ 2021-08-05 23:43     ` Mergen Imeev via Tarantool-patches
  2021-08-06  0:13       ` Vladislav Shpilevoy via Tarantool-patches
  0 siblings, 1 reply; 25+ messages in thread
From: Mergen Imeev via Tarantool-patches @ 2021-08-05 23:43 UTC (permalink / raw)
  To: Vladislav Shpilevoy; +Cc: tarantool-patches

Thank you for the review! My answers and new patch below.

On Thu, Aug 05, 2021 at 12:27:20AM +0200, Vladislav Shpilevoy wrote:
> Thanks for the patch!
> 
> On 28.07.2021 22:51, imeevma@tarantool.org wrote:
> > This patch removes deprecated implicit cast from OP_MakeRecord opcode.
> > Not there will be no implicit cast in OP_MakeRecord opcode.
> 
> 1. Not -> Note? The commit seems strange. You basically said the same
> thing 3 times: 1 in th title and 2 in the message.
> 
"Not" was "Now", but I rewrite all commite-message.

> > diff --git a/changelogs/unreleased/gh-4230-implicit-cast-for-comparison.md b/changelogs/unreleased/gh-4230-implicit-cast-for-comparison.md
> > new file mode 100644
> > index 000000000..9f523d3ed
> > --- /dev/null
> > +++ b/changelogs/unreleased/gh-4230-implicit-cast-for-comparison.md
> > @@ -0,0 +1,3 @@
> > +## feature/sql
> > +
> > +* Implicit cast for comparison now works according to defined rules (gh-4230).
> 
> 2. Please, try to be more specific here. Users won't understand anything from
> this sentence.
Fixed.

New patch:


commit 71f945038ee7aca90e854dad29875312adf80a1a
Author: Mergen Imeev <imeevma@gmail.com>
Date:   Tue Jul 27 23:31:45 2021 +0300

    sql: remove implicit cast from OP_MakeRecord
    
    This patch removes deprecated implicit cast from OP_MakeRecord opcode,
    which were used in some rare cases, for example during IN operation
    with subselect as right-value.
    
    Closes #4230
    Part of #4470

diff --git a/changelogs/unreleased/gh-4230-implicit-cast-for-comparison.md b/changelogs/unreleased/gh-4230-implicit-cast-for-comparison.md
new file mode 100644
index 000000000..66103f461
--- /dev/null
+++ b/changelogs/unreleased/gh-4230-implicit-cast-for-comparison.md
@@ -0,0 +1,6 @@
+## feature/sql
+
+* Now any number can be compared to any other number, and values of any scalar
+  type can be compared to any other value of the same type. A value of a
+  non-numeric scalar type cannot be compared with a value of any other scalar
+  type (gh-4230).
\ No newline at end of file
diff --git a/src/box/sql/analyze.c b/src/box/sql/analyze.c
index b87f69512..e97fefb3a 100644
--- a/src/box/sql/analyze.c
+++ b/src/box/sql/analyze.c
@@ -968,12 +968,7 @@ vdbe_emit_analyze_space(struct Parse *parse, struct space *space)
 		sqlVdbeAddOp2(v, OP_Next, idx_cursor, next_row_addr);
 		/* Add the entry to the stat1 table. */
 		callStatGet(v, stat4_reg, STAT_GET_STAT1, stat1_reg);
-		enum field_type types[4] = { FIELD_TYPE_STRING,
-					     FIELD_TYPE_STRING,
-					     FIELD_TYPE_STRING,
-					     field_type_MAX };
-		sqlVdbeAddOp4(v, OP_MakeRecord, tab_name_reg, 4, tmp_reg,
-				  (char *)types, sizeof(types));
+		sqlVdbeAddOp3(v, OP_MakeRecord, tab_name_reg, 4, tmp_reg);
 		sqlVdbeAddOp4(v, OP_IdxInsert, tmp_reg, 0, 0,
 				  (char *)stat1, P4_SPACEPTR);
 		/* Add the entries to the stat4 table. */
diff --git a/src/box/sql/delete.c b/src/box/sql/delete.c
index 62a726fdd..5226dd6ea 100644
--- a/src/box/sql/delete.c
+++ b/src/box/sql/delete.c
@@ -328,12 +328,8 @@ sql_table_delete_from(struct Parse *parse, struct SrcList *tab_list,
 			 * key.
 			 */
 			key_len = 0;
-			struct index *pk = space_index(space, 0);
-			enum field_type *types = is_view ? NULL :
-						 sql_index_type_str(parse->db,
-								    pk->def);
-			sqlVdbeAddOp4(v, OP_MakeRecord, reg_pk, pk_len,
-					  reg_key, (char *)types, P4_DYNAMIC);
+			sqlVdbeAddOp3(v, OP_MakeRecord, reg_pk, pk_len,
+				      reg_key);
 			/* Set flag to save memory allocating one
 			 * by malloc.
 			 */
diff --git a/src/box/sql/expr.c b/src/box/sql/expr.c
index b9fc5bfc5..83766b3fb 100644
--- a/src/box/sql/expr.c
+++ b/src/box/sql/expr.c
@@ -2829,8 +2829,6 @@ sqlCodeSubselect(Parse * pParse,	/* Parsing context */
 				struct ExprList_item *pItem;
 				int r1, r2, r3;
 
-				enum field_type lhs_type =
-					sql_expr_type(pLeft);
 				bool unused;
 				struct coll *unused_coll;
 				if (sql_expr_coll(pParse, pExpr->pLeft, &unused,
@@ -2856,11 +2854,8 @@ sqlCodeSubselect(Parse * pParse,	/* Parsing context */
 						jmpIfDynamic = -1;
 					}
 					r3 = sqlExprCodeTarget(pParse, pE2, r1);
-					enum field_type types[2] =
-						{ lhs_type, field_type_MAX };
-	 				sqlVdbeAddOp4(v, OP_MakeRecord, r3,
-							  1, r2, (char *)types,
-							  sizeof(types));
+					sqlVdbeAddOp3(v, OP_MakeRecord, r3, 1,
+						      r2);
 					sql_expr_type_cache_change(pParse,
 								   r3, 1);
 					sqlVdbeAddOp2(v, OP_IdxInsert, r2,
diff --git a/src/box/sql/fk_constraint.c b/src/box/sql/fk_constraint.c
index 0dd10c420..2a9399d78 100644
--- a/src/box/sql/fk_constraint.c
+++ b/src/box/sql/fk_constraint.c
@@ -262,13 +262,8 @@ fk_constraint_lookup_parent(struct Parse *parse_context, struct space *parent,
 					  link->child_field + 1 + reg_data,
 					  temp_regs + i);
 		}
-		struct index *idx = space_index(parent, referenced_idx);
-		assert(idx != NULL);
-		sqlVdbeAddOp4(v, OP_MakeRecord, temp_regs, field_count,
-				  rec_reg,
-				  (char *) sql_index_type_str(parse_context->db,
-							      idx->def),
-				  P4_DYNAMIC);
+		sqlVdbeAddOp3(v, OP_MakeRecord, temp_regs, field_count,
+			      rec_reg);
 		sqlVdbeAddOp4Int(v, OP_Found, cursor, ok_label, rec_reg, 0);
 		sqlReleaseTempReg(parse_context, rec_reg);
 		sqlReleaseTempRange(parse_context, temp_regs, field_count);
diff --git a/src/box/sql/insert.c b/src/box/sql/insert.c
index 02e9f9673..21b4f2407 100644
--- a/src/box/sql/insert.c
+++ b/src/box/sql/insert.c
@@ -42,20 +42,6 @@
 #include "box/box.h"
 #include "box/schema.h"
 
-enum field_type *
-sql_index_type_str(struct sql *db, const struct index_def *idx_def)
-{
-	uint32_t column_count = idx_def->key_def->part_count;
-	uint32_t sz = (column_count + 1) * sizeof(enum field_type);
-	enum field_type *types = (enum field_type *) sqlDbMallocRaw(db, sz);
-	if (types == NULL)
-		return NULL;
-	for (uint32_t i = 0; i < column_count; i++)
-		types[i] = idx_def->key_def->parts[i].type;
-	types[column_count] = field_type_MAX;
-	return types;
-}
-
 void
 sql_emit_table_types(struct Vdbe *v, struct space_def *def, int reg)
 {
diff --git a/src/box/sql/sqlInt.h b/src/box/sql/sqlInt.h
index 6b3444cf3..adac80a64 100644
--- a/src/box/sql/sqlInt.h
+++ b/src/box/sql/sqlInt.h
@@ -3771,10 +3771,6 @@ int sqlVarintLen(u64 v);
 #define getVarint    sqlGetVarint
 #define putVarint    sqlPutVarint
 
-/** Return string consisting of fields types of given index. */
-enum field_type *
-sql_index_type_str(struct sql *db, const struct index_def *idx_def);
-
 /**
  * Code an OP_ApplyType opcode that will force types
  * for given range of register starting from @reg.
diff --git a/src/box/sql/update.c b/src/box/sql/update.c
index 24c7cfa27..22f82390c 100644
--- a/src/box/sql/update.c
+++ b/src/box/sql/update.c
@@ -251,11 +251,7 @@ sqlUpdate(Parse * pParse,		/* The parser context */
 		nKey = pk_part_count;
 		regKey = iPk;
 	} else {
-		enum field_type *types = is_view ? NULL :
-					 sql_index_type_str(pParse->db,
-							    pPk->def);
-		sqlVdbeAddOp4(v, OP_MakeRecord, iPk, pk_part_count,
-				  regKey, (char *) types, P4_DYNAMIC);
+		sqlVdbeAddOp3(v, OP_MakeRecord, iPk, pk_part_count, regKey);
 		/*
 		 * Set flag to save memory allocating one by
 		 * malloc.
@@ -420,12 +416,8 @@ sqlUpdate(Parse * pParse,		/* The parser context */
 			int key_reg;
 			if (okOnePass) {
 				key_reg = sqlGetTempReg(pParse);
-				enum field_type *types =
-					sql_index_type_str(pParse->db,
-							   pPk->def);
-				sqlVdbeAddOp4(v, OP_MakeRecord, iPk,
-						  pk_part_count, key_reg,
-						  (char *) types, P4_DYNAMIC);
+				sqlVdbeAddOp3(v, OP_MakeRecord, iPk,
+					      pk_part_count, key_reg);
 			} else {
 				assert(nKey == 0);
 				key_reg = regKey;
diff --git a/src/box/sql/vdbe.c b/src/box/sql/vdbe.c
index aedff8b09..fcea9eefe 100644
--- a/src/box/sql/vdbe.c
+++ b/src/box/sql/vdbe.c
@@ -2049,24 +2049,17 @@ case OP_ApplyType: {
 	break;
 }
 
-/* Opcode: MakeRecord P1 P2 P3 P4 P5
+/* Opcode: MakeRecord P1 P2 P3 * P5
  * Synopsis: r[P3]=mkrec(r[P1@P2])
  *
  * Convert P2 registers beginning with P1 into the [record format]
  * use as a data record in a database table or as a key
  * in an index.  The OP_Column opcode can decode the record later.
  *
- * P4 may be a string that is P2 characters long.  The nth character of the
- * string indicates the column type that should be used for the nth
- * field of the index key.
- *
- * If P4 is NULL then all index fields have type SCALAR.
- *
  * If P5 is not NULL then record under construction is intended to be inserted
  * into ephemeral space. Thus, sort of memory optimization can be performed.
  */
 case OP_MakeRecord: {
-	Mem *pRec;             /* The new record */
 	Mem *pData0;           /* First field to be combined into the record */
 	Mem MAYBE_UNUSED *pLast;  /* Last field of the record */
 	int nField;            /* Number of fields in the record */
@@ -2087,7 +2080,6 @@ case OP_MakeRecord: {
 	 * is the offset from the beginning of the record to data0.
 	 */
 	nField = pOp->p1;
-	enum field_type *types = pOp->p4.types;
 	bIsEphemeral = pOp->p5;
 	assert(nField>0 && pOp->p2>0 && pOp->p2+nField<=(p->nMem+1 - p->nCursor)+1);
 	pData0 = &aMem[nField];
@@ -2098,15 +2090,6 @@ case OP_MakeRecord: {
 	assert(pOp->p3<pOp->p1 || pOp->p3>=pOp->p1+pOp->p2);
 	pOut = vdbe_prepare_null_out(p, pOp->p3);
 
-	/* Apply the requested types to all inputs */
-	assert(pData0<=pLast);
-	if (types != NULL) {
-		pRec = pData0;
-		do {
-			mem_cast_implicit_old(pRec++, *(types++));
-		} while(types[0] != field_type_MAX);
-	}
-
 	struct region *region = &fiber()->gc;
 	size_t used = region_used(region);
 	uint32_t tuple_size;
diff --git a/src/box/sql/wherecode.c b/src/box/sql/wherecode.c
index 92d374200..df6cc92e1 100644
--- a/src/box/sql/wherecode.c
+++ b/src/box/sql/wherecode.c
@@ -602,9 +602,6 @@ codeAllEqualityTerms(Parse * pParse,	/* Parsing context */
 	nReg = pLoop->nEq + nExtraReg;
 	pParse->nMem += nReg;
 
-	enum field_type *type = sql_index_type_str(pParse->db, idx_def);
-	assert(type != NULL || pParse->db->mallocFailed);
-
 	if (nSkip) {
 		int iIdxCur = pLevel->iIdxCur;
 		sqlVdbeAddOp1(v, (bRev ? OP_Last : OP_Rewind), iIdxCur);
@@ -647,17 +644,7 @@ codeAllEqualityTerms(Parse * pParse,	/* Parsing context */
 				sqlVdbeAddOp2(v, OP_SCopy, r1, regBase + j);
 			}
 		}
-		if (pTerm->eOperator & WO_IN) {
-			if (pTerm->pExpr->flags & EP_xIsSelect) {
-				/* No type ever needs to be (or should be) applied to a value
-				 * from the RHS of an "? IN (SELECT ...)" expression. The
-				 * sqlFindInIndex() routine has already ensured that the
-				 * type of the comparison has been applied to the value.
-				 */
-				if (type != NULL)
-					type[j] = FIELD_TYPE_SCALAR;
-			}
-		} else if ((pTerm->eOperator & WO_ISNULL) == 0) {
+		if ((pTerm->eOperator & (WO_IN | WO_ISNULL)) == 0) {
 			Expr *pRight = pTerm->pExpr->pRight;
 			if (sqlExprCanBeNull(pRight)) {
 				sqlVdbeAddOp2(v, OP_IsNull, regBase + j,
diff --git a/test/sql-tap/cast.test.lua b/test/sql-tap/cast.test.lua
index b570fc878..927114772 100755
--- a/test/sql-tap/cast.test.lua
+++ b/test/sql-tap/cast.test.lua
@@ -1,6 +1,6 @@
 #!/usr/bin/env tarantool
 local test = require("sqltester")
-test:plan(108)
+test:plan(109)
 
 --!./tcltestrunner.lua
 -- 2005 June 25
@@ -1156,4 +1156,13 @@ test:do_execsql_test(
         10000000000000000
     })
 
+-- Make sure that there is no unnecessary implicit casts in IN operator.
+test:do_catchsql_test(
+    "cast-12",
+    [[
+        SELECT 1 IN (SELECT '1');
+    ]], {
+        1, "Type mismatch: can not convert integer(1) to string"
+    })
+
 test:finish_test()
diff --git a/test/sql-tap/in3.test.lua b/test/sql-tap/in3.test.lua
index 5f3f543af..4536fb0d3 100755
--- a/test/sql-tap/in3.test.lua
+++ b/test/sql-tap/in3.test.lua
@@ -342,7 +342,7 @@ test:do_test(
         return exec_neph(" SELECT y IN (SELECT a FROM t1) FROM t2 ")
     end, {
         -- <in3-3.5>
-        1, true
+        1, false
         -- </in3-3.5>
     })
 
@@ -366,7 +366,7 @@ test:do_test(
         return exec_neph(" SELECT y IN (SELECT c FROM t1) FROM t2 ")
     end, {
         -- <in3-3.7>
-        1, true
+        1, false
         -- </in3-3.7>
     })
 

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

* Re: [Tarantool-patches] [PATCH v1 7/7] sql: remove unused MEM cast functions
  2021-08-04 22:27   ` Vladislav Shpilevoy via Tarantool-patches
@ 2021-08-05 23:45     ` Mergen Imeev via Tarantool-patches
  0 siblings, 0 replies; 25+ messages in thread
From: Mergen Imeev via Tarantool-patches @ 2021-08-05 23:45 UTC (permalink / raw)
  To: Vladislav Shpilevoy; +Cc: tarantool-patches

Thank you for the review! My answers and new patch below. Also, patch become a
lot less since I decided to not remove "precise" cast functions and
mem_cast_implicit().

On Thu, Aug 05, 2021 at 12:27:41AM +0200, Vladislav Shpilevoy wrote:
> Thanks for the patch!
> 
> > diff --git a/src/box/sql/vdbeaux.c b/src/box/sql/vdbeaux.c
> > index 4a1fdb637..fc06e502e 100644
> > --- a/src/box/sql/vdbeaux.c
> > +++ b/src/box/sql/vdbeaux.c
> > @@ -2323,17 +2323,15 @@ sqlVdbeDb(Vdbe * v)
> >   * The returned value must be freed by the caller using sqlValueFree().
> >   */
> >  sql_value *
> > -sqlVdbeGetBoundValue(Vdbe * v, int iVar, u8 aff)
> > +sqlVdbeGetBoundValue(Vdbe * v, int iVar)
> 
> 1. No need for a whitespace after *.
> 
Fixed.

> >  {
> >  	assert(iVar > 0);
> >  	if (v) {
> >  		Mem *pMem = &v->aVar[iVar - 1];
> >  		if (!mem_is_null(pMem)) {
> >  			sql_value *pRet = sqlValueNew(v->db);
> > -			if (pRet) {
> > +			if (pRet)
> 
> 2. != NULL.
Fixed.

New patch:


commit a399bca95e72aea615600e3e8c6a15a8c11f2987
Author: Mergen Imeev <imeevma@gmail.com>
Date:   Tue Jul 27 23:37:06 2021 +0300

    sql: remove unused MEM cast functions
    
    This patch removes functions that become unused due to changes of
    implicit and explicit cast rules.
    
    Part of #4470

diff --git a/src/box/sql/mem.c b/src/box/sql/mem.c
index e654cac41..d1ef92a86 100644
--- a/src/box/sql/mem.c
+++ b/src/box/sql/mem.c
@@ -1293,88 +1293,6 @@ mem_cast_implicit(struct Mem *mem, enum field_type type)
 	return -1;
 }
 
-int
-mem_cast_implicit_old(struct Mem *mem, enum field_type type)
-{
-	if (mem->type == MEM_TYPE_NULL)
-		return 0;
-	switch (type) {
-	case FIELD_TYPE_UNSIGNED:
-		if (mem->type == MEM_TYPE_UINT)
-			return 0;
-		if (mem->type == MEM_TYPE_DOUBLE)
-			return double_to_uint_precise(mem);
-		if (mem->type == MEM_TYPE_STR)
-			return str_to_uint(mem);
-		return -1;
-	case FIELD_TYPE_STRING:
-		if ((mem->type & (MEM_TYPE_STR | MEM_TYPE_BIN)) != 0)
-			return 0;
-		if ((mem->type & (MEM_TYPE_INT | MEM_TYPE_UINT)) != 0)
-			return int_to_str0(mem);
-		if (mem->type == MEM_TYPE_DOUBLE)
-			return double_to_str0(mem);
-		if (mem->type == MEM_TYPE_UUID)
-			return uuid_to_str0(mem);
-		return -1;
-	case FIELD_TYPE_DOUBLE:
-		if (mem->type == MEM_TYPE_DOUBLE)
-			return 0;
-		if ((mem->type & (MEM_TYPE_INT | MEM_TYPE_UINT)) != 0)
-			return int_to_double(mem);
-		if (mem->type == MEM_TYPE_STR)
-			return bin_to_str(mem);
-		return -1;
-	case FIELD_TYPE_INTEGER:
-		if ((mem->type & (MEM_TYPE_INT | MEM_TYPE_UINT)) != 0)
-			return 0;
-		if (mem->type == MEM_TYPE_STR)
-			return str_to_int(mem);
-		if (mem->type == MEM_TYPE_DOUBLE)
-			return double_to_int_precise(mem);
-		return -1;
-	case FIELD_TYPE_BOOLEAN:
-		if (mem->type == MEM_TYPE_BOOL)
-			return 0;
-		return -1;
-	case FIELD_TYPE_VARBINARY:
-		if (mem->type == MEM_TYPE_BIN)
-			return 0;
-		if (mem->type == MEM_TYPE_UUID)
-			return uuid_to_bin(mem);
-		return -1;
-	case FIELD_TYPE_NUMBER:
-		if (mem_is_num(mem))
-			return 0;
-		if (mem->type == MEM_TYPE_STR)
-			return mem_to_number(mem);
-		return -1;
-	case FIELD_TYPE_MAP:
-		if (mem->type == MEM_TYPE_MAP)
-			return 0;
-		return -1;
-	case FIELD_TYPE_ARRAY:
-		if (mem->type == MEM_TYPE_ARRAY)
-			return 0;
-		return -1;
-	case FIELD_TYPE_SCALAR:
-		if ((mem->type & (MEM_TYPE_MAP | MEM_TYPE_ARRAY)) != 0)
-			return -1;
-		return 0;
-	case FIELD_TYPE_UUID:
-		if (mem->type == MEM_TYPE_UUID)
-			return 0;
-		if (mem->type == MEM_TYPE_STR)
-			return str_to_uuid(mem);
-		if (mem->type == MEM_TYPE_BIN)
-			return bin_to_uuid(mem);
-		return -1;
-	default:
-		break;
-	}
-	return -1;
-}
-
 int
 mem_cast_implicit_number(struct Mem *mem, enum field_type type)
 {
diff --git a/src/box/sql/mem.h b/src/box/sql/mem.h
index 4f61cbab0..9681fc736 100644
--- a/src/box/sql/mem.h
+++ b/src/box/sql/mem.h
@@ -740,12 +740,6 @@ mem_cast_explicit(struct Mem *mem, enum field_type type);
 int
 mem_cast_implicit(struct Mem *mem, enum field_type type);
 
-/**
- * Convert the given MEM to given type according to legacy implicit cast rules.
- */
-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
diff --git a/src/box/sql/vdbe.h b/src/box/sql/vdbe.h
index 118f1cd83..be112c72d 100644
--- a/src/box/sql/vdbe.h
+++ b/src/box/sql/vdbe.h
@@ -266,7 +266,7 @@ sql *sqlVdbeDb(Vdbe *);
 void sqlVdbeSetSql(Vdbe *, const char *z, int n);
 void sqlVdbeSwap(Vdbe *, Vdbe *);
 VdbeOp *sqlVdbeTakeOpArray(Vdbe *, int *, int *);
-sql_value *sqlVdbeGetBoundValue(Vdbe *, int, u8);
+sql_value *sqlVdbeGetBoundValue(Vdbe *, int);
 char *sqlVdbeExpandSql(Vdbe *, const char *);
 
 /**
diff --git a/src/box/sql/vdbeaux.c b/src/box/sql/vdbeaux.c
index 4a1fdb637..61be7b489 100644
--- a/src/box/sql/vdbeaux.c
+++ b/src/box/sql/vdbeaux.c
@@ -2323,17 +2323,15 @@ sqlVdbeDb(Vdbe * v)
  * The returned value must be freed by the caller using sqlValueFree().
  */
 sql_value *
-sqlVdbeGetBoundValue(Vdbe * v, int iVar, u8 aff)
+sqlVdbeGetBoundValue(struct Vdbe *v, int iVar)
 {
 	assert(iVar > 0);
 	if (v) {
 		Mem *pMem = &v->aVar[iVar - 1];
 		if (!mem_is_null(pMem)) {
 			sql_value *pRet = sqlValueNew(v->db);
-			if (pRet) {
+			if (pRet != NULL)
 				mem_copy(pRet, pMem);
-				mem_cast_implicit_old(pRet, aff);
-			}
 			return pRet;
 		}
 	}
diff --git a/src/box/sql/whereexpr.c b/src/box/sql/whereexpr.c
index fe7329ea8..6849f13ec 100644
--- a/src/box/sql/whereexpr.c
+++ b/src/box/sql/whereexpr.c
@@ -309,9 +309,7 @@ like_optimization_is_valid(Parse *pParse, Expr *pExpr, Expr **ppPrefix,
 	if (op == TK_VARIABLE) {
 		Vdbe *pReprepare = pParse->pReprepare;
 		int iCol = pRight->iColumn;
-		pVal =
-		    sqlVdbeGetBoundValue(pReprepare, iCol,
-					     FIELD_TYPE_SCALAR);
+		pVal = sqlVdbeGetBoundValue(pReprepare, iCol);
 		if (pVal != NULL && mem_is_str(pVal)) {
 			if (mem_as_str0(pVal) == NULL)
 				return -1;

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

* Re: [Tarantool-patches] [PATCH v1 6/7] sql: remove implicit cast from OP_MustBeInt
  2021-07-28 20:51 ` [Tarantool-patches] [PATCH v1 6/7] sql: remove implicit cast from OP_MustBeInt Mergen Imeev via Tarantool-patches
@ 2021-08-05 23:47   ` Mergen Imeev via Tarantool-patches
  0 siblings, 0 replies; 25+ messages in thread
From: Mergen Imeev via Tarantool-patches @ 2021-08-05 23:47 UTC (permalink / raw)
  To: tarantool-patches

I dropped this patch since I decided not to drop "precise" functions.

On 28.07.2021 23:51, Mergen Imeev via Tarantool-patches wrote:
> This patch removes implicit casting from STRING to number in
> OP_MustBeInt opcode.
>
> Part of #4470
> ---
>   src/box/sql/vdbe.c | 3 ++-
>   1 file changed, 2 insertions(+), 1 deletion(-)
>
> diff --git a/src/box/sql/vdbe.c b/src/box/sql/vdbe.c
> index 81551acf3..89e77baee 100644
> --- a/src/box/sql/vdbe.c
> +++ b/src/box/sql/vdbe.c
> @@ -1435,7 +1435,8 @@ case OP_AddImm: {            /* in1 */
>    */
>   case OP_MustBeInt: {            /* jump, in1 */
>   	pIn1 = &aMem[pOp->p1];
> -	if (mem_to_int_precise(pIn1) != 0) {
> +	enum field_type type = FIELD_TYPE_INTEGER;
> +	if (!mem_is_num(pIn1) || mem_cast_implicit_number(pIn1, type) != 0) {
>   		if (pOp->p2 != 0)
>   			goto jump_to_p2;
>   		diag_set(ClientError, ER_SQL_TYPE_MISMATCH,

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

* Re: [Tarantool-patches] [PATCH v1 2/7] sql: remove implicit cast from comparison opcodes
  2021-08-05 23:33     ` Mergen Imeev via Tarantool-patches
@ 2021-08-06  0:13       ` Vladislav Shpilevoy via Tarantool-patches
  0 siblings, 0 replies; 25+ messages in thread
From: Vladislav Shpilevoy via Tarantool-patches @ 2021-08-06  0:13 UTC (permalink / raw)
  To: Mergen Imeev; +Cc: tarantool-patches

>>> diff --git a/src/box/sql/vdbe.c b/src/box/sql/vdbe.c
>>> index d143ce364..62f58def9 100644
>>> --- a/src/box/sql/vdbe.c
>>> +++ b/src/box/sql/vdbe.c
>> <...>
>>
>>> -	switch( pOp->opcode) {
>>> -	case OP_Eq:    res2 = res==0;     break;
>>> -	case OP_Ne:    res2 = res;        break;
>>> -	case OP_Lt:    res2 = res<0;      break;
>>> -	case OP_Le:    res2 = res<=0;     break;
>>> -	case OP_Gt:    res2 = res>0;      break;
>>> -	default:       res2 = res>=0;     break;
>>> +	bool result;
>>> +	switch(pOp->opcode) {
>>
>> 3. It does not look good to have a second switch-case here. But I
>> can't find a better solution right away. We can't wrap this all
>> into a function, because need to make goto abort_due_to_error and
>> goto jump_to_p2 in some places. Up to you if you want to find a
>> way to fix it.
>>
>> As a side note, the code now is incomparably simpler than it was.
>> It is getting actually understable, nice.
>>
> I divided these 6 opcode to two grops: EQ & NE and all others. ALso, I actually
> can avoid branching in any groups using somethins like:
> 
> int op = pOp->opcode;
> bool result = (op == OP_Lt && cmp_res < 0) || (op == OP_Gt && cmp_res > 0) || ...
> But not sure that it will be easier to read. What do you think?

That is still branching - && and || are not better than 'if' usually.
Up to you.

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

* Re: [Tarantool-patches] [PATCH v1 1/7] sql: rework implicit cast fo assignment
  2021-08-05 23:27     ` Mergen Imeev via Tarantool-patches
@ 2021-08-06  0:13       ` Vladislav Shpilevoy via Tarantool-patches
  0 siblings, 0 replies; 25+ messages in thread
From: Vladislav Shpilevoy via Tarantool-patches @ 2021-08-06  0:13 UTC (permalink / raw)
  To: Mergen Imeev; +Cc: tarantool-patches

Thanks for the fixes!

> diff --git a/changelogs/unreleased/gh-4470-implicit-cast-for-assignment.md b/changelogs/unreleased/gh-4470-implicit-cast-for-assignment.md
> new file mode 100644
> index 000000000..6e3e78cf7
> --- /dev/null
> +++ b/changelogs/unreleased/gh-4470-implicit-cast-for-assignment.md
> @@ -0,0 +1,6 @@
> +## feature/sql
> +
> +* Now a numeric value can be cast to another numeric type only if the cast is
> +  precise. In addition, a UUID value cannot be implicitly cast to
> +  STRING/VARBINARY, and a STRING/VARBINARY value cannot be implicitly cast to
> +  a UUID (gh-4470).
> \ No newline at end of file

Please, add an empty line in the end of the file.

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

* Re: [Tarantool-patches] [PATCH v1 5/7] sql: remove implicit cast from OP_MakeRecord
  2021-08-05 23:43     ` Mergen Imeev via Tarantool-patches
@ 2021-08-06  0:13       ` Vladislav Shpilevoy via Tarantool-patches
  0 siblings, 0 replies; 25+ messages in thread
From: Vladislav Shpilevoy via Tarantool-patches @ 2021-08-06  0:13 UTC (permalink / raw)
  To: Mergen Imeev; +Cc: tarantool-patches

Thanks for the patch!

> diff --git a/changelogs/unreleased/gh-4230-implicit-cast-for-comparison.md b/changelogs/unreleased/gh-4230-implicit-cast-for-comparison.md
> new file mode 100644
> index 000000000..66103f461
> --- /dev/null
> +++ b/changelogs/unreleased/gh-4230-implicit-cast-for-comparison.md
> @@ -0,0 +1,6 @@
> +## feature/sql
> +
> +* Now any number can be compared to any other number, and values of any scalar
> +  type can be compared to any other value of the same type. A value of a
> +  non-numeric scalar type cannot be compared with a value of any other scalar
> +  type (gh-4230).
> \ No newline at end of file

Please, add an empty line in the end of the file.

After this and the other similar comment in other commit
the patchset LGTM.

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

end of thread, other threads:[~2021-08-06  0:14 UTC | newest]

Thread overview: 25+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2021-07-28 20:51 [Tarantool-patches] [PATCH v1 0/7] Rework implicit cast Mergen Imeev via Tarantool-patches
2021-07-28 20:51 ` [Tarantool-patches] [PATCH v1 1/7] sql: rework implicit cast fo assignment Mergen Imeev via Tarantool-patches
2021-07-30 21:55   ` Vladislav Shpilevoy via Tarantool-patches
2021-08-04 22:21     ` Vladislav Shpilevoy via Tarantool-patches
2021-08-05 23:27     ` Mergen Imeev via Tarantool-patches
2021-08-06  0:13       ` Vladislav Shpilevoy via Tarantool-patches
2021-07-28 20:51 ` [Tarantool-patches] [PATCH v1 2/7] sql: remove implicit cast from comparison opcodes Mergen Imeev via Tarantool-patches
2021-08-04 22:24   ` Vladislav Shpilevoy via Tarantool-patches
2021-08-05 23:33     ` Mergen Imeev via Tarantool-patches
2021-08-06  0:13       ` Vladislav Shpilevoy via Tarantool-patches
2021-07-28 20:51 ` [Tarantool-patches] [PATCH v1 3/7] sql: rework OP_Seek* opcodes Mergen Imeev via Tarantool-patches
2021-08-04 22:25   ` Vladislav Shpilevoy via Tarantool-patches
2021-08-05 23:40     ` Mergen Imeev via Tarantool-patches
2021-07-28 20:51 ` [Tarantool-patches] [PATCH v1 4/7] sql: remove unnecessary calls of OP_ApplyType Mergen Imeev via Tarantool-patches
2021-08-04 22:26   ` Vladislav Shpilevoy via Tarantool-patches
2021-08-05 23:41     ` Mergen Imeev via Tarantool-patches
2021-07-28 20:51 ` [Tarantool-patches] [PATCH v1 5/7] sql: remove implicit cast from OP_MakeRecord Mergen Imeev via Tarantool-patches
2021-08-04 22:27   ` Vladislav Shpilevoy via Tarantool-patches
2021-08-05 23:43     ` Mergen Imeev via Tarantool-patches
2021-08-06  0:13       ` Vladislav Shpilevoy via Tarantool-patches
2021-07-28 20:51 ` [Tarantool-patches] [PATCH v1 6/7] sql: remove implicit cast from OP_MustBeInt Mergen Imeev via Tarantool-patches
2021-08-05 23:47   ` Mergen Imeev via Tarantool-patches
2021-07-28 20:51 ` [Tarantool-patches] [PATCH v1 7/7] sql: remove unused MEM cast functions Mergen Imeev via Tarantool-patches
2021-08-04 22:27   ` Vladislav Shpilevoy via Tarantool-patches
2021-08-05 23:45     ` Mergen Imeev via Tarantool-patches

Tarantool development patches archive

This inbox may be cloned and mirrored by anyone:

	git clone --mirror https://lists.tarantool.org/tarantool-patches/0 tarantool-patches/git/0.git

	# If you have public-inbox 1.1+ installed, you may
	# initialize and index your mirror using the following commands:
	public-inbox-init -V2 tarantool-patches tarantool-patches/ https://lists.tarantool.org/tarantool-patches \
		tarantool-patches@dev.tarantool.org.
	public-inbox-index tarantool-patches

Example config snippet for mirrors.


AGPL code for this site: git clone https://public-inbox.org/public-inbox.git