🐛 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:
Syngnat
2026-06-29 20:09:01 +08:00
parent b3f69ac401
commit b434247838
2 changed files with 168 additions and 4 deletions

View File

@@ -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)

View File

@@ -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()