use super::exec;
use super::transcript::{BlockKind, Transcript};
use crate::config::runtime::RuntimeConfig;
use crate::interactive::tui::types::LastQuery;
use crate::render::TerminalEvent;
use std::sync::Arc;
use std::sync::mpsc::Receiver;
#[derive(Clone)]
pub(crate) enum Followup {
Sql { connection: Option<String> },
Export { path: String },
Chart {
kind: Option<crate::chart::ChartKind>,
path: Option<String>,
},
Explain,
}
#[derive(Clone)]
pub(crate) struct SqlTask {
pub profile: Option<String>,
pub sql: String,
pub followup: Followup,
}
pub(crate) fn spawn(runtime: Arc<RuntimeConfig>, task: SqlTask) -> Receiver<TerminalEvent> {
let (tx, rx) = std::sync::mpsc::channel();
std::thread::spawn(move || {
let event = match tokio::runtime::Builder::new_current_thread()
.enable_all()
.build()
{
Ok(runtime_handle) => {
runtime_handle.block_on(exec::run_sql(&runtime, task.profile.as_deref(), &task.sql))
}
Err(error) => TerminalEvent::Error {
message: error.to_string(),
},
};
let _ = tx.send(event);
});
rx
}
pub(crate) fn complete(
task: &SqlTask,
event: TerminalEvent,
transcript: &mut Transcript,
last_query: &mut Option<LastQuery>,
) {
let TerminalEvent::QueryResult { result } = event else {
let TerminalEvent::Error { message } = event else {
return;
};
transcript.push(BlockKind::Error, message);
return;
};
match &task.followup {
Followup::Sql { connection } => {
*last_query = Some(LastQuery {
sql: task.sql.clone(),
connection: connection.clone(),
});
let table = super::table::format_table(&result);
transcript.push(
BlockKind::Table,
super::table::with_scope_line(table, connection.as_deref(), &result.executed_sql),
);
}
Followup::Export { path } => {
match super::export::write_result(&result, std::path::Path::new(path)) {
Ok(n) => {
let mut msg = format!("Exported {n} row(s) to {path}");
if result.truncated {
msg.push_str(" (result was truncated)");
}
transcript.push(BlockKind::System, msg);
}
Err(msg) => transcript.push(BlockKind::Error, msg),
}
}
Followup::Chart { kind, path } => {
complete_chart(&result, *kind, path.as_deref(), transcript);
}
Followup::Explain => {
transcript.push(BlockKind::Tool, super::table::format_plan(&result));
}
}
}
fn complete_chart(
result: &saya_types::QueryResult,
kind: Option<crate::chart::ChartKind>,
path_arg: Option<&str>,
transcript: &mut Transcript,
) {
let mut spec = crate::chart::suggest_spec(result);
if let Some(k) = kind {
spec.kind = k;
}
let html = match crate::chart::render_html(result, &spec) {
Ok(html) => html,
Err(msg) => {
transcript.push(BlockKind::System, msg);
return;
}
};
let path = match path_arg {
Some(path) => std::path::PathBuf::from(path),
None => match crate::chart::reserve_temp_chart() {
Ok(mut chart) => {
if let Err(msg) = chart.write_html(&html) {
transcript.push(BlockKind::Error, msg);
return;
}
chart.path().to_path_buf()
}
Err(msg) => {
transcript.push(BlockKind::Error, msg);
return;
}
},
};
if path_arg.is_some()
&& let Err(msg) = crate::chart::write_html(&html, &path)
{
transcript.push(BlockKind::Error, msg);
return;
}
let mut note = format!("Chart written to {}", path.display());
if result.truncated {
note.push_str(" (result was truncated)");
}
match crate::chart::open_file(&path) {
Ok(()) => note.push_str(" (opening in your browser)"),
Err(e) => note.push_str(&format!(" — open it manually ({e})")),
}
transcript.push(BlockKind::System, note);
}