use std::{
io::{stdin, IsTerminal, Read},
path::PathBuf,
};
use anyhow::{Context, Result};
use clap::Parser;
use csv::WriterBuilder;
use matdb::{Db, ResultSet};
use rustyline::{Config, DefaultEditor};
#[derive(Parser, Debug)]
#[command(version, about, long_about = None)]
struct Args {
path: PathBuf,
#[arg(short, long)]
repl: Option<bool>,
}
fn main() -> Result<()> {
let args = Args::parse();
let mut state = Db::open(args.path).context("failed to open database")?;
if args.repl.unwrap_or_else(|| stdin().is_terminal()) {
let mut rl =
DefaultEditor::with_config(Config::builder().auto_add_history(true).build()).unwrap();
loop {
let readline = rl.readline("matdb> ");
match readline {
Ok(line) => {
if let Err(e) = handle_input(&mut state, line) {
eprintln!("{e:?}");
}
}
Err(rustyline::error::ReadlineError::Interrupted) => {}
Err(_) => break,
}
}
} else {
let mut input = Vec::new();
stdin().read_to_end(&mut input).unwrap();
handle_input(
&mut state,
String::from_utf8(input).context("failed reading from stdin")?,
)?;
}
Ok(())
}
fn handle_input(state: &mut Db, input: String) -> Result<()> {
let rows = state
.run(&input, Vec::new())
.context("failed executing input")?;
state.finish_tx().context("failed finishing transaction")?;
if let Some(ResultSet { column_names, rows }) = rows {
let mut buf = Vec::new();
{
let mut wtr = WriterBuilder::new().from_writer(&mut buf);
wtr.write_record(&column_names).unwrap();
for row in rows {
wtr.write_record(row.as_slice().iter().map(|v| match v {
matdb::Value::Null => "NULL".to_string(),
matdb::Value::Bool(true) => "TRUE".to_string(),
matdb::Value::Bool(false) => "FALSE".to_string(),
matdb::Value::Int(i) => i.to_string(),
matdb::Value::String(s) => s.to_string(),
}))
.unwrap();
}
wtr.flush().unwrap();
}
print!("{}", String::from_utf8(buf).unwrap());
}
Ok(())
}