Documentation
¶
Overview ¶
Package page provides transport-neutral keyset pagination for Bob SELECT queries. Callers own authorization, non-null stored sort values, and correspondence between Sort columns and the typed row key. The final column must itself be unique in the selected relation. Mixed directions are supported; null sort values are not.
Sort IDs version the expressions, directions, key types and comparison semantics. Cursors expose values and allow edits; they are seek boundaries, not credentials. Concurrent writes or changed filters can skip or repeat rows between requests. Code generation may eventually align Sort and key; handwritten pairs have no compile-time field/type correspondence guarantee.
Keys support exactly int, int8, int16, int32, int64, string, time.Time and the standard uuid.UUID. Named types, unsigned values, pointers and null wrappers require explicit caller conversion. Timestamps normalize to UTC with nanosecond precision. Versioned JSON cursors use unpadded base64url, limited to 16,384 bytes after encoding; unsupported values and malformed cursors return typed errors.
Example ¶
package main
import (
"context"
"errors"
"fmt"
"github.com/stephenafamo/bob"
"github.com/stephenafamo/bob/dialect/sqlite"
sd "github.com/stephenafamo/bob/dialect/sqlite/dialect"
"github.com/stephenafamo/bob/dialect/sqlite/sm"
"github.com/stephenafamo/bob/expr"
"github.com/stephenafamo/scan"
"go.auvem.com/corestack/page"
)
func main() {
ctx := context.Background()
db, err := bob.Open("sqlite3", ":memory:")
if err != nil {
panic(err)
}
defer db.Close()
db.SetMaxOpenConns(1)
if _, err := db.ExecContext(ctx, "CREATE TABLE orders (id BIGINT PRIMARY KEY, priority BIGINT NOT NULL, tenant BIGINT NOT NULL)"); err != nil {
panic(err)
}
if _, err := db.ExecContext(ctx, "INSERT INTO orders VALUES (1,10,7),(2,10,7),(3,5,7),(4,20,8)"); err != nil {
panic(err)
}
type Order struct {
ID int64
Priority int64
}
sort := page.Sort{ID: "orders/priority/v1", Columns: []page.Column{
{Expression: expr.Quote("priority"), Direction: page.Desc},
{Expression: expr.Quote("id"), Direction: page.Asc, Unique: true},
}}
// With Bob-generated models, use their column references for these expressions.
// The key must independently match their order and actual stored value types.
key := func(o Order) []any { return []any{o.Priority, o.ID} }
p, err := page.Prepare(sort, key, page.Request{Size: 2}, page.Limits{Default: 2, Maximum: 100})
if err != nil {
panic(err)
}
q := sqlite.Select(sm.Columns("id", "priority"), sm.From("orders"), page.Mod[*sd.SelectQuery](p))
q.Apply(sm.Where(sqlite.Quote("tenant").EQ(sqlite.Arg(int64(7)))))
rows, err := bob.All(ctx, db, q, scan.StructMapper[Order]())
if err != nil {
panic(err)
}
result, err := p.Page(rows)
if err != nil {
panic(err)
}
fmt.Println(result.Items, result.HasNext, result.HasPrevious)
// Continue with Forward + result.EndCursor, or Backward + result.StartCursor.
// A mismatched key returns no partial Page, including for an overflow row.
bad, err := page.Prepare(sort, func(o Order) []any { return []any{o.ID} }, page.Request{}, page.Limits{Default: 2, Maximum: 100})
if err != nil {
panic(err)
}
_, err = bad.Page(rows)
var keyError *page.KeyError
fmt.Println(errors.As(err, &keyError), keyError.Expected, keyError.Actual)
}
Output: [{1 10} {2 10}] true false true 2 1
Index ¶
Examples ¶
Constants ¶
This section is empty.
Variables ¶
This section is empty.
Functions ¶
func Count ¶
Count executes a separate caller-built, authorized count query returning one int64 column. It never rewrites a paginated query or infers grouped/distinct semantics. Nothing runs unless called.
func Mod ¶
func Mod[Q interface {
AppendWhere(...any)
AppendOrder(bob.Expression)
SetLimit(any)
}, T any](p *Prepared[T]) bob.Mod[Q]
Mod applies validated keyset WHERE, ORDER BY and integer LIMIT size+1 to native PostgreSQL, SQLite or MySQL selects. Apply once to a query with no existing ORDER BY, LIMIT, OFFSET or FETCH; do not override pagination afterward. Callers may compose additional WHERE filters before or after this mod.
Types ¶
type Column ¶
type Column struct {
Expression bob.Expression
Direction Order
Unique bool
}
Column declares a trusted, non-null SQL expression and its canonical direction. Unique asserts that this column alone is unique, including across joins.
type CursorError ¶
type CursorError struct{ Kind string }
CursorError identifies malformed, version, sort, value, or size cursor failures via Kind. It intentionally omits cursor payloads from its message.
func (*CursorError) Error ¶
func (e *CursorError) Error() string
Error implements error without including cursor contents.
type Page ¶
type Page[T any] struct { Items []T StartCursor string EndCursor string HasNext bool HasPrevious bool }
Page contains canonical-order items and cursors for their first and last rows. Empty pages have empty cursors. Overflow determines the fetch-direction flag; anchor presence determines the opposite flag, even for empty/stale results. The opposite flag does not prove another row exists.
type Prepared ¶
type Prepared[T any] struct { // contains filtered or unexported fields }
Prepared holds validated pagination and the typed row key. Construct with Prepare. Treat Sort expressions and the key's captured state as immutable while in use.
func Prepare ¶
func Prepare[T any](sort Sort, key func(T) []any, request Request, limits Limits) (*Prepared[T], error)
Prepare validates a request before a Bob mod can be applied. key must return exactly one supported concrete value per Sort column, in the same order, using actual stored/scanned values (including database timestamp precision).
type Request ¶
Request carries a transport-independent page request. Zero Size uses the configured default; excessive sizes clamp to Maximum. Empty Cursor has no anchor.
type Sort ¶
Sort is an immutable, caller-owned ordering contract. Change ID when its expressions, order, key types, uniqueness or comparison semantics change.
type ValueError ¶
ValueError identifies an unsupported concrete type or invalid supported value at Position in the row key, without exposing the value itself.
func (*ValueError) Error ¶
func (e *ValueError) Error() string
Error implements error without including key values.