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

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