query-forge 0.9.0

Run SQL queries and dataset diffs on XLSX/XML/CSV/JSON/JSONL/Markdown/HTML/Feather/Parquet inputs and export results as text, CSV, JSONL, Markdown, XML, HTML, XLSX, Feather, or Parquet
Documentation
use crate::{QueryResult, QueryValue};
use crate::value::display_value;

pub fn write_text<W: std::io::Write + ?Sized>(
    result: &QueryResult,
    writer: &mut W,
) -> std::io::Result<()> {
    if result.columns.is_empty() {
        return Ok(());
    }

    let mut column_widths = result
        .columns
        .iter()
        .map(|column| column.len())
        .collect::<Vec<_>>();

    for row in &result.rows {
        for index in 0..result.columns.len() {
            let width = row.get(index).map(display_width).unwrap_or_default();
            column_widths[index] = column_widths[index].max(width);
        }
    }

    write_aligned_row(writer, &result.columns, &column_widths)?;
    writer.write_all(b"\n")?;

    for (index, width) in column_widths.iter().enumerate() {
        if index > 0 {
            writer.write_all(b"-+-")?;
        }
        writer.write_all("-".repeat(*width).as_bytes())?;
    }

    for row in &result.rows {
        writer.write_all(b"\n")?;
        write_aligned_result_row(writer, row, result.columns.len(), &column_widths)?;
    }

    Ok(())
}

fn display_width(value: &QueryValue) -> usize {
    match value {
        QueryValue::Null => 0,
        QueryValue::Integer(value) => value.to_string().len(),
        QueryValue::Real(value) => value.to_string().len(),
        QueryValue::Text(value) => value.len(),
    }
}

pub fn render_text(result: &QueryResult) -> String {
    let mut output = Vec::new();
    write_text(result, &mut output).expect("writing to memory cannot fail");
    String::from_utf8(output).expect("table output should always be valid UTF-8")
}

fn write_aligned_row<W: std::io::Write + ?Sized>(
    output: &mut W,
    values: &[String],
    column_widths: &[usize],
) -> std::io::Result<()> {
    for (index, value) in values.iter().enumerate() {
        if index > 0 {
            output.write_all(b" | ")?;
        }

        write!(output, "{value:<width$}", width = column_widths[index])?;
    }

    Ok(())
}

fn write_aligned_result_row<W: std::io::Write + ?Sized>(
    output: &mut W,
    row: &[crate::QueryValue],
    column_count: usize,
    column_widths: &[usize],
) -> std::io::Result<()> {
    for (index, width) in column_widths.iter().enumerate().take(column_count) {
        if index > 0 {
            output.write_all(b" | ")?;
        }

        let rendered_value = row.get(index).map(display_value).unwrap_or_default();
        write!(output, "{rendered_value:<width$}")?;
    }

    Ok(())
}