matdb 0.1.0

An experimental embedded SQL-like DBMS
Documentation
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};

/// CLI tool to interact with matdb database
#[derive(Parser, Debug)]
#[command(version, about, long_about = None)]
struct Args {
    /// File path of the database file
    path: PathBuf,

    /// Whether to start a REPL interface, by default determined by checking if in a tty
    #[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(())
}