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}