fix(core): preserve mysql numeric export literals

This commit is contained in:
LRcoding 2026-07-08 17:31:29 +08:00 committed by GitHub
parent 430f097602
commit ae627de320
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 93 additions and 3 deletions

View File

@ -482,7 +482,9 @@ fn looks_like_numeric_literal(raw: &str) -> bool {
if trimmed.is_empty() {
return false;
}
trimmed.parse::<i64>().is_ok() || trimmed.parse::<u64>().is_ok() || trimmed.parse::<f64>().is_ok()
trimmed.parse::<i64>().is_ok()
|| trimmed.parse::<u64>().is_ok()
|| trimmed.parse::<f64>().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<String> {
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(

View File

@ -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,
}

View File

@ -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,
}