Skip to main content

keelson_models/
model.rs

1use keelson_core::Query;
2use keelson_exec::{ExecError, ExecFuture, Executor, FromRow};
3
4/// The no-op every hook defaults to.
5fn done<'a>() -> ExecFuture<'a, Result<(), ExecError>> {
6    Box::pin(std::future::ready(Ok(())))
7}
8
9/// A readable model: enough to `SELECT` and map rows. No primary key
10/// required — a database view, a reporting projection, a read-only slice of a
11/// table are all `View`s. [`Table`] adds the mutations.
12///
13/// Implemented by the generated model *marker* type (`users::Users`), not by
14/// the row struct: the marker carries the associated types and the hooks, the
15/// row struct stays plain data.
16///
17/// # Hooks
18///
19/// [`after_select`](View::after_select) is a trait default method — **static
20/// dispatch, no downcasting**, the deliberate departure from bob's runtime
21/// type-assertion opt-in: the generator emits nothing for a model without
22/// hooks, an application overrides the method on its model, and the call
23/// resolves at compile time. The hook receives `&dyn Executor` — exactly the
24/// executor the caller passed in — so it runs *inside the caller's
25/// transaction* when there is one, and cannot end a transaction it did not
26/// open (the execution layer was shaped for precisely this; see
27/// `docs/execution.md` §Q2).
28///
29/// There is deliberately no `before_select`: everything a before-select hook
30/// could do to the query, a query mod already does at the same call site, and
31/// ad-hoc pre-query work rides the [`QueryExtensions`] hook channel
32/// ([`ModelSelect::add_hook`](crate::ModelSelect::add_hook)).
33///
34/// [`QueryExtensions`]: keelson_core::QueryExtensions
35pub trait View: Sized + Send + Sync + 'static {
36    /// The row struct rows decode into, `rel` field included.
37    type Row: FromRow + Send + Sync + 'static;
38
39    /// The dialect's `SELECT` statement type. Generated models are tied to
40    /// one dialect here — which is what makes a dialect/backend mismatch a
41    /// compile-time impossibility rather than a runtime check.
42    type Select: Query + Send + Sync + 'static;
43
44    /// The seeded `SELECT`: this model's columns, `FROM` this model's table.
45    /// Everything else — filters, mods, preloads — is applied on top by
46    /// [`ModelTable::query`](crate::ModelTable::query).
47    fn base_select() -> Self::Select;
48
49    /// Runs after rows are mapped and loaders have finished, on the caller's
50    /// executor. `&mut` so a hook may massage the result set.
51    fn after_select<'a>(
52        db: &'a dyn Executor,
53        rows: &'a mut Vec<Self::Row>,
54    ) -> ExecFuture<'a, Result<(), ExecError>> {
55        let _ = (db, rows);
56        done()
57    }
58}
59
60/// A writable model: a [`View`] with a primary key and the three mutations.
61///
62/// The `View`/`Table` split is the surface contract: `SELECT`-only models
63/// implement `View` alone, and `insert`/`update`/`delete` simply do not exist
64/// on them — misuse-resistance by trait bound, not by runtime error.
65///
66/// # What the generator emits per method
67///
68/// The `*_query` methods are the codegen seam: each returns (or completes) a
69/// plain Layer 1 statement of this model's dialect, so everything the machinery
70/// runs is an ordinary [`Query`] that raw mods can keep modifying. The
71/// hand-written model in `keelson-models/tests/` is the byte-for-byte
72/// specification of what the generator will write.
73///
74/// # Hooks
75///
76/// Same design as [`View::after_select`]: trait default methods, statically
77/// dispatched, `&dyn Executor` in. The before-mutation hooks additionally
78/// receive the `Setter` **mutably** — stamping a timestamp or normalising a
79/// value before it is written is the canonical before-hook, and giving the
80/// hook the same three-state `Setter` the caller used means it can also tell
81/// "not mentioned" from "set to NULL".
82pub trait Table: View {
83    /// The primary key's Rust type. A composite key is a tuple.
84    type Pk: Send + 'static;
85
86    /// The generated three-state setter struct.
87    type Setter: Default + Send + 'static;
88
89    /// The dialect's `INSERT` statement type.
90    type Insert: Query + Send + Sync + 'static;
91
92    /// The dialect's `UPDATE` statement type.
93    type Update: Query + Send + Sync + 'static;
94
95    /// The dialect's `DELETE` statement type.
96    type Delete: Query + Send + Sync + 'static;
97
98    /// An `INSERT` of exactly the set fields, `RETURNING` this model's
99    /// columns (on dialects that have `RETURNING`; see the per-dialect notes
100    /// in the crate docs). An all-unset setter inserts the row the schema's
101    /// defaults describe.
102    fn insert_query(setter: Self::Setter) -> Self::Insert;
103
104    /// The bare `UPDATE` of this model's table, with no assignments yet:
105    /// filters and mods apply to this, and the assignments arrive at run time
106    /// via [`apply_setter`](Table::apply_setter) — *after*
107    /// [`before_update`](Table::before_update) has had its chance to touch
108    /// the setter.
109    fn update_query() -> Self::Update;
110
111    /// Turn the set fields into `SET` assignments on `q`. Unset fields do not
112    /// appear.
113    fn apply_setter(setter: Self::Setter, q: &mut Self::Update);
114
115    /// The bare `DELETE FROM` this model's table.
116    fn delete_query() -> Self::Delete;
117
118    /// This row's primary key — what a keyed loader groups by.
119    fn pk(row: &Self::Row) -> Self::Pk;
120
121    /// Runs before the `INSERT` is built; may rewrite the setter.
122    fn before_insert<'a>(
123        db: &'a dyn Executor,
124        setter: &'a mut Self::Setter,
125    ) -> ExecFuture<'a, Result<(), ExecError>> {
126        let _ = (db, setter);
127        done()
128    }
129
130    /// Runs after the `INSERT`, with the returned rows (empty when the insert
131    /// ran for its side effect only), on the caller's executor — inside the
132    /// caller's transaction when there is one.
133    fn after_insert<'a>(
134        db: &'a dyn Executor,
135        rows: &'a [Self::Row],
136    ) -> ExecFuture<'a, Result<(), ExecError>> {
137        let _ = (db, rows);
138        done()
139    }
140
141    /// Runs before the assignments are built; may rewrite the setter.
142    fn before_update<'a>(
143        db: &'a dyn Executor,
144        setter: &'a mut Self::Setter,
145    ) -> ExecFuture<'a, Result<(), ExecError>> {
146        let _ = (db, setter);
147        done()
148    }
149
150    /// Runs after the `UPDATE`, with how many rows it touched (or returned).
151    fn after_update<'a>(
152        db: &'a dyn Executor,
153        affected: u64,
154    ) -> ExecFuture<'a, Result<(), ExecError>> {
155        let _ = (db, affected);
156        done()
157    }
158
159    /// Runs before the `DELETE`.
160    fn before_delete(db: &dyn Executor) -> ExecFuture<'_, Result<(), ExecError>> {
161        let _ = db;
162        done()
163    }
164
165    /// Runs after the `DELETE`, with how many rows it removed (or returned).
166    fn after_delete<'a>(
167        db: &'a dyn Executor,
168        affected: u64,
169    ) -> ExecFuture<'a, Result<(), ExecError>> {
170        let _ = (db, affected);
171        done()
172    }
173}