Help us shape the future of Go! Take the 2026 Go Developer Survey

page

package
v0.2.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Oct 28, 2026 License: BSD-3-Clause Imports: 15 Imported by: 0

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

func Count(ctx context.Context, executor bob.Executor, query bob.Query) (int64, error)

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 Direction

type Direction uint8

Direction selects which side of an optional anchor to fetch.

const (
	// Forward maps to first/after and is the default direction.
	Forward Direction = iota
	// Backward maps to last/before; returned items retain canonical sort order.
	Backward
)

type KeyError

type KeyError struct {
	Expected int
	Actual   int
}

KeyError identifies a programmer error in the row key's arity.

func (*KeyError) Error

func (e *KeyError) Error() string

Error implements error with the expected and actual key lengths.

type Limits

type Limits struct {
	Default int
	Maximum int
}

Limits configures page sizes; require 1 <= Default <= Maximum < max int.

type Order

type Order uint8

Order is a column's canonical ordering direction.

const (
	// Asc orders smaller values first.
	Asc Order = iota
	// Desc orders larger values first.
	Desc
)

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).

func (*Prepared[T]) Page

func (p *Prepared[T]) Page(rows []T) (Page[T], error)

Page validates all fetched rows, including overflow, and assembles a page. Pass rows in query order, with at most size+1 rows. It copies retained items; backward pagination never reverses the caller's slice. Errors return no page.

type Request

type Request struct {
	Direction Direction
	Size      int
	Cursor    string
}

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

type Sort struct {
	ID      string
	Columns []Column
}

Sort is an immutable, caller-owned ordering contract. Change ID when its expressions, order, key types, uniqueness or comparison semantics change.

type ValueError

type ValueError struct {
	Position int
	Type     string
}

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.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL