mirror of
https://github.com/Syngnat/GoNavi.git
synced 2026-08-11 01:03:51 +08:00
- quoteSyncSQLString 改为方言感知:新增 syncDialectEscapesBackslash,对 mysql/mariadb/ clickhouse/tdengine/starrocks/diros/oceanbase 先翻倍反斜杠再翻倍单引号;postgres 系、 sqlserver、sqlite、duckdb 保持原样(翻倍反而会写入两个反斜杠并损坏比对) - 含反斜杠的主键原先在 IN 列表里被目标库解释成别的字符串,导致源端存在的行先被判为插入、 反查时又匹配不到而被判为删除并真的从目标删掉,最终 Success=true 但目标永久缺行 - dbType 逐层传入 formatSyncSQLLiteral 的 3 个调用点:buildPKInSelectQuery、 buildKeysetPagedTableQuery、buildSourceQueryPKInSelectSQL - escapeMySQLStringLiteral 同类修复:列注释与 DEFAULT 值内联进 MySQL/ClickHouse DDL 时, 以反斜杠结尾的注释会让 COMMENT '...\' 吞掉闭合引号并生成语法错误的 DDL - 补 5 项回归测试,方言分组做双向断言(漏加与误加都会失败)
106 lines
4.6 KiB
Go
106 lines
4.6 KiB
Go
package sync
|
||
|
||
import (
|
||
"strings"
|
||
"testing"
|
||
|
||
"GoNavi-Wails/internal/connection"
|
||
)
|
||
|
||
// TestQuoteSyncSQLStringEscapesBackslashPerDialect 覆盖分页比对字面量的方言感知转义。
|
||
//
|
||
// 回归背景:quoteSyncSQLString 原先只翻倍单引号。含反斜杠的主键(Windows 路径、正则、
|
||
// 转义 JSON)在 MySQL/ClickHouse 等以反斜杠转义的目标上会被解释成别的字符串,导致
|
||
// IN 列表匹配不到:源端存在的行先被判为需要插入,随后反查时又匹配不到而被判为需要删除,
|
||
// 于是真的从目标删掉,最终 Success=true 但目标永久缺行。
|
||
func TestQuoteSyncSQLStringEscapesBackslashPerDialect(t *testing.T) {
|
||
t.Parallel()
|
||
|
||
// 分页白名单中以反斜杠转义的方言:必须翻倍。
|
||
for _, dbType := range []string{"mysql", "mariadb", "clickhouse", "tdengine", "starrocks", "diros"} {
|
||
if got := quoteSyncSQLString(dbType, `C:\logs`); got != `'C:\\logs'` {
|
||
t.Errorf("quoteSyncSQLString(%q, %q) = %s,期望 %s", dbType, `C:\logs`, got, `'C:\\logs'`)
|
||
}
|
||
}
|
||
|
||
// 白名单中反斜杠为普通字面字符的方言:翻倍反而会损坏数据,必须保持原样。
|
||
for _, dbType := range []string{"postgres", "kingbase", "highgo", "vastbase", "opengauss", "gaussdb", "sqlserver", "sqlite", "duckdb"} {
|
||
if got := quoteSyncSQLString(dbType, `C:\logs`); got != `'C:\logs'` {
|
||
t.Errorf("quoteSyncSQLString(%q, %q) = %s,期望 %s", dbType, `C:\logs`, got, `'C:\logs'`)
|
||
}
|
||
}
|
||
|
||
// 单引号翻倍的既有行为必须保持不变。
|
||
if got := quoteSyncSQLString("mysql", `O'Brien`); got != `'O''Brien'` {
|
||
t.Errorf("mysql 单引号处理 = %s,期望 %s", got, `'O''Brien'`)
|
||
}
|
||
if got := quoteSyncSQLString("postgres", `O'Brien`); got != `'O''Brien'` {
|
||
t.Errorf("postgres 单引号处理 = %s,期望 %s", got, `'O''Brien'`)
|
||
}
|
||
// 反斜杠与单引号并存时顺序不能颠倒(先反斜杠、后单引号)。
|
||
if got := quoteSyncSQLString("mysql", `a\'b`); got != `'a\\''b'` {
|
||
t.Errorf("mysql 混合转义 = %s,期望 %s", got, `'a\\''b'`)
|
||
}
|
||
}
|
||
|
||
// TestBuildPKInSelectQueryEscapesBackslashInPrimaryKey 端到端断言主键字面量不会越出引号。
|
||
func TestBuildPKInSelectQueryEscapesBackslashInPrimaryKey(t *testing.T) {
|
||
t.Parallel()
|
||
|
||
cols := []connection.ColumnDefinition{{Name: "id"}, {Name: "name"}}
|
||
|
||
mysqlQuery := buildPKInSelectQuery("mysql", "app.users", cols, "id", []interface{}{`C:\logs`})
|
||
if !strings.Contains(mysqlQuery, `'C:\\logs'`) {
|
||
t.Errorf("MySQL 主键字面量未转义反斜杠:%s", mysqlQuery)
|
||
}
|
||
|
||
pgQuery := buildPKInSelectQuery("postgres", "app.users", cols, "id", []interface{}{`C:\logs`})
|
||
if !strings.Contains(pgQuery, `'C:\logs'`) {
|
||
t.Errorf("PostgreSQL 主键字面量不应翻倍反斜杠:%s", pgQuery)
|
||
}
|
||
}
|
||
|
||
// TestBuildKeysetPagedTableQueryEscapesBackslash keyset 分页的 WHERE pk > 字面量同样受影响。
|
||
func TestBuildKeysetPagedTableQueryEscapesBackslash(t *testing.T) {
|
||
t.Parallel()
|
||
|
||
cols := []connection.ColumnDefinition{{Name: "id"}}
|
||
query := buildKeysetPagedTableQuery("mysql", "app.users", cols, "id", `a\b`, true, 100)
|
||
if !strings.Contains(query, `'a\\b'`) {
|
||
t.Errorf("keyset 分页字面量未转义反斜杠:%s", query)
|
||
}
|
||
}
|
||
|
||
// TestEscapeMySQLStringLiteralEscapesBackslash 列注释与 DEFAULT 值的 DDL 字面量。
|
||
func TestEscapeMySQLStringLiteralEscapesBackslash(t *testing.T) {
|
||
t.Parallel()
|
||
|
||
if got := escapeMySQLStringLiteral(`C:\tmp`); got != `C:\\tmp` {
|
||
t.Errorf("escapeMySQLStringLiteral(%q) = %q,期望 %q", `C:\tmp`, got, `C:\\tmp`)
|
||
}
|
||
// 以反斜杠结尾的注释原先会吞掉闭合引号,生成语法错误的 DDL。
|
||
if got := escapeMySQLStringLiteral(`ends with\`); got != `ends with\\` {
|
||
t.Errorf("escapeMySQLStringLiteral(%q) = %q,期望 %q", `ends with\`, got, `ends with\\`)
|
||
}
|
||
if got := escapeMySQLStringLiteral(`O'Brien`); got != `O''Brien` {
|
||
t.Errorf("单引号处理被破坏:%q", got)
|
||
}
|
||
}
|
||
|
||
// TestSyncDialectEscapesBackslashCoversPaginationWhitelist 固定方言分组,
|
||
// 漏加(数据被改写)与误加(反斜杠被写成两个)两个方向都必须失败。
|
||
func TestSyncDialectEscapesBackslashCoversPaginationWhitelist(t *testing.T) {
|
||
t.Parallel()
|
||
|
||
for _, dbType := range []string{"mysql", "mariadb", "clickhouse", "tdengine", "starrocks", "diros", "oceanbase"} {
|
||
if !syncDialectEscapesBackslash(dbType) {
|
||
t.Errorf("syncDialectEscapesBackslash(%q) = false,期望 true", dbType)
|
||
}
|
||
}
|
||
for _, dbType := range []string{"postgres", "kingbase", "highgo", "vastbase", "opengauss", "gaussdb", "sqlserver", "sqlite", "duckdb", ""} {
|
||
if syncDialectEscapesBackslash(dbType) {
|
||
t.Errorf("syncDialectEscapesBackslash(%q) = true,期望 false", dbType)
|
||
}
|
||
}
|
||
}
|