From 50874601c167e698cec4c2ffa3e8f2749f5e5363 Mon Sep 17 00:00:00 2001 From: zipg Date: Mon, 17 Aug 2026 14:54:08 +0800 Subject: [PATCH] fix(mysql): support ShardingSphere binary previews --- crates/dbx-core/src/db/mysql.rs | 124 +++++++++++++++++++++++++++++--- 1 file changed, 115 insertions(+), 9 deletions(-) diff --git a/crates/dbx-core/src/db/mysql.rs b/crates/dbx-core/src/db/mysql.rs index 12f2452115..9c5dfa2a3a 100644 --- a/crates/dbx-core/src/db/mysql.rs +++ b/crates/dbx-core/src/db/mysql.rs @@ -287,11 +287,24 @@ fn is_mysql_blob_column(column: &mysql_async::Column) -> bool { } fn is_mysql_binary_string_column(column: &mysql_async::Column) -> bool { - is_mysql_binary_charset(column) - && matches!( - column.column_type(), - ColumnType::MYSQL_TYPE_STRING | ColumnType::MYSQL_TYPE_VAR_STRING | ColumnType::MYSQL_TYPE_VARCHAR - ) + let is_string = matches!( + column.column_type(), + ColumnType::MYSQL_TYPE_STRING | ColumnType::MYSQL_TYPE_VAR_STRING | ColumnType::MYSQL_TYPE_VARCHAR + ); + if !is_string { + return false; + } + + if is_mysql_binary_charset(column) { + return true; + } + + // ShardingSphere Proxy 5.3.0 rewrites BINARY/VARBINARY result metadata to + // utf8mb4 CHAR/VARCHAR, but preserves BINARY_FLAG and adds UNSIGNED_FLAG + // to every string column. A real text column with a binary collation can + // carry BINARY_FLAG, so require both flags to avoid converting it to Hex. + let flags = column.flags(); + flags.contains(ColumnFlags::BINARY_FLAG) && flags.contains(ColumnFlags::UNSIGNED_FLAG) } fn mysql_blob_preview(bytes: &[u8], label: &str) -> serde_json::Value { @@ -387,7 +400,7 @@ pub(crate) fn mysql_column_type_name(column: &mysql_async::Column) -> String { } } MYSQL_TYPE_VARCHAR | MYSQL_TYPE_VAR_STRING => { - if binary { + if is_mysql_binary_string_column(column) { "varbinary" } else { "varchar" @@ -400,7 +413,7 @@ pub(crate) fn mysql_column_type_name(column: &mysql_async::Column) -> String { "enum" } else if flags.contains(mysql_async::consts::ColumnFlags::SET_FLAG) { "set" - } else if binary { + } else if is_mysql_binary_string_column(column) { "binary" } else { "char" @@ -639,7 +652,8 @@ fn mysql_bounded_value_preview( return None; } - let end = if is_mysql_binary_charset(column) { + let binary = is_mysql_binary_charset(column) || is_mysql_binary_string_column(column); + let end = if binary { preview_bytes.min(bytes.len()) } else { let mut end = preview_bytes.min(bytes.len()); @@ -648,7 +662,7 @@ fn mysql_bounded_value_preview( } end }; - let mut value = if is_mysql_binary_charset(column) { + let mut value = if binary { mysql_bytes_to_json(bytes[..end].to_vec(), column) } else { let text = String::from_utf8_lossy(&bytes[..end]); @@ -6373,6 +6387,32 @@ mod tests { assert_eq!(mysql_bytes_to_json(b"SN-A0001".to_vec(), &column), serde_json::json!("SN-A0001")); } + #[test] + fn mysql_shardingsphere_binary_flags_restore_proxy_binary_types() { + let proxy_flags = ColumnFlags::BINARY_FLAG | ColumnFlags::UNSIGNED_FLAG; + let binary_column = mysql_test_column(ColumnType::MYSQL_TYPE_STRING, 45, proxy_flags, 8); + let varbinary_column = mysql_test_column(ColumnType::MYSQL_TYPE_VAR_STRING, 45, proxy_flags, 32); + + assert_eq!(mysql_column_type_name(&binary_column), "binary"); + assert_eq!(mysql_column_type_name(&varbinary_column), "varbinary"); + assert_eq!( + mysql_bytes_to_json(b"150010\0\0".to_vec(), &binary_column), + serde_json::json!("0x3135303031300000") + ); + assert_eq!( + mysql_bytes_to_json(vec![0xde, 0xad, 0xbe, 0xef], &varbinary_column), + serde_json::json!("0xdeadbeef") + ); + } + + #[test] + fn mysql_shardingsphere_unsigned_text_flags_remain_text() { + let text_column = mysql_test_column(ColumnType::MYSQL_TYPE_VAR_STRING, 45, ColumnFlags::UNSIGNED_FLAG, 32); + + assert_eq!(mysql_column_type_name(&text_column), "varchar"); + assert_eq!(mysql_bytes_to_json(b"TEXT-001".to_vec(), &text_column), serde_json::json!("TEXT-001")); + } + #[test] fn mysql_binary_values_preserve_all_bytes_as_hex() { let binary_column = mysql_test_column(ColumnType::MYSQL_TYPE_STRING, 63, ColumnFlags::BINARY_FLAG, 8); @@ -7348,4 +7388,70 @@ mod tests { vec!["USE `db`", "SET NAMES utf8mb4", "SET SESSION group_concat_max_len = 1048576"] ); } + + #[tokio::test] + #[ignore = "requires a live ShardingSphere Proxy 5.3.0 fixture"] + async fn live_shardingsphere_proxy_preserves_binary_columns() { + let url = std::env::var("DBX_MYSQL_SHARDING_PROXY_URL") + .expect("DBX_MYSQL_SHARDING_PROXY_URL must point to the live proxy fixture"); + let opts = mysql_async::Opts::from_url(&url).expect("valid MySQL proxy URL"); + let pool = mysql_async::Pool::new(opts); + + let columns = + get_columns(&pool, "dbx_sharding_proxy_test", "binary_samples").await.expect("load proxy column metadata"); + let result = execute_query_with_max_rows( + &pool, + "SELECT id, fixed_value, variable_value, char_value, text_value, binary_collated \ + FROM binary_samples ORDER BY id LIMIT 100", + false, + Some(100), + MySqlQueryDialect::default(), + ) + .await + .expect("query proxy binary fixture"); + pool.disconnect().await.expect("disconnect proxy pool"); + + assert_eq!( + columns.iter().map(|column| column.data_type.as_str()).collect::>(), + vec!["int", "binary(8)", "varbinary(32)", "char(8)", "varchar(32)", "varchar(32)"] + ); + assert_eq!(result.column_types, vec!["int", "binary", "varbinary", "char", "varchar", "varchar"]); + assert_eq!( + result.rows, + vec![ + vec![ + serde_json::json!("1"), + serde_json::json!("0x3135303031300000"), + serde_json::json!("0x534e2d4130303031"), + serde_json::json!("CHAR-001"), + serde_json::json!("TEXT-001"), + serde_json::json!("BIN-TEXT-001"), + ], + vec![ + serde_json::json!("2"), + serde_json::json!("0xdeadbeef00000000"), + serde_json::json!("0xdeadbeef"), + serde_json::json!("CHAR-002"), + serde_json::json!("TEXT-002"), + serde_json::json!("BIN-TEXT-002"), + ], + vec![ + serde_json::json!("3"), + serde_json::json!("0x0000000000000000"), + serde_json::json!("0x"), + serde_json::json!("CHAR-003"), + serde_json::json!("TEXT-003"), + serde_json::json!("BIN-TEXT-003"), + ], + vec![ + serde_json::json!("4"), + serde_json::json!("0x7f80810000000000"), + serde_json::json!("0x7f8081"), + serde_json::json!("CHAR-004"), + serde_json::json!("TEXT-004"), + serde_json::json!("BIN-TEXT-004"), + ], + ] + ); + } }