resolute 0.5.0

Compile-time-checked PostgreSQL queries with a pure-Rust wire protocol driver.
Documentation
//! Runtime support for compile-time checked queries.
//!
//! The `query!()` macro generates a `CheckedQuery<T>` that holds the SQL,
//! encoded params, and a mapper function. The user calls `.fetch_all()`,
//! `.fetch_one()`, `.fetch_opt()`, or `.execute()` to run it.
//!
//! All terminator methods accept `&impl Executor`, so they work with
//! `Client`, `Transaction`, and `PooledClient`.

use crate::encode::SqlParam;
use crate::error::TypedError;
use crate::executor::Executor;
use crate::row::Row;

/// A compile-time checked query ready for execution.
/// Generated by the `query!()` macro.
#[must_use = "CheckedQuery does nothing until a terminator like .fetch_all() is awaited"]
pub struct CheckedQuery<'a, T> {
    #[doc(hidden)]
    pub sql: &'a str,
    #[doc(hidden)]
    pub params: Vec<&'a dyn SqlParam>,
    #[doc(hidden)]
    pub _marker: std::marker::PhantomData<T>,
    #[doc(hidden)]
    pub mapper: fn(&Row) -> Result<T, TypedError>,
}

impl<'a, T> std::fmt::Debug for CheckedQuery<'a, T> {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        f.debug_struct("CheckedQuery")
            .field("sql", &self.sql)
            .field("param_count", &self.params.len())
            .finish()
    }
}

impl<'a, T> CheckedQuery<'a, T> {
    /// Execute and return all rows.
    ///
    /// # Errors
    ///
    /// Returns whatever the executor's `query` returns (wire error, broken
    /// connection, param encoding failure). Additionally, if any row's
    /// mapper fails to decode a column, that error is propagated.
    pub async fn fetch_all(self, db: &impl Executor) -> Result<Vec<T>, TypedError> {
        let rows = db.query(self.sql, &self.params).await?;
        rows.iter().map(self.mapper).collect()
    }

    /// Execute and return exactly one row.
    ///
    /// # Errors
    ///
    /// Same as [`fetch_all`](Self::fetch_all), plus `TypedError::NotExactlyOne`
    /// when the result set has zero or more than one row.
    pub async fn fetch_one(self, db: &impl Executor) -> Result<T, TypedError> {
        let row = db.query_one(self.sql, &self.params).await?;
        (self.mapper)(&row)
    }

    /// Execute and return an optional row.
    ///
    /// # Errors
    ///
    /// Same as [`fetch_all`](Self::fetch_all), plus `TypedError::NotExactlyOne`
    /// when the result set has more than one row. Zero rows returns `Ok(None)`.
    pub async fn fetch_opt(self, db: &impl Executor) -> Result<Option<T>, TypedError> {
        let row = db.query_opt(self.sql, &self.params).await?;
        match row {
            Some(r) => Ok(Some((self.mapper)(&r)?)),
            None => Ok(None),
        }
    }

    /// Execute a DML statement (INSERT/UPDATE/DELETE without RETURNING) and
    /// return the affected row count. The mapper is ignored.
    ///
    /// # Errors
    ///
    /// Same as the executor's `execute`: wire error, broken connection, or
    /// param encoding failure.
    pub async fn execute(self, db: &impl Executor) -> Result<u64, TypedError> {
        db.execute(self.sql, &self.params).await
    }
}

/// An unchecked query (no compile-time validation).
/// Generated by `query_unchecked!()`.
#[must_use = "UncheckedQuery does nothing until a terminator like .fetch_all() is awaited"]
pub struct UncheckedQuery<'a> {
    #[doc(hidden)]
    pub sql: &'a str,
    #[doc(hidden)]
    pub params: Vec<&'a dyn SqlParam>,
}

impl<'a> std::fmt::Debug for UncheckedQuery<'a> {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        f.debug_struct("UncheckedQuery")
            .field("sql", &self.sql)
            .field("param_count", &self.params.len())
            .finish()
    }
}

impl<'a> UncheckedQuery<'a> {
    /// Execute and return raw rows.
    ///
    /// # Errors
    ///
    /// Returns whatever the executor's `query` returns: wire error, broken
    /// connection, or param encoding failure.
    pub async fn fetch_all(self, db: &impl Executor) -> Result<Vec<Row>, TypedError> {
        db.query(self.sql, &self.params).await
    }

    /// Execute and return exactly one row.
    ///
    /// # Errors
    ///
    /// Same as [`fetch_all`](Self::fetch_all), plus `TypedError::NotExactlyOne`
    /// for zero or multi-row result sets.
    pub async fn fetch_one(self, db: &impl Executor) -> Result<Row, TypedError> {
        db.query_one(self.sql, &self.params).await
    }

    /// Execute and return an optional row.
    ///
    /// # Errors
    ///
    /// Same as [`fetch_all`](Self::fetch_all), plus `TypedError::NotExactlyOne`
    /// when the result set has more than one row. Zero rows returns `Ok(None)`.
    pub async fn fetch_opt(self, db: &impl Executor) -> Result<Option<Row>, TypedError> {
        db.query_opt(self.sql, &self.params).await
    }

    /// Execute a statement (INSERT/UPDATE/DELETE), return affected row count.
    ///
    /// # Errors
    ///
    /// Same as the executor's `execute`.
    pub async fn execute(self, db: &impl Executor) -> Result<u64, TypedError> {
        db.execute(self.sql, &self.params).await
    }
}