ant

package module
v0.1.0 Latest Latest
Warning

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

Go to latest
Published: Nov 1, 2026 License: Apache-2.0 Imports: 30 Imported by: 0

README

ant

ant is a Go ORM for Go 1.27 and later, in which models are plain structs and queries are checked by the compiler, with no code generation step. You declare a typed database context in the style of Entity Framework Core, reference columns through field pointers such as &U.Age instead of strings, and get change tracking through SaveChanges. Errors stay explicit and appear at terminal calls. ant targets PostgreSQL, MySQL, MariaDB, SQLite, and SQL Server on top of database/sql.

ant is in development, and has not been released. Models, relations, change tracking, transactions, global filters, soft deletes, bulk writes, interceptors, and migrations are in place on PostgreSQL. MySQL, MariaDB, SQLite, and SQL Server run the same queries and writes, without migrations yet: databases has the details. The roadmap shows the current state, the feature examples show how everything is used, the recipes cover common jobs, and ant-examples has small projects that use it.

A taste

type User struct {
    ant.Base               // ID, CreatedAt, UpdatedAt
    Name  string
    Age   int
    Posts []Post           // 1:N via Post.UserID
}

type Post struct {
    ant.Base
    UserID int64
    Title  string
}

type AppDB struct {
    ant.Context
    Users ant.Table[User]
    Posts ant.Table[Post]
}

var U = ant.Fields[User]() // field probe: &U.Age identifies the age column
db, err := ant.Open[AppDB](ctx, postgres.Dialect, dsn)
alice := &User{Name: "Alice", Age: 30}
err = db.Users.Insert(ctx, alice)
user, err := db.Users.Find(ctx, alice.ID)
user.Age++
err = db.SaveChanges(ctx) // UPDATE users SET age = $1, ... WHERE id = $2

adults, err := db.Users.
    Where(ant.Gt(&U.Age, 18)). // ant.Gt(&U.Age, "18") does not compile
    Include(&U.Posts).         // batched, no N+1
    All(ctx)

Documentation

  • The problem: what ant is meant to fix, with before and after code.
  • Existing solutions: an assessment of eleven Go database libraries, with a comparison table.
  • Vision: design decisions, the target API, how field probes work, known limits, and non-goals.
  • Migrations: generating and applying schema changes, adopting an existing database, and leaving ant's tooling.
  • Decisions: refinements to the original brief and the reasons for them.
  • Roadmap: every planned feature by phase, and what is not planned.
  • Reference: feature catalogs of EF Core, Eloquent, and Go libraries.
  • Feature examples: one small test per feature, showing how to use it and how it behaves at the edges.

Development

Requires Go 1.27 or later, Docker, make, and staticcheck.

make test              # unit tests, no database needed
make fmt               # lay the code out, in every module
make lint              # go vet, staticcheck, and fail on anything fmt would change
make db-up DATABASES=postgres  # start PostgreSQL, or leave DATABASES out for all
make test-pg           # run the API tests and feature examples on PostgreSQL
make bench             # compare ant with plain database/sql
make test-integration  # run them on all five databases
make db-down           # stop the databases and discard their data

Tests for features that do not exist yet fail with ErrNotImplemented.

License

Apache License 2.0. See LICENSE.

Documentation

Overview

Package ant is a Go ORM with plain struct models, compiler-checked queries, and change tracking, and no code generation.

Declare a context, open it, and query through field probes:

type AppDB struct {
	ant.Context
	Users ant.Table[User]
}

var U = ant.Fields[User]()

db, err := ant.Open[AppDB](ctx, postgres.Dialect, dsn)
adults, err := db.Users.Where(ant.Gt(&U.Age, 18)).All(ctx)

Index

Examples

Constants

View Source
const (
	// Detached entities are not tracked.
	Detached = track.Detached
	// Added entities are inserted by the next SaveChanges.
	Added = track.Added
	// Unchanged entities match what was loaded.
	Unchanged = track.Unchanged
	// Modified entities are updated by the next SaveChanges.
	Modified = track.Modified
	// Deleted entities are removed by the next SaveChanges.
	Deleted = track.Deleted
)

Entity states.

Variables

View Source
var (
	// ErrNotFound reports that a query for one row matched none.
	ErrNotFound = errors.New("ant: not found")
	// ErrMultipleRows reports that Single matched more than one row.
	ErrMultipleRows = errors.New("ant: query matched more than one row")
	// ErrNotImplemented reports a feature with no implementation yet.
	ErrNotImplemented = errs.ErrNotImplemented
	// ErrUnsupported reports something the connected database cannot do, such as
	// MERGE on MySQL. dialect.Supports asks in advance.
	ErrUnsupported = errs.ErrUnsupported
	// ErrNotProbe reports a field pointer that does not come from Fields.
	ErrNotProbe = errors.New("ant: pointer is not a field of a probe from ant.Fields")
	// ErrNotColumn reports a probe field that is a relation, used as a column.
	ErrNotColumn = errors.New("ant: field is a relation, not a column")
	// ErrNotRelation reports a probe field that is not a relation.
	ErrNotRelation = errors.New("ant: field is not a relation")
	// ErrWrongModel reports a probe from a model the query does not use.
	ErrWrongModel = errors.New("ant: field belongs to a model outside this query")
	// ErrNoContext reports a type passed to Open that does not embed Context.
	ErrNoContext = errors.New("ant: type does not embed ant.Context")
	// ErrNotOpen reports a table or context that did not come from Open.
	ErrNotOpen = errors.New("ant: context is not open")
	// ErrNoKey reports a key operation on a model with no primary key.
	ErrNoKey = errors.New("ant: model has no primary key")
	// ErrKeyType reports a key whose type does not match the primary key.
	ErrKeyType = errors.New("ant: key type does not match the primary key")
	// ErrNoForeignKey reports a relation whose foreign key cannot be found by
	// convention. Name it in Configure.
	ErrNoForeignKey = errs.ErrNoForeignKey
	// ErrNoColumnType reports a field whose database type cannot be worked out
	// from its Go type. Set it with Column(...).Type(...).
	ErrNoColumnType = errors.New("ant: no database type for field")
	// ErrBadSchema reports models that cannot become a valid schema.
	ErrBadSchema = errors.New("ant: invalid schema")
	// ErrUnknownColumn reports a result column with no field, under WithStrict.
	ErrUnknownColumn = errors.New("ant: result column has no field")
	// ErrNoRows reports an operation given no rows to work on.
	ErrNoRows = errors.New("ant: no rows given")
	// ErrNilEntity reports a nil pointer passed to a write.
	ErrNilEntity = errors.New("ant: entity is nil")
	// ErrNoCondition reports a bulk write with no condition. Call Unfiltered
	// to affect every row on purpose.
	ErrNoCondition = errors.New("ant: bulk write has no condition")
	// ErrNoAssignment reports an Update with nothing to set.
	ErrNoAssignment = errors.New("ant: update has no assignments")
	// ErrThenWithoutOrder reports ThenBy on a query with no OrderBy.
	ErrThenWithoutOrder = errors.New("ant: ThenBy needs a preceding OrderBy")
	// ErrIncludeInEach reports Include with Each, which would need a second
	// query while rows stream. Use All, or Chunk for large results.
	ErrIncludeInEach = errors.New("ant: Each does not support Include")
	// ErrConcurrency reports that SaveChanges found a row changed or removed
	// since it was loaded. Nothing was saved.
	ErrConcurrency = errors.New("ant: row was changed or removed since it was loaded")
	// ErrKeyChanged reports a tracked entity whose primary key was modified. A
	// key identifies the row, so use a new entity instead.
	ErrKeyChanged = errors.New("ant: primary key of a tracked entity was changed")
	// ErrCycle reports new entities that depend on each other, so neither can
	// be inserted first. Save them in two steps.
	ErrCycle = errors.New("ant: new entities depend on each other")
	// ErrNotTracked reports an operation on an entity the context is not
	// tracking.
	ErrNotTracked = errors.New("ant: entity is not tracked")
	// ErrNotInTx reports Commit, Rollback, or a savepoint outside a transaction.
	ErrNotInTx = errors.New("ant: not in a transaction")
	// ErrTxDone reports use of a transaction after it ended.
	ErrTxDone = errors.New("ant: transaction has ended")
	// ErrTxType reports a Tx closure whose parameter is not this context's type.
	ErrTxType = errors.New("ant: Tx closure takes a different context type")
	// ErrIncludeInSubquery reports a query with Include used as a subquery.
	ErrIncludeInSubquery = errors.New("ant: subquery cannot use Include")
	// ErrCursor reports a cursor that this query cannot use.
	ErrCursor = errors.New("ant: invalid cursor")
	// ErrNotManyToMany reports a link operation on another kind of relation.
	ErrNotManyToMany = errors.New("ant: relation is not many-to-many")
	// ErrReadOnly reports a write to a read-only model, such as a view.
	ErrReadOnly = errors.New("ant: model is read-only")
	// ErrNotSoftDeleted reports Restore on a model with no DeletedAt field.
	ErrNotSoftDeleted = errors.New("ant: model has no DeletedAt field")
	// ErrNoColumns reports a query whose selection leaves no columns.
	ErrNoColumns = errors.New("ant: query selects no columns")
)

Errors returned by terminal calls.

Functions

func Add

func Add[T any](h Handle, entity *T) error

Add schedules a new entity for insertion by the next SaveChanges.

func Attach

func Attach[T any](h Handle, entity *T) error

Attach tracks an entity as it is, assuming it matches the database.

func AvgOf

func AvgOf[T any, V Number](ctx context.Context, q *Query[T], field *V) (float64, error)

AvgOf averages a column over matching rows, giving zero if there are none.

func Begin

func Begin[DB any](ctx context.Context, db *DB, options ...TxOption) (*DB, error)

Begin starts a transaction and returns a context bound to it. End it with Commit or Rollback. Prefer Context.Tx, which cannot leave one open.

func Clone

func Clone[T any](entity *T) *T

Clone returns a copy of entity with its key and relations cleared, ready to be inserted as a new row.

func CurrentTx

func CurrentTx(h Handle) *sql.Tx

CurrentTx returns the transaction a context is bound to, or nil. Pass it to UseTx to share one transaction between contexts of different types.

func Detach

func Detach[T any](h Handle, entity *T)

Detach stops tracking an entity. Unsaved changes to it are forgotten.

func FailedSQL

func FailedSQL(err error) string

FailedSQL returns the statement behind a query error, or "". Values are not in it, only their placeholders, so it is safe for an internal log.

func Fields

func Fields[T any]() *T

Fields returns a probe for T. A probe holds no data. Its field addresses identify columns, so &U.Age names the age column of User:

var U = ant.Fields[User]()

Declare one probe per model at package level.

func GenerateKey

func GenerateKey[E, V any](b *ModelBuilder[E], field *V, fn func() V)

GenerateKey sets a primary key that fn fills in Go when it is zero, such as a UUID.

func IsCheckViolation

func IsCheckViolation(err error) bool

IsCheckViolation reports whether err is a check constraint failure.

func IsDirty

func IsDirty[T, V any](e *EntityEntry[T], field *V) bool

IsDirty reports whether a field differs from what was loaded.

func IsForeignKeyViolation

func IsForeignKeyViolation(err error) bool

IsForeignKeyViolation reports whether err is a foreign key failure.

func IsNotNullViolation

func IsNotNullViolation(err error) bool

IsNotNullViolation reports whether err is a NOT NULL failure.

func IsRetryable

func IsRetryable(err error) bool

IsRetryable reports whether a transaction failed from a deadlock or a serialization conflict, and would likely work if run again.

func IsUniqueViolation

func IsUniqueViolation(err error) bool

IsUniqueViolation reports whether err is a unique constraint failure.

func Link[T, R any](ctx context.Context, h Handle, owner *T, rel *[]R, targets ...*R) error

Link adds rows to a many-to-many relation's join table, skipping pairs that are already there. It does not change owner's relation field.

func MarkModified

func MarkModified[T any](h Handle, entity *T) error

MarkModified makes the next SaveChanges write every field of an entity. Use it for a disconnected entity whose earlier values are unknown.

func MaxOf

func MaxOf[T any, V Ordered](ctx context.Context, q *Query[T], field *V) (V, error)

MaxOf returns a column's largest value, or zero if there are no rows.

func MinOf

func MinOf[T any, V Ordered](ctx context.Context, q *Query[T], field *V) (V, error)

MinOf returns a column's smallest value, or zero if there are no rows.

func Observe

func Observe[T any](h Handle, o Observer[T]) error

Observe registers an observer for model T, on this context and its sessions.

func OnPrimary

func OnPrimary(ctx context.Context) context.Context

OnPrimary sends the reads made with ctx to the primary, not a replica.

func Open

func Open[DB any](
	ctx context.Context,
	d dialect.Dialect,
	dsn string,
	options ...Option,
) (*DB, error)

Open connects to a database and returns a populated context. DB must be a struct that embeds Context. The application imports the database driver.

func Original

func Original[T, V any](e *EntityEntry[T], field *V) (V, error)

Original returns a field's value as it was loaded. It returns ErrNotTracked for an entity with no loaded values.

func Pluck

func Pluck[T, V any](ctx context.Context, q *Query[T], field *V) ([]V, error)

Pluck returns one column of every matching row.

func Pointers

func Pointers[T any](values []T) []*T

Pointers returns pointers to the elements of values, for passing a slice of values to Load.

func Remove

func Remove[T any](h Handle, entity *T) error

Remove schedules an entity for deletion by the next SaveChanges.

func SQLState

func SQLState(err error) string

SQLState returns the database's error code for err, or "". Dialects translate their own codes to SQLSTATE, so one value means one thing on every database.

func SchemaOf

func SchemaOf[DB any](d dialect.Dialect) (*schema.Schema, error)

SchemaOf returns the schema DB's models describe: its Table fields, and models reached through their relations. It needs no connection. Views and models excluded from migrations have no table in the result.

func Session

func Session[DB any](db *DB, options ...Option) *DB

Session returns a new context on the same connections with its own tracker, which suits one unit of work, such as one web request. Tracking on a long-lived context grows without end, so prefer a session per request.

A session takes its settings from db, and options change them for the session. An application that keeps tracking off on its shared context passes WithTracking(true) to the sessions that use SaveChanges, which otherwise has nothing to save.

func SumOf

func SumOf[T any, V Number](ctx context.Context, q *Query[T], field *V) (V, error)

SumOf adds up a column over matching rows, giving zero if there are none.

func SyncLinks[T, R any](ctx context.Context, h Handle, owner *T, rel *[]R, targets ...*R) error

SyncLinks makes targets the only links of owner in a many-to-many relation.

func TableOf

func TableOf[T any]() (schemaName, table string, err error)

TableOf returns the database schema and table of model T, as configured.

func Toggle

func Toggle[T, R any](ctx context.Context, h Handle, owner *T, rel *[]R, targets ...*R) error

Toggle links the targets that are not linked to owner, and unlinks those that are.

func TrackGraph

func TrackGraph[T any](h Handle, root *T, decide func(GraphNode) State) error

TrackGraph walks a graph that came from outside, such as a request body, and tracks each entity in the state decide returns for it. Returning Detached skips the entity and everything below it. It is the way to save a tree whose earlier values are unknown:

ant.TrackGraph(db, order, func(n ant.GraphNode) ant.State {
	if n.HasKey {
		return ant.Modified
	}
	return ant.Added
})
err := db.SaveChanges(ctx)

Foreign keys between the entities are filled in by SaveChanges.

func Tracked

func Tracked[T any](h Handle) []*T

Tracked returns the tracked entities of type T, oldest first.

func Unlink[T, R any](ctx context.Context, h Handle, owner *T, rel *[]R, targets ...*R) error

Unlink removes rows from a many-to-many relation's join table. The targets themselves stay.

func UseConn

func UseConn[DB any](db *DB, conn *sql.Conn) (*DB, error)

UseConn returns a context on one connection from database/sql, such as one holding a session setting or an advisory lock. Everything it does, including transactions, stays on conn. It shares db's tracker. The caller closes conn.

func UseTx

func UseTx[DB any](db *DB, tx *sql.Tx) (*DB, error)

UseTx returns a context bound to a transaction started outside ant, so that ant's queries can join it. The caller commits or rolls back tx. Observers wait for a commit that ant cannot see, so they do not run.

func Watch

func Watch(ctx context.Context) context.Context

Watch returns a context that counts statements for Diagnostics. Use one per request, in middleware.

func WithoutHooks

func WithoutHooks(ctx context.Context) context.Context

WithoutHooks skips model hooks for writes made with ctx.

func Wrap

func Wrap[DB any](d dialect.Dialect, pool *sql.DB, options ...Option) (*DB, error)

Wrap returns a context on an existing pool. The pool stays yours, but Close closes it, as with Open.

Types

type Action

type Action = meta.Action

Action is what happens to dependent rows when their parent is deleted or its key changes.

const (
	NoAction Action = iota
	Cascade
	SetNull
	Restrict
)

Actions.

type AfterDeleter

type AfterDeleter interface {
	AfterDelete(ctx context.Context) error
}

AfterDeleter runs after an entity is deleted.

type AfterIncluder

type AfterIncluder interface {
	AfterInclude(ctx context.Context) error
}

AfterIncluder runs on each entity a query returns, after Include, so its relations are there.

type AfterInserter

type AfterInserter interface {
	AfterInsert(ctx context.Context) error
}

AfterInserter runs after an entity is inserted.

type AfterLoader

type AfterLoader interface {
	AfterLoad(ctx context.Context) error
}

AfterLoader runs on each entity a query returns, before Include.

type AfterUpdater

type AfterUpdater interface {
	AfterUpdate(ctx context.Context) error
}

AfterUpdater runs after an entity is updated.

type Alert

type Alert struct {
	Kind    string // "budget", "repeated", or "cartesian"
	Message string
	SQL     string
}

Alert is a warning about how the database is being used.

type Assignment

type Assignment struct {
	// contains filtered or unexported fields
}

Assignment sets a column in a bulk update.

func Dec

func Dec[V Number](field *V, n V) Assignment

Dec subtracts n from a numeric column, in the database.

func Inc

func Inc[V Number](field *V, n V) Assignment

Inc adds n to a numeric column, in the database.

func Set

func Set[V any](field *V, v V) Assignment

Set assigns v to field. Zero values are written like any other value.

func SetExpr

func SetExpr[V any](field *V, value Expr[V]) Assignment

SetExpr assigns an expression to field, such as another column.

type Base

type Base struct {
	ID        int64
	CreatedAt time.Time
	UpdatedAt time.Time
}

Base is an optional embeddable model base. ID is a generated primary key. Any model's time.Time fields named CreatedAt and UpdatedAt are maintained for it.

type BeforeDeleter

type BeforeDeleter interface {
	BeforeDelete(ctx context.Context) error
}

BeforeDeleter runs before an entity is deleted.

type BeforeInserter

type BeforeInserter interface {
	BeforeInsert(ctx context.Context) error
}

BeforeInserter runs before an entity is inserted.

type BeforeUpdater

type BeforeUpdater interface {
	BeforeUpdate(ctx context.Context) error
}

BeforeUpdater runs before a changed entity is updated. Changes it makes are saved too.

type BulkRows

type BulkRows struct {
	Schema  string
	Table   string
	Columns []string
	Rows    [][]any
}

BulkRows is entities prepared for a bulk loader outside ant, such as COPY.

func PrepareBulk

func PrepareBulk[T any](h Handle, entities []*T) (*BulkRows, error)

PrepareBulk fills what Insert fills in Go, such as timestamps and generated keys, and returns the values to load. Keys from the database are left to it, and do not come back. Hooks do not run.

type CaseExpr

type CaseExpr[V any] struct {
	// contains filtered or unexported fields
}

CaseExpr builds a CASE expression. See Case.

func Case

func Case[V any]() CaseExpr[V]

Case starts a CASE expression:

ant.Case[string]().When(ant.Lt(&U.Age, 18), "minor").Else("adult")

func (CaseExpr[V]) Else

func (c CaseExpr[V]) Else(value V) Expr[V]

Else finishes the expression with a value for when no branch matches.

func (CaseExpr[V]) ElseExpr

func (c CaseExpr[V]) ElseExpr(value Expr[V]) Expr[V]

ElseExpr finishes the expression, with an expression for rows no branch matched.

func (CaseExpr[V]) End

func (c CaseExpr[V]) End() Expr[sql.Null[V]]

End finishes the expression, which is NULL when no branch matches.

func (CaseExpr[V]) When

func (c CaseExpr[V]) When(cond Cond, then V) CaseExpr[V]

When adds a branch.

func (CaseExpr[V]) WhenExpr

func (c CaseExpr[V]) WhenExpr(cond Cond, then Expr[V]) CaseExpr[V]

WhenExpr adds a branch whose result is an expression, such as another column.

type Change

type Change struct {
	Model   string
	State   State
	Entity  any      // a pointer to the entity
	Changed []string // field names, for an update
}

Change describes one entity a SaveChanges is about to write.

type ColumnBuilder

type ColumnBuilder struct {
	// contains filtered or unexported fields
}

ColumnBuilder configures how a column is declared in migrations. Queries are not affected, except by Default and Generated, which say where a value comes from.

func (*ColumnBuilder) Collation

func (c *ColumnBuilder) Collation(name string) *ColumnBuilder

Collation sets the column's collation.

func (*ColumnBuilder) Comment

func (c *ColumnBuilder) Comment(text string) *ColumnBuilder

Comment describes the column in the database.

func (*ColumnBuilder) Default

func (c *ColumnBuilder) Default(expr string) *ColumnBuilder

Default sets the column's default, a SQL expression such as now() or 'draft'. As with the ant:"default" tag, a zero field is left for the database to fill.

func (*ColumnBuilder) Enum

func (c *ColumnBuilder) Enum(name string, values ...string) *ColumnBuilder

Enum stores the column as a native enum type, which migrations create and extend. Removing a value takes a migration by hand, since rows may hold it.

func (*ColumnBuilder) Fixed

func (c *ColumnBuilder) Fixed(n int) *ColumnBuilder

Fixed makes a string column exactly n characters.

func (*ColumnBuilder) Generated

func (c *ColumnBuilder) Generated(expr string) *ColumnBuilder

Generated makes the column a stored generated column, computed by the database from expr. The field is read-only.

func (*ColumnBuilder) Length

func (c *ColumnBuilder) Length(n int) *ColumnBuilder

Length limits a string column to n characters.

func (*ColumnBuilder) NotNull

func (c *ColumnBuilder) NotNull() *ColumnBuilder

NotNull refuses NULL in a column whose Go type could hold it.

func (*ColumnBuilder) Nullable

func (c *ColumnBuilder) Nullable() *ColumnBuilder

Nullable allows NULL in a column whose Go type has no way to hold it.

func (*ColumnBuilder) Precision

func (c *ColumnBuilder) Precision(precision, scale int) *ColumnBuilder

Precision makes the column an exact number with the given digits in total and after the point.

func (*ColumnBuilder) Sensitive

func (c *ColumnBuilder) Sensitive() *ColumnBuilder

Sensitive keeps the column's values out of logs, even with LogValues.

func (*ColumnBuilder) Sequence

func (c *ColumnBuilder) Sequence(name string, options ...SequenceOption) *ColumnBuilder

Sequence fills the column from a database sequence, which migrations create. As with Default, a zero field is left for the database.

func (*ColumnBuilder) Type

func (c *ColumnBuilder) Type(sql string) *ColumnBuilder

Type sets the database type, written as it would be in SQL. It overrides the type that comes from the Go type, and is how a custom Go type joins migrations.

func (*ColumnBuilder) Unique

func (c *ColumnBuilder) Unique(name ...string) *ColumnBuilder

Unique adds a unique constraint on the column, like the ant:"unique" tag. An optional name replaces table_column_key.

type ConcurrencyError

type ConcurrencyError struct {
	Model string
	Key   any
}

ConcurrencyError is the ErrConcurrency from SaveChanges, naming the row.

func (*ConcurrencyError) Error

func (e *ConcurrencyError) Error() string

Error implements error.

func (*ConcurrencyError) Unwrap

func (e *ConcurrencyError) Unwrap() error

Unwrap makes errors.Is(err, ErrConcurrency) true.

type Cond

type Cond struct {
	// contains filtered or unexported fields
}

Cond is a query condition. Errors in a condition surface at the terminal call of the query that uses it.

func AllOf

func AllOf[V comparable](v V, fields ...*V) Cond

AllOf matches rows where every one of fields equals v.

func And

func And(conds ...Cond) Cond

And matches rows where every condition matches. With no conditions, it matches everything.

func AnyOf

func AnyOf[V comparable](v V, fields ...*V) Cond

AnyOf matches rows where at least one of fields equals v.

func Between

func Between[V Ordered](field *V, low, high V) Cond

Between matches rows where field is within low and high, inclusive.

func Eq

func Eq[V comparable](field *V, v V) Cond

Eq matches rows where field equals v. A v stored as NULL, such as a nil pointer, matches NULL. A non-nil pointer compares the pointed-to value.

Example
package main

import (
	"database/sql"
	"fmt"
	"log"

	_ "github.com/jackc/pgx/v5/stdlib"
	"github.com/skudra-go/ant"
	"github.com/skudra-go/ant/dialect/postgres"
)

type User struct {
	ant.Base
	Name  string
	Email string `ant:"unique"`
	Age   int
	Bio   *string
	Posts []Post
}

type Post struct {
	ant.Base
	UserID    int64
	Title     string
	Published bool
	User      *User
}

type AppDB struct {
	ant.Context
	Users ant.Table[User]
	Posts ant.Table[Post]
}

var Us = ant.Fields[User]()

func db() *AppDB {
	pool, err := sql.Open("pgx", "postgres://nobody@127.0.0.1:1/none")
	if err != nil {
		log.Fatal(err)
	}

	out, err := ant.Wrap[AppDB](postgres.Dialect, pool)
	if err != nil {
		log.Fatal(err)
	}

	return out
}

func show(text string, args []any, err error) {
	if err != nil {
		log.Fatal(err)
	}

	fmt.Println(text)
	fmt.Println(args)
}

func main() {
	show(db().Users.Where(ant.Eq(&Us.Name, "Ada")).ToSQL())
}
Output:
SELECT "users"."id", "users"."created_at", "users"."updated_at", "users"."name", "users"."email", "users"."age", "users"."bio" FROM "users" WHERE "users"."name" = $1
[Ada]

func EqCol

func EqCol[V comparable](a, b *V) Cond

EqCol matches rows where two columns are equal.

func Exists

func Exists(q Selectable) Cond

Exists matches rows for which q returns anything. Conditions in q can refer to the outer query's models.

func FailedCond

func FailedCond(err error) Cond

FailedCond returns a condition that reports err at the terminal call, for use by condition helpers that can fail.

func Future

func Future(field *time.Time) Cond

Future matches rows where a time column is after the database's current time.

func Gt

func Gt[V Ordered](field *V, v V) Cond

Gt matches rows where field is greater than v.

func Gte

func Gte[V Ordered](field *V, v V) Cond

Gte matches rows where field is greater than or equal to v.

func Has

func Has[R any](rel *[]R, conds ...Cond) Cond

Has matches rows with at least one related row in a one-to-many relation that meets conds. Build it at run time, not in a package variable, because it needs the related model's configuration.

Example
package main

import (
	"database/sql"
	"fmt"
	"log"

	_ "github.com/jackc/pgx/v5/stdlib"
	"github.com/skudra-go/ant"
	"github.com/skudra-go/ant/dialect/postgres"
)

type User struct {
	ant.Base
	Name  string
	Email string `ant:"unique"`
	Age   int
	Bio   *string
	Posts []Post
}

type Post struct {
	ant.Base
	UserID    int64
	Title     string
	Published bool
	User      *User
}

type AppDB struct {
	ant.Context
	Users ant.Table[User]
	Posts ant.Table[Post]
}

var (
	Us = ant.Fields[User]()
	Ps = ant.Fields[Post]()
)

func db() *AppDB {
	pool, err := sql.Open("pgx", "postgres://nobody@127.0.0.1:1/none")
	if err != nil {
		log.Fatal(err)
	}

	out, err := ant.Wrap[AppDB](postgres.Dialect, pool)
	if err != nil {
		log.Fatal(err)
	}

	return out
}

func show(text string, args []any, err error) {
	if err != nil {
		log.Fatal(err)
	}

	fmt.Println(text)
	fmt.Println(args)
}

func main() {
	show(db().Users.Column(&Us.ID).Where(ant.Has(&Us.Posts, ant.Eq(&Ps.Published, true))).ToSQL())
}
Output:
SELECT "users"."id" FROM "users" WHERE EXISTS (SELECT 1 FROM "posts" AS "ant_related" WHERE "ant_related"."user_id" = "users"."id" AND ("ant_related"."published" = $1))
[true]

func HasNone

func HasNone[R any](rel *[]R, conds ...Cond) Cond

HasNone matches rows with no related row that meets conds.

func HasOne

func HasOne[R any](rel **R, conds ...Cond) Cond

HasOne matches rows with a related row in a many-to-one or one-to-one relation that meets conds. See Has.

func ILike

func ILike[V ~string](
	field *V,
	pattern V,
) Cond

ILike is Like ignoring case.

func In

func In[V comparable](field *V, values ...V) Cond

In matches rows where field equals one of values. With no values, it matches nothing.

Example
package main

import (
	"database/sql"
	"fmt"
	"log"

	_ "github.com/jackc/pgx/v5/stdlib"
	"github.com/skudra-go/ant"
	"github.com/skudra-go/ant/dialect/postgres"
)

type User struct {
	ant.Base
	Name  string
	Email string `ant:"unique"`
	Age   int
	Bio   *string
	Posts []Post
}

type Post struct {
	ant.Base
	UserID    int64
	Title     string
	Published bool
	User      *User
}

type AppDB struct {
	ant.Context
	Users ant.Table[User]
	Posts ant.Table[Post]
}

var Us = ant.Fields[User]()

func db() *AppDB {
	pool, err := sql.Open("pgx", "postgres://nobody@127.0.0.1:1/none")
	if err != nil {
		log.Fatal(err)
	}

	out, err := ant.Wrap[AppDB](postgres.Dialect, pool)
	if err != nil {
		log.Fatal(err)
	}

	return out
}

func show(text string, args []any, err error) {
	if err != nil {
		log.Fatal(err)
	}

	fmt.Println(text)
	fmt.Println(args)
}

func main() {
	show(db().Users.Column(&Us.ID).Where(ant.In(&Us.Age, 18, 21)).ToSQL())
}
Output:
SELECT "users"."id" FROM "users" WHERE "users"."age" IN ($1, $2)
[18 21]

func InSub

func InSub[V any](field *V, sub Sub[V]) Cond

InSub matches rows where field is one of the subquery's values.

func InTuples

func InTuples(fields []FieldRef, tuples ...[]any) Cond

InTuples matches rows whose columns, taken together, equal one of the tuples:

ant.InTuples([]ant.FieldRef{ant.F(&O.Region), ant.F(&O.Number)},
	[]any{"eu", 1}, []any{"us", 7})

As with In, an empty list matches nothing.

func IsNotNull

func IsNotNull[V any](field *V) Cond

IsNotNull matches rows where field is not NULL.

func IsNull

func IsNull[V any](field *V) Cond

IsNull matches rows where field is NULL.

func Like

func Like[V ~string](field *V, pattern V) Cond

Like matches rows where field matches a SQL LIKE pattern, in which % matches any text and _ matches one character.

func Lt

func Lt[V Ordered](field *V, v V) Cond

Lt matches rows where field is less than v.

func Lte

func Lte[V Ordered](field *V, v V) Cond

Lte matches rows where field is less than or equal to v.

func Ne

func Ne[V comparable](field *V, v V) Cond

Ne matches rows where field does not equal v. A v stored as NULL matches non-NULL values. As in SQL, a NULL column never matches a non-NULL v.

func NoneOf

func NoneOf[V comparable](v V, fields ...*V) Cond

NoneOf matches rows where none of fields equals v.

func Not

func Not(c Cond) Cond

Not matches rows where c does not match.

func NotExists

func NotExists(q Selectable) Cond

NotExists matches rows for which q returns nothing.

func NotIn

func NotIn[V comparable](field *V, values ...V) Cond

NotIn matches rows where field equals none of values. With no values, it matches everything, including NULL.

func NotInSub

func NotInSub[V any](field *V, sub Sub[V]) Cond

NotInSub matches rows where field is none of the subquery's values. As in SQL, a NULL among the values means nothing matches.

func Or

func Or(conds ...Cond) Cond

Or matches rows where any condition matches. With no conditions, it matches nothing.

Example
package main

import (
	"database/sql"
	"fmt"
	"log"

	_ "github.com/jackc/pgx/v5/stdlib"
	"github.com/skudra-go/ant"
	"github.com/skudra-go/ant/dialect/postgres"
)

type User struct {
	ant.Base
	Name  string
	Email string `ant:"unique"`
	Age   int
	Bio   *string
	Posts []Post
}

type Post struct {
	ant.Base
	UserID    int64
	Title     string
	Published bool
	User      *User
}

type AppDB struct {
	ant.Context
	Users ant.Table[User]
	Posts ant.Table[Post]
}

var Us = ant.Fields[User]()

func db() *AppDB {
	pool, err := sql.Open("pgx", "postgres://nobody@127.0.0.1:1/none")
	if err != nil {
		log.Fatal(err)
	}

	out, err := ant.Wrap[AppDB](postgres.Dialect, pool)
	if err != nil {
		log.Fatal(err)
	}

	return out
}

func show(text string, args []any, err error) {
	if err != nil {
		log.Fatal(err)
	}

	fmt.Println(text)
	fmt.Println(args)
}

func main() {
	show(db().Users.Column(&Us.ID).Where(ant.Or(ant.Lt(&Us.Age, 18), ant.IsNull(&Us.Bio))).ToSQL())
}
Output:
SELECT "users"."id" FROM "users" WHERE ("users"."age" < $1 OR "users"."bio" IS NULL)
[18]

func Past

func Past(field *time.Time) Cond

Past matches rows where a time column is before the database's current time.

func SQLCond

func SQLCond(text string, args ...any) Cond

SQLCond returns a condition from raw SQL. Each ? takes the next argument. An argument that is an Expr renders inline, and any other is a bound value. Never build the text from untrusted values.

func Today

func Today(field *time.Time) Cond

Today matches rows where a time column falls on the database's current date.

type Conflict

type Conflict struct {
	// contains filtered or unexported fields
}

Conflict says what to do when an insert hits a unique constraint. Start one with OnConflict.

func OnConflict

func OnConflict[V any](field *V) *Conflict

OnConflict names the column of the unique constraint. Add more with And.

func (*Conflict) And

func (c *Conflict) And(ref FieldRef) *Conflict

And adds a column for a constraint on several columns.

func (*Conflict) DoNothing

func (c *Conflict) DoNothing() *Conflict

DoNothing keeps the existing row.

func (*Conflict) DoUpdate

func (c *Conflict) DoUpdate(fields ...FieldRef) *Conflict

DoUpdate overwrites only the named columns of the existing row. By default, every column except the key and the constraint's own is overwritten.

func (*Conflict) Set

func (c *Conflict) Set(assignments ...Assignment) *Conflict

Set assigns values on conflict, as well as any named in DoUpdate.

type Context

type Context struct {
	// contains filtered or unexported fields
}

Context is embedded in a user's database struct. Its methods are promoted, so they are called directly on that struct.

func (*Context) ClearTracked

func (c *Context) ClearTracked()

ClearTracked stops tracking everything, forgetting unsaved changes.

func (*Context) Close

func (c *Context) Close() error

Close closes the underlying connection pool, for every context that shares it.

func (*Context) Commit

func (c *Context) Commit() error

Commit ends a transaction from Begin, keeping its changes.

func (*Context) DB

func (c *Context) DB() *sql.DB

DB returns the underlying pool, for settings such as SetMaxOpenConns.

func (*Context) DebugTracked

func (c *Context) DebugTracked() string

DebugTracked describes every tracked entity, for logs and debugging.

func (*Context) Dialect

func (c *Context) Dialect() dialect.Dialect

Dialect returns the dialect the context was opened with.

func (*Context) EnsureCreated

func (c *Context) EnsureCreated(ctx context.Context) error

EnsureCreated creates the tables, indexes, and foreign keys of models that have no table yet, directly from the structs, and leaves existing tables as they are. It is for tests and prototypes. Use migrations for anything with data to keep.

func (*Context) EnsureDeleted

func (c *Context) EnsureDeleted(ctx context.Context) error

EnsureDeleted drops the tables of the context's models, with their data and whatever depends on them. It is for tests.

func (*Context) HasChanges

func (c *Context) HasChanges() bool

HasChanges reports whether SaveChanges has anything to write.

func (*Context) Release

func (c *Context) Release(ctx context.Context, name string) error

Release forgets a savepoint, keeping its changes.

func (*Context) Rollback

func (c *Context) Rollback() error

Rollback ends a transaction from Begin, discarding its changes.

func (*Context) RollbackTo

func (c *Context) RollbackTo(ctx context.Context, name string) error

RollbackTo undoes the statements since a savepoint. The tracker is not rewound, so reload entities whose changes it undid.

func (*Context) SaveChanges

func (c *Context) SaveChanges(ctx context.Context) error

SaveChanges writes every change to tracked entities in one transaction: inserts and updates with parents first, then deletes with children first. It updates only the columns that changed. If anything fails, nothing is saved and the tracker keeps its pending changes, so the call can be retried.

func (*Context) Savepoint

func (c *Context) Savepoint(ctx context.Context, name string) error

Savepoint marks a point to return to with RollbackTo.

func (*Context) Tx

func (c *Context) Tx[DB any](ctx context.Context, f func(tx *DB) error, options ...TxOption) error

Tx runs f in a transaction, passing a context of the same type bound to it. The transaction commits if f returns nil, and rolls back if it returns an error or panics. Inside a transaction, Tx uses a savepoint instead, so only the inner part rolls back. The context shares this one's tracker, and a rollback restores the tracker's view of what was saved.

type CursorPage

type CursorPage[T any] struct {
	Items []*T
	// Next is the cursor for the following page, empty on the last page.
	Next string
}

CursorPage is one page from After.

type DeletedAt

type DeletedAt = meta.DeletedAt

DeletedAt is the type of a soft delete field. A model with one is marked deleted instead of removed, and queries skip such rows unless asked.

type Diagnostics

type Diagnostics struct {
	// Budget alerts when statements in a scope spend longer than this in total,
	// which a slow-query log cannot see if each one is fast.
	Budget time.Duration
	// RepeatLimit alerts when one statement runs more than this many times in a
	// scope, which is what an N+1 pattern looks like.
	RepeatLimit int
	// Alert receives warnings. The default logs them with slog.
	Alert func(ctx context.Context, a Alert)
}

Diagnostics configures warnings about query patterns. They need a scope to count in, usually one web request: pass a context from Watch to the calls in it.

type Entity

type Entity[E Entity[E]] interface {
	Configure(b *ModelBuilder[E])
}

Entity is implemented by models that override conventions. Configure runs once, when a context with the model opens, or on first use by Raw.

type EntityEntry

type EntityEntry[T any] struct {
	// contains filtered or unexported fields
}

EntityEntry inspects one entity's tracking state.

func Entry

func Entry[T any](h Handle, entity *T) *EntityEntry[T]

Entry returns the tracking state of an entity. An untracked entity is Detached.

func (*EntityEntry[T]) Changed

func (e *EntityEntry[T]) Changed() []string

Changed returns the names of the fields that differ from what was loaded.

func (*EntityEntry[T]) State

func (e *EntityEntry[T]) State() State

State returns the entity's state, comparing it with what was loaded.

type Events

type Events struct {
	// Saving runs before SaveChanges writes.
	Saving func(ctx context.Context, changes []Change)
	// Saved runs after SaveChanges commits, or, in a transaction, after it wrote.
	Saved func(ctx context.Context, changes []Change)
	// SaveFailed runs when SaveChanges fails.
	SaveFailed func(ctx context.Context, err error)
	// Tracked runs when the tracker starts following an entity. It must not call
	// back into the context.
	Tracked func(model string, entity any)
	// StateChanged runs when an entity is marked with a new state, such as by
	// Remove. It must not call back into the context.
	StateChanged func(model string, entity any, from, to State)
}

Events are callbacks on a context's unit of work. Interceptors watch statements, and observers watch one model, and these watch the unit of work as a whole. A nil callback is skipped. They cannot stop anything: use a SaveInterceptor for that.

type Expr

type Expr[V any] struct {
	// contains filtered or unexported fields
}

Expr is a typed SQL expression: a column, an aggregate, or a raw fragment. V is the Go type it produces, so comparisons and mappings are checked by the compiler.

func Avg

func Avg[V Number](field *V) Expr[float64]

Avg averages a column.

func AvgRelated

func AvgRelated[R any, V Number](rel *[]R, field *V, conds ...Cond) Expr[sql.Null[float64]]

AvgRelated averages a field of the related rows. It is NULL if there are none.

func Coalesce

func Coalesce[V any](
	field *V,
	fallback V,
) Expr[V]

Coalesce returns the first non-NULL of field and fallback.

func Col

func Col[V any](field *V) Expr[V]

Col returns the expression for a column.

func Collate

func Collate[V ~string](field *V, collation string) Expr[V]

Collate applies a collation to a text column.

func Count

func Count() Expr[int64]

Count counts rows.

func CountDistinct

func CountDistinct[V any](
	field *V,
) Expr[int64]

CountDistinct counts the different non-NULL values of field.

func CountOf

func CountOf[V any](field *V) Expr[int64]

CountOf counts rows where field is not NULL.

func CountRelated

func CountRelated[R any](rel *[]R, conds ...Cond) Expr[int64]

CountRelated counts the rows related through a one-to-many relation that meet conds. Use it with Query.With or in a Mapping.

func DatePart

func DatePart(field *time.Time, part string) Expr[float64]

DatePart extracts part of a date column, such as "year" or "month".

func DenseRank

func DenseRank() Expr[int64]

DenseRank ranks rows within a window, with no gaps after ties.

func FailedExpr

func FailedExpr[V any](err error) Expr[V]

FailedExpr returns an expression that reports err at the terminal call, for helpers that can fail while building one.

func Func

func Func[R any](name string, args ...any) Expr[R]

Func calls a database function by name. Arguments that are an Expr render inline, and any other is a bound value. The name is not quoted, so it must never come from untrusted input.

func Lag

func Lag[V any](
	field *V,
) Expr[sql.Null[V]]

Lag returns field from the previous row of a window.

func Lead

func Lead[V any](
	field *V,
) Expr[sql.Null[V]]

Lead returns field from the next row of a window.

func Length

func Length[V ~string](field *V) Expr[int]

Length returns the length of a text column, in characters.

func Lower

func Lower[V ~string](field *V) Expr[V]

Lower lowercases a text column.

func Max

func Max[V Ordered](field *V) Expr[V]

Max returns a column's largest value.

func MaxRelated

func MaxRelated[R any, V Ordered](rel *[]R, field *V, conds ...Cond) Expr[sql.Null[V]]

MaxRelated returns the largest value of a field of the related rows. It is NULL if there are none.

func Min

func Min[V Ordered](field *V) Expr[V]

Min returns a column's smallest value.

func MinRelated

func MinRelated[R any, V Ordered](rel *[]R, field *V, conds ...Cond) Expr[sql.Null[V]]

MinRelated returns the smallest value of a field of the related rows. It is NULL if there are none.

func Rank

func Rank() Expr[int64]

Rank ranks rows within a window, leaving gaps after ties.

func RowNumber

func RowNumber() Expr[int64]

RowNumber numbers rows within a window, from 1.

func SQL

func SQL[V any](text string, args ...any) Expr[V]

SQL returns an expression from raw SQL. Each ? takes the next argument. An argument that is an Expr renders inline, and any other is a bound value. Never build the text from untrusted values.

func Sum

func Sum[V Number](field *V) Expr[V]

Sum adds up a column.

func SumRelated

func SumRelated[R any, V Number](rel *[]R, field *V, conds ...Cond) Expr[V]

SumRelated adds up a field of the related rows, giving zero if there are none.

func Upper

func Upper[V ~string](field *V) Expr[V]

Upper uppercases a text column.

func (Expr[V]) Asc

func (e Expr[V]) Asc() Sort

Asc orders by the expression, ascending.

func (Expr[V]) Desc

func (e Expr[V]) Desc() Sort

Desc orders by the expression, descending.

func (Expr[V]) Div

func (e Expr[V]) Div(v V) Expr[V]

Div divides by v.

func (Expr[V]) Eq

func (e Expr[V]) Eq(v V) Cond

Eq matches rows where the expression equals v.

func (Expr[V]) EqExpr

func (e Expr[V]) EqExpr(other Expr[V]) Cond

EqExpr matches rows where two expressions are equal.

func (Expr[V]) Filter

func (e Expr[V]) Filter(conds ...Cond) Expr[V]

Filter limits an aggregate to rows that meet conds.

func (Expr[V]) Gt

func (e Expr[V]) Gt(v V) Cond

Gt matches rows where the expression is greater than v.

func (Expr[V]) GtExpr

func (e Expr[V]) GtExpr(other Expr[V]) Cond

GtExpr matches rows where the expression is greater than another expression.

func (Expr[V]) Gte

func (e Expr[V]) Gte(v V) Cond

Gte matches rows where the expression is greater than or equal to v.

func (Expr[V]) IsNotNull

func (e Expr[V]) IsNotNull() Cond

IsNotNull matches rows where the expression is not NULL.

func (Expr[V]) IsNull

func (e Expr[V]) IsNull() Cond

IsNull matches rows where the expression is NULL.

func (Expr[V]) Lt

func (e Expr[V]) Lt(v V) Cond

Lt matches rows where the expression is less than v.

func (Expr[V]) LtExpr

func (e Expr[V]) LtExpr(other Expr[V]) Cond

LtExpr matches rows where the expression is less than another expression.

func (Expr[V]) Lte

func (e Expr[V]) Lte(v V) Cond

Lte matches rows where the expression is less than or equal to v.

func (Expr[V]) Minus

func (e Expr[V]) Minus(v V) Expr[V]

Minus subtracts v.

func (Expr[V]) Ne

func (e Expr[V]) Ne(v V) Cond

Ne matches rows where the expression does not equal v.

func (Expr[V]) Over

func (e Expr[V]) Over(w Window) Expr[V]

Over makes an aggregate or ranking function a window function.

func (Expr[V]) Plus

func (e Expr[V]) Plus(v V) Expr[V]

Plus adds v.

func (Expr[V]) Times

func (e Expr[V]) Times(v V) Expr[V]

Times multiplies by v.

type FieldConvention

type FieldConvention struct {
	Name   string
	Column string
	Type   reflect.Type
	// contains filtered or unexported fields
}

FieldConvention is one column, offered to a rule from WithConvention.

func (*FieldConvention) SetLength

func (f *FieldConvention) SetLength(n int)

SetLength limits a string column, as ColumnBuilder.Length does.

func (*FieldConvention) SetSensitive

func (f *FieldConvention) SetSensitive()

SetSensitive keeps the column out of logs, as ColumnBuilder.Sensitive does.

func (*FieldConvention) SetType

func (f *FieldConvention) SetType(sql string)

SetType sets the database type, as ColumnBuilder.Type does.

type FieldRef

type FieldRef struct {
	// contains filtered or unexported fields
}

FieldRef names a column of any type, from F.

func F

func F[V any](field *V) FieldRef

F names a column, for APIs that take a list of columns of different types.

type ForeignKeyBuilder

type ForeignKeyBuilder struct {
	// contains filtered or unexported fields
}

ForeignKeyBuilder configures a foreign key from ModelBuilder.ForeignKey.

func (*ForeignKeyBuilder) Name

Name names the constraint, in place of table_columns_fkey.

func (*ForeignKeyBuilder) NoIndex

func (k *ForeignKeyBuilder) NoIndex() *ForeignKeyBuilder

NoIndex leaves out the index migrations otherwise add on the columns.

func (*ForeignKeyBuilder) OnDelete

func (k *ForeignKeyBuilder) OnDelete(action Action) *ForeignKeyBuilder

OnDelete sets the action when the parent is deleted.

func (*ForeignKeyBuilder) OnUpdate

func (k *ForeignKeyBuilder) OnUpdate(action Action) *ForeignKeyBuilder

OnUpdate sets the action when the parent's key changes.

func (*ForeignKeyBuilder) References

func (k *ForeignKeyBuilder) References(fields ...FieldRef) *ForeignKeyBuilder

References names the columns the key points to, in order.

type Gob

type Gob[V any] struct {
	V V
}

Gob stores a Go value in a binary column with encoding/gob, for a value that only Go reads and JSON does not fit.

func (Gob[V]) ColumnType

func (g Gob[V]) ColumnType(dialect string) string

ColumnType implements schema.Typed.

func (*Gob[V]) Scan

func (g *Gob[V]) Scan(src any) error

Scan implements sql.Scanner.

func (Gob[V]) Value

func (g Gob[V]) Value() (driver.Value, error)

Value implements driver.Valuer.

type GraphNode

type GraphNode struct {
	Model  string // the entity's type name
	Entity any    // a pointer to the entity
	Parent any    // a pointer to the entity it was reached from, or nil
	// HasKey reports whether the entity has a key already, which usually means the
	// row exists.
	HasKey bool
}

GraphNode is one entity TrackGraph reached.

type Grouped

type Grouped struct {
	// contains filtered or unexported fields
}

Grouped is a query with GROUP BY. Read it with Project.

func Then

func Then[V any](g *Grouped, field *V) *Grouped

Then groups by another column as well.

func (*Grouped) Having

func (g *Grouped) Having(conds ...Cond) *Grouped

Having keeps groups that meet conds, which can use aggregates such as ant.Count().Gt(1).

func (*Grouped) Limit

func (g *Grouped) Limit(limit int) *Grouped

Limit caps the number of groups returned.

func (*Grouped) Offset

func (g *Grouped) Offset(offset int) *Grouped

Offset skips the first groups.

func (*Grouped) Order

func (g *Grouped) Order(terms ...Sort) *Grouped

Order orders the groups by terms from Asc and Desc.

type Handle

type Handle interface {
	// contains filtered or unexported methods
}

Handle is any value that embeds Context, such as a *AppDB.

type IndexBuilder

type IndexBuilder struct {
	// contains filtered or unexported fields
}

IndexBuilder configures an index.

func (*IndexBuilder) And

func (i *IndexBuilder) And[V any](field *V) *IndexBuilder

And adds a column to the index.

func (*IndexBuilder) AndExpr

func (i *IndexBuilder) AndExpr(expr string) *IndexBuilder

AndExpr adds a SQL expression to the index.

func (*IndexBuilder) Concurrently

func (i *IndexBuilder) Concurrently() *IndexBuilder

Concurrently builds the index without blocking writes, when it is added to a table that already exists. PostgreSQL cannot do that in a transaction, so the migration holding it runs without one.

func (*IndexBuilder) Desc

func (i *IndexBuilder) Desc() *IndexBuilder

Desc sorts the most recently added column in descending order.

func (*IndexBuilder) Include

func (i *IndexBuilder) Include[V any](field *V) *IndexBuilder

Include stores a column in the index without indexing it.

func (*IndexBuilder) Name

func (i *IndexBuilder) Name(name string) *IndexBuilder

Name sets the index name, in place of table_columns_idx.

func (*IndexBuilder) Unique

func (i *IndexBuilder) Unique() *IndexBuilder

Unique makes the index unique.

func (*IndexBuilder) Using

func (i *IndexBuilder) Using(method string) *IndexBuilder

Using sets the index method, such as gin.

func (*IndexBuilder) Where

func (i *IndexBuilder) Where(condition string) *IndexBuilder

Where makes the index partial, covering rows that match a SQL condition.

type InsertOption

type InsertOption func(*insertOptions)

InsertOption configures InsertMany.

func BatchSize

func BatchSize(n int) InsertOption

BatchSize sets the most rows in one statement. The default is 1000.

func WithConflict

func WithConflict(c *Conflict) InsertOption

WithConflict applies a Conflict to every row of InsertMany.

type Interceptor

type Interceptor = exec.Interceptor

Interceptor observes every statement a context runs, for logging, metrics, and tracing. Before can return a new context, as tracing needs, and an error from it stops the statement.

type JSON

type JSON[V any] struct {
	V V
}

JSON stores a Go value in a JSON column. The zero value of V is stored as its JSON form, and a NULL column reads as the zero value.

func (JSON[V]) ColumnType

func (j JSON[V]) ColumnType(dialect string) string

ColumnType implements schema.Typed.

func (JSON[V]) MarshalJSON

func (j JSON[V]) MarshalJSON() ([]byte, error)

MarshalJSON writes the value inside, so that an API response shows the document and not this wrapper.

func (*JSON[V]) Scan

func (j *JSON[V]) Scan(src any) error

Scan implements sql.Scanner.

func (*JSON[V]) UnmarshalJSON

func (j *JSON[V]) UnmarshalJSON(data []byte) error

UnmarshalJSON reads the value inside.

func (JSON[V]) Value

func (j JSON[V]) Value() (driver.Value, error)

Value implements driver.Valuer.

type Join2

type Join2[A, B any] struct {
	// contains filtered or unexported fields
}

Join2 is a query across two joined models.

func JoinLateral

func JoinLateral[T, J any](q *Query[T], sub *Query[J], on ...Cond) *Join2[T, J]

JoinLateral joins a query that runs once per row of T, so its conditions can refer to T's columns. With Limit, this reads the top rows per parent, which a plain join cannot express:

ant.JoinLateral(db.Users.Query(), db.Posts.
	Where(ant.EqCol(&P.UserID, &U.ID)).
	OrderByDesc(&P.CreatedAt).Limit(3))

func JoinSub

func JoinSub[T, J any](q *Query[T], sub *Query[J], on ...Cond) *Join2[T, J]

JoinSub joins the results of another query. Probes of J refer to its columns, so sub can filter, group, or limit first. Its global filters already applied.

func LeftJoinLateral

func LeftJoinLateral[T, J any](q *Query[T], sub *Query[J], on ...Cond) *Join2[T, J]

LeftJoinLateral is JoinLateral, keeping rows of T with no match.

func LeftJoinSub

func LeftJoinSub[T, J any](q *Query[T], sub *Query[J], on ...Cond) *Join2[T, J]

LeftJoinSub is JoinSub, keeping rows of T with no match.

func (*Join2[A, B]) Distinct

func (j *Join2[A, B]) Distinct() *Join2[A, B]

Distinct removes duplicate rows.

func (*Join2[A, B]) GroupBy

func (j *Join2[A, B]) GroupBy[V any](field *V) *Grouped

GroupBy groups the joined rows by a column. See Query.GroupBy.

func (*Join2[A, B]) GroupByExpr

func (j *Join2[A, B]) GroupByExpr[V any](e Expr[V]) *Grouped

GroupByExpr groups the joined rows by an expression.

func (*Join2[A, B]) Join

func (j *Join2[A, B]) Join[C any](rel **C) *Join3[A, B, C]

Join adds a many-to-one or one-to-one relation of any model in the query.

func (*Join2[A, B]) JoinMany

func (j *Join2[A, B]) JoinMany[C any](rel *[]C) *Join3[A, B, C]

JoinMany adds a one-to-many relation of any model in the query.

func (*Join2[A, B]) JoinOn

func (j *Join2[A, B]) JoinOn[C any](on ...Cond) *Join3[A, B, C]

JoinOn adds a model on conditions.

func (*Join2[A, B]) LeftJoin

func (j *Join2[A, B]) LeftJoin[C any](rel **C) *Join3[A, B, C]

LeftJoin adds a many-to-one or one-to-one relation, keeping unmatched rows.

func (*Join2[A, B]) LeftJoinMany

func (j *Join2[A, B]) LeftJoinMany[C any](rel *[]C) *Join3[A, B, C]

LeftJoinMany adds a one-to-many relation, keeping rows with no related row.

func (*Join2[A, B]) LeftJoinOn

func (j *Join2[A, B]) LeftJoinOn[C any](on ...Cond) *Join3[A, B, C]

LeftJoinOn adds a model on conditions, keeping unmatched rows.

func (*Join2[A, B]) Limit

func (j *Join2[A, B]) Limit(limit int) *Join2[A, B]

Limit caps the number of rows returned.

func (*Join2[A, B]) Offset

func (j *Join2[A, B]) Offset(offset int) *Join2[A, B]

Offset skips the first rows.

func (*Join2[A, B]) Order

func (j *Join2[A, B]) Order(terms ...Sort) *Join2[A, B]

Order orders results by terms from Asc and Desc, replacing any earlier ordering.

func (*Join2[A, B]) OrderBy

func (j *Join2[A, B]) OrderBy[V Ordered](field *V) *Join2[A, B]

OrderBy orders results by a field of any joined model, ascending, replacing any earlier ordering.

func (*Join2[A, B]) OrderByDesc

func (j *Join2[A, B]) OrderByDesc[V Ordered](field *V) *Join2[A, B]

OrderByDesc orders results by a field of any joined model, descending, replacing any earlier ordering.

func (*Join2[A, B]) Select

func (j *Join2[A, B]) Select[R any](f func(*A, *B) R) *Projection[R]

Select projects each joined row through f. The join, filters, and ordering run in the database, and f runs in Go once per row. Every stored column of each model is fetched, because Go cannot inspect what f reads. Use Project to fetch only some columns. A side a left, right, or full join did not match is nil. Results are not tracked.

func (*Join2[A, B]) Where

func (j *Join2[A, B]) Where(c ...Cond) *Join2[A, B]

Where adds conditions on any joined model.

type Join3

type Join3[A, B, C any] struct {
	// contains filtered or unexported fields
}

Join3 is a query across three joined models.

func (*Join3[A, B, C]) Distinct

func (j *Join3[A, B, C]) Distinct() *Join3[A, B, C]

Distinct removes duplicate rows.

func (*Join3[A, B, C]) GroupBy

func (j *Join3[A, B, C]) GroupBy[V any](field *V) *Grouped

GroupBy groups the joined rows by a column. See Query.GroupBy.

func (*Join3[A, B, C]) GroupByExpr

func (j *Join3[A, B, C]) GroupByExpr[V any](e Expr[V]) *Grouped

GroupByExpr groups the joined rows by an expression.

func (*Join3[A, B, C]) Join

func (j *Join3[A, B, C]) Join[D any](rel **D) *Join4[A, B, C, D]

Join adds a many-to-one or one-to-one relation of any model in the query.

func (*Join3[A, B, C]) JoinMany

func (j *Join3[A, B, C]) JoinMany[D any](rel *[]D) *Join4[A, B, C, D]

JoinMany adds a one-to-many relation of any model in the query.

func (*Join3[A, B, C]) JoinOn

func (j *Join3[A, B, C]) JoinOn[D any](on ...Cond) *Join4[A, B, C, D]

JoinOn adds a model on conditions.

func (*Join3[A, B, C]) LeftJoin

func (j *Join3[A, B, C]) LeftJoin[D any](rel **D) *Join4[A, B, C, D]

LeftJoin adds a many-to-one or one-to-one relation, keeping unmatched rows.

func (*Join3[A, B, C]) LeftJoinMany

func (j *Join3[A, B, C]) LeftJoinMany[D any](rel *[]D) *Join4[A, B, C, D]

LeftJoinMany adds a one-to-many relation, keeping rows with no related row.

func (*Join3[A, B, C]) LeftJoinOn

func (j *Join3[A, B, C]) LeftJoinOn[D any](on ...Cond) *Join4[A, B, C, D]

LeftJoinOn adds a model on conditions, keeping unmatched rows.

func (*Join3[A, B, C]) Limit

func (j *Join3[A, B, C]) Limit(limit int) *Join3[A, B, C]

Limit caps the number of rows returned.

func (*Join3[A, B, C]) Offset

func (j *Join3[A, B, C]) Offset(offset int) *Join3[A, B, C]

Offset skips the first rows.

func (*Join3[A, B, C]) Order

func (j *Join3[A, B, C]) Order(terms ...Sort) *Join3[A, B, C]

Order orders results by terms from Asc and Desc, replacing any earlier ordering.

func (*Join3[A, B, C]) OrderBy

func (j *Join3[A, B, C]) OrderBy[V Ordered](field *V) *Join3[A, B, C]

OrderBy orders results by a field of any joined model, ascending, replacing any earlier ordering.

func (*Join3[A, B, C]) OrderByDesc

func (j *Join3[A, B, C]) OrderByDesc[V Ordered](field *V) *Join3[A, B, C]

OrderByDesc orders results by a field of any joined model, descending, replacing any earlier ordering.

func (*Join3[A, B, C]) Select

func (j *Join3[A, B, C]) Select[R any](f func(*A, *B, *C) R) *Projection[R]

Select projects each joined row through f. The join, filters, and ordering run in the database, and f runs in Go once per row. Every stored column of each model is fetched, because Go cannot inspect what f reads. Use Project to fetch only some columns. A side a left, right, or full join did not match is nil. Results are not tracked.

func (*Join3[A, B, C]) Where

func (j *Join3[A, B, C]) Where(c ...Cond) *Join3[A, B, C]

Where adds conditions on any joined model.

type Join4

type Join4[A, B, C, D any] struct {
	// contains filtered or unexported fields
}

Join4 is a query across four joined models.

func (*Join4[A, B, C, D]) Distinct

func (j *Join4[A, B, C, D]) Distinct() *Join4[A, B, C, D]

Distinct removes duplicate rows.

func (*Join4[A, B, C, D]) GroupBy

func (j *Join4[A, B, C, D]) GroupBy[V any](field *V) *Grouped

GroupBy groups the joined rows by a column. See Query.GroupBy.

func (*Join4[A, B, C, D]) GroupByExpr

func (j *Join4[A, B, C, D]) GroupByExpr[V any](
	e Expr[V],
) *Grouped

GroupByExpr groups the joined rows by an expression.

func (*Join4[A, B, C, D]) Limit

func (j *Join4[A, B, C, D]) Limit(limit int) *Join4[A, B, C, D]

Limit caps the number of rows returned.

func (*Join4[A, B, C, D]) Offset

func (j *Join4[A, B, C, D]) Offset(offset int) *Join4[A, B, C, D]

Offset skips the first rows.

func (*Join4[A, B, C, D]) Order

func (j *Join4[A, B, C, D]) Order(terms ...Sort) *Join4[A, B, C, D]

Order orders results by terms from Asc and Desc, replacing any earlier ordering.

func (*Join4[A, B, C, D]) OrderBy

func (j *Join4[A, B, C, D]) OrderBy[V Ordered](field *V) *Join4[A, B, C, D]

OrderBy orders results by a field of any joined model, ascending, replacing any earlier ordering.

func (*Join4[A, B, C, D]) OrderByDesc

func (j *Join4[A, B, C, D]) OrderByDesc[V Ordered](field *V) *Join4[A, B, C, D]

OrderByDesc orders results by a field of any joined model, descending, replacing any earlier ordering.

func (*Join4[A, B, C, D]) Select

func (j *Join4[A, B, C, D]) Select[R any](f func(*A, *B, *C, *D) R) *Projection[R]

Select projects each joined row through f. The join, filters, and ordering run in the database, and f runs in Go once per row. Every stored column of each model is fetched, because Go cannot inspect what f reads. Use Project to fetch only some columns. A side a left, right, or full join did not match is nil. Results are not tracked.

func (*Join4[A, B, C, D]) Where

func (j *Join4[A, B, C, D]) Where(c ...Cond) *Join4[A, B, C, D]

Where adds conditions on any joined model.

type KeyBuilder

type KeyBuilder struct {
	// contains filtered or unexported fields
}

KeyBuilder configures a primary key.

func (*KeyBuilder) NotGenerated

func (k *KeyBuilder) NotGenerated() *KeyBuilder

NotGenerated means the application always supplies the key, so a zero key is written as zero.

type LogOption

type LogOption func(*logging)

LogOption configures WithLogger.

func LogValues

func LogValues() LogOption

LogValues includes argument values in logs. They are left out by default, because they may be passwords or personal data.

func SlowThreshold

func SlowThreshold(d time.Duration) LogOption

SlowThreshold logs statements at or over d at warning level.

type Mapping

type Mapping struct {
	// contains filtered or unexported fields
}

Mapping sets one field of a projection, from Into or IntoExpr.

func Into

func Into[V any](dst *V, src *V) Mapping

Into maps a column to a field of the projected type. dst is a field of a probe of that type, and src a field of a probe in the query.

func IntoExpr

func IntoExpr[V any](dst *V, src Expr[V]) Mapping

IntoExpr maps an expression to a field of the projected type.

type Merger

type Merger[T any] struct {
	// contains filtered or unexported fields
}

Merger builds a MERGE. Start one with Merge.

func Merge

func Merge[T, S any](t *Table[T], source *Query[S], on ...Cond) *Merger[T]

Merge combines source rows into T's table, deciding per row what to do, which Upsert cannot: one statement can update, insert, and delete. Clauses run in the order added, and the first that fits a row wins. PostgreSQL has it from version 15.

ant.Merge(&db.Stock, db.Staged.Query(), ant.EqCol(&Stock.SKU, &Staged.SKU)).
	WhenMatchedIf(ant.Eq(&Staged.Count, 0)).Delete().
	WhenMatched(ant.SetExpr(&Stock.Count, ant.Col(&Staged.Count))).
	WhenNotMatched(ant.Into(&Stock.SKU, &Staged.SKU), ant.Into(&Stock.Count, &Staged.Count)).
	Exec(ctx)

func (*Merger[T]) Delete

func (m *Merger[T]) Delete() *Merger[T]

Delete turns the last clause, which must have no assignments, into a delete.

func (*Merger[T]) Exec

func (m *Merger[T]) Exec(ctx context.Context) (int64, error)

Exec runs the statement and returns the number of rows changed.

func (*Merger[T]) WhenMatched

func (m *Merger[T]) WhenMatched(set ...Assignment) *Merger[T]

WhenMatched updates target rows that have a source row.

func (*Merger[T]) WhenMatchedIf

func (m *Merger[T]) WhenMatchedIf(c Cond, set ...Assignment) *Merger[T]

WhenMatchedIf updates matched rows that also meet a condition.

func (*Merger[T]) WhenNotMatched

func (m *Merger[T]) WhenNotMatched(insert ...Mapping) *Merger[T]

WhenNotMatched inserts source rows with no target row. Each mapping names a target column and the source value for it.

type Mixin

type Mixin struct {
	// contains filtered or unexported fields
}

Mixin configures whichever model embeds a MixinConfigurer.

func (*Mixin) Check

func (m *Mixin) Check(name, expr string)

Check adds a check constraint.

func (*Mixin) Eq

func (m *Mixin) Eq(column string, value any) Cond

Eq is a condition on a column by name, for use in a Filter. It works in joins, as ant.Eq does, because it knows which table it belongs to.

func (*Mixin) Filter

func (m *Mixin) Filter(name string, filter func(context.Context) Cond)

Filter adds a global filter, as ModelBuilder.Filter does.

func (*Mixin) Index

func (m *Mixin) Index(columns ...string) *IndexBuilder

Index adds an index on columns of the model, named after them.

func (*Mixin) Table

func (m *Mixin) Table() string

Table returns the model's table name.

type MixinConfigurer

type MixinConfigurer interface {
	ConfigureMixin(m *Mixin)
}

MixinConfigurer is implemented by a struct meant to be embedded in many models, to add the configuration that goes with its fields. It works by column name, since it does not know which model embeds it.

type ModelBuilder

type ModelBuilder[E any] struct {
	// contains filtered or unexported fields
}

ModelBuilder collects configuration for model E. Errors surface from Open.

func (*ModelBuilder[E]) BelongsTo

func (b *ModelBuilder[E]) BelongsTo[R any](rel **R) *RelationBuilder

BelongsTo configures a many-to-one relation.

func (*ModelBuilder[E]) Check

func (b *ModelBuilder[E]) Check(name, expr string)

Check adds a check constraint. expr is SQL, as in age >= 0.

func (*ModelBuilder[E]) Column

func (b *ModelBuilder[E]) Column[V any](field *V) *ColumnBuilder

Column configures how a column is declared in migrations.

func (*ModelBuilder[E]) Comment

func (b *ModelBuilder[E]) Comment(text string)

Comment describes the table in the database.

func (*ModelBuilder[E]) Comparer

func (b *ModelBuilder[E]) Comparer[V any](field *V, equal func(a, b V) bool)

Comparer sets how change tracking compares a field, in place of a deep comparison. It suits a slice whose order does not matter, or a map with volatile entries. A change the comparer does not see is not saved.

func (*ModelBuilder[E]) CompositeKey

func (b *ModelBuilder[E]) CompositeKey(fields ...FieldRef)

CompositeKey sets a primary key of several columns. Rows are read with FindKey.

func (*ModelBuilder[E]) Discriminator

func (b *ModelBuilder[E]) Discriminator[V comparable](field *V, value V)

Discriminator shares a table between several models: rows of this one hold value in field. Queries see only those rows, and inserts set it. It is single-table inheritance, without the inheritance Go does not have:

func (Dog) Configure(b *ant.ModelBuilder[Dog]) {
	b.Table("animals")
	b.Discriminator(&D.Kind, "dog")
}

Migrations merge the models into one table, and a column only some of them declare allows NULL.

func (*ModelBuilder[E]) ExcludeFromMigrations

func (b *ModelBuilder[E]) ExcludeFromMigrations()

ExcludeFromMigrations means migrations leave this model's table alone, because something else owns it. Queries still work.

func (*ModelBuilder[E]) Filter

func (b *ModelBuilder[E]) Filter(name string, filter func(context.Context) Cond)

Filter adds a global filter, applied to every query of this model, including joins and subqueries. It receives the call's context, which suits tenancy. A filter that returns a Cond with an error fails the query. Query.IgnoreFilters switches it off.

func (*ModelBuilder[E]) ForeignKey

func (b *ModelBuilder[E]) ForeignKey(fields ...FieldRef) *ForeignKeyBuilder

ForeignKey declares a foreign key on its own, for one no relation can express, such as a key of several columns. It adds a constraint and nothing else, so relations and queries do not know about it.

b.ForeignKey(ant.F(&L.OrderID), ant.F(&L.Region)).References(ant.F(&O.ID), ant.F(&O.Region))

func (*ModelBuilder[E]) HasMany

func (b *ModelBuilder[E]) HasMany[R any](
	rel *[]R,
) *RelationBuilder

HasMany configures a one-to-many relation.

func (*ModelBuilder[E]) HasManyThrough

func (b *ModelBuilder[E]) HasManyThrough[R, A, C any](
	rel *[]R,
	parentKey *A,
	throughKey *C,
) *RelationBuilder

HasManyThrough configures a read-only relation that crosses a table in between: a Country has many Posts through Users. parentKey is the column on the middle model that refers to the owner, and throughKey the column on the target that refers to the middle model's key.

b.HasManyThrough(&C.Posts, &U.CountryID, &P.UserID)

func (*ModelBuilder[E]) HasOne

func (b *ModelBuilder[E]) HasOne[R any](
	rel **R,
) *RelationBuilder

HasOne configures a one-to-one relation.

func (*ModelBuilder[E]) HasOneThrough

func (b *ModelBuilder[E]) HasOneThrough[R, A, C any](
	rel **R,
	parentKey *A,
	throughKey *C,
) *RelationBuilder

HasOneThrough is HasManyThrough for a single related row.

func (*ModelBuilder[E]) Index

func (b *ModelBuilder[E]) Index[V any](field *V) *IndexBuilder

Index declares an index on field. Add more columns with And, and more options with the other IndexBuilder methods.

func (*ModelBuilder[E]) IndexExpr

func (b *ModelBuilder[E]) IndexExpr(expr string) *IndexBuilder

IndexExpr declares an index on a SQL expression, such as lower(email). Name it with Name.

func (*ModelBuilder[E]) Key

func (b *ModelBuilder[E]) Key[V any](field *V) *KeyBuilder

Key sets the primary key. An integer key is database-generated unless NotGenerated says otherwise.

func (*ModelBuilder[E]) KeyName

func (b *ModelBuilder[E]) KeyName(name string)

KeyName names the primary key constraint, in place of table_pkey. It is for matching an existing database.

func (*ModelBuilder[E]) ManyToMany

func (b *ModelBuilder[E]) ManyToMany[R any](rel *[]R) *RelationBuilder

ManyToMany configures a many-to-many relation through a join table, named with Through. Its columns default to the singular model names plus _id, such as user_id and role_id. For extra columns on the join table, model it instead and use two one-to-many relations.

func (*ModelBuilder[E]) NoKey

func (b *ModelBuilder[E]) NoKey()

NoKey removes the primary key, for rows with no identity.

func (*ModelBuilder[E]) NoTimestamps

func (b *ModelBuilder[E]) NoTimestamps()

NoTimestamps stops CreatedAt and UpdatedAt being maintained.

func (*ModelBuilder[E]) PartitionByHash

func (b *ModelBuilder[E]) PartitionByHash(fields ...FieldRef)

PartitionByHash declares the table partitioned by a hash of the columns.

func (*ModelBuilder[E]) PartitionByList

func (b *ModelBuilder[E]) PartitionByList(fields ...FieldRef)

PartitionByList declares the table partitioned by lists of column values.

func (*ModelBuilder[E]) PartitionByRange

func (b *ModelBuilder[E]) PartitionByRange(fields ...FieldRef)

PartitionByRange declares the table partitioned by ranges of the columns. A partitioned table's primary key and unique constraints must include them, so a time-partitioned table usually has CompositeKey(id, time). Migrations create the parent, and package postgres manages the partitions themselves. See postgres.Partitions.

func (*ModelBuilder[E]) Schema

func (b *ModelBuilder[E]) Schema(name string)

Schema sets the database schema.

func (*ModelBuilder[E]) Table

func (b *ModelBuilder[E]) Table(name string)

Table sets the table name.

func (*ModelBuilder[E]) Validate

func (b *ModelBuilder[E]) Validate[V any](field *V, rule func(V) error)

Validate adds a rule for a field, checked before Insert and SaveChanges write it, as a Validate hook is. A failure is a *ValidationError. Rules do not run for bulk writes, which load no entities. Package validate has common ones.

func (*ModelBuilder[E]) View

func (b *ModelBuilder[E]) View(name string)

View maps the model to a view, which can be queried but not written.

type ModelConvention

type ModelConvention struct {
	// contains filtered or unexported fields
}

ModelConvention is one model, offered to a rule from WithConvention.

func (*ModelConvention) Fields

func (m *ModelConvention) Fields() []*FieldConvention

Fields returns the model's columns.

func (*ModelConvention) Name

func (m *ModelConvention) Name() string

Name returns the model's type name.

func (*ModelConvention) SetTable

func (m *ModelConvention) SetTable(name string)

SetTable changes the model's table name.

func (*ModelConvention) Table

func (m *ModelConvention) Table() string

Table returns the model's table name.

type Naming

type Naming struct {
	Table  func(goName string) string
	Column func(goName string) string
}

Naming replaces the conventions that turn Go names into database names. A nil function keeps the convention. It receives the type or field name.

type Number

type Number interface {
	~int | ~int8 | ~int16 | ~int32 | ~int64 |
		~uint | ~uint8 | ~uint16 | ~uint32 | ~uint64 |
		~float32 | ~float64
}

Number is a type that SUM and AVG accept.

type Observer

type Observer[T any] struct {
	Inserted func(ctx context.Context, entity *T)
	Updated  func(ctx context.Context, entity *T)
	Deleted  func(ctx context.Context, entity *T)
}

Observer receives entities after their change is committed, so it never sees a write that rolled back. It runs once per SaveChanges or Insert entity, after the outermost transaction ends, and cannot affect the write.

type Option

type Option func(*settings)

Option configures a context in Open.

func WithConvention

func WithConvention(rule func(*ModelConvention)) Option

WithConvention adds a rule applied to every model as it is configured, before its Configure method, which can override it. Metadata is global, so a process has one set of rules, added in the first Open.

ant.WithConvention(func(m *ant.ModelConvention) {
	for _, f := range m.Fields() {
		if f.Type.Kind() == reflect.String {
			f.SetLength(255)
		}
	}
})

func WithDetailedErrors

func WithDetailedErrors() Option

WithDetailedErrors adds the failing statement to every query error's message, which helps in development. SQL text can reveal a schema, and raw SQL can hold values, so leave it off where errors reach users. FailedSQL reads the statement from an error whether this is on or not.

func WithDiagnostics

func WithDiagnostics(d Diagnostics) Option

WithDiagnostics turns on warnings. They cost a little per statement, and only count in contexts from Watch. Joining two one-to-many relations in one query multiplies its rows, and that warns too, with or without a scope.

func WithEvents

func WithEvents(e Events) Option

WithEvents registers callbacks on the context and its sessions.

func WithInterceptor

func WithInterceptor(i Interceptor) Option

WithInterceptor adds an interceptor. Several run in order, and After runs in reverse order.

func WithLogger

func WithLogger(logger *slog.Logger, options ...LogOption) Option

WithLogger logs every statement to logger: failures at error level, slow ones at warning level, and the rest at debug level. Values are never logged unless LogValues asks.

func WithLooseScan

func WithLooseScan() Option

WithLooseScan ignores result columns with no field. It is the default, and exists to say so where WithStrict is on for other contexts.

func WithNaming

func WithNaming(naming Naming) Option

WithNaming sets a naming strategy, such as PascalCase table names for an existing database. Names set explicitly are kept. Metadata is global, and each model is named once, on first use, so a process has one strategy, set in the first Open.

func WithNow

func WithNow(now func() time.Time) Option

WithNow sets the clock for automatic timestamps, which suits tests.

func WithReplicas

func WithReplicas(pools ...*sql.DB) Option

WithReplicas sends reads outside transactions to these pools in turn. Writes, locks, and transactions use the primary. Close closes them. A failing replica is an error for that read, not a retry elsewhere.

func WithRetry

func WithRetry(policy Retry) Option

WithRetry applies a retry policy to every Tx and SaveChanges of this context, unless Attempts says otherwise for one call. The work must be safe to run again, so a function given to Tx should change nothing outside the database.

func WithStickyWrites

func WithStickyWrites(d time.Duration) Option

WithStickyWrites sends a context's reads to the primary for d after each of its writes, so that replication lag cannot hide what it just wrote.

func WithStrict

func WithStrict() Option

WithStrict makes a query fail if a result column has no field, which usually means a model has fallen behind the schema, or a SELECT * picked up something new.

func WithTracking

func WithTracking(on bool) Option

WithTracking sets whether queries track what they load. It is on by default. Turn it off for contexts that only read, or that use explicit updates.

func WithoutAutoTx

func WithoutAutoTx() Option

WithoutAutoTx stops SaveChanges and Insert from opening a transaction of their own, so a failure partway leaves earlier statements in place. Use it only to control transactions yourself, or where a transaction is unavailable.

type Ordered

type Ordered interface {
	cmp.Ordered | time.Time
}

Ordered is a type with a database ordering. It extends cmp.Ordered with time.Time, which cmp.Ordered excludes.

type Outcome

type Outcome = exec.Outcome

Outcome is what happened to a statement. Duration ends when the database answered, before rows are read.

type Page

type Page[T any] struct {
	Items  []*T
	Total  int64
	Number int // from 1
	Size   int
}

Page is one page of a query, with the totals needed to show paging links.

func (*Page[T]) HasNext

func (p *Page[T]) HasNext() bool

HasNext reports whether another page follows this one.

func (*Page[T]) Pages

func (p *Page[T]) Pages() int

Pages returns the number of pages.

type Projection

type Projection[R any] struct {
	// contains filtered or unexported fields
}

Projection is a query that returns values of R. Results are not tracked.

func Project

func Project[R any](q Selectable, mappings ...Mapping) *Projection[R]

Project selects only the mapped expressions, and scans them into R. Use it instead of Select to avoid fetching whole rows:

var S = ant.Fields[Summary]()

ant.Project[Summary](db.Posts.Join(&P.User),
	ant.Into(&S.Author, &U.Name),
	ant.Into(&S.Title, &P.Title))

func (*Projection[R]) All

func (p *Projection[R]) All(ctx context.Context) (out []R, err error)

All returns every projected row, as an empty slice if there are none.

func (*Projection[R]) Each

func (p *Projection[R]) Each(ctx context.Context) iter.Seq2[R, error]

Each streams projected rows, stopping after the first error.

func (*Projection[R]) First

func (p *Projection[R]) First(ctx context.Context) (*R, error)

First returns the first projected row, or ErrNotFound.

type Query

type Query[T any] struct {
	// contains filtered or unexported fields
}

Query is a query for rows of T. Queries are immutable: every builder method returns a new query, so one can safely be the base of several others. Terminal methods run it.

func FromFunc

func FromFunc[T any](h Handle, call string, args ...any) *Query[T]

FromFunc reads from a table-valued function, written with ? for its arguments, as in generate_series(?, ?). Name its result columns if they differ from T's: unnest(?) AS "users"(name).

func FromRaw

func FromRaw[T any](h Handle, text string, args ...any) *Query[T]

FromRaw returns a query over raw SQL, which conditions, ordering, and paging then build on. The SQL must return T's columns. Results are not tracked.

func Related[T, R any](h Handle, entity *T, rel *[]R) *Query[R]

Related returns a query for the rows related to entity through a one-to-many relation.

func RelatedOne

func RelatedOne[T, R any](h Handle, entity *T, rel **R) *Query[R]

RelatedOne returns a query for the row related to entity through a many-to-one or one-to-one relation.

func Values

func Values[T any](h Handle, rows ...T) *Query[T]

Values reads rows held in memory as if they were a table, for joining against a list or using it in a subquery. Each value takes the column's type, so this needs a dialect with migration support. Results are not tracked.

wanted := ant.Values(db, Wanted{ID: 1, Limit: 5}, Wanted{ID: 2, Limit: 9})
ant.JoinSub(db.Users.Query(), wanted, ant.EqCol(&U.ID, &W.ID))

func (*Query[T]) After

func (q *Query[T]) After(ctx context.Context, cursor string, size int) (*CursorPage[T], error)

After returns the page following cursor, which is empty for the first page. Each page costs the same however deep it is, and rows added meanwhile do not shift it. The ordering must use columns of this table that are never NULL. Pass Next from the previous page to continue.

func (*Query[T]) All

func (q *Query[T]) All(ctx context.Context) ([]*T, error)

All returns every matching row, as an empty slice if there are none.

func (*Query[T]) Chunk

func (q *Query[T]) Chunk(ctx context.Context, size int, f func([]*T) error) error

Chunk reads matching rows in groups of size, calling f with each until f returns an error or the rows run out. Paging uses Offset, so order the query and do not change matching rows in f. Use ChunkByKey when f changes them.

func (*Query[T]) ChunkByKey

func (q *Query[T]) ChunkByKey(ctx context.Context, size int, f func([]*T) error) error

ChunkByKey reads matching rows in key order, in groups of size. It tracks its place by key, so f can change or delete the rows it receives.

func (*Query[T]) ClearOrder

func (q *Query[T]) ClearOrder() *Query[T]

ClearOrder removes any ordering.

func (*Query[T]) Column

func (q *Query[T]) Column[V any](field *V) *Query[T]

Column limits the query to the named columns, and can be repeated. Other fields keep their zero value, so the results are never tracked.

func (*Query[T]) Count

func (q *Query[T]) Count(ctx context.Context) (int64, error)

Count returns the number of matching rows. Limit, Offset, and Distinct are honored, by counting over a subquery.

func (*Query[T]) CrossJoin

func (q *Query[T]) CrossJoin[J any]() *Join2[T, J]

CrossJoin pairs every row of T with every row of J.

func (*Query[T]) Debug

func (q *Query[T]) Debug() *Query[T]

Debug logs this query's SQL and values when it runs, at info level, through slog's default logger. It is for a moment of curiosity at a desk: unlike WithLogger, it shows values, so do not leave it in code.

func (*Query[T]) Delete

func (q *Query[T]) Delete(ctx context.Context) (int64, error)

Delete removes every matching row and returns the number removed. A model with a DeletedAt field is soft-deleted. With Limit or Offset, it removes only the rows the query selects. Tracked instances of those rows stay tracked, so prefer Remove with SaveChanges for loaded entities.

func (*Query[T]) DeleteReturning

func (q *Query[T]) DeleteReturning(ctx context.Context) ([]*T, error)

DeleteReturning is Delete, returning the removed rows.

func (*Query[T]) DeleteSQL

func (q *Query[T]) DeleteSQL(ctx context.Context) (string, []any, error)

DeleteSQL returns the statement Delete would run.

Example
package main

import (
	"context"
	"database/sql"
	"fmt"
	"log"

	_ "github.com/jackc/pgx/v5/stdlib"
	"github.com/skudra-go/ant"
	"github.com/skudra-go/ant/dialect/postgres"
)

type User struct {
	ant.Base
	Name  string
	Email string `ant:"unique"`
	Age   int
	Bio   *string
	Posts []Post
}

type Post struct {
	ant.Base
	UserID    int64
	Title     string
	Published bool
	User      *User
}

type AppDB struct {
	ant.Context
	Users ant.Table[User]
	Posts ant.Table[Post]
}

var Ps = ant.Fields[Post]()

func db() *AppDB {
	pool, err := sql.Open("pgx", "postgres://nobody@127.0.0.1:1/none")
	if err != nil {
		log.Fatal(err)
	}

	out, err := ant.Wrap[AppDB](postgres.Dialect, pool)
	if err != nil {
		log.Fatal(err)
	}

	return out
}

func ctx() context.Context { return context.Background() }

func show(text string, args []any, err error) {
	if err != nil {
		log.Fatal(err)
	}

	fmt.Println(text)
	fmt.Println(args)
}

func main() {
	show(db().Posts.Where(ant.Eq(&Ps.Published, false)).DeleteSQL(ctx()))
}
Output:
DELETE FROM "posts" WHERE "posts"."published" = $1
[false]

func (*Query[T]) Distinct

func (q *Query[T]) Distinct() *Query[T]

Distinct removes duplicate rows.

func (*Query[T]) DoesNotExist

func (q *Query[T]) DoesNotExist(ctx context.Context) (bool, error)

DoesNotExist reports whether no row matches.

func (*Query[T]) Each

func (q *Query[T]) Each(ctx context.Context) iter.Seq2[T, error]

Each streams matching rows, stopping after the first error. Values are copies, so streamed rows are not tracked.

func (*Query[T]) Except

func (q *Query[T]) Except(other *Query[T]) *Query[T]

Except returns the rows of q that are not in other.

func (*Query[T]) Exists

func (q *Query[T]) Exists(ctx context.Context) (bool, error)

Exists reports whether any row matches. It stops at the first one.

func (*Query[T]) Explain

func (q *Query[T]) Explain(ctx context.Context) (string, error)

Explain returns the database's plan for this query.

func (*Query[T]) Find

func (q *Query[T]) Find[K comparable](ctx context.Context, key K) (*T, error)

Find returns the entity with the given primary key, within this query's conditions, or ErrNotFound.

func (*Query[T]) First

func (q *Query[T]) First(ctx context.Context) (*T, error)

First returns the first matching row, or ErrNotFound. Without an ordering, which row is first is up to the database.

func (*Query[T]) ForShare

func (q *Query[T]) ForShare() *Query[T]

ForShare locks the selected rows against changes until the transaction ends, while allowing other readers.

func (*Query[T]) ForUpdate

func (q *Query[T]) ForUpdate() *Query[T]

ForUpdate locks the selected rows until the transaction ends.

func (*Query[T]) ForceDelete

func (q *Query[T]) ForceDelete(ctx context.Context) (int64, error)

ForceDelete removes rows for real, even from a soft-deleted model. Use WithDeleted to reach rows that are already soft-deleted.

func (*Query[T]) From

func (q *Query[T]) From(sub *Query[T]) *Query[T]

From reads from sub instead of the table. Conditions, ordering, and paging apply to its results, and they are not tracked.

func (*Query[T]) FromCTE

func (q *Query[T]) FromCTE(name string, sub *Query[T]) *Query[T]

FromCTE names sub in a WITH clause, and reads from it.

func (*Query[T]) FullJoinOn

func (q *Query[T]) FullJoinOn[J any](on ...Cond) *Join2[T, J]

FullJoinOn joins J on conditions, keeping unmatched rows of both.

func (*Query[T]) GroupBy

func (q *Query[T]) GroupBy[V any](field *V) *Grouped

GroupBy groups rows by a column. Add more with Then, and read the results with Project:

ant.Project[Stat](db.Posts.GroupBy(&P.UserID),
	ant.Into(&S.UserID, &P.UserID),
	ant.IntoExpr(&S.Posts, ant.Count()))

func (*Query[T]) GroupByExpr

func (q *Query[T]) GroupByExpr[V any](e Expr[V]) *Grouped

GroupByExpr groups rows by an expression.

func (*Query[T]) Hint

func (q *Query[T]) Hint(hint string) *Query[T]

Hint adds an optimizer hint, written /*+ hint */ before the statement, in the form extensions such as pg_hint_plan read. A database that does not know it sees a comment. Prefer fixing the query, since a hint outlives the reason for it.

func (*Query[T]) IgnoreFilters

func (q *Query[T]) IgnoreFilters(names ...string) *Query[T]

IgnoreFilters switches off the named global filters for this query, or all of them with no names. Related queries from Include keep theirs.

func (*Query[T]) Include

func (q *Query[T]) Include[R any](rel *[]R, shape ...func(*Query[R]) *Query[R]) *Query[T]

Include loads a one-to-many relation in one extra query per relation, however many parents there are. Parents with no related rows get an empty slice, so a nil slice always means the relation was not loaded.

The optional function shapes the related query. It can filter, order, and limit, per parent, and call Include again for nested relations:

db.Users.Include(&U.Posts, func(q *ant.Query[Post]) *ant.Query[Post] {
	return q.Where(ant.Eq(&P.Published, true)).OrderByDesc(&P.ID).Limit(3)
})

func (*Query[T]) IncludeJoined

func (q *Query[T]) IncludeJoined[R any](rel **R) *Query[T]

IncludeJoined loads a many-to-one or one-to-one relation in the same query, through a left join, instead of the extra query IncludeOne runs. It suits a relation most rows have, read often enough that a round trip matters. Parents with the same related row each get their own value unless tracked.

Example
package main

import (
	"database/sql"
	"fmt"
	"log"

	_ "github.com/jackc/pgx/v5/stdlib"
	"github.com/skudra-go/ant"
	"github.com/skudra-go/ant/dialect/postgres"
)

type User struct {
	ant.Base
	Name  string
	Email string `ant:"unique"`
	Age   int
	Bio   *string
	Posts []Post
}

type Post struct {
	ant.Base
	UserID    int64
	Title     string
	Published bool
	User      *User
}

type AppDB struct {
	ant.Context
	Users ant.Table[User]
	Posts ant.Table[Post]
}

var Ps = ant.Fields[Post]()

func db() *AppDB {
	pool, err := sql.Open("pgx", "postgres://nobody@127.0.0.1:1/none")
	if err != nil {
		log.Fatal(err)
	}

	out, err := ant.Wrap[AppDB](postgres.Dialect, pool)
	if err != nil {
		log.Fatal(err)
	}

	return out
}

func main() {
	text, _, err := db().Posts.Column(&Ps.ID).IncludeJoined(&Ps.User).ToSQL()
	if err != nil {
		log.Fatal(err)
	}

	fmt.Println(text[:len("SELECT \"posts\".\"id\"")])
}
Output:
SELECT "posts"."id"

func (*Query[T]) IncludeOne

func (q *Query[T]) IncludeOne[R any](rel **R, shape ...func(*Query[R]) *Query[R]) *Query[T]

IncludeOne loads a many-to-one or one-to-one relation in one extra query. An unmatched relation stays nil. Parents with the same related row share one value. See Include for the optional function.

func (*Query[T]) Intersect

func (q *Query[T]) Intersect(other *Query[T]) *Query[T]

Intersect returns the rows in both q and other.

func (*Query[T]) Join

func (q *Query[T]) Join[J any](
	rel **J,
) *Join2[T, J]

Join joins through a many-to-one or one-to-one relation. Unmatched rows are dropped.

func (*Query[T]) JoinMany

func (q *Query[T]) JoinMany[J any](rel *[]J) *Join2[T, J]

JoinMany joins through a one-to-many relation, with one result per related row.

func (*Query[T]) JoinOn

func (q *Query[T]) JoinOn[J any](
	on ...Cond,
) *Join2[T, J]

JoinOn joins J on conditions rather than a relation.

func (*Query[T]) JoinUsing

func (q *Query[T]) JoinUsing[J, V any](field *V) *Join2[T, J]

JoinUsing joins J on a column with the same name in both tables.

func (*Query[T]) Latest

func (q *Query[T]) Latest(field *time.Time) *Query[T]

Latest orders by a time column, newest first.

func (*Query[T]) LeftJoin

func (q *Query[T]) LeftJoin[J any](rel **J) *Join2[T, J]

LeftJoin joins through a many-to-one or one-to-one relation, keeping unmatched rows. Their joined value is nil in Select.

func (*Query[T]) LeftJoinMany

func (q *Query[T]) LeftJoinMany[J any](rel *[]J) *Join2[T, J]

LeftJoinMany joins through a one-to-many relation, keeping rows with no related row.

func (*Query[T]) LeftJoinOn

func (q *Query[T]) LeftJoinOn[J any](on ...Cond) *Join2[T, J]

LeftJoinOn joins J on conditions, keeping unmatched rows of T.

func (*Query[T]) Limit

func (q *Query[T]) Limit(n int) *Query[T]

Limit caps the number of rows returned.

func (*Query[T]) NoTracking

func (q *Query[T]) NoTracking() *Query[T]

NoTracking returns entities the change tracker does not see, including those from Include.

func (*Query[T]) NoTrackingResolved

func (q *Query[T]) NoTrackingResolved() *Query[T]

NoTrackingResolved is NoTracking, except that one row is one instance in the results, as it is with tracking. It suits a read-only query that joins or includes the same row many times, and costs less than tracking it.

func (*Query[T]) NoWait

func (q *Query[T]) NoWait() *Query[T]

NoWait makes a lock fail at once instead of waiting for another transaction.

func (*Query[T]) Offset

func (q *Query[T]) Offset(n int) *Query[T]

Offset skips the first n rows. Without an ordering, which rows are skipped is up to the database.

func (*Query[T]) Oldest

func (q *Query[T]) Oldest(field *time.Time) *Query[T]

Oldest orders by a time column, oldest first.

func (*Query[T]) Omit

func (q *Query[T]) Omit[V any](field *V) *Query[T]

Omit leaves a column out of the query. Its field keeps its zero value, so the results are never tracked.

func (*Query[T]) OnlyDeleted

func (q *Query[T]) OnlyDeleted() *Query[T]

OnlyDeleted returns only soft-deleted rows.

func (*Query[T]) Order

func (q *Query[T]) Order(terms ...Sort) *Query[T]

Order orders results by terms from Asc and Desc, replacing any earlier ordering.

func (*Query[T]) OrderBy

func (q *Query[T]) OrderBy[V Ordered](field *V) *Query[T]

OrderBy orders results by field, ascending, replacing any earlier ordering.

func (*Query[T]) OrderByDesc

func (q *Query[T]) OrderByDesc[V Ordered](field *V) *Query[T]

OrderByDesc orders results by field, descending, replacing any earlier ordering.

Example
package main

import (
	"database/sql"
	"fmt"
	"log"

	_ "github.com/jackc/pgx/v5/stdlib"
	"github.com/skudra-go/ant"
	"github.com/skudra-go/ant/dialect/postgres"
)

type User struct {
	ant.Base
	Name  string
	Email string `ant:"unique"`
	Age   int
	Bio   *string
	Posts []Post
}

type Post struct {
	ant.Base
	UserID    int64
	Title     string
	Published bool
	User      *User
}

type AppDB struct {
	ant.Context
	Users ant.Table[User]
	Posts ant.Table[Post]
}

var Us = ant.Fields[User]()

func db() *AppDB {
	pool, err := sql.Open("pgx", "postgres://nobody@127.0.0.1:1/none")
	if err != nil {
		log.Fatal(err)
	}

	out, err := ant.Wrap[AppDB](postgres.Dialect, pool)
	if err != nil {
		log.Fatal(err)
	}

	return out
}

func show(text string, args []any, err error) {
	if err != nil {
		log.Fatal(err)
	}

	fmt.Println(text)
	fmt.Println(args)
}

func main() {
	show(db().Users.Column(&Us.ID).OrderByDesc(&Us.Age).ThenBy(&Us.Name).Limit(10).ToSQL())
}
Output:
SELECT "users"."id" FROM "users" ORDER BY "users"."age" DESC, "users"."name" LIMIT $1
[10]

func (*Query[T]) OrderByRandom

func (q *Query[T]) OrderByRandom() *Query[T]

OrderByRandom shuffles the results.

func (*Query[T]) Page

func (q *Query[T]) Page(ctx context.Context, number, size int) (*Page[T], error)

Page returns one page, counted from 1, with the total row count. Order the query, or pages may overlap. Use After for large tables, where counting and skipping rows gets slow.

func (*Query[T]) Recursive

func (q *Query[T]) Recursive(rel *[]T) *Query[T]

Recursive extends q with rows reached by repeatedly following a one-to-many relation of the same model, such as a folder and everything under it.

func (*Query[T]) RecursiveOne

func (q *Query[T]) RecursiveOne(rel **T) *Query[T]

RecursiveOne extends q with rows reached by repeatedly following a many-to-one relation of the same model, such as a folder and all its ancestors.

func (*Query[T]) Restore

func (q *Query[T]) Restore(ctx context.Context) (int64, error)

Restore undeletes matching soft-deleted rows and returns how many.

func (*Query[T]) RightJoinOn

func (q *Query[T]) RightJoinOn[J any](on ...Cond) *Join2[T, J]

RightJoinOn joins J on conditions, keeping unmatched rows of J. Their T value is nil in Select.

func (*Query[T]) Scope

func (q *Query[T]) Scope(scopes ...func(*Query[T]) *Query[T]) *Query[T]

Scope applies reusable query logic:

func Adults(q *ant.Query[User]) *ant.Query[User] {
	return q.Where(ant.Gte(&U.Age, 18))
}

db.Users.Query().Scope(Adults).All(ctx)

func (*Query[T]) Single

func (q *Query[T]) Single(ctx context.Context) (*T, error)

Single returns the only matching row. It returns ErrNotFound if there are none, and ErrMultipleRows if there are several.

func (*Query[T]) SkipLocked

func (q *Query[T]) SkipLocked() *Query[T]

SkipLocked leaves out rows that another transaction has locked, which suits job queues.

func (*Query[T]) Tag

func (q *Query[T]) Tag(name string) *Query[T]

Tag adds a comment to the SQL, so a query can be found in database logs.

func (*Query[T]) ThenBy

func (q *Query[T]) ThenBy[V Ordered](field *V) *Query[T]

ThenBy adds an ascending ordering after OrderBy.

func (*Query[T]) ThenByDesc

func (q *Query[T]) ThenByDesc[V Ordered](field *V) *Query[T]

ThenByDesc adds a descending ordering after OrderBy.

func (*Query[T]) ToSQL

func (q *Query[T]) ToSQL() (string, []any, error)

ToSQL returns the SELECT this query would run. Global filters that need a context see an empty one.

func (*Query[T]) ToSQLContext

func (q *Query[T]) ToSQLContext(ctx context.Context) (string, []any, error)

ToSQLContext is ToSQL with global filters resolved against ctx.

func (*Query[T]) Unfiltered

func (q *Query[T]) Unfiltered() *Query[T]

Unfiltered allows Update and Delete to affect every row. Without it, they return ErrNoCondition unless a condition can exclude rows, which an empty ant.And or ant.NotIn cannot.

func (*Query[T]) Union

func (q *Query[T]) Union(other *Query[T]) *Query[T]

Union returns the rows of q or other, without duplicates. Conditions, ordering, and paging added afterward apply to the combined rows.

func (*Query[T]) UnionAll

func (q *Query[T]) UnionAll(other *Query[T]) *Query[T]

UnionAll is Union, keeping duplicates.

func (*Query[T]) Update

func (q *Query[T]) Update(ctx context.Context, a ...Assignment) (int64, error)

Update applies assignments to every matching row and returns the number of rows changed. Zero values are written like any other value. With Limit or Offset, it changes only the rows the query selects.

func (*Query[T]) UpdateReturning

func (q *Query[T]) UpdateReturning(ctx context.Context, a ...Assignment) ([]*T, error)

UpdateReturning is Update, returning the changed rows. They are not tracked.

func (*Query[T]) UpdateSQL

func (q *Query[T]) UpdateSQL(ctx context.Context, set ...Assignment) (string, []any, error)

UpdateSQL returns the statement Update would run, for sending it some other way, such as in a pipelined batch.

func (*Query[T]) When

func (q *Query[T]) When(ok bool, f func(*Query[T]) *Query[T]) *Query[T]

When applies f only if ok is true, for conditions decided at runtime.

func (*Query[T]) Where

func (q *Query[T]) Where(c ...Cond) *Query[T]

Where adds conditions, all of which must match.

func (*Query[T]) With

func (q *Query[T]) With[V any](field *V, value Expr[V]) *Query[T]

With fills a field tagged ant:"computed" from an expression, such as a count of related rows.

func (*Query[T]) WithDeleted

func (q *Query[T]) WithDeleted() *Query[T]

WithDeleted includes soft-deleted rows.

type RawQuery

type RawQuery[T any] struct {
	// contains filtered or unexported fields
}

RawQuery is a raw SQL statement. Its text reaches the database as written, so pass values as arguments, never by building the text from them.

func Raw

func Raw[T any](h Handle, query string, args ...any) *RawQuery[T]

Raw prepares a raw SQL statement. Pass arguments in one of three forms: the dialect's placeholders such as $1, sql.Named arguments written @name, or portable ? marks. A slice inside IN (@ids) or IN (?) expands to its elements. See dialect.Bind for the exact rules.

T is what each row scans into. A struct takes columns by name, ignoring columns with no field, and need not be a model. A value type such as int takes a single column. T is unused by Exec, so ant.Raw[any] suits it.

func RawFile

func RawFile[T any](h Handle, fsys fs.FS, name string, args ...any) *RawQuery[T]

RawFile is Raw with the statement read from a file in fsys, such as an embed.FS. A file that cannot be read is an error at the terminal call.

func (*RawQuery[T]) All

func (r *RawQuery[T]) All(ctx context.Context) ([]T, error)

All runs the statement and returns every row, as an empty slice if there are none.

func (*RawQuery[T]) Exec

func (r *RawQuery[T]) Exec(ctx context.Context) (int64, error)

Exec runs the statement and returns the number of rows affected.

func (*RawQuery[T]) First

func (r *RawQuery[T]) First(ctx context.Context) (*T, error)

First runs the statement and returns the first row, or ErrNotFound.

type RelationBuilder

type RelationBuilder struct {
	// contains filtered or unexported fields
}

RelationBuilder configures a relation.

func (*RelationBuilder) ConstraintName

func (r *RelationBuilder) ConstraintName(name string) *RelationBuilder

ConstraintName names the foreign key, in place of table_column_fkey.

func (*RelationBuilder) ForeignKey

func (r *RelationBuilder) ForeignKey[V any](field *V) *RelationBuilder

ForeignKey sets the foreign key column, on either model.

func (*RelationBuilder) Inverse

func (r *RelationBuilder) Inverse[P any](back **P) *RelationBuilder

Inverse fills a pointer on each loaded child back to its parent, so a Post from Include(&U.Posts) has its User without another query.

func (*RelationBuilder) Latest

func (r *RelationBuilder) Latest[V Ordered](field *V) *RelationBuilder

Latest makes a has-one relation the newest of many, by field: Include reads that row for each parent.

func (*RelationBuilder) Morph

func (r *RelationBuilder) Morph[V comparable](field *V, value V) *RelationBuilder

Morph makes the relation polymorphic: the foreign key can refer to several tables, and field says which. Set it on each relation with the value for that target, which is also the place to keep a map of type names:

b.HasMany(&Post{}.Comments).ForeignKey(&C.OwnerID).Morph(&C.OwnerType, "post")
b.BelongsTo(&C.Post).ForeignKey(&C.OwnerID).Morph(&C.OwnerType, "post")

Saving through the relation sets field. A foreign key cannot point at several tables, so migrations declare none.

func (*RelationBuilder) NoForeignKey

func (r *RelationBuilder) NoForeignKey() *RelationBuilder

NoForeignKey keeps the relation in queries and leaves its foreign key out of migrations. It suits a relation across databases, or a very busy table.

func (*RelationBuilder) NoIndex

func (r *RelationBuilder) NoIndex() *RelationBuilder

NoIndex leaves out the index migrations otherwise add on the foreign key.

func (*RelationBuilder) Oldest

func (r *RelationBuilder) Oldest[V Ordered](field *V) *RelationBuilder

Oldest makes a has-one relation the oldest of many, by field.

func (*RelationBuilder) OnDelete

func (r *RelationBuilder) OnDelete(action Action) *RelationBuilder

OnDelete sets the action when the parent is deleted.

func (*RelationBuilder) OnUpdate

func (r *RelationBuilder) OnUpdate(action Action) *RelationBuilder

OnUpdate sets the action when the parent's key changes.

func (*RelationBuilder) PivotColumns

func (r *RelationBuilder) PivotColumns(local, foreign string) *RelationBuilder

PivotColumns names the join table's columns, for the owner and the target.

func (*RelationBuilder) References

func (r *RelationBuilder) References[V any](field *V) *RelationBuilder

References sets the column the foreign key points to, in place of the primary key.

func (*RelationBuilder) Through

func (r *RelationBuilder) Through(table string) *RelationBuilder

Through names the join table of a many-to-many relation.

func (*RelationBuilder) Touch

func (r *RelationBuilder) Touch() *RelationBuilder

Touch updates the parent's UpdatedAt whenever SaveChanges writes this child, as a comment makes its post look recently active. Set it on the child's relation to the parent.

func (*RelationBuilder) Where

func (r *RelationBuilder) Where(conds ...Cond) *RelationBuilder

Where scopes the relation: Include, Has, joins, and counts see only the target rows that meet conds.

b.HasMany(&U.LivePosts).ForeignKey(&P.UserID).Where(ant.Eq(&P.Published, true))

type Retry

type Retry struct {
	// Attempts is the number of runs in all.
	Attempts int
}

Retry says how to run a transaction again after a deadlock or serialization failure, which a database reports when two transactions collide and only one can win.

type SaveInterceptor

type SaveInterceptor interface {
	SavingChanges(ctx context.Context, changes []Change) error
}

SaveInterceptor is an Interceptor that also sees each SaveChanges before it writes, for auditing. An error cancels the save.

type Selectable

type Selectable interface {
	// contains filtered or unexported methods
}

Selectable is a query that Project can read from.

type SequenceOption

type SequenceOption func(*meta.Sequence)

SequenceOption configures a sequence.

func IncrementBy

func IncrementBy(n int64) SequenceOption

IncrementBy sets a sequence's step.

func StartAt

func StartAt(n int64) SequenceOption

StartAt sets a sequence's first value.

type Sort

type Sort struct {
	// contains filtered or unexported fields
}

Sort is one term of an ordering, from Asc or Desc.

func (Sort) NullsFirst

func (s Sort) NullsFirst() Sort

NullsFirst sorts NULL values before all others.

func (Sort) NullsLast

func (s Sort) NullsLast() Sort

NullsLast sorts NULL values after all others.

type State

type State = track.State

State is the lifecycle state of an entity.

type Statement

type Statement = exec.Statement

Statement is one SQL statement on its way to the database. An interceptor can change it. Args holds real values, so take care what you record.

func DryRun

func DryRun[DB any](ctx context.Context, db *DB, fn func(tx *DB) error) ([]Statement, error)

DryRun runs fn in a transaction and rolls it back, returning every statement it ran. Nothing stays, though sequences move on, and whatever fn does outside the database still happens. It shows what a piece of code really does to a database, including what the unit of work decides to write.

type Sub

type Sub[V any] struct {
	// contains filtered or unexported fields
}

Sub is a query for one column of type V, for use in a condition or as a value.

func SubExpr

func SubExpr[V any](q Selectable, value Expr[V]) Sub[V]

SubExpr makes q a subquery selecting an expression.

func SubOf

func SubOf[V any](q Selectable, field *V) Sub[V]

SubOf makes q a subquery selecting field. A subquery can refer to models of the outer query in its conditions.

func (Sub[V]) Expr

func (s Sub[V]) Expr() Expr[V]

Expr uses the subquery as a single value. It must return at most one row.

type Table

type Table[T any] struct {
	// contains filtered or unexported fields
}

Table is a typed entry point to one model. Declare Table fields on a struct that embeds Context, and Open populates them.

func (*Table[T]) All

func (t *Table[T]) All(ctx context.Context) ([]*T, error)

All returns every row.

func (*Table[T]) Column

func (t *Table[T]) Column[V any](field *V) *Query[T]

Column starts a query limited to the named column. See Query.Column.

func (*Table[T]) Count

func (t *Table[T]) Count(ctx context.Context) (int64, error)

Count returns the number of rows.

func (*Table[T]) CrossJoin

func (t *Table[T]) CrossJoin[J any]() *Join2[T, J]

CrossJoin starts a query that pairs every row with every row of J.

func (*Table[T]) DeleteByID

func (t *Table[T]) DeleteByID[K comparable](ctx context.Context, key K) error

DeleteByID removes the entity with the given primary key, and stops tracking it, or returns ErrNotFound.

func (*Table[T]) Distinct

func (t *Table[T]) Distinct() *Query[T]

Distinct starts a query that removes duplicate rows.

func (*Table[T]) Each

func (t *Table[T]) Each(ctx context.Context) iter.Seq2[T, error]

Each streams every row.

func (*Table[T]) Exists

func (t *Table[T]) Exists(ctx context.Context) (bool, error)

Exists reports whether the table has any rows.

func (*Table[T]) Find

func (t *Table[T]) Find[K comparable](ctx context.Context, key K) (*T, error)

Find returns the entity with the given primary key, or ErrNotFound. A tracked entity is returned without a query, with any unsaved changes.

func (*Table[T]) FindKey

func (t *Table[T]) FindKey(ctx context.Context, parts ...any) (*T, error)

FindKey returns the entity with a composite primary key, given its parts in key order, or ErrNotFound.

func (*Table[T]) First

func (t *Table[T]) First(ctx context.Context) (*T, error)

First returns the first row, or ErrNotFound.

func (*Table[T]) FirstOrCreate

func (t *Table[T]) FirstOrCreate(ctx context.Context, e *T, conds ...Cond) (*T, bool, error)

FirstOrCreate returns the first row that meets conds, or inserts e and returns it. If another writer inserts a matching row first and a unique constraint stops this insert, it returns that row. The bool reports whether e was created.

func (*Table[T]) FirstOrNew

func (t *Table[T]) FirstOrNew(ctx context.Context, e *T, conds ...Cond) (*T, bool, error)

FirstOrNew returns the first row that meets conds, or e, not saved. The bool reports whether a row was found.

func (*Table[T]) FullJoinOn

func (t *Table[T]) FullJoinOn[J any](on ...Cond) *Join2[T, J]

FullJoinOn starts a query fully joined on conditions.

func (*Table[T]) GroupBy

func (t *Table[T]) GroupBy[V any](field *V) *Grouped

GroupBy starts a query grouped by a column. See Query.GroupBy.

func (*Table[T]) GroupByExpr

func (t *Table[T]) GroupByExpr[V any](e Expr[V]) *Grouped

GroupByExpr starts a query grouped by an expression.

func (*Table[T]) IgnoreFilters

func (t *Table[T]) IgnoreFilters(
	names ...string,
) *Query[T]

IgnoreFilters starts a query with the named global filters off.

func (*Table[T]) Include

func (t *Table[T]) Include[R any](rel *[]R, shape ...func(*Query[R]) *Query[R]) *Query[T]

Include starts a query that loads a one-to-many relation. See Query.Include.

func (*Table[T]) IncludeJoined

func (t *Table[T]) IncludeJoined[R any](rel **R) *Query[T]

IncludeJoined starts a query that reads a relation in the same statement. See Query.IncludeJoined.

func (*Table[T]) IncludeOne

func (t *Table[T]) IncludeOne[R any](rel **R, shape ...func(*Query[R]) *Query[R]) *Query[T]

IncludeOne starts a query that loads a many-to-one or one-to-one relation.

func (*Table[T]) Insert

func (t *Table[T]) Insert(ctx context.Context, entities ...*T) error

Insert writes entities and the new entities related to them, parents first, in one transaction, so either all are saved or none are. Generated keys and foreign keys are filled in. A zero value in a generated key means the database assigns it, and every other field is written as it is, zero or not. A related entity that already has a generated key is linked, not inserted.

func (*Table[T]) InsertFrom

func (t *Table[T]) InsertFrom(ctx context.Context, rows *Projection[T]) (int64, error)

InsertFrom inserts the rows of a projection, and returns how many. The projection's mappings name the columns:

db.Archive.InsertFrom(ctx, ant.Project[Archive](db.Posts.Where(old),
	ant.Into(&A.Title, &P.Title)))

func (*Table[T]) InsertMany

func (t *Table[T]) InsertMany(
	ctx context.Context,
	entities []*T,
	options ...InsertOption,
) (int64, error)

InsertMany writes many rows quickly, in multi-row statements inside one transaction, and returns how many were inserted. Unlike Insert, it ignores relations and does not track the rows. Generated keys are copied back, except with WithConflict, where rows may be skipped.

func (*Table[T]) InsertOnly

func (t *Table[T]) InsertOnly(ctx context.Context, entities ...*T) error

InsertOnly writes entities and ignores their relation fields.

func (*Table[T]) Join

func (t *Table[T]) Join[J any](rel **J) *Join2[T, J]

Join starts a query joined through a relation. See Query.Join.

func (*Table[T]) JoinMany

func (t *Table[T]) JoinMany[J any](rel *[]J) *Join2[T, J]

JoinMany starts a query joined through a one-to-many relation.

func (*Table[T]) JoinOn

func (t *Table[T]) JoinOn[J any](on ...Cond) *Join2[T, J]

JoinOn starts a query joined on conditions.

func (*Table[T]) LeftJoin

func (t *Table[T]) LeftJoin[J any](rel **J) *Join2[T, J]

LeftJoin starts a query left-joined through a relation.

func (*Table[T]) LeftJoinMany

func (t *Table[T]) LeftJoinMany[J any](rel *[]J) *Join2[T, J]

LeftJoinMany starts a query left-joined through a one-to-many relation.

func (*Table[T]) LeftJoinOn

func (t *Table[T]) LeftJoinOn[J any](on ...Cond) *Join2[T, J]

LeftJoinOn starts a query left-joined on conditions.

func (*Table[T]) Limit

func (t *Table[T]) Limit(n int) *Query[T]

Limit starts a query capped at n rows.

func (*Table[T]) Load

func (t *Table[T]) Load[R any](ctx context.Context, rel *[]R, entities ...*T) error

Load fills a one-to-many relation on entities already in memory, in one extra query. See Include for the optional shape, passed through LoadShaped.

func (*Table[T]) LoadMissing

func (t *Table[T]) LoadMissing[R any](ctx context.Context, rel *[]R, entities ...*T) error

LoadMissing is Load for only the entities whose relation is still nil.

func (*Table[T]) LoadOne

func (t *Table[T]) LoadOne[R any](ctx context.Context, rel **R, entities ...*T) error

LoadOne fills a many-to-one or one-to-one relation on entities already in memory, in one extra query.

func (*Table[T]) NoTracking

func (t *Table[T]) NoTracking() *Query[T]

NoTracking starts a query whose results are not tracked.

func (*Table[T]) Offset

func (t *Table[T]) Offset(n int) *Query[T]

Offset starts a query that skips n rows.

func (*Table[T]) Omit

func (t *Table[T]) Omit[V any](field *V) *Query[T]

Omit starts a query that leaves a column out. See Query.Omit.

func (*Table[T]) OnlyDeleted

func (t *Table[T]) OnlyDeleted() *Query[T]

OnlyDeleted starts a query for soft-deleted rows.

func (*Table[T]) Order

func (t *Table[T]) Order(terms ...Sort) *Query[T]

Order starts a query ordered by terms from Asc and Desc.

func (*Table[T]) OrderBy

func (t *Table[T]) OrderBy[V Ordered](field *V) *Query[T]

OrderBy starts a query ordered by field, ascending.

func (*Table[T]) OrderByDesc

func (t *Table[T]) OrderByDesc[V Ordered](
	field *V,
) *Query[T]

OrderByDesc starts a query ordered by field, descending.

func (*Table[T]) Prune

func (t *Table[T]) Prune(ctx context.Context, conds ...Cond) (int64, error)

Prune removes rows that meet conds for good, soft-deleted or not, in batches, so that clearing out old rows does not hold one long transaction. It returns how many went, and stops at the first error.

func (*Table[T]) Query

func (t *Table[T]) Query() *Query[T]

Query starts a query with nothing added yet.

func (*Table[T]) Reload

func (t *Table[T]) Reload(ctx context.Context, e *T) error

Reload overwrites the columns of e with the stored row, found by e's primary key, or returns ErrNotFound. Relations are left alone.

func (*Table[T]) RightJoinOn

func (t *Table[T]) RightJoinOn[J any](on ...Cond) *Join2[T, J]

RightJoinOn starts a query right-joined on conditions.

func (*Table[T]) Scope

func (t *Table[T]) Scope(scopes ...func(*Query[T]) *Query[T]) *Query[T]

Scope starts a query from reusable logic. See Query.Scope.

func (*Table[T]) Single

func (t *Table[T]) Single(ctx context.Context) (*T, error)

Single returns the only row. See Query.Single.

func (*Table[T]) Truncate

func (t *Table[T]) Truncate(ctx context.Context) error

Truncate removes every row, faster than Delete, and cannot be used on a table that others refer to unless they are empty too. It stops tracking nothing, so call ClearTracked if the context holds these rows.

func (*Table[T]) Unfiltered

func (t *Table[T]) Unfiltered() *Query[T]

Unfiltered starts a query whose Update or Delete affects every row.

func (*Table[T]) UpdateMany

func (t *Table[T]) UpdateMany(
	ctx context.Context,
	entities []*T,
	fields ...FieldRef,
) (int64, error)

UpdateMany writes the named columns of each entity to its own row, in one transaction, and returns how many rows changed. Use it for values that differ per row. Tracked entities keep their snapshots, so prefer SaveChanges for them.

func (*Table[T]) Upsert

func (t *Table[T]) Upsert(ctx context.Context, e *T, conflict *Conflict) (bool, error)

Upsert inserts e, or follows conflict if a unique constraint already holds the row. With an update, e receives the stored row, including its key. With DoNothing, it stays as it was and Upsert reports false.

func (*Table[T]) Where

func (t *Table[T]) Where(c ...Cond) *Query[T]

Where starts a query with conditions.

func (*Table[T]) With

func (t *Table[T]) With[V any](field *V, value Expr[V]) *Query[T]

With starts a query that fills a computed field. See Query.With.

func (*Table[T]) WithDeleted

func (t *Table[T]) WithDeleted() *Query[T]

WithDeleted starts a query that includes soft-deleted rows.

type TxOption

type TxOption func(*txOptions)

TxOption configures a transaction.

func Attempts

func Attempts(n int) TxOption

Attempts retries a transaction that fails from a deadlock or serialization conflict, up to n runs in all, waiting a little longer each time. f must be safe to run again, so it should not change anything outside the database.

func Isolation

func Isolation(level sql.IsolationLevel) TxOption

Isolation sets the transaction isolation level.

func ReadOnly

func ReadOnly() TxOption

ReadOnly makes the transaction read-only.

type UnixTime

type UnixTime = meta.UnixTime

UnixTime is a time stored as whole seconds since 1970. On CreatedAt and UpdatedAt, it is maintained like a time.Time.

type ValidationError

type ValidationError struct {
	Model string
	Field string
	Err   error
}

ValidationError reports a field that failed a rule from ModelBuilder.Validate.

func (*ValidationError) Error

func (e *ValidationError) Error() string

func (*ValidationError) Unwrap

func (e *ValidationError) Unwrap() error

Unwrap returns the rule's error.

type Validator

type Validator interface {
	Validate(ctx context.Context) error
}

Validator checks an entity before Insert or SaveChanges writes it.

type Window

type Window struct {
	// contains filtered or unexported fields
}

Window is an OVER clause, from PartitionBy or OrderedBy.

func OrderedBy

func OrderedBy(terms ...Sort) Window

OrderedBy starts a window over all rows in the given order.

func PartitionBy

func PartitionBy[V any](field *V) Window

PartitionBy starts a window divided by a column.

func (Window) OrderBy

func (w Window) OrderBy(terms ...Sort) Window

OrderBy orders rows within the window.

func (Window) PartitionBy

func (w Window) PartitionBy(exprs ...exprNode) Window

PartitionBy divides the window by more expressions.

Directories

Path Synopsis
Package anttest holds helpers for testing code that uses ant, against a real database:
Package anttest holds helpers for testing code that uses ant, against a real database:
contrib
otel module
Package dialect defines the interface each database implements, plus helpers they share.
Package dialect defines the interface each database implements, plus helpers they share.
mysql
Package mysql holds the MySQL and MariaDB dialects.
Package mysql holds the MySQL and MariaDB dialects.
postgres
Package postgres is the PostgreSQL dialect.
Package postgres is the PostgreSQL dialect.
postgres/pgxext
Package pgxext holds PostgreSQL features that database/sql cannot reach and that need pgx itself, so that ant and dialect/postgres stay free of any driver.
Package pgxext holds PostgreSQL features that database/sql cannot reach and that need pgx itself, so that ant and dialect/postgres stay free of any driver.
sqlite
Package sqlite is the SQLite dialect.
Package sqlite is the SQLite dialect.
sqlserver
Package sqlserver is the SQL Server dialect.
Package sqlserver is the SQL Server dialect.
Package ids makes unique keys in Go, for use with ant.GenerateKey:
Package ids makes unique keys in Go, for use with ant.GenerateKey:
internal
errs
Package errs holds sentinel errors shared by ant's packages.
Package errs holds sentinel errors shared by ant's packages.
exec
Package exec defines the seam between ant and database/sql.
Package exec defines the seam between ant and database/sql.
meta
Package meta is the model registry.
Package meta is the model registry.
scan
Package scan maps database rows onto Go values.
Package scan maps database rows onto Go values.
testdb
Package testdb runs ant's integration tests against every database listed in ANT_TEST_DATABASES.
Package testdb runs ant's integration tests against every database listed in ANT_TEST_DATABASES.
track
Package track implements the change tracker: identity map, snapshots, and diffing.
Package track implements the change tracker: identity map, snapshots, and diffing.
Package metrics counts statements, with no dependencies, and writes them in Prometheus text format, which most monitoring systems read:
Package metrics counts statements, with no dependencies, and writes them in Prometheus text format, which most monitoring systems read:
Package migrate keeps a database in step with its models.
Package migrate keeps a database in step with its models.
Package qb is the dialect-neutral query AST.
Package qb is the dialect-neutral query AST.
Package schema describes a database schema as plain data, and works out the changes that turn one schema into another.
Package schema describes a database schema as plain data, and works out the changes that turn one schema into another.
Package secure stores values encrypted, with AES-256-GCM, so that a database dump reveals nothing.
Package secure stores values encrypted, with AES-256-GCM, so that a database dump reveals nothing.
tests
features/fixture
Package fixture holds the models and setup shared by the feature examples.
Package fixture holds the models and setup shared by the feature examples.
tools
antfmt module
antvet module
Package validate holds common rules for ModelBuilder.Validate:
Package validate holds common rules for ModelBuilder.Validate:

Jump to

Keyboard shortcuts

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