fix(export): declare utf8mb4 charset in MySQL export script
This commit is contained in:
2 files changed
+26
No files matched your search
@@ -30,6 +30,15 @@ static EXPORT_CANCELLED: std::sync::LazyLock<RwLock<HashSet<String>>> =
|
||||
const EXPORT_CANCELLED_ERROR: &str = "Export cancelled";
|
||||
const EXPORT_CANCEL_POLL_INTERVAL: Duration = Duration::from_millis(50);
|
||||
|
||||
/// MySQL scripts written by the exporter are always UTF-8: values are decoded off the
|
||||
/// wire and reformatted into Rust strings before they are written, so the file encoding
|
||||
/// never follows the connection charset. Declaring that encoding in the script keeps the
|
||||
/// importing client's default out of the decoding decision -- a `latin1` `mysql` CLI (a
|
||||
/// docker entrypoint initializing a container, for example) would otherwise re-encode
|
||||
/// every non-ASCII value into mojibake (#10242). `mysqldump` writes the same statement
|
||||
/// for the same reason.
|
||||
const MYSQL_EXPORT_CHARSET_STATEMENT: &str = "SET NAMES utf8mb4;";
|
||||
|
||||
pub fn database_export_client_session_id(export_id: &str) -> String {
|
||||
task_client_session_id("database-export", export_id)
|
||||
}
|
||||
@@ -3020,6 +3029,13 @@ async fn export_database_sql_core_inner(
|
||||
writeln!(file, "-- Generated by DBX").map_err(|e| format!("Failed to write file: {e}"))?;
|
||||
writeln!(file).map_err(|e| format!("Failed to write file: {e}"))?;
|
||||
|
||||
// Declare the script's encoding before the first statement that can carry non-ASCII
|
||||
// text (the database name is written by the preamble right below, and table, column
|
||||
// and DEFAULT values follow).
|
||||
if matches!(db_type, DatabaseType::Mysql) {
|
||||
writeln!(file, "{MYSQL_EXPORT_CHARSET_STATEMENT}").map_err(|e| format!("Failed to write file: {e}"))?;
|
||||
}
|
||||
|
||||
if let Some(preamble) = create_database_preamble {
|
||||
writeln!(file, "-- Database setup").map_err(|e| format!("Failed to write file: {e}"))?;
|
||||
writeln!(file, "{preamble}").map_err(|e| format!("Failed to write file: {e}"))?;
|
||||
|
||||
@@ -404,6 +404,16 @@ async fn live_mysql_database_export_creates_missing_destination_directory() {
|
||||
let exported = std::fs::read_to_string(&file_path).unwrap();
|
||||
assert!(exported.contains("'alpha'"), "exported SQL should contain the seeded row");
|
||||
|
||||
// #10242: the script must declare its own encoding before the first statement that can
|
||||
// carry non-ASCII text, otherwise an importing client whose default charset is not
|
||||
// utf8mb4 (a `latin1` mysql CLI in a docker entrypoint, for example) re-encodes every
|
||||
// non-ASCII value the exporter wrote as UTF-8 into mojibake.
|
||||
let charset = exported.find("SET NAMES utf8mb4;").expect("MySQL exports must declare their encoding");
|
||||
let create_database = exported.find("CREATE DATABASE").expect("the CREATE DATABASE preamble should be exported");
|
||||
let create_table = exported.find("CREATE TABLE").expect("the table DDL should be exported");
|
||||
assert!(charset < create_database, "SET NAMES must precede the CREATE DATABASE preamble");
|
||||
assert!(charset < create_table, "SET NAMES must precede the table DDL");
|
||||
|
||||
std::fs::remove_dir_all(dir).unwrap();
|
||||
}
|
||||
|
||||
|
||||
Reference in new issue
Block a user