feat: add Oracle database support
This commit is contained in:
parent
5dfe340551
commit
2e971ce152
|
|
@ -8,6 +8,17 @@ version = "2.0.1"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "320119579fcad9c21884f5c4861d16174d0e06250625266f50fe6898340abefa"
|
||||
|
||||
[[package]]
|
||||
name = "aes"
|
||||
version = "0.8.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b169f7a6d4742236a0a00c541b845991d0ac43e546831af1249753ab4c3aa3a0"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"cipher",
|
||||
"cpufeatures",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ahash"
|
||||
version = "0.7.8"
|
||||
|
|
@ -353,6 +364,28 @@ version = "1.5.0"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8"
|
||||
|
||||
[[package]]
|
||||
name = "aws-lc-rs"
|
||||
version = "1.16.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0ec6fb3fe69024a75fa7e1bfb48aa6cf59706a101658ea01bfd33b2b248a038f"
|
||||
dependencies = [
|
||||
"aws-lc-sys",
|
||||
"zeroize",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "aws-lc-sys"
|
||||
version = "0.40.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f50037ee5e1e41e7b8f9d161680a725bd1626cb6f8c7e901f91f942850852fe7"
|
||||
dependencies = [
|
||||
"cc",
|
||||
"cmake",
|
||||
"dunce",
|
||||
"fs_extra",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "base64"
|
||||
version = "0.21.7"
|
||||
|
|
@ -422,6 +455,15 @@ dependencies = [
|
|||
"generic-array",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "block-padding"
|
||||
version = "0.3.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a8894febbff9f758034a5b8e12d87918f56dfc64a8e1fe757d65e29041538d93"
|
||||
dependencies = [
|
||||
"generic-array",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "block2"
|
||||
version = "0.6.2"
|
||||
|
|
@ -633,6 +675,15 @@ version = "0.3.0"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "37b2a672a2cb129a2e41c10b1224bb368f9f37a2b16b612598138befd7b37eb5"
|
||||
|
||||
[[package]]
|
||||
name = "cbc"
|
||||
version = "0.1.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "26b52a9543ae338f279b96b0b9fed9c8093744685043739079ce85cd58f289a6"
|
||||
dependencies = [
|
||||
"cipher",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "cc"
|
||||
version = "1.2.61"
|
||||
|
|
@ -698,6 +749,25 @@ dependencies = [
|
|||
"windows-link 0.2.1",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "cipher"
|
||||
version = "0.4.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "773f3b9af64447d2ce9850330c473515014aa235e6a783b02db81ff39e4a3dad"
|
||||
dependencies = [
|
||||
"crypto-common",
|
||||
"inout",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "cmake"
|
||||
version = "0.1.58"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c0f78a02292a74a88ac736019ab962ece0bc380e3f977bf72e376c5d78ff0678"
|
||||
dependencies = [
|
||||
"cc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "combine"
|
||||
version = "4.6.7"
|
||||
|
|
@ -1048,6 +1118,7 @@ dependencies = [
|
|||
"duckdb",
|
||||
"log",
|
||||
"mongodb",
|
||||
"oracle-rs",
|
||||
"portpicker",
|
||||
"redis",
|
||||
"reqwest 0.12.28",
|
||||
|
|
@ -1593,6 +1664,12 @@ dependencies = [
|
|||
"percent-encoding",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "fs_extra"
|
||||
version = "1.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "42703706b716c37f96a77aea830392ad231f44c9e9a67872fa5548707e11b11c"
|
||||
|
||||
[[package]]
|
||||
name = "funty"
|
||||
version = "2.0.0"
|
||||
|
|
@ -2172,6 +2249,17 @@ dependencies = [
|
|||
"windows-sys 0.61.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "hostname"
|
||||
version = "0.4.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "617aaa3557aef3810a6369d0a99fac8a080891b68bd9f9812a1eeda0c0730cbd"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"libc",
|
||||
"windows-link 0.2.1",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "html5ever"
|
||||
version = "0.29.1"
|
||||
|
|
@ -2267,7 +2355,7 @@ dependencies = [
|
|||
"tokio",
|
||||
"tokio-rustls",
|
||||
"tower-service",
|
||||
"webpki-roots",
|
||||
"webpki-roots 1.0.7",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
|
@ -2492,6 +2580,16 @@ dependencies = [
|
|||
"cfb",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "inout"
|
||||
version = "0.1.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "879f10e63c20629ecabbb64a8010319738c66a5cd0c29b02d63d272b03751d01"
|
||||
dependencies = [
|
||||
"block-padding",
|
||||
"generic-array",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ipconfig"
|
||||
version = "0.3.4"
|
||||
|
|
@ -3112,7 +3210,7 @@ dependencies = [
|
|||
"tokio-util",
|
||||
"typed-builder",
|
||||
"uuid",
|
||||
"webpki-roots",
|
||||
"webpki-roots 1.0.7",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
|
@ -3505,6 +3603,39 @@ version = "0.2.0"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "04744f49eae99ab78e0d5c0b603ab218f515ea8cfe5a456d7629ad883a3b6e7d"
|
||||
|
||||
[[package]]
|
||||
name = "oracle-rs"
|
||||
version = "0.1.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5c9ca364b441f92b717658c62e85207158b30285deb01811ccd923017f61612e"
|
||||
dependencies = [
|
||||
"aes",
|
||||
"async-trait",
|
||||
"bytes",
|
||||
"cbc",
|
||||
"chrono",
|
||||
"hex",
|
||||
"hmac",
|
||||
"hostname",
|
||||
"indexmap 2.14.0",
|
||||
"md-5",
|
||||
"pbkdf2",
|
||||
"pkcs8",
|
||||
"rand 0.8.6",
|
||||
"rustls",
|
||||
"rustls-pemfile",
|
||||
"rustls-pki-types",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"sha1",
|
||||
"sha2",
|
||||
"thiserror 1.0.69",
|
||||
"tokio",
|
||||
"tokio-rustls",
|
||||
"tracing",
|
||||
"webpki-roots 0.26.11",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "os_pipe"
|
||||
version = "1.2.3"
|
||||
|
|
@ -3582,6 +3713,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||
checksum = "f8ed6a7761f76e3b9f92dfb0a60a6a6477c61024b775147ff0973a02653abaf2"
|
||||
dependencies = [
|
||||
"digest",
|
||||
"hmac",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
|
@ -3803,6 +3935,21 @@ dependencies = [
|
|||
"spki",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pkcs5"
|
||||
version = "0.7.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e847e2c91a18bfa887dd028ec33f2fe6f25db77db3619024764914affe8b69a6"
|
||||
dependencies = [
|
||||
"aes",
|
||||
"cbc",
|
||||
"der",
|
||||
"pbkdf2",
|
||||
"scrypt",
|
||||
"sha2",
|
||||
"spki",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pkcs8"
|
||||
version = "0.10.2"
|
||||
|
|
@ -3810,6 +3957,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||
checksum = "f950b2377845cebe5cf8b5165cb3cc1a5e0fa5cfa3e1f7f55707d8fd82e0a7b7"
|
||||
dependencies = [
|
||||
"der",
|
||||
"pkcs5",
|
||||
"rand_core 0.6.4",
|
||||
"spki",
|
||||
]
|
||||
|
||||
|
|
@ -4359,7 +4508,7 @@ dependencies = [
|
|||
"wasm-bindgen",
|
||||
"wasm-bindgen-futures",
|
||||
"web-sys",
|
||||
"webpki-roots",
|
||||
"webpki-roots 1.0.7",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
|
@ -4563,6 +4712,7 @@ version = "0.23.40"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ef86cd5876211988985292b91c96a8f2d298df24e75989a43a3c73f2d4d8168b"
|
||||
dependencies = [
|
||||
"aws-lc-rs",
|
||||
"log",
|
||||
"once_cell",
|
||||
"ring",
|
||||
|
|
@ -4572,6 +4722,15 @@ dependencies = [
|
|||
"zeroize",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rustls-pemfile"
|
||||
version = "2.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "dce314e5fee3f39953d46bb63bb8a46d40c2f8fb7cc5a3b6cab2bde9721d6e50"
|
||||
dependencies = [
|
||||
"rustls-pki-types",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rustls-pki-types"
|
||||
version = "1.14.1"
|
||||
|
|
@ -4588,6 +4747,7 @@ version = "0.103.13"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "61c429a8649f110dddef65e2a5ad240f747e85f7758a6bccc7e5777bd33f756e"
|
||||
dependencies = [
|
||||
"aws-lc-rs",
|
||||
"ring",
|
||||
"rustls-pki-types",
|
||||
"untrusted",
|
||||
|
|
@ -4605,6 +4765,15 @@ version = "1.0.23"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9774ba4a74de5f7b1c1451ed6cd5285a32eddb5cccb8cc655a4e50009e06477f"
|
||||
|
||||
[[package]]
|
||||
name = "salsa20"
|
||||
version = "0.10.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "97a22f5af31f73a954c10289c93e8a50cc23d971e80ee446f1f6f7137a088213"
|
||||
dependencies = [
|
||||
"cipher",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "same-file"
|
||||
version = "1.0.6"
|
||||
|
|
@ -4680,6 +4849,17 @@ version = "1.2.0"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49"
|
||||
|
||||
[[package]]
|
||||
name = "scrypt"
|
||||
version = "0.11.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0516a385866c09368f0b5bcd1caff3366aace790fcd46e2bb032697bb172fd1f"
|
||||
dependencies = [
|
||||
"pbkdf2",
|
||||
"salsa20",
|
||||
"sha2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "seahash"
|
||||
version = "4.1.0"
|
||||
|
|
@ -6853,6 +7033,15 @@ dependencies = [
|
|||
"system-deps",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "webpki-roots"
|
||||
version = "0.26.11"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "521bc38abb08001b01866da9f51eb7c5d647a19260e00054a8c7fd5f9e57f7a9"
|
||||
dependencies = [
|
||||
"webpki-roots 1.0.7",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "webpki-roots"
|
||||
version = "1.0.7"
|
||||
|
|
|
|||
|
|
@ -42,3 +42,4 @@ tiberius = { version = "0.12.3", features = ["tds73", "chrono"] }
|
|||
tokio-util = { version = "0.7", features = ["compat"] }
|
||||
reqwest = { version = "0.12", features = ["json"] }
|
||||
mongodb = "3.2.5"
|
||||
oracle-rs = "0.1"
|
||||
|
|
|
|||
|
|
@ -16,6 +16,7 @@ pub enum PoolKind {
|
|||
MongoDb(mongodb::Client),
|
||||
ClickHouse(db::clickhouse_driver::ChClient),
|
||||
SqlServer(std::sync::Arc<tokio::sync::Mutex<db::sqlserver::SqlServerClient>>),
|
||||
Oracle(std::sync::Arc<tokio::sync::Mutex<db::oracle_driver::OraclePool>>),
|
||||
}
|
||||
|
||||
pub struct AppState {
|
||||
|
|
@ -38,20 +39,24 @@ impl AppState {
|
|||
connection_id: &str,
|
||||
database: Option<&str>,
|
||||
) -> Result<String, String> {
|
||||
let is_embedded = {
|
||||
let db_type = {
|
||||
let configs = self.configs.lock().await;
|
||||
configs.get(connection_id)
|
||||
.map(|c| c.db_type == DatabaseType::Sqlite || c.db_type == DatabaseType::DuckDb)
|
||||
.unwrap_or(false)
|
||||
configs.get(connection_id).map(|c| c.db_type.clone())
|
||||
};
|
||||
|
||||
let is_embedded = matches!(db_type, Some(DatabaseType::Sqlite) | Some(DatabaseType::DuckDb));
|
||||
if is_embedded {
|
||||
return Ok(connection_id.to_string());
|
||||
}
|
||||
|
||||
let pool_key = match database {
|
||||
Some(db) => format!("{connection_id}:{db}"),
|
||||
None => connection_id.to_string(),
|
||||
let is_single_conn = matches!(db_type, Some(DatabaseType::Oracle));
|
||||
let pool_key = if is_single_conn {
|
||||
connection_id.to_string()
|
||||
} else {
|
||||
match database {
|
||||
Some(db) => format!("{connection_id}:{db}"),
|
||||
None => connection_id.to_string(),
|
||||
}
|
||||
};
|
||||
|
||||
let conns = self.connections.lock().await;
|
||||
|
|
@ -69,7 +74,9 @@ impl AppState {
|
|||
|
||||
let mut db_config = config.clone();
|
||||
if let Some(db) = database {
|
||||
db_config.database = Some(db.to_string());
|
||||
if db_config.db_type != DatabaseType::Oracle {
|
||||
db_config.database = Some(db.to_string());
|
||||
}
|
||||
}
|
||||
|
||||
let url = db_config.connection_url();
|
||||
|
|
@ -102,6 +109,14 @@ impl AppState {
|
|||
).await?;
|
||||
PoolKind::SqlServer(std::sync::Arc::new(tokio::sync::Mutex::new(client)))
|
||||
}
|
||||
DatabaseType::Oracle => {
|
||||
let pool = db::oracle_driver::OraclePool::connect(
|
||||
&db_config.host, db_config.port,
|
||||
db_config.database.as_deref().unwrap_or("ORCL"),
|
||||
&db_config.username, &db_config.password,
|
||||
).await?;
|
||||
PoolKind::Oracle(std::sync::Arc::new(tokio::sync::Mutex::new(pool)))
|
||||
}
|
||||
};
|
||||
|
||||
self.connections.lock().await.insert(pool_key.clone(), pool);
|
||||
|
|
@ -113,9 +128,19 @@ impl AppState {
|
|||
connection_id: &str,
|
||||
database: Option<&str>,
|
||||
) -> Result<String, String> {
|
||||
let pool_key = match database {
|
||||
Some(db) => format!("{connection_id}:{db}"),
|
||||
None => connection_id.to_string(),
|
||||
let is_single_conn = {
|
||||
let configs = self.configs.lock().await;
|
||||
configs.get(connection_id)
|
||||
.map(|c| c.db_type == DatabaseType::Oracle)
|
||||
.unwrap_or(false)
|
||||
};
|
||||
let pool_key = if is_single_conn {
|
||||
connection_id.to_string()
|
||||
} else {
|
||||
match database {
|
||||
Some(db) => format!("{connection_id}:{db}"),
|
||||
None => connection_id.to_string(),
|
||||
}
|
||||
};
|
||||
self.connections.lock().await.remove(&pool_key);
|
||||
self.get_or_create_pool(connection_id, database).await
|
||||
|
|
@ -196,6 +221,14 @@ pub async fn test_connection(config: ConnectionConfig) -> Result<String, String>
|
|||
).await?;
|
||||
Ok("Connection successful".to_string())
|
||||
}
|
||||
DatabaseType::Oracle => {
|
||||
let _pool = db::oracle_driver::OraclePool::connect(
|
||||
&config.host, config.port,
|
||||
config.database.as_deref().unwrap_or("ORCL"),
|
||||
&config.username, &config.password,
|
||||
).await?;
|
||||
Ok("Connection successful".to_string())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -245,6 +278,14 @@ pub async fn connect_db(
|
|||
config.database.as_deref(),
|
||||
).await?;
|
||||
PoolKind::SqlServer(std::sync::Arc::new(tokio::sync::Mutex::new(client))) }
|
||||
DatabaseType::Oracle => {
|
||||
let pool = db::oracle_driver::OraclePool::connect(
|
||||
&config.host, config.port,
|
||||
config.database.as_deref().unwrap_or("ORCL"),
|
||||
&config.username, &config.password,
|
||||
).await?;
|
||||
PoolKind::Oracle(std::sync::Arc::new(tokio::sync::Mutex::new(pool)))
|
||||
}
|
||||
};
|
||||
|
||||
state.connections.lock().await.insert(id.clone(), pool);
|
||||
|
|
@ -275,6 +316,7 @@ pub async fn disconnect_db(
|
|||
PoolKind::MongoDb(_) => {},
|
||||
PoolKind::ClickHouse(_) => {},
|
||||
PoolKind::SqlServer(_) => {},
|
||||
PoolKind::Oracle(_) => {},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -132,6 +132,15 @@ async fn do_execute(
|
|||
.map_err(|_| format!("Query timed out after {} seconds", QUERY_TIMEOUT.as_secs()))?
|
||||
.map(truncate_result)
|
||||
}
|
||||
PoolKind::Oracle(pool) => {
|
||||
let pool = pool.clone();
|
||||
drop(connections);
|
||||
let mut pool = pool.lock().await;
|
||||
timeout(QUERY_TIMEOUT, db::oracle_driver::execute_query(&mut pool, sql))
|
||||
.await
|
||||
.map_err(|_| format!("Query timed out after {} seconds", QUERY_TIMEOUT.as_secs()))?
|
||||
.map(truncate_result)
|
||||
}
|
||||
PoolKind::Redis(_) => Err("Use Redis-specific commands".to_string()),
|
||||
PoolKind::MongoDb(_) => Err("Use MongoDB-specific commands".to_string()),
|
||||
}
|
||||
|
|
|
|||
|
|
@ -75,6 +75,13 @@ fn extract_clickhouse(connections: &std::collections::HashMap<String, PoolKind>,
|
|||
}
|
||||
}
|
||||
|
||||
fn extract_oracle(connections: &std::collections::HashMap<String, PoolKind>, key: &str) -> Option<std::sync::Arc<tokio::sync::Mutex<db::oracle_driver::OraclePool>>> {
|
||||
match connections.get(key)? {
|
||||
PoolKind::Oracle(pool) => Some(pool.clone()),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub async fn list_databases(
|
||||
state: State<'_, Arc<AppState>>,
|
||||
|
|
@ -91,6 +98,11 @@ pub async fn list_databases(
|
|||
let mut client = client.lock().await;
|
||||
return db::sqlserver::list_databases(&mut client).await;
|
||||
}
|
||||
if let Some(pool) = extract_oracle(&connections, &connection_id) {
|
||||
drop(connections);
|
||||
let pool = pool.lock().await;
|
||||
return db::oracle_driver::list_databases(&pool).await;
|
||||
}
|
||||
}
|
||||
|
||||
let connections = state.connections.lock().await;
|
||||
|
|
@ -120,6 +132,11 @@ pub async fn list_schemas(
|
|||
let mut client = client.lock().await;
|
||||
return db::sqlserver::list_schemas(&mut client).await;
|
||||
}
|
||||
if let Some(pool) = extract_oracle(&connections, &pool_key) {
|
||||
drop(connections);
|
||||
let pool = pool.lock().await;
|
||||
return db::oracle_driver::list_schemas(&pool).await;
|
||||
}
|
||||
}
|
||||
|
||||
let connections = state.connections.lock().await;
|
||||
|
|
@ -156,6 +173,11 @@ pub async fn list_tables(
|
|||
let mut client = client.lock().await;
|
||||
return db::sqlserver::list_tables(&mut client, &schema).await;
|
||||
}
|
||||
if let Some(pool) = extract_oracle(&connections, &pool_key) {
|
||||
drop(connections);
|
||||
let pool = pool.lock().await;
|
||||
return db::oracle_driver::list_tables(&pool, &schema).await;
|
||||
}
|
||||
}
|
||||
|
||||
let connections = state.connections.lock().await;
|
||||
|
|
@ -195,6 +217,11 @@ pub async fn get_columns(
|
|||
let mut client = client.lock().await;
|
||||
return db::sqlserver::get_columns(&mut client, &schema, &table).await;
|
||||
}
|
||||
if let Some(pool) = extract_oracle(&connections, &pool_key) {
|
||||
drop(connections);
|
||||
let pool = pool.lock().await;
|
||||
return db::oracle_driver::get_columns(&pool, &schema, &table).await;
|
||||
}
|
||||
}
|
||||
|
||||
let connections = state.connections.lock().await;
|
||||
|
|
@ -225,6 +252,11 @@ pub async fn list_indexes(
|
|||
let mut client = client.lock().await;
|
||||
return db::sqlserver::list_indexes(&mut client, &schema, &table).await;
|
||||
}
|
||||
if let Some(pool) = extract_oracle(&connections, &pool_key) {
|
||||
drop(connections);
|
||||
let pool = pool.lock().await;
|
||||
return db::oracle_driver::list_indexes(&pool, &schema, &table).await;
|
||||
}
|
||||
}
|
||||
|
||||
let connections = state.connections.lock().await;
|
||||
|
|
@ -255,6 +287,11 @@ pub async fn list_foreign_keys(
|
|||
let mut client = client.lock().await;
|
||||
return db::sqlserver::list_foreign_keys(&mut client, &schema, &table).await;
|
||||
}
|
||||
if let Some(pool) = extract_oracle(&connections, &pool_key) {
|
||||
drop(connections);
|
||||
let pool = pool.lock().await;
|
||||
return db::oracle_driver::list_foreign_keys(&pool, &schema, &table).await;
|
||||
}
|
||||
}
|
||||
|
||||
let connections = state.connections.lock().await;
|
||||
|
|
@ -285,6 +322,11 @@ pub async fn list_triggers(
|
|||
let mut client = client.lock().await;
|
||||
return db::sqlserver::list_triggers(&mut client, &schema, &table).await;
|
||||
}
|
||||
if let Some(pool) = extract_oracle(&connections, &pool_key) {
|
||||
drop(connections);
|
||||
let pool = pool.lock().await;
|
||||
return db::oracle_driver::list_triggers(&pool, &schema, &table).await;
|
||||
}
|
||||
}
|
||||
|
||||
let connections = state.connections.lock().await;
|
||||
|
|
@ -336,6 +378,11 @@ pub async fn get_table_ddl(
|
|||
let mut client = client.lock().await;
|
||||
return build_sqlserver_ddl(&mut client, &schema, &table).await;
|
||||
}
|
||||
if let Some(pool) = extract_oracle(&connections, &pool_key) {
|
||||
drop(connections);
|
||||
let pool = pool.lock().await;
|
||||
return build_oracle_ddl(&pool, &schema, &table).await;
|
||||
}
|
||||
}
|
||||
|
||||
let connections = state.connections.lock().await;
|
||||
|
|
@ -429,3 +476,35 @@ async fn build_sqlserver_ddl(client: &mut db::sqlserver::SqlServerClient, schema
|
|||
}
|
||||
Ok(ddl)
|
||||
}
|
||||
|
||||
async fn build_oracle_ddl(pool: &db::oracle_driver::OraclePool, schema: &str, table: &str) -> Result<String, String> {
|
||||
let columns = db::oracle_driver::get_columns(pool, schema, table).await?;
|
||||
let indexes = db::oracle_driver::list_indexes(pool, schema, table).await?;
|
||||
let fkeys = db::oracle_driver::list_foreign_keys(pool, 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)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
pub mod clickhouse_driver;
|
||||
pub mod mongo_driver;
|
||||
pub mod mysql;
|
||||
pub mod oracle_driver;
|
||||
pub mod postgres;
|
||||
pub mod redis_driver;
|
||||
pub mod sqlite;
|
||||
|
|
|
|||
|
|
@ -0,0 +1,250 @@
|
|||
use oracle_rs::{Config, Connection};
|
||||
use std::time::Instant;
|
||||
|
||||
use super::{ColumnInfo, DatabaseInfo, ForeignKeyInfo, IndexInfo, QueryResult, TableInfo, TriggerInfo};
|
||||
|
||||
pub struct OraclePool {
|
||||
pub conn: Connection,
|
||||
host: String,
|
||||
port: u16,
|
||||
service: String,
|
||||
user: String,
|
||||
pass: String,
|
||||
}
|
||||
|
||||
impl OraclePool {
|
||||
pub async fn connect(host: &str, port: u16, service: &str, user: &str, pass: &str) -> Result<Self, String> {
|
||||
let config = Config::new(host, port, service, user, pass);
|
||||
let conn = Connection::connect_with_config(config)
|
||||
.await
|
||||
.map_err(|e| format!("Oracle connection failed: {e}"))?;
|
||||
Ok(Self {
|
||||
conn,
|
||||
host: host.to_string(),
|
||||
port,
|
||||
service: service.to_string(),
|
||||
user: user.to_string(),
|
||||
pass: pass.to_string(),
|
||||
})
|
||||
}
|
||||
|
||||
pub async fn reconnect(&mut self) -> Result<(), String> {
|
||||
let config = Config::new(&self.host, self.port, &self.service, &self.user, &self.pass);
|
||||
self.conn = Connection::connect_with_config(config)
|
||||
.await
|
||||
.map_err(|e| format!("Oracle reconnect failed: {e}"))?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
fn value_to_json(val: &oracle_rs::Value) -> serde_json::Value {
|
||||
match val {
|
||||
oracle_rs::Value::Null => serde_json::Value::Null,
|
||||
oracle_rs::Value::String(s) => serde_json::Value::String(s.clone()),
|
||||
oracle_rs::Value::Integer(n) => serde_json::Value::Number((*n).into()),
|
||||
oracle_rs::Value::Float(f) => serde_json::Number::from_f64(*f)
|
||||
.map(serde_json::Value::Number)
|
||||
.unwrap_or(serde_json::Value::Null),
|
||||
oracle_rs::Value::Boolean(b) => serde_json::Value::Bool(*b),
|
||||
oracle_rs::Value::Json(v) => v.clone(),
|
||||
_ => serde_json::Value::String(format!("{val:?}")),
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn list_databases(pool: &OraclePool) -> Result<Vec<DatabaseInfo>, String> {
|
||||
let result = pool.conn.query(
|
||||
"SELECT username FROM all_users ORDER BY username",
|
||||
&[],
|
||||
).await.map_err(|e| e.to_string())?;
|
||||
Ok(result.rows.iter().map(|row| {
|
||||
DatabaseInfo {
|
||||
name: row.get_string(0).unwrap_or("").to_string(),
|
||||
}
|
||||
}).collect())
|
||||
}
|
||||
|
||||
pub async fn list_schemas(pool: &OraclePool) -> Result<Vec<String>, String> {
|
||||
let result = pool.conn.query(
|
||||
"SELECT username FROM all_users ORDER BY username",
|
||||
&[],
|
||||
).await.map_err(|e| e.to_string())?;
|
||||
Ok(result.rows.iter().map(|row| {
|
||||
row.get_string(0).unwrap_or("").to_string()
|
||||
}).collect())
|
||||
}
|
||||
|
||||
pub async fn list_tables(pool: &OraclePool, schema: &str) -> Result<Vec<TableInfo>, String> {
|
||||
let sql = format!(
|
||||
"SELECT table_name, 'TABLE' AS table_type FROM all_tables WHERE owner = '{s}' \
|
||||
UNION ALL \
|
||||
SELECT view_name, 'VIEW' FROM all_views WHERE owner = '{s}' \
|
||||
ORDER BY 1",
|
||||
s = schema.replace('\'', "''")
|
||||
);
|
||||
let result = pool.conn.query(&sql, &[]).await.map_err(|e| e.to_string())?;
|
||||
Ok(result.rows.iter().map(|row| {
|
||||
TableInfo {
|
||||
name: row.get_string(0).unwrap_or("").to_string(),
|
||||
table_type: row.get_string(1).unwrap_or("TABLE").to_string(),
|
||||
}
|
||||
}).collect())
|
||||
}
|
||||
|
||||
pub async fn get_columns(pool: &OraclePool, schema: &str, table: &str) -> Result<Vec<ColumnInfo>, String> {
|
||||
let s = schema.replace('\'', "''");
|
||||
let t = table.replace('\'', "''");
|
||||
|
||||
let pk_result = pool.conn.query(
|
||||
&format!(
|
||||
"SELECT cols.COLUMN_NAME FROM ALL_CONS_COLUMNS cols \
|
||||
JOIN ALL_CONSTRAINTS cons ON cols.CONSTRAINT_NAME = cons.CONSTRAINT_NAME AND cols.OWNER = cons.OWNER \
|
||||
WHERE cons.CONSTRAINT_TYPE = 'P' AND cons.OWNER = '{s}' AND cons.TABLE_NAME = '{t}'"
|
||||
),
|
||||
&[],
|
||||
).await.map_err(|e| e.to_string())?;
|
||||
let pk_names: std::collections::HashSet<String> = pk_result.rows.iter()
|
||||
.filter_map(|row| row.get_string(0).map(|s| s.to_string()))
|
||||
.collect();
|
||||
|
||||
let col_result = pool.conn.query(
|
||||
&format!(
|
||||
"SELECT COLUMN_NAME, DATA_TYPE, NULLABLE \
|
||||
FROM ALL_TAB_COLUMNS \
|
||||
WHERE OWNER = '{s}' AND TABLE_NAME = '{t}' \
|
||||
ORDER BY COLUMN_ID"
|
||||
),
|
||||
&[],
|
||||
).await.map_err(|e| e.to_string())?;
|
||||
|
||||
Ok(col_result.rows.iter().map(|row| {
|
||||
let name = row.get_string(0).unwrap_or("").to_string();
|
||||
ColumnInfo {
|
||||
is_primary_key: pk_names.contains(&name),
|
||||
name,
|
||||
data_type: row.get_string(1).unwrap_or("").to_string(),
|
||||
is_nullable: row.get_string(2).unwrap_or("N") == "Y",
|
||||
column_default: None,
|
||||
extra: None,
|
||||
}
|
||||
}).collect())
|
||||
}
|
||||
|
||||
pub async fn list_indexes(pool: &OraclePool, schema: &str, table: &str) -> Result<Vec<IndexInfo>, String> {
|
||||
let sql = format!(
|
||||
"SELECT i.INDEX_NAME, \
|
||||
LISTAGG(ic.COLUMN_NAME, ',') WITHIN GROUP (ORDER BY ic.COLUMN_POSITION) AS columns, \
|
||||
i.UNIQUENESS, \
|
||||
CASE WHEN c.CONSTRAINT_TYPE = 'P' THEN 1 ELSE 0 END AS IS_PK \
|
||||
FROM ALL_INDEXES i \
|
||||
JOIN ALL_IND_COLUMNS ic ON i.INDEX_NAME = ic.INDEX_NAME AND i.TABLE_OWNER = ic.TABLE_OWNER \
|
||||
LEFT JOIN ALL_CONSTRAINTS c ON i.INDEX_NAME = c.INDEX_NAME AND i.TABLE_OWNER = c.OWNER \
|
||||
AND c.CONSTRAINT_TYPE = 'P' \
|
||||
WHERE i.TABLE_OWNER = '{s}' AND i.TABLE_NAME = '{t}' \
|
||||
GROUP BY i.INDEX_NAME, i.UNIQUENESS, c.CONSTRAINT_TYPE \
|
||||
ORDER BY i.INDEX_NAME",
|
||||
s = schema.replace('\'', "''"), t = table.replace('\'', "''")
|
||||
);
|
||||
let result = pool.conn.query(&sql, &[]).await.map_err(|e| e.to_string())?;
|
||||
Ok(result.rows.iter().map(|row| {
|
||||
let cols_str = row.get_string(1).unwrap_or("");
|
||||
IndexInfo {
|
||||
name: row.get_string(0).unwrap_or("").to_string(),
|
||||
columns: cols_str.split(',').map(|s| s.to_string()).collect(),
|
||||
is_unique: row.get_string(2).unwrap_or("") == "UNIQUE",
|
||||
is_primary: row.get_i64(3).unwrap_or(0) == 1,
|
||||
}
|
||||
}).collect())
|
||||
}
|
||||
|
||||
pub async fn list_foreign_keys(pool: &OraclePool, schema: &str, table: &str) -> Result<Vec<ForeignKeyInfo>, String> {
|
||||
let sql = format!(
|
||||
"SELECT c.CONSTRAINT_NAME, cc.COLUMN_NAME, rc.TABLE_NAME, rcc.COLUMN_NAME \
|
||||
FROM ALL_CONSTRAINTS c \
|
||||
JOIN ALL_CONS_COLUMNS cc ON c.CONSTRAINT_NAME = cc.CONSTRAINT_NAME AND c.OWNER = cc.OWNER \
|
||||
JOIN ALL_CONSTRAINTS rc ON c.R_CONSTRAINT_NAME = rc.CONSTRAINT_NAME AND c.R_OWNER = rc.OWNER \
|
||||
JOIN ALL_CONS_COLUMNS rcc ON rc.CONSTRAINT_NAME = rcc.CONSTRAINT_NAME AND rc.OWNER = rcc.OWNER \
|
||||
WHERE c.CONSTRAINT_TYPE = 'R' AND c.OWNER = '{s}' AND c.TABLE_NAME = '{t}' \
|
||||
ORDER BY c.CONSTRAINT_NAME",
|
||||
s = schema.replace('\'', "''"), t = table.replace('\'', "''")
|
||||
);
|
||||
let result = pool.conn.query(&sql, &[]).await.map_err(|e| e.to_string())?;
|
||||
Ok(result.rows.iter().map(|row| {
|
||||
ForeignKeyInfo {
|
||||
name: row.get_string(0).unwrap_or("").to_string(),
|
||||
column: row.get_string(1).unwrap_or("").to_string(),
|
||||
ref_table: row.get_string(2).unwrap_or("").to_string(),
|
||||
ref_column: row.get_string(3).unwrap_or("").to_string(),
|
||||
}
|
||||
}).collect())
|
||||
}
|
||||
|
||||
pub async fn list_triggers(pool: &OraclePool, schema: &str, table: &str) -> Result<Vec<TriggerInfo>, String> {
|
||||
let sql = format!(
|
||||
"SELECT TRIGGER_NAME, TRIGGERING_EVENT, TRIGGER_TYPE \
|
||||
FROM ALL_TRIGGERS \
|
||||
WHERE OWNER = '{s}' AND TABLE_NAME = '{t}' \
|
||||
ORDER BY TRIGGER_NAME",
|
||||
s = schema.replace('\'', "''"), t = table.replace('\'', "''")
|
||||
);
|
||||
let result = pool.conn.query(&sql, &[]).await.map_err(|e| e.to_string())?;
|
||||
Ok(result.rows.iter().map(|row| {
|
||||
TriggerInfo {
|
||||
name: row.get_string(0).unwrap_or("").to_string(),
|
||||
event: row.get_string(1).unwrap_or("").to_string(),
|
||||
timing: row.get_string(2).unwrap_or("").to_string(),
|
||||
}
|
||||
}).collect())
|
||||
}
|
||||
|
||||
pub async fn execute_query(pool: &mut OraclePool, 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")
|
||||
{
|
||||
let result = pool.conn.query(sql, &[]).await.map_err(|e| e.to_string())?;
|
||||
let columns: Vec<String> = result.columns.iter().map(|c| c.name.clone()).collect();
|
||||
let rows: Vec<Vec<serde_json::Value>> = result.rows.iter().map(|row| {
|
||||
(0..columns.len()).map(|i| {
|
||||
row.get(i)
|
||||
.map(|v| value_to_json(v))
|
||||
.unwrap_or(serde_json::Value::Null)
|
||||
}).collect()
|
||||
}).collect();
|
||||
|
||||
Ok(QueryResult {
|
||||
columns,
|
||||
rows,
|
||||
affected_rows: 0,
|
||||
execution_time_ms: start.elapsed().as_millis(),
|
||||
truncated: false,
|
||||
})
|
||||
} else {
|
||||
match pool.conn.execute(sql, &[]).await {
|
||||
Ok(result) => {
|
||||
pool.conn.commit().await.map_err(|e| e.to_string())?;
|
||||
Ok(QueryResult {
|
||||
columns: vec![],
|
||||
rows: vec![],
|
||||
affected_rows: result.rows_affected,
|
||||
execution_time_ms: start.elapsed().as_millis(),
|
||||
truncated: false,
|
||||
})
|
||||
}
|
||||
Err(e) => {
|
||||
let msg = e.to_string();
|
||||
if msg.contains("Server rejected") || msg.contains("closed the connection") {
|
||||
let _ = pool.reconnect().await;
|
||||
Err("Operation failed — possibly a constraint violation (foreign key, unique, or check constraint).".to_string())
|
||||
} else {
|
||||
Err(msg)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -41,6 +41,8 @@ pub enum DatabaseType {
|
|||
SqlServer,
|
||||
#[serde(rename = "mongodb")]
|
||||
MongoDb,
|
||||
#[serde(rename = "oracle")]
|
||||
Oracle,
|
||||
}
|
||||
|
||||
impl ConnectionConfig {
|
||||
|
|
@ -84,6 +86,10 @@ impl ConnectionConfig {
|
|||
format!("mongodb://{}:{}@{host}:{port}{db_part}", self.username, self.password)
|
||||
}
|
||||
}
|
||||
DatabaseType::Oracle => format!(
|
||||
"oracle://{}:{}@{host}:{port}{db_part}",
|
||||
self.username, self.password
|
||||
),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -277,7 +277,7 @@ function quoteIdent(tab: ActiveTab, name: string): string {
|
|||
function qualifiedTableName(tab: NonNullable<typeof activeTab.value>): string {
|
||||
const config = connectionStore.getConfig(tab.connectionId);
|
||||
if (!tab.tableMeta) return "";
|
||||
if (config?.db_type === "postgres" && tab.tableMeta.schema) {
|
||||
if ((config?.db_type === "postgres" || config?.db_type === "oracle") && tab.tableMeta.schema) {
|
||||
return `${quoteIdent(tab, tab.tableMeta.schema)}.${quoteIdent(tab, tab.tableMeta.tableName)}`;
|
||||
}
|
||||
return quoteIdent(tab, tab.tableMeta.tableName);
|
||||
|
|
@ -293,9 +293,16 @@ function buildTableSql(
|
|||
tab: NonNullable<typeof activeTab.value>,
|
||||
options: { orderBy?: string; limit?: number; offset?: number } = {},
|
||||
): string {
|
||||
const config = connectionStore.getConfig(tab.connectionId);
|
||||
const limit = options.limit ?? 100;
|
||||
const orderBy = options.orderBy ?? defaultOrderBy(tab);
|
||||
const order = orderBy ? ` ORDER BY ${orderBy}` : "";
|
||||
|
||||
if (config?.db_type === "oracle") {
|
||||
const offset = options.offset ? ` OFFSET ${options.offset} ROWS` : "";
|
||||
return `SELECT * FROM ${qualifiedTableName(tab)}${order}${offset} FETCH FIRST ${limit} ROWS ONLY`;
|
||||
}
|
||||
|
||||
const offset = options.offset ? ` OFFSET ${options.offset}` : "";
|
||||
return `SELECT * FROM ${qualifiedTableName(tab)}${order} LIMIT ${limit}${offset};`;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -87,6 +87,7 @@ function onDbTypeChange(val: string) {
|
|||
mongodb: { type: "mongodb", port: 27017, user: "" },
|
||||
clickhouse: { type: "clickhouse", port: 8123, user: "default" },
|
||||
sqlserver: { type: "sqlserver", port: 1433, user: "sa" },
|
||||
oracle: { type: "oracle", port: 1521, user: "system" },
|
||||
tidb: { type: "mysql", port: 4000, user: "root" },
|
||||
oceanbase: { type: "mysql", port: 2881, user: "root" },
|
||||
goldendb: { type: "mysql", port: 3306, user: "root" },
|
||||
|
|
@ -110,6 +111,7 @@ function onDbTypeChange(val: string) {
|
|||
const iconTypeMap: Record<string, string> = {
|
||||
mysql: "mysql", postgres: "postgres", sqlite: "sqlite", redis: "redis",
|
||||
mongodb: "mongodb", duckdb: "duckdb", clickhouse: "clickhouse", sqlserver: "sqlserver",
|
||||
oracle: "oracle",
|
||||
mariadb: "mariadb", tidb: "tidb", oceanbase: "mysql", goldendb: "mysql",
|
||||
opengauss: "postgres", gaussdb: "postgres", kingbase: "postgres", vastbase: "postgres",
|
||||
};
|
||||
|
|
@ -123,6 +125,7 @@ const dbOptions = [
|
|||
{ value: "duckdb", label: "DuckDB" },
|
||||
{ value: "clickhouse", label: "ClickHouse" },
|
||||
{ value: "sqlserver", label: "SQL Server" },
|
||||
{ value: "oracle", label: "Oracle" },
|
||||
{ value: "mariadb", label: "MariaDB" },
|
||||
];
|
||||
|
||||
|
|
|
|||
File diff suppressed because one or more lines are too long
|
|
@ -144,7 +144,7 @@ async function openData() {
|
|||
if (!(node.type === "table" || node.type === "view") || !node.connectionId || !node.database) return;
|
||||
await connectionStore.ensureConnected(node.connectionId);
|
||||
const config = connectionStore.getConfig(node.connectionId);
|
||||
const qualifiedName = config?.db_type === "postgres" && node.schema
|
||||
const qualifiedName = (config?.db_type === "postgres" || config?.db_type === "oracle") && node.schema
|
||||
? `${quoteIdent(node.schema)}.${quoteIdent(node.label)}`
|
||||
: quoteIdent(node.label);
|
||||
const tabId = queryStore.createTab(node.connectionId, node.database, node.label, "data");
|
||||
|
|
@ -153,7 +153,9 @@ async function openData() {
|
|||
const columns = await api.getColumns(node.connectionId, node.database, querySchema, node.label);
|
||||
const pks = columns.filter((c) => c.is_primary_key).map((c) => c.name);
|
||||
const order = pks.length ? ` ORDER BY ${pks.map((pk) => `${quoteIdent(pk)} ASC`).join(", ")}` : "";
|
||||
const sql = `SELECT * FROM ${qualifiedName}${order} LIMIT 100;`;
|
||||
const sql = config?.db_type === "oracle"
|
||||
? `SELECT * FROM ${qualifiedName}${order} FETCH FIRST 100 ROWS ONLY`
|
||||
: `SELECT * FROM ${qualifiedName}${order} LIMIT 100;`;
|
||||
queryStore.updateSql(tabId, sql);
|
||||
queryStore.setTableMeta(tabId, {
|
||||
schema: node.schema,
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
export type DatabaseType = "mysql" | "postgres" | "sqlite" | "redis" | "duckdb" | "clickhouse" | "sqlserver" | "mongodb";
|
||||
export type DatabaseType = "mysql" | "postgres" | "sqlite" | "redis" | "duckdb" | "clickhouse" | "sqlserver" | "mongodb" | "oracle";
|
||||
|
||||
export interface ConnectionConfig {
|
||||
id: string;
|
||||
|
|
|
|||
Loading…
Reference in New Issue