mirror of
https://github.com/Syngnat/GoNavi.git
synced 2026-07-07 19:11:39 +08:00
🐛 fix(mysql): 兼容 MyCAT 场景下数据库列表解析逻辑
- 扩展数据库名字段识别,兼容 SCHEMA、database_name 等返回列名 - 按驱动返回列顺序兜底提取单列结果,避免非标准列名导致误判为空 - 补充 MyCAT 风格回归测试,覆盖 SHOW DATABASES 与当前库回退逻辑 Close #552
This commit is contained in:
@@ -163,7 +163,8 @@ func (s *Service) stopMCPHTTPServer(ctx context.Context, message string) (ai.MCP
|
||||
}
|
||||
|
||||
// Shutdown 释放 AI Service 中的运行时资源。
|
||||
func (s *Service) Shutdown(ctx context.Context) {
|
||||
func (s *Service) Shutdown() {
|
||||
ctx := context.Background()
|
||||
_, _ = s.stopMCPHTTPServer(ctx, "应用关闭,GoNavi MCP HTTP 服务已停止")
|
||||
}
|
||||
|
||||
|
||||
@@ -57,7 +57,7 @@ func TestMCPHTTPServerLifecycleFromAIService(t *testing.T) {
|
||||
service := NewServiceWithSecretStore(secretstore.NewUnavailableStore("test"))
|
||||
InitializeLifecycle(service, context.Background())
|
||||
t.Cleanup(func() {
|
||||
service.Shutdown(context.Background())
|
||||
service.Shutdown()
|
||||
})
|
||||
|
||||
initial := service.AIGetMCPHTTPServerStatus()
|
||||
@@ -123,7 +123,7 @@ func TestMCPHTTPServerStartUsesCustomAddrAndToken(t *testing.T) {
|
||||
service := NewServiceWithSecretStore(secretstore.NewUnavailableStore("test"))
|
||||
InitializeLifecycle(service, context.Background())
|
||||
t.Cleanup(func() {
|
||||
service.Shutdown(context.Background())
|
||||
service.Shutdown()
|
||||
})
|
||||
|
||||
started, err := service.AIStartMCPHTTPServer(ai.MCPHTTPServerOptions{
|
||||
|
||||
@@ -178,8 +178,8 @@ func (a *App) LogWindowDiagnostic(stage string, payload string) {
|
||||
logger.Warnf("窗口诊断:stage=%s payload=%s", stage, payload)
|
||||
}
|
||||
|
||||
// Shutdown is called when the app terminates
|
||||
func (a *App) Shutdown(ctx context.Context) {
|
||||
// Shutdown is called when the app terminates.
|
||||
func (a *App) Shutdown() {
|
||||
logger.Infof("应用开始关闭,准备释放资源")
|
||||
a.rollbackPendingSQLTransactionsOnShutdown()
|
||||
a.mu.Lock()
|
||||
|
||||
@@ -594,6 +594,18 @@ var mysqlDatabaseQueries = []string{
|
||||
"SELECT DATABASE() AS `Database`",
|
||||
}
|
||||
|
||||
var mysqlDatabaseNameKeys = []string{
|
||||
"Database",
|
||||
"database",
|
||||
"DATABASE",
|
||||
"database_name",
|
||||
"DATABASE_NAME",
|
||||
"schema",
|
||||
"SCHEMA",
|
||||
"schema_name",
|
||||
"SCHEMA_NAME",
|
||||
}
|
||||
|
||||
func collectMySQLDatabaseNames(queryFn func(string) ([]map[string]interface{}, []string, error)) ([]string, error) {
|
||||
if queryFn == nil {
|
||||
return nil, fmt.Errorf("查询函数为空")
|
||||
@@ -603,34 +615,59 @@ func collectMySQLDatabaseNames(queryFn func(string) ([]map[string]interface{}, [
|
||||
seen := make(map[string]struct{}, 8)
|
||||
var lastErr error
|
||||
|
||||
appendNames := func(rows []map[string]interface{}) {
|
||||
for _, row := range rows {
|
||||
for _, key := range []string{"Database", "database"} {
|
||||
val, ok := row[key]
|
||||
if !ok || val == nil {
|
||||
continue
|
||||
}
|
||||
name := strings.TrimSpace(fmt.Sprintf("%v", val))
|
||||
if name == "" || strings.EqualFold(name, "<nil>") {
|
||||
continue
|
||||
}
|
||||
if _, exists := seen[name]; exists {
|
||||
continue
|
||||
}
|
||||
seen[name] = struct{}{}
|
||||
names = append(names, name)
|
||||
break
|
||||
normalizeName := func(val interface{}) string {
|
||||
if val == nil {
|
||||
return ""
|
||||
}
|
||||
name := strings.TrimSpace(fmt.Sprintf("%v", val))
|
||||
if name == "" || strings.EqualFold(name, "<nil>") || strings.EqualFold(name, "null") {
|
||||
return ""
|
||||
}
|
||||
return name
|
||||
}
|
||||
|
||||
extractName := func(row map[string]interface{}, columns []string) string {
|
||||
for _, key := range mysqlDatabaseNameKeys {
|
||||
if name := normalizeName(row[key]); name != "" {
|
||||
return name
|
||||
}
|
||||
}
|
||||
for _, column := range columns {
|
||||
if name := normalizeName(row[column]); name != "" {
|
||||
return name
|
||||
}
|
||||
}
|
||||
if len(row) == 1 {
|
||||
for _, val := range row {
|
||||
if name := normalizeName(val); name != "" {
|
||||
return name
|
||||
}
|
||||
}
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
appendNames := func(rows []map[string]interface{}, columns []string) {
|
||||
for _, row := range rows {
|
||||
name := extractName(row, columns)
|
||||
if name == "" {
|
||||
continue
|
||||
}
|
||||
if _, exists := seen[name]; exists {
|
||||
continue
|
||||
}
|
||||
seen[name] = struct{}{}
|
||||
names = append(names, name)
|
||||
}
|
||||
}
|
||||
|
||||
for _, sqlText := range mysqlDatabaseQueries {
|
||||
rows, _, err := queryFn(sqlText)
|
||||
rows, columns, err := queryFn(sqlText)
|
||||
if err != nil {
|
||||
lastErr = err
|
||||
continue
|
||||
}
|
||||
appendNames(rows)
|
||||
appendNames(rows, columns)
|
||||
if len(names) > 0 {
|
||||
return names, nil
|
||||
}
|
||||
|
||||
@@ -15,8 +15,8 @@ func TestCollectMySQLDatabaseNames_FallsBackToCurrentDatabase(t *testing.T) {
|
||||
return nil, nil, errors.New("Error 1227 (42000): Access denied; you need (at least one of) the SHOW DATABASES privilege(s) for this operation")
|
||||
case mysqlDatabaseQueries[1]:
|
||||
return []map[string]interface{}{
|
||||
{"Database": "biz_app"},
|
||||
}, nil, nil
|
||||
{"database_name": "biz_app"},
|
||||
}, []string{"database_name"}, nil
|
||||
default:
|
||||
return nil, nil, errors.New("unexpected query")
|
||||
}
|
||||
@@ -31,6 +31,33 @@ func TestCollectMySQLDatabaseNames_FallsBackToCurrentDatabase(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestCollectMySQLDatabaseNames_AcceptsMyCATStyleSchemaColumn(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
got, err := collectMySQLDatabaseNames(func(query string) ([]map[string]interface{}, []string, error) {
|
||||
switch query {
|
||||
case mysqlDatabaseQueries[0]:
|
||||
return []map[string]interface{}{
|
||||
{"SCHEMA": "analytics"},
|
||||
}, []string{"SCHEMA"}, nil
|
||||
case mysqlDatabaseQueries[1]:
|
||||
return []map[string]interface{}{
|
||||
{"Database": "should_not_be_used"},
|
||||
}, []string{"Database"}, nil
|
||||
default:
|
||||
return nil, nil, errors.New("unexpected query")
|
||||
}
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatalf("collectMySQLDatabaseNames 返回错误: %v", err)
|
||||
}
|
||||
|
||||
want := []string{"analytics"}
|
||||
if !reflect.DeepEqual(got, want) {
|
||||
t.Fatalf("unexpected database names, got=%v want=%v", got, want)
|
||||
}
|
||||
}
|
||||
|
||||
func TestCollectMySQLDatabaseNames_PrefersShowDatabasesWhenAvailable(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
|
||||
@@ -47,10 +47,7 @@ func (b *AppBackend) Close(ctx context.Context) error {
|
||||
if b == nil || b.app == nil {
|
||||
return nil
|
||||
}
|
||||
if ctx == nil {
|
||||
ctx = context.Background()
|
||||
}
|
||||
b.app.Shutdown(ctx)
|
||||
b.app.Shutdown()
|
||||
return nil
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user