mirror of
https://github.com/Syngnat/GoNavi.git
synced 2026-07-12 16:02:48 +08:00
🐛 fix(ddl): 补齐 kingbase 表DDL索引输出
- 在 SHOW CREATE TABLE 不可用时,fallback 额外拉取索引元数据并生成 CREATE INDEX 语句\n- 跳过与主键重复的索引,避免在 fallback DDL 中重复输出主键索引\n- 补充 kingbase DDL fallback 的索引回归测试\n\nFixes #601
This commit is contained in:
@@ -3,6 +3,7 @@ package app
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"sort"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
@@ -1796,7 +1797,12 @@ func resolveCreateStatementWithFallbackWithText(dbInst db.Database, config conne
|
||||
return "", colErr
|
||||
}
|
||||
|
||||
fallbackDDL, buildErr := buildFallbackCreateStatementWithText(dbType, ddlSchemaName, ddlTableName, columns, text)
|
||||
var indexes []connection.IndexDefinition
|
||||
if indexRows, idxErr := dbInst.GetIndexes(metadataSchemaName, metadataTableName); idxErr == nil {
|
||||
indexes = indexRows
|
||||
}
|
||||
|
||||
fallbackDDL, buildErr := buildFallbackCreateStatementWithText(dbType, ddlSchemaName, ddlTableName, columns, indexes, text)
|
||||
if buildErr != nil {
|
||||
if sourceErr != nil {
|
||||
return "", sourceErr
|
||||
@@ -1901,10 +1907,10 @@ func hasCreateTableOrViewHead(sqlText string) bool {
|
||||
}
|
||||
|
||||
func buildFallbackCreateStatement(dbType string, schemaName string, tableName string, columns []connection.ColumnDefinition) (string, error) {
|
||||
return buildFallbackCreateStatementWithText(dbType, schemaName, tableName, columns, defaultDBBackendText)
|
||||
return buildFallbackCreateStatementWithText(dbType, schemaName, tableName, columns, nil, defaultDBBackendText)
|
||||
}
|
||||
|
||||
func buildFallbackCreateStatementWithText(dbType string, schemaName string, tableName string, columns []connection.ColumnDefinition, text func(string, map[string]any) string) (string, error) {
|
||||
func buildFallbackCreateStatementWithText(dbType string, schemaName string, tableName string, columns []connection.ColumnDefinition, indexes []connection.IndexDefinition, text func(string, map[string]any) string) (string, error) {
|
||||
if text == nil {
|
||||
text = defaultDBBackendText
|
||||
}
|
||||
@@ -1962,6 +1968,7 @@ func buildFallbackCreateStatementWithText(dbType string, schemaName string, tabl
|
||||
if len(primaryKeys) > 0 {
|
||||
columnLines = append(columnLines, " PRIMARY KEY ("+strings.Join(primaryKeys, ", ")+")")
|
||||
}
|
||||
indexStatements := buildFallbackIndexStatements(dbType, qualifiedTable, primaryKeys, indexes)
|
||||
|
||||
ddl := strings.Builder{}
|
||||
ddl.WriteString("CREATE TABLE ")
|
||||
@@ -1969,6 +1976,10 @@ func buildFallbackCreateStatementWithText(dbType string, schemaName string, tabl
|
||||
ddl.WriteString(" (\n")
|
||||
ddl.WriteString(strings.Join(columnLines, ",\n"))
|
||||
ddl.WriteString("\n);")
|
||||
if len(indexStatements) > 0 {
|
||||
ddl.WriteString("\n")
|
||||
ddl.WriteString(strings.Join(indexStatements, "\n"))
|
||||
}
|
||||
if len(columnCommentLines) > 0 {
|
||||
ddl.WriteString("\n")
|
||||
ddl.WriteString(strings.Join(columnCommentLines, "\n"))
|
||||
@@ -1976,6 +1987,108 @@ func buildFallbackCreateStatementWithText(dbType string, schemaName string, tabl
|
||||
return ddl.String(), nil
|
||||
}
|
||||
|
||||
type fallbackIndexGroup struct {
|
||||
Name string
|
||||
Unique bool
|
||||
Columns []string
|
||||
}
|
||||
|
||||
func buildFallbackIndexStatements(dbType string, qualifiedTable string, primaryKeys []string, indexes []connection.IndexDefinition) []string {
|
||||
grouped := groupFallbackIndexDefinitions(indexes)
|
||||
if len(grouped) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
statements := make([]string, 0, len(grouped))
|
||||
for _, idx := range grouped {
|
||||
if strings.TrimSpace(idx.Name) == "" || len(idx.Columns) == 0 {
|
||||
continue
|
||||
}
|
||||
if sameFallbackColumnNameList(idx.Columns, primaryKeys) {
|
||||
continue
|
||||
}
|
||||
|
||||
quotedColumns := make([]string, 0, len(idx.Columns))
|
||||
for _, columnName := range idx.Columns {
|
||||
columnName = strings.TrimSpace(columnName)
|
||||
if columnName == "" {
|
||||
continue
|
||||
}
|
||||
quotedColumns = append(quotedColumns, quoteIdentByType(dbType, columnName))
|
||||
}
|
||||
if len(quotedColumns) == 0 {
|
||||
continue
|
||||
}
|
||||
|
||||
prefix := "CREATE INDEX"
|
||||
if idx.Unique {
|
||||
prefix = "CREATE UNIQUE INDEX"
|
||||
}
|
||||
statements = append(statements, fmt.Sprintf(
|
||||
"%s %s ON %s (%s);",
|
||||
prefix,
|
||||
quoteIdentByType(dbType, idx.Name),
|
||||
qualifiedTable,
|
||||
strings.Join(quotedColumns, ", "),
|
||||
))
|
||||
}
|
||||
|
||||
return statements
|
||||
}
|
||||
|
||||
func groupFallbackIndexDefinitions(indexes []connection.IndexDefinition) []fallbackIndexGroup {
|
||||
if len(indexes) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
groupMap := make(map[string][]connection.IndexDefinition)
|
||||
order := make([]string, 0)
|
||||
for _, idx := range indexes {
|
||||
name := strings.TrimSpace(idx.Name)
|
||||
if name == "" {
|
||||
continue
|
||||
}
|
||||
if _, ok := groupMap[name]; !ok {
|
||||
order = append(order, name)
|
||||
}
|
||||
groupMap[name] = append(groupMap[name], idx)
|
||||
}
|
||||
|
||||
grouped := make([]fallbackIndexGroup, 0, len(order))
|
||||
for _, name := range order {
|
||||
rows := groupMap[name]
|
||||
sort.SliceStable(rows, func(i, j int) bool {
|
||||
return rows[i].SeqInIndex < rows[j].SeqInIndex
|
||||
})
|
||||
|
||||
group := fallbackIndexGroup{Name: name, Unique: true}
|
||||
for _, row := range rows {
|
||||
if row.NonUnique != 0 {
|
||||
group.Unique = false
|
||||
}
|
||||
columnName := strings.TrimSpace(row.ColumnName)
|
||||
if columnName != "" {
|
||||
group.Columns = append(group.Columns, columnName)
|
||||
}
|
||||
}
|
||||
grouped = append(grouped, group)
|
||||
}
|
||||
|
||||
return grouped
|
||||
}
|
||||
|
||||
func sameFallbackColumnNameList(a []string, b []string) bool {
|
||||
if len(a) == 0 || len(a) != len(b) {
|
||||
return false
|
||||
}
|
||||
for i := range a {
|
||||
if !strings.EqualFold(strings.TrimSpace(a[i]), strings.TrimSpace(b[i])) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
func buildFallbackColumnCommentStatement(dbType string, schemaName string, tableName string, qualifiedTable string, columnName string, comment string) string {
|
||||
colName := strings.TrimSpace(columnName)
|
||||
commentText := strings.TrimSpace(comment)
|
||||
|
||||
@@ -13,6 +13,8 @@ type fakeCreateStatementDB struct {
|
||||
createErr error
|
||||
columns []connection.ColumnDefinition
|
||||
columnsErr error
|
||||
indexes []connection.IndexDefinition
|
||||
indexesErr error
|
||||
queryRows []map[string]interface{}
|
||||
queryErr error
|
||||
|
||||
@@ -20,7 +22,10 @@ type fakeCreateStatementDB struct {
|
||||
createTable string
|
||||
colsSchema string
|
||||
colsTable string
|
||||
indexSchema string
|
||||
indexTable string
|
||||
columnsCalls int
|
||||
indexCalls int
|
||||
queries []string
|
||||
}
|
||||
|
||||
@@ -49,7 +54,10 @@ func (f *fakeCreateStatementDB) GetAllColumns(dbName string) ([]connection.Colum
|
||||
return nil, nil
|
||||
}
|
||||
func (f *fakeCreateStatementDB) GetIndexes(dbName, tableName string) ([]connection.IndexDefinition, error) {
|
||||
return nil, nil
|
||||
f.indexCalls++
|
||||
f.indexSchema = dbName
|
||||
f.indexTable = tableName
|
||||
return f.indexes, f.indexesErr
|
||||
}
|
||||
func (f *fakeCreateStatementDB) GetForeignKeys(dbName, tableName string) ([]connection.ForeignKeyDefinition, error) {
|
||||
return nil, nil
|
||||
@@ -278,6 +286,49 @@ func TestResolveCreateStatementWithFallback_KingbaseIncludesColumnComments(t *te
|
||||
}
|
||||
}
|
||||
|
||||
func TestResolveCreateStatementWithFallback_KingbaseIncludesSecondaryIndexes(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
dbInst := &fakeCreateStatementDB{
|
||||
createSQL: "SHOW CREATE TABLE not directly supported in Kingbase/Postgres via SQL",
|
||||
columns: []connection.ColumnDefinition{
|
||||
{Name: "id", Type: "bigint", Nullable: "NO", Key: "PRI"},
|
||||
{Name: "tenant_id", Type: "bigint", Nullable: "NO"},
|
||||
{Name: "api_name", Type: "character varying(100 char)", Nullable: "YES"},
|
||||
},
|
||||
indexes: []connection.IndexDefinition{
|
||||
{Name: "mes_third_sys_log_pkey", ColumnName: "id", NonUnique: 0, SeqInIndex: 1, IndexType: "BTREE"},
|
||||
{Name: "idx_mes_third_sys_log_tenant_api", ColumnName: "tenant_id", NonUnique: 1, SeqInIndex: 1, IndexType: "BTREE"},
|
||||
{Name: "idx_mes_third_sys_log_tenant_api", ColumnName: "api_name", NonUnique: 1, SeqInIndex: 2, IndexType: "BTREE"},
|
||||
{Name: "uk_mes_third_sys_log_api", ColumnName: "api_name", NonUnique: 0, SeqInIndex: 1, IndexType: "BTREE"},
|
||||
},
|
||||
}
|
||||
|
||||
ddl, err := resolveCreateStatementWithFallback(dbInst, connection.ConnectionConfig{
|
||||
Type: "kingbase",
|
||||
}, "demo_db", "ldf_server.mes_third_sys_log")
|
||||
if err != nil {
|
||||
t.Fatalf("resolveCreateStatementWithFallback() unexpected error: %v", err)
|
||||
}
|
||||
if dbInst.indexCalls != 1 {
|
||||
t.Fatalf("expected one GetIndexes call, got %d", dbInst.indexCalls)
|
||||
}
|
||||
if dbInst.indexSchema != "ldf_server" || dbInst.indexTable != "mes_third_sys_log" {
|
||||
t.Fatalf("expected index target ldf_server.mes_third_sys_log, got %q.%q", dbInst.indexSchema, dbInst.indexTable)
|
||||
}
|
||||
for _, want := range []string{
|
||||
`CREATE INDEX idx_mes_third_sys_log_tenant_api ON ldf_server.mes_third_sys_log (tenant_id, api_name);`,
|
||||
`CREATE UNIQUE INDEX uk_mes_third_sys_log_api ON ldf_server.mes_third_sys_log (api_name);`,
|
||||
} {
|
||||
if !strings.Contains(ddl, want) {
|
||||
t.Fatalf("expected fallback DDL to contain %q, got: %s", want, ddl)
|
||||
}
|
||||
}
|
||||
if strings.Contains(ddl, `CREATE UNIQUE INDEX mes_third_sys_log_pkey`) {
|
||||
t.Fatalf("primary key index should not be emitted separately, got: %s", ddl)
|
||||
}
|
||||
}
|
||||
|
||||
func TestResolveCreateStatementWithFallback_KeepQualifiedSchema(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
|
||||
Reference in New Issue
Block a user