feat: use native postgres path for GaussDB connections

This commit is contained in:
t8y2 2026-05-25 19:07:30 +08:00
parent 792c816a17
commit 234099aebd
9 changed files with 196 additions and 34 deletions

8
Cargo.lock generated
View File

@ -5519,8 +5519,6 @@ dependencies = [
[[package]]
name = "postgres-protocol"
version = "0.6.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "56201207dac53e2f38e848e31b4b91616a6bb6e0c7205b77718994a7f49e70fc"
dependencies = [
"base64 0.22.1",
"byteorder",
@ -5529,7 +5527,9 @@ dependencies = [
"hmac 0.13.0",
"md-5 0.11.0",
"memchr",
"pbkdf2 0.12.2",
"rand 0.10.1",
"sha1 0.10.6",
"sha2 0.11.0",
"stringprep",
]
@ -5537,8 +5537,6 @@ dependencies = [
[[package]]
name = "postgres-types"
version = "0.2.13"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8dc729a129e682e8d24170cd30ae1aa01b336b096cbb56df6d534ffec133d186"
dependencies = [
"bytes",
"chrono",
@ -8210,8 +8208,6 @@ dependencies = [
[[package]]
name = "tokio-postgres"
version = "0.7.17"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4dd8df5ef180f6364759a6f00f7aadda4fbbac86cdee37480826a6ff9f3574ce"
dependencies = [
"async-trait",
"byteorder",

View File

@ -2,6 +2,11 @@
resolver = "2"
members = ["src-tauri", "crates/dbx-core", "crates/dbx-web"]
[patch.crates-io]
tokio-postgres = { path = "../tokio-postgres-gaussdb/tokio-postgres" }
postgres-types = { path = "../tokio-postgres-gaussdb/postgres-types" }
postgres-protocol = { path = "../tokio-postgres-gaussdb/postgres-protocol" }
[profile.release]
panic = "abort"
strip = true

View File

@ -192,12 +192,11 @@
{
"dbType": "gaussdb",
"label": "GaussDB",
"runtimeMode": "agent",
"mcpMode": "bridge",
"agentKey": "gaussdb",
"runtimeMode": "native",
"mcpMode": "direct",
"singleConnectionPool": false,
"metadataConnectionScoped": false,
"skipTcpProbe": true,
"skipTcpProbe": false,
"defaultPort": 8000
},
{
@ -280,12 +279,11 @@
{
"dbType": "opengauss",
"label": "openGauss",
"runtimeMode": "agent",
"mcpMode": "bridge",
"agentKey": "opengauss",
"runtimeMode": "native",
"mcpMode": "direct",
"singleConnectionPool": false,
"metadataConnectionScoped": false,
"skipTcpProbe": true,
"skipTcpProbe": false,
"defaultPort": 5432
},
{

View File

@ -23,7 +23,6 @@ pub const AGENT_TYPES: &[(&str, &str)] = &[
("vertica", "Vertica"),
("firebird", "Firebird"),
("exasol", "Exasol"),
("opengauss", "openGauss"),
("oceanbase-oracle", "OceanBase Oracle Mode"),
("gbase", "GBase"),
("access", "Microsoft Access"),
@ -40,7 +39,6 @@ pub const AGENT_TYPES: &[(&str, &str)] = &[
("bigquery", "Google BigQuery"),
("kylin", "Apache Kylin"),
("sundb", "SunDB"),
("gaussdb", "GaussDB"),
("yashandb", "崖山 YashanDB"),
("tdengine", "TDengine"),
("mongodb", "MongoDB (Legacy)"),

View File

@ -190,7 +190,9 @@ impl AppState {
DatabaseType::Doris | DatabaseType::StarRocks => {
PoolKind::Mysql(db::mysql::connect_bare(&url).await?, MysqlMode::Bare)
}
DatabaseType::Postgres | DatabaseType::Redshift => PoolKind::Postgres(db::postgres::connect(&url).await?),
DatabaseType::Postgres | DatabaseType::Redshift | DatabaseType::Gaussdb | DatabaseType::OpenGauss => {
PoolKind::Postgres(db::postgres::connect(&url).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?;
@ -268,7 +270,6 @@ impl AppState {
| DatabaseType::Vertica
| DatabaseType::Firebird
| DatabaseType::Exasol
| DatabaseType::OpenGauss
| DatabaseType::OceanbaseOracle
| DatabaseType::Gbase
| DatabaseType::Oracle
@ -284,8 +285,7 @@ impl AppState {
| DatabaseType::Kylin
| DatabaseType::Sundb
| DatabaseType::Tdengine
| DatabaseType::Access
| DatabaseType::Gaussdb => {
| DatabaseType::Access => {
let connect_params =
agent_connect_params(&db_config, &host, port, db_config.effective_database().unwrap_or(""));
let mut client =
@ -586,6 +586,8 @@ fn default_dbx_dir() -> PathBuf {
}
pub fn connection_url_for_endpoint(config: &ConnectionConfig, host: &str, port: u16) -> String {
let normalized = native_postgres_url_config(config);
let config = normalized.as_ref().unwrap_or(config);
if host == config.host && port == config.port {
config.connection_url()
} else {
@ -594,6 +596,8 @@ pub fn connection_url_for_endpoint(config: &ConnectionConfig, host: &str, port:
}
pub fn redacted_connection_url_for_endpoint(config: &ConnectionConfig, host: &str, port: u16) -> String {
let normalized = native_postgres_url_config(config);
let config = normalized.as_ref().unwrap_or(config);
if host == config.host && port == config.port {
config.redacted_connection_url()
} else {
@ -601,6 +605,27 @@ pub fn redacted_connection_url_for_endpoint(config: &ConnectionConfig, host: &st
}
}
fn native_postgres_url_config(config: &ConnectionConfig) -> Option<ConnectionConfig> {
match config.db_type {
DatabaseType::Gaussdb | DatabaseType::OpenGauss => {
let mut normalized = config.clone();
if config.db_type == DatabaseType::Gaussdb {
let params = normalized.url_params.as_deref().unwrap_or("").trim().trim_start_matches('?');
if !params.to_lowercase().contains("sslmode=") {
normalized.url_params = Some(if params.is_empty() {
"sslmode=disable".to_string()
} else {
format!("sslmode=disable&{params}")
});
}
}
normalized.db_type = DatabaseType::Postgres;
Some(normalized)
}
_ => None,
}
}
pub fn agent_connect_params(config: &ConnectionConfig, host: &str, port: u16, database: &str) -> serde_json::Value {
let agent_database = if config.db_type == DatabaseType::MongoDb {
mongo_agent_database(config, database)
@ -808,9 +833,10 @@ async fn detect_ob_oracle_mode(config: &ConnectionConfig, pool: &db::mysql::MySq
#[cfg(test)]
mod tests {
use super::{
agent_connect_params, database_connection_config, metadata_connection_config, uses_tcp_probe, AppState,
PoolKind,
agent_connect_params, connection_url_for_endpoint, database_connection_config, metadata_connection_config,
redacted_connection_url_for_endpoint, uses_tcp_probe, AppState, PoolKind,
};
use crate::db;
use crate::models::connection::{ConnectionConfig, DatabaseType, ProxyType};
use crate::schema;
use crate::storage::Storage;
@ -1029,6 +1055,37 @@ mod tests {
(AppState::new(storage), dir)
}
fn live_postgres_like_config(
db_type: DatabaseType,
host: &str,
port: u16,
username: &str,
password: &str,
url_params: Option<&str>,
) -> ConnectionConfig {
let mut config = mysql_config(Some("postgres"));
config.db_type = db_type;
config.host = host.to_string();
config.port = port;
config.username = username.to_string();
config.password = password.to_string();
config.url_params = url_params.map(str::to_string);
config
}
async fn assert_live_postgres_like_query(config: ConnectionConfig) {
let url = connection_url_for_endpoint(&config, &config.host, config.port);
let pool = db::postgres::connect(&url).await.unwrap_or_else(|err| {
panic!("failed to connect to {:?} at {}:{}: {}", config.db_type, config.host, config.port, err)
});
let result =
db::postgres::execute_query(&pool, "SELECT current_database(), current_schema()").await.unwrap_or_else(
|err| panic!("failed to query {:?} at {}:{}: {}", config.db_type, config.host, config.port, err),
);
assert_eq!(result.rows.len(), 1);
pool.close();
}
#[test]
fn mysql_metadata_connection_ignores_saved_default_database() {
let config = mysql_config(Some("app"));
@ -1058,6 +1115,64 @@ mod tests {
assert_eq!(scoped.database.as_deref(), Some("analytics"));
}
#[test]
fn gaussdb_endpoint_url_uses_postgres_scheme_for_native_driver() {
let mut config = mysql_config(Some("postgres"));
config.db_type = DatabaseType::Gaussdb;
config.username = "gaussdb".to_string();
config.password = "secret".to_string();
assert_eq!(
connection_url_for_endpoint(&config, &config.host, config.port),
"postgres://gaussdb:secret@127.0.0.1:3306/postgres?sslmode=disable"
);
assert_eq!(
redacted_connection_url_for_endpoint(&config, &config.host, config.port),
"postgres://127.0.0.1:3306/postgres?sslmode=disable"
);
}
#[test]
fn opengauss_endpoint_url_uses_postgres_scheme_for_native_driver() {
let mut config = mysql_config(Some("postgres"));
config.db_type = DatabaseType::OpenGauss;
config.username = "gaussdb".to_string();
config.password = "secret".to_string();
assert_eq!(
connection_url_for_endpoint(&config, &config.host, config.port),
"postgres://gaussdb:secret@127.0.0.1:3306/postgres"
);
}
#[test]
fn gaussdb_endpoint_url_keeps_explicit_sslmode() {
let mut config = mysql_config(Some("postgres"));
config.db_type = DatabaseType::Gaussdb;
config.username = "gaussdb".to_string();
config.password = "secret".to_string();
config.url_params = Some("sslmode=require&application_name=dbx".to_string());
assert_eq!(
connection_url_for_endpoint(&config, &config.host, config.port),
"postgres://gaussdb:secret@127.0.0.1:3306/postgres?sslmode=require&application_name=dbx"
);
}
#[test]
fn gaussdb_endpoint_url_prepends_default_sslmode_to_custom_params() {
let mut config = mysql_config(Some("postgres"));
config.db_type = DatabaseType::Gaussdb;
config.username = "gaussdb".to_string();
config.password = "secret".to_string();
config.url_params = Some("application_name=dbx".to_string());
assert_eq!(
connection_url_for_endpoint(&config, &config.host, config.port),
"postgres://gaussdb:secret@127.0.0.1:3306/postgres?sslmode=disable&application_name=dbx"
);
}
#[test]
fn mongodb_database_connection_keeps_saved_database_for_auth() {
let mut config = mysql_config(Some("admin"));
@ -1193,4 +1308,50 @@ mod tests {
state.proxy_tunnels.stop_tunnel("proxied").await;
let _ = std::fs::remove_dir_all(dir);
}
#[tokio::test]
#[ignore = "requires a reachable GaussDB instance via environment variables"]
async fn live_gaussdb_native_connection_succeeds() {
let host = std::env::var("DBX_TEST_GAUSSDB_HOST").expect("DBX_TEST_GAUSSDB_HOST not set");
let port = std::env::var("DBX_TEST_GAUSSDB_PORT")
.expect("DBX_TEST_GAUSSDB_PORT not set")
.parse::<u16>()
.expect("DBX_TEST_GAUSSDB_PORT should be a u16");
let username = std::env::var("DBX_TEST_GAUSSDB_USER").expect("DBX_TEST_GAUSSDB_USER not set");
let password = std::env::var("DBX_TEST_GAUSSDB_PASSWORD").expect("DBX_TEST_GAUSSDB_PASSWORD not set");
let url_params = std::env::var("DBX_TEST_GAUSSDB_URL_PARAMS").ok();
assert_live_postgres_like_query(live_postgres_like_config(
DatabaseType::Gaussdb,
&host,
port,
&username,
&password,
url_params.as_deref(),
))
.await;
}
#[tokio::test]
#[ignore = "requires a reachable openGauss instance via environment variables"]
async fn live_opengauss_native_connection_succeeds() {
let host = std::env::var("DBX_TEST_OPENGAUSS_HOST").expect("DBX_TEST_OPENGAUSS_HOST not set");
let port = std::env::var("DBX_TEST_OPENGAUSS_PORT")
.expect("DBX_TEST_OPENGAUSS_PORT not set")
.parse::<u16>()
.expect("DBX_TEST_OPENGAUSS_PORT should be a u16");
let username = std::env::var("DBX_TEST_OPENGAUSS_USER").expect("DBX_TEST_OPENGAUSS_USER not set");
let password = std::env::var("DBX_TEST_OPENGAUSS_PASSWORD").expect("DBX_TEST_OPENGAUSS_PASSWORD not set");
let url_params = std::env::var("DBX_TEST_OPENGAUSS_URL_PARAMS").ok();
assert_live_postgres_like_query(live_postgres_like_config(
DatabaseType::OpenGauss,
&host,
port,
&username,
&password,
url_params.as_deref(),
))
.await;
}
}

View File

@ -14,7 +14,6 @@ pub fn agent_key(db_type: &DatabaseType, driver_profile: Option<&str>) -> Option
DatabaseType::Vertica => Some("vertica"),
DatabaseType::Firebird => Some("firebird"),
DatabaseType::Exasol => Some("exasol"),
DatabaseType::OpenGauss => Some("opengauss"),
DatabaseType::OceanbaseOracle => Some("oceanbase-oracle"),
DatabaseType::Gbase => Some("gbase"),
DatabaseType::Oracle => match driver_profile {
@ -32,7 +31,6 @@ pub fn agent_key(db_type: &DatabaseType, driver_profile: Option<&str>) -> Option
DatabaseType::Bigquery => Some("bigquery"),
DatabaseType::Kylin => Some("kylin"),
DatabaseType::Sundb => Some("sundb"),
DatabaseType::Gaussdb => Some("gaussdb"),
DatabaseType::MongoDb => Some("mongodb"),
DatabaseType::Tdengine => Some("tdengine"),
DatabaseType::Access => Some("access"),

View File

@ -122,6 +122,7 @@ fn pg_value_to_json(row: &Row, idx: usize, type_name: &str) -> serde_json::Value
pub async fn connect(url: &str) -> Result<Pool, String> {
validate_postgres_ssl_paths(url)?;
let _ = rustls::crypto::aws_lc_rs::default_provider().install_default();
let tz = iana_time_zone::get_timezone().unwrap_or_else(|_| "UTC".to_string());

View File

@ -49,7 +49,6 @@ fn driver_manifest() -> DriverManifest {
fn maps_agent_database_types_to_driver_keys() {
assert_eq!(agent_key(&DatabaseType::Trino, None), Some("trino"));
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::Yashandb, None), Some("yashandb"));
assert_eq!(agent_key(&DatabaseType::Databricks, None), Some("databricks"));
@ -58,7 +57,6 @@ fn maps_agent_database_types_to_driver_keys() {
assert_eq!(agent_key(&DatabaseType::Vertica, None), Some("vertica"));
assert_eq!(agent_key(&DatabaseType::Firebird, None), Some("firebird"));
assert_eq!(agent_key(&DatabaseType::Exasol, None), Some("exasol"));
assert_eq!(agent_key(&DatabaseType::OpenGauss, None), Some("opengauss"));
assert_eq!(agent_key(&DatabaseType::OceanbaseOracle, None), Some("oceanbase-oracle"));
assert_eq!(agent_key(&DatabaseType::Gbase, None), Some("gbase"));
assert_eq!(agent_key(&DatabaseType::Access, None), Some("access"));
@ -80,12 +78,13 @@ fn classifies_agent_database_types() {
assert!(is_agent_type(&DatabaseType::Vertica));
assert!(is_agent_type(&DatabaseType::Firebird));
assert!(is_agent_type(&DatabaseType::Exasol));
assert!(is_agent_type(&DatabaseType::OpenGauss));
assert!(is_agent_type(&DatabaseType::OceanbaseOracle));
assert!(is_agent_type(&DatabaseType::Gbase));
assert!(is_agent_type(&DatabaseType::Access));
assert!(!is_agent_type(&DatabaseType::Mysql));
assert!(!is_agent_type(&DatabaseType::Jdbc));
assert!(!is_agent_type(&DatabaseType::Gaussdb));
assert!(!is_agent_type(&DatabaseType::OpenGauss));
}
#[test]
@ -128,6 +127,8 @@ fn skips_tcp_probe_for_local_file_plugin_and_agent_types() {
assert!(skips_tcp_probe(&DatabaseType::Gbase));
assert!(!skips_tcp_probe(&DatabaseType::Postgres));
assert!(!skips_tcp_probe(&DatabaseType::Mysql));
assert!(!skips_tcp_probe(&DatabaseType::Gaussdb));
assert!(!skips_tcp_probe(&DatabaseType::OpenGauss));
}
#[test]

View File

@ -236,13 +236,15 @@ pub async fn test_connection(state: State<'_, Arc<AppState>>, config: Connection
}
Err(e) => Err(e),
},
DatabaseType::Postgres | DatabaseType::Redshift => match db::postgres::connect(&url).await {
Ok(pool) => {
pool.close();
Ok("Connection successful".to_string())
DatabaseType::Postgres | DatabaseType::Redshift | DatabaseType::Gaussdb | DatabaseType::OpenGauss => {
match db::postgres::connect(&url).await {
Ok(pool) => {
pool.close();
Ok("Connection successful".to_string())
}
Err(e) => Err(e),
}
Err(e) => Err(e),
},
}
DatabaseType::Sqlite => match db::sqlite::connect_path(&expand_tilde(&config.host)).await {
Ok(_) => Ok("Connection successful".to_string()),
Err(e) => Err(e),
@ -344,7 +346,9 @@ pub async fn connect_db(state: State<'_, Arc<AppState>>, config: ConnectionConfi
DatabaseType::Doris | DatabaseType::StarRocks => {
PoolKind::Mysql(db::mysql::connect_bare(&url).await?, MysqlMode::Bare)
}
DatabaseType::Postgres | DatabaseType::Redshift => PoolKind::Postgres(db::postgres::connect(&url).await?),
DatabaseType::Postgres | DatabaseType::Redshift | DatabaseType::Gaussdb | DatabaseType::OpenGauss => {
PoolKind::Postgres(db::postgres::connect(&url).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?;