From fb500ee33b2ff9f2b9535f2c36260d4ce1d94dd0 Mon Sep 17 00:00:00 2001 From: Syngnat Date: Sat, 11 Apr 2026 21:53:52 +0800 Subject: [PATCH] =?UTF-8?q?=F0=9F=90=9B=20fix(mysql):=20=E5=9B=9E=E9=80=80?= =?UTF-8?q?=E5=BD=93=E5=89=8D=E6=95=B0=E6=8D=AE=E5=BA=93=E5=88=97=E8=A1=A8?= =?UTF-8?q?=E6=9F=A5=E8=AF=A2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Fixes #327 --- .../2026-04-11-issue-backlog-tracking.md | 21 ++++- internal/db/mysql_impl.go | 70 +++++++++++++--- internal/db/mysql_metadata_test.go | 84 +++++++++++++++++++ 3 files changed, 161 insertions(+), 14 deletions(-) create mode 100644 internal/db/mysql_metadata_test.go diff --git a/docs/issues/2026-04-11-issue-backlog-tracking.md b/docs/issues/2026-04-11-issue-backlog-tracking.md index 0543e39..282a93c 100644 --- a/docs/issues/2026-04-11-issue-backlog-tracking.md +++ b/docs/issues/2026-04-11-issue-backlog-tracking.md @@ -20,7 +20,10 @@ | #315 | 窗体内缩放异常 | Fixed | `e19dd82` | | #316 | 人大金仓数据库驱动版本过低 | Fixed | `2500183` | | #317 | 驱动管理增加导入 jar 功能 | Blocked | - | -| #318 | mysql,bit 列,修改成 1 失败 | Fixed | Pending | +| #318 | mysql,bit 列,修改成 1 失败 | Fixed | `bee78be` | +| #319 | 关于运行外部 sql 文件的一些建议 | Deferred | - | +| #320 | 无法连接达梦数据库 | Investigating | - | +| #327 | SHOW DATABASES 报错 | Fixed | Pending | ## Notes @@ -36,6 +39,22 @@ - 处理:为 MySQL `bit` 列补充写入值归一化,将常见文本/布尔/数值输入转换为驱动可接受的 `[]byte`。 - 验证:补充 `internal/db/mysql_value_test.go` 回归测试,覆盖 `bit(1)` 的 insert/update 写入路径。 +### #319 + +- 现有应用已支持“运行外部 SQL 文件”,但 issue 诉求包含目录树、目录加载、双击文件打开等整组工作区能力。 +- 该项已超出单点缺陷修复范围,暂按功能增强项顺延,避免在逐条修 bug 流程中引入大范围 UI/状态管理重构。 + +### #320 + +- 达梦当前走可选 Go 驱动代理安装链路,不支持 JAR 导入属于既有架构边界。 +- “卡在 20%” 初步定位在驱动下载阶段前的占位进度,后续需要补充下载链路诊断与失败可观测性后再落地修复。 + +### #327 + +- 根因:低权限 MySQL 账号执行 `SHOW DATABASES` 会直接报错,当前实现没有回退路径。 +- 处理:为数据库列表查询增加 `SELECT DATABASE()` 回退,仅保留当前连接库时也能正常展示。 +- 验证:补充 `internal/db/mysql_metadata_test.go` 回归测试,覆盖有权限、多库和低权限回退场景。 + ## Next - 继续处理下一个最早且可直接落地的开放 issue。 diff --git a/internal/db/mysql_impl.go b/internal/db/mysql_impl.go index e8d4cd5..d93e1f1 100644 --- a/internal/db/mysql_impl.go +++ b/internal/db/mysql_impl.go @@ -74,6 +74,62 @@ func normalizeMySQLAddress(host string, port int) string { return fmt.Sprintf("%s:%d", h, p) } +var mysqlDatabaseQueries = []string{ + "SHOW DATABASES", + "SELECT DATABASE() AS `Database`", +} + +func collectMySQLDatabaseNames(queryFn func(string) ([]map[string]interface{}, []string, error)) ([]string, error) { + if queryFn == nil { + return nil, fmt.Errorf("查询函数为空") + } + + names := make([]string, 0, 8) + 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 + } + } + } + + for _, sqlText := range mysqlDatabaseQueries { + rows, _, err := queryFn(sqlText) + if err != nil { + lastErr = err + continue + } + appendNames(rows) + if len(names) > 0 { + return names, nil + } + } + + if len(names) > 0 { + return names, nil + } + if lastErr != nil { + return nil, lastErr + } + return nil, fmt.Errorf("未获取到可用数据库") +} + func applyMySQLURI(config connection.ConnectionConfig) connection.ConnectionConfig { uriText := strings.TrimSpace(config.URI) if uriText == "" { @@ -364,19 +420,7 @@ func (m *MySQLDB) Exec(query string) (int64, error) { } func (m *MySQLDB) GetDatabases() ([]string, error) { - data, _, err := m.Query("SHOW DATABASES") - if err != nil { - return nil, err - } - var dbs []string - for _, row := range data { - if val, ok := row["Database"]; ok { - dbs = append(dbs, fmt.Sprintf("%v", val)) - } else if val, ok := row["database"]; ok { - dbs = append(dbs, fmt.Sprintf("%v", val)) - } - } - return dbs, nil + return collectMySQLDatabaseNames(m.Query) } func (m *MySQLDB) GetTables(dbName string) ([]string, error) { diff --git a/internal/db/mysql_metadata_test.go b/internal/db/mysql_metadata_test.go new file mode 100644 index 0000000..45df52a --- /dev/null +++ b/internal/db/mysql_metadata_test.go @@ -0,0 +1,84 @@ +package db + +import ( + "errors" + "reflect" + "testing" +) + +func TestCollectMySQLDatabaseNames_FallsBackToCurrentDatabase(t *testing.T) { + t.Parallel() + + got, err := collectMySQLDatabaseNames(func(query string) ([]map[string]interface{}, []string, error) { + switch query { + case mysqlDatabaseQueries[0]: + 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 + default: + return nil, nil, errors.New("unexpected query") + } + }) + if err != nil { + t.Fatalf("collectMySQLDatabaseNames 返回错误: %v", err) + } + + want := []string{"biz_app"} + if !reflect.DeepEqual(got, want) { + t.Fatalf("unexpected database names, got=%v want=%v", got, want) + } +} + +func TestCollectMySQLDatabaseNames_PrefersShowDatabasesWhenAvailable(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{}{ + {"Database": "analytics"}, + {"database": "audit"}, + }, nil, nil + case mysqlDatabaseQueries[1]: + return []map[string]interface{}{ + {"Database": "should_not_be_used"}, + }, nil, nil + default: + return nil, nil, errors.New("unexpected query") + } + }) + if err != nil { + t.Fatalf("collectMySQLDatabaseNames 返回错误: %v", err) + } + + want := []string{"analytics", "audit"} + if !reflect.DeepEqual(got, want) { + t.Fatalf("unexpected database names, got=%v want=%v", got, want) + } +} + +func TestCollectMySQLDatabaseNames_ReturnsOriginalErrorWhenNoDatabaseResolved(t *testing.T) { + t.Parallel() + + expectErr := errors.New("show databases denied") + got, err := collectMySQLDatabaseNames(func(query string) ([]map[string]interface{}, []string, error) { + switch query { + case mysqlDatabaseQueries[0]: + return nil, nil, expectErr + case mysqlDatabaseQueries[1]: + return []map[string]interface{}{ + {"Database": nil}, + }, nil, nil + default: + return nil, nil, errors.New("unexpected query") + } + }) + if err == nil { + t.Fatalf("期望返回错误,实际 got=%v", got) + } + if !errors.Is(err, expectErr) { + t.Fatalf("错误不符合预期: %v", err) + } +}