feat(mysql): reuse byte buffers for UTF-8 strings

This commit is contained in:
vrustx 2026-07-19 09:02:47 +08:00 committed by GitHub
parent d14fd84709
commit 66bdcf8153
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
1 changed files with 22 additions and 15 deletions

View File

@ -73,21 +73,27 @@ where
row.get_opt::<T, I>(index).and_then(|result| result.ok())
}
/// 字节转 String合法 UTF-8绝大多数场景时直接复用入参缓冲零拷贝
/// 仅在非法序列时退化为 lossy 替换。from_utf8_lossy(&b).to_string() 即使
/// 对合法输入也会多一次分配+拷贝。
fn bytes_to_string_lossy(bytes: Vec<u8>) -> String {
String::from_utf8(bytes).unwrap_or_else(|err| String::from_utf8_lossy(err.as_bytes()).into_owned())
}
fn get_str(row: &mysql_async::Row, idx: usize) -> String {
row_get::<String, _>(row, idx)
.or_else(|| row_get::<Vec<u8>, _>(row, idx).map(|b| String::from_utf8_lossy(&b).to_string()))
.or_else(|| row_get::<Vec<u8>, _>(row, idx).map(bytes_to_string_lossy))
.unwrap_or_default()
}
fn get_str_by_name(row: &mysql_async::Row, name: &str) -> String {
row_get::<String, _>(row, name)
.or_else(|| row_get::<Vec<u8>, _>(row, name).map(|b| String::from_utf8_lossy(&b).to_string()))
.or_else(|| row_get::<Vec<u8>, _>(row, name).map(bytes_to_string_lossy))
.unwrap_or_default()
}
fn get_opt_str(row: &mysql_async::Row, name: &str) -> Option<String> {
row_get::<String, _>(row, name)
.or_else(|| row_get::<Vec<u8>, _>(row, name).map(|b| String::from_utf8_lossy(&b).to_string()))
row_get::<String, _>(row, name).or_else(|| row_get::<Vec<u8>, _>(row, name).map(bytes_to_string_lossy))
}
/// First non-empty string value among the named columns (e.g. Doris `CatalogName`
@ -279,7 +285,7 @@ fn mysql_bytes_to_json(bytes: Vec<u8>, column: &mysql_async::Column) -> serde_js
.map(serde_json::Value::String)
.unwrap_or_else(|| super::binary_value_to_json(&bytes));
}
serde_json::Value::String(String::from_utf8_lossy(&bytes).to_string())
serde_json::Value::String(bytes_to_string_lossy(bytes))
}
/// Map a MySQL column to a user-facing type name for the result-grid header.
@ -3364,11 +3370,7 @@ pub async fn show_create_table_ddl(pool: &MySqlPool, database: &str, table: &str
let row = rows.first().ok_or("DDL not found")?;
row.get_opt::<String, usize>(1)
.and_then(|result| result.ok())
.or_else(|| {
row.get_opt::<Vec<u8>, usize>(1)
.and_then(|result| result.ok())
.map(|b| String::from_utf8_lossy(&b).to_string())
})
.or_else(|| row.get_opt::<Vec<u8>, usize>(1).and_then(|result| result.ok()).map(bytes_to_string_lossy))
.ok_or_else(|| "Failed to read DDL".to_string())
}
@ -3551,11 +3553,7 @@ pub async fn show_create_table_ddl_from(
let row = rows.first().ok_or("DDL not found")?;
row.get_opt::<String, usize>(1)
.and_then(|result| result.ok())
.or_else(|| {
row.get_opt::<Vec<u8>, usize>(1)
.and_then(|result| result.ok())
.map(|b| String::from_utf8_lossy(&b).to_string())
})
.or_else(|| row.get_opt::<Vec<u8>, usize>(1).and_then(|result| result.ok()).map(bytes_to_string_lossy))
.ok_or_else(|| "Failed to read DDL".to_string())
}
@ -3910,6 +3908,15 @@ mod tests {
use crate::db::connection_timeout;
use mysql_async::consts::ColumnFlags;
#[test]
fn bytes_to_string_reuses_valid_utf8_and_falls_back_lossy() {
assert_eq!(super::bytes_to_string_lossy("héllo 世界".as_bytes().to_vec()), "héllo 世界");
assert_eq!(super::bytes_to_string_lossy(vec![]), "");
// 非法 UTF-8 序列退化为替换字符,与 from_utf8_lossy 语义一致
let invalid = vec![0x66, 0x6f, 0xff, 0x6f];
assert_eq!(super::bytes_to_string_lossy(invalid.clone()), String::from_utf8_lossy(&invalid));
}
#[test]
fn mysql_column_type_names_map_to_friendly_names() {
use mysql_async::consts::ColumnType::*;