mirror of
https://github.com/Syngnat/GoNavi.git
synced 2026-06-07 23:19:35 +08:00
🐛 fix(kingbase): 统一金仓标识符引用策略
- 标识符处理:下沉 Kingbase 引用逻辑,普通小写 schema/table 不再强制双引号包裹 - 表操作修复:修复截断、清空、导入、导出等路径生成异常双引号 SQL - 同步链路修复:统一数据同步、预览、迁移建表中的 Kingbase schema.table 拼接规则 - 自定义驱动兼容:补齐 kingbase8/kingbasees/kingbasev8 别名归一与写入路径处理 - 回归覆盖:新增 ldf_server.andon_events、转义引号、保留字和大小写标识符测试
This commit is contained in:
@@ -12,6 +12,8 @@ func normalizeMigrationDBType(dbType string) string {
|
||||
return "diros"
|
||||
case "postgresql":
|
||||
return "postgres"
|
||||
case "kingbase8", "kingbasees", "kingbasev8":
|
||||
return "kingbase"
|
||||
case "opengauss", "open_gauss", "open-gauss":
|
||||
return "opengauss"
|
||||
case "dm", "dm8":
|
||||
|
||||
@@ -118,16 +118,16 @@ func TestBuildMySQLToKingbaseCreateTablePlan_GeneratesAndSkipsIndexes(t *testing
|
||||
if err != nil {
|
||||
t.Fatalf("buildMySQLToKingbaseCreateTablePlan returned error: %v", err)
|
||||
}
|
||||
if !strings.Contains(createSQL, `CREATE TABLE "public"."orders"`) {
|
||||
if !strings.Contains(createSQL, `CREATE TABLE public.orders`) {
|
||||
t.Fatalf("unexpected create SQL: %s", createSQL)
|
||||
}
|
||||
if !strings.Contains(createSQL, `PRIMARY KEY ("id")`) {
|
||||
if !strings.Contains(createSQL, `PRIMARY KEY (id)`) {
|
||||
t.Fatalf("create SQL missing primary key: %s", createSQL)
|
||||
}
|
||||
if idxCreate != 1 || idxSkip != 2 {
|
||||
t.Fatalf("unexpected index summary: create=%d skip=%d", idxCreate, idxSkip)
|
||||
}
|
||||
if len(postSQL) != 1 || !strings.Contains(postSQL[0], `CREATE INDEX "idx_user_status"`) {
|
||||
if len(postSQL) != 1 || !strings.Contains(postSQL[0], `CREATE INDEX idx_user_status`) {
|
||||
t.Fatalf("unexpected post SQL: %v", postSQL)
|
||||
}
|
||||
if len(warnings) != 0 {
|
||||
@@ -177,7 +177,7 @@ func TestBuildSchemaMigrationPlan_AutoCreateWhenTargetMissing(t *testing.T) {
|
||||
if !strings.Contains(plan.PlannedAction, "自动建表") {
|
||||
t.Fatalf("unexpected planned action: %s", plan.PlannedAction)
|
||||
}
|
||||
if !strings.Contains(plan.CreateTableSQL, `CREATE TABLE "public"."orders"`) {
|
||||
if !strings.Contains(plan.CreateTableSQL, `CREATE TABLE public.orders`) {
|
||||
t.Fatalf("unexpected create table SQL: %s", plan.CreateTableSQL)
|
||||
}
|
||||
}
|
||||
@@ -665,13 +665,13 @@ func TestBuildTDengineToPGLikePlan_AutoCreateWhenTargetMissing(t *testing.T) {
|
||||
if !plan.AutoCreate {
|
||||
t.Fatalf("expected auto create enabled")
|
||||
}
|
||||
if !strings.Contains(plan.CreateTableSQL, `CREATE TABLE "public"."cpu"`) {
|
||||
if !strings.Contains(plan.CreateTableSQL, `CREATE TABLE public.cpu`) {
|
||||
t.Fatalf("unexpected create table sql: %s", plan.CreateTableSQL)
|
||||
}
|
||||
if !strings.Contains(plan.CreateTableSQL, `"ts" timestamp`) {
|
||||
if !strings.Contains(plan.CreateTableSQL, `ts timestamp`) {
|
||||
t.Fatalf("expected timestamp mapping, got: %s", plan.CreateTableSQL)
|
||||
}
|
||||
if !strings.Contains(plan.CreateTableSQL, `"payload" jsonb`) {
|
||||
if !strings.Contains(plan.CreateTableSQL, `payload jsonb`) {
|
||||
t.Fatalf("expected json mapping, got: %s", plan.CreateTableSQL)
|
||||
}
|
||||
if len(plan.Warnings) == 0 || !strings.Contains(strings.Join(plan.Warnings, " "), "TAG") {
|
||||
|
||||
@@ -1,6 +1,10 @@
|
||||
package sync
|
||||
|
||||
import "strings"
|
||||
import (
|
||||
"strings"
|
||||
|
||||
"GoNavi-Wails/internal/db"
|
||||
)
|
||||
|
||||
func normalizeSyncMode(mode string) string {
|
||||
m := strings.ToLower(strings.TrimSpace(mode))
|
||||
@@ -21,9 +25,11 @@ func quoteIdentByType(dbType string, ident string) string {
|
||||
return ident
|
||||
}
|
||||
|
||||
switch dbType {
|
||||
switch normalizeMigrationDBType(dbType) {
|
||||
case "mysql", "mariadb", "oceanbase", "diros", "sphinx", "clickhouse", "tdengine":
|
||||
return "`" + strings.ReplaceAll(ident, "`", "``") + "`"
|
||||
case "kingbase":
|
||||
return db.QuoteKingbaseIdentifier(ident)
|
||||
case "sqlserver":
|
||||
escaped := strings.ReplaceAll(ident, "]", "]]")
|
||||
return "[" + escaped + "]"
|
||||
@@ -38,9 +44,21 @@ func quoteQualifiedIdentByType(dbType string, ident string) string {
|
||||
return raw
|
||||
}
|
||||
|
||||
normalizedType := normalizeMigrationDBType(dbType)
|
||||
if normalizedType == "kingbase" {
|
||||
schema, table := db.SplitKingbaseQualifiedName(raw)
|
||||
if table == "" {
|
||||
return quoteIdentByType(normalizedType, raw)
|
||||
}
|
||||
if schema == "" {
|
||||
return quoteIdentByType(normalizedType, table)
|
||||
}
|
||||
return quoteIdentByType(normalizedType, schema) + "." + quoteIdentByType(normalizedType, table)
|
||||
}
|
||||
|
||||
parts := strings.Split(raw, ".")
|
||||
if len(parts) <= 1 {
|
||||
return quoteIdentByType(dbType, raw)
|
||||
return quoteIdentByType(normalizedType, raw)
|
||||
}
|
||||
|
||||
quotedParts := make([]string, 0, len(parts))
|
||||
@@ -49,11 +67,11 @@ func quoteQualifiedIdentByType(dbType string, ident string) string {
|
||||
if part == "" {
|
||||
continue
|
||||
}
|
||||
quotedParts = append(quotedParts, quoteIdentByType(dbType, part))
|
||||
quotedParts = append(quotedParts, quoteIdentByType(normalizedType, part))
|
||||
}
|
||||
|
||||
if len(quotedParts) == 0 {
|
||||
return quoteIdentByType(dbType, raw)
|
||||
return quoteIdentByType(normalizedType, raw)
|
||||
}
|
||||
return strings.Join(quotedParts, ".")
|
||||
}
|
||||
@@ -65,6 +83,17 @@ func normalizeSchemaAndTable(dbType string, dbName string, tableName string) (st
|
||||
return rawDB, rawTable
|
||||
}
|
||||
|
||||
normalizedType := normalizeMigrationDBType(dbType)
|
||||
if normalizedType == "kingbase" {
|
||||
schema, table := db.SplitKingbaseQualifiedName(rawTable)
|
||||
if schema != "" && table != "" {
|
||||
return schema, table
|
||||
}
|
||||
if table != "" {
|
||||
return "public", table
|
||||
}
|
||||
}
|
||||
|
||||
if parts := strings.SplitN(rawTable, ".", 2); len(parts) == 2 {
|
||||
schema := strings.TrimSpace(parts[0])
|
||||
table := strings.TrimSpace(parts[1])
|
||||
@@ -73,7 +102,7 @@ func normalizeSchemaAndTable(dbType string, dbName string, tableName string) (st
|
||||
}
|
||||
}
|
||||
|
||||
switch strings.ToLower(strings.TrimSpace(dbType)) {
|
||||
switch normalizedType {
|
||||
case "postgres", "kingbase", "highgo", "vastbase", "opengauss":
|
||||
return "public", rawTable
|
||||
case "duckdb":
|
||||
@@ -92,7 +121,7 @@ func qualifiedNameForQuery(dbType string, schema string, table string, original
|
||||
return raw
|
||||
}
|
||||
|
||||
switch strings.ToLower(strings.TrimSpace(dbType)) {
|
||||
switch normalizeMigrationDBType(dbType) {
|
||||
case "postgres", "kingbase", "highgo", "vastbase", "opengauss":
|
||||
s := strings.TrimSpace(schema)
|
||||
if s == "" {
|
||||
|
||||
60
internal/sync/sql_helpers_test.go
Normal file
60
internal/sync/sql_helpers_test.go
Normal file
@@ -0,0 +1,60 @@
|
||||
package sync
|
||||
|
||||
import "testing"
|
||||
|
||||
func TestQuoteQualifiedIdentByType_KingbaseLeavesLowercaseQualifiedTableUnquoted(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
got := quoteQualifiedIdentByType("kingbase", "ldf_server.andon_events")
|
||||
if got != "ldf_server.andon_events" {
|
||||
t.Fatalf("unexpected kingbase qualified identifier: %s", got)
|
||||
}
|
||||
}
|
||||
|
||||
func TestQuoteQualifiedIdentByType_KingbaseNormalizesEscapedQuotedQualifiedTable(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
got := quoteQualifiedIdentByType("kingbase", `\"Idf_server\".\"andon_events\"`)
|
||||
if got != `"Idf_server".andon_events` {
|
||||
t.Fatalf("unexpected kingbase qualified identifier: %s", got)
|
||||
}
|
||||
}
|
||||
|
||||
func TestQuoteQualifiedIdentByType_KingbaseAliasUsesKingbaseQuoting(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
got := quoteQualifiedIdentByType("kingbase8", `\"ldf_server\".\"andon_events\"`)
|
||||
if got != "ldf_server.andon_events" {
|
||||
t.Fatalf("unexpected kingbase alias qualified identifier: %s", got)
|
||||
}
|
||||
}
|
||||
|
||||
func TestQuoteIdentByType_KingbaseStillQuotesReservedAndMixedCaseIdentifiers(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
if got := quoteIdentByType("kingbase", "select"); got != `"select"` {
|
||||
t.Fatalf("expected reserved word to stay quoted, got %s", got)
|
||||
}
|
||||
if got := quoteIdentByType("kingbase", "CamelName"); got != `"CamelName"` {
|
||||
t.Fatalf("expected mixed-case identifier to stay quoted, got %s", got)
|
||||
}
|
||||
}
|
||||
|
||||
func TestNormalizeSchemaAndTable_KingbaseNormalizesEscapedQualifiedName(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
schema, table := normalizeSchemaAndTable("kingbase", "demo", `\"Idf_server\".\"andon_events\"`)
|
||||
if schema != "Idf_server" || table != "andon_events" {
|
||||
t.Fatalf("unexpected kingbase schema/table: %q.%q", schema, table)
|
||||
}
|
||||
}
|
||||
|
||||
func TestNormalizeMigrationDBType_KingbaseAliases(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
for _, in := range []string{"kingbase8", "kingbasees", "kingbasev8"} {
|
||||
if got := normalizeMigrationDBType(in); got != "kingbase" {
|
||||
t.Fatalf("normalizeMigrationDBType(%q)=%q, want kingbase", in, got)
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user