luncheon 0.2.11

A library to make working with SQLITE in a more typed and traited way
Documentation
use rusqlite::{Row, ToSql, params};
#[cfg(feature = "tracing")]
use tracing::debug;

use crate::{Database, Res, Table, flatten_traced};

pub trait Select<R>
where
    Self: Table,
{
    const SELECT: &str;

    fn select(database: &Database) -> Res<Vec<R>> {
        let mut stmt = database.prepare_reader(Self::SELECT)?;
        let rows: Vec<R> = stmt.query_map((), Self::func)?.flatten().collect();
        #[cfg(feature = "tracing")]
        debug!("Rows returned from select: {}", rows.len());
        Ok(rows)
    }

    /// Can cause problems if the query already has a limit
    fn select_limited(database: &Database, limit: usize) -> Res<Vec<R>> {
        let limited_query = format!("{} LIMIT {limit}", Self::SELECT);
        let mut stmt = database.prepare_reader(limited_query)?;
        let rows: Vec<R> = stmt.query_map((), Self::func)?.flatten().collect();
        debug_assert!(
            rows.len() <= limit,
            "Returned '{}' rows, but the maximum amount is '{}'",
            rows.len(),
            limit
        );
        Ok(rows)
    }

    fn select_one(database: &Database) -> Res<Option<R>> {
        let mut rows = Self::select_limited(database, 1)?;
        if rows.len() == 1 {
            Ok(Some(rows.swap_remove(0)))
        } else if rows.is_empty() {
            Ok(None)
        } else {
            Err(format!(
                "No items returned from select on '{}' table",
                Self::tbl_name()
            )
            .into())
        }
    }

    fn select_distinct(database: &Database) -> Res<Vec<R>> {
        // TODO: Can cause problems if 'SELECT' is referenced in the actual query
        let distinct_query = Self::SELECT.replace("SELECT", "SELECT DISTINCT");
        let mut stmt = database.prepare_reader(distinct_query)?;
        let rows: Vec<R> = stmt.query_map((), Self::func)?.flatten().collect();
        #[cfg(feature = "tracing")]
        debug!("Rows returned from select: {}", rows.len());
        Ok(rows)
    }

    fn func(row: &Row) -> Result<R, rusqlite::Error>;
}

/// Select rows by B
pub trait SelectBy<R, B: ToSql>
where
    Self: Table,
{
    const SELECT_BY: &str;

    fn select_by(database: &Database, by: B) -> Res<Vec<R>> {
        let mut stmt = database.prepare_reader(Self::SELECT_BY)?;
        let rows = stmt.query_map(params![by], Self::func)?.flatten().collect();
        Ok(rows)
    }

    /// Must return exactly one row
    fn select_by_one(database: &Database, by: B) -> Res<R> {
        let mut stmt = database.prepare_reader(Self::SELECT_BY)?;
        let rows = stmt.query_row(params![by], Self::func)?;
        Ok(rows)
    }

    fn func(row: &Row) -> Result<R, rusqlite::Error>;
}

/// Select into something
pub trait SelectInto<R, C: From<Vec<R>>>
where
    Self: Table,
{
    const SELECT_INTO: &str;

    fn select_into(&self, database: &Database) -> Res<C> {
        let mut stmt = database.prepare_reader(Self::SELECT_INTO)?;
        let rows_iter = stmt.query_map(params![], Self::func)?;
        let rows: Vec<R> = flatten_traced::<R, _>(rows_iter).collect();
        Ok(rows.into())
    }
    fn func(row: &Row) -> Result<R, rusqlite::Error>;
}