From mboxrd@z Thu Jan 1 00:00:00 1970 Return-Path: Received: from smtp34.i.mail.ru (smtp34.i.mail.ru [94.100.177.94]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by dev.tarantool.org (Postfix) with ESMTPS id 3C51443E8AE for ; Fri, 27 Mar 2020 14:33:45 +0300 (MSK) From: imeevma@tarantool.org Date: Fri, 27 Mar 2020 14:33:43 +0300 Message-Id: <3068e777d61da1f73764c68bc94074fe01119af9.1585308644.git.imeevma@gmail.com> In-Reply-To: References: Subject: [Tarantool-patches] [PATCH v4 3/3] sql: add '\0' to the BLOB when it is cast to INTEGER List-Id: Tarantool development patches List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , To: korablev@tarantool.org, tsafin@tarantool.org, tarantool-patches@dev.tarantool.org Prior to this patch, due to the absence of the '\0' character at the end of the BLOB, it was possible to get an error or incorrect result when using CAST() from BLOB to INTEGER or UNSIGNED. This has now been fixed, but the maximum length of a BLOB that could be cast to INTEGER or UNSIGNED was limited to 12287 bytes. Examples of wrong CAST() from BLOB to INTEGER: CREATE TABLE t (i INT PRIMARY KEY, a VARBINARY, b INT, c INT); INSERT INTO t VALUES (1, X'33', 0x33, 0x00), (2, X'34', 0x41, 0); Example of wrong result: SELECT CAST(a AS INTEGER) FROM t WHERE i = 1; Result: 33 Example of error during CAST(): SELECT CAST(a AS INTEGER) FROM t WHERE i = 2; Result: 'Type mismatch: can not convert varbinary to integer' Closes #4766 --- src/box/sql/util.c | 17 ++++++--- .../gh-4766-wrong-cast-from-blob-to-int.test.lua | 42 +++++++++++++++++++++- 2 files changed, 53 insertions(+), 6 deletions(-) diff --git a/src/box/sql/util.c b/src/box/sql/util.c index f908e9c..c556b98 100644 --- a/src/box/sql/util.c +++ b/src/box/sql/util.c @@ -467,14 +467,21 @@ sql_atoi64(const char *z, int64_t *val, bool *is_neg, int length) if (*z == '-') *is_neg = true; + /* + * BLOB data may not end with '\0'. Because of this, the + * strtoll() and strtoull() functions may return an + * incorrect result. To fix this, let's copy the value for + * decoding into static memory and add '\0' to it. + */ + if (length > SMALL_STATIC_SIZE - 1) + return -1; + const char *str_value = tt_cstr(z, length); char *end = NULL; errno = 0; - if (*z == '-') { - *is_neg = true; - *val = strtoll(z, &end, 10); + if (*is_neg) { + *val = strtoll(str_value, &end, 10); } else { - *is_neg = false; - uint64_t u_val = strtoull(z, &end, 10); + uint64_t u_val = strtoull(str_value, &end, 10); *val = u_val; } /* Overflow and underflow errors. */ diff --git a/test/sql-tap/gh-4766-wrong-cast-from-blob-to-int.test.lua b/test/sql-tap/gh-4766-wrong-cast-from-blob-to-int.test.lua index 559e33d..37aae87 100755 --- a/test/sql-tap/gh-4766-wrong-cast-from-blob-to-int.test.lua +++ b/test/sql-tap/gh-4766-wrong-cast-from-blob-to-int.test.lua @@ -1,6 +1,6 @@ #!/usr/bin/env tarantool test = require("sqltester") -test:plan(12) +test:plan(15) -- -- Make sure that STRING or BLOB that contains DOUBLE value cannot @@ -107,4 +107,44 @@ test:do_catchsql_test( 1, "Type mismatch: can not convert varbinary to integer" }) +-- +-- Make sure that a blob as part of a tuple can be cast to NUMBER, +-- INTEGER and UNSIGNED. Prior to this patch, an error could +-- appear due to the absence of '\0' at the end of the BLOB. +-- +test:do_execsql_test( + "gh-4766-13", + [[ + CREATE TABLE t1 (a VARBINARY PRIMARY KEY); + INSERT INTO t1 VALUES (X'33'), (X'372020202020'); + SELECT a, CAST(a AS NUMBER), CAST(a AS INTEGER), CAST(a AS UNSIGNED) FROM t1; + ]], { + '3', 3, 3, 3, '7 ', 7, 7, 7 + }) + +-- +-- Make sure that BLOB longer than 12287 bytes cannot be cast to +-- INTEGER. +-- +long_str = string.rep('0', 12284) +test:do_execsql_test( + "gh-4766-14", + "SELECT CAST('" .. long_str .. "123'" .. " AS INTEGER);", { + 123 + }) + + +test:do_catchsql_test( + "gh-4766-15", + "SELECT CAST('" .. long_str .. "1234'" .. " AS INTEGER);", { + 1, "Type mismatch: can not convert 000000000000000000000000000000000" .. + "0000000000000000000000000000000000000000000000000000000000000000000" .. + "0000000000000000000000000000000000000000000000000000000000000000000" .. + "0000000000000000000000000000000000000000000000000000000000000000000" .. + "0000000000000000000000000000000000000000000000000000000000000000000" .. + "0000000000000000000000000000000000000000000000000000000000000000000" .. + "0000000000000000000000000000000000000000000000000000000000000000000" .. + "000000000000000000000000000000000000000000000" + }) + test:finish_test() -- 2.7.4