mirror of
https://github.com/t8y2/dbx.git
synced 2026-10-21 04:25:40 +00:00
fix(db): update Oracle and OceanBase agent URLs
This commit is contained in:
@@ -15,8 +15,10 @@ pub fn agent_connect_params(config: &ConnectionConfig, host: &str, port: u16, da
|
||||
};
|
||||
let connection_string = if config.db_type == DatabaseType::MongoDb {
|
||||
config.connection_url_with_host(host, port)
|
||||
} else if matches!(config.db_type, DatabaseType::Oracle | DatabaseType::OceanbaseOracle) {
|
||||
} else if config.db_type == DatabaseType::Oracle {
|
||||
oracle_jdbc_connection_string(config, host, port, database)
|
||||
} else if config.db_type == DatabaseType::OceanbaseOracle {
|
||||
oceanbase_oracle_jdbc_connection_string(config, host, port, database)
|
||||
} else if matches!(config.db_type, DatabaseType::Kingbase | DatabaseType::Highgo | DatabaseType::Vastbase) {
|
||||
postgres_like_agent_jdbc_connection_string(config, host, port, database)
|
||||
} else if config.db_type == DatabaseType::SapHana {
|
||||
@@ -205,6 +207,64 @@ pub fn oracle_error_with_driver_hint(config: &ConnectionConfig, err: &str) -> St
|
||||
)
|
||||
}
|
||||
|
||||
pub fn oracle_alternate_connect_configs(config: &ConnectionConfig, err: &str) -> Vec<ConnectionConfig> {
|
||||
if config.db_type != DatabaseType::Oracle {
|
||||
return Vec::new();
|
||||
}
|
||||
if config.driver_profile.as_deref() == Some("oracle-10g") {
|
||||
return Vec::new();
|
||||
}
|
||||
if config.connection_string.as_deref().is_some_and(|value| !value.trim().is_empty()) {
|
||||
return Vec::new();
|
||||
}
|
||||
if !oracle_listener_error_can_retry(err) {
|
||||
return Vec::new();
|
||||
}
|
||||
|
||||
let database = config.effective_database().unwrap_or("").trim();
|
||||
if database.is_empty() {
|
||||
return Vec::new();
|
||||
}
|
||||
|
||||
let host = config.host.trim();
|
||||
let port = config.port;
|
||||
let current_url = oracle_jdbc_connection_string(config, host, port, database);
|
||||
let service_url = oracle_service_jdbc_url(host, port, database);
|
||||
let sid_url = oracle_sid_jdbc_url(host, port, database);
|
||||
let legacy_service_url = oracle_legacy_service_jdbc_url(host, port, database);
|
||||
let descriptor_service_url = oracle_descriptor_jdbc_url(host, port, database, "SERVICE_NAME");
|
||||
let descriptor_sid_url = oracle_descriptor_jdbc_url(host, port, database, "SID");
|
||||
|
||||
let normalized = err.to_lowercase();
|
||||
let candidates = if normalized.contains("ora-12505") {
|
||||
vec![service_url, descriptor_service_url, legacy_service_url]
|
||||
} else if normalized.contains("ora-12514") {
|
||||
vec![sid_url, descriptor_sid_url, legacy_service_url]
|
||||
} else {
|
||||
match config.oracle_connection_type.as_deref() {
|
||||
Some("sid") => vec![service_url, legacy_service_url, descriptor_sid_url, descriptor_service_url],
|
||||
_ => vec![sid_url, legacy_service_url, descriptor_service_url, descriptor_sid_url],
|
||||
}
|
||||
};
|
||||
|
||||
let mut urls = Vec::new();
|
||||
for url in candidates {
|
||||
if url == current_url || urls.iter().any(|seen| seen == &url) {
|
||||
continue;
|
||||
}
|
||||
urls.push(url);
|
||||
}
|
||||
|
||||
urls.into_iter()
|
||||
.map(|url| {
|
||||
let mut retry = config.clone();
|
||||
retry.oracle_connection_type = None;
|
||||
retry.connection_string = Some(url);
|
||||
retry
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
|
||||
fn oracle_jdbc_connection_string(config: &ConnectionConfig, host: &str, port: u16, database: &str) -> String {
|
||||
if let Some(connection_string) = config.connection_string.as_deref().filter(|value| !value.trim().is_empty()) {
|
||||
let connection_string = connection_string.trim();
|
||||
@@ -220,12 +280,54 @@ fn oracle_jdbc_connection_string(config: &ConnectionConfig, host: &str, port: u1
|
||||
}
|
||||
|
||||
if config.oracle_connection_type.as_deref() == Some("sid") {
|
||||
format!("jdbc:oracle:thin:@{host}:{port}:{database}")
|
||||
oracle_sid_jdbc_url(host, port, database)
|
||||
} else {
|
||||
format!("jdbc:oracle:thin:@//{host}:{port}/{database}")
|
||||
oracle_service_jdbc_url(host, port, database)
|
||||
}
|
||||
}
|
||||
|
||||
fn oracle_listener_error_can_retry(err: &str) -> bool {
|
||||
let normalized = err.to_lowercase();
|
||||
normalized.contains("ora-12505")
|
||||
|| normalized.contains("ora-12514")
|
||||
|| normalized.contains("ora-12541")
|
||||
|| normalized.contains("no listener")
|
||||
|| err.contains("没有监听程序")
|
||||
}
|
||||
|
||||
fn oracle_service_jdbc_url(host: &str, port: u16, database: &str) -> String {
|
||||
format!("jdbc:oracle:thin:@//{host}:{port}/{database}")
|
||||
}
|
||||
|
||||
fn oracle_sid_jdbc_url(host: &str, port: u16, database: &str) -> String {
|
||||
format!("jdbc:oracle:thin:@{host}:{port}:{database}")
|
||||
}
|
||||
|
||||
fn oracle_legacy_service_jdbc_url(host: &str, port: u16, database: &str) -> String {
|
||||
format!("jdbc:oracle:thin:@{host}:{port}/{database}")
|
||||
}
|
||||
|
||||
fn oracle_descriptor_jdbc_url(host: &str, port: u16, database: &str, key: &str) -> String {
|
||||
format!("jdbc:oracle:thin:@(DESCRIPTION=(ADDRESS=(PROTOCOL=TCP)(HOST={host})(PORT={port}))(CONNECT_DATA=({key}={database})))")
|
||||
}
|
||||
|
||||
fn oceanbase_oracle_jdbc_connection_string(config: &ConnectionConfig, host: &str, port: u16, database: &str) -> String {
|
||||
if let Some(connection_string) = config.connection_string.as_deref().filter(|value| !value.trim().is_empty()) {
|
||||
let connection_string = connection_string.trim();
|
||||
if host == config.host && port == config.port {
|
||||
return connection_string.to_string();
|
||||
}
|
||||
return crate::models::connection::rewrite_jdbc_url_host(connection_string, host, port);
|
||||
}
|
||||
|
||||
let database = database.trim();
|
||||
if database.is_empty() {
|
||||
return String::new();
|
||||
}
|
||||
|
||||
append_agent_url_params(format!("jdbc:oceanbase://{host}:{port}/{database}"), config.url_params.as_deref())
|
||||
}
|
||||
|
||||
fn postgres_like_agent_jdbc_connection_string(
|
||||
config: &ConnectionConfig,
|
||||
host: &str,
|
||||
@@ -262,24 +364,35 @@ pub fn oracle_auth_fallback_profiles(config: &ConnectionConfig, err: &str) -> Ve
|
||||
}
|
||||
|
||||
pub fn oracle_alternate_connect_config(config: &ConnectionConfig, err: &str) -> Option<ConnectionConfig> {
|
||||
if config.db_type != DatabaseType::Oracle {
|
||||
return None;
|
||||
}
|
||||
if config.driver_profile.as_deref() == Some("oracle-10g") {
|
||||
return None;
|
||||
}
|
||||
if config.connection_string.as_deref().is_some_and(|value| !value.trim().is_empty()) {
|
||||
return None;
|
||||
}
|
||||
let normalized = err.to_lowercase();
|
||||
if !normalized.contains("ora-12505") && !normalized.contains("ora-12514") {
|
||||
return None;
|
||||
}
|
||||
oracle_alternate_connect_configs(config, err).into_iter().next()
|
||||
}
|
||||
|
||||
let mut retry = config.clone();
|
||||
retry.oracle_connection_type =
|
||||
Some(if config.oracle_connection_type.as_deref() == Some("sid") { "service_name" } else { "sid" }.to_string());
|
||||
Some(retry)
|
||||
pub fn oracle_alternate_connect_config_labels(configs: &[ConnectionConfig]) -> Vec<String> {
|
||||
configs
|
||||
.iter()
|
||||
.map(|config| {
|
||||
config
|
||||
.connection_string
|
||||
.as_deref()
|
||||
.map(oracle_connection_string_label)
|
||||
.unwrap_or_else(|| config.oracle_connection_type.as_deref().unwrap_or("service_name").to_string())
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
|
||||
fn oracle_connection_string_label(connection_string: &str) -> String {
|
||||
let upper = connection_string.to_ascii_uppercase();
|
||||
if upper.contains("(SERVICE_NAME=") {
|
||||
"descriptor service name".to_string()
|
||||
} else if upper.contains("(SID=") {
|
||||
"descriptor SID".to_string()
|
||||
} else if connection_string.starts_with("jdbc:oracle:thin:@//") {
|
||||
"service name".to_string()
|
||||
} else if connection_string.contains(':') && !connection_string.contains('/') {
|
||||
"SID".to_string()
|
||||
} else {
|
||||
"legacy service name".to_string()
|
||||
}
|
||||
}
|
||||
|
||||
fn sap_hana_jdbc_connection_string(config: &ConnectionConfig, host: &str, port: u16, database: &str) -> String {
|
||||
@@ -575,7 +688,7 @@ mod tests {
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn oceanbase_oracle_uses_oracle_jdbc_connection_string_for_agent_protocol() {
|
||||
fn oceanbase_oracle_uses_oceanbase_jdbc_connection_string_for_agent_protocol() {
|
||||
let mut cfg = config(DatabaseType::OceanbaseOracle, Some("sys"));
|
||||
cfg.host = "oceanbase.example.com".to_string();
|
||||
cfg.port = 2881;
|
||||
@@ -584,7 +697,19 @@ mod tests {
|
||||
|
||||
assert_eq!(params["database"], "sys");
|
||||
assert_eq!(params["sysdba"], false);
|
||||
assert_eq!(params["connection_string"], "jdbc:oracle:thin:@//oceanbase.example.com:2881/sys");
|
||||
assert_eq!(params["connection_string"], "jdbc:oceanbase://oceanbase.example.com:2881/sys");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn oceanbase_oracle_jdbc_url_appends_params_and_rewrites_forwarded_host() {
|
||||
let mut cfg = config(DatabaseType::OceanbaseOracle, Some("sys"));
|
||||
cfg.host = "oceanbase.example.com".to_string();
|
||||
cfg.port = 2881;
|
||||
cfg.url_params = Some("useSSL=false".to_string());
|
||||
|
||||
let params = agent_connect_params(&cfg, "127.0.0.1", 12881, "sys");
|
||||
|
||||
assert_eq!(params["connection_string"], "jdbc:oceanbase://127.0.0.1:12881/sys?useSSL=false");
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -631,9 +756,9 @@ mod tests {
|
||||
|
||||
let retry = oracle_alternate_connect_config(&cfg, "ORA-12514: listener does not know service").unwrap();
|
||||
|
||||
assert_eq!(retry.oracle_connection_type.as_deref(), Some("sid"));
|
||||
assert_eq!(retry.connection_string.as_deref(), Some("jdbc:oracle:thin:@127.0.0.1:3306:ORCL"));
|
||||
assert!(oracle_alternate_connect_config(&retry, "ORA-01017: invalid username/password").is_none());
|
||||
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_some());
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -666,6 +791,28 @@ mod tests {
|
||||
assert!(oracle_alternate_connect_config(&cfg, "ORA-12514: listener does not know service").is_none());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn oracle_no_listener_errors_try_common_jdbc_url_variants() {
|
||||
let mut cfg = config(DatabaseType::Oracle, Some("ORCL"));
|
||||
cfg.host = "oracle.example.com".to_string();
|
||||
cfg.port = 1521;
|
||||
cfg.driver_profile = Some("oracle".to_string());
|
||||
cfg.oracle_connection_type = Some("service_name".to_string());
|
||||
|
||||
let retries = oracle_alternate_connect_configs(&cfg, "ORA-12541: TNS:no listener");
|
||||
let urls: Vec<_> = retries.iter().filter_map(|retry| retry.connection_string.as_deref()).collect();
|
||||
|
||||
assert_eq!(
|
||||
urls,
|
||||
vec![
|
||||
"jdbc:oracle:thin:@oracle.example.com:1521:ORCL",
|
||||
"jdbc:oracle:thin:@oracle.example.com:1521/ORCL",
|
||||
"jdbc:oracle:thin:@(DESCRIPTION=(ADDRESS=(PROTOCOL=TCP)(HOST=oracle.example.com)(PORT=1521))(CONNECT_DATA=(SERVICE_NAME=ORCL)))",
|
||||
"jdbc:oracle:thin:@(DESCRIPTION=(ADDRESS=(PROTOCOL=TCP)(HOST=oracle.example.com)(PORT=1521))(CONNECT_DATA=(SID=ORCL)))",
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn sap_hana_url_includes_selected_database_and_params() {
|
||||
let mut cfg = config(DatabaseType::SapHana, Some("TENANT1"));
|
||||
|
||||
@@ -8,8 +8,8 @@ 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, oracle_auth_fallback_profiles, oracle_error_with_driver_hint,
|
||||
should_retry_oracle_with_10g_driver,
|
||||
oracle_alternate_connect_config_labels, oracle_alternate_connect_configs, oracle_auth_fallback_profiles,
|
||||
oracle_error_with_driver_hint, should_retry_oracle_with_10g_driver,
|
||||
};
|
||||
use crate::agent_manager::{JavaRuntimeMode, DEFAULT_JRE_KEY};
|
||||
use crate::database_capabilities;
|
||||
@@ -553,27 +553,48 @@ impl AppState {
|
||||
let connect_result =
|
||||
client.call_method::<serde_json::Value>(AgentMethod::Connect, connect_params.clone()).await;
|
||||
if let Err(err) = connect_result {
|
||||
if let Some(alternate_config) = oracle_alternate_connect_config(&db_config, &err) {
|
||||
let alternate_configs = oracle_alternate_connect_configs(&db_config, &err);
|
||||
if !alternate_configs.is_empty() {
|
||||
log::warn!(
|
||||
"Oracle connect failed with {:?} descriptor: {}. Retrying with {:?} descriptor.",
|
||||
"Oracle connect failed with {:?} descriptor: {}. Retrying with Oracle JDBC URL variants: {:?}.",
|
||||
db_config.oracle_connection_type,
|
||||
err,
|
||||
alternate_config.oracle_connection_type
|
||||
oracle_alternate_connect_config_labels(&alternate_configs)
|
||||
);
|
||||
client
|
||||
.call_method::<serde_json::Value>(
|
||||
AgentMethod::Connect,
|
||||
agent_connect_params(
|
||||
&alternate_config,
|
||||
&host,
|
||||
port,
|
||||
alternate_config.effective_database().unwrap_or(""),
|
||||
),
|
||||
)
|
||||
.await
|
||||
.map_err(|alternate_err| {
|
||||
format!("{err}\n\nFallback with alternate Oracle descriptor failed: {alternate_err}")
|
||||
})?;
|
||||
let mut fallback_errors = Vec::new();
|
||||
let mut connected = false;
|
||||
for alternate_config in alternate_configs {
|
||||
let label = oracle_alternate_connect_config_labels(std::slice::from_ref(&alternate_config))
|
||||
.into_iter()
|
||||
.next()
|
||||
.unwrap_or_else(|| "alternate".to_string());
|
||||
match client
|
||||
.call_method::<serde_json::Value>(
|
||||
AgentMethod::Connect,
|
||||
agent_connect_params(
|
||||
&alternate_config,
|
||||
&host,
|
||||
port,
|
||||
alternate_config.effective_database().unwrap_or(""),
|
||||
),
|
||||
)
|
||||
.await
|
||||
{
|
||||
Ok(_) => {
|
||||
connected = true;
|
||||
break;
|
||||
}
|
||||
Err(alternate_err) => {
|
||||
fallback_errors.push(format!("{label}: {alternate_err}"));
|
||||
}
|
||||
}
|
||||
}
|
||||
if !connected {
|
||||
return Err(format!(
|
||||
"{err}\n\nFallback with alternate Oracle JDBC URLs failed: {}",
|
||||
fallback_errors.join("\n")
|
||||
));
|
||||
}
|
||||
} else if should_retry_oracle_with_10g_driver(&db_config, &err) {
|
||||
log::warn!(
|
||||
"Oracle connect failed with profile {:?}: {}. Retrying with legacy Oracle profiles.",
|
||||
@@ -1480,16 +1501,18 @@ mod tests {
|
||||
)
|
||||
.expect("listener errors should allow alternate descriptor retry");
|
||||
assert_eq!(retry.driver_profile.as_deref(), Some("oracle"));
|
||||
assert_eq!(retry.oracle_connection_type.as_deref(), Some("sid"));
|
||||
assert_eq!(retry.connection_string.as_deref(), Some("jdbc:oracle:thin:@127.0.0.1:3306:ORCL"));
|
||||
|
||||
let mut sid_config = config.clone();
|
||||
sid_config.oracle_connection_type = Some("sid".to_string());
|
||||
let service_retry = oracle_alternate_connect_config(
|
||||
&retry,
|
||||
&sid_config,
|
||||
"Agent RPC error (-1): ORA-12505: listener does not currently know of SID given",
|
||||
)
|
||||
.expect("SID listener errors should allow service-name retry");
|
||||
assert_eq!(service_retry.oracle_connection_type.as_deref(), Some("service_name"));
|
||||
assert_eq!(service_retry.connection_string.as_deref(), Some("jdbc:oracle:thin:@//127.0.0.1:3306/ORCL"));
|
||||
|
||||
assert!(oracle_alternate_connect_config(&config, "ORA-12541: TNS:no listener").is_none());
|
||||
assert!(oracle_alternate_connect_config(&config, "ORA-12541: TNS:no listener").is_some());
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
||||
Reference in New Issue
Block a user