[Tarantool-patches] [PATCH v1 1/7] sql: rework implicit cast fo assignment
imeevma at tarantool.org
imeevma at tarantool.org
Wed Jul 28 23:51:08 MSK 2021
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
More information about the Tarantool-patches
mailing list