fix(mongodb): support legacy driver fallback
This commit is contained in:
1 parent
090f073206
commit
7bb869d398
13 files changed
+205
-72
No files matched your search
@@ -27,6 +27,13 @@ const GBASE_PROFILES: &[AgentDriverProfile] = &[
|
||||
AgentDriverProfile { profile: "gbase8a", key: "gbase8a", label: "GBase 8a", store_visible: true },
|
||||
];
|
||||
|
||||
const MONGODB_PROFILES: &[AgentDriverProfile] = &[AgentDriverProfile {
|
||||
profile: "mongodb-legacy",
|
||||
key: "mongodb",
|
||||
label: "MongoDB (Legacy)",
|
||||
store_visible: false,
|
||||
}];
|
||||
|
||||
const AGENT_CATALOG: &[AgentCatalogEntry] = &[
|
||||
AgentCatalogEntry {
|
||||
db_type: DatabaseType::Dameng,
|
||||
@@ -246,7 +253,7 @@ const AGENT_CATALOG: &[AgentCatalogEntry] = &[
|
||||
key: "mongodb",
|
||||
label: "MongoDB (Legacy)",
|
||||
store_visible: true,
|
||||
profiles: &[],
|
||||
profiles: MONGODB_PROFILES,
|
||||
},
|
||||
AgentCatalogEntry {
|
||||
db_type: DatabaseType::Iris,
|
||||
|
||||
@@ -190,6 +190,30 @@ pub fn mongo_legacy_error_with_auth_hint(err: &str) -> String {
|
||||
)
|
||||
}
|
||||
|
||||
pub fn mongo_uses_legacy_driver(config: &ConnectionConfig) -> bool {
|
||||
config.driver_profile.as_deref().is_some_and(|profile| {
|
||||
profile.eq_ignore_ascii_case("mongodb-legacy")
|
||||
|| profile.eq_ignore_ascii_case("mongodb_legacy")
|
||||
|| profile.eq_ignore_ascii_case("legacy")
|
||||
})
|
||||
}
|
||||
|
||||
pub fn should_retry_mongo_with_legacy_driver(err: &str) -> bool {
|
||||
let normalized = err.to_lowercase();
|
||||
if normalized.contains("wire version") {
|
||||
return true;
|
||||
}
|
||||
|
||||
let looks_like_handshake_io_error = normalized.contains("unexpected end of file")
|
||||
|| normalized.contains("connection reset by peer")
|
||||
|| normalized.contains("broken pipe");
|
||||
looks_like_handshake_io_error
|
||||
&& (normalized.contains("server selection timeout")
|
||||
|| normalized.contains("no available servers")
|
||||
|| normalized.contains("topology:")
|
||||
|| normalized.contains("i/o error"))
|
||||
}
|
||||
|
||||
pub fn oracle_error_with_driver_hint(config: &ConnectionConfig, err: &str) -> String {
|
||||
if config.db_type != DatabaseType::Oracle {
|
||||
return err.to_string();
|
||||
|
||||
@@ -8,8 +8,9 @@ use mysql_async::Row as MysqlRow;
|
||||
|
||||
use crate::agent_connection::{
|
||||
agent_connect_params, h2_file_path_from_jdbc_url, is_h2_file_connection, mongo_legacy_error_with_auth_hint,
|
||||
oracle_alternate_connect_config_labels, oracle_alternate_connect_configs, oracle_auth_fallback_profiles,
|
||||
oracle_error_with_driver_hint, should_retry_oracle_with_10g_driver,
|
||||
mongo_uses_legacy_driver, oracle_alternate_connect_config_labels, oracle_alternate_connect_configs,
|
||||
oracle_auth_fallback_profiles, oracle_error_with_driver_hint, should_retry_mongo_with_legacy_driver,
|
||||
should_retry_oracle_with_10g_driver,
|
||||
};
|
||||
use crate::agent_manager::{JavaRuntimeMode, DEFAULT_JRE_KEY};
|
||||
use crate::database_capabilities;
|
||||
@@ -437,35 +438,49 @@ impl AppState {
|
||||
return Err("DuckDB support is not compiled in this build. Rebuild with default features.".to_string());
|
||||
}
|
||||
DatabaseType::MongoDb => {
|
||||
let native_err = match db::mongo_driver::connect(&url, connect_timeout, idle_timeout).await {
|
||||
Ok(client) => match db::mongo_driver::test_connection(
|
||||
&client,
|
||||
connect_timeout,
|
||||
db_config.effective_database(),
|
||||
)
|
||||
.await
|
||||
{
|
||||
Ok(()) => {
|
||||
let mut conns = self.connections.write().await;
|
||||
// Re-check: another task may have created the pool while we were connecting.
|
||||
if conns.contains_key(&pool_key) {
|
||||
return Ok(pool_key);
|
||||
}
|
||||
conns.insert(pool_key.clone(), PoolKind::MongoDb(client));
|
||||
return Ok(pool_key);
|
||||
}
|
||||
Err(e) => e,
|
||||
},
|
||||
Err(e) => e,
|
||||
};
|
||||
if native_err.contains("wire version") {
|
||||
log::info!("Native MongoDB driver failed ({native_err}), falling back to agent driver");
|
||||
if mongo_uses_legacy_driver(&db_config) {
|
||||
log::info!("Using configured MongoDB legacy driver for connection_id={connection_id}");
|
||||
let connect_params = serde_json::json!({ "connection": agent_connect_params(&db_config, &host, port, db_config.effective_database().unwrap_or("")) });
|
||||
let mut client = self.agent_manager.spawn(&DatabaseType::MongoDb, None).await?;
|
||||
let mut client = self.agent_manager.spawn(&DatabaseType::MongoDb, Some("mongodb-legacy")).await?;
|
||||
client.connect(connect_params).await.map_err(|err| mongo_legacy_error_with_auth_hint(&err))?;
|
||||
PoolKind::Agent(Arc::new(tokio::sync::Mutex::new(client)))
|
||||
} else {
|
||||
return Err(native_err);
|
||||
let native_err = match db::mongo_driver::connect(&url, connect_timeout, idle_timeout).await {
|
||||
Ok(client) => match db::mongo_driver::test_connection(
|
||||
&client,
|
||||
connect_timeout,
|
||||
db_config.effective_database(),
|
||||
)
|
||||
.await
|
||||
{
|
||||
Ok(()) => {
|
||||
let mut conns = self.connections.write().await;
|
||||
// Re-check: another task may have created the pool while we were connecting.
|
||||
if conns.contains_key(&pool_key) {
|
||||
return Ok(pool_key);
|
||||
}
|
||||
conns.insert(pool_key.clone(), PoolKind::MongoDb(client));
|
||||
return Ok(pool_key);
|
||||
}
|
||||
Err(e) => e,
|
||||
},
|
||||
Err(e) => e,
|
||||
};
|
||||
if should_retry_mongo_with_legacy_driver(&native_err) {
|
||||
log::info!("Native MongoDB driver failed ({native_err}), falling back to agent driver");
|
||||
let connect_params = serde_json::json!({ "connection": agent_connect_params(&db_config, &host, port, db_config.effective_database().unwrap_or("")) });
|
||||
let mut client =
|
||||
self.agent_manager.spawn(&DatabaseType::MongoDb, Some("mongodb-legacy")).await?;
|
||||
client.connect(connect_params).await.map_err(|err| {
|
||||
format!(
|
||||
"{native_err}\n\nFallback with MongoDB (Legacy) driver failed: {}",
|
||||
mongo_legacy_error_with_auth_hint(&err)
|
||||
)
|
||||
})?;
|
||||
PoolKind::Agent(Arc::new(tokio::sync::Mutex::new(client)))
|
||||
} else {
|
||||
return Err(native_err);
|
||||
}
|
||||
}
|
||||
}
|
||||
DatabaseType::ClickHouse => {
|
||||
@@ -1297,8 +1312,8 @@ mod tests {
|
||||
AppState, PoolKind,
|
||||
};
|
||||
use crate::agent_connection::{
|
||||
agent_connect_params, mongo_legacy_error_with_auth_hint, oracle_alternate_connect_config,
|
||||
should_retry_oracle_with_10g_driver,
|
||||
agent_connect_params, mongo_legacy_error_with_auth_hint, mongo_uses_legacy_driver,
|
||||
oracle_alternate_connect_config, should_retry_mongo_with_legacy_driver, should_retry_oracle_with_10g_driver,
|
||||
};
|
||||
use crate::agent_manager::{AgentState, JavaRuntimeConfig, JavaRuntimeMode, DEFAULT_JRE_KEY};
|
||||
use crate::db;
|
||||
@@ -1433,6 +1448,19 @@ mod tests {
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn mongo_legacy_retry_covers_old_server_handshake_eof() {
|
||||
let err = r#"MongoDB connection failed: Kind: Server selection timeout: No available servers. Topology: { Type: Unknown, Servers: [ { Address: db.example.com:27017, Type: Unknown, Error: Kind: I/O error: unexpected end of file } ] }"#;
|
||||
|
||||
assert!(mongo_uses_legacy_driver(&ConnectionConfig {
|
||||
driver_profile: Some("mongodb-legacy".to_string()),
|
||||
..mysql_config(None)
|
||||
}));
|
||||
assert!(should_retry_mongo_with_legacy_driver(err));
|
||||
assert!(should_retry_mongo_with_legacy_driver("server reports wire version 5, but this driver requires 8"));
|
||||
assert!(!should_retry_mongo_with_legacy_driver("Authentication failed."));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn agent_connect_params_build_oracle_service_connection_string() {
|
||||
let mut config = mysql_config(Some("ORCLPDB1"));
|
||||
|
||||
Reference in new issue
Block a user