chore: add rustfmt.toml and apply unified code formatting
This commit is contained in:
1 parent
edcdd652ae
commit
41445ff474
53 files changed
+1177
-2592
No files matched your search
@@ -1,5 +1,5 @@
|
||||
use std::sync::Arc;
|
||||
use tauri::{Emitter, AppHandle, State};
|
||||
use tauri::{AppHandle, Emitter, State};
|
||||
|
||||
use super::connection::AppState;
|
||||
pub use dbx_core::ai::*;
|
||||
@@ -10,17 +10,12 @@ pub async fn ai_test_connection(config: AiConfig) -> Result<String, String> {
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub async fn save_ai_config(
|
||||
state: State<'_, Arc<AppState>>,
|
||||
config: AiConfig,
|
||||
) -> Result<(), String> {
|
||||
pub async fn save_ai_config(state: State<'_, Arc<AppState>>, config: AiConfig) -> Result<(), String> {
|
||||
state.storage.save_ai_config(&config).await
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub async fn load_ai_config(
|
||||
state: State<'_, Arc<AppState>>,
|
||||
) -> Result<Option<AiConfig>, String> {
|
||||
pub async fn load_ai_config(state: State<'_, Arc<AppState>>) -> Result<Option<AiConfig>, String> {
|
||||
state.storage.load_ai_config().await
|
||||
}
|
||||
|
||||
@@ -30,11 +25,7 @@ pub async fn ai_complete(request: AiCompletionRequest) -> Result<String, String>
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub async fn ai_stream(
|
||||
app: AppHandle,
|
||||
session_id: String,
|
||||
request: AiCompletionRequest,
|
||||
) -> Result<(), String> {
|
||||
pub async fn ai_stream(app: AppHandle, session_id: String, request: AiCompletionRequest) -> Result<(), String> {
|
||||
let cancelled = dbx_core::ai::register_stream(&session_id).await;
|
||||
|
||||
let result = dbx_core::ai::stream(&session_id, &request, &cancelled, |chunk| {
|
||||
@@ -52,24 +43,16 @@ pub async fn ai_cancel_stream(session_id: String) -> Result<bool, String> {
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub async fn save_ai_conversation(
|
||||
state: State<'_, Arc<AppState>>,
|
||||
conversation: AiConversation,
|
||||
) -> Result<(), String> {
|
||||
pub async fn save_ai_conversation(state: State<'_, Arc<AppState>>, conversation: AiConversation) -> Result<(), String> {
|
||||
state.storage.save_ai_conversation(&conversation).await
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub async fn load_ai_conversations(
|
||||
state: State<'_, Arc<AppState>>,
|
||||
) -> Result<Vec<AiConversation>, String> {
|
||||
pub async fn load_ai_conversations(state: State<'_, Arc<AppState>>) -> Result<Vec<AiConversation>, String> {
|
||||
state.storage.load_ai_conversations().await
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub async fn delete_ai_conversation(
|
||||
state: State<'_, Arc<AppState>>,
|
||||
id: String,
|
||||
) -> Result<(), String> {
|
||||
pub async fn delete_ai_conversation(state: State<'_, Arc<AppState>>, id: String) -> Result<(), String> {
|
||||
state.storage.delete_ai_conversation(&id).await
|
||||
}
|
||||
@@ -2,74 +2,52 @@ use std::sync::Arc;
|
||||
use tauri::State;
|
||||
|
||||
pub use dbx_core::connection::{
|
||||
connection_url_for_endpoint, expand_tilde, probe_connection_endpoint,
|
||||
redacted_connection_url_for_endpoint, AppState, PoolKind,
|
||||
connection_url_for_endpoint, expand_tilde, probe_connection_endpoint, redacted_connection_url_for_endpoint,
|
||||
AppState, PoolKind,
|
||||
};
|
||||
use dbx_core::db;
|
||||
use dbx_core::models::connection::{ConnectionConfig, DatabaseType};
|
||||
|
||||
#[tauri::command]
|
||||
pub async fn save_connections(
|
||||
state: State<'_, Arc<AppState>>,
|
||||
configs: Vec<ConnectionConfig>,
|
||||
) -> Result<(), String> {
|
||||
pub async fn save_connections(state: State<'_, Arc<AppState>>, configs: Vec<ConnectionConfig>) -> Result<(), String> {
|
||||
state.storage.save_connections(&configs).await
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub async fn load_connections(
|
||||
state: State<'_, Arc<AppState>>,
|
||||
) -> Result<Vec<ConnectionConfig>, String> {
|
||||
pub async fn load_connections(state: State<'_, Arc<AppState>>) -> Result<Vec<ConnectionConfig>, String> {
|
||||
state.storage.load_connections().await
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub async fn save_sidebar_layout(
|
||||
state: State<'_, Arc<AppState>>,
|
||||
layout: serde_json::Value,
|
||||
) -> Result<(), String> {
|
||||
pub async fn save_sidebar_layout(state: State<'_, Arc<AppState>>, layout: serde_json::Value) -> Result<(), String> {
|
||||
state.storage.save_sidebar_layout(&layout).await
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub async fn load_sidebar_layout(
|
||||
state: State<'_, Arc<AppState>>,
|
||||
) -> Result<Option<serde_json::Value>, String> {
|
||||
pub async fn load_sidebar_layout(state: State<'_, Arc<AppState>>) -> Result<Option<serde_json::Value>, String> {
|
||||
state.storage.load_sidebar_layout().await
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub async fn test_connection(
|
||||
state: State<'_, Arc<AppState>>,
|
||||
config: ConnectionConfig,
|
||||
) -> Result<String, String> {
|
||||
pub async fn test_connection(state: State<'_, Arc<AppState>>, config: ConnectionConfig) -> Result<String, String> {
|
||||
let tunnel_id = format!("{}:test", config.id);
|
||||
let connection_id = if config.ssh_enabled && !config.ssh_host.is_empty() {
|
||||
tunnel_id.as_str()
|
||||
} else {
|
||||
config.id.as_str()
|
||||
};
|
||||
let connection_id =
|
||||
if config.ssh_enabled && !config.ssh_host.is_empty() { tunnel_id.as_str() } else { config.id.as_str() };
|
||||
let (host, port) = state.connection_host_port(connection_id, &config).await?;
|
||||
let probe_result = probe_connection_endpoint(&config, &host, port).await;
|
||||
let url = connection_url_for_endpoint(&config, &host, port);
|
||||
let target = redacted_connection_url_for_endpoint(&config, &host, port);
|
||||
log::info!(
|
||||
"[test_connection] db_type={:?} target={}",
|
||||
config.db_type,
|
||||
target
|
||||
);
|
||||
log::info!("[test_connection] db_type={:?} target={}", config.db_type, target);
|
||||
let result = match probe_result {
|
||||
Err(e) => Err(e),
|
||||
Ok(()) => match config.db_type {
|
||||
DatabaseType::Mysql if config.needs_bare_mysql() => {
|
||||
match db::mysql::connect_bare(&url).await {
|
||||
Ok(pool) => {
|
||||
pool.close().await;
|
||||
Ok("Connection successful".to_string())
|
||||
}
|
||||
Err(e) => Err(e),
|
||||
DatabaseType::Mysql if config.needs_bare_mysql() => match db::mysql::connect_bare(&url).await {
|
||||
Ok(pool) => {
|
||||
pool.close().await;
|
||||
Ok("Connection successful".to_string())
|
||||
}
|
||||
}
|
||||
Err(e) => Err(e),
|
||||
},
|
||||
DatabaseType::Mysql => match db::mysql::connect(&url).await {
|
||||
Ok(pool) => {
|
||||
pool.close().await;
|
||||
@@ -77,70 +55,48 @@ pub async fn test_connection(
|
||||
}
|
||||
Err(e) => Err(e),
|
||||
},
|
||||
DatabaseType::Doris | DatabaseType::StarRocks => {
|
||||
match db::mysql::connect_bare(&url).await {
|
||||
Ok(pool) => {
|
||||
pool.close().await;
|
||||
Ok("Connection successful".to_string())
|
||||
}
|
||||
Err(e) => Err(e),
|
||||
DatabaseType::Doris | DatabaseType::StarRocks => match db::mysql::connect_bare(&url).await {
|
||||
Ok(pool) => {
|
||||
pool.close().await;
|
||||
Ok("Connection successful".to_string())
|
||||
}
|
||||
}
|
||||
DatabaseType::Postgres | DatabaseType::Redshift => {
|
||||
match db::postgres::connect(&url).await {
|
||||
Ok(pool) => {
|
||||
pool.close().await;
|
||||
Ok("Connection successful".to_string())
|
||||
}
|
||||
Err(e) => Err(e),
|
||||
Err(e) => Err(e),
|
||||
},
|
||||
DatabaseType::Postgres | DatabaseType::Redshift => match db::postgres::connect(&url).await {
|
||||
Ok(pool) => {
|
||||
pool.close().await;
|
||||
Ok("Connection successful".to_string())
|
||||
}
|
||||
}
|
||||
DatabaseType::Sqlite => {
|
||||
match db::sqlite::connect_path(&expand_tilde(&config.host)).await {
|
||||
Ok(pool) => {
|
||||
pool.close().await;
|
||||
Ok("Connection successful".to_string())
|
||||
}
|
||||
Err(e) => Err(e),
|
||||
Err(e) => Err(e),
|
||||
},
|
||||
DatabaseType::Sqlite => match db::sqlite::connect_path(&expand_tilde(&config.host)).await {
|
||||
Ok(pool) => {
|
||||
pool.close().await;
|
||||
Ok("Connection successful".to_string())
|
||||
}
|
||||
}
|
||||
DatabaseType::Redis => db::redis_driver::connect(&url)
|
||||
.await
|
||||
.map(|_| "Connection successful".to_string()),
|
||||
Err(e) => Err(e),
|
||||
},
|
||||
DatabaseType::Redis => db::redis_driver::connect(&url).await.map(|_| "Connection successful".to_string()),
|
||||
DatabaseType::DuckDb => duckdb::Connection::open(&expand_tilde(&config.host))
|
||||
.map(|_| "Connection successful".to_string())
|
||||
.map_err(|e| e.to_string()),
|
||||
DatabaseType::MongoDb => match db::mongo_driver::connect(&url).await {
|
||||
Ok(client) => db::mongo_driver::test_connection(&client)
|
||||
.await
|
||||
.map(|_| "Connection successful".to_string()),
|
||||
Ok(client) => {
|
||||
db::mongo_driver::test_connection(&client).await.map(|_| "Connection successful".to_string())
|
||||
}
|
||||
Err(e) => Err(e.to_string()),
|
||||
},
|
||||
DatabaseType::ClickHouse => {
|
||||
let username = if config.username.is_empty() {
|
||||
None
|
||||
} else {
|
||||
Some(config.username.clone())
|
||||
};
|
||||
let password = if config.password.is_empty() {
|
||||
None
|
||||
} else {
|
||||
Some(config.password.clone())
|
||||
};
|
||||
let username = if config.username.is_empty() { None } else { Some(config.username.clone()) };
|
||||
let password = if config.password.is_empty() { None } else { Some(config.password.clone()) };
|
||||
let client = db::clickhouse_driver::ChClient::new(&url, username, password);
|
||||
db::clickhouse_driver::test_connection(&client)
|
||||
db::clickhouse_driver::test_connection(&client).await.map(|_| "Connection successful".to_string())
|
||||
}
|
||||
DatabaseType::SqlServer => {
|
||||
db::sqlserver::connect(&host, port, &config.username, &config.password, config.database.as_deref())
|
||||
.await
|
||||
.map(|_| "Connection successful".to_string())
|
||||
}
|
||||
DatabaseType::SqlServer => db::sqlserver::connect(
|
||||
&host,
|
||||
port,
|
||||
&config.username,
|
||||
&config.password,
|
||||
config.database.as_deref(),
|
||||
)
|
||||
.await
|
||||
.map(|_| "Connection successful".to_string()),
|
||||
DatabaseType::Oracle => db::oracle_driver::connect(
|
||||
&host,
|
||||
port,
|
||||
@@ -151,14 +107,9 @@ pub async fn test_connection(
|
||||
.await
|
||||
.map(|_| "Connection successful".to_string()),
|
||||
DatabaseType::Elasticsearch => {
|
||||
let client = db::elasticsearch_driver::EsClient::new(
|
||||
&url,
|
||||
Some(&config.username),
|
||||
Some(&config.password),
|
||||
);
|
||||
db::elasticsearch_driver::test_connection(&client)
|
||||
.await
|
||||
.map(|_| "Connection successful".to_string())
|
||||
let client =
|
||||
db::elasticsearch_driver::EsClient::new(&url, Some(&config.username), Some(&config.password));
|
||||
db::elasticsearch_driver::test_connection(&client).await.map(|_| "Connection successful".to_string())
|
||||
}
|
||||
},
|
||||
};
|
||||
@@ -171,10 +122,7 @@ pub async fn test_connection(
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub async fn connect_db(
|
||||
state: State<'_, Arc<AppState>>,
|
||||
config: ConnectionConfig,
|
||||
) -> Result<String, String> {
|
||||
pub async fn connect_db(state: State<'_, Arc<AppState>>, config: ConnectionConfig) -> Result<String, String> {
|
||||
let id = config.id.clone();
|
||||
|
||||
let (host, port) = state.connection_host_port(&id, &config).await?;
|
||||
@@ -182,26 +130,17 @@ pub async fn connect_db(
|
||||
let url = connection_url_for_endpoint(&config, &host, port);
|
||||
|
||||
let pool = match config.db_type {
|
||||
DatabaseType::Mysql if config.needs_bare_mysql() => {
|
||||
PoolKind::Mysql(db::mysql::connect_bare(&url).await?, true)
|
||||
}
|
||||
DatabaseType::Mysql if config.needs_bare_mysql() => PoolKind::Mysql(db::mysql::connect_bare(&url).await?, true),
|
||||
DatabaseType::Mysql => PoolKind::Mysql(db::mysql::connect(&url).await?, false),
|
||||
DatabaseType::Doris | DatabaseType::StarRocks => {
|
||||
PoolKind::Mysql(db::mysql::connect_bare(&url).await?, true)
|
||||
}
|
||||
DatabaseType::Postgres | DatabaseType::Redshift => {
|
||||
PoolKind::Postgres(db::postgres::connect(&url).await?)
|
||||
}
|
||||
DatabaseType::Sqlite => {
|
||||
PoolKind::Sqlite(db::sqlite::connect_path(&expand_tilde(&config.host)).await?)
|
||||
}
|
||||
DatabaseType::Doris | DatabaseType::StarRocks => PoolKind::Mysql(db::mysql::connect_bare(&url).await?, true),
|
||||
DatabaseType::Postgres | DatabaseType::Redshift => PoolKind::Postgres(db::postgres::connect(&url).await?),
|
||||
DatabaseType::Sqlite => PoolKind::Sqlite(db::sqlite::connect_path(&expand_tilde(&config.host)).await?),
|
||||
DatabaseType::Redis => {
|
||||
let con = db::redis_driver::connect(&url).await?;
|
||||
PoolKind::Redis(tokio::sync::Mutex::new(con))
|
||||
}
|
||||
DatabaseType::DuckDb => {
|
||||
let con =
|
||||
duckdb::Connection::open(&expand_tilde(&config.host)).map_err(|e| e.to_string())?;
|
||||
let con = duckdb::Connection::open(&expand_tilde(&config.host)).map_err(|e| e.to_string())?;
|
||||
PoolKind::DuckDb(std::sync::Arc::new(std::sync::Mutex::new(con)))
|
||||
}
|
||||
DatabaseType::MongoDb => {
|
||||
@@ -210,29 +149,16 @@ pub async fn connect_db(
|
||||
PoolKind::MongoDb(client)
|
||||
}
|
||||
DatabaseType::ClickHouse => {
|
||||
let username = if config.username.is_empty() {
|
||||
None
|
||||
} else {
|
||||
Some(config.username.clone())
|
||||
};
|
||||
let password = if config.password.is_empty() {
|
||||
None
|
||||
} else {
|
||||
Some(config.password.clone())
|
||||
};
|
||||
let username = if config.username.is_empty() { None } else { Some(config.username.clone()) };
|
||||
let password = if config.password.is_empty() { None } else { Some(config.password.clone()) };
|
||||
let client = db::clickhouse_driver::ChClient::new(&url, username, password);
|
||||
db::clickhouse_driver::test_connection(&client).await?;
|
||||
PoolKind::ClickHouse(client)
|
||||
}
|
||||
DatabaseType::SqlServer => {
|
||||
let client = db::sqlserver::connect(
|
||||
&host,
|
||||
port,
|
||||
&config.username,
|
||||
&config.password,
|
||||
config.database.as_deref(),
|
||||
)
|
||||
.await?;
|
||||
let client =
|
||||
db::sqlserver::connect(&host, port, &config.username, &config.password, config.database.as_deref())
|
||||
.await?;
|
||||
PoolKind::SqlServer(std::sync::Arc::new(tokio::sync::Mutex::new(client)))
|
||||
}
|
||||
DatabaseType::Oracle => {
|
||||
@@ -247,11 +173,7 @@ pub async fn connect_db(
|
||||
PoolKind::Oracle(std::sync::Arc::new(tokio::sync::Mutex::new(client)))
|
||||
}
|
||||
DatabaseType::Elasticsearch => {
|
||||
let client = db::elasticsearch_driver::EsClient::new(
|
||||
&url,
|
||||
Some(&config.username),
|
||||
Some(&config.password),
|
||||
);
|
||||
let client = db::elasticsearch_driver::EsClient::new(&url, Some(&config.username), Some(&config.password));
|
||||
db::elasticsearch_driver::test_connection(&client).await?;
|
||||
PoolKind::Elasticsearch(client)
|
||||
}
|
||||
@@ -264,16 +186,10 @@ pub async fn connect_db(
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub async fn disconnect_db(
|
||||
state: State<'_, Arc<AppState>>,
|
||||
connection_id: String,
|
||||
) -> Result<(), String> {
|
||||
pub async fn disconnect_db(state: State<'_, Arc<AppState>>, connection_id: String) -> Result<(), String> {
|
||||
let mut conns = state.connections.lock().await;
|
||||
let keys_to_remove: Vec<String> = conns
|
||||
.keys()
|
||||
.filter(|k| *k == &connection_id || k.starts_with(&format!("{connection_id}:")))
|
||||
.cloned()
|
||||
.collect();
|
||||
let keys_to_remove: Vec<String> =
|
||||
conns.keys().filter(|k| *k == &connection_id || k.starts_with(&format!("{connection_id}:"))).cloned().collect();
|
||||
for key in keys_to_remove {
|
||||
if let Some(pool) = conns.remove(&key) {
|
||||
match pool {
|
||||
|
||||
@@ -5,10 +5,7 @@ use super::connection::AppState;
|
||||
pub use dbx_core::history::HistoryEntry;
|
||||
|
||||
#[tauri::command]
|
||||
pub async fn save_history(
|
||||
state: State<'_, Arc<AppState>>,
|
||||
entry: HistoryEntry,
|
||||
) -> Result<(), String> {
|
||||
pub async fn save_history(state: State<'_, Arc<AppState>>, entry: HistoryEntry) -> Result<(), String> {
|
||||
state.storage.save_history_entry(&entry).await
|
||||
}
|
||||
|
||||
@@ -27,9 +24,6 @@ pub async fn clear_history(state: State<'_, Arc<AppState>>) -> Result<(), String
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub async fn delete_history_entry(
|
||||
state: State<'_, Arc<AppState>>,
|
||||
id: String,
|
||||
) -> Result<(), String> {
|
||||
pub async fn delete_history_entry(state: State<'_, Arc<AppState>>, id: String) -> Result<(), String> {
|
||||
state.storage.delete_history_entry(&id).await
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::sync::Arc;
|
||||
use tauri::{Emitter, AppHandle, Manager};
|
||||
use tauri::{AppHandle, Emitter, Manager};
|
||||
use tokio::io::{AsyncReadExt, AsyncWriteExt};
|
||||
use tokio::net::TcpListener;
|
||||
|
||||
@@ -82,7 +82,10 @@ pub fn start(app_handle: AppHandle, state: Arc<AppState>) {
|
||||
});
|
||||
}
|
||||
|
||||
fn find_config_by_name<'a>(configs: &'a [crate::models::connection::ConnectionConfig], name: &str) -> Option<&'a crate::models::connection::ConnectionConfig> {
|
||||
fn find_config_by_name<'a>(
|
||||
configs: &'a [crate::models::connection::ConnectionConfig],
|
||||
name: &str,
|
||||
) -> Option<&'a crate::models::connection::ConnectionConfig> {
|
||||
configs.iter().find(|c| c.name.eq_ignore_ascii_case(name))
|
||||
}
|
||||
|
||||
@@ -94,14 +97,21 @@ async fn respond(stream: &mut tokio::net::TcpStream, status: &str, body: &str) {
|
||||
async fn handle_open_table(app: &AppHandle, state: &Arc<AppState>, body: &str, stream: &mut tokio::net::TcpStream) {
|
||||
let req: OpenTableRequest = match serde_json::from_str(body) {
|
||||
Ok(r) => r,
|
||||
Err(_) => { respond(stream, "400 Bad Request", "").await; return; }
|
||||
Err(_) => {
|
||||
respond(stream, "400 Bad Request", "").await;
|
||||
return;
|
||||
}
|
||||
};
|
||||
let configs = match state.storage.load_connections().await {
|
||||
Ok(c) => c,
|
||||
Err(_) => { respond(stream, "500 Internal Server Error", "").await; return; }
|
||||
Err(_) => {
|
||||
respond(stream, "500 Internal Server Error", "").await;
|
||||
return;
|
||||
}
|
||||
};
|
||||
let Some(config) = find_config_by_name(&configs, &req.connection_name) else {
|
||||
respond(stream, "404 Not Found", "Connection not found").await; return;
|
||||
respond(stream, "404 Not Found", "Connection not found").await;
|
||||
return;
|
||||
};
|
||||
let event = McpOpenTableEvent {
|
||||
connection_id: config.id.clone(),
|
||||
@@ -116,14 +126,21 @@ async fn handle_open_table(app: &AppHandle, state: &Arc<AppState>, body: &str, s
|
||||
async fn handle_execute_query(app: &AppHandle, state: &Arc<AppState>, body: &str, stream: &mut tokio::net::TcpStream) {
|
||||
let req: ExecuteQueryRequest = match serde_json::from_str(body) {
|
||||
Ok(r) => r,
|
||||
Err(_) => { respond(stream, "400 Bad Request", "").await; return; }
|
||||
Err(_) => {
|
||||
respond(stream, "400 Bad Request", "").await;
|
||||
return;
|
||||
}
|
||||
};
|
||||
let configs = match state.storage.load_connections().await {
|
||||
Ok(c) => c,
|
||||
Err(_) => { respond(stream, "500 Internal Server Error", "").await; return; }
|
||||
Err(_) => {
|
||||
respond(stream, "500 Internal Server Error", "").await;
|
||||
return;
|
||||
}
|
||||
};
|
||||
let Some(config) = find_config_by_name(&configs, &req.connection_name) else {
|
||||
respond(stream, "404 Not Found", "Connection not found").await; return;
|
||||
respond(stream, "404 Not Found", "Connection not found").await;
|
||||
return;
|
||||
};
|
||||
let event = McpExecuteQueryEvent {
|
||||
connection_id: config.id.clone(),
|
||||
|
||||
@@ -53,7 +53,8 @@ pub async fn mongo_update_document(
|
||||
id: String,
|
||||
doc_json: String,
|
||||
) -> Result<u64, String> {
|
||||
dbx_core::mongo_ops::mongo_update_document_core(&state, &connection_id, &database, &collection, &id, &doc_json).await
|
||||
dbx_core::mongo_ops::mongo_update_document_core(&state, &connection_id, &database, &collection, &id, &doc_json)
|
||||
.await
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
|
||||
@@ -16,20 +16,11 @@ pub async fn execute_query(
|
||||
sql: String,
|
||||
execution_id: Option<String>,
|
||||
) -> Result<db::QueryResult, String> {
|
||||
let registered_query = execution_id
|
||||
.as_ref()
|
||||
.filter(|id| !id.trim().is_empty())
|
||||
.map(|id| state.running_queries.register(id.clone()));
|
||||
let registered_query =
|
||||
execution_id.as_ref().filter(|id| !id.trim().is_empty()).map(|id| state.running_queries.register(id.clone()));
|
||||
let cancel_token = registered_query.as_ref().map(|query| query.token());
|
||||
|
||||
dbx_core::query::execute_sql_statement(
|
||||
&state,
|
||||
&connection_id,
|
||||
&database,
|
||||
&sql,
|
||||
cancel_token,
|
||||
)
|
||||
.await
|
||||
dbx_core::query::execute_sql_statement(&state, &connection_id, &database, &sql, cancel_token).await
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
@@ -40,27 +31,15 @@ pub async fn execute_multi(
|
||||
sql: String,
|
||||
execution_id: Option<String>,
|
||||
) -> Result<Vec<db::QueryResult>, String> {
|
||||
let registered_query = execution_id
|
||||
.as_ref()
|
||||
.filter(|id| !id.trim().is_empty())
|
||||
.map(|id| state.running_queries.register(id.clone()));
|
||||
let registered_query =
|
||||
execution_id.as_ref().filter(|id| !id.trim().is_empty()).map(|id| state.running_queries.register(id.clone()));
|
||||
let cancel_token = registered_query.as_ref().map(|query| query.token());
|
||||
|
||||
dbx_core::query::execute_multi_core(
|
||||
&state,
|
||||
&connection_id,
|
||||
&database,
|
||||
&sql,
|
||||
cancel_token,
|
||||
)
|
||||
.await
|
||||
dbx_core::query::execute_multi_core(&state, &connection_id, &database, &sql, cancel_token).await
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub async fn cancel_query(
|
||||
state: State<'_, Arc<AppState>>,
|
||||
execution_id: String,
|
||||
) -> Result<bool, String> {
|
||||
pub async fn cancel_query(state: State<'_, Arc<AppState>>, execution_id: String) -> Result<bool, String> {
|
||||
Ok(state.running_queries.cancel(&execution_id))
|
||||
}
|
||||
|
||||
|
||||
@@ -5,10 +5,7 @@ use crate::commands::connection::AppState;
|
||||
use dbx_core::db::redis_driver::{RedisScanResult, RedisValue};
|
||||
|
||||
#[tauri::command]
|
||||
pub async fn redis_list_databases(
|
||||
state: State<'_, Arc<AppState>>,
|
||||
connection_id: String,
|
||||
) -> Result<Vec<u32>, String> {
|
||||
pub async fn redis_list_databases(state: State<'_, Arc<AppState>>, connection_id: String) -> Result<Vec<u32>, String> {
|
||||
dbx_core::redis_ops::redis_list_databases_core(&state, &connection_id).await
|
||||
}
|
||||
|
||||
@@ -56,7 +53,10 @@ pub async fn redis_delete_key(
|
||||
#[tauri::command]
|
||||
pub async fn redis_hash_set(
|
||||
state: State<'_, Arc<AppState>>,
|
||||
connection_id: String, key: String, field: String, value: String,
|
||||
connection_id: String,
|
||||
key: String,
|
||||
field: String,
|
||||
value: String,
|
||||
) -> Result<(), String> {
|
||||
dbx_core::redis_ops::redis_hash_set_core(&state, &connection_id, &key, &field, &value).await
|
||||
}
|
||||
@@ -64,7 +64,9 @@ pub async fn redis_hash_set(
|
||||
#[tauri::command]
|
||||
pub async fn redis_hash_del(
|
||||
state: State<'_, Arc<AppState>>,
|
||||
connection_id: String, key: String, field: String,
|
||||
connection_id: String,
|
||||
key: String,
|
||||
field: String,
|
||||
) -> Result<(), String> {
|
||||
dbx_core::redis_ops::redis_hash_del_core(&state, &connection_id, &key, &field).await
|
||||
}
|
||||
@@ -72,7 +74,9 @@ pub async fn redis_hash_del(
|
||||
#[tauri::command]
|
||||
pub async fn redis_list_push(
|
||||
state: State<'_, Arc<AppState>>,
|
||||
connection_id: String, key: String, value: String,
|
||||
connection_id: String,
|
||||
key: String,
|
||||
value: String,
|
||||
) -> Result<(), String> {
|
||||
dbx_core::redis_ops::redis_list_push_core(&state, &connection_id, &key, &value).await
|
||||
}
|
||||
@@ -80,7 +84,9 @@ pub async fn redis_list_push(
|
||||
#[tauri::command]
|
||||
pub async fn redis_list_remove(
|
||||
state: State<'_, Arc<AppState>>,
|
||||
connection_id: String, key: String, index: i64,
|
||||
connection_id: String,
|
||||
key: String,
|
||||
index: i64,
|
||||
) -> Result<(), String> {
|
||||
dbx_core::redis_ops::redis_list_remove_core(&state, &connection_id, &key, index).await
|
||||
}
|
||||
@@ -88,7 +94,9 @@ pub async fn redis_list_remove(
|
||||
#[tauri::command]
|
||||
pub async fn redis_set_add(
|
||||
state: State<'_, Arc<AppState>>,
|
||||
connection_id: String, key: String, member: String,
|
||||
connection_id: String,
|
||||
key: String,
|
||||
member: String,
|
||||
) -> Result<(), String> {
|
||||
dbx_core::redis_ops::redis_set_add_core(&state, &connection_id, &key, &member).await
|
||||
}
|
||||
@@ -96,7 +104,9 @@ pub async fn redis_set_add(
|
||||
#[tauri::command]
|
||||
pub async fn redis_set_remove(
|
||||
state: State<'_, Arc<AppState>>,
|
||||
connection_id: String, key: String, member: String,
|
||||
connection_id: String,
|
||||
key: String,
|
||||
member: String,
|
||||
) -> Result<(), String> {
|
||||
dbx_core::redis_ops::redis_set_remove_core(&state, &connection_id, &key, &member).await
|
||||
}
|
||||
@@ -12,8 +12,7 @@ use crate::commands::connection::AppState;
|
||||
use crate::commands::query::execute_sql_statement;
|
||||
|
||||
pub use dbx_core::sql::{
|
||||
statement_summary, SqlFilePreview, SqlFileProgress, SqlFileRequest,
|
||||
SqlFileStatus, SqlStatementSplitter,
|
||||
statement_summary, SqlFilePreview, SqlFileProgress, SqlFileRequest, SqlFileStatus, SqlStatementSplitter,
|
||||
};
|
||||
|
||||
static SQL_FILE_EXECUTIONS: std::sync::LazyLock<RwLock<HashMap<String, CancellationToken>>> =
|
||||
@@ -38,25 +37,15 @@ struct SqlFileSummary {
|
||||
#[tauri::command]
|
||||
pub async fn preview_sql_file(file_path: String) -> Result<SqlFilePreview, String> {
|
||||
let path = PathBuf::from(&file_path);
|
||||
let metadata = tokio::fs::metadata(&path)
|
||||
.await
|
||||
.map_err(|e| e.to_string())?;
|
||||
let mut file = tokio::fs::File::open(&path)
|
||||
.await
|
||||
.map_err(|e| e.to_string())?;
|
||||
let metadata = tokio::fs::metadata(&path).await.map_err(|e| e.to_string())?;
|
||||
let mut file = tokio::fs::File::open(&path).await.map_err(|e| e.to_string())?;
|
||||
let mut buffer = vec![0; 4096];
|
||||
let bytes_read = tokio::io::AsyncReadExt::read(&mut file, &mut buffer)
|
||||
.await
|
||||
.map_err(|e| e.to_string())?;
|
||||
let bytes_read = tokio::io::AsyncReadExt::read(&mut file, &mut buffer).await.map_err(|e| e.to_string())?;
|
||||
buffer.truncate(bytes_read);
|
||||
let preview = String::from_utf8_lossy(&buffer).to_string();
|
||||
|
||||
Ok(SqlFilePreview {
|
||||
file_name: path
|
||||
.file_name()
|
||||
.and_then(|name| name.to_str())
|
||||
.unwrap_or("script.sql")
|
||||
.to_string(),
|
||||
file_name: path.file_name().and_then(|name| name.to_str()).unwrap_or("script.sql").to_string(),
|
||||
file_path,
|
||||
size_bytes: metadata.len(),
|
||||
preview,
|
||||
@@ -76,18 +65,7 @@ pub async fn execute_sql_file(
|
||||
}
|
||||
|
||||
let started_at = Instant::now();
|
||||
emit_progress(
|
||||
&app,
|
||||
&request.execution_id,
|
||||
SqlFileStatus::Started,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
started_at,
|
||||
"",
|
||||
None,
|
||||
);
|
||||
emit_progress(&app, &request.execution_id, SqlFileStatus::Started, 0, 0, 0, 0, started_at, "", None);
|
||||
|
||||
let result = execute_sql_file_inner(&app, &state, &request, token, started_at).await;
|
||||
{
|
||||
@@ -334,19 +312,14 @@ fn register_sql_file_execution(
|
||||
token: CancellationToken,
|
||||
) -> Result<(), String> {
|
||||
if executions.contains_key(&execution_id) {
|
||||
return Err(format!(
|
||||
"SQL file execution '{execution_id}' already exists"
|
||||
));
|
||||
return Err(format!("SQL file execution '{execution_id}' already exists"));
|
||||
}
|
||||
|
||||
executions.insert(execution_id, token);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn remove_sql_file_execution(
|
||||
executions: &mut HashMap<String, CancellationToken>,
|
||||
execution_id: &str,
|
||||
) {
|
||||
fn remove_sql_file_execution(executions: &mut HashMap<String, CancellationToken>, execution_id: &str) {
|
||||
executions.remove(execution_id);
|
||||
}
|
||||
|
||||
@@ -394,11 +367,7 @@ fn statement_error_decision(
|
||||
);
|
||||
|
||||
if continue_on_error {
|
||||
return StatementErrorDecision {
|
||||
progress: vec![statement_failed],
|
||||
failure_count,
|
||||
result: Ok(false),
|
||||
};
|
||||
return StatementErrorDecision { progress: vec![statement_failed], failure_count, result: Ok(false) };
|
||||
}
|
||||
|
||||
let terminal_error = sql_file_progress(
|
||||
@@ -413,11 +382,7 @@ fn statement_error_decision(
|
||||
Some(error.clone()),
|
||||
);
|
||||
|
||||
StatementErrorDecision {
|
||||
progress: vec![statement_failed, terminal_error],
|
||||
failure_count,
|
||||
result: Err(error),
|
||||
}
|
||||
StatementErrorDecision { progress: vec![statement_failed, terminal_error], failure_count, result: Err(error) }
|
||||
}
|
||||
|
||||
fn emit_progress(
|
||||
@@ -559,11 +524,7 @@ async fn run_statements_for_test(
|
||||
}
|
||||
|
||||
SqlFileSummary {
|
||||
status: if token.is_cancelled() {
|
||||
SqlFileStatus::Cancelled
|
||||
} else {
|
||||
SqlFileStatus::Done
|
||||
},
|
||||
status: if token.is_cancelled() { SqlFileStatus::Cancelled } else { SqlFileStatus::Done },
|
||||
success_count,
|
||||
failure_count,
|
||||
failed_statement_index,
|
||||
@@ -586,12 +547,7 @@ mod execution_tests {
|
||||
|
||||
#[tokio::test]
|
||||
async fn stops_on_first_failure_by_default() {
|
||||
let summary = run_fake_script(
|
||||
vec!["ok 1".into(), "fail 2".into(), "ok 3".into()],
|
||||
false,
|
||||
None,
|
||||
)
|
||||
.await;
|
||||
let summary = run_fake_script(vec!["ok 1".into(), "fail 2".into(), "ok 3".into()], false, None).await;
|
||||
|
||||
assert_eq!(summary.success_count, 1);
|
||||
assert_eq!(summary.failure_count, 1);
|
||||
@@ -601,12 +557,7 @@ mod execution_tests {
|
||||
|
||||
#[tokio::test]
|
||||
async fn continues_after_failure_when_enabled() {
|
||||
let summary = run_fake_script(
|
||||
vec!["ok 1".into(), "fail 2".into(), "ok 3".into()],
|
||||
true,
|
||||
None,
|
||||
)
|
||||
.await;
|
||||
let summary = run_fake_script(vec!["ok 1".into(), "fail 2".into(), "ok 3".into()], true, None).await;
|
||||
|
||||
assert_eq!(summary.success_count, 2);
|
||||
assert_eq!(summary.failure_count, 1);
|
||||
@@ -615,12 +566,7 @@ mod execution_tests {
|
||||
|
||||
#[tokio::test]
|
||||
async fn cancellation_stops_before_next_statement() {
|
||||
let summary = run_fake_script(
|
||||
vec!["ok 1".into(), "ok 2".into(), "ok 3".into()],
|
||||
true,
|
||||
Some(1),
|
||||
)
|
||||
.await;
|
||||
let summary = run_fake_script(vec!["ok 1".into(), "ok 2".into(), "ok 3".into()], true, Some(1)).await;
|
||||
|
||||
assert_eq!(summary.success_count, 1);
|
||||
assert_eq!(summary.status, SqlFileStatus::Cancelled);
|
||||
@@ -628,15 +574,7 @@ mod execution_tests {
|
||||
|
||||
#[test]
|
||||
fn file_io_errors_build_terminal_error_progress() {
|
||||
let progress = file_io_error_progress(
|
||||
"exec-1",
|
||||
4,
|
||||
2,
|
||||
1,
|
||||
17,
|
||||
Instant::now(),
|
||||
"read failed".to_string(),
|
||||
);
|
||||
let progress = file_io_error_progress("exec-1", 4, 2, 1, 17, Instant::now(), "read failed".to_string());
|
||||
|
||||
assert_eq!(progress.execution_id, "exec-1");
|
||||
assert_eq!(progress.status, SqlFileStatus::Error);
|
||||
@@ -655,13 +593,9 @@ mod execution_tests {
|
||||
let replacement = CancellationToken::new();
|
||||
executions.insert("dup".to_string(), original.clone());
|
||||
|
||||
let result =
|
||||
register_sql_file_execution(&mut executions, "dup".to_string(), replacement.clone());
|
||||
let result = register_sql_file_execution(&mut executions, "dup".to_string(), replacement.clone());
|
||||
|
||||
assert_eq!(
|
||||
result.unwrap_err(),
|
||||
"SQL file execution 'dup' already exists"
|
||||
);
|
||||
assert_eq!(result.unwrap_err(), "SQL file execution 'dup' already exists");
|
||||
assert_eq!(executions.len(), 1);
|
||||
|
||||
executions.get("dup").unwrap().cancel();
|
||||
@@ -720,17 +654,8 @@ mod execution_tests {
|
||||
|
||||
#[test]
|
||||
fn progress_payload_serializes_camel_case_status() {
|
||||
let progress = sql_file_progress(
|
||||
"exec-1",
|
||||
SqlFileStatus::StatementDone,
|
||||
1,
|
||||
1,
|
||||
0,
|
||||
3,
|
||||
Instant::now(),
|
||||
"select 1",
|
||||
None,
|
||||
);
|
||||
let progress =
|
||||
sql_file_progress("exec-1", SqlFileStatus::StatementDone, 1, 1, 0, 3, Instant::now(), "select 1", None);
|
||||
|
||||
let value = serde_json::to_value(progress).unwrap();
|
||||
|
||||
|
||||
@@ -8,10 +8,7 @@ use crate::commands::connection::AppState;
|
||||
use crate::commands::transfer::get_db_type;
|
||||
|
||||
// Re-export types for backward compatibility
|
||||
pub use dbx_core::table_import::{
|
||||
TableImportPreview, TableImportProgress,
|
||||
TableImportRequest, TableImportSummary,
|
||||
};
|
||||
pub use dbx_core::table_import::{TableImportPreview, TableImportProgress, TableImportRequest, TableImportSummary};
|
||||
|
||||
static CANCELLED_IMPORTS: std::sync::LazyLock<RwLock<HashSet<String>>> =
|
||||
std::sync::LazyLock::new(|| RwLock::new(HashSet::new()));
|
||||
@@ -44,9 +41,7 @@ pub async fn import_table_file(
|
||||
let pool_key = if request.database.is_empty() {
|
||||
request.connection_id.clone()
|
||||
} else {
|
||||
state
|
||||
.get_or_create_pool(&request.connection_id, Some(&request.database))
|
||||
.await?
|
||||
state.get_or_create_pool(&request.connection_id, Some(&request.database)).await?
|
||||
};
|
||||
|
||||
let result = dbx_core::table_import::import_table_file_core(
|
||||
|
||||
@@ -4,10 +4,7 @@ use tauri::{AppHandle, Emitter, State};
|
||||
use crate::commands::connection::AppState;
|
||||
|
||||
// Re-export types and functions used by other modules
|
||||
pub use dbx_core::transfer::{
|
||||
get_db_type,
|
||||
TransferProgress, TransferRequest, TransferStatus,
|
||||
};
|
||||
pub use dbx_core::transfer::{get_db_type, TransferProgress, TransferRequest, TransferStatus};
|
||||
|
||||
fn emit_progress(app: &AppHandle, progress: TransferProgress) {
|
||||
let _ = app.emit("transfer-progress", progress);
|
||||
@@ -27,12 +24,10 @@ pub async fn start_transfer(
|
||||
let target_db_type = get_db_type(&state, &request.target_connection_id).await?;
|
||||
|
||||
// Ensure pools
|
||||
let source_pool_key = state
|
||||
.get_or_create_pool(&request.source_connection_id, Some(&request.source_database))
|
||||
.await?;
|
||||
let target_pool_key = state
|
||||
.get_or_create_pool(&request.target_connection_id, Some(&request.target_database))
|
||||
.await?;
|
||||
let source_pool_key =
|
||||
state.get_or_create_pool(&request.source_connection_id, Some(&request.source_database)).await?;
|
||||
let target_pool_key =
|
||||
state.get_or_create_pool(&request.target_connection_id, Some(&request.target_database)).await?;
|
||||
|
||||
tokio::spawn(async move {
|
||||
let total_tables = request.tables.len();
|
||||
@@ -40,16 +35,19 @@ pub async fn start_transfer(
|
||||
|
||||
for (i, table) in request.tables.iter().enumerate() {
|
||||
if dbx_core::transfer::is_cancelled(&transfer_id).await {
|
||||
emit_progress(&app, TransferProgress {
|
||||
transfer_id: transfer_id.clone(),
|
||||
table: table.clone(),
|
||||
table_index: i,
|
||||
total_tables,
|
||||
rows_transferred: 0,
|
||||
total_rows: None,
|
||||
status: TransferStatus::Cancelled,
|
||||
error: None,
|
||||
});
|
||||
emit_progress(
|
||||
&app,
|
||||
TransferProgress {
|
||||
transfer_id: transfer_id.clone(),
|
||||
table: table.clone(),
|
||||
table_index: i,
|
||||
total_tables,
|
||||
rows_transferred: 0,
|
||||
total_rows: None,
|
||||
status: TransferStatus::Cancelled,
|
||||
error: None,
|
||||
},
|
||||
);
|
||||
dbx_core::transfer::clear_cancelled(&transfer_id).await;
|
||||
return;
|
||||
}
|
||||
@@ -57,48 +55,68 @@ pub async fn start_transfer(
|
||||
log::info!("[transfer] table {}/{}: {}", i + 1, total_tables, table);
|
||||
|
||||
match dbx_core::transfer::transfer_table(
|
||||
&state, &request, table, i,
|
||||
&source_db_type, &target_db_type,
|
||||
&source_pool_key, &target_pool_key,
|
||||
&state,
|
||||
&request,
|
||||
table,
|
||||
i,
|
||||
&source_db_type,
|
||||
&target_db_type,
|
||||
&source_pool_key,
|
||||
&target_pool_key,
|
||||
|progress| emit_progress(&app, progress),
|
||||
).await {
|
||||
)
|
||||
.await
|
||||
{
|
||||
Ok(rows) => {
|
||||
emit_progress(&app, TransferProgress {
|
||||
transfer_id: transfer_id.clone(),
|
||||
table: table.clone(),
|
||||
table_index: i,
|
||||
total_tables,
|
||||
rows_transferred: rows,
|
||||
total_rows: Some(rows),
|
||||
status: if i == total_tables - 1 { TransferStatus::Done } else { TransferStatus::TableDone },
|
||||
error: None,
|
||||
});
|
||||
emit_progress(
|
||||
&app,
|
||||
TransferProgress {
|
||||
transfer_id: transfer_id.clone(),
|
||||
table: table.clone(),
|
||||
table_index: i,
|
||||
total_tables,
|
||||
rows_transferred: rows,
|
||||
total_rows: Some(rows),
|
||||
status: if i == total_tables - 1 {
|
||||
TransferStatus::Done
|
||||
} else {
|
||||
TransferStatus::TableDone
|
||||
},
|
||||
error: None,
|
||||
},
|
||||
);
|
||||
}
|
||||
Err(e) => {
|
||||
if e == "Cancelled" {
|
||||
emit_progress(&app, TransferProgress {
|
||||
emit_progress(
|
||||
&app,
|
||||
TransferProgress {
|
||||
transfer_id: transfer_id.clone(),
|
||||
table: table.clone(),
|
||||
table_index: i,
|
||||
total_tables,
|
||||
rows_transferred: 0,
|
||||
total_rows: None,
|
||||
status: TransferStatus::Cancelled,
|
||||
error: None,
|
||||
},
|
||||
);
|
||||
dbx_core::transfer::clear_cancelled(&transfer_id).await;
|
||||
return;
|
||||
}
|
||||
emit_progress(
|
||||
&app,
|
||||
TransferProgress {
|
||||
transfer_id: transfer_id.clone(),
|
||||
table: table.clone(),
|
||||
table_index: i,
|
||||
total_tables,
|
||||
rows_transferred: 0,
|
||||
total_rows: None,
|
||||
status: TransferStatus::Cancelled,
|
||||
error: None,
|
||||
});
|
||||
dbx_core::transfer::clear_cancelled(&transfer_id).await;
|
||||
return;
|
||||
}
|
||||
emit_progress(&app, TransferProgress {
|
||||
transfer_id: transfer_id.clone(),
|
||||
table: table.clone(),
|
||||
table_index: i,
|
||||
total_tables,
|
||||
rows_transferred: 0,
|
||||
total_rows: None,
|
||||
status: TransferStatus::Error,
|
||||
error: Some(e),
|
||||
});
|
||||
status: TransferStatus::Error,
|
||||
error: Some(e),
|
||||
},
|
||||
);
|
||||
dbx_core::transfer::clear_cancelled(&transfer_id).await;
|
||||
return;
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user