From 22ccdef5949598b0eeb56ca54c8bee524506c1a5 Mon Sep 17 00:00:00 2001 From: zipg Date: Wed, 22 Jul 2026 23:02:08 +0800 Subject: [PATCH] feat(sqlserver): add explicit legacy driver selection --- .../connection/ConnectionDialog.vue | 55 +++--- .../sqlServerLegacyCompatibility.spec.ts | 51 ++++-- .../sqlServerLegacyCompatibility.ts | 27 ++- .../connectionStore.sqlserverLegacy.spec.ts | 53 +++++- apps/desktop/src/stores/connectionStore.ts | 7 +- crates/dbx-core/src/connection.rs | 163 +++++++++--------- crates/dbx-core/src/db/sqlserver.rs | 18 +- src-tauri/src/commands/connection.rs | 9 +- 8 files changed, 228 insertions(+), 155 deletions(-) diff --git a/apps/desktop/src/components/connection/ConnectionDialog.vue b/apps/desktop/src/components/connection/ConnectionDialog.vue index 1715ce893..8819c1972 100644 --- a/apps/desktop/src/components/connection/ConnectionDialog.vue +++ b/apps/desktop/src/components/connection/ConnectionDialog.vue @@ -50,7 +50,7 @@ import { normalizeKafkaBootstrapServers } from "@/lib/connection/kafkaBootstrapS import { normalizeRocketmqNamesrvAddr } from "@/lib/connection/rocketmqNamesrv"; import { detectMqUiAuthKind, isMqAuthKindAllowedForSystem, type MqUiAuthKind } from "@/lib/connection/mqAuth"; import { driverInstallProgressPercent, type DriverInstallProgress } from "@/lib/connection/driverInstallProgressUi"; -import { isSqlServerLegacyCompatibilityMode, requiresSqlServerLegacyCompatibilityComponent, setSqlServerLegacyCompatibilityMode, SQLSERVER_LEGACY_COMPATIBILITY_DRIVER_KEY } from "@/lib/connection/sqlServerLegacyCompatibility"; +import { requiresSqlServerLegacyCompatibilityComponent, setSqlServerLegacyCompatibilityConfig, sqlServerUsesLegacyCompatibility, SQLSERVER_LEGACY_COMPATIBILITY_DRIVER_KEY } from "@/lib/connection/sqlServerLegacyCompatibility"; import { ArrowLeft, ArrowDown, @@ -1330,33 +1330,24 @@ async function installSqlServerLegacyCompatibilityComponentIfNeeded(): Promise { return `${label} ${formatInstallSize(progress.downloaded ?? 0)} / ${formatInstallSize(progress.total)} (${agentInstallPercent.value ?? 0}%)`; }); const canCloseAgentInstallDialog = computed(() => !agentInstallRunning.value || !!agentInstallError.value); -const sqlServerLegacyCompatibilityModeEnabled = computed(() => form.value.db_type === "sqlserver" && isSqlServerLegacyCompatibilityMode(form.value.url_params)); +const sqlServerDriverMode = computed<"auto" | "legacy">(() => (sqlServerUsesLegacyCompatibility(form.value) ? "legacy" : "auto")); const shouldUseWideConnectionDialog = computed(() => dialogStep.value === "config" && (canChooseVisibleDatabases.value || (canChooseVisibleSchemas.value && !visibleFilterUsesSchemas.value))); const connectionDialogContentClass = computed(() => { if (dialogStep.value === "select") return "sm:max-w-[760px]"; @@ -2505,10 +2496,6 @@ async function testConnection() { const message = config ? connectionErrorWithDriverUpdateHint(config, rawMessage) : rawMessage; const fallback = config ? await tryNacosDockerConsoleFallback(config, message, runId) : null; if (runId !== testRunId) return; - const shouldShowSqlServerLegacyMode = !fallback && config?.db_type === "sqlserver" && !isSqlServerLegacyCompatibilityMode(config.url_params) && isSqlServerTlsHandshakeFailure(message); - if (shouldShowSqlServerLegacyMode) { - configTab.value = "advanced"; - } if (fallback) { applySuccessfulConnectionTest(fallback.result, fallback.config, submittedSourceName); void persistSuccessfulConnectionTest(fallback.result, fallback.config, submittedSourceName, runId); @@ -5236,6 +5223,22 @@ function openExternalUrl(url: string) { +
+ +
+ + + + + + + + {{ t("connection.sqlServerLegacyCompatibilityModeHint") }} + + +
+
+
@@ -5838,18 +5841,6 @@ function openExternalUrl(url: string) {
-
- -
- -

- {{ t("connection.sqlServerLegacyCompatibilityModeHint") }} -

-
-
diff --git a/apps/desktop/src/lib/__tests__/connection/sqlServerLegacyCompatibility.spec.ts b/apps/desktop/src/lib/__tests__/connection/sqlServerLegacyCompatibility.spec.ts index e20b90e0a..28f75a7dd 100644 --- a/apps/desktop/src/lib/__tests__/connection/sqlServerLegacyCompatibility.spec.ts +++ b/apps/desktop/src/lib/__tests__/connection/sqlServerLegacyCompatibility.spec.ts @@ -1,5 +1,5 @@ import { describe, expect, it } from "vitest"; -import { isSqlServerLegacyCompatibilityMode, requiresSqlServerLegacyCompatibilityComponent, setSqlServerLegacyCompatibilityMode } from "@/lib/connection/sqlServerLegacyCompatibility"; +import { isSqlServerNativeEncryptionDisabled, requiresSqlServerLegacyCompatibilityComponent, setSqlServerLegacyCompatibilityConfig, setSqlServerNativeEncryptionDisabled, sqlServerUsesLegacyCompatibility } from "@/lib/connection/sqlServerLegacyCompatibility"; import type { ConnectionConfig } from "@/types/database"; function connectionConfig(urlParams?: string): ConnectionConfig { @@ -25,26 +25,51 @@ function connectionConfig(urlParams?: string): ConnectionConfig { } describe("SQL Server legacy compatibility", () => { - it("treats existing disabled encryption params as legacy compatibility opt-in", () => { - expect(isSqlServerLegacyCompatibilityMode("sqlserverEncryption=disabled")).toBe(true); - expect(isSqlServerLegacyCompatibilityMode("applicationName=dbx;encrypt=false")).toBe(true); - expect(isSqlServerLegacyCompatibilityMode("?Encrypt=0&applicationName=dbx")).toBe(true); - expect(isSqlServerLegacyCompatibilityMode("encrypt=true")).toBe(false); + it("recognizes native encryption policy independently from the legacy driver profile", () => { + expect(isSqlServerNativeEncryptionDisabled("sqlserverEncryption=disabled")).toBe(true); + expect(isSqlServerNativeEncryptionDisabled("applicationName=dbx;encrypt=false")).toBe(true); + expect(isSqlServerNativeEncryptionDisabled("?Encrypt=0&applicationName=dbx")).toBe(true); + expect(isSqlServerNativeEncryptionDisabled("encrypt=true")).toBe(false); }); - it("updates URL params without keeping conflicting encryption params", () => { - expect(setSqlServerLegacyCompatibilityMode("applicationName=dbx;encrypt=true", true)).toBe("applicationName=dbx&sqlserverEncryption=disabled"); - expect(setSqlServerLegacyCompatibilityMode("applicationName=dbx;sqlserverEncryption=disabled", false)).toBe("applicationName=dbx"); + it("updates native encryption params without changing the driver profile", () => { + expect(setSqlServerNativeEncryptionDisabled("applicationName=dbx;encrypt=true", true)).toBe("applicationName=dbx&sqlserverEncryption=disabled"); + expect(setSqlServerNativeEncryptionDisabled("applicationName=dbx;sqlserverEncryption=disabled", false)).toBe("applicationName=dbx"); }); - it("requires the hidden component only for SQL Server legacy compatibility connections", () => { - expect(requiresSqlServerLegacyCompatibilityComponent(connectionConfig("sqlserverEncryption=disabled"))).toBe(true); - expect(requiresSqlServerLegacyCompatibilityComponent(connectionConfig("encrypt=true"))).toBe(false); + it("keeps historical disabled-encryption connections on the native driver", () => { + const config = connectionConfig("sqlserverEncryption=disabled"); + + expect(sqlServerUsesLegacyCompatibility(config)).toBe(false); + expect(requiresSqlServerLegacyCompatibilityComponent(config)).toBe(false); expect( requiresSqlServerLegacyCompatibilityComponent({ - ...connectionConfig("sqlserverEncryption=disabled"), + ...config, + driver_profile: "sqlserver-legacy", db_type: "mysql", }), ).toBe(false); }); + + it("treats a persisted legacy driver profile as compatibility mode", () => { + const config = connectionConfig(""); + config.driver_profile = "sqlserver-legacy"; + + expect(sqlServerUsesLegacyCompatibility(config)).toBe(true); + expect(requiresSqlServerLegacyCompatibilityComponent(config)).toBe(true); + }); + + it("updates the explicit driver profile without rewriting native encryption params", () => { + const config = connectionConfig("applicationName=dbx&encrypt=false"); + + setSqlServerLegacyCompatibilityConfig(config, true); + expect(config.driver_profile).toBe("sqlserver-legacy"); + expect(config.driver_label).toBe("SQL Server legacy compatibility component"); + expect(config.url_params).toBe("applicationName=dbx&encrypt=false"); + + setSqlServerLegacyCompatibilityConfig(config, false); + expect(config.driver_profile).toBe("sqlserver"); + expect(config.driver_label).toBe("SQL Server"); + expect(config.url_params).toBe("applicationName=dbx&encrypt=false"); + }); }); diff --git a/apps/desktop/src/lib/connection/sqlServerLegacyCompatibility.ts b/apps/desktop/src/lib/connection/sqlServerLegacyCompatibility.ts index 36294512e..a8349ce6a 100644 --- a/apps/desktop/src/lib/connection/sqlServerLegacyCompatibility.ts +++ b/apps/desktop/src/lib/connection/sqlServerLegacyCompatibility.ts @@ -1,34 +1,45 @@ import type { ConnectionConfig } from "@/types/database"; export const SQLSERVER_LEGACY_COMPATIBILITY_DRIVER_KEY = "sqlserver-legacy"; +export const SQLSERVER_LEGACY_COMPATIBILITY_DRIVER_LABEL = "SQL Server legacy compatibility component"; +export const SQLSERVER_NATIVE_DRIVER_PROFILE = "sqlserver"; +export const SQLSERVER_NATIVE_DRIVER_LABEL = "SQL Server"; -const SQLSERVER_LEGACY_DISABLED_VALUES = new Set(["disabled", "disable", "false", "0", "off", "no"]); +const SQLSERVER_ENCRYPTION_DISABLED_VALUES = new Set(["disabled", "disable", "false", "0", "off", "no"]); -export function isSqlServerLegacyCompatibilityMode(params: string | undefined): boolean { +export function isSqlServerNativeEncryptionDisabled(params: string | undefined): boolean { const normalized = (params || "").trim().replace(/^\?/, "").replace(/;/g, "&"); if (!normalized) return false; const parsed = new URLSearchParams(normalized); for (const [key, value] of parsed.entries()) { const normalizedKey = key.trim().toLowerCase(); if (normalizedKey === "sqlserverencryption" || normalizedKey === "encrypt") { - // Accept JDBC-style `encrypt=false` from imported SQL Server URLs as the same opt-in. - if (SQLSERVER_LEGACY_DISABLED_VALUES.has(value.trim().toLowerCase())) return true; + if (SQLSERVER_ENCRYPTION_DISABLED_VALUES.has(value.trim().toLowerCase())) return true; } } return false; } -export function setSqlServerLegacyCompatibilityMode(params: string | undefined, enabled: boolean): string { +export function setSqlServerNativeEncryptionDisabled(params: string | undefined, disabled: boolean): string { const normalized = (params || "").trim().replace(/^\?/, "").replace(/;/g, "&"); const parsed = new URLSearchParams(normalized); for (const key of Array.from(parsed.keys())) { const normalizedKey = key.trim().toLowerCase(); if (normalizedKey === "sqlserverencryption" || normalizedKey === "encrypt") parsed.delete(key); } - if (enabled) parsed.set("sqlserverEncryption", "disabled"); + if (disabled) parsed.set("sqlserverEncryption", "disabled"); return parsed.toString(); } -export function requiresSqlServerLegacyCompatibilityComponent(config: ConnectionConfig): boolean { - return config.db_type === "sqlserver" && isSqlServerLegacyCompatibilityMode(config.url_params); +export function sqlServerUsesLegacyCompatibility(config: Pick): boolean { + return config.db_type === "sqlserver" && config.driver_profile === SQLSERVER_LEGACY_COMPATIBILITY_DRIVER_KEY; +} + +export function setSqlServerLegacyCompatibilityConfig(config: Pick, enabled: boolean): void { + config.driver_profile = enabled ? SQLSERVER_LEGACY_COMPATIBILITY_DRIVER_KEY : SQLSERVER_NATIVE_DRIVER_PROFILE; + config.driver_label = enabled ? SQLSERVER_LEGACY_COMPATIBILITY_DRIVER_LABEL : SQLSERVER_NATIVE_DRIVER_LABEL; +} + +export function requiresSqlServerLegacyCompatibilityComponent(config: ConnectionConfig): boolean { + return sqlServerUsesLegacyCompatibility(config); } diff --git a/apps/desktop/src/stores/__tests__/connectionStore.sqlserverLegacy.spec.ts b/apps/desktop/src/stores/__tests__/connectionStore.sqlserverLegacy.spec.ts index 13f2f346d..4c5375a7a 100644 --- a/apps/desktop/src/stores/__tests__/connectionStore.sqlserverLegacy.spec.ts +++ b/apps/desktop/src/stores/__tests__/connectionStore.sqlserverLegacy.spec.ts @@ -11,7 +11,7 @@ function installLocalStorage() { }); } -function sqlServerLegacyConnection(): ConnectionConfig { +function sqlServerNativeConnectionWithDisabledEncryption(): ConnectionConfig { return { id: "sqlserver-1", name: "SQL Server", @@ -41,7 +41,7 @@ describe("connectionStore SQL Server legacy compatibility", () => { setActivePinia(createPinia()); }); - it("installs the legacy compatibility component before connecting saved legacy configs", async () => { + it("does not preinstall the legacy component for historical disabled-encryption configs", async () => { const connectDb = vi.fn().mockResolvedValue("sqlserver-1"); const installAgent = vi.fn().mockResolvedValue(undefined); @@ -54,10 +54,53 @@ describe("connectionStore SQL Server legacy compatibility", () => { const { useConnectionStore } = await import("@/stores/connectionStore"); const store = useConnectionStore(); - await store.connect(sqlServerLegacyConnection()); + await store.connect(sqlServerNativeConnectionWithDisabledEncryption()); - expect(installAgent).toHaveBeenCalledWith("sqlserver-legacy"); + expect(installAgent).not.toHaveBeenCalled(); expect(connectDb).toHaveBeenCalledTimes(1); - expect(installAgent.mock.invocationCallOrder[0]).toBeLessThan(connectDb.mock.invocationCallOrder[0]); + }); + + it("reconnects persisted legacy profiles without reinstalling an installed component", async () => { + const connectDb = vi.fn().mockResolvedValue("sqlserver-1"); + const installAgent = vi.fn().mockResolvedValue(undefined); + const config = sqlServerNativeConnectionWithDisabledEncryption(); + config.driver_profile = "sqlserver-legacy"; + config.driver_label = "SQL Server legacy compatibility component"; + + vi.doMock("@/lib/backend/tauriRuntime", () => ({ isTauriRuntime: () => true })); + vi.doMock("@/lib/backend/api", () => ({ + connectDb, + installAgent, + isAgentInstalled: vi.fn().mockResolvedValue(true), + })); + + const { useConnectionStore } = await import("@/stores/connectionStore"); + const store = useConnectionStore(); + await store.connect(config); + + expect(installAgent).not.toHaveBeenCalled(); + expect(connectDb).toHaveBeenCalledOnce(); + expect(connectDb).toHaveBeenCalledWith(expect.objectContaining({ driver_profile: "sqlserver-legacy" }), expect.any(Number)); + }); + + it("does not install or retry legacy after a TLS error followed by SQL Server 18456", async () => { + const connectDb = vi.fn().mockRejectedValue(new Error("TLS negotiation failed\nSQL Server error 18456: Login failed")); + const installAgent = vi.fn().mockResolvedValue(undefined); + const config = sqlServerNativeConnectionWithDisabledEncryption(); + + vi.doMock("@/lib/backend/tauriRuntime", () => ({ isTauriRuntime: () => true })); + vi.doMock("@/lib/backend/api", () => ({ + connectDb, + installAgent, + isAgentInstalled: vi.fn().mockResolvedValue(false), + listInstalledAgents: vi.fn().mockResolvedValue([]), + })); + + const { useConnectionStore } = await import("@/stores/connectionStore"); + const store = useConnectionStore(); + await expect(store.connect(config)).rejects.toThrow("18456"); + + expect(connectDb).toHaveBeenCalledOnce(); + expect(installAgent).not.toHaveBeenCalled(); }); }); diff --git a/apps/desktop/src/stores/connectionStore.ts b/apps/desktop/src/stores/connectionStore.ts index eaac7c106..ab88cfdc3 100644 --- a/apps/desktop/src/stores/connectionStore.ts +++ b/apps/desktop/src/stores/connectionStore.ts @@ -2046,7 +2046,9 @@ export const useConnectionStore = defineStore("connection", () => { const localAttempt = beginLocalConnectionAttempt(config.id); try { await beforeConnectHandler?.(config); - await ensureSqlServerLegacyCompatibilityComponentInstalled(config); + if (config.db_type === "sqlserver") { + await ensureSqlServerLegacyCompatibilityComponentInstalled(config); + } ensureLocalConnectionAttemptActive(config.id, localAttempt); const id = await withConnectionAttemptTimeout(api.connectDb(config, localAttempt), config); await ensureLocalConnectionAttemptActiveAfterConnectResult(config.id, localAttempt, id); @@ -2213,6 +2215,9 @@ export const useConnectionStore = defineStore("connection", () => { const localAttempt = beginLocalConnectionAttempt(connectionId); const connectPromise = (async () => { await beforeConnectHandler?.(config); + if (config.db_type === "sqlserver") { + await ensureSqlServerLegacyCompatibilityComponentInstalled(config); + } ensureLocalConnectionAttemptActive(connectionId, localAttempt); const id = await withConnectionAttemptTimeout(api.connectDb(config, localAttempt), config); await ensureLocalConnectionAttemptActiveAfterConnectResult(connectionId, localAttempt, id); diff --git a/crates/dbx-core/src/connection.rs b/crates/dbx-core/src/connection.rs index 138c1cc00..64a7340b6 100644 --- a/crates/dbx-core/src/connection.rs +++ b/crates/dbx-core/src/connection.rs @@ -333,15 +333,21 @@ pub fn sqlserver_legacy_agent_config(config: &ConnectionConfig) -> ConnectionCon legacy_config } -pub fn sqlserver_legacy_agent_error(native_error: &str, agent_error: &str) -> String { - let install_hint = if agent_error.contains("driver is not installed") { - format!("\n\n{SQLSERVER_LEGACY_DRIVER_INSTALL_HINT}") +pub fn sqlserver_uses_legacy_driver(config: &ConnectionConfig) -> bool { + config + .driver_profile + .as_deref() + .is_some_and(|profile| profile.eq_ignore_ascii_case(db::sqlserver::SQLSERVER_LEGACY_DRIVER_PROFILE)) +} + +pub fn sqlserver_legacy_driver_error(agent_error: &str) -> String { + // AgentManager currently returns launch failures as strings. This exact marker is generated + // internally and only adds guidance for an explicitly selected compatibility driver. + if agent_error.contains("driver is not installed") { + format!("{agent_error}\n\n{SQLSERVER_LEGACY_DRIVER_INSTALL_HINT}") } else { - String::new() - }; - format!( - "{native_error}\n\nFallback with SQL Server legacy compatibility component failed: {agent_error}{install_hint}" - ) + agent_error.to_string() + } } pub async fn connect_mysql_metadata_pool( @@ -748,26 +754,48 @@ impl AppState { Ok(PoolKind::ExternalDriver { driver_id: driver_id.to_string(), config: Arc::new(config.clone()), session }) } - pub async fn test_sqlserver_connection_with_legacy_fallback( + pub async fn test_sqlserver_connection( &self, config: &ConnectionConfig, host: &str, port: u16, connect_timeout: Duration, ) -> Result { - self.test_sqlserver_connection_with_legacy_fallback_with_info(config, host, port, connect_timeout) - .await - .map(|result| result.message) + self.test_sqlserver_connection_with_info(config, host, port, connect_timeout).await.map(|result| result.message) } - pub async fn test_sqlserver_connection_with_legacy_fallback_with_info( + pub async fn test_sqlserver_connection_with_info( &self, config: &ConnectionConfig, host: &str, port: u16, connect_timeout: Duration, ) -> Result { - match db::sqlserver::connect_with_port_explicit( + if sqlserver_uses_legacy_driver(config) { + let legacy_config = sqlserver_legacy_agent_config(config); + let connect_params = + agent_connect_params(&legacy_config, host, port, legacy_config.effective_database().unwrap_or("")); + let mut client = self + .agent_manager + .spawn(&legacy_config.db_type, legacy_config.driver_profile.as_deref()) + .await + .map_err(|err| sqlserver_legacy_driver_error(&err))?; + let response = client + .call_method_with_timeout::( + AgentMethod::TestConnection, + connect_params, + Some(agent_connect_timeout(&legacy_config)), + ) + .await + .map_err(|err| sqlserver_legacy_driver_error(&err))?; + client.disconnect().await.ok(); + return Ok(ConnectionTestResult::success( + "Connection successful (via SQL Server legacy compatibility driver)", + ) + .with_database_info(database_info_from_protocol_value(&response))); + } + + db::sqlserver::connect_with_port_explicit( host, port, config.sqlserver_port_explicit(), @@ -776,44 +804,38 @@ impl AppState { config.database.as_deref(), connect_timeout, ) - .await - { - Ok(_) => Ok(ConnectionTestResult::success("Connection successful")), - Err(native_error) - if db::sqlserver::sqlserver_legacy_compatibility_enabled(config.url_params.as_deref()) => - { - let legacy_config = sqlserver_legacy_agent_config(config); - let connect_params = - agent_connect_params(&legacy_config, host, port, legacy_config.effective_database().unwrap_or("")); - let mut client = self - .agent_manager - .spawn(&legacy_config.db_type, legacy_config.driver_profile.as_deref()) - .await - .map_err(|err| sqlserver_legacy_agent_error(&native_error, &err))?; - let response = client - .call_method_with_timeout::( - AgentMethod::TestConnection, - connect_params, - Some(agent_connect_timeout(&legacy_config)), - ) - .await - .map_err(|err| sqlserver_legacy_agent_error(&native_error, &err))?; - client.disconnect().await.ok(); - Ok(ConnectionTestResult::success("Connection successful (via SQL Server legacy compatibility driver)") - .with_database_info(database_info_from_protocol_value(&response))) - } - Err(err) => Err(err), - } + .await?; + Ok(ConnectionTestResult::success("Connection successful")) } - pub async fn connect_sqlserver_pool_with_legacy_fallback( + pub async fn connect_sqlserver_pool( &self, config: &ConnectionConfig, host: &str, port: u16, connect_timeout: Duration, ) -> Result { - match db::sqlserver::connect_with_port_explicit( + if sqlserver_uses_legacy_driver(config) { + let legacy_config = sqlserver_legacy_agent_config(config); + let connect_params = + agent_connect_params(&legacy_config, host, port, legacy_config.effective_database().unwrap_or("")); + let mut client = self + .agent_manager + .spawn(&legacy_config.db_type, legacy_config.driver_profile.as_deref()) + .await + .map_err(|err| sqlserver_legacy_driver_error(&err))?; + client + .call_method_with_timeout::( + AgentMethod::Connect, + connect_params, + Some(agent_connect_timeout(&legacy_config)), + ) + .await + .map_err(|err| sqlserver_legacy_driver_error(&err))?; + return Ok(PoolKind::Agent(Arc::new(tokio::sync::Mutex::new(client)))); + } + + let client = db::sqlserver::connect_with_port_explicit( host, port, config.sqlserver_port_explicit(), @@ -822,32 +844,8 @@ impl AppState { config.database.as_deref(), connect_timeout, ) - .await - { - Ok(client) => Ok(PoolKind::SqlServer(Arc::new(tokio::sync::Mutex::new(client)))), - Err(native_error) - if db::sqlserver::sqlserver_legacy_compatibility_enabled(config.url_params.as_deref()) => - { - let legacy_config = sqlserver_legacy_agent_config(config); - let connect_params = - agent_connect_params(&legacy_config, host, port, legacy_config.effective_database().unwrap_or("")); - let mut client = self - .agent_manager - .spawn(&legacy_config.db_type, legacy_config.driver_profile.as_deref()) - .await - .map_err(|err| sqlserver_legacy_agent_error(&native_error, &err))?; - client - .call_method_with_timeout::( - AgentMethod::Connect, - connect_params, - Some(agent_connect_timeout(&legacy_config)), - ) - .await - .map_err(|err| sqlserver_legacy_agent_error(&native_error, &err))?; - Ok(PoolKind::Agent(Arc::new(tokio::sync::Mutex::new(client)))) - } - Err(err) => Err(err), - } + .await?; + Ok(PoolKind::SqlServer(Arc::new(tokio::sync::Mutex::new(client)))) } pub fn external_driver_runtime_env(&self, driver_id: &str) -> Result { @@ -1381,9 +1379,7 @@ impl AppState { db::clickhouse_driver::test_connection(&client, connect_timeout).await?; PoolKind::ClickHouse(client) } - DatabaseType::SqlServer => { - self.connect_sqlserver_pool_with_legacy_fallback(&db_config, &host, port, connect_timeout).await? - } + DatabaseType::SqlServer => self.connect_sqlserver_pool(&db_config, &host, port, connect_timeout).await?, DatabaseType::Elasticsearch => { let mut client = db::elasticsearch_driver::EsClient::from_config( &url, @@ -3753,8 +3749,8 @@ mod tests { metadata_connection_config, mysql_metadata_fallback_url, oceanbase_mysql_query_timeout_sql, oceanbase_mysql_setup_queries, prestosql_jdbc_config_for_endpoint, redacted_connection_url_for_endpoint, redis_sentinel_transport_id, redis_sentinel_transport_prefix, sqlserver_legacy_agent_config, - sqlserver_legacy_agent_error, task_client_session_id, uses_bare_mysql_pool, uses_tcp_probe, - validate_h2_database_path, AppState, MysqlMode, PoolKind, PRESTOSQL_JDBC_DRIVER_CLASS, + sqlserver_legacy_driver_error, sqlserver_uses_legacy_driver, task_client_session_id, uses_bare_mysql_pool, + uses_tcp_probe, validate_h2_database_path, AppState, MysqlMode, PoolKind, PRESTOSQL_JDBC_DRIVER_CLASS, }; use crate::agent_connection::{ agent_connect_params, mongo_legacy_error_with_auth_hint, mongo_uses_legacy_driver, @@ -3891,17 +3887,24 @@ mod tests { assert_eq!(legacy.db_type, DatabaseType::SqlServer); assert_eq!(legacy.driver_profile.as_deref(), Some(crate::db::sqlserver::SQLSERVER_LEGACY_DRIVER_PROFILE)); assert_eq!(legacy.driver_label.as_deref(), Some(crate::db::sqlserver::SQLSERVER_LEGACY_DRIVER_LABEL)); + assert!(sqlserver_uses_legacy_driver(&legacy)); } #[test] - fn sqlserver_legacy_agent_error_mentions_driver_manager_when_missing() { - let message = sqlserver_legacy_agent_error( - "native failed", + fn sqlserver_legacy_url_param_does_not_force_agent_driver() { + let mut config = mysql_config(Some("master")); + config.db_type = DatabaseType::SqlServer; + config.url_params = Some("applicationName=dbx;encrypt=false".to_string()); + + assert!(!sqlserver_uses_legacy_driver(&config)); + } + + #[test] + fn sqlserver_legacy_driver_error_mentions_driver_manager_when_missing() { + let message = sqlserver_legacy_driver_error( "sqlserver-legacy driver is not installed. Please install it from the Driver Manager.", ); - assert!(message.contains("native failed")); - assert!(message.contains("Fallback with SQL Server legacy compatibility component failed")); assert!(message.contains("Driver Manager")); assert!(message.contains("enable SQL Server legacy compatibility mode again")); } diff --git a/crates/dbx-core/src/db/sqlserver.rs b/crates/dbx-core/src/db/sqlserver.rs index de8bcf299..1439555a6 100644 --- a/crates/dbx-core/src/db/sqlserver.rs +++ b/crates/dbx-core/src/db/sqlserver.rs @@ -177,7 +177,7 @@ async fn try_connect_legacy_sqlserver_encryption( Err(errors.join("\n")) } -pub fn sqlserver_legacy_compatibility_enabled(url_params: Option<&str>) -> bool { +pub fn sqlserver_native_encryption_disabled(url_params: Option<&str>) -> bool { let Some(params) = url_params.map(str::trim).filter(|params| !params.is_empty()) else { return false; }; @@ -2355,14 +2355,14 @@ mod tests { } #[test] - fn sqlserver_legacy_compatibility_flag_accepts_dbx_and_jdbc_params() { - assert!(!super::sqlserver_legacy_compatibility_enabled(None)); - assert!(!super::sqlserver_legacy_compatibility_enabled(Some("encrypt=true"))); - assert!(super::sqlserver_legacy_compatibility_enabled(Some("sqlserverEncryption=disabled"))); - assert!(super::sqlserver_legacy_compatibility_enabled(Some("applicationName=dbx;sqlserverEncryption=off"))); - assert!(super::sqlserver_legacy_compatibility_enabled(Some("?sqlserverEncryption=false&applicationName=dbx"))); - assert!(super::sqlserver_legacy_compatibility_enabled(Some("applicationName=dbx;encrypt=false"))); - assert!(super::sqlserver_legacy_compatibility_enabled(Some("?Encrypt=0&applicationName=dbx"))); + fn sqlserver_native_encryption_flag_accepts_dbx_and_jdbc_params() { + assert!(!super::sqlserver_native_encryption_disabled(None)); + assert!(!super::sqlserver_native_encryption_disabled(Some("encrypt=true"))); + assert!(super::sqlserver_native_encryption_disabled(Some("sqlserverEncryption=disabled"))); + assert!(super::sqlserver_native_encryption_disabled(Some("applicationName=dbx;sqlserverEncryption=off"))); + assert!(super::sqlserver_native_encryption_disabled(Some("?sqlserverEncryption=false&applicationName=dbx"))); + assert!(super::sqlserver_native_encryption_disabled(Some("applicationName=dbx;encrypt=false"))); + assert!(super::sqlserver_native_encryption_disabled(Some("?Encrypt=0&applicationName=dbx"))); } #[test] diff --git a/src-tauri/src/commands/connection.rs b/src-tauri/src/commands/connection.rs index 70ee9fef5..6f3d34f31 100644 --- a/src-tauri/src/commands/connection.rs +++ b/src-tauri/src/commands/connection.rs @@ -897,10 +897,7 @@ async fn test_connection_with_info_inner( .map(|_| "Connection successful".to_string()) } DatabaseType::SqlServer => { - match state - .test_sqlserver_connection_with_legacy_fallback_with_info(&config, &host, port, connect_timeout) - .await - { + match state.test_sqlserver_connection_with_info(&config, &host, port, connect_timeout).await { Ok(details) => { database_info = details.database_info; Ok(details.message) @@ -1229,9 +1226,7 @@ pub async fn connect_db( db::clickhouse_driver::test_connection(&client, connect_timeout).await?; PoolKind::ClickHouse(client) } - DatabaseType::SqlServer => { - state.connect_sqlserver_pool_with_legacy_fallback(&db_config, &host, port, connect_timeout).await? - } + DatabaseType::SqlServer => state.connect_sqlserver_pool(&db_config, &host, port, connect_timeout).await?, DatabaseType::Elasticsearch => { let mut client = db::elasticsearch_driver::EsClient::from_config( &url,