feat: add 12 new agent-driven databases, migrate GaussDB to agent

Add H2, Snowflake, Trino, Hive, DB2, Informix, Neo4j, Cassandra,
BigQuery, Apache Kylin, SunDB as new agent-driven database types.
Migrate GaussDB from native rust-gaussdb to JDBC agent.

Remove rust-gaussdb dependency. Total agent-driven databases: 17.
This commit is contained in:
t8y2
2026-05-13 09:47:22 +08:00
parent c50323cd6c
commit 7ce2cf2163
16 changed files with 245 additions and 574 deletions
-1
View File
@@ -30,6 +30,5 @@ russh = "0.60"
portpicker = "0.1.1"
csv = "1"
calamine = "0.30.1"
rust-gaussdb = { git = "https://github.com/t8y2/rust-gaussdb", branch = "main" }
base64 = "0.22"
async-trait = "0.1"
+12
View File
@@ -122,6 +122,18 @@ impl AgentManager {
DatabaseType::Vastbase => Some("vastbase"),
DatabaseType::Goldendb => Some("goldendb"),
DatabaseType::Oracle => Some("oracle"),
DatabaseType::H2 => Some("h2"),
DatabaseType::Snowflake => Some("snowflake"),
DatabaseType::Trino => Some("trino"),
DatabaseType::Hive => Some("hive"),
DatabaseType::Db2 => Some("db2"),
DatabaseType::Informix => Some("informix"),
DatabaseType::Neo4j => Some("neo4j"),
DatabaseType::Cassandra => Some("cassandra"),
DatabaseType::Bigquery => Some("bigquery"),
DatabaseType::Kylin => Some("kylin"),
DatabaseType::Sundb => Some("sundb"),
DatabaseType::Gaussdb => Some("gaussdb"),
_ => None,
}
}
+26 -14
View File
@@ -44,7 +44,6 @@ pub enum PoolKind {
SqlServer(Arc<tokio::sync::Mutex<db::sqlserver::SqlServerClient>>),
Elasticsearch(db::elasticsearch_driver::EsClient),
Agent(Arc<tokio::sync::Mutex<db::agent_driver::AgentDriverClient>>),
Gaussdb(Arc<tokio::sync::Mutex<db::gaussdb_driver::GaussdbClient>>),
ExternalTabular(Arc<external::ExternalPool>),
ExternalDriver { driver_id: String, config: ConnectionConfig, session: Arc<PluginDriverSession> },
}
@@ -214,7 +213,19 @@ impl AppState {
| DatabaseType::Kingbase
| DatabaseType::Vastbase
| DatabaseType::Goldendb
| DatabaseType::Oracle => {
| DatabaseType::Oracle
| DatabaseType::H2
| DatabaseType::Snowflake
| DatabaseType::Trino
| DatabaseType::Hive
| DatabaseType::Db2
| DatabaseType::Informix
| DatabaseType::Neo4j
| DatabaseType::Cassandra
| DatabaseType::Bigquery
| DatabaseType::Kylin
| DatabaseType::Sundb
| DatabaseType::Gaussdb => {
let mut client = self.agent_manager.spawn(&db_config.db_type).await?;
client
.call::<serde_json::Value>(
@@ -230,17 +241,6 @@ impl AppState {
.await?;
PoolKind::Agent(Arc::new(tokio::sync::Mutex::new(client)))
}
DatabaseType::Gaussdb => {
let client = db::gaussdb_driver::connect(
&host,
port,
db_config.effective_database().unwrap_or("postgres"),
&db_config.username,
&db_config.password,
)
.await?;
PoolKind::Gaussdb(Arc::new(tokio::sync::Mutex::new(client)))
}
DatabaseType::Jdbc => {
let mut jdbc_config = db_config.clone();
if host != config.host || port != config.port {
@@ -406,7 +406,19 @@ pub async fn probe_connection_endpoint(config: &ConnectionConfig, host: &str, po
| DatabaseType::Kingbase
| DatabaseType::Vastbase
| DatabaseType::Goldendb
| DatabaseType::Oracle => Ok(()),
| DatabaseType::Oracle
| DatabaseType::H2
| DatabaseType::Snowflake
| DatabaseType::Trino
| DatabaseType::Hive
| DatabaseType::Db2
| DatabaseType::Informix
| DatabaseType::Neo4j
| DatabaseType::Cassandra
| DatabaseType::Bigquery
| DatabaseType::Kylin
| DatabaseType::Sundb
| DatabaseType::Gaussdb => Ok(()),
_ => db::probe_tcp_endpoint(&format!("{:?}", config.db_type), host, port).await,
}
}
-304
View File
@@ -1,304 +0,0 @@
use std::time::Instant;
use crate::sql::starts_with_executable_sql_keyword;
use crate::types::{ColumnInfo, DatabaseInfo, ForeignKeyInfo, IndexInfo, QueryResult, TableInfo, TriggerInfo};
use super::CONNECTION_TIMEOUT_SECS;
pub struct GaussdbClient {
client: rust_gaussdb::Client,
}
unsafe impl Send for GaussdbClient {}
impl GaussdbClient {
async fn query_rows(&mut self, sql: &str) -> Result<Vec<Vec<Option<String>>>, String> {
let rows = self.client.query(sql, &[]).await.map_err(|e| e.to_string())?;
let mut result = Vec::new();
for row in &rows {
let mut vals = Vec::new();
for i in 0..row.columns().len() {
vals.push(row.try_get::<String>(i));
}
result.push(vals);
}
Ok(result)
}
async fn query_single_column(&mut self, sql: &str) -> Result<Vec<String>, String> {
Ok(self.query_rows(sql).await?.into_iter().filter_map(|r| r.into_iter().next().flatten()).collect())
}
}
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 {
rust_gaussdb::Client::connect(&dsn).await.map_err(|e| format!("GaussDB connection failed: {e}"))
})
.await
.map_err(|_| format!("GaussDB connection timed out ({CONNECTION_TIMEOUT_SECS}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 AND datallowconn = true \
ORDER BY datname",
)
.await?;
Ok(rows.into_iter().map(|name| DatabaseInfo { name }).collect())
}
pub async fn list_schemas(client: &mut GaussdbClient) -> Result<Vec<String>, String> {
client
.query_single_column(
"SELECT DISTINCT nspname FROM pg_catalog.pg_namespace n \
WHERE nspname NOT LIKE 'pg_%' \
AND nspname NOT IN ('information_schema', 'cstore', 'snapshot', 'db4ai', 'dbe_perf', \
'dbe_pldebugger', 'dbe_pldeveloper', 'pkg_service', 'pkg_util', 'sqladvisor', 'blockchain') \
ORDER BY nspname",
)
.await
}
pub async fn list_tables(client: &mut GaussdbClient, schema: &str) -> Result<Vec<TableInfo>, String> {
let s = schema.replace('\'', "''");
let sql = format!(
"SELECT c.relname, CASE c.relkind WHEN 'r' THEN 'TABLE' WHEN 'v' THEN 'VIEW' WHEN 'm' THEN 'VIEW' ELSE 'TABLE' END \
FROM pg_catalog.pg_class c \
JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace \
WHERE n.nspname = '{s}' AND c.relkind IN ('r', 'v', 'm') \
ORDER BY c.relname"
);
let rows = client.query_rows(&sql).await?;
Ok(rows
.into_iter()
.map(|r| {
let raw_type = r.get(1).and_then(|v| v.clone()).unwrap_or_default();
TableInfo {
name: r.first().and_then(|v| v.clone()).unwrap_or_default(),
table_type: if raw_type.contains("VIEW") { "VIEW".to_string() } else { "TABLE".to_string() },
comment: None,
}
})
.collect())
}
pub async fn get_columns(client: &mut GaussdbClient, schema: &str, table: &str) -> Result<Vec<ColumnInfo>, String> {
let s = schema.replace('\'', "''");
let t = table.replace('\'', "''");
let pk_rows = client
.query_single_column(&format!(
"SELECT a.attname FROM pg_catalog.pg_index i \
JOIN pg_catalog.pg_attribute a ON a.attrelid = i.indrelid AND a.attnum = ANY(i.indkey) \
WHERE i.indrelid = (SELECT oid FROM pg_catalog.pg_class WHERE relname = '{t}' \
AND relnamespace = (SELECT oid FROM pg_catalog.pg_namespace WHERE nspname = '{s}')) \
AND i.indisprimary"
))
.await?;
let pk_names: std::collections::HashSet<String> = pk_rows.into_iter().collect();
let col_rows = client
.query_rows(&format!(
"SELECT a.attname, format_type(a.atttypid, a.atttypmod), \
CASE WHEN a.attnotnull THEN 'NO' ELSE 'YES' END, \
pg_catalog.pg_get_expr(d.adbin, d.adrelid), \
CASE WHEN t.typname IN ('numeric', 'float4', 'float8') THEN COALESCE(((a.atttypmod - 4) >> 16) & 65535, -1) ELSE NULL END, \
CASE WHEN t.typname = 'numeric' THEN COALESCE((a.atttypmod - 4) & 65535, -1) ELSE NULL END, \
CASE WHEN a.atttypmod > 0 AND t.typname IN ('varchar', 'bpchar') THEN a.atttypmod - 4 ELSE NULL END \
FROM pg_catalog.pg_attribute a \
JOIN pg_catalog.pg_type t ON t.oid = a.atttypid \
LEFT JOIN pg_catalog.pg_attrdef d ON d.adrelid = a.attrelid AND d.adnum = a.attnum \
WHERE a.attrelid = (SELECT oid FROM pg_catalog.pg_class WHERE relname = '{t}' \
AND relnamespace = (SELECT oid FROM pg_catalog.pg_namespace WHERE nspname = '{s}')) \
AND a.attnum > 0 AND NOT a.attisdropped \
ORDER BY a.attnum"
))
.await?;
Ok(col_rows
.into_iter()
.map(|r| {
let name = r.first().and_then(|v| v.clone()).unwrap_or_default();
let data_type = r.get(1).and_then(|v| v.clone()).unwrap_or_default();
let num_prec = r.get(4).and_then(|v| v.as_ref()?.parse::<i32>().ok());
let num_scale = r.get(5).and_then(|v| v.as_ref()?.parse::<i32>().ok());
let char_len = r.get(6).and_then(|v| v.as_ref()?.parse::<i32>().ok());
ColumnInfo {
is_primary_key: pk_names.contains(&name),
name,
data_type,
is_nullable: r.get(2).and_then(|v| v.as_ref()).map(|v| v == "YES").unwrap_or(false),
column_default: r.get(3).and_then(|v| v.clone()).filter(|v| !v.is_empty()),
extra: None,
comment: None,
numeric_precision: num_prec,
numeric_scale: num_scale,
character_maximum_length: char_len,
}
})
.collect())
}
pub async fn list_indexes(client: &mut GaussdbClient, schema: &str, table: &str) -> Result<Vec<IndexInfo>, String> {
let s = schema.replace('\'', "''");
let t = table.replace('\'', "''");
let sql = format!(
"SELECT indexname, indexdef FROM pg_indexes WHERE schemaname = '{s}' AND tablename = '{t}' ORDER BY indexname"
);
let rows = client.query_rows(&sql).await?;
Ok(rows
.into_iter()
.map(|r| {
let name = r.first().and_then(|v| v.clone()).unwrap_or_default();
let def = r.get(1).and_then(|v| v.clone()).unwrap_or_default();
let is_unique = def.to_uppercase().contains("UNIQUE");
let is_primary = def.to_uppercase().contains("PRIMARY");
let columns = def
.rsplit_once('(')
.and_then(|(_, rest)| rest.strip_suffix(')'))
.map(|cols| cols.split(',').map(|c| c.trim().trim_matches('"').to_string()).collect())
.unwrap_or_default();
IndexInfo {
name,
columns,
is_unique,
is_primary,
filter: None,
index_type: None,
included_columns: None,
comment: None,
}
})
.collect())
}
pub async fn list_foreign_keys(
client: &mut GaussdbClient,
schema: &str,
table: &str,
) -> Result<Vec<ForeignKeyInfo>, String> {
let s = schema.replace('\'', "''");
let t = table.replace('\'', "''");
let sql = format!(
"SELECT con.conname, a.attname, cl2.relname, a2.attname \
FROM pg_catalog.pg_constraint con \
JOIN pg_catalog.pg_class cl ON cl.oid = con.conrelid \
JOIN pg_catalog.pg_namespace n ON n.oid = cl.relnamespace \
JOIN pg_catalog.pg_attribute a ON a.attrelid = con.conrelid AND a.attnum = ANY(con.conkey) \
JOIN pg_catalog.pg_class cl2 ON cl2.oid = con.confrelid \
JOIN pg_catalog.pg_attribute a2 ON a2.attrelid = con.confrelid AND a2.attnum = ANY(con.confkey) \
WHERE con.contype = 'f' AND n.nspname = '{s}' AND cl.relname = '{t}' \
ORDER BY con.conname"
);
let rows = client.query_rows(&sql).await?;
Ok(rows
.into_iter()
.map(|r| ForeignKeyInfo {
name: r.first().and_then(|v| v.clone()).unwrap_or_default(),
column: r.get(1).and_then(|v| v.clone()).unwrap_or_default(),
ref_table: r.get(2).and_then(|v| v.clone()).unwrap_or_default(),
ref_column: r.get(3).and_then(|v| v.clone()).unwrap_or_default(),
})
.collect())
}
pub async fn list_triggers(client: &mut GaussdbClient, schema: &str, table: &str) -> Result<Vec<TriggerInfo>, String> {
let s = schema.replace('\'', "''");
let t = table.replace('\'', "''");
let sql = format!(
"SELECT t.tgname, \
CASE WHEN t.tgtype & 4 = 4 THEN 'INSERT' WHEN t.tgtype & 8 = 8 THEN 'DELETE' \
WHEN t.tgtype & 16 = 16 THEN 'UPDATE' ELSE 'UNKNOWN' END, \
CASE WHEN t.tgtype & 2 = 2 THEN 'BEFORE' ELSE 'AFTER' END \
FROM pg_catalog.pg_trigger t \
JOIN pg_catalog.pg_class c ON c.oid = t.tgrelid \
JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace \
WHERE NOT t.tgisinternal AND n.nspname = '{s}' AND c.relname = '{t}' \
ORDER BY t.tgname"
);
let rows = client.query_rows(&sql).await?;
Ok(rows
.into_iter()
.map(|r| TriggerInfo {
name: r.first().and_then(|v| v.clone()).unwrap_or_default(),
event: r.get(1).and_then(|v| v.clone()).unwrap_or_default(),
timing: r.get(2).and_then(|v| v.clone()).unwrap_or_default(),
})
.collect())
}
pub async fn execute_query(client: &mut GaussdbClient, sql: &str) -> Result<QueryResult, String> {
let start = Instant::now();
let sql = sql.trim().trim_end_matches(';');
if starts_with_executable_sql_keyword(sql, &["SELECT", "WITH", "SHOW", "EXPLAIN"]) {
let rows = client.client.query(sql, &[]).await.map_err(|e| e.to_string())?;
let columns: Vec<String> = if let Some(first) = rows.first() {
first.columns().iter().map(|c| c.name.clone()).collect()
} else {
Vec::new()
};
let mut result_rows = Vec::new();
for row in &rows {
let vals: Vec<serde_json::Value> = (0..row.columns().len())
.map(|i| row.try_get::<String>(i).map(serde_json::Value::String).unwrap_or(serde_json::Value::Null))
.collect();
result_rows.push(vals);
if result_rows.len() >= crate::query::MAX_ROWS {
break;
}
}
let truncated = result_rows.len() >= crate::query::MAX_ROWS;
Ok(QueryResult {
columns,
rows: result_rows,
affected_rows: 0,
execution_time_ms: start.elapsed().as_millis(),
truncated,
})
} else {
let affected = client.client.execute(sql).await.map_err(|e| e.to_string())?;
Ok(QueryResult {
columns: vec![],
rows: vec![],
affected_rows: affected,
execution_time_ms: start.elapsed().as_millis(),
truncated: false,
})
}
}
#[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");
}
}
-1
View File
@@ -3,7 +3,6 @@ pub mod clickhouse_driver;
pub mod duckdb_driver;
pub mod elasticsearch_driver;
pub mod file_validator;
pub mod gaussdb_driver;
pub mod mongo_driver;
pub mod mysql;
pub mod ob_oracle;
+59
View File
@@ -117,6 +117,21 @@ pub enum DatabaseType {
Vastbase,
Goldendb,
Gaussdb,
#[serde(rename = "h2")]
H2,
Snowflake,
Trino,
Hive,
#[serde(rename = "db2")]
Db2,
Informix,
#[serde(rename = "neo4j")]
Neo4j,
Cassandra,
#[serde(rename = "bigquery")]
Bigquery,
Kylin,
Sundb,
Jdbc,
}
@@ -216,6 +231,17 @@ impl ConnectionConfig {
DatabaseType::Vastbase => format!("vastbase://{host}:{port}{db_part}"),
DatabaseType::Goldendb => format!("goldendb://{host}:{port}{db_part}"),
DatabaseType::Gaussdb => format!("gaussdb://{host}:{port}{db_part}"),
DatabaseType::H2 => format!("h2://{host}:{port}{db_part}"),
DatabaseType::Snowflake => format!("snowflake://{host}/{db_part}"),
DatabaseType::Trino => format!("trino://{host}:{port}{db_part}"),
DatabaseType::Hive => format!("hive://{host}:{port}{db_part}"),
DatabaseType::Db2 => format!("db2://{host}:{port}{db_part}"),
DatabaseType::Informix => format!("informix://{host}:{port}{db_part}"),
DatabaseType::Neo4j => format!("neo4j://{host}:{port}{db_part}"),
DatabaseType::Cassandra => format!("cassandra://{host}:{port}{db_part}"),
DatabaseType::Bigquery => format!("bigquery://{host}/{db_part}"),
DatabaseType::Kylin => format!("kylin://{host}:{port}{db_part}"),
DatabaseType::Sundb => format!("sundb://{host}:{port}{db_part}"),
DatabaseType::Jdbc => "jdbc:<redacted>".to_string(),
}
}
@@ -296,6 +322,39 @@ impl ConnectionConfig {
DatabaseType::Gaussdb => {
format!("gaussdb://{}:{}@{host}:{port}{db_part}", username, password)
}
DatabaseType::H2 => {
format!("h2://{}:{}@{host}:{port}{db_part}", username, password)
}
DatabaseType::Snowflake => {
format!("snowflake://{}:{}@{host}/{db_part}", username, password)
}
DatabaseType::Trino => {
format!("trino://{}:{}@{host}:{port}{db_part}", username, password)
}
DatabaseType::Hive => {
format!("hive://{}:{}@{host}:{port}{db_part}", username, password)
}
DatabaseType::Db2 => {
format!("db2://{}:{}@{host}:{port}{db_part}", username, password)
}
DatabaseType::Informix => {
format!("informix://{}:{}@{host}:{port}{db_part}", username, password)
}
DatabaseType::Neo4j => {
format!("neo4j://{}:{}@{host}:{port}{db_part}", username, password)
}
DatabaseType::Cassandra => {
format!("cassandra://{}:{}@{host}:{port}{db_part}", username, password)
}
DatabaseType::Bigquery => {
format!("bigquery://{}:{}@{host}/{db_part}", username, password)
}
DatabaseType::Kylin => {
format!("kylin://{}:{}@{host}:{port}{db_part}", username, password)
}
DatabaseType::Sundb => {
format!("sundb://{}:{}@{host}:{port}{db_part}", username, password)
}
DatabaseType::Jdbc => {
self.connection_string.as_deref().filter(|value| !value.is_empty()).unwrap_or("jdbc:").to_string()
}
+2 -15
View File
@@ -227,17 +227,6 @@ pub async fn do_execute(
.await
.map(truncate_result)
}
PoolKind::Gaussdb(client) => {
let client = client.clone();
let sql = sql.to_string();
drop(connections);
wait_for_query(cancel_token, async move {
let mut client = client.lock().await;
db::gaussdb_driver::execute_query(&mut client, &sql).await
})
.await
.map(truncate_result)
}
PoolKind::ExternalTabular(ext_pool) => {
if !starts_with_executable_sql_keyword(sql, &["SELECT", "WITH", "SHOW", "DESCRIBE", "EXPLAIN", "PRAGMA"]) {
return Err("External data sources are read-only. Only SELECT queries are supported.".to_string());
@@ -476,7 +465,7 @@ pub async fn execute_statements(
/// Execute multiple SQL statements within a single transaction.
/// For sqlx-based pools (Postgres/MySQL/SQLite), uses the Transaction API to
/// guarantee all statements run on the same physical connection.
/// For custom drivers (ClickHouse/SqlServer/Agent/Gaussdb), uses explicit
/// For custom drivers (ClickHouse/SqlServer/Agent), uses explicit
/// BEGIN/COMMIT/ROLLBACK on the already-single-connection client.
/// For databases that don't support explicit transactions (Redis, MongoDB, Oracle),
/// executes statements sequentially without transaction.
@@ -503,9 +492,7 @@ pub async fn execute_statements_in_transaction(
PoolKind::Postgres(pg) => TxPath::Pg(pg.clone()),
PoolKind::Mysql(mp, _mode) => TxPath::Mysql(mp.clone(), false),
PoolKind::Sqlite(sq) => TxPath::Sqlite(sq.clone()),
PoolKind::ClickHouse(_) | PoolKind::SqlServer(_) | PoolKind::Agent(_) | PoolKind::Gaussdb(_) => {
TxPath::Explicit
}
PoolKind::ClickHouse(_) | PoolKind::SqlServer(_) | PoolKind::Agent(_) => TxPath::Explicit,
PoolKind::DuckDb(_)
| PoolKind::Redis(_)
| PoolKind::MongoDb(_)
-108
View File
@@ -112,16 +112,6 @@ pub fn extract_agent(
}
}
pub fn extract_gaussdb(
connections: &HashMap<String, PoolKind>,
key: &str,
) -> Option<Arc<tokio::sync::Mutex<db::gaussdb_driver::GaussdbClient>>> {
match connections.get(key)? {
PoolKind::Gaussdb(client) => Some(client.clone()),
_ => None,
}
}
pub async fn list_databases_core(state: &AppState, connection_id: &str) -> Result<Vec<db::DatabaseInfo>, String> {
{
let connections = state.connections.read().await;
@@ -150,11 +140,6 @@ pub async fn list_databases_core(state: &AppState, connection_id: &str) -> Resul
let mut client = client.lock().await;
return client.call("list_databases", serde_json::json!({})).await;
}
if let Some(client) = extract_gaussdb(&connections, connection_id) {
drop(connections);
let mut client = client.lock().await;
return db::gaussdb_driver::list_databases(&mut client).await;
}
}
let connections = state.connections.read().await;
@@ -198,11 +183,6 @@ pub async fn list_schemas_core(state: &AppState, connection_id: &str, database:
let mut client = client.lock().await;
return client.call("list_schemas", serde_json::json!({"database": database})).await;
}
if let Some(client) = extract_gaussdb(&connections, &pool_key) {
drop(connections);
let mut client = client.lock().await;
return db::gaussdb_driver::list_schemas(&mut client).await;
}
}
let connections = state.connections.read().await;
@@ -264,11 +244,6 @@ pub async fn list_tables_core(
let mut client = client.lock().await;
return client.call("list_tables", serde_json::json!({"schema": schema})).await;
}
if let Some(client) = extract_gaussdb(&connections, &pool_key) {
drop(connections);
let mut client = client.lock().await;
return db::gaussdb_driver::list_tables(&mut client, schema).await;
}
}
let connections = state.connections.read().await;
@@ -392,11 +367,6 @@ pub async fn get_columns_core(
let mut client = client.lock().await;
return client.call("get_columns", serde_json::json!({"schema": schema, "table": table})).await;
}
if let Some(client) = extract_gaussdb(&connections, &pool_key) {
drop(connections);
let mut client = client.lock().await;
return db::gaussdb_driver::get_columns(&mut client, schema, table).await;
}
}
let connections = state.connections.read().await;
@@ -437,11 +407,6 @@ pub async fn list_indexes_core(
let mut client = client.lock().await;
return client.call("list_indexes", serde_json::json!({"schema": schema, "table": table})).await;
}
if let Some(client) = extract_gaussdb(&connections, &pool_key) {
drop(connections);
let mut client = client.lock().await;
return db::gaussdb_driver::list_indexes(&mut client, schema, table).await;
}
}
let connections = state.connections.read().await;
@@ -482,11 +447,6 @@ pub async fn list_foreign_keys_core(
let mut client = client.lock().await;
return client.call("list_foreign_keys", serde_json::json!({"schema": schema, "table": table})).await;
}
if let Some(client) = extract_gaussdb(&connections, &pool_key) {
drop(connections);
let mut client = client.lock().await;
return db::gaussdb_driver::list_foreign_keys(&mut client, schema, table).await;
}
}
let connections = state.connections.read().await;
@@ -527,11 +487,6 @@ pub async fn list_triggers_core(
let mut client = client.lock().await;
return client.call("list_triggers", serde_json::json!({"schema": schema, "table": table})).await;
}
if let Some(client) = extract_gaussdb(&connections, &pool_key) {
drop(connections);
let mut client = client.lock().await;
return db::gaussdb_driver::list_triggers(&mut client, schema, table).await;
}
}
let connections = state.connections.read().await;
@@ -598,11 +553,6 @@ pub async fn get_table_ddl_core(
let mut client = client.lock().await;
return client.call("get_table_ddl", serde_json::json!({"schema": schema, "table": table})).await;
}
if let Some(client) = extract_gaussdb(&connections, &pool_key) {
drop(connections);
let mut client = client.lock().await;
return build_gaussdb_ddl(&mut client, schema, table).await;
}
}
let connections = state.connections.read().await;
@@ -747,13 +697,6 @@ pub async fn get_object_source_core(
db::sqlserver::execute_query(&mut client, &sqlserver_object_source_sql(schema, name, &object_type))
.await?,
)?
} else if let Some(client) = extract_gaussdb(&connections, &pool_key) {
drop(connections);
let mut client = client.lock().await;
first_string_cell(
db::gaussdb_driver::execute_query(&mut client, &postgres_object_source_sql(schema, name, &object_type))
.await?,
)?
} else {
match connections.get(&pool_key).ok_or("Pool not found")? {
PoolKind::Mysql(pool, _) => mysql_object_source(pool, name, &object_type).await?,
@@ -964,54 +907,3 @@ pub async fn build_sqlserver_ddl(
}
Ok(ddl)
}
pub async fn build_gaussdb_ddl(
client: &mut db::gaussdb_driver::GaussdbClient,
schema: &str,
table: &str,
) -> Result<String, String> {
let columns = db::gaussdb_driver::get_columns(client, schema, table).await?;
let indexes = db::gaussdb_driver::list_indexes(client, schema, table).await?;
let fkeys = db::gaussdb_driver::list_foreign_keys(client, schema, table).await?;
let mut ddl = format!("CREATE TABLE \"{schema}\".\"{table}\" (\n");
let col_lines: Vec<String> = columns
.iter()
.map(|c| {
let mut line = format!(" \"{}\" {}", c.name, c.data_type);
if !c.is_nullable {
line.push_str(" NOT NULL");
}
if let Some(ref def) = c.column_default {
line.push_str(&format!(" DEFAULT {def}"));
}
line
})
.collect();
ddl.push_str(&col_lines.join(",\n"));
let pks: Vec<&str> = columns.iter().filter(|c| c.is_primary_key).map(|c| c.name.as_str()).collect();
if !pks.is_empty() {
ddl.push_str(&format!(
",\n PRIMARY KEY ({})",
pks.iter().map(|k| format!("\"{k}\"")).collect::<Vec<_>>().join(", ")
));
}
for fk in &fkeys {
ddl.push_str(&format!(
",\n CONSTRAINT \"{}\" FOREIGN KEY (\"{}\") REFERENCES \"{}\"(\"{}\")",
fk.name, fk.column, fk.ref_table, fk.ref_column
));
}
ddl.push_str("\n);\n");
for idx in &indexes {
if idx.is_primary {
continue;
}
let unique = if idx.is_unique { "UNIQUE " } else { "" };
let cols = idx.columns.iter().map(|c| format!("\"{c}\"")).collect::<Vec<_>>().join(", ");
ddl.push_str(&format!("\nCREATE {unique}INDEX \"{}\" ON \"{schema}\".\"{table}\" ({cols});", idx.name));
}
Ok(ddl)
}