Documentation
¶
Overview ¶
Package db is Anetos's data layer: connections, a typed query builder, model CRUD with timestamps, soft deletes and hooks, transactions, raw SQL scanned into structs, and pagination. It is built on database/sql and works with PostgreSQL, MySQL/MariaDB and SQLite through the driver modules (anetos.dev/anetos/drivers/postgres, …/mysql, …/sqlite).
Connect once at startup; the connection is then available in every context the app creates (handlers, jobs, app.Go tasks):
database, err := db.Connect(ctx, app, sqlite.Driver(), postgres.Driver())
Models are plain structs:
type Post struct {
db.Model // id, created_at, updated_at
db.SoftDeletes // deleted_at
Title string `db:"title" json:"title"`
AuthorID int64 `db:"author_id" json:"author_id"`
}
err := db.Create(ctx, &post)
post, err := db.Find[Post](ctx, id) // ErrNotFound → 404
posts, err := db.Query[Post](ctx).
Where(db.C("author_id").Eq(id)).
Latest().
Paginate(page, 20)
Columns are the db tags, or the snake_case field names; the table is the snake_case plural of the type name unless a TableName method says otherwise. Struct, pointer-to-struct and slice-of-struct fields without a db tag are ignored (they are reserved for relations).
Transactions ¶
Tx runs a function in a transaction carried by its context, so everything called with that context joins it; nested calls use savepoints.
Raw SQL ¶
Raw scans any query into structs or values, and Exec runs statements. Placeholders are written ? on every database, or :name with Named.
Validation ¶
Importing db registers the unique and exists validation rules, which query the database in the request's context.
Index ¶
- Constants
- Variables
- func AfterCommit(ctx context.Context, fn func(ctx context.Context))
- func Attach[T, R any](ctx context.Context, row *T, rel Rel[T, R], ids ...any) error
- func Avg[V, T any](q *Q[T], col Column[V]) (float64, error)
- func Columns[T any]() ([]string, error)
- func CosineDistance(a, b Vector) (float64, error)
- func CosineDistanceBlobs(a, b []byte) (float64, error)
- func Create[T any](ctx context.Context, row *T) error
- func CreateMany[T any](ctx context.Context, rows []T) error
- func Delete[T any](ctx context.Context, row *T) error
- func Detach[T, R any](ctx context.Context, row *T, rel Rel[T, R], ids ...any) error
- func DetachAll[T, R any](ctx context.Context, row *T, rel Rel[T, R]) error
- func EmbeddingsTable(table string) string
- func EscapeLike(s string) string
- func Exec(ctx context.Context, query string, args ...any) (sql.Result, error)
- func Find[T any](ctx context.Context, id any) (T, error)
- func ForceDelete[T any](ctx context.Context, row *T) error
- func InTx(ctx context.Context) bool
- func KeyOf(row any) (table string, key any, err error)
- func Load[T any](ctx context.Context, row *T, rels ...Relation[T]) error
- func LoadMany[T any](ctx context.Context, rows []T, rels ...Relation[T]) error
- func LocalHost(host string) bool
- func Max[V, T any](q *Q[T], col Column[V]) (V, error)
- func Min[V, T any](q *Q[T], col Column[V]) (V, error)
- func Pluck[V, T any](q *Q[T], col Column[V]) ([]V, error)
- func Plural(name string) string
- func PruneChunks[T any](ctx context.Context) (int64, error)
- func PruneTrashed[T any](app *anetos.App, after time.Duration) error
- func Raw[T any](ctx context.Context, query string, args ...any) ([]T, error)
- func RawFirst[T any](ctx context.Context, query string, args ...any) (T, error)
- func RecordID[T any](row T) (int64, error)
- func ReplaceChunks[T any](ctx context.Context, recordID int64, chunks []Chunk) error
- func Restore[T any](ctx context.Context, row *T) error
- func Save[T any](ctx context.Context, row *T) error
- func Select[R, T any](q *Q[T], terms ...string) ([]R, error)
- func SoftDeleting[T any]() (bool, error)
- func Sum[V, T any](q *Q[T], col Column[V]) (V, error)
- func Sync[T, R any](ctx context.Context, row *T, rel Rel[T, R], ids ...any) error
- func TableOf[T any]() (string, error)
- func Tx(ctx context.Context, fn func(ctx context.Context) error) error
- func TxWith(ctx context.Context, opts *sql.TxOptions, fn func(ctx context.Context) error) (err error)
- func Untracked(ctx context.Context) context.Context
- func Update[T any](ctx context.Context, row *T) error
- func Upsert[T any](ctx context.Context, rows []T, conflict []string, update ...string) error
- func WithDB(ctx context.Context, d *DB) context.Context
- func WithTestTx(ctx context.Context, tx *sql.Tx) (context.Context, error)
- func WithTx(ctx context.Context, tx *sql.Tx) (context.Context, error)
- func WithoutTx(ctx context.Context) context.Context
- type AfterCreateHook
- type AfterDeleteHook
- type AfterSaveHook
- type AfterUpdateHook
- type Assignment
- type BeforeCreateHook
- type BeforeDeleteHook
- type BeforeSaveHook
- type BeforeUpdateHook
- type Bulk
- type Capability
- type Chunk
- type ChunkMatch
- type Column
- func (c Column[T]) Asc() Order
- func (c Column[T]) Between(lo, hi T) Expr
- func (c Column[T]) Contains(s string) Expr
- func (c Column[T]) Desc() Order
- func (c Column[T]) Eq(v T) Expr
- func (c Column[T]) Gt(v T) Expr
- func (c Column[T]) Gte(v T) Expr
- func (c Column[T]) In(vs ...T) Expr
- func (c Column[T]) IsNull() Expr
- func (c Column[T]) Like(pattern string) Expr
- func (c Column[T]) Lt(v T) Expr
- func (c Column[T]) Lte(v T) Expr
- func (c Column[T]) Name() string
- func (c Column[T]) Ne(v T) Expr
- func (c Column[T]) NotIn(vs ...T) Expr
- func (c Column[T]) NotLike(pattern string) Expr
- func (c Column[T]) NotNull() Expr
- func (c Column[T]) Of(table string) Column[T]
- func (c Column[T]) Set(v T) Assignment
- func (c Column[T]) SetRaw(sql string, args ...any) Assignment
- func (c Column[T]) StartsWith(s string) Expr
- type Config
- type CursorPage
- type DB
- func (d *DB) Check(ctx context.Context) error
- func (d *DB) CheckCapabilities(ctx context.Context, feature string, caps ...Capability) error
- func (d *DB) CheckSearch(ctx context.Context) error
- func (d *DB) CheckTimeZone(ctx context.Context) error
- func (d *DB) Close() error
- func (d *DB) Dialect() Dialect
- func (d *DB) OnRepeatedQuery(fn func(ctx context.Context, r RepeatedQuery))
- func (d *DB) Ping(ctx context.Context) error
- func (d *DB) Require(feature string, caps ...Capability) error
- func (d *DB) SQL() *sql.DB
- func (d *DB) SearchConfig() SearchConfig
- func (d *DB) Supports(ctx context.Context, c Capability) (bool, error)
- func (d *DB) Track(ctx context.Context, u anetos.Unit) (context.Context, func())
- func (d *DB) Watch(table string, w Watcher, bulkValues int) error
- func (d *DB) Watchers(table string) []Watcher
- type Dialect
- type Driver
- type Expr
- type Model
- type Named
- type Op
- type Option
- type Order
- type Page
- type Pruned
- type Q
- func (q *Q[T]) All() iter.Seq2[T, error]
- func (q *Q[T]) Count() (int64, error)
- func (q *Q[T]) CursorPaginate(cursor string, perPage int) (CursorPage[T], error)
- func (q *Q[T]) Delete() (int64, error)
- func (q *Q[T]) Distinct() *Q[T]
- func (q *Q[T]) Exists() (bool, error)
- func (q *Q[T]) Find(id any) (T, error)
- func (q *Q[T]) First() (T, error)
- func (q *Q[T]) ForShare() *Q[T]
- func (q *Q[T]) ForUpdate() *Q[T]
- func (q *Q[T]) ForceDelete() (int64, error)
- func (q *Q[T]) Get() ([]T, error)
- func (q *Q[T]) GroupBy(cols ...string) *Q[T]
- func (q *Q[T]) Having(conds ...Expr) *Q[T]
- func (q *Q[T]) Hybrid(text, model string, v Vector) *Q[T]
- func (q *Q[T]) Join(sql string, args ...any) *Q[T]
- func (q *Q[T]) Latest() *Q[T]
- func (q *Q[T]) Limit(n int) *Q[T]
- func (q *Q[T]) Offset(n int) *Q[T]
- func (q *Q[T]) Oldest() *Q[T]
- func (q *Q[T]) OnlyTrashed() *Q[T]
- func (q *Q[T]) OrderBy(orders ...Order) *Q[T]
- func (q *Q[T]) Paginate(page, perPage int) (Page[T], error)
- func (q *Q[T]) Restore() (int64, error)
- func (q *Q[T]) Scope(scopes ...func(*Q[T]) *Q[T]) *Q[T]
- func (q *Q[T]) Search(text string) *Q[T]
- func (q *Q[T]) Similar(model string, v Vector) *Q[T]
- func (q *Q[T]) Update(assignments ...Assignment) (int64, error)
- func (q *Q[T]) Where(conds ...Expr) *Q[T]
- func (q *Q[T]) WhereDoesntHave(rel Relation[T], conds ...Expr) *Q[T]
- func (q *Q[T]) WhereHas(rel Relation[T], conds ...Expr) *Q[T]
- func (q *Q[T]) WhereKeys(ids ...any) *Q[T]
- func (q *Q[T]) WhereRaw(sql string, args ...any) *Q[T]
- func (q *Q[T]) With(rels ...Relation[T]) *Q[T]
- func (q *Q[T]) WithContext(ctx context.Context) *Q[T]
- func (q *Q[T]) WithTrashed() *Q[T]
- type Rel
- type Relation
- type RepeatedQuery
- type RowValues
- type SearchConfig
- type SearchIndex
- type SetExpr
- type SoftDeletes
- type Tabler
- type Timestamps
- type Values
- type Vector
- type Watcher
- type Write
Constants ¶
const ( TLSVerify = "verify" // encrypt, and check the server's certificate and name TLSSkipVerify = "skip-verify" // encrypt only: open to a man in the middle TLSNone = "none" // plain text )
The values of Config.TLS.
const DefaultPerPage = 15
DefaultPerPage is used when Paginate or CursorPaginate get perPage < 1.
const SQLiteCosineFunction = "anetos_vec_distance_cosine"
SQLiteCosineFunction is the SQL function SQLite queries use for CosineDistanceBlobs; the SQLite driver registers it.
const SQLiteTimeFormat = "2006-01-02 15:04:05.999999999"
SQLiteTimeFormat is how times are stored in SQLite: UTC text in the format of SQLite's own CURRENT_TIMESTAMP and datetime(), with fractional seconds when present. Using one format makes text comparison and sorting match time order, also against values written by SQL defaults.
const SearchIndexesTable = "search_indexes"
SearchIndexesTable is the table where migrations record the search indexes they make.
const SimilarCandidates = 200
SimilarCandidates is how many of the nearest chunks Q.Similar and Q.Hybrid consider (and how many keyword matches Hybrid ranks): conditions of the query (Where) apply to the records they come from, so a query whose conditions exclude most records finds fewer.
Variables ¶
var ErrInvalidCursor error = invalidCursor{}
ErrInvalidCursor is returned by CursorPaginate for a cursor it didn't produce. It reports status 400 to the web package.
var ErrNoDB = errors.New("db: no database in context (use db.Connect, or db.WithDB in tests and scripts)")
ErrNoDB is returned when a query's context has no database. Use Connect (which adds the database to every context the app creates) or WithDB.
var ErrNotFound error = notFound{}
ErrNotFound is returned by First, Find and similar methods when no row matches. It reports status 404 to the web package, so a handler can return it directly:
post, err := db.Find[Post](c, in.ID)
if err != nil {
return nil, err // 404 if the post doesn't exist
}
Functions ¶
func AfterCommit ¶
AfterCommit runs fn once the transaction in ctx commits, or right away if ctx has no transaction (or only a test's, from WithTestTx). Use it for work that must only happen if the data was saved, such as sending an email or dispatching a job. If the transaction (or the nested transaction fn was registered in) rolls back, fn never runs. fn gets a context outside the transaction (in a test's transaction from WithTestTx, the test's context).
func Attach ¶ added in v0.1.1
Attach links row to the related rows with the given primary keys through the pivot table of a many_to_many relation. Keys already linked are skipped, also when another transaction links them at the same time, so Attach can be repeated; this needs a primary key or unique index on the pivot's two columns.
err := db.Attach(ctx, &post, PostRels.Tags, goTag.ID, webTag.ID)
func Columns ¶
Columns returns the column names of model T in field order, with the columns of embedded structs where they are embedded. It is the list `anetos gen` writes typed columns for, and handy for raw SQL:
cols, err := db.Columns[Post]() sql := "SELECT " + strings.Join(cols, ", ") + " FROM posts WHERE …"
func CosineDistance ¶ added in v0.3.0
CosineDistance returns 1 minus the cosine of the angle between a and b: 0 for the same direction, 1 for unrelated, 2 for opposite. It is the distance Q.Similar orders by; SQLite's driver computes it with this function. Vectors of different lengths are an error; a zero vector is at distance 1 from every other.
func CosineDistanceBlobs ¶ added in v0.3.0
CosineDistanceBlobs is CosineDistance of two vectors stored as little-endian float32s, as SQLite stores them: the SQL function anetos_vec_distance_cosine, which the SQLite driver registers.
func Create ¶
Create inserts row. A zero integer primary key is generated by the database and set on row. Models with Timestamps get created_at and updated_at set (unless already set).
func CreateMany ¶
CreateMany inserts rows in as few statements as possible, running the create hooks for each row. Generated primary keys are set on the rows on PostgreSQL and SQLite, but not on MySQL, which can't report them for a multi-row insert.
func Delete ¶
Delete deletes row, found by primary key. Models with SoftDeletes are soft-deleted: deleted_at is set in the database and on row. It returns ErrNotFound if no (non-deleted) row matched.
func Detach ¶ added in v0.1.1
Detach unlinks row from the related rows with the given primary keys (none: nothing to do). The related rows stay.
func EmbeddingsTable ¶ added in v0.3.0
EmbeddingsTable returns the table holding table's embeddings: "<table>_embeddings" (migrate.Schema.CreateEmbeddings makes it).
func EscapeLike ¶ added in v0.3.0
EscapeLike escapes LIKE's wildcards (% and _) and its escape character (!) in s, for a pattern used with ESCAPE '!':
db.SQL("title LIKE ? ESCAPE '!'", "%"+db.EscapeLike(q)+"%")
func Find ¶
Find returns the row of model T whose primary key is id, or ErrNotFound.
func ForceDelete ¶
ForceDelete deletes row even if the model uses SoftDeletes.
func KeyOf ¶ added in v0.3.0
KeyOf returns the table and primary key of row, a pointer to a model (or a model), as the db package writes them; for audit entries and links to a row.
func Load ¶ added in v0.1.1
Load loads relations of one row:
err := db.Load(ctx, &post, PostRels.Comments)
func LoadMany ¶ added in v0.1.1
LoadMany loads relations of rows, in place, with one query per relation:
err := db.LoadMany(ctx, posts, PostRels.Author)
func LocalHost ¶ added in v0.3.0
LocalHost reports whether host is this machine: localhost, a loopback address, or a Unix socket path.
func Pluck ¶
Pluck returns the values of one column of the matching rows. Columns made with JSONCol are decoded from JSON.
emails, err := db.Pluck(db.Query[User](ctx).Where(active), db.Col[string]("email"))
func Plural ¶
Plural returns the plural the db package uses for table names: it pluralizes the last word of a snake_case name (blog_post → blog_posts, category → categories, person → people). The migrate package uses it to guess referenced tables.
func PruneChunks ¶ added in v0.3.0
PruneChunks deletes the chunks of T's records that no longer exist, and returns how many: those a cascading delete left on MariaDB, where the embeddings table has no foreign key (see migrate.Schema.CreateEmbeddings).
func PruneTrashed ¶ added in v0.3.0
PruneTrashed registers model T, which must embed SoftDeletes: its rows soft-deleted more than after ago are deleted for good by the db:prune-trashed command, or by PruneAllTrashed (in a scheduled task, say). The first call adds the command to the app.
err := db.PruneTrashed[models.Post](app, 90*24*time.Hour)
Rows are deleted in batches of 1,000, each in its own transaction, as Q.ForceDelete does: a table watched by the audit log gets a bulk entry per batch. Related rows go only where foreign keys cascade.
func Raw ¶
Raw runs a SQL query and scans every row into T: a struct (columns match db tags or snake_case field names; extra columns are ignored) or a single value such as int64 or string for one-column results.
type row struct {
Name string `db:"name"`
Total int64 `db:"total"`
}
rows, err := db.Raw[row](ctx, `
SELECT u.name, COUNT(p.id) AS total
FROM users u JOIN posts p ON p.author_id = u.id
WHERE p.created_at > ?
GROUP BY u.name`, since)
Write parameters as ? on every database (?? for a literal ?), or as :name with a single Named argument. Native placeholders ($1) also work when the query has no ?.
func RawFirst ¶
RawFirst is like Raw but returns only the first row, or ErrNotFound.
func RecordID ¶ added in v0.3.0
RecordID returns row's primary key as an int64, the key of its embeddings: an integer primary key that isn't zero.
func ReplaceChunks ¶ added in v0.3.0
ReplaceChunks makes chunks the record's chunks, in one transaction: its other chunks are deleted. The chunks are stored for recordID, at their positions in the slice (their RecordID and Position are ignored).
func Save ¶
Save creates row if its generated primary key is zero, and updates it otherwise. It is for models with an integer primary key; use Create and Update for others.
func Select ¶
Select runs the query with the given SELECT terms (written in SQL) and scans the rows into R, matching columns to R's db tags (or snake_case field names). Use it for aggregates per group:
type authorStats struct {
AuthorID int64 `db:"author_id"`
Posts int64 `db:"posts"`
}
stats, err := db.Select[authorStats](
db.Query[Post](ctx).GroupBy("author_id"),
"author_id", "COUNT(*) AS posts")
func SoftDeleting ¶ added in v0.3.0
SoftDeleting reports whether model T embeds SoftDeletes, so that Delete sets deleted_at and queries skip deleted rows.
func Sum ¶
Sum returns the sum of col over the matching rows (zero if none), respecting Limit and Offset. Distinct is ignored (use Select with "SUM(DISTINCT col)"), and GroupBy is an error (use Select for per-group totals). The same holds for Avg, Min and Max.
total, err := db.Sum(db.Query[Order](ctx).Where(paid), db.Col[int64]("amount"))
func Sync ¶ added in v0.1.1
Sync makes the given primary keys row's exact set of related rows: missing links are added and the others removed, in one transaction.
func TableOf ¶ added in v0.3.0
TableOf returns the table of model T (its Tabler name, or the type's name in snake case, plural).
func Tx ¶
Tx runs fn in a transaction on the database in ctx. Queries made with the ctx passed to fn run in the transaction. If fn returns an error or panics, the transaction is rolled back; otherwise it is committed.
err := db.Tx(ctx, func(ctx context.Context) error {
if err := db.Create(ctx, &order); err != nil {
return err
}
return db.Create(ctx, &payment)
})
Calling Tx inside a transaction starts a nested one using a savepoint: an error rolls back only the nested part.
Don't run queries of one transaction from several goroutines at once.
func TxWith ¶
func TxWith(ctx context.Context, opts *sql.TxOptions, fn func(ctx context.Context) error) (err error)
TxWith is like Tx with transaction options (isolation level, read-only). Options are ignored for nested transactions, which inherit the outer one.
func Untracked ¶ added in v0.2.0
Untracked returns ctx with its queries left out of repeated-query detection (DB.Track), for code whose repeated queries are by design: the framework's database stores use it.
func Update ¶
Update writes every column of row (except the primary key, created_at and deleted_at) to its database row, found by primary key, and returns ErrNotFound if there is no such row. Models with Timestamps get updated_at set. Soft-deleted rows can be updated; use Restore to undelete one.
func Upsert ¶
Upsert inserts rows, or updates the columns in update of rows that conflict with an existing one on the conflict columns (which need a unique index). With no update columns, conflicting rows are left as they are. Models with Timestamps also update updated_at. Hooks don't run, and generated keys aren't set on rows.
err := db.Upsert(ctx, prices, []string{"sku"}, "price")
func WithDB ¶
WithDB returns ctx with d as the database for queries made with it. Use it in tests and for additional connections:
ctx = db.WithDB(ctx, analytics) rows, err := db.Query[Event](ctx).Get()
A transaction started on another DB in ctx is not used for d.
func WithTestTx ¶ added in v0.2.0
WithTestTx is WithTx for test helpers, whose transaction is rolled back at the end of the test instead of committed: work done at its level counts as committed. AfterCommit callbacks registered directly in it run at once, and those of a Tx directly inside it run when that Tx commits (as they would in the app, without the test's transaction). anetostest uses it.
func WithTx ¶
WithTx returns ctx with tx, a transaction on the database in ctx that the caller began and will commit or roll back itself. Queries made with the returned context run in tx, and Tx on it uses savepoints. Use it to share a transaction with code that works with *sql.Tx directly. AfterCommit callbacks registered on the returned context never run, since the db package doesn't see the commit: neither do what relies on them (queue.AfterCommit dispatches, async event listeners, and the recording of jobs the database queue driver writes in tx, which anetostest and queue.Queue.Observe see).
func WithoutTx ¶ added in v0.2.0
WithoutTx returns ctx without its transaction on the database in ctx: queries made with it use their own connections, and their writes stay if the transaction rolls back. Use it for records that must outlive a failed transaction, such as an audit log of the attempt. It needs a second connection while the transaction holds one. Don't use it with SQLite, whose transactions hold the write lock from the start: a write made this way waits for the transaction and fails after the busy timeout.
Types ¶
type AfterCreateHook ¶
type AfterCreateHook interface {
// AfterCreate runs after the insert; an error is returned by Create.
AfterCreate(ctx context.Context) error
}
AfterCreateHook runs after a row is inserted.
type AfterDeleteHook ¶
type AfterDeleteHook interface {
// AfterDelete runs after the delete; an error is returned by Delete.
AfterDelete(ctx context.Context) error
}
AfterDeleteHook runs after a row is deleted or soft-deleted.
type AfterSaveHook ¶
type AfterSaveHook interface {
// AfterSave runs after an insert or update; an error is returned.
AfterSave(ctx context.Context) error
}
AfterSaveHook runs after a row is inserted or updated.
type AfterUpdateHook ¶
type AfterUpdateHook interface {
// AfterUpdate runs after the update; an error is returned by Update.
AfterUpdate(ctx context.Context) error
}
AfterUpdateHook runs after a row is updated.
type Assignment ¶
type Assignment struct {
// contains filtered or unexported fields
}
Assignment sets a column in Q.Update, from Column.Set or Column.SetRaw.
type BeforeCreateHook ¶
type BeforeCreateHook interface {
// BeforeCreate runs in the insert's context; an error stops the insert.
BeforeCreate(ctx context.Context) error
}
BeforeCreateHook runs before a row is inserted.
type BeforeDeleteHook ¶
type BeforeDeleteHook interface {
// BeforeDelete runs in the delete's context; an error stops the delete.
BeforeDelete(ctx context.Context) error
}
BeforeDeleteHook runs before a row is deleted or soft-deleted.
type BeforeSaveHook ¶
type BeforeSaveHook interface {
// BeforeSave runs before an insert or update; an error stops it.
BeforeSave(ctx context.Context) error
}
BeforeSaveHook runs before a row is inserted or updated.
type BeforeUpdateHook ¶
type BeforeUpdateHook interface {
// BeforeUpdate runs in the update's context; an error stops the update.
BeforeUpdate(ctx context.Context) error
}
BeforeUpdateHook runs before a row is updated.
type Bulk ¶ added in v0.3.0
type Bulk struct {
// Where is the condition the rows were chosen by, in the database's
// SQL ("" for CreateMany and Upsert), and Args its arguments.
Where string
// Args are the arguments of Where.
Args []any
// Set are a bulk update's assignments by column: values, or SetExpr
// for expressions. updated_at is left out.
Set map[string]any
// Keys are the primary keys of every row written.
Keys []any
// Before are rows' values before the write, for as many rows as the
// watchers asked for ([DB.Watch]): the assigned columns of an update,
// every column of a force delete, the updated columns of rows an
// upsert updated. Empty for creates, deletes and restores.
Before []RowValues
// After are rows' values after an upsert or a create, within the same
// limit.
After []RowValues
// Complete reports whether Before and After hold every row's values.
Complete bool
}
Bulk describes a write to many rows at once: a query's Update, Delete, ForceDelete or Restore, CreateMany or Upsert.
type Capability ¶ added in v0.3.0
type Capability string
Capability is something a database server may or may not be able to do, depending on the database, its version and its extensions.
const ( // FullText is full-text search: [Q.Search] and search indexes. Every // supported database has it. FullText Capability = "full-text search" // BM25 is BM25 ranking of search results: SQLite's FTS5 has it, and // PostgreSQL 17+ with the pg_textsearch extension. BM25 Capability = "BM25 ranking" )
The capabilities features can require (DB.Require).
const VectorSearch Capability = "vector search"
VectorSearch is the vector search capability: Q.Similar, Q.Hybrid and embeddings tables. PostgreSQL has it with the pgvector extension, MariaDB from 11.7, and SQLite by comparing every vector (fine for tens of thousands of chunks); MySQL Community doesn't.
type Chunk ¶ added in v0.3.0
type Chunk struct {
// RecordID is the record's primary key.
RecordID int64 `db:"record_id"`
// Position is the passage's position in the record, from 0.
Position int `db:"chunk"`
// Content is the passage's text.
Content string `db:"content"`
// ContentHash identifies the text, so unchanged text isn't embedded
// again.
ContentHash string `db:"content_hash"`
// Model is the embedding model's name.
Model string `db:"model"`
// Embedding is the passage's vector.
Embedding Vector `db:"embedding"`
}
Chunk is a row of an embeddings table (EmbeddingsTable): one passage of a record, with its embedding. Package ai reads and writes them.
type ChunkMatch ¶ added in v0.3.0
type ChunkMatch struct {
Chunk
// Distance is the cosine distance to the vector: 0 is the same
// direction.
Distance float64 `db:"distance"`
}
ChunkMatch is a chunk and its distance to a vector (NearestChunks).
func NearestChunks ¶ added in v0.3.0
func NearestChunks[T any](ctx context.Context, model string, v Vector, recordIDs ...int64) ([]ChunkMatch, error)
NearestChunks returns the chunks of the records with the IDs (made by the embedding model named model; "" for any) by their distance to v, nearest first: the passages to show for the records Q.Similar or Q.Hybrid found.
type Column ¶
type Column[T any] struct { // contains filtered or unexported fields }
Column is a typed reference to a column, used to build conditions, orderings and assignments that the compiler checks:
posts, err := db.Query[Post](ctx).
Where(models.PostCols.Title.Like("%go%")).
OrderBy(models.PostCols.Title.Asc()).
Get()
`anetos gen` declares the columns of every model (see the model code generation guide). Declare others with Col or JSONCol, or use C for an untyped column. T is the type of the model field, so a nullable column is a Column[*time.Time] and compares with new(t).
func C ¶
C returns an untyped column reference, for quick queries:
db.Query[Post](ctx).Where(db.C("author_id").Eq(id))
func JSONCol ¶
JSONCol returns a reference to a column stored as JSON (a `db:",json"` field): values given to its methods are encoded as JSON, and Pluck decodes them.
func (Column[T]) Contains ¶ added in v0.3.0
Contains is column LIKE '%s%' with s taken literally: its % and _ match themselves, so text from a search box can't widen the match. Case sensitivity follows the database, as for Column.Like.
func (Column[T]) Like ¶
Like is column LIKE pattern. Case sensitivity follows the database (PostgreSQL is case-sensitive; MySQL and SQLite usually aren't for ASCII).
func (Column[T]) Of ¶
Of returns the column qualified with table (replacing any qualifier; "" removes it), for queries with joins:
db.Query[Post](ctx).Join("JOIN authors ON authors.id = posts.author_id").
Where(models.PostCols.ID.Of("posts").Gt(100))
func (Column[T]) Set ¶
func (c Column[T]) Set(v T) Assignment
Set assigns v to the column in Q.Update. A nil pointer, map or slice sets NULL.
func (Column[T]) SetRaw ¶
func (c Column[T]) SetRaw(sql string, args ...any) Assignment
SetRaw assigns a SQL expression (with ? placeholders) to the column:
q.Update(db.C("views").SetRaw("views + ?", 1))
func (Column[T]) StartsWith ¶ added in v0.3.0
StartsWith is column LIKE 's%' with s taken literally (see Column.Contains).
type Config ¶
type Config struct {
// Connection selects the driver: sqlite, postgres or mysql.
Connection string `env:"DB_CONNECTION" default:"sqlite"`
// URL is a complete connection string in the driver's format. When set,
// Host, Port, Database, Username and Password are ignored.
URL string `env:"DB_URL"`
Host string `env:"DB_HOST" default:"127.0.0.1"` // server host (PostgreSQL, MySQL)
Port int `env:"DB_PORT"` // 0: the driver's default port
Database string `env:"DB_DATABASE"` // database name; for SQLite, the file (default database/app.db)
Username string `env:"DB_USERNAME"` // user to connect as
Password string `env:"DB_PASSWORD"` // that user's password
// TLS secures connections built from DB_HOST (PostgreSQL, MySQL):
// "verify" (encrypt, and check the server's certificate and name),
// "skip-verify" (encrypt only), or "none". Empty: "none" for a local
// host (localhost, a loopback address, a Unix socket), "verify" for
// any other, so a remote database is never reached in plain text by
// default. With DB_URL, put the driver's own options in the URL.
// DB_TLS (v0.3).
TLS string `env:"DB_TLS"`
// TLSCA is a PEM file of the certificate authorities that sign the
// server's certificate, for "verify" when they aren't the system's
// (a managed database's own CA). Setting it means "verify" unless
// DB_TLS says otherwise, also for a local host (a tunnel); it can't
// be combined with "skip-verify" or "none". DB_TLS_CA.
TLSCA string `env:"DB_TLS_CA"`
// Pool settings, as for database/sql's DB.SetMaxOpenConns and friends.
MaxOpenConns int `env:"DB_MAX_OPEN_CONNS" default:"25"` // connections open at most
MaxIdleConns int `env:"DB_MAX_IDLE_CONNS" default:"25"` // idle connections kept
ConnMaxLifetime time.Duration `env:"DB_CONN_MAX_LIFETIME" default:"30m"` // a connection is closed after this long
ConnMaxIdleTime time.Duration `env:"DB_CONN_MAX_IDLE_TIME" default:"5m"` // an idle connection is closed after this long
// LogQueries logs every query with its duration at debug level. Unset
// means on in development and off elsewhere.
LogQueries *bool `env:"DB_LOG_QUERIES"`
// SlowQuery logs queries that take at least this long as warnings.
// Zero disables it.
SlowQuery time.Duration `env:"DB_SLOW_QUERY" default:"500ms"`
// RepeatedQueries logs a warning when a unit of work (a request, a
// job…) runs the same query this many times or more: an N+1. Unset
// means 5 in development and testing, off elsewhere; 0 disables it.
RepeatedQueries *int `env:"DB_REPEATED_QUERIES"`
// AllowLocalTimeZone lets the database session run in a time zone
// other than UTC, which [DB.Check] otherwise refuses: for a legacy
// database whose times are local. The app still writes UTC.
// DB_ALLOW_LOCAL_TIMEZONE, default false.
AllowLocalTimeZone bool `env:"DB_ALLOW_LOCAL_TIMEZONE"`
}
Config is a database connection's configuration. Connect reads it from the DB_* keys; LoadConfig reads it with another prefix for extra connections.
func LoadConfig ¶
LoadConfig reads a Config from src with every key prefixed by prefix, so LoadConfig(src, "ANALYTICS_") reads ANALYTICS_DB_CONNECTION, ANALYTICS_DB_HOST and so on. An empty prefix reads the DB_* keys.
func (Config) LogValue ¶
LogValue logs the Config as Config.String does, for every slog handler (JSON included).
func (Config) String ¶
String describes the connection without secrets: the password, and passwords inside URL, are masked, so a printed or logged Config leaks nothing (fmt's %v, %+v and %#v, and slog through Config.LogValue).
func (Config) TLSMode ¶ added in v0.3.0
TLSMode returns the TLS the connection built from Host uses: TLS if set, else TLSVerify with TLSCA set or a host other than this machine, and TLSNone for a local host (localhost, a loopback address, a Unix socket path). Drivers use it; with URL set, the URL decides and it returns "".
type CursorPage ¶
type CursorPage[T any] struct { Data []T `json:"data"` // the page's rows PerPage int `json:"per_page"` // rows per page NextCursor string `json:"next_cursor"` // "" on the last page PrevCursor string `json:"prev_cursor"` // "" on the first page }
CursorPage is one page of results from Q.CursorPaginate.
type DB ¶
type DB struct {
// contains filtered or unexported fields
}
DB is a database connection pool with its dialect. It is safe for concurrent use; create one per database and share it.
Queries find their DB in the context: Connect adds it to every context the app creates, and WithDB adds it to others.
func Connect ¶
Connect opens the app's default database from the DB_* configuration and makes it available to the app:
database, err := db.Connect(ctx, app, sqlite.Driver(), postgres.Driver())
DB_CONNECTION picks one of the given drivers, so an app can use SQLite in development and PostgreSQL in production with both compiled in. Connect opens the connection pool, adds the DB to every context the app creates (see anetos.App.AddContextValue), provides it as a *db.DB service, and closes it in a shutdown hook. It pings the database when the app boots (right away if it already has), so the app and every command that boots it fail fast when the database is unreachable, while `help` doesn't need one; then it runs DB.Check, so the app doesn't start with settings or features the database can't serve (SEARCH_*, DB.Require).
Queries are logged at debug level in development unless DB_LOG_QUERIES says otherwise.
func Open ¶
Open opens a connection pool with drv, configured by cfg. Zero pool settings keep database/sql's defaults. It doesn't connect; call DB.Ping to check the connection.
func (*DB) Check ¶ added in v0.3.0
Check runs the checks Connect runs when the app boots: the session's time zone (DB.CheckTimeZone), the search settings (DB.CheckSearch), the features' requirements (DB.Require), and that the search indexes were built for the current settings (unless the command being run changes the schema, such as migrate and search:reindex; see cmd.Command.ChangesSchema). Call it after Open to check a DB the same way.
func (*DB) CheckCapabilities ¶ added in v0.3.0
CheckCapabilities returns nil if the database provides caps, else the error DB.Require would stop the app with, naming feature: for code that checks before it does something, such as a migration.
func (*DB) CheckSearch ¶ added in v0.3.0
CheckSearch checks that the database supports the search settings: SEARCH_LANGUAGE (on PostgreSQL, a text search configuration the server has) and SEARCH_RANKING. Migrations creating search indexes check it too.
func (*DB) CheckTimeZone ¶ added in v0.3.0
CheckTimeZone returns an error unless the database session's time zone is UTC, so times the database writes itself (CURRENT_TIMESTAMP, NOW(), MySQL's TIMESTAMP columns) are UTC like the ones the app writes. The drivers open sessions in UTC, so a zone comes from the connection string (timezone= or time_zone= in DB_URL); DB.Check runs it at boot unless DB_ALLOW_LOCAL_TIMEZONE is set. SQLite has no session zone.
func (*DB) OnRepeatedQuery ¶ added in v0.2.0
func (d *DB) OnRepeatedQuery(fn func(ctx context.Context, r RepeatedQuery))
OnRepeatedQuery calls fn, besides logging a warning, for each repeated query DB.Track finds from now on: anetostest records them. fn must be safe for concurrent use.
func (*DB) Require ¶ added in v0.3.0
func (d *DB) Require(feature string, caps ...Capability) error
Require records that feature needs the database to provide caps. The app checks every requirement when it boots (Connect, or DB.Check), and refuses to start if one isn't met, naming the feature, the database and how to get the capability. Once the DB is checked, Require checks at once and returns the error.
err := database.Require("relevance ranking", db.BM25)
func (*DB) SearchConfig ¶ added in v0.3.0
func (d *DB) SearchConfig() SearchConfig
SearchConfig returns the DB's search settings.
func (*DB) Supports ¶ added in v0.3.0
Supports reports whether the database can provide c, asking the server when it depends on it (its version, its extensions).
func (*DB) Track ¶ added in v0.2.0
Track counts the queries made on d with the returned context, until the returned function is called; then it logs a warning (and calls the DB.OnRepeatedQuery functions) for each query run at least the threshold number of times (WithRepeatedQueries). Connect has every unit of work of the app tracked this way (anetos.App.AroundUnits). Without a threshold, it returns ctx and a no-op.
func (*DB) Watch ¶ added in v0.3.0
Watch makes w watch the writes the db package makes to table: Create, CreateMany, Upsert, Update, Save, Delete, ForceDelete, Restore, and a query's Update, Delete, ForceDelete and Restore. For a watched table, each of these runs in a transaction (a savepoint inside one already open, so model hooks run inside it too) and calls w after writing:
- an update first reads the row's values FOR UPDATE, so the watcher gets the values before and after;
- a force delete (or a delete without SoftDeletes) reads the values the row had;
- a bulk write first selects the matching rows' keys (and the values of up to bulkValues rows) FOR UPDATE, then writes in chunks of 1,000 keys, with the condition and the keys, and fails if a chunk changes fewer rows than it selected, so what the watcher is told is exactly what was written;
- CreateMany on a database that can't report a multi-row insert's keys (MySQL) inserts the rows one by one.
A watched table's model needs a primary key. Raw SQL (Exec), pivot writes (Attach, Sync…) and the database's own cascades aren't seen. Call Watch before the app writes, typically at setup.
type Dialect ¶
type Dialect interface {
// Name identifies the dialect: "postgres", "mysql" or "sqlite".
Name() string
// Placeholder returns the n-th (1-based) parameter placeholder.
Placeholder(n int) string
// QuoteIdent quotes one identifier (no dots).
QuoteIdent(name string) string
// Returning reports whether INSERT … RETURNING is supported.
Returning() bool
// DefaultValues is the INSERT clause for a row with no explicit columns.
DefaultValues() string
// Upsert returns the clause that follows INSERT … VALUES to update the
// columns in update (or do nothing if it is empty) when a row conflicts
// on the conflict columns. Column names are already quoted.
Upsert(conflict, update []string) string
// LockClause returns the row-locking suffix for SELECT … FOR UPDATE
// (share=false) or FOR SHARE, or "" if the database has no row locks.
LockClause(share bool) string
// Arg converts a query argument before it is sent to the driver.
Arg(v any) any
}
Dialect is the SQL flavor of a database: placeholders, identifier quoting and the few statements that differ between databases. The db package ships dialects for PostgreSQL, MySQL/MariaDB and SQLite; driver modules pair one with a database/sql driver in a Driver.
The interface may grow before v1.0; implement it outside this package only if you are prepared to follow those changes.
type Driver ¶
type Driver struct {
// Name is the value of DB_CONNECTION that selects this driver.
Name string
// Dialect writes the database's SQL.
Dialect Dialect
// Tune, if set, adjusts cfg before the pool is opened and configured,
// for settings a database needs (an in-memory SQLite database must use
// a single connection, for example).
Tune func(cfg *Config)
// Open opens a connection pool for cfg. It should not connect yet.
Open func(cfg Config) (*sql.DB, error)
// InspectURL, if set, reads a DB_URL in the driver's format: the
// server's host ("" or a path for a Unix socket) and how connections
// are secured, [TLSVerify], [TLSSkipVerify] or [TLSNone] (TLSNone
// when they may fall back to plain text). The doctor command uses it
// (v0.3).
InspectURL func(url string) (host, tls string, err error)
}
Driver pairs a Dialect with a way to open connections. Driver modules provide one each: sqlite.Driver(), postgres.Driver(), mysql.Driver().
type Expr ¶
type Expr interface {
// contains filtered or unexported methods
}
Expr is a condition or value expression, built with Column methods, And, Or, Not and SQL.
type Model ¶
type Model struct {
ID int64 `db:"id,pk" json:"id"` // primary key, set by Create when zero
Timestamps
}
Model gives a struct an auto-increment ID and timestamps:
type Post struct {
db.Model
Title string `db:"title" json:"title"`
}
type Named ¶
Named holds named arguments for raw SQL written with :name parameters:
db.Raw[User](ctx, "SELECT * FROM users WHERE email = :email", db.Named{"email": e})
type Op ¶ added in v0.3.0
type Op string
Op is the kind of a watched write.
const ( // OpCreate inserts rows: Create, CreateMany. OpCreate Op = "create" // OpUpdate changes rows: Update, Save, and Update on a query. OpUpdate Op = "update" // OpDelete soft-deletes rows (models with SoftDeletes): the rows stay. OpDelete Op = "delete" // OpRestore undeletes soft-deleted rows. OpRestore Op = "restore" // OpForceDelete removes rows from the database: ForceDelete, and // Delete on a model without SoftDeletes. OpForceDelete Op = "force_delete" // OpUpsert inserts rows or updates those that conflict (Upsert). OpUpsert Op = "upsert" )
The kinds of watched writes.
type Option ¶
type Option func(*DB)
Option configures a DB created with Open or New.
func WithLocalTimeZone ¶ added in v0.3.0
WithLocalTimeZone makes DB.Check accept, or not, a session time zone other than UTC (DB_ALLOW_LOCAL_TIMEZONE).
func WithLogger ¶
WithLogger sets the logger for query logs. The default discards them.
func WithQueryLog ¶
WithQueryLog turns logging of every query on or off.
func WithRepeatedQueries ¶ added in v0.2.0
WithRepeatedQueries makes DB.Track report queries a unit of work runs n or more times; n below 2 disables it. Connect sets it from DB_REPEATED_QUERIES (with a default in development and testing), and Open from Config.RepeatedQueries when set. For a database opened with Open, have the app's units tracked with app.AroundUnits(d.Track).
func WithSearch ¶ added in v0.3.0
func WithSearch(cfg SearchConfig) Option
WithSearch sets the search settings of a DB from Open or New (Connect reads them from SEARCH_*). Default: simple, default.
func WithSlowQuery ¶
WithSlowQuery sets the duration from which queries are logged as slow; zero disables it.
type Order ¶
type Order struct {
// contains filtered or unexported fields
}
Order is an ORDER BY term, from Column.Asc, Column.Desc or OrderRaw.
type Page ¶
type Page[T any] struct { Data []T `json:"data"` // the page's rows; empty, not nil, past the end CurrentPage int `json:"current_page"` // 1-based PerPage int `json:"per_page"` // rows per page Total int64 `json:"total"` // rows on all pages LastPage int `json:"last_page"` // number of the last page; 1 when there are no rows }
Page is one page of results from Q.Paginate. Its JSON form follows Laravel's paginator: data, current_page, per_page, total, last_page.
type Pruned ¶ added in v0.3.0
type Pruned struct {
// Table is the model's table.
Table string
// Rows is how many rows were deleted (or, in a dry run, would be).
Rows int64
}
Pruned is what PruneAllTrashed did to one table.
func PruneAllTrashed ¶ added in v0.3.0
PruneAllTrashed deletes for good the rows of every model registered with PruneTrashed that were soft-deleted longer ago than the model's duration, and reports how many per table. Run it from a scheduled task:
err := sched.Add(schedule.Daily(), "db:prune-trashed", func(ctx context.Context) error {
_, err := db.PruneAllTrashed(ctx)
return err
})
It stops at the first error, returning what it did so far.
type Q ¶
type Q[T any] struct { // contains filtered or unexported fields }
Q is a query on the table of model T. Build one with Query; each method returns a new Q, so a base query can be reused:
published := db.Query[Post](ctx).Where(db.C("published").Eq(true))
recent, err := published.Latest().Limit(10).Get()
total, err := published.Count()
Models with SoftDeletes exclude deleted rows unless Q.WithTrashed or Q.OnlyTrashed is used.
func Query ¶
Query starts a query on the table of model T, using the database (and transaction) in ctx.
func (*Q[T]) All ¶
All streams the matching rows, for results too large to hold at once:
for post, err := range db.Query[Post](ctx).All() {
if err != nil {
return err
}
…
}
The connection stays busy until the loop ends, so don't run other queries in the same transaction inside the loop.
func (*Q[T]) CursorPaginate ¶
func (q *Q[T]) CursorPaginate(cursor string, perPage int) (CursorPage[T], error)
CursorPaginate returns perPage rows after (or before) cursor, which is "" for the first page or a NextCursor/PrevCursor from a previous page. Unlike Paginate it stays fast deep into large tables and doesn't skip or repeat rows when rows are added, but it can't jump to a page number.
The query's OrderBy terms must be plain columns (not OrderRaw) whose values aren't NULL; the primary key is added as a tie-breaker, and used alone when there is no OrderBy.
func (*Q[T]) Delete ¶
Delete deletes every matching row and returns how many. Models with SoftDeletes are soft-deleted: deleted_at is set on matching rows that aren't already deleted. Model hooks don't run for mass deletes.
func (*Q[T]) Find ¶
Find returns the row whose primary key is id, or ErrNotFound.
func (*Q[T]) First ¶
First returns the first matching row, or ErrNotFound. Without OrderBy, which row is first is up to the database.
func (*Q[T]) ForShare ¶
ForShare locks the selected rows against changes until the transaction ends.
func (*Q[T]) ForUpdate ¶
ForUpdate locks the selected rows until the transaction ends (SELECT … FOR UPDATE). Use it inside Tx. On SQLite it does nothing: SQLite transactions lock the whole database.
func (*Q[T]) ForceDelete ¶
ForceDelete deletes every matching row, even for models with SoftDeletes. Soft-deleted rows only match with WithTrashed or OnlyTrashed.
func (*Q[T]) Get ¶
Get returns every matching row. It returns an empty (non-nil) slice if there are none.
func (*Q[T]) Hybrid ¶ added in v0.3.0
Hybrid ranks the rows by both full-text search of text (Q.Search) and similarity to v (Q.Similar), merged by reciprocal rank fusion: a row's score adds 1/(60 + its rank) for each of the two lists it's in (the best SimilarCandidates of each), so rows that both find come first, without comparing their scores. It needs a search index and an embeddings table. Text without words makes it Q.Similar.
func (*Q[T]) Join ¶
Join adds a join clause written in SQL:
q.Join("JOIN users ON users.id = posts.author_id").Where(db.C("users.active").Eq(true))
The query still selects only T's columns; qualify column names that exist in both tables.
func (*Q[T]) OnlyTrashed ¶
OnlyTrashed returns only soft-deleted rows.
func (*Q[T]) Paginate ¶
Paginate returns page (1-based) of the results, perPage rows each, with the total count. Pages past the end are empty. Limit and Offset set on the query are replaced.
perPage often comes from the request; bound it (for example with validate:"max:100") so a client can't ask for a million rows.
func (*Q[T]) Restore ¶
Restore undeletes the matching soft-deleted rows (live rows are left alone, whatever WithTrashed says).
func (*Q[T]) Scope ¶
Scope applies reusable query modifiers:
func Published(q *db.Q[Post]) *db.Q[Post] { return q.Where(db.C("published").Eq(true)) }
posts, err := db.Query[Post](ctx).Scope(Published).Get()
func (*Q[T]) Search ¶ added in v0.3.0
Search keeps the rows matching text and orders them by relevance, best first; OrderBy terms, before or after, break ties:
posts, err := db.Query[Post](ctx).Where(published).Search(q).Paginate(page, 20)
The model's table needs a search index (migrate.Table.SearchIndex). Every word of text must match in an indexed column: as a prefix for words of three letters or more ("generic" finds "generics"), whole for shorter ones; at most ten words count; with a SEARCH_LANGUAGE other than simple, words are also stemmed. Punctuation is ignored, and text without words leaves the query as it is. A second Search replaces the first. Databases differ in which words they index: MySQL and MariaDB skip words shorter than three letters and common English words ("the"), so there those only match the longer words they start, and are optional next to other words. Update, Delete and CursorPaginate refuse a query with Search.
func (*Q[T]) Similar ¶ added in v0.3.0
Similar keeps the rows that have embeddings (in the table EmbeddingsTable names, made by migrate.Schema.CreateEmbeddings) and orders them by how close their nearest chunk is to v, nearest first: the cosine distance of vectors made by the embedding model named model (other models' vectors are ignored; "" compares every one). It considers the SimilarCandidates nearest chunks. Most apps call it through package ai, which embeds the query text:
posts, err := db.Query[Post](ctx).Where(published).Similar("text-embedding-3-small", queryVector).Limit(10).Get()
The database needs the VectorSearch capability. Search and Hybrid replace it; Update, Delete and CursorPaginate refuse it.
func (*Q[T]) Update ¶
func (q *Q[T]) Update(assignments ...Assignment) (int64, error)
Update sets columns on every matching row and returns how many rows matched. Columns may be qualified with the model's own table. Models with Timestamps also get updated_at set. Model hooks don't run for mass updates.
n, err := db.Query[Post](ctx).Where(author.Eq(id)).Update(published.Set(false))
func (*Q[T]) Where ¶
Where adds conditions, all of which must hold (AND). Build them with Column methods, And, Or, Not and SQL.
func (*Q[T]) WhereDoesntHave ¶ added in v0.1.1
WhereDoesntHave keeps the rows with no related row matching conds.
func (*Q[T]) WhereHas ¶ added in v0.1.1
WhereHas keeps the rows that have at least one related row matching conds (and the relation's own Where conditions):
// posts with an approved comment db.Query[Post](ctx).WhereHas(PostRels.Comments, CommentCols.Approved.Eq(true))
It is an EXISTS subquery; conds use the related model's columns.
func (*Q[T]) WhereKeys ¶ added in v0.3.0
WhereKeys keeps the rows whose primary key is one of ids (none: no rows), as Q.Find does for one.
func (*Q[T]) With ¶ added in v0.1.1
With loads relations of the rows the query returns, with one query per relation (and level of nesting) whatever the number of rows, instead of one per row:
posts, err := db.Query[Post](ctx). With(PostRels.Author, PostRels.Comments.With(CommentRels.Author)). Get()
Get, First, Find, Paginate and CursorPaginate load them; All returns an error, since streaming rows one at a time can't batch their relations.
func (*Q[T]) WithContext ¶
WithContext returns the query with another context, for example a base query built outside a transaction and run inside one:
err := db.Tx(ctx, func(ctx context.Context) error {
rows, err := published.WithContext(ctx).ForUpdate().Get()
…
})
func (*Q[T]) WithTrashed ¶
WithTrashed includes soft-deleted rows.
type Rel ¶ added in v0.1.1
type Rel[T, R any] struct { // contains filtered or unexported fields }
Rel is the relation field of model T that holds R values, as a value to pass to Q.With, Load, Q.WhereHas, Attach and friends. Its methods return a new Rel.
func RelOf ¶ added in v0.1.1
RelOf returns the relation of model T declared on field, a pointer to R or a slice of R with a rel tag. `anetos gen` writes these; declared by hand, a wrong field or type is reported by Rel.Err and by the queries that use it. Nothing is checked before first use, so RelOf is safe in package-level variables.
var PostComments = db.RelOf[Post, Comment]("Comments")
func (Rel[T, R]) Err ¶ added in v0.1.1
Err reports why the relation can't be used, as a query using it would: no such relation field, one that doesn't hold R, a bad rel tag, or key columns that don't exist. Tests can check hand-written handles with it.
func (Rel[T, R]) OrderBy ¶ added in v0.1.1
OrderBy orders the related rows of a has_many or many_to_many relation, and decides which row a has_one relation gets when several match (the first). Without it, primary key order.
func (Rel[T, R]) Where ¶ added in v0.1.1
Where limits the related rows loaded (or required by Q.WhereHas):
PostRels.Comments.Where(CommentCols.Approved.Eq(true))
func (Rel[T, R]) With ¶ added in v0.1.1
With also loads relations of the related rows:
db.Query[Post](ctx).With(PostRels.Comments.With(CommentRels.Author))
func (Rel[T, R]) WithTrashed ¶ added in v0.1.1
WithTrashed includes soft-deleted related rows, which are left out by default.
type Relation ¶ added in v0.1.1
type Relation[T any] interface { // contains filtered or unexported methods }
Relation is a relation of model T, as Q.With, Load, LoadMany and Q.WhereHas take it. Its only implementation is Rel; `anetos gen` writes one per relation field (PostRels.Comments).
type RepeatedQuery ¶ added in v0.2.0
type RepeatedQuery struct {
// Unit is the unit of work: "request GET /posts", "job SendDigest".
Unit anetos.Unit
// SQL is the query, with placeholders: every run had this text, with
// whatever arguments.
SQL string
// Count is how many times the unit ran it.
Count int
// Caller is where the app ran it from ("handlers/posts.go:42"): the
// first frame outside the framework and the standard library when
// Count reached the threshold (a plugin's code counts as the app's);
// empty when there is none.
Caller string
}
RepeatedQuery is a query a unit of work (a request, a job…) ran many times: the sign of an N+1, a query in a loop that one query (eager loading with With, WhereIn, a join) could replace.
func (RepeatedQuery) String ¶ added in v0.2.0
func (r RepeatedQuery) String() string
String describes r in one line.
type RowValues ¶ added in v0.3.0
type RowValues struct {
// Key is the row's primary key.
Key any
// Values are the row's column values.
Values Values
}
RowValues are the values of the row with Key.
type SearchConfig ¶ added in v0.3.0
type SearchConfig struct {
// Language is how words are matched: "simple" (the default) matches
// them as written, in any language; a language stems them ("generic"
// also finds "generics" and "running" finds "run"): "english" on
// PostgreSQL and SQLite, and on PostgreSQL any text search
// configuration the server has (german, french, …). MySQL only has
// "simple". Search indexes are built for it, and the app refuses to
// start when an index was built for another one (run search:reindex).
// SEARCH_LANGUAGE.
Language string `env:"SEARCH_LANGUAGE" default:"simple"`
// Ranking orders results: "default", the database's own ranking, or
// "bm25", which the database must provide (SQLite; PostgreSQL 17+
// with the pg_textsearch extension), or the app refuses to start.
// SEARCH_RANKING.
Ranking string `env:"SEARCH_RANKING" default:"default"`
}
SearchConfig is the full-text search settings, read by Connect.
func (SearchConfig) Validate ¶ added in v0.3.0
func (c SearchConfig) Validate() error
Validate checks the settings' form; DB.CheckSearch checks that the database supports them.
type SearchIndex ¶ added in v0.3.0
type SearchIndex struct {
// Table is the indexed table.
Table string
// Columns are the indexed columns, most important first.
Columns []string
// Language and Ranking are the settings it was built for.
Language, Ranking string
}
SearchIndex is a search index, as recorded by the migration that made it (see migrate.Table.SearchIndex).
func SearchIndexes ¶ added in v0.3.0
func SearchIndexes(ctx context.Context) ([]SearchIndex, error)
SearchIndexes returns the search indexes of the database in ctx, by table name.
type SetExpr ¶ added in v0.3.0
type SetExpr struct {
// SQL is the expression, in the database's SQL.
SQL string `json:"sql"`
// Args are its arguments.
Args []any `json:"args,omitempty"`
}
SetExpr is an assignment of a bulk update that isn't a plain value (Column.SetRaw), as SQL with its arguments.
type SoftDeletes ¶
type SoftDeletes struct {
DeletedAt *time.Time `db:"deleted_at" json:"deleted_at,omitempty"` // nil unless deleted
}
SoftDeletes makes Delete set deleted_at instead of removing the row, and hides deleted rows from queries unless WithTrashed or OnlyTrashed is used.
func (SoftDeletes) Trashed ¶
func (s SoftDeletes) Trashed() bool
Trashed reports whether the row has been soft-deleted.
type Tabler ¶
type Tabler interface {
// TableName returns the table's name. It must not depend on the value.
TableName() string
}
Tabler lets a model choose its table name. Without it, the table is the snake_case plural of the type name (Post → posts, Category → categories).
type Timestamps ¶
type Timestamps struct {
CreatedAt time.Time `db:"created_at" json:"created_at"` // set by Create
UpdatedAt time.Time `db:"updated_at" json:"updated_at"` // set by Create, Update and mass updates
}
Timestamps adds created_at and updated_at columns, set automatically by Create, Update and mass updates.
type Values ¶ added in v0.3.0
Values are a row's column values, by column name: nil, the field's value (ints, floats, strings, bools, []byte, the value of a driver.Valuer), a time.Time (UTC, to the microsecond), or a json.RawMessage for JSON columns.
type Vector ¶ added in v0.3.0
type Vector []float32
Vector is an embedding: a column of the vector type migrations make (migrate.Table.Vector), read and written as the database needs it (pgvector's text, MariaDB's and SQLite's binary float32s).
type Watcher ¶ added in v0.3.0
type Watcher interface {
// Written runs after the write, in its transaction, with its context:
// queries made with ctx are part of the transaction. An error rolls
// the write back and is returned by the call that made it.
Written(ctx context.Context, w *Write) error
}
A Watcher is told about the writes to the tables it watches (DB.Watch).
type Write ¶ added in v0.3.0
type Write struct {
// Op is the kind of write.
Op Op
// Table is the table written.
Table string
// Key is the primary key of the row (single-row writes).
Key any
// Before are the row's values before an update or a force delete,
// read in the write's transaction, so they are the database's.
Before Values
// After are the row's values after a create (without readonly
// columns, which the database sets) or an update (the values before,
// with the columns the update wrote).
After Values
// Bulk describes a bulk write; nil for single-row writes.
Bulk *Bulk
}
Write describes a write to a watched table. Single-row writes set Key and the values; bulk writes set Bulk.
Source Files
¶
Directories
¶
| Path | Synopsis |
|---|---|
|
Package dbtest is a conformance suite for db drivers.
|
Package dbtest is a conformance suite for db drivers. |
|
Package factory makes model values for tests and seeders:
|
Package factory makes model values for tests and seeders: |
|
Package migrate changes database schemas with versioned migrations written in Go (or SQL), compiled into the app's binary, and fills them with seeders.
|
Package migrate changes database schemas with versioned migrations written in Go (or SQL), compiled into the app's binary, and fills them with seeders. |