//! Small helpers the repositories share. //! //! These exist so a repository method reads as one line each, and so the //! [`DbError`] mapping lives in one place instead of on every `?`. use rusqlite::{Connection, Params, Row}; use crate::error::DbError; /// Fetch every matching row, mapped by `f`. pub fn query_all(conn: &Connection, sql: &str, params: P, f: F) -> Result, DbError> where P: Params, F: FnMut(&Row<'_>) -> rusqlite::Result, { let mut stmt = conn.prepare(sql)?; let rows = stmt.query_map(params, f)?; Ok(rows.collect::>>()?) } /// Fetch at most one row, mapped by `f`. `None` when nothing matched. pub fn query_opt( conn: &Connection, sql: &str, params: P, f: F, ) -> Result, DbError> where P: Params, F: FnOnce(&Row<'_>) -> rusqlite::Result, { use rusqlite::OptionalExtension as _; Ok(conn.query_row(sql, params, f).optional()?) } /// Fetch exactly one row, mapped by `f`. /// /// For queries that always produce a row -- aggregates, and the `RETURNING *` /// on an insert -- where no rows means something is wrong rather than absent. pub fn query_one(conn: &Connection, sql: &str, params: P, f: F) -> Result where P: Params, F: FnOnce(&Row<'_>) -> rusqlite::Result, { Ok(conn.query_row(sql, params, f)?) } /// Run a statement, returning the number of rows it changed. /// /// The return value is the number of rows affected. pub fn execute(conn: &Connection, sql: &str, params: P) -> Result { Ok(conn.execute(sql, params)?) } /// Build a comma-separated list of `n` numbered bind placeholders /// (`?1,?2,...,?n`) for an `IN (...)` clause. /// /// Callers guard the empty case before building the query (an empty `IN ()` is /// invalid SQL); this returns `""` for `n == 0` so the guard stays their /// responsibility. #[must_use] pub fn bind_placeholders(n: usize) -> String { (1..=n) .map(|i| format!("?{i}")) .collect::>() .join(", ") } #[cfg(test)] mod tests { use super::*; #[test] fn placeholders_are_numbered_from_one() { assert_eq!(bind_placeholders(0), ""); assert_eq!(bind_placeholders(1), "?1"); assert_eq!(bind_placeholders(3), "?1, ?2, ?3"); } }