From 3da3a3fb132f36b68ed67508bcb61ec54a44583b Mon Sep 17 00:00:00 2001 From: Syngnat Date: Sun, 14 Jun 2026 16:36:42 +0800 Subject: [PATCH] =?UTF-8?q?=F0=9F=90=9B=20fix(mysql):=20=E5=85=BC=E5=AE=B9?= =?UTF-8?q?=20MyCAT=20=E5=9C=BA=E6=99=AF=E4=B8=8B=E6=95=B0=E6=8D=AE?= =?UTF-8?q?=E5=BA=93=E5=88=97=E8=A1=A8=E8=A7=A3=E6=9E=90=E9=80=BB=E8=BE=91?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 扩展数据库名字段识别,兼容 SCHEMA、database_name 等返回列名 - 按驱动返回列顺序兜底提取单列结果,避免非标准列名导致误判为空 - 补充 MyCAT 风格回归测试,覆盖 SHOW DATABASES 与当前库回退逻辑 Close #552 --- frontend/wailsjs/go/aiservice/Service.d.ts | 4 +- frontend/wailsjs/go/aiservice/Service.js | 6 +- frontend/wailsjs/go/app/App.d.ts | 4 +- frontend/wailsjs/go/app/App.js | 6 +- go.mod | 2 +- go.sum | 6 ++ internal/ai/service/mcp_http_server.go | 3 +- internal/ai/service/mcp_http_server_test.go | 4 +- internal/app/app.go | 4 +- internal/db/mysql_impl.go | 75 +++++++++++++++------ internal/db/mysql_metadata_test.go | 31 ++++++++- internal/mcpserver/backend.go | 5 +- main.go | 4 +- 13 files changed, 111 insertions(+), 43 deletions(-) diff --git a/frontend/wailsjs/go/aiservice/Service.d.ts b/frontend/wailsjs/go/aiservice/Service.d.ts index d4a7546..74c6134 100755 --- a/frontend/wailsjs/go/aiservice/Service.d.ts +++ b/frontend/wailsjs/go/aiservice/Service.d.ts @@ -1,7 +1,7 @@ +// @ts-nocheck // Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL // This file is automatically generated. DO NOT EDIT import {ai} from '../models'; -import {context} from '../models'; export function AICallMCPTool(arg1:string,arg2:string):Promise; @@ -79,4 +79,4 @@ export function AITestMCPServer(arg1:ai.MCPServerConfig):Promise>; -export function Shutdown(arg1:context.Context):Promise; +export function Shutdown():Promise; diff --git a/frontend/wailsjs/go/aiservice/Service.js b/frontend/wailsjs/go/aiservice/Service.js index 768dd8d..563bc76 100755 --- a/frontend/wailsjs/go/aiservice/Service.js +++ b/frontend/wailsjs/go/aiservice/Service.js @@ -1,4 +1,4 @@ -// @ts-check +// @ts-nocheck // Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL // This file is automatically generated. DO NOT EDIT @@ -154,6 +154,6 @@ export function AITestProvider(arg1) { return window['go']['aiservice']['Service']['AITestProvider'](arg1); } -export function Shutdown(arg1) { - return window['go']['aiservice']['Service']['Shutdown'](arg1); +export function Shutdown() { + return window['go']['aiservice']['Service']['Shutdown'](); } diff --git a/frontend/wailsjs/go/app/App.d.ts b/frontend/wailsjs/go/app/App.d.ts index fca2f85..00ab9b9 100755 --- a/frontend/wailsjs/go/app/App.d.ts +++ b/frontend/wailsjs/go/app/App.d.ts @@ -1,3 +1,4 @@ +// @ts-nocheck // Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL // This file is automatically generated. DO NOT EDIT import {connection} from '../models'; @@ -5,7 +6,6 @@ import {sync} from '../models'; import {app} from '../models'; import {jvm} from '../models'; import {redis} from '../models'; -import {context} from '../models'; export function ApplyChanges(arg1:connection.ConnectionConfig,arg2:string,arg3:string,arg4:connection.ChangeSet):Promise; @@ -307,7 +307,7 @@ export function SetMacNativeWindowControls(arg1:boolean):Promise; export function SetWindowTranslucency(arg1:number,arg2:number):Promise; -export function Shutdown(arg1:context.Context):Promise; +export function Shutdown():Promise; export function StartSecurityUpdate(arg1:app.StartSecurityUpdateRequest):Promise; diff --git a/frontend/wailsjs/go/app/App.js b/frontend/wailsjs/go/app/App.js index a1afcf6..5a39a38 100755 --- a/frontend/wailsjs/go/app/App.js +++ b/frontend/wailsjs/go/app/App.js @@ -1,4 +1,4 @@ -// @ts-check +// @ts-nocheck // Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL // This file is automatically generated. DO NOT EDIT @@ -602,8 +602,8 @@ export function SetWindowTranslucency(arg1, arg2) { return window['go']['app']['App']['SetWindowTranslucency'](arg1, arg2); } -export function Shutdown(arg1) { - return window['go']['app']['App']['Shutdown'](arg1); +export function Shutdown() { + return window['go']['app']['App']['Shutdown'](); } export function StartSecurityUpdate(arg1) { diff --git a/go.mod b/go.mod index 6240e3a..3d12b15 100644 --- a/go.mod +++ b/go.mod @@ -8,6 +8,7 @@ require ( github.com/ClickHouse/clickhouse-go/v2 v2.43.0 github.com/HuaweiCloudDeveloper/gaussdb-go v1.0.0-rc1 github.com/apache/iotdb-client-go v1.3.7 + github.com/apache/rocketmq-client-go/v2 v2.1.2 github.com/caretdev/go-irisnative v0.2.1 github.com/duckdb/duckdb-go/v2 v2.5.5 github.com/eclipse/paho.mqtt.golang v1.5.1 @@ -35,7 +36,6 @@ require ( ) require ( - github.com/apache/rocketmq-client-go/v2 v2.1.2 // indirect github.com/apache/thrift v0.22.0 // indirect github.com/elastic/elastic-transport-go/v8 v8.9.0 // indirect github.com/emirpasic/gods v1.18.1 // indirect diff --git a/go.sum b/go.sum index 235c3d6..fe01c68 100644 --- a/go.sum +++ b/go.sum @@ -22,6 +22,7 @@ github.com/Azure/azure-sdk-for-go/sdk/security/keyvault/internal v1.1.1/go.mod h github.com/AzureAD/microsoft-authentication-library-for-go v1.4.2 h1:oygO0locgZJe7PpYPXT5A29ZkwJaPqcva7BVeemZOZs= github.com/AzureAD/microsoft-authentication-library-for-go v1.4.2/go.mod h1:wP83P5OoQ5p6ip3ScPr0BAq0BvuPAvacpEuSzyouqAI= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= +github.com/BurntSushi/toml v1.1.0 h1:ksErzDEI1khOiGPgpwuI7x2ebx/uXQNw7xJpn9Eq1+I= github.com/BurntSushi/toml v1.1.0/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ= github.com/ClickHouse/ch-go v0.71.0 h1:bUdZ/EZj/LcVHsMqaRUP2holqygrPWQKeMjc6nZoyRM= github.com/ClickHouse/ch-go v0.71.0/go.mod h1:NwbNc+7jaqfY58dmdDUbG4Jl22vThgx1cYjBw0vtgXw= @@ -163,6 +164,7 @@ github.com/google/pprof v0.0.0-20250317173921-a4b03ec1a45e/go.mod h1:boTsfXsheKC github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1 h1:EGx4pi6eqNxGaHF6qqu48+N2wcFQ5qg5FXgOdqsJ5d8= github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= github.com/gorilla/websocket v1.5.0/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= github.com/gorilla/websocket v1.5.3 h1:saDtZ6Pbx/0u+bgYQ3q96pZgCzfhKXGPqt7kZ72aNNg= @@ -186,6 +188,7 @@ github.com/jchv/go-winloader v0.0.0-20210711035445-715c2860da7e h1:Q3+PugElBCf4P github.com/jchv/go-winloader v0.0.0-20210711035445-715c2860da7e/go.mod h1:alcuEEnZsY1WQsagKhZDsoPCRoOijYqhZvPwLG0kzVs= github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= +github.com/jtolds/gls v4.20.0+incompatible h1:xdiiI2gbIgH/gLH7ADydsJ1uDOEzR8yvV7C0MuV77Wo= github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU= github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= @@ -308,7 +311,9 @@ github.com/sijms/go-ora/v2 v2.9.0/go.mod h1:QgFInVi3ZWyqAiJwzBQA+nbKYKH77tdp1PYo github.com/sirupsen/logrus v1.4.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ= github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= +github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d h1:zE9ykElWQ6/NYmHa3jpm/yHnI4xSofP+UP6SpjHcSeM= github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc= +github.com/smartystreets/goconvey v1.6.4 h1:fv0U8FUIMPNf1L9lnHLvLhgicrIVChEkdzIKYqbNC9s= github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= @@ -545,6 +550,7 @@ gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWD gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= diff --git a/internal/ai/service/mcp_http_server.go b/internal/ai/service/mcp_http_server.go index 3f1ac3f..edbc13a 100644 --- a/internal/ai/service/mcp_http_server.go +++ b/internal/ai/service/mcp_http_server.go @@ -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 服务已停止") } diff --git a/internal/ai/service/mcp_http_server_test.go b/internal/ai/service/mcp_http_server_test.go index df9e306..f62ae74 100644 --- a/internal/ai/service/mcp_http_server_test.go +++ b/internal/ai/service/mcp_http_server_test.go @@ -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{ diff --git a/internal/app/app.go b/internal/app/app.go index 9e9e4d8..4b9b0dd 100644 --- a/internal/app/app.go +++ b/internal/app/app.go @@ -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() diff --git a/internal/db/mysql_impl.go b/internal/db/mysql_impl.go index 2efcf17..ce3124e 100644 --- a/internal/db/mysql_impl.go +++ b/internal/db/mysql_impl.go @@ -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, "") { - 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, "") || 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 } diff --git a/internal/db/mysql_metadata_test.go b/internal/db/mysql_metadata_test.go index b3855ff..1731083 100644 --- a/internal/db/mysql_metadata_test.go +++ b/internal/db/mysql_metadata_test.go @@ -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() diff --git a/internal/mcpserver/backend.go b/internal/mcpserver/backend.go index 4e0df29..81c2df6 100644 --- a/internal/mcpserver/backend.go +++ b/internal/mcpserver/backend.go @@ -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 } diff --git a/main.go b/main.go index 69ac507..8daf28f 100644 --- a/main.go +++ b/main.go @@ -51,8 +51,8 @@ func main() { aiservice.InitializeLifecycle(aiService, ctx) }, OnShutdown: func(ctx context.Context) { - aiService.Shutdown(ctx) - application.Shutdown(ctx) + aiService.Shutdown() + application.Shutdown() }, Bind: []interface{}{ application,