use colored::Colorize;
fn use_default_client_for_one_shot(conn: &crate::engines::Conn, s: &mut crate::settings::Settings) {
if crate::engines::speaks_client_flags(conn.engine, s) {
return;
}
let configured = conn.engine.client_argv(s).join(" ");
let default = conn.engine.default_client();
if !crate::engines::on_path(default) {
eprintln!(
"{}",
format!(
"esql: `{configured}` cannot take a query or another database, and `{default}` is not installed."
)
.red()
);
eprintln!(
"{}",
format!(
" install it: {}",
crate::engines::install_hint(conn.engine)
)
.dimmed()
);
std::process::exit(127);
}
eprintln!(
"{}",
format!(
"esql: `{configured}` cannot take a query or another database, so this runs with `{default}`."
)
.dimmed()
);
s.set(conn.engine.client_setting(), default);
}
fn push_query(argv: &mut Vec<String>, conn: &crate::engines::Conn, sql: String) {
if let Some(flag) = conn.engine.query_flag() {
argv.push(flag.to_string());
}
argv.push(sql);
}
fn print_markdown(
program: &str,
args: &[String],
conn: &crate::engines::Conn,
settings: &crate::settings::Settings,
rows: crate::markdown::Rows,
) -> ! {
use std::process::{Command, Stdio};
let out = Command::new(program)
.args(args)
.envs(conn.connect_env(settings))
.envs(conn.secret_env(settings))
.stdin(Stdio::inherit())
.stderr(Stdio::inherit())
.output();
match out {
Ok(out) => {
print!(
"{}",
crate::markdown::render(&String::from_utf8_lossy(&out.stdout), rows)
);
std::process::exit(out.status.code().unwrap_or(1));
}
Err(e) => {
eprintln!("{}", format!("esql: could not run {program}: {e}").red());
std::process::exit(127);
}
}
}
pub fn run(args: Vec<String>, md: bool) {
if args.is_empty() {
eprintln!(
"{}",
"esql: no connection given. Try `esql ls` or just `esql`.".red()
);
std::process::exit(2);
}
let (conn, db) = match crate::engines::find_target(&args[0]) {
Ok(found) => found,
Err(e) => {
eprintln!("{}", format!("esql: {e}").red());
std::process::exit(2);
}
};
if db.is_some() && conn.engine == crate::engines::Engine::Sqlite {
eprintln!(
"{}",
format!(
"esql: `{}` is a sqlite file, and another database is another file.",
conn.name
)
.red()
);
eprintln!(
"{}",
" save that file as its own connection with `c` in `esql`.".dimmed()
);
std::process::exit(2);
}
let rest: Vec<String> = args.into_iter().skip(1).collect();
let session = rest.is_empty();
let first = rest.first().cloned().unwrap_or_default();
let snippet: Option<String> = match first.strip_prefix(':') {
Some(name) => match crate::snippets::get(name) {
Some(s) => Some(s.sql),
None => {
eprintln!("{}", format!("esql: no snippet called '{name}'").red());
eprintln!(
"{}",
" `esql` and the Snippets tab list and create them.".dimmed()
);
std::process::exit(2);
}
},
None => None,
};
let adhoc = !session && first != "--" && snippet.is_none() && !first.starts_with('-');
let table = match (md, conn.engine.table_output()) {
(false, _) => None,
(true, None) => {
eprintln!(
"{}",
format!(
"esql: `--md` cannot read `{}`'s rows back, since it has no quoted output.",
conn.engine.default_client()
)
.red()
);
std::process::exit(2);
}
(true, Some(_)) if session => {
eprintln!(
"{}",
"esql: `--md` formats a query's rows, so give it one: `esql --md <name> 'select 1'`."
.red()
);
std::process::exit(2);
}
(true, Some(t)) => Some(t),
};
let mut settings = crate::settings::load();
if db.is_some() || snippet.is_some() || adhoc || table.is_some() {
use_default_client_for_one_shot(&conn, &mut settings);
}
if conn.engine == crate::engines::Engine::MsSql
&& settings.mssql_passwords
&& crate::engines::mssql::has_password(&conn.name)
&& !crate::engines::mssql::pass_is_private()
{
eprintln!(
"{}",
format!(
"esql: `{}` can be read by others, so its password is not used: `chmod 600` it.",
crate::ini::collapse_tilde(&crate::engines::mssql::pass_path().to_string_lossy())
)
.dimmed()
);
}
if conn.engine == crate::engines::Engine::Sqlite
&& conn.read_only()
&& !crate::engines::speaks_client_flags(conn.engine, &settings)
{
eprintln!(
"{}",
format!(
"esql: `{}` cannot open a file read-only, so this runs with `sqlite3`.",
conn.engine.client_argv(&settings).join(" ")
)
.dimmed()
);
}
let program = conn.connect_argv_db(&settings, db.as_deref())[0].clone();
if !crate::engines::on_path(&program) {
eprintln!(
"{}",
format!("esql: `{program}` is not installed, and easysql is only a front end for it.")
.red()
);
eprintln!(
" {} {}",
"install it:".dimmed(),
crate::engines::install_hint(conn.engine).bold()
);
if crate::engines::install_argv(conn.engine).is_some() {
eprintln!(
" {}",
"or run `esql` and press Enter on it, which offers to do this for you.".dimmed()
);
}
std::process::exit(127);
}
match crate::vias::ensure(&conn.key()) {
Some(Ok(msg)) => eprintln!("{}", format!("esql: {msg}").dimmed()),
Some(Err(e)) => {
eprintln!(
"{}",
format!("esql: could not reopen the tunnel: {e}").red()
);
eprintln!(
"{}",
" the connection points at it, so this will not connect.".dimmed()
);
std::process::exit(1);
}
None => {}
}
if conn.engine == crate::engines::Engine::Pg && conn.read_only() {
let check =
crate::engines::pg::check_read_only(&conn, db.as_deref(), settings.probe_timeout);
if let Some((headline, fix)) = check.refusal(&conn.name) {
eprintln!("{}", format!("esql: {headline}").red());
eprintln!("{}", format!(" {fix}").dimmed());
std::process::exit(1);
}
if let crate::engines::pg::ReadOnlyCheck::Unknown(said) = check {
eprintln!(
"{}",
format!(
"esql: could not confirm `{}` is read-only: {said}",
conn.name
)
.dimmed()
);
}
}
crate::history::record(&conn.key());
let mut argv = conn.connect_argv_db(&settings, db.as_deref());
if let Some((flags, _)) = table {
argv.extend(flags.iter().map(|f| f.to_string()));
}
if let Some(sql) = snippet.clone() {
push_query(&mut argv, &conn, sql);
argv.extend(rest.into_iter().skip(1));
} else if first == "--" {
argv.extend(rest.into_iter().skip(1));
} else if adhoc {
push_query(&mut argv, &conn, first);
argv.extend(rest.into_iter().skip(1));
} else {
argv.extend(rest);
}
if conn.engine == crate::engines::Engine::Pg && snippet.is_none() {
let _ = crate::snippets::sync_psqlrc();
}
if settings.hints && session {
eprintln!(
"{}",
format!(" {}", crate::engines::hint_line(conn.engine, &settings)).dimmed()
);
}
let (program, rest) = argv.split_first().expect("connect_argv is never empty");
let program = program.clone();
let rest: Vec<String> = rest.to_vec();
if let Some((_, rows)) = table {
print_markdown(&program, &rest, &conn, &settings, rows);
}
#[cfg(unix)]
{
use std::os::unix::process::CommandExt;
let err = std::process::Command::new(&program)
.args(&rest)
.envs(conn.connect_env(&settings))
.envs(conn.secret_env(&settings))
.exec();
eprintln!("{}", format!("esql: could not run {program}: {err}").red());
eprintln!(
"{}",
format!("install it: {}", crate::engines::install_hint(conn.engine)).dimmed()
);
std::process::exit(127);
}
#[cfg(not(unix))]
{
match std::process::Command::new(&program)
.args(&rest)
.envs(conn.connect_env(&settings))
.envs(conn.secret_env(&settings))
.status()
{
Ok(status) => std::process::exit(status.code().unwrap_or(1)),
Err(e) => {
eprintln!("{}", format!("esql: could not run {program}: {e}").red());
std::process::exit(127);
}
}
}
}