Files
go/page/example_test.go
T
end 8615d8ca66 feat(page): add typed keyset pagination for Bob
Add versioned typed cursors, composable native query mods, bidirectional page assembly and opt-in authorized counts. Cover SQLite and PostgreSQL outcomes, strict cursor boundaries and a runnable example; require PostgreSQL service tests in CI.

Refs: #6
2026-10-27 12:55:23 -07:00

70 lines
2.3 KiB
Go

package page_test
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 Example() {
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
}