feat: add tiered Oracle agent profiles

This commit is contained in:
t8y2 committed 2026-05-30 22:43:02 +08:00
1 parent f13c46b903
commit c334550252
11 files changed
+152 -38

No files matched your search

@@ -170,6 +170,7 @@ const driverProfiles: Record<
}, },
sqlserver: { type: "sqlserver", port: 1433, user: "sa", label: "SQL Server", icon: "sqlserver" }, sqlserver: { type: "sqlserver", port: 1433, user: "sa", label: "SQL Server", icon: "sqlserver" },
oracle: { type: "oracle", port: 1521, user: "system", label: "Oracle", icon: "oracle" }, 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" }, "oracle-10g": { type: "oracle", port: 1521, user: "system", label: "Oracle 10g", icon: "oracle" },
elasticsearch: { elasticsearch: {
type: "elasticsearch", type: "elasticsearch",
@@ -458,6 +459,7 @@ const iconTypeMap: Record<string, string> = {
clickhouse: "clickhouse", clickhouse: "clickhouse",
sqlserver: "sqlserver", sqlserver: "sqlserver",
oracle: "oracle", oracle: "oracle",
"oracle-legacy": "oracle",
"oracle-10g": "oracle", "oracle-10g": "oracle",
elasticsearch: "elasticsearch", elasticsearch: "elasticsearch",
mariadb: "mariadb", mariadb: "mariadb",
@@ -1955,14 +1957,17 @@ function openExternalUrl(url: string) {
<div v-if="form.db_type === 'oracle'" class="grid grid-cols-4 items-center gap-4"> <div v-if="form.db_type === 'oracle'" class="grid grid-cols-4 items-center gap-4">
<Label class="text-right text-xs">{{ t("connection.version") }}</Label> <Label class="text-right text-xs">{{ t("connection.version") }}</Label>
<Select <Select
:model-value="selectedType === 'oracle-10g' ? 'oracle-10g' : 'oracle'" :model-value="
selectedType === 'oracle-legacy' || selectedType === 'oracle-10g' ? selectedType : 'oracle'
"
@update:model-value="(val) => applyProfile(String(val), true)" @update:model-value="(val) => applyProfile(String(val), true)"
> >
<SelectTrigger class="col-span-3 h-8 text-sm"> <SelectTrigger class="col-span-3 h-8 text-sm">
<SelectValue /> <SelectValue />
</SelectTrigger> </SelectTrigger>
<SelectContent> <SelectContent>
<SelectItem value="oracle">Oracle 11g+</SelectItem> <SelectItem value="oracle">Oracle 19c+</SelectItem>
<SelectItem value="oracle-legacy">Oracle 11g-19c</SelectItem>
<SelectItem value="oracle-10g">Oracle 10g</SelectItem> <SelectItem value="oracle-10g">Oracle 10g</SelectItem>
</SelectContent> </SelectContent>
</Select> </Select>
@@ -20,7 +20,9 @@ const assetIcons: Record<string, string> = {
elasticsearch: "elasticsearch", elasticsearch: "elasticsearch",
oracle: "oracle", oracle: "oracle",
"oracle-10g": "oracle", "oracle-10g": "oracle",
"oracle-legacy": "oracle",
oracle_10g: "oracle", oracle_10g: "oracle",
oracle_legacy: "oracle",
sqlserver: "sqlserver", sqlserver: "sqlserver",
access: "access.png", access: "access.png",
oceanbase: "oceanbase", oceanbase: "oceanbase",
@@ -12,6 +12,6 @@ export function showAgentDriverInstallHint(
driverProfile?: string, driverProfile?: string,
): boolean { ): boolean {
if (!supportsDriverManagement(dbType)) return false; 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; return drivers.find((driver) => driver.db_type === driverKey)?.installed !== true;
} }
@@ -91,6 +91,7 @@
"skipTcpProbe": true, "skipTcpProbe": true,
"defaultPort": 1521, "defaultPort": 1521,
"driverProfiles": [ "driverProfiles": [
{ "profile": "oracle-legacy", "label": "Oracle Legacy", "agentKey": "oracle-legacy" },
{ "profile": "oracle-10g", "label": "Oracle 10g", "agentKey": "oracle-10g" } { "profile": "oracle-10g", "label": "Oracle 10g", "agentKey": "oracle-10g" }
] ]
}, },
+4 -2
View File
@@ -17,8 +17,10 @@ pub struct AgentDriverProfile {
pub store_visible: bool, pub store_visible: bool,
} }
const ORACLE_PROFILES: &[AgentDriverProfile] = const ORACLE_PROFILES: &[AgentDriverProfile] = &[
&[AgentDriverProfile { profile: "oracle-10g", key: "oracle-10g", label: "Oracle 10g", store_visible: true }]; 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] = &[ const AGENT_CATALOG: &[AgentCatalogEntry] = &[
AgentCatalogEntry { AgentCatalogEntry {
+33 -5
View File
@@ -110,14 +110,22 @@ fn postgres_like_agent_jdbc_connection_string(
} }
pub fn should_retry_oracle_with_10g_driver(config: &ConnectionConfig, err: &str) -> bool { 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 { if config.db_type != DatabaseType::Oracle {
return false; return Vec::new();
}
if config.driver_profile.as_deref() == Some("oracle-10g") {
return false;
} }
let normalized = err.to_lowercase(); 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<ConnectionConfig> { pub fn oracle_alternate_connect_config(config: &ConnectionConfig, err: &str) -> Option<ConnectionConfig> {
@@ -295,6 +303,26 @@ mod tests {
assert!(oracle_alternate_connect_config(&cfg, "ORA-12541: TNS:no listener").is_none()); 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] #[test]
fn oracle_custom_connection_string_skips_alternate_descriptor_retry() { fn oracle_custom_connection_string_skips_alternate_descriptor_retry() {
let mut cfg = config(DatabaseType::Oracle, Some("ORCL")); let mut cfg = config(DatabaseType::Oracle, Some("ORCL"));
+5
View File
@@ -152,7 +152,12 @@ mod tests {
let manager = test_manager("profile-key"); 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-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")); 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; manager.stop_daemon_by_key("oracle-10g").await;
} }
} }
+35 -11
View File
@@ -8,7 +8,7 @@ use mysql_async::Row as MysqlRow;
use crate::agent_connection::{ use crate::agent_connection::{
agent_connect_params, mongo_legacy_error_with_auth_hint, oracle_alternate_connect_config, 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::database_capabilities;
use crate::db; use crate::db;
@@ -333,19 +333,39 @@ impl AppState {
})?; })?;
} else if should_retry_oracle_with_10g_driver(&db_config, &err) { } else if should_retry_oracle_with_10g_driver(&db_config, &err) {
log::warn!( 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, db_config.driver_profile,
err err
); );
let mut fallback_client = let mut fallback_errors = Vec::new();
self.agent_manager.spawn(&db_config.db_type, Some("oracle-10g")).await?; let mut connected_client = None;
fallback_client for profile in oracle_auth_fallback_profiles(&db_config, &err) {
.call_method::<serde_json::Value>(AgentMethod::Connect, connect_params) match self.agent_manager.spawn(&db_config.db_type, Some(profile)).await {
.await Ok(mut fallback_client) => {
.map_err(|fallback_err| { match fallback_client
format!("{err}\n\nFallback with oracle-10g driver failed: {fallback_err}") .call_method::<serde_json::Value>(AgentMethod::Connect, connect_params.clone())
})?; .await
client = fallback_client; {
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 { } else {
return Err(err); return Err(err);
} }
@@ -1090,6 +1110,10 @@ mod tests {
config.driver_profile = Some("oracle-10g".to_string()); 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-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()); config.driver_profile = Some("oracle".to_string());
assert!(!should_retry_oracle_with_10g_driver( assert!(!should_retry_oracle_with_10g_driver(
@@ -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::Gbase, None), Some("gbase"));
assert_eq!(agent_key(&DatabaseType::Access, None), Some("access")); assert_eq!(agent_key(&DatabaseType::Access, None), Some("access"));
assert_eq!(agent_key(&DatabaseType::Oracle, None), Some("oracle")); 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::Oracle, Some("oracle-10g")), Some("oracle-10g"));
assert_eq!(agent_key(&DatabaseType::Postgres, None), None); assert_eq!(agent_key(&DatabaseType::Postgres, None), None);
} }
@@ -28,6 +28,7 @@ test("uses the selected Oracle driver profile for install hints", () => {
"oracle", "oracle",
[ [
{ db_type: "oracle", installed: false }, { db_type: "oracle", installed: false },
{ db_type: "oracle-legacy", installed: false },
{ db_type: "oracle-10g", installed: true }, { db_type: "oracle-10g", installed: true },
], ],
"oracle-10g", "oracle-10g",
@@ -39,11 +40,24 @@ test("uses the selected Oracle driver profile for install hints", () => {
"oracle", "oracle",
[ [
{ db_type: "oracle", installed: true }, { db_type: "oracle", installed: true },
{ db_type: "oracle-legacy", installed: false },
{ db_type: "oracle-10g", installed: false }, { db_type: "oracle-10g", installed: false },
], ],
"oracle", "oracle",
), ),
false, 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); assert.equal(showAgentDriverInstallHint("oracle", [{ db_type: "oracle", installed: false }], "oracle"), true);
}); });
+49 -17
View File
@@ -3,7 +3,7 @@ use tauri::State;
pub use dbx_core::agent_connection::{ pub use dbx_core::agent_connection::{
agent_connect_params, mongo_legacy_error_with_auth_hint, oracle_alternate_connect_config, 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::{ pub use dbx_core::connection::{
connection_url_for_endpoint, expand_tilde, metadata_connection_config, probe_connection_endpoint, 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}") format!("{err}\n\nFallback with alternate Oracle descriptor failed: {alternate_err}")
})?; })?;
} else if should_retry_oracle_with_10g_driver(config, &err) { } else if should_retry_oracle_with_10g_driver(config, &err) {
state let mut fallback_errors = Vec::new();
.agent_manager let mut connected = false;
.call_daemon_method::<serde_json::Value>( for profile in oracle_auth_fallback_profiles(config, &err) {
&config.db_type, match state
Some("oracle-10g"), .agent_manager
AgentMethod::TestConnection, .call_daemon_method::<serde_json::Value>(
connect_params, &config.db_type,
) Some(profile),
.await AgentMethod::TestConnection,
.map_err(|fallback_err| format!("{err}\n\nFallback with oracle-10g driver failed: {fallback_err}"))?; 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 { } else {
return Err(err); return Err(err);
} }
@@ -102,12 +118,28 @@ async fn connect_agent_pool(
format!("{err}\n\nFallback with alternate Oracle descriptor failed: {alternate_err}") format!("{err}\n\nFallback with alternate Oracle descriptor failed: {alternate_err}")
})?; })?;
} else if should_retry_oracle_with_10g_driver(config, &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?; let mut fallback_errors = Vec::new();
fallback_client let mut connected_client = None;
.call_method::<serde_json::Value>(AgentMethod::Connect, connect_params) for profile in oracle_auth_fallback_profiles(config, &err) {
.await match state.agent_manager.spawn(&config.db_type, Some(profile)).await {
.map_err(|fallback_err| format!("{err}\n\nFallback with oracle-10g driver failed: {fallback_err}"))?; Ok(mut fallback_client) => {
client = fallback_client; match fallback_client
.call_method::<serde_json::Value>(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 { } else {
return Err(err); return Err(err);
} }