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}