sqlite-extras 0.1.1

Sqlite database insights
Documentation
mod queries;

#[macro_use]
extern crate prettytable;
use std::{env, fmt};

use prettytable::{Cell, Row as TableRow, Table};
pub use queries::{
    compile_options::CompileOptions, index_size::IndexSize, integrity_check::IntegrityCheck,
    pragma::Pragma, sequence_number::SequenceNumber, shared::Query, table_size::TableSize,
    total_size::TotalSize,
};
use sqlx::SqlitePool;

pub fn render_table<T: Query>(items: Vec<T>) {
    let mut table = Table::new();
    table.add_row(T::headers());

    let columns_count = T::headers().len();

    for item in items {
        table.add_row(item.to_row());
    }
    table.set_titles(TableRow::new(vec![
        Cell::new(T::description().as_str()).style_spec(format!("H{}", columns_count).as_str())
    ]));
    table.printstd();
}

pub async fn table_size() -> Result<Vec<TableSize>, SQExtrasError> {
    get_rows().await
}

pub async fn index_size() -> Result<Vec<IndexSize>, SQExtrasError> {
    get_rows().await
}

pub async fn integrity_check() -> Result<Vec<IntegrityCheck>, SQExtrasError> {
    get_rows().await
}

pub async fn pragma() -> Result<Vec<Pragma>, SQExtrasError> {
    get_rows().await
}

pub async fn total_size() -> Result<Vec<TotalSize>, SQExtrasError> {
    get_rows().await
}

pub async fn compile_options() -> Result<Vec<CompileOptions>, SQExtrasError> {
    get_rows().await
}

pub async fn sequence_number() -> Result<Vec<SequenceNumber>, SQExtrasError> {
    get_rows().await
}

#[derive(Debug, Clone)]
#[non_exhaustive]
pub enum SQExtrasError {
    MissingConfigVars(),
    DbConnectionError(String),
    Unknown(String),
}

impl fmt::Display for SQExtrasError {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        let msg = match self {
            Self::MissingConfigVars() => {
                "Both $DATABASE_URL and $SQLITE_EXTRAS_DATABASE_URL are not set."
            }
            Self::DbConnectionError(e) => &format!("Cannot connect to database: '{}'", e),
            Self::Unknown(e) => &format!("Unknown sqlite-extras error: '{}'", e),
        };

        write!(f, "{}", msg)
    }
}

impl std::error::Error for SQExtrasError {}

fn db_url() -> Result<String, SQExtrasError> {
    env::var("SQLITE_EXTRAS_DATABASE_URL")
        .or_else(|_| env::var("DATABASE_URL"))
        .map_err(|_| SQExtrasError::MissingConfigVars())
}

async fn get_rows<T: Query>() -> Result<Vec<T>, SQExtrasError> {
    let conn = match SqlitePool::connect(db_url()?.as_str()).await {
        Ok(conn) => conn,
        Err(e) => return Err(SQExtrasError::DbConnectionError(format!("{}", e))),
    };

    let query = T::read_file();

    Ok(match sqlx::query(&query).fetch_all(&conn).await {
        Ok(rows) => rows.iter().map(T::new).collect(),
        Err(e) => return Err(SQExtrasError::Unknown(format!("{}", e))),
    })
}

#[cfg(test)]
mod tests {
    use super::*;

    #[tokio::test]
    async fn it_works() -> Result<(), Box<dyn std::error::Error>> {
        std::env::set_var(
            "SQLITE_EXTRAS_DATABASE_URL",
            format!("sqlite://{}/test.db", env::current_dir()?.to_str().unwrap()),
        );
        render_table(table_size().await?);
        render_table(index_size().await?);
        render_table(integrity_check().await?);
        render_table(pragma().await?);
        render_table(total_size().await?);
        render_table(compile_options().await?);
        render_table(sequence_number().await?);
        Ok(())
    }
}