fix(db): normalize default database handling

This commit is contained in:
t8y2 2026-05-09 15:53:06 +08:00
parent 5cffe12014
commit af029daf57
8 changed files with 144 additions and 30 deletions

View File

@ -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"));
}
}

View File

@ -30,6 +30,7 @@ impl GaussdbClient {
}
pub async fn connect(host: &str, port: u16, database: &str, user: &str, pass: &str) -> Result<GaussdbClient, String> {
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<Vec<DatabaseInfo>, 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<Quer
})
}
}
#[cfg(test)]
mod tests {
use super::normalize_database;
#[test]
fn normalize_database_defaults_blank_to_postgres() {
assert_eq!(normalize_database(""), "postgres");
assert_eq!(normalize_database(" "), "postgres");
}
#[test]
fn normalize_database_keeps_explicit_database() {
assert_eq!(normalize_database("app"), "app");
}
}

View File

@ -75,6 +75,26 @@ pub enum DatabaseType {
}
impl ConnectionConfig {
pub fn effective_database(&self) -> 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"));

View File

@ -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() {
</div>
<div class="grid grid-cols-4 items-center gap-4">
<Label class="text-right">{{ t("connection.database") }}</Label>
<Input
v-model="form.database"
class="col-span-3"
:placeholder="t('connection.databasePlaceholder')"
/>
<Label class="text-right">{{ databaseLabel }}</Label>
<Input v-model="form.database" class="col-span-3" :placeholder="databasePlaceholder" />
</div>
<div v-if="selectedType === 'dm'" class="grid grid-cols-4 items-center gap-4">
@ -852,14 +868,14 @@ async function browseDbFilePath() {
</div>
<div
v-if="form.db_type === 'mysql' || form.db_type === 'postgres'"
v-if="form.db_type === 'mysql' || form.db_type === 'postgres' || form.db_type === 'redshift'"
class="grid grid-cols-4 items-center gap-4"
>
<Label class="text-right">{{ t("connection.urlParams") }}</Label>
<Input
v-model="form.url_params"
class="col-span-3"
:placeholder="form.db_type === 'postgres' ? 'sslmode=disable' : 'charset=utf8mb4'"
:placeholder="form.db_type === 'mysql' ? 'charset=utf8mb4' : 'sslmode=disable'"
/>
</div>
</template>

View File

@ -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",

View File

@ -74,6 +74,8 @@ export default {
password: "密码",
database: "数据库",
databasePlaceholder: "可选",
databasePlaceholderWithDefault: "可选,默认 {database}",
serviceName: "服务名/SID",
driverName: "驱动名称",
driverNamePlaceholder: "厂商或环境名称",
urlParams: "URL 参数",

View File

@ -78,7 +78,7 @@ export const FIELD_LINEAGE_SUPPORTED_TYPES = new Set<DatabaseType>([
export const FETCH_FIRST_TYPES = new Set<DatabaseType>(["oracle", "dameng"]);
export const TREE_SCHEMA_TYPES = new Set<DatabaseType>(["postgres", "sqlserver", "gaussdb"]);
export const TREE_SCHEMA_TYPES = new Set<DatabaseType>(["postgres", "redshift", "sqlserver", "gaussdb"]);
export const PG_LIKE_STRUCTURE_TYPES = new Set<DatabaseType>(["postgres", "redshift", "gaussdb"]);

View File

@ -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 {