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}