Skip to content
/ server Public
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 15 additions & 13 deletions client/mysqldump.c
Original file line number Diff line number Diff line change
Expand Up @@ -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)))
{
Expand Down Expand Up @@ -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"
Expand All @@ -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,"
Expand Down Expand Up @@ -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;
Expand All @@ -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;
Expand Down
64 changes: 64 additions & 0 deletions mysql-test/main/mysqldump-system-collation.result
Original file line number Diff line number Diff line change
@@ -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;
82 changes: 82 additions & 0 deletions mysql-test/main/mysqldump-system-collation.test
Original file line number Diff line number Diff line change
@@ -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