From f6989d1767ade3486af35246a8ad5be50507ca10 Mon Sep 17 00:00:00 2001 From: Alexander Barkov Date: Tue, 2 Jul 2024 17:59:59 +0400 Subject: [PATCH] MDEV-10865 COLLATE keyword doesn't work in PREPARE query Fixing applying the COLLATE clause to a parameter caused an error error: COLLATION '...' is not valid for CHARACTER SET 'binary' Fix: - Changing the collation derivation for a non-prepared Item_param to DERIVATION_IGNORABLE. - Allowing to apply any COLLATE clause to expressions with DERIVATION_IGNORABLE. This includes: 1. A non-prepared Item_param 2. An explicit NULL 3. Expressions derived from #1 and #2 For example: SELECT ? COLLATE utf8mb_unicode_ci; SELECT NULL COLLATE utf8mb_unicode_ci; SELECT CONCAT(?) COLLATE utf8mb_unicode_ci; SELECT CONCAT(NULL) COLLATE utf8mb_unicode_ci - Additional change: preserving the collation of an expression when the expression gets assigned to a PS parameter and evaluates to SQL NULL. Before this change, the collation of the parameter was erroneously set to &my_charset_binary. - Additional change: removing the multiplication to mbmaxlen from the fix_char_length_ulonglong() argument, because the multiplication already happens inside fix_char_length_ulonglong(). This fixes a too large column size created for a COLLATE clause. --- mysql-test/main/ctype_collate_context.result | 100 +++++++++++++++++++ mysql-test/main/ctype_collate_context.test | 100 +++++++++++++++++++ mysql-test/main/ctype_utf32.result | 85 ++++++++++++++++ mysql-test/main/ctype_utf32.test | 76 ++++++++++++++ mysql-test/main/ctype_utf8mb4.result | 82 +++++++++++---- mysql-test/main/ctype_utf8mb4.test | 71 +++++++++---- sql/item.cc | 10 +- sql/item.h | 27 ++++- sql/item_strfunc.cc | 36 +++++-- sql/sql_type.cc | 82 +++++++++++++++ sql/sql_type.h | 6 ++ 11 files changed, 626 insertions(+), 49 deletions(-) diff --git a/mysql-test/main/ctype_collate_context.result b/mysql-test/main/ctype_collate_context.result index c924f11d9e2..4a6c86f2d9b 100644 --- a/mysql-test/main/ctype_collate_context.result +++ b/mysql-test/main/ctype_collate_context.result @@ -3716,3 +3716,103 @@ DROP PROCEDURE p; # # End of 10.9 tests # +# +# Start of 10.11 tests +# +# +# MDEV-10865 COLLATE keyword doesn't work in PREPARE query +# +SET NAMES utf8mb4; +# +# A context collation and an explicit NULL +# +SELECT NULL COLLATE uca1400_ai_ci; +ERROR HY000: 'NULL' is not allowed in this context +SELECT CONCAT(NULL) COLLATE uca1400_ai_ci; +ERROR HY000: 'NULL' is not allowed in this context +PREPARE stmt FROM 'CREATE TABLE t1 AS SELECT NULL COLLATE uca1400_ai_ci AS c1'; +ERROR HY000: 'NULL' is not allowed in this context +PREPARE stmt FROM 'CREATE TABLE t1 AS SELECT CONCAT(NULL) COLLATE uca1400_ai_ci AS c1'; +ERROR HY000: 'NULL' is not allowed in this context +# +# A context collation and a parameter bound to NULL +# +PREPARE stmt FROM 'CREATE TABLE t1 AS SELECT ? COLLATE uca1400_ai_ci AS c1'; +EXECUTE stmt USING NULL; +ERROR HY000: 'NULL' is not allowed in this context +EXECUTE stmt USING CONCAT(NULL); +ERROR HY000: 'NULL' is not allowed in this context +EXECUTE stmt USING NULL COLLATE uca1400_ai_ci; +ERROR HY000: 'NULL' is not allowed in this context +EXECUTE stmt USING CONCAT(NULL) COLLATE uca1400_ai_ci; +ERROR HY000: 'NULL' is not allowed in this context +# +# A context collation and CONVERT(NULL USING ...) +# +EXECUTE stmt USING CONVERT(NULL USING utf8mb4); +SHOW CREATE TABLE t1; +Table Create Table +t1 CREATE TABLE `t1` ( + `c1` char(0) CHARACTER SET utf8mb4 COLLATE utf8mb4_uca1400_ai_ci DEFAULT NULL +) ENGINE=MyISAM DEFAULT CHARSET=latin1 COLLATE=latin1_swedish_ci +DROP TABLE t1; +EXECUTE stmt USING CONVERT(NULL USING utf8mb4) COLLATE uca1400_ai_ci; +SHOW CREATE TABLE t1; +Table Create Table +t1 CREATE TABLE `t1` ( + `c1` char(0) CHARACTER SET utf8mb4 COLLATE utf8mb4_uca1400_ai_ci DEFAULT NULL +) ENGINE=MyISAM DEFAULT CHARSET=latin1 COLLATE=latin1_swedish_ci +DROP TABLE t1; +EXECUTE stmt USING CONVERT(NULL USING binary); +ERROR 42000: COLLATION 'uca1400_ai_ci' is not valid for CHARACTER SET 'binary' +EXECUTE stmt USING CONVERT(NULL USING latin1); +ERROR 42000: COLLATION 'uca1400_ai_ci' is not valid for CHARACTER SET 'latin1' +# +# A context collation and an expression with a parameter +# whose character does not get resolved when bound to a not-NULL value +# +PREPARE stmt FROM 'CREATE TABLE t1 AS SELECT LEFT(NULL,?) COLLATE uca1400_ai_ci AS c1'; +EXECUTE stmt USING NULL; +ERROR HY000: 'NULL' is not allowed in this context +# +# A context collation and an expression with a parameter +# whose character set gets resolved when bound to a not-NULL value +# +PREPARE stmt FROM 'CREATE TABLE t1 AS SELECT ? COLLATE uca1400_ai_ci AS c1'; +EXECUTE stmt USING 1; +SHOW CREATE TABLE t1; +Table Create Table +t1 CREATE TABLE `t1` ( + `c1` varchar(1) CHARACTER SET utf8mb4 COLLATE utf8mb4_uca1400_ai_ci DEFAULT NULL +) ENGINE=MyISAM DEFAULT CHARSET=latin1 COLLATE=latin1_swedish_ci +DROP TABLE t1; +PREPARE stmt FROM 'CREATE TABLE t1 AS SELECT ? COLLATE uca1400_ai_ci AS c1'; +EXECUTE stmt USING _binary'test'; +ERROR 42000: COLLATION 'uca1400_ai_ci' is not valid for CHARACTER SET 'binary' +PREPARE stmt FROM 'CREATE TABLE t1 AS SELECT ? COLLATE uca1400_ai_ci AS c1'; +EXECUTE stmt USING 'test'; +SHOW CREATE TABLE t1; +Table Create Table +t1 CREATE TABLE `t1` ( + `c1` varchar(4) CHARACTER SET utf8mb4 COLLATE utf8mb4_uca1400_ai_ci DEFAULT NULL +) ENGINE=MyISAM DEFAULT CHARSET=latin1 COLLATE=latin1_swedish_ci +DROP TABLE t1; +PREPARE stmt FROM 'CREATE TABLE t1 AS SELECT ? COLLATE uca1400_ai_ci AS c1'; +EXECUTE stmt USING 'test' COLLATE utf8mb4_bin; +SHOW CREATE TABLE t1; +Table Create Table +t1 CREATE TABLE `t1` ( + `c1` varchar(4) CHARACTER SET utf8mb4 COLLATE utf8mb4_uca1400_ai_ci DEFAULT NULL +) ENGINE=MyISAM DEFAULT CHARSET=latin1 COLLATE=latin1_swedish_ci +DROP TABLE t1; +PREPARE stmt FROM 'CREATE TABLE t1 AS SELECT ? COLLATE uca1400_ai_ci AS c1'; +EXECUTE stmt USING _latin1'test' COLLATE latin1_bin; +SHOW CREATE TABLE t1; +Table Create Table +t1 CREATE TABLE `t1` ( + `c1` varchar(4) CHARACTER SET utf8mb4 COLLATE utf8mb4_uca1400_ai_ci DEFAULT NULL +) ENGINE=MyISAM DEFAULT CHARSET=latin1 COLLATE=latin1_swedish_ci +DROP TABLE t1; +# +# End of 10.11 tests +# diff --git a/mysql-test/main/ctype_collate_context.test b/mysql-test/main/ctype_collate_context.test index 95a46f2d830..168f5234cf0 100644 --- a/mysql-test/main/ctype_collate_context.test +++ b/mysql-test/main/ctype_collate_context.test @@ -401,3 +401,103 @@ DROP PROCEDURE p; --echo # --echo # End of 10.9 tests --echo # + +--echo # +--echo # Start of 10.11 tests +--echo # + +--echo # +--echo # MDEV-10865 COLLATE keyword doesn't work in PREPARE query +--echo # + +SET NAMES utf8mb4; + +--echo # +--echo # A context collation and an explicit NULL +--echo # + +--error ER_NOT_ALLOWED_IN_THIS_CONTEXT +SELECT NULL COLLATE uca1400_ai_ci; +--error ER_NOT_ALLOWED_IN_THIS_CONTEXT +SELECT CONCAT(NULL) COLLATE uca1400_ai_ci; +--error ER_NOT_ALLOWED_IN_THIS_CONTEXT +PREPARE stmt FROM 'CREATE TABLE t1 AS SELECT NULL COLLATE uca1400_ai_ci AS c1'; +--error ER_NOT_ALLOWED_IN_THIS_CONTEXT +PREPARE stmt FROM 'CREATE TABLE t1 AS SELECT CONCAT(NULL) COLLATE uca1400_ai_ci AS c1'; + + +--echo # +--echo # A context collation and a parameter bound to NULL +--echo # + +PREPARE stmt FROM 'CREATE TABLE t1 AS SELECT ? COLLATE uca1400_ai_ci AS c1'; +--error ER_NOT_ALLOWED_IN_THIS_CONTEXT +EXECUTE stmt USING NULL; +--error ER_NOT_ALLOWED_IN_THIS_CONTEXT +EXECUTE stmt USING CONCAT(NULL); +--error ER_NOT_ALLOWED_IN_THIS_CONTEXT +EXECUTE stmt USING NULL COLLATE uca1400_ai_ci; +--error ER_NOT_ALLOWED_IN_THIS_CONTEXT +EXECUTE stmt USING CONCAT(NULL) COLLATE uca1400_ai_ci; + +--echo # +--echo # A context collation and CONVERT(NULL USING ...) +--echo # + +EXECUTE stmt USING CONVERT(NULL USING utf8mb4); +SHOW CREATE TABLE t1; +DROP TABLE t1; + +EXECUTE stmt USING CONVERT(NULL USING utf8mb4) COLLATE uca1400_ai_ci; +SHOW CREATE TABLE t1; +DROP TABLE t1; + +--error ER_COLLATION_CHARSET_MISMATCH +EXECUTE stmt USING CONVERT(NULL USING binary); + +--error ER_COLLATION_CHARSET_MISMATCH +EXECUTE stmt USING CONVERT(NULL USING latin1); + +--echo # +--echo # A context collation and an expression with a parameter +--echo # whose character does not get resolved when bound to a not-NULL value +--echo # + +PREPARE stmt FROM 'CREATE TABLE t1 AS SELECT LEFT(NULL,?) COLLATE uca1400_ai_ci AS c1'; +--error ER_NOT_ALLOWED_IN_THIS_CONTEXT +EXECUTE stmt USING NULL; + + +--echo # +--echo # A context collation and an expression with a parameter +--echo # whose character set gets resolved when bound to a not-NULL value +--echo # + +PREPARE stmt FROM 'CREATE TABLE t1 AS SELECT ? COLLATE uca1400_ai_ci AS c1'; +EXECUTE stmt USING 1; +SHOW CREATE TABLE t1; +DROP TABLE t1; + +PREPARE stmt FROM 'CREATE TABLE t1 AS SELECT ? COLLATE uca1400_ai_ci AS c1'; +--error ER_COLLATION_CHARSET_MISMATCH +EXECUTE stmt USING _binary'test'; + +PREPARE stmt FROM 'CREATE TABLE t1 AS SELECT ? COLLATE uca1400_ai_ci AS c1'; +EXECUTE stmt USING 'test'; +SHOW CREATE TABLE t1; +DROP TABLE t1; + +PREPARE stmt FROM 'CREATE TABLE t1 AS SELECT ? COLLATE uca1400_ai_ci AS c1'; +EXECUTE stmt USING 'test' COLLATE utf8mb4_bin; +SHOW CREATE TABLE t1; +DROP TABLE t1; + +PREPARE stmt FROM 'CREATE TABLE t1 AS SELECT ? COLLATE uca1400_ai_ci AS c1'; +EXECUTE stmt USING _latin1'test' COLLATE latin1_bin; +SHOW CREATE TABLE t1; +DROP TABLE t1; + + +--echo # +--echo # End of 10.11 tests +--echo # diff --git a/mysql-test/main/ctype_utf32.result b/mysql-test/main/ctype_utf32.result index f9523a783ea..75ec9019da7 100644 --- a/mysql-test/main/ctype_utf32.result +++ b/mysql-test/main/ctype_utf32.result @@ -3024,3 +3024,88 @@ HEX(DATE_FORMAT(TIME'11:22:33',@format)) # # End of 10.4 tests # +# +# Start of 10.11 tests +# +# +# MDEV-10865 COLLATE keyword doesn't work in PREPARE query +# +# +# The collation is not applicable to the PS parameter +# +SET NAMES utf8mb4; +CREATE TABLE t1 ( +c1 varchar(500) COLLATE utf32_unicode_ci NOT NULL +); +INSERT INTO t1 VALUES ('jj'); +PREPARE stmt FROM 'SELECT * FROM t1 WHERE c1 LIKE ? COLLATE utf32_unicode_ci'; +EXECUTE stmt USING 'jj'; +ERROR 42000: COLLATION 'utf32_unicode_ci' is not valid for CHARACTER SET 'utf8mb4' +DROP TABLE t1; +PREPARE stmt FROM 'CREATE TABLE t1 AS SELECT ? COLLATE utf32_unicode_ci AS c1'; +EXECUTE stmt USING 'test'; +ERROR 42000: COLLATION 'utf32_unicode_ci' is not valid for CHARACTER SET 'utf8mb4' +PREPARE stmt FROM 'CREATE TABLE t1 AS SELECT CONCAT(?) COLLATE utf32_unicode_ci AS c1'; +EXECUTE stmt USING 'test'; +ERROR 42000: COLLATION 'utf32_unicode_ci' is not valid for CHARACTER SET 'utf8mb4' +CREATE TABLE t1 AS SELECT NULL COLLATE utf32_unicode_ci AS c1; +SHOW CREATE TABLE t1; +Table Create Table +t1 CREATE TABLE `t1` ( + `c1` char(0) CHARACTER SET utf32 COLLATE utf32_unicode_ci DEFAULT NULL +) ENGINE=MyISAM DEFAULT CHARSET=latin1 COLLATE=latin1_swedish_ci +DROP TABLE t1; +CREATE TABLE t1 AS SELECT CONCAT(NULL) COLLATE utf32_unicode_ci AS c1; +SHOW CREATE TABLE t1; +Table Create Table +t1 CREATE TABLE `t1` ( + `c1` char(0) CHARACTER SET utf32 COLLATE utf32_unicode_ci DEFAULT NULL +) ENGINE=MyISAM DEFAULT CHARSET=latin1 COLLATE=latin1_swedish_ci +DROP TABLE t1; +# +# The collation is applicable to the PS parameter +# +SET NAMES utf8mb4, collation_connection=utf32_general_ci; +CREATE TABLE t1 ( +c1 varchar(500) COLLATE utf32_unicode_ci NOT NULL +); +INSERT INTO t1 VALUES ('jj'); +PREPARE stmt FROM 'SELECT * FROM t1 WHERE c1 LIKE ? COLLATE utf32_unicode_ci'; +EXECUTE stmt USING 'jj'; +c1 +jj +DROP TABLE t1; +PREPARE stmt FROM 'CREATE TABLE t1 AS SELECT ? COLLATE utf32_unicode_ci AS c1'; +EXECUTE stmt USING 'test'; +SHOW CREATE TABLE t1; +Table Create Table +t1 CREATE TABLE `t1` ( + `c1` varchar(4) CHARACTER SET utf32 COLLATE utf32_unicode_ci DEFAULT NULL +) ENGINE=MyISAM DEFAULT CHARSET=latin1 COLLATE=latin1_swedish_ci +DROP TABLE t1; +PREPARE stmt FROM 'CREATE TABLE t1 AS SELECT CONCAT(?) COLLATE utf32_unicode_ci AS c1'; +EXECUTE stmt USING 'test'; +SHOW CREATE TABLE t1; +Table Create Table +t1 CREATE TABLE `t1` ( + `c1` varchar(4) CHARACTER SET utf32 COLLATE utf32_unicode_ci DEFAULT NULL +) ENGINE=MyISAM DEFAULT CHARSET=latin1 COLLATE=latin1_swedish_ci +DROP TABLE t1; +CREATE TABLE t1 AS SELECT NULL COLLATE utf32_unicode_ci AS c1; +SHOW CREATE TABLE t1; +Table Create Table +t1 CREATE TABLE `t1` ( + `c1` char(0) CHARACTER SET utf32 COLLATE utf32_unicode_ci DEFAULT NULL +) ENGINE=MyISAM DEFAULT CHARSET=latin1 COLLATE=latin1_swedish_ci +DROP TABLE t1; +CREATE TABLE t1 AS SELECT CONCAT(NULL) COLLATE utf32_unicode_ci AS c1; +SHOW CREATE TABLE t1; +Table Create Table +t1 CREATE TABLE `t1` ( + `c1` char(0) CHARACTER SET utf32 COLLATE utf32_unicode_ci DEFAULT NULL +) ENGINE=MyISAM DEFAULT CHARSET=latin1 COLLATE=latin1_swedish_ci +DROP TABLE t1; +SET NAMES utf8mb4; +# +# End of 10.11 tests +# diff --git a/mysql-test/main/ctype_utf32.test b/mysql-test/main/ctype_utf32.test index bcbc3b14691..e5310dd5c7f 100644 --- a/mysql-test/main/ctype_utf32.test +++ b/mysql-test/main/ctype_utf32.test @@ -1167,4 +1167,80 @@ SELECT HEX(DATE_FORMAT(TIME'11:22:33',@format)); --echo # End of 10.4 tests --echo # +--echo # +--echo # Start of 10.11 tests +--echo # + +--echo # +--echo # MDEV-10865 COLLATE keyword doesn't work in PREPARE query +--echo # + +--echo # +--echo # The collation is not applicable to the PS parameter +--echo # + +SET NAMES utf8mb4; +CREATE TABLE t1 ( + c1 varchar(500) COLLATE utf32_unicode_ci NOT NULL +); +INSERT INTO t1 VALUES ('jj'); +PREPARE stmt FROM 'SELECT * FROM t1 WHERE c1 LIKE ? COLLATE utf32_unicode_ci'; +--error ER_COLLATION_CHARSET_MISMATCH +EXECUTE stmt USING 'jj'; +DROP TABLE t1; + +PREPARE stmt FROM 'CREATE TABLE t1 AS SELECT ? COLLATE utf32_unicode_ci AS c1'; +--error ER_COLLATION_CHARSET_MISMATCH +EXECUTE stmt USING 'test'; + +PREPARE stmt FROM 'CREATE TABLE t1 AS SELECT CONCAT(?) COLLATE utf32_unicode_ci AS c1'; +--error ER_COLLATION_CHARSET_MISMATCH +EXECUTE stmt USING 'test'; + +CREATE TABLE t1 AS SELECT NULL COLLATE utf32_unicode_ci AS c1; +SHOW CREATE TABLE t1; +DROP TABLE t1; + +CREATE TABLE t1 AS SELECT CONCAT(NULL) COLLATE utf32_unicode_ci AS c1; +SHOW CREATE TABLE t1; +DROP TABLE t1; + + +--echo # +--echo # The collation is applicable to the PS parameter +--echo # + +SET NAMES utf8mb4, collation_connection=utf32_general_ci; +CREATE TABLE t1 ( + c1 varchar(500) COLLATE utf32_unicode_ci NOT NULL +); +INSERT INTO t1 VALUES ('jj'); +PREPARE stmt FROM 'SELECT * FROM t1 WHERE c1 LIKE ? COLLATE utf32_unicode_ci'; +EXECUTE stmt USING 'jj'; +DROP TABLE t1; + +PREPARE stmt FROM 'CREATE TABLE t1 AS SELECT ? COLLATE utf32_unicode_ci AS c1'; +EXECUTE stmt USING 'test'; +SHOW CREATE TABLE t1; +DROP TABLE t1; + +PREPARE stmt FROM 'CREATE TABLE t1 AS SELECT CONCAT(?) COLLATE utf32_unicode_ci AS c1'; +EXECUTE stmt USING 'test'; +SHOW CREATE TABLE t1; +DROP TABLE t1; + +CREATE TABLE t1 AS SELECT NULL COLLATE utf32_unicode_ci AS c1; +SHOW CREATE TABLE t1; +DROP TABLE t1; + +CREATE TABLE t1 AS SELECT CONCAT(NULL) COLLATE utf32_unicode_ci AS c1; +SHOW CREATE TABLE t1; +DROP TABLE t1; + +SET NAMES utf8mb4; + +--echo # +--echo # End of 10.11 tests +--echo # + --enable_service_connection diff --git a/mysql-test/main/ctype_utf8mb4.result b/mysql-test/main/ctype_utf8mb4.result index 26e8784e4f1..591ddf2cd9e 100644 --- a/mysql-test/main/ctype_utf8mb4.result +++ b/mysql-test/main/ctype_utf8mb4.result @@ -4182,24 +4182,6 @@ DROP TABLE t1; # End of 10.2 tests # # -# Start of 10.5 tests -# -# -# MDEV-24584 Selecting INT column with COLLATE utf8mb4_general_ci throws an error -# -SET NAMES utf8mb4; -SELECT 1 COLLATE utf8mb4_general_ci; -1 COLLATE utf8mb4_general_ci -1 -SELECT 1 COLLATE utf8mb4_bin; -1 COLLATE utf8mb4_bin -1 -SELECT 1 COLLATE latin1_swedish_ci; -ERROR 42000: COLLATION 'latin1_swedish_ci' is not valid for CHARACTER SET 'utf8mb4' -# -# End of 10.5 tests -# -# # Start of 10.6 tests # # @@ -4227,3 +4209,67 @@ DROP TABLE t1; # # End of 10.6 tests # +# +# Start of 10.11 tests +# +# +# MDEV-24584 Selecting INT column with COLLATE utf8mb4_general_ci throws an error +# +SET NAMES utf8mb4; +SELECT 1 COLLATE utf8mb4_general_ci; +1 COLLATE utf8mb4_general_ci +1 +SELECT 1 COLLATE utf8mb4_bin; +1 COLLATE utf8mb4_bin +1 +SELECT 1 COLLATE latin1_swedish_ci; +ERROR 42000: COLLATION 'latin1_swedish_ci' is not valid for CHARACTER SET 'utf8mb4' +# +# MDEV-10865 COLLATE keyword doesn't work in PREPARE query +# +SET NAMES utf8mb4; +CREATE TABLE t1 ( +c1 varchar(500) COLLATE utf8mb4_unicode_ci NOT NULL +) CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; +INSERT INTO t1 VALUES ('jj'); +SELECT * FROM t1 WHERE c1 LIKE 'jj' COLLATE utf8mb4_unicode_ci; +c1 +jj +PREPARE stmt FROM 'SELECT * FROM t1 WHERE c1 LIKE ? COLLATE utf8mb4_unicode_ci'; +EXECUTE stmt USING 'jj'; +c1 +jj +DROP TABLE t1; +PREPARE stmt FROM 'CREATE TABLE t1 AS SELECT ? COLLATE utf8mb4_unicode_ci AS c1'; +EXECUTE stmt USING 'test'; +SHOW CREATE TABLE t1; +Table Create Table +t1 CREATE TABLE `t1` ( + `c1` varchar(4) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci DEFAULT NULL +) ENGINE=MyISAM DEFAULT CHARSET=latin1 COLLATE=latin1_swedish_ci +DROP TABLE t1; +PREPARE stmt FROM 'CREATE TABLE t1 AS SELECT CONCAT(?) COLLATE utf8mb4_unicode_ci AS c1'; +EXECUTE stmt USING 'test'; +SHOW CREATE TABLE t1; +Table Create Table +t1 CREATE TABLE `t1` ( + `c1` varchar(4) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci DEFAULT NULL +) ENGINE=MyISAM DEFAULT CHARSET=latin1 COLLATE=latin1_swedish_ci +DROP TABLE t1; +CREATE TABLE t1 AS SELECT NULL COLLATE utf8mb4_unicode_ci AS c1; +SHOW CREATE TABLE t1; +Table Create Table +t1 CREATE TABLE `t1` ( + `c1` char(0) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci DEFAULT NULL +) ENGINE=MyISAM DEFAULT CHARSET=latin1 COLLATE=latin1_swedish_ci +DROP TABLE t1; +CREATE TABLE t1 AS SELECT CONCAT(NULL) COLLATE utf8mb4_unicode_ci AS c1; +SHOW CREATE TABLE t1; +Table Create Table +t1 CREATE TABLE `t1` ( + `c1` char(0) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci DEFAULT NULL +) ENGINE=MyISAM DEFAULT CHARSET=latin1 COLLATE=latin1_swedish_ci +DROP TABLE t1; +# +# End of 10.11 tests +# diff --git a/mysql-test/main/ctype_utf8mb4.test b/mysql-test/main/ctype_utf8mb4.test index 46a9c14dec0..b412a755d6d 100644 --- a/mysql-test/main/ctype_utf8mb4.test +++ b/mysql-test/main/ctype_utf8mb4.test @@ -2068,25 +2068,6 @@ DROP TABLE t1; --echo # End of 10.2 tests --echo # - ---echo # ---echo # Start of 10.5 tests ---echo # - ---echo # ---echo # MDEV-24584 Selecting INT column with COLLATE utf8mb4_general_ci throws an error ---echo # - -SET NAMES utf8mb4; -SELECT 1 COLLATE utf8mb4_general_ci; -SELECT 1 COLLATE utf8mb4_bin; ---error ER_COLLATION_CHARSET_MISMATCH -SELECT 1 COLLATE latin1_swedish_ci; - ---echo # ---echo # End of 10.5 tests ---echo # - --echo # --echo # Start of 10.6 tests --echo # @@ -2112,3 +2093,55 @@ DROP TABLE t1; --echo # --echo # End of 10.6 tests --echo # + + +--echo # +--echo # Start of 10.11 tests +--echo # + +--echo # +--echo # MDEV-24584 Selecting INT column with COLLATE utf8mb4_general_ci throws an error +--echo # + +SET NAMES utf8mb4; +SELECT 1 COLLATE utf8mb4_general_ci; +SELECT 1 COLLATE utf8mb4_bin; +--error ER_COLLATION_CHARSET_MISMATCH +SELECT 1 COLLATE latin1_swedish_ci; + +--echo # +--echo # MDEV-10865 COLLATE keyword doesn't work in PREPARE query +--echo # + +SET NAMES utf8mb4; +CREATE TABLE t1 ( + c1 varchar(500) COLLATE utf8mb4_unicode_ci NOT NULL +) CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; +INSERT INTO t1 VALUES ('jj'); +SELECT * FROM t1 WHERE c1 LIKE 'jj' COLLATE utf8mb4_unicode_ci; +PREPARE stmt FROM 'SELECT * FROM t1 WHERE c1 LIKE ? COLLATE utf8mb4_unicode_ci'; +EXECUTE stmt USING 'jj'; +DROP TABLE t1; + +PREPARE stmt FROM 'CREATE TABLE t1 AS SELECT ? COLLATE utf8mb4_unicode_ci AS c1'; +EXECUTE stmt USING 'test'; +SHOW CREATE TABLE t1; +DROP TABLE t1; + +PREPARE stmt FROM 'CREATE TABLE t1 AS SELECT CONCAT(?) COLLATE utf8mb4_unicode_ci AS c1'; +EXECUTE stmt USING 'test'; +SHOW CREATE TABLE t1; +DROP TABLE t1; + +CREATE TABLE t1 AS SELECT NULL COLLATE utf8mb4_unicode_ci AS c1; +SHOW CREATE TABLE t1; +DROP TABLE t1; + +CREATE TABLE t1 AS SELECT CONCAT(NULL) COLLATE utf8mb4_unicode_ci AS c1; +SHOW CREATE TABLE t1; +DROP TABLE t1; + + +--echo # +--echo # End of 10.11 tests +--echo # diff --git a/sql/item.cc b/sql/item.cc index 2be91c68818..488e24c3497 100644 --- a/sql/item.cc +++ b/sql/item.cc @@ -4119,6 +4119,7 @@ Item_param::Item_param(THD *thd, const LEX_CSTRING *name_arg, */ set_maybe_null(); with_flags= with_flags | item_with_t::PARAM; + collation= DTCollation(&my_charset_bin, DERIVATION_IGNORABLE); } @@ -4174,7 +4175,7 @@ void Item_param::sync_clones() } -void Item_param::set_null() +void Item_param::set_null(const DTCollation &c) { DBUG_ENTER("Item_param::set_null"); /* @@ -4189,6 +4190,7 @@ void Item_param::set_null() */ max_length= 0; decimals= 0; + collation= c; state= NULL_VALUE; DBUG_VOID_RETURN; } @@ -4447,7 +4449,7 @@ bool Item_param::set_from_item(THD *thd, Item *item) longlong val= item->val_int(); if (item->null_value) { - set_null(); + set_null(DTCollation_numeric()); DBUG_RETURN(false); } else @@ -4465,7 +4467,7 @@ bool Item_param::set_from_item(THD *thd, Item *item) DBUG_RETURN(set_value(thd, item, &tmp, h)); } else - set_null(); + set_null_string(item->collation); DBUG_RETURN(0); } @@ -5048,7 +5050,7 @@ Item_param::set_value(THD *thd, sp_rcontext *ctx, Item **it) if (arg->save_in_value(thd, &tmp) || set_value(thd, arg, &tmp, arg->type_handler())) { - set_null(); + set_null_string(arg->collation); return false; } /* It is wrapper => other set_* shoud set null_value */ diff --git a/sql/item.h b/sql/item.h index 566255840fc..dfb540cf1d4 100644 --- a/sql/item.h +++ b/sql/item.h @@ -4234,7 +4234,32 @@ public: void set_default(); void set_ignore(); - void set_null(); + void set_null(const DTCollation &c); + void set_null_string(const DTCollation &c) + { + /* + We need to distinguish explicit NULL (marked by DERIVATION_IGNORABLE) + from other item types: + + - These statements should give an error, because + the character set of the bound parameter is not known: + EXECUTE IMMEDIATE "SELECT ? COLLATE utf8mb4_bin" USING NULL; + EXECUTE IMMEDIATE "SELECT ? COLLATE utf8mb4_bin" USING CONCAT(NULL); + + - These statements should return a good result, because + the character set of the bound parameter is known: + EXECUTE IMMEDIATE "SELECT ? COLLATE utf8mb4_bin" + USING CONVERT(NULL USING utf8mb4); + EXECUTE IMMEDIATE "SELECT ? COLLATE utf8mb4_bin" + USING CAST(NULL AS CHAR CHARACTER SET utf8mb4); + */ + set_null(DTCollation(c.collation, MY_MAX(c.derivation, + DERIVATION_COERCIBLE))); + } + void set_null() + { + set_null(DTCollation(&my_charset_bin, DERIVATION_IGNORABLE)); + } void set_int(longlong i, uint32 max_length_arg); void set_double(double i); void set_decimal(const char *str, ulong length); diff --git a/sql/item_strfunc.cc b/sql/item_strfunc.cc index 2a9c14e7145..33b5f716ea8 100644 --- a/sql/item_strfunc.cc +++ b/sql/item_strfunc.cc @@ -3880,21 +3880,43 @@ String *Item_func_set_collation::val_str(String *str) str=args[0]->val_str(str); if ((null_value=args[0]->null_value)) return 0; + /* + Let SCS be the character set of the source - args[0]. + Let TCS be the character set of the target - i.e. the character set + of the collation specified in the COLLATE clause. + + It's OK to return SQL NULL if SCS is not equal to TCS. + This is possible on the explicit NULL or expressions derived from + the explicit NULL: + SELECT NULL COLLATE utf8mb4_general_ci; + SELECT COALESCE(NULL) COLLATE utf8mb4_general_ci; + + But for a non-NULL result SCS and TCS must be compatible: + 1. Either SCS==TCS + 2. Or SCS can be can be reinterpeted to TCS. + This scenario is possible when args[0] is numeric and TCS->mbmaxlen==1. + + If SCS and TCS are not compatible here, then something went wrong during + fix_fields(), e.g. an Item_func_conv_charset was not added two wrap args[0]. + */ + DBUG_ASSERT(my_charset_same(args[0]->collation.collation, + collation.collation) || + (args[0]->collation.repertoire == MY_REPERTOIRE_ASCII && + !(collation.collation->state & MY_CS_NONASCII))); str->set_charset(collation.collation); return str; } bool Item_func_set_collation::fix_length_and_dec(THD *thd) { - if (agg_arg_charsets_for_string_result(collation, args, 1)) + if (agg_arg_charsets_for_string_result(collation, args, 1) || + collation.merge_collation(m_set_collation, + args[0]->collation.repertoire, + with_param() && + thd->lex->is_ps_or_view_context_analysis())) return true; - Lex_exact_charset_opt_extended_collate cl(collation.collation, true); - if (cl.merge_collation_override(m_set_collation)) - return true; - collation.set(cl.collation().charset_info(), DERIVATION_EXPLICIT, - args[0]->collation.repertoire); ulonglong max_char_length= (ulonglong) args[0]->max_char_length(); - fix_char_length_ulonglong(max_char_length * collation.collation->mbmaxlen); + fix_char_length_ulonglong(max_char_length); return FALSE; } diff --git a/sql/sql_type.cc b/sql/sql_type.cc index b1911c79e14..e914549b931 100644 --- a/sql/sql_type.cc +++ b/sql/sql_type.cc @@ -33,6 +33,88 @@ const DTCollation &DTCollation_numeric::singleton() return tmp; } + +bool +DTCollation::merge_charset_and_collation(CHARSET_INFO *cs, + const Lex_extended_collation_st &cl, + my_repertoire_t repertoire) +{ + Lex_exact_charset_opt_extended_collate cscl(cs, true); + if (cscl.merge_collation_override(cl)) + return true; + set(cscl.collation().charset_info(), DERIVATION_EXPLICIT, repertoire); + return false; +} + + +bool DTCollation::merge_collation(const Lex_extended_collation_st &cl, + my_repertoire_t repertoire, + bool allow_ignorable_with_context_collation) +{ + if (derivation != DERIVATION_IGNORABLE) + { + // A known character set + an extended collation + return merge_charset_and_collation(collation, cl, repertoire); + } + + if (cl.type() == Lex_extended_collation::TYPE_EXACT) + { + /* + An unknown character set + an exact collation. + Just use this exact collation. + Examples: + - Expressions derived from an explicit NULL: + SELECT NULL COLLATE utf8mb4_general_ci; + SELECT CONCAT(NULL) COLLATE utf8mb4_general_ci; + Any collation is applicable to an explicit NULL. + + - Expressions with PS parameters (at PREPARE time, not bound yet) + SELECT ? COLLATE utf8mb4_general_ci; + SELECT CONCAT(?) COLLATE utf8mb4_general_ci; + The collation will be checked for applicability to the + character set of the actual bound parameter at the EXECUTE time. + We're now in PREPARE: let's assume it will be applicable. + */ + set(cl.charset_info(), DERIVATION_EXPLICIT, repertoire); + return false; + } + + // An unknown character set + a contextually typed collation + if (allow_ignorable_with_context_collation) + { + /* + Expressions with non-bound PS parameters, PREPARE time. + SELECT ? COLLATE uca1400_ai_ci; + SELECT CONCAT(?) COLLATE uca1400_ai_ci; + There is a chance the character set of the actual bound parameter + will be known at the EXECUTE time (unless an explicit NULL is bound). + + For now let's use utf8mb4 to resolve collations like uca1400_ai_ci. + The real character set of the actual bound parameter expression will be + later used to resolve the collation again, during the EXECUTE time. + */ + return merge_charset_and_collation(&my_charset_utf8mb4_general_ci, + cl, repertoire); + } + + /* + Expressions with an unknown character set: + - Either without PS parameters at all: + SELECT NULL COLLATE uca1400_ai_ci; + SELECT CONCAT(NULL) COLLATE uca1400_ai_ci; + - Or with PS parameters bound to NULL at EXECUTE time: + EXECUTE IMMEDIATE + 'SELECT ? COLLATE uca1400_ai_ci' USING NULL; + EXECUTE IMMEDIATE + 'SELECT CONCAT(?) COLLATE uca1400_ai_ci' USING NULL; + EXECUTE IMMEDIATE + 'SELECT ? COLLATE uca1400_ai_ci' USING CONCAT(NULL); + */ + my_error(ER_NOT_ALLOWED_IN_THIS_CONTEXT, MYF(0), "NULL"); + return true; +} + + Named_type_handler type_handler_row("row"); Named_type_handler type_handler_null("null"); diff --git a/sql/sql_type.h b/sql/sql_type.h index 374f35ca753..ea736f92ae7 100644 --- a/sql/sql_type.h +++ b/sql/sql_type.h @@ -3076,6 +3076,12 @@ public: bool aggregate(const DTCollation &dt, uint flags= 0); bool set(DTCollation &dt1, DTCollation &dt2, uint flags= 0) { set(dt1); return aggregate(dt2, flags); } + bool merge_charset_and_collation(CHARSET_INFO *cs, + const Lex_extended_collation_st &cl, + my_repertoire_t repertoire); + bool merge_collation(const Lex_extended_collation_st &cl, + my_repertoire_t repertoire, + bool allow_ignorable_with_context_collation); const char *derivation_name() const { switch(derivation)