1use std::marker::PhantomData;
6
7use crate::db::FlozRowBound;
8
9use crate::db::Executor;
10use crate::error::FlozError;
11use crate::expr::{Expr, OrderExpr};
12use crate::query::quote_table;
13use crate::value::Value;
14
15#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
17pub struct Paginated<T> {
18 pub items: Vec<T>,
20 pub total: i64,
22 pub page: i64,
24 pub per_page: i64,
26 pub total_pages: i64,
28 pub has_next: bool,
30 pub has_prev: bool,
32}
33
34impl<T> Paginated<T> {
35 pub fn new(items: Vec<T>, total: i64, page: i64, per_page: i64) -> Self {
37 let total_pages = if per_page > 0 && total > 0 {
38 (total + per_page - 1) / per_page
39 } else {
40 0
41 };
42 Self {
43 items,
44 total,
45 page,
46 per_page,
47 total_pages,
48 has_next: page < total_pages,
49 has_prev: page > 1,
50 }
51 }
52}
53
54pub struct PaginateQuery<T> {
66 table: String,
67 filter: Option<Expr>,
68 order: Vec<OrderExpr>,
69 page: i64,
70 per_page: i64,
71 _phantom: PhantomData<T>,
72}
73
74impl<T> PaginateQuery<T> {
75 pub fn new(table: &str) -> Self {
77 Self {
78 table: table.to_string(),
79 filter: None,
80 order: Vec::new(),
81 page: 1,
82 per_page: 25,
83 _phantom: PhantomData,
84 }
85 }
86
87 pub fn page(mut self, page: i64) -> Self {
89 self.page = if page < 1 { 1 } else { page };
90 self
91 }
92
93 pub fn per_page(mut self, per_page: i64) -> Self {
95 self.per_page = if per_page < 1 { 1 } else { per_page };
96 self
97 }
98
99 pub fn where_(mut self, expr: Expr) -> Self {
101 self.filter = Some(expr);
102 self
103 }
104
105 pub fn order_by(mut self, order: OrderExpr) -> Self {
107 self.order.push(order);
108 self
109 }
110
111 pub async fn execute(self, db: &impl Executor) -> Result<Paginated<T>, FlozError>
113 where
114 T: Send + Unpin + FlozRowBound,
115 {
116 let quoted = quote_table(&self.table);
117 let offset = (self.page - 1) * self.per_page;
118
119 let mut count_sql = format!("SELECT COUNT(*) as count FROM {}", quoted);
121 let mut count_params: Vec<Value> = Vec::new();
122 let mut count_idx = 0usize;
123 if let Some(ref filter) = self.filter {
124 count_sql.push_str(" WHERE ");
125 filter.to_sql(&mut count_sql, &mut count_params, &mut count_idx);
126 }
127
128 let row: CountRow = db.fetch_one(&count_sql, count_params).await?;
129 let total = row.count;
130
131 let mut data_sql = format!("SELECT * FROM {}", quoted);
133 let mut data_params: Vec<Value> = Vec::new();
134 let mut data_idx = 0usize;
135 if let Some(ref filter) = self.filter {
136 data_sql.push_str(" WHERE ");
137 filter.to_sql(&mut data_sql, &mut data_params, &mut data_idx);
138 }
139
140 if !self.order.is_empty() {
142 data_sql.push_str(" ORDER BY ");
143 for (i, ord) in self.order.iter().enumerate() {
144 if i > 0 {
145 data_sql.push_str(", ");
146 }
147 ord.to_sql(&mut data_sql);
148 }
149 }
150
151 data_idx += 1;
153 data_sql.push_str(&format!(" LIMIT ${}", data_idx));
154 data_params.push(Value::BigInt(self.per_page));
155 data_idx += 1;
156 data_sql.push_str(&format!(" OFFSET ${}", data_idx));
157 data_params.push(Value::BigInt(offset));
158
159 let items: Vec<T> = db.fetch_all(&data_sql, data_params).await?;
160
161 Ok(Paginated::new(items, total, self.page, self.per_page))
162 }
163}
164
165#[derive(Debug, sqlx::FromRow)]
167struct CountRow {
168 count: i64,
169}
170
171#[cfg(test)]
176mod tests {
177 use super::*;
178
179 #[test]
180 fn paginated_basic() {
181 let p: Paginated<()> = Paginated::new(vec![(); 10], 50, 1, 10);
182 assert_eq!(p.total, 50);
183 assert_eq!(p.page, 1);
184 assert_eq!(p.per_page, 10);
185 assert_eq!(p.total_pages, 5);
186 assert!(p.has_next);
187 assert!(!p.has_prev);
188 }
189
190 #[test]
191 fn paginated_last_page() {
192 let p: Paginated<()> = Paginated::new(vec![(); 5], 25, 3, 10);
193 assert_eq!(p.total_pages, 3);
194 assert!(!p.has_next);
195 assert!(p.has_prev);
196 }
197
198 #[test]
199 fn paginated_middle_page() {
200 let p: Paginated<()> = Paginated::new(vec![(); 10], 30, 2, 10);
201 assert_eq!(p.total_pages, 3);
202 assert!(p.has_next);
203 assert!(p.has_prev);
204 }
205
206 #[test]
207 fn paginated_empty() {
208 let p: Paginated<()> = Paginated::new(vec![], 0, 1, 10);
209 assert_eq!(p.total_pages, 0);
210 assert!(!p.has_next);
211 assert!(!p.has_prev);
212 }
213
214 #[test]
215 fn paginated_single_page() {
216 let p: Paginated<()> = Paginated::new(vec![(); 3], 3, 1, 10);
217 assert_eq!(p.total_pages, 1);
218 assert!(!p.has_next);
219 assert!(!p.has_prev);
220 }
221
222 #[test]
223 fn paginated_remainder_page() {
224 let p: Paginated<()> = Paginated::new(vec![(); 1], 21, 3, 10);
225 assert_eq!(p.total_pages, 3);
226 assert!(!p.has_next);
227 assert!(p.has_prev);
228 }
229
230 #[test]
231 fn paginate_query_clamps_page() {
232 let q = PaginateQuery::<()>::new("test").page(0);
233 assert_eq!(q.page, 1);
234 }
235
236 #[test]
237 fn paginate_query_clamps_per_page() {
238 let q = PaginateQuery::<()>::new("test").per_page(-5);
239 assert_eq!(q.per_page, 1);
240 }
241
242 #[test]
243 fn paginate_query_defaults() {
244 let q = PaginateQuery::<()>::new("users");
245 assert_eq!(q.page, 1);
246 assert_eq!(q.per_page, 25);
247 assert!(q.filter.is_none());
248 assert!(q.order.is_empty());
249 }
250}