graphql_starter/
sqlx.rs

1//! Utilities to work with [sqlx]
2
3/// Similar to `sqlx::query_as!` but with pagination capabilities.
4///
5/// **Note**: this macro won't populate `total_items` in the resulting page, it must be queried afterwards if needed.
6#[macro_export]
7macro_rules! sqlx_query_paginated_as {
8    (
9        $page:ident,
10        $executor:expr,
11        [$($cols:ident $(. $order:ident())? : $ty:path),*],
12        $out_struct:path,
13        $query:expr
14    ) => (
15        $crate::sqlx_query_paginated_as!(
16            columns = [$($cols $(. $order())? : $ty),*],
17            page = $page,
18            executor = $executor,
19            record = $out_struct,
20            query = $query,
21            args = []
22        )
23    );
24
25    (
26        $page:ident,
27        $executor:expr,
28        [$($cols:ident $(. $order:ident())? : $ty:path),*],
29        $out_struct:path,
30        $query:expr,
31        $($args:tt)*
32    ) => (
33        $crate::sqlx_query_paginated_as!(
34            columns = [$($cols $(. $order())? : $ty),*],
35            page = $page,
36            executor = $executor,
37            record = $out_struct,
38            query = $query,
39            args = [$($args)*]
40        )
41    );
42
43    (
44        columns = [$($cols:ident $(. $order:ident())? : $ty:path),*],
45        page = $page:ident,
46        executor = $executor:expr,
47        record = $out_struct:path,
48        query = $query:expr,
49        args = [$($args:expr),*]
50    ) => ({
51        use $crate::{
52            error::{GenericErrorCode, MapToErr},
53            pagination::{IntoCursorVec, Page, PageQuery},
54        };
55        let limit;
56        let backward;
57        let mut rows = match $page {
58            PageQuery::Forward(page) => {
59                backward = false;
60                limit = page.first;
61                if let Some(after) = page.after {
62                    let after: ($($ty,)*) = after.as_data()?;
63                    tracing::trace!("Fetching data after: {after:#?}");
64                    $crate::sqlx_expand_paginated_query!(
65                        record = $out_struct,
66                        query = $query,
67                        args = [$($args),*],
68                        extra_row = true,
69                        columns = [$($cols $(. $order())?),*],
70                        first = (page.first as i64),
71                        after = after
72                    )
73                    .fetch_all($executor)
74                    .await
75                } else {
76                    $crate::sqlx_expand_paginated_query!(
77                        record = $out_struct,
78                        query = $query,
79                        args = [$($args),*],
80                        extra_row = true,
81                        columns = [$($cols $(. $order())?),*],
82                        first = (page.first as i64)
83                    )
84                    .fetch_all($executor)
85                    .await
86                }
87            }
88            PageQuery::Backward(page) => {
89                backward = true;
90                limit = page.last;
91                if let Some(before) = page.before {
92                    let before: ($($ty,)*) = before.as_data()?;
93                    tracing::trace!("Fetching data before: {before:#?}");
94                    $crate::sqlx_expand_paginated_query!(
95                        record = $out_struct,
96                        query = $query,
97                        args = [$($args),*],
98                        extra_row = true,
99                        columns = [$($cols $(. $order())?),*],
100                        last = (page.last as i64),
101                        before = before
102                    )
103                    .fetch_all($executor)
104                    .await
105                } else {
106                    $crate::sqlx_expand_paginated_query!(
107                        record = $out_struct,
108                        query = $query,
109                        args = [$($args),*],
110                        extra_row = true,
111                        columns = [$($cols $(. $order())?),*],
112                        last = (page.last as i64)
113                    )
114                    .fetch_all($executor)
115                    .await
116                }
117            }
118        }
119        .map_to_err_with(
120            GenericErrorCode::InternalServerError,
121            "Error fetching paginated query",
122        )?;
123
124        let mut has_previous_page = false;
125        let mut has_next_page = false;
126        if rows.len() > limit {
127            if backward {
128                has_previous_page = true;
129                rows.remove(0);
130            } else {
131                has_next_page = true;
132                rows.remove(rows.len() - 1);
133            }
134        }
135
136        Page::from_iter(
137            has_previous_page,
138            has_next_page,
139            None,
140            rows.with_cursor(|r| $crate::struct_to_tuple!(r => $($cols),*))?,
141        )
142    });
143}
144
145#[macro_export]
146/// Builds a tuple from the given struct fields
147macro_rules! struct_to_tuple {
148    ($struct:ident => $($field:ident),*) => {
149        ( $($struct . $field .clone()),* )
150    };
151}