fix(sqlserver): preserve backslashes in grid edits

This commit is contained in:
zipg 2026-07-22 18:53:36 +08:00 committed by GitHub
parent a73adfdcf0
commit 8eba342a7f
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
1 changed files with 84 additions and 3 deletions

View File

@ -1801,6 +1801,9 @@ pub fn format_grid_sql_literal(
let escaped_text = literal_text.replace('\\', "\\\\").replace('\'', "''");
return format!("E'{escaped_text}'");
}
if database_type == Some(DatabaseType::SqlServer) {
return format_sqlserver_unicode_literal(&literal_text);
}
let escaped_text = if database_type == Some(DatabaseType::Neo4j) {
literal_text.replace('\\', "\\\\").replace('\'', "\\'")
} else if is_sqlite_literal_database(database_type) {
@ -1811,10 +1814,39 @@ pub fn format_grid_sql_literal(
literal_text.replace('\\', "\\\\").replace('\'', "''")
};
let escaped = format!("'{escaped_text}'");
if database_type == Some(DatabaseType::SqlServer) {
format!("N{escaped}")
escaped
}
fn format_sqlserver_unicode_literal(text: &str) -> String {
let mut parts = Vec::new();
let mut segment = String::new();
for ch in text.chars() {
let line_break = match ch {
'\r' => Some(13),
'\n' => Some(10),
_ => None,
};
if let Some(codepoint) = line_break {
if !segment.is_empty() {
parts.push(format!("N'{}'", segment.replace('\'', "''")));
segment.clear();
}
// Keep physical newlines out of generated SQL so a preceding
// backslash cannot be consumed as a line-continuation marker.
parts.push(format!("NCHAR({codepoint})"));
} else {
segment.push(ch);
}
}
if !segment.is_empty() {
parts.push(format!("N'{}'", segment.replace('\'', "''")));
}
if parts.is_empty() {
"N''".to_string()
} else {
escaped
parts.join(" + ")
}
}
@ -4795,6 +4827,55 @@ mod tests {
}
}
#[test]
fn sqlserver_literals_do_not_double_escape_backslashes() {
assert_eq!(format_grid_sql_literal(&json!(r".\SQL2016"), Some(DatabaseType::SqlServer), None), r"N'.\SQL2016'");
assert_eq!(
format_grid_sql_literal(&json!(r".\SQL2016's"), Some(DatabaseType::SqlServer), None),
r"N'.\SQL2016''s'"
);
}
#[test]
fn sqlserver_literals_preserve_backslashes_before_line_breaks() {
assert_eq!(
format_grid_sql_literal(&json!("line1\\\nline2"), Some(DatabaseType::SqlServer), None),
r"N'line1\' + NCHAR(10) + N'line2'"
);
assert_eq!(
format_grid_sql_literal(&json!("line1\\\r\nline2"), Some(DatabaseType::SqlServer), None),
r"N'line1\' + NCHAR(13) + NCHAR(10) + N'line2'"
);
}
#[test]
fn prepares_sqlserver_updates_without_doubling_backslashes() {
let result = prepare_data_grid_save(DataGridSaveStatementOptions {
database_type: Some(DatabaseType::SqlServer),
identifier_quote: None,
table_meta: DataGridTableMeta {
catalog: None,
database: None,
schema: Some("dbo".to_string()),
table_name: "dbx_issue_4181".to_string(),
primary_keys: vec!["id".to_string()],
columns: Some(vec![column("id", "int", false, None), column("value", "nvarchar(100)", true, None)]),
},
columns: vec!["id".to_string(), "value".to_string()],
source_columns: None,
rows: vec![vec![json!(1), json!("initial")]],
dirty_rows: vec![(0, vec![(1, json!(r".\SQL2016"))])],
deleted_rows: vec![],
new_rows: vec![],
});
assert_eq!(result.validation_error, None);
assert_eq!(
result.statements,
vec![r"UPDATE [dbo].[dbx_issue_4181] SET [value] = N'.\SQL2016' WHERE [id] = 1;"]
);
}
#[test]
fn mysql_and_neo4j_literals_keep_doubling_backslashes() {
// MySQL (default sql_mode, without NO_BACKSLASH_ESCAPES) and Neo4j do treat