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