diff --git a/client/mysqldump.c b/client/mysqldump.c index 869e3b981194a..cc855cda854db 100644 --- a/client/mysqldump.c +++ b/client/mysqldump.c @@ -4736,10 +4736,10 @@ static int dump_all_users_roles_and_grants() "SELECT CONCAT(QUOTE(u.user), '@', QUOTE(u.Host)) AS u " "FROM mysql.user u " " /*!80001 LEFT JOIN mysql.role_edges e " - " ON u.user=e.from_user " - " AND u.host=e.from_host " + " ON u.user=e.from_user COLLATE utf8mb4_bin " + " AND u.host=e.from_host COLLATE utf8mb4_bin " " WHERE e.from_user IS NULL */" - " /*M!100005 WHERE is_role='N' */")) + " /*M!100005 WHERE BINARY is_role='N' */")) return 1; while ((row= mysql_fetch_row(tableres))) { @@ -4806,7 +4806,7 @@ static int dump_all_users_roles_and_grants() " (SELECT 1 as n, roles_mapping.*" " FROM mysql.roles_mapping" " JOIN mysql.user USING (user,host)" - " WHERE is_role='N'" + " WHERE BINARY is_role='N'" " AND Admin_option='Y'" " UNION SELECT c.n+1, r.*" " FROM create_role_order c" @@ -4828,16 +4828,16 @@ static int dump_all_users_roles_and_grants() " (SELECT 1 AS n," " re.*" " FROM mysql.role_edges re" - " JOIN mysql.user u ON re.TO_HOST=u.HOST" - " AND re.TO_USER = u.USER" - " LEFT JOIN mysql.role_edges re2 ON re.TO_USER=re2.FROM_USER" - " AND re2.TO_HOST=re2.FROM_HOST" + " JOIN mysql.user u ON re.TO_HOST=u.HOST COLLATE utf8mb4_bin" + " AND re.TO_USER = u.USER COLLATE utf8mb4_bin" + " LEFT JOIN mysql.role_edges re2 ON re.TO_USER=re2.FROM_USER COLLATE utf8mb4_bin" + " AND re2.TO_HOST=re2.FROM_HOST COLLATE utf8mb4_bin" " WHERE re2.FROM_USER IS NULL" " UNION SELECT c.n+1," " re.*" " FROM create_role_order c" - " JOIN mysql.role_edges re ON c.FROM_USER=re.TO_USER" - " AND c.FROM_HOST=re.TO_HOST) " + " JOIN mysql.role_edges re ON c.FROM_USER=re.TO_USER COLLATE utf8mb4_bin" + " AND c.FROM_HOST=re.TO_HOST COLLATE utf8mb4_bin) " "SELECT CONCAT(QUOTE(FROM_USER), '/*!80001 @', QUOTE(FROM_HOST), '*/') AS r," " CONCAT(QUOTE(TO_USER), IF(n=1, CONCAT('@', QUOTE(TO_HOST))," " CONCAT('/*!80001 @', QUOTE(TO_HOST), ' */'))) AS u," @@ -4872,13 +4872,15 @@ static int dump_all_users_roles_and_grants() if (maria_roles_exist && mysql_query_with_error_report(mysql, &tableres, "select IF(default_role='', 'NONE', QUOTE(default_role)) as r," "concat(QUOTE(User), '@', QUOTE(Host)) as u FROM mysql.user " - "/*M!100005 WHERE is_role='N' */")) + "/*M!100005 WHERE BINARY is_role='N' */")) return 1; if (mysql_roles_exist && mysql_query_with_error_report(mysql, &tableres, "SELECT IF(DEFAULT_ROLE_HOST IS NULL, 'NONE', CONCAT(QUOTE(DEFAULT_ROLE_USER)," " '@', QUOTE(DEFAULT_ROLE_HOST))) as r," " CONCAT(QUOTE(mu.USER),'@',QUOTE(mu.HOST)) as u " - "FROM mysql.user mu LEFT JOIN mysql.default_roles using (USER, HOST)")) + "FROM mysql.user mu LEFT JOIN mysql.default_roles dr" + " ON mu.USER = dr.USER COLLATE utf8mb4_bin" + " AND mu.HOST = dr.HOST COLLATE utf8mb4_bin")) { mysql_free_result(tableres); return 1; @@ -4897,7 +4899,7 @@ static int dump_all_users_roles_and_grants() "SELECT DISTINCT QUOTE(m.role) AS r " " FROM mysql.roles_mapping m" " JOIN mysql.user u ON u.user = m.role" - " WHERE is_role='Y'" + " WHERE BINARY is_role='Y'" " AND Admin_option='Y'" " ORDER BY m.role")) return 1; diff --git a/mysql-test/main/mysqldump-system-collation.result b/mysql-test/main/mysqldump-system-collation.result new file mode 100644 index 0000000000000..2b9a92d8513ba --- /dev/null +++ b/mysql-test/main/mysqldump-system-collation.result @@ -0,0 +1,64 @@ +# +# MDEV-37442: Illegal mix of collations during "mariadb-dump --system=user" +# +# When a server is migrated from MySQL 8.0, the mysql.user, mysql.role_edges +# and mysql.default_roles tables retain their original utf8mb4 collations. +# If the user and role tables end up with different utf8mb4 collations +# (e.g. utf8mb4_general_ci vs utf8mb4_unicode_ci), MariaDB's mysqldump +# --system=user fails with "Illegal mix of collations" when joining the +# tables because no explicit collation was specified on the JOIN keys. +# +# Fix: Added COLLATE utf8mb4_bin on every JOIN key so the explicit +# collation always wins over any implicit column collation. +# +# Simulate the mismatch: a user table with utf8mb4_general_ci joined +# against role_edges with utf8mb4_unicode_ci. Both have IMPLICIT +# coercibility, so MariaDB cannot resolve the conflict automatically. +# This is the same type of error that mysqldump triggered on a migrated +# server. COLLATE utf8mb4_bin (EXPLICIT coercibility) resolves it. +CREATE TEMPORARY TABLE t_mysql_user ( +User char(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT '', +Host char(60) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT '' +); +CREATE TEMPORARY TABLE t_role_edges ( +FROM_HOST char(60) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT '', +FROM_USER char(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT '' +); +INSERT INTO t_mysql_user VALUES ('mdev37442_user', '%'); +INSERT INTO t_role_edges VALUES ('', 'mdev37442_role'); +# Without COLLATE: utf8mb4_general_ci (IMPLICIT) vs utf8mb4_unicode_ci (IMPLICIT) +# -> ERROR 1267: Illegal mix of collations +SELECT u.User FROM t_mysql_user u +JOIN t_role_edges e ON u.User = e.FROM_USER; +ERROR HY000: Illegal mix of collations (utf8mb4_general_ci,IMPLICIT) and (utf8mb4_unicode_ci,IMPLICIT) for operation '=' +# With COLLATE utf8mb4_bin: EXPLICIT coercibility wins on both sides -> OK +SELECT u.User FROM t_mysql_user u +JOIN t_role_edges e ON u.User = e.FROM_USER COLLATE utf8mb4_bin; +User +DROP TEMPORARY TABLE t_mysql_user, t_role_edges; +# +# Create the full MySQL-origin role tables and verify that +# mysqldump --system=user completes without error. +DROP TABLE IF EXISTS mysql.role_edges; +DROP TABLE IF EXISTS mysql.default_roles; +CREATE TABLE mysql.role_edges ( +FROM_HOST char(60) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT '', +FROM_USER char(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT '', +TO_HOST char(60) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT '', +TO_USER char(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT '', +WITH_ADMIN_OPTION enum('N','Y') CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT 'N', +PRIMARY KEY (FROM_HOST, FROM_USER, TO_HOST, TO_USER) +) DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci; +CREATE TABLE mysql.default_roles ( +HOST char(60) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT '', +USER char(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT '', +DEFAULT_ROLE_HOST char(60) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT '', +DEFAULT_ROLE_USER char(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT '', +PRIMARY KEY (HOST, USER, DEFAULT_ROLE_HOST, DEFAULT_ROLE_USER) +) DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci; +INSERT INTO mysql.role_edges VALUES ('', 'mdev37442_role', '%', 'mdev37442_user', 'N'); +INSERT INTO mysql.default_roles VALUES ('%', 'mdev37442_user', '', 'mdev37442_role'); +# mysqldump --system=user must succeed despite the collation difference. +# Cleanup +DROP TABLE mysql.role_edges; +DROP TABLE mysql.default_roles; diff --git a/mysql-test/main/mysqldump-system-collation.test b/mysql-test/main/mysqldump-system-collation.test new file mode 100644 index 0000000000000..ffe95163a8d5c --- /dev/null +++ b/mysql-test/main/mysqldump-system-collation.test @@ -0,0 +1,82 @@ +--source include/not_embedded.inc + +--echo # +--echo # MDEV-37442: Illegal mix of collations during "mariadb-dump --system=user" +--echo # +--echo # When a server is migrated from MySQL 8.0, the mysql.user, mysql.role_edges +--echo # and mysql.default_roles tables retain their original utf8mb4 collations. +--echo # If the user and role tables end up with different utf8mb4 collations +--echo # (e.g. utf8mb4_general_ci vs utf8mb4_unicode_ci), MariaDB's mysqldump +--echo # --system=user fails with "Illegal mix of collations" when joining the +--echo # tables because no explicit collation was specified on the JOIN keys. +--echo # +--echo # Fix: Added COLLATE utf8mb4_bin on every JOIN key so the explicit +--echo # collation always wins over any implicit column collation. +--echo # + +--echo # Simulate the mismatch: a user table with utf8mb4_general_ci joined +--echo # against role_edges with utf8mb4_unicode_ci. Both have IMPLICIT +--echo # coercibility, so MariaDB cannot resolve the conflict automatically. +--echo # This is the same type of error that mysqldump triggered on a migrated +--echo # server. COLLATE utf8mb4_bin (EXPLICIT coercibility) resolves it. + +CREATE TEMPORARY TABLE t_mysql_user ( + User char(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT '', + Host char(60) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT '' +); +CREATE TEMPORARY TABLE t_role_edges ( + FROM_HOST char(60) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT '', + FROM_USER char(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT '' +); +INSERT INTO t_mysql_user VALUES ('mdev37442_user', '%'); +INSERT INTO t_role_edges VALUES ('', 'mdev37442_role'); + +--echo # Without COLLATE: utf8mb4_general_ci (IMPLICIT) vs utf8mb4_unicode_ci (IMPLICIT) +--echo # -> ERROR 1267: Illegal mix of collations +--error ER_CANT_AGGREGATE_2COLLATIONS +SELECT u.User FROM t_mysql_user u + JOIN t_role_edges e ON u.User = e.FROM_USER; + +--echo # With COLLATE utf8mb4_bin: EXPLICIT coercibility wins on both sides -> OK +SELECT u.User FROM t_mysql_user u + JOIN t_role_edges e ON u.User = e.FROM_USER COLLATE utf8mb4_bin; + +DROP TEMPORARY TABLE t_mysql_user, t_role_edges; + +--echo # +--echo # Create the full MySQL-origin role tables and verify that +--echo # mysqldump --system=user completes without error. + +--disable_warnings +DROP TABLE IF EXISTS mysql.role_edges; +DROP TABLE IF EXISTS mysql.default_roles; +--enable_warnings + +CREATE TABLE mysql.role_edges ( + FROM_HOST char(60) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT '', + FROM_USER char(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT '', + TO_HOST char(60) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT '', + TO_USER char(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT '', + WITH_ADMIN_OPTION enum('N','Y') CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT 'N', + PRIMARY KEY (FROM_HOST, FROM_USER, TO_HOST, TO_USER) +) DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci; + +CREATE TABLE mysql.default_roles ( + HOST char(60) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT '', + USER char(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT '', + DEFAULT_ROLE_HOST char(60) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT '', + DEFAULT_ROLE_USER char(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT '', + PRIMARY KEY (HOST, USER, DEFAULT_ROLE_HOST, DEFAULT_ROLE_USER) +) DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci; + +INSERT INTO mysql.role_edges VALUES ('', 'mdev37442_role', '%', 'mdev37442_user', 'N'); +INSERT INTO mysql.default_roles VALUES ('%', 'mdev37442_user', '', 'mdev37442_role'); + +--echo # mysqldump --system=user must succeed despite the collation difference. +--exec $MYSQL_DUMP --skip-comments --system=user > $MYSQLTEST_VARDIR/tmp/mdev37442.sql + +--echo # Cleanup +DROP TABLE mysql.role_edges; +DROP TABLE mysql.default_roles; +--remove_file $MYSQLTEST_VARDIR/tmp/mdev37442.sql +