Documentation
¶
Overview ¶
Example (Metadata) ¶
package main
import (
"fmt"
"github.com/emoss08/keel/internal/testmodels/fleet/fleetdb"
"github.com/emoss08/keel/schema"
)
func main() {
t := fleetdb.ShipmentsSchema()
fmt.Println(t.Label, t.Plural, t.Report.Category, t.Doc)
for _, c := range t.Columns {
if c.Report == nil || !c.Report.Included {
continue
}
fmt.Println(c.Label, c.Report.Kind == schema.Measure, c.Report.Format, c.Doc)
for _, v := range c.Enum {
fmt.Println(" ", v.Value, v.Label)
}
}
}
Output:
Example (Migrations) ¶
package main
import (
"context"
"embed"
"errors"
"fmt"
"io/fs"
"log"
"os"
"github.com/emoss08/keel/migrate"
)
//go:embed testdata/migrations/*.sql
var migrationFiles embed.FS
func main() {
ctx := context.Background()
dir, err := fs.Sub(migrationFiles, "testdata/migrations")
if err != nil {
log.Fatal(err)
}
sources := migrate.Sources(migrate.Source{Name: "core", FS: dir})
m, err := migrate.Open(ctx, os.Getenv("KEEL_DSN"), sources, migrate.Options{
After: []string{"ANALYZE"},
Observer: func(e migrate.Event) {
switch e.Kind {
case migrate.Retry:
log.Printf("%s: lock timeout, attempt %d, blocked by %v", e.Migration.ID(), e.Attempt, e.Blockers)
case migrate.MigrationEnd:
log.Printf("%s in %s", e.Migration.ID(), e.Duration)
}
},
})
if err != nil {
log.Fatal(err)
}
defer m.Close()
res, err := m.Up(ctx, migrate.UpOptions{})
switch {
case errors.Is(err, migrate.ErrChanged):
log.Fatal("an applied migration was edited; add a new migration instead")
case errors.Is(err, migrate.ErrOutOfOrder):
log.Fatal("a migration is older than the newest applied one; give it a newer version")
case err != nil:
log.Fatal(err)
}
fmt.Println("applied", len(res.Applied))
status, err := m.Status(ctx)
if err != nil {
log.Fatal(err)
}
for _, s := range status {
fmt.Println(s.ID(), s.State, s.Directives.NoTx)
}
if err := migrate.Require(ctx, m.Conn(), sources.Version()); errors.Is(err, migrate.ErrBehind) {
log.Fatal(err)
}
}
Output:
Example (ModelHooks) ¶
package main
import (
"context"
"fmt"
"log"
"github.com/emoss08/keel"
"github.com/emoss08/keel/internal/testmodels/fleet"
"github.com/emoss08/keel/internal/testmodels/fleet/fleetdb"
"github.com/emoss08/keel/internal/testmodels/ids"
)
// appDB is the application's database, opened once at startup (see ExampleOpen).
var appDB *keel.DB
// scope is what the application's authentication middleware puts on a request context.
type scope struct {
OrganizationID ids.ID
BusinessUnitID ids.ID
SystemReason string
}
type scopeKey struct{}
func withTenant(ctx context.Context, org, bu ids.ID) context.Context {
return context.WithValue(ctx, scopeKey{}, scope{OrganizationID: org, BusinessUnitID: bu})
}
// requestContext returns the context of a request authenticated for one tenant.
func requestContext() context.Context {
return withTenant(context.Background(), "org_acme", "bu_dallas")
}
func main() {
ctx, db := requestContext(), appDB
w := fleetdb.Workers
grace := &fleet.Worker{FirstName: " Grace ", LastName: "Hopper"}
if err := w.Create(ctx, db, grace); err != nil {
log.Fatal(err)
}
fmt.Println(grace.FirstName, grace.WholeName, grace.HiredAt)
got, err := w.Get(ctx, db, grace.ID)
if err != nil {
log.Fatal(err)
}
fmt.Println(got.Initials)
}
Output:
Example (NullZero) ¶
package main
import (
"context"
"fmt"
"log"
"github.com/emoss08/keel"
"github.com/emoss08/keel/internal/testmodels/fleet"
"github.com/emoss08/keel/internal/testmodels/fleet/fleetdb"
"github.com/emoss08/keel/internal/testmodels/ids"
)
// appDB is the application's database, opened once at startup (see ExampleOpen).
var appDB *keel.DB
// scope is what the application's authentication middleware puts on a request context.
type scope struct {
OrganizationID ids.ID
BusinessUnitID ids.ID
SystemReason string
}
type scopeKey struct{}
func withTenant(ctx context.Context, org, bu ids.ID) context.Context {
return context.WithValue(ctx, scopeKey{}, scope{OrganizationID: org, BusinessUnitID: bu})
}
// requestContext returns the context of a request authenticated for one tenant.
func requestContext() context.Context {
return withTenant(context.Background(), "org_acme", "bu_dallas")
}
func main() {
ctx, db := requestContext(), appDB
c := fleetdb.Contacts
contact := &fleet.Contact{Name: "Dana"}
if err := c.Create(ctx, db, contact); err != nil {
log.Fatal(err)
}
unassigned, err := c.Where(c.CustomerID.Eq("")).Count(ctx, db)
if err != nil {
log.Fatal(err)
}
_, err = c.Where(c.Name.Eq("Dana")).Update(ctx, db, c.Phone.To(""))
if err != nil {
log.Fatal(err)
}
fmt.Println(unassigned)
}
Output:
Example (Operators) ¶
package main
import (
"context"
"fmt"
"log"
"time"
"github.com/emoss08/keel"
"github.com/emoss08/keel/internal/testmodels/fleet"
"github.com/emoss08/keel/internal/testmodels/fleet/fleetdb"
"github.com/emoss08/keel/internal/testmodels/ids"
)
// appDB is the application's database, opened once at startup (see ExampleOpen).
var appDB *keel.DB
// scope is what the application's authentication middleware puts on a request context.
type scope struct {
OrganizationID ids.ID
BusinessUnitID ids.ID
SystemReason string
}
type scopeKey struct{}
func withTenant(ctx context.Context, org, bu ids.ID) context.Context {
return context.WithValue(ctx, scopeKey{}, scope{OrganizationID: org, BusinessUnitID: bu})
}
// requestContext returns the context of a request authenticated for one tenant.
func requestContext() context.Context {
return withTenant(context.Background(), "org_acme", "bu_dallas")
}
func main() {
ctx, db := requestContext(), appDB
s := fleetdb.Shipments
since := time.Now().Add(-24 * time.Hour)
n, err := s.Where(
s.Status.In(fleet.StatusNew, fleet.StatusAssigned),
s.Priority.Gte(2),
s.Weight.Between(10_000, 40_000),
s.BillToID.IsNull(),
s.ProNumber.HasPrefix("PRO-"),
s.Hazmat.IsFalse(),
s.Total.Gt(fleet.Money{Cents: 50_000}),
s.Labels.Has("expedite"),
s.Attributes.Contains(map[string]any{"temperature": "frozen"}),
s.Attributes.Text("trailer", "type").Eq("reefer"),
s.SearchVector.Match("dallas reefer"),
keel.Or(s.CanceledAt.IsNull(), s.CanceledAt.Gt(since.Unix())),
keel.Not(s.Customer().Name.ContainsFold("test")),
).Count(ctx, db)
if err != nil {
log.Fatal(err)
}
st := fleetdb.Stops
late, err := st.Where(st.ArrivedAt.Gte(since), st.Kind.Eq("delivery")).Count(ctx, db)
if err != nil {
log.Fatal(err)
}
fmt.Println(n, late)
}
Output:
Example (QuickStart) ¶
package main
import (
"context"
"crypto/rand"
"encoding/hex"
"errors"
"fmt"
"log"
"os"
"github.com/emoss08/keel"
"github.com/emoss08/keel/internal/testmodels/fleet"
"github.com/emoss08/keel/internal/testmodels/fleet/fleetdb"
"github.com/emoss08/keel/internal/testmodels/ids"
)
func newID(prefix string) string {
b := make([]byte, 10)
_, _ = rand.Read(b)
return prefix + hex.EncodeToString(b)
}
type tenantCtxKey struct{}
func main() {
ctx := context.Background()
db, err := keel.Open(ctx, keel.Config{
DSN: os.Getenv("DATABASE_URL"),
TenantColumns: []string{"organization_id", "business_unit_id"},
Tenant: func(ctx context.Context) ([]any, bool) {
t, ok := ctx.Value(tenantCtxKey{}).([2]ids.ID)
if !ok {
return nil, false
}
return []any{t[0], t[1]}, true
},
NewID: newID,
})
if err != nil {
log.Fatal(err)
}
defer db.Close()
ctx = context.WithValue(ctx, tenantCtxKey{}, [2]ids.ID{"org_acme", "bu_dallas"})
c, s := fleetdb.Customers, fleetdb.Shipments
cust := &fleet.Customer{Code: "ACME", Name: "Acme Freight"}
if err := c.Create(ctx, db, cust); err != nil {
log.Fatal(err)
}
sh := &fleet.Shipment{CustomerID: cust.ID, ProNumber: "PRO-1001", Status: fleet.StatusNew}
if err := s.Create(ctx, db, sh); err != nil {
log.Fatal(err)
}
open, err := s.
Where(s.Status.In(fleet.StatusNew, fleet.StatusAssigned), s.Customer().Code.Eq("ACME")).
Load(s.Customer()).
Order(s.CreatedAt.Desc()).
Limit(50).
List(ctx, db)
if err != nil {
log.Fatal(err)
}
fmt.Println(len(open), open[0].Customer.Name)
sh.Status = fleet.StatusAssigned
if err := s.Save(ctx, db, sh); errors.Is(err, keel.ErrConflict) {
fmt.Println("someone else saved this shipment first")
} else if err != nil {
log.Fatal(err)
}
err = keel.Write(ctx, db, func(ctx context.Context) error {
mv := &fleet.Move{ShipmentID: sh.ID, Sequence: 1, Status: fleet.StatusAssigned}
if err := fleetdb.Moves.Create(ctx, db, mv); err != nil {
return err
}
_, err := s.Where(s.ID.Eq(sh.ID)).Update(ctx, db, s.Status.To(fleet.StatusInTransit))
return err
})
if err != nil {
log.Fatal(err)
}
}
Output:
Example (RelationFilters) ¶
package main
import (
"context"
"fmt"
"log"
"github.com/emoss08/keel"
"github.com/emoss08/keel/internal/testmodels/fleet"
"github.com/emoss08/keel/internal/testmodels/fleet/fleetdb"
"github.com/emoss08/keel/internal/testmodels/ids"
)
// appDB is the application's database, opened once at startup (see ExampleOpen).
var appDB *keel.DB
// scope is what the application's authentication middleware puts on a request context.
type scope struct {
OrganizationID ids.ID
BusinessUnitID ids.ID
SystemReason string
}
type scopeKey struct{}
func withTenant(ctx context.Context, org, bu ids.ID) context.Context {
return context.WithValue(ctx, scopeKey{}, scope{OrganizationID: org, BusinessUnitID: bu})
}
// requestContext returns the context of a request authenticated for one tenant.
func requestContext() context.Context {
return withTenant(context.Background(), "org_acme", "bu_dallas")
}
func main() {
ctx, db := requestContext(), appDB
s := fleetdb.Shipments
m := s.Moves()
st := m.Stops()
rows, err := s.Where(
s.Customer().Code.Eq("ACME"),
keel.Or(s.BillToID.IsNull(), s.BillTo().Code.Ne("ACME")),
m.Any(m.Status.Eq(fleet.StatusInTransit), m.Loaded.IsTrue()),
m.Any(st.Any(st.Kind.Eq("delivery"), st.Location().City.Eq("Dallas"))),
s.Tags().None(s.Tags().Name.Eq("on-hold")),
).Limit(100).List(ctx, db)
if err != nil {
log.Fatal(err)
}
fmt.Println(len(rows))
}
Output:
Example (RelationKeys) ¶
package main
import (
"context"
"fmt"
"log"
"github.com/emoss08/keel"
"github.com/emoss08/keel/internal/testmodels/fleet/fleetdb"
"github.com/emoss08/keel/internal/testmodels/ids"
)
// appDB is the application's database, opened once at startup (see ExampleOpen).
var appDB *keel.DB
// scope is what the application's authentication middleware puts on a request context.
type scope struct {
OrganizationID ids.ID
BusinessUnitID ids.ID
SystemReason string
}
type scopeKey struct{}
func withTenant(ctx context.Context, org, bu ids.ID) context.Context {
return context.WithValue(ctx, scopeKey{}, scope{OrganizationID: org, BusinessUnitID: bu})
}
// requestContext returns the context of a request authenticated for one tenant.
func requestContext() context.Context {
return withTenant(context.Background(), "org_acme", "bu_dallas")
}
func main() {
ctx, db := requestContext(), appDB
l := fleetdb.Leaves
e := l.Entries()
leaves, err := l.
Where(e.Any(e.Hours.Gte(8))).
Load(e.Order(e.PostedAt.Asc()), l.LastEntry()).
Order(l.ID.Asc()).
Limit(100).
List(ctx, db)
if err != nil {
log.Fatal(err)
}
for _, lv := range leaves {
fmt.Println(lv.ID, len(lv.Entries), lv.LastEntry != nil)
}
}
Output:
Example (TenantErrors) ¶
package main
import (
"context"
"errors"
"fmt"
"github.com/emoss08/keel"
"github.com/emoss08/keel/internal/testmodels/fleet"
"github.com/emoss08/keel/internal/testmodels/fleet/fleetdb"
"github.com/emoss08/keel/internal/testmodels/ids"
)
// appDB is the application's database, opened once at startup (see ExampleOpen).
var appDB *keel.DB
// scope is what the application's authentication middleware puts on a request context.
type scope struct {
OrganizationID ids.ID
BusinessUnitID ids.ID
SystemReason string
}
type scopeKey struct{}
func withTenant(ctx context.Context, org, bu ids.ID) context.Context {
return context.WithValue(ctx, scopeKey{}, scope{OrganizationID: org, BusinessUnitID: bu})
}
func withSystem(ctx context.Context, reason string) context.Context {
return context.WithValue(ctx, scopeKey{}, scope{SystemReason: reason})
}
// requestContext returns the context of a request authenticated for one tenant.
func requestContext() context.Context {
return withTenant(context.Background(), "org_acme", "bu_dallas")
}
func main() {
db := appDB
s := fleetdb.Shipments
_, err := s.Query().Limit(10).List(context.Background(), db)
fmt.Println(errors.Is(err, keel.ErrNoScope))
sys := withSystem(context.Background(), "report")
_, err = s.Query().Limit(10).List(sys, db)
fmt.Println(errors.Is(err, keel.ErrAllTenantsRequired))
_, err = s.AllTenants().Limit(10).List(requestContext(), db)
fmt.Println(errors.Is(err, keel.ErrSystemScopeRequired))
err = fleetdb.Customers.Create(requestContext(), db,
&fleet.Customer{OrganizationID: "org_other", BusinessUnitID: "bu_other", Code: "X", Name: "X"})
fmt.Println(errors.Is(err, keel.ErrTenantMismatch))
plant, err := fleetdb.Plants.Get(context.Background(), db, "DAL1")
fmt.Println(plant, err)
}
Output:
Index ¶
- Constants
- Variables
- func AfterCommit(ctx context.Context, fn func(ctx context.Context))
- func Alias[H any](ref *Ref, alias string, build func(*Ref) H) H
- func Batch(ctx context.Context, db *DB, items ...BatchItem) error
- func Budget(ctx context.Context, opts ...BudgetOption) context.Context
- func Code(err error) string
- func ColumnOf(e Expr) (table *schema.Table, index int32, ok bool)
- func ConstraintName(err error) string
- func Exec(ctx context.Context, db *DB, sql string, args ...any) (int64, error)
- func ExecAll(ctx context.Context, db *DB, sql string, args ...any) (int64, error)
- func ExecBatch(ctx context.Context, db *DB, stmts ...Statement) ([]int64, error)
- func InTx(ctx context.Context, db *DB) bool
- func Int16s[S ~int16](vs []S) []int16
- func Int32s[S ~int32](vs []S) []int32
- func Int64s[S ~int64](vs []S) []int64
- func Into[R any](ctx context.Context, db *DB, p Projection) ([]R, error)
- func IntoOne[R any](ctx context.Context, db *DB, p Projection) (R, error)
- func IsCheck(err error, constraints ...string) bool
- func IsExclusion(err error, constraints ...string) bool
- func IsForeignKey(err error, constraints ...string) bool
- func IsNotNull(err error) bool
- func IsRetryable(err error) bool
- func IsUnique(err error, constraints ...string) bool
- func JoinTx(ctx context.Context, db *DB, conn *pgx.Conn) (context.Context, func(committed bool))
- func LockKey(ctx context.Context, db *DB, key string) error
- func MarkChanged(ctx context.Context, db *DB, tables ...string)
- func OnCreate[T any](db *DB, t tabler[T], phase Phase, ...)
- func OnDelete[T any](db *DB, t tabler[T], phase Phase, ...)
- func OnReplica(ctx context.Context) context.Context
- func OnReplicaAfter(ctx context.Context, db *DB, pos Position) context.Context
- func OnUpdate[T any](db *DB, t tabler[T], phase Phase, ...)
- func OnWrite[T any](db *DB, t tabler[T], phase Phase, ...)
- func Path[H any](parent *Ref, rel int32, build func(*Ref) H) H
- func Pointers[T any](vs []T) []*T
- func QueryAll[T any](ctx context.Context, db *DB, to pgx.RowToFunc[T], sql string, args ...any) ([]T, error)
- func QueryRows(ctx context.Context, db *DB, fn func(row pgx.CollectableRow) error, sql string, ...) error
- func QueryValue[T any](ctx context.Context, db *DB, sql string, args ...any) (T, error)
- func Read(ctx context.Context, db *DB, fn func(ctx context.Context) error, ...) error
- func ReadValue[T any](ctx context.Context, db *DB, fn func(ctx context.Context) (T, error), ...) (T, error)
- func Savepoint(ctx context.Context, db *DB, fn func(ctx context.Context) error) error
- func SetLinks[K any](ctx context.Context, db *DB, rel interface{ ... }, parent K, targets []K) error
- func Strings[S ~string](vs []S) []string
- func TenantKeyOf(values ...any) string
- func TryLockKey(ctx context.Context, db *DB, key string) (bool, error)
- func WithReadFilter(ctx context.Context, f ReadFilter) context.Context
- func Write(ctx context.Context, db *DB, fn func(ctx context.Context) error, ...) error
- func WriteValue[T any](ctx context.Context, db *DB, fn func(ctx context.Context) (T, error), ...) (T, error)
- type Agg
- func ArrayAgg(e Expr) Agg
- func Avg(e Expr) Agg
- func BoolAnd(p Pred) Agg
- func BoolOr(p Pred) Agg
- func Count() Agg
- func CountOf(e Expr) Agg
- func CumeDist() Agg
- func DenseRank() Agg
- func FirstValue(e Expr) Agg
- func Grouping(es ...Expr) Agg
- func JSONBAgg(e Expr) Agg
- func Lag(e Expr, offset int, def any) Agg
- func LastValue(e Expr) Agg
- func Lead(e Expr, offset int, def any) Agg
- func Max(e Expr) Agg
- func Min(e Expr) Agg
- func Mode(order Order) Agg
- func NthValue(e Expr, n int) Agg
- func Ntile(n int) Agg
- func PercentRank() Agg
- func PercentileCont(fraction float64, order Order) Agg
- func PercentileDisc(fraction float64, order Order) Agg
- func Rank() Agg
- func RowNumber() Agg
- func StringAgg(e Expr, sep string) Agg
- func Sum(e Expr) Agg
- func (a Agg) Asc() Order
- func (a Agg) Desc() Order
- func (a Agg) Distinct() Agg
- func (a Agg) Eq(v any) Pred
- func (a Agg) Filter(p Pred) Agg
- func (a Agg) Gt(v any) Pred
- func (a Agg) Gte(v any) Pred
- func (a Agg) Lt(v any) Pred
- func (a Agg) Lte(v any) Pred
- func (a Agg) Ne(v any) Pred
- func (a Agg) OrderBy(os ...Order) Agg
- func (a Agg) Over(partition []Expr, order ...Order) Agg
- func (a Agg) OverWindow(w Window) Agg
- type AnyCol
- func (c AnyCol) Add(v NumericValue) NumericExpr
- func (c AnyCol) Avg() Agg
- func (c AnyCol) Between(lo, hi any) Pred
- func (c AnyCol) Div(v NumericValue) NumericExpr
- func (c AnyCol) Eq(v any) Pred
- func (c AnyCol) Gt(v any) Pred
- func (c AnyCol) Gte(v any) Pred
- func (c AnyCol) In(slice any) Pred
- func (c AnyCol) InArray(a NumericArrayValue) Pred
- func (c AnyCol) Increment(v any) Assignment
- func (c AnyCol) IsDistinctFrom(v any) Pred
- func (c AnyCol) IsDistinctFromExpr(o NumericValue) Pred
- func (c AnyCol) IsNotDistinctFrom(v any) Pred
- func (c AnyCol) IsNotDistinctFromExpr(o NumericValue) Pred
- func (c AnyCol) Lt(v any) Pred
- func (c AnyCol) Lte(v any) Pred
- func (c AnyCol) Mod(v NumericValue) NumericExpr
- func (c AnyCol) Mul(v NumericValue) NumericExpr
- func (c AnyCol) Ne(v any) Pred
- func (c AnyCol) Neg() NumericExpr
- func (c AnyCol) NotIn(slice any) Pred
- func (c AnyCol) NotInArray(a NumericArrayValue) Pred
- func (c AnyCol) Sub(v NumericValue) NumericExpr
- func (c AnyCol) Sum() Agg
- func (c AnyCol) To(v any) Assignment
- type Assignment
- type BatchItem
- func AllInto[T any](q *Query[T], dst *[]T) BatchItem
- func CountEstimateInto[T any](q *Query[T], exactUpTo int64, dst *RowCount) BatchItem
- func CountInto[T any](q *Query[T], dst *int64) BatchItem
- func CountUpToInto[T any](q *Query[T], limit int64, dst *int64) BatchItem
- func DynCountEstimateInto(q *DynQuery, exactUpTo int64, dst *RowCount) BatchItem
- func DynCountInto(q *DynQuery, dst *int64) BatchItem
- func DynExistsInto(q *DynQuery, dst *bool) BatchItem
- func DynInto(q *DynQuery, dst **DynResult) BatchItem
- func DynPageInto(q *DynQuery, p PageRequest, dst **DynResult) BatchItem
- func ExistsInto[T any](q *Query[T], dst *bool) BatchItem
- func FirstInto[T any](q *Query[T], dst *T) BatchItem
- func IntoSlice[R any](p Projection, dst *[]R) BatchItem
- func IntoValue[R any](p Projection, dst *R) BatchItem
- func ListInto[T any](q *Query[T], dst *[]T) BatchItem
- func OneInto[T any](q *Query[T], dst *T) BatchItem
- func PageInto[T any](q *Query[T], p PageRequest, dst *Page[T]) BatchItem
- type BoolArrayCase
- type BoolArrayExpr
- func (e BoolArrayExpr) At(i int) BoolExpr
- func (e BoolArrayExpr) Coalesce(o BoolArrayValue) BoolArrayExpr
- func (e BoolArrayExpr) ContainedByExpr(o BoolArrayValue) Pred
- func (e BoolArrayExpr) ContainsAllExpr(o BoolArrayValue) Pred
- func (e BoolArrayExpr) IsNotNull() Pred
- func (e BoolArrayExpr) IsNull() Pred
- func (e BoolArrayExpr) Len() Int32Expr
- func (e BoolArrayExpr) NullIf(o BoolArrayValue) BoolArrayExpr
- func (e BoolArrayExpr) OverlapsExpr(o BoolArrayValue) Pred
- func (e BoolArrayExpr) Unnest() BoolExpr
- func (e BoolArrayExpr) Without(v BoolValue) BoolArrayExpr
- type BoolArrayValue
- type BoolCase
- type BoolCol
- func (c BoolCol) Coalesce(vs ...any) BoolExpr
- func (c BoolCol) Eq(v bool) Pred
- func (c BoolCol) IsDistinctFrom(v bool) Pred
- func (c BoolCol) IsDistinctFromExpr(o BoolValue) Pred
- func (c BoolCol) IsFalse() Pred
- func (c BoolCol) IsNotDistinctFrom(v bool) Pred
- func (c BoolCol) IsNotDistinctFromExpr(o BoolValue) Pred
- func (c BoolCol) IsTrue() Pred
- func (c BoolCol) To(v bool) Assignment
- type BoolExpr
- func (e BoolExpr) Asc() Order
- func (e BoolExpr) Coalesce(vs ...any) BoolExpr
- func (e BoolExpr) Desc() Order
- func (e BoolExpr) Eq(v bool) Pred
- func (e BoolExpr) EqExpr(o BoolValue) Pred
- func (e BoolExpr) IsDistinctFrom(v bool) Pred
- func (e BoolExpr) IsDistinctFromExpr(o BoolValue) Pred
- func (e BoolExpr) IsFalse() Pred
- func (e BoolExpr) IsNotDistinctFrom(v bool) Pred
- func (e BoolExpr) IsNotDistinctFromExpr(o BoolValue) Pred
- func (e BoolExpr) IsNotNull() Pred
- func (e BoolExpr) IsNull() Pred
- func (e BoolExpr) IsTrue() Pred
- type BoolValue
- type BudgetError
- type BudgetOption
- type BudgetReport
- type BytesCol
- type CTETable
- type CacheConfig
- type CacheOption
- type CacheStats
- type Col
- func (c Col) Asc() Order
- func (c Col) Coalesce(vs ...any) Expr
- func (c Col) Count() Agg
- func (c Col) CountDistinct() Agg
- func (c Col) Desc() Order
- func (c Col) EqCol(other Expr) Pred
- func (c Col) Excluded() Expr
- func (c Col) InQuery(q subquery) Pred
- func (c Col) IsNotNull() Pred
- func (c Col) IsNull() Pred
- func (c Col) Max() Agg
- func (c Col) Min() Agg
- func (c Col) NeCol(other Expr) Pred
- func (c Col) NotInQuery(q subquery) Pred
- func (c Col) ToDefault() Assignment
- func (c Col) ToExpr(e Expr) Assignment
- func (c Col) ToNull() Assignment
- type Column
- type Compound
- type Config
- type Conflict
- type ConflictError
- type Constraint
- type ConstraintError
- type ConstraintKind
- type DB
- func (db *DB) CacheStats() CacheStats
- func (db *DB) Close()
- func (db *DB) DataVersions(ctx context.Context, tenantKey string, tables ...string) (map[string]int64, error)
- func (db *DB) FlushDataVersions(ctx context.Context) error
- func (db *DB) ForgetColumnTypes()
- func (db *DB) Intercept(t interface{ ... }, fn Interceptor)
- func (db *DB) InterceptAll(fn Interceptor)
- func (db *DB) JSON() JSONCodec
- func (db *DB) Ping(ctx context.Context) error
- func (db *DB) Pool() *pgxpool.Pool
- func (db *DB) ReadCommitFailures() int64
- func (db *DB) ReplicaFallbacks() uint64
- func (db *DB) Replicas() []ReplicaStats
- func (db *DB) TenantColumns() []string
- func (db *DB) TenantKey(ctx context.Context) string
- func (db *DB) TenantWaits() int64
- type DataVersionsConfig
- type Decimal
- type DeclaredFunc
- type Drift
- type DriftKind
- type DynCTE
- type DynCaseBuilder
- type DynColumn
- type DynExpr
- func (d DynExpr) Add(v any) DynExpr
- func (d DynExpr) And(ps ...Pred) DynExpr
- func (d DynExpr) As(label string) DynExpr
- func (d DynExpr) Asc() Order
- func (d DynExpr) At(i int) DynExpr
- func (d DynExpr) Avg() DynExpr
- func (d DynExpr) Between(lo, hi any) DynExpr
- func (d DynExpr) Cast(t ValueType) DynExpr
- func (d DynExpr) Coalesce(vs ...any) DynExpr
- func (d DynExpr) Collate(name string) DynExpr
- func (d DynExpr) Concat(v any) DynExpr
- func (d DynExpr) Contains(s string) DynExpr
- func (d DynExpr) ContainsFold(s string) DynExpr
- func (d DynExpr) Count() DynExpr
- func (d DynExpr) CountDistinct() DynExpr
- func (d DynExpr) CoveredBy(v any) DynExpr
- func (d DynExpr) Covers(v any) DynExpr
- func (d DynExpr) Desc() Order
- func (d DynExpr) Distinct() DynExpr
- func (d DynExpr) Div(v any) DynExpr
- func (d DynExpr) Eq(v any) DynExpr
- func (d DynExpr) Err() error
- func (d DynExpr) Filter(p Pred) DynExpr
- func (d DynExpr) Gt(v any) DynExpr
- func (d DynExpr) Gte(v any) DynExpr
- func (d DynExpr) HasPrefix(s string) DynExpr
- func (d DynExpr) HasSuffix(s string) DynExpr
- func (d DynExpr) ILike(pattern any) DynExpr
- func (d DynExpr) In(list any) DynExpr
- func (d DynExpr) InUnnest(arrays ...any) DynExpr
- func (d DynExpr) IsDistinctFrom(v any) DynExpr
- func (d DynExpr) IsFalse() DynExpr
- func (d DynExpr) IsNotNull() DynExpr
- func (d DynExpr) IsNull() DynExpr
- func (d DynExpr) IsTrue() DynExpr
- func (d DynExpr) Label() string
- func (d DynExpr) Like(pattern any) DynExpr
- func (d DynExpr) Lt(v any) DynExpr
- func (d DynExpr) Lte(v any) DynExpr
- func (d DynExpr) Match(query string, opts ...SearchOption) DynExpr
- func (d DynExpr) Max() DynExpr
- func (d DynExpr) Min() DynExpr
- func (d DynExpr) Mod(v any) DynExpr
- func (d DynExpr) Mul(v any) DynExpr
- func (d DynExpr) Ne(v any) DynExpr
- func (d DynExpr) Neg() DynExpr
- func (d DynExpr) Not() DynExpr
- func (d DynExpr) NotIn(list any) DynExpr
- func (d DynExpr) NullIf(v any) DynExpr
- func (d DynExpr) Or(ps ...Pred) DynExpr
- func (d DynExpr) OrderBy(os ...Order) DynExpr
- func (d DynExpr) Over(w Window) DynExpr
- func (d DynExpr) Rank(query string, opts ...SearchOption) DynExpr
- func (d DynExpr) RankCD(query string, opts ...SearchOption) DynExpr
- func (d DynExpr) Sub(v any) DynExpr
- func (d DynExpr) Sum() DynExpr
- type DynPreAgg
- type DynQuery
- func (q *DynQuery) AllTenants() *DynQuery
- func (q *DynQuery) Compute(name string, e DynExpr) *DynQuery
- func (q *DynQuery) Count(ctx context.Context, db *DB) (int64, error)
- func (q *DynQuery) CountEstimate(ctx context.Context, db *DB, exactUpTo int64) (RowCount, error)
- func (q *DynQuery) Cube(es ...Expr) *DynQuery
- func (q *DynQuery) CustomPlan() *DynQuery
- func (q *DynQuery) Distinct() *DynQuery
- func (q *DynQuery) Err() error
- func (q *DynQuery) Exists(ctx context.Context, db *DB) (bool, error)
- func (q *DynQuery) Explain(ctx context.Context, db *DB, opts ExplainOptions) (*Plan, error)
- func (q *DynQuery) Group(es ...Expr) *DynQuery
- func (q *DynQuery) GroupingSets(sets ...[]Expr) *DynQuery
- func (q *DynQuery) Having(ps ...Pred) *DynQuery
- func (q *DynQuery) Join(c *DynCTE, on ...Pred) *DynQuery
- func (q *DynQuery) LeftJoin(c *DynCTE, on ...Pred) *DynQuery
- func (q *DynQuery) Limit(n int) *DynQuery
- func (q *DynQuery) Offset(n int) *DynQuery
- func (q *DynQuery) Omit(names ...string) *DynQuery
- func (q *DynQuery) Order(os ...Order) *DynQuery
- func (q *DynQuery) Page(ctx context.Context, db *DB, p PageRequest) (*DynResult, error)
- func (q *DynQuery) Path(segments ...string) DynExpr
- func (q *DynQuery) Rel(path ...string) *DynRel
- func (q *DynQuery) Rollup(es ...Expr) *DynQuery
- func (q *DynQuery) Run(ctx context.Context, db *DB) (*DynResult, error)
- func (q *DynQuery) Select(es ...DynExpr) *DynQuery
- func (q *DynQuery) Stream(ctx context.Context, db *DB, size int) iter.Seq2[*DynResult, error]
- func (q *DynQuery) Table() *schema.Table
- func (q *DynQuery) Timeout(d time.Duration) *DynQuery
- func (q *DynQuery) ToSQL(ctx context.Context, db *DB) (string, []any, error)
- func (q *DynQuery) Where(ps ...Pred) *DynQuery
- func (q *DynQuery) Window(name string, w Window) *DynQuery
- func (q *DynQuery) With(sub *DynQuery) *DynCTE
- type DynRel
- type DynResult
- type ExplainOptions
- type Expr
- type Float64ArrayCase
- type Float64ArrayExpr
- func (e Float64ArrayExpr) At(i int) Float64Expr
- func (e Float64ArrayExpr) Coalesce(o Float64ArrayValue) Float64ArrayExpr
- func (e Float64ArrayExpr) ContainedByExpr(o Float64ArrayValue) Pred
- func (e Float64ArrayExpr) ContainsAllExpr(o Float64ArrayValue) Pred
- func (e Float64ArrayExpr) IsNotNull() Pred
- func (e Float64ArrayExpr) IsNull() Pred
- func (e Float64ArrayExpr) Len() Int32Expr
- func (e Float64ArrayExpr) NullIf(o Float64ArrayValue) Float64ArrayExpr
- func (e Float64ArrayExpr) OverlapsExpr(o Float64ArrayValue) Pred
- func (e Float64ArrayExpr) Unnest() Float64Expr
- func (e Float64ArrayExpr) Without(v Float64Value) Float64ArrayExpr
- type Float64ArrayValue
- type Float64Case
- type Float64Col
- func (c Float64Col) Abs() Float64Expr
- func (c Float64Col) Add(v Float64Value) Float64Expr
- func (c Float64Col) Avg() Agg
- func (c Float64Col) Between(lo, hi float64) Pred
- func (c Float64Col) Ceil() Float64Expr
- func (c Float64Col) Coalesce(vs ...any) Float64Expr
- func (c Float64Col) Div(v Float64Value) Float64Expr
- func (c Float64Col) Eq(v float64) Pred
- func (c Float64Col) Exp() Float64Expr
- func (c Float64Col) Floor() Float64Expr
- func (c Float64Col) Greatest(others ...Float64Value) Float64Expr
- func (c Float64Col) Gt(v float64) Pred
- func (c Float64Col) Gte(v float64) Pred
- func (c Float64Col) InArray(a Float64ArrayValue) Pred
- func (c Float64Col) Increment(v float64) Assignment
- func (c Float64Col) IsDistinctFrom(v float64) Pred
- func (c Float64Col) IsDistinctFromExpr(o Float64Value) Pred
- func (c Float64Col) IsNotDistinctFrom(v float64) Pred
- func (c Float64Col) IsNotDistinctFromExpr(o Float64Value) Pred
- func (c Float64Col) Least(others ...Float64Value) Float64Expr
- func (c Float64Col) Ln() Float64Expr
- func (c Float64Col) Lt(v float64) Pred
- func (c Float64Col) Lte(v float64) Pred
- func (c Float64Col) Mul(v Float64Value) Float64Expr
- func (c Float64Col) Ne(v float64) Pred
- func (c Float64Col) Neg() Float64Expr
- func (c Float64Col) NotInArray(a Float64ArrayValue) Pred
- func (c Float64Col) NullIf(v float64) Float64Expr
- func (c Float64Col) Round() Float64Expr
- func (c Float64Col) Sub(v Float64Value) Float64Expr
- func (c Float64Col) Sum() Agg
- func (c Float64Col) To(v float64) Assignment
- type Float64Expr
- func (e Float64Expr) Abs() Float64Expr
- func (e Float64Expr) Add(v Float64Value) Float64Expr
- func (e Float64Expr) Asc() Order
- func (e Float64Expr) Avg() Agg
- func (e Float64Expr) Between(lo, hi float64) Pred
- func (e Float64Expr) Ceil() Float64Expr
- func (e Float64Expr) Coalesce(vs ...any) Float64Expr
- func (e Float64Expr) Desc() Order
- func (e Float64Expr) Div(v Float64Value) Float64Expr
- func (e Float64Expr) Eq(v float64) Pred
- func (e Float64Expr) EqExpr(o Float64Value) Pred
- func (e Float64Expr) Exp() Float64Expr
- func (e Float64Expr) Floor() Float64Expr
- func (e Float64Expr) Greatest(others ...Float64Value) Float64Expr
- func (e Float64Expr) Gt(v float64) Pred
- func (e Float64Expr) GtExpr(o Float64Value) Pred
- func (e Float64Expr) Gte(v float64) Pred
- func (e Float64Expr) GteExpr(o Float64Value) Pred
- func (e Float64Expr) InArray(a Float64ArrayValue) Pred
- func (e Float64Expr) IsDistinctFrom(v float64) Pred
- func (e Float64Expr) IsDistinctFromExpr(o Float64Value) Pred
- func (e Float64Expr) IsNotDistinctFrom(v float64) Pred
- func (e Float64Expr) IsNotDistinctFromExpr(o Float64Value) Pred
- func (e Float64Expr) IsNotNull() Pred
- func (e Float64Expr) IsNull() Pred
- func (e Float64Expr) Least(others ...Float64Value) Float64Expr
- func (e Float64Expr) Ln() Float64Expr
- func (e Float64Expr) Lt(v float64) Pred
- func (e Float64Expr) LtExpr(o Float64Value) Pred
- func (e Float64Expr) Lte(v float64) Pred
- func (e Float64Expr) LteExpr(o Float64Value) Pred
- func (e Float64Expr) Max() Agg
- func (e Float64Expr) Min() Agg
- func (e Float64Expr) Mul(v Float64Value) Float64Expr
- func (e Float64Expr) Ne(v float64) Pred
- func (e Float64Expr) NeExpr(o Float64Value) Pred
- func (e Float64Expr) Neg() Float64Expr
- func (e Float64Expr) NotInArray(a Float64ArrayValue) Pred
- func (e Float64Expr) NullIf(v float64) Float64Expr
- func (e Float64Expr) Round() Float64Expr
- func (e Float64Expr) Sub(v Float64Value) Float64Expr
- func (e Float64Expr) Sum() Agg
- type Float64Value
- type FrameBound
- type FrameExclusion
- type Func0
- type Func1
- type Func2
- type Func3
- type FuncDecl
- type FuncResult
- type Handle
- func (h Handle[T]) AllTenants() *Query[T]
- func (h Handle[T]) Any(ps ...Pred) Pred
- func (h Handle[T]) Cache(opts ...CacheOption) *Query[T]
- func (h Handle[T]) Compute(col Column, e Expr) *Query[T]
- func (h Handle[T]) Copy(ctx context.Context, db *DB, rows []T) (int64, error)
- func (h Handle[T]) Create(ctx context.Context, db *DB, row T) error
- func (h Handle[T]) CreateMany(ctx context.Context, db *DB, rows []T) error
- func (h Handle[T]) CreateOrSkip(ctx context.Context, db *DB, rows []T, targets ...Expr) (int64, error)
- func (h Handle[T]) Cube(es ...Expr) *Query[T]
- func (h Handle[T]) DeleteRow(ctx context.Context, db *DB, row T) error
- func (h Handle[T]) DistinctOn(es ...Expr) *Query[T]
- func (h Handle[T]) Group(es ...Expr) *Query[T]
- func (h Handle[T]) GroupingSets(sets ...[]Expr) *Query[T]
- func (h Handle[T]) InsertFrom(ctx context.Context, db *DB, src subquery, cols ...Column) (int64, error)
- func (h Handle[T]) InsertFromSQL(ctx context.Context, db *DB, src subquery, cols ...Column) (string, []any, error)
- func (h Handle[T]) Join(t interface{ ... }, on ...Pred) *Query[T]
- func (h Handle[T]) JoinLateral(sub interface{ ... }) *Query[T]
- func (h Handle[T]) LeftJoin(t interface{ ... }, on ...Pred) *Query[T]
- func (h Handle[T]) LeftJoinLateral(sub interface{ ... }) *Query[T]
- func (h Handle[T]) Limit(n int) *Query[T]
- func (h Handle[T]) Load(ls ...Loader) *Query[T]
- func (h Handle[T]) LoadStrategy(s LoadStrategy) *Query[T]
- func (h Handle[T]) Lock(strength LockStrength, wait ...LockWait) *Query[T]
- func (h Handle[T]) None(ps ...Pred) Pred
- func (h Handle[T]) Omit(cols ...Column) *Query[T]
- func (h Handle[T]) Order(os ...Order) *Query[T]
- func (h Handle[T]) Pick(es ...Expr) *Query[T]
- func (h Handle[T]) Query() *Query[T]
- func (h Handle[T]) Rollup(es ...Expr) *Query[T]
- func (h Handle[T]) Save(ctx context.Context, db *DB, row T, cols ...Column) error
- func (h Handle[T]) SaveExcept(ctx context.Context, db *DB, row T, cols ...Column) error
- func (h Handle[T]) TableRef() *Ref
- func (h Handle[T]) Upsert(ctx context.Context, db *DB, rows []T, c Conflict) ([]bool, error)
- func (h Handle[T]) UpsertFrom(ctx context.Context, db *DB, src subquery, c Conflict, cols ...Column) (int64, error)
- func (h Handle[T]) UpsertFromSQL(ctx context.Context, db *DB, src subquery, c Conflict, cols ...Column) (string, []any, error)
- func (h Handle[T]) Where(ps ...Pred) *Query[T]
- func (h Handle[T]) With(ctes ...*CTETable) *Query[T]
- type HookOption
- type Int16Col
- func (c Int16Col) Between(lo, hi int16) Pred
- func (c Int16Col) Eq(v int16) Pred
- func (c Int16Col) Gt(v int16) Pred
- func (c Int16Col) Gte(v int16) Pred
- func (c Int16Col) In(vs ...int16) Pred
- func (c Int16Col) Increment(v int16) Assignment
- func (c Int16Col) IsDistinctFrom(v int16) Pred
- func (c Int16Col) IsNotDistinctFrom(v int16) Pred
- func (c Int16Col) Lt(v int16) Pred
- func (c Int16Col) Lte(v int16) Pred
- func (c Int16Col) Ne(v int16) Pred
- func (c Int16Col) NotIn(vs ...int16) Pred
- func (c Int16Col) Sum() Agg
- func (c Int16Col) To(v int16) Assignment
- type Int32ArrayCase
- type Int32ArrayExpr
- func (e Int32ArrayExpr) At(i int) Int32Expr
- func (e Int32ArrayExpr) Coalesce(o Int32ArrayValue) Int32ArrayExpr
- func (e Int32ArrayExpr) ContainedByExpr(o Int32ArrayValue) Pred
- func (e Int32ArrayExpr) ContainsAllExpr(o Int32ArrayValue) Pred
- func (e Int32ArrayExpr) IsNotNull() Pred
- func (e Int32ArrayExpr) IsNull() Pred
- func (e Int32ArrayExpr) Len() Int32Expr
- func (e Int32ArrayExpr) NullIf(o Int32ArrayValue) Int32ArrayExpr
- func (e Int32ArrayExpr) OverlapsExpr(o Int32ArrayValue) Pred
- func (e Int32ArrayExpr) Unnest() Int32Expr
- func (e Int32ArrayExpr) Without(v Int32Value) Int32ArrayExpr
- type Int32ArrayValue
- type Int32Case
- type Int32Col
- func (c Int32Col) Abs() Int32Expr
- func (c Int32Col) Add(v Int32Value) Int32Expr
- func (c Int32Col) Avg() Agg
- func (c Int32Col) Between(lo, hi int32) Pred
- func (c Int32Col) Coalesce(vs ...any) Int32Expr
- func (c Int32Col) Div(v Int32Value) Int32Expr
- func (c Int32Col) Eq(v int32) Pred
- func (c Int32Col) Greatest(others ...Int32Value) Int32Expr
- func (c Int32Col) Gt(v int32) Pred
- func (c Int32Col) Gte(v int32) Pred
- func (c Int32Col) In(vs ...int32) Pred
- func (c Int32Col) InArray(a Int32ArrayValue) Pred
- func (c Int32Col) Increment(v int32) Assignment
- func (c Int32Col) IsDistinctFrom(v int32) Pred
- func (c Int32Col) IsDistinctFromExpr(o Int32Value) Pred
- func (c Int32Col) IsNotDistinctFrom(v int32) Pred
- func (c Int32Col) IsNotDistinctFromExpr(o Int32Value) Pred
- func (c Int32Col) Least(others ...Int32Value) Int32Expr
- func (c Int32Col) Lt(v int32) Pred
- func (c Int32Col) Lte(v int32) Pred
- func (c Int32Col) Mod(v Int32Value) Int32Expr
- func (c Int32Col) Mul(v Int32Value) Int32Expr
- func (c Int32Col) Ne(v int32) Pred
- func (c Int32Col) Neg() Int32Expr
- func (c Int32Col) NotIn(vs ...int32) Pred
- func (c Int32Col) NotInArray(a Int32ArrayValue) Pred
- func (c Int32Col) NullIf(v int32) Int32Expr
- func (c Int32Col) Sub(v Int32Value) Int32Expr
- func (c Int32Col) Sum() Agg
- func (c Int32Col) To(v int32) Assignment
- type Int32Expr
- func ArrayPosition(list []string, v StringValue) Int32Expr
- func CastInt32(e Expr) Int32Expr
- func ConstInt32(n int32) Int32Expr
- func Int32(n int32) Int32Expr
- func Int32Of(e Expr) Int32Expr
- func Int32Ptr(p *int32) Int32Expr
- func NullInt32() Int32Expr
- func WidthBucket(v Int64Value, bounds []int64) Int32Expr
- func WidthBucketRange(v Float64Value, lo, hi float64, buckets int) Int32Expr
- func (e Int32Expr) Abs() Int32Expr
- func (e Int32Expr) Add(v Int32Value) Int32Expr
- func (e Int32Expr) Asc() Order
- func (e Int32Expr) Avg() Agg
- func (e Int32Expr) Between(lo, hi int32) Pred
- func (e Int32Expr) Coalesce(vs ...any) Int32Expr
- func (e Int32Expr) Desc() Order
- func (e Int32Expr) Div(v Int32Value) Int32Expr
- func (e Int32Expr) Eq(v int32) Pred
- func (e Int32Expr) EqExpr(o Int32Value) Pred
- func (e Int32Expr) Greatest(others ...Int32Value) Int32Expr
- func (e Int32Expr) Gt(v int32) Pred
- func (e Int32Expr) GtExpr(o Int32Value) Pred
- func (e Int32Expr) Gte(v int32) Pred
- func (e Int32Expr) GteExpr(o Int32Value) Pred
- func (e Int32Expr) In(vs ...int32) Pred
- func (e Int32Expr) InArray(a Int32ArrayValue) Pred
- func (e Int32Expr) IsDistinctFrom(v int32) Pred
- func (e Int32Expr) IsDistinctFromExpr(o Int32Value) Pred
- func (e Int32Expr) IsNotDistinctFrom(v int32) Pred
- func (e Int32Expr) IsNotDistinctFromExpr(o Int32Value) Pred
- func (e Int32Expr) IsNotNull() Pred
- func (e Int32Expr) IsNull() Pred
- func (e Int32Expr) Least(others ...Int32Value) Int32Expr
- func (e Int32Expr) Lt(v int32) Pred
- func (e Int32Expr) LtExpr(o Int32Value) Pred
- func (e Int32Expr) Lte(v int32) Pred
- func (e Int32Expr) LteExpr(o Int32Value) Pred
- func (e Int32Expr) Max() Agg
- func (e Int32Expr) Min() Agg
- func (e Int32Expr) Mod(v Int32Value) Int32Expr
- func (e Int32Expr) Mul(v Int32Value) Int32Expr
- func (e Int32Expr) Ne(v int32) Pred
- func (e Int32Expr) NeExpr(o Int32Value) Pred
- func (e Int32Expr) Neg() Int32Expr
- func (e Int32Expr) NotIn(vs ...int32) Pred
- func (e Int32Expr) NotInArray(a Int32ArrayValue) Pred
- func (e Int32Expr) NullIf(v int32) Int32Expr
- func (e Int32Expr) Sub(v Int32Value) Int32Expr
- func (e Int32Expr) Sum() Agg
- type Int32Value
- type Int64ArrayCase
- type Int64ArrayCol
- func (c Int64ArrayCol) Append(v int64) Assignment
- func (c Int64ArrayCol) At(i int) Int64Expr
- func (c Int64ArrayCol) ContainedBy(vs ...int64) Pred
- func (c Int64ArrayCol) ContainedByExpr(o Int64ArrayValue) Pred
- func (c Int64ArrayCol) ContainsAll(vs ...int64) Pred
- func (c Int64ArrayCol) ContainsAllExpr(o Int64ArrayValue) Pred
- func (c Int64ArrayCol) Has(v int64) Pred
- func (c Int64ArrayCol) Len() Int32Expr
- func (c Int64ArrayCol) Overlaps(vs ...int64) Pred
- func (c Int64ArrayCol) OverlapsExpr(o Int64ArrayValue) Pred
- func (c Int64ArrayCol) Remove(v int64) Assignment
- func (c Int64ArrayCol) To(vs []int64) Assignment
- func (c Int64ArrayCol) Unnest() Int64Expr
- func (c Int64ArrayCol) Without(v Int64Value) Int64ArrayExpr
- type Int64ArrayExpr
- func (e Int64ArrayExpr) At(i int) Int64Expr
- func (e Int64ArrayExpr) Coalesce(o Int64ArrayValue) Int64ArrayExpr
- func (e Int64ArrayExpr) ContainedByExpr(o Int64ArrayValue) Pred
- func (e Int64ArrayExpr) ContainsAllExpr(o Int64ArrayValue) Pred
- func (e Int64ArrayExpr) IsNotNull() Pred
- func (e Int64ArrayExpr) IsNull() Pred
- func (e Int64ArrayExpr) Len() Int32Expr
- func (e Int64ArrayExpr) NullIf(o Int64ArrayValue) Int64ArrayExpr
- func (e Int64ArrayExpr) OverlapsExpr(o Int64ArrayValue) Pred
- func (e Int64ArrayExpr) Unnest() Int64Expr
- func (e Int64ArrayExpr) Without(v Int64Value) Int64ArrayExpr
- type Int64ArrayValue
- type Int64Case
- type Int64Col
- func (c Int64Col) Abs() Int64Expr
- func (c Int64Col) Add(v Int64Value) Int64Expr
- func (c Int64Col) Avg() Agg
- func (c Int64Col) Between(lo, hi int64) Pred
- func (c Int64Col) Coalesce(vs ...any) Int64Expr
- func (c Int64Col) Div(v Int64Value) Int64Expr
- func (c Int64Col) Eq(v int64) Pred
- func (c Int64Col) Greatest(others ...Int64Value) Int64Expr
- func (c Int64Col) Gt(v int64) Pred
- func (c Int64Col) Gte(v int64) Pred
- func (c Int64Col) In(vs ...int64) Pred
- func (c Int64Col) InArray(a Int64ArrayValue) Pred
- func (c Int64Col) Increment(v int64) Assignment
- func (c Int64Col) IsDistinctFrom(v int64) Pred
- func (c Int64Col) IsDistinctFromExpr(o Int64Value) Pred
- func (c Int64Col) IsNotDistinctFrom(v int64) Pred
- func (c Int64Col) IsNotDistinctFromExpr(o Int64Value) Pred
- func (c Int64Col) Least(others ...Int64Value) Int64Expr
- func (c Int64Col) Lt(v int64) Pred
- func (c Int64Col) Lte(v int64) Pred
- func (c Int64Col) Mod(v Int64Value) Int64Expr
- func (c Int64Col) Mul(v Int64Value) Int64Expr
- func (c Int64Col) Ne(v int64) Pred
- func (c Int64Col) Neg() Int64Expr
- func (c Int64Col) NotIn(vs ...int64) Pred
- func (c Int64Col) NotInArray(a Int64ArrayValue) Pred
- func (c Int64Col) NullIf(v int64) Int64Expr
- func (c Int64Col) Sub(v Int64Value) Int64Expr
- func (c Int64Col) Sum() Agg
- func (c Int64Col) To(v int64) Assignment
- type Int64Expr
- func CastInt64(e Expr) Int64Expr
- func ConstInt64(n int64) Int64Expr
- func Epoch(t TimeValue) Int64Expr
- func GenerateSeries(start, stop Int64Value) Int64Expr
- func GenerateSeriesBy(start, stop, step Int64Value) Int64Expr
- func Int64(n int64) Int64Expr
- func Int64Of(e Expr) Int64Expr
- func Int64Ptr(p *int64) Int64Expr
- func NowEpoch() Int64Expr
- func NullInt64() Int64Expr
- func (e Int64Expr) Abs() Int64Expr
- func (e Int64Expr) Add(v Int64Value) Int64Expr
- func (e Int64Expr) Asc() Order
- func (e Int64Expr) Avg() Agg
- func (e Int64Expr) Between(lo, hi int64) Pred
- func (e Int64Expr) Coalesce(vs ...any) Int64Expr
- func (e Int64Expr) Desc() Order
- func (e Int64Expr) Div(v Int64Value) Int64Expr
- func (e Int64Expr) Eq(v int64) Pred
- func (e Int64Expr) EqExpr(o Int64Value) Pred
- func (e Int64Expr) Greatest(others ...Int64Value) Int64Expr
- func (e Int64Expr) Gt(v int64) Pred
- func (e Int64Expr) GtExpr(o Int64Value) Pred
- func (e Int64Expr) Gte(v int64) Pred
- func (e Int64Expr) GteExpr(o Int64Value) Pred
- func (e Int64Expr) In(vs ...int64) Pred
- func (e Int64Expr) InArray(a Int64ArrayValue) Pred
- func (e Int64Expr) IsDistinctFrom(v int64) Pred
- func (e Int64Expr) IsDistinctFromExpr(o Int64Value) Pred
- func (e Int64Expr) IsNotDistinctFrom(v int64) Pred
- func (e Int64Expr) IsNotDistinctFromExpr(o Int64Value) Pred
- func (e Int64Expr) IsNotNull() Pred
- func (e Int64Expr) IsNull() Pred
- func (e Int64Expr) Least(others ...Int64Value) Int64Expr
- func (e Int64Expr) Lt(v int64) Pred
- func (e Int64Expr) LtExpr(o Int64Value) Pred
- func (e Int64Expr) Lte(v int64) Pred
- func (e Int64Expr) LteExpr(o Int64Value) Pred
- func (e Int64Expr) Max() Agg
- func (e Int64Expr) Min() Agg
- func (e Int64Expr) Mod(v Int64Value) Int64Expr
- func (e Int64Expr) Mul(v Int64Value) Int64Expr
- func (e Int64Expr) Ne(v int64) Pred
- func (e Int64Expr) NeExpr(o Int64Value) Pred
- func (e Int64Expr) Neg() Int64Expr
- func (e Int64Expr) NotIn(vs ...int64) Pred
- func (e Int64Expr) NotInArray(a Int64ArrayValue) Pred
- func (e Int64Expr) NullIf(v int64) Int64Expr
- func (e Int64Expr) Sub(v Int64Value) Int64Expr
- func (e Int64Expr) Sum() Agg
- type Int64Range
- type Int64Value
- type Intercepted
- type Interceptor
- type IntervalCase
- type IntervalExpr
- func (e IntervalExpr) Add(o IntervalValue) IntervalExpr
- func (e IntervalExpr) Asc() Order
- func (e IntervalExpr) Avg() Agg
- func (e IntervalExpr) Desc() Order
- func (e IntervalExpr) Div(n NumberValue) IntervalExpr
- func (e IntervalExpr) EqExpr(o IntervalValue) Pred
- func (e IntervalExpr) GtExpr(o IntervalValue) Pred
- func (e IntervalExpr) GteExpr(o IntervalValue) Pred
- func (e IntervalExpr) IsDistinctFromExpr(o IntervalValue) Pred
- func (e IntervalExpr) IsNotDistinctFromExpr(o IntervalValue) Pred
- func (e IntervalExpr) IsNotNull() Pred
- func (e IntervalExpr) IsNull() Pred
- func (e IntervalExpr) LtExpr(o IntervalValue) Pred
- func (e IntervalExpr) LteExpr(o IntervalValue) Pred
- func (e IntervalExpr) Max() Agg
- func (e IntervalExpr) Min() Agg
- func (e IntervalExpr) Mul(n NumberValue) IntervalExpr
- func (e IntervalExpr) NeExpr(o IntervalValue) Pred
- func (e IntervalExpr) Neg() IntervalExpr
- func (e IntervalExpr) Seconds() Float64Expr
- func (e IntervalExpr) Sub(o IntervalValue) IntervalExpr
- func (e IntervalExpr) Sum() Agg
- type IntervalValue
- type Isolation
- type JSONBCase
- type JSONBCol
- func (c JSONBCol) ArrayElementsText() StringExpr
- func (c JSONBCol) Coalesce(v any) JSONBExpr
- func (c JSONBCol) Concat(o JSONBValue) JSONBExpr
- func (c JSONBCol) Contains(v any) Pred
- func (c JSONBCol) HasAllKeys(keys ...string) Pred
- func (c JSONBCol) HasAnyKey(keys ...string) Pred
- func (c JSONBCol) HasKey(key string) Pred
- func (c JSONBCol) IsDistinctFromExpr(o JSONBValue) Pred
- func (c JSONBCol) IsNotDistinctFromExpr(o JSONBValue) Pred
- func (c JSONBCol) Path(path ...string) DynExpr
- func (c JSONBCol) Set(path []string, v JSONBValue) JSONBExpr
- func (c JSONBCol) Text(path ...string) StringExpr
- func (c JSONBCol) To(v any) Assignment
- func (c JSONBCol) TypeOf() StringExpr
- type JSONBExpr
- func (e JSONBExpr) ArrayElementsText() StringExpr
- func (e JSONBExpr) Coalesce(v any) JSONBExpr
- func (e JSONBExpr) Concat(o JSONBValue) JSONBExpr
- func (e JSONBExpr) Contains(v any) Pred
- func (e JSONBExpr) ContainsExpr(o JSONBValue) Pred
- func (e JSONBExpr) HasKey(key string) Pred
- func (e JSONBExpr) IsDistinctFromExpr(o JSONBValue) Pred
- func (e JSONBExpr) IsNotDistinctFromExpr(o JSONBValue) Pred
- func (e JSONBExpr) IsNotNull() Pred
- func (e JSONBExpr) IsNull() Pred
- func (e JSONBExpr) Set(path []string, v JSONBValue) JSONBExpr
- func (e JSONBExpr) Text(path ...string) StringExpr
- func (e JSONBExpr) TypeOf() StringExpr
- type JSONBValue
- type JSONCodec
- type JSONPair
- type LoadStrategy
- type Loader
- type LocalCache
- type LockStrength
- type LockWait
- type Mutation
- type NumberValue
- type NumericArrayCase
- type NumericArrayExpr
- func (e NumericArrayExpr) At(i int) NumericExpr
- func (e NumericArrayExpr) Coalesce(o NumericArrayValue) NumericArrayExpr
- func (e NumericArrayExpr) ContainedByExpr(o NumericArrayValue) Pred
- func (e NumericArrayExpr) ContainsAllExpr(o NumericArrayValue) Pred
- func (e NumericArrayExpr) IsNotNull() Pred
- func (e NumericArrayExpr) IsNull() Pred
- func (e NumericArrayExpr) Len() Int32Expr
- func (e NumericArrayExpr) NullIf(o NumericArrayValue) NumericArrayExpr
- func (e NumericArrayExpr) OverlapsExpr(o NumericArrayValue) Pred
- func (e NumericArrayExpr) Unnest() NumericExpr
- func (e NumericArrayExpr) Without(v NumericValue) NumericArrayExpr
- type NumericArrayValue
- type NumericCase
- type NumericExpr
- func (e NumericExpr) Abs() NumericExpr
- func (e NumericExpr) Add(v NumericValue) NumericExpr
- func (e NumericExpr) Asc() Order
- func (e NumericExpr) Avg() Agg
- func (e NumericExpr) Between(lo, hi any) Pred
- func (e NumericExpr) Ceil() NumericExpr
- func (e NumericExpr) Coalesce(vs ...any) NumericExpr
- func (e NumericExpr) Desc() Order
- func (e NumericExpr) Div(v NumericValue) NumericExpr
- func (e NumericExpr) Eq(v any) Pred
- func (e NumericExpr) EqExpr(o NumericValue) Pred
- func (e NumericExpr) Exp() NumericExpr
- func (e NumericExpr) Floor() NumericExpr
- func (e NumericExpr) Greatest(others ...NumericValue) NumericExpr
- func (e NumericExpr) Gt(v any) Pred
- func (e NumericExpr) GtExpr(o NumericValue) Pred
- func (e NumericExpr) Gte(v any) Pred
- func (e NumericExpr) GteExpr(o NumericValue) Pred
- func (e NumericExpr) InArray(a NumericArrayValue) Pred
- func (e NumericExpr) IsDistinctFrom(v any) Pred
- func (e NumericExpr) IsDistinctFromExpr(o NumericValue) Pred
- func (e NumericExpr) IsNotDistinctFrom(v any) Pred
- func (e NumericExpr) IsNotDistinctFromExpr(o NumericValue) Pred
- func (e NumericExpr) IsNotNull() Pred
- func (e NumericExpr) IsNull() Pred
- func (e NumericExpr) Least(others ...NumericValue) NumericExpr
- func (e NumericExpr) Ln() NumericExpr
- func (e NumericExpr) Lt(v any) Pred
- func (e NumericExpr) LtExpr(o NumericValue) Pred
- func (e NumericExpr) Lte(v any) Pred
- func (e NumericExpr) LteExpr(o NumericValue) Pred
- func (e NumericExpr) Max() Agg
- func (e NumericExpr) Min() Agg
- func (e NumericExpr) Mod(v NumericValue) NumericExpr
- func (e NumericExpr) Mul(v NumericValue) NumericExpr
- func (e NumericExpr) Ne(v any) Pred
- func (e NumericExpr) NeExpr(o NumericValue) Pred
- func (e NumericExpr) Neg() NumericExpr
- func (e NumericExpr) NotInArray(a NumericArrayValue) Pred
- func (e NumericExpr) NullIf(v any) NumericExpr
- func (e NumericExpr) Round() NumericExpr
- func (e NumericExpr) RoundTo(places int) NumericExpr
- func (e NumericExpr) Sub(v NumericValue) NumericExpr
- func (e NumericExpr) Sum() Agg
- type NumericValue
- type Observer
- type Op
- type Order
- type Page
- type PageRequest
- type Phase
- type Plan
- type PlanCache
- type PoolState
- type Position
- type PreAggStrategy
- type Pred
- type Projection
- type Query
- func (q *Query[T]) All(ctx context.Context, db *DB) ([]T, error)
- func (q *Query[T]) AllTenants() *Query[T]
- func (q *Query[T]) Any(ps ...Pred) Pred
- func (q *Query[T]) Cache(opts ...CacheOption) *Query[T]
- func (q *Query[T]) Clone() *Query[T]
- func (q *Query[T]) Compute(col Column, e Expr) *Query[T]
- func (q *Query[T]) Count(ctx context.Context, db *DB) (int64, error)
- func (q *Query[T]) CountEstimate(ctx context.Context, db *DB, exactUpTo int64) (RowCount, error)
- func (q *Query[T]) CountUpTo(ctx context.Context, db *DB, limit int64) (int64, error)
- func (q *Query[T]) Cube(es ...Expr) *Query[T]
- func (q *Query[T]) CustomPlan() *Query[T]
- func (q *Query[T]) Delete(ctx context.Context, db *DB) (int64, error)
- func (q *Query[T]) Distinct() *Query[T]
- func (q *Query[T]) DistinctOn(es ...Expr) *Query[T]
- func (q *Query[T]) Each(ctx context.Context, db *DB, size int, ...) error
- func (q *Query[T]) Exists(ctx context.Context, db *DB) (bool, error)
- func (q *Query[T]) Explain(ctx context.Context, db *DB, opts ExplainOptions) (*Plan, error)
- func (q *Query[T]) First(ctx context.Context, db *DB) (T, error)
- func (q *Query[T]) Group(es ...Expr) *Query[T]
- func (q *Query[T]) GroupingSets(sets ...[]Expr) *Query[T]
- func (q *Query[T]) Having(ps ...Pred) *Query[T]
- func (q *Query[T]) Join(t interface{ ... }, on ...Pred) *Query[T]
- func (q *Query[T]) JoinLateral(sub interface{ ... }) *Query[T]
- func (q *Query[T]) LeftJoin(t interface{ ... }, on ...Pred) *Query[T]
- func (q *Query[T]) LeftJoinLateral(sub interface{ ... }) *Query[T]
- func (q *Query[T]) Limit(n int) *Query[T]
- func (q *Query[T]) List(ctx context.Context, db *DB) ([]T, error)
- func (q *Query[T]) Load(ls ...Loader) *Query[T]
- func (q *Query[T]) LoadStrategy(s LoadStrategy) *Query[T]
- func (q *Query[T]) Lock(strength LockStrength, wait ...LockWait) *Query[T]
- func (q *Query[T]) None(ps ...Pred) Pred
- func (q *Query[T]) Offset(n int) *Query[T]
- func (q *Query[T]) Omit(cols ...Column) *Query[T]
- func (q *Query[T]) One(ctx context.Context, db *DB) (T, error)
- func (q *Query[T]) Order(os ...Order) *Query[T]
- func (q *Query[T]) Page(ctx context.Context, db *DB, p PageRequest) (Page[T], error)
- func (q *Query[T]) Pick(es ...Expr) *Query[T]
- func (q *Query[T]) Rollup(es ...Expr) *Query[T]
- func (q *Query[T]) TableRef() *Ref
- func (q *Query[T]) Timeout(d time.Duration) *Query[T]
- func (q *Query[T]) ToSQL(ctx context.Context, db *DB) (string, []any, error)
- func (q *Query[T]) Update(ctx context.Context, db *DB, sets ...Assignment) (int64, error)
- func (q *Query[T]) UpdateReturning(ctx context.Context, db *DB, sets ...Assignment) ([]T, error)
- func (q *Query[T]) Where(ps ...Pred) *Query[T]
- func (q *Query[T]) Window(name string, w Window) *Query[T]
- func (q *Query[T]) With(ctes ...*CTETable) *Query[T]
- type QueryEvent
- type RawExpr
- type ReadFilter
- type Ref
- type Registry
- type Repeat
- type ReplicaConfig
- type ReplicaStats
- type Row
- type RowCount
- type RowsTable
- type SearchMode
- type SearchOption
- type Shape
- type SharedCache
- type SlotHolder
- type SlowWait
- type SlowWaitConfig
- type Statement
- type StringArrayCase
- type StringArrayCol
- func (c StringArrayCol) Append(v string) Assignment
- func (c StringArrayCol) At(i int) StringExpr
- func (c StringArrayCol) ContainedBy(vs ...string) Pred
- func (c StringArrayCol) ContainedByExpr(o StringArrayValue) Pred
- func (c StringArrayCol) ContainsAll(vs ...string) Pred
- func (c StringArrayCol) ContainsAllExpr(o StringArrayValue) Pred
- func (c StringArrayCol) Has(v string) Pred
- func (c StringArrayCol) Len() Int32Expr
- func (c StringArrayCol) Overlaps(vs ...string) Pred
- func (c StringArrayCol) OverlapsExpr(o StringArrayValue) Pred
- func (c StringArrayCol) Remove(v string) Assignment
- func (c StringArrayCol) To(vs []string) Assignment
- func (c StringArrayCol) Unnest() StringExpr
- func (c StringArrayCol) Without(v StringValue) StringArrayExpr
- type StringArrayExpr
- func (e StringArrayExpr) At(i int) StringExpr
- func (e StringArrayExpr) Coalesce(o StringArrayValue) StringArrayExpr
- func (e StringArrayExpr) ContainedByExpr(o StringArrayValue) Pred
- func (e StringArrayExpr) ContainsAllExpr(o StringArrayValue) Pred
- func (e StringArrayExpr) IsNotNull() Pred
- func (e StringArrayExpr) IsNull() Pred
- func (e StringArrayExpr) Len() Int32Expr
- func (e StringArrayExpr) NullIf(o StringArrayValue) StringArrayExpr
- func (e StringArrayExpr) OverlapsExpr(o StringArrayValue) Pred
- func (e StringArrayExpr) Unnest() StringExpr
- func (e StringArrayExpr) Without(v StringValue) StringArrayExpr
- type StringArrayValue
- type StringCase
- type StringCol
- func (c StringCol) Between(lo, hi string) Pred
- func (c StringCol) Cat(vs ...StringValue) StringExpr
- func (c StringCol) Coalesce(vs ...any) StringExpr
- func (c StringCol) Collate(name string) StringExpr
- func (c StringCol) Contains(s string) Pred
- func (c StringCol) ContainsFold(s string) Pred
- func (c StringCol) Eq(v string) Pred
- func (c StringCol) Greatest(others ...StringValue) StringExpr
- func (c StringCol) Gt(v string) Pred
- func (c StringCol) Gte(v string) Pred
- func (c StringCol) HasPrefix(s string) Pred
- func (c StringCol) HasPrefixFold(s string) Pred
- func (c StringCol) HasSuffix(s string) Pred
- func (c StringCol) ILike(pattern string) Pred
- func (c StringCol) ILikeExpr(pattern StringValue) Pred
- func (c StringCol) In(vs ...string) Pred
- func (c StringCol) InArray(a StringArrayValue) Pred
- func (c StringCol) IsDistinctFrom(v string) Pred
- func (c StringCol) IsDistinctFromExpr(o StringValue) Pred
- func (c StringCol) IsNotDistinctFrom(v string) Pred
- func (c StringCol) IsNotDistinctFromExpr(o StringValue) Pred
- func (c StringCol) LTrim() StringExpr
- func (c StringCol) LTrimChars(chars string) StringExpr
- func (c StringCol) Least(others ...StringValue) StringExpr
- func (c StringCol) Left(n int) StringExpr
- func (c StringCol) Length() Int32Expr
- func (c StringCol) Like(pattern string) Pred
- func (c StringCol) LikeExpr(pattern StringValue) Pred
- func (c StringCol) Lower() StringExpr
- func (c StringCol) Lt(v string) Pred
- func (c StringCol) Lte(v string) Pred
- func (c StringCol) MD5() StringExpr
- func (c StringCol) Match(re string) Pred
- func (c StringCol) MatchFold(re string) Pred
- func (c StringCol) Ne(v string) Pred
- func (c StringCol) NotIn(vs ...string) Pred
- func (c StringCol) NotInArray(a StringArrayValue) Pred
- func (c StringCol) NullIf(v string) StringExpr
- func (c StringCol) RTrim() StringExpr
- func (c StringCol) RTrimChars(chars string) StringExpr
- func (c StringCol) RegexpReplace(pattern, replacement, flags string) StringExpr
- func (c StringCol) Replace(from, to string) StringExpr
- func (c StringCol) Right(n int) StringExpr
- func (c StringCol) SplitPart(delim string, n int) StringExpr
- func (c StringCol) SplitPartExpr(delim StringValue, n Int32Value) StringExpr
- func (c StringCol) Substr(start, count int) StringExpr
- func (c StringCol) SubstrFrom(start int) StringExpr
- func (c StringCol) To(v string) Assignment
- func (c StringCol) Trim() StringExpr
- func (c StringCol) TrimChars(chars string) StringExpr
- func (c StringCol) Upper() StringExpr
- type StringExpr
- func CastText(e Expr) StringExpr
- func Concat(args ...any) StringExpr
- func ConcatWS(sep string, args ...any) StringExpr
- func ConstText(s string) StringExpr
- func GenRandomUUID() StringExpr
- func NullText() StringExpr
- func StringOf(e Expr) StringExpr
- func Text(s string) StringExpr
- func TextPtr(p *string) StringExpr
- func (e StringExpr) Asc() Order
- func (e StringExpr) Between(lo, hi string) Pred
- func (e StringExpr) Cat(vs ...StringValue) StringExpr
- func (e StringExpr) Coalesce(vs ...any) StringExpr
- func (e StringExpr) Collate(name string) StringExpr
- func (e StringExpr) Contains(s string) Pred
- func (e StringExpr) ContainsFold(s string) Pred
- func (e StringExpr) Desc() Order
- func (e StringExpr) Eq(v string) Pred
- func (e StringExpr) EqExpr(o StringValue) Pred
- func (e StringExpr) Greatest(others ...StringValue) StringExpr
- func (e StringExpr) Gt(v string) Pred
- func (e StringExpr) GtExpr(o StringValue) Pred
- func (e StringExpr) Gte(v string) Pred
- func (e StringExpr) GteExpr(o StringValue) Pred
- func (e StringExpr) HasPrefix(s string) Pred
- func (e StringExpr) HasPrefixFold(s string) Pred
- func (e StringExpr) HasSuffix(s string) Pred
- func (e StringExpr) ILike(p string) Pred
- func (e StringExpr) ILikeExpr(p StringValue) Pred
- func (e StringExpr) In(vs ...string) Pred
- func (e StringExpr) InArray(a StringArrayValue) Pred
- func (e StringExpr) IsDistinctFrom(v string) Pred
- func (e StringExpr) IsDistinctFromExpr(o StringValue) Pred
- func (e StringExpr) IsNotDistinctFrom(v string) Pred
- func (e StringExpr) IsNotDistinctFromExpr(o StringValue) Pred
- func (e StringExpr) IsNotNull() Pred
- func (e StringExpr) IsNull() Pred
- func (e StringExpr) LTrim() StringExpr
- func (e StringExpr) LTrimChars(chars string) StringExpr
- func (e StringExpr) Least(others ...StringValue) StringExpr
- func (e StringExpr) Left(n int) StringExpr
- func (e StringExpr) Length() Int32Expr
- func (e StringExpr) Like(p string) Pred
- func (e StringExpr) LikeExpr(p StringValue) Pred
- func (e StringExpr) Lower() StringExpr
- func (e StringExpr) Lt(v string) Pred
- func (e StringExpr) LtExpr(o StringValue) Pred
- func (e StringExpr) Lte(v string) Pred
- func (e StringExpr) LteExpr(o StringValue) Pred
- func (e StringExpr) MD5() StringExpr
- func (e StringExpr) Match(re string) Pred
- func (e StringExpr) MatchFold(re string) Pred
- func (e StringExpr) Max() Agg
- func (e StringExpr) Min() Agg
- func (e StringExpr) Ne(v string) Pred
- func (e StringExpr) NeExpr(o StringValue) Pred
- func (e StringExpr) NotIn(vs ...string) Pred
- func (e StringExpr) NotInArray(a StringArrayValue) Pred
- func (e StringExpr) NullIf(v string) StringExpr
- func (e StringExpr) RTrim() StringExpr
- func (e StringExpr) RTrimChars(chars string) StringExpr
- func (e StringExpr) RegexpReplace(pattern, replacement, flags string) StringExpr
- func (e StringExpr) Replace(from, to string) StringExpr
- func (e StringExpr) Right(n int) StringExpr
- func (e StringExpr) SplitPart(delim string, n int) StringExpr
- func (e StringExpr) SplitPartExpr(delim StringValue, n Int32Value) StringExpr
- func (e StringExpr) Substr(start, count int) StringExpr
- func (e StringExpr) SubstrFrom(start int) StringExpr
- func (e StringExpr) Trim() StringExpr
- func (e StringExpr) TrimChars(chars string) StringExpr
- func (e StringExpr) Upper() StringExpr
- type StringValue
- type TSVectorCol
- type Table
- type TimeArrayCase
- type TimeArrayExpr
- func (e TimeArrayExpr) At(i int) TimeExpr
- func (e TimeArrayExpr) Coalesce(o TimeArrayValue) TimeArrayExpr
- func (e TimeArrayExpr) ContainedByExpr(o TimeArrayValue) Pred
- func (e TimeArrayExpr) ContainsAllExpr(o TimeArrayValue) Pred
- func (e TimeArrayExpr) IsNotNull() Pred
- func (e TimeArrayExpr) IsNull() Pred
- func (e TimeArrayExpr) Len() Int32Expr
- func (e TimeArrayExpr) NullIf(o TimeArrayValue) TimeArrayExpr
- func (e TimeArrayExpr) OverlapsExpr(o TimeArrayValue) Pred
- func (e TimeArrayExpr) Unnest() TimeExpr
- func (e TimeArrayExpr) Without(v TimeValue) TimeArrayExpr
- type TimeArrayValue
- type TimeCase
- type TimeCol
- func (c TimeCol) Add(i IntervalValue) TimeExpr
- func (c TimeCol) AtTimeZone(zone StringValue) TimeExpr
- func (c TimeCol) Between(lo, hi time.Time) Pred
- func (c TimeCol) Coalesce(vs ...any) TimeExpr
- func (c TimeCol) Eq(v time.Time) Pred
- func (c TimeCol) Greatest(others ...TimeValue) TimeExpr
- func (c TimeCol) Gt(v time.Time) Pred
- func (c TimeCol) Gte(v time.Time) Pred
- func (c TimeCol) InArray(a TimeArrayValue) Pred
- func (c TimeCol) IsDistinctFrom(v time.Time) Pred
- func (c TimeCol) IsDistinctFromExpr(o TimeValue) Pred
- func (c TimeCol) IsNotDistinctFrom(v time.Time) Pred
- func (c TimeCol) IsNotDistinctFromExpr(o TimeValue) Pred
- func (c TimeCol) Least(others ...TimeValue) TimeExpr
- func (c TimeCol) Lt(v time.Time) Pred
- func (c TimeCol) Lte(v time.Time) Pred
- func (c TimeCol) Ne(v time.Time) Pred
- func (c TimeCol) NotInArray(a TimeArrayValue) Pred
- func (c TimeCol) Since(u TimeValue) IntervalExpr
- func (c TimeCol) Sub(i IntervalValue) TimeExpr
- func (c TimeCol) To(v time.Time) Assignment
- func (c TimeCol) ToChar(format string) StringExpr
- type TimeExpr
- func CastTime(e Expr) TimeExpr
- func DateTrunc(field string, t TimeValue) TimeExpr
- func GenerateTimeSeries(start, stop TimeValue, step string) TimeExpr
- func Now() TimeExpr
- func NullTime() TimeExpr
- func Time(t time.Time) TimeExpr
- func TimeOf(e Expr) TimeExpr
- func TimePtr(p *time.Time) TimeExpr
- func ToTimestamp(seconds NumberValue) TimeExpr
- func (e TimeExpr) Add(i IntervalValue) TimeExpr
- func (e TimeExpr) Asc() Order
- func (e TimeExpr) AtTimeZone(zone StringValue) TimeExpr
- func (e TimeExpr) Between(lo, hi time.Time) Pred
- func (e TimeExpr) Coalesce(vs ...any) TimeExpr
- func (e TimeExpr) Desc() Order
- func (e TimeExpr) Eq(v time.Time) Pred
- func (e TimeExpr) EqExpr(o TimeValue) Pred
- func (e TimeExpr) Greatest(others ...TimeValue) TimeExpr
- func (e TimeExpr) Gt(v time.Time) Pred
- func (e TimeExpr) GtExpr(o TimeValue) Pred
- func (e TimeExpr) Gte(v time.Time) Pred
- func (e TimeExpr) GteExpr(o TimeValue) Pred
- func (e TimeExpr) InArray(a TimeArrayValue) Pred
- func (e TimeExpr) IsDistinctFrom(v time.Time) Pred
- func (e TimeExpr) IsDistinctFromExpr(o TimeValue) Pred
- func (e TimeExpr) IsNotDistinctFrom(v time.Time) Pred
- func (e TimeExpr) IsNotDistinctFromExpr(o TimeValue) Pred
- func (e TimeExpr) IsNotNull() Pred
- func (e TimeExpr) IsNull() Pred
- func (e TimeExpr) Least(others ...TimeValue) TimeExpr
- func (e TimeExpr) Lt(v time.Time) Pred
- func (e TimeExpr) LtExpr(o TimeValue) Pred
- func (e TimeExpr) Lte(v time.Time) Pred
- func (e TimeExpr) LteExpr(o TimeValue) Pred
- func (e TimeExpr) Max() Agg
- func (e TimeExpr) Min() Agg
- func (e TimeExpr) Ne(v time.Time) Pred
- func (e TimeExpr) NeExpr(o TimeValue) Pred
- func (e TimeExpr) NotInArray(a TimeArrayValue) Pred
- func (e TimeExpr) Since(u TimeValue) IntervalExpr
- func (e TimeExpr) Sub(i IntervalValue) TimeExpr
- func (e TimeExpr) ToChar(format string) StringExpr
- type TimeValue
- type TupleExpr
- func (w TupleExpr) Eq(o TupleExpr) Pred
- func (w TupleExpr) Gt(o TupleExpr) Pred
- func (w TupleExpr) Gte(o TupleExpr) Pred
- func (w TupleExpr) InUnnest(arrays ...Expr) Pred
- func (w TupleExpr) IsDistinctFrom(o TupleExpr) Pred
- func (w TupleExpr) IsNotDistinctFrom(o TupleExpr) Pred
- func (w TupleExpr) Lt(o TupleExpr) Pred
- func (w TupleExpr) Lte(o TupleExpr) Pred
- func (w TupleExpr) Ne(o TupleExpr) Pred
- type TxInfo
- type TxOption
- type ValueType
- type WaitKind
- type Window
Examples ¶
- Package (Metadata)
- Package (Migrations)
- Package (ModelHooks)
- Package (NullZero)
- Package (Operators)
- Package (QuickStart)
- Package (RelationFilters)
- Package (RelationKeys)
- Package (SharedParameters)
- Package (TenantErrors)
- Agg.OverWindow
- Batch
- CTE
- CommitPosition
- Conflict.Set
- DB.DataVersions
- DB.Intercept
- Detached
- DynInto
- DynQuery.Compute
- DynQuery.Explain
- DynQuery.Page
- DynQuery.Rollup
- DynQuery.Stream
- DynQuery.With
- DynRel.PreAggregate
- DynUnionAll
- Exists
- Handle.Copy
- Handle.Create
- Handle.CreateMany
- Handle.CreateOrSkip
- Handle.DeleteRow
- Handle.InsertFrom
- Handle.Save
- Handle.Save (Columns)
- Handle.SaveExcept
- Handle.Upsert
- Into
- IsUnique
- JoinTx
- LockKey
- MarkChanged
- Observer
- OnCreate
- OnReplica
- OnUpdate
- OnWrite
- Open
- Open (DataVersions)
- Open (Pool)
- Open (Replicas)
- Query.AllTenants
- Query.Compute
- Query.Count
- Query.CountEstimate
- Query.CustomPlan
- Query.Delete
- Query.DistinctOn
- Query.Each
- Query.Explain
- Query.GroupingSets
- Query.Join
- Query.LeftJoinLateral
- Query.Load
- Query.Load (Limit)
- Query.Load (Pick)
- Query.Lock
- Query.Omit
- Query.One
- Query.Page
- Query.Rollup
- Query.Timeout
- Query.ToSQL
- Query.Update
- Query.Window
- QueryRows
- Raw
- Read
- ReadValue
- RecursiveCTE
- Registry
- ReplicaTx
- RowNumber
- Savepoint
- TSVectorCol.Match
- TenantKeyOf
- UnionAll
- Unnest
- When
- Write
- WriteValue
Constants ¶
const ( ForUpdate LockStrength = "FOR UPDATE" ForNoKeyUpdate LockStrength = "FOR NO KEY UPDATE" Wait LockWait = "" NoWait LockWait = " NOWAIT" SkipLocked LockWait = " SKIP LOCKED" )
const DefaultCacheBytes = 64 << 20
DefaultCacheBytes is the size of the MemoryCache the result cache keeps when CacheConfig.Local is nil.
Variables ¶
var ( // UnboundedPreceding is the first row of the partition. UnboundedPreceding = FrameBound{/* contains filtered or unexported fields */} // CurrentRow is the current row (in Range and Groups frames, its first or last peer). CurrentRow = FrameBound{/* contains filtered or unexported fields */} // UnboundedFollowing is the last row of the partition. UnboundedFollowing = FrameBound{/* contains filtered or unexported fields */} )
var ( EmptyJSONObject = []byte("{}") EmptyJSONArray = []byte("[]") )
EmptyJSONObject and EmptyJSONArray are written for a nil map or slice in a non-pointer jsonb field, so the column holds {} or [] rather than NULL or JSON null.
var ( // ErrNotFound is returned by One, First and Get when no row matches. ErrNotFound = errors.New("keel: no rows") // ErrNotSingular is returned by One when more than one row matches. ErrNotSingular = errors.New("keel: more than one row") // ErrConflict is matched by a *ConflictError: Save found a different version. ErrConflict = errors.New("keel: version conflict") // ErrNoScope is returned for a statement on a tenant table without a tenant or system scope. ErrNoScope = errors.New("keel: no tenant or system scope on the context") // ErrAllTenantsRequired is returned for a statement on a tenant table under a system scope // that did not declare AllTenants. ErrAllTenantsRequired = errors.New("keel: system scope statement on a tenant table must declare AllTenants") // ErrSystemScopeRequired is returned when AllTenants is used without a system scope. ErrSystemScopeRequired = errors.New("keel: AllTenants requires a system scope") // ErrTenantMismatch is returned when a row's tenant columns differ from the scope's. ErrTenantMismatch = errors.New("keel: row belongs to a different tenant than the scope") // ErrUnbounded is returned by List without a Limit. ErrUnbounded = errors.New("keel: List needs a Limit; use Page, or All for a set known to be small") // ErrReadOnly is returned by a write inside a read-only transaction. ErrReadOnly = errors.New("keel: write inside a read-only transaction") // ErrScopeMismatch is returned by a statement, Read, Write or Savepoint that would join the // transaction on the context under another scope than the one the transaction began with. Run // such work in its own transaction with Detached. ErrScopeMismatch = errors.New("keel: the context's scope differs from its transaction's; use keel.Detached for a transaction of its own") // ErrWaitLimit is returned by work that waited Config.MaxWait for a tenant slot or a pool // connection without getting one. ErrWaitLimit = errors.New("keel: waited longer than Config.MaxWait") )
var ErrBudgetExceeded = errors.New("keel: budget exceeded")
ErrBudgetExceeded is matched by a *BudgetError: work under a Budget went past one of its limits.
Functions ¶
func AfterCommit ¶
AfterCommit runs fn after the transaction on ctx commits; it is dropped if the transaction rolls back. Outside a transaction fn runs at once.
func Alias ¶
Alias returns a handle that references the same table as ref under its own alias, for self-joins and correlated subqueries. Generated As methods call it. Aliases are memoised, so alias names should be constants.
func Batch ¶
Batch runs independent reads in one round trip. The main statements of every item are sent together, in the transaction on the context, else in an implicit transaction when Config.OnBegin is set (BEGIN, the OnBegin statements, the items' statements and, when no relation loads follow, COMMIT in one round trip), else outside a transaction. The to-many relation loads of all items follow, one round trip per level.
Each item keeps its terminal's checks: scope errors, interceptors, AfterLoad hooks, ErrUnbounded for a list without a Limit, ErrNotFound and ErrNotSingular for a single-row item. Batch returns the first error, and fills the destinations only when every item succeeded. The shortest Timeout among the items bounds the whole batch.
var rows []*shipment.Shipment
var total int64
var cust *customer.Customer
err := keel.Batch(ctx, db,
keel.ListInto(s.Where(open).Order(s.CreatedAt.Desc()).Limit(50), &rows),
keel.CountInto(s.Where(open), &total),
keel.OneInto(c.Where(c.ID.Eq(id)), &cust),
)
Example ¶
package main
import (
"context"
"fmt"
"log"
"github.com/emoss08/keel"
"github.com/emoss08/keel/internal/testmodels/fleet"
"github.com/emoss08/keel/internal/testmodels/fleet/fleetdb"
"github.com/emoss08/keel/internal/testmodels/ids"
)
// appDB is the application's database, opened once at startup (see ExampleOpen).
var appDB *keel.DB
// scope is what the application's authentication middleware puts on a request context.
type scope struct {
OrganizationID ids.ID
BusinessUnitID ids.ID
SystemReason string
}
type scopeKey struct{}
func withTenant(ctx context.Context, org, bu ids.ID) context.Context {
return context.WithValue(ctx, scopeKey{}, scope{OrganizationID: org, BusinessUnitID: bu})
}
// requestContext returns the context of a request authenticated for one tenant.
func requestContext() context.Context {
return withTenant(context.Background(), "org_acme", "bu_dallas")
}
func main() {
ctx, db := requestContext(), appDB
type statusCount struct {
Status fleet.Status
Loads int64
}
s, c := fleetdb.Shipments, fleetdb.Customers
open := s.Where(s.Status.In(fleet.StatusNew, fleet.StatusAssigned))
var rows []*fleet.Shipment
var total int64
var byStatus []statusCount
var cust *fleet.Customer
err := keel.Batch(ctx, db,
keel.ListInto(open.Clone().Order(s.CreatedAt.Desc()).Limit(50), &rows),
keel.CountInto(open.Clone(), &total),
keel.IntoSlice(s.Group(s.Status).Pick(s.Status, keel.Count()), &byStatus),
keel.OneInto(c.Where(c.ID.Eq("cus_01")), &cust),
)
if err != nil {
log.Fatal(err)
}
fmt.Println(len(rows), total, len(byStatus), cust.Name)
}
Output:
func Budget ¶
func Budget(ctx context.Context, opts ...BudgetOption) context.Context
Budget returns a context that counts the round trips, statements and repeated statement texts of the work keel does under it, every terminal, transaction and goroutine included, and limits them as opts say. A budget under another one counts against both. Without limits it only counts, for BudgetReportFrom. A statement text counts once per round trip it is sent in, so the chunks of one CreateMany, which share a text and a round trip, are not repeats. It is safe for concurrent use.
func ColumnOf ¶
ColumnOf returns the table and the column index of a generated column handle, such as fleetdb.Shipments.CreatedAt, for packages that take columns in declarations. ok is false for an expression that is not a column, and for a column reached through a relation path (fleetdb.Shipments.Customer.Name), which belongs to a related row rather than to the table. A column of an alias made with As is the table's own column.
func ConstraintName ¶
ConstraintName returns the constraint a Postgres error names, or "".
func Exec ¶
Exec runs one SQL statement that keel does not render, such as DDL or a write to a bookkeeping table without a model, and returns the number of rows it affected. It joins the transaction on the context, so the statement commits or rolls back with the rest of it; outside a transaction it runs as any terminal does, in an implicit transaction when Config.OnBegin is set. Inside a Read, Postgres refuses a statement that writes.
Observers see the statement with the Op "exec". keel cannot see what the statement touches, so it bypasses scope checks, interceptors and hooks, always runs on the primary, records no commit position for OnReplica outside a Write, and changes no data version: call MarkChanged with the tables it writes. Bind values as $1, $2, … arguments; never format them into sql.
func ExecAll ¶
ExecAll runs one statement keel does not render on the primary and on every replica, outside any transaction on the context, and returns the rows it affected on all of them together. It is for statements about the servers rather than the data, which must reach whichever server a backend runs on: cancelling the backends tagged with an application_name, for one. Every server is tried; the errors of those that fail are joined. Observers see one statement per server with the Op "exec"; the replica's events carry its name.
func ExecBatch ¶
ExecBatch runs statements keel does not render in one round trip, in order, as Exec runs one: joining the transaction on the context, else in an implicit transaction when Config.OnBegin is set. It returns the rows each affected. The first failing statement aborts the round trip; inside a transaction the transaction is then failed, and outside one nothing commits. Observers see each statement with the Op "exec".
func Into ¶
Into runs a projection and decodes each row into R: the picked expressions fill R's exported fields in order. R may also be a single scalar type when one expression is picked.
type statusCount struct {
Status shipment.Status
Total int64
}
rows, err := keel.Into[statusCount](ctx, db, s.Group(s.Status).Pick(s.Status, keel.Count()))
Example ¶
package main
import (
"context"
"fmt"
"log"
"github.com/emoss08/keel"
"github.com/emoss08/keel/internal/testmodels/fleet"
"github.com/emoss08/keel/internal/testmodels/fleet/fleetdb"
"github.com/emoss08/keel/internal/testmodels/ids"
)
// appDB is the application's database, opened once at startup (see ExampleOpen).
var appDB *keel.DB
// scope is what the application's authentication middleware puts on a request context.
type scope struct {
OrganizationID ids.ID
BusinessUnitID ids.ID
SystemReason string
}
type scopeKey struct{}
func withTenant(ctx context.Context, org, bu ids.ID) context.Context {
return context.WithValue(ctx, scopeKey{}, scope{OrganizationID: org, BusinessUnitID: bu})
}
// requestContext returns the context of a request authenticated for one tenant.
func requestContext() context.Context {
return withTenant(context.Background(), "org_acme", "bu_dallas")
}
func main() {
ctx, db := requestContext(), appDB
s := fleetdb.Shipments
type statusTotal struct {
Status fleet.Status
Loads int64
Hazmat int64
Revenue fleet.Money
}
rows, err := keel.Into[statusTotal](ctx, db, s.
Group(s.Status).
Pick(s.Status, keel.Count(), keel.Count().Filter(s.Hazmat.IsTrue()), s.Total.Sum()).
Having(keel.Count().Gt(10)).
Order(keel.Count().Desc()))
if err != nil {
log.Fatal(err)
}
heaviest, err := keel.IntoOne[*int64](ctx, db, s.Pick(s.Weight.Max()).Where(s.Status.Eq(fleet.StatusNew)))
if err != nil {
log.Fatal(err)
}
fmt.Println(rows, heaviest)
}
Output:
func IsCheck ¶
IsCheck reports a check constraint violation, optionally on one of the named constraints.
func IsExclusion ¶
IsExclusion reports an exclusion constraint violation.
func IsForeignKey ¶
IsForeignKey reports a foreign key violation, optionally on one of the named constraints.
func IsRetryable ¶
IsRetryable reports a serialization failure or deadlock, which a retried transaction can resolve.
func IsUnique ¶
IsUnique reports a unique violation, optionally on one of the named constraints.
Example ¶
package main
import (
"context"
"fmt"
"github.com/emoss08/keel"
"github.com/emoss08/keel/internal/testmodels/fleet"
"github.com/emoss08/keel/internal/testmodels/fleet/fleetdb"
"github.com/emoss08/keel/internal/testmodels/ids"
)
// appDB is the application's database, opened once at startup (see ExampleOpen).
var appDB *keel.DB
// scope is what the application's authentication middleware puts on a request context.
type scope struct {
OrganizationID ids.ID
BusinessUnitID ids.ID
SystemReason string
}
type scopeKey struct{}
func withTenant(ctx context.Context, org, bu ids.ID) context.Context {
return context.WithValue(ctx, scopeKey{}, scope{OrganizationID: org, BusinessUnitID: bu})
}
// requestContext returns the context of a request authenticated for one tenant.
func requestContext() context.Context {
return withTenant(context.Background(), "org_acme", "bu_dallas")
}
func main() {
ctx, db := requestContext(), appDB
err := fleetdb.Shipments.Create(ctx, db, &fleet.Shipment{
CustomerID: "cus_01", ProNumber: "PRO-1001", Status: fleet.StatusNew,
})
switch {
case keel.IsUnique(err, "uq_shipments_pro"):
fmt.Println("PRO number already used")
case keel.IsForeignKey(err, "fk_shipments_customer"):
fmt.Println("no such customer")
case err != nil:
fmt.Println("SQLSTATE", keel.Code(err), "constraint", keel.ConstraintName(err))
}
}
Output:
func JoinTx ¶
JoinTx makes keel run inside a transaction another data layer began on conn, for an application moving to keel while it still writes through another library over the same pgx connection (Bun on pgx's stdlib driver, plain pgx). Every keel operation on the returned context runs on conn, in that transaction: keel sends no BEGIN, COMMIT or Config.OnBegin statements (the owner began the transaction and set its scope), Read and Write join it, Savepoint nests in it, and replicas are not used. Scope checks, tenancy, interceptors, read filters, hooks and observers apply as in a transaction keel began. The transaction's scope is ctx's: keel work under another scope returns ErrScopeMismatch, and a Detached Read or Write runs on keel's own pool instead.
The owner calls done once the transaction ends: done(true) after COMMIT runs the Committed hooks and AfterCommit callbacks and hands the tables the transaction changed to the data versions; done(false) after ROLLBACK drops them. Calls after the first do nothing, and keel refuses to run on the context afterwards.
tx, err := bunDB.BeginTx(ctx, nil) // on a pgx connection
ctx, done := keel.JoinTx(ctx, db, conn)
err = keel.Write(ctx, db, func(ctx context.Context) error { … })
… tx.Commit() …
done(err == nil)
conn is used by one goroutine at a time, as any transaction is.
Example ¶
package main
import (
"context"
"log"
"github.com/emoss08/keel"
"github.com/emoss08/keel/internal/testmodels/fleet"
"github.com/emoss08/keel/internal/testmodels/fleet/fleetdb"
"github.com/emoss08/keel/internal/testmodels/ids"
)
// appDB is the application's database, opened once at startup (see ExampleOpen).
var appDB *keel.DB
// scope is what the application's authentication middleware puts on a request context.
type scope struct {
OrganizationID ids.ID
BusinessUnitID ids.ID
SystemReason string
}
type scopeKey struct{}
func withTenant(ctx context.Context, org, bu ids.ID) context.Context {
return context.WithValue(ctx, scopeKey{}, scope{OrganizationID: org, BusinessUnitID: bu})
}
// requestContext returns the context of a request authenticated for one tenant.
func requestContext() context.Context {
return withTenant(context.Background(), "org_acme", "bu_dallas")
}
func main() {
ctx, db := requestContext(), appDB
conn, err := db.Pool().Acquire(ctx)
if err != nil {
log.Fatal(err)
}
defer conn.Release()
tx, err := conn.Begin(ctx)
if err != nil {
log.Fatal(err)
}
kctx, done := keel.JoinTx(ctx, db, conn.Conn())
err = fleetdb.Customers.Create(kctx, db, &fleet.Customer{Code: "ACME", Name: "Acme"})
if err == nil {
_, err = tx.Exec(ctx, "UPDATE plants SET name = upper(name)")
}
if err != nil {
_ = tx.Rollback(ctx)
done(false)
log.Fatal(err)
}
err = tx.Commit(ctx)
done(err == nil)
if err != nil {
log.Fatal(err)
}
}
Output:
func LockKey ¶
LockKey takes a transaction-scoped advisory lock on key, waiting for it, so work without a row to lock is serialised across processes. It needs a transaction; the lock is released when the transaction ends.
Example ¶
package main
import (
"context"
"fmt"
"log"
"github.com/emoss08/keel"
"github.com/emoss08/keel/internal/testmodels/ids"
)
// appDB is the application's database, opened once at startup (see ExampleOpen).
var appDB *keel.DB
// scope is what the application's authentication middleware puts on a request context.
type scope struct {
OrganizationID ids.ID
BusinessUnitID ids.ID
SystemReason string
}
type scopeKey struct{}
func withTenant(ctx context.Context, org, bu ids.ID) context.Context {
return context.WithValue(ctx, scopeKey{}, scope{OrganizationID: org, BusinessUnitID: bu})
}
// requestContext returns the context of a request authenticated for one tenant.
func requestContext() context.Context {
return withTenant(context.Background(), "org_acme", "bu_dallas")
}
func main() {
ctx, db := requestContext(), appDB
err := keel.Write(ctx, db, func(ctx context.Context) error {
if err := keel.LockKey(ctx, db, "invoice-run:org_acme"); err != nil {
return err
}
return nil
})
if err != nil {
log.Fatal(err)
}
err = keel.Write(ctx, db, func(ctx context.Context) error {
got, err := keel.TryLockKey(ctx, db, "rate-refresh")
if err != nil {
return err
}
if !got {
fmt.Println("another process is refreshing rates")
}
return nil
})
if err != nil {
log.Fatal(err)
}
}
Output:
func MarkChanged ¶
MarkChanged records that tables changed, for writes keel cannot see into: Exec, statements sent through Pool, triggers. Under a tenant scope the pairs are the tenant's; under a system scope or without a scope they are recorded for no tenant (""), which moves the tables' version for every tenant. Inside a transaction they are written after it commits. It does nothing when data versions are off.
Example ¶
package main
import (
"context"
"log"
"github.com/emoss08/keel"
"github.com/emoss08/keel/internal/testmodels/ids"
)
// appDB is the application's database, opened once at startup (see ExampleOpen).
var appDB *keel.DB
// scope is what the application's authentication middleware puts on a request context.
type scope struct {
OrganizationID ids.ID
BusinessUnitID ids.ID
SystemReason string
}
type scopeKey struct{}
func withTenant(ctx context.Context, org, bu ids.ID) context.Context {
return context.WithValue(ctx, scopeKey{}, scope{OrganizationID: org, BusinessUnitID: bu})
}
// requestContext returns the context of a request authenticated for one tenant.
func requestContext() context.Context {
return withTenant(context.Background(), "org_acme", "bu_dallas")
}
func main() {
ctx, db := requestContext(), appDB
err := keel.Write(ctx, db, func(ctx context.Context) error {
_, err := keel.Exec(ctx, db, `UPDATE shipments SET priority = priority + 1
WHERE organization_id = $1 AND business_unit_id = $2 AND status = 'New'`, "org_acme", "bu_dallas")
if err != nil {
return err
}
keel.MarkChanged(ctx, db, "shipments")
return nil
})
if err != nil {
log.Fatal(err)
}
}
Output:
func OnCreate ¶
func OnCreate[T any](db *DB, t tabler[T], phase Phase, fn func(ctx context.Context, m *Mutation[T]) error, opts ...HookOption, )
OnCreate registers a hook for creates of T's table.
Example ¶
package main
import (
"context"
"errors"
"github.com/emoss08/keel"
"github.com/emoss08/keel/internal/testmodels/fleet"
"github.com/emoss08/keel/internal/testmodels/fleet/fleetdb"
)
// appDB is the application's database, opened once at startup (see ExampleOpen).
var appDB *keel.DB
func main() {
db := appDB
s := fleetdb.Shipments
keel.OnCreate(db, s, keel.BeforeWrite, func(ctx context.Context, m *keel.Mutation[*fleet.Shipment]) error {
if m.Row.ProNumber == "" {
return errors.New("shipment needs a PRO number")
}
if m.Row.Attributes == nil {
m.Row.Attributes = map[string]any{}
}
return nil
})
}
Output:
func OnDelete ¶
func OnDelete[T any](db *DB, t tabler[T], phase Phase, fn func(ctx context.Context, m *Mutation[T]) error, opts ...HookOption, )
OnDelete registers a hook for deletes of T's table.
func OnReplica ¶
OnReplica marks ctx so that read terminals (One, First, List, All, Page, Count, CountUpTo, CountEstimate, Exists, Each, Into, IntoOne, Batch) called with it outside a transaction run on a replica within Config.MaxReplicaLag, the least loaded relative to its weight, and on the primary when none qualifies. Explicit transactions keep using the primary unless started with ReplicaTx.
Reads on the context see its own writes: a write made with ctx, or a context derived from it, records the primary's WAL position after its commit, in the commit's round trip, and later replica reads on ctx run only where that position has been replayed. A replica the lag tracker has seen replay it is used as is; one it has not yet seen is used when the write is older than the replica's lag, with a check of its replay position sent first in the read's own round trip, and the read moves to the primary when the check fails. A context that is already marked is returned unchanged, so the positions are shared with it.
Example ¶
package main
import (
"context"
"fmt"
"log"
"github.com/emoss08/keel"
"github.com/emoss08/keel/internal/testmodels/fleet"
"github.com/emoss08/keel/internal/testmodels/fleet/fleetdb"
"github.com/emoss08/keel/internal/testmodels/ids"
)
// appDB is the application's database, opened once at startup (see ExampleOpen).
var appDB *keel.DB
// scope is what the application's authentication middleware puts on a request context.
type scope struct {
OrganizationID ids.ID
BusinessUnitID ids.ID
SystemReason string
}
type scopeKey struct{}
func withTenant(ctx context.Context, org, bu ids.ID) context.Context {
return context.WithValue(ctx, scopeKey{}, scope{OrganizationID: org, BusinessUnitID: bu})
}
// requestContext returns the context of a request authenticated for one tenant.
func requestContext() context.Context {
return withTenant(context.Background(), "org_acme", "bu_dallas")
}
func main() {
ctx, db := keel.OnReplica(requestContext()), appDB
s := fleetdb.Shipments
page, err := s.Where(s.Status.Eq(fleet.StatusNew)).
Order(s.CreatedAt.Desc()).
Page(ctx, db, keel.After("", 50))
if err != nil {
log.Fatal(err)
}
fmt.Println(len(page.Items))
sh := &fleet.Shipment{CustomerID: "cus_01", ProNumber: "PRO-3001", Status: fleet.StatusNew}
if err := s.Create(ctx, db, sh); err != nil {
log.Fatal(err)
}
got, err := s.Get(ctx, db, sh.ID)
if err != nil {
log.Fatal(err)
}
fmt.Println(got.ProNumber)
}
Output:
func OnReplicaAfter ¶
OnReplicaAfter is OnReplica for reads that must see everything db committed up to pos, such as a position an earlier request returned from CommitPosition and the client sent back.
func OnUpdate ¶
func OnUpdate[T any](db *DB, t tabler[T], phase Phase, fn func(ctx context.Context, m *Mutation[T]) error, opts ...HookOption, )
OnUpdate registers a hook for updates of T's table: Save, Upsert conflicts and bulk updates.
Example ¶
package main
import (
"context"
"log"
"github.com/emoss08/keel"
"github.com/emoss08/keel/internal/testmodels/fleet"
"github.com/emoss08/keel/internal/testmodels/fleet/fleetdb"
)
// appDB is the application's database, opened once at startup (see ExampleOpen).
var appDB *keel.DB
func main() {
db := appDB
s := fleetdb.Shipments
keel.OnUpdate(db, s, keel.AfterWrite, func(ctx context.Context, m *keel.Mutation[*fleet.Shipment]) error {
if m.Old == nil || !m.Changed(s.Status) || m.Old.Status == m.Row.Status {
return nil
}
log.Printf("shipment %s: %s -> %s", m.Row.ID, m.Old.Status, m.Row.Status)
return nil
}, keel.Needs(s.Status))
}
Output:
func OnWrite ¶
func OnWrite[T any](db *DB, t tabler[T], phase Phase, fn func(ctx context.Context, m *Mutation[T]) error, opts ...HookOption, )
OnWrite registers a hook for every write of T's table.
Example ¶
package main
import (
"context"
"log"
"github.com/emoss08/keel"
"github.com/emoss08/keel/internal/testmodels/fleet"
"github.com/emoss08/keel/internal/testmodels/fleet/fleetdb"
)
// appDB is the application's database, opened once at startup (see ExampleOpen).
var appDB *keel.DB
func main() {
db := appDB
c := fleetdb.Customers
keel.OnWrite(db, c, keel.Committed, func(ctx context.Context, m *keel.Mutation[*fleet.Customer]) error {
log.Printf("customer %s %s; refresh the search index", m.Row.ID, m.Op)
return nil
})
}
Output:
func Path ¶
Path returns the handle for relation rel of the table referenced by parent, building it with build on first use. Generated relation methods call it. Every handle type built for one relation of one parent references the same Ref, so their columns resolve to the same join.
func Pointers ¶
func Pointers[T any](vs []T) []*T
Pointers returns pointers to the elements of vs, for a custom type whose driver.Valuer has a pointer receiver: the driver encodes each element through it.
func QueryAll ¶
func QueryAll[T any](ctx context.Context, db *DB, to pgx.RowToFunc[T], sql string, args ...any) ([]T, error)
QueryAll runs one SQL statement keel does not render, as QueryRows does, and returns every row it returns converted by to, such as pgx.RowTo[int64] or pgx.RowToStructByName[T].
func QueryRows ¶
func QueryRows(ctx context.Context, db *DB, fn func(row pgx.CollectableRow) error, sql string, args ...any, ) error
QueryRows runs one SQL statement keel does not render and calls fn for each row it returns, in order, with the rows positioned on that row: read it with Scan, Values or a pgx row function such as pgx.RowToStructByName. It joins the transaction on the context, Read or Write, so a read inside a Read with ReplicaTx runs on that replica; outside a transaction it runs on the primary, in an implicit transaction when Config.OnBegin is set. An error from fn stops the rows and is returned. fn must not run statements of its own on the same transaction: the connection is busy until the rows are read.
Observers see the statement with the Op "query". As with Exec, keel cannot see what the statement touches: scope checks, interceptors, read filters and hooks do not apply, and a write through it changes no data version. Bind values as $1, $2, … arguments.
Example ¶
package main
import (
"context"
"fmt"
"log"
"github.com/emoss08/keel"
"github.com/emoss08/keel/internal/testmodels/fleet"
"github.com/emoss08/keel/internal/testmodels/ids"
"github.com/jackc/pgx/v5"
)
// appDB is the application's database, opened once at startup (see ExampleOpen).
var appDB *keel.DB
// scope is what the application's authentication middleware puts on a request context.
type scope struct {
OrganizationID ids.ID
BusinessUnitID ids.ID
SystemReason string
}
type scopeKey struct{}
func withTenant(ctx context.Context, org, bu ids.ID) context.Context {
return context.WithValue(ctx, scopeKey{}, scope{OrganizationID: org, BusinessUnitID: bu})
}
// requestContext returns the context of a request authenticated for one tenant.
func requestContext() context.Context {
return withTenant(context.Background(), "org_acme", "bu_dallas")
}
func main() {
ctx, db := requestContext(), appDB
err := keel.Write(ctx, db, func(ctx context.Context) error {
n, err := keel.QueryValue[int64](ctx, db, "SELECT count(*) FROM keel_rollup_dirty WHERE rollup = $1", "shipments_daily")
if err != nil {
return err
}
fmt.Println("pending slices:", n)
return keel.QueryRows(ctx, db, func(row pgx.CollectableRow) error {
var code string
var loads int64
if err := row.Scan(&code, &loads); err != nil {
return err
}
fmt.Println(code, loads)
return nil
}, `SELECT c.code, count(*) FROM shipments s JOIN customers c ON c.id = s.customer_id
WHERE s.status = $1 GROUP BY c.code`, fleet.StatusDelivered)
})
if err != nil {
log.Fatal(err)
}
}
Output:
func QueryValue ¶
QueryValue runs one SQL statement keel does not render, as QueryRows does, and scans the first column of its first row into a T. It returns ErrNotFound when the statement returns no row.
func Read ¶
Read runs fn in a read-only transaction, on the primary unless ReplicaTx is given, at the isolation level an option gives, else Config.ReadIsolation. A Read or Write already on the context is joined, unless Detached is given; joining it under another scope than the one it began with returns ErrScopeMismatch.
When fn returns, keel sends COMMIT (ROLLBACK when fn failed) and returns without waiting for the reply, which a goroutine reads before the connection goes back to the pool; a connection whose reply fails is closed. A read-only transaction has nothing left to commit, so the reply carries no information the caller needs. Serializable reads, whose COMMIT can report a serialization failure, and Config.AwaitReadCommit wait as writes do.
Example ¶
package main
import (
"context"
"fmt"
"log"
"github.com/emoss08/keel"
"github.com/emoss08/keel/internal/testmodels/fleet"
"github.com/emoss08/keel/internal/testmodels/fleet/fleetdb"
"github.com/emoss08/keel/internal/testmodels/ids"
)
// appDB is the application's database, opened once at startup (see ExampleOpen).
var appDB *keel.DB
// scope is what the application's authentication middleware puts on a request context.
type scope struct {
OrganizationID ids.ID
BusinessUnitID ids.ID
SystemReason string
}
type scopeKey struct{}
func withTenant(ctx context.Context, org, bu ids.ID) context.Context {
return context.WithValue(ctx, scopeKey{}, scope{OrganizationID: org, BusinessUnitID: bu})
}
// requestContext returns the context of a request authenticated for one tenant.
func requestContext() context.Context {
return withTenant(context.Background(), "org_acme", "bu_dallas")
}
func main() {
ctx, db := requestContext(), appDB
s := fleetdb.Shipments
var open, late int64
err := keel.Read(ctx, db, func(ctx context.Context) error {
var err error
if open, err = s.Where(s.Status.Ne(fleet.StatusDelivered)).Count(ctx, db); err != nil {
return err
}
late, err = s.Where(s.Status.Eq(fleet.StatusInTransit), s.Priority.Gte(2)).Count(ctx, db)
return err
}, keel.RepeatableRead)
if err != nil {
log.Fatal(err)
}
fmt.Println(open, late)
}
Output:
func ReadValue ¶
func ReadValue[T any](ctx context.Context, db *DB, fn func(ctx context.Context) (T, error), opts ...TxOption, ) (T, error)
ReadValue is Read for a function that returns a value.
Example ¶
package main
import (
"context"
"fmt"
"log"
"github.com/emoss08/keel"
"github.com/emoss08/keel/internal/testmodels/fleet"
"github.com/emoss08/keel/internal/testmodels/fleet/fleetdb"
"github.com/emoss08/keel/internal/testmodels/ids"
)
// appDB is the application's database, opened once at startup (see ExampleOpen).
var appDB *keel.DB
// scope is what the application's authentication middleware puts on a request context.
type scope struct {
OrganizationID ids.ID
BusinessUnitID ids.ID
SystemReason string
}
type scopeKey struct{}
func withTenant(ctx context.Context, org, bu ids.ID) context.Context {
return context.WithValue(ctx, scopeKey{}, scope{OrganizationID: org, BusinessUnitID: bu})
}
// requestContext returns the context of a request authenticated for one tenant.
func requestContext() context.Context {
return withTenant(context.Background(), "org_acme", "bu_dallas")
}
func main() {
ctx, db := requestContext(), appDB
s := fleetdb.Shipments
sh, err := keel.ReadValue(ctx, db, func(ctx context.Context) (*fleet.Shipment, error) {
return s.Where(s.ProNumber.Eq("PRO-1001")).Load(s.Customer(), s.Moves()).One(ctx, db)
})
if err != nil {
log.Fatal(err)
}
fmt.Println(sh.Customer.Name)
}
Output:
func Savepoint ¶
Savepoint runs fn inside a savepoint of the transaction on ctx: an error from fn rolls back to the savepoint, drops the Committed hooks and AfterCommit callbacks queued inside it, and is returned, leaving the transaction usable. Outside a transaction it runs fn in a new Write. Under another scope than the transaction's it returns ErrScopeMismatch.
Example ¶
package main
import (
"context"
"log"
"github.com/emoss08/keel"
"github.com/emoss08/keel/internal/testmodels/fleet"
"github.com/emoss08/keel/internal/testmodels/fleet/fleetdb"
"github.com/emoss08/keel/internal/testmodels/ids"
)
// appDB is the application's database, opened once at startup (see ExampleOpen).
var appDB *keel.DB
// scope is what the application's authentication middleware puts on a request context.
type scope struct {
OrganizationID ids.ID
BusinessUnitID ids.ID
SystemReason string
}
type scopeKey struct{}
func withTenant(ctx context.Context, org, bu ids.ID) context.Context {
return context.WithValue(ctx, scopeKey{}, scope{OrganizationID: org, BusinessUnitID: bu})
}
// requestContext returns the context of a request authenticated for one tenant.
func requestContext() context.Context {
return withTenant(context.Background(), "org_acme", "bu_dallas")
}
func main() {
ctx, db := requestContext(), appDB
c := fleetdb.Customers
err := keel.Write(ctx, db, func(ctx context.Context) error {
for _, code := range []string{"ACME", "GLOBEX", "ACME"} {
err := keel.Savepoint(ctx, db, func(ctx context.Context) error {
return c.Create(ctx, db, &fleet.Customer{Code: code, Name: code})
})
if keel.IsUnique(err, "uq_customers_code") {
continue
}
if err != nil {
return err
}
}
return nil
})
if err != nil {
log.Fatal(err)
}
}
Output:
func SetLinks ¶
func SetLinks[K any](ctx context.Context, db *DB, rel interface{ TableRef() *Ref }, parent K, targets []K, ) error
SetLinks makes the rows the parent row with key parent links to through the many-to-many relation rel exactly the rows with keys targets: one statement deletes the parent's join rows to every other target and inserts the missing ones (ON CONFLICT DO NOTHING, so the join table needs a primary key or unique index over its columns). rel is the relation's handle, as a generated relation method returns it (shipmentdb.Shipments.Tags()). An empty targets unlinks every row.
Keys are strings or integers, or named types of them. A join table between two tenant tables is written under the scope's tenant: its tenant columns take the scope's values and the delete is limited to them, so a tenant scope is required; a system scope is refused, since the parent's tenant is not known. Observers see the statement with the Op "links"; the join table has no model, so no hook or interceptor runs, and its data version moves for the scope's tenant.
func TenantKeyOf ¶
TenantKeyOf returns the data-versions tenant key of tenant values, given in the order of Config.TenantColumns: the values as text joined with "/", with "\" and "/" inside a value escaped with "\". Values of named types are keyed as their underlying type.
Example ¶
package main
import (
"context"
"fmt"
"log"
"github.com/emoss08/keel"
"github.com/emoss08/keel/internal/testmodels/ids"
)
// appDB is the application's database, opened once at startup (see ExampleOpen).
var appDB *keel.DB
// scope is what the application's authentication middleware puts on a request context.
type scope struct {
OrganizationID ids.ID
BusinessUnitID ids.ID
SystemReason string
}
type scopeKey struct{}
func withSystem(ctx context.Context, reason string) context.Context {
return context.WithValue(ctx, scopeKey{}, scope{SystemReason: reason})
}
func main() {
ctx, db := withSystem(context.Background(), "warm report caches"), appDB
tenant := keel.TenantKeyOf(ids.ID("org_acme"), ids.ID("bu_dallas"))
versions, err := db.DataVersions(ctx, tenant, "shipments")
if err != nil {
log.Fatal(err)
}
fmt.Println(tenant, versions["shipments"])
}
Output:
func TryLockKey ¶
TryLockKey is LockKey without waiting: it reports whether the lock was taken.
func WithReadFilter ¶
func WithReadFilter(ctx context.Context, f ReadFilter) context.Context
WithReadFilter returns a context whose read statements apply f to every table reference they make: the base table, joined relations, picks and each table a pick walks, EXISTS, subqueries, CTE bodies and pre-aggregations. Interceptors cover only a statement's base table; a read filter covers every table, so a joined row the filter excludes reads as absent (its columns NULL) and a related row it excludes does not count. f is called each time a statement references a table, so it should return quickly, from a cache when the predicates cost anything to build. A ReadFilter already on ctx is replaced.
func Write ¶
Write runs fn in a read-write transaction on the primary. A Write already on the context is joined, unless Detached is given; a Read is not, and returns ErrReadOnly. Joining a transaction under another scope than the one it began with returns ErrScopeMismatch. On a context marked with OnReplica, COMMIT reads the commit position in its round trip, for the context's replica reads.
Example ¶
package main
import (
"context"
"log"
"time"
"github.com/emoss08/keel"
"github.com/emoss08/keel/internal/testmodels/fleet"
"github.com/emoss08/keel/internal/testmodels/fleet/fleetdb"
"github.com/emoss08/keel/internal/testmodels/ids"
)
// appDB is the application's database, opened once at startup (see ExampleOpen).
var appDB *keel.DB
// scope is what the application's authentication middleware puts on a request context.
type scope struct {
OrganizationID ids.ID
BusinessUnitID ids.ID
SystemReason string
}
type scopeKey struct{}
func withTenant(ctx context.Context, org, bu ids.ID) context.Context {
return context.WithValue(ctx, scopeKey{}, scope{OrganizationID: org, BusinessUnitID: bu})
}
// requestContext returns the context of a request authenticated for one tenant.
func requestContext() context.Context {
return withTenant(context.Background(), "org_acme", "bu_dallas")
}
func main() {
ctx, db := requestContext(), appDB
s := fleetdb.Shipments
err := keel.Write(ctx, db, func(ctx context.Context) error {
sh, err := s.Where(s.ID.Eq("shp_01")).Lock(keel.ForUpdate, keel.NoWait).One(ctx, db)
if err != nil {
return err
}
sh.Status = fleet.StatusAssigned
if err := s.Save(ctx, db, sh, s.Status); err != nil {
return err
}
mv := &fleet.Move{ShipmentID: sh.ID, Sequence: 1, Status: fleet.StatusAssigned}
if err := fleetdb.Moves.Create(ctx, db, mv); err != nil {
return err
}
keel.AfterCommit(ctx, func(ctx context.Context) {
log.Printf("shipment %s assigned", sh.ID)
})
return nil
}, keel.LockTimeout(2*time.Second))
if err != nil {
log.Fatal(err)
}
}
Output:
func WriteValue ¶
func WriteValue[T any](ctx context.Context, db *DB, fn func(ctx context.Context) (T, error), opts ...TxOption, ) (T, error)
WriteValue is Write for a function that returns a value.
Example ¶
package main
import (
"context"
"fmt"
"log"
"github.com/emoss08/keel"
"github.com/emoss08/keel/internal/testmodels/fleet"
"github.com/emoss08/keel/internal/testmodels/fleet/fleetdb"
"github.com/emoss08/keel/internal/testmodels/ids"
)
// appDB is the application's database, opened once at startup (see ExampleOpen).
var appDB *keel.DB
// scope is what the application's authentication middleware puts on a request context.
type scope struct {
OrganizationID ids.ID
BusinessUnitID ids.ID
SystemReason string
}
type scopeKey struct{}
func withTenant(ctx context.Context, org, bu ids.ID) context.Context {
return context.WithValue(ctx, scopeKey{}, scope{OrganizationID: org, BusinessUnitID: bu})
}
// requestContext returns the context of a request authenticated for one tenant.
func requestContext() context.Context {
return withTenant(context.Background(), "org_acme", "bu_dallas")
}
func main() {
ctx, db := requestContext(), appDB
id, err := keel.WriteValue(ctx, db, func(ctx context.Context) (ids.ID, error) {
c := &fleet.Customer{Code: "UMBRELLA", Name: "Umbrella Transport"}
if err := fleetdb.Customers.Create(ctx, db, c); err != nil {
return "", err
}
sh := &fleet.Shipment{CustomerID: c.ID, ProNumber: "PRO-2001", Status: fleet.StatusNew}
if err := fleetdb.Shipments.Create(ctx, db, sh); err != nil {
return "", err
}
return sh.ID, nil
}, keel.Serializable, keel.Retry(3))
if err != nil {
log.Fatal(err)
}
fmt.Println(id)
}
Output:
Types ¶
type Agg ¶
type Agg struct {
// contains filtered or unexported fields
}
Agg is a computed value: an aggregate (count, sum, min, max, avg), a window function or a scalar function keel knows. It compares to values, orders, and filters or windows when it aggregates.
func BoolAnd ¶
BoolAnd is the aggregate bool_and(p): whether p holds for every row, NULL over no rows.
func CumeDist ¶
func CumeDist() Agg
CumeDist is cume_dist(): the fraction of partition rows ordered before or tied with the row.
func FirstValue ¶
FirstValue is first_value(e): e in the first row of the window frame.
func Grouping ¶
Grouping is GROUPING(es...): a bit mask with a 1 for each of es that the row's grouping set does not group by. In a query with GroupingSets, Rollup or Cube it tells a subtotal or total row from a detail row; it may appear in Pick, Having and Order. Each of es must be written exactly as it is in the grouping.
func JSONBAgg ¶
JSONBAgg is the aggregate jsonb_agg(e), the values of e as a jsonb array (NULL over no rows). OrderBy orders the elements.
func Lag ¶
Lag is lag(e, offset, def): e in the row offset rows before the current row in its partition, or def (a value bound as a parameter or an expression) when there is no such row. A nil def is NULL.
func LastValue ¶
LastValue is last_value(e): e in the last row of the window frame. The default frame ends at the current row's last peer; give the window a frame that reaches UnboundedFollowing to read the partition's last row.
func Mode ¶
Mode is mode() WITHIN GROUP (ORDER BY order): the group's most frequent value, the first in order on a tie.
func Ntile ¶
Ntile is ntile(n): the bucket, from 1 to n, of the row when the partition is split into n buckets as evenly as possible. n is bound as a parameter.
func PercentRank ¶
func PercentRank() Agg
PercentRank is percent_rank(): (rank - 1) / (partition rows - 1), from 0 to 1.
func PercentileCont ¶
PercentileCont is percentile_cont(fraction) WITHIN GROUP (ORDER BY order): the value at fraction (0 to 1) of the ordered group, interpolated between rows.
func PercentileDisc ¶
PercentileDisc is percentile_disc(fraction) WITHIN GROUP (ORDER BY order): the first value of the ordered group whose position reaches fraction.
func Rank ¶
func Rank() Agg
Rank is rank(): the row's rank in its partition by the window's order, with gaps after ties.
func RowNumber ¶
func RowNumber() Agg
RowNumber is row_number(): the row's position in its window partition, from 1. Give it a window with Over or OverWindow.
Example ¶
package main
import (
"context"
"fmt"
"log"
"github.com/emoss08/keel"
"github.com/emoss08/keel/internal/testmodels/fleet"
"github.com/emoss08/keel/internal/testmodels/fleet/fleetdb"
"github.com/emoss08/keel/internal/testmodels/ids"
)
// appDB is the application's database, opened once at startup (see ExampleOpen).
var appDB *keel.DB
// scope is what the application's authentication middleware puts on a request context.
type scope struct {
OrganizationID ids.ID
BusinessUnitID ids.ID
SystemReason string
}
type scopeKey struct{}
func withTenant(ctx context.Context, org, bu ids.ID) context.Context {
return context.WithValue(ctx, scopeKey{}, scope{OrganizationID: org, BusinessUnitID: bu})
}
// requestContext returns the context of a request authenticated for one tenant.
func requestContext() context.Context {
return withTenant(context.Background(), "org_acme", "bu_dallas")
}
func main() {
ctx, db := requestContext(), appDB
s := fleetdb.Shipments
type ranked struct {
Customer ids.ID
Pro string
Rank int64
}
rows, err := keel.Into[ranked](ctx, db, s.
Pick(s.CustomerID, s.ProNumber,
keel.RowNumber().Over([]keel.Expr{s.CustomerID}, s.CreatedAt.Desc())).
Where(s.Status.Eq(fleet.StatusDelivered)).
Limit(500))
if err != nil {
log.Fatal(err)
}
fmt.Println(len(rows))
}
Output:
func (Agg) Distinct ¶
Distinct aggregates the distinct values of the argument: array_agg(DISTINCT e).
func (Agg) OrderBy ¶
OrderBy orders the rows an aggregate reads, for an aggregate whose result depends on their order: array_agg(e ORDER BY …), string_agg(e, sep ORDER BY …).
func (Agg) Over ¶
Over turns the aggregate into a window function over the rows that share partition, ordered by order. It is OverWindow(PartitionBy(partition...).OrderBy(order...)).
func (Agg) OverWindow ¶
OverWindow turns the aggregate into a window function over w: a running or moving aggregate, a rank, an offset or a value in the window frame.
Example ¶
package main
import (
"context"
"fmt"
"log"
"github.com/emoss08/keel"
"github.com/emoss08/keel/internal/testmodels/fleet"
"github.com/emoss08/keel/internal/testmodels/fleet/fleetdb"
"github.com/emoss08/keel/internal/testmodels/ids"
)
// appDB is the application's database, opened once at startup (see ExampleOpen).
var appDB *keel.DB
// scope is what the application's authentication middleware puts on a request context.
type scope struct {
OrganizationID ids.ID
BusinessUnitID ids.ID
SystemReason string
}
type scopeKey struct{}
func withTenant(ctx context.Context, org, bu ids.ID) context.Context {
return context.WithValue(ctx, scopeKey{}, scope{OrganizationID: org, BusinessUnitID: bu})
}
// requestContext returns the context of a request authenticated for one tenant.
func requestContext() context.Context {
return withTenant(context.Background(), "org_acme", "bu_dallas")
}
func main() {
ctx, db := requestContext(), appDB
s := fleetdb.Shipments
byDay := keel.PartitionBy(s.CustomerID).OrderBy(s.CreatedAt.Asc())
type trend struct {
Pro string
Running fleet.Money
Moving float64
Share float64
Rank int64
Change *fleet.Money
}
rows, err := keel.Into[trend](ctx, db, s.Pick(
s.ProNumber,
keel.Sum(s.Total).OverWindow(byDay.Rows(keel.UnboundedPreceding, keel.CurrentRow)),
keel.Avg(s.Total).OverWindow(byDay.Rows(keel.Preceding(2), keel.CurrentRow)),
keel.Raw("? * 100.0 / ?", s.Total, keel.Sum(s.Total).OverWindow(keel.Window{})),
keel.Rank().OverWindow(keel.PartitionBy(s.CustomerID).OrderBy(s.Total.Desc())),
keel.Raw("? - ?", s.Total, keel.Lag(s.Total, 1, nil).OverWindow(byDay)),
).Order(s.CustomerID.Asc(), s.CreatedAt.Asc()).Limit(1000))
if err != nil {
log.Fatal(err)
}
fmt.Println(len(rows))
}
Output:
type AnyCol ¶
type AnyCol struct{ Col }
AnyCol is a column of a type keel has no dedicated kind for (numeric through a decimal type, a custom sql.Scanner). Generated wrappers give it typed methods.
func (AnyCol) Add ¶
func (c AnyCol) Add(v NumericValue) NumericExpr
Add is (col + v), an expression over a numeric (scanner) column; Increment is the assignment.
func (AnyCol) In ¶
In takes a slice of values (for example []decimal.Decimal) bound as one array parameter.
func (AnyCol) InArray ¶
func (c AnyCol) InArray(a NumericArrayValue) Pred
InArray is (col = ANY(a)).
func (AnyCol) Increment ¶
func (c AnyCol) Increment(v any) Assignment
func (AnyCol) IsDistinctFrom ¶
func (AnyCol) IsDistinctFromExpr ¶
func (c AnyCol) IsDistinctFromExpr(o NumericValue) Pred
IsDistinctFromExpr is col IS DISTINCT FROM o.
func (AnyCol) IsNotDistinctFrom ¶
IsNotDistinctFrom is col IS NOT DISTINCT FROM v.
func (AnyCol) IsNotDistinctFromExpr ¶
func (c AnyCol) IsNotDistinctFromExpr(o NumericValue) Pred
IsNotDistinctFromExpr is col IS NOT DISTINCT FROM o.
func (AnyCol) NotInArray ¶
func (c AnyCol) NotInArray(a NumericArrayValue) Pred
NotInArray is (col <> ALL(a)).
func (AnyCol) To ¶
func (c AnyCol) To(v any) Assignment
type Assignment ¶
type Assignment struct {
// contains filtered or unexported fields
}
Assignment is one SET term of an update, built by a column's To, ToNull or Add.
type BatchItem ¶
type BatchItem struct {
// contains filtered or unexported fields
}
BatchItem is one read of a Batch with its typed destination. ListInto, AllInto, OneInto, FirstInto, PageInto, CountInto, CountUpToInto, CountEstimateInto and ExistsInto read what the query terminal of the same name returns; IntoSlice and IntoValue read what Into and IntoOne return. An item holds no result, so it can run in more than one Batch.
func CountEstimateInto ¶
CountEstimateInto stores the number of matching rows, counted exactly up to exactUpTo and estimated by the planner above it, in dst, as CountEstimate does. Its count and EXPLAIN join the batch's round trip.
func CountUpToInto ¶
CountUpToInto stores the number of matching rows, counted up to limit, in dst, as CountUpTo does.
func DynCountEstimateInto ¶
DynCountEstimateInto stores the number of rows the dynamic query returns, counted exactly up to exactUpTo and estimated by the planner above it, in dst, as DynQuery.CountEstimate does.
func DynCountInto ¶
DynCountInto stores the number of rows the dynamic query returns in dst: SELECT count(*) FROM (query).
func DynExistsInto ¶
DynExistsInto stores whether the dynamic query returns any row in dst: SELECT EXISTS (query), which stops at the first row. The query's scope, tenancy and interceptors apply as they do to its other terminals.
func DynInto ¶
DynInto reads the rows of a dynamic query into dst, as Run does, as one item of a Batch: a report and its row count or dynamic pivot values in one round trip.
Example ¶
package main
import (
"context"
"fmt"
"log"
"github.com/emoss08/keel"
"github.com/emoss08/keel/internal/testmodels/fleet/fleetdb"
"github.com/emoss08/keel/internal/testmodels/ids"
)
// appDB is the application's database, opened once at startup (see ExampleOpen).
var appDB *keel.DB
// scope is what the application's authentication middleware puts on a request context.
type scope struct {
OrganizationID ids.ID
BusinessUnitID ids.ID
SystemReason string
}
type scopeKey struct{}
func withTenant(ctx context.Context, org, bu ids.ID) context.Context {
return context.WithValue(ctx, scopeKey{}, scope{OrganizationID: org, BusinessUnitID: bu})
}
// requestContext returns the context of a request authenticated for one tenant.
func requestContext() context.Context {
return withTenant(context.Background(), "org_acme", "bu_dallas")
}
// reportTables is the registry of the tables reports can start from.
var reportTables, _ = keel.NewRegistry(fleetdb.ShipmentsSchema(), fleetdb.CustomersSchema(),
fleetdb.MovesSchema(), fleetdb.StopsSchema(), fleetdb.TagsSchema(), fleetdb.LocationsSchema())
func main() {
ctx, db := requestContext(), appDB
q := reportTables.Query("shipments")
q.Select(q.Path("status"), keel.DynCount().As("loads")).Group(q.Path("status"))
statuses := reportTables.Query("shipments")
statuses.Select(statuses.Path("status")).Distinct()
var report, pivot *keel.DynResult
var rows int64
err := keel.Batch(ctx, db, keel.DynInto(q, &report), keel.DynInto(statuses, &pivot),
keel.DynCountInto(q, &rows))
if err != nil {
log.Fatal(err)
}
fmt.Println(report.Rows, pivot.Rows, rows)
}
Output:
func DynPageInto ¶
func DynPageInto(q *DynQuery, p PageRequest, dst **DynResult) BatchItem
DynPageInto reads one keyset page of the dynamic query into dst, as Page does. A request with Total also counts the query's rows, and with Estimate reads the planner's estimate past the limit, in the same round trip.
func ExistsInto ¶
ExistsInto stores whether any row matches in dst, as Exists does.
func FirstInto ¶
FirstInto reads the first row in the query's order into dst, as First does: the batch fails with ErrNotFound when no row matches.
func IntoSlice ¶
func IntoSlice[R any](p Projection, dst *[]R) BatchItem
IntoSlice decodes the rows of a projection into dst, as Into does.
func IntoValue ¶
func IntoValue[R any](p Projection, dst *R) BatchItem
IntoValue decodes the only row of a projection into dst, as IntoOne does: the batch fails with ErrNotFound when the projection returns no row and ErrNotSingular when it returns more.
func ListInto ¶
ListInto reads the query's rows into dst, as List does. The query must have a Limit.
type BoolArrayCase ¶
type BoolArrayCase struct {
// contains filtered or unexported fields
}
BoolArrayCase builds a CASE whose result is boolean[]. Start it with CaseBoolArray.
func CaseBoolArray ¶
func CaseBoolArray() BoolArrayCase
CaseBoolArray starts a CASE whose result is boolean[].
func (BoolArrayCase) Else ¶
func (b BoolArrayCase) Else(v BoolArrayValue) BoolArrayExpr
Else ends the CASE with ELSE v.
func (BoolArrayCase) End ¶
func (b BoolArrayCase) End() BoolArrayExpr
End ends the CASE without ELSE.
func (BoolArrayCase) When ¶
func (b BoolArrayCase) When(cond Pred, v BoolArrayValue) BoolArrayCase
When adds WHEN cond THEN v.
type BoolArrayExpr ¶
type BoolArrayExpr struct {
// contains filtered or unexported fields
}
BoolArrayExpr is a computed boolean[] value, with the methods of StringArrayExpr.
func ArrayOfBool ¶
func ArrayOfBool(es ...BoolValue) BoolArrayExpr
ArrayOfBool is ARRAY[es…] of boolean values.
func (BoolArrayExpr) At ¶
func (e BoolArrayExpr) At(i int) BoolExpr
At is (e)[i], the i-th element from 1.
func (BoolArrayExpr) Coalesce ¶
func (e BoolArrayExpr) Coalesce(o BoolArrayValue) BoolArrayExpr
Coalesce is coalesce(e, o).
func (BoolArrayExpr) ContainedByExpr ¶
func (e BoolArrayExpr) ContainedByExpr(o BoolArrayValue) Pred
ContainedByExpr is (e <@ o).
func (BoolArrayExpr) ContainsAllExpr ¶
func (e BoolArrayExpr) ContainsAllExpr(o BoolArrayValue) Pred
ContainsAllExpr is (e @> o).
func (BoolArrayExpr) IsNotNull ¶
func (e BoolArrayExpr) IsNotNull() Pred
IsNotNull is e IS NOT NULL.
func (BoolArrayExpr) NullIf ¶
func (e BoolArrayExpr) NullIf(o BoolArrayValue) BoolArrayExpr
NullIf is nullif(e, o): NULL where e equals o, as NullIf(keel.BoolArray(nil)) turns an empty array into NULL.
func (BoolArrayExpr) OverlapsExpr ¶
func (e BoolArrayExpr) OverlapsExpr(o BoolArrayValue) Pred
OverlapsExpr is (e && o).
func (BoolArrayExpr) Unnest ¶
func (e BoolArrayExpr) Unnest() BoolExpr
Unnest is the set-returning unnest(e), for keel.Rows.
func (BoolArrayExpr) Without ¶
func (e BoolArrayExpr) Without(v BoolValue) BoolArrayExpr
Without is array_remove(e, v).
type BoolArrayValue ¶
type BoolArrayValue interface {
Expr
// contains filtered or unexported methods
}
BoolArrayValue is a boolean[] value: a BoolArrayExpr.
type BoolCase ¶
type BoolCase struct {
// contains filtered or unexported fields
}
BoolCase builds a CASE whose result is boolean, itself a predicate. Start it with CaseBool.
type BoolCol ¶
type BoolCol struct{ Col }
BoolCol is a boolean column.
func (BoolCol) IsDistinctFrom ¶
IsDistinctFrom is col IS DISTINCT FROM v.
func (BoolCol) IsDistinctFromExpr ¶
IsDistinctFromExpr is col IS DISTINCT FROM o.
func (BoolCol) IsNotDistinctFrom ¶
IsNotDistinctFrom is col IS NOT DISTINCT FROM v.
func (BoolCol) IsNotDistinctFromExpr ¶
IsNotDistinctFromExpr is col IS NOT DISTINCT FROM o.
func (BoolCol) To ¶
func (c BoolCol) To(v bool) Assignment
type BoolExpr ¶
type BoolExpr struct {
// contains filtered or unexported fields
}
BoolExpr is a computed boolean value. It is a predicate, so Where and the combinators take it.
func CastBool ¶
CastBool is CAST(e AS boolean): text 'true', 't', 'yes', 'on', '1' and their opposites, or an integer.
func (BoolExpr) IsDistinctFrom ¶
IsDistinctFrom is e IS DISTINCT FROM v.
func (BoolExpr) IsDistinctFromExpr ¶
IsDistinctFromExpr is e IS DISTINCT FROM o.
func (BoolExpr) IsNotDistinctFrom ¶
IsNotDistinctFrom is e IS NOT DISTINCT FROM v.
func (BoolExpr) IsNotDistinctFromExpr ¶
IsNotDistinctFromExpr is e IS NOT DISTINCT FROM o.
type BoolValue ¶
type BoolValue interface {
Expr
// contains filtered or unexported methods
}
BoolValue is a boolean value: a BoolCol or a BoolExpr.
type BudgetError ¶
type BudgetError struct {
// Limit names the limit crossed: "round trips", "statements" or "repeats".
Limit string
// Max is the limit's value.
Max int
// Report is the budget's account when the error was made.
Report BudgetReport
}
BudgetError is the error of the terminal whose round trip took a Budget past a limit, returned after its statements ran, and of every later round trip under that budget, refused before it is sent. A write outside a transaction has committed when it returns a BudgetError.
func (*BudgetError) Error ¶
func (e *BudgetError) Error() string
func (*BudgetError) Is ¶
func (e *BudgetError) Is(target error) bool
Is makes errors.Is(err, ErrBudgetExceeded) true.
type BudgetOption ¶
type BudgetOption func(b *budget)
BudgetOption configures a Budget.
func MaxRepeats ¶
func MaxRepeats(n int) BudgetOption
MaxRepeats limits how many round trips one statement text may be sent in to n: a text sent in more than n crosses it. MaxRepeats(1) allows no repeated text at all.
func MaxRoundTrips ¶
func MaxRoundTrips(n int) BudgetOption
MaxRoundTrips limits the budget to n round trips.
func MaxStatements ¶
func MaxStatements(n int) BudgetOption
MaxStatements limits the budget to n statements.
func OnExceeded ¶
func OnExceeded(fn func(ctx context.Context, r BudgetReport)) BudgetOption
OnExceeded makes crossing a limit call fn, once per budget, with the context of the terminal that crossed it and the account at that moment, instead of failing the terminal: for logging N+1 reads in production while the work goes on.
type BudgetReport ¶
type BudgetReport struct {
// RoundTrips counts the round trips that carried keel's statements, with the BEGIN keel sends
// on its own before a COPY; the COMMIT or ROLLBACK that ends a transaction is not counted.
RoundTrips int
// Statements counts the statements sent: queries, relation loads, writes, COPY, savepoints,
// advisory locks and raw SQL, not BEGIN, the Config.OnBegin statements or COMMIT.
Statements int
// Repeats lists the statement texts sent in more than one round trip, the most repeated first.
Repeats []Repeat
}
BudgetReport is the account of the work under a Budget.
func BudgetReportFrom ¶
func BudgetReportFrom(ctx context.Context) (BudgetReport, bool)
BudgetReportFrom returns the account of the innermost budget on ctx, and false when ctx carries none.
type BytesCol ¶
type BytesCol struct{ Col }
BytesCol is a bytea column.
func (BytesCol) To ¶
func (c BytesCol) To(v []byte) Assignment
type CTETable ¶
type CTETable struct {
// contains filtered or unexported fields
}
CTETable is a named subquery (WITH name AS (…)) that queries declare with With and then join, filter on or select from like a table.
recent := keel.CTE("recent", []string{"customer_id", "last_at"},
s.Group(s.CustomerID).Pick(s.CustomerID, s.CreatedAt.Max()))
rows, err := c.With(recent).
Join(recent, recent.Col("customer_id").EqCol(c.ID)).
Where(recent.Col("last_at").Lt(cutoff)).
All(ctx, db)
func CTE ¶
CTE declares a common table expression. columns name the body's output columns, in order.
Example ¶
package main
import (
"context"
"fmt"
"log"
"github.com/emoss08/keel"
"github.com/emoss08/keel/internal/testmodels/fleet"
"github.com/emoss08/keel/internal/testmodels/fleet/fleetdb"
"github.com/emoss08/keel/internal/testmodels/ids"
)
// appDB is the application's database, opened once at startup (see ExampleOpen).
var appDB *keel.DB
// scope is what the application's authentication middleware puts on a request context.
type scope struct {
OrganizationID ids.ID
BusinessUnitID ids.ID
SystemReason string
}
type scopeKey struct{}
func withTenant(ctx context.Context, org, bu ids.ID) context.Context {
return context.WithValue(ctx, scopeKey{}, scope{OrganizationID: org, BusinessUnitID: bu})
}
// requestContext returns the context of a request authenticated for one tenant.
func requestContext() context.Context {
return withTenant(context.Background(), "org_acme", "bu_dallas")
}
func main() {
ctx, db := requestContext(), appDB
s, c := fleetdb.Shipments, fleetdb.Customers
busy := keel.CTE("busy", []string{"customer_id", "loads"},
s.Group(s.CustomerID).Pick(s.CustomerID, keel.Count()).Where(s.Status.Eq(fleet.StatusInTransit)))
customers, err := c.With(busy).
Join(busy, busy.Col("customer_id").EqCol(c.ID)).
Where(busy.Col("loads").Gte(10)).
Order(c.Name.Asc()).
All(ctx, db)
if err != nil {
log.Fatal(err)
}
type load struct {
Customer ids.ID
Loads int64
}
top, err := keel.Into[load](ctx, db, busy.Query().With(busy).
Pick(busy.Col("customer_id"), busy.Col("loads")).
Order(busy.Col("loads").Desc()).
Limit(5))
if err != nil {
log.Fatal(err)
}
fmt.Println(len(customers), top)
}
Output:
func RecursiveCTE ¶
RecursiveCTE declares WITH RECURSIVE name (columns) AS (base UNION ALL step). Give it its step with Recurse, which can join the CTE itself to walk a hierarchy:
chain := keel.RecursiveCTE("chain", []string{"id", "depth"}, w.Pick(w.ID, keel.Raw("0")).Where(w.ID.Eq(root)))
sub := w.As("sub")
chain.Recurse(sub.Pick(sub.ID, keel.Raw("? + 1", chain.Col("depth"))).
Join(chain, chain.Col("id").EqCol(sub.ManagerID)))
Example ¶
package main
import (
"context"
"fmt"
"log"
"github.com/emoss08/keel"
"github.com/emoss08/keel/internal/testmodels/fleet/fleetdb"
"github.com/emoss08/keel/internal/testmodels/ids"
)
// appDB is the application's database, opened once at startup (see ExampleOpen).
var appDB *keel.DB
// scope is what the application's authentication middleware puts on a request context.
type scope struct {
OrganizationID ids.ID
BusinessUnitID ids.ID
SystemReason string
}
type scopeKey struct{}
func withTenant(ctx context.Context, org, bu ids.ID) context.Context {
return context.WithValue(ctx, scopeKey{}, scope{OrganizationID: org, BusinessUnitID: bu})
}
// requestContext returns the context of a request authenticated for one tenant.
func requestContext() context.Context {
return withTenant(context.Background(), "org_acme", "bu_dallas")
}
func main() {
ctx, db := requestContext(), appDB
w := fleetdb.Workers
chain := keel.RecursiveCTE("chain", []string{"id", "depth"},
w.Pick(w.ID, keel.Raw("0")).Where(w.ID.Eq("wrk_dispatch_lead")))
sub := w.As("sub")
chain.Recurse(sub.
Pick(sub.ID, keel.Raw("? + 1", chain.Col("depth"))).
Join(chain, chain.Col("id").EqCol(sub.ManagerID)))
type node struct {
ID ids.ID
Depth int32
}
tree, err := keel.Into[node](ctx, db, chain.Query().With(chain).
Pick(chain.Col("id"), chain.Col("depth")).
Order(chain.Col("depth").Asc()))
if err != nil {
log.Fatal(err)
}
team, err := w.With(chain).Where(w.ID.InQuery(chain.Query().Pick(chain.Col("id")))).All(ctx, db)
if err != nil {
log.Fatal(err)
}
fmt.Println(len(tree), len(team))
}
Output:
func Unnest ¶
Unnest declares a CTE over Go slices, one per column, read row by row: WITH name (columns) AS (SELECT * FROM unnest($1::t1[], $2::t2[], …)). Each slice binds as one array parameter, so the statement text does not depend on the number of rows. It is the source of a bulk update that sets every row its own values, in one statement:
out := keel.Unnest("outcome", []string{"id", "status"}, ids, statuses)
n, err := t.With(out).Join(out, out.Col("id").EqCol(t.ID)).
Update(ctx, db, t.Status.ToExpr(out.Col("status")))
Slices hold strings, integers, floats, bools, times or byte slices, or named types of those; they must be of equal length.
Example ¶
package main
import (
"context"
"fmt"
"log"
"github.com/emoss08/keel"
"github.com/emoss08/keel/internal/testmodels/fleet/fleetdb"
"github.com/emoss08/keel/internal/testmodels/ids"
)
// appDB is the application's database, opened once at startup (see ExampleOpen).
var appDB *keel.DB
// scope is what the application's authentication middleware puts on a request context.
type scope struct {
OrganizationID ids.ID
BusinessUnitID ids.ID
SystemReason string
}
type scopeKey struct{}
func withTenant(ctx context.Context, org, bu ids.ID) context.Context {
return context.WithValue(ctx, scopeKey{}, scope{OrganizationID: org, BusinessUnitID: bu})
}
// requestContext returns the context of a request authenticated for one tenant.
func requestContext() context.Context {
return withTenant(context.Background(), "org_acme", "bu_dallas")
}
func main() {
ctx, db := requestContext(), appDB
c := fleetdb.Contacts
ids := []ids.ID{"con_01", "con_02"}
phones := []string{"555-0101", "555-0102"}
scores := []int64{10, 20}
out := keel.Unnest("outcome", []string{"id", "phone", "score"}, ids, phones, scores)
n, err := c.With(out).
Join(out, out.Col("id").EqCol(c.ID)).
Where(c.VIP.IsFalse()).
Update(ctx, db, c.Phone.ToExpr(out.Col("phone")), c.Score.ToExpr(out.Col("score")))
if err != nil {
log.Fatal(err)
}
fmt.Println(n)
}
Output:
func (*CTETable) Distinct ¶
Distinct makes the recursion UNION instead of UNION ALL, so repeated rows end it.
func (*CTETable) Query ¶
Query starts a query over the CTE's rows. Pick what to read and run it with Into.
type CacheConfig ¶
type CacheConfig struct {
// Local is the in-process store; nil keeps a MemoryCache of DefaultCacheBytes.
Local LocalCache
// local miss reads it, and an entry read while this process had no unflushed writes to its
// tables is written to it.
Shared SharedCache
// Key returns key material for what the scope does not capture but the rows depend on, such
// as the row-level security settings Config.OnBegin sets (Trenova returns its encoded read
// scope). It is required when Config.OnBegin is set. It runs on the terminal's context without
// its transaction and with its tenant slot: a statement it sends runs in a transaction of its
// own (on a second pool connection when the caller holds one), never joins or begins the one
// the terminal reads in, and shares the terminal's slot rather than waiting for another.
Key func(ctx context.Context) string
// MaxEntryBytes caps an encoded entry; a larger result is not cached. Default 1 MiB.
MaxEntryBytes int
// OnError receives the errors of the shared store, the listener and the decoders; the cache
// treats each as a miss or a bypass.
OnError func(err error)
// Listen is the connection the listener LISTENs for data versions on; nil uses the primary
// pool's settings (and its BeforeConnect). LISTEN needs a session, so behind a
// transaction-mode pooler (PgBouncer) point it at Postgres directly. Build it with
// pgx.ParseConfig; it is used as given, without the pool's BeforeConnect.
Listen *pgx.ConnConfig
}
CacheConfig configures the result cache (Config.Cache): query results kept by the statements that read them and served while the data versions of every table they read are unchanged, so a hit costs no round trip. It needs Config.DataVersions.Enabled.
type CacheOption ¶
type CacheOption func(*cacheSpec)
CacheOption configures Query.Cache.
func AlsoReads ¶
func AlsoReads(tables ...string) CacheOption
AlsoReads names tables the query reads that keel cannot see, such as a table only a keel.Raw fragment reads, so a change to them invalidates its cached results too.
func TTL ¶
func TTL(d time.Duration) CacheOption
TTL keeps a cached result for at most d. Validity never depends on it: an entry stops being served as soon as a table it read changes. It bounds how long an unused entry holds memory. The default is one minute.
type CacheStats ¶
type CacheStats struct {
// Hits are terminals served from the cache, Misses terminals that asked for it and read the
// database, Bypasses terminals that asked for it where it does not apply.
Hits, Misses, Bypasses int64
// Errors counts the failures reported to CacheConfig.OnError.
Errors int64
// Bytes is what the local store holds.
Bytes int64
// Versions is the number of (tenant, table) pairs whose versions the process holds.
Versions int
// Listening reports that the listener is connected; while it is not, the cache is bypassed.
Listening bool
}
CacheStats counts the result cache's traffic and contents.
type Col ¶
Col references one column of a table. Generated code builds every column statically; the typed column kinds embed it.
func (Col) Coalesce ¶
Coalesce is coalesce(col, vs…): the first of col and vs that is not NULL, each v an expression or a Go value bound as a parameter of col's type.
func (Col) Excluded ¶
Excluded is the value the upsert proposed for the column, in the row that conflicted (EXCLUDED.col). It is valid only in a Conflict.Set assignment; anywhere else the statement fails.
func (Col) ToDefault ¶
func (c Col) ToDefault() Assignment
ToDefault sets the column to its database default.
func (Col) ToExpr ¶
func (c Col) ToExpr(e Expr) Assignment
ToExpr sets the column to an expression.
When the column's tag declares its SQL type (sqltype=), e is cast to it, so a text expression can be assigned to an enum column.
type Column ¶
type Column interface {
// contains filtered or unexported methods
}
Column is any generated column handle (StringCol, Int64Col, a generated enum column, ...). Save, SaveExcept, Omit, Compute, Conflict.Update and Needs take columns as Column, so a caller can build the list at run time: cols := []keel.Column{t.Status, t.Note}; t.Save(ctx, db, row, cols...).
type Compound ¶
type Compound struct {
// contains filtered or unexported fields
}
Compound is a UNION, UNION ALL, INTERSECT or EXCEPT of queries that pick the same number of expressions. Use it as a CTE body, a subquery, or read it with Into.
func Union ¶
func Union(qs ...subquery) Compound
Union is q1 UNION q2 …: the distinct rows of every query.
func UnionAll ¶
func UnionAll(qs ...subquery) Compound
UnionAll is q1 UNION ALL q2 …: every row of every query.
Example ¶
package main
import (
"context"
"fmt"
"log"
"github.com/emoss08/keel"
"github.com/emoss08/keel/internal/testmodels/fleet"
"github.com/emoss08/keel/internal/testmodels/fleet/fleetdb"
"github.com/emoss08/keel/internal/testmodels/ids"
)
// appDB is the application's database, opened once at startup (see ExampleOpen).
var appDB *keel.DB
// scope is what the application's authentication middleware puts on a request context.
type scope struct {
OrganizationID ids.ID
BusinessUnitID ids.ID
SystemReason string
}
type scopeKey struct{}
func withTenant(ctx context.Context, org, bu ids.ID) context.Context {
return context.WithValue(ctx, scopeKey{}, scope{OrganizationID: org, BusinessUnitID: bu})
}
// requestContext returns the context of a request authenticated for one tenant.
func requestContext() context.Context {
return withTenant(context.Background(), "org_acme", "bu_dallas")
}
func main() {
ctx, db := requestContext(), appDB
s, c := fleetdb.Shipments, fleetdb.Customers
codes, err := keel.Into[string](ctx, db, keel.UnionAll(
s.Pick(s.ProNumber).Where(s.Hazmat.IsTrue()),
c.Pick(c.Code).Where(c.Email.IsNull()),
))
if err != nil {
log.Fatal(err)
}
flagged := keel.CTE("flagged", []string{"customer_id"}, keel.Union(
s.Pick(s.CustomerID).Where(s.Hazmat.IsTrue()),
s.Pick(s.CustomerID).Where(s.Status.Eq(fleet.StatusCanceled)),
))
n, err := c.With(flagged).
Where(c.ID.InQuery(flagged.Query().Pick(flagged.Col("customer_id")))).
Count(ctx, db)
if err != nil {
log.Fatal(err)
}
fmt.Println(len(codes), n)
}
Output:
type Config ¶
type Config struct {
// DSN is the connection string. Pool, when set, is used instead.
DSN string
Pool *pgxpool.Config
// TenantColumns names the tenant columns, in the order Tenant returns their values.
TenantColumns []string
// Tenant returns the tenant of the context.
Tenant func(ctx context.Context) ([]any, bool)
// System reports whether the context carries a system scope, and its reason.
System func(ctx context.Context) (reason string, ok bool)
// OnBegin returns statements to run at the start of every transaction, explicit or implicit,
// for example a row-level security set_config. When it is set, terminals outside a transaction
// run in an implicit one. It is called when the transaction begins, with its first statement,
// whichever terminal or callback sends it, on the context the transaction was opened with,
// without that transaction and with its tenant slot: a statement it sends runs in a transaction
// of its own on a second pool connection, which calls OnBegin again, and shares the slot rather
// than waiting for another.
OnBegin func(ctx context.Context, tx TxInfo) ([]Statement, error)
// NewID returns a new primary key for autoid columns.
NewID func(prefix string) string
// Now returns the time stamped into autocreate and autoupdate columns. Defaults to time.Now.
Now func() time.Time
// Observers see every statement.
Observers []Observer
// ObserveArgs hands observers each statement's bound values (QueryEvent.Values). The values
// are the data itself, so turn it on only where a query log may hold it, in development.
ObserveArgs bool
// Tables are the generated tables (keeltables.All()) that constraint violations resolve
// against: a *ConstraintError of a listed table carries the model's field names, labels and
// the message its Constraints method declares. Open reads their constraints' columns from the
// catalog in one query, so resolving a violation never waits for a connection.
Tables []*schema.Table
// AfterConnect runs on every new connection, after keel registers its types; use it to
// register codecs (a decimal type, a faster JSON codec).
AfterConnect func(ctx context.Context, conn *pgx.Conn) error
// LoadTypes lists user-defined types (domains, composites) and their array types to register
// on every connection, through pgx's LoadTypes, after the enums. Enums are registered without
// being listed.
LoadTypes []string
// SkipEnums turns off registering every enum type of the database on each new connection.
// keel reads them all from the catalog in one query, however many there are. Registered enums
// bind as arrays and decode into one shared string per label, so enum columns cost no
// allocation per row.
SkipEnums bool
// JSON encodes and decodes json and jsonb values. Each function left nil defaults to sonic in
// its encoding/json-compatible mode, which encodes a value to the bytes encoding/json would;
// set sonic.ConfigDefault's functions, which skip HTML escaping and map key sorting, for more
// speed. Its UnmarshalNumber decodes the columns tagged jsonnumber.
JSON *JSONCodec
// PlanCache chooses how statements are prepared. The default, PlanCacheAuto, prepares every
// statement shape once per connection.
PlanCache PlanCache
// StatementCacheCapacity is how many prepared statements (or, with PlanCacheOff, statement
// descriptions) each connection keeps. Defaults to 512; the least recently used is
// deallocated beyond it.
StatementCacheCapacity int
// Replicas are streaming replicas of the primary. Reads run on one only when their context is
// marked with OnReplica, or inside a Read started with ReplicaTx. Every replica pool gets the
// same connection setup as the primary's.
Replicas []ReplicaConfig
// MaxReplicaLag is how far behind the primary a replica may be and still serve reads.
// Defaults to 2s.
MaxReplicaLag time.Duration
// DataVersions turns on the per-tenant, per-table change counters keel maintains from its own
// write path.
DataVersions DataVersionsConfig
// Cache configures the result cache; nil turns it off.
Cache *CacheConfig
// ReadIsolation is the isolation level of explicit Read transactions that pass none of their
// own. Zero leaves it to the server's default (READ COMMITTED unless the server says
// otherwise); RepeatableRead gives every statement of a Read one snapshot. A ReplicaTx Read
// runs REPEATABLE READ in place of Serializable, which a hot standby refuses.
ReadIsolation Isolation
// AwaitReadCommit makes read-only transactions wait for the reply to their COMMIT or ROLLBACK,
// as writes do. By default keel sends it and returns, and a goroutine reads the reply before the
// connection goes back to the pool.
AwaitReadCommit bool
// DeadlineTimeouts makes every transaction keel begins, explicit or implicit, set
// statement_timeout to the time left before its context's deadline, in its BEGIN round trip,
// so the server stops a statement the caller no longer waits for even when the cancel request
// does not reach it. Statements outside a transaction rely on the context's cancellation alone.
DeadlineTimeouts bool
// TenantConcurrency bounds how many terminals and transactions of one tenant run at once on
// the DB; zero turns the bound off. Work past it waits for a slot or for its context. A
// transaction holds one slot from its start to its end and its statements take none; work
// started under a context that carries a slot of the same tenant (a Detached transaction, a
// write's hooks) shares it. System scopes and unscoped work take none. DB.TenantWaits counts
// the waits.
TenantConcurrency int
// SlowWaits reports work that waits too long for a tenant slot, a pool connection or another
// goroutine's read of a result cache entry (see SlowWaitConfig). Off unless both of its fields
// are set.
SlowWaits SlowWaitConfig
// MaxWait bounds each wait for a tenant slot, a pool connection or another goroutine's read
// of a result cache entry, whatever the context's deadline; zero (the default) leaves waits
// to the context. A slot or connection wait that reaches it fails with ErrWaitLimit, and a
// cache wait stops waiting and reads the database itself, so work without a deadline (a
// background job) cannot wait forever behind a leaked slot or a stuck request.
MaxWait time.Duration
// LoadStrategy is how a read loads its to-many relations when the query does not choose
// (Query.LoadStrategy). The default, LoadPipelined, sends the root statement and every level in
// one round trip where every level can read one snapshot; LoadPerLevel sends a round trip per level.
LoadStrategy LoadStrategy
}
Config configures a DB.
type Conflict ¶
type Conflict struct {
// contains filtered or unexported fields
}
Conflict is the ON CONFLICT clause of an upsert. Its methods return a changed copy.
func OnConflict ¶
OnConflict names what an upsert conflicts on, as a unique index declares it: columns of the table, or expressions for an index on expressions, such as keel.Raw("md5(?)", t.Endpoint) for ON CONFLICT (md5(endpoint)). Expressions must not bind values; columns in them are written without an alias, as the index names them.
func (Conflict) DoNothing ¶
DoNothing skips a conflicting row instead of updating it: ON CONFLICT … DO NOTHING.
func (Conflict) Set ¶
func (c Conflict) Set(sets ...Assignment) Conflict
Set adds assignments for a conflicting row, built as for Query.Update: To binds a value, Increment adds to the stored value, ToNull, ToDefault and ToExpr set the rest. Inside an expression the table's columns read the stored row and a column's Excluded reads the proposed row:
keel.OnConflict(c.Code).Set(c.Name.ToExpr(c.Name.Excluded()), c.Hits.Increment(1))
Update and Set combine; a column must not be assigned twice. Unless Set assigns them, the autoupdate column takes the proposed row's stamp and the version column is incremented.
Example ¶
package main
import (
"context"
"log"
"github.com/emoss08/keel"
"github.com/emoss08/keel/internal/testmodels/fleet"
"github.com/emoss08/keel/internal/testmodels/fleet/fleetdb"
"github.com/emoss08/keel/internal/testmodels/ids"
)
// appDB is the application's database, opened once at startup (see ExampleOpen).
var appDB *keel.DB
// scope is what the application's authentication middleware puts on a request context.
type scope struct {
OrganizationID ids.ID
BusinessUnitID ids.ID
SystemReason string
}
type scopeKey struct{}
func withTenant(ctx context.Context, org, bu ids.ID) context.Context {
return context.WithValue(ctx, scopeKey{}, scope{OrganizationID: org, BusinessUnitID: bu})
}
// requestContext returns the context of a request authenticated for one tenant.
func requestContext() context.Context {
return withTenant(context.Background(), "org_acme", "bu_dallas")
}
func main() {
ctx, db := requestContext(), appDB
c := fleetdb.Customers
rows := []*fleet.Customer{{Code: "ACME", Name: "Acme Freight"}}
_, err := c.Upsert(ctx, db, rows,
keel.OnConflict(c.OrganizationID, c.BusinessUnitID, c.Code).Set(
c.Name.ToExpr(c.Name.Excluded()),
c.Email.ToExpr(keel.Raw("coalesce(?, ?)", c.Email, c.Email.Excluded())),
c.Version.Increment(10),
))
if err != nil {
log.Fatal(err)
}
}
Output:
func (Conflict) Update ¶
Update names the columns a conflicting row takes from the proposed row: SET col = EXCLUDED.col. Without Update or Set, a conflicting row takes every updatable column.
func (Conflict) UpdateWhere ¶
UpdateWhere updates a conflicting row only when ps hold: ON CONFLICT … DO UPDATE SET … WHERE …. The table's columns read the stored row and a column's Excluded the proposed row; values bind as anywhere else. A row the condition rejects is neither inserted nor updated, so UpsertFrom does not count it, which makes a capped counter one statement:
keel.OnConflict(u.Day).Set(u.Requests.Increment(1)).UpdateWhere(u.Requests.Lt(limit))
Upsert refuses it, since it reports every row as inserted or updated.
type ConflictError ¶
ConflictError reports a Save whose version no longer matched the stored row.
func (*ConflictError) Error ¶
func (e *ConflictError) Error() string
func (*ConflictError) Is ¶
func (e *ConflictError) Is(target error) bool
Is makes errors.Is(err, ErrConflict) true.
type Constraint ¶
type Constraint = schema.Constraint
Constraint names a constraint of a model's table and the message its violation reports. A model declares the ones whose message matters with a Constraints method, which keel gen records in the generated table and keel.Verify checks against the database:
func (Location) Constraints() []keel.Constraint {
return []keel.Constraint{
{Name: "idx_locations_code", Message: "A location with this code already exists"},
}
}
type ConstraintError ¶
type ConstraintError struct {
Kind ConstraintKind
// Table is the violated table's name, and Model its generated schema when Config.Tables lists
// it, else nil.
Table string
Model *schema.Table
// Constraint is the violated constraint's name; empty for a NOT NULL violation.
Constraint string
// Columns are the constraint's columns, without the tenant columns: its key columns in order,
// then the columns its index expressions and predicate read (lower(code), WHERE is_default).
// Fields are their JSON names on the model and Labels their labels, empty when the model is
// unknown; a Field the model declares for the constraint replaces them.
Columns []string
Fields []string
Labels []string
// Message is the message the model declares for the constraint, or "".
Message string
Err *pgconn.PgError
}
ConstraintError is a statement's violation of a constraint, resolved against the model of the table: every keel statement returns one for a unique, foreign key, check, not-null or exclusion violation, wrapping the Postgres error. Read it with errors.As.
func (*ConstraintError) Error ¶
func (e *ConstraintError) Error() string
func (*ConstraintError) Unwrap ¶
func (e *ConstraintError) Unwrap() error
Unwrap returns the Postgres error, so Code, IsUnique and the other helpers see it.
type ConstraintKind ¶
type ConstraintKind uint8
ConstraintKind is the kind of constraint a statement violated.
const ( // ConstraintUnique is a unique constraint or unique index (23505). ConstraintUnique ConstraintKind = iota + 1 // ConstraintForeignKey is a foreign key (23503). ConstraintForeignKey // ConstraintCheck is a check constraint (23514). ConstraintCheck // ConstraintNotNull is a NOT NULL column (23502). ConstraintNotNull // ConstraintExclusion is an exclusion constraint (23P01). ConstraintExclusion )
func (ConstraintKind) String ¶
func (k ConstraintKind) String() string
String returns the kind's name.
type DB ¶
type DB struct {
// contains filtered or unexported fields
}
DB is a pool of connections and the configuration keel applies to them.
func Open ¶
Open connects to Postgres. With Config.Replicas it also opens a pool per replica and starts a goroutine that tracks their lag; with Config.DataVersions.Enabled it starts the goroutine that writes data versions. Close stops both.
Example ¶
package main
import (
"context"
"crypto/rand"
"encoding/hex"
"log"
"os"
"time"
"github.com/emoss08/keel"
"github.com/emoss08/keel/internal/testmodels/ids"
)
// scope is what the application's authentication middleware puts on a request context.
type scope struct {
OrganizationID ids.ID
BusinessUnitID ids.ID
SystemReason string
}
type scopeKey struct{}
func newID(prefix string) string {
b := make([]byte, 10)
_, _ = rand.Read(b)
return prefix + hex.EncodeToString(b)
}
// slowQueryLog is an Observer that logs statements slower than threshold.
type slowQueryLog struct{ threshold time.Duration }
func (slowQueryLog) BeforeQuery(ctx context.Context, _ *keel.QueryEvent) context.Context {
return ctx
}
func (l slowQueryLog) AfterQuery(_ context.Context, e *keel.QueryEvent) {
if e.Duration >= l.threshold {
log.Printf("slow %s on %s (%d args, %d rows): %v: %s",
e.Op, e.Table, e.Args, e.Rows, e.Duration, e.SQL)
}
}
// appConfig is the configuration of a multi-tenant application.
func appConfig(dsn string) keel.Config {
return keel.Config{
DSN: dsn,
TenantColumns: []string{"organization_id", "business_unit_id"},
Tenant: func(ctx context.Context) ([]any, bool) {
sc, ok := ctx.Value(scopeKey{}).(scope)
if !ok || sc.SystemReason != "" {
return nil, false
}
return []any{sc.OrganizationID, sc.BusinessUnitID}, true
},
System: func(ctx context.Context) (string, bool) {
sc, ok := ctx.Value(scopeKey{}).(scope)
return sc.SystemReason, ok && sc.SystemReason != ""
},
OnBegin: func(ctx context.Context, tx keel.TxInfo) ([]keel.Statement, error) {
sc, _ := ctx.Value(scopeKey{}).(scope)
return []keel.Statement{{
SQL: "SELECT set_config('app.organization_id', $1, true)",
Args: []any{string(sc.OrganizationID)},
}}, nil
},
NewID: newID,
Observers: []keel.Observer{slowQueryLog{threshold: 200 * time.Millisecond}},
}
}
func main() {
ctx := context.Background()
db, err := keel.Open(ctx, appConfig(os.Getenv("DATABASE_URL")))
if err != nil {
log.Fatal(err)
}
defer db.Close()
if err := db.Ping(ctx); err != nil {
log.Fatal(err)
}
}
Output:
Example (DataVersions) ¶
package main
import (
"context"
"crypto/rand"
"encoding/hex"
"log"
"os"
"time"
"github.com/emoss08/keel"
"github.com/emoss08/keel/internal/testmodels/ids"
)
// scope is what the application's authentication middleware puts on a request context.
type scope struct {
OrganizationID ids.ID
BusinessUnitID ids.ID
SystemReason string
}
type scopeKey struct{}
func newID(prefix string) string {
b := make([]byte, 10)
_, _ = rand.Read(b)
return prefix + hex.EncodeToString(b)
}
// slowQueryLog is an Observer that logs statements slower than threshold.
type slowQueryLog struct{ threshold time.Duration }
func (slowQueryLog) BeforeQuery(ctx context.Context, _ *keel.QueryEvent) context.Context {
return ctx
}
func (l slowQueryLog) AfterQuery(_ context.Context, e *keel.QueryEvent) {
if e.Duration >= l.threshold {
log.Printf("slow %s on %s (%d args, %d rows): %v: %s",
e.Op, e.Table, e.Args, e.Rows, e.Duration, e.SQL)
}
}
// appConfig is the configuration of a multi-tenant application.
func appConfig(dsn string) keel.Config {
return keel.Config{
DSN: dsn,
TenantColumns: []string{"organization_id", "business_unit_id"},
Tenant: func(ctx context.Context) ([]any, bool) {
sc, ok := ctx.Value(scopeKey{}).(scope)
if !ok || sc.SystemReason != "" {
return nil, false
}
return []any{sc.OrganizationID, sc.BusinessUnitID}, true
},
System: func(ctx context.Context) (string, bool) {
sc, ok := ctx.Value(scopeKey{}).(scope)
return sc.SystemReason, ok && sc.SystemReason != ""
},
OnBegin: func(ctx context.Context, tx keel.TxInfo) ([]keel.Statement, error) {
sc, _ := ctx.Value(scopeKey{}).(scope)
return []keel.Statement{{
SQL: "SELECT set_config('app.organization_id', $1, true)",
Args: []any{string(sc.OrganizationID)},
}}, nil
},
NewID: newID,
Observers: []keel.Observer{slowQueryLog{threshold: 200 * time.Millisecond}},
}
}
func main() {
ctx := context.Background()
cfg := appConfig(os.Getenv("DATABASE_URL"))
cfg.DataVersions = keel.DataVersionsConfig{
Enabled: true,
Interval: time.Second,
OnError: func(err error) { log.Printf("data versions: %v", err) },
}
db, err := keel.Open(ctx, cfg)
if err != nil {
log.Fatal(err)
}
defer db.Close()
}
Output:
Example (Pool) ¶
package main
import (
"context"
"crypto/rand"
"encoding/hex"
"log"
"os"
"time"
"github.com/emoss08/keel"
"github.com/emoss08/keel/internal/testmodels/ids"
"github.com/jackc/pgx/v5/pgxpool"
)
// scope is what the application's authentication middleware puts on a request context.
type scope struct {
OrganizationID ids.ID
BusinessUnitID ids.ID
SystemReason string
}
type scopeKey struct{}
func newID(prefix string) string {
b := make([]byte, 10)
_, _ = rand.Read(b)
return prefix + hex.EncodeToString(b)
}
// slowQueryLog is an Observer that logs statements slower than threshold.
type slowQueryLog struct{ threshold time.Duration }
func (slowQueryLog) BeforeQuery(ctx context.Context, _ *keel.QueryEvent) context.Context {
return ctx
}
func (l slowQueryLog) AfterQuery(_ context.Context, e *keel.QueryEvent) {
if e.Duration >= l.threshold {
log.Printf("slow %s on %s (%d args, %d rows): %v: %s",
e.Op, e.Table, e.Args, e.Rows, e.Duration, e.SQL)
}
}
// appConfig is the configuration of a multi-tenant application.
func appConfig(dsn string) keel.Config {
return keel.Config{
DSN: dsn,
TenantColumns: []string{"organization_id", "business_unit_id"},
Tenant: func(ctx context.Context) ([]any, bool) {
sc, ok := ctx.Value(scopeKey{}).(scope)
if !ok || sc.SystemReason != "" {
return nil, false
}
return []any{sc.OrganizationID, sc.BusinessUnitID}, true
},
System: func(ctx context.Context) (string, bool) {
sc, ok := ctx.Value(scopeKey{}).(scope)
return sc.SystemReason, ok && sc.SystemReason != ""
},
OnBegin: func(ctx context.Context, tx keel.TxInfo) ([]keel.Statement, error) {
sc, _ := ctx.Value(scopeKey{}).(scope)
return []keel.Statement{{
SQL: "SELECT set_config('app.organization_id', $1, true)",
Args: []any{string(sc.OrganizationID)},
}}, nil
},
NewID: newID,
Observers: []keel.Observer{slowQueryLog{threshold: 200 * time.Millisecond}},
}
}
func main() {
ctx := context.Background()
pc, err := pgxpool.ParseConfig(os.Getenv("DATABASE_URL"))
if err != nil {
log.Fatal(err)
}
pc.MaxConns = 32
pc.MaxConnIdleTime = 5 * time.Minute
cfg := appConfig("")
cfg.Pool = pc
cfg.PlanCache = keel.PlanCacheCustom
cfg.StatementCacheCapacity = 1024
db, err := keel.Open(ctx, cfg)
if err != nil {
log.Fatal(err)
}
defer db.Close()
}
Output:
Example (Replicas) ¶
package main
import (
"context"
"crypto/rand"
"encoding/hex"
"fmt"
"log"
"os"
"time"
"github.com/emoss08/keel"
"github.com/emoss08/keel/internal/testmodels/ids"
)
// scope is what the application's authentication middleware puts on a request context.
type scope struct {
OrganizationID ids.ID
BusinessUnitID ids.ID
SystemReason string
}
type scopeKey struct{}
func newID(prefix string) string {
b := make([]byte, 10)
_, _ = rand.Read(b)
return prefix + hex.EncodeToString(b)
}
// slowQueryLog is an Observer that logs statements slower than threshold.
type slowQueryLog struct{ threshold time.Duration }
func (slowQueryLog) BeforeQuery(ctx context.Context, _ *keel.QueryEvent) context.Context {
return ctx
}
func (l slowQueryLog) AfterQuery(_ context.Context, e *keel.QueryEvent) {
if e.Duration >= l.threshold {
log.Printf("slow %s on %s (%d args, %d rows): %v: %s",
e.Op, e.Table, e.Args, e.Rows, e.Duration, e.SQL)
}
}
// appConfig is the configuration of a multi-tenant application.
func appConfig(dsn string) keel.Config {
return keel.Config{
DSN: dsn,
TenantColumns: []string{"organization_id", "business_unit_id"},
Tenant: func(ctx context.Context) ([]any, bool) {
sc, ok := ctx.Value(scopeKey{}).(scope)
if !ok || sc.SystemReason != "" {
return nil, false
}
return []any{sc.OrganizationID, sc.BusinessUnitID}, true
},
System: func(ctx context.Context) (string, bool) {
sc, ok := ctx.Value(scopeKey{}).(scope)
return sc.SystemReason, ok && sc.SystemReason != ""
},
OnBegin: func(ctx context.Context, tx keel.TxInfo) ([]keel.Statement, error) {
sc, _ := ctx.Value(scopeKey{}).(scope)
return []keel.Statement{{
SQL: "SELECT set_config('app.organization_id', $1, true)",
Args: []any{string(sc.OrganizationID)},
}}, nil
},
NewID: newID,
Observers: []keel.Observer{slowQueryLog{threshold: 200 * time.Millisecond}},
}
}
func main() {
ctx := context.Background()
cfg := appConfig(os.Getenv("DATABASE_URL"))
cfg.Replicas = []keel.ReplicaConfig{
{DSN: os.Getenv("REPLICA_A_URL")},
{DSN: os.Getenv("REPLICA_B_URL"), Weight: 2},
}
cfg.MaxReplicaLag = 2 * time.Second
db, err := keel.Open(ctx, cfg)
if err != nil {
log.Fatal(err)
}
defer db.Close()
for _, r := range db.Replicas() {
fmt.Println(r.Name, r.Healthy, r.Lag, r.Position, r.InFlight, r.Reads, r.Err)
}
fmt.Println("replica reads that ran on the primary:", db.ReplicaFallbacks())
}
Output:
func (*DB) CacheStats ¶
func (db *DB) CacheStats() CacheStats
CacheStats returns the result cache's counters; all zero without Config.Cache.
func (*DB) Close ¶
func (db *DB) Close()
Close stops the replica lag tracker, writes the pending data versions, waits for the replies to read-only COMMITs keel did not wait for, and closes every connection, the replicas' included. Calls after the first do nothing.
func (*DB) DataVersions ¶
func (db *DB) DataVersions(ctx context.Context, tenantKey string, tables ...string) (map[string]int64, error)
DataVersions returns the current version of each table for the tenant with key tenantKey (see TenantKey), in one query; a table never changed has version 0. A table's version also moves with the changes recorded for no tenant ("") and for each shorter prefix of the key, such as a table that holds only the first tenant column, so it changes whenever data the tenant can read in it changes. Versions are written at most Config.DataVersions.Interval after the write commits.
It reads the primary, joining the transaction on ctx. On a context marked with OnReplica it also raises the context's commit position to the primary's current one, in the same round trip, so data read from a replica afterwards is at least as new as the versions.
Example ¶
package main
import (
"context"
"fmt"
"log"
"github.com/emoss08/keel"
"github.com/emoss08/keel/internal/testmodels/ids"
)
// appDB is the application's database, opened once at startup (see ExampleOpen).
var appDB *keel.DB
// scope is what the application's authentication middleware puts on a request context.
type scope struct {
OrganizationID ids.ID
BusinessUnitID ids.ID
SystemReason string
}
type scopeKey struct{}
func withTenant(ctx context.Context, org, bu ids.ID) context.Context {
return context.WithValue(ctx, scopeKey{}, scope{OrganizationID: org, BusinessUnitID: bu})
}
// requestContext returns the context of a request authenticated for one tenant.
func requestContext() context.Context {
return withTenant(context.Background(), "org_acme", "bu_dallas")
}
func main() {
ctx, db := requestContext(), appDB
tenant := db.TenantKey(ctx)
versions, err := db.DataVersions(ctx, tenant, "shipments", "customers")
if err != nil {
log.Fatal(err)
}
cacheKey := fmt.Sprintf("open-shipments:%s:%d:%d", tenant, versions["shipments"], versions["customers"])
fmt.Println(cacheKey)
}
Output:
func (*DB) FlushDataVersions ¶
FlushDataVersions writes the pending data versions now instead of at the next interval, for tests and before a planned shutdown (Close also flushes). It does nothing when data versions are off.
func (*DB) ForgetColumnTypes ¶
func (db *DB) ForgetColumnTypes()
ForgetColumnTypes drops the cached column types, so the next bulk write reads them again. Call it after a migration changes a table while the process runs.
func (*DB) Intercept ¶
func (db *DB) Intercept(t interface{ TableRef() *Ref }, fn Interceptor)
Intercept registers fn for the statements whose base is t's table, as Interceptor describes: reads, Save, DeleteRow and bulk updates and deletes. Joined relations are not intercepted.
Example ¶
package main
import (
"context"
"errors"
"fmt"
"log"
"github.com/emoss08/keel"
"github.com/emoss08/keel/internal/testmodels/fleet/fleetdb"
"github.com/emoss08/keel/internal/testmodels/ids"
)
// appDB is the application's database, opened once at startup (see ExampleOpen).
var appDB *keel.DB
// scope is what the application's authentication middleware puts on a request context.
type scope struct {
OrganizationID ids.ID
BusinessUnitID ids.ID
SystemReason string
}
type scopeKey struct{}
func withTenant(ctx context.Context, org, bu ids.ID) context.Context {
return context.WithValue(ctx, scopeKey{}, scope{OrganizationID: org, BusinessUnitID: bu})
}
// requestContext returns the context of a request authenticated for one tenant.
func requestContext() context.Context {
return withTenant(context.Background(), "org_acme", "bu_dallas")
}
type portalKey struct{}
func main() {
db := appDB
s := fleetdb.Shipments
db.Intercept(s, func(ctx context.Context, q *keel.Intercepted) error {
customers, ok := ctx.Value(portalKey{}).([]ids.ID)
if !ok {
return nil
}
if q.Op == keel.OpDelete {
return errors.New("portal users cannot delete shipments")
}
q.Where(s.CustomerID.In(customers...))
return nil
})
ctx := context.WithValue(requestContext(), portalKey{}, []ids.ID{"cus_01"})
rows, err := s.Query().Limit(20).List(ctx, db)
if err != nil {
log.Fatal(err)
}
fmt.Println(len(rows))
}
Output:
func (*DB) InterceptAll ¶
func (db *DB) InterceptAll(fn Interceptor)
InterceptAll registers fn for the statements of every table that Intercept would cover: reads, Save, DeleteRow and bulk updates and deletes, never inserts. It runs before the interceptors registered for one table.
func (*DB) JSON ¶
JSON returns the codec db encodes and decodes json and jsonb values with: Config.JSON, with each function it leaves nil taken from the default, sonic in its encoding/json-compatible mode. Packages built on keel use it to read JSON the way the application configured.
func (*DB) Pool ¶
Pool returns the underlying pgx pool, for work keel does not cover (LISTEN, COPY of foreign data). Statements sent through it bypass scope checks and hooks.
func (*DB) ReadCommitFailures ¶
ReadCommitFailures returns how many connections keel closed since Open because the reply to a read-only COMMIT or ROLLBACK it did not wait for failed.
func (*DB) ReplicaFallbacks ¶
ReplicaFallbacks counts the reads that asked for a replica and ran on the primary because no replica was healthy, within the lag bound and past the context's commit position.
func (*DB) Replicas ¶
func (db *DB) Replicas() []ReplicaStats
Replicas returns the state of db's replicas, in Config.Replicas order.
func (*DB) TenantColumns ¶
TenantColumns returns Config.TenantColumns: the tenant columns, in the order the values of a tenant (Config.Tenant) and of a tenant key (TenantKey) come in. The slice is a copy.
func (*DB) TenantKey ¶
TenantKey returns the data-versions tenant key of ctx's scope: TenantKeyOf the tenant values, or "" under a system scope or without a scope.
func (*DB) TenantWaits ¶
TenantWaits returns how many terminals and transactions have waited for a tenant slot (Config.TenantConcurrency) since Open.
type DataVersionsConfig ¶
type DataVersionsConfig struct {
// Enabled turns data versions on. They are off by default: each changed (tenant, table) pair
// costs a row upsert per Interval.
Enabled bool
// Interval is how often pending pairs are written; a pair changed many times within it is
// bumped once. Defaults to 1s.
Interval time.Duration
// OnError receives the errors of background writes; the pairs of a failed write are retried
// at the next interval.
OnError func(err error)
}
DataVersionsConfig configures data versions: a counter per tenant and table that keel bumps after a write to the table commits, for caches keyed on the data they read. keel keeps them in keel_data_versions, which it creates on first use under an advisory lock.
type Decimal ¶
type Decimal string
Decimal is an exact decimal number in the text form Postgres writes for numeric: "-12.3400", "0.5", "NaN", "Infinity", "-Infinity". It keeps every digit and the scale, compares exactly as text after normalisation by the application, binds as a numeric parameter, and goes to CSV, JSON and spreadsheets without conversion.
type DeclaredFunc ¶
type DeclaredFunc interface {
Decl() *FuncDecl
}
DeclaredFunc is a typed function declaration, accepted by VerifyFuncs: a Func0, Func1, Func2 or Func3.
type Drift ¶
type Drift struct {
Kind DriftKind `json:"kind"`
// Table is the table the difference is in: the model's, a join table, or a relation's target;
// for a function drift, the declared function's name.
Table string `json:"table"`
// Column is the column, empty for a whole table.
Column string `json:"column,omitempty"`
// Field is the model field or relation (Shipment.Weight), empty for a whole table.
Field string `json:"field,omitempty"`
// Detail says what differs.
Detail string `json:"detail"`
// Fix suggests how to make them agree.
Fix string `json:"fix"`
}
Drift is one difference between a model and the live database.
func Verify ¶
Verify compares the metadata of tables (keeltables.All(), say) with the live catalog and returns every difference that would make keel's statements fail or decode wrongly: missing tables, views and columns, primary keys that differ from the models' pk columns, nullable columns read into fields that cannot hold NULL, generated columns on fields keel writes, jsonb fields on columns that are not json, Go kinds that cannot hold the column's type, many-to-many join tables without their key or shared tenant columns, and relation keys that do not exist. Drifts come in the order of tables, then columns; none means the models and the database agree.
Verify reads the catalog in one statement on a pool connection of its own, outside any transaction and scope, resolving names through the connection's search_path as keel's statements do. Views and materialized views are checked for their columns and types only: Postgres records neither a primary key nor nullability for them. A noselect column is not checked for NULL, since it is read only when a query picks it. A field of a scanner or other custom type (schema.KindOther) is not type-checked, since its Scan method decides what it accepts.
func VerifyFuncs ¶
VerifyFuncs checks typed function declarations against the live catalog: each declared function must exist as a plain function (not a procedure or an aggregate), resolved through the connection's search_path when its name carries no schema, with an overload that takes the declared number of arguments (counting defaults and a variadic last argument) and returns a type the declared result kind decodes (the rules Verify applies to columns; polymorphic results are not checked). It reads the catalog in one statement on a pool connection, outside any transaction and scope, and returns one Drift per mismatch in the order of fns; none means every call the declarations make resolves.
type DriftKind ¶
type DriftKind string
DriftKind names what a Drift found.
const ( // DriftMissingTable is a model whose table or view does not exist. DriftMissingTable DriftKind = "missing_table" // DriftMissingColumn is a stored field whose column does not exist. DriftMissingColumn DriftKind = "missing_column" // DriftPrimaryKey is a model whose pk columns are not the table's primary key. DriftPrimaryKey DriftKind = "primary_key" // DriftNullable is a nullable column read into a field that cannot hold NULL. DriftNullable DriftKind = "nullable" // DriftGenerated is a generated or always-identity column on a field keel writes. DriftGenerated DriftKind = "generated" // DriftJSON is a jsonb field on a column that is neither json nor jsonb. DriftJSON DriftKind = "json" // DriftType is a field whose Go kind cannot hold the column's type. DriftType DriftKind = "type" // DriftJoinTable is a many-to-many join table that is missing, or lacks a key or tenant column. DriftJoinTable DriftKind = "join_table" // DriftRelationKey is a relation whose key column or target table does not exist. DriftRelationKey DriftKind = "relation_key" // DriftConstraint is a constraint a model's Constraints method names that the table lacks. DriftConstraint DriftKind = "constraint" // DriftMissingFunction is a declared database function (DeclareFunc0 … DeclareFunc3) that does // not exist, or exists only as a procedure, aggregate or window function. DriftMissingFunction DriftKind = "missing_function" // DriftFunctionArity is a declared function none of whose overloads takes the declared number // of arguments. DriftFunctionArity DriftKind = "function_arity" // DriftFunctionResult is a declared function whose overloads of the declared arity return a // type the declared result kind cannot hold. DriftFunctionResult DriftKind = "function_result" )
type DynCTE ¶
type DynCTE struct {
// contains filtered or unexported fields
}
DynCTE is a dynamic query declared on another with With: WITH keel_cte (…) AS (sub). A statement names its CTEs keel_cte, keel_cte_2, … in the order it uses them, across every query nested in it, so no name shadows another. Its columns are sub's selected expressions, addressed by label.
type DynCaseBuilder ¶
type DynCaseBuilder struct {
// contains filtered or unexported fields
}
DynCaseBuilder builds CASE WHEN … THEN … [ELSE …] END. Start it with DynCase.
func (DynCaseBuilder) Else ¶
func (b DynCaseBuilder) Else(v any) DynExpr
Else ends the CASE with ELSE v.
func (DynCaseBuilder) End ¶
func (b DynCaseBuilder) End() DynExpr
End ends the CASE without ELSE: rows no WHEN matches are NULL.
func (DynCaseBuilder) When ¶
func (b DynCaseBuilder) When(cond Pred, then any) DynCaseBuilder
When adds WHEN cond THEN then; then is a DynExpr, an Expr or a value bound as a parameter.
type DynColumn ¶
type DynColumn struct {
Name string
Type ValueType
// OID is the column's Postgres type.
OID uint32
// Nulls is a bitmap with bit i%64 of word i/64 set when row i is NULL; it is shorter than the
// rows when the last rows are not NULL, and empty when none is.
Nulls []uint64
String []string
Int64 []int64
Float64 []float64
Numeric []Decimal
Bool []bool
Time []time.Time
Bytes [][]byte
JSON [][]byte
// contains filtered or unexported fields
}
DynColumn is one column of a dynamic result: its label, its value type and one typed vector, the one Type selects, with a zero value at each NULL row. Strings, decimals, bytes and JSON share the result's memory instead of allocating per value.
type DynExpr ¶
type DynExpr struct {
// contains filtered or unexported fields
}
DynExpr is an expression of a dynamic query, built at run time: a column or relation path resolved by name against the schema, a literal bound as a parameter, or an operator, function, CASE, CAST, aggregate or window over other expressions. It is a Pred, so Where, Having, And, Or and Filter take it, and an Expr, so it mixes with keel's typed expressions.
A name that does not resolve, a function outside the allow-list or a misplaced Filter makes an expression that carries an error: Err reports it, and a statement that uses the expression fails with it. Identifiers in the SQL only ever come from the schema; every value is a bound parameter.
func DynArray ¶
DynArray is the array ARRAY[vs…]: DynExprs, Exprs or values bound as parameters. Cast an element when Postgres cannot infer the array's type from its elements alone.
func DynConst ¶
DynConst writes v into the SQL text as a constant instead of binding it: an integer, a float, a bool or a string (a named type over one of them included), the string quoted with its single quotes doubled. Use it where Postgres must see the constant itself, as when the expression has to match an expression index or the predicate of a partial index, which a parameter does not under a generic plan. Values from users belong in DynLit: each distinct constant is a distinct statement.
func DynFunc ¶
DynFunc calls the function name of keel's allow-list with args: DynExprs, Exprs or values bound as parameters. Scalar functions: abs, ceil, floor, round, trunc, sign, sqrt, power, mod, greatest, least, coalesce, nullif, lower, upper, initcap, trim, ltrim, rtrim, btrim, length, substr, left, right, replace, concat, concat_ws, strpos, split_part, lpad, rpad, md5, to_char, date_trunc, date_part, date_bin, age, to_timestamp, timezone, make_date, now, jsonb_extract_path_text, jsonb_typeof, jsonb_array_length, cardinality, array_to_string, array_append, array_remove, array_position, regexp_replace, jsonb_build_array, jsonb_build_object, jsonb_set, width_bucket, gen_random_uuid, int4range, int8range, numrange, tstzrange, ln, exp, log, and the set-returning unnest, generate_series, jsonb_each, jsonb_each_text, jsonb_array_elements, jsonb_array_elements_text and jsonb_object_keys, for keel.Rows. Aggregates, which take Filter and Over: sum, avg, min, max, count (count() is count(*)), bool_and, bool_or, string_agg, stddev, stddev_samp, stddev_pop, variance, var_samp, var_pop, array_agg, jsonb_agg, jsonb_object_agg. Any other name makes an expression that fails.
func DynLit ¶
DynLit binds v as a parameter. A nil v is NULL. Postgres infers the parameter's type from where it is used; Cast fixes it where the context does not (a literal on both sides of an operator, a CASE result).
func DynOf ¶
DynOf wraps one of keel's typed expressions (a column of a generated handle, an Agg such as RowNumber or Grouping, a Raw) as a DynExpr. An Agg keeps Filter and Over.
func DynRow ¶
DynRow is the row value ROW(vs…): DynExprs, Exprs or values bound as parameters. Rows compare element by element in order, so DynRow(a, b).Gt(DynRow(x, y)) is the keyset condition of a page ordered by a, b ascending, which an index on (a, b) serves.
func ResolvePath ¶
ResolvePath resolves a column path on a typed handle, query or alias, for code that builds a typed query's filters and order from names chosen at run time (an API's filter and sort fields). The segments are read as Path reads them: the last names a column (its SQL name or Go field name), the ones before it to-one relations (their Go name or its snake_case form) followed from t's table. The result is a column of the same relation path the generated handles build, so ResolvePath(s, "customer", "name") and s.Customer().Name are one join: the typed query LEFT JOINs the relations with their tenant condition where the expression is used, in Where, Order (a keyset page included) and Pick. A name that does not resolve, or a path through a to-many relation, makes an expression that carries the error, which the query's terminal returns.
func (DynExpr) As ¶
As returns the expression with label as its result column's name. The label is kept in Go and never written into the SQL, so it may be any text.
func (DynExpr) Cast ¶
Cast is CAST(d AS t). A literal is cast as $n::t, and equal literals cast to the same type share one parameter.
func (DynExpr) Collate ¶
Collate is d COLLATE "name", ordering or comparing d by that collation ("C" orders by bytes).
func (DynExpr) ContainsFold ¶
ContainsFold is Contains ignoring case.
func (DynExpr) CountDistinct ¶
CountDistinct is count(DISTINCT d).
func (DynExpr) Distinct ¶
Distinct aggregates the distinct values of d's argument: array_agg(DISTINCT e). d must be an aggregate.
func (DynExpr) Div ¶
Div is d / v. Integer division truncates; Cast one side to TypeNumeric or TypeFloat64 for a fraction, and wrap the divisor with NullIf(0) where it can be zero.
func (DynExpr) Eq ¶
Eq is d = v. v is a DynExpr, an Expr or a value bound as a parameter, as for every operator.
func (DynExpr) Err ¶
Err returns the error the expression carries: an unresolved name, a function outside the allow-list, a misplaced Filter or Over. A statement using the expression fails with it.
func (DynExpr) Filter ¶
Filter restricts the rows an aggregate sees: agg FILTER (WHERE p). d must be an aggregate.
func (DynExpr) In ¶
In is d = ANY(list): list is a slice ([]string, []int64, []any of one type) bound as one array parameter, so the statement text does not depend on its length.
func (DynExpr) InUnnest ¶
InUnnest is d IN (SELECT * FROM unnest(arrays…)): d, usually a DynRow, equals one of the rows the arrays zip into, element i of each array making row i. Each array is bound as one parameter, so the statement keeps one shape whatever the number of rows.
func (DynExpr) IsDistinctFrom ¶
IsDistinctFrom is d IS DISTINCT FROM v: inequality that treats NULL as a value.
func (DynExpr) Label ¶
Label is the name the expression's result column gets: the one given with As, else the path a column was resolved from ("customer.name"), else the function or aggregate name.
func (DynExpr) Match ¶
func (d DynExpr) Match(query string, opts ...SearchOption) DynExpr
Match is d @@ query, as TSVectorCol.Match: d is a tsvector column or expression.
func (DynExpr) OrderBy ¶
OrderBy orders the rows an aggregate reads: jsonb_agg(e ORDER BY …). d must be an aggregate.
func (DynExpr) Over ¶
Over turns an aggregate or window function into a window function over w: a running or moving aggregate, a rank, an offset or a frame value.
func (DynExpr) Rank ¶
func (d DynExpr) Rank(query string, opts ...SearchOption) DynExpr
Rank is ts_rank(d, query), as TSVectorCol.Rank.
type DynPreAgg ¶
type DynPreAgg struct {
// contains filtered or unexported fields
}
DynPreAgg aggregates the rows of a to-many relation per parent row, so measures over the relation do not fan the query's rows out. Measure returns a column of the per-parent result; the query can select it, filter on it in Having, or aggregate it again (sum of per-shipment distances per customer). A parent without related rows reads NULL.
func (*DynPreAgg) AllParents ¶
AllParents makes a PreAggCTE aggregate the related rows of every parent in scope instead of only the parents the query's filters select: one pass without the semi-join that restricts the rows, cheaper when the filters keep most parents, dearer when they keep few. It does nothing to a PreAggLateral, which reads each selected parent's rows.
type DynQuery ¶
type DynQuery struct {
// contains filtered or unexported fields
}
DynQuery is a query whose tables, columns, relations and expressions are chosen at run time. It renders through the same machinery as typed queries: every table it references, joined, in a subquery or in a pre-aggregation, gets the scope's tenant predicate (or needs AllTenants under a system scope), interceptors apply to every statement on a table, and identifiers only ever come from the schema. Its result is a typed columnar DynResult.
A DynQuery is a builder: methods change it and return it, and it must not be used by several goroutines at once. Builder errors (an unknown name, an invalid expression) are kept and returned by Err and by every terminal.
func DynExcept ¶
DynExcept is the rows of the first part that no other part returns: (q1) EXCEPT (q2) ….
func DynFromQuery ¶
DynFromQuery starts a dynamic query over the rows of sub, as a derived table: its columns are sub's selected expressions, resolved by label with Path. Use it to filter, group or order the result of another dynamic query.
func DynIntersect ¶
DynIntersect is the rows every part returns: (q1) INTERSECT (q2) ….
func DynUnion ¶
DynUnion is the distinct rows of every part: (q1) UNION (q2) …. The parts select the same number of expressions; the result's columns take the first part's labels. Filter, order and limit it like any dynamic query.
func DynUnionAll ¶
DynUnionAll is every row of every part: (q1) UNION ALL (q2) ….
Example ¶
package main
import (
"context"
"fmt"
"log"
"github.com/emoss08/keel"
"github.com/emoss08/keel/internal/testmodels/fleet/fleetdb"
"github.com/emoss08/keel/internal/testmodels/ids"
)
// appDB is the application's database, opened once at startup (see ExampleOpen).
var appDB *keel.DB
// scope is what the application's authentication middleware puts on a request context.
type scope struct {
OrganizationID ids.ID
BusinessUnitID ids.ID
SystemReason string
}
type scopeKey struct{}
func withTenant(ctx context.Context, org, bu ids.ID) context.Context {
return context.WithValue(ctx, scopeKey{}, scope{OrganizationID: org, BusinessUnitID: bu})
}
// requestContext returns the context of a request authenticated for one tenant.
func requestContext() context.Context {
return withTenant(context.Background(), "org_acme", "bu_dallas")
}
// reportTables is the registry of the tables reports can start from.
var reportTables, _ = keel.NewRegistry(fleetdb.ShipmentsSchema(), fleetdb.CustomersSchema(),
fleetdb.MovesSchema(), fleetdb.StopsSchema(), fleetdb.TagsSchema(), fleetdb.LocationsSchema())
func main() {
ctx, db := requestContext(), appDB
names := func(table string) *keel.DynQuery {
q := reportTables.Query(table)
return q.Select(q.Path("name").As("name"))
}
all := keel.DynUnionAll(names("customers"), names("locations"), names("tags"))
all.Order(all.Path("name").Asc())
res, err := all.Run(ctx, db)
if err != nil {
log.Fatal(err)
}
fmt.Println(res.Column("name").String)
}
Output:
func (*DynQuery) AllTenants ¶
AllTenants lets the query cross tenants. It needs a system scope. It covers the query's joined relations, Any and None, and pre-aggregations; a query used in With, DynFromQuery or a set operation declares it itself.
func (*DynQuery) Compute ¶
Compute fills the computed field named name (its SQL name or Go field name) with e, as Query.Compute does: Path(name) then reads e, in Select, Where, Order and keyset pages, and a query without Select returns the field after the table's default columns. e's type must fit the field when keel knows it.
Example ¶
package main
import (
"context"
"fmt"
"log"
"github.com/emoss08/keel"
"github.com/emoss08/keel/internal/testmodels/fleet/fleetdb"
"github.com/emoss08/keel/internal/testmodels/ids"
)
// appDB is the application's database, opened once at startup (see ExampleOpen).
var appDB *keel.DB
// scope is what the application's authentication middleware puts on a request context.
type scope struct {
OrganizationID ids.ID
BusinessUnitID ids.ID
SystemReason string
}
type scopeKey struct{}
func withTenant(ctx context.Context, org, bu ids.ID) context.Context {
return context.WithValue(ctx, scopeKey{}, scope{OrganizationID: org, BusinessUnitID: bu})
}
// requestContext returns the context of a request authenticated for one tenant.
func requestContext() context.Context {
return withTenant(context.Background(), "org_acme", "bu_dallas")
}
func main() {
ctx, db := requestContext(), appDB
c, s := fleetdb.Contacts, fleetdb.Shipments
q := keel.DynFrom(fleetdb.ContactsSchema())
q.Compute("loads", keel.DynOf(keel.Scalar(s.Pick(keel.Count()).Where(s.CustomerID.EqCol(c.CustomerID))))).
Select(q.Path("name"), q.Path("loads")).
Order(q.Path("loads").Desc()).
Limit(10)
res, err := q.Run(ctx, db)
if err != nil {
log.Fatal(err)
}
fmt.Println(res.Rows)
}
Output:
func (*DynQuery) CountEstimate ¶
CountEstimate returns the number of rows the dynamic query returns, counted exactly up to exactUpTo, and the planner's estimate when it returns more, as Query.CountEstimate does: the count wraps the query (SELECT count(*) FROM (SELECT 1 FROM (query) d LIMIT exactUpTo+1) c), so a Limit on the query bounds both the count and the estimate, and EXPLAIN (FORMAT JSON) of the query rides in the same round trip.
func (*DynQuery) CustomPlan ¶
CustomPlan plans the query for its parameter values on every execution, as Query.CustomPlan.
func (*DynQuery) Err ¶
Err returns the first builder error: an unknown table, column or relation, an invalid expression, a label selected twice.
func (*DynQuery) Exists ¶
Exists reports whether the query returns any row, in one statement that stops at the first row.
func (*DynQuery) Explain ¶
Explain returns the plan of the dynamic query's statement, as Query.Explain does: an analytics guard compares TotalCost and Rows with its limits before running a report.
Example ¶
package main
import (
"context"
"fmt"
"log"
"github.com/emoss08/keel"
"github.com/emoss08/keel/internal/testmodels/fleet/fleetdb"
"github.com/emoss08/keel/internal/testmodels/ids"
)
// appDB is the application's database, opened once at startup (see ExampleOpen).
var appDB *keel.DB
// scope is what the application's authentication middleware puts on a request context.
type scope struct {
OrganizationID ids.ID
BusinessUnitID ids.ID
SystemReason string
}
type scopeKey struct{}
func withTenant(ctx context.Context, org, bu ids.ID) context.Context {
return context.WithValue(ctx, scopeKey{}, scope{OrganizationID: org, BusinessUnitID: bu})
}
// requestContext returns the context of a request authenticated for one tenant.
func requestContext() context.Context {
return withTenant(context.Background(), "org_acme", "bu_dallas")
}
// reportTables is the registry of the tables reports can start from.
var reportTables, _ = keel.NewRegistry(fleetdb.ShipmentsSchema(), fleetdb.CustomersSchema(),
fleetdb.MovesSchema(), fleetdb.StopsSchema(), fleetdb.TagsSchema(), fleetdb.LocationsSchema())
func main() {
ctx, db := requestContext(), appDB
q := reportTables.Query("shipments")
q.Select(q.Path("customer", "code"), q.Path("total").Sum().As("revenue")).
Group(q.Path("customer", "code"))
plan, err := q.Explain(ctx, db, keel.ExplainOptions{})
if err != nil {
log.Fatal(err)
}
if plan.TotalCost > 1_000_000 || plan.Rows > 100_000 {
log.Fatal("report too expensive")
}
fmt.Println(plan.TotalCost)
}
Output:
func (*DynQuery) GroupingSets ¶
GroupingSets groups by each of sets in turn: GROUP BY GROUPING SETS (…), as Query.GroupingSets.
func (*DynQuery) Omit ¶
Omit leaves the columns named (SQL or Go field names) out of the default columns a query without Select returns, as Query.Omit does for typed reads.
func (*DynQuery) Page ¶
Page reads the page of rows after the request's cursor, in the query's order. Every order term must be a selected expression or one keel can add as a hidden column; a query on a table that neither groups nor is DISTINCT gets its primary key appended to the order, so each row has a unique position. Grouped and derived queries must order by expressions that are unique together. The result's Next is the cursor of the following page.
Example ¶
package main
import (
"context"
"fmt"
"log"
"github.com/emoss08/keel"
"github.com/emoss08/keel/internal/testmodels/fleet/fleetdb"
"github.com/emoss08/keel/internal/testmodels/ids"
)
// appDB is the application's database, opened once at startup (see ExampleOpen).
var appDB *keel.DB
// scope is what the application's authentication middleware puts on a request context.
type scope struct {
OrganizationID ids.ID
BusinessUnitID ids.ID
SystemReason string
}
type scopeKey struct{}
func withTenant(ctx context.Context, org, bu ids.ID) context.Context {
return context.WithValue(ctx, scopeKey{}, scope{OrganizationID: org, BusinessUnitID: bu})
}
// requestContext returns the context of a request authenticated for one tenant.
func requestContext() context.Context {
return withTenant(context.Background(), "org_acme", "bu_dallas")
}
// reportTables is the registry of the tables reports can start from.
var reportTables, _ = keel.NewRegistry(fleetdb.ShipmentsSchema(), fleetdb.CustomersSchema(),
fleetdb.MovesSchema(), fleetdb.StopsSchema(), fleetdb.TagsSchema(), fleetdb.LocationsSchema())
func main() {
ctx, db := requestContext(), appDB
q := reportTables.Query("shipments")
pro := q.Path("pro_number")
q.Select(pro, q.Path("customer", "code")).Order(pro.Desc())
cursor := ""
for {
res, err := q.Page(ctx, db, keel.After(cursor, 50))
if err != nil {
log.Fatal(err)
}
fmt.Println(res.Column("pro_number").String)
if res.Next == "" {
break
}
cursor = res.Next
}
}
Output:
func (*DynQuery) Path ¶
Path resolves a column by name: the last segment names a column (its SQL name or Go field name), the segments before it relations (their Go name or its snake_case form) followed from the base table, each of them to-one. Path("status"), Path("customer", "name"). The relations are LEFT JOINed with their tenant condition when the expression is used. On a derived query the one segment is the label of a column of the inner query.
func (*DynQuery) Rel ¶
Rel resolves a relation path from the base table: Rel("moves"), Rel("moves", "stops"), Rel("customer").
func (*DynQuery) Rollup ¶
Rollup groups by es and each prefix of es down to the grand total, as Query.Rollup.
Example ¶
package main
import (
"context"
"fmt"
"log"
"github.com/emoss08/keel"
"github.com/emoss08/keel/internal/testmodels/fleet/fleetdb"
"github.com/emoss08/keel/internal/testmodels/ids"
)
// appDB is the application's database, opened once at startup (see ExampleOpen).
var appDB *keel.DB
// scope is what the application's authentication middleware puts on a request context.
type scope struct {
OrganizationID ids.ID
BusinessUnitID ids.ID
SystemReason string
}
type scopeKey struct{}
func withTenant(ctx context.Context, org, bu ids.ID) context.Context {
return context.WithValue(ctx, scopeKey{}, scope{OrganizationID: org, BusinessUnitID: bu})
}
// requestContext returns the context of a request authenticated for one tenant.
func requestContext() context.Context {
return withTenant(context.Background(), "org_acme", "bu_dallas")
}
// reportTables is the registry of the tables reports can start from.
var reportTables, _ = keel.NewRegistry(fleetdb.ShipmentsSchema(), fleetdb.CustomersSchema(),
fleetdb.MovesSchema(), fleetdb.StopsSchema(), fleetdb.TagsSchema(), fleetdb.LocationsSchema())
func main() {
ctx, db := requestContext(), appDB
q := reportTables.Query("shipments")
code := q.Path("customer", "code")
month := keel.DynFunc("date_trunc", "month", keel.DynFunc("to_timestamp", q.Path("created_at"))).
As("month")
revenue := q.Path("total").Sum()
level := keel.DynOf(keel.Grouping(code, month))
q.Select(code, month, revenue.As("revenue"),
keel.DynCount().Filter(q.Path("hazmat").IsTrue()).As("hazmat loads"),
revenue.Sum().Over(keel.Window{}).As("all revenue"),
keel.DynOf(keel.Rank().OverWindow(keel.OrderBy(revenue.Desc()))).As("rank"),
level.As("level")).
Rollup(code, month).
Order(level.Asc(), code.Asc(), month.Asc())
res, err := q.Run(ctx, db)
if err != nil {
log.Fatal(err)
}
fmt.Println(res.Rows)
}
Output:
func (*DynQuery) Select ¶
Select sets the expressions the query returns, in order. Each becomes a column of the result named by its Label. Without Select a table query returns the table's default columns.
func (*DynQuery) Stream ¶
Stream runs the query once and yields its rows in batches of size rows, decoding each batch as the rows arrive, so an export of any size holds one batch in memory. Each batch is a separate DynResult the consumer may keep. A query without rows yields one empty batch, so the columns are known. Breaking out of the loop stops the query: outside a transaction keel cancels it; inside one the driver reads and discards the remaining rows. The loop body must not run statements on the transaction the stream reads from.
Example ¶
package main
import (
"context"
"fmt"
"log"
"github.com/emoss08/keel"
"github.com/emoss08/keel/internal/testmodels/fleet/fleetdb"
"github.com/emoss08/keel/internal/testmodels/ids"
)
// appDB is the application's database, opened once at startup (see ExampleOpen).
var appDB *keel.DB
// scope is what the application's authentication middleware puts on a request context.
type scope struct {
OrganizationID ids.ID
BusinessUnitID ids.ID
SystemReason string
}
type scopeKey struct{}
func withTenant(ctx context.Context, org, bu ids.ID) context.Context {
return context.WithValue(ctx, scopeKey{}, scope{OrganizationID: org, BusinessUnitID: bu})
}
// requestContext returns the context of a request authenticated for one tenant.
func requestContext() context.Context {
return withTenant(context.Background(), "org_acme", "bu_dallas")
}
// reportTables is the registry of the tables reports can start from.
var reportTables, _ = keel.NewRegistry(fleetdb.ShipmentsSchema(), fleetdb.CustomersSchema(),
fleetdb.MovesSchema(), fleetdb.StopsSchema(), fleetdb.TagsSchema(), fleetdb.LocationsSchema())
func main() {
ctx, db := requestContext(), appDB
q := reportTables.Query("shipments")
q.Select(q.Path("pro_number"), q.Path("customer", "name"), q.Path("total"), q.Path("weight"))
for batch, err := range q.Stream(ctx, db, 5000) {
if err != nil {
log.Fatal(err)
}
weight := batch.Column("weight")
for i := range batch.Rows {
if weight.IsNull(i) {
continue
}
fmt.Println(batch.Columns[0].String[i], weight.Int64[i])
}
}
}
Output:
func (*DynQuery) Table ¶
Table returns the query's base table: a registered table, or the derived table of DynFromQuery and the set operations.
func (*DynQuery) With ¶
With declares sub as a common table expression of q and returns it, to Join and to read columns from with Col.
Example ¶
package main
import (
"context"
"fmt"
"log"
"github.com/emoss08/keel"
"github.com/emoss08/keel/internal/testmodels/fleet/fleetdb"
"github.com/emoss08/keel/internal/testmodels/ids"
)
// appDB is the application's database, opened once at startup (see ExampleOpen).
var appDB *keel.DB
// scope is what the application's authentication middleware puts on a request context.
type scope struct {
OrganizationID ids.ID
BusinessUnitID ids.ID
SystemReason string
}
type scopeKey struct{}
func withTenant(ctx context.Context, org, bu ids.ID) context.Context {
return context.WithValue(ctx, scopeKey{}, scope{OrganizationID: org, BusinessUnitID: bu})
}
// requestContext returns the context of a request authenticated for one tenant.
func requestContext() context.Context {
return withTenant(context.Background(), "org_acme", "bu_dallas")
}
// reportTables is the registry of the tables reports can start from.
var reportTables, _ = keel.NewRegistry(fleetdb.ShipmentsSchema(), fleetdb.CustomersSchema(),
fleetdb.MovesSchema(), fleetdb.StopsSchema(), fleetdb.TagsSchema(), fleetdb.LocationsSchema())
func main() {
ctx, db := requestContext(), appDB
sub := reportTables.Query("shipments")
sub.Select(sub.Path("customer_id").As("customer"), sub.Path("total").Sum().As("revenue")).
Group(sub.Path("customer_id"))
c := reportTables.Query("customers")
totals := c.With(sub)
c.Join(totals, totals.Col("customer").Eq(c.Path("id"))).
Select(c.Path("code"), totals.Col("revenue")).
Where(totals.Col("revenue").Gt(10_000))
res, err := c.Run(ctx, db)
if err != nil {
log.Fatal(err)
}
outer := keel.DynFromQuery(sub)
outer.Select(keel.DynCount().As("customers"), outer.Path("revenue").Avg().As("average"))
summary, err := outer.Run(ctx, db)
if err != nil {
log.Fatal(err)
}
fmt.Println(res.Rows, summary.Column("average").Numeric[0])
}
Output:
type DynRel ¶
type DynRel struct {
// contains filtered or unexported fields
}
DynRel is a relation path of a dynamic query, to-one or to-many, for semi-joins (Any, None) and pre-aggregations. Build it with Rel.
func (*DynRel) Any ¶
Any is true when a row of the relation matches ps: EXISTS (…), correlated with the row, with the related tables' tenant predicates and interceptors. A path through several to-many relations nests one EXISTS per to-many relation.
func (*DynRel) Col ¶
Col resolves a column of the relation's table, through further to-one relations: q.Rel("moves").Col("status"). Use it inside Any, None and pre-aggregated measures.
func (*DynRel) PreAggregate ¶
func (d *DynRel) PreAggregate(s PreAggStrategy) *DynPreAgg
PreAggregate aggregates the relation per parent row with strategy s. The path must contain a to-many relation; the first one is the level the rows are aggregated to, and to-many relations after it are joined inside the aggregation.
Example ¶
package main
import (
"context"
"fmt"
"log"
"github.com/emoss08/keel"
"github.com/emoss08/keel/internal/testmodels/fleet/fleetdb"
"github.com/emoss08/keel/internal/testmodels/ids"
)
// appDB is the application's database, opened once at startup (see ExampleOpen).
var appDB *keel.DB
// scope is what the application's authentication middleware puts on a request context.
type scope struct {
OrganizationID ids.ID
BusinessUnitID ids.ID
SystemReason string
}
type scopeKey struct{}
func withTenant(ctx context.Context, org, bu ids.ID) context.Context {
return context.WithValue(ctx, scopeKey{}, scope{OrganizationID: org, BusinessUnitID: bu})
}
// requestContext returns the context of a request authenticated for one tenant.
func requestContext() context.Context {
return withTenant(context.Background(), "org_acme", "bu_dallas")
}
// reportTables is the registry of the tables reports can start from.
var reportTables, _ = keel.NewRegistry(fleetdb.ShipmentsSchema(), fleetdb.CustomersSchema(),
fleetdb.MovesSchema(), fleetdb.StopsSchema(), fleetdb.TagsSchema(), fleetdb.LocationsSchema())
func main() {
ctx, db := requestContext(), appDB
q := reportTables.Query("shipments")
code := q.Path("customer", "code")
moves := q.Rel("moves")
stops := q.Rel("moves", "stops")
pa := moves.PreAggregate(keel.PreAggCTE)
legs := pa.Measure(keel.DynCount().As("legs"))
loaded := pa.Measure(keel.DynCount().Filter(moves.Col("loaded").IsTrue()).As("loaded legs"))
q.Select(code, q.Path("total").Sum().As("revenue"), legs.Sum().As("legs"),
loaded.Sum().As("loaded legs")).
Where(moves.Any(moves.Col("status").Eq("New")),
stops.None(stops.Col("location", "city").Eq("Dallas"))).
Group(code)
res, err := q.Run(ctx, db)
if err != nil {
log.Fatal(err)
}
fmt.Println(res.Rows)
}
Output:
type DynResult ¶
type DynResult struct {
Columns []DynColumn
Rows int
Next string
Total int64
TotalExact bool
// contains filtered or unexported fields
}
DynResult is the typed columnar result of a dynamic query: one DynColumn per selected expression, in order, and the number of rows. Next is the cursor of the following page after Page, empty on the last page. Total and TotalExact are the query's row count when the page request asked for it, as on Page.
func (*DynResult) CursorAt ¶
CursorAt returns the cursor that resumes the page after row i: Page with After(res.CursorAt(i), n) reads the n rows that follow it. It is the format of Next, computed from the row's order values (hidden order columns included) without a statement. It returns "" on a result that Page did not read, and panics when i is out of range.
type ExplainOptions ¶
type ExplainOptions struct {
// Analyze runs the statement (EXPLAIN (ANALYZE, BUFFERS, FORMAT JSON)) to report actual rows,
// timings and buffer use. It runs inside a transaction that is rolled back, or a savepoint
// rolled back to when the context carries a transaction, so nothing the statement does is
// kept.
Analyze bool
// GenericPlan explains the plan Postgres builds without the parameters' values
// (EXPLAIN (GENERIC_PLAN)), the one a prepared statement settles on after its first runs. A
// predicate that reaches a partial index or an expression index only through a parameter's
// value shows here as the scan it really gets. It cannot be combined with Analyze.
GenericPlan bool
}
ExplainOptions configures Explain.
type Expr ¶
type Expr interface {
// contains filtered or unexported methods
}
Expr is a SQL expression. keel's column, aggregate and combinator values implement it; code outside keel builds expressions from those and from Raw.
func ArrayOf ¶
func ArrayOf(q subquery) Expr
ArrayOf is the array of the values of a query that picks one expression: ARRAY(SELECT …), an empty array when the query returns no rows, where array_agg would be NULL. A correlated query collects a row's related values, distinct with Distinct:
keel.ArrayOf(st.Pick(st.City).Where(st.ShipmentID.EqCol(s.ID)).Distinct())
func Scalar ¶
func Scalar(q subquery) Expr
Scalar is the value of a query that picks one expression and returns at most one row, as an expression: (SELECT …). A query that references the outer statement's columns is correlated, so Scalar fills a computed field with an aggregate over related rows:
c.Compute(c.Loads, keel.Scalar(s.Pick(keel.Count()).Where(s.CustomerID.EqCol(c.CustomerID))))
The subquery is scoped like any statement: it gets its tables' tenant predicates.
type Float64ArrayCase ¶
type Float64ArrayCase struct {
// contains filtered or unexported fields
}
Float64ArrayCase builds a CASE whose result is double precision[]. Start it with CaseFloat64Array.
func CaseFloat64Array ¶
func CaseFloat64Array() Float64ArrayCase
CaseFloat64Array starts a CASE whose result is double precision[].
func (Float64ArrayCase) Else ¶
func (b Float64ArrayCase) Else(v Float64ArrayValue) Float64ArrayExpr
Else ends the CASE with ELSE v.
func (Float64ArrayCase) End ¶
func (b Float64ArrayCase) End() Float64ArrayExpr
End ends the CASE without ELSE.
func (Float64ArrayCase) When ¶
func (b Float64ArrayCase) When(cond Pred, v Float64ArrayValue) Float64ArrayCase
When adds WHEN cond THEN v.
type Float64ArrayExpr ¶
type Float64ArrayExpr struct {
// contains filtered or unexported fields
}
Float64ArrayExpr is a computed double precision[] value, with the methods of StringArrayExpr.
func ArrayOfFloat64 ¶
func ArrayOfFloat64(es ...Float64Value) Float64ArrayExpr
ArrayOfFloat64 is ARRAY[es…] of double precision values.
func Float64Array ¶
func Float64Array(vs []float64) Float64ArrayExpr
Float64Array binds vs as $n::double precision[].
func Float64ArrayOf ¶
func Float64ArrayOf(e Expr) Float64ArrayExpr
Float64ArrayOf types e as double precision[].
func (Float64ArrayExpr) At ¶
func (e Float64ArrayExpr) At(i int) Float64Expr
At is (e)[i], the i-th element from 1.
func (Float64ArrayExpr) Coalesce ¶
func (e Float64ArrayExpr) Coalesce(o Float64ArrayValue) Float64ArrayExpr
Coalesce is coalesce(e, o).
func (Float64ArrayExpr) ContainedByExpr ¶
func (e Float64ArrayExpr) ContainedByExpr(o Float64ArrayValue) Pred
ContainedByExpr is (e <@ o).
func (Float64ArrayExpr) ContainsAllExpr ¶
func (e Float64ArrayExpr) ContainsAllExpr(o Float64ArrayValue) Pred
ContainsAllExpr is (e @> o).
func (Float64ArrayExpr) IsNotNull ¶
func (e Float64ArrayExpr) IsNotNull() Pred
IsNotNull is e IS NOT NULL.
func (Float64ArrayExpr) NullIf ¶
func (e Float64ArrayExpr) NullIf(o Float64ArrayValue) Float64ArrayExpr
NullIf is nullif(e, o): NULL where e equals o, as NullIf(keel.Float64Array(nil)) turns an empty array into NULL.
func (Float64ArrayExpr) OverlapsExpr ¶
func (e Float64ArrayExpr) OverlapsExpr(o Float64ArrayValue) Pred
OverlapsExpr is (e && o).
func (Float64ArrayExpr) Unnest ¶
func (e Float64ArrayExpr) Unnest() Float64Expr
Unnest is the set-returning unnest(e), for keel.Rows.
func (Float64ArrayExpr) Without ¶
func (e Float64ArrayExpr) Without(v Float64Value) Float64ArrayExpr
Without is array_remove(e, v).
type Float64ArrayValue ¶
type Float64ArrayValue interface {
Expr
// contains filtered or unexported methods
}
Float64ArrayValue is a double precision[] value: a Float64ArrayExpr.
type Float64Case ¶
type Float64Case struct {
// contains filtered or unexported fields
}
Float64Case builds a CASE whose result is double precision. Start it with CaseFloat64.
func CaseFloat64 ¶
func CaseFloat64() Float64Case
CaseFloat64 starts a CASE whose result is double precision.
func (Float64Case) Else ¶
func (b Float64Case) Else(v Float64Value) Float64Expr
Else ends the CASE with ELSE v.
func (Float64Case) When ¶
func (b Float64Case) When(cond Pred, v Float64Value) Float64Case
When adds WHEN cond THEN v.
type Float64Col ¶
type Float64Col struct{ Col }
Float64Col is a double precision or real column.
func (Float64Col) Add ¶
func (c Float64Col) Add(v Float64Value) Float64Expr
Add is (col + v), an expression; Increment is the assignment.
func (Float64Col) Avg ¶
func (c Float64Col) Avg() Agg
func (Float64Col) Between ¶
func (c Float64Col) Between(lo, hi float64) Pred
func (Float64Col) Coalesce ¶
func (c Float64Col) Coalesce(vs ...any) Float64Expr
Coalesce is coalesce(col, vs…), typed as double precision.
func (Float64Col) Eq ¶
func (c Float64Col) Eq(v float64) Pred
func (Float64Col) Greatest ¶
func (c Float64Col) Greatest(others ...Float64Value) Float64Expr
Greatest is greatest(col, others…).
func (Float64Col) Gt ¶
func (c Float64Col) Gt(v float64) Pred
func (Float64Col) Gte ¶
func (c Float64Col) Gte(v float64) Pred
func (Float64Col) InArray ¶
func (c Float64Col) InArray(a Float64ArrayValue) Pred
InArray is (col = ANY(a)).
func (Float64Col) Increment ¶
func (c Float64Col) Increment(v float64) Assignment
func (Float64Col) IsDistinctFrom ¶
func (c Float64Col) IsDistinctFrom(v float64) Pred
func (Float64Col) IsDistinctFromExpr ¶
func (c Float64Col) IsDistinctFromExpr(o Float64Value) Pred
IsDistinctFromExpr is col IS DISTINCT FROM o.
func (Float64Col) IsNotDistinctFrom ¶
func (c Float64Col) IsNotDistinctFrom(v float64) Pred
IsNotDistinctFrom is col IS NOT DISTINCT FROM v.
func (Float64Col) IsNotDistinctFromExpr ¶
func (c Float64Col) IsNotDistinctFromExpr(o Float64Value) Pred
IsNotDistinctFromExpr is col IS NOT DISTINCT FROM o.
func (Float64Col) Least ¶
func (c Float64Col) Least(others ...Float64Value) Float64Expr
Least is least(col, others…).
func (Float64Col) Lt ¶
func (c Float64Col) Lt(v float64) Pred
func (Float64Col) Lte ¶
func (c Float64Col) Lte(v float64) Pred
func (Float64Col) Ne ¶
func (c Float64Col) Ne(v float64) Pred
func (Float64Col) NotInArray ¶
func (c Float64Col) NotInArray(a Float64ArrayValue) Pred
NotInArray is (col <> ALL(a)).
func (Float64Col) NullIf ¶
func (c Float64Col) NullIf(v float64) Float64Expr
NullIf is nullif(col, v).
func (Float64Col) Sum ¶
func (c Float64Col) Sum() Agg
func (Float64Col) To ¶
func (c Float64Col) To(v float64) Assignment
type Float64Expr ¶
type Float64Expr struct {
// contains filtered or unexported fields
}
Float64Expr is a computed double precision value: a date part, a logarithm, a rounded float.
func CastFloat64 ¶
func CastFloat64(e Expr) Float64Expr
CastFloat64 is CAST(e AS double precision) of a number or of text holding one.
func ConstFloat64 ¶
func ConstFloat64(f float64) Float64Expr
ConstFloat64 writes f into the SQL as '0.5'::double precision, a double precision constant (Postgres would read a bare 0.5 as numeric and a bare 1 as integer, dividing as integers). NaN and infinities make an expression that fails.
func DatePart ¶
func DatePart(field string, t TimeValue) Float64Expr
DatePart is date_part('field', t): a field of t as double precision ("hour", "dow", "epoch", …). An unknown field makes an expression that fails.
func Float64 ¶
func Float64(f float64) Float64Expr
Float64 binds f as a double precision value, $n::double precision.
func Float64Of ¶
func Float64Of(e Expr) Float64Expr
Float64Of types e as double precision without changing the SQL; CastFloat64 converts.
func Float64Ptr ¶
func Float64Ptr(p *float64) Float64Expr
Float64Ptr binds *p as $n::double precision, NULL when p is nil.
func (Float64Expr) Asc ¶
func (e Float64Expr) Asc() Order
func (Float64Expr) Avg ¶
func (e Float64Expr) Avg() Agg
func (Float64Expr) Between ¶
func (e Float64Expr) Between(lo, hi float64) Pred
func (Float64Expr) Coalesce ¶
func (e Float64Expr) Coalesce(vs ...any) Float64Expr
Coalesce is coalesce(e, vs…).
func (Float64Expr) Desc ¶
func (e Float64Expr) Desc() Order
func (Float64Expr) Eq ¶
func (e Float64Expr) Eq(v float64) Pred
func (Float64Expr) EqExpr ¶
func (e Float64Expr) EqExpr(o Float64Value) Pred
func (Float64Expr) Greatest ¶
func (e Float64Expr) Greatest(others ...Float64Value) Float64Expr
Greatest is greatest(e, others…).
func (Float64Expr) Gt ¶
func (e Float64Expr) Gt(v float64) Pred
func (Float64Expr) GtExpr ¶
func (e Float64Expr) GtExpr(o Float64Value) Pred
func (Float64Expr) Gte ¶
func (e Float64Expr) Gte(v float64) Pred
func (Float64Expr) GteExpr ¶
func (e Float64Expr) GteExpr(o Float64Value) Pred
func (Float64Expr) InArray ¶
func (e Float64Expr) InArray(a Float64ArrayValue) Pred
InArray is (e = ANY(a)).
func (Float64Expr) IsDistinctFrom ¶
func (e Float64Expr) IsDistinctFrom(v float64) Pred
func (Float64Expr) IsDistinctFromExpr ¶
func (e Float64Expr) IsDistinctFromExpr(o Float64Value) Pred
IsDistinctFromExpr is e IS DISTINCT FROM o.
func (Float64Expr) IsNotDistinctFrom ¶
func (e Float64Expr) IsNotDistinctFrom(v float64) Pred
IsNotDistinctFrom is e IS NOT DISTINCT FROM v.
func (Float64Expr) IsNotDistinctFromExpr ¶
func (e Float64Expr) IsNotDistinctFromExpr(o Float64Value) Pred
IsNotDistinctFromExpr is e IS NOT DISTINCT FROM o.
func (Float64Expr) IsNotNull ¶
func (e Float64Expr) IsNotNull() Pred
func (Float64Expr) IsNull ¶
func (e Float64Expr) IsNull() Pred
func (Float64Expr) Least ¶
func (e Float64Expr) Least(others ...Float64Value) Float64Expr
Least is least(e, others…).
func (Float64Expr) Lt ¶
func (e Float64Expr) Lt(v float64) Pred
func (Float64Expr) LtExpr ¶
func (e Float64Expr) LtExpr(o Float64Value) Pred
func (Float64Expr) Lte ¶
func (e Float64Expr) Lte(v float64) Pred
func (Float64Expr) LteExpr ¶
func (e Float64Expr) LteExpr(o Float64Value) Pred
func (Float64Expr) Max ¶
func (e Float64Expr) Max() Agg
func (Float64Expr) Min ¶
func (e Float64Expr) Min() Agg
func (Float64Expr) Ne ¶
func (e Float64Expr) Ne(v float64) Pred
func (Float64Expr) NeExpr ¶
func (e Float64Expr) NeExpr(o Float64Value) Pred
func (Float64Expr) NotInArray ¶
func (e Float64Expr) NotInArray(a Float64ArrayValue) Pred
NotInArray is (e <> ALL(a)).
func (Float64Expr) NullIf ¶
func (e Float64Expr) NullIf(v float64) Float64Expr
NullIf is nullif(e, v).
func (Float64Expr) Round ¶
func (e Float64Expr) Round() Float64Expr
Round is round(e), to the nearest integer, halves away from zero. Postgres rounds double precision only to whole numbers; CastNumeric(e).RoundTo(places) rounds to decimal places.
func (Float64Expr) Sum ¶
func (e Float64Expr) Sum() Agg
type Float64Value ¶
type Float64Value interface {
Expr
// contains filtered or unexported methods
}
Float64Value is a double precision value: a Float64Col or a Float64Expr.
type FrameBound ¶
type FrameBound struct {
// contains filtered or unexported fields
}
FrameBound is one end of a window frame: UnboundedPreceding, Preceding(n), CurrentRow, Following(n) or UnboundedFollowing.
func Following ¶
func Following(offset any) FrameBound
Following is the frame bound offset after the current row, measured as Preceding measures it.
func Preceding ¶
func Preceding(offset any) FrameBound
Preceding is the frame bound offset before the current row: a row count in a Rows frame, a peer group count in a Groups frame, and a distance in the order column's type in a Range frame (an integer, a float, a decimal, or a time.Duration for a timestamp order). offset is bound as a parameter.
type FrameExclusion ¶
type FrameExclusion string
FrameExclusion removes rows around the current row from a window frame.
const ( // ExcludeNoOthers excludes nothing, the default. ExcludeNoOthers FrameExclusion = "NO OTHERS" // ExcludeCurrentRow excludes the current row. ExcludeCurrentRow FrameExclusion = "CURRENT ROW" // ExcludeGroup excludes the current row and its peers. ExcludeGroup FrameExclusion = "GROUP" // ExcludeTies excludes the current row's peers but not the row itself. ExcludeTies FrameExclusion = "TIES" )
type Func0 ¶
type Func0[R FuncResult] struct { // contains filtered or unexported fields }
Func0 is a database function of no arguments returning R, declared with DeclareFunc0.
func DeclareFunc0 ¶
func DeclareFunc0[R FuncResult](name string) Func0[R]
DeclareFunc0 declares the database function name ("fn" or "schema.fn") taking no arguments and returning R:
var nextBatch = keel.DeclareFunc0[keel.Int64Expr]("billing.next_batch_number")
type Func1 ¶
type Func1[R FuncResult, A Expr] struct { // contains filtered or unexported fields }
Func1 is a database function of one argument of type A (a value interface such as StringValue, or an expression type) returning R, declared with DeclareFunc1.
func DeclareFunc1 ¶
func DeclareFunc1[R FuncResult, A Expr](name string) Func1[R, A]
DeclareFunc1 declares the database function name taking one A and returning R:
var slug = keel.DeclareFunc1[keel.StringExpr, keel.StringValue]("public.slugify")
q.Where(slug.Call(c.Name).Eq("acme-freight"))
type Func2 ¶
type Func2[R FuncResult, A, B Expr] struct { // contains filtered or unexported fields }
Func2 is a database function of two arguments returning R, declared with DeclareFunc2.
func DeclareFunc2 ¶
func DeclareFunc2[R FuncResult, A, B Expr](name string) Func2[R, A, B]
DeclareFunc2 declares the database function name taking an A and a B and returning R.
type Func3 ¶
type Func3[R FuncResult, A, B, C Expr] struct { // contains filtered or unexported fields }
Func3 is a database function of three arguments returning R, declared with DeclareFunc3.
func DeclareFunc3 ¶
func DeclareFunc3[R FuncResult, A, B, C Expr](name string) Func3[R, A, B, C]
DeclareFunc3 declares the database function name taking an A, a B and a C and returning R.
type FuncDecl ¶
type FuncDecl struct {
// contains filtered or unexported fields
}
FuncDecl is a database function declared with DeclareFunc0 … DeclareFunc3: its name, the number of arguments its calls pass and the kind of value it returns. VerifyFuncs checks it against the catalog.
type FuncResult ¶
type FuncResult interface {
StringExpr | Int64Expr | Int32Expr | Float64Expr | NumericExpr | BoolExpr | TimeExpr | JSONBExpr
Expr
// contains filtered or unexported methods
}
FuncResult is the expression type a declared function returns, which fixes how its calls compare and what they decode into.
type Handle ¶
Handle is embedded in every generated table type. It starts queries and runs writes for rows of type T, the model's pointer type: Handle[*fleet.Shipment]. Every pointer type shares one shape, so the compiler builds one copy of keel's generic code for all models instead of one per model.
func (Handle[T]) AllTenants ¶
AllTenants starts a query that crosses tenants. It needs a system scope.
func (Handle[T]) Any ¶
Any is true when at least one row of the relation path matches ps: EXISTS (…).
func (Handle[T]) Cache ¶
func (h Handle[T]) Cache(opts ...CacheOption) *Query[T]
Cache starts a query served from the result cache; see Query.Cache.
func (Handle[T]) Copy ¶
Copy inserts rows through Postgres's binary COPY protocol and returns how many it inserted. It streams the rows, so it beats CreateMany for loads of many thousands of rows.
Rows are prepared as Create prepares them: the autoid, tenant, autocreate and autoupdate columns are filled, a row of another tenant is refused, and the BeforeCreate, BeforeSave and BeforeWrite hooks run. COPY returns nothing, so readonly columns are not read back, and AfterWrite and Committed hooks see the rows as written. Enum columns need their types registered on the connection, which keel does unless Config.SkipEnums is set.
Copy joins the transaction on the context. Outside one it runs in an implicit transaction when Config.OnBegin is set, after the OnBegin statements, when the table has AfterWrite hooks, or when the context is marked with OnReplica on a DB with replicas, so that its commit position is read with COMMIT.
Example ¶
package main
import (
"context"
"fmt"
"log"
"github.com/emoss08/keel"
"github.com/emoss08/keel/internal/testmodels/fleet"
"github.com/emoss08/keel/internal/testmodels/fleet/fleetdb"
"github.com/emoss08/keel/internal/testmodels/ids"
)
// appDB is the application's database, opened once at startup (see ExampleOpen).
var appDB *keel.DB
// scope is what the application's authentication middleware puts on a request context.
type scope struct {
OrganizationID ids.ID
BusinessUnitID ids.ID
SystemReason string
}
type scopeKey struct{}
func withTenant(ctx context.Context, org, bu ids.ID) context.Context {
return context.WithValue(ctx, scopeKey{}, scope{OrganizationID: org, BusinessUnitID: bu})
}
// requestContext returns the context of a request authenticated for one tenant.
func requestContext() context.Context {
return withTenant(context.Background(), "org_acme", "bu_dallas")
}
func main() {
ctx, db := requestContext(), appDB
rows := make([]*fleet.Shipment, 0, 50_000)
for i := range 50_000 {
rows = append(rows, &fleet.Shipment{
CustomerID: "cus_01",
ProNumber: fmt.Sprintf("IMPORT-%06d", i),
Status: fleet.StatusNew,
})
}
n, err := fleetdb.Shipments.Copy(ctx, db, rows)
if err != nil {
log.Fatal(err)
}
fmt.Println(n, "shipments imported")
}
Output:
func (Handle[T]) Create ¶
Create inserts row. The autoid column, the tenant columns and the autocreate and autoupdate columns are filled first; readonly columns are read back.
Example ¶
package main
import (
"context"
"fmt"
"log"
"github.com/emoss08/keel"
"github.com/emoss08/keel/internal/testmodels/fleet"
"github.com/emoss08/keel/internal/testmodels/fleet/fleetdb"
"github.com/emoss08/keel/internal/testmodels/ids"
)
// appDB is the application's database, opened once at startup (see ExampleOpen).
var appDB *keel.DB
// scope is what the application's authentication middleware puts on a request context.
type scope struct {
OrganizationID ids.ID
BusinessUnitID ids.ID
SystemReason string
}
type scopeKey struct{}
func withTenant(ctx context.Context, org, bu ids.ID) context.Context {
return context.WithValue(ctx, scopeKey{}, scope{OrganizationID: org, BusinessUnitID: bu})
}
// requestContext returns the context of a request authenticated for one tenant.
func requestContext() context.Context {
return withTenant(context.Background(), "org_acme", "bu_dallas")
}
func main() {
ctx, db := requestContext(), appDB
sh := &fleet.Shipment{
CustomerID: "cus_01",
ProNumber: "PRO-1001",
Status: fleet.StatusNew,
Total: fleet.Money{Cents: 125_000},
Labels: []string{"expedite"},
Attributes: map[string]any{"trailer": map[string]any{"type": "reefer"}},
}
if err := fleetdb.Shipments.Create(ctx, db, sh); err != nil {
log.Fatal(err)
}
fmt.Println(sh.ID, sh.OrganizationID, sh.CreatedAt, sh.StageRank)
}
Output:
func (Handle[T]) CreateMany ¶
CreateMany inserts rows with as few statements as Postgres's parameter limit allows, all sent in one round trip.
Example ¶
package main
import (
"context"
"log"
"github.com/emoss08/keel"
"github.com/emoss08/keel/internal/testmodels/fleet"
"github.com/emoss08/keel/internal/testmodels/fleet/fleetdb"
"github.com/emoss08/keel/internal/testmodels/ids"
)
// appDB is the application's database, opened once at startup (see ExampleOpen).
var appDB *keel.DB
// scope is what the application's authentication middleware puts on a request context.
type scope struct {
OrganizationID ids.ID
BusinessUnitID ids.ID
SystemReason string
}
type scopeKey struct{}
func withTenant(ctx context.Context, org, bu ids.ID) context.Context {
return context.WithValue(ctx, scopeKey{}, scope{OrganizationID: org, BusinessUnitID: bu})
}
// requestContext returns the context of a request authenticated for one tenant.
func requestContext() context.Context {
return withTenant(context.Background(), "org_acme", "bu_dallas")
}
func main() {
ctx, db := requestContext(), appDB
stops := []*fleet.Stop{
{MoveID: "mov_01", LocationID: "loc_chicago", Sequence: 1, Kind: "pickup"},
{MoveID: "mov_01", LocationID: "loc_dallas", Sequence: 2, Kind: "delivery"},
}
if err := fleetdb.Stops.CreateMany(ctx, db, stops); err != nil {
log.Fatal(err)
}
}
Output:
func (Handle[T]) CreateOrSkip ¶
func (h Handle[T]) CreateOrSkip(ctx context.Context, db *DB, rows []T, targets ...Expr) (int64, error)
CreateOrSkip inserts rows, skipping those that conflict on targets (columns or index expressions, as OnConflict takes them), and returns how many were inserted. Without targets it skips rows that violate any unique constraint (ON CONFLICT DO NOTHING). Inserted rows run the create hooks; skipped rows run none.
Example ¶
package main
import (
"context"
"fmt"
"log"
"github.com/emoss08/keel"
"github.com/emoss08/keel/internal/testmodels/fleet"
"github.com/emoss08/keel/internal/testmodels/fleet/fleetdb"
"github.com/emoss08/keel/internal/testmodels/ids"
)
// appDB is the application's database, opened once at startup (see ExampleOpen).
var appDB *keel.DB
// scope is what the application's authentication middleware puts on a request context.
type scope struct {
OrganizationID ids.ID
BusinessUnitID ids.ID
SystemReason string
}
type scopeKey struct{}
func withTenant(ctx context.Context, org, bu ids.ID) context.Context {
return context.WithValue(ctx, scopeKey{}, scope{OrganizationID: org, BusinessUnitID: bu})
}
// requestContext returns the context of a request authenticated for one tenant.
func requestContext() context.Context {
return withTenant(context.Background(), "org_acme", "bu_dallas")
}
func main() {
ctx, db := requestContext(), appDB
c := fleetdb.Customers
rows := []*fleet.Customer{{Code: "ACME", Name: "Acme"}, {Code: "INITECH", Name: "Initech"}}
n, err := c.CreateOrSkip(ctx, db, rows, c.OrganizationID, c.BusinessUnitID, c.Code)
if err != nil {
log.Fatal(err)
}
fmt.Println(n, "new customers")
}
Output:
func (Handle[T]) DeleteRow ¶
DeleteRow deletes row by its primary key. Interceptors' predicates join the primary key in the WHERE clause. It returns ErrNotFound when no row was deleted, including a row they exclude.
Example ¶
package main
import (
"context"
"errors"
"fmt"
"log"
"github.com/emoss08/keel"
"github.com/emoss08/keel/internal/testmodels/fleet/fleetdb"
"github.com/emoss08/keel/internal/testmodels/ids"
)
// appDB is the application's database, opened once at startup (see ExampleOpen).
var appDB *keel.DB
// scope is what the application's authentication middleware puts on a request context.
type scope struct {
OrganizationID ids.ID
BusinessUnitID ids.ID
SystemReason string
}
type scopeKey struct{}
func withTenant(ctx context.Context, org, bu ids.ID) context.Context {
return context.WithValue(ctx, scopeKey{}, scope{OrganizationID: org, BusinessUnitID: bu})
}
// requestContext returns the context of a request authenticated for one tenant.
func requestContext() context.Context {
return withTenant(context.Background(), "org_acme", "bu_dallas")
}
func main() {
ctx, db := requestContext(), appDB
st, err := fleetdb.Stops.Get(ctx, db, "stp_01")
if err != nil {
log.Fatal(err)
}
if err := fleetdb.Stops.DeleteRow(ctx, db, st); errors.Is(err, keel.ErrNotFound) {
fmt.Println("already deleted")
} else if err != nil {
log.Fatal(err)
}
}
Output:
func (Handle[T]) DistinctOn ¶
DistinctOn starts a DISTINCT ON query.
func (Handle[T]) GroupingSets ¶
GroupingSets starts a query grouped by GROUPING SETS.
func (Handle[T]) InsertFrom ¶
func (h Handle[T]) InsertFrom(ctx context.Context, db *DB, src subquery, cols ...Column, ) (int64, error)
InsertFrom inserts the rows of src into cols: INSERT INTO t (cols) SELECT …. src must pick one expression per column, in order. Under a tenant scope the tenant columns are filled from the scope and must not be listed.
Example ¶
package main
import (
"context"
"fmt"
"log"
"github.com/emoss08/keel"
"github.com/emoss08/keel/internal/testmodels/fleet/fleetdb"
"github.com/emoss08/keel/internal/testmodels/ids"
)
// appDB is the application's database, opened once at startup (see ExampleOpen).
var appDB *keel.DB
// scope is what the application's authentication middleware puts on a request context.
type scope struct {
OrganizationID ids.ID
BusinessUnitID ids.ID
SystemReason string
}
type scopeKey struct{}
func withTenant(ctx context.Context, org, bu ids.ID) context.Context {
return context.WithValue(ctx, scopeKey{}, scope{OrganizationID: org, BusinessUnitID: bu})
}
// requestContext returns the context of a request authenticated for one tenant.
func requestContext() context.Context {
return withTenant(context.Background(), "org_acme", "bu_dallas")
}
func main() {
ctx, db := requestContext(), appDB
tg, c := fleetdb.Tags, fleetdb.Customers
n, err := tg.InsertFrom(ctx, db,
c.Pick(keel.Raw("'tag_' || ?", c.ID), c.Name).Where(c.Email.IsNotNull()),
tg.ID, tg.Name)
if err != nil {
log.Fatal(err)
}
fmt.Println(n, "tags created")
}
Output:
func (Handle[T]) InsertFromSQL ¶
func (h Handle[T]) InsertFromSQL(ctx context.Context, db *DB, src subquery, cols ...Column, ) (string, []any, error)
InsertFromSQL renders the statement InsertFrom sends, without running it.
func (Handle[T]) JoinLateral ¶
JoinLateral starts a query with CROSS JOIN LATERAL (sub).
func (Handle[T]) LeftJoinLateral ¶
LeftJoinLateral starts a query with LEFT JOIN LATERAL (sub) ON TRUE.
func (Handle[T]) LoadStrategy ¶
func (h Handle[T]) LoadStrategy(s LoadStrategy) *Query[T]
LoadStrategy starts a query that loads its relations with s, as Query.LoadStrategy does.
func (Handle[T]) Lock ¶
func (h Handle[T]) Lock(strength LockStrength, wait ...LockWait) *Query[T]
Lock starts a query that locks the rows it selects.
func (Handle[T]) Save ¶
Save updates row by its primary key. With a version column it compares the stored version, increments it and returns a *ConflictError (matching ErrConflict) when another writer got there first. cols narrows the columns written; the autoupdate column is always written. Interceptors' predicates join the primary key in the WHERE clause: a row they exclude is not written, and Save reports it as ErrConflict on a table with a version column, else ErrNotFound.
Example ¶
package main
import (
"context"
"errors"
"fmt"
"log"
"github.com/emoss08/keel"
"github.com/emoss08/keel/internal/testmodels/fleet/fleetdb"
"github.com/emoss08/keel/internal/testmodels/ids"
)
// appDB is the application's database, opened once at startup (see ExampleOpen).
var appDB *keel.DB
// scope is what the application's authentication middleware puts on a request context.
type scope struct {
OrganizationID ids.ID
BusinessUnitID ids.ID
SystemReason string
}
type scopeKey struct{}
func withTenant(ctx context.Context, org, bu ids.ID) context.Context {
return context.WithValue(ctx, scopeKey{}, scope{OrganizationID: org, BusinessUnitID: bu})
}
// requestContext returns the context of a request authenticated for one tenant.
func requestContext() context.Context {
return withTenant(context.Background(), "org_acme", "bu_dallas")
}
func main() {
ctx, db := requestContext(), appDB
c := fleetdb.Customers
cust, err := c.Get(ctx, db, "cus_01")
if err != nil {
log.Fatal(err)
}
cust.Name = "Acme Freight"
err = c.Save(ctx, db, cust)
var conflict *keel.ConflictError
switch {
case errors.As(err, &conflict):
fmt.Printf("%s changed since version %d; reload and retry\n", conflict.Table, conflict.Version)
case err != nil:
log.Fatal(err)
default:
fmt.Println("saved version", cust.Version)
}
}
Output:
Example (Columns) ¶
package main
import (
"context"
"log"
"time"
"github.com/emoss08/keel"
"github.com/emoss08/keel/internal/testmodels/fleet"
"github.com/emoss08/keel/internal/testmodels/fleet/fleetdb"
"github.com/emoss08/keel/internal/testmodels/ids"
)
// appDB is the application's database, opened once at startup (see ExampleOpen).
var appDB *keel.DB
// scope is what the application's authentication middleware puts on a request context.
type scope struct {
OrganizationID ids.ID
BusinessUnitID ids.ID
SystemReason string
}
type scopeKey struct{}
func withTenant(ctx context.Context, org, bu ids.ID) context.Context {
return context.WithValue(ctx, scopeKey{}, scope{OrganizationID: org, BusinessUnitID: bu})
}
// requestContext returns the context of a request authenticated for one tenant.
func requestContext() context.Context {
return withTenant(context.Background(), "org_acme", "bu_dallas")
}
func main() {
ctx, db := requestContext(), appDB
s := fleetdb.Shipments
sh, err := s.Get(ctx, db, "shp_01")
if err != nil {
log.Fatal(err)
}
now := time.Now().Unix()
sh.Status = fleet.StatusCanceled
sh.CanceledAt = &now
if err := s.Save(ctx, db, sh, s.Status, s.CanceledAt); err != nil {
log.Fatal(err)
}
}
Output:
func (Handle[T]) SaveExcept ¶
SaveExcept is Save writing every updatable column except cols: the columns a caller must never overwrite from a row it read (secrets, columns another process maintains). The autoupdate column is always written.
Example ¶
package main
import (
"context"
"log"
"github.com/emoss08/keel"
"github.com/emoss08/keel/internal/testmodels/fleet/fleetdb"
"github.com/emoss08/keel/internal/testmodels/ids"
)
// appDB is the application's database, opened once at startup (see ExampleOpen).
var appDB *keel.DB
// scope is what the application's authentication middleware puts on a request context.
type scope struct {
OrganizationID ids.ID
BusinessUnitID ids.ID
SystemReason string
}
type scopeKey struct{}
func withTenant(ctx context.Context, org, bu ids.ID) context.Context {
return context.WithValue(ctx, scopeKey{}, scope{OrganizationID: org, BusinessUnitID: bu})
}
// requestContext returns the context of a request authenticated for one tenant.
func requestContext() context.Context {
return withTenant(context.Background(), "org_acme", "bu_dallas")
}
func main() {
ctx, db := requestContext(), appDB
c := fleetdb.Contacts
contact, err := c.Where(c.Name.Eq("Dana")).One(ctx, db)
if err != nil {
log.Fatal(err)
}
contact.Phone = "555-0100"
if err := c.SaveExcept(ctx, db, contact, c.Balance); err != nil {
log.Fatal(err)
}
}
Output:
func (Handle[T]) Upsert ¶
Upsert inserts rows, updating the existing row on conflict. It reports, per row, whether it was inserted (true) or updated (false). Rows that conflict get the stored version and readonly columns back. Inserted rows run the create hooks and updated rows the update hooks; outside a transaction the upsert runs in an implicit one when the table has AfterWrite hooks for either, so a failing hook rolls every row back.
Example ¶
package main
import (
"context"
"fmt"
"log"
"github.com/emoss08/keel"
"github.com/emoss08/keel/internal/testmodels/fleet"
"github.com/emoss08/keel/internal/testmodels/fleet/fleetdb"
"github.com/emoss08/keel/internal/testmodels/ids"
)
// appDB is the application's database, opened once at startup (see ExampleOpen).
var appDB *keel.DB
// scope is what the application's authentication middleware puts on a request context.
type scope struct {
OrganizationID ids.ID
BusinessUnitID ids.ID
SystemReason string
}
type scopeKey struct{}
func withTenant(ctx context.Context, org, bu ids.ID) context.Context {
return context.WithValue(ctx, scopeKey{}, scope{OrganizationID: org, BusinessUnitID: bu})
}
// requestContext returns the context of a request authenticated for one tenant.
func requestContext() context.Context {
return withTenant(context.Background(), "org_acme", "bu_dallas")
}
func main() {
ctx, db := requestContext(), appDB
c := fleetdb.Customers
rows := []*fleet.Customer{
{Code: "ACME", Name: "Acme Freight"},
{Code: "GLOBEX", Name: "Globex Logistics"},
}
inserted, err := c.Upsert(ctx, db, rows,
keel.OnConflict(c.OrganizationID, c.BusinessUnitID, c.Code).Update(c.Name))
if err != nil {
log.Fatal(err)
}
for i, r := range rows {
fmt.Println(r.Code, "inserted:", inserted[i], "version:", r.Version)
}
}
Output:
func (Handle[T]) UpsertFrom ¶
func (h Handle[T]) UpsertFrom(ctx context.Context, db *DB, src subquery, c Conflict, cols ...Column, ) (int64, error)
UpsertFrom inserts the rows of src into cols, as InsertFrom does, and resolves rows that conflict with c: INSERT INTO t AS alias (cols) SELECT … ON CONFLICT … DO UPDATE SET …. Without Update, Set or DoNothing a conflicting row takes the listed columns that are updatable. The autoupdate column of an updated row is stamped with the statement's time unless Set assigns it, and the version column is incremented. It returns the number of rows inserted or updated; hooks do not run, as for InsertFrom.
func (Handle[T]) UpsertFromSQL ¶
func (h Handle[T]) UpsertFromSQL(ctx context.Context, db *DB, src subquery, c Conflict, cols ...Column, ) (string, []any, error)
UpsertFromSQL renders the statement UpsertFrom sends, without running it.
type HookOption ¶
type HookOption func(*hookEntry)
HookOption configures a table hook.
func Needs ¶
func Needs(cols ...Column) HookOption
Needs asks for the previous values of cols on updates and deletes, read in the same statement (RETURNING old.…, which needs PostgreSQL 18) into Mutation.Old. It costs nothing unless the table has AfterWrite or Committed hooks for the write's op. A computed field has no stored value, so naming one panics when the hook is registered.
type Int16Col ¶
type Int16Col struct{ Col }
Int16Col is a smallint column.
func (Int16Col) Increment ¶
func (c Int16Col) Increment(v int16) Assignment
func (Int16Col) IsDistinctFrom ¶
func (Int16Col) IsNotDistinctFrom ¶
IsNotDistinctFrom is col IS NOT DISTINCT FROM v.
func (Int16Col) To ¶
func (c Int16Col) To(v int16) Assignment
type Int32ArrayCase ¶
type Int32ArrayCase struct {
// contains filtered or unexported fields
}
Int32ArrayCase builds a CASE whose result is integer[]. Start it with CaseInt32Array.
func CaseInt32Array ¶
func CaseInt32Array() Int32ArrayCase
CaseInt32Array starts a CASE whose result is integer[].
func (Int32ArrayCase) Else ¶
func (b Int32ArrayCase) Else(v Int32ArrayValue) Int32ArrayExpr
Else ends the CASE with ELSE v.
func (Int32ArrayCase) End ¶
func (b Int32ArrayCase) End() Int32ArrayExpr
End ends the CASE without ELSE.
func (Int32ArrayCase) When ¶
func (b Int32ArrayCase) When(cond Pred, v Int32ArrayValue) Int32ArrayCase
When adds WHEN cond THEN v.
type Int32ArrayExpr ¶
type Int32ArrayExpr struct {
// contains filtered or unexported fields
}
Int32ArrayExpr is a computed integer[] value, with the methods of StringArrayExpr.
func ArrayOfInt32 ¶
func ArrayOfInt32(es ...Int32Value) Int32ArrayExpr
ArrayOfInt32 is ARRAY[es…] of integer values.
func (Int32ArrayExpr) At ¶
func (e Int32ArrayExpr) At(i int) Int32Expr
At is (e)[i], the i-th element from 1.
func (Int32ArrayExpr) Coalesce ¶
func (e Int32ArrayExpr) Coalesce(o Int32ArrayValue) Int32ArrayExpr
Coalesce is coalesce(e, o).
func (Int32ArrayExpr) ContainedByExpr ¶
func (e Int32ArrayExpr) ContainedByExpr(o Int32ArrayValue) Pred
ContainedByExpr is (e <@ o).
func (Int32ArrayExpr) ContainsAllExpr ¶
func (e Int32ArrayExpr) ContainsAllExpr(o Int32ArrayValue) Pred
ContainsAllExpr is (e @> o).
func (Int32ArrayExpr) IsNotNull ¶
func (e Int32ArrayExpr) IsNotNull() Pred
IsNotNull is e IS NOT NULL.
func (Int32ArrayExpr) NullIf ¶
func (e Int32ArrayExpr) NullIf(o Int32ArrayValue) Int32ArrayExpr
NullIf is nullif(e, o): NULL where e equals o, as NullIf(keel.Int32Array(nil)) turns an empty array into NULL.
func (Int32ArrayExpr) OverlapsExpr ¶
func (e Int32ArrayExpr) OverlapsExpr(o Int32ArrayValue) Pred
OverlapsExpr is (e && o).
func (Int32ArrayExpr) Unnest ¶
func (e Int32ArrayExpr) Unnest() Int32Expr
Unnest is the set-returning unnest(e), for keel.Rows.
func (Int32ArrayExpr) Without ¶
func (e Int32ArrayExpr) Without(v Int32Value) Int32ArrayExpr
Without is array_remove(e, v).
type Int32ArrayValue ¶
type Int32ArrayValue interface {
Expr
// contains filtered or unexported methods
}
Int32ArrayValue is an integer[] value: an Int32ArrayExpr.
type Int32Case ¶
type Int32Case struct {
// contains filtered or unexported fields
}
Int32Case builds a CASE whose result is integer. Start it with CaseInt32.
func (Int32Case) Else ¶
func (b Int32Case) Else(v Int32Value) Int32Expr
Else ends the CASE with ELSE v.
type Int32Col ¶
type Int32Col struct{ Col }
Int32Col is an integer column.
func (Int32Col) Add ¶
func (c Int32Col) Add(v Int32Value) Int32Expr
Add is (col + v), an expression; Increment is the assignment.
func (Int32Col) Div ¶
func (c Int32Col) Div(v Int32Value) Int32Expr
Div is (col / v), truncated toward zero.
func (Int32Col) Greatest ¶
func (c Int32Col) Greatest(others ...Int32Value) Int32Expr
Greatest is greatest(col, others…).
func (Int32Col) InArray ¶
func (c Int32Col) InArray(a Int32ArrayValue) Pred
InArray is (col = ANY(a)).
func (Int32Col) Increment ¶
func (c Int32Col) Increment(v int32) Assignment
func (Int32Col) IsDistinctFrom ¶
func (Int32Col) IsDistinctFromExpr ¶
func (c Int32Col) IsDistinctFromExpr(o Int32Value) Pred
IsDistinctFromExpr is col IS DISTINCT FROM o.
func (Int32Col) IsNotDistinctFrom ¶
IsNotDistinctFrom is col IS NOT DISTINCT FROM v.
func (Int32Col) IsNotDistinctFromExpr ¶
func (c Int32Col) IsNotDistinctFromExpr(o Int32Value) Pred
IsNotDistinctFromExpr is col IS NOT DISTINCT FROM o.
func (Int32Col) Least ¶
func (c Int32Col) Least(others ...Int32Value) Int32Expr
Least is least(col, others…).
func (Int32Col) NotInArray ¶
func (c Int32Col) NotInArray(a Int32ArrayValue) Pred
NotInArray is (col <> ALL(a)).
func (Int32Col) To ¶
func (c Int32Col) To(v int32) Assignment
type Int32Expr ¶
type Int32Expr struct {
// contains filtered or unexported fields
}
Int32Expr is a computed integer value: a length, a position, a bucket number.
func ArrayPosition ¶
func ArrayPosition(list []string, v StringValue) Int32Expr
ArrayPosition is array_position(list, v): the position of v in list, from 1, or NULL. list binds as one text[] parameter, so ordering by it keeps rows in a caller's order: Order(keel.ArrayPosition(ids, q.ID).Asc()).
func WidthBucket ¶
func WidthBucket(v Int64Value, bounds []int64) Int32Expr
WidthBucket is width_bucket(v, bounds): the number of bounds, sorted ascending, that are at most v (0 below the first, len(bounds) at or above the last). bounds binds as one bigint[] parameter.
func WidthBucketRange ¶
func WidthBucketRange(v Float64Value, lo, hi float64, buckets int) Int32Expr
WidthBucketRange is width_bucket(v, lo, hi, buckets): the bucket, from 1 to buckets, of v in buckets equal-width buckets over [lo, hi); 0 below lo and buckets+1 at or above hi.
func (Int32Expr) Div ¶
func (e Int32Expr) Div(v Int32Value) Int32Expr
Div is (e / v), truncated toward zero.
func (Int32Expr) EqExpr ¶
func (e Int32Expr) EqExpr(o Int32Value) Pred
func (Int32Expr) Greatest ¶
func (e Int32Expr) Greatest(others ...Int32Value) Int32Expr
Greatest is greatest(e, others…).
func (Int32Expr) GtExpr ¶
func (e Int32Expr) GtExpr(o Int32Value) Pred
func (Int32Expr) GteExpr ¶
func (e Int32Expr) GteExpr(o Int32Value) Pred
func (Int32Expr) InArray ¶
func (e Int32Expr) InArray(a Int32ArrayValue) Pred
InArray is (e = ANY(a)).
func (Int32Expr) IsDistinctFrom ¶
func (Int32Expr) IsDistinctFromExpr ¶
func (e Int32Expr) IsDistinctFromExpr(o Int32Value) Pred
IsDistinctFromExpr is e IS DISTINCT FROM o.
func (Int32Expr) IsNotDistinctFrom ¶
IsNotDistinctFrom is e IS NOT DISTINCT FROM v.
func (Int32Expr) IsNotDistinctFromExpr ¶
func (e Int32Expr) IsNotDistinctFromExpr(o Int32Value) Pred
IsNotDistinctFromExpr is e IS NOT DISTINCT FROM o.
func (Int32Expr) Least ¶
func (e Int32Expr) Least(others ...Int32Value) Int32Expr
Least is least(e, others…).
func (Int32Expr) LtExpr ¶
func (e Int32Expr) LtExpr(o Int32Value) Pred
func (Int32Expr) LteExpr ¶
func (e Int32Expr) LteExpr(o Int32Value) Pred
func (Int32Expr) NeExpr ¶
func (e Int32Expr) NeExpr(o Int32Value) Pred
func (Int32Expr) NotInArray ¶
func (e Int32Expr) NotInArray(a Int32ArrayValue) Pred
NotInArray is (e <> ALL(a)).
type Int32Value ¶
type Int32Value interface {
Expr
// contains filtered or unexported methods
}
Int32Value is an integer value: an Int32Col or an Int32Expr.
type Int64ArrayCase ¶
type Int64ArrayCase struct {
// contains filtered or unexported fields
}
Int64ArrayCase builds a CASE whose result is bigint[]. Start it with CaseInt64Array.
func CaseInt64Array ¶
func CaseInt64Array() Int64ArrayCase
CaseInt64Array starts a CASE whose result is bigint[].
func (Int64ArrayCase) Else ¶
func (b Int64ArrayCase) Else(v Int64ArrayValue) Int64ArrayExpr
Else ends the CASE with ELSE v.
func (Int64ArrayCase) End ¶
func (b Int64ArrayCase) End() Int64ArrayExpr
End ends the CASE without ELSE.
func (Int64ArrayCase) When ¶
func (b Int64ArrayCase) When(cond Pred, v Int64ArrayValue) Int64ArrayCase
When adds WHEN cond THEN v.
type Int64ArrayCol ¶
type Int64ArrayCol struct{ Col }
Int64ArrayCol is a bigint[] column.
func (Int64ArrayCol) Append ¶
func (c Int64ArrayCol) Append(v int64) Assignment
Append sets the column to itself with v appended: array_append(col, v).
func (Int64ArrayCol) ContainedBy ¶
func (c Int64ArrayCol) ContainedBy(vs ...int64) Pred
func (Int64ArrayCol) ContainedByExpr ¶
func (c Int64ArrayCol) ContainedByExpr(o Int64ArrayValue) Pred
ContainedByExpr is (col <@ o).
func (Int64ArrayCol) ContainsAll ¶
func (c Int64ArrayCol) ContainsAll(vs ...int64) Pred
func (Int64ArrayCol) ContainsAllExpr ¶
func (c Int64ArrayCol) ContainsAllExpr(o Int64ArrayValue) Pred
ContainsAllExpr is (col @> o).
func (Int64ArrayCol) Has ¶
func (c Int64ArrayCol) Has(v int64) Pred
Has reports whether the array holds v.
func (Int64ArrayCol) Overlaps ¶
func (c Int64ArrayCol) Overlaps(vs ...int64) Pred
func (Int64ArrayCol) OverlapsExpr ¶
func (c Int64ArrayCol) OverlapsExpr(o Int64ArrayValue) Pred
OverlapsExpr is (col && o).
func (Int64ArrayCol) Remove ¶
func (c Int64ArrayCol) Remove(v int64) Assignment
Remove sets the column to itself without the elements equal to v: array_remove(col, v).
func (Int64ArrayCol) To ¶
func (c Int64ArrayCol) To(vs []int64) Assignment
func (Int64ArrayCol) Unnest ¶
func (c Int64ArrayCol) Unnest() Int64Expr
Unnest is the set-returning unnest(col), for keel.Rows.
func (Int64ArrayCol) Without ¶
func (c Int64ArrayCol) Without(v Int64Value) Int64ArrayExpr
Without is array_remove(col, v), an expression; Remove is the assignment.
type Int64ArrayExpr ¶
type Int64ArrayExpr struct {
// contains filtered or unexported fields
}
Int64ArrayExpr is a computed bigint[] value, with the methods of StringArrayExpr.
func ArrayOfInt64 ¶
func ArrayOfInt64(es ...Int64Value) Int64ArrayExpr
ArrayOfInt64 is ARRAY[es…] of bigint values: keel.ArrayOfInt64(keel.Int64Of(keel.Count().Filter(p)), …) returns several filtered counts in one column.
func (Int64ArrayExpr) At ¶
func (e Int64ArrayExpr) At(i int) Int64Expr
At is (e)[i], the i-th element from 1.
func (Int64ArrayExpr) Coalesce ¶
func (e Int64ArrayExpr) Coalesce(o Int64ArrayValue) Int64ArrayExpr
Coalesce is coalesce(e, o).
func (Int64ArrayExpr) ContainedByExpr ¶
func (e Int64ArrayExpr) ContainedByExpr(o Int64ArrayValue) Pred
ContainedByExpr is (e <@ o).
func (Int64ArrayExpr) ContainsAllExpr ¶
func (e Int64ArrayExpr) ContainsAllExpr(o Int64ArrayValue) Pred
ContainsAllExpr is (e @> o).
func (Int64ArrayExpr) IsNotNull ¶
func (e Int64ArrayExpr) IsNotNull() Pred
IsNotNull is e IS NOT NULL.
func (Int64ArrayExpr) NullIf ¶
func (e Int64ArrayExpr) NullIf(o Int64ArrayValue) Int64ArrayExpr
NullIf is nullif(e, o): NULL where e equals o, as NullIf(keel.Int64Array(nil)) turns an empty array into NULL.
func (Int64ArrayExpr) OverlapsExpr ¶
func (e Int64ArrayExpr) OverlapsExpr(o Int64ArrayValue) Pred
OverlapsExpr is (e && o).
func (Int64ArrayExpr) Unnest ¶
func (e Int64ArrayExpr) Unnest() Int64Expr
Unnest is the set-returning unnest(e), for keel.Rows.
func (Int64ArrayExpr) Without ¶
func (e Int64ArrayExpr) Without(v Int64Value) Int64ArrayExpr
Without is array_remove(e, v).
type Int64ArrayValue ¶
type Int64ArrayValue interface {
Expr
// contains filtered or unexported methods
}
Int64ArrayValue is a bigint[] value: an Int64ArrayCol or an Int64ArrayExpr.
type Int64Case ¶
type Int64Case struct {
// contains filtered or unexported fields
}
Int64Case builds a CASE whose result is bigint. Start it with CaseInt64.
func (Int64Case) Else ¶
func (b Int64Case) Else(v Int64Value) Int64Expr
Else ends the CASE with ELSE v.
type Int64Col ¶
type Int64Col struct{ Col }
Int64Col is a bigint column.
func (Int64Col) Add ¶
func (c Int64Col) Add(v Int64Value) Int64Expr
Add is (col + v), an expression; Increment is the assignment col = col + v.
func (Int64Col) Div ¶
func (c Int64Col) Div(v Int64Value) Int64Expr
Div is (col / v), truncated toward zero.
func (Int64Col) Greatest ¶
func (c Int64Col) Greatest(others ...Int64Value) Int64Expr
Greatest is greatest(col, others…).
func (Int64Col) InArray ¶
func (c Int64Col) InArray(a Int64ArrayValue) Pred
InArray is (col = ANY(a)).
func (Int64Col) Increment ¶
func (c Int64Col) Increment(v int64) Assignment
func (Int64Col) IsDistinctFrom ¶
func (Int64Col) IsDistinctFromExpr ¶
func (c Int64Col) IsDistinctFromExpr(o Int64Value) Pred
IsDistinctFromExpr is col IS DISTINCT FROM o.
func (Int64Col) IsNotDistinctFrom ¶
IsNotDistinctFrom is col IS NOT DISTINCT FROM v.
func (Int64Col) IsNotDistinctFromExpr ¶
func (c Int64Col) IsNotDistinctFromExpr(o Int64Value) Pred
IsNotDistinctFromExpr is col IS NOT DISTINCT FROM o.
func (Int64Col) Least ¶
func (c Int64Col) Least(others ...Int64Value) Int64Expr
Least is least(col, others…).
func (Int64Col) NotInArray ¶
func (c Int64Col) NotInArray(a Int64ArrayValue) Pred
NotInArray is (col <> ALL(a)).
func (Int64Col) To ¶
func (c Int64Col) To(v int64) Assignment
type Int64Expr ¶
type Int64Expr struct {
// contains filtered or unexported fields
}
Int64Expr is a computed bigint value.
func CastInt64 ¶
CastInt64 is CAST(e AS bigint) of a number of another kind (a fraction rounds to the nearest integer), of text holding an integer, or of any expression Postgres casts to bigint.
func ConstInt64 ¶
ConstInt64 writes n into the SQL: 768, or (-1). Postgres types an integer constant as integer when it fits and bigint otherwise, and compares and computes it with a bigint column as written in an index (dimensions = 768).
func GenerateSeries ¶
func GenerateSeries(start, stop Int64Value) Int64Expr
GenerateSeries is the set-returning generate_series(start, stop): the integers from start to stop, for keel.Rows.
func GenerateSeriesBy ¶
func GenerateSeriesBy(start, stop, step Int64Value) Int64Expr
GenerateSeriesBy is generate_series(start, stop, step).
func NowEpoch ¶
func NowEpoch() Int64Expr
NowEpoch is the current time in Unix seconds, CAST(extract(epoch FROM now()) AS bigint), for models that store times as epoch seconds.
func (Int64Expr) Coalesce ¶
Coalesce is coalesce(e, vs…): each v an integer bound as a parameter or an expression, taken in order where everything before it is NULL.
func (Int64Expr) Div ¶
func (e Int64Expr) Div(v Int64Value) Int64Expr
Div is (e / v), truncated toward zero.
func (Int64Expr) EqExpr ¶
func (e Int64Expr) EqExpr(o Int64Value) Pred
func (Int64Expr) Greatest ¶
func (e Int64Expr) Greatest(others ...Int64Value) Int64Expr
Greatest is greatest(e, others…), the largest non-NULL value.
func (Int64Expr) GtExpr ¶
func (e Int64Expr) GtExpr(o Int64Value) Pred
func (Int64Expr) GteExpr ¶
func (e Int64Expr) GteExpr(o Int64Value) Pred
func (Int64Expr) InArray ¶
func (e Int64Expr) InArray(a Int64ArrayValue) Pred
InArray is (e = ANY(a)).
func (Int64Expr) IsDistinctFrom ¶
func (Int64Expr) IsDistinctFromExpr ¶
func (e Int64Expr) IsDistinctFromExpr(o Int64Value) Pred
IsDistinctFromExpr is e IS DISTINCT FROM o.
func (Int64Expr) IsNotDistinctFrom ¶
IsNotDistinctFrom is e IS NOT DISTINCT FROM v.
func (Int64Expr) IsNotDistinctFromExpr ¶
func (e Int64Expr) IsNotDistinctFromExpr(o Int64Value) Pred
IsNotDistinctFromExpr is e IS NOT DISTINCT FROM o.
func (Int64Expr) Least ¶
func (e Int64Expr) Least(others ...Int64Value) Int64Expr
Least is least(e, others…), the smallest non-NULL value.
func (Int64Expr) LtExpr ¶
func (e Int64Expr) LtExpr(o Int64Value) Pred
func (Int64Expr) LteExpr ¶
func (e Int64Expr) LteExpr(o Int64Value) Pred
func (Int64Expr) Mod ¶
func (e Int64Expr) Mod(v Int64Value) Int64Expr
Mod is (e % v), the remainder, with the sign of e.
func (Int64Expr) NeExpr ¶
func (e Int64Expr) NeExpr(o Int64Value) Pred
func (Int64Expr) NotInArray ¶
func (e Int64Expr) NotInArray(a Int64ArrayValue) Pred
NotInArray is (e <> ALL(a)).
type Int64Range ¶
type Int64Range struct {
// contains filtered or unexported fields
}
Int64Range is a bigint range, built by Int8Range.
func Int8Range ¶
func Int8Range(lo, hi Int64Value) Int64Range
Int8Range is int8range(lo, hi): the bigints from lo, included, to hi, excluded; a NULL bound is unbounded.
func (Int64Range) Covers ¶
func (g Int64Range) Covers(v Int64Value) Pred
Covers is g @> v: v lies in the range.
func (Int64Range) Overlaps ¶
func (g Int64Range) Overlaps(o Int64Range) Pred
Overlaps is g && o: the ranges share a value.
type Int64Value ¶
type Int64Value interface {
Expr
// contains filtered or unexported methods
}
Int64Value is a bigint value: an Int64Col or an Int64Expr.
type Intercepted ¶
type Intercepted struct {
Op Op
Read bool
Table *schema.Table
// contains filtered or unexported fields
}
Intercepted is the statement an interceptor sees: its Op (OpRead, OpUpdate or OpDelete), whether it is a read, and its base table.
func (*Intercepted) Where ¶
func (i *Intercepted) Where(ps ...Pred)
Where adds predicates to the intercepted statement.
type Interceptor ¶
type Interceptor func(ctx context.Context, q *Intercepted) error
Interceptor sees a statement on a table before it is rendered and may add predicates to its WHERE clause. It sees every statement whose base is the table and that has a WHERE clause:
- reads: every query terminal and Batch item, Page's total, projections, ToSQL, to-many relation loads, Any and None, subqueries, and the source query of InsertFrom (OpRead);
- Save and bulk updates (OpUpdate);
- DeleteRow and bulk deletes (OpDelete).
On Save and DeleteRow the predicates join the primary key, so a row they exclude is not written. Interceptors never see inserts (Create, CreateMany, Copy, Upsert, CreateOrSkip and the INSERT of InsertFrom), including the update an Upsert applies to a conflicting row, or tables joined into a statement through a relation path.
type IntervalCase ¶
type IntervalCase struct {
// contains filtered or unexported fields
}
IntervalCase builds a CASE whose result is an interval. Start it with CaseInterval.
func CaseInterval ¶
func CaseInterval() IntervalCase
CaseInterval starts a CASE whose result is an interval.
func (IntervalCase) Else ¶
func (b IntervalCase) Else(v IntervalValue) IntervalExpr
Else ends the CASE with ELSE v.
func (IntervalCase) When ¶
func (b IntervalCase) When(cond Pred, v IntervalValue) IntervalCase
When adds WHEN cond THEN v.
type IntervalExpr ¶
type IntervalExpr struct {
// contains filtered or unexported fields
}
IntervalExpr is a computed interval: the difference of two times, a bound interval.
func CastInterval ¶
func CastInterval(e Expr) IntervalExpr
CastInterval is CAST(e AS interval): interval text as an interval.
func Duration ¶
func Duration(d time.Duration) IntervalExpr
Duration binds d as $n::interval, in microseconds, Postgres's resolution.
func Interval ¶
func Interval(s string) IntervalExpr
Interval binds s, an interval in Postgres's input syntax ("1 day", "36 hours", "P1DT12H"), as $n::interval.
func IntervalOf ¶
func IntervalOf(e Expr) IntervalExpr
IntervalOf types e as an interval without changing the SQL.
func (IntervalExpr) GteExpr ¶
func (e IntervalExpr) GteExpr(o IntervalValue) Pred
GteExpr is e >= o.
func (IntervalExpr) IsDistinctFromExpr ¶
func (e IntervalExpr) IsDistinctFromExpr(o IntervalValue) Pred
IsDistinctFromExpr is e IS DISTINCT FROM o.
func (IntervalExpr) IsNotDistinctFromExpr ¶
func (e IntervalExpr) IsNotDistinctFromExpr(o IntervalValue) Pred
IsNotDistinctFromExpr is e IS NOT DISTINCT FROM o.
func (IntervalExpr) LteExpr ¶
func (e IntervalExpr) LteExpr(o IntervalValue) Pred
LteExpr is e <= o.
func (IntervalExpr) Mul ¶
func (e IntervalExpr) Mul(n NumberValue) IntervalExpr
Mul is (e * n): keel.Interval("1 day").Mul(c.GraceDays) is that many days.
func (IntervalExpr) Seconds ¶
func (e IntervalExpr) Seconds() Float64Expr
Seconds is date_part('epoch', e): the interval's length in seconds, months counted as 30 days.
type IntervalValue ¶
type IntervalValue interface {
Expr
// contains filtered or unexported methods
}
IntervalValue is an interval: an IntervalExpr (keel.Interval, keel.Duration, TimeExpr.Since, a typed view).
type Isolation ¶
type Isolation uint8
Isolation is a transaction isolation level. The zero value leaves the level to the server's default_transaction_isolation, READ COMMITTED unless the server is configured otherwise. A level is a TxOption, and Config.ReadIsolation sets the level of explicit Read transactions that do not pass one.
const ( // ReadCommitted runs the transaction at READ COMMITTED: each statement sees what was committed // before it began. ReadCommitted Isolation = iota + 1 // RepeatableRead runs the transaction at REPEATABLE READ: every statement sees the snapshot // taken by the transaction's first statement. A read-only transaction at this level reads one // consistent snapshot across all its statements. RepeatableRead // Serializable runs the transaction at SERIALIZABLE. Combine it with Retry: a serialization // failure is expected under contention. Serializable )
The isolation levels keel begins transactions with.
type JSONBCase ¶
type JSONBCase struct {
// contains filtered or unexported fields
}
JSONBCase builds a CASE whose result is jsonb. Start it with CaseJSONB.
func (JSONBCase) Else ¶
func (b JSONBCase) Else(v JSONBValue) JSONBExpr
Else ends the CASE with ELSE v.
type JSONBCol ¶
type JSONBCol struct{ Col }
JSONBCol is a jsonb column.
func (JSONBCol) ArrayElementsText ¶
func (c JSONBCol) ArrayElementsText() StringExpr
ArrayElementsText is jsonb_array_elements_text(col), for keel.Rows.
func (JSONBCol) Coalesce ¶
Coalesce is coalesce(col, vs…), typed as jsonb; a value v is encoded as JSON.
func (JSONBCol) HasAllKeys ¶
HasAllKeys is col ?& keys.
func (JSONBCol) IsDistinctFromExpr ¶
func (c JSONBCol) IsDistinctFromExpr(o JSONBValue) Pred
IsDistinctFromExpr is col IS DISTINCT FROM o.
func (JSONBCol) IsNotDistinctFromExpr ¶
func (c JSONBCol) IsNotDistinctFromExpr(o JSONBValue) Pred
IsNotDistinctFromExpr is col IS NOT DISTINCT FROM o.
func (JSONBCol) Path ¶
Path is col #> path: the value at path as jsonb, NULL when the path is missing; no path is the whole document.
func (JSONBCol) Set ¶
func (c JSONBCol) Set(path []string, v JSONBValue) JSONBExpr
Set is jsonb_set(col, path, v); assign it with ToExpr.
func (JSONBCol) Text ¶
func (c JSONBCol) Text(path ...string) StringExpr
Text is col #>> path: the value at path as text.
func (JSONBCol) To ¶
func (c JSONBCol) To(v any) Assignment
To sets the column to v encoded as JSON.
type JSONBExpr ¶
type JSONBExpr struct {
// contains filtered or unexported fields
}
JSONBExpr is a computed jsonb value: a built object or array, a document with a value set.
func JSONB ¶
JSONB binds v encoded as JSON (with the DB's JSON codec) as $n::jsonb. A []byte or json.RawMessage is already JSON and binds as it is.
func JSONBuildArray ¶
JSONBuildArray is jsonb_build_array(vs…); no values is the empty array.
func JSONBuildObject ¶
JSONBuildObject is jsonb_build_object(key, value, …), every key bound as text.
func NullJSONB ¶
func NullJSONB() JSONBExpr
NullJSONB is NULL::jsonb, SQL NULL rather than the JSON null keel.JSONB(nil) binds.
func (JSONBExpr) ArrayElementsText ¶
func (e JSONBExpr) ArrayElementsText() StringExpr
ArrayElementsText is the set-returning jsonb_array_elements_text(e): one text row per element of the array e, for keel.Rows.
func (JSONBExpr) Coalesce ¶
Coalesce is coalesce(e, v): v, an expression or a value encoded as JSON, where e is NULL.
func (JSONBExpr) Concat ¶
func (e JSONBExpr) Concat(o JSONBValue) JSONBExpr
Concat is e || o: the objects merged (o's keys win) or the arrays concatenated.
func (JSONBExpr) ContainsExpr ¶
func (e JSONBExpr) ContainsExpr(o JSONBValue) Pred
ContainsExpr is e @> o.
func (JSONBExpr) IsDistinctFromExpr ¶
func (e JSONBExpr) IsDistinctFromExpr(o JSONBValue) Pred
IsDistinctFromExpr is e IS DISTINCT FROM o.
func (JSONBExpr) IsNotDistinctFromExpr ¶
func (e JSONBExpr) IsNotDistinctFromExpr(o JSONBValue) Pred
IsNotDistinctFromExpr is e IS NOT DISTINCT FROM o.
func (JSONBExpr) Set ¶
func (e JSONBExpr) Set(path []string, v JSONBValue) JSONBExpr
Set is jsonb_set(e, path, v): e with the value at path replaced by v, created when missing.
func (JSONBExpr) Text ¶
func (e JSONBExpr) Text(path ...string) StringExpr
Text is e #>> path: the value at path as text.
func (JSONBExpr) TypeOf ¶
func (e JSONBExpr) TypeOf() StringExpr
TypeOf is jsonb_typeof(e): "object", "array", "string", "number", "boolean" or "null".
type JSONBValue ¶
type JSONBValue interface {
Expr
// contains filtered or unexported methods
}
JSONBValue is a jsonb value: a JSONBCol or a JSONBExpr.
type JSONCodec ¶
type JSONCodec struct {
Marshal func(v any) ([]byte, error)
Unmarshal func(data []byte, v any) error
// UnmarshalNumber decodes the columns tagged jsonnumber: as Unmarshal, except that a number
// decoded into an interface value (map[string]any, []any, any) is a json.Number holding its
// literal instead of a float64, so integers past 2^53 keep every digit. With sonic,
// sonic.Config{UseNumber: true}.Froze().Unmarshal.
UnmarshalNumber func(data []byte, v any) error
}
JSONCodec is a JSON implementation.
type JSONPair ¶
type JSONPair struct {
// contains filtered or unexported fields
}
JSONPair is one key and value of JSONBuildObject.
type LoadStrategy ¶
type LoadStrategy uint8
LoadStrategy is how a read loads its to-many relations, many-to-many relations and the picks it does not join: Config.LoadStrategy sets the default and Query.LoadStrategy chooses for one query and the loads below it.
const ( // LoadPipelined sends the read's statement and the statement of every level of its relation // tree in one round trip. A level selects its parents' keys with a subquery over the parent // level's own statement (its filters, joins, order, limit, offset and keyset), nested for // deeper levels, so it needs no keys read back. It runs only where every statement reads one // snapshot: in a transaction at RepeatableRead or Serializable, or outside a transaction, // where keel runs the read in an implicit REPEATABLE READ READ ONLY transaction of its own (in // the same round trip). A level whose parent level's rows are not the same every time its // statement runs loads per level (see the guide's "One round trip"). It is the default. LoadPipelined LoadStrategy = iota + 1 // LoadPerLevel sends one round trip per level: each level reads its parents' keys from the // rows of the level before it and binds them as arrays (= ANY($1), unnest). LoadPerLevel )
type Loader ¶
type Loader interface {
// contains filtered or unexported methods
}
Loader is a relation path to load, optionally narrowed: a relation handle (s.Customer()) or a query started from one (m.Order(m.Sequence.Asc())).
type LocalCache ¶
type LocalCache interface {
// Get returns the value stored under key and true, or false when there is none or it expired.
Get(key string) ([]byte, bool)
// Set stores value under key for ttl. A store may refuse a value, which is then a miss.
Set(key string, value []byte, ttl time.Duration)
// Bytes returns the bytes the store holds, for CacheStats.
Bytes() int64
}
LocalCache is the in-process store of the result cache (CacheConfig.Local): encoded entries by key, each with a time to live. Values it returns are only read, never changed; every hit decodes them into new rows. keel.MemoryCache is the implementation keel provides.
func MemoryCache ¶
func MemoryCache(maxBytes int64) LocalCache
MemoryCache returns a LocalCache of at most maxBytes bytes of keys, values and bookkeeping, analytics' otter-backed memory store: a full store evicts the entries least likely to be read again, and a value larger than maxBytes is not kept. A maxBytes of zero or less is DefaultCacheBytes.
type LockWait ¶
type LockWait string
LockWait is what a locking query does when a row is already locked.
type Mutation ¶
type Mutation[T any] struct { Op Op // Row is the row as written: the entity passed to an entity write, or the row returned by a // bulk write. For DeleteRow it is the entity passed, which may hold only the primary key. Row T // Old holds the stored values, before the write, of the columns the table's hooks declared // with Needs: for every update (Save, an Upsert's conflicting rows, bulk updates) and every // delete (DeleteRow, bulk deletes). Other fields are zero. Nil for creates, and when no hook // of the table declared Needs. Old T // contains filtered or unexported fields }
Mutation is one row written, as a table hook sees it.
type NumberValue ¶
type NumberValue interface {
Expr
// contains filtered or unexported methods
}
NumberValue is any number: a smallint, integer, bigint, double precision or scanner column, or an Int32Expr, Int64Expr, Float64Expr or NumericExpr.
type NumericArrayCase ¶
type NumericArrayCase struct {
// contains filtered or unexported fields
}
NumericArrayCase builds a CASE whose result is numeric[]. Start it with CaseNumericArray.
func CaseNumericArray ¶
func CaseNumericArray() NumericArrayCase
CaseNumericArray starts a CASE whose result is numeric[].
func (NumericArrayCase) Else ¶
func (b NumericArrayCase) Else(v NumericArrayValue) NumericArrayExpr
Else ends the CASE with ELSE v.
func (NumericArrayCase) End ¶
func (b NumericArrayCase) End() NumericArrayExpr
End ends the CASE without ELSE.
func (NumericArrayCase) When ¶
func (b NumericArrayCase) When(cond Pred, v NumericArrayValue) NumericArrayCase
When adds WHEN cond THEN v.
type NumericArrayExpr ¶
type NumericArrayExpr struct {
// contains filtered or unexported fields
}
NumericArrayExpr is a computed numeric[] value, with the methods of StringArrayExpr.
func ArrayOfNumeric ¶
func ArrayOfNumeric(es ...NumericValue) NumericArrayExpr
ArrayOfNumeric is ARRAY[es…] of numeric values.
func NumericArray ¶
func NumericArray(vs any) NumericArrayExpr
NumericArray binds vs, a slice of decimals or of any values Postgres reads as numeric, as $n::numeric[].
func NumericArrayOf ¶
func NumericArrayOf(e Expr) NumericArrayExpr
NumericArrayOf types e as numeric[].
func (NumericArrayExpr) At ¶
func (e NumericArrayExpr) At(i int) NumericExpr
At is (e)[i], the i-th element from 1.
func (NumericArrayExpr) Coalesce ¶
func (e NumericArrayExpr) Coalesce(o NumericArrayValue) NumericArrayExpr
Coalesce is coalesce(e, o).
func (NumericArrayExpr) ContainedByExpr ¶
func (e NumericArrayExpr) ContainedByExpr(o NumericArrayValue) Pred
ContainedByExpr is (e <@ o).
func (NumericArrayExpr) ContainsAllExpr ¶
func (e NumericArrayExpr) ContainsAllExpr(o NumericArrayValue) Pred
ContainsAllExpr is (e @> o).
func (NumericArrayExpr) IsNotNull ¶
func (e NumericArrayExpr) IsNotNull() Pred
IsNotNull is e IS NOT NULL.
func (NumericArrayExpr) NullIf ¶
func (e NumericArrayExpr) NullIf(o NumericArrayValue) NumericArrayExpr
NullIf is nullif(e, o): NULL where e equals o, as NullIf(keel.NumericArray(nil)) turns an empty array into NULL.
func (NumericArrayExpr) OverlapsExpr ¶
func (e NumericArrayExpr) OverlapsExpr(o NumericArrayValue) Pred
OverlapsExpr is (e && o).
func (NumericArrayExpr) Unnest ¶
func (e NumericArrayExpr) Unnest() NumericExpr
Unnest is the set-returning unnest(e), for keel.Rows.
func (NumericArrayExpr) Without ¶
func (e NumericArrayExpr) Without(v NumericValue) NumericArrayExpr
Without is array_remove(e, v).
type NumericArrayValue ¶
type NumericArrayValue interface {
Expr
// contains filtered or unexported methods
}
NumericArrayValue is a numeric[] value: a NumericArrayExpr.
type NumericCase ¶
type NumericCase struct {
// contains filtered or unexported fields
}
NumericCase builds a CASE whose result is numeric. Start it with CaseNumeric.
func CaseNumeric ¶
func CaseNumeric() NumericCase
CaseNumeric starts a CASE whose result is numeric.
func (NumericCase) Else ¶
func (b NumericCase) Else(v NumericValue) NumericExpr
Else ends the CASE with ELSE v.
func (NumericCase) When ¶
func (b NumericCase) When(cond Pred, v NumericValue) NumericCase
When adds WHEN cond THEN v.
type NumericExpr ¶
type NumericExpr struct {
// contains filtered or unexported fields
}
NumericExpr is a computed numeric value. Its comparisons take any value Postgres reads as numeric (a decimal type, an integer, a float), as an AnyCol's do.
func CastNumeric ¶
func CastNumeric(e Expr) NumericExpr
CastNumeric is CAST(e AS numeric) of a number or of text holding one: exact arithmetic and rounding to decimal places.
func ConstNumeric ¶
func ConstNumeric(s string) NumericExpr
ConstNumeric writes s, a decimal such as "0.035" or "-12", into the SQL as '0.035'::numeric. A string that is not an optionally signed decimal makes an expression that fails.
func Numeric ¶
func Numeric(v any) NumericExpr
Numeric binds v, a decimal or any value Postgres reads as numeric, as $n::numeric.
func NumericOf ¶
func NumericOf(e Expr) NumericExpr
NumericOf types e as numeric without changing the SQL; CastNumeric converts.
func (NumericExpr) Asc ¶
func (e NumericExpr) Asc() Order
func (NumericExpr) Avg ¶
func (e NumericExpr) Avg() Agg
func (NumericExpr) Between ¶
func (e NumericExpr) Between(lo, hi any) Pred
func (NumericExpr) Coalesce ¶
func (e NumericExpr) Coalesce(vs ...any) NumericExpr
Coalesce is coalesce(e, vs…).
func (NumericExpr) Desc ¶
func (e NumericExpr) Desc() Order
func (NumericExpr) Eq ¶
func (e NumericExpr) Eq(v any) Pred
func (NumericExpr) EqExpr ¶
func (e NumericExpr) EqExpr(o NumericValue) Pred
func (NumericExpr) Greatest ¶
func (e NumericExpr) Greatest(others ...NumericValue) NumericExpr
Greatest is greatest(e, others…).
func (NumericExpr) Gt ¶
func (e NumericExpr) Gt(v any) Pred
func (NumericExpr) GtExpr ¶
func (e NumericExpr) GtExpr(o NumericValue) Pred
func (NumericExpr) Gte ¶
func (e NumericExpr) Gte(v any) Pred
func (NumericExpr) GteExpr ¶
func (e NumericExpr) GteExpr(o NumericValue) Pred
func (NumericExpr) InArray ¶
func (e NumericExpr) InArray(a NumericArrayValue) Pred
InArray is (e = ANY(a)).
func (NumericExpr) IsDistinctFrom ¶
func (e NumericExpr) IsDistinctFrom(v any) Pred
func (NumericExpr) IsDistinctFromExpr ¶
func (e NumericExpr) IsDistinctFromExpr(o NumericValue) Pred
IsDistinctFromExpr is e IS DISTINCT FROM o.
func (NumericExpr) IsNotDistinctFrom ¶
func (e NumericExpr) IsNotDistinctFrom(v any) Pred
IsNotDistinctFrom is e IS NOT DISTINCT FROM v.
func (NumericExpr) IsNotDistinctFromExpr ¶
func (e NumericExpr) IsNotDistinctFromExpr(o NumericValue) Pred
IsNotDistinctFromExpr is e IS NOT DISTINCT FROM o.
func (NumericExpr) IsNotNull ¶
func (e NumericExpr) IsNotNull() Pred
func (NumericExpr) IsNull ¶
func (e NumericExpr) IsNull() Pred
func (NumericExpr) Least ¶
func (e NumericExpr) Least(others ...NumericValue) NumericExpr
Least is least(e, others…).
func (NumericExpr) Lt ¶
func (e NumericExpr) Lt(v any) Pred
func (NumericExpr) LtExpr ¶
func (e NumericExpr) LtExpr(o NumericValue) Pred
func (NumericExpr) Lte ¶
func (e NumericExpr) Lte(v any) Pred
func (NumericExpr) LteExpr ¶
func (e NumericExpr) LteExpr(o NumericValue) Pred
func (NumericExpr) Max ¶
func (e NumericExpr) Max() Agg
func (NumericExpr) Min ¶
func (e NumericExpr) Min() Agg
func (NumericExpr) Ne ¶
func (e NumericExpr) Ne(v any) Pred
func (NumericExpr) NeExpr ¶
func (e NumericExpr) NeExpr(o NumericValue) Pred
func (NumericExpr) NotInArray ¶
func (e NumericExpr) NotInArray(a NumericArrayValue) Pred
NotInArray is (e <> ALL(a)).
func (NumericExpr) Round ¶
func (e NumericExpr) Round() NumericExpr
Round is round(e), to the nearest integer, halves away from zero.
func (NumericExpr) RoundTo ¶
func (e NumericExpr) RoundTo(places int) NumericExpr
RoundTo is round(e, places): e rounded to places decimal places (to tens, hundreds, … when places is negative).
func (NumericExpr) Sum ¶
func (e NumericExpr) Sum() Agg
type NumericValue ¶
type NumericValue interface {
Expr
// contains filtered or unexported methods
}
NumericValue is a numeric value: a scanner column (AnyCol, how decimal columns are typed) or a NumericExpr.
type Observer ¶
type Observer interface {
BeforeQuery(ctx context.Context, e *QueryEvent) context.Context
AfterQuery(ctx context.Context, e *QueryEvent)
}
Observer sees every statement keel sends: queries, relation loads, writes, COPY, savepoints and advisory locks, but not BEGIN, the Config.OnBegin statements, plan-mode switches or COMMIT.
For each statement keel calls every observer's BeforeQuery in order, the first with the terminal's context and each later one with the context the previous one returned. The context the last one returns is the statement's context: AfterQuery gets it, observers in reverse order, so an observer can start a span in BeforeQuery and end it in AfterQuery. A round trip that carries one statement runs under that statement's context, so a deadline, cancellation or value an observer adds reaches the execution. Statements sent in one round trip (a Batch, the statements of one level of relation loads, the chunks of a CreateMany) each start from the terminal's context, so their contexts are siblings; the round trip itself runs under the terminal's context, and the statements share a start time and a duration.
Example ¶
package main
import (
"context"
"crypto/rand"
"encoding/hex"
"log"
"os"
"time"
"github.com/emoss08/keel"
"github.com/emoss08/keel/internal/testmodels/ids"
)
// scope is what the application's authentication middleware puts on a request context.
type scope struct {
OrganizationID ids.ID
BusinessUnitID ids.ID
SystemReason string
}
type scopeKey struct{}
func newID(prefix string) string {
b := make([]byte, 10)
_, _ = rand.Read(b)
return prefix + hex.EncodeToString(b)
}
// slowQueryLog is an Observer that logs statements slower than threshold.
type slowQueryLog struct{ threshold time.Duration }
func (slowQueryLog) BeforeQuery(ctx context.Context, _ *keel.QueryEvent) context.Context {
return ctx
}
func (l slowQueryLog) AfterQuery(_ context.Context, e *keel.QueryEvent) {
if e.Duration >= l.threshold {
log.Printf("slow %s on %s (%d args, %d rows): %v: %s",
e.Op, e.Table, e.Args, e.Rows, e.Duration, e.SQL)
}
}
// traceKey carries a tracingObserver's span on a statement's context.
type traceKey struct{}
// statementSpan is the span of one statement.
type statementSpan struct {
name string
start time.Time
}
// tracingObserver starts a span in BeforeQuery and ends the span it finds on the statement's
// context in AfterQuery, as a tracing library's observer would.
type tracingObserver struct{}
func (tracingObserver) BeforeQuery(ctx context.Context, e *keel.QueryEvent) context.Context {
return context.WithValue(ctx, traceKey{}, &statementSpan{name: e.Op + " " + e.Table, start: time.Now()})
}
func (tracingObserver) AfterQuery(ctx context.Context, e *keel.QueryEvent) {
sp, ok := ctx.Value(traceKey{}).(*statementSpan)
if !ok {
return
}
log.Printf("span %s: %v, %d rows, error %v", sp.name, time.Since(sp.start), e.Rows, e.Err)
}
// appConfig is the configuration of a multi-tenant application.
func appConfig(dsn string) keel.Config {
return keel.Config{
DSN: dsn,
TenantColumns: []string{"organization_id", "business_unit_id"},
Tenant: func(ctx context.Context) ([]any, bool) {
sc, ok := ctx.Value(scopeKey{}).(scope)
if !ok || sc.SystemReason != "" {
return nil, false
}
return []any{sc.OrganizationID, sc.BusinessUnitID}, true
},
System: func(ctx context.Context) (string, bool) {
sc, ok := ctx.Value(scopeKey{}).(scope)
return sc.SystemReason, ok && sc.SystemReason != ""
},
OnBegin: func(ctx context.Context, tx keel.TxInfo) ([]keel.Statement, error) {
sc, _ := ctx.Value(scopeKey{}).(scope)
return []keel.Statement{{
SQL: "SELECT set_config('app.organization_id', $1, true)",
Args: []any{string(sc.OrganizationID)},
}}, nil
},
NewID: newID,
Observers: []keel.Observer{slowQueryLog{threshold: 200 * time.Millisecond}},
}
}
func main() {
cfg := appConfig(os.Getenv("DATABASE_URL"))
cfg.Observers = append(cfg.Observers, tracingObserver{})
db, err := keel.Open(context.Background(), cfg)
if err != nil {
log.Fatal(err)
}
defer db.Close()
}
Output:
func RecordShapes ¶
RecordShapes returns an observer that writes each distinct statement text it sees to w once, as a JSON line of a Shape. Only statements EXPLAIN can plan are written (those that start with SELECT, WITH, INSERT, UPDATE, DELETE, MERGE, VALUES or TABLE, and not keel's advisory locks), so savepoints, COPY, DDL run through Exec and Explain's own statements are left out. It is safe for concurrent use: a statement already seen costs a read-locked map lookup, and each line goes to w in one Write under a lock, so lines never interleave. It keeps every distinct text in memory, which suits a test run or a staging soak, the runs a plan guard records; write errors are dropped, and RecordShapesFile reports them.
func RecordShapesFile ¶
RecordShapesFile returns an observer that appends the shapes it sees to the file at path, creating it, as RecordShapes writes them, and a function that closes the file and returns the first write error. The file is opened for appending, so the test binaries of one go test ./... can share it: each line is one write, and keel plans reads each distinct text once.
type Order ¶
type Order struct {
// contains filtered or unexported fields
}
Order is one ORDER BY term.
type Page ¶
type Page[T any] struct { Items []T // Next is the cursor of the following page, empty on the last page. Next string // Total is the number of matching rows when the request asked for it. It is exact up to the // request's limit; when more rows match it is the limit, or, when the request asked for an // Estimate, the planner's estimate (never less than the limit plus one). Total int64 // TotalExact reports that Total is the exact number of matching rows. It is false when the // request asked for no total and when more rows matched than the request's limit. TotalExact bool // contains filtered or unexported fields }
Page is one keyset page.
func (Page[T]) CursorAt ¶
CursorAt returns the cursor that resumes the query after Items[i]: After(p.CursorAt(i), n) reads the n rows that follow it. It is the cursor format Next uses (Next is the cursor of the last item when a page follows), computed from the row's order values without a statement, for APIs that give every row a cursor, such as a GraphQL connection's edges. It panics when i is out of range, as indexing Items does, and returns "" on a Page that Page did not read.
type PageRequest ¶
type PageRequest struct {
// contains filtered or unexported fields
}
PageRequest asks for one keyset page.
func After ¶
func After(cursor string, size int) PageRequest
After asks for the page of size rows that follows cursor. An empty cursor asks for the first page.
func (PageRequest) Estimate ¶
func (p PageRequest) Estimate() PageRequest
Estimate makes a total that passes Total's limit the planner's estimate of the matching rows, as CountEstimate reads it: EXPLAIN (FORMAT JSON) of the counted rows rides in the page's round trip. A list showing "about 1.2M" pays for counting at most limit+1 rows. It needs Total.
func (PageRequest) Total ¶
func (p PageRequest) Total(limit int64) PageRequest
Total asks for the number of matching rows, counted exactly up to limit, in the same round trip. When more rows match, Page.Total is limit and Page.TotalExact is false; Estimate makes it the planner's estimate instead.
type Phase ¶
type Phase uint8
Phase is when a table hook runs.
const ( // BeforeWrite runs inside the transaction before an entity write (Create, CreateMany, Copy, // Save, Upsert, CreateOrSkip, DeleteRow). It may change the row or abort the write by returning // an error. Bulk writes (Query.Update, Query.Delete) do not run BeforeWrite hooks. BeforeWrite Phase = iota // AfterWrite runs inside the transaction after the statement, for entity and bulk writes. An error // rolls the transaction back: outside a transaction, a write that may run AfterWrite hooks runs in // an implicit one, and the hook's context carries it, so what the hook runs through keel (Exec, // QueryRows, reads and writes) joins the write and rolls back with it. BeforeWrite hooks get the // same context when the write runs in an implicit transaction. AfterWrite // Committed runs after the transaction commits. Errors are not returned to the caller; // report them from the hook. Committed )
type Plan ¶
type Plan struct {
// TotalCost is the planner's estimated cost of the whole statement, in its cost units.
TotalCost float64
// Rows is the planner's estimate of the rows the statement returns.
Rows float64
// ActualRows is the number of rows the statement returned; set only with Analyze, as are the
// other measured fields.
ActualRows float64
// ActualTime is the top plan node's total time in milliseconds.
ActualTime float64
// PlanningTime is the time spent planning, in milliseconds.
PlanningTime float64
// ExecutionTime is the time spent executing, in milliseconds.
ExecutionTime float64
// JSON is EXPLAIN's output.
JSON []byte
}
Plan is the plan Postgres chose for a statement: the planner's estimates, the measured values when Explain analyzed it, and the whole plan as EXPLAIN's JSON.
type PlanCache ¶
type PlanCache uint8
PlanCache chooses how keel prepares statements.
const ( // PlanCacheAuto prepares each statement shape once per connection and lets Postgres choose // between a generic and a custom plan (plan_cache_mode = auto): it compares the generic plan's // cost with the custom plans of the first executions and keeps custom plans when the generic // one is worse. This skips parsing and, usually, planning. PlanCacheAuto PlanCache = iota // PlanCacheCustom prepares statements but plans every execution for its values // (plan_cache_mode = force_custom_plan). It skips parsing only; use it when parameter values // change plans a lot, as tenants of very different sizes can. PlanCacheCustom // PlanCacheOff prepares nothing: every execution is parsed and planned. Use it behind a // connection pooler that cannot track prepared statements (PgBouncer before 1.21, or without // max_prepared_statements). PlanCacheOff )
type PoolState ¶
PoolState is a connection pool's state: connections checked out, idle, open and the most it opens.
type Position ¶
type Position uint64
Position is a WAL position (an LSN) of the primary, as Postgres prints it: 16/B374D848.
func CommitPosition ¶
CommitPosition returns the newest position of db that replica reads on ctx must have replayed: the commit position of the last write made with ctx after OnReplica, or the position given to OnReplicaAfter. It is zero when there is none or db has no replicas, and the largest Position when keel could not read a commit's position, which keeps the context's reads on the primary.
Example ¶
package main
import (
"context"
"fmt"
"log"
"github.com/emoss08/keel"
"github.com/emoss08/keel/internal/testmodels/fleet"
"github.com/emoss08/keel/internal/testmodels/fleet/fleetdb"
"github.com/emoss08/keel/internal/testmodels/ids"
)
// appDB is the application's database, opened once at startup (see ExampleOpen).
var appDB *keel.DB
// scope is what the application's authentication middleware puts on a request context.
type scope struct {
OrganizationID ids.ID
BusinessUnitID ids.ID
SystemReason string
}
type scopeKey struct{}
func withTenant(ctx context.Context, org, bu ids.ID) context.Context {
return context.WithValue(ctx, scopeKey{}, scope{OrganizationID: org, BusinessUnitID: bu})
}
// requestContext returns the context of a request authenticated for one tenant.
func requestContext() context.Context {
return withTenant(context.Background(), "org_acme", "bu_dallas")
}
func main() {
ctx, db := keel.OnReplica(requestContext()), appDB
s := fleetdb.Shipments
sh := &fleet.Shipment{CustomerID: "cus_01", ProNumber: "PRO-3002", Status: fleet.StatusNew}
if err := s.Create(ctx, db, sh); err != nil {
log.Fatal(err)
}
cookie := keel.CommitPosition(ctx, db).String()
pos, err := keel.ParsePosition(cookie)
if err != nil {
log.Fatal(err)
}
next := keel.OnReplicaAfter(requestContext(), db, pos)
n, err := s.Where(s.Status.Eq(fleet.StatusNew)).Count(next, db)
if err != nil {
log.Fatal(err)
}
fmt.Println(n)
}
Output:
func ParsePosition ¶
ParsePosition parses an LSN as Postgres and Position.String print it.
type PreAggStrategy ¶
type PreAggStrategy uint8
PreAggStrategy chooses how a pre-aggregation reads a to-many relation.
const ( // PreAggCTE aggregates the related rows in one grouped pass, keyed by the parent and filtered // to the parents the query selects, in a CTE joined to the query: one scan of the related // table whatever the number of parents. The default for reports. PreAggCTE PreAggStrategy = iota + 1 // PreAggLateral aggregates each parent's related rows in a LEFT JOIN LATERAL subquery: one // index probe per parent, best when the query selects few parents (a page, a top N). PreAggLateral )
type Pred ¶
type Pred interface {
Expr
// contains filtered or unexported methods
}
Pred is a boolean expression, accepted by Where, Having and the combinators.
func Exists ¶
func Exists(q subquery) Pred
Exists is EXISTS (q).
Example ¶
package main
import (
"context"
"fmt"
"log"
"time"
"github.com/emoss08/keel"
"github.com/emoss08/keel/internal/testmodels/fleet/fleetdb"
"github.com/emoss08/keel/internal/testmodels/ids"
)
// appDB is the application's database, opened once at startup (see ExampleOpen).
var appDB *keel.DB
// scope is what the application's authentication middleware puts on a request context.
type scope struct {
OrganizationID ids.ID
BusinessUnitID ids.ID
SystemReason string
}
type scopeKey struct{}
func withTenant(ctx context.Context, org, bu ids.ID) context.Context {
return context.WithValue(ctx, scopeKey{}, scope{OrganizationID: org, BusinessUnitID: bu})
}
// requestContext returns the context of a request authenticated for one tenant.
func requestContext() context.Context {
return withTenant(context.Background(), "org_acme", "bu_dallas")
}
func main() {
ctx, db := requestContext(), appDB
c, s := fleetdb.Customers, fleetdb.Shipments
cutoff := time.Now().AddDate(0, -3, 0).Unix()
idle, err := c.Where(keel.NotExists(
s.Where(s.CustomerID.EqCol(c.ID), s.CreatedAt.Gte(cutoff)),
)).All(ctx, db)
if err != nil {
log.Fatal(err)
}
hazmatShippers, err := c.Where(
c.ID.InQuery(s.Pick(s.CustomerID).Where(s.Hazmat.IsTrue())),
).Count(ctx, db)
if err != nil {
log.Fatal(err)
}
fmt.Println(len(idle), hazmatShippers)
}
Output:
func When ¶
When returns p when cond holds and nil otherwise. Where and the combinators skip nil, which makes optional filters one expression each.
Example ¶
package main
import (
"context"
"fmt"
"log"
"github.com/emoss08/keel"
"github.com/emoss08/keel/internal/testmodels/fleet"
"github.com/emoss08/keel/internal/testmodels/fleet/fleetdb"
"github.com/emoss08/keel/internal/testmodels/ids"
)
// appDB is the application's database, opened once at startup (see ExampleOpen).
var appDB *keel.DB
// scope is what the application's authentication middleware puts on a request context.
type scope struct {
OrganizationID ids.ID
BusinessUnitID ids.ID
SystemReason string
}
type scopeKey struct{}
func withTenant(ctx context.Context, org, bu ids.ID) context.Context {
return context.WithValue(ctx, scopeKey{}, scope{OrganizationID: org, BusinessUnitID: bu})
}
// requestContext returns the context of a request authenticated for one tenant.
func requestContext() context.Context {
return withTenant(context.Background(), "org_acme", "bu_dallas")
}
// shipmentFilter is a search request with optional fields.
type shipmentFilter struct {
Search string
Statuses []fleet.Status
CustomerID ids.ID
HazmatOnly bool
}
func main() {
ctx, db := requestContext(), appDB
f := shipmentFilter{Search: "PRO-10", Statuses: []fleet.Status{fleet.StatusNew}}
s := fleetdb.Shipments
rows, err := s.Where(
keel.When(f.Search != "", s.ProNumber.ContainsFold(f.Search)),
keel.When(len(f.Statuses) > 0, s.Status.In(f.Statuses...)),
keel.When(f.CustomerID != "", s.CustomerID.Eq(f.CustomerID)),
keel.When(f.HazmatOnly, s.Hazmat.IsTrue()),
).Order(s.CreatedAt.Desc()).Limit(50).List(ctx, db)
if err != nil {
log.Fatal(err)
}
fmt.Println(len(rows))
}
Output:
type Projection ¶
type Projection interface {
// contains filtered or unexported methods
}
Projection is a statement whose output columns fill a struct: a query that picks expressions, or a Compound. *Query[T] and Compound implement it.
type Query ¶
type Query[T any] struct { // contains filtered or unexported fields }
Query is a query over model T. Builder methods change the query and return it.
func (*Query[T]) AllTenants ¶
AllTenants lets the query cross tenants. It needs a system scope. The query's relation loads and its Any and None predicates cross tenants with it; a query used as a subquery (Exists, InQuery, a lateral join, a CTE, a set operation) declares AllTenants itself.
Example ¶
package main
import (
"context"
"fmt"
"log"
"time"
"github.com/emoss08/keel"
"github.com/emoss08/keel/internal/testmodels/fleet"
"github.com/emoss08/keel/internal/testmodels/fleet/fleetdb"
"github.com/emoss08/keel/internal/testmodels/ids"
)
// appDB is the application's database, opened once at startup (see ExampleOpen).
var appDB *keel.DB
// scope is what the application's authentication middleware puts on a request context.
type scope struct {
OrganizationID ids.ID
BusinessUnitID ids.ID
SystemReason string
}
type scopeKey struct{}
func withSystem(ctx context.Context, reason string) context.Context {
return context.WithValue(ctx, scopeKey{}, scope{SystemReason: reason})
}
func main() {
ctx, db := withSystem(context.Background(), "purge canceled shipments"), appDB
s := fleetdb.Shipments
m := s.Moves()
cutoff := time.Now().AddDate(-1, 0, 0).Unix()
stale, err := s.AllTenants().
Where(s.Status.Eq(fleet.StatusCanceled), s.CanceledAt.Lt(cutoff), m.None(m.Loaded.IsTrue())).
Load(s.Customer(), m.Stops()).
Limit(1000).
List(ctx, db)
if err != nil {
log.Fatal(err)
}
n, err := s.AllTenants().
Where(s.Status.Eq(fleet.StatusCanceled), s.CanceledAt.Lt(cutoff)).
Delete(ctx, db)
if err != nil {
log.Fatal(err)
}
fmt.Println(len(stale), n)
}
Output:
func (*Query[T]) Any ¶
Any is true when at least one row of the query matches ps. On a query started from a to-many relation path it is correlated with the parent row.
func (*Query[T]) Cache ¶
func (q *Query[T]) Cache(opts ...CacheOption) *Query[T]
Cache serves the query's One, First, List, All, Count, CountUpTo, CountEstimate, Exists and Page (and the Batch items of the same names) from the result cache (Config.Cache) while the data versions of every table they read (the base table, joins, relation loads, subqueries, Any and None, CTEs, and AlsoReads) are unchanged for the tenant. A Batch is served from the cache only when every item's query asks for it, for the shortest of their TTLs. The cache is bypassed inside a write transaction, a joined one and a REPEATABLE READ or SERIALIZABLE one that has already begun (sent a statement, so its snapshot is taken), under a system scope, for a query with Lock, and while the cache's listener is disconnected. A snapshot transaction that has not begun is served from the cache, and a hit leaves it unbegun: its snapshot is taken by its first statement that reads the database. Without Config.Cache it does nothing.
func (*Query[T]) Compute ¶
Compute selects e into the computed field col of the rows the query returns: a search rank, an aggregate over related rows, a value of a joined table. col must be a field tagged computed and e's type must fit it (an int64 field takes count(*), a float64 field ts_rank, a string field a string expression); expressions whose type keel cannot tell, such as Raw, are checked by the driver when rows decode. The query can filter and order by col too: keel writes e in its place.
s.Compute(s.Rank, s.SearchVector.Rank(q)).Where(s.SearchVector.Match(q)).Order(s.Rank.Desc())
Example ¶
package main
import (
"context"
"fmt"
"log"
"github.com/emoss08/keel"
"github.com/emoss08/keel/internal/testmodels/fleet/fleetdb"
"github.com/emoss08/keel/internal/testmodels/ids"
)
// appDB is the application's database, opened once at startup (see ExampleOpen).
var appDB *keel.DB
// scope is what the application's authentication middleware puts on a request context.
type scope struct {
OrganizationID ids.ID
BusinessUnitID ids.ID
SystemReason string
}
type scopeKey struct{}
func withTenant(ctx context.Context, org, bu ids.ID) context.Context {
return context.WithValue(ctx, scopeKey{}, scope{OrganizationID: org, BusinessUnitID: bu})
}
// requestContext returns the context of a request authenticated for one tenant.
func requestContext() context.Context {
return withTenant(context.Background(), "org_acme", "bu_dallas")
}
func main() {
ctx, db := requestContext(), appDB
c, s := fleetdb.Contacts, fleetdb.Shipments
rows, err := c.
Compute(c.CustomerName, c.Customer().Name).
Compute(c.Loads, keel.Scalar(s.Pick(keel.Count()).Where(s.CustomerID.EqCol(c.CustomerID)))).
Where(c.Loads.Gt(0)).
Order(c.Loads.Desc()).
Limit(50).
List(ctx, db)
if err != nil {
log.Fatal(err)
}
for _, r := range rows {
fmt.Println(r.Name, r.CustomerName, r.Loads)
}
}
Output:
func (*Query[T]) Count ¶
Count returns the number of matching rows.
Example ¶
package main
import (
"context"
"fmt"
"log"
"github.com/emoss08/keel"
"github.com/emoss08/keel/internal/testmodels/fleet"
"github.com/emoss08/keel/internal/testmodels/fleet/fleetdb"
"github.com/emoss08/keel/internal/testmodels/ids"
)
// appDB is the application's database, opened once at startup (see ExampleOpen).
var appDB *keel.DB
// scope is what the application's authentication middleware puts on a request context.
type scope struct {
OrganizationID ids.ID
BusinessUnitID ids.ID
SystemReason string
}
type scopeKey struct{}
func withTenant(ctx context.Context, org, bu ids.ID) context.Context {
return context.WithValue(ctx, scopeKey{}, scope{OrganizationID: org, BusinessUnitID: bu})
}
// requestContext returns the context of a request authenticated for one tenant.
func requestContext() context.Context {
return withTenant(context.Background(), "org_acme", "bu_dallas")
}
func main() {
ctx, db := requestContext(), appDB
s := fleetdb.Shipments
open := s.Where(s.Status.In(fleet.StatusNew, fleet.StatusAssigned))
n, err := open.Count(ctx, db)
if err != nil {
log.Fatal(err)
}
upTo, err := open.CountUpTo(ctx, db, 1000)
if err != nil {
log.Fatal(err)
}
exists, err := open.Exists(ctx, db)
if err != nil {
log.Fatal(err)
}
fmt.Println(n, upTo, exists)
}
Output:
func (*Query[T]) CountEstimate ¶
CountEstimate returns the number of matching rows, counted exactly up to exactUpTo, and the planner's estimate when more match: a list header shows "1,204" for a small set and "about 1.2M" for a large one without counting it. The count reads at most exactUpTo+1 rows (SELECT count(*) FROM (SELECT 1 … LIMIT exactUpTo+1)), and EXPLAIN (FORMAT JSON) of the same rows without the limit, with the same text and bound arguments, rides in the same round trip; the estimate is the top plan node's row count, so it reflects the query's filter, joins, groups and the bound values. The query's own Limit and Offset do not apply, as for CountUpTo. An estimate is as good as the table's statistics: it can be well off on a table whose statistics are stale, but it is never less than exactUpTo+1. exactUpTo must not be negative.
Example ¶
ctx, db := requestContext(), appDB
s := fleetdb.Shipments
c, err := s.Where(s.Status.In(fleet.StatusNew, fleet.StatusAssigned)).CountEstimate(ctx, db, 1000)
if err != nil {
log.Fatal(err)
}
if c.Exact {
fmt.Println(c.N, "shipments")
} else {
fmt.Println("about", c.N, "shipments")
}
func (*Query[T]) Cube ¶
Cube groups by every subset of es: CUBE (a, b) is GROUPING SETS ((a, b), (a), (b), ()).
func (*Query[T]) CustomPlan ¶
CustomPlan plans this query for its parameter values on every execution, even when the connection would reuse a generic plan. Use it for queries whose best plan depends on the values, such as a filter whose selectivity differs widely between tenants. It costs no round trip.
Example ¶
package main
import (
"context"
"fmt"
"log"
"github.com/emoss08/keel"
"github.com/emoss08/keel/internal/testmodels/fleet"
"github.com/emoss08/keel/internal/testmodels/fleet/fleetdb"
"github.com/emoss08/keel/internal/testmodels/ids"
)
// appDB is the application's database, opened once at startup (see ExampleOpen).
var appDB *keel.DB
// scope is what the application's authentication middleware puts on a request context.
type scope struct {
OrganizationID ids.ID
BusinessUnitID ids.ID
SystemReason string
}
type scopeKey struct{}
func withTenant(ctx context.Context, org, bu ids.ID) context.Context {
return context.WithValue(ctx, scopeKey{}, scope{OrganizationID: org, BusinessUnitID: bu})
}
// requestContext returns the context of a request authenticated for one tenant.
func requestContext() context.Context {
return withTenant(context.Background(), "org_acme", "bu_dallas")
}
func main() {
ctx, db := requestContext(), appDB
s := fleetdb.Shipments
rows, err := s.
Where(s.Status.Eq(fleet.StatusDelivered), s.CustomerID.Eq("cus_01")).
CustomPlan().
Order(s.CreatedAt.Desc()).
Limit(100).
List(ctx, db)
if err != nil {
log.Fatal(err)
}
fmt.Println(len(rows))
}
Output:
func (*Query[T]) Delete ¶
Delete deletes every matching row and returns the number of rows deleted.
Example ¶
package main
import (
"context"
"fmt"
"log"
"github.com/emoss08/keel"
"github.com/emoss08/keel/internal/testmodels/fleet/fleetdb"
"github.com/emoss08/keel/internal/testmodels/ids"
)
// appDB is the application's database, opened once at startup (see ExampleOpen).
var appDB *keel.DB
// scope is what the application's authentication middleware puts on a request context.
type scope struct {
OrganizationID ids.ID
BusinessUnitID ids.ID
SystemReason string
}
type scopeKey struct{}
func withTenant(ctx context.Context, org, bu ids.ID) context.Context {
return context.WithValue(ctx, scopeKey{}, scope{OrganizationID: org, BusinessUnitID: bu})
}
// requestContext returns the context of a request authenticated for one tenant.
func requestContext() context.Context {
return withTenant(context.Background(), "org_acme", "bu_dallas")
}
func main() {
ctx, db := requestContext(), appDB
st := fleetdb.Stops
n, err := st.Where(st.MoveID.Eq("mov_01"), st.ArrivedAt.IsNull()).Delete(ctx, db)
if err != nil {
log.Fatal(err)
}
fmt.Println(n, "stops deleted")
}
Output:
func (*Query[T]) DistinctOn ¶
DistinctOn keeps the first row of each group of es (DISTINCT ON).
Example ¶
package main
import (
"context"
"fmt"
"log"
"github.com/emoss08/keel"
"github.com/emoss08/keel/internal/testmodels/fleet/fleetdb"
"github.com/emoss08/keel/internal/testmodels/ids"
)
// appDB is the application's database, opened once at startup (see ExampleOpen).
var appDB *keel.DB
// scope is what the application's authentication middleware puts on a request context.
type scope struct {
OrganizationID ids.ID
BusinessUnitID ids.ID
SystemReason string
}
type scopeKey struct{}
func withTenant(ctx context.Context, org, bu ids.ID) context.Context {
return context.WithValue(ctx, scopeKey{}, scope{OrganizationID: org, BusinessUnitID: bu})
}
// requestContext returns the context of a request authenticated for one tenant.
func requestContext() context.Context {
return withTenant(context.Background(), "org_acme", "bu_dallas")
}
func main() {
ctx, db := requestContext(), appDB
s := fleetdb.Shipments
latest, err := s.
DistinctOn(s.CustomerID).
Order(s.CustomerID.Asc(), s.CreatedAt.Desc()).
All(ctx, db)
if err != nil {
log.Fatal(err)
}
fmt.Println(len(latest), "customers")
}
Output:
func (*Query[T]) Each ¶
func (q *Query[T]) Each(ctx context.Context, db *DB, size int, fn func(ctx context.Context, batch []T) error, ) error
Each runs the query page by page in keyset order and calls fn with each batch of at most size rows, holding one batch in memory at a time.
Example ¶
package main
import (
"context"
"fmt"
"log"
"github.com/emoss08/keel"
"github.com/emoss08/keel/internal/testmodels/fleet"
"github.com/emoss08/keel/internal/testmodels/fleet/fleetdb"
"github.com/emoss08/keel/internal/testmodels/ids"
)
// appDB is the application's database, opened once at startup (see ExampleOpen).
var appDB *keel.DB
// scope is what the application's authentication middleware puts on a request context.
type scope struct {
OrganizationID ids.ID
BusinessUnitID ids.ID
SystemReason string
}
type scopeKey struct{}
func withSystem(ctx context.Context, reason string) context.Context {
return context.WithValue(ctx, scopeKey{}, scope{SystemReason: reason})
}
func main() {
ctx, db := withSystem(context.Background(), "nightly hazmat audit"), appDB
s := fleetdb.Shipments
err := s.AllTenants().Where(s.Hazmat.IsTrue()).Each(ctx, db, 500,
func(ctx context.Context, batch []*fleet.Shipment) error {
for _, sh := range batch {
fmt.Println(sh.OrganizationID, sh.ProNumber)
}
return nil
})
if err != nil {
log.Fatal(err)
}
}
Output:
func (*Query[T]) Explain ¶
Explain returns the plan of the query's main statement, rendered as ToSQL renders it under ctx's scope, with its parameters bound so the plan is the one Postgres chooses for these values.
Example ¶
package main
import (
"context"
"fmt"
"log"
"github.com/emoss08/keel"
"github.com/emoss08/keel/internal/testmodels/fleet"
"github.com/emoss08/keel/internal/testmodels/fleet/fleetdb"
"github.com/emoss08/keel/internal/testmodels/ids"
)
// appDB is the application's database, opened once at startup (see ExampleOpen).
var appDB *keel.DB
// scope is what the application's authentication middleware puts on a request context.
type scope struct {
OrganizationID ids.ID
BusinessUnitID ids.ID
SystemReason string
}
type scopeKey struct{}
func withTenant(ctx context.Context, org, bu ids.ID) context.Context {
return context.WithValue(ctx, scopeKey{}, scope{OrganizationID: org, BusinessUnitID: bu})
}
// requestContext returns the context of a request authenticated for one tenant.
func requestContext() context.Context {
return withTenant(context.Background(), "org_acme", "bu_dallas")
}
func main() {
ctx, db := requestContext(), appDB
s := fleetdb.Shipments
q := s.Where(s.Status.Eq(fleet.StatusNew)).Order(s.CreatedAt.Desc()).Limit(50)
plan, err := q.Explain(ctx, db, keel.ExplainOptions{})
if err != nil {
log.Fatal(err)
}
if plan.TotalCost > 100_000 {
fmt.Println("too expensive for an interactive request")
}
analyzed, err := q.Explain(ctx, db, keel.ExplainOptions{Analyze: true})
if err != nil {
log.Fatal(err)
}
fmt.Println(analyzed.Rows, analyzed.ActualRows, analyzed.ExecutionTime)
}
Output:
func (*Query[T]) GroupingSets ¶
GroupingSets groups by each of sets in turn, in one statement: GROUP BY GROUPING SETS ((a, b), (a), ()). Each set yields its own rows, with the expressions it does not group by NULL; an empty set is the grand total. Grouping tells the rows of each set apart. Group expressions added before or after are grouped in every set.
Example ¶
package main
import (
"context"
"fmt"
"log"
"github.com/emoss08/keel"
"github.com/emoss08/keel/internal/testmodels/fleet"
"github.com/emoss08/keel/internal/testmodels/fleet/fleetdb"
"github.com/emoss08/keel/internal/testmodels/ids"
)
// appDB is the application's database, opened once at startup (see ExampleOpen).
var appDB *keel.DB
// scope is what the application's authentication middleware puts on a request context.
type scope struct {
OrganizationID ids.ID
BusinessUnitID ids.ID
SystemReason string
}
type scopeKey struct{}
func withTenant(ctx context.Context, org, bu ids.ID) context.Context {
return context.WithValue(ctx, scopeKey{}, scope{OrganizationID: org, BusinessUnitID: bu})
}
// requestContext returns the context of a request authenticated for one tenant.
func requestContext() context.Context {
return withTenant(context.Background(), "org_acme", "bu_dallas")
}
func main() {
ctx, db := requestContext(), appDB
s := fleetdb.Shipments
code := s.Customer().Code
level := keel.Grouping(code, s.Status)
type total struct {
Customer *string
Status *fleet.Status
Loads int64
}
rows, err := keel.Into[total](ctx, db, s.
Pick(code, s.Status, keel.Count()).
GroupingSets([]keel.Expr{code}, []keel.Expr{s.Status}, nil).
Having(level.Gt(0)))
if err != nil {
log.Fatal(err)
}
fmt.Println(len(rows))
}
Output:
func (*Query[T]) Join ¶
Join adds an INNER JOIN of another table on the given predicates.
Example ¶
package main
import (
"context"
"fmt"
"log"
"github.com/emoss08/keel"
"github.com/emoss08/keel/internal/testmodels/fleet/fleetdb"
"github.com/emoss08/keel/internal/testmodels/ids"
)
// appDB is the application's database, opened once at startup (see ExampleOpen).
var appDB *keel.DB
// scope is what the application's authentication middleware puts on a request context.
type scope struct {
OrganizationID ids.ID
BusinessUnitID ids.ID
SystemReason string
}
type scopeKey struct{}
func withTenant(ctx context.Context, org, bu ids.ID) context.Context {
return context.WithValue(ctx, scopeKey{}, scope{OrganizationID: org, BusinessUnitID: bu})
}
// requestContext returns the context of a request authenticated for one tenant.
func requestContext() context.Context {
return withTenant(context.Background(), "org_acme", "bu_dallas")
}
func main() {
ctx, db := requestContext(), appDB
w := fleetdb.Workers
mgr := w.As("mgr")
reports, err := w.
Join(mgr, mgr.ID.EqCol(w.ManagerID)).
Where(mgr.LastName.Eq("Hopper")).
Order(w.LastName.Asc()).
All(ctx, db)
if err != nil {
log.Fatal(err)
}
s, c := fleetdb.Shipments, fleetdb.Customers
rows, err := s.
LeftJoin(c, c.ID.EqCol(s.BillToID)).
Where(keel.Or(c.ID.IsNull(), c.Email.IsNotNull())).
Limit(100).
List(ctx, db)
if err != nil {
log.Fatal(err)
}
fmt.Println(len(reports), len(rows))
}
Output:
func (*Query[T]) JoinLateral ¶
JoinLateral adds CROSS JOIN LATERAL (sub).
func (*Query[T]) LeftJoinLateral ¶
LeftJoinLateral adds LEFT JOIN LATERAL (sub) ON TRUE. Columns of sub's table, referenced outside sub, read sub's output.
Example ¶
package main
import (
"context"
"fmt"
"log"
"github.com/emoss08/keel"
"github.com/emoss08/keel/internal/testmodels/fleet/fleetdb"
"github.com/emoss08/keel/internal/testmodels/ids"
)
// appDB is the application's database, opened once at startup (see ExampleOpen).
var appDB *keel.DB
// scope is what the application's authentication middleware puts on a request context.
type scope struct {
OrganizationID ids.ID
BusinessUnitID ids.ID
SystemReason string
}
type scopeKey struct{}
func withTenant(ctx context.Context, org, bu ids.ID) context.Context {
return context.WithValue(ctx, scopeKey{}, scope{OrganizationID: org, BusinessUnitID: bu})
}
// requestContext returns the context of a request authenticated for one tenant.
func requestContext() context.Context {
return withTenant(context.Background(), "org_acme", "bu_dallas")
}
func main() {
ctx, db := requestContext(), appDB
m := fleetdb.Moves
last := fleetdb.Stops.As("last_stop")
type moveEnd struct {
Move ids.ID
Location ids.ID
Sequence int32
}
rows, err := keel.Into[moveEnd](ctx, db, m.
LeftJoinLateral(last.
Pick(last.LocationID, last.Sequence).
Where(last.MoveID.EqCol(m.ID)).
Order(last.Sequence.Desc()).
Limit(1)).
Pick(m.ID, last.LocationID, last.Sequence).
Where(m.ShipmentID.Eq("shp_01")))
if err != nil {
log.Fatal(err)
}
fmt.Println(rows)
}
Output:
func (*Query[T]) Load ¶
Load loads relations into the rows.
Example ¶
package main
import (
"context"
"fmt"
"log"
"github.com/emoss08/keel"
"github.com/emoss08/keel/internal/testmodels/fleet"
"github.com/emoss08/keel/internal/testmodels/fleet/fleetdb"
"github.com/emoss08/keel/internal/testmodels/ids"
)
// appDB is the application's database, opened once at startup (see ExampleOpen).
var appDB *keel.DB
// scope is what the application's authentication middleware puts on a request context.
type scope struct {
OrganizationID ids.ID
BusinessUnitID ids.ID
SystemReason string
}
type scopeKey struct{}
func withTenant(ctx context.Context, org, bu ids.ID) context.Context {
return context.WithValue(ctx, scopeKey{}, scope{OrganizationID: org, BusinessUnitID: bu})
}
// requestContext returns the context of a request authenticated for one tenant.
func requestContext() context.Context {
return withTenant(context.Background(), "org_acme", "bu_dallas")
}
func main() {
ctx, db := requestContext(), appDB
s := fleetdb.Shipments
m := s.Moves()
cu := s.Customer()
rows, err := s.
Where(s.Status.Eq(fleet.StatusInTransit)).
Load(
cu.Pick(cu.Name, cu.Code),
s.BillTo(),
m.Where(m.Status.Ne(fleet.StatusCanceled)).
Order(m.Sequence.Asc()).
Load(m.Stops().Location()),
s.Tags(),
).
Order(s.CreatedAt.Desc()).
Limit(50).
List(ctx, db)
if err != nil {
log.Fatal(err)
}
for _, sh := range rows {
fmt.Println(sh.ProNumber, sh.Customer.Name, len(sh.Moves), len(sh.Tags))
for _, mv := range sh.Moves {
for _, st := range mv.Stops {
fmt.Println(" ", st.Sequence, st.Location.City)
}
}
}
}
Output:
Example (Limit) ¶
package main
import (
"context"
"fmt"
"log"
"github.com/emoss08/keel"
"github.com/emoss08/keel/internal/testmodels/fleet/fleetdb"
"github.com/emoss08/keel/internal/testmodels/ids"
)
// appDB is the application's database, opened once at startup (see ExampleOpen).
var appDB *keel.DB
// scope is what the application's authentication middleware puts on a request context.
type scope struct {
OrganizationID ids.ID
BusinessUnitID ids.ID
SystemReason string
}
type scopeKey struct{}
func withTenant(ctx context.Context, org, bu ids.ID) context.Context {
return context.WithValue(ctx, scopeKey{}, scope{OrganizationID: org, BusinessUnitID: bu})
}
// requestContext returns the context of a request authenticated for one tenant.
func requestContext() context.Context {
return withTenant(context.Background(), "org_acme", "bu_dallas")
}
func main() {
ctx, db := requestContext(), appDB
s := fleetdb.Shipments
m := s.Moves()
rows, err := s.
Load(m.Order(m.Sequence.Desc()).Limit(1)).
Order(s.CreatedAt.Desc()).
Limit(50).
List(ctx, db)
if err != nil {
log.Fatal(err)
}
for _, sh := range rows {
if len(sh.Moves) > 0 {
fmt.Println(sh.ProNumber, "last move", sh.Moves[0].ID)
}
}
}
Output:
Example (Pick) ¶
package main
import (
"context"
"fmt"
"log"
"github.com/emoss08/keel"
"github.com/emoss08/keel/internal/testmodels/fleet/fleetdb"
"github.com/emoss08/keel/internal/testmodels/ids"
)
// appDB is the application's database, opened once at startup (see ExampleOpen).
var appDB *keel.DB
// scope is what the application's authentication middleware puts on a request context.
type scope struct {
OrganizationID ids.ID
BusinessUnitID ids.ID
SystemReason string
}
type scopeKey struct{}
func withTenant(ctx context.Context, org, bu ids.ID) context.Context {
return context.WithValue(ctx, scopeKey{}, scope{OrganizationID: org, BusinessUnitID: bu})
}
// requestContext returns the context of a request authenticated for one tenant.
func requestContext() context.Context {
return withTenant(context.Background(), "org_acme", "bu_dallas")
}
func main() {
ctx, db := requestContext(), appDB
s := fleetdb.Shipments
origin := s.OriginStop()
rows, err := s.
Where(origin.Location().City.Eq("Austin")).
Load(origin.Location(), s.DestinationStop().Location()).
Order(s.CreatedAt.Desc()).
Limit(50).
List(ctx, db)
if err != nil {
log.Fatal(err)
}
for _, sh := range rows {
if sh.DestinationStop != nil {
fmt.Println(sh.ProNumber, sh.OriginStop.Location.City, "→", sh.DestinationStop.Location.City)
}
}
}
Output:
func (*Query[T]) LoadStrategy ¶
func (q *Query[T]) LoadStrategy(s LoadStrategy) *Query[T]
LoadStrategy chooses how the query's relations load, overriding Config.LoadStrategy for the query and every load below it; a load can choose again for its own subtree: Load(m.LoadStrategy(keel.LoadPerLevel)).
func (*Query[T]) Lock ¶
func (q *Query[T]) Lock(strength LockStrength, wait ...LockWait) *Query[T]
Lock locks the table's selected rows. Joined relations are not locked.
Example ¶
package main
import (
"context"
"log"
"github.com/emoss08/keel"
"github.com/emoss08/keel/internal/testmodels/fleet"
"github.com/emoss08/keel/internal/testmodels/fleet/fleetdb"
"github.com/emoss08/keel/internal/testmodels/ids"
)
// appDB is the application's database, opened once at startup (see ExampleOpen).
var appDB *keel.DB
// scope is what the application's authentication middleware puts on a request context.
type scope struct {
OrganizationID ids.ID
BusinessUnitID ids.ID
SystemReason string
}
type scopeKey struct{}
func withTenant(ctx context.Context, org, bu ids.ID) context.Context {
return context.WithValue(ctx, scopeKey{}, scope{OrganizationID: org, BusinessUnitID: bu})
}
// requestContext returns the context of a request authenticated for one tenant.
func requestContext() context.Context {
return withTenant(context.Background(), "org_acme", "bu_dallas")
}
func main() {
ctx, db := requestContext(), appDB
s := fleetdb.Shipments
err := keel.Write(ctx, db, func(ctx context.Context) error {
batch, err := s.
Where(s.Status.Eq(fleet.StatusNew)).
Order(s.CreatedAt.Asc()).
Limit(20).
Lock(keel.ForUpdate, keel.SkipLocked).
List(ctx, db)
if err != nil {
return err
}
for _, sh := range batch {
sh.Status = fleet.StatusAssigned
if err := s.Save(ctx, db, sh, s.Status); err != nil {
return err
}
}
return nil
})
if err != nil {
log.Fatal(err)
}
}
Output:
func (*Query[T]) Omit ¶
Omit leaves cols out of the columns the query reads, so a heavy or secret column (a transcript, raw file contents, a token) is not read; the fields stay zero. The primary key and the columns relation loads need are read anyway. It works on loads too: Load(r.Omit(r.Payload)).
Example ¶
package main
import (
"context"
"fmt"
"log"
"github.com/emoss08/keel"
"github.com/emoss08/keel/internal/testmodels/fleet/fleetdb"
"github.com/emoss08/keel/internal/testmodels/ids"
)
// appDB is the application's database, opened once at startup (see ExampleOpen).
var appDB *keel.DB
// scope is what the application's authentication middleware puts on a request context.
type scope struct {
OrganizationID ids.ID
BusinessUnitID ids.ID
SystemReason string
}
type scopeKey struct{}
func withTenant(ctx context.Context, org, bu ids.ID) context.Context {
return context.WithValue(ctx, scopeKey{}, scope{OrganizationID: org, BusinessUnitID: bu})
}
// requestContext returns the context of a request authenticated for one tenant.
func requestContext() context.Context {
return withTenant(context.Background(), "org_acme", "bu_dallas")
}
func main() {
ctx, db := requestContext(), appDB
c := fleetdb.Contacts
contacts, err := c.Omit(c.Avatar, c.Prefs).Order(c.Name.Asc()).Limit(50).List(ctx, db)
if err != nil {
log.Fatal(err)
}
fmt.Println(len(contacts))
}
Output:
func (*Query[T]) One ¶
One returns the only matching row: ErrNotFound when none matches, ErrNotSingular when more do.
Example ¶
package main
import (
"context"
"errors"
"fmt"
"log"
"github.com/emoss08/keel"
"github.com/emoss08/keel/internal/testmodels/fleet"
"github.com/emoss08/keel/internal/testmodels/fleet/fleetdb"
"github.com/emoss08/keel/internal/testmodels/ids"
)
// appDB is the application's database, opened once at startup (see ExampleOpen).
var appDB *keel.DB
// scope is what the application's authentication middleware puts on a request context.
type scope struct {
OrganizationID ids.ID
BusinessUnitID ids.ID
SystemReason string
}
type scopeKey struct{}
func withTenant(ctx context.Context, org, bu ids.ID) context.Context {
return context.WithValue(ctx, scopeKey{}, scope{OrganizationID: org, BusinessUnitID: bu})
}
// requestContext returns the context of a request authenticated for one tenant.
func requestContext() context.Context {
return withTenant(context.Background(), "org_acme", "bu_dallas")
}
func main() {
ctx, db := requestContext(), appDB
s := fleetdb.Shipments
byID, err := s.Get(ctx, db, "shp_01")
if errors.Is(err, keel.ErrNotFound) {
fmt.Println("no such shipment")
} else if err != nil {
log.Fatal(err)
}
sh, err := s.Where(s.ProNumber.Eq("PRO-1001")).One(ctx, db)
if err != nil {
log.Fatal(err)
}
newest, err := s.Where(s.CustomerID.Eq(sh.CustomerID)).Order(s.CreatedAt.Desc()).First(ctx, db)
if err != nil {
log.Fatal(err)
}
recent, err := s.Where(s.Status.Eq(fleet.StatusNew)).Order(s.CreatedAt.Desc()).Limit(50).List(ctx, db)
if err != nil {
log.Fatal(err)
}
tags, err := fleetdb.Tags.Query().All(ctx, db)
if err != nil {
log.Fatal(err)
}
fmt.Println(byID, newest.ProNumber, len(recent), len(tags))
}
Output:
func (*Query[T]) Page ¶
Page returns the page of rows after the request's cursor. The query's order may name columns of its table, columns of to-one relations (belongs, one and pick paths, joined as any read joins them) and expressions; keel adds the primary key when the order does not name it, so every row has a unique position.
Example ¶
package main
import (
"context"
"fmt"
"log"
"github.com/emoss08/keel"
"github.com/emoss08/keel/internal/testmodels/fleet"
"github.com/emoss08/keel/internal/testmodels/fleet/fleetdb"
"github.com/emoss08/keel/internal/testmodels/ids"
)
// appDB is the application's database, opened once at startup (see ExampleOpen).
var appDB *keel.DB
// scope is what the application's authentication middleware puts on a request context.
type scope struct {
OrganizationID ids.ID
BusinessUnitID ids.ID
SystemReason string
}
type scopeKey struct{}
func withTenant(ctx context.Context, org, bu ids.ID) context.Context {
return context.WithValue(ctx, scopeKey{}, scope{OrganizationID: org, BusinessUnitID: bu})
}
// requestContext returns the context of a request authenticated for one tenant.
func requestContext() context.Context {
return withTenant(context.Background(), "org_acme", "bu_dallas")
}
func main() {
ctx, db := requestContext(), appDB
s := fleetdb.Shipments
cursor := ""
page, err := s.
Where(s.Status.In(fleet.StatusNew, fleet.StatusAssigned)).
Load(s.Customer()).
Order(s.CreatedAt.Desc()).
Page(ctx, db, keel.After(cursor, 50).Total(10_000))
if err != nil {
log.Fatal(err)
}
fmt.Println(len(page.Items), page.Total, page.TotalExact)
if page.Next != "" {
fmt.Println("next page:", page.Next)
}
}
Output:
func (*Query[T]) Pick ¶
Pick selects only es. When every expression is a column of the table, the rows still decode into T with the other fields left zero; otherwise the query is a projection for Into.
func (*Query[T]) Rollup ¶
Rollup groups by es and by each prefix of es down to the grand total: ROLLUP (a, b) is GROUPING SETS ((a, b), (a), ()), the rows with their subtotals and total.
Example ¶
package main
import (
"context"
"fmt"
"log"
"github.com/emoss08/keel"
"github.com/emoss08/keel/internal/testmodels/fleet"
"github.com/emoss08/keel/internal/testmodels/fleet/fleetdb"
"github.com/emoss08/keel/internal/testmodels/ids"
)
// appDB is the application's database, opened once at startup (see ExampleOpen).
var appDB *keel.DB
// scope is what the application's authentication middleware puts on a request context.
type scope struct {
OrganizationID ids.ID
BusinessUnitID ids.ID
SystemReason string
}
type scopeKey struct{}
func withTenant(ctx context.Context, org, bu ids.ID) context.Context {
return context.WithValue(ctx, scopeKey{}, scope{OrganizationID: org, BusinessUnitID: bu})
}
// requestContext returns the context of a request authenticated for one tenant.
func requestContext() context.Context {
return withTenant(context.Background(), "org_acme", "bu_dallas")
}
func main() {
ctx, db := requestContext(), appDB
s := fleetdb.Shipments
code := s.Customer().Code
level := keel.Grouping(code, s.Status)
type line struct {
Customer *string
Status *fleet.Status
Loads int64
Revenue fleet.Money
Level int32
}
rows, err := keel.Into[line](ctx, db, s.
Pick(code, s.Status, keel.Count(), s.Total.Sum(), level).
Rollup(code, s.Status).
Order(level.Asc(), code.Asc(), s.Status.Asc()))
if err != nil {
log.Fatal(err)
}
for _, r := range rows {
switch r.Level {
case 0:
fmt.Println(*r.Customer, *r.Status, r.Loads)
case 1:
fmt.Println(*r.Customer, "subtotal", r.Loads)
default:
fmt.Println("total", r.Loads)
}
}
}
Output:
func (*Query[T]) Timeout ¶
Timeout bounds the query's execution.
Example ¶
package main
import (
"context"
"errors"
"fmt"
"log"
"time"
"github.com/emoss08/keel"
"github.com/emoss08/keel/internal/testmodels/fleet/fleetdb"
"github.com/emoss08/keel/internal/testmodels/ids"
)
// appDB is the application's database, opened once at startup (see ExampleOpen).
var appDB *keel.DB
// scope is what the application's authentication middleware puts on a request context.
type scope struct {
OrganizationID ids.ID
BusinessUnitID ids.ID
SystemReason string
}
type scopeKey struct{}
func withTenant(ctx context.Context, org, bu ids.ID) context.Context {
return context.WithValue(ctx, scopeKey{}, scope{OrganizationID: org, BusinessUnitID: bu})
}
// requestContext returns the context of a request authenticated for one tenant.
func requestContext() context.Context {
return withTenant(context.Background(), "org_acme", "bu_dallas")
}
func main() {
ctx, db := requestContext(), appDB
s := fleetdb.Shipments
n, err := s.Where(s.SearchVector.Match("reefer")).Timeout(2*time.Second).Count(ctx, db)
if errors.Is(err, context.DeadlineExceeded) {
fmt.Println("search took too long")
} else if err != nil {
log.Fatal(err)
}
fmt.Println(n)
}
Output:
func (*Query[T]) ToSQL ¶
ToSQL renders the query's main statement as it would run under ctx's scope, for logging, tests and EXPLAIN. Relation loads that run as separate statements are not included.
Example ¶
package main
import (
"context"
"fmt"
"log"
"github.com/emoss08/keel"
"github.com/emoss08/keel/internal/testmodels/fleet"
"github.com/emoss08/keel/internal/testmodels/fleet/fleetdb"
"github.com/emoss08/keel/internal/testmodels/ids"
)
// appDB is the application's database, opened once at startup (see ExampleOpen).
var appDB *keel.DB
// scope is what the application's authentication middleware puts on a request context.
type scope struct {
OrganizationID ids.ID
BusinessUnitID ids.ID
SystemReason string
}
type scopeKey struct{}
func withTenant(ctx context.Context, org, bu ids.ID) context.Context {
return context.WithValue(ctx, scopeKey{}, scope{OrganizationID: org, BusinessUnitID: bu})
}
// requestContext returns the context of a request authenticated for one tenant.
func requestContext() context.Context {
return withTenant(context.Background(), "org_acme", "bu_dallas")
}
func main() {
ctx, db := requestContext(), appDB
s := fleetdb.Shipments
sql, args, err := s.
Pick(s.ID, s.ProNumber).
Where(s.Status.In(fleet.StatusNew, fleet.StatusAssigned), s.Customer().Code.Eq("ACME")).
Order(s.CreatedAt.Desc()).
Limit(20).
ToSQL(ctx, db)
if err != nil {
log.Fatal(err)
}
fmt.Println(sql)
fmt.Println(args)
}
Output:
func (*Query[T]) Update ¶
Update sets columns on every matching row and returns the number of rows changed. The autoupdate column is stamped and the version column incremented unless sets name them.
Example ¶
package main
import (
"context"
"fmt"
"log"
"time"
"github.com/emoss08/keel"
"github.com/emoss08/keel/internal/testmodels/fleet"
"github.com/emoss08/keel/internal/testmodels/fleet/fleetdb"
"github.com/emoss08/keel/internal/testmodels/ids"
)
// appDB is the application's database, opened once at startup (see ExampleOpen).
var appDB *keel.DB
// scope is what the application's authentication middleware puts on a request context.
type scope struct {
OrganizationID ids.ID
BusinessUnitID ids.ID
SystemReason string
}
type scopeKey struct{}
func withTenant(ctx context.Context, org, bu ids.ID) context.Context {
return context.WithValue(ctx, scopeKey{}, scope{OrganizationID: org, BusinessUnitID: bu})
}
// requestContext returns the context of a request authenticated for one tenant.
func requestContext() context.Context {
return withTenant(context.Background(), "org_acme", "bu_dallas")
}
func main() {
ctx, db := requestContext(), appDB
s := fleetdb.Shipments
now := time.Now().Unix()
n, err := s.
Where(s.Status.Eq(fleet.StatusNew), s.Customer().Code.Eq("ACME")).
Update(ctx, db, s.Status.To(fleet.StatusCanceled), s.CanceledAt.To(now), s.Priority.Increment(1))
if err != nil {
log.Fatal(err)
}
fmt.Println(n, "shipments canceled")
}
Output:
func (*Query[T]) UpdateReturning ¶
UpdateReturning sets columns on every matching row, as Update does, and returns the updated rows read in the same statement (WITH keel_upd AS (UPDATE … RETURNING …) SELECT …): their values after the update, readonly columns included, with the autoupdate stamp and the incremented version. The query's Load paths are read too, to-one relations joined into the statement and to-many relations loaded after it, both as of the statement's snapshot (before the update); Order orders the rows; Pick and Omit narrow the columns read back (the primary key and tenant columns are always read, every default column when the table has update hooks, which receive the same rows). The update's tenant predicate and interceptors choose the rows; read interceptors do not apply to them. Limit, Offset, Lock, Group and DistinctOn are refused.
func (*Query[T]) Window ¶
Window declares a named window (WINDOW name AS (…)) that window functions of the query use through NamedWindow. name must match [a-z_][a-z0-9_]*.
Example ¶
package main
import (
"context"
"fmt"
"log"
"github.com/emoss08/keel"
"github.com/emoss08/keel/internal/testmodels/fleet"
"github.com/emoss08/keel/internal/testmodels/fleet/fleetdb"
"github.com/emoss08/keel/internal/testmodels/ids"
)
// appDB is the application's database, opened once at startup (see ExampleOpen).
var appDB *keel.DB
// scope is what the application's authentication middleware puts on a request context.
type scope struct {
OrganizationID ids.ID
BusinessUnitID ids.ID
SystemReason string
}
type scopeKey struct{}
func withTenant(ctx context.Context, org, bu ids.ID) context.Context {
return context.WithValue(ctx, scopeKey{}, scope{OrganizationID: org, BusinessUnitID: bu})
}
// requestContext returns the context of a request authenticated for one tenant.
func requestContext() context.Context {
return withTenant(context.Background(), "org_acme", "bu_dallas")
}
func main() {
ctx, db := requestContext(), appDB
s := fleetdb.Shipments
type numbered struct {
Pro string
N int64
Running fleet.Money
Tile int32
}
rows, err := keel.Into[numbered](ctx, db, s.
Pick(s.ProNumber,
keel.RowNumber().OverWindow(keel.NamedWindow("per_customer")),
keel.Sum(s.Total).OverWindow(keel.NamedWindow("per_customer").
Rows(keel.UnboundedPreceding, keel.CurrentRow)),
keel.Ntile(4).OverWindow(keel.OrderBy(s.Total.Desc()))).
Window("per_customer", keel.PartitionBy(s.CustomerID).OrderBy(s.CreatedAt.Asc())).
Limit(500))
if err != nil {
log.Fatal(err)
}
fmt.Println(len(rows))
}
Output:
type QueryEvent ¶
type QueryEvent struct {
Op string
Table string
SQL string
Args int
Values []any
Replica string
Tx bool
Start time.Time
Duration time.Duration
Rows int64
Err error
}
QueryEvent describes one statement. SQL never contains values; Args is the number of bound parameters, and Values holds them only when Config.ObserveArgs is set. Replica names the replica the statement ran on (ReplicaStats.Name), and is empty for the primary. Tx reports that the statement ran in an explicit transaction (Write, Read or Tx), not an implicit one. Err is the error the terminal returns: a constraint violation is already a *ConstraintError.
type RawExpr ¶
type RawExpr struct {
// contains filtered or unexported fields
}
RawExpr is the result of Raw. It is both an expression and a predicate.
func Raw ¶
Raw is SQL text that keel does not understand. Each ? in sql is replaced by the next argument: an Expr (a column, a subquery) is written in place with its alias resolved, any other value is bound as a parameter. Use ?? for a literal question mark.
Example ¶
package main
import (
"context"
"fmt"
"log"
"github.com/emoss08/keel"
"github.com/emoss08/keel/internal/testmodels/fleet/fleetdb"
"github.com/emoss08/keel/internal/testmodels/ids"
)
// appDB is the application's database, opened once at startup (see ExampleOpen).
var appDB *keel.DB
// scope is what the application's authentication middleware puts on a request context.
type scope struct {
OrganizationID ids.ID
BusinessUnitID ids.ID
SystemReason string
}
type scopeKey struct{}
func withTenant(ctx context.Context, org, bu ids.ID) context.Context {
return context.WithValue(ctx, scopeKey{}, scope{OrganizationID: org, BusinessUnitID: bu})
}
// requestContext returns the context of a request authenticated for one tenant.
func requestContext() context.Context {
return withTenant(context.Background(), "org_acme", "bu_dallas")
}
func main() {
ctx, db := requestContext(), appDB
s := fleetdb.Shipments
n, err := s.Where(
keel.Raw("length(?) > ?", s.ProNumber, 8),
keel.Raw("? ?? 'trailer'", s.Attributes),
).Count(ctx, db)
if err != nil {
log.Fatal(err)
}
fmt.Println(n)
}
Output:
type ReadFilter ¶
ReadFilter returns the predicates every reference to table t in a read statement must also satisfy, for row-level rules that sit beside tenancy: "own records only", "this region's customers". The predicates are built over the table's own columns, from a generated handle (shipmentdb.Shipments.OwnerID.Eq(id)) or a dynamic query on the table (keel.DynFrom(t).Path("owner_id").Eq(id)); keel resolves those columns to each reference it filters. A predicate may also test related rows with an EXISTS (DynFrom(t).Rel(…).Any), but it cannot join a relation.
type Ref ¶
Ref is one reference to a table inside statements. Generated code builds the base Ref of every model statically; relation paths and aliases derive further Refs on first use.
type Registry ¶
type Registry struct {
// contains filtered or unexported fields
}
Registry finds an application's tables by name, for code that chooses them at run time: report compilers, data tables with user-chosen columns, generic exports. Build it once from the generated tables (the <Model>Schema functions of the generated packages) and share it; it is read-only after NewRegistry.
Example ¶
package main
import (
"context"
"fmt"
"log"
"github.com/emoss08/keel"
"github.com/emoss08/keel/internal/testmodels/fleet/fleetdb"
"github.com/emoss08/keel/internal/testmodels/ids"
)
// appDB is the application's database, opened once at startup (see ExampleOpen).
var appDB *keel.DB
// scope is what the application's authentication middleware puts on a request context.
type scope struct {
OrganizationID ids.ID
BusinessUnitID ids.ID
SystemReason string
}
type scopeKey struct{}
func withTenant(ctx context.Context, org, bu ids.ID) context.Context {
return context.WithValue(ctx, scopeKey{}, scope{OrganizationID: org, BusinessUnitID: bu})
}
// requestContext returns the context of a request authenticated for one tenant.
func requestContext() context.Context {
return withTenant(context.Background(), "org_acme", "bu_dallas")
}
// reportTables is the registry of the tables reports can start from.
var reportTables, _ = keel.NewRegistry(fleetdb.ShipmentsSchema(), fleetdb.CustomersSchema(),
fleetdb.MovesSchema(), fleetdb.StopsSchema(), fleetdb.TagsSchema(), fleetdb.LocationsSchema())
func main() {
ctx, db := requestContext(), appDB
search := "acme"
q := reportTables.Query("shipments")
pro := q.Path("pro_number")
customer := q.Path("customer", "name")
q.Select(pro, customer, q.Path("status"), q.Path("total")).
Where(q.Path("status").In([]string{"New", "Assigned"}), customer.ContainsFold(search)).
Order(pro.Desc()).
Limit(100)
res, err := q.Run(ctx, db)
if err != nil {
log.Fatal(err)
}
total := res.Column("total")
for i := range res.Rows {
fmt.Println(res.Columns[0].String[i], res.Column("customer.name").String[i], total.Numeric[i])
}
}
Output:
func NewRegistry ¶
NewRegistry indexes tables by their SQL name. A table listed twice is an error.
func (*Registry) Query ¶
Query starts a dynamic query over the table named name. An unknown name makes a query whose terminals fail.
type Repeat ¶
type Repeat struct {
SQL string
Count int
// CallSites are the distinct places (file:line) outside keel and the generated packages that
// sent the text again after its first round trip, at most maxCallSites of them. The first run
// is not captured: keel records a stack only once a text repeats, so a budget costs nothing
// for statements that do not.
CallSites []string
}
Repeat is a statement text sent in more than one round trip under a Budget: the shape of an N+1 read when the count follows the size of a result.
type ReplicaConfig ¶
type ReplicaConfig struct {
// DSN is the replica's connection string. Pool, when set, is used instead; keel applies the
// same query mode, statement cache and AfterConnect setup as on the primary.
DSN string
Pool *pgxpool.Config
// Weight is the replica's capacity relative to the others: at equal load a replica of weight
// 2 takes twice the reads of one of weight 1. Defaults to 1.
Weight int
}
ReplicaConfig is one streaming replica of the primary.
type ReplicaStats ¶
type ReplicaStats struct {
// Name is host:port/database.
Name string
Weight int
// Healthy reports that the last check reached the replica and found it in recovery.
Healthy bool
// Lag bounds how far the replica was behind the primary at the last check: it had replayed
// everything the primary had written Lag before the check.
Lag time.Duration
// Position is the WAL position the replica had replayed at the last check.
Position Position
CheckedAt time.Time
// InFlight counts the terminals and transactions running on the replica now.
InFlight int64
// Reads counts the terminals and transactions routed to the replica.
Reads uint64
// Err is the error of the last check, if it failed or found the server not in recovery.
Err error
}
ReplicaStats is the state of one replica as the lag tracker last saw it, with its routing counters.
type Row ¶
type Row struct{}
Row is the row type of a query over a CTE or a compound query; read such queries with Into.
type RowCount ¶
type RowCount struct {
// N is the number of rows: counted when Exact is set, else the planner's estimate, never less
// than the limit plus one.
N int64
// Exact reports that N was counted: no more rows matched than the limit.
Exact bool
}
RowCount is a number of matching rows that is exact up to a limit and the planner's estimate above it, as CountEstimate returns it.
type RowsTable ¶
type RowsTable struct {
// contains filtered or unexported fields
}
RowsTable is a set of rows computed by expressions, usually set-returning functions such as unnest or generate_series, joined laterally so the expressions can read the row they join: LEFT JOIN LATERAL (SELECT unnest(r.operations) AS "op") op ON TRUE. Build it with Rows and join it with JoinLateral or LeftJoinLateral.
func Rows ¶
Rows declares a lateral source named name whose columns are the values of exprs, one column per expression in order. Several set-returning functions zip, row i of each making row i.
op := keel.Rows("op", []string{"op"}, keel.DynFunc("unnest", rp.Operations))
roles.LeftJoinLateral(op).Pick(r.ID, op.Col("op"))
type SearchMode ¶
type SearchMode uint8
SearchMode is how a search string becomes a tsquery.
const ( // WebSearch reads the string as a web search engine does: words are ANDed, "quoted words" // match as a phrase, or separates alternatives and -word excludes a word // (websearch_to_tsquery). It is the default. WebSearch SearchMode = iota + 1 // Plain ANDs the string's words, ignoring punctuation and operators (plainto_tsquery). Plain // Phrase matches the string's words next to each other, in order (phraseto_tsquery). Phrase // Prefix matches each word of the string as the start of a word, so a word typed in part // matches: "dal ree" is to_tsquery('dal':* & 'ree':*). Prefix // AnyWord matches rows holding any of the string's words: to_tsquery('dallas' | 'reefer'). AnyWord // AnyPrefix matches rows holding a word that starts with any of the string's words. AnyPrefix // TSQuery reads the string as tsquery syntax (to_tsquery): ('dallas':* & 'reefer') | 'tx'. // Build it from words the code has cleaned, never from user input: a malformed string fails the // statement. TSQuery // AnyWebSearch reads the string as WebSearch does but matches rows holding any of its words or // "quoted phrases", without any -word: "dallas reefer -frozen" finds rows with either word and // without "frozen". AnyWebSearch )
type SearchOption ¶
type SearchOption func(*search)
SearchOption shapes a full-text search: the configurations and modes its query is built with, and the normalization of a rank.
func RankNormalization ¶
func RankNormalization(mask int) SearchOption
RankNormalization sets the normalization of Rank and RankCD, the bit mask ts_rank takes as its third argument (1 divides the rank by 1 + the logarithm of the document length, 32 scales it to rank/(rank+1), …). Match ignores it.
func SearchAlso ¶
func SearchAlso(mode SearchMode, text string) SearchOption
SearchAlso ORs another term into the query: text read with mode, under each of the search's configurations. A search of the words typed so far can add their prefixes, ANDed, alongside a WebSearch of the whole string:
m.SearchVector.Match(raw, keel.SearchAlso(keel.TSQuery, "'dal':* & 'ree':*"))
func SearchConfig ¶
func SearchConfig(configs ...string) SearchOption
SearchConfig builds the query with each of the text search configurations named and ORs them: SearchConfig("simple", "english") finds a word whether the column's vector stemmed it or not. Names are configuration identifiers ([a-z_][a-z0-9_]*, optionally schema-qualified); Postgres refuses one it does not have when the statement runs. Without SearchConfig the column's tsvector=<config> tag option applies, else the database's default_text_search_config.
func SearchModes ¶
func SearchModes(modes ...SearchMode) SearchOption
SearchModes builds the query with each of modes and ORs them: SearchModes(keel.WebSearch, keel.Prefix) finds whole words and the words being typed. Without it the mode is WebSearch.
type Shape ¶
type Shape struct {
SQL string `json:"sql"`
Op string `json:"op"`
Table string `json:"table"`
Args int `json:"args"`
}
Shape is one distinct statement text keel sent: the text (which never contains values), its operation and table as QueryEvent reports them, and the number of parameters it binds. RecordShapes writes shapes as JSON lines, {"sql": "...", "op": "...", "table": "...", "args": n}, and keel plans reads them back to explain every one.
type SharedCache ¶
type SharedCache interface {
Get(ctx context.Context, key string) ([]byte, bool, error)
Set(ctx context.Context, key string, value []byte, ttl time.Duration) error
}
SharedCache is a store every process shares (CacheConfig.Shared), such as Redis: encoded entries by key, each with a time to live. keel/cache/rediscache adapts a Redis client, and the stores of keel/analytics/cache fit as they are.
type SlotHolder ¶
SlotHolder is work holding a tenant slot: how long it has held it and the stack that took it.
type SlowWait ¶
type SlowWait struct {
Kind WaitKind
Tenant string
Waited time.Duration
Stack []string
Limit int
Waiters int
Holders []SlotHolder
Pool PoolState
}
SlowWait describes a wait that crossed SlowWaitConfig.After. Stack is the waiting caller's, from its first frame outside keel, as "function file:line". For a tenant slot, Limit is the tenant's bound, Waiters counts the work waiting for its slots, this one included, and Holders lists the work holding them: how long each has held its slot and the stack that took it. For a connection, Pool is the pool's state when the report was made.
type SlowWaitConfig ¶
SlowWaitConfig reports work that waits longer than After for a tenant slot (Config.TenantConcurrency), a pool connection, or another goroutine's read of the result cache entry it needs, so a starved tenant or an exhausted pool is visible while it happens rather than as requests that hang. Report is called once per wait that crosses After, from the waiting goroutine or a timer's; it must return quickly and must not run statements on the DB. Both fields must be set for keel to watch: it then records the call stack of every tenant slot it hands out and of every connection wait, about a microsecond each.
type StringArrayCase ¶
type StringArrayCase struct {
// contains filtered or unexported fields
}
StringArrayCase builds a CASE whose result is text[]. Start it with CaseStringArray.
func CaseStringArray ¶
func CaseStringArray() StringArrayCase
CaseStringArray starts a CASE whose result is text[].
func (StringArrayCase) Else ¶
func (b StringArrayCase) Else(v StringArrayValue) StringArrayExpr
Else ends the CASE with ELSE v.
func (StringArrayCase) End ¶
func (b StringArrayCase) End() StringArrayExpr
End ends the CASE without ELSE.
func (StringArrayCase) When ¶
func (b StringArrayCase) When(cond Pred, v StringArrayValue) StringArrayCase
When adds WHEN cond THEN v.
type StringArrayCol ¶
type StringArrayCol struct{ Col }
StringArrayCol is a text[] or varchar[] column.
func (StringArrayCol) Append ¶
func (c StringArrayCol) Append(v string) Assignment
Append sets the column to itself with v appended: array_append(col, v). A NULL array becomes {v}.
func (StringArrayCol) At ¶
func (c StringArrayCol) At(i int) StringExpr
At is (col)[i], the i-th element from 1.
func (StringArrayCol) ContainedBy ¶
func (c StringArrayCol) ContainedBy(vs ...string) Pred
func (StringArrayCol) ContainedByExpr ¶
func (c StringArrayCol) ContainedByExpr(o StringArrayValue) Pred
ContainedByExpr is (col <@ o).
func (StringArrayCol) ContainsAll ¶
func (c StringArrayCol) ContainsAll(vs ...string) Pred
func (StringArrayCol) ContainsAllExpr ¶
func (c StringArrayCol) ContainsAllExpr(o StringArrayValue) Pred
ContainsAllExpr is (col @> o).
func (StringArrayCol) Has ¶
func (c StringArrayCol) Has(v string) Pred
Has reports whether the array holds v.
func (StringArrayCol) Len ¶
func (c StringArrayCol) Len() Int32Expr
Len is cardinality(col): the number of elements, 0 for an empty array and NULL for NULL.
func (StringArrayCol) Overlaps ¶
func (c StringArrayCol) Overlaps(vs ...string) Pred
func (StringArrayCol) OverlapsExpr ¶
func (c StringArrayCol) OverlapsExpr(o StringArrayValue) Pred
OverlapsExpr is (col && o).
func (StringArrayCol) Remove ¶
func (c StringArrayCol) Remove(v string) Assignment
Remove sets the column to itself without the elements equal to v: array_remove(col, v).
func (StringArrayCol) To ¶
func (c StringArrayCol) To(vs []string) Assignment
func (StringArrayCol) Unnest ¶
func (c StringArrayCol) Unnest() StringExpr
Unnest is the set-returning unnest(col): one row per element, for keel.Rows.
func (StringArrayCol) Without ¶
func (c StringArrayCol) Without(v StringValue) StringArrayExpr
Without is array_remove(col, v), an expression; Remove is the assignment.
type StringArrayExpr ¶
type StringArrayExpr struct {
// contains filtered or unexported fields
}
StringArrayExpr is a computed text[] value. Its methods are those of every array expression: At, Len (cardinality), Unnest, Without (array_remove), ContainsAllExpr (@>), ContainedByExpr (<@), OverlapsExpr (&&), Coalesce, IsNull, IsNotNull.
func ArrayOfString ¶
func ArrayOfString(es ...StringValue) StringArrayExpr
ArrayOfString is ARRAY[es…] of text values.
func (StringArrayExpr) At ¶
func (e StringArrayExpr) At(i int) StringExpr
At is (e)[i], the i-th element from 1.
func (StringArrayExpr) Coalesce ¶
func (e StringArrayExpr) Coalesce(o StringArrayValue) StringArrayExpr
Coalesce is coalesce(e, o).
func (StringArrayExpr) ContainedByExpr ¶
func (e StringArrayExpr) ContainedByExpr(o StringArrayValue) Pred
ContainedByExpr is (e <@ o): o holds every element of e.
func (StringArrayExpr) ContainsAllExpr ¶
func (e StringArrayExpr) ContainsAllExpr(o StringArrayValue) Pred
ContainsAllExpr is (e @> o): e holds every element of o.
func (StringArrayExpr) IsNotNull ¶
func (e StringArrayExpr) IsNotNull() Pred
IsNotNull is e IS NOT NULL.
func (StringArrayExpr) NullIf ¶
func (e StringArrayExpr) NullIf(o StringArrayValue) StringArrayExpr
NullIf is nullif(e, o): NULL where e equals o, as NullIf(keel.TextArray(nil)) turns an empty array into NULL.
func (StringArrayExpr) OverlapsExpr ¶
func (e StringArrayExpr) OverlapsExpr(o StringArrayValue) Pred
OverlapsExpr is (e && o): the arrays share an element.
func (StringArrayExpr) Unnest ¶
func (e StringArrayExpr) Unnest() StringExpr
Unnest is the set-returning unnest(e), for keel.Rows.
func (StringArrayExpr) Without ¶
func (e StringArrayExpr) Without(v StringValue) StringArrayExpr
Without is array_remove(e, v).
type StringArrayValue ¶
type StringArrayValue interface {
Expr
// contains filtered or unexported methods
}
StringArrayValue is a text[] value: a StringArrayCol or a StringArrayExpr.
type StringCase ¶
type StringCase struct {
// contains filtered or unexported fields
}
StringCase builds a CASE whose result is text. Start it with CaseString.
func (StringCase) Else ¶
func (b StringCase) Else(v StringValue) StringExpr
Else ends the CASE with ELSE v.
func (StringCase) End ¶
func (b StringCase) End() StringExpr
End ends the CASE without ELSE: rows no WHEN matches are NULL.
func (StringCase) When ¶
func (b StringCase) When(cond Pred, v StringValue) StringCase
When adds WHEN cond THEN v.
type StringCol ¶
type StringCol struct{ Col }
StringCol is a text, varchar, enum or other string-valued column.
func (StringCol) Cat ¶
func (c StringCol) Cat(vs ...StringValue) StringExpr
Cat is (col || v), each further value appended the same way.
func (StringCol) Coalesce ¶
func (c StringCol) Coalesce(vs ...any) StringExpr
Coalesce is coalesce(col, vs…), typed as text: each v is a value bound as a parameter (sharing the placeholder of an equal value compared with the column) or an expression.
func (StringCol) Collate ¶
func (c StringCol) Collate(name string) StringExpr
Collate is (col COLLATE "name").
func (StringCol) Contains ¶
Contains reports whether the column contains s. % and _ in s match themselves.
func (StringCol) ContainsFold ¶
ContainsFold is Contains ignoring case.
func (StringCol) Greatest ¶
func (c StringCol) Greatest(others ...StringValue) StringExpr
Greatest is greatest(col, others…).
func (StringCol) HasPrefixFold ¶
HasPrefixFold is HasPrefix ignoring case.
func (StringCol) ILikeExpr ¶
func (c StringCol) ILikeExpr(pattern StringValue) Pred
ILikeExpr is LikeExpr, ignoring case.
func (StringCol) InArray ¶
func (c StringCol) InArray(a StringArrayValue) Pred
InArray is (col = ANY(a)): keel.StringArrayOf(cte.Col("ids")) types a CTE's array column.
func (StringCol) IsDistinctFrom ¶
func (StringCol) IsDistinctFromExpr ¶
func (c StringCol) IsDistinctFromExpr(o StringValue) Pred
IsDistinctFromExpr is col IS DISTINCT FROM o.
func (StringCol) IsNotDistinctFrom ¶
IsNotDistinctFrom is col IS NOT DISTINCT FROM v.
func (StringCol) IsNotDistinctFromExpr ¶
func (c StringCol) IsNotDistinctFromExpr(o StringValue) Pred
IsNotDistinctFromExpr is col IS NOT DISTINCT FROM o.
func (StringCol) LTrimChars ¶
func (c StringCol) LTrimChars(chars string) StringExpr
LTrimChars is ltrim(col, chars).
func (StringCol) Least ¶
func (c StringCol) Least(others ...StringValue) StringExpr
Least is least(col, others…).
func (StringCol) LikeExpr ¶
func (c StringCol) LikeExpr(pattern StringValue) Pred
LikeExpr matches the LIKE pattern an expression computes, as col LIKE pattern; % and _ in the pattern keep their meaning, so escape user text that feeds it.
func (StringCol) NotInArray ¶
func (c StringCol) NotInArray(a StringArrayValue) Pred
NotInArray is (col <> ALL(a)).
func (StringCol) RTrimChars ¶
func (c StringCol) RTrimChars(chars string) StringExpr
RTrimChars is rtrim(col, chars).
func (StringCol) RegexpReplace ¶
func (c StringCol) RegexpReplace(pattern, replacement, flags string) StringExpr
RegexpReplace is regexp_replace(col, pattern, replacement[, flags]).
func (StringCol) Replace ¶
func (c StringCol) Replace(from, to string) StringExpr
Replace is replace(col, from, to).
func (StringCol) SplitPart ¶
func (c StringCol) SplitPart(delim string, n int) StringExpr
SplitPart is split_part(col, delim, n).
func (StringCol) SplitPartExpr ¶
func (c StringCol) SplitPartExpr(delim StringValue, n Int32Value) StringExpr
SplitPartExpr is split_part(col, delim, n) with the delimiter and field number as expressions.
func (StringCol) Substr ¶
func (c StringCol) Substr(start, count int) StringExpr
Substr is substr(col, start, count).
func (StringCol) SubstrFrom ¶
func (c StringCol) SubstrFrom(start int) StringExpr
SubstrFrom is substr(col, start).
func (StringCol) To ¶
func (c StringCol) To(v string) Assignment
func (StringCol) TrimChars ¶
func (c StringCol) TrimChars(chars string) StringExpr
TrimChars is btrim(col, chars).
type StringExpr ¶
type StringExpr struct {
// contains filtered or unexported fields
}
StringExpr is a computed text value: a string function, a jsonb path read as text, a typed view.
func CastText ¶
func CastText(e Expr) StringExpr
CastText is CAST(e AS text): an enum, a number, a uuid or a time as text.
func Concat ¶
func Concat(args ...any) StringExpr
Concat is concat(args…): the text of every argument joined, NULL arguments skipped. An argument is an expression of any type or a Go value bound under the cast its type implies.
func ConcatWS ¶
func ConcatWS(sep string, args ...any) StringExpr
ConcatWS is concat_ws(sep, args…): the non-NULL arguments joined with sep, as keel.ConcatWS(" ", w.FirstName, w.LastName) builds a full name.
func ConstText ¶
func ConstText(s string) StringExpr
ConstText writes s into the SQL as a quoted literal, its single quotes doubled. The literal is of unknown type until it meets a value, as a constant in an index definition is: coalescing a varchar column with ConstText("") resolves to varchar and matches an index on that expression. A string holding a NUL byte makes an expression that fails.
func GenRandomUUID ¶
func GenRandomUUID() StringExpr
GenRandomUUID is the text of a new random UUID: CAST(gen_random_uuid() AS text).
func StringOf ¶
func StringOf(e Expr) StringExpr
StringOf types e, an expression keel cannot type itself (a Raw, a Scalar, a DynExpr, a column of a Rows source), as text. It changes nothing in the SQL; CastText converts.
func Text ¶
func Text(s string) StringExpr
Text binds s as a text value, $n::text, where a function or a comparison between expressions takes a StringValue.
func (StringExpr) Asc ¶
func (e StringExpr) Asc() Order
func (StringExpr) Between ¶
func (e StringExpr) Between(lo, hi string) Pred
func (StringExpr) Cat ¶
func (e StringExpr) Cat(vs ...StringValue) StringExpr
Cat is (e || v), each further value appended the same way, ((e || v) || w): text joined with SQL's NULL semantics, so a NULL operand makes the whole result NULL. keel.Concat and ConcatWS skip NULL arguments instead. Pass keel.Text(s) for a Go string.
func (StringExpr) Coalesce ¶
func (e StringExpr) Coalesce(vs ...any) StringExpr
Coalesce is coalesce(e, vs…): each v a string bound as a parameter or an expression, taken in order where everything before it is NULL.
func (StringExpr) Collate ¶
func (e StringExpr) Collate(name string) StringExpr
Collate is (e COLLATE "name"): e compared and ordered by that collation, as Collate("C") orders by bytes, which a text_pattern_ops or "C" index serves. A name that is empty, longer than 63 bytes or holds a double quote or control character makes an expression that fails.
func (StringExpr) Contains ¶
func (e StringExpr) Contains(s string) Pred
func (StringExpr) ContainsFold ¶
func (e StringExpr) ContainsFold(s string) Pred
func (StringExpr) Desc ¶
func (e StringExpr) Desc() Order
func (StringExpr) Eq ¶
func (e StringExpr) Eq(v string) Pred
func (StringExpr) EqExpr ¶
func (e StringExpr) EqExpr(o StringValue) Pred
func (StringExpr) Greatest ¶
func (e StringExpr) Greatest(others ...StringValue) StringExpr
Greatest is greatest(e, others…), the largest non-NULL value.
func (StringExpr) Gt ¶
func (e StringExpr) Gt(v string) Pred
func (StringExpr) GtExpr ¶
func (e StringExpr) GtExpr(o StringValue) Pred
func (StringExpr) Gte ¶
func (e StringExpr) Gte(v string) Pred
func (StringExpr) GteExpr ¶
func (e StringExpr) GteExpr(o StringValue) Pred
func (StringExpr) HasPrefix ¶
func (e StringExpr) HasPrefix(s string) Pred
func (StringExpr) HasPrefixFold ¶
func (e StringExpr) HasPrefixFold(s string) Pred
func (StringExpr) HasSuffix ¶
func (e StringExpr) HasSuffix(s string) Pred
func (StringExpr) ILike ¶
func (e StringExpr) ILike(p string) Pred
func (StringExpr) ILikeExpr ¶
func (e StringExpr) ILikeExpr(p StringValue) Pred
func (StringExpr) In ¶
func (e StringExpr) In(vs ...string) Pred
func (StringExpr) InArray ¶
func (e StringExpr) InArray(a StringArrayValue) Pred
InArray is (e = ANY(a)): e equals an element of a.
func (StringExpr) IsDistinctFrom ¶
func (e StringExpr) IsDistinctFrom(v string) Pred
func (StringExpr) IsDistinctFromExpr ¶
func (e StringExpr) IsDistinctFromExpr(o StringValue) Pred
IsDistinctFromExpr is e IS DISTINCT FROM o.
func (StringExpr) IsNotDistinctFrom ¶
func (e StringExpr) IsNotDistinctFrom(v string) Pred
IsNotDistinctFrom is e IS NOT DISTINCT FROM v.
func (StringExpr) IsNotDistinctFromExpr ¶
func (e StringExpr) IsNotDistinctFromExpr(o StringValue) Pred
IsNotDistinctFromExpr is e IS NOT DISTINCT FROM o.
func (StringExpr) IsNotNull ¶
func (e StringExpr) IsNotNull() Pred
func (StringExpr) IsNull ¶
func (e StringExpr) IsNull() Pred
func (StringExpr) LTrim ¶
func (e StringExpr) LTrim() StringExpr
LTrim is ltrim(e): e without leading spaces.
func (StringExpr) LTrimChars ¶
func (e StringExpr) LTrimChars(chars string) StringExpr
LTrimChars is ltrim(e, chars): e without leading characters in chars (ltrim(n, '0') drops leading zeros).
func (StringExpr) Least ¶
func (e StringExpr) Least(others ...StringValue) StringExpr
Least is least(e, others…), the smallest non-NULL value.
func (StringExpr) Left ¶
func (e StringExpr) Left(n int) StringExpr
Left is left(e, n): the first n characters, or all but the last -n when n is negative.
func (StringExpr) Length ¶
func (e StringExpr) Length() Int32Expr
Length is length(e), the number of characters.
func (StringExpr) Like ¶
func (e StringExpr) Like(p string) Pred
func (StringExpr) LikeExpr ¶
func (e StringExpr) LikeExpr(p StringValue) Pred
func (StringExpr) Lt ¶
func (e StringExpr) Lt(v string) Pred
func (StringExpr) LtExpr ¶
func (e StringExpr) LtExpr(o StringValue) Pred
func (StringExpr) Lte ¶
func (e StringExpr) Lte(v string) Pred
func (StringExpr) LteExpr ¶
func (e StringExpr) LteExpr(o StringValue) Pred
func (StringExpr) Match ¶
func (e StringExpr) Match(re string) Pred
func (StringExpr) MatchFold ¶
func (e StringExpr) MatchFold(re string) Pred
func (StringExpr) Max ¶
func (e StringExpr) Max() Agg
func (StringExpr) Min ¶
func (e StringExpr) Min() Agg
func (StringExpr) Ne ¶
func (e StringExpr) Ne(v string) Pred
func (StringExpr) NeExpr ¶
func (e StringExpr) NeExpr(o StringValue) Pred
func (StringExpr) NotIn ¶
func (e StringExpr) NotIn(vs ...string) Pred
func (StringExpr) NotInArray ¶
func (e StringExpr) NotInArray(a StringArrayValue) Pred
NotInArray is (e <> ALL(a)).
func (StringExpr) NullIf ¶
func (e StringExpr) NullIf(v string) StringExpr
NullIf is nullif(e, v): NULL where e equals v, as NullIf("") turns empty strings into NULL.
func (StringExpr) RTrim ¶
func (e StringExpr) RTrim() StringExpr
RTrim is rtrim(e): e without trailing spaces.
func (StringExpr) RTrimChars ¶
func (e StringExpr) RTrimChars(chars string) StringExpr
RTrimChars is rtrim(e, chars): e without trailing characters in chars.
func (StringExpr) RegexpReplace ¶
func (e StringExpr) RegexpReplace(pattern, replacement, flags string) StringExpr
RegexpReplace is regexp_replace(e, pattern, replacement[, flags]): the first match of the POSIX pattern replaced, every match with flags "g". Empty flags leave the argument out.
func (StringExpr) Replace ¶
func (e StringExpr) Replace(from, to string) StringExpr
Replace is replace(e, from, to): every occurrence of from replaced by to.
func (StringExpr) Right ¶
func (e StringExpr) Right(n int) StringExpr
Right is right(e, n): the last n characters, or all but the first -n when n is negative.
func (StringExpr) SplitPart ¶
func (e StringExpr) SplitPart(delim string, n int) StringExpr
SplitPart is split_part(e, delim, n): the n-th field of e split at delim, from 1; a negative n counts from the end.
func (StringExpr) SplitPartExpr ¶
func (e StringExpr) SplitPartExpr(delim StringValue, n Int32Value) StringExpr
SplitPartExpr is split_part(e, delim, n) with the delimiter and field number as expressions: keel.ConstText and keel.ConstInt32 write them into the statement, so it matches an expression index such as lower(split_part(to_address, '@', 2)).
func (StringExpr) Substr ¶
func (e StringExpr) Substr(start, count int) StringExpr
Substr is substr(e, start, count): count characters from position start, from 1.
func (StringExpr) SubstrFrom ¶
func (e StringExpr) SubstrFrom(start int) StringExpr
SubstrFrom is substr(e, start): the characters from position start, from 1, to the end.
func (StringExpr) Trim ¶
func (e StringExpr) Trim() StringExpr
Trim is btrim(e): e without leading and trailing spaces (SQL trim(e)).
func (StringExpr) TrimChars ¶
func (e StringExpr) TrimChars(chars string) StringExpr
TrimChars is btrim(e, chars): e without leading and trailing characters in chars.
type StringValue ¶
type StringValue interface {
Expr
// contains filtered or unexported methods
}
StringValue is a text value: a string column (a generated enum or id column included) or a StringExpr.
type TSVectorCol ¶
type TSVectorCol struct{ Col }
TSVectorCol is a tsvector column. Match, Rank and RankCD search it.
func (TSVectorCol) Match ¶
func (c TSVectorCol) Match(query string, opts ...SearchOption) Pred
Match is col @@ query: the rows whose vector matches the search string, built by websearch_to_tsquery with the column's configuration unless opts say otherwise. The string is a bound parameter, so any user input is safe.
s.SearchVector.Match(q) // websearch_to_tsquery
s.SearchVector.Match(q, keel.SearchConfig("simple", "english")) // either configuration
s.SearchVector.Match(q, keel.SearchModes(keel.WebSearch, keel.Prefix)) // words or prefixes
Example ¶
package main
import (
"context"
"fmt"
"log"
"github.com/emoss08/keel"
"github.com/emoss08/keel/internal/testmodels/fleet/fleetdb"
"github.com/emoss08/keel/internal/testmodels/ids"
)
// appDB is the application's database, opened once at startup (see ExampleOpen).
var appDB *keel.DB
// scope is what the application's authentication middleware puts on a request context.
type scope struct {
OrganizationID ids.ID
BusinessUnitID ids.ID
SystemReason string
}
type scopeKey struct{}
func withTenant(ctx context.Context, org, bu ids.ID) context.Context {
return context.WithValue(ctx, scopeKey{}, scope{OrganizationID: org, BusinessUnitID: bu})
}
// requestContext returns the context of a request authenticated for one tenant.
func requestContext() context.Context {
return withTenant(context.Background(), "org_acme", "bu_dallas")
}
func main() {
ctx, db := requestContext(), appDB
n := fleetdb.Notes
q := "dallas ree"
rows, err := n.
Compute(n.Rank, n.SearchVector.Rank(q)).
Where(n.SearchVector.Match(q, keel.SearchConfig("simple", "english"),
keel.SearchModes(keel.WebSearch, keel.Prefix))).
Order(n.Rank.Desc()).
Limit(20).
List(ctx, db)
if err != nil {
log.Fatal(err)
}
for _, r := range rows {
fmt.Println(r.Body, r.Rank)
}
}
Output:
func (TSVectorCol) Rank ¶
func (c TSVectorCol) Rank(query string, opts ...SearchOption) Agg
Rank is ts_rank(col, query): how well the row's vector matches the search string, a real number for Order and for a computed field. opts build the query as for Match.
func (TSVectorCol) RankCD ¶
func (c TSVectorCol) RankCD(query string, opts ...SearchOption) Agg
RankCD is ts_rank_cd(col, query): the cover density rank, which also weighs how close the matching words are to each other.
type Table ¶
type Table struct{}
Table marks a struct as a keel model. Its `keel` tag names the table:
type Shipment struct {
keel.Table `keel:"shipments"`
...
}
type TimeArrayCase ¶
type TimeArrayCase struct {
// contains filtered or unexported fields
}
TimeArrayCase builds a CASE whose result is timestamptz[]. Start it with CaseTimeArray.
func CaseTimeArray ¶
func CaseTimeArray() TimeArrayCase
CaseTimeArray starts a CASE whose result is timestamptz[].
func (TimeArrayCase) Else ¶
func (b TimeArrayCase) Else(v TimeArrayValue) TimeArrayExpr
Else ends the CASE with ELSE v.
func (TimeArrayCase) End ¶
func (b TimeArrayCase) End() TimeArrayExpr
End ends the CASE without ELSE.
func (TimeArrayCase) When ¶
func (b TimeArrayCase) When(cond Pred, v TimeArrayValue) TimeArrayCase
When adds WHEN cond THEN v.
type TimeArrayExpr ¶
type TimeArrayExpr struct {
// contains filtered or unexported fields
}
TimeArrayExpr is a computed timestamptz[] value, with the methods of StringArrayExpr.
func ArrayOfTime ¶
func ArrayOfTime(es ...TimeValue) TimeArrayExpr
ArrayOfTime is ARRAY[es…] of times.
func TimeArray ¶
func TimeArray(vs []time.Time) TimeArrayExpr
TimeArray binds vs as $n::timestamptz[].
func (TimeArrayExpr) At ¶
func (e TimeArrayExpr) At(i int) TimeExpr
At is (e)[i], the i-th element from 1.
func (TimeArrayExpr) Coalesce ¶
func (e TimeArrayExpr) Coalesce(o TimeArrayValue) TimeArrayExpr
Coalesce is coalesce(e, o).
func (TimeArrayExpr) ContainedByExpr ¶
func (e TimeArrayExpr) ContainedByExpr(o TimeArrayValue) Pred
ContainedByExpr is (e <@ o).
func (TimeArrayExpr) ContainsAllExpr ¶
func (e TimeArrayExpr) ContainsAllExpr(o TimeArrayValue) Pred
ContainsAllExpr is (e @> o).
func (TimeArrayExpr) IsNotNull ¶
func (e TimeArrayExpr) IsNotNull() Pred
IsNotNull is e IS NOT NULL.
func (TimeArrayExpr) NullIf ¶
func (e TimeArrayExpr) NullIf(o TimeArrayValue) TimeArrayExpr
NullIf is nullif(e, o): NULL where e equals o, as NullIf(keel.TimeArray(nil)) turns an empty array into NULL.
func (TimeArrayExpr) OverlapsExpr ¶
func (e TimeArrayExpr) OverlapsExpr(o TimeArrayValue) Pred
OverlapsExpr is (e && o).
func (TimeArrayExpr) Unnest ¶
func (e TimeArrayExpr) Unnest() TimeExpr
Unnest is the set-returning unnest(e), for keel.Rows.
func (TimeArrayExpr) Without ¶
func (e TimeArrayExpr) Without(v TimeValue) TimeArrayExpr
Without is array_remove(e, v).
type TimeArrayValue ¶
type TimeArrayValue interface {
Expr
// contains filtered or unexported methods
}
TimeArrayValue is a timestamptz[] value: a TimeArrayExpr.
type TimeCase ¶
type TimeCase struct {
// contains filtered or unexported fields
}
TimeCase builds a CASE whose result is a time. Start it with CaseTime.
type TimeCol ¶
type TimeCol struct{ Col }
TimeCol is a timestamp, timestamptz or date column.
func (TimeCol) AtTimeZone ¶
func (c TimeCol) AtTimeZone(zone StringValue) TimeExpr
AtTimeZone is timezone(zone, col).
func (TimeCol) IsDistinctFrom ¶
IsDistinctFrom is col IS DISTINCT FROM v.
func (TimeCol) IsDistinctFromExpr ¶
IsDistinctFromExpr is col IS DISTINCT FROM o.
func (TimeCol) IsNotDistinctFrom ¶
IsNotDistinctFrom is col IS NOT DISTINCT FROM v.
func (TimeCol) IsNotDistinctFromExpr ¶
IsNotDistinctFromExpr is col IS NOT DISTINCT FROM o.
func (TimeCol) NotInArray ¶
func (c TimeCol) NotInArray(a TimeArrayValue) Pred
NotInArray is (col <> ALL(a)).
func (TimeCol) ToChar ¶
func (c TimeCol) ToChar(format string) StringExpr
ToChar is to_char(col, format).
type TimeExpr ¶
type TimeExpr struct {
// contains filtered or unexported fields
}
TimeExpr is a computed time: now(), to_timestamp of epoch seconds, a time in a zone, a truncated time.
func CastTime ¶
CastTime is CAST(e AS timestamptz): a timestamp, a date or time text as a timestamptz.
func DateTrunc ¶
DateTrunc is date_trunc('field', t): t truncated to the start of its field ("day", "month", "year", …). An unknown field makes an expression that fails.
func GenerateTimeSeries ¶
GenerateTimeSeries is generate_series(start, stop, step): the times from start to stop every step, an interval such as "1 day" bound as $n::interval.
func ToTimestamp ¶
func ToTimestamp(seconds NumberValue) TimeExpr
ToTimestamp is to_timestamp(seconds): Unix seconds as a timestamptz.
func (TimeExpr) Add ¶
func (e TimeExpr) Add(i IntervalValue) TimeExpr
Add is (e + i): the time an interval later.
func (TimeExpr) AtTimeZone ¶
func (e TimeExpr) AtTimeZone(zone StringValue) TimeExpr
AtTimeZone is timezone(zone, e): a timestamptz as the local time in zone, a timestamp without time zone read as a time in zone. Pass keel.Text(name) for a zone name from Go.
func (TimeExpr) Coalesce ¶
Coalesce is coalesce(e, vs…): each v a time bound as a parameter or an expression, taken in order where everything before it is NULL.
func (TimeExpr) IsDistinctFrom ¶
IsDistinctFrom is e IS DISTINCT FROM v.
func (TimeExpr) IsDistinctFromExpr ¶
IsDistinctFromExpr is e IS DISTINCT FROM o.
func (TimeExpr) IsNotDistinctFrom ¶
IsNotDistinctFrom is e IS NOT DISTINCT FROM v.
func (TimeExpr) IsNotDistinctFromExpr ¶
IsNotDistinctFromExpr is e IS NOT DISTINCT FROM o.
func (TimeExpr) NotInArray ¶
func (e TimeExpr) NotInArray(a TimeArrayValue) Pred
NotInArray is (e <> ALL(a)).
func (TimeExpr) Since ¶
func (e TimeExpr) Since(u TimeValue) IntervalExpr
Since is (e - u): the interval from u to e, negative when u is later.
func (TimeExpr) Sub ¶
func (e TimeExpr) Sub(i IntervalValue) TimeExpr
Sub is (e - i): the time an interval earlier.
func (TimeExpr) ToChar ¶
func (e TimeExpr) ToChar(format string) StringExpr
ToChar is to_char(e, format): e formatted by a Postgres template ("YYYY-MM-DD").
type TimeValue ¶
type TimeValue interface {
Expr
// contains filtered or unexported methods
}
TimeValue is a time: a TimeCol or a TimeExpr.
type TupleExpr ¶
type TupleExpr struct {
// contains filtered or unexported fields
}
TupleExpr is a row value, ROW(a, b, …), built by keel.Tuple. Rows compare element by element in order, so keel.Tuple(e.OccurredAt, e.ID).Gt(keel.Tuple(after.At, after.ID)) is the keyset condition of a page ordered by occurred_at, id ascending, which an index on (occurred_at, id) serves as one range scan; a Desc page uses Lt.
func Tuple ¶
Tuple is the row value ROW(vs…): each v is an expression written in place, nil as NULL, or a Go value bound as a parameter whose type Postgres takes from the element it is compared with. A row needs at least two values; one with fewer makes a comparison that fails.
func (TupleExpr) InUnnest ¶
InUnnest is (w IN (SELECT * FROM unnest(arrays…))): w equals one of the rows the arrays zip into, element i of each array making row i. Pass typed array literals (keel.TextArray(keys), keel.Int64Array(ns)), each bound as one parameter, so the statement keeps one shape whatever the number of rows.
func (TupleExpr) IsDistinctFrom ¶
IsDistinctFrom is (w IS DISTINCT FROM o): some element differs, NULL counting as a value, as an upsert's UpdateWhere skips a row whose stored values equal the proposed ones.
func (TupleExpr) IsNotDistinctFrom ¶
IsNotDistinctFrom is (w IS NOT DISTINCT FROM o).
type TxOption ¶
type TxOption interface {
// contains filtered or unexported methods
}
TxOption configures Read and Write. The isolation levels (ReadCommitted, RepeatableRead, Serializable) are options themselves: keel.Read(ctx, db, fn, keel.RepeatableRead).
func Detached ¶
func Detached() TxOption
Detached runs Read or Write in a new transaction on a connection of its own even when the context carries a transaction, which it then neither joins nor waits for: it commits or rolls back on its own, and its work is not undone when the outer transaction rolls back. It begins with the context's scope, so Config.OnBegin sees that scope, and for fn's duration it hides the outer transaction: statements, Savepoint, LockKey and AfterCommit inside fn use the detached transaction, and its AfterCommit callbacks run when it commits, with the caller's context. Use it for work under another scope than the outer transaction's (a system-scoped read inside a tenant's transaction) and for work that must commit whatever the caller does (allocating a number from a sequence table).
It sees what other transactions committed, not the outer transaction's uncommitted writes, and holds a second pool connection while the outer one holds its first: size the pool for it, and do not wait inside it on locks the outer transaction holds, which never come free. Outside a transaction it is a plain Read or Write.
Example ¶
package main
import (
"context"
"fmt"
"log"
"time"
"github.com/emoss08/keel"
"github.com/emoss08/keel/internal/testmodels/fleet"
"github.com/emoss08/keel/internal/testmodels/fleet/fleetdb"
"github.com/emoss08/keel/internal/testmodels/ids"
)
// appDB is the application's database, opened once at startup (see ExampleOpen).
var appDB *keel.DB
// scope is what the application's authentication middleware puts on a request context.
type scope struct {
OrganizationID ids.ID
BusinessUnitID ids.ID
SystemReason string
}
type scopeKey struct{}
func withTenant(ctx context.Context, org, bu ids.ID) context.Context {
return context.WithValue(ctx, scopeKey{}, scope{OrganizationID: org, BusinessUnitID: bu})
}
func withSystem(ctx context.Context, reason string) context.Context {
return context.WithValue(ctx, scopeKey{}, scope{SystemReason: reason})
}
// requestContext returns the context of a request authenticated for one tenant.
func requestContext() context.Context {
return withTenant(context.Background(), "org_acme", "bu_dallas")
}
func main() {
ctx, db := requestContext(), appDB
s := fleetdb.Shipments
err := keel.Write(ctx, db, func(ctx context.Context) error {
pro, err := keel.WriteValue(ctx, db, func(ctx context.Context) (int64, error) {
return keel.QueryValue[int64](ctx, db,
"UPDATE pro_sequences SET next = next + 1 RETURNING next - 1")
}, keel.Detached(), keel.LockTimeout(time.Second))
if err != nil {
return err
}
open, err := keel.ReadValue(withSystem(ctx, "open shipments across tenants"), db,
func(ctx context.Context) (int64, error) {
return s.AllTenants().Where(s.Status.Ne(fleet.StatusDelivered)).Count(ctx, db)
}, keel.Detached())
if err != nil {
return err
}
fmt.Println("open shipments:", open)
return s.Create(ctx, db, &fleet.Shipment{ProNumber: fmt.Sprintf("PRO-%d", pro), Status: fleet.StatusNew})
})
if err != nil {
log.Fatal(err)
}
}
Output:
func LockTimeout ¶
LockTimeout bounds how long any statement of the transaction waits for a lock.
func ReplicaTx ¶
func ReplicaTx() TxOption
ReplicaTx runs a Read on a replica, for long read-only work such as a report or an export: the transaction holds a replica connection from its first statement to its end, and every terminal in it runs there. It picks the replica as OnReplica does, among those the lag tracker has seen replay the context's commit position, falls back to the primary when none qualifies, and is refused on a Write and with Serializable, which a hot standby does not run.
Example ¶
package main
import (
"context"
"fmt"
"log"
"github.com/emoss08/keel"
"github.com/emoss08/keel/internal/testmodels/fleet"
"github.com/emoss08/keel/internal/testmodels/fleet/fleetdb"
"github.com/emoss08/keel/internal/testmodels/ids"
)
// appDB is the application's database, opened once at startup (see ExampleOpen).
var appDB *keel.DB
// scope is what the application's authentication middleware puts on a request context.
type scope struct {
OrganizationID ids.ID
BusinessUnitID ids.ID
SystemReason string
}
type scopeKey struct{}
func withTenant(ctx context.Context, org, bu ids.ID) context.Context {
return context.WithValue(ctx, scopeKey{}, scope{OrganizationID: org, BusinessUnitID: bu})
}
// requestContext returns the context of a request authenticated for one tenant.
func requestContext() context.Context {
return withTenant(context.Background(), "org_acme", "bu_dallas")
}
func main() {
ctx, db := requestContext(), appDB
s := fleetdb.Shipments
err := keel.Read(ctx, db, func(ctx context.Context) error {
return s.Where(s.Status.Eq(fleet.StatusDelivered)).Each(ctx, db, 1000,
func(ctx context.Context, batch []*fleet.Shipment) error {
for _, sh := range batch {
fmt.Println(sh.ProNumber, sh.Total)
}
return nil
})
}, keel.ReplicaTx(), keel.RepeatableRead)
if err != nil {
log.Fatal(err)
}
}
Output:
func Retry ¶
Retry runs a top-level transaction up to n more times after a serialization failure or a deadlock. fn must be safe to repeat.
func Setting ¶
Setting sets the run-time parameter name to value for the transaction, as SET LOCAL does: set_config(name, value, true), for a planner or extension setting one query needs, such as hnsw.ef_search. A transaction Read or Write begins queues it with BEGIN, in the same round trip. A Read or Write that joins a transaction already on the context sets it for fn and restores the joined transaction's value when fn returns.
type ValueType ¶
type ValueType uint8
ValueType is the type of a column of a dynamic result, and a type Cast accepts.
const ( // TypeString is text, varchar, char, name, enums, uuid and any type read in its text form. TypeString ValueType = iota + 1 // TypeInt64 is smallint, integer, bigint and oid. TypeInt64 // TypeFloat64 is real and double precision. TypeFloat64 // TypeNumeric is numeric, kept exact as a Decimal. TypeNumeric // TypeBool is boolean. TypeBool // TypeTime is timestamptz and timestamp, in UTC. TypeTime // TypeDate is date, as midnight UTC. TypeDate // TypeBytes is bytea. TypeBytes // TypeJSON is json, jsonb and every other type (arrays, ranges, intervals), as raw JSON. TypeJSON )
type WaitKind ¶
type WaitKind uint8
WaitKind is what a slow wait waited for.
const ( // WaitTenantSlot is a terminal or transaction waiting for one of its tenant's slots. WaitTenantSlot WaitKind = iota + 1 // WaitConnection is a terminal, transaction or version read waiting for a pool connection. WaitConnection // WaitCacheFill is a terminal waiting for another goroutine's read of the same result cache // entry. WaitCacheFill )
The kinds of wait SlowWaitConfig reports.
type Window ¶
type Window struct {
// contains filtered or unexported fields
}
Window is a window specification: the partition, the order and the frame of a window function. The zero Window is OVER (): every row of the result, unordered. Build one with PartitionBy, OrderBy or NamedWindow and give it to Agg.OverWindow; methods return a changed copy.
running := keel.Sum(s.Weight).OverWindow(keel.PartitionBy(s.CustomerID).
OrderBy(s.CreatedAt.Asc()).Rows(keel.UnboundedPreceding, keel.CurrentRow))
func NamedWindow ¶
NamedWindow refers to a window the query declares with Window. It may add an order, when the named window has none, and a frame; it cannot add a partition.
func PartitionBy ¶
PartitionBy starts a window whose partitions are the rows that share es.
func (Window) Exclude ¶
func (w Window) Exclude(x FrameExclusion) Window
Exclude removes rows around the current row from the frame. It needs a frame.
func (Window) Groups ¶
func (w Window) Groups(start, end FrameBound) Window
Groups sets the frame to GROUPS BETWEEN start AND end: offsets count peer groups.
func (Window) PartitionBy ¶
PartitionBy adds partition expressions.
func (Window) Range ¶
func (w Window) Range(start, end FrameBound) Window
Range sets the frame to RANGE BETWEEN start AND end: offsets are distances in the value of the window's single order expression, and CurrentRow includes the row's peers.
func (Window) Rows ¶
func (w Window) Rows(start, end FrameBound) Window
Rows sets the frame to ROWS BETWEEN start AND end: offsets count rows.
Source Files
¶
- agg.go
- arraycodec.go
- batch.go
- budget.go
- bulk.go
- cache.go
- cachekey.go
- cachestore.go
- columnar.go
- columns.go
- compute.go
- constraint.go
- convert.go
- copy.go
- cte.go
- dataversion.go
- db.go
- dynamic.go
- dynamic_expr.go
- dynamic_result.go
- errors.go
- exec.go
- explain.go
- expr.go
- fetch.go
- funcs.go
- hooks.go
- infer.go
- into.go
- join.go
- json.go
- jsonnumber.go
- links.go
- literals.go
- load.go
- maxwait.go
- nullzero.go
- observe.go
- operators.go
- page.go
- pick.go
- query.go
- rawexec.go
- readfilter.go
- ref.go
- render.go
- replica.go
- rows.go
- scan.go
- scope.go
- search.go
- select.go
- shapes.go
- slowwait.go
- tenantlimit.go
- tx.go
- typedarray.go
- typedcase.go
- typedtuple.go
- unnest.go
- verify.go
- versionbus.go
- write.go
Directories
¶
| Path | Synopsis |
|---|---|
|
analyticscli
Package analyticscli adds the analytics commands to a keelcli database binary:
|
Package analyticscli adds the analytics commands to a keelcli database binary: |
|
cache
Package cache holds result caches for keel/analytics: byte values by key, each with a time to live.
|
Package cache holds result caches for keel/analytics: byte values by key, each with a time to live. |
|
diff
Package diff compares two runs of the same report: which rows appeared, disappeared or moved, and by how much.
|
Package diff compares two runs of the same report: which rows appeared, disappeared or moved, and by how much. |
|
export
Package export writes a result (a table.Reader) as CSV, a JSON envelope, an XLSX workbook or an Arrow IPC stream, streaming: each writer holds one batch at a time, so memory stays flat however many rows the result has.
|
Package export writes a result (a table.Reader) as CSV, a JSON envelope, an XLSX workbook or an Arrow IPC stream, streaming: each writer holds one batch at a time, so memory stays flat however many rows the result has. |
|
format
Package format is the display layer of keel/analytics: how a report column's values are shown.
|
Package format is the display layer of keel/analytics: how a report column's values are shown. |
|
format/internal/cldrgen
command
Command cldrgen writes currency_cldr.go: where each CLDR locale places the currency symbol around a number.
|
Command cldrgen writes currency_cldr.go: where each CLDR locale places the currency symbol around a number. |
|
formula
Package formula is the typed expression language of keel/analytics report calculations: a lexer, a Pratt parser, an AST that also has a JSON encoding, a type checker over an environment, a function registry with deterministic SQL rendering descriptors, and a canonical printer.
|
Package formula is the typed expression language of keel/analytics report calculations: a lexer, a Pratt parser, an AST that also has a JSON encoding, a type checker over an environment, a function registry with deterministic SQL rendering descriptors, and a canonical printer. |
|
ir
Package ir is the report definition of keel/analytics: the types an application stores for a user-built report, their JSON encoding and JSON Schema, versioning with upgrades, validation with user-facing errors, dashboard filter merging and drill-through rewriting.
|
Package ir is the report definition of keel/analytics: the types an application stores for a user-built report, their JSON encoding and JSON Schema, versioning with upgrades, validation with user-facing errors, dashboard filter merging and drill-through rewriting. |
|
ir/internal/gen
command
Command gen writes the generated files of analytics/ir: the JSON Schema, the TypeScript golden module and the formula reference in the guide.
|
Command gen writes the generated files of analytics/ir: the JSON Schema, the TypeScript golden module and the formula reference in the guide. |
|
ir/internal/schemagen
Package schemagen generates the JSON Schema of analytics/ir definitions from the Go types, with descriptions taken from their doc comments.
|
Package schemagen generates the JSON Schema of analytics/ir definitions from the Go types, with descriptions taken from their doc comments. |
|
ir/tsgen
Package tsgen writes analytics/ir as a TypeScript module: the definition types and JSON Schema, the operator table per value type, the chart and transform rules, the formula function registry and applyFilters, so a client builder uses the server's rules instead of copies of them.
|
Package tsgen writes analytics/ir as a TypeScript module: the definition types and JSON Schema, the operator table per value type, the chart and transform rules, the formula function registry and applyFilters, so a client builder uses the server's rules instead of copies of them. |
|
rollup
Package rollup maintains pre-aggregated tables for keel/analytics and routes reports to them.
|
Package rollup maintains pre-aggregated tables for keel/analytics and routes reports to them. |
|
table
Package table is the typed, columnar result that analytics produces and its writers and diffs consume: a schema of columns and a stream of row batches, each column held in one typed vector with a null bitmap.
|
Package table is the typed, columnar result that analytics produces and its writers and diffs consume: a schema of columns and a stream of row batches, each column held in one typed vector with a null bitmap. |
|
value
Package value names the types a report column holds and the columnar vectors that hold its values.
|
Package value names the types a report column holds and the columnar vectors that hold its values. |
|
cache
|
|
|
rediscache
Package rediscache puts keel's result cache (keel.CacheConfig.Shared) on a Redis-compatible server (Redis, Valkey, KeyDB, Dragonfly), reached through the application's own client, so keel depends on no client library.
|
Package rediscache puts keel's result cache (keel.CacheConfig.Shared) on a Redis-compatible server (Redis, Valkey, KeyDB, Dragonfly), reached through the application's own client, so keel depends on no client library. |
|
cmd
|
|
|
keel
command
Command keel generates typed data-access packages for keel models and manages migrations.
|
Command keel generates typed data-access packages for keel models and manages migrations. |
|
Package gen writes the typed data-access packages for keel models.
|
Package gen writes the typed data-access packages for keel models. |
|
internal
|
|
|
testdump
Package testdump gives the squash tests the pg_dump command of the test database's server.
|
Package testdump gives the squash tests the pg_dump command of the test database's server. |
|
testmodels/fleet
Package fleet holds the models keel's tests run against.
|
Package fleet holds the models keel's tests run against. |
|
testmodels/geo
Package geo holds models with PostGIS columns, read and written through a Go type with its own codec, as Trenova's postgis.Point is: a sql.Scanner and driver.Valuer on the pointer that decode EWKB and encode WKB.
|
Package geo holds models with PostGIS columns, read and written through a Go type with its own codec, as Trenova's postgis.Point is: a sql.Scanner and driver.Valuer on the pointer that decode EWKB and encode WKB. |
|
testmodels/ids
Package ids holds the identifier type of the test models.
|
Package ids holds the identifier type of the test models. |
|
Package keelcli turns an application's migrations and seeds into the command line its database binaries are made of.
|
Package keelcli turns an application's migrations and seeds into the command line its database binaries are made of. |
|
cmd/demo
command
Command demo is a keelcli database binary over the fake engine, for trying the console and the live views without a database.
|
Command demo is a keelcli database binary over the fake engine, for trying the console and the live views without a database. |
|
factsport
Package factsport declares the facts about a database that keelcli's doctor and console header read: the connection, server settings and extensions, the backends holding locks or running long queries, and the environment mark.
|
Package factsport declares the facts about a database that keelcli's doctor and console header read: the connection, server settings and extensions, the backends holding locks or running long queries, and the environment mark. |
|
fake
Package fake is an in-memory migration engine, seed engine and database that implements every keelcli port with simulated timing.
|
Package fake is an in-memory migration engine, seed engine and database that implements every keelcli port with simulated timing. |
|
internal/adapt
Package adapt implements keelcli's ports over the real engines: keel/migrate for migrations and templates, keel/seed for seeds and the environment mark, and the connection pool for the facts doctor and the console header read.
|
Package adapt implements keelcli's ports over the real engines: keel/migrate for migrations and templates, keel/seed for seeds and the environment mark, and the connection pool for the facts doctor and the console header read. |
|
internal/doctor
Package doctor runs keelcli's database checks from the facts and migration ports and renders them; the doctor command and the console's Doctor tab share it.
|
Package doctor runs keelcli's database checks from the facts and migration ports and renders them; the doctor command and the console's Doctor tab share it. |
|
internal/guard
Package guard decides whether a destructive action may run against a database: it reads the database and its environment mark and refuses a production-marked database unless the user said so.
|
Package guard decides whether a destructive action may run against a database: it reads the database and its environment mark and refuses a production-marked database unless the user said so. |
|
internal/plans
Package plans is keelcli's plan guard: it explains recorded statement shapes with EXPLAIN (GENERIC_PLAN, VERBOSE, FORMAT JSON) and checks every plan against the rules, so a query that would scan a large table, cost too much or sort a page without an index is caught before it ships.
|
Package plans is keelcli's plan guard: it explains recorded statement shapes with EXPLAIN (GENERIC_PLAN, VERBOSE, FORMAT JSON) and checks every plan against the rules, so a query that would scan a large table, cost too much or sort a page without an index is caught before it ships. |
|
internal/render
Package render turns keelcli's results into text: tables that fit a width, one line per run event, and JSON.
|
Package render turns keelcli's results into text: tables that fit a width, one line per run event, and JSON. |
|
internal/term
Package term detects whether keelcli's streams are terminals and how wide they are.
|
Package term detects whether keelcli's streams are terminals and how wide they are. |
|
internal/ui/busy
Package busy shows a spinner beside a label while one engine call runs, for the commands whose call takes a while but reports no progress: template build, squash, reset.
|
Package busy shows a spinner beside a label while one engine call runs, for the commands whose call takes a while but reports no progress: template build, squash, reset. |
|
internal/ui/console
Package console is keelcli's full-screen console: tabs for migrations, seeds, templates, lint findings and doctor checks over the same ports the commands call.
|
Package console is keelcli's full-screen console: tabs for migrations, seeds, templates, lint findings and doctor checks over the same ports the commands call. |
|
internal/ui/live
Package live is the view of a running deploy step: one row per migration or seed with a spinner, elapsed time, retries, rows and per-tenant progress, a panel naming the backends a lock waits for, and a summary of what ran and what was left undone.
|
Package live is the view of a running deploy step: one row per migration or seed with a spinner, elapsed time, retries, rows and per-tenant progress, a panel naming the backends a lock waits for, and a summary of what ran and what was left undone. |
|
internal/ui/prompt
Package prompt holds keelcli's Huh forms: the confirmation of a destructive action and the questions migrate create asks.
|
Package prompt holds keelcli's Huh forms: the confirmation of a destructive action and the questions migrate create asks. |
|
internal/ui/sqlview
Package sqlview highlights migration SQL for the terminal: keywords, strings, numbers, comments and keel directives, with line numbers and a marked line for lint findings.
|
Package sqlview highlights migration SQL for the terminal: keywords, strings, numbers, comments and keel directives, with line numbers and a marked line for lint findings. |
|
internal/ui/theme
Package theme is keelcli's one definition of colour and style.
|
Package theme is keelcli's one definition of colour and style. |
|
migrateport
Package migrateport declares what keelcli needs from a migration engine.
|
Package migrateport declares what keelcli needs from a migration engine. |
|
seedport
Package seedport declares what keelcli needs from a seed engine.
|
Package seedport declares what keelcli needs from a seed engine. |
|
Package keellog prints the statements keel sends, for a developer reading a terminal: one header line per statement (operation, table, transaction, duration, rows or outcome, and the code that issued it), then the SQL broken at its clauses, with the bound values written in when keel.Config.ObserveArgs is set.
|
Package keellog prints the statements keel sends, for a developer reading a terminal: one header line per statement (operation, table, transaction, duration, rows or outcome, and the code that issued it), then the SQL broken at its clauses, with the bound values written in when keel.Config.ObserveArgs is set. |
|
Package keeltest holds test helpers for code that builds statements with keel: golden snapshots of rendered statements, and plan assertions that an index serves a query.
|
Package keeltest holds test helpers for code that builds statements with keel: golden snapshots of rendered statements, and plan assertions that an index serves a query. |
|
Package migrate applies SQL and Go migrations to a Postgres database.
|
Package migrate applies SQL and Go migrations to a Postgres database. |
|
Package numeric reads and writes github.com/shopspring/decimal values in PostgreSQL's binary numeric format.
|
Package numeric reads and writes github.com/shopspring/decimal values in PostgreSQL's binary numeric format. |
|
Package postgis maps PostGIS geometry and geography columns to Go values and builds spatial SQL with keel's expressions.
|
Package postgis maps PostGIS geometry and geography columns to Go values and builds spatial SQL with keel's expressions. |
|
Package schema holds the runtime description of keel models.
|
Package schema holds the runtime description of keel models. |
|
Package seed applies the data a database needs, reference data and development fixtures, as declared seeds that re-run exactly when what they are made from changes.
|
Package seed applies the data a database needs, reference data and development fixtures, as declared seeds that re-run exactly when what they are made from changes. |
|
Package vector builds pgvector expressions for keel queries: the distance operators, the casts an expression index is declared over, and the HNSW search settings.
|
Package vector builds pgvector expressions for keel queries: the distance operators, the casts an expression index is declared over, and the HNSW search settings. |