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}