narrowdb 0.3.2

A lightweight columnar database engine for log and time-series data
Documentation
use anyhow::{Context, Result, bail};

use narrowdb::bench::run_benchmark;
use narrowdb::{DbOptions, NarrowDb};

fn main() -> Result<()> {
    let args = std::env::args().collect::<Vec<_>>();
    if args.len() < 2 {
        print_usage();
        return Ok(());
    }

    match args[1].as_str() {
        "exec" => exec_command(&args),
        "bench" => bench_command(&args),
        _ => {
            print_usage();
            Ok(())
        }
    }
}

fn exec_command(args: &[String]) -> Result<()> {
    if args.len() < 4 {
        bail!("usage: narrowdb exec <db-file> <sql> [--query-threads N]");
    }
    let query_threads = parse_query_threads_arg(&args[4..])?;
    let db = NarrowDb::open(
        &args[2],
        DbOptions {
            query_threads,
            ..DbOptions::default()
        },
    )?;
    let results = db.execute_sql(&args[3])?;
    for result in results {
        if result.columns.is_empty() {
            continue;
        }
        println!("{}", result.columns.join(" | "));
        for row in result.rows {
            println!(
                "{}",
                row.iter()
                    .map(ToString::to_string)
                    .collect::<Vec<_>>()
                    .join(" | ")
            );
        }
    }
    Ok(())
}

fn bench_command(args: &[String]) -> Result<()> {
    if args.len() < 3 {
        bail!("usage: narrowdb bench <db-file> [rows] [--query-threads N]");
    }
    let (rows, query_threads) = parse_bench_args(args)?;
    run_benchmark(&args[2], rows, query_threads)
}

fn print_usage() {
    println!("narrowdb");
    println!("  exec  <db-file> <sql> [--query-threads N]");
    println!("  bench <db-file> [rows] [--query-threads N]");
}

fn parse_bench_args(args: &[String]) -> Result<(usize, Option<usize>)> {
    let mut rows = None;
    let mut query_threads = None;
    let mut index = 3;

    while index < args.len() {
        match args[index].as_str() {
            "--query-threads" => {
                index += 1;
                query_threads = Some(parse_query_threads_value(
                    args.get(index)
                        .context("missing value for --query-threads")?,
                )?);
            }
            value if rows.is_none() => {
                rows = Some(value.parse::<usize>().context("rows must be an integer")?);
            }
            other => bail!("unknown argument: {other}"),
        }
        index += 1;
    }

    Ok((rows.unwrap_or(1_000_000), query_threads))
}

fn parse_query_threads_arg(args: &[String]) -> Result<Option<usize>> {
    if args.is_empty() {
        return Ok(None);
    }
    if args.len() != 2 || args[0] != "--query-threads" {
        bail!("usage: narrowdb exec <db-file> <sql> [--query-threads N]");
    }
    Ok(Some(parse_query_threads_value(&args[1])?))
}

fn parse_query_threads_value(value: &str) -> Result<usize> {
    let threads = value
        .parse::<usize>()
        .context("query threads must be an integer")?;
    if threads == 0 {
        bail!("query threads must be greater than zero");
    }
    Ok(threads)
}