Skip to main content

keelson_models/
select.rs

1use std::fmt;
2use std::sync::Arc;
3
4use keelson_core::{Dialect, Expression, Mod, Query, QueryExtensions, QueryType, SqlWriter};
5use keelson_exec::{ExecError, ExecFuture, ExecHook, Execute as _, Executor, FromRow, Row};
6
7use crate::View;
8use crate::delegate::delegate_clause;
9
10/// The mapper-mod payload, pinned: after the base row struct is decoded, each
11/// mapper mod reads *more* of the same [`Row`] into the struct — the prefixed
12/// preload columns a same-query `LEFT JOIN` added.
13///
14/// This completes the wiring core's [`QueryExtensions`] left open: core fixed
15/// the mechanism with type-parameter payloads, keelson-exec pinned `Hook`
16/// ([`ExecHook`]) and the raw-row `Loader`, and the mapper-mod payload was
17/// explicitly deferred to Layer 2 "which owns the row-mapper it would modify"
18/// — this is that row-mapper, so this is where the type gets pinned.
19pub type MapperMod<T> = Arc<dyn Fn(&mut Row, &mut T) -> Result<(), ExecError> + Send + Sync>;
20
21/// The then-load payload, pinned: runs after the rows are mapped, with the
22/// caller's executor and the **decoded** models, so a second query can be
23/// keyed by the first's keys and its results attached to the `rel` fields.
24///
25/// Deliberately typed over the model rather than reusing keelson-exec's
26/// row-level `ExecLoader`: a then-loader's whole job is to mutate decoded
27/// structs (`post.rel.user = …`), which `&[Row]` cannot express. `ExecLoader`
28/// remains the payload for row-level extensions; model queries never use it.
29pub type Loader<T> = Arc<
30    dyn for<'a> Fn(&'a dyn Executor, &'a mut Vec<T>) -> ExecFuture<'a, Result<(), ExecError>>
31        + Send
32        + Sync,
33>;
34
35/// Wrap a closure as an [`ExecHook`]. The named-function-plus-`Box::pin`
36/// shape generated code uses:
37///
38/// ```text
39/// q.add_hook(hook(|db| Box::pin(async move { … })));
40/// ```
41pub fn hook<F>(f: F) -> ExecHook
42where
43    F: for<'a> Fn(&'a dyn Executor) -> ExecFuture<'a, Result<(), ExecError>>
44        + Send
45        + Sync
46        + 'static,
47{
48    Arc::new(f)
49}
50
51/// Wrap a closure as a [`MapperMod`].
52pub fn mapper_mod<T, F>(f: F) -> MapperMod<T>
53where
54    F: Fn(&mut Row, &mut T) -> Result<(), ExecError> + Send + Sync + 'static,
55{
56    Arc::new(f)
57}
58
59/// Wrap a closure as a [`Loader`].
60pub fn loader<T, F>(f: F) -> Loader<T>
61where
62    F: for<'a> Fn(&'a dyn Executor, &'a mut Vec<T>) -> ExecFuture<'a, Result<(), ExecError>>
63        + Send
64        + Sync
65        + 'static,
66{
67    Arc::new(f)
68}
69
70/// A model `SELECT`: the dialect statement plus the extension payloads the
71/// query carries — hooks, preload mapper mods, then-loaders.
72///
73/// Still a [`Query`]: `build()` hands back the same `(String, Vec<Value>)`
74/// escape hatch as everything else, and the raw `Execute` verbs keep working.
75/// The model verbs — [`all`](ModelSelect::all), [`one`](ModelSelect::one),
76/// [`optional`](ModelSelect::optional) — are the path that also runs the
77/// extensions, reading them back through [`QueryExtensions`], which this type
78/// implements with the pinned payload types.
79///
80/// Layer 1 interop: the wrapper implements every `Has*` clause trait its
81/// statement implements (see `delegate.rs`), so shared dialect mods
82/// (`select::limit(20)`, `select::where_("raw sql")`, joins, CTEs, …) apply to
83/// it directly, in the same tuple as typed filters. Statement-specific mods go
84/// through [`apply`](ModelSelect::apply).
85pub struct ModelSelect<M: View> {
86    query: M::Select,
87    hooks: Vec<ExecHook>,
88    mapper_mods: Vec<MapperMod<M::Row>>,
89    loaders: Vec<Loader<M::Row>>,
90}
91
92impl<M: View> fmt::Debug for ModelSelect<M> {
93    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
94        f.debug_struct("ModelSelect")
95            .field("query", &self.query)
96            .field("hooks", &self.hooks.len())
97            .field("mapper_mods", &self.mapper_mods.len())
98            .field("loaders", &self.loaders.len())
99            .finish()
100    }
101}
102
103impl<M: View> ModelSelect<M> {
104    pub(crate) fn new(query: M::Select) -> Self {
105        ModelSelect {
106            query,
107            hooks: Vec::new(),
108            mapper_mods: Vec::new(),
109            loaders: Vec::new(),
110        }
111    }
112
113    /// Apply mods written against the concrete dialect statement — the escape
114    /// hatch for the statement-specific ones (psql's `select::distinct()`)
115    /// that are not generic over a `Has*` trait and so cannot land on the
116    /// wrapper directly.
117    pub fn apply(&mut self, mods: impl Mod<M::Select>) {
118        mods.apply(&mut self.query);
119    }
120
121    /// The dialect statement, for inspection.
122    pub fn as_select(&self) -> &M::Select {
123        &self.query
124    }
125
126    /// Attach a pre-query hook. Runs on the caller's executor, before the
127    /// statement, in attachment order.
128    pub fn add_hook(&mut self, hook: ExecHook) {
129        self.hooks.push(hook);
130    }
131
132    /// Attach a mapper mod — generated preload mods call this.
133    pub fn add_mapper_mod(&mut self, mapper_mod: MapperMod<M::Row>) {
134        self.mapper_mods.push(mapper_mod);
135    }
136
137    /// Attach a then-loader — generated then-load mods call this.
138    pub fn add_loader(&mut self, loader: Loader<M::Row>) {
139        self.loaders.push(loader);
140    }
141
142    /// Every row, mapped, loaded, hooked.
143    ///
144    /// The order is the contract: hooks → the statement (through the same
145    /// traced verb funnel as everything else) → base [`FromRow`] plus mapper
146    /// mods, per row → then-loaders → [`View::after_select`]. All of it on
147    /// `db`, so inside `db`'s transaction when `db` is one.
148    pub async fn all(&self, db: &dyn Executor) -> Result<Vec<M::Row>, ExecError> {
149        for h in self.hooks() {
150            h(db).await?;
151        }
152        let rows = self.query.fetch_rows(db).await?;
153        let mut models = Vec::with_capacity(rows.len());
154        for mut row in rows {
155            let mut model = M::Row::from_row(&mut row)?;
156            for mm in self.mapper_mods() {
157                mm(&mut row, &mut model)?;
158            }
159            models.push(model);
160        }
161        for l in self.loaders() {
162            l(db, &mut models).await?;
163        }
164        M::after_select(db, &mut models).await?;
165        Ok(models)
166    }
167
168    /// Exactly one row — zero is [`ExecError::RowNotFound`], two is
169    /// [`ExecError::TooManyRows`], matching the execution layer's "one means
170    /// one".
171    pub async fn one(&self, db: &dyn Executor) -> Result<M::Row, ExecError> {
172        let mut models = self.all(db).await?;
173        match models.len() {
174            0 => Err(ExecError::RowNotFound),
175            1 => Ok(models.pop().expect("len checked")),
176            _ => Err(ExecError::TooManyRows),
177        }
178    }
179
180    /// At most one row; a second is still [`ExecError::TooManyRows`].
181    pub async fn optional(&self, db: &dyn Executor) -> Result<Option<M::Row>, ExecError> {
182        let mut models = self.all(db).await?;
183        match models.len() {
184            0 => Ok(None),
185            1 => Ok(models.pop()),
186            _ => Err(ExecError::TooManyRows),
187        }
188    }
189}
190
191impl<M: View> Expression for ModelSelect<M> {
192    fn write_sql(&self, w: &mut SqlWriter<'_>) {
193        self.query.write_sql(w);
194    }
195}
196
197impl<M: View> Query for ModelSelect<M> {
198    fn query_type(&self) -> QueryType {
199        self.query.query_type()
200    }
201
202    fn dialect(&self) -> &dyn Dialect {
203        self.query.dialect()
204    }
205}
206
207/// The extension points, answered with the pinned payload types — the
208/// completion of the `QueryExtensions` wiring core left with type parameters.
209impl<M: View> QueryExtensions<ExecHook, Loader<M::Row>, MapperMod<M::Row>> for ModelSelect<M> {
210    fn hooks(&self) -> &[ExecHook] {
211        &self.hooks
212    }
213
214    fn loaders(&self) -> &[Loader<M::Row>] {
215        &self.loaders
216    }
217
218    fn mapper_mods(&self) -> &[MapperMod<M::Row>] {
219        &self.mapper_mods
220    }
221}
222
223delegate_clause!(
224    ModelSelect,
225    View,
226    Select,
227    HasWith,
228    with_mut,
229    keelson_core::clause::With
230);
231delegate_clause!(
232    ModelSelect,
233    View,
234    Select,
235    HasSelectList,
236    select_list_mut,
237    keelson_core::clause::SelectList
238);
239delegate_clause!(
240    ModelSelect,
241    View,
242    Select,
243    HasTableRef,
244    table_ref_mut,
245    keelson_core::clause::TableRef
246);
247delegate_clause!(
248    ModelSelect,
249    View,
250    Select,
251    HasJoins,
252    joins_mut,
253    Vec<keelson_core::clause::Join>
254);
255delegate_clause!(
256    ModelSelect,
257    View,
258    Select,
259    HasWhere,
260    where_mut,
261    keelson_core::clause::Where
262);
263delegate_clause!(
264    ModelSelect,
265    View,
266    Select,
267    HasGroupBy,
268    group_by_mut,
269    keelson_core::clause::GroupBy
270);
271delegate_clause!(
272    ModelSelect,
273    View,
274    Select,
275    HasHaving,
276    having_mut,
277    keelson_core::clause::Having
278);
279delegate_clause!(
280    ModelSelect,
281    View,
282    Select,
283    HasWindows,
284    windows_mut,
285    keelson_core::clause::Windows
286);
287delegate_clause!(
288    ModelSelect,
289    View,
290    Select,
291    HasOrderBy,
292    order_by_mut,
293    keelson_core::clause::OrderBy
294);
295delegate_clause!(
296    ModelSelect,
297    View,
298    Select,
299    HasLimit,
300    limit_mut,
301    keelson_core::clause::Limit
302);
303delegate_clause!(
304    ModelSelect,
305    View,
306    Select,
307    HasOffset,
308    offset_mut,
309    keelson_core::clause::Offset
310);
311delegate_clause!(
312    ModelSelect,
313    View,
314    Select,
315    HasFetch,
316    fetch_mut,
317    keelson_core::clause::Fetch
318);
319delegate_clause!(
320    ModelSelect,
321    View,
322    Select,
323    HasLocks,
324    locks_mut,
325    keelson_core::clause::Locks
326);
327delegate_clause!(
328    ModelSelect,
329    View,
330    Select,
331    HasCombines,
332    combines_mut,
333    keelson_core::clause::Combines
334);