Skip to main content

keelson_core/
mods.rs

1use std::fmt;
2use std::sync::Arc;
3
4use crate::error::Result;
5
6/// Something that modifies a query in place.
7///
8/// Mods are the whole composition story: `psql::select((a, b, c))` is one mod made
9/// of three, applied left to right. The query type is the type parameter, so a
10/// mod for a statement that has no such clause simply does not implement
11/// `Mod<ThatQuery>` and the invalid combination fails to compile.
12///
13/// `apply` consumes `self`, so a mod can hand owned data straight to the query
14/// without cloning.
15pub trait Mod<Q> {
16    /// Apply this modification to `q`.
17    fn apply(self, q: &mut Q);
18}
19
20/// No mods at all — `psql::select(())`.
21impl<Q> Mod<Q> for () {
22    fn apply(self, _q: &mut Q) {}
23}
24
25/// `None` applies nothing, which is how a conditional mod is written:
26/// `cond.then(|| select::where_(..))`. No `if` statement, no `Vec` juggling.
27impl<Q, M: Mod<Q>> Mod<Q> for Option<M> {
28    fn apply(self, q: &mut Q) {
29        if let Some(m) = self {
30            m.apply(q);
31        }
32    }
33}
34
35impl<Q, M: Mod<Q>> Mod<Q> for Vec<M> {
36    fn apply(self, q: &mut Q) {
37        for m in self {
38            m.apply(q);
39        }
40    }
41}
42
43impl<Q, M: Mod<Q>, const N: usize> Mod<Q> for [M; N] {
44    fn apply(self, q: &mut Q) {
45        for m in self {
46            m.apply(q);
47        }
48    }
49}
50
51macro_rules! impl_mod_tuple {
52    ($($name:ident),+) => {
53        #[allow(non_snake_case)]
54        impl<Q, $($name: Mod<Q>),+> Mod<Q> for ($($name,)+) {
55            fn apply(self, q: &mut Q) {
56                let ($($name,)+) = self;
57                $($name.apply(q);)+
58            }
59        }
60    };
61}
62
63impl_mod_tuple!(A);
64impl_mod_tuple!(A, B);
65impl_mod_tuple!(A, B, C);
66impl_mod_tuple!(A, B, C, D);
67impl_mod_tuple!(A, B, C, D, E);
68impl_mod_tuple!(A, B, C, D, E, F);
69impl_mod_tuple!(A, B, C, D, E, F, G);
70impl_mod_tuple!(A, B, C, D, E, F, G, H);
71impl_mod_tuple!(A, B, C, D, E, F, G, H, I);
72impl_mod_tuple!(A, B, C, D, E, F, G, H, I, J);
73impl_mod_tuple!(A, B, C, D, E, F, G, H, I, J, K);
74impl_mod_tuple!(A, B, C, D, E, F, G, H, I, J, K, L);
75impl_mod_tuple!(A, B, C, D, E, F, G, H, I, J, K, L, M);
76impl_mod_tuple!(A, B, C, D, E, F, G, H, I, J, K, L, M, N);
77impl_mod_tuple!(A, B, C, D, E, F, G, H, I, J, K, L, M, N, O);
78impl_mod_tuple!(A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P);
79
80/// A [`Mod`] from a closure.
81///
82/// The building block every `select::*` / `insert::*` helper is written in terms
83/// of: `pub fn where_<Q: HasWhere>(e: impl Expression + 'static) -> impl Mod<Q>`
84/// returns one of these.
85pub struct ModFn<F>(F);
86
87/// Wrap a closure as a [`Mod`].
88///
89/// Intentionally unbounded here: the query type comes from the [`Mod`] impl at
90/// the use site, so an inline `|q: &mut SelectQuery|` needs no turbofish.
91pub fn mod_fn<F>(f: F) -> ModFn<F> {
92    ModFn(f)
93}
94
95impl<F> fmt::Debug for ModFn<F> {
96    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
97        f.write_str("ModFn")
98    }
99}
100
101impl<Q, F: FnOnce(&mut Q)> Mod<Q> for ModFn<F> {
102    fn apply(self, q: &mut Q) {
103        (self.0)(q);
104    }
105}
106
107/// A mod that runs when the query is built rather than when it is assembled.
108///
109/// bob calls these contextual mods. They exist for what cannot be decided at
110/// assembly time — a `WHERE` that depends on the schema in use, say — and they
111/// run on every build, so a query keeps them as `Vec<Arc<dyn BuildMod<Q>>>` and
112/// applies them to a clone of itself at the top of `write_sql`.
113///
114/// `&self` rather than `self`, because unlike a [`Mod`] they are applied more than
115/// once. Unlike rendering they can fail, and they run before there is any SQL to
116/// attach a failure to, so this one returns a `Result`; the caller records it on
117/// the writer.
118pub trait BuildMod<Q>: fmt::Debug + Send + Sync {
119    /// Apply this modification to `q`, or explain why it cannot be applied.
120    fn apply(&self, q: &mut Q) -> Result<()>;
121}
122
123impl<Q, T: BuildMod<Q> + ?Sized> BuildMod<Q> for Arc<T> {
124    fn apply(&self, q: &mut Q) -> Result<()> {
125        (**self).apply(q)
126    }
127}
128
129impl<Q, T: BuildMod<Q> + ?Sized> BuildMod<Q> for Box<T> {
130    fn apply(&self, q: &mut Q) -> Result<()> {
131        (**self).apply(q)
132    }
133}
134
135#[cfg(test)]
136mod tests {
137    use super::*;
138    use crate::error::Error;
139
140    /// Stand-in query: mods append their marker, so the applied order is visible.
141    type Q = Vec<&'static str>;
142
143    struct Push(&'static str);
144
145    impl Mod<Q> for Push {
146        fn apply(self, q: &mut Q) {
147            q.push(self.0);
148        }
149    }
150
151    fn applied<M: Mod<Q>>(m: M) -> Q {
152        let mut q = Q::new();
153        m.apply(&mut q);
154        q
155    }
156
157    #[test]
158    fn unit_applies_nothing() {
159        assert_eq!(applied(()), Vec::<&str>::new());
160    }
161
162    #[test]
163    fn tuples_apply_left_to_right() {
164        assert_eq!(applied((Push("a"),)), vec!["a"]);
165        assert_eq!(applied((Push("a"), Push("b"))), vec!["a", "b"]);
166        assert_eq!(
167            applied((Push("a"), Push("b"), Push("c"), Push("d"))),
168            vec!["a", "b", "c", "d"]
169        );
170    }
171
172    #[test]
173    fn tuples_reach_arity_sixteen() {
174        let q = applied((
175            Push("1"),
176            Push("2"),
177            Push("3"),
178            Push("4"),
179            Push("5"),
180            Push("6"),
181            Push("7"),
182            Push("8"),
183            Push("9"),
184            Push("10"),
185            Push("11"),
186            Push("12"),
187            Push("13"),
188            Push("14"),
189            Push("15"),
190            Push("16"),
191        ));
192        assert_eq!(q.len(), 16);
193        assert_eq!(q.first(), Some(&"1"));
194        assert_eq!(q.last(), Some(&"16"));
195    }
196
197    #[test]
198    fn tuples_nest_so_arity_is_never_a_ceiling() {
199        assert_eq!(
200            applied((Push("a"), (Push("b"), (Push("c"), Push("d"))), Push("e"))),
201            vec!["a", "b", "c", "d", "e"]
202        );
203    }
204
205    #[test]
206    fn tuples_mix_mod_kinds() {
207        let q = applied((
208            Push("first"),
209            None::<Push>,
210            Some(Push("maybe")),
211            vec![Push("v1"), Push("v2")],
212            [Push("a1"), Push("a2")],
213            (),
214            mod_fn(|q: &mut Q| q.push("closure")),
215        ));
216        assert_eq!(q, vec!["first", "maybe", "v1", "v2", "a1", "a2", "closure"]);
217    }
218
219    // `then` not `then_some`: the point is the idiom the design doc documents, and
220    // a real mod is a function call that must stay unevaluated.
221    #[allow(clippy::unnecessary_lazy_evaluations)]
222    #[test]
223    fn option_is_how_conditionals_are_written() {
224        let admin = false;
225        assert_eq!(applied((!admin).then(|| Push("scoped"))), vec!["scoped"]);
226        let admin = true;
227        assert_eq!(
228            applied((!admin).then(|| Push("scoped"))),
229            Vec::<&str>::new()
230        );
231    }
232
233    #[test]
234    fn nested_options_collapse() {
235        assert_eq!(applied(Some(Some(Push("deep")))), vec!["deep"]);
236        assert_eq!(applied(Some(None::<Push>)), Vec::<&str>::new());
237    }
238
239    #[test]
240    fn empty_collections_apply_nothing() {
241        assert_eq!(applied(Vec::<Push>::new()), Vec::<&str>::new());
242        assert_eq!(applied([] as [Push; 0]), Vec::<&str>::new());
243    }
244
245    /// An erased mod, as a list assembled at run time would hold them.
246    type BoxedMod = Box<dyn FnOnce(&mut Q)>;
247
248    #[test]
249    fn a_vec_of_erased_mods_is_a_mod() {
250        // The `Vec<M>` impl covers boxed mods too, which is what a runtime-built
251        // list of conditions needs.
252        let mods: Vec<BoxedMod> = vec![
253            Box::new(|q: &mut Q| q.push("one")),
254            Box::new(|q: &mut Q| q.push("two")),
255        ];
256        let mods: Vec<_> = mods.into_iter().map(mod_fn).collect();
257        assert_eq!(applied(mods), vec!["one", "two"]);
258    }
259
260    #[derive(Debug)]
261    struct AppendAtBuild(&'static str);
262
263    impl BuildMod<Q> for AppendAtBuild {
264        fn apply(&self, q: &mut Q) -> Result<()> {
265            q.push(self.0);
266            Ok(())
267        }
268    }
269
270    #[derive(Debug)]
271    struct Refuse;
272
273    impl BuildMod<Q> for Refuse {
274        fn apply(&self, _q: &mut Q) -> Result<()> {
275            Err(Error::Incomplete("a schema"))
276        }
277    }
278
279    #[test]
280    fn build_mods_run_repeatedly_and_can_fail() {
281        let mods: Vec<Arc<dyn BuildMod<Q>>> = vec![Arc::new(AppendAtBuild("once"))];
282
283        let mut first = Q::new();
284        let mut second = Q::new();
285        for m in &mods {
286            m.apply(&mut first).unwrap();
287            m.apply(&mut second).unwrap();
288        }
289        assert_eq!(first, vec!["once"]);
290        assert_eq!(
291            second,
292            vec!["once"],
293            "a build mod is not consumed by a build"
294        );
295
296        assert!(Refuse.apply(&mut first).is_err());
297    }
298}