diff --git a/apps/desktop/src/components/connection/ConnectionDialog.vue b/apps/desktop/src/components/connection/ConnectionDialog.vue index 0c47c4dc6..c5bf462d0 100644 --- a/apps/desktop/src/components/connection/ConnectionDialog.vue +++ b/apps/desktop/src/components/connection/ConnectionDialog.vue @@ -170,6 +170,7 @@ const driverProfiles: Record< }, sqlserver: { type: "sqlserver", port: 1433, user: "sa", label: "SQL Server", icon: "sqlserver" }, oracle: { type: "oracle", port: 1521, user: "system", label: "Oracle", icon: "oracle" }, + "oracle-legacy": { type: "oracle", port: 1521, user: "system", label: "Oracle Legacy", icon: "oracle" }, "oracle-10g": { type: "oracle", port: 1521, user: "system", label: "Oracle 10g", icon: "oracle" }, elasticsearch: { type: "elasticsearch", @@ -458,6 +459,7 @@ const iconTypeMap: Record = { clickhouse: "clickhouse", sqlserver: "sqlserver", oracle: "oracle", + "oracle-legacy": "oracle", "oracle-10g": "oracle", elasticsearch: "elasticsearch", mariadb: "mariadb", @@ -1955,14 +1957,17 @@ function openExternalUrl(url: string) {
diff --git a/apps/desktop/src/components/icons/DatabaseIcon.vue b/apps/desktop/src/components/icons/DatabaseIcon.vue index 76d88fd91..93f719b86 100644 --- a/apps/desktop/src/components/icons/DatabaseIcon.vue +++ b/apps/desktop/src/components/icons/DatabaseIcon.vue @@ -20,7 +20,9 @@ const assetIcons: Record = { elasticsearch: "elasticsearch", oracle: "oracle", "oracle-10g": "oracle", + "oracle-legacy": "oracle", oracle_10g: "oracle", + oracle_legacy: "oracle", sqlserver: "sqlserver", access: "access.png", oceanbase: "oceanbase", diff --git a/apps/desktop/src/lib/agentDriverInstallHint.ts b/apps/desktop/src/lib/agentDriverInstallHint.ts index c79cddc50..68ae9d38d 100644 --- a/apps/desktop/src/lib/agentDriverInstallHint.ts +++ b/apps/desktop/src/lib/agentDriverInstallHint.ts @@ -12,6 +12,6 @@ export function showAgentDriverInstallHint( driverProfile?: string, ): boolean { if (!supportsDriverManagement(dbType)) return false; - const driverKey = dbType === "oracle" && driverProfile === "oracle-10g" ? "oracle-10g" : dbType; + const driverKey = dbType === "oracle" && driverProfile ? driverProfile : dbType; return drivers.find((driver) => driver.db_type === driverKey)?.installed !== true; } diff --git a/crates/dbx-core/assets/database-drivers.manifest.json b/crates/dbx-core/assets/database-drivers.manifest.json index ea8da538f..fa6d331a7 100644 --- a/crates/dbx-core/assets/database-drivers.manifest.json +++ b/crates/dbx-core/assets/database-drivers.manifest.json @@ -91,6 +91,7 @@ "skipTcpProbe": true, "defaultPort": 1521, "driverProfiles": [ + { "profile": "oracle-legacy", "label": "Oracle Legacy", "agentKey": "oracle-legacy" }, { "profile": "oracle-10g", "label": "Oracle 10g", "agentKey": "oracle-10g" } ] }, diff --git a/crates/dbx-core/src/agent_catalog.rs b/crates/dbx-core/src/agent_catalog.rs index 17b589dba..bfd5784d6 100644 --- a/crates/dbx-core/src/agent_catalog.rs +++ b/crates/dbx-core/src/agent_catalog.rs @@ -17,8 +17,10 @@ pub struct AgentDriverProfile { pub store_visible: bool, } -const ORACLE_PROFILES: &[AgentDriverProfile] = - &[AgentDriverProfile { profile: "oracle-10g", key: "oracle-10g", label: "Oracle 10g", store_visible: true }]; +const ORACLE_PROFILES: &[AgentDriverProfile] = &[ + AgentDriverProfile { profile: "oracle-legacy", key: "oracle-legacy", label: "Oracle Legacy", store_visible: true }, + AgentDriverProfile { profile: "oracle-10g", key: "oracle-10g", label: "Oracle 10g", store_visible: true }, +]; const AGENT_CATALOG: &[AgentCatalogEntry] = &[ AgentCatalogEntry { diff --git a/crates/dbx-core/src/agent_connection.rs b/crates/dbx-core/src/agent_connection.rs index 03f9dfecf..ce957383f 100644 --- a/crates/dbx-core/src/agent_connection.rs +++ b/crates/dbx-core/src/agent_connection.rs @@ -110,14 +110,22 @@ fn postgres_like_agent_jdbc_connection_string( } pub fn should_retry_oracle_with_10g_driver(config: &ConnectionConfig, err: &str) -> bool { + !oracle_auth_fallback_profiles(config, err).is_empty() +} + +pub fn oracle_auth_fallback_profiles(config: &ConnectionConfig, err: &str) -> Vec<&'static str> { if config.db_type != DatabaseType::Oracle { - return false; - } - if config.driver_profile.as_deref() == Some("oracle-10g") { - return false; + return Vec::new(); } let normalized = err.to_lowercase(); - normalized.contains("ora-28040") || normalized.contains("no matching authentication protocol") + if !normalized.contains("ora-28040") && !normalized.contains("no matching authentication protocol") { + return Vec::new(); + } + match config.driver_profile.as_deref() { + Some("oracle-10g") => Vec::new(), + Some("oracle-legacy") => vec!["oracle-10g"], + _ => vec!["oracle-legacy", "oracle-10g"], + } } pub fn oracle_alternate_connect_config(config: &ConnectionConfig, err: &str) -> Option { @@ -295,6 +303,26 @@ mod tests { assert!(oracle_alternate_connect_config(&cfg, "ORA-12541: TNS:no listener").is_none()); } + #[test] + fn oracle_auth_errors_use_legacy_then_10g_fallbacks() { + let mut cfg = config(DatabaseType::Oracle, Some("ORCL")); + cfg.driver_profile = Some("oracle".to_string()); + + assert_eq!( + oracle_auth_fallback_profiles(&cfg, "ORA-28040: No matching authentication protocol"), + vec!["oracle-legacy", "oracle-10g"] + ); + + cfg.driver_profile = Some("oracle-legacy".to_string()); + assert_eq!( + oracle_auth_fallback_profiles(&cfg, "ORA-28040: No matching authentication protocol"), + vec!["oracle-10g"] + ); + + cfg.driver_profile = Some("oracle-10g".to_string()); + assert!(oracle_auth_fallback_profiles(&cfg, "ORA-28040: No matching authentication protocol").is_empty()); + } + #[test] fn oracle_custom_connection_string_skips_alternate_descriptor_retry() { let mut cfg = config(DatabaseType::Oracle, Some("ORCL")); diff --git a/crates/dbx-core/src/agent_manager.rs b/crates/dbx-core/src/agent_manager.rs index d1b66b62b..7e1805f23 100644 --- a/crates/dbx-core/src/agent_manager.rs +++ b/crates/dbx-core/src/agent_manager.rs @@ -152,7 +152,12 @@ mod tests { let manager = test_manager("profile-key"); assert_eq!(AgentManager::db_type_to_agent_key(&DatabaseType::Oracle, Some("oracle-10g")), Some("oracle-10g")); + assert_eq!( + AgentManager::db_type_to_agent_key(&DatabaseType::Oracle, Some("oracle-legacy")), + Some("oracle-legacy") + ); assert_eq!(AgentManager::db_type_to_agent_key(&DatabaseType::Oracle, None), Some("oracle")); + manager.stop_daemon_by_key("oracle-legacy").await; manager.stop_daemon_by_key("oracle-10g").await; } } diff --git a/crates/dbx-core/src/connection.rs b/crates/dbx-core/src/connection.rs index 87582cc84..63364d410 100644 --- a/crates/dbx-core/src/connection.rs +++ b/crates/dbx-core/src/connection.rs @@ -8,7 +8,7 @@ use mysql_async::Row as MysqlRow; use crate::agent_connection::{ agent_connect_params, mongo_legacy_error_with_auth_hint, oracle_alternate_connect_config, - should_retry_oracle_with_10g_driver, + oracle_auth_fallback_profiles, should_retry_oracle_with_10g_driver, }; use crate::database_capabilities; use crate::db; @@ -333,19 +333,39 @@ impl AppState { })?; } else if should_retry_oracle_with_10g_driver(&db_config, &err) { log::warn!( - "Oracle connect failed with profile {:?}: {}. Retrying with oracle-10g profile.", + "Oracle connect failed with profile {:?}: {}. Retrying with legacy Oracle profiles.", db_config.driver_profile, err ); - let mut fallback_client = - self.agent_manager.spawn(&db_config.db_type, Some("oracle-10g")).await?; - fallback_client - .call_method::(AgentMethod::Connect, connect_params) - .await - .map_err(|fallback_err| { - format!("{err}\n\nFallback with oracle-10g driver failed: {fallback_err}") - })?; - client = fallback_client; + let mut fallback_errors = Vec::new(); + let mut connected_client = None; + for profile in oracle_auth_fallback_profiles(&db_config, &err) { + match self.agent_manager.spawn(&db_config.db_type, Some(profile)).await { + Ok(mut fallback_client) => { + match fallback_client + .call_method::(AgentMethod::Connect, connect_params.clone()) + .await + { + Ok(_) => { + connected_client = Some(fallback_client); + break; + } + Err(fallback_err) => { + fallback_errors.push(format!("{profile}: {fallback_err}")); + } + } + } + Err(fallback_err) => { + fallback_errors.push(format!("{profile}: {fallback_err}")); + } + } + } + client = connected_client.ok_or_else(|| { + format!( + "{err}\n\nFallback with legacy Oracle drivers failed: {}", + fallback_errors.join("\n") + ) + })?; } else { return Err(err); } @@ -1090,6 +1110,10 @@ mod tests { config.driver_profile = Some("oracle-10g".to_string()); assert!(!should_retry_oracle_with_10g_driver(&config, "Agent RPC error (-1): ORA-12541: TNS:no listener")); + assert!(!should_retry_oracle_with_10g_driver( + &config, + "Agent RPC error (-1): ORA-28040: No matching authentication protocol" + )); config.driver_profile = Some("oracle".to_string()); assert!(!should_retry_oracle_with_10g_driver( diff --git a/crates/dbx-core/tests/database_capabilities.rs b/crates/dbx-core/tests/database_capabilities.rs index 8e854b589..73e78f9c3 100644 --- a/crates/dbx-core/tests/database_capabilities.rs +++ b/crates/dbx-core/tests/database_capabilities.rs @@ -61,6 +61,7 @@ fn maps_agent_database_types_to_driver_keys() { assert_eq!(agent_key(&DatabaseType::Gbase, None), Some("gbase")); assert_eq!(agent_key(&DatabaseType::Access, None), Some("access")); assert_eq!(agent_key(&DatabaseType::Oracle, None), Some("oracle")); + assert_eq!(agent_key(&DatabaseType::Oracle, Some("oracle-legacy")), Some("oracle-legacy")); assert_eq!(agent_key(&DatabaseType::Oracle, Some("oracle-10g")), Some("oracle-10g")); assert_eq!(agent_key(&DatabaseType::Postgres, None), None); } diff --git a/packages/app-tests/agentDriverInstallHint.test.ts b/packages/app-tests/agentDriverInstallHint.test.ts index 30942fea0..00ccc8857 100644 --- a/packages/app-tests/agentDriverInstallHint.test.ts +++ b/packages/app-tests/agentDriverInstallHint.test.ts @@ -28,6 +28,7 @@ test("uses the selected Oracle driver profile for install hints", () => { "oracle", [ { db_type: "oracle", installed: false }, + { db_type: "oracle-legacy", installed: false }, { db_type: "oracle-10g", installed: true }, ], "oracle-10g", @@ -39,11 +40,24 @@ test("uses the selected Oracle driver profile for install hints", () => { "oracle", [ { db_type: "oracle", installed: true }, + { db_type: "oracle-legacy", installed: false }, { db_type: "oracle-10g", installed: false }, ], "oracle", ), false, ); + assert.equal( + showAgentDriverInstallHint( + "oracle", + [ + { db_type: "oracle", installed: true }, + { db_type: "oracle-legacy", installed: true }, + { db_type: "oracle-10g", installed: false }, + ], + "oracle-legacy", + ), + false, + ); assert.equal(showAgentDriverInstallHint("oracle", [{ db_type: "oracle", installed: false }], "oracle"), true); }); diff --git a/src-tauri/src/commands/connection.rs b/src-tauri/src/commands/connection.rs index 7c9d4140a..2dcfe2c51 100644 --- a/src-tauri/src/commands/connection.rs +++ b/src-tauri/src/commands/connection.rs @@ -3,7 +3,7 @@ use tauri::State; pub use dbx_core::agent_connection::{ agent_connect_params, mongo_legacy_error_with_auth_hint, oracle_alternate_connect_config, - should_retry_oracle_with_10g_driver, + oracle_auth_fallback_profiles, should_retry_oracle_with_10g_driver, }; pub use dbx_core::connection::{ connection_url_for_endpoint, expand_tilde, metadata_connection_config, probe_connection_endpoint, @@ -57,16 +57,32 @@ async fn test_agent_connection( format!("{err}\n\nFallback with alternate Oracle descriptor failed: {alternate_err}") })?; } else if should_retry_oracle_with_10g_driver(config, &err) { - state - .agent_manager - .call_daemon_method::( - &config.db_type, - Some("oracle-10g"), - AgentMethod::TestConnection, - connect_params, - ) - .await - .map_err(|fallback_err| format!("{err}\n\nFallback with oracle-10g driver failed: {fallback_err}"))?; + let mut fallback_errors = Vec::new(); + let mut connected = false; + for profile in oracle_auth_fallback_profiles(config, &err) { + match state + .agent_manager + .call_daemon_method::( + &config.db_type, + Some(profile), + AgentMethod::TestConnection, + connect_params.clone(), + ) + .await + { + Ok(_) => { + connected = true; + break; + } + Err(fallback_err) => fallback_errors.push(format!("{profile}: {fallback_err}")), + } + } + if !connected { + return Err(format!( + "{err}\n\nFallback with legacy Oracle drivers failed: {}", + fallback_errors.join("\n") + )); + } } else { return Err(err); } @@ -102,12 +118,28 @@ async fn connect_agent_pool( format!("{err}\n\nFallback with alternate Oracle descriptor failed: {alternate_err}") })?; } else if should_retry_oracle_with_10g_driver(config, &err) { - let mut fallback_client = state.agent_manager.spawn(&config.db_type, Some("oracle-10g")).await?; - fallback_client - .call_method::(AgentMethod::Connect, connect_params) - .await - .map_err(|fallback_err| format!("{err}\n\nFallback with oracle-10g driver failed: {fallback_err}"))?; - client = fallback_client; + let mut fallback_errors = Vec::new(); + let mut connected_client = None; + for profile in oracle_auth_fallback_profiles(config, &err) { + match state.agent_manager.spawn(&config.db_type, Some(profile)).await { + Ok(mut fallback_client) => { + match fallback_client + .call_method::(AgentMethod::Connect, connect_params.clone()) + .await + { + Ok(_) => { + connected_client = Some(fallback_client); + break; + } + Err(fallback_err) => fallback_errors.push(format!("{profile}: {fallback_err}")), + } + } + Err(fallback_err) => fallback_errors.push(format!("{profile}: {fallback_err}")), + } + } + client = connected_client.ok_or_else(|| { + format!("{err}\n\nFallback with legacy Oracle drivers failed: {}", fallback_errors.join("\n")) + })?; } else { return Err(err); }