diff --git a/crates/dbx-core/src/connection.rs b/crates/dbx-core/src/connection.rs index 11f2f9e60..b47c4272f 100644 --- a/crates/dbx-core/src/connection.rs +++ b/crates/dbx-core/src/connection.rs @@ -239,6 +239,7 @@ impl AppState { | DatabaseType::Kylin | DatabaseType::Sundb | DatabaseType::Tdengine + | DatabaseType::Access | DatabaseType::Gaussdb => { let mut client = self.agent_manager.spawn(&db_config.db_type, db_config.driver_profile.as_deref()).await?; @@ -437,6 +438,7 @@ pub fn agent_connect_params(config: &ConnectionConfig, host: &str, port: u16, da "username": config.username, "password": config.password, "url_params": config.url_params.as_deref().unwrap_or(""), + "connection_string": config.connection_string.as_deref().unwrap_or(""), }) } diff --git a/crates/dbx-core/src/database_capabilities.rs b/crates/dbx-core/src/database_capabilities.rs index 49a9c8ea7..bdca40517 100644 --- a/crates/dbx-core/src/database_capabilities.rs +++ b/crates/dbx-core/src/database_capabilities.rs @@ -25,6 +25,7 @@ pub fn agent_key(db_type: &DatabaseType, driver_profile: Option<&str>) -> Option DatabaseType::Gaussdb => Some("gaussdb"), DatabaseType::MongoDb => Some("mongodb"), DatabaseType::Tdengine => Some("tdengine"), + DatabaseType::Access => Some("access"), _ => None, } } @@ -44,6 +45,7 @@ pub fn is_single_connection_pool(db_type: &DatabaseType) -> bool { | DatabaseType::Highgo | DatabaseType::Vastbase | DatabaseType::Goldendb + | DatabaseType::Access | DatabaseType::Jdbc ) } diff --git a/crates/dbx-core/src/models/connection.rs b/crates/dbx-core/src/models/connection.rs index e78482aeb..112e881d4 100644 --- a/crates/dbx-core/src/models/connection.rs +++ b/crates/dbx-core/src/models/connection.rs @@ -120,6 +120,7 @@ pub enum DatabaseType { Vastbase, Goldendb, Gaussdb, + Access, #[serde(rename = "h2")] H2, Snowflake, @@ -213,6 +214,7 @@ impl ConnectionConfig { DatabaseType::Sqlite | DatabaseType::DuckDb => { format!("{}?mode=rwc", self.host) } + DatabaseType::Access => self.host.clone(), DatabaseType::Redis => { let scheme = if self.ssl { "rediss" } else { "redis" }; format!("{scheme}://{host}:{port}/") @@ -284,6 +286,7 @@ impl ConnectionConfig { DatabaseType::Sqlite | DatabaseType::DuckDb => { format!("{}?mode=rwc", self.host) } + DatabaseType::Access => self.host.clone(), DatabaseType::Redis => { let scheme = if self.ssl { "rediss" } else { "redis" }; if self.username.is_empty() && self.password.is_empty() { diff --git a/crates/dbx-core/tests/database_capabilities.rs b/crates/dbx-core/tests/database_capabilities.rs index 1d1ffbb96..4337f3b12 100644 --- a/crates/dbx-core/tests/database_capabilities.rs +++ b/crates/dbx-core/tests/database_capabilities.rs @@ -9,6 +9,7 @@ fn maps_agent_database_types_to_driver_keys() { assert_eq!(agent_key(&DatabaseType::Hive, None), Some("hive")); assert_eq!(agent_key(&DatabaseType::Gaussdb, None), Some("gaussdb")); assert_eq!(agent_key(&DatabaseType::Tdengine, None), Some("tdengine")); + 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-10g")), Some("oracle-10g")); assert_eq!(agent_key(&DatabaseType::Postgres, None), None); @@ -20,6 +21,7 @@ fn classifies_agent_database_types() { assert!(is_agent_type(&DatabaseType::Trino)); assert!(is_agent_type(&DatabaseType::Hive)); assert!(is_agent_type(&DatabaseType::Tdengine)); + assert!(is_agent_type(&DatabaseType::Access)); assert!(!is_agent_type(&DatabaseType::Mysql)); assert!(!is_agent_type(&DatabaseType::Jdbc)); } @@ -30,6 +32,7 @@ fn identifies_single_connection_pool_types() { assert!(is_single_connection_pool(&DatabaseType::DuckDb)); assert!(is_single_connection_pool(&DatabaseType::Oracle)); assert!(is_single_connection_pool(&DatabaseType::Dameng)); + assert!(is_single_connection_pool(&DatabaseType::Access)); assert!(is_single_connection_pool(&DatabaseType::Jdbc)); assert!(!is_single_connection_pool(&DatabaseType::Trino)); assert!(!is_single_connection_pool(&DatabaseType::Postgres)); @@ -49,6 +52,7 @@ fn skips_tcp_probe_for_local_file_plugin_and_agent_types() { assert!(skips_tcp_probe(&DatabaseType::Sqlite)); assert!(skips_tcp_probe(&DatabaseType::DuckDb)); assert!(skips_tcp_probe(&DatabaseType::Jdbc)); + assert!(skips_tcp_probe(&DatabaseType::Access)); assert!(skips_tcp_probe(&DatabaseType::Trino)); assert!(skips_tcp_probe(&DatabaseType::Oracle)); assert!(skips_tcp_probe(&DatabaseType::Tdengine)); diff --git a/public/icons/database/access.png b/public/icons/database/access.png new file mode 100644 index 000000000..44138a10e Binary files /dev/null and b/public/icons/database/access.png differ diff --git a/public/icons/database/goldendb.png b/public/icons/database/goldendb.png new file mode 100644 index 000000000..b635a723b Binary files /dev/null and b/public/icons/database/goldendb.png differ diff --git a/public/icons/database/vastbase.png b/public/icons/database/vastbase.png new file mode 100644 index 000000000..1007b05fa Binary files /dev/null and b/public/icons/database/vastbase.png differ diff --git a/src-tauri/src/commands/agents.rs b/src-tauri/src/commands/agents.rs index 2dfaef853..00cc5cd33 100644 --- a/src-tauri/src/commands/agents.rs +++ b/src-tauri/src/commands/agents.rs @@ -21,6 +21,7 @@ const AGENT_TYPES: &[(&str, &str)] = &[ ("highgo", "瀚高 HighGo"), ("vastbase", "Vastbase"), ("goldendb", "GoldenDB"), + ("access", "Microsoft Access"), ("oracle", "Oracle"), ("oracle-10g", "Oracle 10g"), ("h2", "H2"), @@ -104,13 +105,14 @@ mod tests { use dbx_core::agent_manager::AgentManager; #[test] - fn built_in_agent_list_includes_tdengine() { + fn built_in_agent_list_includes_access_and_tdengine() { let dir = std::env::temp_dir().join(format!("dbx-agent-list-test-{}", uuid::Uuid::new_v4())); let manager = AgentManager::new_with_base_dir(dir.clone()); let agents = build_agent_list(&manager, None); assert!(agents.iter().any(|agent| agent.db_type == "tdengine" && agent.label == "TDengine")); + assert!(agents.iter().any(|agent| agent.db_type == "access" && agent.label == "Microsoft Access")); let _ = std::fs::remove_dir_all(dir); } diff --git a/src/components/connection/ConnectionDialog.vue b/src/components/connection/ConnectionDialog.vue index 59f555b6d..2c30a2aa3 100644 --- a/src/components/connection/ConnectionDialog.vue +++ b/src/components/connection/ConnectionDialog.vue @@ -130,6 +130,7 @@ const driverProfiles: Record< redis: { type: "redis", port: 6379, user: "", label: "Redis", icon: "redis" }, sqlite: { type: "sqlite", port: 0, user: "", label: "SQLite", icon: "sqlite" }, duckdb: { type: "duckdb", port: 0, user: "", label: "DuckDB", icon: "duckdb" }, + access: { type: "access", port: 0, user: "", label: "Microsoft Access", icon: "access" }, mongodb: { type: "mongodb", port: 27017, user: "", label: "MongoDB", icon: "mongodb" }, clickhouse: { type: "clickhouse", @@ -248,7 +249,7 @@ function applyProfile(val: string, preserveConnectionFields = false) { form.value.port = profile.port; form.value.username = profile.user; form.value.url_params = profile.urlParams || ""; - if (profile.type === "sqlite" || profile.type === "duckdb") { + if (profile.type === "sqlite" || profile.type === "duckdb" || profile.type === "access") { form.value.host = ""; } if (profile.type === "jdbc") { @@ -358,6 +359,7 @@ const iconTypeMap: Record = { mysql: "mysql", postgres: "postgres", sqlite: "sqlite", + access: "access", redis: "redis", mongodb: "mongodb", duckdb: "duckdb", @@ -402,6 +404,7 @@ const dbOptions = [ { value: "mysql", label: "MySQL" }, { value: "postgres", label: "PostgreSQL" }, { value: "sqlite", label: "SQLite" }, + { value: "access", label: "Microsoft Access" }, { value: "redis", label: "Redis" }, { value: "mongodb", label: "MongoDB" }, { value: "duckdb", label: "DuckDB" }, @@ -467,8 +470,15 @@ const selectedDbIcon = computed(() => iconTypeMap[selectedType.value] || selecte const isJdbcConnection = computed(() => form.value.db_type === "jdbc"); const connectionUrlPlaceholder = computed(() => getUrlPlaceholder(form.value.db_type)); -const canUseSsh = computed(() => form.value.db_type !== "sqlite"); -const canUseProxy = computed(() => form.value.db_type !== "sqlite" && form.value.db_type !== "duckdb"); +const filePathPlaceholder = computed(() => { + if (form.value.db_type === "duckdb") return "/path/to/database.duckdb"; + if (form.value.db_type === "access") return "/path/to/database.accdb"; + return "/path/to/database.db"; +}); +const canUseSsh = computed(() => form.value.db_type !== "sqlite" && form.value.db_type !== "access"); +const canUseProxy = computed( + () => form.value.db_type !== "sqlite" && form.value.db_type !== "duckdb" && form.value.db_type !== "access", +); const shouldShowAgentDriverInstallHint = computed(() => showAgentDriverInstallHint(form.value.db_type, agentDrivers.value), ); @@ -677,7 +687,9 @@ async function browseDbFilePath() { const filters = form.value.db_type === "duckdb" ? [{ name: "DuckDB", extensions: ["duckdb", "db"] }] - : [{ name: "SQLite", extensions: ["db", "sqlite", "sqlite3"] }]; + : form.value.db_type === "access" + ? [{ name: "Microsoft Access", extensions: ["accdb", "mdb"] }] + : [{ name: "SQLite", extensions: ["db", "sqlite", "sqlite3"] }]; const selected = await open({ title: "Select Database File", multiple: false, @@ -1040,12 +1052,14 @@ function openExternalUrl(url: string) { - -