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 rendered in
20/// stays in its type, so it can only be run by an executor of that dialect,
21/// 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. A query built from
25/// one `prepare!` struct and run with another is therefore caught at
26/// `resolve` rather than at compile time. Placeholder names are qualified by
27/// the module and struct they were declared in, so that mismatch is always
28/// an `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, meaning `count_sql` with
54    /// the 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 it)
78/// when a placeholder in the template has no matching field in the `Params`
79/// passed in: a query prepared with one `prepare!` struct and run with
80/// another, or a hand-constructed `expr::placeholder` name.
81#[derive(Debug, Clone, PartialEq, Eq)]
82pub struct UnresolvedPlaceholder(pub &'static str);
83
84impl std::fmt::Display for UnresolvedPlaceholder {
85    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
86        write!(f, "no value provided for placeholder `{}`", self.0)
87    }
88}
89impl std::error::Error for UnresolvedPlaceholder {}
90
91impl<D, Params: PreparedParams, Output> Prepared<D, Params, Output> {
92    /// Substitutes every named placeholder with the matching value from
93    /// `params`, producing the same `(String, Vec<Value>)` shape `to_sql()`
94    /// returns.
95    pub fn resolve(&self, params: Params) -> Result<(String, Vec<Value>), UnresolvedPlaceholder> {
96        let named = params.into_named_values();
97        let mut resolved = Vec::with_capacity(self.template.len());
98        for v in &self.template {
99            match v {
100                Value::Placeholder(name) => {
101                    let found = named
102                        .iter()
103                        .find(|(n, _)| n == name)
104                        .map(|(_, v)| v.clone())
105                        .ok_or(UnresolvedPlaceholder(name))?;
106                    resolved.push(found);
107                }
108                other => resolved.push(other.clone()),
109            }
110        }
111        Ok((self.sql.clone(), resolved))
112    }
113}