sqlx-record/.claude/skills/sqlx-pagination.md

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