diff --git a/crates/dbx-core/src/data_grid_sql.rs b/crates/dbx-core/src/data_grid_sql.rs index 907735184..2d4edf1e4 100644 --- a/crates/dbx-core/src/data_grid_sql.rs +++ b/crates/dbx-core/src/data_grid_sql.rs @@ -982,11 +982,7 @@ fn build_primary_key_where( .unwrap_or(usize::MAX), ) .unwrap_or(&Value::Null); - format!( - "{} = {}", - predicate_ident(database_type, primary_key), - format_grid_sql_literal(value, database_type, column_info_for(column_info, primary_key)) - ) + build_column_predicate(database_type, primary_key, value, column_info_for(column_info, primary_key)) }) .collect::>() .join(" AND ") @@ -1026,11 +1022,30 @@ fn build_column_predicate( let ident = predicate_ident(database_type, column); if value.is_null() { format!("{ident} IS NULL") + } else if uses_mysql_binary_text_predicate(database_type, value, column_info) { + format!("BINARY {ident} = {}", format_grid_sql_literal(value, database_type, column_info)) } else { format!("{ident} = {}", format_grid_sql_literal(value, database_type, column_info)) } } +fn uses_mysql_binary_text_predicate( + database_type: Option, + value: &Value, + column_info: Option<&DataGridColumnInfo>, +) -> bool { + database_type == Some(DatabaseType::Mysql) + && value.is_string() + && column_info.map(|column| is_textual_column_type(&column.data_type)).unwrap_or(false) +} + +fn is_textual_column_type(data_type: &str) -> bool { + let lower = data_type.to_ascii_lowercase(); + lower.split(|ch: char| !ch.is_ascii_alphanumeric()).any(|token| { + matches!(token, "char" | "varchar" | "text" | "tinytext" | "mediumtext" | "longtext" | "enum" | "set") + }) +} + fn is_oracle_row_id(database_type: Option, name: Option<&str>) -> bool { database_type == Some(DatabaseType::Oracle) && name.is_some_and(|name| name.eq_ignore_ascii_case(DBX_ROWID_COLUMN)) } @@ -1520,6 +1535,36 @@ mod tests { ); } + #[test] + fn mysql_text_predicates_use_binary_comparison_for_width_sensitive_edits() { + let result = prepare_data_grid_save(DataGridSaveStatementOptions { + database_type: Some(DatabaseType::Mysql), + table_meta: DataGridTableMeta { + schema: None, + table_name: "parts".to_string(), + primary_keys: vec![], + columns: Some(vec![column("code", "varchar(32)", true, None)]), + }, + columns: vec!["code".to_string()], + source_columns: None, + rows: vec![vec![json!("S471355(0)")]], + dirty_rows: vec![(0, vec![(0, json!("S471355(0)"))])], + deleted_rows: vec![], + new_rows: vec![], + }); + + assert_eq!( + result.statements, + vec!["UPDATE `parts` SET `code` = 'S471355(0)' WHERE BINARY `code` = 'S471355(0)';"] + ); + assert_eq!( + result.rollback_statements, + vec![ + "UPDATE `parts` SET `code` = 'S471355(0)' WHERE BINARY `code` = 'S471355(0)' AND BINARY `code` = 'S471355(0)';" + ] + ); + } + #[test] fn validates_duplicate_inserted_primary_keys() { let result = prepare_data_grid_save(DataGridSaveStatementOptions {