diff --git a/src/box/sql/expr.c b/src/box/sql/expr.c index 838fbd21ad17e261a24352dc9d5dbd876801479b..88c4a9f53784777af527489743b4251dc3771f81 100644 --- a/src/box/sql/expr.c +++ b/src/box/sql/expr.c @@ -4081,16 +4081,48 @@ sqlExprCodeTarget(Parse * pParse, Expr * pExpr, int target) testcase(i == 31); constMask |= MASKBIT32(i); } - if ((pDef->funcFlags & SQL_FUNC_NEEDCOLL) != - 0 && coll == NULL) { - bool unused; - uint32_t id; + } + /* + * Function arguments may have different + * collations. The following code + * checks if they are compatible and + * finds the collation to be used. This + * is done using ANSI rules from + * collations_check_compatibility(). + */ + if ((pDef->funcFlags & SQL_FUNC_NEEDCOLL) != 0) { + struct coll *unused = NULL; + uint32_t curr_id = COLL_NONE; + bool is_curr_forced = false; + + uint32_t next_id = COLL_NONE; + bool is_next_forced = false; + + if (sql_expr_coll(pParse, pFarg->a[0].pExpr, + &is_curr_forced, &curr_id, + &unused) != 0) + return 0; + + for (int j = 1; j < nFarg; j++) { if (sql_expr_coll(pParse, - pFarg->a[i].pExpr, - &unused, &id, - &coll) != 0) + pFarg->a[j].pExpr, + &is_next_forced, + &next_id, + &unused) != 0) return 0; + + if (collations_check_compatibility( + curr_id, is_curr_forced, + next_id, is_next_forced, + &curr_id) != 0) { + pParse->is_aborted = true; + return 0; + } + is_curr_forced = curr_id == next_id ? + is_next_forced : + is_curr_forced; } + coll = coll_by_id(curr_id)->coll; } if (pFarg) { if (constMask) { diff --git a/src/box/sql/sqlInt.h b/src/box/sql/sqlInt.h index 72bd4ee0fe7fb0e04d86a10fb5d76f82a9b6dc98..2c07492c143a860e33026ce37dc6a481be45e74a 100644 --- a/src/box/sql/sqlInt.h +++ b/src/box/sql/sqlInt.h @@ -1661,7 +1661,13 @@ struct FuncDestructor { #define SQL_FUNC_LIKE 0x0004 /* Candidate for the LIKE optimization */ #define SQL_FUNC_CASE 0x0008 /* Case-sensitive LIKE-type function */ #define SQL_FUNC_EPHEM 0x0010 /* Ephemeral. Delete with VDBE */ -#define SQL_FUNC_NEEDCOLL 0x0020 /* sqlGetFuncCollSeq() might be called */ +#define SQL_FUNC_NEEDCOLL 0x0020 /* sqlGetFuncCollSeq() might be called. + * The flag is set when the collation + * of function arguments should be + * determined, using rules in + * collations_check_compatibility() + * function. + */ #define SQL_FUNC_LENGTH 0x0040 /* Built-in length() function */ #define SQL_FUNC_TYPEOF 0x0080 /* Built-in typeof() function */ #define SQL_FUNC_COUNT 0x0100 /* Built-in count(*) aggregate */ diff --git a/test/sql-tap/func5.test.lua b/test/sql-tap/func5.test.lua index bb9e11a04960744a1915e22f2c8f3762a73ced34..337bd3b9498b10e387f1534c2a891aed8dcf07a4 100755 --- a/test/sql-tap/func5.test.lua +++ b/test/sql-tap/func5.test.lua @@ -1,6 +1,6 @@ #!/usr/bin/env tarantool test = require("sqltester") -test:plan(5) +test:plan(15) --!./tcltestrunner.lua -- 2010 August 27 @@ -98,5 +98,135 @@ test:do_execsql_test( -- </func5-2.2> }) +-- The following tests ensures that max() and min() functions +-- raise error if argument's collations are incompatible. + +test:do_catchsql_test( + "func-5-3.1", + [[ + SELECT max('a' COLLATE "unicode", 'A' COLLATE "unicode_ci"); + ]], + { + -- <func5-3.1> + 1, "Illegal mix of collations" + -- </func5-3.1> + } +) + +test:do_catchsql_test( + "func-5-3.2", + [[ + CREATE TABLE test1 (s1 VARCHAR(5) PRIMARY KEY COLLATE "unicode"); + CREATE TABLE test2 (s2 VARCHAR(5) PRIMARY KEY COLLATE "unicode_ci"); + INSERT INTO test1 VALUES ('a'); + INSERT INTO test2 VALUES ('a'); + SELECT max(s1, s2) FROM test1 JOIN test2; + ]], + { + -- <func5-3.2> + 1, "Illegal mix of collations" + -- </func5-3.2> + } +) + +test:do_catchsql_test( + "func-5-3.3", + [[ + SELECT max ('abc', 'asd' COLLATE "binary", 'abc' COLLATE "unicode") + ]], + { + -- <func5-3.3> + 1, "Illegal mix of collations" + -- </func5-3.3> + } +) + +test:do_execsql_test( + "func-5-3.4", + [[ + SELECT max (s1, 'asd' COLLATE "binary", s2) FROM test1 JOIN test2; + ]], { + -- <func5-3.4> + "asd" + -- </func5-3.4> + } +) + +test:do_catchsql_test( + "func-5.3.5", + [[ + CREATE TABLE test3 (s3 VARCHAR(5) PRIMARY KEY COLLATE "unicode"); + CREATE TABLE test4 (s4 VARCHAR(5) PRIMARY KEY COLLATE "unicode"); + CREATE TABLE test5 (s5 VARCHAR(5) PRIMARY KEY COLLATE "binary"); + INSERT INTO test3 VALUES ('a'); + INSERT INTO test4 VALUES ('a'); + INSERT INTO test5 VALUES ('a'); + SELECT max(s3, s4, s5) FROM test3 JOIN test4 JOIN test5; + ]], + { + -- <func5-3.5> + 1, "Illegal mix of collations" + -- </func5-3.5> + } +) + +test:do_catchsql_test( + "func-5-3.6", + [[ + SELECT min('a' COLLATE "unicode", 'A' COLLATE "unicode_ci"); + ]], + { + -- <func5-3.6> + 1, "Illegal mix of collations" + -- </func5-3.6> + } +) + +test:do_catchsql_test( + "func-5-3.7", + [[ + SELECT min(s1, s2) FROM test1 JOIN test2; + ]], + { + -- <func5-3.7> + 1, "Illegal mix of collations" + -- </func5-3.7> + } +) + +test:do_catchsql_test( + "func-5-3.8", + [[ + SELECT min ('abc', 'asd' COLLATE "binary", 'abc' COLLATE "unicode") + ]], + { + -- <func5-3.8> + 1, "Illegal mix of collations" + -- </func5-3.8> + } +) + +test:do_execsql_test( + "func-5-3.9", + [[ + SELECT min (s1, 'asd' COLLATE "binary", s2) FROM test1 JOIN test2; + ]], { + -- <func5-3.9> + "a" + -- </func5-3.9> + } +) + +test:do_catchsql_test( + "func-5.3.10", + [[ + SELECT min(s3, s4, s5) FROM test3 JOIN test4 JOIN test5; + ]], + { + -- <func5-3.10> + 1, "Illegal mix of collations" + -- <func5-3.10> + } +) test:finish_test()