From af029daf574e710d1071eebccb0843a0741f5e2c Mon Sep 17 00:00:00 2001 From: t8y2 <1156263951@qq.com> Date: Sat, 9 May 2026 15:53:06 +0800 Subject: [PATCH] fix(db): normalize default database handling --- crates/dbx-core/src/connection.rs | 31 ++++++-- crates/dbx-core/src/db/gaussdb_driver.rs | 26 +++++++ crates/dbx-core/src/models/connection.rs | 75 ++++++++++++++++--- .../connection/ConnectionDialog.vue | 34 ++++++--- src/i18n/locales/en.ts | 2 + src/i18n/locales/zh-CN.ts | 2 + src/lib/databaseCapabilities.ts | 2 +- src/stores/connectionStore.ts | 2 + 8 files changed, 144 insertions(+), 30 deletions(-) diff --git a/crates/dbx-core/src/connection.rs b/crates/dbx-core/src/connection.rs index 44d120b4c..67a98da85 100644 --- a/crates/dbx-core/src/connection.rs +++ b/crates/dbx-core/src/connection.rs @@ -58,10 +58,7 @@ pub fn metadata_connection_config(config: &ConnectionConfig) -> ConnectionConfig 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 - { + if db_config.db_type != DatabaseType::Oracle && db_config.db_type != DatabaseType::Dameng { db_config.database = Some(db.to_string()); } } @@ -90,8 +87,7 @@ impl AppState { return Ok(connection_id.to_string()); } - let is_single_conn = - matches!(db_type, Some(DatabaseType::Oracle) | Some(DatabaseType::Dameng) | Some(DatabaseType::Gaussdb)); + let is_single_conn = matches!(db_type, Some(DatabaseType::Oracle) | Some(DatabaseType::Dameng)); let pool_key = if is_single_conn { connection_id.to_string() } else { @@ -207,7 +203,7 @@ impl AppState { let client = db::gaussdb_driver::connect( &host, port, - db_config.database.as_deref().unwrap_or(""), + db_config.effective_database().unwrap_or("postgres"), &db_config.username, &db_config.password, ) @@ -272,7 +268,6 @@ impl AppState { c.db_type == DatabaseType::Oracle || c.db_type == DatabaseType::Elasticsearch || c.db_type == DatabaseType::Dameng - || c.db_type == DatabaseType::Gaussdb }) .unwrap_or(false) }; @@ -378,4 +373,24 @@ mod tests { assert_eq!(scoped.database.as_deref(), Some("analytics")); } + + #[test] + fn gaussdb_database_connection_keeps_requested_database() { + let mut config = mysql_config(Some("postgres")); + config.db_type = DatabaseType::Gaussdb; + + let scoped = database_connection_config(&config, Some("analytics")); + + assert_eq!(scoped.database.as_deref(), Some("analytics")); + } + + #[test] + fn oracle_database_connection_ignores_requested_database() { + let mut config = mysql_config(Some("ORCL")); + config.db_type = DatabaseType::Oracle; + + let scoped = database_connection_config(&config, Some("analytics")); + + assert_eq!(scoped.database.as_deref(), Some("ORCL")); + } } diff --git a/crates/dbx-core/src/db/gaussdb_driver.rs b/crates/dbx-core/src/db/gaussdb_driver.rs index 1fca3a961..e473fc2d0 100644 --- a/crates/dbx-core/src/db/gaussdb_driver.rs +++ b/crates/dbx-core/src/db/gaussdb_driver.rs @@ -30,6 +30,7 @@ impl GaussdbClient { } pub async fn connect(host: &str, port: u16, database: &str, user: &str, pass: &str) -> Result { + let database = normalize_database(database); let dsn = format!("host={host} port={port} user={user} password={pass} dbname={database}"); let result = tokio::time::timeout(std::time::Duration::from_secs(CONNECTION_TIMEOUT_SECS), async { @@ -41,6 +42,15 @@ pub async fn connect(host: &str, port: u16, database: &str, user: &str, pass: &s result.map(|client| GaussdbClient { client }) } +fn normalize_database(database: &str) -> &str { + let database = database.trim(); + if database.is_empty() { + "postgres" + } else { + database + } +} + pub async fn list_databases(client: &mut GaussdbClient) -> Result, String> { let rows = client .query_single_column("SELECT datname FROM pg_database WHERE datistemplate = false ORDER BY datname") @@ -276,3 +286,19 @@ pub async fn execute_query(client: &mut GaussdbClient, sql: &str) -> Result Option<&str> { + self.database + .as_deref() + .map(str::trim) + .filter(|database| !database.is_empty()) + .or_else(|| self.default_database()) + } + + fn default_database(&self) -> Option<&'static str> { + match self.db_type { + DatabaseType::Postgres => match self.driver_profile.as_deref() { + Some("cockroachdb") => Some("defaultdb"), + _ => Some("postgres"), + }, + DatabaseType::Redshift => Some("dev"), + DatabaseType::Gaussdb => Some("postgres"), + _ => None, + } + } + pub fn needs_bare_mysql(&self) -> bool { matches!(self.db_type, DatabaseType::Doris | DatabaseType::StarRocks) || self @@ -94,12 +114,7 @@ impl ConnectionConfig { pub fn redacted_connection_url_with_host(&self, host: &str, port: u16) -> String { let host = bracket_ipv6(host); - let db_part = self - .database - .as_deref() - .filter(|d| !d.is_empty()) - .map(|d| format!("/{}", encode_url_part(d))) - .unwrap_or_default(); + let db_part = self.effective_database().map(|d| format!("/{}", encode_url_part(d))).unwrap_or_default(); let params = self.normalized_url_params(); match self.db_type { @@ -148,12 +163,7 @@ impl ConnectionConfig { pub fn connection_url_with_host(&self, host: &str, port: u16) -> String { let host = bracket_ipv6(host); - let db_part = self - .database - .as_deref() - .filter(|d| !d.is_empty()) - .map(|d| format!("/{}", encode_url_part(d))) - .unwrap_or_default(); + let db_part = self.effective_database().map(|d| format!("/{}", encode_url_part(d))).unwrap_or_default(); let username = encode_url_part(&self.username); let password = encode_url_part(&self.password); let params = self.normalized_url_params(); @@ -413,6 +423,47 @@ mod tests { assert_eq!(config.connection_url(), "postgres://postgres:secret@10.1.2.3:2883/test?sslmode=disable"); } + #[test] + fn postgres_url_defaults_to_postgres_database_when_omitted() { + let mut config = mysql_config("root", "secret", None); + config.db_type = DatabaseType::Postgres; + + assert_eq!(config.connection_url(), "postgres://root:secret@10.1.2.3:2883/postgres"); + } + + #[test] + fn postgres_url_defaults_to_postgres_database_when_empty() { + let mut config = mysql_config("root", "secret", Some("")); + config.db_type = DatabaseType::Postgres; + + assert_eq!(config.connection_url(), "postgres://root:secret@10.1.2.3:2883/postgres"); + } + + #[test] + fn redshift_url_defaults_to_dev_database_when_empty() { + let mut config = mysql_config("awsuser", "secret", Some("")); + config.db_type = DatabaseType::Redshift; + + assert_eq!(config.connection_url(), "postgres://awsuser:secret@10.1.2.3:2883/dev"); + } + + #[test] + fn cockroachdb_url_defaults_to_defaultdb_database() { + let mut config = mysql_config("root", "secret", None); + config.db_type = DatabaseType::Postgres; + config.driver_profile = Some("cockroachdb".to_string()); + + assert_eq!(config.connection_url(), "postgres://root:secret@10.1.2.3:2883/defaultdb"); + } + + #[test] + fn gaussdb_url_defaults_to_postgres_database() { + let mut config = mysql_config("gaussdb", "secret", None); + config.db_type = DatabaseType::Gaussdb; + + assert_eq!(config.connection_url(), "gaussdb://gaussdb:secret@10.1.2.3:2883/postgres"); + } + #[test] fn mongodb_form_url_without_params_does_not_force_topology_or_auth() { let config = mongodb_config("root", "secret", Some("admin")); diff --git a/src/components/connection/ConnectionDialog.vue b/src/components/connection/ConnectionDialog.vue index 5385b25d7..7751851ee 100644 --- a/src/components/connection/ConnectionDialog.vue +++ b/src/components/connection/ConnectionDialog.vue @@ -157,7 +157,7 @@ const driverProfiles: Record< icon: "starrocks", urlParams: "", }, - redshift: { type: "postgres", port: 5439, user: "awsuser", label: "Redshift", icon: "redshift" }, + redshift: { type: "redshift", port: 5439, user: "awsuser", label: "Redshift", icon: "redshift" }, cockroachdb: { type: "postgres", port: 26257, @@ -273,6 +273,26 @@ watch( }, ); +const databaseLabel = computed(() => + form.value.db_type === "oracle" ? t("connection.serviceName") : t("connection.database"), +); + +const databasePlaceholder = computed(() => { + const fallback = defaultDatabaseForProfile(); + if (!fallback) return t("connection.databasePlaceholder"); + return t("connection.databasePlaceholderWithDefault", { database: fallback }); +}); + +function defaultDatabaseForProfile() { + if (form.value.db_type === "redshift") return "dev"; + if (form.value.db_type === "gaussdb") return "postgres"; + if (selectedType.value === "cockroachdb") return "defaultdb"; + if (form.value.db_type === "postgres") return "postgres"; + if (form.value.db_type === "sqlserver") return "master"; + if (form.value.db_type === "oracle") return "ORCL"; + return ""; +} + function onDbTypeChange(val: string) { customDriverName.value = ""; applyProfile(val, !!editingId.value); @@ -821,12 +841,8 @@ async function browseDbFilePath() {
- - + +
@@ -852,14 +868,14 @@ async function browseDbFilePath() {
diff --git a/src/i18n/locales/en.ts b/src/i18n/locales/en.ts index 1d05e5c01..bc5653352 100644 --- a/src/i18n/locales/en.ts +++ b/src/i18n/locales/en.ts @@ -75,6 +75,8 @@ export default { password: "Password", database: "Database", databasePlaceholder: "Optional", + databasePlaceholderWithDefault: "Optional, defaults to {database}", + serviceName: "Service/SID", driverName: "Driver Name", driverNamePlaceholder: "Vendor or environment name", urlParams: "URL Params", diff --git a/src/i18n/locales/zh-CN.ts b/src/i18n/locales/zh-CN.ts index e8263454b..1145d4dd2 100644 --- a/src/i18n/locales/zh-CN.ts +++ b/src/i18n/locales/zh-CN.ts @@ -74,6 +74,8 @@ export default { password: "密码", database: "数据库", databasePlaceholder: "可选", + databasePlaceholderWithDefault: "可选,默认 {database}", + serviceName: "服务名/SID", driverName: "驱动名称", driverNamePlaceholder: "厂商或环境名称", urlParams: "URL 参数", diff --git a/src/lib/databaseCapabilities.ts b/src/lib/databaseCapabilities.ts index 62080b4a3..3ec383eee 100644 --- a/src/lib/databaseCapabilities.ts +++ b/src/lib/databaseCapabilities.ts @@ -78,7 +78,7 @@ export const FIELD_LINEAGE_SUPPORTED_TYPES = new Set([ export const FETCH_FIRST_TYPES = new Set(["oracle", "dameng"]); -export const TREE_SCHEMA_TYPES = new Set(["postgres", "sqlserver", "gaussdb"]); +export const TREE_SCHEMA_TYPES = new Set(["postgres", "redshift", "sqlserver", "gaussdb"]); export const PG_LIKE_STRUCTURE_TYPES = new Set(["postgres", "redshift", "gaussdb"]); diff --git a/src/stores/connectionStore.ts b/src/stores/connectionStore.ts index cf70dd734..40a24d0ec 100644 --- a/src/stores/connectionStore.ts +++ b/src/stores/connectionStore.ts @@ -138,6 +138,8 @@ export const useConnectionStore = defineStore("connection", () => { let dbType = config.db_type; if ((profile === "gaussdb" || profile === "opengauss") && dbType === "postgres") { dbType = "gaussdb" as ConnectionConfig["db_type"]; + } else if (profile === "redshift" && dbType === "postgres") { + dbType = "redshift" as ConnectionConfig["db_type"]; } return {