libsql_orm/pagination.rs
1//! Pagination support for libsql-orm
2//!
3//! This module provides both offset-based and cursor-based pagination for handling
4//! large datasets efficiently. Offset-based pagination is simpler but less efficient
5//! for large datasets, while cursor-based pagination provides better performance
6//! and consistency.
7//!
8//! # Offset-based Pagination
9//!
10//! ```rust
11//! use libsql_orm::{Pagination, PaginatedResult, Model};
12//!
13//! async fn paginate_users(db: &Database) -> Result<(), Box<dyn std::error::Error>> {
14//! let pagination = Pagination::new(1, 10); // Page 1, 10 items per page
15//! let result: PaginatedResult<User> = User::find_paginated(&pagination, db).await?;
16//!
17//! println!("Page {}/{}", result.pagination.page, result.pagination.total_pages.unwrap_or(0));
18//! println!("Total items: {}", result.pagination.total.unwrap_or(0));
19//!
20//! for user in result.data {
21//! println!("User: {}", user.name);
22//! }
23//!
24//! Ok(())
25//! }
26//! ```
27//!
28//! # Cursor-based Pagination
29//!
30//! ```rust
31//! use libsql_orm::{CursorPagination, CursorPaginatedResult};
32//!
33//! async fn cursor_paginate(db: &Database) -> Result<(), Box<dyn std::error::Error>> {
34//! let mut cursor_pagination = CursorPagination::new(10);
35//!
36//! loop {
37//! // Implement cursor-based pagination logic here
38//! // This would typically use a timestamp or ID as the cursor
39//! break;
40//! }
41//!
42//! Ok(())
43//! }
44//! ```
45
46use serde::{Deserialize, Serialize};
47
48/// Pagination parameters for queries
49///
50/// Provides offset-based pagination with helpful utility methods for calculating
51/// offsets, page numbers, and navigation state.
52///
53/// # Examples
54///
55/// ```rust
56/// use libsql_orm::Pagination;
57///
58/// let mut pagination = Pagination::new(2, 10); // Page 2, 10 items per page
59/// pagination.set_total(45); // 45 total items
60///
61/// assert_eq!(pagination.offset(), 10); // Skip first 10 items
62/// assert_eq!(pagination.limit(), 10); // Take 10 items
63/// assert_eq!(pagination.total_pages, Some(5)); // 5 total pages
64/// assert!(pagination.has_prev()); // Has previous page
65/// assert!(pagination.has_next()); // Has next page
66/// ```
67#[derive(Debug, Clone, Serialize, Deserialize)]
68pub struct Pagination {
69 /// Page number (1-based)
70 pub page: u32,
71 /// Number of items per page
72 pub per_page: u32,
73 /// Total number of items (set after query execution)
74 pub total: Option<u64>,
75 /// Total number of pages (calculated)
76 pub total_pages: Option<u32>,
77}
78
79impl Pagination {
80 /// Create a new pagination instance
81 pub fn new(page: u32, per_page: u32) -> Self {
82 Self {
83 page,
84 per_page,
85 total: None,
86 total_pages: None,
87 }
88 }
89
90 /// Get the offset for SQL LIMIT/OFFSET
91 pub fn offset(&self) -> u32 {
92 (self.page - 1) * self.per_page
93 }
94
95 /// Get the limit for SQL LIMIT/OFFSET
96 pub fn limit(&self) -> u32 {
97 self.per_page
98 }
99
100 /// Set the total count and calculate total pages
101 pub fn set_total(&mut self, total: u64) {
102 self.total = Some(total);
103 self.total_pages = Some(((total as f64) / (self.per_page as f64)).ceil() as u32);
104 }
105
106 /// Check if there's a next page
107 pub fn has_next(&self) -> bool {
108 if let (Some(total_pages), Some(current_page)) = (self.total_pages, Some(self.page)) {
109 current_page < total_pages
110 } else {
111 false
112 }
113 }
114
115 /// Check if there's a previous page
116 pub fn has_prev(&self) -> bool {
117 self.page > 1
118 }
119
120 /// Get the start item number for the current page
121 pub fn start_item(&self) -> u32 {
122 (self.page - 1) * self.per_page + 1
123 }
124
125 /// Get the end item number for the current page
126 pub fn end_item(&self) -> u32 {
127 self.page * self.per_page
128 }
129
130 /// Get the next page number
131 pub fn next_page(&self) -> Option<u32> {
132 if self.has_next() {
133 Some(self.page + 1)
134 } else {
135 None
136 }
137 }
138
139 /// Get the previous page number
140 pub fn prev_page(&self) -> Option<u32> {
141 if self.has_prev() {
142 Some(self.page - 1)
143 } else {
144 None
145 }
146 }
147}
148
149impl Default for Pagination {
150 fn default() -> Self {
151 Self::new(1, 20)
152 }
153}
154
155/// Paginated result containing data and pagination metadata
156///
157/// Contains both the data items for the current page and pagination metadata
158/// including current page, total pages, and navigation information.
159///
160/// # Examples
161///
162/// ```rust
163/// use libsql_orm::{PaginatedResult, Pagination};
164///
165/// let pagination = Pagination::new(1, 10);
166/// let data = vec!["item1".to_string(), "item2".to_string()];
167/// let result = PaginatedResult::with_total(data, pagination, 25);
168///
169/// println!("Items on page: {}", result.len());
170/// println!("Total items: {}", result.pagination.total.unwrap_or(0));
171/// ```
172#[derive(Debug, Clone, Serialize, Deserialize)]
173pub struct PaginatedResult<T> {
174 /// The data items for the current page
175 pub data: Vec<T>,
176 /// Pagination metadata
177 pub pagination: Pagination,
178}
179
180impl<T> PaginatedResult<T> {
181 /// Create a new paginated result
182 pub fn new(data: Vec<T>, pagination: Pagination) -> Self {
183 Self { data, pagination }
184 }
185
186 /// Create a paginated result with total count
187 pub fn with_total(data: Vec<T>, mut pagination: Pagination, total: u64) -> Self {
188 pagination.set_total(total);
189 Self { data, pagination }
190 }
191
192 /// Get the data items
193 pub fn data(&self) -> &[T] {
194 &self.data
195 }
196
197 /// Get the pagination metadata
198 pub fn pagination(&self) -> &Pagination {
199 &self.pagination
200 }
201
202 /// Get the number of items in the current page
203 pub fn len(&self) -> usize {
204 self.data.len()
205 }
206
207 /// Check if the current page is empty
208 pub fn is_empty(&self) -> bool {
209 self.data.is_empty()
210 }
211
212 /// Map the data items to a new type
213 pub fn map<U, F>(self, f: F) -> PaginatedResult<U>
214 where
215 F: FnMut(T) -> U,
216 {
217 PaginatedResult {
218 data: self.data.into_iter().map(f).collect(),
219 pagination: self.pagination,
220 }
221 }
222}
223
224/// Cursor-based pagination for better performance with large datasets
225///
226/// Cursor-based pagination uses a cursor (typically a timestamp or ID) to mark
227/// the position in the dataset, providing consistent results even when data is
228/// being added or removed during pagination.
229///
230/// # Advantages
231///
232/// - **Consistent results**: No duplicate or missing items when data changes
233/// - **Better performance**: No need to count total items or skip large offsets
234/// - **Real-time friendly**: Works well with live data streams
235///
236/// # Examples
237///
238/// ```rust
239/// use libsql_orm::CursorPagination;
240///
241/// // First page
242/// let pagination = CursorPagination::new(10);
243///
244/// // Subsequent pages using cursor from previous result
245/// let next_pagination = CursorPagination::with_cursor(10, Some("cursor_value".to_string()));
246/// ```
247#[derive(Debug, Clone, Serialize, Deserialize)]
248pub struct CursorPagination {
249 /// Cursor for the next page
250 pub cursor: Option<String>,
251 /// Number of items per page
252 pub limit: u32,
253 /// Whether to include the cursor item in results
254 pub include_cursor: bool,
255 /// Whether there are more items
256 pub has_next: bool,
257 /// Whether there are previous items
258 pub has_prev: bool,
259 /// Cursor for the next page
260 pub next_cursor: Option<String>,
261 /// Cursor for the previous page
262 pub prev_cursor: Option<String>,
263 /// Total number of items
264 pub total: Option<u64>,
265}
266
267impl CursorPagination {
268 /// Create a new cursor pagination instance
269 pub fn new(limit: u32) -> Self {
270 Self {
271 cursor: None,
272 limit,
273 include_cursor: false,
274 has_next: false,
275 has_prev: false,
276 next_cursor: None,
277 prev_cursor: None,
278 total: None,
279 }
280 }
281
282 /// Create with a specific cursor
283 pub fn with_cursor(limit: u32, cursor: Option<String>) -> Self {
284 let has_prev = cursor.is_some();
285 Self {
286 cursor,
287 limit,
288 include_cursor: false,
289 has_next: false,
290 has_prev,
291 next_cursor: None,
292 prev_cursor: None,
293 total: None,
294 }
295 }
296
297 /// Create with a specific cursor (deprecated, use with_cursor(limit, cursor) instead)
298 pub fn with_cursor_old(cursor: String, limit: u32) -> Self {
299 Self {
300 cursor: Some(cursor),
301 limit,
302 include_cursor: false,
303 has_next: false,
304 has_prev: true,
305 next_cursor: None,
306 prev_cursor: None,
307 total: None,
308 }
309 }
310
311 /// Set the cursor
312 pub fn set_cursor(&mut self, cursor: Option<String>) {
313 self.cursor = cursor;
314 }
315
316 /// Get the limit for SQL queries
317 pub fn limit(&self) -> u32 {
318 self.limit
319 }
320}
321
322impl Default for CursorPagination {
323 fn default() -> Self {
324 Self::new(20)
325 }
326}
327
328/// Cursor-based paginated result
329///
330/// Contains data items and cursor pagination metadata for navigating
331/// through large datasets efficiently.
332///
333/// # Examples
334///
335/// ```rust
336/// use libsql_orm::{CursorPaginatedResult, CursorPagination};
337///
338/// let pagination = CursorPagination::new(10);
339/// let data = vec!["item1".to_string(), "item2".to_string()];
340/// let result = CursorPaginatedResult::new(data, pagination);
341///
342/// println!("Items: {}", result.data().len());
343/// println!("Has next: {}", result.pagination().has_next);
344/// ```
345#[derive(Debug, Clone, Serialize, Deserialize)]
346pub struct CursorPaginatedResult<T> {
347 /// The data items
348 pub data: Vec<T>,
349 /// Pagination metadata
350 pub pagination: CursorPagination,
351}
352
353impl<T> CursorPaginatedResult<T> {
354 /// Create a new cursor paginated result
355 pub fn new(data: Vec<T>, pagination: CursorPagination) -> Self {
356 Self { data, pagination }
357 }
358
359 /// Get the data items
360 pub fn data(&self) -> &[T] {
361 &self.data
362 }
363
364 /// Get the pagination metadata
365 pub fn pagination(&self) -> &CursorPagination {
366 &self.pagination
367 }
368}