mirror of
https://github.com/Syngnat/GoNavi.git
synced 2026-06-19 13:09:39 +08:00
🐛 fix(mysql): 兼容 MyCAT 场景下数据库列表解析逻辑
- 扩展数据库名字段识别,兼容 SCHEMA、database_name 等返回列名 - 按驱动返回列顺序兜底提取单列结果,避免非标准列名导致误判为空 - 补充 MyCAT 风格回归测试,覆盖 SHOW DATABASES 与当前库回退逻辑 Close #552
This commit is contained in:
4
frontend/wailsjs/go/aiservice/Service.d.ts
vendored
4
frontend/wailsjs/go/aiservice/Service.d.ts
vendored
@@ -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<ai.MCPToolCallResult>;
|
||||
|
||||
@@ -79,4 +79,4 @@ export function AITestMCPServer(arg1:ai.MCPServerConfig):Promise<Record<string,
|
||||
|
||||
export function AITestProvider(arg1:ai.ProviderConfig):Promise<Record<string, any>>;
|
||||
|
||||
export function Shutdown(arg1:context.Context):Promise<void>;
|
||||
export function Shutdown():Promise<void>;
|
||||
|
||||
@@ -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']();
|
||||
}
|
||||
|
||||
4
frontend/wailsjs/go/app/App.d.ts
vendored
4
frontend/wailsjs/go/app/App.d.ts
vendored
@@ -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<connection.QueryResult>;
|
||||
|
||||
@@ -307,7 +307,7 @@ export function SetMacNativeWindowControls(arg1:boolean):Promise<void>;
|
||||
|
||||
export function SetWindowTranslucency(arg1:number,arg2:number):Promise<void>;
|
||||
|
||||
export function Shutdown(arg1:context.Context):Promise<void>;
|
||||
export function Shutdown():Promise<void>;
|
||||
|
||||
export function StartSecurityUpdate(arg1:app.StartSecurityUpdateRequest):Promise<app.SecurityUpdateStatus>;
|
||||
|
||||
|
||||
@@ -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) {
|
||||
|
||||
2
go.mod
2
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
|
||||
|
||||
6
go.sum
6
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=
|
||||
|
||||
@@ -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