use gpui_kit::prelude::*;
use gpui_kit::{Context, Window};
use std::path::Path;
use crate::db::export::{self, Format};
use crate::db::query::QueryResult;
use crate::db::{RowKey, runtime};
use crate::ui::data_grid::Scope;
use crate::ui::sql_file;
use super::TableView;
use super::sql;
impl TableView {
pub(crate) fn export(&mut self, format: Format, cx: &mut Context<Self>) {
let (sql, params) = self.select_sql(false, cx);
let table = self.object.name.clone();
let connection = self.connection.clone();
let by_row_id = matches!(self.row_key, Some(RowKey::RowId(_)));
let query = async move {
match runtime::spawn(async move { connection.run_query_with(&sql, params).await }).await
{
Ok(Ok(mut result)) => {
if by_row_id {
sql::take_row_id(&mut result);
}
Ok(result)
}
Ok(Err(error)) => Err(format!("{error:#}")),
Err(_) => Err("exporting the table was cancelled".to_string()),
}
};
self.export_result(format, table, query, cx);
}
pub(crate) fn export_picked(&mut self, format: Format, cx: &mut Context<Self>) {
let snapshot = self.grid.read(cx).snapshot(Scope::Picked, cx);
if snapshot.rows.is_empty() {
return;
}
let table = self.object.name.clone();
let result = QueryResult {
columns: snapshot.columns,
column_types: snapshot.types,
rows: snapshot.rows,
..QueryResult::default()
};
let query = async move { Ok(result) };
self.export_result(format, table, query, cx);
}
fn export_result(
&mut self,
format: Format,
table: String,
query: impl Future<Output = Result<QueryResult, String>> + 'static,
cx: &mut Context<Self>,
) {
let engine = self.connection.config.engine;
let prompt = sql_file::prompt_for_save(None, &table, format.extension(), cx);
cx.spawn(async move |this, cx| {
let path = match prompt.await {
Ok(Some(path)) => path,
Ok(None) => return,
Err(error) => {
let message = format!("{error:#}");
this.update_in(cx, |this, window, cx| this.fail_export(message, window, cx))
.ok();
return;
}
};
let result = match query.await {
Ok(result) => result,
Err(message) => {
this.update_in(cx, |this, window, cx| this.fail_export(message, window, cx))
.ok();
return;
}
};
let rows = result.row_count();
let rendered = cx
.background_spawn(async move {
export::render(
format,
engine,
&table,
&result.columns,
&result.column_types,
&result.rows,
)
})
.await;
let export::Rendered { text, skipped } = rendered;
let written = cx
.background_spawn(sql_file::write(path.clone(), text))
.await;
this.update_in(cx, |this, window, cx| {
match written {
Ok(()) => {
this.error = None;
this.notice = Some(export_notice(rows, &path, skipped));
}
Err(error) => {
let message = format!("{error:#}");
this.error = Some(message.clone());
crate::ui::notify_error(window, cx, format!("Error: {message}"));
}
}
cx.notify();
})
.ok();
})
.detach();
}
fn fail_export(&mut self, message: String, window: &mut Window, cx: &mut Context<Self>) {
self.error = Some(message.clone());
self.notice = None;
crate::ui::notify_error(window, cx, format!("Error: {message}"));
cx.notify();
}
}
fn export_notice(rows: usize, path: &Path, skipped: usize) -> String {
let unit = if rows == 1 { "row" } else { "rows" };
let mut notice = format!("Exported {rows} {unit} to {}", path.display());
match skipped {
0 => {}
1 => notice.push_str(" (1 value could not be read back and was written as NULL)"),
skipped => notice.push_str(&format!(
" ({skipped} values could not be read back and were written as NULL)"
)),
}
notice
}