cronback_lib/database/
pagination.rs

1use proto::common::{PaginationIn, PaginationOut};
2use sea_orm::{
3    ColumnTrait,
4    EntityTrait,
5    ModelTrait,
6    QueryFilter,
7    QueryOrder,
8    QuerySelect,
9    Related,
10    Select,
11    SelectTwo,
12};
13
14// Should be implemented for models that provide pagination cursor.
15pub trait PaginatedEntity: EntityTrait {
16    fn cursor_column() -> Self::Column;
17}
18
19pub trait PaginatedSelect<E: EntityTrait> {
20    fn with_pagination(self, pagination: &PaginationIn) -> Self;
21}
22
23impl<E> PaginatedSelect<E> for Select<E>
24where
25    E: EntityTrait + PaginatedEntity,
26{
27    fn with_pagination(self, pagination: &PaginationIn) -> Select<E> {
28        let cursor_column = E::cursor_column();
29        let mut query = self
30            .order_by_desc(cursor_column)
31            // Trick. We want to know if there is a next page, so we ask for one
32            // more
33            .limit(Some(pagination.paginated_query_limit()));
34
35        if let Some(ref cursor) = pagination.cursor {
36            query = query.filter(cursor_column.lte(cursor));
37        }
38        query
39    }
40}
41
42impl<E, R> PaginatedSelect<E> for SelectTwo<E, R>
43where
44    E: EntityTrait + PaginatedEntity + Related<R>,
45    R: EntityTrait,
46{
47    fn with_pagination(self, pagination: &PaginationIn) -> SelectTwo<E, R> {
48        let cursor_column = E::cursor_column();
49        let mut query = self
50            .order_by_desc(cursor_column)
51            // Trick. We want to know if there is a next page, so we ask for one
52            // more
53            .limit(Some(pagination.paginated_query_limit()));
54
55        if let Some(ref cursor) = pagination.cursor {
56            query = query.filter(cursor_column.lte(cursor));
57        }
58        query
59    }
60}
61
62#[derive(Debug)]
63pub struct PaginatedResponse<T> {
64    pub pagination: PaginationOut,
65    pub data: Vec<T>,
66}
67
68impl<T> PaginatedResponse<T> {
69    pub fn from(data: Vec<T>, pagination: PaginationOut) -> Self {
70        Self { data, pagination }
71    }
72}
73
74impl<T> PaginatedResponse<T>
75where
76    T: ModelTrait,
77    T::Entity: PaginatedEntity,
78{
79    pub fn paginate(mut results: Vec<T>, pagination: &PaginationIn) -> Self {
80        // 1. Clip the bottom of results
81        //
82        // Despite only adding 1 to the limit at the time of query, we
83        // can't trust if this will remain true in the future. So we
84        // clip the result to the limit.
85        let next_cursor = {
86            // drain panics if we slice outside the result boundaries
87            let clip = std::cmp::min(results.len(), pagination.limit());
88            let mut drained = results.drain(clip..);
89            drained.next()
90        };
91        // 2. Set the has_more flag to true
92        let has_more = next_cursor.is_some();
93        let cursor_column = <T::Entity as PaginatedEntity>::cursor_column();
94
95        let pagination_out = PaginationOut {
96            next_cursor: next_cursor.map(|x| {
97                x.get(cursor_column)
98                    .expect("Cursor column is not string convertible!")
99            }),
100            has_more,
101        };
102
103        Self {
104            pagination: pagination_out,
105            data: results,
106        }
107    }
108}