Bug #120685 XA recovery corrupts database name with special characters in MDL lock key (double filename_to_tablename())
Submitted: 15 Jun 6:28
Reporter: fei yang Email Updates:
Status: Open Impact on me:
None 
Category:MySQL Server: XA transactions Severity:S3 (Non-critical)
Version:8.0, 9.7.0 OS:Any
Assigned to: CPU Architecture:Any

[15 Jun 6:28] fei yang
Description:
When a database name contains characters that require filename encoding (e.g. # → @0023, $ → @0024), XA recovery after server restart produces a corrupted MDL lock key. The OBJECT_SCHEMA in performance_schema.metadata_locks shows kly??1 instead of the correct kly#$1, because # and $ are replaced with ?.

This breaks MDL lock protection: DDL operations (DROP TABLE, ALTER TABLE) on the affected table are not blocked by the XA-prepared transaction, which violates the XA contract.

How to repeat:
-- Create database with special characters
CREATE DATABASE `kly#$1`;
CREATE TABLE `kly#$1`.t1 (a INT);

-- Start XA transaction, prepare it
XA START 'xatest';
INSERT INTO `kly#$1`.t1 VALUES (10);
XA END 'xatest';
XA PREPARE 'xatest';

-- Disconnect the session, then restart the server

-- After restart, check MDL locks:
SELECT object_type, object_schema, object_name, lock_type, lock_duration, lock_status
  FROM performance_schema.metadata_locks
  WHERE object_name = 't1' AND lock_status = 'GRANTED';

-- Expected: OBJECT_SCHEMA = 'kly#$1'
-- Actual:   OBJECT_SCHEMA = 'kly??1'

Error log output during recovery:

[ERROR] Invalid (old?) table or database name 'kly#$1'

Suggested fix:
Remove the redundant filename_to_tablename() calls in xarecover_create_mdl_backup(), since InnoDB already provides decoded names through dict_name::get_table(convert=true):

// Before (buggy):
char db_buff[NAME_CHAR_LEN * FILENAME_CHARSET_MBMAXLEN + 1];
int len = filename_to_tablename(tbl_name->db, db_buff, sizeof(db_buff));
db_buff[len] = '\0';
// ...
MDL_REQUEST_INIT(table_mdl_request, MDL_key::TABLE, db_buff, name_buff, ...)

// After (fixed):
// tbl_name->db and tbl_name->tablename are already decoded by InnoDB,
// use them directly as MDL key arguments.
MDL_REQUEST_INIT(table_mdl_request, MDL_key::TABLE,
                 tbl_name->db, tbl_name->tablename, ...)
Caveat: This change assumes all recover_t handler implementations decode names before returning them. If any storage engine returns filename-encoded names (without going through dict_name::get_table(convert=true)), removing filename_to_tablename() would break that path. Verification is needed across all recover_t implementations.