165 lines
3.8 KiB
Markdown
165 lines
3.8 KiB
Markdown
# sqlx-record Pagination Skill
|
|
|
|
Guide to pagination with Page<T> and PageRequest.
|
|
|
|
## Triggers
|
|
- "pagination", "paginate"
|
|
- "page request", "page size"
|
|
- "total pages", "has next"
|
|
|
|
## Overview
|
|
|
|
`sqlx-record` provides built-in pagination support with the `Page<T>` container and `PageRequest` options.
|
|
|
|
## PageRequest
|
|
|
|
Create pagination options with 1-indexed page numbers:
|
|
|
|
```rust
|
|
use sqlx_record::prelude::PageRequest;
|
|
|
|
// Create request for page 1 with 20 items per page
|
|
let request = PageRequest::new(1, 20);
|
|
|
|
// First page shorthand
|
|
let request = PageRequest::first(20);
|
|
|
|
// Access offset/limit for manual queries
|
|
request.offset() // 0 for page 1, 20 for page 2, etc.
|
|
request.limit() // page_size
|
|
```
|
|
|
|
## Page<T>
|
|
|
|
Paginated results container:
|
|
|
|
```rust
|
|
use sqlx_record::prelude::Page;
|
|
|
|
// Properties
|
|
page.items // Vec<T> - items for this page
|
|
page.total_count // u64 - total records matching filters
|
|
page.page // u32 - current page (1-indexed)
|
|
page.page_size // u32 - items per page
|
|
|
|
// Computed methods
|
|
page.total_pages() // u32 - ceil(total_count / page_size)
|
|
page.has_next() // bool - page < total_pages
|
|
page.has_prev() // bool - page > 1
|
|
page.is_empty() // bool - items.is_empty()
|
|
page.len() // usize - items.len()
|
|
|
|
// Transformation
|
|
page.map(|item| transform(item)) // Page<U>
|
|
page.into_items() // Vec<T>
|
|
page.iter() // impl Iterator<Item = &T>
|
|
```
|
|
|
|
## Entity Paginate Method
|
|
|
|
Generated on all entities:
|
|
|
|
```rust
|
|
pub async fn paginate(
|
|
executor,
|
|
filters: Vec<Filter>,
|
|
index: Option<&str>, // MySQL index hint
|
|
order_by: Vec<(&str, bool)>, // (field, is_ascending)
|
|
page_request: PageRequest
|
|
) -> Result<Page<Self>, Error>
|
|
```
|
|
|
|
## Usage Examples
|
|
|
|
### Basic Pagination
|
|
|
|
```rust
|
|
use sqlx_record::prelude::*;
|
|
|
|
// Get first page of 20 users
|
|
let page = User::paginate(
|
|
&pool,
|
|
filters![],
|
|
None,
|
|
vec![("created_at", false)], // ORDER BY created_at DESC
|
|
PageRequest::new(1, 20)
|
|
).await?;
|
|
|
|
println!("Page {} of {}", page.page, page.total_pages());
|
|
println!("Showing {} of {} users", page.len(), page.total_count);
|
|
|
|
for user in page.iter() {
|
|
println!("{}: {}", user.id, user.name);
|
|
}
|
|
```
|
|
|
|
### With Filters
|
|
|
|
```rust
|
|
// Active users only, page 3
|
|
let page = User::paginate(
|
|
&pool,
|
|
filters![("is_active", true)],
|
|
None,
|
|
vec![("name", true)], // ORDER BY name ASC
|
|
PageRequest::new(3, 10)
|
|
).await?;
|
|
```
|
|
|
|
### With Index Hint (MySQL)
|
|
|
|
```rust
|
|
// Use specific index for performance
|
|
let page = User::paginate(
|
|
&pool,
|
|
filters![("status", "active")],
|
|
Some("idx_users_status"), // MySQL: USE INDEX(idx_users_status)
|
|
vec![("created_at", false)],
|
|
PageRequest::new(1, 50)
|
|
).await?;
|
|
```
|
|
|
|
### Navigation Logic
|
|
|
|
```rust
|
|
let page = User::paginate(&pool, filters![], None, vec![], PageRequest::new(current, 20)).await?;
|
|
|
|
if page.has_prev() {
|
|
println!("Previous: page {}", page.page - 1);
|
|
}
|
|
if page.has_next() {
|
|
println!("Next: page {}", page.page + 1);
|
|
}
|
|
```
|
|
|
|
### Transform Results
|
|
|
|
```rust
|
|
// Convert to DTOs
|
|
let dto_page: Page<UserDto> = page.map(|user| UserDto::from(user));
|
|
|
|
// Or consume items
|
|
let items: Vec<User> = page.into_items();
|
|
```
|
|
|
|
## Comparison with Manual Pagination
|
|
|
|
```rust
|
|
// Manual approach (still available)
|
|
let offset = (page_num - 1) * page_size;
|
|
let items = User::find_ordered_with_limit(
|
|
&pool, filters, None, order_by, Some((offset, page_size))
|
|
).await?;
|
|
let total = User::count(&pool, filters.clone(), None).await?;
|
|
|
|
// With paginate() - simpler
|
|
let page = User::paginate(&pool, filters, None, order_by, PageRequest::new(page_num, page_size)).await?;
|
|
```
|
|
|
|
## Notes
|
|
|
|
- Page numbers are 1-indexed (page 1 is first page)
|
|
- `paginate()` executes two queries: count + select
|
|
- For very large tables, consider cursor-based pagination instead
|
|
- Index hints only work on MySQL, ignored on Postgres/SQLite
|