rust_query/
dummy.rs

1use std::marker::PhantomData;
2
3use sea_query::Iden;
4
5use crate::{alias::Field, ast::MySelect, value::MyTyp, IntoColumn};
6
7pub struct Cacher<'x, 't, S> {
8    pub(crate) _p: PhantomData<fn(&'t S) -> &'t S>,
9    pub(crate) ast: &'x MySelect,
10}
11
12impl<S> Copy for Cacher<'_, '_, S> {}
13
14impl<S> Clone for Cacher<'_, '_, S> {
15    fn clone(&self) -> Self {
16        *self
17    }
18}
19
20pub struct Cached<'t, T> {
21    _p: PhantomData<fn(&'t T) -> &'t T>,
22    field: Field,
23}
24
25impl<'t, T> Clone for Cached<'t, T> {
26    fn clone(&self) -> Self {
27        *self
28    }
29}
30impl<'t, T> Copy for Cached<'t, T> {}
31
32impl<'t, S> Cacher<'_, 't, S> {
33    pub fn cache<T>(&mut self, val: impl IntoColumn<'t, S, Typ = T>) -> Cached<'t, T> {
34        let expr = val.build_expr(self.ast.builder());
35        let new_field = || self.ast.scope.new_field();
36        let field = *self.ast.select.get_or_init(expr, new_field);
37        Cached {
38            _p: PhantomData,
39            field,
40        }
41    }
42}
43
44#[derive(Clone, Copy)]
45pub struct Row<'x, 't, 'a> {
46    pub(crate) _p: PhantomData<fn(&'t ()) -> &'t ()>,
47    pub(crate) _p2: PhantomData<fn(&'a ()) -> &'a ()>,
48    pub(crate) row: &'x rusqlite::Row<'x>,
49}
50
51impl<'t, 'a> Row<'_, 't, 'a> {
52    pub fn get<T: MyTyp>(&self, val: Cached<'t, T>) -> T::Out<'a> {
53        let idx = &*val.field.to_string();
54        T::from_sql(self.row.get_ref_unwrap(idx)).unwrap()
55    }
56}
57
58/// This trait is implemented by everything that can be retrieved from the database.
59///
60/// Implement it on custom structs using [crate::FromDummy].
61pub trait Dummy<'t, 'a, S>: Sized {
62    /// The type that results from querying this dummy.
63    type Out;
64
65    #[doc(hidden)]
66    fn prepare(self, cacher: Cacher<'_, 't, S>) -> impl FnMut(Row<'_, 't, 'a>) -> Self::Out + 't;
67
68    /// Map a dummy to another dummy using native rust.
69    ///
70    /// This is useful when retrieving a struct from the database that contains types not supported by the database.
71    /// It is also useful in migrations to process rows using arbitrary rust.
72    fn map_dummy<T>(self, f: impl FnMut(Self::Out) -> T + 't) -> impl Dummy<'t, 'a, S, Out = T> {
73        DummyMap(self, f)
74    }
75}
76
77struct DummyMap<A, F>(A, F);
78
79impl<'t, 'a, S, A, F, T> Dummy<'t, 'a, S> for DummyMap<A, F>
80where
81    A: Dummy<'t, 'a, S>,
82    F: FnMut(A::Out) -> T + 't,
83{
84    type Out = T;
85
86    fn prepare(
87        mut self,
88        cacher: Cacher<'_, 't, S>,
89    ) -> impl FnMut(Row<'_, 't, 'a>) -> Self::Out + 't {
90        let mut cached = self.0.prepare(cacher);
91        move |row| self.1(cached(row))
92    }
93}
94
95impl<'t, 'a, S, T: IntoColumn<'t, S, Typ: MyTyp>> Dummy<'t, 'a, S> for T {
96    type Out = <T::Typ as MyTyp>::Out<'a>;
97
98    fn prepare(
99        self,
100        mut cacher: Cacher<'_, 't, S>,
101    ) -> impl FnMut(Row<'_, 't, 'a>) -> Self::Out + 't {
102        let cached = cacher.cache(self);
103        move |row| row.get(cached)
104    }
105}
106
107impl<'t, 'a, S, A: Dummy<'t, 'a, S>, B: Dummy<'t, 'a, S>> Dummy<'t, 'a, S> for (A, B) {
108    type Out = (A::Out, B::Out);
109
110    fn prepare(self, cacher: Cacher<'_, 't, S>) -> impl FnMut(Row<'_, 't, 'a>) -> Self::Out + 't {
111        let mut prepared_a = self.0.prepare(cacher);
112        let mut prepared_b = self.1.prepare(cacher);
113        move |row| (prepared_a(row), prepared_b(row))
114    }
115}
116
117#[cfg(test)]
118#[allow(unused)]
119mod tests {
120    use super::*;
121
122    struct User {
123        a: i64,
124        b: String,
125    }
126
127    struct UserDummy<A, B> {
128        a: A,
129        b: B,
130    }
131
132    impl<'t, 'a, S, A, B> Dummy<'t, 'a, S> for UserDummy<A, B>
133    where
134        A: IntoColumn<'t, S, Typ = i64>,
135        B: IntoColumn<'t, S, Typ = String>,
136    {
137        type Out = User;
138
139        fn prepare(
140            self,
141            mut cacher: Cacher<'_, 't, S>,
142        ) -> impl FnMut(Row<'_, 't, 'a>) -> Self::Out + 't {
143            let a = cacher.cache(self.a);
144            let b = cacher.cache(self.b);
145            move |row| User {
146                a: row.get(a),
147                b: row.get(b),
148            }
149        }
150    }
151}