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}