Skip to content

feat: a Pager that cuts a keyset page and the cursor to the next - #7

Merged
iamralch merged 1 commit into
mainfrom
feat/pager
Sep 24, 2026
Merged

iamralch merged 1 commit into
mainfrom
feat/pager

Conversation

@iamralch

Copy link
Copy Markdown
Contributor

Whether there is a next page is only knowable by fetching one row past it, and until now every caller did that by hand: bind size + 1, truncate, and call Cursor::after_row on the last row kept — two halves of one rule in two places that had to agree.

let pager = Pager::new(Cursor::new(order_by.clone()), 50);

query.bind_value(pager.limit());          // 51: one past the page
let rows = query.build()?.fetch_all(&pool).await?;

let page = pager.next_page(rows)?;        // Page { rows: at most 50, cursor }
let next_page_token = page.cursor.map(|cursor| cursor.encode());
  • Pager::new(cursor, size) — the ordering comes from cursor (typically Cursor::new(order_by)); a position it already carries is replaced. A size of 0 is taken as 1.
  • Pager::limit() — size + 1, saturating at i64::MAX. The only place the extra row appears.
  • Pager::next_page(rows) — keeps size rows; cursor is after the last one kept if the extra came back, None on the last page.
  • Page { pub rows, pub cursor } — plain data.

Backward paging (prev_page, Cursor::before_row) is deliberately left out; it fits on top without changing any of this.

Tests: unit tests for limit, and live SQLite and PostgreSQL tests paging a table through encoded tokens — every row once, a tie broken by the primary key, and an exactly-full last page with no cursor. README's keyset section now shows the pager.

Whether there is a next page is only knowable by fetching one row past it,
and until now every caller did that by hand: bind `size + 1`, truncate,
and call `Cursor::after_row` on the last row kept -- two halves of one rule
in two places that had to agree.

`Pager::new(cursor, size)` holds both. `limit()` is what to fetch, one past
the page; `next_page(rows)` keeps the page and, if the extra row came back,
positions the cursor after the last row kept. The result is a plain
`Page { rows, cursor }`, `cursor` being `None` on the last page. The pager
holds no position, so one serves a whole traversal.
@iamralch iamralch self-assigned this Sep 24, 2026
@iamralch iamralch added the enhancement New feature or request label Sep 24, 2026
@iamralch
iamralch merged commit 129d8e6 into main Sep 24, 2026
5 checks passed
@iamralch
iamralch deleted the feat/pager branch September 24, 2026 08:45
@github-actions github-actions Bot mentioned this pull request Sep 24, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant