feat: add GaussDB ODBC support and flatten connection list
- Add GaussDB/openGauss support via ODBC (resolves #106) - Use pg_catalog queries instead of information_schema for compatibility - Share ODBC environment across drivers with auto ODBCSYSINI detection - Fix ODBC error message false positive on "Driver" keyword match - Merge connection dialog categories into a single flat list - Remove duplicate DDL button from data grid footer
This commit is contained in:
parent
969f441753
commit
b79e5b53bc
|
|
@ -29,6 +29,7 @@ pub enum PoolKind {
|
|||
Oracle(Arc<tokio::sync::Mutex<db::oracle_driver::OracleClient>>),
|
||||
Elasticsearch(db::elasticsearch_driver::EsClient),
|
||||
Dameng(Arc<std::sync::Mutex<db::dm_driver::DmClient>>),
|
||||
Gaussdb(Arc<std::sync::Mutex<db::gaussdb_driver::GaussdbClient>>),
|
||||
}
|
||||
|
||||
pub struct AppState {
|
||||
|
|
@ -61,7 +62,8 @@ impl AppState {
|
|||
return Ok(connection_id.to_string());
|
||||
}
|
||||
|
||||
let is_single_conn = matches!(db_type, Some(DatabaseType::Oracle) | Some(DatabaseType::Dameng));
|
||||
let is_single_conn =
|
||||
matches!(db_type, Some(DatabaseType::Oracle) | Some(DatabaseType::Dameng) | Some(DatabaseType::Gaussdb));
|
||||
let pool_key = if is_single_conn {
|
||||
connection_id.to_string()
|
||||
} else {
|
||||
|
|
@ -96,7 +98,10 @@ impl AppState {
|
|||
|
||||
let mut db_config = config.clone();
|
||||
if let Some(db) = database {
|
||||
if db_config.db_type != DatabaseType::Oracle && db_config.db_type != DatabaseType::Dameng {
|
||||
if db_config.db_type != DatabaseType::Oracle
|
||||
&& db_config.db_type != DatabaseType::Dameng
|
||||
&& db_config.db_type != DatabaseType::Gaussdb
|
||||
{
|
||||
db_config.database = Some(db.to_string());
|
||||
}
|
||||
}
|
||||
|
|
@ -174,6 +179,17 @@ impl AppState {
|
|||
.await?;
|
||||
PoolKind::Dameng(Arc::new(std::sync::Mutex::new(client)))
|
||||
}
|
||||
DatabaseType::Gaussdb => {
|
||||
let client = db::gaussdb_driver::connect(
|
||||
&host,
|
||||
port,
|
||||
db_config.database.as_deref().unwrap_or(""),
|
||||
&db_config.username,
|
||||
&db_config.password,
|
||||
)
|
||||
.await?;
|
||||
PoolKind::Gaussdb(Arc::new(std::sync::Mutex::new(client)))
|
||||
}
|
||||
};
|
||||
|
||||
self.connections.lock().await.insert(pool_key.clone(), pool);
|
||||
|
|
@ -221,6 +237,7 @@ 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)
|
||||
};
|
||||
|
|
|
|||
|
|
@ -1,14 +1,11 @@
|
|||
use std::time::Instant;
|
||||
|
||||
use odbc_api::{buffers::TextRowSet, ConnectionOptions, Cursor, Environment, ResultSetMetadata};
|
||||
use odbc_api::{buffers::TextRowSet, ConnectionOptions, Cursor, ResultSetMetadata};
|
||||
|
||||
use crate::types::{ColumnInfo, DatabaseInfo, ForeignKeyInfo, IndexInfo, QueryResult, TableInfo, TriggerInfo};
|
||||
|
||||
use super::CONNECTION_TIMEOUT_SECS;
|
||||
|
||||
static ENV: std::sync::LazyLock<Environment> =
|
||||
std::sync::LazyLock::new(|| Environment::new().expect("Failed to initialize ODBC environment"));
|
||||
|
||||
pub struct DmClient {
|
||||
conn: odbc_api::Connection<'static>,
|
||||
}
|
||||
|
|
@ -60,10 +57,10 @@ pub async fn connect(host: &str, port: u16, database: &str, user: &str, pass: &s
|
|||
let result = tokio::time::timeout(
|
||||
std::time::Duration::from_secs(CONNECTION_TIMEOUT_SECS),
|
||||
tokio::task::spawn_blocking(move || {
|
||||
ENV.connect_with_connection_string(&conn_str, ConnectionOptions::default())
|
||||
super::ODBC_ENV.connect_with_connection_string(&conn_str, ConnectionOptions::default())
|
||||
.map_err(|e| {
|
||||
let msg = e.to_string();
|
||||
if msg.contains("Data source name not found") || msg.contains("Driver") {
|
||||
if msg.contains("Data source name not found") || msg.contains("Can't open lib") {
|
||||
format!(
|
||||
"DM8 ODBC driver not found. Please install the DM8 ODBC driver \
|
||||
and register it in odbcinst.ini (Linux/macOS) or the ODBC Data Source Administrator (Windows). \
|
||||
|
|
|
|||
|
|
@ -0,0 +1,342 @@
|
|||
use std::time::Instant;
|
||||
|
||||
use odbc_api::{buffers::TextRowSet, ConnectionOptions, Cursor, ResultSetMetadata};
|
||||
|
||||
use crate::types::{ColumnInfo, DatabaseInfo, ForeignKeyInfo, IndexInfo, QueryResult, TableInfo, TriggerInfo};
|
||||
|
||||
use super::CONNECTION_TIMEOUT_SECS;
|
||||
|
||||
pub struct GaussdbClient {
|
||||
conn: odbc_api::Connection<'static>,
|
||||
}
|
||||
|
||||
unsafe impl Send for GaussdbClient {}
|
||||
|
||||
impl GaussdbClient {
|
||||
pub fn query_rows(&self, sql: &str) -> Result<Vec<Vec<String>>, String> {
|
||||
match self.conn.execute(sql, (), None).map_err(|e| e.to_string())? {
|
||||
Some(cursor) => read_cursor(cursor),
|
||||
None => Ok(vec![]),
|
||||
}
|
||||
}
|
||||
|
||||
fn query_single_column(&self, sql: &str) -> Result<Vec<String>, String> {
|
||||
Ok(self.query_rows(sql)?.into_iter().filter_map(|r| r.into_iter().next()).collect())
|
||||
}
|
||||
}
|
||||
|
||||
fn read_cursor(cursor: impl Cursor) -> Result<Vec<Vec<String>>, String> {
|
||||
let mut cursor = cursor;
|
||||
let col_count = cursor.num_result_cols().map_err(|e| e.to_string())? as u16;
|
||||
let buffer = TextRowSet::for_cursor(1000, &mut cursor, Some(8192)).map_err(|e| e.to_string())?;
|
||||
let mut row_cursor = cursor.bind_buffer(buffer).map_err(|e| e.to_string())?;
|
||||
let mut rows = Vec::new();
|
||||
while let Some(batch) = row_cursor.fetch().map_err(|e| e.to_string())? {
|
||||
for row_idx in 0..batch.num_rows() {
|
||||
let vals: Vec<String> = (0..col_count as usize)
|
||||
.map(|col| {
|
||||
batch.at(col, row_idx).and_then(|bytes| std::str::from_utf8(bytes).ok()).unwrap_or("").to_string()
|
||||
})
|
||||
.collect();
|
||||
rows.push(vals);
|
||||
}
|
||||
}
|
||||
Ok(rows)
|
||||
}
|
||||
|
||||
pub async fn connect(host: &str, port: u16, database: &str, user: &str, pass: &str) -> Result<GaussdbClient, String> {
|
||||
let conn_str = format!(
|
||||
"Driver={{GaussDBA}};Servername={host};Port={port};Database={db};UID={user};PWD={pass}",
|
||||
host = host,
|
||||
port = port,
|
||||
db = database,
|
||||
user = user,
|
||||
pass = pass,
|
||||
);
|
||||
|
||||
let result = tokio::time::timeout(
|
||||
std::time::Duration::from_secs(CONNECTION_TIMEOUT_SECS),
|
||||
tokio::task::spawn_blocking(move || {
|
||||
super::ODBC_ENV.connect_with_connection_string(&conn_str, ConnectionOptions::default())
|
||||
.map_err(|e| {
|
||||
let msg = e.to_string();
|
||||
if msg.contains("Data source name not found") || msg.contains("Can't open lib") {
|
||||
format!(
|
||||
"GaussDB ODBC driver not found. Please install the GaussDB ODBC driver \
|
||||
and register it in odbcinst.ini (Linux/macOS) or the ODBC Data Source Administrator (Windows). \
|
||||
Original error: {msg}"
|
||||
)
|
||||
} else {
|
||||
format!("GaussDB connection failed: {msg}")
|
||||
}
|
||||
})
|
||||
.map(|conn| GaussdbClient { conn })
|
||||
}),
|
||||
)
|
||||
.await
|
||||
.map_err(|_| format!("GaussDB connection timed out ({CONNECTION_TIMEOUT_SECS}s)"))?
|
||||
.map_err(|e| format!("GaussDB connection task failed: {e}"))?;
|
||||
|
||||
result
|
||||
}
|
||||
|
||||
pub fn list_databases(client: &GaussdbClient) -> Result<Vec<DatabaseInfo>, String> {
|
||||
let rows =
|
||||
client.query_single_column("SELECT datname FROM pg_database WHERE datistemplate = false ORDER BY datname")?;
|
||||
Ok(rows.into_iter().map(|name| DatabaseInfo { name }).collect())
|
||||
}
|
||||
|
||||
pub fn list_schemas(client: &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') \
|
||||
AND EXISTS (SELECT 1 FROM pg_catalog.pg_class c WHERE c.relnamespace = n.oid AND c.relkind IN ('r', 'v', 'm')) \
|
||||
ORDER BY nspname",
|
||||
)
|
||||
}
|
||||
|
||||
pub fn list_tables(client: &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)?;
|
||||
Ok(rows
|
||||
.into_iter()
|
||||
.map(|r| {
|
||||
let raw_type = r.get(1).cloned().unwrap_or_default();
|
||||
TableInfo {
|
||||
name: r.first().cloned().unwrap_or_default(),
|
||||
table_type: if raw_type.contains("VIEW") { "VIEW".to_string() } else { "TABLE".to_string() },
|
||||
}
|
||||
})
|
||||
.collect())
|
||||
}
|
||||
|
||||
pub fn get_columns(client: &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"
|
||||
))?;
|
||||
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"
|
||||
))?;
|
||||
|
||||
Ok(col_rows
|
||||
.into_iter()
|
||||
.map(|r| {
|
||||
let name = r.first().cloned().unwrap_or_default();
|
||||
let data_type = r.get(1).cloned().unwrap_or_default();
|
||||
let num_prec = r.get(4).and_then(|v| v.parse::<i32>().ok());
|
||||
let num_scale = r.get(5).and_then(|v| v.parse::<i32>().ok());
|
||||
let char_len = r.get(6).and_then(|v| v.parse::<i32>().ok());
|
||||
ColumnInfo {
|
||||
is_primary_key: pk_names.contains(&name),
|
||||
name,
|
||||
data_type,
|
||||
is_nullable: r.get(2).map(|v| v == "YES").unwrap_or(false),
|
||||
column_default: r.get(3).filter(|v| !v.is_empty()).cloned(),
|
||||
extra: None,
|
||||
comment: None,
|
||||
numeric_precision: num_prec,
|
||||
numeric_scale: num_scale,
|
||||
character_maximum_length: char_len,
|
||||
}
|
||||
})
|
||||
.collect())
|
||||
}
|
||||
|
||||
pub fn list_indexes(client: &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)?;
|
||||
Ok(rows
|
||||
.into_iter()
|
||||
.map(|r| {
|
||||
let name = r.first().cloned().unwrap_or_default();
|
||||
let def = r.get(1).cloned().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 fn list_foreign_keys(client: &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)?;
|
||||
Ok(rows
|
||||
.into_iter()
|
||||
.map(|r| ForeignKeyInfo {
|
||||
name: r.first().cloned().unwrap_or_default(),
|
||||
column: r.get(1).cloned().unwrap_or_default(),
|
||||
ref_table: r.get(2).cloned().unwrap_or_default(),
|
||||
ref_column: r.get(3).cloned().unwrap_or_default(),
|
||||
})
|
||||
.collect())
|
||||
}
|
||||
|
||||
pub fn list_triggers(client: &GaussdbClient, schema: &str, table: &str) -> Result<Vec<TriggerInfo>, String> {
|
||||
let s = schema.replace('\'', "''");
|
||||
let t = table.replace('\'', "''");
|
||||
let sql = format!(
|
||||
"SELECT t.tgname, em.event, 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 \
|
||||
CROSS JOIN LATERAL ( \
|
||||
SELECT 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 AS event \
|
||||
) em \
|
||||
WHERE NOT t.tgisinternal AND n.nspname = '{s}' AND c.relname = '{t}' \
|
||||
ORDER BY t.tgname"
|
||||
);
|
||||
let rows = client.query_rows(&sql)?;
|
||||
Ok(rows
|
||||
.into_iter()
|
||||
.map(|r| TriggerInfo {
|
||||
name: r.first().cloned().unwrap_or_default(),
|
||||
event: r.get(1).cloned().unwrap_or_default(),
|
||||
timing: r.get(2).cloned().unwrap_or_default(),
|
||||
})
|
||||
.collect())
|
||||
}
|
||||
|
||||
pub fn execute_query_sync(client: &GaussdbClient, sql: &str) -> Result<QueryResult, String> {
|
||||
let start = Instant::now();
|
||||
let sql = sql.trim().trim_end_matches(';');
|
||||
let trimmed = sql.to_uppercase();
|
||||
|
||||
if trimmed.starts_with("SELECT")
|
||||
|| trimmed.starts_with("WITH")
|
||||
|| trimmed.starts_with("SHOW")
|
||||
|| trimmed.starts_with("DESCRIBE")
|
||||
|| trimmed.starts_with("EXPLAIN")
|
||||
{
|
||||
match client.conn.execute(sql, (), None).map_err(|e| e.to_string())? {
|
||||
Some(mut cursor) => {
|
||||
let col_count = cursor.num_result_cols().map_err(|e| e.to_string())? as u16;
|
||||
let columns: Vec<String> = (1..=col_count)
|
||||
.map(|i| cursor.col_name(i).map_err(|e| e.to_string()).unwrap_or_else(|_| format!("col{i}")))
|
||||
.collect();
|
||||
|
||||
let buffer = TextRowSet::for_cursor(1000, &mut cursor, Some(8192)).map_err(|e| e.to_string())?;
|
||||
let mut row_cursor = cursor.bind_buffer(buffer).map_err(|e| e.to_string())?;
|
||||
|
||||
let mut rows = Vec::new();
|
||||
while let Some(batch) = row_cursor.fetch().map_err(|e| e.to_string())? {
|
||||
for row_idx in 0..batch.num_rows() {
|
||||
let vals: Vec<serde_json::Value> = (0..col_count as usize)
|
||||
.map(|col| {
|
||||
batch
|
||||
.at(col, row_idx)
|
||||
.and_then(|bytes| std::str::from_utf8(bytes).ok())
|
||||
.map(|s| serde_json::Value::String(s.to_string()))
|
||||
.unwrap_or(serde_json::Value::Null)
|
||||
})
|
||||
.collect();
|
||||
rows.push(vals);
|
||||
if rows.len() >= crate::query::MAX_ROWS {
|
||||
break;
|
||||
}
|
||||
}
|
||||
if rows.len() >= crate::query::MAX_ROWS {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
let truncated = rows.len() >= crate::query::MAX_ROWS;
|
||||
Ok(QueryResult {
|
||||
columns,
|
||||
rows,
|
||||
affected_rows: 0,
|
||||
execution_time_ms: start.elapsed().as_millis(),
|
||||
truncated,
|
||||
})
|
||||
}
|
||||
None => Ok(QueryResult {
|
||||
columns: vec![],
|
||||
rows: vec![],
|
||||
affected_rows: 0,
|
||||
execution_time_ms: start.elapsed().as_millis(),
|
||||
truncated: false,
|
||||
}),
|
||||
}
|
||||
} else {
|
||||
match client.conn.execute(sql, (), None) {
|
||||
Ok(Some(_cursor)) => Ok(QueryResult {
|
||||
columns: vec![],
|
||||
rows: vec![],
|
||||
affected_rows: 0,
|
||||
execution_time_ms: start.elapsed().as_millis(),
|
||||
truncated: false,
|
||||
}),
|
||||
Ok(None) => Ok(QueryResult {
|
||||
columns: vec![],
|
||||
rows: vec![],
|
||||
affected_rows: 0,
|
||||
execution_time_ms: start.elapsed().as_millis(),
|
||||
truncated: false,
|
||||
}),
|
||||
Err(e) => Err(e.to_string()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -3,6 +3,7 @@ pub mod dm_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 oracle_driver;
|
||||
|
|
@ -22,6 +23,19 @@ pub use file_validator::validate_file_path;
|
|||
pub const CONNECTION_TIMEOUT_SECS: u64 = 5;
|
||||
pub const TCP_PROBE_TIMEOUT_SECS: u64 = 3;
|
||||
|
||||
pub static ODBC_ENV: std::sync::LazyLock<odbc_api::Environment> = std::sync::LazyLock::new(|| {
|
||||
if std::env::var("ODBCSYSINI").is_err() {
|
||||
for dir in &["/opt/homebrew/etc", "/usr/local/etc", "/etc"] {
|
||||
let path = std::path::Path::new(dir).join("odbcinst.ini");
|
||||
if path.exists() {
|
||||
std::env::set_var("ODBCSYSINI", dir);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
odbc_api::Environment::new().expect("Failed to initialize ODBC environment")
|
||||
});
|
||||
|
||||
pub fn connection_timeout() -> Duration {
|
||||
Duration::from_secs(CONNECTION_TIMEOUT_SECS)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -71,6 +71,7 @@ pub enum DatabaseType {
|
|||
StarRocks,
|
||||
Redshift,
|
||||
Dameng,
|
||||
Gaussdb,
|
||||
}
|
||||
|
||||
impl ConnectionConfig {
|
||||
|
|
@ -130,6 +131,7 @@ impl ConnectionConfig {
|
|||
DatabaseType::Oracle => format!("oracle://{host}:{port}{db_part}"),
|
||||
DatabaseType::Elasticsearch => format!("http://{host}:{port}"),
|
||||
DatabaseType::Dameng => format!("dm://{host}:{port}{db_part}"),
|
||||
DatabaseType::Gaussdb => format!("gaussdb://{host}:{port}{db_part}"),
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -191,6 +193,9 @@ impl ConnectionConfig {
|
|||
DatabaseType::Dameng => {
|
||||
format!("dm://{}:{}@{host}:{port}{db_part}", username, password)
|
||||
}
|
||||
DatabaseType::Gaussdb => {
|
||||
format!("gaussdb://{}:{}@{host}:{port}{db_part}", username, password)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -228,6 +228,20 @@ 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 task = tokio::task::spawn_blocking(move || {
|
||||
let client = client.lock().map_err(|e| e.to_string())?;
|
||||
db::gaussdb_driver::execute_query_sync(&client, &sql)
|
||||
});
|
||||
task.await.map_err(|e| e.to_string())?
|
||||
})
|
||||
.await
|
||||
.map(truncate_result)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -110,6 +110,16 @@ pub fn extract_dameng(
|
|||
}
|
||||
}
|
||||
|
||||
pub fn extract_gaussdb(
|
||||
connections: &HashMap<String, PoolKind>,
|
||||
key: &str,
|
||||
) -> Option<Arc<std::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.lock().await;
|
||||
|
|
@ -132,6 +142,11 @@ pub async fn list_databases_core(state: &AppState, connection_id: &str) -> Resul
|
|||
let client = client.lock().map_err(|e| e.to_string())?;
|
||||
return db::dm_driver::list_databases(&client);
|
||||
}
|
||||
if let Some(client) = extract_gaussdb(&connections, connection_id) {
|
||||
drop(connections);
|
||||
let client = client.lock().map_err(|e| e.to_string())?;
|
||||
return db::gaussdb_driver::list_databases(&client);
|
||||
}
|
||||
}
|
||||
|
||||
let connections = state.connections.lock().await;
|
||||
|
|
@ -166,6 +181,11 @@ pub async fn list_schemas_core(state: &AppState, connection_id: &str, database:
|
|||
let client = client.lock().map_err(|e| e.to_string())?;
|
||||
return db::dm_driver::list_schemas(&client);
|
||||
}
|
||||
if let Some(client) = extract_gaussdb(&connections, &pool_key) {
|
||||
drop(connections);
|
||||
let client = client.lock().map_err(|e| e.to_string())?;
|
||||
return db::gaussdb_driver::list_schemas(&client);
|
||||
}
|
||||
}
|
||||
|
||||
let connections = state.connections.lock().await;
|
||||
|
|
@ -211,6 +231,11 @@ pub async fn list_tables_core(
|
|||
let client = client.lock().map_err(|e| e.to_string())?;
|
||||
return db::dm_driver::list_tables(&client, schema);
|
||||
}
|
||||
if let Some(client) = extract_gaussdb(&connections, &pool_key) {
|
||||
drop(connections);
|
||||
let client = client.lock().map_err(|e| e.to_string())?;
|
||||
return db::gaussdb_driver::list_tables(&client, schema);
|
||||
}
|
||||
}
|
||||
|
||||
let connections = state.connections.lock().await;
|
||||
|
|
@ -259,6 +284,11 @@ pub async fn get_columns_core(
|
|||
let client = client.lock().map_err(|e| e.to_string())?;
|
||||
return db::dm_driver::get_columns(&client, schema, table);
|
||||
}
|
||||
if let Some(client) = extract_gaussdb(&connections, &pool_key) {
|
||||
drop(connections);
|
||||
let client = client.lock().map_err(|e| e.to_string())?;
|
||||
return db::gaussdb_driver::get_columns(&client, schema, table);
|
||||
}
|
||||
}
|
||||
|
||||
let connections = state.connections.lock().await;
|
||||
|
|
@ -298,6 +328,11 @@ pub async fn list_indexes_core(
|
|||
let client = client.lock().map_err(|e| e.to_string())?;
|
||||
return db::dm_driver::list_indexes(&client, schema, table);
|
||||
}
|
||||
if let Some(client) = extract_gaussdb(&connections, &pool_key) {
|
||||
drop(connections);
|
||||
let client = client.lock().map_err(|e| e.to_string())?;
|
||||
return db::gaussdb_driver::list_indexes(&client, schema, table);
|
||||
}
|
||||
}
|
||||
|
||||
let connections = state.connections.lock().await;
|
||||
|
|
@ -337,6 +372,11 @@ pub async fn list_foreign_keys_core(
|
|||
let client = client.lock().map_err(|e| e.to_string())?;
|
||||
return db::dm_driver::list_foreign_keys(&client, schema, table);
|
||||
}
|
||||
if let Some(client) = extract_gaussdb(&connections, &pool_key) {
|
||||
drop(connections);
|
||||
let client = client.lock().map_err(|e| e.to_string())?;
|
||||
return db::gaussdb_driver::list_foreign_keys(&client, schema, table);
|
||||
}
|
||||
}
|
||||
|
||||
let connections = state.connections.lock().await;
|
||||
|
|
@ -376,6 +416,11 @@ pub async fn list_triggers_core(
|
|||
let client = client.lock().map_err(|e| e.to_string())?;
|
||||
return db::dm_driver::list_triggers(&client, schema, table);
|
||||
}
|
||||
if let Some(client) = extract_gaussdb(&connections, &pool_key) {
|
||||
drop(connections);
|
||||
let client = client.lock().map_err(|e| e.to_string())?;
|
||||
return db::gaussdb_driver::list_triggers(&client, schema, table);
|
||||
}
|
||||
}
|
||||
|
||||
let connections = state.connections.lock().await;
|
||||
|
|
@ -441,6 +486,11 @@ pub async fn get_table_ddl_core(
|
|||
let client = client.lock().map_err(|e| e.to_string())?;
|
||||
return build_dameng_ddl(&client, schema, table);
|
||||
}
|
||||
if let Some(client) = extract_gaussdb(&connections, &pool_key) {
|
||||
drop(connections);
|
||||
let client = client.lock().map_err(|e| e.to_string())?;
|
||||
return build_gaussdb_ddl(&client, schema, table);
|
||||
}
|
||||
}
|
||||
|
||||
let connections = state.connections.lock().await;
|
||||
|
|
@ -695,3 +745,54 @@ pub fn build_dameng_ddl(client: &db::dm_driver::DmClient, schema: &str, table: &
|
|||
}
|
||||
Ok(ddl)
|
||||
}
|
||||
|
||||
pub fn build_gaussdb_ddl(
|
||||
client: &db::gaussdb_driver::GaussdbClient,
|
||||
schema: &str,
|
||||
table: &str,
|
||||
) -> Result<String, String> {
|
||||
let columns = db::gaussdb_driver::get_columns(client, schema, table)?;
|
||||
let indexes = db::gaussdb_driver::list_indexes(client, schema, table)?;
|
||||
let fkeys = db::gaussdb_driver::list_foreign_keys(client, schema, table)?;
|
||||
|
||||
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)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -121,6 +121,15 @@ pub async fn test_connection(state: State<'_, Arc<AppState>>, config: Connection
|
|||
)
|
||||
.await
|
||||
.map(|_| "Connection successful".to_string()),
|
||||
DatabaseType::Gaussdb => db::gaussdb_driver::connect(
|
||||
&host,
|
||||
port,
|
||||
config.database.as_deref().unwrap_or(""),
|
||||
&config.username,
|
||||
&config.password,
|
||||
)
|
||||
.await
|
||||
.map(|_| "Connection successful".to_string()),
|
||||
},
|
||||
};
|
||||
|
||||
|
|
@ -199,6 +208,17 @@ pub async fn connect_db(state: State<'_, Arc<AppState>>, config: ConnectionConfi
|
|||
.await?;
|
||||
PoolKind::Dameng(std::sync::Arc::new(std::sync::Mutex::new(client)))
|
||||
}
|
||||
DatabaseType::Gaussdb => {
|
||||
let client = db::gaussdb_driver::connect(
|
||||
&host,
|
||||
port,
|
||||
config.database.as_deref().unwrap_or(""),
|
||||
&config.username,
|
||||
&config.password,
|
||||
)
|
||||
.await?;
|
||||
PoolKind::Gaussdb(std::sync::Arc::new(std::sync::Mutex::new(client)))
|
||||
}
|
||||
};
|
||||
|
||||
state.connections.lock().await.insert(id.clone(), pool);
|
||||
|
|
@ -226,6 +246,7 @@ pub async fn disconnect_db(state: State<'_, Arc<AppState>>, connection_id: Strin
|
|||
PoolKind::Oracle(_) => {}
|
||||
PoolKind::Elasticsearch(_) => {}
|
||||
PoolKind::Dameng(_) => {}
|
||||
PoolKind::Gaussdb(_) => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -138,7 +138,7 @@ const driverProfiles: Record<
|
|||
icon: "opengauss",
|
||||
urlParams: "sslmode=disable",
|
||||
},
|
||||
gaussdb: { type: "postgres", port: 5432, user: "gaussdb", label: "GaussDB", icon: "gaussdb" },
|
||||
gaussdb: { type: "gaussdb", port: 5432, user: "gaussdb", label: "GaussDB", icon: "gaussdb" },
|
||||
kingbase: { type: "postgres", port: 54321, user: "system", label: "KingBase", icon: "kingbase" },
|
||||
vastbase: { type: "postgres", port: 5432, user: "vastbase", label: "Vastbase", icon: "vastbase" },
|
||||
doris: { type: "mysql", port: 9030, user: "root", label: "Doris", icon: "doris", urlParams: "" },
|
||||
|
|
@ -323,9 +323,7 @@ const dbOptions = [
|
|||
{ value: "elasticsearch", label: "Elasticsearch" },
|
||||
{ value: "mariadb", label: "MariaDB" },
|
||||
{ value: "dm", label: "DM (Dameng)" },
|
||||
];
|
||||
|
||||
const mysqlCompat = [
|
||||
{ value: "gaussdb", label: "GaussDB" },
|
||||
{ value: "tidb", label: "TiDB" },
|
||||
{ value: "oceanbase", label: "OceanBase" },
|
||||
{ value: "goldendb", label: "GoldenDB" },
|
||||
|
|
@ -333,24 +331,16 @@ const mysqlCompat = [
|
|||
{ value: "selectdb", label: "SelectDB" },
|
||||
{ value: "starrocks", label: "StarRocks" },
|
||||
{ value: "tdengine", label: "TDengine" },
|
||||
{ value: "custom_mysql", label: "Custom" },
|
||||
];
|
||||
|
||||
const pgCompat = [
|
||||
{ value: "opengauss", label: "openGauss" },
|
||||
{ value: "gaussdb", label: "GaussDB" },
|
||||
{ value: "kingbase", label: "KingBase" },
|
||||
{ value: "vastbase", label: "Vastbase" },
|
||||
{ value: "redshift", label: "Redshift" },
|
||||
{ value: "cockroachdb", label: "CockroachDB" },
|
||||
{ value: "custom_postgres", label: "Custom" },
|
||||
{ value: "custom_mysql", label: "Custom (MySQL)" },
|
||||
{ value: "custom_postgres", label: "Custom (PostgreSQL)" },
|
||||
];
|
||||
|
||||
const dbCategories = computed<DbCategory[]>(() => [
|
||||
{ key: "mainstream", title: t("connection.mainstream"), options: dbOptions },
|
||||
{ key: "mysql", title: `MySQL ${t("connection.compatible")}`, options: mysqlCompat },
|
||||
{ key: "postgres", title: `PostgreSQL ${t("connection.compatible")}`, options: pgCompat },
|
||||
]);
|
||||
const dbCategories = computed<DbCategory[]>(() => [{ key: "all", title: "", options: dbOptions }]);
|
||||
|
||||
const filteredDbCategories = computed<DbCategory[]>(() => {
|
||||
const keyword = dbSearchQuery.value.trim().toLowerCase();
|
||||
|
|
@ -854,6 +844,20 @@ async function browseDbFilePath() {
|
|||
</p>
|
||||
</div>
|
||||
|
||||
<div v-if="selectedType === 'gaussdb'" class="grid grid-cols-4 items-center gap-4">
|
||||
<span />
|
||||
<p class="col-span-3 text-xs text-muted-foreground">
|
||||
{{ t("connection.gaussdbOdbcHint") }}
|
||||
<a
|
||||
href="https://support.huaweicloud.com/mgtg-dws/dws_01_0032.html"
|
||||
target="_blank"
|
||||
class="underline text-primary hover:text-primary/80"
|
||||
>
|
||||
{{ t("connection.gaussdbDownload") }}
|
||||
</a>
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div v-if="form.db_type === 'oracle'" class="grid grid-cols-4 items-center gap-4">
|
||||
<Label class="text-right text-xs">SYSDBA</Label>
|
||||
<label class="col-span-3 flex items-center gap-2 cursor-pointer">
|
||||
|
|
|
|||
|
|
@ -49,8 +49,17 @@ const props = defineProps<{
|
|||
focusTableName?: string;
|
||||
}>();
|
||||
|
||||
const SQL_TYPES: DatabaseType[] = ["mysql", "postgres", "sqlite", "sqlserver", "oracle", "redshift", "dameng"];
|
||||
const SCHEMA_AWARE_TYPES: DatabaseType[] = ["postgres", "sqlserver", "oracle", "redshift", "dameng"];
|
||||
const SQL_TYPES: DatabaseType[] = [
|
||||
"mysql",
|
||||
"postgres",
|
||||
"sqlite",
|
||||
"sqlserver",
|
||||
"oracle",
|
||||
"redshift",
|
||||
"dameng",
|
||||
"gaussdb",
|
||||
];
|
||||
const SCHEMA_AWARE_TYPES: DatabaseType[] = ["postgres", "sqlserver", "oracle", "redshift", "dameng", "gaussdb"];
|
||||
const CARD_WIDTH = 270;
|
||||
const COLUMN_ROW_HEIGHT = 24;
|
||||
const CARD_HEADER_HEIGHT = 44;
|
||||
|
|
|
|||
|
|
@ -77,7 +77,8 @@ async function resolveSchema(connectionId: string, database: string): Promise<st
|
|||
config?.db_type === "sqlserver" ||
|
||||
config?.db_type === "oracle" ||
|
||||
config?.db_type === "redshift" ||
|
||||
config?.db_type === "dameng";
|
||||
config?.db_type === "dameng" ||
|
||||
config?.db_type === "gaussdb";
|
||||
if (needsSchema) {
|
||||
const schemas = await api.listSchemas(connectionId, database);
|
||||
return schemas.includes("public") ? "public" : (schemas[0] ?? "");
|
||||
|
|
|
|||
|
|
@ -1663,9 +1663,6 @@ function escapeAndHighlightKeywords(s: string): string {
|
|||
<Button v-if="hasPendingChanges" variant="ghost" size="sm" class="h-5 text-xs" @click="discardChanges">
|
||||
{{ t("grid.discard") }}
|
||||
</Button>
|
||||
<Button variant="ghost" size="sm" class="h-5 text-xs" @click="toggleDdl">
|
||||
<Code2 class="w-3 h-3 mr-1" /> DDL
|
||||
</Button>
|
||||
</template>
|
||||
|
||||
<span class="ml-auto flex items-center gap-1">
|
||||
|
|
|
|||
|
|
@ -54,7 +54,7 @@ type SearchTableTask = {
|
|||
table: TableInfo;
|
||||
};
|
||||
|
||||
const SCHEMA_AWARE_TYPES = new Set<DatabaseType>(["postgres", "sqlserver", "oracle", "redshift", "dameng"]);
|
||||
const SCHEMA_AWARE_TYPES = new Set<DatabaseType>(["postgres", "sqlserver", "oracle", "redshift", "dameng", "gaussdb"]);
|
||||
const SYSTEM_SCHEMAS = new Set([
|
||||
"information_schema",
|
||||
"pg_catalog",
|
||||
|
|
|
|||
|
|
@ -87,7 +87,16 @@ const emit = defineEmits<{
|
|||
}>();
|
||||
|
||||
const sqlFileUnsupportedTypes = new Set(["redis", "mongodb", "elasticsearch"]);
|
||||
const diagramSupportedTypes = new Set(["mysql", "postgres", "sqlite", "sqlserver", "oracle", "redshift", "dameng"]);
|
||||
const diagramSupportedTypes = new Set([
|
||||
"mysql",
|
||||
"postgres",
|
||||
"sqlite",
|
||||
"sqlserver",
|
||||
"oracle",
|
||||
"redshift",
|
||||
"dameng",
|
||||
"gaussdb",
|
||||
]);
|
||||
const databaseSearchSupportedTypes = new Set([
|
||||
"mysql",
|
||||
"postgres",
|
||||
|
|
@ -98,6 +107,7 @@ const databaseSearchSupportedTypes = new Set([
|
|||
"duckdb",
|
||||
"clickhouse",
|
||||
"dameng",
|
||||
"gaussdb",
|
||||
]);
|
||||
const tableImportSupportedTypes = new Set([
|
||||
"mysql",
|
||||
|
|
@ -111,6 +121,7 @@ const tableImportSupportedTypes = new Set([
|
|||
"starrocks",
|
||||
"redshift",
|
||||
"dameng",
|
||||
"gaussdb",
|
||||
]);
|
||||
const tableStructureSupportedTypes = new Set(["mysql", "postgres", "sqlite", "sqlserver"]);
|
||||
const fieldLineageSupportedTypes = new Set([
|
||||
|
|
@ -121,6 +132,7 @@ const fieldLineageSupportedTypes = new Set([
|
|||
"oracle",
|
||||
"redshift",
|
||||
"dameng",
|
||||
"gaussdb",
|
||||
]);
|
||||
const isExportingDatabase = ref(false);
|
||||
|
||||
|
|
@ -133,7 +145,13 @@ function quoteIdent(name: string): string {
|
|||
}
|
||||
|
||||
function isSchemaAwareDbType(dbType?: DatabaseType): boolean {
|
||||
return dbType === "postgres" || dbType === "oracle" || dbType === "sqlserver" || dbType === "dameng";
|
||||
return (
|
||||
dbType === "postgres" ||
|
||||
dbType === "oracle" ||
|
||||
dbType === "sqlserver" ||
|
||||
dbType === "dameng" ||
|
||||
dbType === "gaussdb"
|
||||
);
|
||||
}
|
||||
|
||||
function qualifiedTableName(tableName: string, schema?: string): string {
|
||||
|
|
@ -242,7 +260,7 @@ async function toggle() {
|
|||
queryStore.updateSql(tab, node.label);
|
||||
} else if (node.type === "database" && node.connectionId && node.database) {
|
||||
const config = connectionStore.getConfig(node.connectionId);
|
||||
if (config?.db_type === "postgres" || config?.db_type === "sqlserver") {
|
||||
if (config?.db_type === "postgres" || config?.db_type === "sqlserver" || config?.db_type === "gaussdb") {
|
||||
await connectionStore.loadSchemas(node.connectionId, node.database);
|
||||
} else {
|
||||
await connectionStore.loadTables(node.connectionId, node.database);
|
||||
|
|
@ -472,7 +490,7 @@ async function confirmDuplicateStructure() {
|
|||
let sql: string;
|
||||
if (dbType === "mysql") {
|
||||
sql = `CREATE TABLE ${target} LIKE ${source};`;
|
||||
} else if (dbType === "postgres" || dbType === "redshift") {
|
||||
} else if (dbType === "postgres" || dbType === "redshift" || dbType === "gaussdb") {
|
||||
sql = `CREATE TABLE ${target} (LIKE ${source} INCLUDING ALL);`;
|
||||
} else if (dbType === "sqlserver") {
|
||||
sql = `SELECT TOP 0 * INTO ${target} FROM ${source};`;
|
||||
|
|
|
|||
|
|
@ -34,6 +34,7 @@ const SQL_TYPES: DatabaseType[] = [
|
|||
"clickhouse",
|
||||
"duckdb",
|
||||
"dameng",
|
||||
"gaussdb",
|
||||
];
|
||||
|
||||
const sqlConnections = computed(() => store.connections.filter((c) => SQL_TYPES.includes(c.db_type)));
|
||||
|
|
@ -134,7 +135,8 @@ async function loadTables() {
|
|||
config?.db_type === "postgres" ||
|
||||
config?.db_type === "sqlserver" ||
|
||||
config?.db_type === "oracle" ||
|
||||
config?.db_type === "dameng";
|
||||
config?.db_type === "dameng" ||
|
||||
config?.db_type === "gaussdb";
|
||||
if (needsSchema) {
|
||||
const schemas = await api.listSchemas(sourceConnectionId.value, sourceDatabase.value);
|
||||
sourceSchema.value = schemas.includes("public") ? "public" : (schemas[0] ?? "");
|
||||
|
|
@ -225,7 +227,8 @@ async function startTransfer() {
|
|||
targetConfig?.db_type === "postgres" ||
|
||||
targetConfig?.db_type === "sqlserver" ||
|
||||
targetConfig?.db_type === "oracle" ||
|
||||
targetConfig?.db_type === "dameng";
|
||||
targetConfig?.db_type === "dameng" ||
|
||||
targetConfig?.db_type === "gaussdb";
|
||||
if (targetNeedsSchema && !targetSchema.value) {
|
||||
try {
|
||||
const schemas = await api.listSchemas(targetConnectionId.value, targetDatabase.value);
|
||||
|
|
|
|||
|
|
@ -14,7 +14,13 @@ export function useSchemaOptions() {
|
|||
|
||||
function isSchemaAware(connectionId: string): boolean {
|
||||
const dbType = connectionStore.getConfig(connectionId)?.db_type;
|
||||
return dbType === "postgres" || dbType === "sqlserver" || dbType === "oracle" || dbType === "dameng";
|
||||
return (
|
||||
dbType === "postgres" ||
|
||||
dbType === "sqlserver" ||
|
||||
dbType === "oracle" ||
|
||||
dbType === "dameng" ||
|
||||
dbType === "gaussdb"
|
||||
);
|
||||
}
|
||||
|
||||
async function loadSchemaOptions(connectionId: string, database: string) {
|
||||
|
|
|
|||
|
|
@ -113,6 +113,8 @@ export default {
|
|||
sshExposeLan: "Expose tunnel to LAN",
|
||||
dmCompatHint: "Requires DM8 ODBC driver installed on your system.",
|
||||
dmDownload: "Download from Dameng",
|
||||
gaussdbOdbcHint: "Requires GaussDB ODBC driver installed on your system.",
|
||||
gaussdbDownload: "Download from Huawei Cloud",
|
||||
mongoLegacyHint:
|
||||
"For older MongoDB servers, enter authSource=admin&authMechanism=SCRAM-SHA-1 here; add directConnection=true only when needed for direct standalone connections.",
|
||||
compatible: "Compatible",
|
||||
|
|
|
|||
|
|
@ -112,6 +112,8 @@ export default {
|
|||
sshExposeLan: "允许局域网访问隧道",
|
||||
dmCompatHint: "需要在系统上安装达梦 DM8 ODBC 驱动程序。",
|
||||
dmDownload: "前往达梦官网下载",
|
||||
gaussdbOdbcHint: "需要在系统上安装 GaussDB ODBC 驱动程序。",
|
||||
gaussdbDownload: "前往华为云下载",
|
||||
mongoLegacyHint:
|
||||
"连接旧版 MongoDB 时,可在此填写 authSource=admin&authMechanism=SCRAM-SHA-1;直连单节点时可按需追加 directConnection=true。",
|
||||
compatible: "兼容",
|
||||
|
|
|
|||
|
|
@ -26,7 +26,8 @@ export function qualifiedTableName(
|
|||
(databaseType === "postgres" ||
|
||||
databaseType === "oracle" ||
|
||||
databaseType === "sqlserver" ||
|
||||
databaseType === "dameng") &&
|
||||
databaseType === "dameng" ||
|
||||
databaseType === "gaussdb") &&
|
||||
schema
|
||||
) {
|
||||
return `${quoteTableIdentifier(databaseType, schema)}.${quoteTableIdentifier(databaseType, tableName)}`;
|
||||
|
|
|
|||
|
|
@ -129,6 +129,7 @@ export const useConnectionStore = defineStore("connection", () => {
|
|||
starrocks: "StarRocks",
|
||||
redshift: "Redshift",
|
||||
dameng: "DM (Dameng)",
|
||||
gaussdb: "GaussDB",
|
||||
};
|
||||
return {
|
||||
...config,
|
||||
|
|
@ -653,7 +654,13 @@ export const useConnectionStore = defineStore("connection", () => {
|
|||
|
||||
function isSchemaAwareDatabase(connectionId: string): boolean {
|
||||
const dbType = getConfig(connectionId)?.db_type;
|
||||
return dbType === "postgres" || dbType === "sqlserver" || dbType === "oracle" || dbType === "dameng";
|
||||
return (
|
||||
dbType === "postgres" ||
|
||||
dbType === "sqlserver" ||
|
||||
dbType === "oracle" ||
|
||||
dbType === "dameng" ||
|
||||
dbType === "gaussdb"
|
||||
);
|
||||
}
|
||||
|
||||
async function listCompletionTables(connectionId: string, database: string): Promise<SqlCompletionTable[]> {
|
||||
|
|
|
|||
|
|
@ -12,7 +12,8 @@ export type DatabaseType =
|
|||
| "doris"
|
||||
| "starrocks"
|
||||
| "redshift"
|
||||
| "dameng";
|
||||
| "dameng"
|
||||
| "gaussdb";
|
||||
|
||||
export interface ConnectionConfig {
|
||||
id: string;
|
||||
|
|
|
|||
Loading…
Reference in New Issue