From 4cc8ab64824cef24faba6c1390fdb993f6207399 Mon Sep 17 00:00:00 2001 From: Syngnat Date: Fri, 12 Jun 2026 12:57:47 +0800 Subject: [PATCH] =?UTF-8?q?=F0=9F=90=9B=20fix(driver-manager/sql-editor):?= =?UTF-8?q?=20=E4=BC=98=E5=8C=96=E9=A9=B1=E5=8A=A8=E4=BB=A3=E7=90=86?= =?UTF-8?q?=E6=9B=B4=E6=96=B0=E6=8F=90=E7=A4=BA=E5=92=8C=E4=BA=8B=E5=8A=A1?= =?UTF-8?q?=E6=8F=90=E4=BA=A4=E6=8E=A7=E4=BB=B6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 调整 driver-agent revision 为提示性校验,允许旧代理继续安装使用并保留需重装提示 - 精简 SQL 编辑器 DML 事务模式与自动提交档位展示 - 补充旧 revision 安装回归和事务控件断言 --- .../QueryEditor.external-sql-save.test.tsx | 14 +-- .../QueryEditorTransactionSettings.tsx | 16 ++-- frontend/src/v2-theme.css | 18 ++-- internal/app/methods_driver.go | 80 +++++++++-------- internal/app/methods_driver_version_test.go | 86 +++++++------------ 5 files changed, 104 insertions(+), 110 deletions(-) diff --git a/frontend/src/components/QueryEditor.external-sql-save.test.tsx b/frontend/src/components/QueryEditor.external-sql-save.test.tsx index f10d11f..8c9f739 100644 --- a/frontend/src/components/QueryEditor.external-sql-save.test.tsx +++ b/frontend/src/components/QueryEditor.external-sql-save.test.tsx @@ -3727,9 +3727,12 @@ describe('QueryEditor external SQL save', () => { expect(transactionSettingsSource).toContain('gn-v2-query-toolbar-transaction-mode-select'); expect(transactionSettingsSource).toContain('gn-v2-query-toolbar-transaction-delay-select'); expect(transactionSettingsSource).toContain('参考 DBeaver'); - expect(transactionSettingsSource).toContain("label: '手动提交'"); - expect(transactionSettingsSource).toContain("label: '自动提交'"); + expect(transactionSettingsSource).toContain("label: '手动'"); + expect(transactionSettingsSource).toContain("label: '自动'"); + expect(transactionSettingsSource).not.toContain("label: '手动提交'"); + expect(transactionSettingsSource).not.toContain("label: '自动提交'"); expect(transactionSettingsSource).toContain("label: '立即'"); + expect(transactionSettingsSource).toContain("label: '3s'"); expect(source).toContain('QueryEditorTransactionToolbar'); expect(transactionToolbarSource).toContain("className={isV2Ui ? 'gn-v2-query-transaction-toolbar' : undefined}"); expect(transactionToolbarSource).toContain("'未提交'"); @@ -3738,12 +3741,15 @@ describe('QueryEditor external SQL save', () => { expect(transactionToolbarSource).toContain("'自动提交中'"); expect(transactionToolbarSource).toContain('onFinish'); expect(toolbarSource).toContain('gn-v2-query-toolbar-action-group'); - expect(transactionSettingsSource).toContain('style={isV2Ui ? undefined : { width: 118 }}'); + expect(transactionSettingsSource).toContain('style={isV2Ui ? undefined : { width: 78 }}'); + expect(transactionSettingsSource).toContain('style={isV2Ui ? undefined : { width: 68 }}'); expect(toolbarSource).toContain('style={isV2Ui ? undefined : { width: 200 }}'); expect(toolbarSource).toContain('style={isV2Ui ? undefined : { width: 170 }}'); expect(css).toContain('body[data-ui-version="v2"] .gn-v2-query-toolbar-selects'); expect(css).toContain('body[data-ui-version="v2"] .gn-v2-query-toolbar-actions'); + expect(css).toContain('width: 74px !important;'); + expect(css).toContain('width: 62px !important;'); expect(css).toContain('flex: 0 1 auto !important;'); expect(css).toContain('justify-content: flex-start;'); expect(css).toContain('height: 32px !important;'); @@ -3755,8 +3761,6 @@ describe('QueryEditor external SQL save', () => { expect(css).toContain('width: 140px !important;'); expect(css).toContain('width: 166px !important;'); expect(css).toContain('width: 132px !important;'); - expect(css).toContain('width: 118px !important;'); - expect(css).toContain('width: 82px !important;'); expect(css).toContain('width: 34px !important;'); expect(css).toContain('@media (max-width: 900px)'); diff --git a/frontend/src/components/QueryEditorTransactionSettings.tsx b/frontend/src/components/QueryEditorTransactionSettings.tsx index b88582b..d9b59c8 100644 --- a/frontend/src/components/QueryEditorTransactionSettings.tsx +++ b/frontend/src/components/QueryEditorTransactionSettings.tsx @@ -5,10 +5,10 @@ export type SqlEditorCommitMode = 'manual' | 'auto'; export const SQL_EDITOR_AUTO_COMMIT_DELAY_OPTIONS = [ { value: 0, label: '立即' }, - { value: 3000, label: '3 秒后' }, - { value: 5000, label: '5 秒后' }, - { value: 10000, label: '10 秒后' }, - { value: 30000, label: '30 秒后' }, + { value: 3000, label: '3s' }, + { value: 5000, label: '5s' }, + { value: 10000, label: '10s' }, + { value: 30000, label: '30s' }, ]; type QueryEditorTransactionSettingsProps = { @@ -30,19 +30,19 @@ const QueryEditorTransactionSettings: React.FC onAutoCommitDelayMsChange(Number(delayMs))} options={SQL_EDITOR_AUTO_COMMIT_DELAY_OPTIONS} diff --git a/frontend/src/v2-theme.css b/frontend/src/v2-theme.css index d829fd3..15591bc 100644 --- a/frontend/src/v2-theme.css +++ b/frontend/src/v2-theme.css @@ -4840,13 +4840,13 @@ body[data-ui-version="v2"] .gn-v2-query-toolbar-max-rows-select { } body[data-ui-version="v2"] .gn-v2-query-toolbar-transaction-mode-select { - width: 118px !important; - flex: 0 0 118px !important; + width: 74px !important; + flex: 0 0 74px !important; } body[data-ui-version="v2"] .gn-v2-query-toolbar-transaction-delay-select { - width: 82px !important; - flex: 0 0 82px !important; + width: 62px !important; + flex: 0 0 62px !important; } body[data-ui-version="v2"] .gn-v2-query-toolbar .ant-select-selector { @@ -4891,15 +4891,19 @@ body[data-ui-version="v2"] .gn-v2-query-transaction-commit-button { display: inline-flex !important; align-items: center; gap: 6px; + border-color: transparent !important; + background: var(--gn-accent-soft) !important; + color: var(--gn-accent-2) !important; + font-weight: 750 !important; } body[data-ui-version="v2"] .gn-v2-query-transaction-commit-button .gn-v2-toolbar-kbd { margin-left: 0; min-width: 18px; justify-content: center; - background: rgba(255, 255, 255, 0.18); - color: #fff; - border-color: rgba(255, 255, 255, 0.22); + border: none; + background: rgba(22, 163, 74, 0.18); + color: var(--gn-accent-2); } body[data-ui-version="v2"] .gn-v2-query-toolbar-icon-action.ant-btn, diff --git a/internal/app/methods_driver.go b/internal/app/methods_driver.go index 64d01a1..fc9a33f 100644 --- a/internal/app/methods_driver.go +++ b/internal/app/methods_driver.go @@ -2868,6 +2868,33 @@ func verifyInstalledOptionalDriverAgentRevision(driverType string, executablePat return actual, nil } +func observeInstalledOptionalDriverAgentRevision(driverType string, executablePath string, selectedVersion string) string { + if !shouldVerifyOptionalDriverAgentRevision(driverType, selectedVersion) { + return "" + } + expected := strings.TrimSpace(db.OptionalDriverAgentRevision(driverType)) + actual, current, err := optionalDriverAgentRevisionCurrent(driverType, executablePath) + if expected == "" { + return strings.TrimSpace(actual) + } + displayName := resolveDriverDisplayName(driverDefinition{Type: driverType}) + if err != nil { + logger.Warnf("%s 驱动代理版本元数据不可用,已保留安装:path=%s version=%s err=%v;建议在驱动管理中重装", + displayName, executablePath, normalizeVersion(selectedVersion), err) + return "" + } + actual = strings.TrimSpace(actual) + if !current { + actualLabel := actual + if actualLabel == "" { + actualLabel = "空" + } + logger.Warnf("%s 驱动代理 revision 不匹配,已保留安装:已安装=%s 当前需要=%s path=%s version=%s;建议在驱动管理中重装", + displayName, actualLabel, expected, executablePath, normalizeVersion(selectedVersion)) + } + return actual +} + func shouldVerifyOptionalDriverAgentRevision(driverType string, selectedVersion string) bool { switch normalizeDriverType(driverType) { case "mongodb": @@ -2963,10 +2990,7 @@ func installOptionalDriverAgentPackage(a *App, definition driverDefinition, sele if strings.TrimSpace(downloadSource) == "" { downloadSource = strings.TrimSpace(downloadURL) } - agentRevision, revisionErr := verifyInstalledOptionalDriverAgentRevision(driverType, runtimePath, selectedVersion) - if revisionErr != nil { - return installedDriverPackage{}, revisionErr - } + agentRevision := observeInstalledOptionalDriverAgentRevision(driverType, runtimePath, selectedVersion) return installedDriverPackage{ DriverType: driverType, Version: strings.TrimSpace(selectedVersion), @@ -3039,10 +3063,7 @@ func installOptionalDriverAgentFromLocalPath(definition driverDefinition, filePa return installedDriverPackage{}, validateErr } - agentRevision, revisionErr := verifyInstalledOptionalDriverAgentRevision(driverType, executablePath, selectedVersion) - if revisionErr != nil { - return installedDriverPackage{}, revisionErr - } + agentRevision := observeInstalledOptionalDriverAgentRevision(driverType, executablePath, selectedVersion) hash, hashErr := hashFileSHA256(executablePath) if hashErr != nil { return installedDriverPackage{}, fmt.Errorf("计算 %s 驱动代理摘要失败:%w", displayName, hashErr) @@ -3403,15 +3424,8 @@ func ensureOptionalDriverAgentBinary(a *App, definition driverDefinition, execut if a != nil { a.emitDriverDownloadProgress(driverType, "downloading", 10, 100, planMessage) } - validateInstalledCandidateRevision := func() error { - if _, revisionErr := verifyInstalledOptionalDriverAgentRevision(driverType, executablePath, selectedVersion); revisionErr != nil { - _ = os.Remove(executablePath) - for _, supportName := range optionalDriverSupportFileNames(driverType) { - _ = os.Remove(filepath.Join(filepath.Dir(executablePath), supportName)) - } - return revisionErr - } - return nil + observeInstalledCandidateRevision := func() { + observeInstalledOptionalDriverAgentRevision(driverType, executablePath, selectedVersion) } if !skipReuseCandidate { if sourcePath, ok := findExistingOptionalDriverAgentCandidate(definition, executablePath); ok { @@ -3426,9 +3440,7 @@ func ensureOptionalDriverAgentBinary(a *App, definition driverDefinition, execut if hashErr != nil { return "", "", fmt.Errorf("计算预置 %s 驱动代理摘要失败:%w", displayName, hashErr) } - if revisionErr := validateInstalledCandidateRevision(); revisionErr != nil { - return "", "", fmt.Errorf("预置 %s 驱动代理 revision 校验失败(来源:%s):%w", displayName, sourcePath, revisionErr) - } + observeInstalledCandidateRevision() return "file://" + sourcePath, hash, nil } } @@ -3463,11 +3475,7 @@ func ensureOptionalDriverAgentBinary(a *App, definition driverDefinition, execut } hash, dlErr := downloadOptionalDriverAgentBinary(a, definition, candidateURL, executablePath) if dlErr == nil { - if revisionErr := validateInstalledCandidateRevision(); revisionErr != nil { - logger.Warnf("预编译 %s 驱动代理 revision 校验失败,url=%s err=%v", displayName, candidateURL, revisionErr) - downloadErrs = appendOptionalDriverAttemptError(downloadErrs, candidateURL, revisionErr) - continue - } + observeInstalledCandidateRevision() return candidateURL, hash, nil } logger.Warnf("下载预编译 %s 驱动代理失败,url=%s err=%v", displayName, candidateURL, dlErr) @@ -3486,11 +3494,7 @@ func ensureOptionalDriverAgentBinary(a *App, definition driverDefinition, execut } source, hash, bundleErr := downloadOptionalDriverAgentFromBundle(a, definition, bundleURL, executablePath) if bundleErr == nil { - if revisionErr := validateInstalledCandidateRevision(); revisionErr != nil { - logger.Warnf("驱动总包 %s 代理 revision 校验失败,source=%s err=%v", displayName, source, revisionErr) - downloadErrs = appendOptionalDriverAttemptError(downloadErrs, source, revisionErr) - continue - } + observeInstalledCandidateRevision() return source, hash, nil } logger.Warnf("从驱动总包提取 %s 驱动代理失败,url=%s err=%v", displayName, bundleURL, bundleErr) @@ -4922,7 +4926,7 @@ func findExistingOptionalDriverAgentCandidate(definition driverDefinition, targe if validateErr := validateOptionalDriverAgentExecutableFunc(driverType, absPath); validateErr != nil { continue } - if !isReusableOptionalDriverAgentRevisionCurrent(driverType, absPath) { + if !isReusableOptionalDriverAgentCandidateRevisionAcceptable(driverType, absPath) { continue } return absPath, true @@ -4930,7 +4934,7 @@ func findExistingOptionalDriverAgentCandidate(definition driverDefinition, targe return "", false } -func isReusableOptionalDriverAgentRevisionCurrent(driverType string, executablePath string) bool { +func isReusableOptionalDriverAgentCandidateRevisionAcceptable(driverType string, executablePath string) bool { expected := strings.TrimSpace(db.OptionalDriverAgentRevision(driverType)) if expected == "" { return true @@ -4938,12 +4942,16 @@ func isReusableOptionalDriverAgentRevisionCurrent(driverType string, executableP actual, current, err := optionalDriverAgentRevisionCurrent(driverType, executablePath) displayName := resolveDriverDisplayName(driverDefinition{Type: driverType}) if err != nil { - logger.Warnf("跳过可复用 %s 驱动代理候选:版本元数据不可用 path=%s err=%v", displayName, executablePath, err) - return false + logger.Warnf("可复用 %s 驱动代理候选版本元数据不可用,仍允许安装:path=%s err=%v;建议在驱动管理中重装", displayName, executablePath, err) + return true } if !current { - logger.Warnf("跳过可复用 %s 驱动代理候选:revision 不匹配 path=%s actual=%s expected=%s", displayName, executablePath, strings.TrimSpace(actual), expected) - return false + actualLabel := strings.TrimSpace(actual) + if actualLabel == "" { + actualLabel = "空" + } + logger.Warnf("可复用 %s 驱动代理候选 revision 不匹配,仍允许安装:path=%s actual=%s expected=%s;建议在驱动管理中重装", displayName, executablePath, actualLabel, expected) + return true } return true } diff --git a/internal/app/methods_driver_version_test.go b/internal/app/methods_driver_version_test.go index 99a9e49..3ead1f6 100644 --- a/internal/app/methods_driver_version_test.go +++ b/internal/app/methods_driver_version_test.go @@ -538,6 +538,13 @@ func TestDownloadOptionalDriverAgentBinaryInstallsDuckDBDedicatedZip(t *testing. http.ServeFile(w, r, zipPath) })) defer server.Close() + proxySnapshot := currentGlobalProxyConfig() + if _, err := setGlobalProxyConfig(false, proxySnapshot.Proxy); err != nil { + t.Fatalf("disable global proxy failed: %v", err) + } + t.Cleanup(func() { + _, _ = setGlobalProxyConfig(proxySnapshot.Enabled, proxySnapshot.Proxy) + }) target := filepath.Join(tmpDir, "install", "duckdb-driver-agent.exe") if err := os.MkdirAll(filepath.Dir(target), 0o755); err != nil { @@ -916,12 +923,10 @@ func TestDownloadOptionalDriverAgentFromBundleSharesConcurrentDownload(t *testin } } -func TestEnsureOptionalDriverAgentBinaryFallsBackAfterStaleDownloadRevision(t *testing.T) { +func TestInstallOptionalDriverAgentPackageAcceptsStaleDownloadRevision(t *testing.T) { originalProbe := optionalDriverAgentMetadataProbe - originalGoBinaryLookPath := goBinaryLookPath t.Cleanup(func() { optionalDriverAgentMetadataProbe = originalProbe - goBinaryLookPath = originalGoBinaryLookPath }) tmpDir := t.TempDir() @@ -935,73 +940,46 @@ func TestEnsureOptionalDriverAgentBinaryFallsBackAfterStaleDownloadRevision(t *t http.ServeFile(w, r, staleAgent) })) defer staleServer.Close() - - projectRoot := filepath.Join(tmpDir, "project") - if err := os.MkdirAll(filepath.Join(projectRoot, "cmd", "optional-driver-agent"), 0o755); err != nil { - t.Fatalf("create project root failed: %v", err) - } - if err := os.WriteFile(filepath.Join(projectRoot, "go.mod"), []byte("module GoNavi-Wails\n"), 0o644); err != nil { - t.Fatalf("write go.mod failed: %v", err) - } - if err := os.WriteFile(filepath.Join(projectRoot, "cmd", "optional-driver-agent", "main.go"), []byte("package main\n"), 0o644); err != nil { - t.Fatalf("write optional agent main failed: %v", err) - } - wd, err := os.Getwd() - if err != nil { - t.Fatalf("getwd failed: %v", err) - } - if err := os.Chdir(projectRoot); err != nil { - t.Fatalf("chdir project root failed: %v", err) + proxySnapshot := currentGlobalProxyConfig() + if _, err := setGlobalProxyConfig(false, proxySnapshot.Proxy); err != nil { + t.Fatalf("disable global proxy failed: %v", err) } t.Cleanup(func() { - if err := os.Chdir(wd); err != nil { - t.Fatalf("restore cwd failed: %v", err) - } + _, _ = setGlobalProxyConfig(proxySnapshot.Enabled, proxySnapshot.Proxy) }) - goScript := filepath.Join(tmpDir, "fake-go") - if runtime.GOOS == "windows" { - goScript += ".bat" - } - if runtime.GOOS == "windows" { - if err := os.WriteFile(goScript, []byte("@echo off\r\nsetlocal\r\nset \"out=\"\r\n:loop\r\nif \"%~1\"==\"\" goto done\r\nif \"%~1\"==\"-o\" goto capture\r\nshift\r\ngoto loop\r\n:capture\r\nset \"out=%~2\"\r\nshift\r\nshift\r\ngoto loop\r\n:done\r\nif \"%out%\"==\"\" exit /b 1\r\ncopy /Y \"%GONAVI_TEST_BUILT_AGENT%\" \"%out%\" >nul\r\n"), 0o755); err != nil { - t.Fatalf("write fake go script failed: %v", err) - } - } else { - if err := os.WriteFile(goScript, []byte("#!/usr/bin/env sh\nout=\"\"\nwhile [ \"$#\" -gt 0 ]; do\n if [ \"$1\" = \"-o\" ]; then out=\"$2\"; shift 2; continue; fi\n shift\ndone\ncp \"$GONAVI_TEST_BUILT_AGENT\" \"$out\"\n"), 0o755); err != nil { - t.Fatalf("write fake go script failed: %v", err) - } - } - goBinaryLookPath = func(file string) (string, error) { - return goScript, nil - } - t.Setenv("GONAVI_TEST_BUILT_AGENT", staleAgent) - probeCount := 0 optionalDriverAgentMetadataProbe = func(driverType string, executablePath string) (db.OptionalDriverAgentMetadata, error) { - probeCount++ - revision := "src-stale-agent" - if probeCount > 1 { - revision = db.OptionalDriverAgentRevision(driverType) - } return db.OptionalDriverAgentMetadata{ DriverType: driverType, - AgentRevision: revision, + AgentRevision: "src-stale-agent", }, nil } - targetPath := filepath.Join(tmpDir, optionalDriverExecutableBaseName("sqlserver")) - source, _, err := ensureOptionalDriverAgentBinary( + meta, err := installOptionalDriverAgentPackage( nil, driverDefinition{Type: "sqlserver", Name: "SQL Server"}, - targetPath, - staleServer.URL, "1.9.6", + filepath.Join(tmpDir, "drivers"), + staleServer.URL, ) if err != nil { - t.Fatalf("expected stale direct download to fall back to source build, got %v", err) + t.Fatalf("expected stale direct download to be installed with an update hint, got %v", err) } - if source != "local://go-build/sqlserver-driver-agent" { - t.Fatalf("expected source build fallback, got %q", source) + if meta.DownloadURL != staleServer.URL { + t.Fatalf("expected direct download source to be preserved, got %q", meta.DownloadURL) + } + if meta.AgentRevision != "src-stale-agent" { + t.Fatalf("expected stale agent revision to be recorded, got %q", meta.AgentRevision) + } + if _, err := os.Stat(meta.ExecutablePath); err != nil { + t.Fatalf("expected runtime executable to stay installed, got %v", err) + } + needsUpdate, reason, expectedRevision := optionalDriverAgentRevisionStatus("sqlserver", meta, true) + if !needsUpdate { + t.Fatalf("expected stale installed revision to be surfaced as needsUpdate; expected=%q", expectedRevision) + } + if !strings.Contains(reason, "强烈建议重装") { + t.Fatalf("expected advisory reinstall reason, got %q", reason) } }