1use async_graphql::SimpleObject;
2use serde::{Deserialize, Serialize};
3
4use crate::{
5 aggregate::Aggregate,
6 column::col,
7 cond::{and, gt, lt},
8 cursor::Cursor,
9 driver::PushPrql,
10 either::if_then_else,
11 expr::{case, count, sum, when},
12 filter::Filtered,
13 sort::SortedBy,
14 var::{one, zero},
15};
16
17#[derive(
18 Clone,
19 Copy,
20 Debug,
21 Default,
22 Deserialize,
23 Eq,
24 Hash,
25 PartialEq,
26 PartialOrd,
27 Ord,
28 Serialize,
29 SimpleObject,
30)]
31#[graphql(rename_fields = "snake_case")]
32pub struct TotalCount {
33 pub total_count: i64,
34}
35
36#[derive(Clone, Debug, Deserialize, Eq, Hash, PartialEq, Serialize)]
37pub struct Pagination {
38 pub cursor: Cursor,
39 pub after: Option<String>,
40 pub before: Option<String>,
41 pub first: usize,
42 pub last: usize,
43}
44
45pub fn select_page_info<Query>(
46 query: Query,
47 cursor: Cursor,
48 start: String,
49 end: String,
50) -> SelectPageInfo<Query> {
51 SelectPageInfo {
52 query,
53 cursor,
54 start,
55 end,
56 }
57}
58
59pub fn select_page_items<Query>(query: Query, pagination: Pagination) -> SelectPageItems<Query> {
60 SelectPageItems { query, pagination }
61}
62
63pub struct SelectPageInfo<Query> {
64 pub query: Query,
65 pub cursor: Cursor,
66 pub start: String,
67 pub end: String,
68}
69
70impl<Query> PushPrql for SelectPageInfo<Query>
71where
72 Query: SortedBy + PushPrql,
73{
74 fn push_to_driver(&self, driver: &mut crate::driver::Driver) {
75 use super::sort::Sorting;
76
77 let sorting = self.query.sorting();
78 let order = sorting.order();
79 let start = self.cursor.decode(&self.start);
80 let end = self.cursor.decode(&self.end);
81
82 Aggregate {
83 query: &self.query,
84 aggregations: vec![
85 (col("total_count"), &count() as &dyn PushPrql),
86 (
87 col("has_prev_page"),
88 >(
89 sum(case([when(if_then_else(
90 order.is_asc(),
91 || lt(&sorting, &start),
92 || gt(&sorting, &start),
93 ))
94 .then(one())])),
95 zero(),
96 ) as &dyn PushPrql,
97 ),
98 (
99 col("has_next_page"),
100 >(
101 sum(case([when(if_then_else(
102 order.is_asc(),
103 || gt(&sorting, &end),
104 || lt(&sorting, &end),
105 ))
106 .then(one())])),
107 zero(),
108 ) as &dyn PushPrql,
109 ),
110 ],
111 }
112 .push_to_driver(driver);
113 }
114}
115
116pub struct SelectPageItems<Query> {
117 pub query: Query,
118 pub pagination: Pagination,
119}
120
121impl<Query> PushPrql for SelectPageItems<Query>
122where
123 Query: SortedBy + PushPrql,
124{
125 fn push_to_driver(&self, driver: &mut crate::driver::Driver) {
126 use super::sort::Sorting;
127
128 let sorting = self.query.sorting();
129 let order = sorting.order();
130
131 let after = self
132 .pagination
133 .after
134 .as_ref()
135 .map(|v| self.pagination.cursor.decode(v))
136 .unwrap_or(self.pagination.cursor.min());
137
138 let before = self
139 .pagination
140 .before
141 .as_ref()
142 .map(|v| self.pagination.cursor.decode(v))
143 .unwrap_or(self.pagination.cursor.max());
144
145 Filtered {
146 query: &self.query,
147 filter: and(
148 if_then_else(
149 order.is_asc(),
150 || gt(&sorting, &after),
151 || lt(&sorting, &after),
152 ),
153 if_then_else(
154 order.is_asc(),
155 || lt(&sorting, &before),
156 || gt(&sorting, &before),
157 ),
158 ),
159 }
160 .take(self.pagination.first)
161 .sort(sorting.flip())
162 .take(self.pagination.last)
163 .sort(&sorting)
164 .push_to_driver(driver);
165 }
166}
167
168#[cfg(test)]
169mod test {
170 use super::*;
171 use crate::{driver::Driver, from::from, table::table};
172
173 #[test]
174 fn test_select_page_info() {
175 let mut driver = Driver::new();
176 {
177 let query = from(table("page"))
178 .derive("cursor", col("created_at"))
179 .sort(col("cursor").asc());
180 let cursor = Cursor::String;
181 let start = "start".to_string();
182 let end = "end".to_string();
183 let select_page_info = SelectPageInfo {
184 query,
185 cursor,
186 start,
187 end,
188 };
189 select_page_info.push_to_driver(&mut driver);
190 }
191 assert_eq!(driver.sql(), "WITH table_2 AS (SELECT COUNT(*) AS total_count, created_at AS _expr_0, COALESCE(SUM(CASE WHEN created_at < $1 THEN 1 ELSE NULL END), 0) AS _expr_2 FROM page), table_0 AS (SELECT total_count, _expr_2 > 0 AS has_prev_page, _expr_0, COALESCE(SUM(CASE WHEN _expr_0 > $2 THEN 1 ELSE NULL END), 0) AS _expr_1 FROM table_2), table_1 AS (SELECT total_count, has_prev_page, _expr_1 > 0 AS has_next_page, _expr_0 FROM table_0) SELECT total_count, has_prev_page, has_next_page FROM table_1");
192 }
193
194 #[test]
195 fn test_select_page_items() {
196 let mut driver = Driver::new();
197 {
198 let query = from(table("page"))
199 .derive("cursor", col("created_at"))
200 .sort(col("cursor").desc());
201 let cursor = Cursor::String;
202 let after = Some("after".to_string());
203 let before = Some("before".to_string());
204 let select_page_info = SelectPageItems {
205 query,
206 pagination: Pagination {
207 cursor,
208 after,
209 before,
210 first: 10,
211 last: 5,
212 },
213 };
214 select_page_info.push_to_driver(&mut driver);
215 }
216 assert_eq!(driver.sql(), "WITH table_2 AS (SELECT *, created_at AS cursor FROM page), table_1 AS (SELECT * FROM table_2 WHERE cursor < $1 AND cursor > $2 ORDER BY cursor DESC LIMIT 10), table_0 AS (SELECT * FROM table_1 ORDER BY cursor LIMIT 5) SELECT * FROM table_0 ORDER BY cursor DESC");
217 }
218}