diff --git a/crates/dbx-core/src/connection.rs b/crates/dbx-core/src/connection.rs index 3feb937682..44d120b4c0 100644 --- a/crates/dbx-core/src/connection.rs +++ b/crates/dbx-core/src/connection.rs @@ -47,6 +47,27 @@ pub struct AppState { pub storage: Storage, } +pub fn metadata_connection_config(config: &ConnectionConfig) -> ConnectionConfig { + let mut db_config = config.clone(); + if matches!(db_config.db_type, DatabaseType::Mysql | DatabaseType::Doris | DatabaseType::StarRocks) { + db_config.database = None; + } + db_config +} + +pub fn database_connection_config(config: &ConnectionConfig, database: Option<&str>) -> ConnectionConfig { + let mut db_config = if database.is_some() { config.clone() } else { metadata_connection_config(config) }; + if let Some(db) = database { + if db_config.db_type != DatabaseType::Oracle + && db_config.db_type != DatabaseType::Dameng + && db_config.db_type != DatabaseType::Gaussdb + { + db_config.database = Some(db.to_string()); + } + } + db_config +} + impl AppState { pub fn new(storage: Storage) -> Self { Self { @@ -103,15 +124,7 @@ impl AppState { let config = configs.get(connection_id).ok_or("Connection config not found")?.clone(); drop(configs); - let mut db_config = config.clone(); - if let Some(db) = database { - if db_config.db_type != DatabaseType::Oracle - && db_config.db_type != DatabaseType::Dameng - && db_config.db_type != DatabaseType::Gaussdb - { - db_config.database = Some(db.to_string()); - } - } + let db_config = database_connection_config(&config, database); let (host, port) = self.connection_host_port(connection_id, &db_config).await?; probe_connection_endpoint(&db_config, &host, port).await?; @@ -313,3 +326,56 @@ async fn detect_ob_oracle_mode(config: &ConnectionConfig, pool: &sqlx::mysql::My _ => MysqlMode::Normal, } } + +#[cfg(test)] +mod tests { + use super::{database_connection_config, metadata_connection_config}; + use crate::models::connection::{ConnectionConfig, DatabaseType}; + + fn mysql_config(database: Option<&str>) -> ConnectionConfig { + ConnectionConfig { + id: "conn".to_string(), + name: "MySQL".to_string(), + db_type: DatabaseType::Mysql, + driver_profile: None, + driver_label: None, + url_params: None, + host: "127.0.0.1".to_string(), + port: 3306, + username: "root".to_string(), + password: "secret".to_string(), + database: database.map(str::to_string), + color: None, + ssh_enabled: false, + ssh_host: String::new(), + ssh_port: 22, + ssh_user: String::new(), + ssh_password: String::new(), + ssh_key_path: String::new(), + ssh_key_passphrase: String::new(), + ssh_expose_lan: false, + ssl: false, + sysdba: false, + connection_string: None, + } + } + + #[test] + fn mysql_metadata_connection_ignores_saved_default_database() { + let config = mysql_config(Some("app")); + + let metadata = metadata_connection_config(&config); + + assert_eq!(metadata.database, None); + assert_eq!(metadata.db_type, DatabaseType::Mysql); + } + + #[test] + fn mysql_database_connection_keeps_requested_database() { + let config = mysql_config(Some("app")); + + let scoped = database_connection_config(&config, Some("analytics")); + + assert_eq!(scoped.database.as_deref(), Some("analytics")); + } +} diff --git a/crates/dbx-core/src/connection_secrets.rs b/crates/dbx-core/src/connection_secrets.rs index dd3f0eb1a0..b6a65c4b85 100644 --- a/crates/dbx-core/src/connection_secrets.rs +++ b/crates/dbx-core/src/connection_secrets.rs @@ -288,7 +288,6 @@ mod tests { ssl: false, sysdba: false, connection_string: None, - sysdba: false, } } diff --git a/src-tauri/src/commands/connection.rs b/src-tauri/src/commands/connection.rs index 8ba0da9192..5683bf4863 100644 --- a/src-tauri/src/commands/connection.rs +++ b/src-tauri/src/commands/connection.rs @@ -2,8 +2,8 @@ use std::sync::Arc; use tauri::State; pub use dbx_core::connection::{ - connection_url_for_endpoint, expand_tilde, probe_connection_endpoint, redacted_connection_url_for_endpoint, - AppState, MysqlMode, PoolKind, + connection_url_for_endpoint, expand_tilde, metadata_connection_config, probe_connection_endpoint, + redacted_connection_url_for_endpoint, AppState, MysqlMode, PoolKind, }; use dbx_core::db; use dbx_core::models::connection::{ConnectionConfig, DatabaseType}; @@ -143,13 +143,14 @@ pub async fn test_connection(state: State<'_, Arc>, config: Connection #[tauri::command] pub async fn connect_db(state: State<'_, Arc>, config: ConnectionConfig) -> Result { let id = config.id.clone(); + let db_config = metadata_connection_config(&config); - let (host, port) = state.connection_host_port(&id, &config).await?; - probe_connection_endpoint(&config, &host, port).await?; - let url = connection_url_for_endpoint(&config, &host, port); + let (host, port) = state.connection_host_port(&id, &db_config).await?; + probe_connection_endpoint(&db_config, &host, port).await?; + let url = connection_url_for_endpoint(&db_config, &host, port); - let pool = match config.db_type { - DatabaseType::Mysql if config.needs_bare_mysql() => { + let pool = match db_config.db_type { + DatabaseType::Mysql if db_config.needs_bare_mysql() => { PoolKind::Mysql(db::mysql::connect_bare(&url).await?, MysqlMode::Bare) } DatabaseType::Mysql => PoolKind::Mysql(db::mysql::connect(&url).await?, MysqlMode::Normal), @@ -157,13 +158,13 @@ pub async fn connect_db(state: State<'_, Arc>, config: ConnectionConfi PoolKind::Mysql(db::mysql::connect_bare(&url).await?, MysqlMode::Bare) } DatabaseType::Postgres | DatabaseType::Redshift => PoolKind::Postgres(db::postgres::connect(&url).await?), - DatabaseType::Sqlite => PoolKind::Sqlite(db::sqlite::connect_path(&expand_tilde(&config.host)).await?), + DatabaseType::Sqlite => PoolKind::Sqlite(db::sqlite::connect_path(&expand_tilde(&db_config.host)).await?), DatabaseType::Redis => { let con = db::redis_driver::connect(&url).await?; PoolKind::Redis(tokio::sync::Mutex::new(con)) } DatabaseType::DuckDb => { - let con = duckdb::Connection::open(&expand_tilde(&config.host)).map_err(|e| e.to_string())?; + let con = duckdb::Connection::open(&expand_tilde(&db_config.host)).map_err(|e| e.to_string())?; PoolKind::DuckDb(std::sync::Arc::new(std::sync::Mutex::new(con))) } DatabaseType::MongoDb => { @@ -172,32 +173,38 @@ pub async fn connect_db(state: State<'_, Arc>, config: ConnectionConfi PoolKind::MongoDb(client) } DatabaseType::ClickHouse => { - let username = if config.username.is_empty() { None } else { Some(config.username.clone()) }; - let password = if config.password.is_empty() { None } else { Some(config.password.clone()) }; + let username = if db_config.username.is_empty() { None } else { Some(db_config.username.clone()) }; + let password = if db_config.password.is_empty() { None } else { Some(db_config.password.clone()) }; let client = db::clickhouse_driver::ChClient::new(&url, username, password); db::clickhouse_driver::test_connection(&client).await?; PoolKind::ClickHouse(client) } DatabaseType::SqlServer => { - let client = - db::sqlserver::connect(&host, port, &config.username, &config.password, config.database.as_deref()) - .await?; + let client = db::sqlserver::connect( + &host, + port, + &db_config.username, + &db_config.password, + db_config.database.as_deref(), + ) + .await?; PoolKind::SqlServer(std::sync::Arc::new(tokio::sync::Mutex::new(client))) } DatabaseType::Oracle => { let client = db::oracle_driver::connect( &host, port, - config.database.as_deref().unwrap_or("ORCL"), - &config.username, - &config.password, - config.sysdba, + db_config.database.as_deref().unwrap_or("ORCL"), + &db_config.username, + &db_config.password, + db_config.sysdba, ) .await?; PoolKind::Oracle(std::sync::Arc::new(tokio::sync::Mutex::new(client))) } DatabaseType::Elasticsearch => { - let client = db::elasticsearch_driver::EsClient::new(&url, Some(&config.username), Some(&config.password)); + let client = + db::elasticsearch_driver::EsClient::new(&url, Some(&db_config.username), Some(&db_config.password)); db::elasticsearch_driver::test_connection(&client).await?; PoolKind::Elasticsearch(client) } @@ -205,9 +212,9 @@ pub async fn connect_db(state: State<'_, Arc>, config: ConnectionConfi let client = db::dm_driver::connect( &host, port, - config.database.as_deref().unwrap_or(""), - &config.username, - &config.password, + db_config.database.as_deref().unwrap_or(""), + &db_config.username, + &db_config.password, ) .await?; PoolKind::Dameng(std::sync::Arc::new(std::sync::Mutex::new(client))) @@ -216,9 +223,9 @@ pub async fn connect_db(state: State<'_, Arc>, config: ConnectionConfi let client = db::gaussdb_driver::connect( &host, port, - config.database.as_deref().unwrap_or(""), - &config.username, - &config.password, + db_config.database.as_deref().unwrap_or(""), + &db_config.username, + &db_config.password, ) .await?; PoolKind::Gaussdb(std::sync::Arc::new(tokio::sync::Mutex::new(client))) diff --git a/src-web/src/routes/connection.rs b/src-web/src/routes/connection.rs index 6d521ededd..575d747c66 100644 --- a/src-web/src/routes/connection.rs +++ b/src-web/src/routes/connection.rs @@ -66,9 +66,9 @@ pub async fn connect_db( app.configs.lock().await.insert(connection_id.clone(), config.clone()); - let pool_key = app.get_or_create_pool(&connection_id, config.database.as_deref()).await.map_err(AppError)?; + app.get_or_create_pool(&connection_id, None).await.map_err(AppError)?; - Ok(Json(pool_key)) + Ok(Json(connection_id)) } pub async fn disconnect_db( diff --git a/src/lib/databaseTree.ts b/src/lib/databaseTree.ts new file mode 100644 index 0000000000..80c6a8ed81 --- /dev/null +++ b/src/lib/databaseTree.ts @@ -0,0 +1,13 @@ +import type { DatabaseInfo, TreeNode } from "@/types/database"; + +export function buildDatabaseTreeNodes(connectionId: string, databases: DatabaseInfo[]): TreeNode[] { + return databases.map((db) => ({ + id: `${connectionId}:${db.name}`, + label: db.name, + type: "database" as const, + connectionId, + database: db.name, + isExpanded: false, + children: [], + })); +} diff --git a/src/stores/connectionStore.ts b/src/stores/connectionStore.ts index 0ef56ca4b5..cf70dd7343 100644 --- a/src/stores/connectionStore.ts +++ b/src/stores/connectionStore.ts @@ -21,6 +21,7 @@ import type { SqlCompletionColumn, SqlCompletionTable } from "@/lib/sqlCompletio import * as api from "@/lib/api"; import { isTauriRuntime } from "@/lib/tauriRuntime"; import { isSchemaAware } from "@/lib/databaseCapabilities"; +import { buildDatabaseTreeNodes } from "@/lib/databaseTree"; const PINNED_TREE_NODES_STORAGE_KEY = "dbx-pinned-tree-nodes"; @@ -361,23 +362,8 @@ export const useConnectionStore = defineStore("connection", () => { node.isLoading = true; try { await ensureConnected(connectionId); - let databases = await api.listDatabases(connectionId); - const config = getConfig(connectionId); - if (config?.database) { - databases = databases.filter((db) => db.name === config.database); - } - setChildren( - node, - databases.map((db) => ({ - id: `${connectionId}:${db.name}`, - label: db.name, - type: "database" as const, - connectionId, - database: db.name, - isExpanded: false, - children: [], - })), - ); + const databases = await api.listDatabases(connectionId); + setChildren(node, buildDatabaseTreeNodes(connectionId, databases)); node.isExpanded = true; } finally { node.isLoading = false; diff --git a/tests/databaseTree.test.ts b/tests/databaseTree.test.ts new file mode 100644 index 0000000000..581e30c1b4 --- /dev/null +++ b/tests/databaseTree.test.ts @@ -0,0 +1,17 @@ +import assert from "node:assert/strict"; +import test from "node:test"; +import { buildDatabaseTreeNodes } from "../src/lib/databaseTree.ts"; + +test("设置默认库后侧边栏数据库树仍保留全部数据库", () => { + const nodes = buildDatabaseTreeNodes("conn-1", [ + { name: "campaign_data" }, + { name: "cms" }, + { name: "mk_campaign" }, + ]); + + assert.deepEqual( + nodes.map((node) => node.database), + ["campaign_data", "cms", "mk_campaign"], + ); + assert.equal(nodes.find((node) => node.database === "mk_campaign")?.id, "conn-1:mk_campaign"); +});