rust_query/
dummy.rs

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
use std::marker::PhantomData;

use sea_query::Iden;

use crate::{alias::Field, ast::MySelect, value::MyTyp, IntoColumn};

pub struct Cacher<'x, 't, S> {
    pub(crate) _p: PhantomData<fn(&'t S) -> &'t S>,
    pub(crate) ast: &'x MySelect,
}

impl<S> Copy for Cacher<'_, '_, S> {}

impl<S> Clone for Cacher<'_, '_, S> {
    fn clone(&self) -> Self {
        *self
    }
}

pub struct Cached<'t, T> {
    _p: PhantomData<fn(&'t T) -> &'t T>,
    field: Field,
}

impl<'t, T> Clone for Cached<'t, T> {
    fn clone(&self) -> Self {
        *self
    }
}
impl<'t, T> Copy for Cached<'t, T> {}

impl<'t, S> Cacher<'_, 't, S> {
    pub fn cache<T>(&mut self, val: impl IntoColumn<'t, S, Typ = T>) -> Cached<'t, T> {
        let expr = val.build_expr(self.ast.builder());
        let new_field = || self.ast.scope.new_field();
        let field = *self.ast.select.get_or_init(expr, new_field);
        Cached {
            _p: PhantomData,
            field,
        }
    }
}

#[derive(Clone, Copy)]
pub struct Row<'x, 't, 'a> {
    pub(crate) _p: PhantomData<fn(&'t ()) -> &'t ()>,
    pub(crate) _p2: PhantomData<fn(&'a ()) -> &'a ()>,
    pub(crate) row: &'x rusqlite::Row<'x>,
}

impl<'t, 'a> Row<'_, 't, 'a> {
    pub fn get<T: MyTyp>(&self, val: Cached<'t, T>) -> T::Out<'a> {
        let idx = &*val.field.to_string();
        self.row.get_unwrap(idx)
    }
}

/// This trait is implemented by everything that can be retrieved from the database.
///
/// Implement it on custom structs using [crate::FromDummy].
pub trait Dummy<'t, 'a, S>: Sized {
    /// The type that results from querying this dummy.
    type Out;

    #[doc(hidden)]
    fn prepare(self, cacher: Cacher<'_, 't, S>) -> impl FnMut(Row<'_, 't, 'a>) -> Self::Out + 't;

    /// Map a dummy to another dummy using native rust.
    ///
    /// This is useful when retrieving a struct from the database that contains types not supported by the database.
    /// It is also useful in migrations to process rows using arbitrary rust.
    fn map_dummy<T>(self, f: impl FnMut(Self::Out) -> T + 't) -> impl Dummy<'t, 'a, S, Out = T> {
        DummyMap(self, f)
    }
}

struct DummyMap<A, F>(A, F);

impl<'t, 'a, S, A, F, T> Dummy<'t, 'a, S> for DummyMap<A, F>
where
    A: Dummy<'t, 'a, S>,
    F: FnMut(A::Out) -> T + 't,
{
    type Out = T;

    fn prepare(
        mut self,
        cacher: Cacher<'_, 't, S>,
    ) -> impl FnMut(Row<'_, 't, 'a>) -> Self::Out + 't {
        let mut cached = self.0.prepare(cacher);
        move |row| self.1(cached(row))
    }
}

impl<'t, 'a, S, T: IntoColumn<'t, S, Typ: MyTyp>> Dummy<'t, 'a, S> for T {
    type Out = <T::Typ as MyTyp>::Out<'a>;

    fn prepare(
        self,
        mut cacher: Cacher<'_, 't, S>,
    ) -> impl FnMut(Row<'_, 't, 'a>) -> Self::Out + 't {
        let cached = cacher.cache(self);
        move |row| row.get(cached)
    }
}

impl<'t, 'a, S, A: Dummy<'t, 'a, S>, B: Dummy<'t, 'a, S>> Dummy<'t, 'a, S> for (A, B) {
    type Out = (A::Out, B::Out);

    fn prepare(self, cacher: Cacher<'_, 't, S>) -> impl FnMut(Row<'_, 't, 'a>) -> Self::Out + 't {
        let mut prepared_a = self.0.prepare(cacher);
        let mut prepared_b = self.1.prepare(cacher);
        move |row| (prepared_a(row), prepared_b(row))
    }
}

#[cfg(test)]
#[allow(unused)]
mod tests {
    use super::*;

    struct User {
        a: i64,
        b: String,
    }

    struct UserDummy<A, B> {
        a: A,
        b: B,
    }

    impl<'t, 'a, S, A, B> Dummy<'t, 'a, S> for UserDummy<A, B>
    where
        A: IntoColumn<'t, S, Typ = i64>,
        B: IntoColumn<'t, S, Typ = String>,
    {
        type Out = User;

        fn prepare(
            self,
            mut cacher: Cacher<'_, 't, S>,
        ) -> impl FnMut(Row<'_, 't, 'a>) -> Self::Out + 't {
            let a = cacher.cache(self.a);
            let b = cacher.cache(self.b);
            move |row| User {
                a: row.get(a),
                b: row.get(b),
            }
        }
    }
}