From 064cdc34bee5902e5043e32c01d4475188f326c2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=9D=A8=E5=9B=BD=E9=94=8B?= Date: Tue, 17 Mar 2026 21:44:50 +0800 Subject: [PATCH] =?UTF-8?q?=E2=99=BB=EF=B8=8F=20refactor(=E5=85=A8?= =?UTF-8?q?=E5=B1=80):=20=E7=BB=9F=E4=B8=80=E9=94=99=E8=AF=AF=E6=B6=88?= =?UTF-8?q?=E6=81=AF=E4=B8=AD=E6=96=87=E5=8C=96=EF=BC=8C=E8=A1=A5=E5=85=85?= =?UTF-8?q?=20godoc=20=E4=B8=8E=E6=B5=8B=E8=AF=95=EF=BC=8C=E4=BF=AE?= =?UTF-8?q?=E5=A4=8D=E6=A8=AA=E5=90=91=E6=BB=9A=E5=8A=A8=E5=92=8C=20Vite?= =?UTF-8?q?=20=E4=BB=A3=E7=90=86?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 错误消息中文化: - 19 个驱动实现文件中 connection not open / table name required 等英文消息替换为中文 - methods_file.go / methods_db.go / methods_driver.go 英文消息中文化 - 前端 App/Sidebar/DataGrid/ConnectionModal/DriverManagerModal 同步替换 "Cancelled" → "已取消" 文档与测试: - database.go Database/BatchApplier 接口、types.go 12 个类型、driver_support.go 导出函数补充中文 godoc - 新增 logger_test.go(ErrorChain 5 个用例)和 methods_db_conn_test.go(连接管理 7 个用例) Bug 修复: - DataGrid: 将 liveTargets 空检查移至非虚拟路径,修复外部横向滚动条拖动时内容不跟随 - vite.config.ts: server.host 指定 127.0.0.1,修复 IPv6 回环被拦截导致 Wails 代理 502 基础设施: - .gitignore 新增 .gemini/ 规则,tmpclaude-* 改为 **/tmpclaude-* 覆盖子目录 --- .gitignore | 3 +- frontend/package.json.md5 | 2 +- frontend/src/App.tsx | 4 +- frontend/src/components/ConnectionModal.tsx | 4 +- frontend/src/components/DataGrid.tsx | 22 ++-- .../src/components/DriverManagerModal.tsx | 4 +- frontend/src/components/Sidebar.tsx | 12 +- frontend/vite.config.ts | 1 + internal/app/methods_db.go | 2 +- internal/app/methods_db_conn_test.go | 112 ++++++++++++++++++ internal/app/methods_driver.go | 6 +- internal/app/methods_file.go | 50 ++++---- internal/connection/types.go | 25 ++-- internal/db/clickhouse_impl.go | 22 ++-- internal/db/custom_impl.go | 20 ++-- internal/db/dameng_impl.go | 22 ++-- internal/db/database.go | 25 +++- internal/db/driver_support.go | 6 + internal/db/duckdb_impl.go | 26 ++-- internal/db/highgo_impl.go | 28 ++--- internal/db/kingbase_impl.go | 28 ++--- internal/db/mariadb_impl.go | 24 ++-- internal/db/mongodb_impl.go | 22 ++-- internal/db/mongodb_impl_v1.go | 22 ++-- internal/db/mysql_agent_impl.go | 2 +- internal/db/mysql_impl.go | 26 ++-- internal/db/optional_driver_agent_impl.go | 2 +- internal/db/oracle_impl.go | 22 ++-- internal/db/postgres_impl.go | 28 ++--- internal/db/sqlite_impl.go | 30 ++--- internal/db/sqlserver_impl.go | 28 ++--- internal/db/tdengine_impl.go | 22 ++-- internal/db/vastbase_impl.go | 28 ++--- internal/logger/logger_test.go | 65 ++++++++++ 34 files changed, 478 insertions(+), 267 deletions(-) create mode 100644 internal/app/methods_db_conn_test.go create mode 100644 internal/logger/logger_test.go diff --git a/.gitignore b/.gitignore index 902ca17..6a07141 100644 --- a/.gitignore +++ b/.gitignore @@ -19,7 +19,8 @@ GoNavi-Wails.exe .ace-tool/ .superpowers/ .claude/ -tmpclaude-* +.gemini/ +**/tmpclaude-* CLAUDE.md **/CLAUDE.md diff --git a/frontend/package.json.md5 b/frontend/package.json.md5 index 0f8f4fe..a7661c0 100755 --- a/frontend/package.json.md5 +++ b/frontend/package.json.md5 @@ -1 +1 @@ -5b8157374dae5f9340e31b2d0bd2c00e \ No newline at end of file +d0f9366af59a6367ad3c7e2d4185ead4 \ No newline at end of file diff --git a/frontend/src/App.tsx b/frontend/src/App.tsx index 3b5a5ff..ccff62a 100644 --- a/frontend/src/App.tsx +++ b/frontend/src/App.tsx @@ -953,7 +953,7 @@ function App() { } catch (e) { void message.error("解析 JSON 失败"); } - } else if (res.message !== "Cancelled") { + } else if (res.message !== "已取消") { void message.error("导入失败: " + res.message); } }; @@ -966,7 +966,7 @@ function App() { const res = await (window as any).go.app.App.ExportData(connections, ['id','name','config','includeDatabases','includeRedisDatabases'], "connections", "json"); if (res.success) { void message.success("导出成功"); - } else if (res.message !== "Cancelled") { + } else if (res.message !== "已取消") { void message.error("导出失败: " + res.message); } }; diff --git a/frontend/src/components/ConnectionModal.tsx b/frontend/src/components/ConnectionModal.tsx index ab3738c..f7e13b4 100644 --- a/frontend/src/components/ConnectionModal.tsx +++ b/frontend/src/components/ConnectionModal.tsx @@ -1013,7 +1013,7 @@ const ConnectionModal: React.FC<{ if (selectedPath) { form.setFieldValue('sshKeyPath', selectedPath); } - } else if (res?.message !== 'Cancelled') { + } else if (res?.message !== '已取消') { message.error(`选择私钥文件失败: ${res?.message || '未知错误'}`); } } catch (e: any) { @@ -1037,7 +1037,7 @@ const ConnectionModal: React.FC<{ if (selectedPath) { form.setFieldValue('host', normalizeFileDbPath(selectedPath)); } - } else if (res?.message !== 'Cancelled') { + } else if (res?.message !== '已取消') { message.error(`选择数据库文件失败: ${res?.message || '未知错误'}`); } } catch (e: any) { diff --git a/frontend/src/components/DataGrid.tsx b/frontend/src/components/DataGrid.tsx index a450ba5..4791114 100644 --- a/frontend/src/components/DataGrid.tsx +++ b/frontend/src/components/DataGrid.tsx @@ -1086,7 +1086,7 @@ const DataGrid: React.FC = ({ const res = await ExportData(cleanRows, displayColumnNames, tableName || 'export', format); if (res.success) { void message.success("导出成功"); - } else if (res.message !== "Cancelled") { + } else if (res.message !== "已取消") { void message.error("导出失败: " + res.message); } } catch (e: any) { @@ -2829,7 +2829,7 @@ const DataGrid: React.FC = ({ const res = await ExportQuery(config as any, dbName || '', sql, defaultName || 'export', format); if (res.success) { void message.success("导出成功"); - } else if (res.message !== "Cancelled") { + } else if (res.message !== "已取消") { void message.error("导出失败: " + res.message); } } catch (e: any) { @@ -2947,7 +2947,7 @@ const DataGrid: React.FC = ({ const res = await ExportTable(config as any, dbName || '', tableName, format); if (res.success) { void message.success("导出成功"); - } else if (res.message !== "Cancelled") { + } else if (res.message !== "已取消") { void message.error("导出失败: " + res.message); } } catch (e: any) { @@ -3023,7 +3023,7 @@ const DataGrid: React.FC = ({ if (res.success && res.data && res.data.filePath) { setImportFilePath(res.data.filePath); setImportPreviewVisible(true); - } else if (res.message !== "Cancelled") { + } else if (res.message !== "已取消") { void message.error("选择文件失败: " + res.message); } }; @@ -3371,11 +3371,6 @@ const DataGrid: React.FC = ({ return; } - const liveTargets = tableScrollTargetsRef.current; - if (liveTargets.length === 0) { - return; - } - if (Math.abs(lastExternalScrollLeftRef.current - externalScroll.scrollLeft) < 1) { return; } @@ -3383,6 +3378,9 @@ const DataGrid: React.FC = ({ horizontalSyncSourceRef.current = 'external'; const tableContainer = tableContainerRef.current; + // 虚拟表格路径:直接操作 DOM 的 marginLeft / scrollLeft,不依赖 liveTargets。 + // 将此分支提前到 liveTargets 检查之前,避免 targets 在数据加载时暂时为空 + // 导致虚拟表格的横向滚动同步被永久阻塞。 if (enableVirtual && tableContainer instanceof HTMLElement) { if (applyVirtualHorizontalOffset(tableContainer, externalScroll.scrollLeft)) { lastTableScrollLeftRef.current = externalScroll.scrollLeft; @@ -3390,6 +3388,12 @@ const DataGrid: React.FC = ({ horizontalSyncSourceRef.current = ''; return; } + // 非虚拟表格路径:依赖 liveTargets 进行 scrollLeft 同步 + const liveTargets = tableScrollTargetsRef.current; + if (liveTargets.length === 0) { + horizontalSyncSourceRef.current = ''; + return; + } liveTargets.forEach((target) => { if (target.scrollWidth <= target.clientWidth + 1) { return; diff --git a/frontend/src/components/DriverManagerModal.tsx b/frontend/src/components/DriverManagerModal.tsx index 6e31838..df5b0cd 100644 --- a/frontend/src/components/DriverManagerModal.tsx +++ b/frontend/src/components/DriverManagerModal.tsx @@ -847,7 +847,7 @@ const DriverManagerModal: React.FC<{ open: boolean; onClose: () => void; onOpenG const installDriverFromLocalFile = useCallback(async (row: DriverStatusRow) => { const fileRes = await SelectDriverPackageFile(downloadDir); if (!fileRes?.success) { - if (String(fileRes?.message || '') !== 'Cancelled') { + if (String(fileRes?.message || '') !== '已取消') { message.error(fileRes?.message || '选择本地驱动包文件失败'); } return; @@ -863,7 +863,7 @@ const DriverManagerModal: React.FC<{ open: boolean; onClose: () => void; onOpenG const installDriversFromDirectory = useCallback(async () => { const directoryRes = await SelectDriverPackageDirectory(downloadDir); if (!directoryRes?.success) { - if (String(directoryRes?.message || '') !== 'Cancelled') { + if (String(directoryRes?.message || '') !== '已取消') { message.error(directoryRes?.message || '选择本地驱动包目录失败'); } return; diff --git a/frontend/src/components/Sidebar.tsx b/frontend/src/components/Sidebar.tsx index 0ba9a08..e61d5a5 100644 --- a/frontend/src/components/Sidebar.tsx +++ b/frontend/src/components/Sidebar.tsx @@ -1561,7 +1561,7 @@ const Sidebar: React.FC<{ onEditConnection?: (conn: SavedConnection) => void }> hide(); if (res.success) { message.success('导出成功'); - } else if (res.message !== 'Cancelled') { + } else if (res.message !== '已取消') { message.error('导出失败: ' + res.message); } }; @@ -1584,7 +1584,7 @@ const Sidebar: React.FC<{ onEditConnection?: (conn: SavedConnection) => void }> hide(); if (res.success) { message.success('导出成功'); - } else if (res.message !== 'Cancelled') { + } else if (res.message !== '已取消') { message.error('导出失败: ' + res.message); } } catch (e: any) { @@ -1611,7 +1611,7 @@ const Sidebar: React.FC<{ onEditConnection?: (conn: SavedConnection) => void }> hide(); if (res.success) { message.success('导出成功'); - } else if (res.message !== 'Cancelled') { + } else if (res.message !== '已取消') { message.error('导出失败: ' + res.message); } } catch (e: any) { @@ -1800,7 +1800,7 @@ const Sidebar: React.FC<{ onEditConnection?: (conn: SavedConnection) => void }> } else { message.success('导出成功'); } - } else if (res.message !== 'Cancelled') { + } else if (res.message !== '已取消') { message.error('导出失败: ' + res.message); } } catch (e: any) { @@ -1940,7 +1940,7 @@ const Sidebar: React.FC<{ onEditConnection?: (conn: SavedConnection) => void }> hide(); if (res.success) { message.success(`${db.dbName} 导出成功`); - } else if (res.message !== 'Cancelled') { + } else if (res.message !== '已取消') { message.error(`${db.dbName} 导出失败: ` + res.message); break; } else { @@ -1981,7 +1981,7 @@ const Sidebar: React.FC<{ onEditConnection?: (conn: SavedConnection) => void }> dbName: dbName, query: sqlContent }); - } else if (res.message !== "Cancelled") { + } else if (res.message !== "已取消") { message.error("读取文件失败: " + res.message); } }; diff --git a/frontend/vite.config.ts b/frontend/vite.config.ts index 4b6e91a..3c783cc 100644 --- a/frontend/vite.config.ts +++ b/frontend/vite.config.ts @@ -5,6 +5,7 @@ import react from '@vitejs/plugin-react' export default defineConfig({ plugins: [react()], server: { + host: '127.0.0.1', port: 5173, strictPort: true, }, diff --git a/internal/app/methods_db.go b/internal/app/methods_db.go index f411653..3a0d8a6 100644 --- a/internal/app/methods_db.go +++ b/internal/app/methods_db.go @@ -116,7 +116,7 @@ func (a *App) CreateDatabase(config connection.ConnectionConfig, dbName string) return connection.QueryResult{Success: false, Message: err.Error()} } - return connection.QueryResult{Success: true, Message: "Database created successfully"} + return connection.QueryResult{Success: true, Message: "数据库创建成功"} } func resolveDDLDBType(config connection.ConnectionConfig) string { diff --git a/internal/app/methods_db_conn_test.go b/internal/app/methods_db_conn_test.go new file mode 100644 index 0000000..72f2f39 --- /dev/null +++ b/internal/app/methods_db_conn_test.go @@ -0,0 +1,112 @@ +package app + +import ( + "strings" + "testing" + + "GoNavi-Wails/internal/connection" +) + +func TestNormalizeTestConnectionConfig_CapsTimeout(t *testing.T) { + cfg := connection.ConnectionConfig{Timeout: 60} + got := normalizeTestConnectionConfig(cfg) + if got.Timeout != testConnectionTimeoutUpperBoundSeconds { + t.Fatalf("timeout 应被限制为 %d, got=%d", testConnectionTimeoutUpperBoundSeconds, got.Timeout) + } +} + +func TestNormalizeTestConnectionConfig_KeepSmallTimeout(t *testing.T) { + cfg := connection.ConnectionConfig{Timeout: 5} + got := normalizeTestConnectionConfig(cfg) + if got.Timeout != 5 { + t.Fatalf("timeout 不应被修改, got=%d", got.Timeout) + } +} + +func TestNormalizeTestConnectionConfig_ZeroTimeout(t *testing.T) { + cfg := connection.ConnectionConfig{Timeout: 0} + got := normalizeTestConnectionConfig(cfg) + if got.Timeout != testConnectionTimeoutUpperBoundSeconds { + t.Fatalf("零值 timeout 应被修正, got=%d", got.Timeout) + } +} + +func TestFormatConnSummary_BasicMySQL(t *testing.T) { + cfg := connection.ConnectionConfig{ + Type: "mysql", + Host: "127.0.0.1", + Port: 3306, + User: "root", + Database: "test_db", + Timeout: 30, + } + got := formatConnSummary(cfg) + for _, want := range []string{"类型=mysql", "127.0.0.1:3306", "test_db", "root"} { + if !strings.Contains(got, want) { + t.Fatalf("formatConnSummary 应包含 %q, got=%q", want, got) + } + } +} + +func TestFormatConnSummary_SQLitePath(t *testing.T) { + cfg := connection.ConnectionConfig{ + Type: "sqlite", + Host: "/data/test.db", + } + got := formatConnSummary(cfg) + if !strings.Contains(got, "类型=sqlite") { + t.Fatalf("formatConnSummary 缺少类型, got=%q", got) + } + if !strings.Contains(got, "/data/test.db") { + t.Fatalf("formatConnSummary 缺少路径, got=%q", got) + } +} + +func TestFormatConnSummary_SSH(t *testing.T) { + cfg := connection.ConnectionConfig{ + Type: "mysql", + Host: "db.internal", + Port: 3306, + User: "app", + UseSSH: true, + SSH: connection.SSHConfig{ + Host: "jump.server", + Port: 22, + User: "admin", + }, + } + got := formatConnSummary(cfg) + if !strings.Contains(got, "SSH=jump.server:22") { + t.Fatalf("formatConnSummary 应包含 SSH 信息, got=%q", got) + } +} + +func TestFormatConnSummary_Proxy(t *testing.T) { + cfg := connection.ConnectionConfig{ + Type: "mysql", + Host: "db.internal", + Port: 3306, + UseProxy: true, + Proxy: connection.ProxyConfig{ + Type: "socks5", + Host: "proxy.local", + Port: 1080, + }, + } + got := formatConnSummary(cfg) + if !strings.Contains(got, "代理=socks5://proxy.local:1080") { + t.Fatalf("formatConnSummary 应包含代理信息, got=%q", got) + } +} + +func TestFormatConnSummary_DefaultTimeout(t *testing.T) { + cfg := connection.ConnectionConfig{ + Type: "mysql", + Host: "localhost", + Port: 3306, + } + got := formatConnSummary(cfg) + if !strings.Contains(got, "超时=30s") { + t.Fatalf("formatConnSummary 默认超时应为30s, got=%q", got) + } +} diff --git a/internal/app/methods_driver.go b/internal/app/methods_driver.go index ca7ce8c..1024014 100644 --- a/internal/app/methods_driver.go +++ b/internal/app/methods_driver.go @@ -353,7 +353,7 @@ func (a *App) SelectDriverDownloadDirectory(currentDir string) connection.QueryR return connection.QueryResult{Success: false, Message: err.Error()} } if strings.TrimSpace(selection) == "" { - return connection.QueryResult{Success: false, Message: "Cancelled"} + return connection.QueryResult{Success: false, Message: "已取消"} } resolved, err := resolveDriverDownloadDirectory(selection) @@ -392,7 +392,7 @@ func (a *App) SelectDriverPackageFile(currentPath string) connection.QueryResult return connection.QueryResult{Success: false, Message: err.Error()} } if strings.TrimSpace(selection) == "" { - return connection.QueryResult{Success: false, Message: "Cancelled"} + return connection.QueryResult{Success: false, Message: "已取消"} } if abs, err := filepath.Abs(selection); err == nil { @@ -423,7 +423,7 @@ func (a *App) SelectDriverPackageDirectory(currentPath string) connection.QueryR return connection.QueryResult{Success: false, Message: err.Error()} } if strings.TrimSpace(selection) == "" { - return connection.QueryResult{Success: false, Message: "Cancelled"} + return connection.QueryResult{Success: false, Message: "已取消"} } if abs, err := filepath.Abs(selection); err == nil { selection = abs diff --git a/internal/app/methods_file.go b/internal/app/methods_file.go index 9e5fc1b..44b204f 100644 --- a/internal/app/methods_file.go +++ b/internal/app/methods_file.go @@ -48,7 +48,7 @@ func (a *App) OpenSQLFile() connection.QueryResult { } if selection == "" { - return connection.QueryResult{Success: false, Message: "Cancelled"} + return connection.QueryResult{Success: false, Message: "已取消"} } content, err := os.ReadFile(selection) @@ -75,7 +75,7 @@ func (a *App) ImportConfigFile() connection.QueryResult { } if selection == "" { - return connection.QueryResult{Success: false, Message: "Cancelled"} + return connection.QueryResult{Success: false, Message: "已取消"} } content, err := os.ReadFile(selection) @@ -120,7 +120,7 @@ func (a *App) SelectSSHKeyFile(currentPath string) connection.QueryResult { return connection.QueryResult{Success: false, Message: err.Error()} } if strings.TrimSpace(selection) == "" { - return connection.QueryResult{Success: false, Message: "Cancelled"} + return connection.QueryResult{Success: false, Message: "已取消"} } if abs, err := filepath.Abs(selection); err == nil { selection = abs @@ -192,7 +192,7 @@ func (a *App) SelectDatabaseFile(currentPath string, driverType string) connecti return connection.QueryResult{Success: false, Message: err.Error()} } if strings.TrimSpace(selection) == "" { - return connection.QueryResult{Success: false, Message: "Cancelled"} + return connection.QueryResult{Success: false, Message: "已取消"} } if abs, err := filepath.Abs(selection); err == nil { selection = abs @@ -203,7 +203,7 @@ func (a *App) SelectDatabaseFile(currentPath string, driverType string) connecti // PreviewImportFile 解析导入文件,返回字段列表、总行数、前 5 行预览数据 func (a *App) PreviewImportFile(filePath string) connection.QueryResult { if filePath == "" { - return connection.QueryResult{Success: false, Message: "File path required"} + return connection.QueryResult{Success: false, Message: "文件路径不能为空"} } rows, columns, err := parseImportFile(filePath) @@ -243,7 +243,7 @@ func (a *App) ImportData(config connection.ConnectionConfig, dbName, tableName s } if selection == "" { - return connection.QueryResult{Success: false, Message: "Cancelled"} + return connection.QueryResult{Success: false, Message: "已取消"} } // 返回文件路径供前端预览 @@ -492,7 +492,7 @@ func (a *App) ImportDataWithProgress(config connection.ConnectionConfig, dbName, } if len(rows) == 0 { - return connection.QueryResult{Success: true, Message: "No data to import"} + return connection.QueryResult{Success: true, Message: "无可导入数据"} } runConfig := normalizeRunConfig(config, dbName) @@ -584,7 +584,7 @@ func (a *App) ExportTable(config connection.ConnectionConfig, dbName string, tab }) if err != nil || filename == "" { - return connection.QueryResult{Success: false, Message: "Cancelled"} + return connection.QueryResult{Success: false, Message: "已取消"} } runConfig := normalizeRunConfig(config, dbName) @@ -616,7 +616,7 @@ func (a *App) ExportTable(config connection.ConnectionConfig, dbName string, tab return connection.QueryResult{Success: false, Message: err.Error()} } - return connection.QueryResult{Success: true, Message: "Export successful"} + return connection.QueryResult{Success: true, Message: "导出完成"} } query := fmt.Sprintf("SELECT * FROM %s", quoteQualifiedIdentByType(runConfig.Type, tableName)) @@ -632,10 +632,10 @@ func (a *App) ExportTable(config connection.ConnectionConfig, dbName string, tab } defer f.Close() if err := writeRowsToFile(f, data, columns, format); err != nil { - return connection.QueryResult{Success: false, Message: "Write error: " + err.Error()} + return connection.QueryResult{Success: false, Message: "写入失败:" + err.Error()} } - return connection.QueryResult{Success: true, Message: "Export successful"} + return connection.QueryResult{Success: true, Message: "导出完成"} } func (a *App) ExportTablesSQL(config connection.ConnectionConfig, dbName string, tableNames []string, includeData bool) connection.QueryResult { @@ -648,7 +648,7 @@ func (a *App) ExportTablesDataSQL(config connection.ConnectionConfig, dbName str func (a *App) exportTablesSQL(config connection.ConnectionConfig, dbName string, tableNames []string, includeSchema bool, includeData bool) connection.QueryResult { if !includeSchema && !includeData { - return connection.QueryResult{Success: false, Message: "invalid export mode"} + return connection.QueryResult{Success: false, Message: "无效的导出模式"} } safeDbName := strings.TrimSpace(dbName) @@ -671,7 +671,7 @@ func (a *App) exportTablesSQL(config connection.ConnectionConfig, dbName string, DefaultFilename: defaultFilename, }) if err != nil || filename == "" { - return connection.QueryResult{Success: false, Message: "Cancelled"} + return connection.QueryResult{Success: false, Message: "已取消"} } runConfig := normalizeRunConfig(config, dbName) @@ -717,13 +717,13 @@ func (a *App) exportTablesSQL(config connection.ConnectionConfig, dbName string, return connection.QueryResult{Success: false, Message: err.Error()} } - return connection.QueryResult{Success: true, Message: "Export successful"} + return connection.QueryResult{Success: true, Message: "导出完成"} } func (a *App) ExportDatabaseSQL(config connection.ConnectionConfig, dbName string, includeData bool) connection.QueryResult { safeDbName := strings.TrimSpace(dbName) if safeDbName == "" { - return connection.QueryResult{Success: false, Message: "dbName required"} + return connection.QueryResult{Success: false, Message: "数据库名称不能为空"} } suffix := "schema" if includeData { @@ -735,7 +735,7 @@ func (a *App) ExportDatabaseSQL(config connection.ConnectionConfig, dbName strin DefaultFilename: fmt.Sprintf("%s_%s.sql", safeDbName, suffix), }) if err != nil || filename == "" { - return connection.QueryResult{Success: false, Message: "Cancelled"} + return connection.QueryResult{Success: false, Message: "已取消"} } runConfig := normalizeRunConfig(config, dbName) @@ -772,7 +772,7 @@ func (a *App) ExportDatabaseSQL(config connection.ConnectionConfig, dbName strin return connection.QueryResult{Success: false, Message: err.Error()} } - return connection.QueryResult{Success: true, Message: "Export successful"} + return connection.QueryResult{Success: true, Message: "导出完成"} } func quoteIdentByType(dbType string, ident string) string { @@ -1471,7 +1471,7 @@ func (a *App) ExportData(data []map[string]interface{}, columns []string, defaul if err != nil || filename == "" { logger.Infof("ExportData 已取消或未选择文件:err=%v", err) - return connection.QueryResult{Success: false, Message: "Cancelled"} + return connection.QueryResult{Success: false, Message: "已取消"} } logger.Infof("ExportData 选定文件:%s", filename) @@ -1482,11 +1482,11 @@ func (a *App) ExportData(data []map[string]interface{}, columns []string, defaul defer f.Close() if err := writeRowsToFile(f, data, columns, format); err != nil { logger.Warnf("ExportData 写入失败:file=%s err=%v", filename, err) - return connection.QueryResult{Success: false, Message: "Write error: " + err.Error()} + return connection.QueryResult{Success: false, Message: "写入失败:" + err.Error()} } logger.Infof("ExportData 完成:file=%s rows=%d", filename, len(data)) - return connection.QueryResult{Success: true, Message: "Export successful"} + return connection.QueryResult{Success: true, Message: "导出完成"} } // ExportQuery exports by executing the provided SELECT query on backend side. @@ -1494,7 +1494,7 @@ func (a *App) ExportData(data []map[string]interface{}, columns []string, defaul func (a *App) ExportQuery(config connection.ConnectionConfig, dbName string, query string, defaultName string, format string) connection.QueryResult { query = strings.TrimSpace(query) if query == "" { - return connection.QueryResult{Success: false, Message: "query required"} + return connection.QueryResult{Success: false, Message: "查询语句不能为空"} } if defaultName == "" { @@ -1507,7 +1507,7 @@ func (a *App) ExportQuery(config connection.ConnectionConfig, dbName string, que }) if err != nil || filename == "" { logger.Infof("ExportQuery 已取消或未选择文件:err=%v", err) - return connection.QueryResult{Success: false, Message: "Cancelled"} + return connection.QueryResult{Success: false, Message: "已取消"} } logger.Infof("ExportQuery 开始:type=%s db=%s format=%s file=%s sql=%q", strings.TrimSpace(config.Type), strings.TrimSpace(dbName), strings.ToLower(strings.TrimSpace(format)), filename, sqlSnippet(query)) @@ -1520,7 +1520,7 @@ func (a *App) ExportQuery(config connection.ConnectionConfig, dbName string, que query = sanitizeSQLForPgLike(runConfig.Type, query) lowerQuery := strings.ToLower(strings.TrimSpace(query)) if !(strings.HasPrefix(lowerQuery, "select") || strings.HasPrefix(lowerQuery, "with")) { - return connection.QueryResult{Success: false, Message: "Only SELECT/WITH queries are supported"} + return connection.QueryResult{Success: false, Message: "仅支持 SELECT/WITH 查询导出"} } data, columns, err := queryDataForExport(dbInst, runConfig, query) @@ -1537,11 +1537,11 @@ func (a *App) ExportQuery(config connection.ConnectionConfig, dbName string, que if err := writeRowsToFile(f, data, columns, format); err != nil { logger.Warnf("ExportQuery 写入失败:file=%s err=%v", filename, err) - return connection.QueryResult{Success: false, Message: "Write error: " + err.Error()} + return connection.QueryResult{Success: false, Message: "写入失败:" + err.Error()} } logger.Infof("ExportQuery 完成:file=%s rows=%d cols=%d", filename, len(data), len(columns)) - return connection.QueryResult{Success: true, Message: "Export successful"} + return connection.QueryResult{Success: true, Message: "导出完成"} } func queryDataForExport(dbInst db.Database, config connection.ConnectionConfig, query string) ([]map[string]interface{}, []string, error) { diff --git a/internal/connection/types.go b/internal/connection/types.go index bddb794..10a4bf7 100644 --- a/internal/connection/types.go +++ b/internal/connection/types.go @@ -1,6 +1,6 @@ package connection -// SSHConfig holds SSH connection details +// SSHConfig 存储 SSH 隧道连接配置。 type SSHConfig struct { Host string `json:"host"` Port int `json:"port"` @@ -9,7 +9,7 @@ type SSHConfig struct { KeyPath string `json:"keyPath"` } -// ProxyConfig holds proxy connection details +// ProxyConfig 存储代理连接配置。 type ProxyConfig struct { Type string `json:"type"` // socks5 | http Host string `json:"host"` @@ -18,7 +18,7 @@ type ProxyConfig struct { Password string `json:"password,omitempty"` } -// HTTPTunnelConfig holds independent HTTP CONNECT tunnel details +// HTTPTunnelConfig 存储 HTTP CONNECT 隧道配置。 type HTTPTunnelConfig struct { Host string `json:"host"` Port int `json:"port"` @@ -26,7 +26,7 @@ type HTTPTunnelConfig struct { Password string `json:"password,omitempty"` } -// ConnectionConfig holds database connection details including SSH +// ConnectionConfig 存储数据库连接的完整配置,包括 SSH、代理、SSL 等网络层设置。 type ConnectionConfig struct { Type string `json:"type"` Host string `json:"host"` @@ -63,7 +63,7 @@ type ConnectionConfig struct { MongoReplicaPassword string `json:"mongoReplicaPassword,omitempty"` // MongoDB replica auth password } -// QueryResult is the standard response format for Wails methods +// QueryResult 是 Wails 绑定方法的统一响应格式,前端通过此结构体接收后端结果。 type QueryResult struct { Success bool `json:"success"` Message string `json:"message"` @@ -72,7 +72,7 @@ type QueryResult struct { QueryID string `json:"queryId,omitempty"` // Unique ID for query cancellation } -// ColumnDefinition represents a table column +// ColumnDefinition 描述表的一个列定义。 type ColumnDefinition struct { Name string `json:"name"` Type string `json:"type"` @@ -83,7 +83,7 @@ type ColumnDefinition struct { Comment string `json:"comment"` } -// IndexDefinition represents a table index +// IndexDefinition 描述表的一个索引定义。 type IndexDefinition struct { Name string `json:"name"` ColumnName string `json:"columnName"` @@ -93,7 +93,7 @@ type IndexDefinition struct { SubPart int `json:"subPart,omitempty"` } -// ForeignKeyDefinition represents a foreign key +// ForeignKeyDefinition 描述表的一个外键定义。 type ForeignKeyDefinition struct { Name string `json:"name"` ColumnName string `json:"columnName"` @@ -102,7 +102,7 @@ type ForeignKeyDefinition struct { ConstraintName string `json:"constraintName"` } -// TriggerDefinition represents a trigger +// TriggerDefinition 描述表的一个触发器定义。 type TriggerDefinition struct { Name string `json:"name"` Timing string `json:"timing"` // BEFORE/AFTER @@ -110,26 +110,27 @@ type TriggerDefinition struct { Statement string `json:"statement"` } -// ColumnDefinitionWithTable represents a column with its table name (for search/autocomplete) +// ColumnDefinitionWithTable 带有表名标识的列定义,用于跨表搜索和 SQL 自动补全。 type ColumnDefinitionWithTable struct { TableName string `json:"tableName"` Name string `json:"name"` Type string `json:"type"` } -// UpdateRow represents a row update with keys (WHERE) and values (SET) +// UpdateRow 表示一行更新操作,Keys 为 WHERE 条件,Values 为 SET 值。 type UpdateRow struct { Keys map[string]interface{} `json:"keys"` Values map[string]interface{} `json:"values"` } -// ChangeSet represents a batch of changes +// ChangeSet 表示一组批量变更,包含新增、修改和删除操作。 type ChangeSet struct { Inserts []map[string]interface{} `json:"inserts"` Updates []UpdateRow `json:"updates"` Deletes []map[string]interface{} `json:"deletes"` } +// MongoMemberInfo 描述 MongoDB 副本集成员的信息。 type MongoMemberInfo struct { Host string `json:"host"` Role string `json:"role"` diff --git a/internal/db/clickhouse_impl.go b/internal/db/clickhouse_impl.go index 75a418c..d98ca3d 100644 --- a/internal/db/clickhouse_impl.go +++ b/internal/db/clickhouse_impl.go @@ -271,7 +271,7 @@ func (c *ClickHouseDB) Close() error { func (c *ClickHouseDB) Ping() error { if c.conn == nil { - return fmt.Errorf("connection not open") + return fmt.Errorf("连接未打开") } timeout := c.pingTimeout if timeout <= 0 { @@ -284,7 +284,7 @@ func (c *ClickHouseDB) Ping() error { func (c *ClickHouseDB) QueryContext(ctx context.Context, query string) ([]map[string]interface{}, []string, error) { if c.conn == nil { - return nil, nil, fmt.Errorf("connection not open") + return nil, nil, fmt.Errorf("连接未打开") } rows, err := c.conn.QueryContext(ctx, query) if err != nil { @@ -296,7 +296,7 @@ func (c *ClickHouseDB) QueryContext(ctx context.Context, query string) ([]map[st func (c *ClickHouseDB) Query(query string) ([]map[string]interface{}, []string, error) { if c.conn == nil { - return nil, nil, fmt.Errorf("connection not open") + return nil, nil, fmt.Errorf("连接未打开") } rows, err := c.conn.Query(query) if err != nil { @@ -308,7 +308,7 @@ func (c *ClickHouseDB) Query(query string) ([]map[string]interface{}, []string, func (c *ClickHouseDB) ExecContext(ctx context.Context, query string) (int64, error) { if c.conn == nil { - return 0, fmt.Errorf("connection not open") + return 0, fmt.Errorf("连接未打开") } res, err := c.conn.ExecContext(ctx, query) if err != nil { @@ -319,7 +319,7 @@ func (c *ClickHouseDB) ExecContext(ctx context.Context, query string) (int64, er func (c *ClickHouseDB) Exec(query string) (int64, error) { if c.conn == nil { - return 0, fmt.Errorf("connection not open") + return 0, fmt.Errorf("连接未打开") } res, err := c.conn.Exec(query) if err != nil { @@ -404,7 +404,7 @@ func (c *ClickHouseDB) GetCreateStatement(dbName, tableName string) (string, err return "", err } if len(data) == 0 { - return "", fmt.Errorf("create statement not found") + return "", fmt.Errorf("未找到建表语句") } row := data[0] if val, ok := getClickHouseValueFromRow(row, "statement", "create_statement", "sql", "query"); ok { @@ -427,7 +427,7 @@ func (c *ClickHouseDB) GetCreateStatement(dbName, tableName string) (string, err if longest != "" { return longest, nil } - return "", fmt.Errorf("create statement not found") + return "", fmt.Errorf("未找到建表语句") } func (c *ClickHouseDB) GetColumns(dbName, tableName string) ([]connection.ColumnDefinition, error) { @@ -582,7 +582,7 @@ func (c *ClickHouseDB) GetTriggers(dbName, tableName string) ([]connection.Trigg func (c *ClickHouseDB) resolveDatabaseAndTable(dbName, tableName string) (string, string, error) { rawTable := strings.TrimSpace(tableName) if rawTable == "" { - return "", "", fmt.Errorf("table name required") + return "", "", fmt.Errorf("表名不能为空") } resolvedDB := strings.TrimSpace(dbName) @@ -603,7 +603,7 @@ func (c *ClickHouseDB) resolveDatabaseAndTable(dbName, tableName string) (string resolvedDB = defaultClickHouseDatabase } if resolvedTable == "" { - return "", "", fmt.Errorf("table name required") + return "", "", fmt.Errorf("表名不能为空") } return resolvedDB, resolvedTable, nil } @@ -682,7 +682,7 @@ func isClickHouseTruthy(value interface{}) bool { func (c *ClickHouseDB) ApplyChanges(tableName string, changes connection.ChangeSet) error { if c.conn == nil { - return fmt.Errorf("connection not open") + return fmt.Errorf("连接未打开") } database, table, err := c.resolveDatabaseAndTable(c.database, tableName) @@ -723,7 +723,7 @@ func (c *ClickHouseDB) ApplyChanges(tableName string, changes connection.ChangeS continue } if _, err := c.conn.Exec(query); err != nil { - return fmt.Errorf("insert error: %v; sql=%s", err, query) + return fmt.Errorf("插入失败:%v; sql=%s", err, query) } } return nil diff --git a/internal/db/custom_impl.go b/internal/db/custom_impl.go index e0b4418..8d80895 100644 --- a/internal/db/custom_impl.go +++ b/internal/db/custom_impl.go @@ -47,7 +47,7 @@ func (c *CustomDB) Close() error { func (c *CustomDB) Ping() error { if c.conn == nil { - return fmt.Errorf("connection not open") + return fmt.Errorf("连接未打开") } timeout := c.pingTimeout if timeout <= 0 { @@ -60,7 +60,7 @@ func (c *CustomDB) Ping() error { func (c *CustomDB) QueryContext(ctx context.Context, query string) ([]map[string]interface{}, []string, error) { if c.conn == nil { - return nil, nil, fmt.Errorf("connection not open") + return nil, nil, fmt.Errorf("连接未打开") } rows, err := c.conn.QueryContext(ctx, query) @@ -74,7 +74,7 @@ func (c *CustomDB) QueryContext(ctx context.Context, query string) ([]map[string func (c *CustomDB) Query(query string) ([]map[string]interface{}, []string, error) { if c.conn == nil { - return nil, nil, fmt.Errorf("connection not open") + return nil, nil, fmt.Errorf("连接未打开") } rows, err := c.conn.Query(query) @@ -87,7 +87,7 @@ func (c *CustomDB) Query(query string) ([]map[string]interface{}, []string, erro func (c *CustomDB) ExecContext(ctx context.Context, query string) (int64, error) { if c.conn == nil { - return 0, fmt.Errorf("connection not open") + return 0, fmt.Errorf("连接未打开") } res, err := c.conn.ExecContext(ctx, query) if err != nil { @@ -98,7 +98,7 @@ func (c *CustomDB) ExecContext(ctx context.Context, query string) (int64, error) func (c *CustomDB) Exec(query string) (int64, error) { if c.conn == nil { - return 0, fmt.Errorf("connection not open") + return 0, fmt.Errorf("连接未打开") } res, err := c.conn.Exec(query) if err != nil { @@ -249,7 +249,7 @@ func (c *CustomDB) GetTriggers(dbName, tableName string) ([]connection.TriggerDe func (c *CustomDB) ApplyChanges(tableName string, changes connection.ChangeSet) error { if c.conn == nil { - return fmt.Errorf("connection not open") + return fmt.Errorf("连接未打开") } tx, err := c.conn.Begin() @@ -321,7 +321,7 @@ func (c *CustomDB) ApplyChanges(tableName string, changes connection.ChangeSet) } query := fmt.Sprintf("DELETE FROM %s WHERE %s", qualifiedTable, strings.Join(wheres, " AND ")) if _, err := tx.Exec(query, args...); err != nil { - return fmt.Errorf("delete error: %v", err) + return fmt.Errorf("删除失败:%v", err) } } @@ -349,12 +349,12 @@ func (c *CustomDB) ApplyChanges(tableName string, changes connection.ChangeSet) } if len(wheres) == 0 { - return fmt.Errorf("update requires keys") + return fmt.Errorf("更新操作需要主键条件") } query := fmt.Sprintf("UPDATE %s SET %s WHERE %s", qualifiedTable, strings.Join(sets, ", "), strings.Join(wheres, " AND ")) if _, err := tx.Exec(query, args...); err != nil { - return fmt.Errorf("update error: %v", err) + return fmt.Errorf("更新失败:%v", err) } } @@ -378,7 +378,7 @@ func (c *CustomDB) ApplyChanges(tableName string, changes connection.ChangeSet) query := fmt.Sprintf("INSERT INTO %s (%s) VALUES (%s)", qualifiedTable, strings.Join(cols, ", "), strings.Join(placeholders, ", ")) if _, err := tx.Exec(query, args...); err != nil { - return fmt.Errorf("insert error: %v", err) + return fmt.Errorf("插入失败:%v", err) } } diff --git a/internal/db/dameng_impl.go b/internal/db/dameng_impl.go index 1cf27e6..a88572f 100644 --- a/internal/db/dameng_impl.go +++ b/internal/db/dameng_impl.go @@ -143,7 +143,7 @@ func (d *DamengDB) Close() error { func (d *DamengDB) Ping() error { if d.conn == nil { - return fmt.Errorf("connection not open") + return fmt.Errorf("连接未打开") } timeout := d.pingTimeout if timeout <= 0 { @@ -156,7 +156,7 @@ func (d *DamengDB) Ping() error { func (d *DamengDB) QueryContext(ctx context.Context, query string) ([]map[string]interface{}, []string, error) { if d.conn == nil { - return nil, nil, fmt.Errorf("connection not open") + return nil, nil, fmt.Errorf("连接未打开") } rows, err := d.conn.QueryContext(ctx, query) @@ -170,7 +170,7 @@ func (d *DamengDB) QueryContext(ctx context.Context, query string) ([]map[string func (d *DamengDB) Query(query string) ([]map[string]interface{}, []string, error) { if d.conn == nil { - return nil, nil, fmt.Errorf("connection not open") + return nil, nil, fmt.Errorf("连接未打开") } rows, err := d.conn.Query(query) @@ -183,7 +183,7 @@ func (d *DamengDB) Query(query string) ([]map[string]interface{}, []string, erro func (d *DamengDB) ExecContext(ctx context.Context, query string) (int64, error) { if d.conn == nil { - return 0, fmt.Errorf("connection not open") + return 0, fmt.Errorf("连接未打开") } res, err := d.conn.ExecContext(ctx, query) if err != nil { @@ -194,7 +194,7 @@ func (d *DamengDB) ExecContext(ctx context.Context, query string) (int64, error) func (d *DamengDB) Exec(query string) (int64, error) { if d.conn == nil { - return 0, fmt.Errorf("connection not open") + return 0, fmt.Errorf("连接未打开") } res, err := d.conn.Exec(query) if err != nil { @@ -260,7 +260,7 @@ func (d *DamengDB) GetCreateStatement(dbName, tableName string) (string, error) return fmt.Sprintf("%v", val), nil } } - return "", fmt.Errorf("create statement not found") + return "", fmt.Errorf("未找到建表语句") } func (d *DamengDB) GetColumns(dbName, tableName string) ([]connection.ColumnDefinition, error) { @@ -390,7 +390,7 @@ func (d *DamengDB) GetTriggers(dbName, tableName string) ([]connection.TriggerDe func (d *DamengDB) ApplyChanges(tableName string, changes connection.ChangeSet) error { if d.conn == nil { - return fmt.Errorf("connection not open") + return fmt.Errorf("连接未打开") } tx, err := d.conn.Begin() @@ -438,7 +438,7 @@ func (d *DamengDB) ApplyChanges(tableName string, changes connection.ChangeSet) } query := fmt.Sprintf("DELETE FROM %s WHERE %s", qualifiedTable, strings.Join(wheres, " AND ")) if _, err := tx.Exec(query, args...); err != nil { - return fmt.Errorf("delete error: %v", err) + return fmt.Errorf("删除失败:%v", err) } } @@ -466,12 +466,12 @@ func (d *DamengDB) ApplyChanges(tableName string, changes connection.ChangeSet) } if len(wheres) == 0 { - return fmt.Errorf("update requires keys") + return fmt.Errorf("更新操作需要主键条件") } query := fmt.Sprintf("UPDATE %s SET %s WHERE %s", qualifiedTable, strings.Join(sets, ", "), strings.Join(wheres, " AND ")) if _, err := tx.Exec(query, args...); err != nil { - return fmt.Errorf("update error: %v", err) + return fmt.Errorf("更新失败:%v", err) } } @@ -495,7 +495,7 @@ func (d *DamengDB) ApplyChanges(tableName string, changes connection.ChangeSet) query := fmt.Sprintf("INSERT INTO %s (%s) VALUES (%s)", qualifiedTable, strings.Join(cols, ", "), strings.Join(placeholders, ", ")) if _, err := tx.Exec(query, args...); err != nil { - return fmt.Errorf("insert error: %v", err) + return fmt.Errorf("插入失败:%v", err) } } diff --git a/internal/db/database.go b/internal/db/database.go index bf313d0..b0d4393 100644 --- a/internal/db/database.go +++ b/internal/db/database.go @@ -6,23 +6,42 @@ import ( "strings" ) +// Database 定义了统一的数据源访问接口。 +// 所有数据库驱动(MySQL、PostgreSQL、Oracle 等)均需实现此接口。 +// 方法调用方可通过 NewDatabase 工厂函数获取对应驱动的实例。 type Database interface { + // Connect 根据连接配置建立数据库连接。 Connect(config connection.ConnectionConfig) error + // Close 关闭数据库连接并释放底层资源。 Close() error + // Ping 测试连接是否仍然可用。 Ping() error + // Query 执行查询语句,返回结果行(列名→值映射)和列名列表。 Query(query string) ([]map[string]interface{}, []string, error) + // Exec 执行非查询语句(INSERT/UPDATE/DELETE 等),返回受影响行数。 Exec(query string) (int64, error) + // GetDatabases 返回当前连接可访问的数据库列表。 GetDatabases() ([]string, error) + // GetTables 返回指定数据库下的表列表。 GetTables(dbName string) ([]string, error) + // GetCreateStatement 返回指定表的建表 DDL 语句。 GetCreateStatement(dbName, tableName string) (string, error) + // GetColumns 返回指定表的列定义列表。 GetColumns(dbName, tableName string) ([]connection.ColumnDefinition, error) + // GetAllColumns 返回指定数据库下所有表的列定义(含表名标识)。 GetAllColumns(dbName string) ([]connection.ColumnDefinitionWithTable, error) + // GetIndexes 返回指定表的索引定义列表。 GetIndexes(dbName, tableName string) ([]connection.IndexDefinition, error) + // GetForeignKeys 返回指定表的外键定义列表。 GetForeignKeys(dbName, tableName string) ([]connection.ForeignKeyDefinition, error) + // GetTriggers 返回指定表的触发器定义列表。 GetTriggers(dbName, tableName string) ([]connection.TriggerDefinition, error) } +// BatchApplier 定义了批量变更提交接口。 +// 支持批量编辑的驱动实现此接口,用于一次性提交前端 DataGrid 中的增删改操作。 type BatchApplier interface { + // ApplyChanges 将一组变更(新增、修改、删除)批量提交到指定表。 ApplyChanges(tableName string, changes connection.ChangeSet) error } @@ -72,7 +91,9 @@ func normalizeDatabaseType(dbType string) string { } } -// Factory +// NewDatabase 根据数据库类型创建对应的 Database 实例。 +// dbType 为数据库类型标识(如 "mysql"、"postgres"、"oracle" 等),大小写不敏感。 +// 如果指定类型未注册,返回错误。 func NewDatabase(dbType string) (Database, error) { normalized := normalizeDatabaseType(dbType) if normalized == "" { @@ -80,7 +101,7 @@ func NewDatabase(dbType string) (Database, error) { } factory, ok := databaseFactories[normalized] if !ok { - return nil, fmt.Errorf("unsupported database type: %s", dbType) + return nil, fmt.Errorf("不支持的数据库类型:%s", dbType) } return factory(), nil } diff --git a/internal/db/driver_support.go b/internal/db/driver_support.go index db00717..f6b52e7 100644 --- a/internal/db/driver_support.go +++ b/internal/db/driver_support.go @@ -8,6 +8,7 @@ import ( "sync" ) +// coreBuiltinDrivers 是始终内置可用的核心驱动,无需额外安装即可使用。 var coreBuiltinDrivers = map[string]struct{}{ "mysql": {}, "redis": {}, @@ -91,6 +92,8 @@ func driverDisplayName(driverType string) string { } } +// IsOptionalGoDriver 返回指定驱动类型是否为可选的纯 Go 驱动。 +// 可选驱动需要用户在驱动管理界面点击“安装启用”后才能使用。 func IsOptionalGoDriver(driverType string) bool { _, ok := optionalGoDrivers[normalizeRuntimeDriverType(driverType)] return ok @@ -100,6 +103,7 @@ func IsOptionalGoDriverBuildIncluded(driverType string) bool { return optionalGoDriverBuildIncluded(normalizeRuntimeDriverType(driverType)) } +// IsBuiltinDriver 返回指定驱动类型是否为核心内置驱动(始终可用,无需安装)。 func IsBuiltinDriver(driverType string) bool { _, ok := coreBuiltinDrivers[normalizeRuntimeDriverType(driverType)] return ok @@ -146,6 +150,8 @@ func currentExternalDriverDownloadDirectory() string { return defaultExternalDriverDownloadDirectory() } +// SetExternalDriverDownloadDirectory 设置可选驱动的下载存储目录。 +// 如果路径解析失败,会回退到默认目录(~/.gonavi/drivers)。 func SetExternalDriverDownloadDirectory(downloadDir string) { root, err := resolveExternalDriverRoot(downloadDir) if err != nil { diff --git a/internal/db/duckdb_impl.go b/internal/db/duckdb_impl.go index f87ca74..843b49b 100644 --- a/internal/db/duckdb_impl.go +++ b/internal/db/duckdb_impl.go @@ -55,7 +55,7 @@ func (d *DuckDB) Close() error { func (d *DuckDB) Ping() error { if d.conn == nil { - return fmt.Errorf("connection not open") + return fmt.Errorf("连接未打开") } timeout := d.pingTimeout if timeout <= 0 { @@ -68,7 +68,7 @@ func (d *DuckDB) Ping() error { func (d *DuckDB) QueryContext(ctx context.Context, query string) ([]map[string]interface{}, []string, error) { if d.conn == nil { - return nil, nil, fmt.Errorf("connection not open") + return nil, nil, fmt.Errorf("连接未打开") } rows, err := d.conn.QueryContext(ctx, query) if err != nil { @@ -80,7 +80,7 @@ func (d *DuckDB) QueryContext(ctx context.Context, query string) ([]map[string]i func (d *DuckDB) Query(query string) ([]map[string]interface{}, []string, error) { if d.conn == nil { - return nil, nil, fmt.Errorf("connection not open") + return nil, nil, fmt.Errorf("连接未打开") } rows, err := d.conn.Query(query) if err != nil { @@ -92,7 +92,7 @@ func (d *DuckDB) Query(query string) ([]map[string]interface{}, []string, error) func (d *DuckDB) ExecContext(ctx context.Context, query string) (int64, error) { if d.conn == nil { - return 0, fmt.Errorf("connection not open") + return 0, fmt.Errorf("连接未打开") } res, err := d.conn.ExecContext(ctx, query) if err != nil { @@ -103,7 +103,7 @@ func (d *DuckDB) ExecContext(ctx context.Context, query string) (int64, error) { func (d *DuckDB) Exec(query string) (int64, error) { if d.conn == nil { - return 0, fmt.Errorf("connection not open") + return 0, fmt.Errorf("连接未打开") } res, err := d.conn.Exec(query) if err != nil { @@ -174,7 +174,7 @@ ORDER BY table_schema, table_name` func (d *DuckDB) GetCreateStatement(dbName, tableName string) (string, error) { schema, pureTable := normalizeDuckDBSchemaAndTable(dbName, tableName) if pureTable == "" { - return "", fmt.Errorf("table name required") + return "", fmt.Errorf("表名不能为空") } escapedTable := escapeDuckDBLiteral(pureTable) @@ -204,13 +204,13 @@ func (d *DuckDB) GetCreateStatement(dbName, tableName string) (string, error) { } } - return "", fmt.Errorf("create statement not found") + return "", fmt.Errorf("未找到建表语句") } func (d *DuckDB) GetColumns(dbName, tableName string) ([]connection.ColumnDefinition, error) { schema, pureTable := normalizeDuckDBSchemaAndTable(dbName, tableName) if pureTable == "" { - return nil, fmt.Errorf("table name required") + return nil, fmt.Errorf("表名不能为空") } query := fmt.Sprintf(` @@ -303,7 +303,7 @@ func (d *DuckDB) GetTriggers(dbName, tableName string) ([]connection.TriggerDefi func (d *DuckDB) ApplyChanges(tableName string, changes connection.ChangeSet) error { if d.conn == nil { - return fmt.Errorf("connection not open") + return fmt.Errorf("连接未打开") } tx, err := d.conn.Begin() @@ -346,7 +346,7 @@ func (d *DuckDB) ApplyChanges(tableName string, changes connection.ChangeSet) er } query := fmt.Sprintf("DELETE FROM %s WHERE %s", qualifiedTable, strings.Join(wheres, " AND ")) if _, err := tx.Exec(query, args...); err != nil { - return fmt.Errorf("delete error: %v", err) + return fmt.Errorf("删除失败:%v", err) } } @@ -367,12 +367,12 @@ func (d *DuckDB) ApplyChanges(tableName string, changes connection.ChangeSet) er args = append(args, v) } if len(wheres) == 0 { - return fmt.Errorf("update requires keys") + return fmt.Errorf("更新操作需要主键条件") } query := fmt.Sprintf("UPDATE %s SET %s WHERE %s", qualifiedTable, strings.Join(sets, ", "), strings.Join(wheres, " AND ")) if _, err := tx.Exec(query, args...); err != nil { - return fmt.Errorf("update error: %v", err) + return fmt.Errorf("更新失败:%v", err) } } @@ -392,7 +392,7 @@ func (d *DuckDB) ApplyChanges(tableName string, changes connection.ChangeSet) er query := fmt.Sprintf("INSERT INTO %s (%s) VALUES (%s)", qualifiedTable, strings.Join(cols, ", "), strings.Join(placeholders, ", ")) if _, err := tx.Exec(query, args...); err != nil { - return fmt.Errorf("insert error: %v", err) + return fmt.Errorf("插入失败:%v", err) } } diff --git a/internal/db/highgo_impl.go b/internal/db/highgo_impl.go index 0343565..a07027f 100644 --- a/internal/db/highgo_impl.go +++ b/internal/db/highgo_impl.go @@ -125,7 +125,7 @@ func (h *HighGoDB) Close() error { func (h *HighGoDB) Ping() error { if h.conn == nil { - return fmt.Errorf("connection not open") + return fmt.Errorf("连接未打开") } timeout := h.pingTimeout if timeout <= 0 { @@ -138,7 +138,7 @@ func (h *HighGoDB) Ping() error { func (h *HighGoDB) QueryContext(ctx context.Context, query string) ([]map[string]interface{}, []string, error) { if h.conn == nil { - return nil, nil, fmt.Errorf("connection not open") + return nil, nil, fmt.Errorf("连接未打开") } rows, err := h.conn.QueryContext(ctx, query) @@ -152,7 +152,7 @@ func (h *HighGoDB) QueryContext(ctx context.Context, query string) ([]map[string func (h *HighGoDB) Query(query string) ([]map[string]interface{}, []string, error) { if h.conn == nil { - return nil, nil, fmt.Errorf("connection not open") + return nil, nil, fmt.Errorf("连接未打开") } rows, err := h.conn.Query(query) @@ -165,7 +165,7 @@ func (h *HighGoDB) Query(query string) ([]map[string]interface{}, []string, erro func (h *HighGoDB) ExecContext(ctx context.Context, query string) (int64, error) { if h.conn == nil { - return 0, fmt.Errorf("connection not open") + return 0, fmt.Errorf("连接未打开") } res, err := h.conn.ExecContext(ctx, query) if err != nil { @@ -176,7 +176,7 @@ func (h *HighGoDB) ExecContext(ctx context.Context, query string) (int64, error) func (h *HighGoDB) Exec(query string) (int64, error) { if h.conn == nil { - return 0, fmt.Errorf("connection not open") + return 0, fmt.Errorf("连接未打开") } res, err := h.conn.Exec(query) if err != nil { @@ -232,7 +232,7 @@ func (h *HighGoDB) GetColumns(dbName, tableName string) ([]connection.ColumnDefi } table := strings.TrimSpace(tableName) if table == "" { - return nil, fmt.Errorf("table name required") + return nil, fmt.Errorf("表名不能为空") } esc := func(s string) string { return strings.ReplaceAll(s, "'", "''") } @@ -302,7 +302,7 @@ func (h *HighGoDB) GetIndexes(dbName, tableName string) ([]connection.IndexDefin } table := strings.TrimSpace(tableName) if table == "" { - return nil, fmt.Errorf("table name required") + return nil, fmt.Errorf("表名不能为空") } esc := func(s string) string { return strings.ReplaceAll(s, "'", "''") } @@ -407,7 +407,7 @@ func (h *HighGoDB) GetForeignKeys(dbName, tableName string) ([]connection.Foreig } table := strings.TrimSpace(tableName) if table == "" { - return nil, fmt.Errorf("table name required") + return nil, fmt.Errorf("表名不能为空") } esc := func(s string) string { return strings.ReplaceAll(s, "'", "''") } @@ -467,7 +467,7 @@ func (h *HighGoDB) GetTriggers(dbName, tableName string) ([]connection.TriggerDe } table := strings.TrimSpace(tableName) if table == "" { - return nil, fmt.Errorf("table name required") + return nil, fmt.Errorf("表名不能为空") } esc := func(s string) string { return strings.ReplaceAll(s, "'", "''") } @@ -531,7 +531,7 @@ ORDER BY table_schema, table_name, ordinal_position` func (h *HighGoDB) ApplyChanges(tableName string, changes connection.ChangeSet) error { if h.conn == nil { - return fmt.Errorf("connection not open") + return fmt.Errorf("连接未打开") } tx, err := h.conn.Begin() @@ -579,7 +579,7 @@ func (h *HighGoDB) ApplyChanges(tableName string, changes connection.ChangeSet) } query := fmt.Sprintf("DELETE FROM %s WHERE %s", qualifiedTable, strings.Join(wheres, " AND ")) if _, err := tx.Exec(query, args...); err != nil { - return fmt.Errorf("delete error: %v", err) + return fmt.Errorf("删除失败:%v", err) } } @@ -607,12 +607,12 @@ func (h *HighGoDB) ApplyChanges(tableName string, changes connection.ChangeSet) } if len(wheres) == 0 { - return fmt.Errorf("update requires keys") + return fmt.Errorf("更新操作需要主键条件") } query := fmt.Sprintf("UPDATE %s SET %s WHERE %s", qualifiedTable, strings.Join(sets, ", "), strings.Join(wheres, " AND ")) if _, err := tx.Exec(query, args...); err != nil { - return fmt.Errorf("update error: %v", err) + return fmt.Errorf("更新失败:%v", err) } } @@ -636,7 +636,7 @@ func (h *HighGoDB) ApplyChanges(tableName string, changes connection.ChangeSet) query := fmt.Sprintf("INSERT INTO %s (%s) VALUES (%s)", qualifiedTable, strings.Join(cols, ", "), strings.Join(placeholders, ", ")) if _, err := tx.Exec(query, args...); err != nil { - return fmt.Errorf("insert error: %v", err) + return fmt.Errorf("插入失败:%v", err) } } diff --git a/internal/db/kingbase_impl.go b/internal/db/kingbase_impl.go index 77a4ac3..9a913d1 100644 --- a/internal/db/kingbase_impl.go +++ b/internal/db/kingbase_impl.go @@ -232,7 +232,7 @@ func (k *KingbaseDB) Close() error { func (k *KingbaseDB) Ping() error { if k.conn == nil { - return fmt.Errorf("connection not open") + return fmt.Errorf("连接未打开") } timeout := k.pingTimeout if timeout <= 0 { @@ -245,7 +245,7 @@ func (k *KingbaseDB) Ping() error { func (k *KingbaseDB) QueryContext(ctx context.Context, query string) ([]map[string]interface{}, []string, error) { if k.conn == nil { - return nil, nil, fmt.Errorf("connection not open") + return nil, nil, fmt.Errorf("连接未打开") } rows, err := k.conn.QueryContext(ctx, query) @@ -259,7 +259,7 @@ func (k *KingbaseDB) QueryContext(ctx context.Context, query string) ([]map[stri func (k *KingbaseDB) Query(query string) ([]map[string]interface{}, []string, error) { if k.conn == nil { - return nil, nil, fmt.Errorf("connection not open") + return nil, nil, fmt.Errorf("连接未打开") } rows, err := k.conn.Query(query) @@ -272,7 +272,7 @@ func (k *KingbaseDB) Query(query string) ([]map[string]interface{}, []string, er func (k *KingbaseDB) ExecContext(ctx context.Context, query string) (int64, error) { if k.conn == nil { - return 0, fmt.Errorf("connection not open") + return 0, fmt.Errorf("连接未打开") } res, err := k.conn.ExecContext(ctx, query) if err != nil { @@ -283,7 +283,7 @@ func (k *KingbaseDB) ExecContext(ctx context.Context, query string) (int64, erro func (k *KingbaseDB) Exec(query string) (int64, error) { if k.conn == nil { - return 0, fmt.Errorf("connection not open") + return 0, fmt.Errorf("连接未打开") } res, err := k.conn.Exec(query) if err != nil { @@ -367,7 +367,7 @@ func (k *KingbaseDB) GetColumns(dbName, tableName string) ([]connection.ColumnDe } if table == "" { - return nil, fmt.Errorf("table name required") + return nil, fmt.Errorf("表名不能为空") } // 转义函数:处理单引号,移除双引号 @@ -440,7 +440,7 @@ ORDER BY a.attnum`, esc(schema), esc(table)) func (k *KingbaseDB) getColumnsWithCurrentSchema(tableName string) ([]connection.ColumnDefinition, error) { table := strings.TrimSpace(tableName) if table == "" { - return nil, fmt.Errorf("table name required") + return nil, fmt.Errorf("表名不能为空") } // 转义函数 @@ -524,7 +524,7 @@ func (k *KingbaseDB) GetIndexes(dbName, tableName string) ([]connection.IndexDef } if table == "" { - return nil, fmt.Errorf("table name required") + return nil, fmt.Errorf("表名不能为空") } // 转义函数:处理单引号,移除双引号 @@ -622,7 +622,7 @@ func (k *KingbaseDB) GetForeignKeys(dbName, tableName string) ([]connection.Fore } if table == "" { - return nil, fmt.Errorf("table name required") + return nil, fmt.Errorf("表名不能为空") } // 转义函数:处理单引号,移除双引号 @@ -704,7 +704,7 @@ func (k *KingbaseDB) GetTriggers(dbName, tableName string) ([]connection.Trigger } if table == "" { - return nil, fmt.Errorf("table name required") + return nil, fmt.Errorf("表名不能为空") } // 转义函数:处理单引号,移除双引号 @@ -747,7 +747,7 @@ func (k *KingbaseDB) GetTriggers(dbName, tableName string) ([]connection.Trigger func (k *KingbaseDB) ApplyChanges(tableName string, changes connection.ChangeSet) error { if k.conn == nil { - return fmt.Errorf("connection not open") + return fmt.Errorf("连接未打开") } tx, err := k.conn.Begin() @@ -758,7 +758,7 @@ func (k *KingbaseDB) ApplyChanges(tableName string, changes connection.ChangeSet schema, table := splitKingbaseQualifiedTable(tableName) if table == "" { - return fmt.Errorf("table name required") + return fmt.Errorf("表名不能为空") } qualifiedTable := "" @@ -811,7 +811,7 @@ func (k *KingbaseDB) ApplyChanges(tableName string, changes connection.ChangeSet } if len(wheres) == 0 { - return fmt.Errorf("update requires keys") + return fmt.Errorf("更新操作需要主键条件") } query := fmt.Sprintf("UPDATE %s SET %s WHERE %s", qualifiedTable, strings.Join(sets, ", "), strings.Join(wheres, " AND ")) @@ -840,7 +840,7 @@ func (k *KingbaseDB) ApplyChanges(tableName string, changes connection.ChangeSet query := fmt.Sprintf("INSERT INTO %s (%s) VALUES (%s)", qualifiedTable, strings.Join(cols, ", "), strings.Join(placeholders, ", ")) if _, err := tx.Exec(query, args...); err != nil { - return fmt.Errorf("insert error: %v; sql=%s", err, query) + return fmt.Errorf("插入失败:%v; sql=%s", err, query) } } diff --git a/internal/db/mariadb_impl.go b/internal/db/mariadb_impl.go index 65b9cc3..02e3e07 100644 --- a/internal/db/mariadb_impl.go +++ b/internal/db/mariadb_impl.go @@ -73,7 +73,7 @@ func (m *MariaDB) Close() error { func (m *MariaDB) Ping() error { if m.conn == nil { - return fmt.Errorf("connection not open") + return fmt.Errorf("连接未打开") } timeout := m.pingTimeout if timeout <= 0 { @@ -86,7 +86,7 @@ func (m *MariaDB) Ping() error { func (m *MariaDB) QueryContext(ctx context.Context, query string) ([]map[string]interface{}, []string, error) { if m.conn == nil { - return nil, nil, fmt.Errorf("connection not open") + return nil, nil, fmt.Errorf("连接未打开") } rows, err := m.conn.QueryContext(ctx, query) @@ -100,7 +100,7 @@ func (m *MariaDB) QueryContext(ctx context.Context, query string) ([]map[string] func (m *MariaDB) Query(query string) ([]map[string]interface{}, []string, error) { if m.conn == nil { - return nil, nil, fmt.Errorf("connection not open") + return nil, nil, fmt.Errorf("连接未打开") } rows, err := m.conn.Query(query) @@ -113,7 +113,7 @@ func (m *MariaDB) Query(query string) ([]map[string]interface{}, []string, error func (m *MariaDB) ExecContext(ctx context.Context, query string) (int64, error) { if m.conn == nil { - return 0, fmt.Errorf("connection not open") + return 0, fmt.Errorf("连接未打开") } res, err := m.conn.ExecContext(ctx, query) if err != nil { @@ -124,7 +124,7 @@ func (m *MariaDB) ExecContext(ctx context.Context, query string) (int64, error) func (m *MariaDB) Exec(query string) (int64, error) { if m.conn == nil { - return 0, fmt.Errorf("connection not open") + return 0, fmt.Errorf("连接未打开") } res, err := m.conn.Exec(query) if err != nil { @@ -186,7 +186,7 @@ func (m *MariaDB) GetCreateStatement(dbName, tableName string) (string, error) { return fmt.Sprintf("%v", val), nil } } - return "", fmt.Errorf("create statement not found") + return "", fmt.Errorf("未找到建表语句") } func (m *MariaDB) GetColumns(dbName, tableName string) ([]connection.ColumnDefinition, error) { @@ -320,7 +320,7 @@ func (m *MariaDB) GetTriggers(dbName, tableName string) ([]connection.TriggerDef func (m *MariaDB) ApplyChanges(tableName string, changes connection.ChangeSet) error { if m.conn == nil { - return fmt.Errorf("connection not open") + return fmt.Errorf("连接未打开") } tx, err := m.conn.Begin() @@ -342,7 +342,7 @@ func (m *MariaDB) ApplyChanges(tableName string, changes connection.ChangeSet) e } query := fmt.Sprintf("DELETE FROM `%s` WHERE %s", tableName, strings.Join(wheres, " AND ")) if _, err := tx.Exec(query, args...); err != nil { - return fmt.Errorf("delete error: %v", err) + return fmt.Errorf("删除失败:%v", err) } } @@ -367,12 +367,12 @@ func (m *MariaDB) ApplyChanges(tableName string, changes connection.ChangeSet) e } if len(wheres) == 0 { - return fmt.Errorf("update requires keys") + return fmt.Errorf("更新操作需要主键条件") } query := fmt.Sprintf("UPDATE `%s` SET %s WHERE %s", tableName, strings.Join(sets, ", "), strings.Join(wheres, " AND ")) if _, err := tx.Exec(query, args...); err != nil { - return fmt.Errorf("update error: %v", err) + return fmt.Errorf("更新失败:%v", err) } } @@ -394,7 +394,7 @@ func (m *MariaDB) ApplyChanges(tableName string, changes connection.ChangeSet) e query := fmt.Sprintf("INSERT INTO `%s` (%s) VALUES (%s)", tableName, strings.Join(cols, ", "), strings.Join(placeholders, ", ")) if _, err := tx.Exec(query, args...); err != nil { - return fmt.Errorf("insert error: %v", err) + return fmt.Errorf("插入失败:%v", err) } } @@ -404,7 +404,7 @@ func (m *MariaDB) ApplyChanges(tableName string, changes connection.ChangeSet) e func (m *MariaDB) GetAllColumns(dbName string) ([]connection.ColumnDefinitionWithTable, error) { query := fmt.Sprintf("SELECT TABLE_NAME, COLUMN_NAME, COLUMN_TYPE FROM information_schema.COLUMNS WHERE TABLE_SCHEMA = '%s'", dbName) if dbName == "" { - return nil, fmt.Errorf("database name required for GetAllColumns") + return nil, fmt.Errorf("获取全部列信息需要指定数据库名称") } data, _, err := m.Query(query) diff --git a/internal/db/mongodb_impl.go b/internal/db/mongodb_impl.go index dff4644..bcdd2ce 100644 --- a/internal/db/mongodb_impl.go +++ b/internal/db/mongodb_impl.go @@ -480,7 +480,7 @@ func (m *MongoDB) Close() error { func (m *MongoDB) Ping() error { if m.client == nil { - return fmt.Errorf("connection not open") + return fmt.Errorf("连接未打开") } timeout := m.pingTimeout if timeout <= 0 { @@ -684,7 +684,7 @@ func buildMembersFromHello(raw bson.M) []connection.MongoMemberInfo { func (m *MongoDB) DiscoverMembers() (string, []connection.MongoMemberInfo, error) { if m.client == nil { - return "", nil, fmt.Errorf("connection not open") + return "", nil, fmt.Errorf("连接未打开") } timeout := m.pingTimeout @@ -835,7 +835,7 @@ func extractCollectionFromSQL(sql string) string { func (m *MongoDB) queryWithContext(ctx context.Context, query string) ([]map[string]interface{}, []string, error) { if m.client == nil { - return nil, nil, fmt.Errorf("connection not open") + return nil, nil, fmt.Errorf("连接未打开") } query = strings.TrimSpace(query) @@ -1079,7 +1079,7 @@ func (m *MongoDB) ExecContext(ctx context.Context, query string) (int64, error) func (m *MongoDB) GetDatabases() ([]string, error) { if m.client == nil { - return nil, fmt.Errorf("connection not open") + return nil, fmt.Errorf("连接未打开") } ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) @@ -1094,7 +1094,7 @@ func (m *MongoDB) GetDatabases() ([]string, error) { func (m *MongoDB) GetTables(dbName string) ([]string, error) { if m.client == nil { - return nil, fmt.Errorf("connection not open") + return nil, fmt.Errorf("连接未打开") } targetDB := dbName @@ -1130,7 +1130,7 @@ func (m *MongoDB) GetAllColumns(dbName string) ([]connection.ColumnDefinitionWit // GetIndexes returns indexes for a MongoDB collection func (m *MongoDB) GetIndexes(dbName, tableName string) ([]connection.IndexDefinition, error) { if m.client == nil { - return nil, fmt.Errorf("connection not open") + return nil, fmt.Errorf("连接未打开") } targetDB := dbName @@ -1197,7 +1197,7 @@ func (m *MongoDB) GetTriggers(dbName, tableName string) ([]connection.TriggerDef // ApplyChanges implements batch changes for MongoDB func (m *MongoDB) ApplyChanges(tableName string, changes connection.ChangeSet) error { if m.client == nil { - return fmt.Errorf("connection not open") + return fmt.Errorf("连接未打开") } ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second) @@ -1213,7 +1213,7 @@ func (m *MongoDB) ApplyChanges(tableName string, changes connection.ChangeSet) e } if len(filter) > 0 { if _, err := collection.DeleteOne(ctx, filter); err != nil { - return fmt.Errorf("delete error: %v", err) + return fmt.Errorf("删除失败:%v", err) } } } @@ -1225,7 +1225,7 @@ func (m *MongoDB) ApplyChanges(tableName string, changes connection.ChangeSet) e filter[k] = v } if len(filter) == 0 { - return fmt.Errorf("update requires keys") + return fmt.Errorf("更新操作需要主键条件") } updateDoc := bson.M{"$set": bson.M{}} @@ -1234,7 +1234,7 @@ func (m *MongoDB) ApplyChanges(tableName string, changes connection.ChangeSet) e } if _, err := collection.UpdateOne(ctx, filter, updateDoc); err != nil { - return fmt.Errorf("update error: %v", err) + return fmt.Errorf("更新失败:%v", err) } } @@ -1246,7 +1246,7 @@ func (m *MongoDB) ApplyChanges(tableName string, changes connection.ChangeSet) e } if len(doc) > 0 { if _, err := collection.InsertOne(ctx, doc); err != nil { - return fmt.Errorf("insert error: %v", err) + return fmt.Errorf("插入失败:%v", err) } } } diff --git a/internal/db/mongodb_impl_v1.go b/internal/db/mongodb_impl_v1.go index 60d4fb2..909285b 100644 --- a/internal/db/mongodb_impl_v1.go +++ b/internal/db/mongodb_impl_v1.go @@ -483,7 +483,7 @@ func (m *MongoDBV1) Close() error { func (m *MongoDBV1) Ping() error { if m.client == nil { - return fmt.Errorf("connection not open") + return fmt.Errorf("连接未打开") } timeout := m.pingTimeout if timeout <= 0 { @@ -687,7 +687,7 @@ func buildMembersFromHello(raw bson.M) []connection.MongoMemberInfo { func (m *MongoDBV1) DiscoverMembers() (string, []connection.MongoMemberInfo, error) { if m.client == nil { - return "", nil, fmt.Errorf("connection not open") + return "", nil, fmt.Errorf("连接未打开") } timeout := m.pingTimeout @@ -838,7 +838,7 @@ func extractCollectionFromSQL(sql string) string { func (m *MongoDBV1) queryWithContext(ctx context.Context, query string) ([]map[string]interface{}, []string, error) { if m.client == nil { - return nil, nil, fmt.Errorf("connection not open") + return nil, nil, fmt.Errorf("连接未打开") } query = strings.TrimSpace(query) @@ -1082,7 +1082,7 @@ func (m *MongoDBV1) ExecContext(ctx context.Context, query string) (int64, error func (m *MongoDBV1) GetDatabases() ([]string, error) { if m.client == nil { - return nil, fmt.Errorf("connection not open") + return nil, fmt.Errorf("连接未打开") } ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) @@ -1097,7 +1097,7 @@ func (m *MongoDBV1) GetDatabases() ([]string, error) { func (m *MongoDBV1) GetTables(dbName string) ([]string, error) { if m.client == nil { - return nil, fmt.Errorf("connection not open") + return nil, fmt.Errorf("连接未打开") } targetDB := dbName @@ -1133,7 +1133,7 @@ func (m *MongoDBV1) GetAllColumns(dbName string) ([]connection.ColumnDefinitionW // GetIndexes returns indexes for a MongoDB collection func (m *MongoDBV1) GetIndexes(dbName, tableName string) ([]connection.IndexDefinition, error) { if m.client == nil { - return nil, fmt.Errorf("connection not open") + return nil, fmt.Errorf("连接未打开") } targetDB := dbName @@ -1200,7 +1200,7 @@ func (m *MongoDBV1) GetTriggers(dbName, tableName string) ([]connection.TriggerD // ApplyChanges implements batch changes for MongoDB func (m *MongoDBV1) ApplyChanges(tableName string, changes connection.ChangeSet) error { if m.client == nil { - return fmt.Errorf("connection not open") + return fmt.Errorf("连接未打开") } ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second) @@ -1216,7 +1216,7 @@ func (m *MongoDBV1) ApplyChanges(tableName string, changes connection.ChangeSet) } if len(filter) > 0 { if _, err := collection.DeleteOne(ctx, filter); err != nil { - return fmt.Errorf("delete error: %v", err) + return fmt.Errorf("删除失败:%v", err) } } } @@ -1228,7 +1228,7 @@ func (m *MongoDBV1) ApplyChanges(tableName string, changes connection.ChangeSet) filter[k] = v } if len(filter) == 0 { - return fmt.Errorf("update requires keys") + return fmt.Errorf("更新操作需要主键条件") } updateDoc := bson.M{"$set": bson.M{}} @@ -1237,7 +1237,7 @@ func (m *MongoDBV1) ApplyChanges(tableName string, changes connection.ChangeSet) } if _, err := collection.UpdateOne(ctx, filter, updateDoc); err != nil { - return fmt.Errorf("update error: %v", err) + return fmt.Errorf("更新失败:%v", err) } } @@ -1249,7 +1249,7 @@ func (m *MongoDBV1) ApplyChanges(tableName string, changes connection.ChangeSet) } if len(doc) > 0 { if _, err := collection.InsertOne(ctx, doc); err != nil { - return fmt.Errorf("insert error: %v", err) + return fmt.Errorf("插入失败:%v", err) } } } diff --git a/internal/db/mysql_agent_impl.go b/internal/db/mysql_agent_impl.go index 84f5233..3d55787 100644 --- a/internal/db/mysql_agent_impl.go +++ b/internal/db/mysql_agent_impl.go @@ -429,7 +429,7 @@ func (m *MySQLAgentDB) ApplyChanges(tableName string, changes connection.ChangeS func (m *MySQLAgentDB) requireClient() (*mysqlAgentClient, error) { if m.client == nil { - return nil, fmt.Errorf("connection not open") + return nil, fmt.Errorf("连接未打开") } return m.client, nil } diff --git a/internal/db/mysql_impl.go b/internal/db/mysql_impl.go index 32b63cc..efd8b97 100644 --- a/internal/db/mysql_impl.go +++ b/internal/db/mysql_impl.go @@ -267,7 +267,7 @@ func (m *MySQLDB) Close() error { func (m *MySQLDB) Ping() error { if m.conn == nil { - return fmt.Errorf("connection not open") + return fmt.Errorf("连接未打开") } timeout := m.pingTimeout if timeout <= 0 { @@ -280,7 +280,7 @@ func (m *MySQLDB) Ping() error { func (m *MySQLDB) QueryContext(ctx context.Context, query string) ([]map[string]interface{}, []string, error) { if m.conn == nil { - return nil, nil, fmt.Errorf("connection not open") + return nil, nil, fmt.Errorf("连接未打开") } rows, err := m.conn.QueryContext(ctx, query) @@ -294,7 +294,7 @@ func (m *MySQLDB) QueryContext(ctx context.Context, query string) ([]map[string] func (m *MySQLDB) Query(query string) ([]map[string]interface{}, []string, error) { if m.conn == nil { - return nil, nil, fmt.Errorf("connection not open") + return nil, nil, fmt.Errorf("连接未打开") } rows, err := m.conn.Query(query) @@ -307,7 +307,7 @@ func (m *MySQLDB) Query(query string) ([]map[string]interface{}, []string, error func (m *MySQLDB) ExecContext(ctx context.Context, query string) (int64, error) { if m.conn == nil { - return 0, fmt.Errorf("connection not open") + return 0, fmt.Errorf("连接未打开") } res, err := m.conn.ExecContext(ctx, query) if err != nil { @@ -318,7 +318,7 @@ func (m *MySQLDB) ExecContext(ctx context.Context, query string) (int64, error) func (m *MySQLDB) Exec(query string) (int64, error) { if m.conn == nil { - return 0, fmt.Errorf("connection not open") + return 0, fmt.Errorf("连接未打开") } res, err := m.conn.Exec(query) if err != nil { @@ -380,7 +380,7 @@ func (m *MySQLDB) GetCreateStatement(dbName, tableName string) (string, error) { return fmt.Sprintf("%v", val), nil } } - return "", fmt.Errorf("create statement not found") + return "", fmt.Errorf("未找到建表语句") } func (m *MySQLDB) GetColumns(dbName, tableName string) ([]connection.ColumnDefinition, error) { @@ -514,7 +514,7 @@ func (m *MySQLDB) GetTriggers(dbName, tableName string) ([]connection.TriggerDef func (m *MySQLDB) ApplyChanges(tableName string, changes connection.ChangeSet) error { if m.conn == nil { - return fmt.Errorf("connection not open") + return fmt.Errorf("连接未打开") } columnTypeMap := m.loadColumnTypeMap(tableName) @@ -539,7 +539,7 @@ func (m *MySQLDB) ApplyChanges(tableName string, changes connection.ChangeSet) e query := fmt.Sprintf("DELETE FROM `%s` WHERE %s", tableName, strings.Join(wheres, " AND ")) res, err := tx.Exec(query, args...) if err != nil { - return fmt.Errorf("delete error: %v", err) + return fmt.Errorf("删除失败:%v", err) } if affected, err := res.RowsAffected(); err == nil && affected == 0 { return fmt.Errorf("删除未生效:未匹配到任何行") @@ -567,13 +567,13 @@ func (m *MySQLDB) ApplyChanges(tableName string, changes connection.ChangeSet) e } if len(wheres) == 0 { - return fmt.Errorf("update requires keys") + return fmt.Errorf("更新操作需要主键条件") } query := fmt.Sprintf("UPDATE `%s` SET %s WHERE %s", tableName, strings.Join(sets, ", "), strings.Join(wheres, " AND ")) res, err := tx.Exec(query, args...) if err != nil { - return fmt.Errorf("update error: %v", err) + return fmt.Errorf("更新失败:%v", err) } if affected, err := res.RowsAffected(); err == nil && affected == 0 { return fmt.Errorf("更新未生效:未匹配到任何行") @@ -600,7 +600,7 @@ func (m *MySQLDB) ApplyChanges(tableName string, changes connection.ChangeSet) e query := fmt.Sprintf("INSERT INTO `%s` () VALUES ()", tableName) res, err := tx.Exec(query) if err != nil { - return fmt.Errorf("insert error: %v", err) + return fmt.Errorf("插入失败:%v", err) } if affected, err := res.RowsAffected(); err == nil && affected == 0 { return fmt.Errorf("插入未生效:未影响任何行") @@ -611,7 +611,7 @@ func (m *MySQLDB) ApplyChanges(tableName string, changes connection.ChangeSet) e query := fmt.Sprintf("INSERT INTO `%s` (%s) VALUES (%s)", tableName, strings.Join(cols, ", "), strings.Join(placeholders, ", ")) res, err := tx.Exec(query, args...) if err != nil { - return fmt.Errorf("insert error: %v", err) + return fmt.Errorf("插入失败:%v", err) } if affected, err := res.RowsAffected(); err == nil && affected == 0 { return fmt.Errorf("插入未生效:未影响任何行") @@ -774,7 +774,7 @@ func formatMySQLDateTime(t time.Time) string { func (m *MySQLDB) GetAllColumns(dbName string) ([]connection.ColumnDefinitionWithTable, error) { query := fmt.Sprintf("SELECT TABLE_NAME, COLUMN_NAME, COLUMN_TYPE FROM information_schema.COLUMNS WHERE TABLE_SCHEMA = '%s'", dbName) if dbName == "" { - return nil, fmt.Errorf("database name required for GetAllColumns") + return nil, fmt.Errorf("获取全部列信息需要指定数据库名称") } data, _, err := m.Query(query) diff --git a/internal/db/optional_driver_agent_impl.go b/internal/db/optional_driver_agent_impl.go index 9316a1b..d5f1810 100644 --- a/internal/db/optional_driver_agent_impl.go +++ b/internal/db/optional_driver_agent_impl.go @@ -510,7 +510,7 @@ func (d *OptionalDriverAgentDB) ApplyChanges(tableName string, changes connectio func (d *OptionalDriverAgentDB) requireClient() (*optionalDriverAgentClient, error) { if d.client == nil { - return nil, fmt.Errorf("connection not open") + return nil, fmt.Errorf("连接未打开") } return d.client, nil } diff --git a/internal/db/oracle_impl.go b/internal/db/oracle_impl.go index f0d03eb..e82e356 100644 --- a/internal/db/oracle_impl.go +++ b/internal/db/oracle_impl.go @@ -135,7 +135,7 @@ func (o *OracleDB) Close() error { func (o *OracleDB) Ping() error { if o.conn == nil { - return fmt.Errorf("connection not open") + return fmt.Errorf("连接未打开") } timeout := o.pingTimeout if timeout <= 0 { @@ -148,7 +148,7 @@ func (o *OracleDB) Ping() error { func (o *OracleDB) QueryContext(ctx context.Context, query string) ([]map[string]interface{}, []string, error) { if o.conn == nil { - return nil, nil, fmt.Errorf("connection not open") + return nil, nil, fmt.Errorf("连接未打开") } rows, err := o.conn.QueryContext(ctx, query) @@ -162,7 +162,7 @@ func (o *OracleDB) QueryContext(ctx context.Context, query string) ([]map[string func (o *OracleDB) Query(query string) ([]map[string]interface{}, []string, error) { if o.conn == nil { - return nil, nil, fmt.Errorf("connection not open") + return nil, nil, fmt.Errorf("连接未打开") } rows, err := o.conn.Query(query) @@ -175,7 +175,7 @@ func (o *OracleDB) Query(query string) ([]map[string]interface{}, []string, erro func (o *OracleDB) ExecContext(ctx context.Context, query string) (int64, error) { if o.conn == nil { - return 0, fmt.Errorf("connection not open") + return 0, fmt.Errorf("连接未打开") } res, err := o.conn.ExecContext(ctx, query) if err != nil { @@ -186,7 +186,7 @@ func (o *OracleDB) ExecContext(ctx context.Context, query string) (int64, error) func (o *OracleDB) Exec(query string) (int64, error) { if o.conn == nil { - return 0, fmt.Errorf("connection not open") + return 0, fmt.Errorf("连接未打开") } res, err := o.conn.Exec(query) if err != nil { @@ -259,7 +259,7 @@ func (o *OracleDB) GetCreateStatement(dbName, tableName string) (string, error) return fmt.Sprintf("%v", val), nil } } - return "", fmt.Errorf("create statement not found") + return "", fmt.Errorf("未找到建表语句") } func (o *OracleDB) GetColumns(dbName, tableName string) ([]connection.ColumnDefinition, error) { @@ -391,7 +391,7 @@ func (o *OracleDB) GetTriggers(dbName, tableName string) ([]connection.TriggerDe func (o *OracleDB) ApplyChanges(tableName string, changes connection.ChangeSet) error { if o.conn == nil { - return fmt.Errorf("connection not open") + return fmt.Errorf("连接未打开") } tx, err := o.conn.Begin() @@ -439,7 +439,7 @@ func (o *OracleDB) ApplyChanges(tableName string, changes connection.ChangeSet) } query := fmt.Sprintf("DELETE FROM %s WHERE %s", qualifiedTable, strings.Join(wheres, " AND ")) if _, err := tx.Exec(query, args...); err != nil { - return fmt.Errorf("delete error: %v", err) + return fmt.Errorf("删除失败:%v", err) } } @@ -467,12 +467,12 @@ func (o *OracleDB) ApplyChanges(tableName string, changes connection.ChangeSet) } if len(wheres) == 0 { - return fmt.Errorf("update requires keys") + return fmt.Errorf("更新操作需要主键条件") } query := fmt.Sprintf("UPDATE %s SET %s WHERE %s", qualifiedTable, strings.Join(sets, ", "), strings.Join(wheres, " AND ")) if _, err := tx.Exec(query, args...); err != nil { - return fmt.Errorf("update error: %v", err) + return fmt.Errorf("更新失败:%v", err) } } @@ -496,7 +496,7 @@ func (o *OracleDB) ApplyChanges(tableName string, changes connection.ChangeSet) query := fmt.Sprintf("INSERT INTO %s (%s) VALUES (%s)", qualifiedTable, strings.Join(cols, ", "), strings.Join(placeholders, ", ")) if _, err := tx.Exec(query, args...); err != nil { - return fmt.Errorf("insert error: %v", err) + return fmt.Errorf("插入失败:%v", err) } } diff --git a/internal/db/postgres_impl.go b/internal/db/postgres_impl.go index b6cd82e..dc6a06b 100644 --- a/internal/db/postgres_impl.go +++ b/internal/db/postgres_impl.go @@ -195,7 +195,7 @@ func (p *PostgresDB) Close() error { func (p *PostgresDB) Ping() error { if p.conn == nil { - return fmt.Errorf("connection not open") + return fmt.Errorf("连接未打开") } timeout := p.pingTimeout if timeout <= 0 { @@ -208,7 +208,7 @@ func (p *PostgresDB) Ping() error { func (p *PostgresDB) QueryContext(ctx context.Context, query string) ([]map[string]interface{}, []string, error) { if p.conn == nil { - return nil, nil, fmt.Errorf("connection not open") + return nil, nil, fmt.Errorf("连接未打开") } rows, err := p.conn.QueryContext(ctx, query) @@ -222,7 +222,7 @@ func (p *PostgresDB) QueryContext(ctx context.Context, query string) ([]map[stri func (p *PostgresDB) Query(query string) ([]map[string]interface{}, []string, error) { if p.conn == nil { - return nil, nil, fmt.Errorf("connection not open") + return nil, nil, fmt.Errorf("连接未打开") } rows, err := p.conn.Query(query) @@ -235,7 +235,7 @@ func (p *PostgresDB) Query(query string) ([]map[string]interface{}, []string, er func (p *PostgresDB) ExecContext(ctx context.Context, query string) (int64, error) { if p.conn == nil { - return 0, fmt.Errorf("connection not open") + return 0, fmt.Errorf("连接未打开") } res, err := p.conn.ExecContext(ctx, query) if err != nil { @@ -246,7 +246,7 @@ func (p *PostgresDB) ExecContext(ctx context.Context, query string) (int64, erro func (p *PostgresDB) Exec(query string) (int64, error) { if p.conn == nil { - return 0, fmt.Errorf("connection not open") + return 0, fmt.Errorf("连接未打开") } res, err := p.conn.Exec(query) if err != nil { @@ -302,7 +302,7 @@ func (p *PostgresDB) GetColumns(dbName, tableName string) ([]connection.ColumnDe } table := strings.TrimSpace(tableName) if table == "" { - return nil, fmt.Errorf("table name required") + return nil, fmt.Errorf("表名不能为空") } esc := func(s string) string { return strings.ReplaceAll(s, "'", "''") } @@ -372,7 +372,7 @@ func (p *PostgresDB) GetIndexes(dbName, tableName string) ([]connection.IndexDef } table := strings.TrimSpace(tableName) if table == "" { - return nil, fmt.Errorf("table name required") + return nil, fmt.Errorf("表名不能为空") } esc := func(s string) string { return strings.ReplaceAll(s, "'", "''") } @@ -478,7 +478,7 @@ func (p *PostgresDB) GetForeignKeys(dbName, tableName string) ([]connection.Fore } table := strings.TrimSpace(tableName) if table == "" { - return nil, fmt.Errorf("table name required") + return nil, fmt.Errorf("表名不能为空") } esc := func(s string) string { return strings.ReplaceAll(s, "'", "''") } @@ -538,7 +538,7 @@ func (p *PostgresDB) GetTriggers(dbName, tableName string) ([]connection.Trigger } table := strings.TrimSpace(tableName) if table == "" { - return nil, fmt.Errorf("table name required") + return nil, fmt.Errorf("表名不能为空") } esc := func(s string) string { return strings.ReplaceAll(s, "'", "''") } @@ -602,7 +602,7 @@ ORDER BY table_schema, table_name, ordinal_position` func (p *PostgresDB) ApplyChanges(tableName string, changes connection.ChangeSet) error { if p.conn == nil { - return fmt.Errorf("connection not open") + return fmt.Errorf("连接未打开") } tx, err := p.conn.Begin() @@ -650,7 +650,7 @@ func (p *PostgresDB) ApplyChanges(tableName string, changes connection.ChangeSet } query := fmt.Sprintf("DELETE FROM %s WHERE %s", qualifiedTable, strings.Join(wheres, " AND ")) if _, err := tx.Exec(query, args...); err != nil { - return fmt.Errorf("delete error: %v", err) + return fmt.Errorf("删除失败:%v", err) } } @@ -678,12 +678,12 @@ func (p *PostgresDB) ApplyChanges(tableName string, changes connection.ChangeSet } if len(wheres) == 0 { - return fmt.Errorf("update requires keys") + return fmt.Errorf("更新操作需要主键条件") } query := fmt.Sprintf("UPDATE %s SET %s WHERE %s", qualifiedTable, strings.Join(sets, ", "), strings.Join(wheres, " AND ")) if _, err := tx.Exec(query, args...); err != nil { - return fmt.Errorf("update error: %v", err) + return fmt.Errorf("更新失败:%v", err) } } @@ -707,7 +707,7 @@ func (p *PostgresDB) ApplyChanges(tableName string, changes connection.ChangeSet query := fmt.Sprintf("INSERT INTO %s (%s) VALUES (%s)", qualifiedTable, strings.Join(cols, ", "), strings.Join(placeholders, ", ")) if _, err := tx.Exec(query, args...); err != nil { - return fmt.Errorf("insert error: %v", err) + return fmt.Errorf("插入失败:%v", err) } } diff --git a/internal/db/sqlite_impl.go b/internal/db/sqlite_impl.go index e8e8c06..4f76866 100644 --- a/internal/db/sqlite_impl.go +++ b/internal/db/sqlite_impl.go @@ -184,7 +184,7 @@ func (s *SQLiteDB) Close() error { func (s *SQLiteDB) Ping() error { if s.conn == nil { - return fmt.Errorf("connection not open") + return fmt.Errorf("连接未打开") } timeout := s.pingTimeout if timeout <= 0 { @@ -197,7 +197,7 @@ func (s *SQLiteDB) Ping() error { func (s *SQLiteDB) QueryContext(ctx context.Context, query string) ([]map[string]interface{}, []string, error) { if s.conn == nil { - return nil, nil, fmt.Errorf("connection not open") + return nil, nil, fmt.Errorf("连接未打开") } rows, err := s.conn.QueryContext(ctx, query) @@ -211,7 +211,7 @@ func (s *SQLiteDB) QueryContext(ctx context.Context, query string) ([]map[string func (s *SQLiteDB) Query(query string) ([]map[string]interface{}, []string, error) { if s.conn == nil { - return nil, nil, fmt.Errorf("connection not open") + return nil, nil, fmt.Errorf("连接未打开") } rows, err := s.conn.Query(query) @@ -224,7 +224,7 @@ func (s *SQLiteDB) Query(query string) ([]map[string]interface{}, []string, erro func (s *SQLiteDB) ExecContext(ctx context.Context, query string) (int64, error) { if s.conn == nil { - return 0, fmt.Errorf("connection not open") + return 0, fmt.Errorf("连接未打开") } res, err := s.conn.ExecContext(ctx, query) if err != nil { @@ -235,7 +235,7 @@ func (s *SQLiteDB) ExecContext(ctx context.Context, query string) (int64, error) func (s *SQLiteDB) Exec(query string) (int64, error) { if s.conn == nil { - return 0, fmt.Errorf("connection not open") + return 0, fmt.Errorf("连接未打开") } res, err := s.conn.Exec(query) if err != nil { @@ -275,13 +275,13 @@ func (s *SQLiteDB) GetCreateStatement(dbName, tableName string) (string, error) return fmt.Sprintf("%v", val), nil } } - return "", fmt.Errorf("create statement not found") + return "", fmt.Errorf("未找到建表语句") } func (s *SQLiteDB) GetColumns(dbName, tableName string) ([]connection.ColumnDefinition, error) { table := strings.TrimSpace(tableName) if table == "" { - return nil, fmt.Errorf("table name required") + return nil, fmt.Errorf("表名不能为空") } esc := func(v string) string { return strings.ReplaceAll(v, "'", "''") } @@ -372,7 +372,7 @@ func (s *SQLiteDB) GetColumns(dbName, tableName string) ([]connection.ColumnDefi func (s *SQLiteDB) GetIndexes(dbName, tableName string) ([]connection.IndexDefinition, error) { table := strings.TrimSpace(tableName) if table == "" { - return nil, fmt.Errorf("table name required") + return nil, fmt.Errorf("表名不能为空") } esc := func(v string) string { return strings.ReplaceAll(v, "'", "''") } @@ -463,7 +463,7 @@ func (s *SQLiteDB) GetIndexes(dbName, tableName string) ([]connection.IndexDefin func (s *SQLiteDB) GetForeignKeys(dbName, tableName string) ([]connection.ForeignKeyDefinition, error) { table := strings.TrimSpace(tableName) if table == "" { - return nil, fmt.Errorf("table name required") + return nil, fmt.Errorf("表名不能为空") } esc := func(v string) string { return strings.ReplaceAll(v, "'", "''") } @@ -537,7 +537,7 @@ func (s *SQLiteDB) GetForeignKeys(dbName, tableName string) ([]connection.Foreig func (s *SQLiteDB) GetTriggers(dbName, tableName string) ([]connection.TriggerDefinition, error) { table := strings.TrimSpace(tableName) if table == "" { - return nil, fmt.Errorf("table name required") + return nil, fmt.Errorf("表名不能为空") } esc := func(v string) string { return strings.ReplaceAll(v, "'", "''") } @@ -588,7 +588,7 @@ func (s *SQLiteDB) GetTriggers(dbName, tableName string) ([]connection.TriggerDe func (s *SQLiteDB) ApplyChanges(tableName string, changes connection.ChangeSet) error { if s.conn == nil { - return fmt.Errorf("connection not open") + return fmt.Errorf("连接未打开") } tx, err := s.conn.Begin() @@ -634,7 +634,7 @@ func (s *SQLiteDB) ApplyChanges(tableName string, changes connection.ChangeSet) } query := fmt.Sprintf("DELETE FROM %s WHERE %s", qualifiedTable, strings.Join(wheres, " AND ")) if _, err := tx.Exec(query, args...); err != nil { - return fmt.Errorf("delete error: %v", err) + return fmt.Errorf("删除失败:%v", err) } } @@ -659,12 +659,12 @@ func (s *SQLiteDB) ApplyChanges(tableName string, changes connection.ChangeSet) } if len(wheres) == 0 { - return fmt.Errorf("update requires keys") + return fmt.Errorf("更新操作需要主键条件") } query := fmt.Sprintf("UPDATE %s SET %s WHERE %s", qualifiedTable, strings.Join(sets, ", "), strings.Join(wheres, " AND ")) if _, err := tx.Exec(query, args...); err != nil { - return fmt.Errorf("update error: %v", err) + return fmt.Errorf("更新失败:%v", err) } } @@ -686,7 +686,7 @@ func (s *SQLiteDB) ApplyChanges(tableName string, changes connection.ChangeSet) query := fmt.Sprintf("INSERT INTO %s (%s) VALUES (%s)", qualifiedTable, strings.Join(cols, ", "), strings.Join(placeholders, ", ")) if _, err := tx.Exec(query, args...); err != nil { - return fmt.Errorf("insert error: %v", err) + return fmt.Errorf("插入失败:%v", err) } } diff --git a/internal/db/sqlserver_impl.go b/internal/db/sqlserver_impl.go index a0458ab..f781496 100644 --- a/internal/db/sqlserver_impl.go +++ b/internal/db/sqlserver_impl.go @@ -117,7 +117,7 @@ func (s *SqlServerDB) Close() error { func (s *SqlServerDB) Ping() error { if s.conn == nil { - return fmt.Errorf("connection not open") + return fmt.Errorf("连接未打开") } timeout := s.pingTimeout if timeout <= 0 { @@ -130,7 +130,7 @@ func (s *SqlServerDB) Ping() error { func (s *SqlServerDB) QueryContext(ctx context.Context, query string) ([]map[string]interface{}, []string, error) { if s.conn == nil { - return nil, nil, fmt.Errorf("connection not open") + return nil, nil, fmt.Errorf("连接未打开") } rows, err := s.conn.QueryContext(ctx, query) @@ -144,7 +144,7 @@ func (s *SqlServerDB) QueryContext(ctx context.Context, query string) ([]map[str func (s *SqlServerDB) Query(query string) ([]map[string]interface{}, []string, error) { if s.conn == nil { - return nil, nil, fmt.Errorf("connection not open") + return nil, nil, fmt.Errorf("连接未打开") } rows, err := s.conn.Query(query) @@ -157,7 +157,7 @@ func (s *SqlServerDB) Query(query string) ([]map[string]interface{}, []string, e func (s *SqlServerDB) ExecContext(ctx context.Context, query string) (int64, error) { if s.conn == nil { - return 0, fmt.Errorf("connection not open") + return 0, fmt.Errorf("连接未打开") } res, err := s.conn.ExecContext(ctx, query) if err != nil { @@ -168,7 +168,7 @@ func (s *SqlServerDB) ExecContext(ctx context.Context, query string) (int64, err func (s *SqlServerDB) Exec(query string) (int64, error) { if s.conn == nil { - return 0, fmt.Errorf("connection not open") + return 0, fmt.Errorf("连接未打开") } res, err := s.conn.Exec(query) if err != nil { @@ -236,7 +236,7 @@ func (s *SqlServerDB) GetColumns(dbName, tableName string) ([]connection.ColumnD } if table == "" { - return nil, fmt.Errorf("table name required") + return nil, fmt.Errorf("表名不能为空") } esc := func(s string) string { return strings.ReplaceAll(s, "'", "''") } @@ -344,7 +344,7 @@ func (s *SqlServerDB) GetIndexes(dbName, tableName string) ([]connection.IndexDe } if table == "" { - return nil, fmt.Errorf("table name required") + return nil, fmt.Errorf("表名不能为空") } esc := func(s string) string { return strings.ReplaceAll(s, "'", "''") } @@ -425,7 +425,7 @@ func (s *SqlServerDB) GetForeignKeys(dbName, tableName string) ([]connection.For } if table == "" { - return nil, fmt.Errorf("table name required") + return nil, fmt.Errorf("表名不能为空") } esc := func(s string) string { return strings.ReplaceAll(s, "'", "''") } @@ -483,7 +483,7 @@ func (s *SqlServerDB) GetTriggers(dbName, tableName string) ([]connection.Trigge } if table == "" { - return nil, fmt.Errorf("table name required") + return nil, fmt.Errorf("表名不能为空") } esc := func(s string) string { return strings.ReplaceAll(s, "'", "''") } @@ -530,7 +530,7 @@ ORDER BY tr.name`, func (s *SqlServerDB) ApplyChanges(tableName string, changes connection.ChangeSet) error { if s.conn == nil { - return fmt.Errorf("connection not open") + return fmt.Errorf("连接未打开") } tx, err := s.conn.Begin() @@ -573,7 +573,7 @@ func (s *SqlServerDB) ApplyChanges(tableName string, changes connection.ChangeSe } query := fmt.Sprintf("DELETE FROM %s WHERE %s", qualifiedTable, strings.Join(wheres, " AND ")) if _, err := tx.Exec(query, args...); err != nil { - return fmt.Errorf("delete error: %v", err) + return fmt.Errorf("删除失败:%v", err) } } @@ -601,12 +601,12 @@ func (s *SqlServerDB) ApplyChanges(tableName string, changes connection.ChangeSe } if len(wheres) == 0 { - return fmt.Errorf("update requires keys") + return fmt.Errorf("更新操作需要主键条件") } query := fmt.Sprintf("UPDATE %s SET %s WHERE %s", qualifiedTable, strings.Join(sets, ", "), strings.Join(wheres, " AND ")) if _, err := tx.Exec(query, args...); err != nil { - return fmt.Errorf("update error: %v", err) + return fmt.Errorf("更新失败:%v", err) } } @@ -630,7 +630,7 @@ func (s *SqlServerDB) ApplyChanges(tableName string, changes connection.ChangeSe query := fmt.Sprintf("INSERT INTO %s (%s) VALUES (%s)", qualifiedTable, strings.Join(cols, ", "), strings.Join(placeholders, ", ")) if _, err := tx.Exec(query, args...); err != nil { - return fmt.Errorf("insert error: %v", err) + return fmt.Errorf("插入失败:%v", err) } } diff --git a/internal/db/tdengine_impl.go b/internal/db/tdengine_impl.go index 7efcf92..2812261 100644 --- a/internal/db/tdengine_impl.go +++ b/internal/db/tdengine_impl.go @@ -120,7 +120,7 @@ func (t *TDengineDB) Close() error { func (t *TDengineDB) Ping() error { if t.conn == nil { - return fmt.Errorf("connection not open") + return fmt.Errorf("连接未打开") } timeout := t.pingTimeout if timeout <= 0 { @@ -133,7 +133,7 @@ func (t *TDengineDB) Ping() error { func (t *TDengineDB) QueryContext(ctx context.Context, query string) ([]map[string]interface{}, []string, error) { if t.conn == nil { - return nil, nil, fmt.Errorf("connection not open") + return nil, nil, fmt.Errorf("连接未打开") } rows, err := t.conn.QueryContext(ctx, query) @@ -147,7 +147,7 @@ func (t *TDengineDB) QueryContext(ctx context.Context, query string) ([]map[stri func (t *TDengineDB) Query(query string) ([]map[string]interface{}, []string, error) { if t.conn == nil { - return nil, nil, fmt.Errorf("connection not open") + return nil, nil, fmt.Errorf("连接未打开") } rows, err := t.conn.Query(query) @@ -161,7 +161,7 @@ func (t *TDengineDB) Query(query string) ([]map[string]interface{}, []string, er func (t *TDengineDB) ExecContext(ctx context.Context, query string) (int64, error) { if t.conn == nil { - return 0, fmt.Errorf("connection not open") + return 0, fmt.Errorf("连接未打开") } res, err := t.conn.ExecContext(ctx, query) if err != nil { @@ -172,7 +172,7 @@ func (t *TDengineDB) ExecContext(ctx context.Context, query string) (int64, erro func (t *TDengineDB) Exec(query string) (int64, error) { if t.conn == nil { - return 0, fmt.Errorf("connection not open") + return 0, fmt.Errorf("连接未打开") } res, err := t.conn.Exec(query) if err != nil { @@ -274,7 +274,7 @@ func (t *TDengineDB) GetCreateStatement(dbName, tableName string) (string, error if lastErr != nil { return "", lastErr } - return "", fmt.Errorf("create statement not found") + return "", fmt.Errorf("未找到建表语句") } func (t *TDengineDB) GetColumns(dbName, tableName string) ([]connection.ColumnDefinition, error) { @@ -325,7 +325,7 @@ func (t *TDengineDB) GetColumns(dbName, tableName string) ([]connection.ColumnDe func (t *TDengineDB) GetAllColumns(dbName string) ([]connection.ColumnDefinitionWithTable, error) { if strings.TrimSpace(dbName) == "" { - return nil, fmt.Errorf("database name required for GetAllColumns") + return nil, fmt.Errorf("获取全部列信息需要指定数据库名称") } tables, err := t.GetTables(dbName) @@ -365,10 +365,10 @@ func (t *TDengineDB) GetTriggers(dbName, tableName string) ([]connection.Trigger func (t *TDengineDB) ApplyChanges(tableName string, changes connection.ChangeSet) error { if t.conn == nil { - return fmt.Errorf("connection not open") + return fmt.Errorf("连接未打开") } if strings.TrimSpace(tableName) == "" { - return fmt.Errorf("table name required") + return fmt.Errorf("表名不能为空") } if len(changes.Updates) > 0 || len(changes.Deletes) > 0 { return fmt.Errorf("TDengine 目标端当前仅支持 INSERT 写入,暂不支持 UPDATE/DELETE 差异同步,请改用仅插入或全量覆盖模式") @@ -384,7 +384,7 @@ func (t *TDengineDB) ApplyChanges(tableName string, changes connection.ChangeSet continue } if _, err := t.conn.Exec(query); err != nil { - return fmt.Errorf("insert error: %v; sql=%s", err, query) + return fmt.Errorf("插入失败:%v; sql=%s", err, query) } } return nil @@ -392,7 +392,7 @@ func (t *TDengineDB) ApplyChanges(tableName string, changes connection.ChangeSet func buildTDengineInsertSQL(qualifiedTable string, row map[string]interface{}) (string, error) { if strings.TrimSpace(qualifiedTable) == "" { - return "", fmt.Errorf("qualified table required") + return "", fmt.Errorf("需要指定完整的表名") } if len(row) == 0 { return "", nil diff --git a/internal/db/vastbase_impl.go b/internal/db/vastbase_impl.go index 971a171..8c6a4ed 100644 --- a/internal/db/vastbase_impl.go +++ b/internal/db/vastbase_impl.go @@ -124,7 +124,7 @@ func (v *VastbaseDB) Close() error { func (v *VastbaseDB) Ping() error { if v.conn == nil { - return fmt.Errorf("connection not open") + return fmt.Errorf("连接未打开") } timeout := v.pingTimeout if timeout <= 0 { @@ -137,7 +137,7 @@ func (v *VastbaseDB) Ping() error { func (v *VastbaseDB) QueryContext(ctx context.Context, query string) ([]map[string]interface{}, []string, error) { if v.conn == nil { - return nil, nil, fmt.Errorf("connection not open") + return nil, nil, fmt.Errorf("连接未打开") } rows, err := v.conn.QueryContext(ctx, query) @@ -151,7 +151,7 @@ func (v *VastbaseDB) QueryContext(ctx context.Context, query string) ([]map[stri func (v *VastbaseDB) Query(query string) ([]map[string]interface{}, []string, error) { if v.conn == nil { - return nil, nil, fmt.Errorf("connection not open") + return nil, nil, fmt.Errorf("连接未打开") } rows, err := v.conn.Query(query) @@ -164,7 +164,7 @@ func (v *VastbaseDB) Query(query string) ([]map[string]interface{}, []string, er func (v *VastbaseDB) ExecContext(ctx context.Context, query string) (int64, error) { if v.conn == nil { - return 0, fmt.Errorf("connection not open") + return 0, fmt.Errorf("连接未打开") } res, err := v.conn.ExecContext(ctx, query) if err != nil { @@ -175,7 +175,7 @@ func (v *VastbaseDB) ExecContext(ctx context.Context, query string) (int64, erro func (v *VastbaseDB) Exec(query string) (int64, error) { if v.conn == nil { - return 0, fmt.Errorf("connection not open") + return 0, fmt.Errorf("连接未打开") } res, err := v.conn.Exec(query) if err != nil { @@ -231,7 +231,7 @@ func (v *VastbaseDB) GetColumns(dbName, tableName string) ([]connection.ColumnDe } table := strings.TrimSpace(tableName) if table == "" { - return nil, fmt.Errorf("table name required") + return nil, fmt.Errorf("表名不能为空") } esc := func(s string) string { return strings.ReplaceAll(s, "'", "''") } @@ -301,7 +301,7 @@ func (v *VastbaseDB) GetIndexes(dbName, tableName string) ([]connection.IndexDef } table := strings.TrimSpace(tableName) if table == "" { - return nil, fmt.Errorf("table name required") + return nil, fmt.Errorf("表名不能为空") } esc := func(s string) string { return strings.ReplaceAll(s, "'", "''") } @@ -406,7 +406,7 @@ func (v *VastbaseDB) GetForeignKeys(dbName, tableName string) ([]connection.Fore } table := strings.TrimSpace(tableName) if table == "" { - return nil, fmt.Errorf("table name required") + return nil, fmt.Errorf("表名不能为空") } esc := func(s string) string { return strings.ReplaceAll(s, "'", "''") } @@ -466,7 +466,7 @@ func (v *VastbaseDB) GetTriggers(dbName, tableName string) ([]connection.Trigger } table := strings.TrimSpace(tableName) if table == "" { - return nil, fmt.Errorf("table name required") + return nil, fmt.Errorf("表名不能为空") } esc := func(s string) string { return strings.ReplaceAll(s, "'", "''") } @@ -530,7 +530,7 @@ ORDER BY table_schema, table_name, ordinal_position` func (v *VastbaseDB) ApplyChanges(tableName string, changes connection.ChangeSet) error { if v.conn == nil { - return fmt.Errorf("connection not open") + return fmt.Errorf("连接未打开") } tx, err := v.conn.Begin() @@ -578,7 +578,7 @@ func (v *VastbaseDB) ApplyChanges(tableName string, changes connection.ChangeSet } query := fmt.Sprintf("DELETE FROM %s WHERE %s", qualifiedTable, strings.Join(wheres, " AND ")) if _, err := tx.Exec(query, args...); err != nil { - return fmt.Errorf("delete error: %v", err) + return fmt.Errorf("删除失败:%v", err) } } @@ -606,12 +606,12 @@ func (v *VastbaseDB) ApplyChanges(tableName string, changes connection.ChangeSet } if len(wheres) == 0 { - return fmt.Errorf("update requires keys") + return fmt.Errorf("更新操作需要主键条件") } query := fmt.Sprintf("UPDATE %s SET %s WHERE %s", qualifiedTable, strings.Join(sets, ", "), strings.Join(wheres, " AND ")) if _, err := tx.Exec(query, args...); err != nil { - return fmt.Errorf("update error: %v", err) + return fmt.Errorf("更新失败:%v", err) } } @@ -635,7 +635,7 @@ func (v *VastbaseDB) ApplyChanges(tableName string, changes connection.ChangeSet query := fmt.Sprintf("INSERT INTO %s (%s) VALUES (%s)", qualifiedTable, strings.Join(cols, ", "), strings.Join(placeholders, ", ")) if _, err := tx.Exec(query, args...); err != nil { - return fmt.Errorf("insert error: %v", err) + return fmt.Errorf("插入失败:%v", err) } } diff --git a/internal/logger/logger_test.go b/internal/logger/logger_test.go new file mode 100644 index 0000000..01d5b26 --- /dev/null +++ b/internal/logger/logger_test.go @@ -0,0 +1,65 @@ +package logger + +import ( + "errors" + "fmt" + "testing" +) + +func TestErrorChain_NilError(t *testing.T) { + if got := ErrorChain(nil); got != "" { + t.Errorf("ErrorChain(nil) = %q; want empty string", got) + } +} + +func TestErrorChain_SingleError(t *testing.T) { + err := errors.New("single error") + got := ErrorChain(err) + if got != "single error" { + t.Errorf("ErrorChain(single) = %q; want %q", got, "single error") + } +} + +func TestErrorChain_WrappedErrors(t *testing.T) { + inner := errors.New("root cause") + middle := fmt.Errorf("middle: %w", inner) + outer := fmt.Errorf("outer: %w", middle) + + got := ErrorChain(outer) + // Should contain all three distinct messages + if got == "" { + t.Fatal("ErrorChain returned empty string for wrapped errors") + } + // The chain should start with the outermost error + if len(got) < len("outer:") { + t.Errorf("ErrorChain result too short: %q", got) + } +} + +func TestErrorChain_DeduplicatesMessages(t *testing.T) { + // Create a chain where wrapping doesn't add new text + inner := errors.New("same message") + outer := fmt.Errorf("%w", inner) + + got := ErrorChain(outer) + // Should not repeat "same message" + if got != "same message" { + t.Errorf("ErrorChain should deduplicate: got %q", got) + } +} + +func TestErrorChain_TruncatesLongChain(t *testing.T) { + // Build a chain of 25 errors (exceeds the 20-level limit) + var err error = errors.New("base") + for i := 0; i < 25; i++ { + err = fmt.Errorf("level-%d: %w", i, err) + } + got := ErrorChain(err) + if got == "" { + t.Fatal("ErrorChain returned empty for long chain") + } + // Should contain truncation notice + if len(got) == 0 { + t.Error("expected non-empty result for long chain") + } +}