Repository navigation
Pagination and Chunking
Two jobs, four tools. Pick by what you need.
| Need | Use |
|---|---|
| A page of results with total count and page numbers | Paginate |
| "Load more" or next/previous, no count | SimplePaginate |
| Fast deep paging, stable while rows are inserted | CursorPaginate |
| Process every row in batches | Chunk / ChunkByID |
| Process every row one at a time | Each / Stream |
var users []User
page, err := conn.Select().From("users").OrderBy("id").Paginate(ctx, 2, 15, &users)
// page.Total, page.PerPage, page.CurrentPage, page.LastPage, page.From, page.ToPaginate(ctx, page, perPage, &dest) runs a count (ignoring order, limit and offset), then fetches the page.
-
pageis 1-based. Below 1 becomes 1. -
perPagemust be at least 1. - An empty table gives
Total: 0, LastPage: 1and an empty slice.
p, err := conn.Select().From("users").OrderBy("id").SimplePaginate(ctx, 1, 15, &users)
// p.HasMore, p.CurrentPage, p.PerPageFetches one extra row to learn whether another page exists, and skips the count query. Cheaper than Paginate on large tables.
var users []User
p, err := conn.Select().From("users").CursorPaginate(ctx, 15, "", &users) // first page
p, err = conn.Select().From("users").CursorPaginate(ctx, 15, p.NextCursor, &users) // next page
// p.HasMore, p.NextCursor ("" when there is no next page)
conn.Select().From("events").CursorPaginate(ctx, 50, token, &events,
db.CursorOptions{Column: "created_at", Desc: true})- Pages by
WHERE key > last ORDER BY key LIMIT n, so it stays fast on deep pages and is stable while rows are inserted. - The key column (default
"id") must be unique and sortable. Ties would skip or repeat rows. It replaces any existingORDER BY. - The cursor is an opaque, URL-safe token. Its value is only ever bound as an argument, so a tampered token cannot change the SQL.
- The token is not encrypted or signed and reveals the last key. See Security.
- A
NULLkey cannot be a cursor. The next request is rejected withErrInvalidArgs. - On a query with
Union, keyset paging returnsErrInvalidArgs. Wrap withFromSubfirst.
var batch []User
err := conn.Select().From("users").OrderBy("id").Chunk(ctx, 500, &batch, func() (bool, error) {
for _, u := range batch {
// process u
}
return true, nil // false stops early; an error stops and is returned
})Chunk pages by offset, so it needs an ORDER BY (db.ErrNoOrder otherwise). If the callback changes which rows match the filter, offset paging can skip or repeat rows. Use ChunkByID for that.
err := conn.Select().From("jobs").Where("state", "new").ChunkByID(ctx, 100, &batch, func() (bool, error) {
// safe to update or delete the rows in batch
return true, nil
}) // keyset on "id"; pass a column name last to use another unique column- The next page starts after the last key as fetched, read before the callback runs, so the callback may sort, filter or truncate
batchwithout changing which rows come next. - It replaces any
OrderBywith the key order. - Keyset on a
Unionis rejected.
var row User
err := conn.Select().From("users").OrderBy("id").Each(ctx, &row, func() error {
// row holds the current row, reset before every scan
return nil
})
for u, err := range db.Stream[User](ctx, conn.Select().From("users").OrderBy("id")) {
if err != nil {
return err
}
if u.Name == "stop" {
break // breaking closes the cursor
}
}Both hold a single row in memory. Stream is a Go iterator (iter.Seq2[T, error]). An error ends the sequence after being yielded once.
Single-connection pools (such as an in-memory SQLite database): do not run another query from inside an
Eachcallback or aStreamloop. The open cursor holds the connection.
The Typed API returns pages with items included: db.Paged[T], db.SimplePaged[T] and db.CursorPaged[T] each carry an Items []T field.
quill wiki · v0.2.0 · Repository · pkg.go.dev · Report an issue · MIT License
Basics
Building queries
Data access
- Writing Data
- Reading Data
- Pagination and Chunking
- Transactions
- Typed API
- Struct Mapping
- Hooks and Options
Reference
Guides