Skip to main content

qbrs_core/select/
prepared.rs

1//! Reusable named-placeholder prepared statements (`prepare!{}`).
2
3use std::marker::PhantomData;
4
5use super::{Select, Selection};
6use crate::dialect::Dialect;
7use crate::expr::Value;
8
9/// Implemented by the `prepare!{}`-generated `Params` struct. Consuming
10/// `self` lets each field move straight into its `Value`; a `Prepared` query
11/// is reused across calls, but each call brings its own `Params`.
12pub trait PreparedParams {
13    fn into_named_values(self) -> Vec<(&'static str, Value)>;
14}
15
16/// A query rendered once, with its `Value::Placeholder(name)` slots left
17/// unresolved, reusable across many `.load(executor, params)` calls. `load`
18/// takes the exact `Params` struct `prepare!{}` generated for this query, so
19/// a missing or mistyped value is caught. The dialect it was
20/// rendered in stays in its type, so it can only be run by an executor of
21/// that dialect — the same rule `Select` and `DynSelect` follow.
22///
23/// `Params` is a free parameter, though — nothing ties the placeholder names
24/// baked into the template to the struct that fills them — so a query built
25/// from one `prepare!` struct and run with another is caught at `resolve`
26/// rather than at compile time. Placeholder names are qualified by the
27/// module and struct they were declared in, so that mismatch is always an
28/// `UnresolvedPlaceholder` and never a value bound to the wrong slot.
29pub struct Prepared<D, Params, Output> {
30    sql: String,
31    template: Vec<Value>,
32    _marker: PhantomData<fn() -> (D, Params, Output)>,
33}
34
35impl<D, Scope, Sel> Select<D, Scope, Sel> {
36    /// Renders this query once, leaving its `Value::Placeholder` slots
37    /// unresolved. `Output` is captured here, as `.erase()` does for
38    /// `DynSelect`, so the execution layer can decode rows without `Sel`
39    /// (and therefore `Scope`) still being around.
40    pub fn prepare<Params, Idx>(&self, _dialect: D) -> Prepared<D, Params, Sel::Output>
41    where
42        D: Dialect,
43        Sel: Selection<Scope, Idx>,
44    {
45        let (sql, template) = self.render_as::<Idx>();
46        Prepared {
47            sql,
48            template,
49            _marker: PhantomData,
50        }
51    }
52
53    /// The same query prepared as its own total — `count_sql` with the
54    /// placeholders still unresolved, so a paginated endpoint reuses one
55    /// rendering for the page and one for the count. `Total` rather than
56    /// `i64`: what a statement produces is what decides how it is run, and a
57    /// total is a number, not a row.
58    pub fn prepare_count<Params, Idx>(&self, _dialect: D) -> Prepared<D, Params, Total>
59    where
60        D: Dialect,
61        Sel: Selection<Scope, Idx>,
62    {
63        let (sql, template) = self.count_sql::<Idx>(D::default());
64        Prepared {
65            sql,
66            template,
67            _marker: PhantomData,
68        }
69    }
70}
71
72/// The output of a `prepare_count()`-built query: deliberately not a
73/// decodable row, so a total is counted and never loaded, and a prepared
74/// `SELECT` of one `i64` column is never mistaken for one.
75pub struct Total;
76
77/// Returned by `Prepared::resolve` — and so by the `.load()` that calls
78/// it — when a placeholder in the
79/// template has no matching field in the `Params` passed in: a query
80/// prepared with one `prepare!` struct and run with another, or a
81/// hand-constructed `expr::placeholder` name.
82#[derive(Debug, Clone, PartialEq, Eq)]
83pub struct UnresolvedPlaceholder(pub &'static str);
84
85impl std::fmt::Display for UnresolvedPlaceholder {
86    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
87        write!(f, "no value provided for placeholder `{}`", self.0)
88    }
89}
90impl std::error::Error for UnresolvedPlaceholder {}
91
92impl<D, Params: PreparedParams, Output> Prepared<D, Params, Output> {
93    /// Substitutes every named placeholder with the matching value from
94    /// `params`, producing the same `(String, Vec<Value>)` shape `to_sql()`
95    /// returns.
96    pub fn resolve(&self, params: Params) -> Result<(String, Vec<Value>), UnresolvedPlaceholder> {
97        let named = params.into_named_values();
98        let mut resolved = Vec::with_capacity(self.template.len());
99        for v in &self.template {
100            match v {
101                Value::Placeholder(name) => {
102                    let found = named
103                        .iter()
104                        .find(|(n, _)| n == name)
105                        .map(|(_, v)| v.clone())
106                        .ok_or(UnresolvedPlaceholder(name))?;
107                    resolved.push(found);
108                }
109                other => resolved.push(other.clone()),
110            }
111        }
112        Ok((self.sql.clone(), resolved))
113    }
114}