sntl 0.1.1

Compile-time guarded ORM for PostgreSQL — your data's guardian from compile to production
Documentation
use std::marker::PhantomData;

use crate::core::expr::{Expr, OrderExpr};
use crate::core::query::SelectQuery;
use crate::core::types::Value;

/// PascalCase query builder wrapping `SelectQuery`.
///
/// Generated by `derive(Model)` for each model's `Find()`/`FindId()` methods.
/// Provides a fluent, PascalCase API matching Sentinel's public style.
///
/// `M` is the model type — used by `.Include()` to transition into
/// `IncludeQuery<M, State>` with compile-time relation tracking.
#[must_use = "query does nothing until .FetchAll() or .Build() is called"]
pub struct ModelQuery<M = ()> {
    inner: SelectQuery,
    _model: PhantomData<M>,
}

impl<M> ModelQuery<M> {
    pub fn from_table(table: &str) -> Self {
        Self {
            inner: SelectQuery::new(table),
            _model: PhantomData,
        }
    }

    pub fn from_select(select: SelectQuery) -> Self {
        Self {
            inner: select,
            _model: PhantomData,
        }
    }

    #[allow(non_snake_case)]
    pub fn Where(mut self, expr: Expr) -> Self {
        self.inner = self.inner.where_(expr);
        self
    }

    #[allow(non_snake_case)]
    pub fn OrderBy(mut self, order: OrderExpr) -> Self {
        self.inner = self.inner.order_by(order);
        self
    }

    #[allow(non_snake_case)]
    pub fn Limit(mut self, n: u64) -> Self {
        self.inner = self.inner.limit(n);
        self
    }

    #[allow(non_snake_case)]
    pub fn Offset(mut self, n: u64) -> Self {
        self.inner = self.inner.offset(n);
        self
    }

    #[allow(non_snake_case)]
    pub fn Build(&self) -> (String, Vec<Value>) {
        self.inner.build()
    }

    /// Access the inner `SelectQuery` for execution or further composition.
    pub fn into_inner(self) -> SelectQuery {
        self.inner
    }

    /// Create a cursor query for Portal-based incremental fetching.
    #[allow(non_snake_case)]
    pub fn Cursor(self) -> super::CursorQuery {
        super::CursorQuery::from_select(self.inner)
    }

    /// Create a typed query that skips Prepare for fewer round-trips.
    #[allow(non_snake_case)]
    pub fn Typed(self) -> super::TypedQuery {
        super::TypedQuery::from_select(self.inner)
    }

    /// Execute and fetch all rows.
    #[allow(non_snake_case)]
    pub async fn FetchAll(
        self,
        conn: &mut (impl driver::GenericClient + Send),
    ) -> crate::core::error::Result<Vec<driver::Row>> {
        self.inner.fetch_all(conn).await
    }

    /// Execute and fetch exactly one row. Returns `Error::NotFound` if missing.
    #[allow(non_snake_case)]
    pub async fn FetchOne(
        self,
        conn: &mut (impl driver::GenericClient + Send),
    ) -> crate::core::error::Result<driver::Row> {
        self.inner.fetch_one(conn).await
    }

    /// Execute and fetch an optional row.
    #[allow(non_snake_case)]
    pub async fn FetchOptional(
        self,
        conn: &mut (impl driver::GenericClient + Send),
    ) -> crate::core::error::Result<Option<driver::Row>> {
        self.inner.fetch_optional(conn).await
    }

    /// Execute and return a streaming row iterator.
    #[allow(non_snake_case)]
    pub async fn FetchStream(
        self,
        conn: &mut driver::Connection,
    ) -> crate::core::error::Result<driver::RowStream<'_>> {
        self.inner.fetch_stream(conn).await
    }

    /// Start including relations — transitions to IncludeQuery for type tracking.
    ///
    /// Uses `M::BareState` as the initial state, then applies the include
    /// transition for the given relation.
    #[allow(non_snake_case)]
    pub fn Include<Rel>(
        self,
        inc: crate::core::relation::RelationInclude<M, Rel>,
    ) -> crate::core::query::IncludeQuery<
        M,
        <() as crate::core::relation::IncludeTransition<M, M::BareState, Rel>>::Next,
    >
    where
        M: crate::core::relation::ModelRelations,
        (): crate::core::relation::IncludeTransition<M, M::BareState, Rel>,
    {
        crate::core::query::IncludeQuery::from_parts(self.inner, Vec::new())
            .include_rel::<Rel>(inc.into_spec())
    }
}