3.8 KiB
3.8 KiB
sqlx-record Pagination Skill
Guide to pagination with Page 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:
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
Paginated results container:
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:
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
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
// 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)
// 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
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
// 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
// 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