From ae627de3205f2284fe99228b55cc0b71dbfa254d Mon Sep 17 00:00:00 2001 From: LRcoding <38545827+LwClick@users.noreply.github.com> Date: Wed, 8 Jul 2026 17:31:29 +0800 Subject: [PATCH] fix(core): preserve mysql numeric export literals --- crates/dbx-core/src/transfer.rs | 94 ++++++++++++++++++++++++- crates/dbx-web/src/routes/connection.rs | 1 - src-tauri/src/commands/connection.rs | 1 - 3 files changed, 93 insertions(+), 3 deletions(-) diff --git a/crates/dbx-core/src/transfer.rs b/crates/dbx-core/src/transfer.rs index 045820c00..2f479baf5 100644 --- a/crates/dbx-core/src/transfer.rs +++ b/crates/dbx-core/src/transfer.rs @@ -482,7 +482,9 @@ fn looks_like_numeric_literal(raw: &str) -> bool { if trimmed.is_empty() { return false; } - trimmed.parse::().is_ok() || trimmed.parse::().is_ok() || trimmed.parse::().is_ok() + trimmed.parse::().is_ok() + || trimmed.parse::().is_ok() + || trimmed.parse::().is_ok_and(|value| value.is_finite()) } fn format_mysql_default_literal(raw: &str, data_type: &str) -> String { @@ -971,6 +973,9 @@ pub fn escape_value_typed(val: &serde_json::Value, db_type: &DatabaseType, colum if let Some(binary_literal) = format_postgres_binary_sql_literal(s, db_type, column_type) { return binary_literal; } + if let Some(numeric_literal) = format_mysql_numeric_string_literal(s, db_type, column_type) { + return numeric_literal; + } let literal = format_literal_string(s, db_type, column_type); let escaped = if is_postgres_family_target(db_type) { @@ -1005,6 +1010,37 @@ fn is_mysql_bit_type(column_type: &str) -> bool { lower == "bit" || lower.starts_with("bit(") || lower.starts_with("bit ") } +fn is_mysql_numeric_string_literal_database(db_type: &DatabaseType) -> bool { + matches!( + db_type, + DatabaseType::Mysql + | DatabaseType::Doris + | DatabaseType::StarRocks + | DatabaseType::Goldendb + | DatabaseType::Sundb + ) +} + +fn is_mysql_non_bit_numeric_type(column_type: &str) -> bool { + is_mysql_numeric_base_type(column_type) && !is_mysql_bit_type(column_type) +} + +fn format_mysql_numeric_string_literal( + value: &str, + db_type: &DatabaseType, + column_type: Option<&str>, +) -> Option { + if !is_mysql_numeric_string_literal_database(db_type) || !column_type.is_some_and(is_mysql_non_bit_numeric_type) { + return None; + } + let trimmed = value.trim(); + if looks_like_numeric_literal(trimmed) { + Some(trimmed.to_string()) + } else { + None + } +} + fn is_binary_transfer_column_type(column_type: &str) -> bool { let lower = column_type.trim().to_ascii_lowercase(); let base = lower.split(['(', ' ', '\t', '\n']).next().unwrap_or(""); @@ -5461,6 +5497,62 @@ mod tests { ); } + #[test] + fn mysql_insert_formats_numeric_strings_from_numeric_columns_as_numeric_literals() { + let sql = generate_insert_typed( + &[ + String::from("id"), + String::from("amount"), + String::from("quantity"), + String::from("text_id"), + String::from("bad_number"), + String::from("missing"), + ], + &[ + Some(String::from("bigint(20)")), + Some(String::from("decimal(10,2)")), + Some(String::from("int unsigned")), + Some(String::from("varchar(64)")), + Some(String::from("bigint(20)")), + Some(String::from("bigint(20)")), + ], + &[vec![ + json!("1234567890123"), + json!("12.34"), + json!("42"), + json!("123"), + json!("not-a-number"), + serde_json::Value::Null, + ]], + "orders", + "", + &DatabaseType::Mysql, + ); + + assert_eq!( + sql, + "INSERT INTO `orders` (`id`, `amount`, `quantity`, `text_id`, `bad_number`, `missing`) VALUES\n(1234567890123, 12.34, 42, '123', 'not-a-number', NULL)" + ); + } + + #[test] + fn mysql_upsert_formats_numeric_strings_from_numeric_columns_as_numeric_literals() { + let sql = generate_upsert_typed( + &[String::from("id"), String::from("amount")], + &[Some(String::from("bigint(20)")), Some(String::from("decimal(10,2)"))], + &[vec![json!("1234567890123"), json!("12.34")]], + "orders", + "", + &DatabaseType::Mysql, + &[String::from("id")], + ); + + assert_eq!( + sql, + "INSERT INTO `orders` (`id`, `amount`) VALUES\n(1234567890123, 12.34)\nON DUPLICATE KEY UPDATE `amount` = VALUES(`amount`)" + ); + } + #[test] fn sqlserver_insert_prefixes_string_literals_as_unicode() { let sql = generate_insert_typed( diff --git a/crates/dbx-web/src/routes/connection.rs b/crates/dbx-web/src/routes/connection.rs index da8f0c54f..69923e3bb 100644 --- a/crates/dbx-web/src/routes/connection.rs +++ b/crates/dbx-web/src/routes/connection.rs @@ -304,7 +304,6 @@ mod tests { external_config: None, jdbc_driver_class: None, jdbc_driver_paths: Vec::new(), - agent_java_options: Vec::new(), one_time: false, read_only: false, } diff --git a/src-tauri/src/commands/connection.rs b/src-tauri/src/commands/connection.rs index bd487620a..bbf49f5eb 100644 --- a/src-tauri/src/commands/connection.rs +++ b/src-tauri/src/commands/connection.rs @@ -202,7 +202,6 @@ mod tests { external_config: None, jdbc_driver_class: None, jdbc_driver_paths: Vec::new(), - agent_java_options: Vec::new(), one_time: false, read_only: false, }