query-forge 0.7.0

Run SQL queries and dataset diffs on XLSX/XML/CSV/JSON/JSONL/Markdown/HTML/Parquet inputs and export results as text, CSV, JSONL, Markdown, XML, HTML, XLSX, or Parquet
Documentation
use std::time::Instant;

use anyhow::{Context, Result};
use clap::{CommandFactory, Parser};
use clap_complete::generate;
use clap_mangen::Man;

mod cli;
mod diff;
mod input_spec;
mod inspect;
#[path = "cli_output/mod.rs"]
mod output;
mod query;

use crate::cli::{Cli, Commands};

fn main() {
    match run() {
        Ok(code) => std::process::exit(code),
        Err(error) => {
            eprintln!("{error:#}");
            std::process::exit(2);
        }
    }
}

fn run() -> Result<i32> {
    let cli = Cli::parse();

    match cli.command {
        Commands::Query(cmd) => {
            let sql = query::load_query(&cmd)?;
            let params = query::parse_query_params(&cmd.params)?;
            let type_inference_options = query::resolve_type_inference_options(&cmd);
            let input_normalization_options = query::resolve_input_normalization_options(&cmd);
            let extraction_options = query::resolve_extraction_options(&cmd);
            let input_specs = input_spec::parse_input_specs(&cmd.input, None)?;
            let workbook_inputs = input_specs
                .iter()
                .map(|input| query_forge::WorkbookInput {
                    path: input.path.as_path(),
                    sheet_name: input.sheet_name.as_deref(),
                    table_name: input.table_name.as_deref(),
                })
                .collect::<Vec<_>>();

            let start = Instant::now();
            let result =
                query_forge::run_query_with_params_multi_inputs_and_options_and_normalization(
                    &workbook_inputs,
                    &sql,
                    &params,
                    &type_inference_options,
                    &input_normalization_options,
                    &extraction_options,
                    !cmd.no_headers,
                )?;
            let elapsed = start.elapsed();

            output::write_query_result(&result, cmd.output.as_deref(), cmd.format)?;

            if cmd.meta {
                let input_tables = input_specs
                    .iter()
                    .enumerate()
                    .map(|(i, spec)| output::InputTableMeta {
                        table_name: spec.table_name.clone().unwrap_or_else(|| {
                            if i == 0 {
                                "table".to_owned()
                            } else {
                                format!("table{}", i + 1)
                            }
                        }),
                        source_path: spec.path.display().to_string(),
                        source_selector: spec.sheet_name.clone(),
                    })
                    .collect();

                let meta = output::QueryMetadata {
                    row_count: result.rows.len(),
                    columns: result.columns.clone(),
                    input_tables,
                    execution_time_ms: elapsed.as_millis(),
                };
                output::write_query_metadata(&meta, cmd.meta_format.as_ref());
            }
        }
        Commands::Tables(cmd) => inspect::run_tables_command(cmd)?,
        Commands::Schema(cmd) => inspect::run_schema_command(cmd)?,
        Commands::Inspect(cmd) => inspect::run_inspect_command(cmd)?,
        Commands::Diff(cmd) => return diff::run_diff_command(cmd),
        Commands::Completions(cmd) => {
            let mut app = Cli::command();
            let bin_name = app.get_name().to_owned();
            generate(cmd.shell, &mut app, bin_name, &mut std::io::stdout());
        }
        Commands::ManPage(cmd) => {
            let app = Cli::command();
            let man = Man::new(app);
            match cmd.output {
                Some(path) => {
                    let mut file = std::fs::File::create(&path)
                        .with_context(|| format!("Failed to create man page file: {}", path.display()))?;
                    man.render(&mut file)?;
                }
                None => {
                    man.render(&mut std::io::stdout())?;
                }
            }
        }
    }

    Ok(0)
}