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

orm

package module
v1.0.0 Latest Latest
Warning

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

Go to latest
Published: Oct 20, 2026 License: MIT Imports: 38 Imported by: 0

README

orm

A Django-style ORM for Go: a model is a plain struct, its manager a package-level value, its queries lazy, immutable QuerySets read with Django's lookups ("age__gte", "roles__name"). Relations and prefetching, aggregates with GroupBy/Having, Case/When, subqueries, text and vector search, raw SQL, schema-per-tenant names sets, mirrored dual writes, and Go migration files written by makemigrations.

Connections are plain database/sql.

go get github.com/vuka-lang/orm

It needs Go 1.27 (it uses generic methods: qs.Values[R](…)).

Quick start

package main

import (
	"context"
	"log"
	"time"

	"github.com/vuka-lang/orm"
	_ "github.com/vuka-lang/orm/postgres" // the driver this program opens
)

//go:generate go run github.com/vuka-lang/orm/cmd/ormgen

type User struct {
	orm.Model[User]
	ID        int64
	Name      string
	Email     string
	Age       int
	CreatedAt time.Time
}

func (u *User) Schema(s *orm.Schema) {
	s.Field(&u.Email, orm.Char{Max: 254, Unique: true})
	s.Field(&u.CreatedAt, orm.Datetime{AutoNowAdd: true})
}

type Post struct {
	orm.Model[Post]
	ID     int64
	Title  string
	Author orm.FK[User]
	Tags   orm.M2M[Tag]
}

func (p *Post) Schema(s *orm.Schema) {
	s.Model(orm.Meta{Ordering: []string{"-id"}})
	s.Field(&p.Title, orm.Char{Max: 200})
	s.Field(&p.Author, orm.Rel{OnDelete: orm.Cascade, Related: "posts"})
}

type Tag struct {
	orm.Model[Tag]
	ID   int64
	Name string
}

func main() {
	ctx := context.Background()
	db, err := orm.Connect("postgres", "postgres://localhost:5432/shop?sslmode=disable")
	if err != nil {
		log.Fatal(err)
	}
	orm.AddDatabase("main", db, orm.AsDefault())
	if err := orm.Check(); err != nil { // models the ORM can't map
		log.Fatal(err)
	}
	if err := orm.Migrate(ctx); err != nil { // the registered migrations
		log.Fatal(err)
	}

	users, posts := orm.For[User](), orm.For[Post]()
	u := User{Name: "Ali", Email: "ali@example.com", Age: 30}
	if err := users.Create(ctx, &u); err != nil {
		log.Fatal(err)
	}
	adults, err := users.Filter(UserFields.Age.Gte(18)).OrderBy(UserFields.Age.Desc()).All(ctx)
	ali, err := users.Get(ctx, UserFields.Email.Eq("ali@example.com"))
	gophers, err := posts.Filter(PostFields.Tags.Name.Eq("go"), PostFields.Author.Email.IEndsWith("@example.com")).
		SelectRelated("author").PrefetchRelated(PostFields.Tags).All(ctx)
	n, err := users.Filter(orm.Or(UserFields.Name.IStartsWith("a"), orm.Not(UserFields.Age.Lt(18)))).Count(ctx)

	ali.Age++
	err = ali.Save(ctx) // a model embedding orm.Model saves itself
	err = orm.Transaction(ctx, func(ctx context.Context) error {
		_, err := users.Filter(UserFields.Age.Lt(18)).Delete(ctx)
		return err
	})
	_, _, _, _ = adults, gophers, n, err
}
Schema

A model declares what its Go types don't say in a Schema method: model attributes with s.Model, field attributes with s.Field, the field named by its address. The ORM calls it once, on a zero value; a field with nothing to say isn't listed, its column (the name in snake_case), NULL-ness (a pointer or vuka.Option) and type coming from the Go field.

func (o *Order) Schema(s *orm.Schema) {
	s.Model(orm.Meta{Table: "tbl_orders", Unmanaged: true}, mirror.Legacy{})
	s.Field(&o.ID, orm.PK{}, mirror.Column{Name: "order_id"})
	s.Field(&o.PlacedAt, orm.Datetime{Column: "placed_at", Null: true}, mirror.Omit{})
	s.Field(&o.Customer, orm.Rel{Column: "customer_id", Related: "orders"})
	s.Field(&o.Paid, orm.Column("is_paid"))
}
  • The attributes are values: orm.PK{}, orm.Char{Max: 50}, orm.Column("x"), orm.DefaultTo(0), … (the table in Models lists them). Any value with Apply(*orm.FieldOptions) is a field attribute (orm.FieldAttr; a Validate(v any) error method adds a check of written values), any with ApplyModel(*orm.ModelConfig) a model attribute (orm.ModelAttr): a package of the app's own (mirror.Column, mirror.Legacy above) plugs in the same way.
  • A field of an embedded struct is named through the model, &a.Created or &a.Stamps.Created. A pointer to no field of the receiver, a nil attribute, or a Schema that panics (a field behind a nil pointer embed) is an *orm.SchemaError (codes E0501, E0502, E0503) every query of the model returns and orm.Check reports.
  • orm.SchemaOf(t) is what a type declares, for packages reading their own attributes back (FieldAttrs("Email"), ModelAttrs()).
  • A Meta() method and orm.SetFieldOptions still work, deprecated for Schema; orm: struct tags are still read, under the Schema's attributes.
Typed fields

ormgen (//go:generate go run github.com/vuka-lang/orm/cmd/ormgen, or orm generate) writes, beside the models of post.go, a post_gen.go with each model's typed fields: PostFields, an orm.Field[V] (an orm.TextField[V] for strings, an orm.NumberField[V] for numbers) per column and a field per relation, and PostPath, the same reached through a relation (UserFields.Posts), whose own relations are methods (PostFields.Author.Posts().Title: a type can't hold itself).

// Code generated by ormgen. DO NOT EDIT.

var PostFields = struct {
	PostPath
	Author UserPath
	Tags   TagPath
}{
	PostPath: PostPathAt(""),
	Author:   UserPathAt("author__"),
	Tags:     TagPathAt("tags__"),
}

type PostPath struct {
	prefix string
	ID     orm.NumberField[int64]
	Title  orm.TextField[string]
}

func PostPathAt(prefix string) PostPath {
	return PostPath{
		prefix: prefix,
		ID:     orm.NumberFieldAt[int64](prefix + "id"),
		Title:  orm.TextFieldAt[string](prefix + "title"),
	}
}

They build the same conditions as orm.Q strings (PostFields.Title.IContains("go") is orm.Q{"title__icontains": "go"}), orderings for OrderBy (PostFields.ID.Desc()), arithmetic for Update (orm.Set{"views": PostFields.Views.Add(1)}, Add/Sub/Mul/Div chaining as orm.NumExprs), fields for SaveFields(ctx, PostFields.Title), and relation paths for Load and PrefetchRelated (PostFields.Tags). A pointer or a vuka.Option[V] field is an orm.Field[V], with IsNull for its NULLs. They are a convenience: without them every query works with strings, and an unknown lookup is a runtime error. examples/shop is a program written this way.

orm.WithDB(ctx, db) sends a context's queries to one database (tests, tools); orm.Using(ctx, "name") to another registered one. A model names its database with orm.On("name") or Meta.DB.

Databases

orm.Default is the program's *orm.Databases: orm.AddDatabase(name, db, opts…) fills it, and the database marked orm.AsDefault() (or the only one) is the default. A process serving several apps gives each its own set (orm.NewDatabases()) and puts it on each request's context with orm.WithDatabases(ctx, set). A database added this way is used as it is (no supervisor): the program opened it, and closes it. To have the ORM open, supervise and close them, declare them instead:

dbs, err := orm.OpenDatabases(ctx,
	orm.Declare("main", os.Getenv("DATABASE_URL"), orm.AsDefault(),
		orm.Replicas(os.Getenv("REPLICA_URL")),
		orm.Pool{MaxOpen: 20, MinIdle: 2, Acquire: 2 * time.Second}),
	orm.Declare("analytics", "postgres://…/analytics?pool_max=5&breaker_failures=3", orm.ReadOnly()),
	orm.Routers(tenantRouter{}))
defer dbs.Close(ctx)
ctx = orm.WithDatabases(ctx, dbs)

orm.Declare(name, url, opts…) takes orm.AsDefault(), orm.ReadOnly() (writes refused, see below), orm.Replicas(urls…), orm.Pool{…}, orm.Credentials(fn), orm.Session{…}, orm.Timeout(d) and orm.SlowQuery(d). orm.OpenDatabases is orm.NewDatabases(opts…) then Start(ctx); with nothing declared it reads DATABASE_URL (as default) and DATABASE_URL_<NAME>, as the orm command does. Start first runs set.Check(): a model's Meta (or orm.On/orm.Mirror), a table's Route or a connection handle naming a database the set doesn't declare fails it, and so does a relation between models on two databases that isn't CrossDB. Start and Stop make a *orm.Databases a Starter/Stopper for frameworks; orm.OnOpen(fn) runs work once the databases are open (migrations, a seed).

Multiple databases

A model's databases come from, first to last:

  1. orm.Using(ctx, name) — every query with that context (orm.WithDB still wins, for tests and tools);
  2. the set's routers — orm.Router (ReadDB, WriteDB, AllowRelation, AllowMigrate), registered with orm.Routers(…) or set.AddRouter(r); the first router naming a database wins; embed orm.BaseRouter for the methods you don't decide;
  3. the model's Meta: DB (reads and writes), Read and Write (over DB), and Mirror (writes repeated there, see orm.Mirror); orm.On and orm.Mirror on For are the same, over Meta;
  4. the table's Route (set.Route("categories", orm.Route{DB: "legacy", Mirror: "main"})) — configuration for tables whose models say nothing;
  5. the default.
func (e *Event) Schema(s *orm.Schema) { s.Model(orm.Meta{DB: "analytics"}) }
func (n *Note) Schema(s *orm.Schema)  { s.Model(orm.Meta{Read: "replica", Write: "main"}) }

type tenants struct{ orm.BaseRouter }

func (tenants) ReadDB(ctx context.Context, m orm.ModelInfo) string  { return tenantOf(ctx) }
func (tenants) WriteDB(ctx context.Context, m orm.ModelInfo) string { return tenantOf(ctx) }
type Event struct {
	orm.Base @orm.Meta{DB: "analytics"}
	…
}

AllowRelation and AllowMigrate return bools, so they can't abstain: every router must allow (BaseRouter allows everything).

Read-your-writes. Reads go to the read side — the Read database, or a replica of the database — except inside a transaction, after a write in the same orm.Scope(ctx), or under orm.Primary(ctx): then a model's reads go where it writes. Middleware starts a scope per request (ormweb does); once a request writes Note, its reads of Note see the write. A scope that wrote a database also sends raw reads of it to the primary. orm.ReadYourWrites(false) on the set turns it off.

Cross-database relations. A relation to a model on another database is declared CrossDB — @orm.Rel{CrossDB: true} in Vuka, orm:"crossdb" in a tag, FieldOptions.CrossDB. Its key is a plain indexed column, with no foreign key constraint; PrefetchRelated, Load and FK.Get read the related rows from their own database, in batches by key; filtering by the key (customer, customer__id) reads the column. A query that would join it — a filter, an ordering or SelectRelated through it (Order.Customer.Name.Eq(…)), or through its inverse — is refused before it is sent, naming the relation. Without CrossDB, a relation between models on two databases fails Check, naming both; routers' AllowRelation is consulted for every relation.

Transactions. orm.Transaction(ctx, fn, opts…) (orm.Atomic, by another name) begins on the database of the first write inside it — reads before it run outside, on the models' write databases. A write to another database fails: a transaction spans one database. orm.TxOn("analytics") names the database from the start; orm.Retry(n) runs the transaction again on a deadlock or serialization failure (Postgres 40001/40P01, MySQL 1213/1205, SQLite busy), with a jittered backoff — fn must be safe to run twice, and only the outermost transaction retries. Nested, Atomic is a savepoint (taken when the transaction begins, if it begins inside). orm.OnCommit(ctx, fn) runs once the transaction commits — never for a rolled back one or a rolled back savepoint: an outbox's hook.

err := orm.Transaction(ctx, func(ctx context.Context) error {
	return e.Save(ctx)
}, orm.TxOn("analytics"), orm.Retry(3))

Migrations. A model's table is migrated on the database its writes go to (routed as above, by orm.Default's routers — register them at init for the orm command, which runs before main) and on its Mirror, where every router's AllowMigrate allows it. orm makemigrations --db analytics writes migrations/analytics/ with the models routed there; orm migrate applies every database's (--db for one). A single-database project is unchanged: the default keeps migrations/.

Raw SQL

type Sale struct {
	Day   time.Time
	Total int64
	Note  vuka.Option[string] // NULL → None; a pointer works too
}

sales, err := orm.Raw[Sale](ctx, "SELECT day, SUM(total) AS total, NULL AS note FROM sales WHERE day > ? GROUP BY day", since, orm.On("analytics"))
n, err := orm.RawRow[int64](ctx, "SELECT COUNT(*) FROM sales", orm.On("analytics")) // NotFound for no row
_, err = orm.Exec(ctx, "DELETE FROM sales WHERE day < ?", cutoff, orm.On("analytics"))
posts, err := Posts.Raw("SELECT * FROM posts WHERE views > ?", 10).All(ctx)          // the model's read database

Columns fill struct fields by db tag, column or name; a column the struct has no field for is an error unless orm.Loose() is among the arguments. ? marks arguments everywhere (written $1… on Postgres; ?? is a literal ?), and values are always bound. Without orm.On a raw read goes to the default database's read side (a replica, outside a transaction or a scope that wrote it), Exec to its primary. orm.Timeout(d) among the arguments bounds the statement. Using, routers, transactions and observers apply as to any query; observers see QueryInfo.DB (and Replica).

Connection handles. A struct embedding orm.Conn, named for a database, is injected by type (ormweb provides it):

@orm.Database("analytics")
type Analytics struct{ orm.Conn }

@web.Get("/report")
func Report(ctx context.Context, a *Analytics) Result[[]Sale] {
	return vuka.Of(orm.Raw[Sale](a.With(ctx), "SELECT day, SUM(total) AS total FROM sales GROUP BY day"))
}

In Go, orm.ConnFor[Analytics]("analytics") declares it and returns its constructor (func(*orm.Databases) *Analytics); orm.HandleOf[T](set) makes one. A Conn is a context.Context too (background, carrying its database), so orm.Raw[R](a, …) works; a.With(ctx) keeps the request's cancellation, transaction and scope. A handle among Raw's arguments routes it as orm.On does. Exec, Ping, Stats, DB, Name are its methods.

Connection pool

Every declared database has a supervisor:

orm.Pool field URL parameter Default
MaxOpen pool_max 20 connections at most; negative: no limit
MaxIdle pool_max_idle 5 idle kept (at most MaxOpen); negative: none
MinIdle pool_min_idle 0 opened at start and kept open while up
MaxLifetime pool_max_lifetime 1h less up to 20% at random, so connections don't all reconnect at once; negative: never retired
MaxIdleTime pool_max_idle_time none
Acquire pool_acquire wait with MaxOpen in use, fail Unavailable after this rather than wait forever
Health pool_health 10s ping interval (primary and replicas)
Breaker{Failures, Cooldown} breaker_failures, breaker_cooldown 5, 5s circuit breaker
StartupWait startup_wait 5s how long Start waits for the database; negative doesn't wait

The URL parameters are taken off before the driver sees the URL (MySQL's still goes through orm.MySQLDSN). A zero field takes the default, so pool_max=0 is 20 connections, not unlimited: write pool_max=-1 for no cap. orm.Connect sets the same caps on the pool it opens.

Read-only databases. orm.ReadOnly() has the ORM refuse its own writes and makes each of the database's connections read-only on the server: MySQL runs SET SESSION TRANSACTION READ ONLY, Postgres sets default_transaction_read_only = on, SQLite PRAGMA query_only = ON. So raw SQL (orm.Raw, orm.RawRow) can't write either. SQL that runs on the connection can still switch those back, though: the only boundary is the database's own grants, a user allowed to SELECT and nothing else. A database added with Add (not declared) gets the ORM's refusal alone.

  • States: Connecting, Up, Degraded (a replica down, or the breaker probing), Down. A database that doesn't answer within StartupWait starts Down and the program starts anyway; the supervisor reconnects in the background with exponential backoff and jitter (100ms to 30s). State changes are logged (through orm.Logger(l), else slog.Default()) and reach orm.ObserveState(fn); nothing else is logged but slow queries (orm.SlowQuery(d)).
  • Circuit breaker: only connection errors count — refused, reset, timed out, dropped; never a query's own error (each driver package classifies its errors: orm.Classify(err)). After Failures in a row, statements fail at once with orm.Unavailable; after Cooldown one statement probes, and its success (or a successful health ping) closes it.
  • Retries: a read that fails on a dropped connection is retried once, on a fresh connection (the older ones are retired); writes never are.
  • Replicas: reads go to the healthy replica least in use, the primary when none is; a replica failing a read or a ping is ejected (the read is retried on the primary) until a ping succeeds again.
  • Credentials: orm.Credentials(func(ctx) (user, pass string, err error)) is asked for every new physical connection (a rotating secret, an IAM token); the driver packages put them into the DSN.
  • Session settings: orm.Session{"statement_timeout": "5s"} runs on each new connection: SET on Postgres, SET SESSION on MySQL, PRAGMA on SQLite.
  • Timeouts: orm.Timeout(d) on Declare bounds every statement; orm.WithTimeout(ctx, d) a context's.
  • Stats: orm.Stats(name), set.StatsOf(name), set.Stats() — database/sql's pool statistics, state, breaker, last error and each replica's health.
  • Shutdown: set.Close(ctx) (or Stop) refuses new statements (Unavailable, "closing"), waits for running ones (transactions, open rows) until ctx's deadline, then closes the pools.

Web integration

github.com/vuka-lang/orm/ormweb serves a set in a vuka web app (web doesn't import the ORM; this package is in the ORM's module):

func main() {
	app := web.New(
		ormweb.Databases(
			orm.Declare("main", os.Getenv("DATABASE_URL"), orm.AsDefault(), orm.Replicas(os.Getenv("REPLICA_URL"))),
			orm.Declare("analytics", os.Getenv("ANALYTICS_URL")),
		),
		web.Use(ormweb.UsingFrom(func(r *http.Request) string { return r.Header.Get("X-Tenant") })),
	)
	log.Fatal(app.Run(""))
}

ormweb.Databases(opts…) is one web.Option. It provides *orm.Databases — a supplied value, so the app's start opens it (waiting StartupWait) before anything built from constructors starts, and its stop closes it gracefully after they stop — and every connection handle type; puts the set and an orm.Scope on each request's context; serves GET /healthz: JSON of the status and each database's state, 503 when the default database is down (in development also each one's pool, breaker, last error and replicas); and adds the set's Check to the app's Build, so a model, route or handle naming a database the app doesn't declare is a Build error, reported with web's own.

Development is VUKA_ENV=development in the program's real environment, read before main: a .env file setting it doesn't count, so a file left on a server can't turn on migrations or the details. Its methods tune it: .MigrateOnStart(b) runs pending migrations as the databases open (by default only in development; production migrates when this says so, or with orm migrate), .HealthDetails(b) gives the health route the details (by default only in development: a driver's error text and the replicas' addresses are for operators), .Health(path) moves the health route ("" drops it). ormweb.Set(s, opts…) serves a set made elsewhere. ormweb.Middleware(s), ormweb.HealthHandler(s) (states) and ormweb.DetailedHealthHandler(s) (the whole Report) are the parts for another router; ormweb.UsingFrom(fn) is middleware sending a request's queries to the database fn names. web has no debug toolbar; observers carry the database name for one.

examples/multidb is a whole app: products on main read from a replica, sales on analytics holding their product by a CrossDB key, a raw report through an injected *Analytics, a tenant's database per request, and /healthz (cd examples/multidb && vuka run .).

Drivers

Drivers are opt-in blank imports, database/sql style; the orm package links none.

Import Driver Connect dialect
github.com/vuka-lang/orm/postgres pgx (github.com/jackc/pgx/v5/stdlib) postgres
github.com/vuka-lang/orm/mysql github.com/go-sql-driver/mysql mysql (orm.MySQLDSN: parseTime on, utf8mb4 in the server's collation)
github.com/vuka-lang/orm/sqlite pure-Go github.com/glebarez/go-sqlite sqlite

The SQLite package also registers l2_distance, cosine_distance, inner_product and trigram similarity in Go, so vector and trigram searches run (unindexed) on SQLite in development and tests; its text search is FTS5 (see Search).

On MySQL, Connect adds charset=utf8mb4 to a DSN naming neither a charset nor a collation: the driver would otherwise greet the server in utf8mb4_general_ci, while MySQL 8 and 9 make tables in utf8mb4_0900_ai_ci, and comparing a string the server derives in the connection's collation (a JSON_TABLE column) with a column fails with an illegal mix of collations. SET NAMES utf8mb4 takes the server's default collation, its tables' — on MySQL 8.0, 8.4, 9 and MariaDB alike. A DSN's own charset or collation is kept; orm.MySQLDSN(dsn) is the DSN Connect opens, for a *sql.DB opened elsewhere. orm.ConnectURL("postgres://…" | "mysql://user:pass@tcp(host)/db" | "sqlite:file.db") picks the dialect from the URL. A SQLite file's directory is made when it is missing (sqlite://data/app.db opens on a fresh checkout), by Connect and by a declared database alike. orm.Open(sqlDB, dialect) wraps a *sql.DB you opened yourself.

Errors

The ORM's errors are *orm.Error values with a Kind; errors.Is matches a kind:

if errors.Is(err, orm.ErrNotFound) { … }
var e *orm.Error
if errors.As(err, &e) && e.Kind == orm.Conflict { log.Print(e.Fields) } // map[email:[already exists]]
Kind When
NotFound Get, First, Refresh, Remove found no row
MultipleFound Get matched several rows
Conflict a write broke a unique constraint (Fields: the column)
Invalid a write referred to a row that doesn't exist (a foreign key)
Unavailable the database didn't answer within MigrateWait (Migrate, Verify); a declared database's breaker is open, no connection was free within Acquire, or it is closing

orm.KindOf(err) is 0 for an error the ORM gives no meaning (a driver's, a bad query). A web framework maps the kinds onto HTTP statuses, for example:

orm Kind HTTP
NotFound 404
MultipleFound 409
Conflict 409 (the column → "already exists")
Invalid 422 (the column → "does not exist")
Unavailable 503
none 500

Observing queries

Every statement passes through observers — the hook for logging, tracing, metrics, a debug toolbar or counting queries in tests:

stop := orm.Observe(func(ctx context.Context, q orm.QueryInfo) {
	slog.DebugContext(ctx, "sql", "sql", q.SQL, "args", len(q.Args), "dur", q.Duration, "rows", q.Rows, "err", q.Err)
})
defer stop()
ctx = orm.WithObserver(ctx, fn) // one context's statements only

QueryInfo carries the SQL, its arguments, the database's name (DB, and Replica for a read on one), start time, duration, rows affected (an exec's; -1 for a query), the error, and Refused: a query the ORM refused before sending (Err says why) is reported too, as a statement that never ran. orm.WatchRepeats(ctx, n, warn) calls warn once a context runs the same statement n times — an N+1 that SelectRelated or PrefetchRelated would read in one query.

Migrations and the orm command

Migrations are Go files in the program's migrations package (migrations/<db> for a database other than the default), each registering its operations from init. The schema a database has is the replay of its migrations; makemigrations compares the replay with the models the program declares. Import the package so orm.Migrate (and the commands) apply them:

import _ "example.com/shop/migrations"
go run github.com/vuka-lang/orm/cmd/orm generate [--tests] [--check] [dir]
go run github.com/vuka-lang/orm/cmd/orm makemigrations [name] [--check] [--dry-run] [--empty] [--noinput] [--db name]
go run github.com/vuka-lang/orm/cmd/orm migrate [target|zero] [--db name] [--noinput]
go run github.com/vuka-lang/orm/cmd/orm showmigrations
go run github.com/vuka-lang/orm/cmd/orm sqlmigrate <name> [--backwards] [--db name]

(go get -tool github.com/vuka-lang/orm/cmd/orm then go tool orm ….)

  • generate writes orm_scanners_gen.go per package: an orm.Register[T]() for every type embedding orm.Model[T] and row scanners/writers that skip reflection, and each model's typed fields in <file>_gen.go beside the file declaring it (see Typed fields). Without the scanners the ORM reads rows by reflection. ormgen.Write / ormgen.Generate are the library form; go:generate go run github.com/vuka-lang/orm/cmd/ormgen ./... works too.
  • The migration commands build the program's main package (--main when the module has several) with an init overlaid that serves orm.ServeCLI before main runs, the migrations packages imported and the generated code overlaid where it isn't on disk; nothing is written to the tree but the migration file.
  • They connect by URL: --database name=URL (repeatable; default= for the default), else DATABASE_URL and DATABASE_URL_<NAME> from the environment, else from the module's .env file. --default main names the default database as the models' orm.On calls name it. The program links the drivers it opens.
  • On a terminal makemigrations asks about renames ("Did you rename users.mail to users.email? [y/N]"); --noinput writes a removal and an addition with a note.
  • On a terminal migrate lists the migrations a target (or zero) would unapply, whose backwards operations may drop tables and their data, and asks before running them; an answer other than yes changes nothing. --noinput (CI) doesn't ask; neither does a migrate whose input isn't a terminal.

At run time orm.Migrate(ctx, orm.MigrateOn("main"), orm.MigrateLogger(l)) applies pending migrations under a lock (Postgres advisory lock, MySQL GET_LOCK), recording them in orm.MigrationsTable (orm_migrations by default; a program whose migrations are recorded under another name sets it). orm.ApplyMigrations(ctx, db, target, migs…) runs a given list (tools, tests). orm.CreateExtension("vector") declares a Postgres extension for the next migration; schema.Verify(ctx, models…) checks models against a database at start up.

Generated code is optional

orm generate only makes a program faster; without its files it behaves the same:

Generated Without it
row scanners and writers rows read and written by reflection
typed field sets orm.Q{"name__istartswith": w}, orm.F("name"); an unknown lookup is a runtime error with a did-you-mean
orm.Register[T]() per model a model's orm.For[T](), or its first Objects, registers it; orm.Check(models…) takes others

Each model looks for its scanner once, on its first query, and keeps the answer; a scanner whose columns aren't the model's is ignored. The cost of reflection (Apple M1 Pro, go test -bench Fallback, generated → reflection):

stub driver (the ORM's own cost) SQLite
1000 rows, 6 columns 146 → 195 µs (1.34×) 1.65 → 1.74 ms (1.05×)
1000 rows, 23 columns 525 → 875 µs (1.67×) 5.20 → 5.59 ms (1.07×)
one row by id 1.8 → 2.0 µs (1.12×)
insert 1000 rows 609 → 690 µs (1.13×)

ORM_REFLECT=1 go test runs the ORM's own suite with the generated scanners ignored; TestReflectionFallback does so on every go test.

Testing

github.com/vuka-lang/orm/ormtest opens a fresh database per test with the models' tables made — in-memory SQLite by default, or a schema/database of its own on a real server with ORMTEST_DRIVER=postgres|mysql and ORMTEST_DSN:

ctx := ormtest.Open(t, Users, Posts)
ormtest.Seed(t, ctx, Users, &User{Name: "Ali"})
ctx, queries := ormtest.CountQueries(ctx)

Text search (Django's contrib.postgres.search) and vector search (pgvector's), each written for the database the query runs on:

Postgres MySQL SQLite
text tsvector, websearch_to_tsquery, GIN MATCH … AGAINST, FULLTEXT FTS5 table <table>_fts, kept by triggers
rank ts_rank relevance -bm25(…), the document's weights per column
headline ts_headline substring about the first word snippet() / highlight()
trigrams pg_trgm (%, similarity, GIN/GiST) LIKE similarity in Go, unindexed
vectors pgvector (<->, <=>, <#>, HNSW/IVFFlat) stored (VECTOR(n) on 9, VARBINARY before), not searched distances in Go, unindexed (O(n))
Declaring a document

A document is fields searched together. Declare it as a generated TSVector column, a FullTextIndex, or a GIN index of a SearchVector; each database makes what it can of the declaration:

type Post struct {
	orm.Model[Post]
	ID        int64
	Title     string
	Body      string
	Search    orm.TSVector `orm:"generated"`
	Embedding orm.Vector   `orm:"vector:384"`
}

func (Post) Generated() map[string]orm.Expr {
	return map[string]orm.Expr{"Search": orm.SearchVector("title").Weight("A").
		Add(orm.SearchVector("body").Weight("B")).Config("english")}
}

func (Post) Indexes() []orm.Index {
	return []orm.Index{
		orm.GinIndex("search"),                                     // Postgres
		orm.FullTextIndex("title", "body"),                         // MySQL's FULLTEXT; GIN on Postgres
		orm.HnswIndex("embedding").Ops(orm.Cosine).M(16).EfConstruction(64),
		orm.GinIndex("title").Trigram(),
	}
}

On SQLite the declaration becomes an external-content FTS5 table, posts_fts (content=posts, content_rowid=id), over the document's fields, with AFTER INSERT/UPDATE/DELETE triggers keeping it in step and a rebuild indexing the rows already there. Migrations make and drop it as an index (m.Index{Name: "posts_fts", SQL: …, Drop: …}, SQLite's only), so makemigrations adds it, unapplying removes the table and its triggers, and a table SQLite rebuilds keeps it. An english config stems by FTS5's porter tokenizer; others by unicode61. A model with no document, or whose key isn't an integer, is searched by LIKE on SQLite, unindexed.

Text queries

orm.SearchQuery is web search syntax: every word must match, "a phrase" in order, or between alternatives, -word must not, and word* matches words starting with it.

doc, q := orm.SearchVector("title", "body").Config("english"), orm.SearchQuery(`go -java "web server" or rust*`)
Posts.Filter(orm.Match(doc, q)).
	AnnotateAs(orm.SearchRank(doc, q).As("rank"),
		orm.Headline("body", q, orm.Marks("<mark>", "</mark>"), orm.MaxWords(20)).As("head")).
	OrderBy("-rank")
Posts.Filter(orm.Q{"title__search": "go generics"})                 // the document holding title
Posts.Filter(orm.SearchRank(doc, q).Gt(0.1))                        // typed: OrderedExpr[float64]

The text is always a bound argument: on SQLite it is rewritten into an FTS5 query of quoted phrases (("go" AND "web server") NOT ("java")), never FTS5 syntax of the user's. Where the databases differ:

  • Stopwords. Postgres's english drops its stopwords from queries; SQLite drops the same list from an english document's queries. MySQL drops InnoDB's, and words shorter than innodb_ft_min_token_size.
  • Prefixes. Postgres has no word* in websearch_to_tsquery: a query with one is built of plainto_tsquery / phraseto_tsquery / to_tsquery('word':*) joined by &&, ||, !!.
  • Exclusions only. -java alone matches the rows without it on Postgres and SQLite (a NOT IN of the FTS5 match); MySQL's boolean mode matches nothing.
  • Ranking. ts_rank weighs A 1, B 0.4, C 0.2, D and unweighted 0.1; SQLite passes the same weights to bm25 (0 for columns outside the document), but bm25 also normalises by length: a match in a short text ranks above one in a long text that ts_rank scores alike.
  • Headlines. Marks, MaxWords (at most 64 on SQLite) and HighlightAll map onto ts_headline's StartSel/StopSel/MaxWords/ HighlightAll and onto snippet/highlight. A row without the words gets its first 160 characters about the first word, unmarked, on SQLite; MySQL always gets that substring.

orm.L2Distance, CosineDistance and InnerProduct (negated, as <#>: smaller is nearer) are OrderedExpr[float64], so they filter and order:

Posts.Filter(orm.CosineDistance("embedding", v).Lt(0.3)).Order(orm.CosineDistance("embedding", v).Asc())

Nearest is the k-nearest-neighbour query: ordered by distance, nearest first, annotated as distance:

Posts.Nearest("embedding", v, orm.K(10)).All(ctx)
Posts.Nearest("embedding", v, orm.K(10), orm.MaxDistance(0.4), orm.DistanceAs("d"), orm.EfSearch(100)).
	Values[Hit]("id", "title", "d")

The metric is the field's vector index's (Ops), L2 without one, or an option (orm.Cosine). On Postgres the ORDER BY is pgvector's operator, so an HNSW or IVFFlat index of that metric serves it; orm.EfSearch(n) (hnsw.ef_search) and orm.Probes(n) (ivfflat.probes) are set with SET LOCAL in a transaction of the query's own, or a savepoint of the context's, and end with it. SQLite computes the distance in Go for every row. MySQL stores vectors but doesn't search them: MySQL 9's VECTOR type has no distance function outside HeatWave. A Vector field is a VECTOR(n) column where the server has the type (MySQL 9, MariaDB 11.7; the ORM reads VERSION() once when it makes tables or runs migrations), else a VARBINARY(4n) holding the same little-endian float32s, so an upgraded server turns it into a VECTOR in place (ALTER TABLE … MODIFY … VECTOR(n)). Vector indexes are left out, and a distance, Nearest or Hybrid fails when the query is written, naming the field. orm sqlmigrate --dialect mysql writes the VARBINARY form; a server's version after a slash (mysql/9.3.0) writes its own.

Hybrid ranks rows by a text match and by a vector distance and fuses the ranks by reciprocal rank (a row scores w/(k + rank) per ranking, the text's only where the text matches), annotated rank, distance and score, best first:

Posts.Hybrid(doc, orm.SearchQuery("web server"), "embedding", v,
	orm.K(10), orm.Candidates(100), orm.HybridWeights(1, 0.5), orm.RRF(60)).All(ctx)

Every row is ranked by distance unless Candidates(n) (the n nearest, found by the vector index) or MaxDistance narrow them to those plus the text's matches. It runs on Postgres and on SQLite (FTS5 and Go distances). orm.Fuse("-rank", "distance").K(20).Weights(1, 0.5) is the fusion on its own, over any annotations.

Custom functions

Typed functions

A database function is defined once, with its SQL per dialect, and called like a Go function. Its call is an expression whose comparisons take the function's result type, so a condition on it type-checks:

var (
	EditDist = orm.OrderedFunc[int]("editdist", orm.Arity(2),
		orm.Template("postgres", "levenshtein({0}, {1})"), // fuzzystrmatch
		orm.Template("sqlite", "editdist({0}, {1})"))       // a Go function, below
	Unaccent = orm.TextFunc("unaccent", orm.Arity(1),
		orm.Template("postgres", "unaccent({0})"), orm.Template("", "{0}")).Transform()
)

Users.Filter(EditDist(orm.F("name"), "jon").Le(1)).
	Order(EditDist(orm.F("name"), "jon").Asc(), orm.Ordering{Path: []string{"age"}, Desc: true})
Users.AnnotateAs(EditDist(orm.F("name"), q).As("dist")).Filter(orm.Q{"dist__lt": 3}).Values[[]any]("name", "dist")
Users.Filter(Unaccent(orm.F("name")).IContains("jose"))   // or orm.Q{"name__unaccent__icontains": "jose"}
Users.Filter(…).Update(ctx, orm.Set{"name": orm.Upper(orm.F("name"))})

Arguments are orm.Exprs (orm.F("field") for a field, another call, a Case) or plain values, which are always bound as parameters, never written into the SQL. {0}, {1} stand for the arguments in a Template, {*} for all of them; "" is the template of every dialect not named. A function with no Template is called as name(args…); one with templates is written only where one applies — elsewhere its query fails with function editdist has no SQL on mysql. orm.Arity(n) checks the number of arguments.

Go can't give a generic type's methods to some of its type arguments, so the constructor picks the comparisons:

Constructor Call's type Comparisons
orm.Func[R] TypedExpr[R] Eq, Ne, In, IsNull, NotNull, Asc, Desc, As, Over
orm.OrderedFunc[R] (numbers, strings, time.Time) OrderedExpr[R] those, and Lt, Le, Gt, Ge, Between
orm.TextFunc TextExpr those, and Contains, IContains, StartsWith, EndsWith, Like

Contains on a number doesn't compile. orm.Typed[R](e), orm.Ordered[R](e) and orm.Text(e) give any expression a type (orm.Text(orm.F("title")).Like("Go%")); every typed expression is an Expr, so Where(e, lookup, v) still takes any lookup, and comparisons go in Filter, Exclude, Having and When. The built-ins are typed the same way and keep their Of: orm.Lower, Upper, Trim (text), Length (int64), Abs (float64), Year, Month, Day (int), Date (the date as YYYY-MM-DD text, which every database compares with a date), Coalesce (any). orm.Function(name, …) remains the untyped form (.Of(args…)), falling back to name(args…) on a dialect without a template.

QuerySet.Order(terms…) sorts by expression terms (e.Asc(), e.Desc(), orm.Asc(e), orm.Desc(e)) and orm.Orderings, mixed; AnnotateAs takes expressions named by As.

Aggregates and windows

orm.IsAggregate() (or orm.Aggregate[R]) marks an aggregate: an annotation of it groups a Values listing it by the other names, a condition on it is HAVING's, and orm.AggOf hands it to Aggregate.

var StringAgg = orm.TextFunc("string_agg", orm.IsAggregate(),
	orm.Template("postgres", "string_agg({0}, {1})"),
	orm.Template("mysql", "GROUP_CONCAT({0} SEPARATOR ', ')"),
	orm.Template("sqlite", "group_concat({0}, {1})"))

Users.Annotate("names", StringAgg(orm.F("name"), ", ")).
	Having(StringAgg(orm.F("name"), ", ").Contains("Ali")).Values[[]any]("active", "names")
Users.Aggregate(ctx, orm.AggOf("names", StringAgg(orm.F("name"), ", ")))

.Over(orm.Window{PartitionBy: []string{"team_id"}, OrderBy: []string{"-score"}}) makes a call a window function's (row_number, rank, or an aggregate over a window, which then no longer groups).

Lookups and transforms

orm.RegisterLookup adds a lookup to Q keys and Where, Django's register_lookup. Its function writes the condition per dialect from the left side's SQL and the value (rhs() binds it, once per call); ok false means no SQL on that dialect, and the query says so:

orm.RegisterLookup("unaccent_icontains", func(lhs string, rhs func() string, d orm.Dialect) (string, bool) {
	if d.Name() != "postgres" {
		return "", false
	}
	return "unaccent(" + lhs + ") ILIKE unaccent(" + rhs() + ") ESCAPE '!'", true
}, orm.LookupValue(orm.LikePattern("%", "%")))

Posts.Filter(orm.Q{"title__unaccent_icontains": "cafe"})
Posts.Filter(orm.Where(orm.Lower(orm.F("title")), "unaccent_icontains", "cafe"))

orm.LookupValue maps the value before it is bound (orm.LikePattern escapes a literal into a LIKE pattern). A one-argument function made a transform with .Transform() is a key step before any lookup, built-in or registered: Q{"title__unaccent__icontains": "cafe"}, Q{"title__unaccent__unaccent_icontains": "cafe"}. An unknown lookup or transform fails with a suggestion: unknown lookup "icontain" — did you mean "icontains"?.

Go functions on SQLite

orm/sqlite's Func registers a Go function as an SQL function on every SQLite connection opened after it — register in an init, before Connect:

func init() {
	sqlite.MustFunc("editdist", func(a, b string) int { … })      // deterministic
	sqlite.MustFunc("now_ms", func() int64 { … }, sqlite.Volatile())
	sqlite.MustFunc("initial", func(s *string) *string { … })    // pointers: NULL in and out
}

Parameters and results are strings, []byte, bools, numbers, time.Time, any or pointers to them (a driver.Valuer result too), the last parameter variadic or not, an error result after the value; a NULL passed to a non-pointer parameter makes the call NULL without running it. They run unindexed. The driver registers scalar functions only (no Go aggregates).

Per dialect

Postgres and MySQL run functions written in SQL. Create them in a migration with migration.CreateFunction (unapplying drops them; SQLite gets nothing — use a Go function there), or migration.RunSQL for anything else:

m.CreateFunction{
	Name:    "initials",
	Params:  m.Dialects{Postgres: "s text", MySQL: "s VARCHAR(255)"},
	Returns: m.Dialects{Postgres: "text", MySQL: "VARCHAR(255)"},
	Body:    m.Dialects{Postgres: "SELECT upper(left(s, 1))", MySQL: "RETURN UPPER(LEFT(s, 1))"},
}

It is IMMUTABLE / DETERMINISTIC unless Volatile; Language picks a Postgres language (plpgsql) and a MySQL BEGIN … END body runs as one statement. Extension functions (levenshtein, unaccent) come with m.CreateExtension{Name: "fuzzystrmatch"}.

Vuka

In Vuka a model's attributes are written on its fields, and its queries are the engine's, through the same typed fields Go code uses. Vuka writes the plain Go: Go and Vuka packages share models and databases.

type Post struct {
	orm.Base  @orm.Meta{Table: "posts", Ordering: []string{"-published"}}
	ID        int64             @orm.PK
	Title     string            @orm.Char{Max: 200} @orm.Trigram
	Slug      string            @orm.Slug{Unique: true}
	Author    orm.FK[User]      @orm.Rel{OnDelete: orm.Cascade, Related: "posts"}
	Tags      orm.M2M[Tag]
	Published Option[time.Time] @orm.Index
	Views     int               @orm.DefaultTo(0)
}

func Post.Live() orm.QuerySet[Post] { return Post.Objects.Filter(Post.Published.IsNull(false)) }

posts := Post.Objects.Filter(Post.Title.Contains("Go"), Post.Author.Name.Eq("ada")).
	OrderBy(Post.Views.Desc()).All(ctx)?

@orm.Transaction
func publish(ctx context.Context, slug string) error {
	p := Post.Objects.Get(ctx, Post.Slug.Eq(slug))?
	p.Published = Some(time.Now())
	return p.Save(ctx) // an error rolls back
}

is, in Go:

Vuka Go
orm.Base and the fields' attributes orm.Base[Post] and a Schema method: s.Model(orm.Meta{…}), s.Field(&p.Title, orm.Char{Max: 200}, orm.Trigram{})
Post.Objects orm.Objects[Post](), the model's manager
Post.Title, Post.Author.Name PostFields.Title, PostFields.Author.Name: the typed fields orm generate writes (the vuka command overlays them until it has)
func Post.Live() func PostLive()
@orm.Transaction (@orm.TxOn("db"), @orm.Retry(3)) orm.Transaction(ctx, func(ctx context.Context) error { … }, orm.TxOn("db"), orm.Retry(3))

Everything else is the engine as Go has it: Filter, OrderBy, Annotate, Values[R], Get and First returning (T, error) (? takes them; errors.Is(err, orm.ErrNotFound) tells none), row methods (Save, SaveFields(ctx, Post.Title), Load(ctx, Post.Tags), p.Tags.Add(ctx, &tag)), expressions through a field's Expr() (orm.Lower(Post.Title.Expr())) and names through its Name(). vukatest runs these on SQLite (vuka test ./vukatest/...), and on Postgres or MySQL with ORMTEST_DRIVER.

  • orm.Base is orm.Model without its Objects method, so that Post.Objects is the static Vuka gives the model. @orm.Meta{…} @orm.Entity on the type works too; the orm.Entity type decorator registers the model at start-up, so orm.Check knows it without orm generate.
  • Field attributes are the values of Schema — @orm.PK is orm.PK{}, @orm.Column("x") is orm.Column("x") — and an attribute of the program's own is any value with Apply(*orm.FieldOptions).
  • orm.FK[T], orm.M2M[T] and Option[T] fields are as in Go.

Earlier releases gave Vuka a layer of its own: orm.Query[T], field references typed by their model (vuka.Pred[T]), package orm/fn, orm.Into, and terminals returning Result. It still compiles for code that names it, and goes in the next minor release.

Migrations

orm makemigrations and the other commands build the program through vuka gen when the module has .vuka files (the vuka command on the PATH, or VUKA); a main package declaring models gets the tool's init after its own, so its registrations and field attributes are in. See examples/blog: models with attributes, its migrations written by the command, queries with field references, match on Get, First's Option, orm.Transaction, a typed SQLite function in Go, text, trigram and vector search:

cd examples/blog && vuka run .

Extension points

Two seams let a layer above the ORM (a language runtime, a schema file) drive it without struct tags or the Q map syntax:

  • Predicates as data. orm.Pred{Path, Op, Value, Args} — a field path, an operator (OpEq, OpGte, OpIn, OpIContains, …) and a value, or OpAnd/OpOr/OpNot of predicates — is a Cond that Filter, Exclude and Get take. A foreign type implements orm.Predicate (Pred() orm.Pred) and passes orm.PredCond(p). orm.Ordering{Path, Desc} and orm.Orderings(…) are ordering's counterpart for OrderBy.
  • Field metadata from outside the struct. A model's Schema method (see Schema) takes attributes of any package implementing orm.FieldAttr or orm.ModelAttr. orm.SetFieldOptions(fn), which supplies orm.FieldOptions per field over the tags, is deprecated for it.

License

MIT

Documentation

Overview

Package orm is a Django-style ORM for Go: a model is a plain struct, its manager a package-level value, and its queries lazy, immutable QuerySets read with Django's lookups.

var Users = orm.For[User]()

adults, err := Users.Filter(orm.Q{"age__gte": 18}).OrderBy("-age").All(ctx)
u, err := Users.Get(ctx, orm.Q{"email": e})

Connections are database/sql's: a model reads and writes through the database added under its name (orm.AddDatabase; the default unless the model names another), or the one a context carries (orm.WithDB).

Index

Constants

View Source
const (
	DefaultMaxOpen     = 20
	DefaultMaxIdle     = 5
	DefaultMaxLifetime = time.Hour
)

Pool defaults: a cap on connections, so a burst of requests can't exhaust the server's.

View Source
const (
	Cascade  = "cascade"
	SetNull  = "set_null"
	Restrict = "restrict"
)

What deleting a related row does to the rows holding its key (Rel's OnDelete).

View Source
const CLIEnv = "ORM_CLI_REQUEST"

CLIEnv is the variable the orm command sets to run a program as its migration tool (makemigrations, migrate, showmigrations, sqlmigrate), holding a JSON CLIRequest.

Variables

View Source
var (
	AsInt      = CastType{/* contains filtered or unexported fields */}
	AsFloat    = CastType{/* contains filtered or unexported fields */}
	AsText     = CastType{/* contains filtered or unexported fields */}
	AsDate     = CastType{/* contains filtered or unexported fields */}
	AsDateTime = CastType{/* contains filtered or unexported fields */}
)

The types Cast converts to. AsDecimal and AsType make others.

View Source
var (
	ErrNotFound      error = NotFound
	ErrMultipleFound error = MultipleFound
	ErrConflict      error = Conflict
	ErrInvalid       error = Invalid
	ErrUnavailable   error = Unavailable
)

The kinds as error values, for errors.Is.

View Source
var (
	Lower  = TextFunc("lower", Arity(1)).Transform()
	Upper  = TextFunc("upper", Arity(1)).Transform()
	Trim   = TextFunc("trim", Arity(1)).Transform()
	Length = OrderedFunc[int64]("length", Arity(1),
		Template("mysql", "CHAR_LENGTH({0})"), Template("", "length({0})")).Transform()
	Abs  = OrderedFunc[float64]("abs", Arity(1)).Transform()
	Year = OrderedFunc[int]("year", Arity(1),
		Template("postgres", "CAST(EXTRACT(YEAR FROM {0}) AS INTEGER)"),
		Template("mysql", "year({0})"),
		Template("sqlite", "CAST(strftime('%Y', {0}) AS INTEGER)")).Transform()
	Month = OrderedFunc[int]("month", Arity(1),
		Template("postgres", "CAST(EXTRACT(MONTH FROM {0}) AS INTEGER)"),
		Template("mysql", "month({0})"),
		Template("sqlite", "CAST(strftime('%m', {0}) AS INTEGER)")).Transform()
	Day = OrderedFunc[int]("day", Arity(1),
		Template("postgres", "CAST(EXTRACT(DAY FROM {0}) AS INTEGER)"),
		Template("mysql", "day({0})"),
		Template("sqlite", "CAST(strftime('%d', {0}) AS INTEGER)")).Transform()
	Date = OrderedFunc[string]("date", Arity(1),
		Template("postgres", "CAST({0} AS DATE)"),
		Template("mysql", "DATE({0})"),
		Template("sqlite", "date({0})")).Transform()
	Coalesce = Func[any]("coalesce")
)

Functions every model can use, transforms all: Q{"name__lower": "ali"}, Q{"created_at__year": 2026}, Q{"created_at__date": "2026-10-06"}. Called, each is typed: orm.Lower(orm.F("name")).StartsWith("a"); Of gives the untyped call. Date's value is the date as text, YYYY-MM-DD, which every database compares with a date.

View Source
var Clock = time.Now

Clock is the time auto_now and auto_now_add fields take.

View Source
var Default = NewDatabases()

Default is the program's databases: those AddDatabase adds, queried by every context without WithDatabases.

View Source
var ErrUnfiltered = errors.New("orm: no condition")

ErrUnfiltered is the error of an Update or Delete with no condition: one that would change every row of the table. Unfiltered says that is what is meant.

View Source
var MigrateWait = 30 * time.Second

MigrateWait is how long Migrate (and Verify) wait for a database to answer.

View Source
var MigrationsTable = "orm_migrations"

MigrationsTable is the table recording each database's applied migrations. Set it before migrating to keep a history recorded under another name.

Functions

func AddDatabase

func AddDatabase(name string, db *DB, opts ...DatabaseOption)

AddDatabase adds db to Default as name.

func ApplyMigrations

func ApplyMigrations(ctx context.Context, d *DB, target string, migrations ...migration.Migration) error

ApplyMigrations takes d through migrations (one database's) as orm migrate does: to target (a name, or its leading number), applying those up to it and unapplying those after; "zero" unapplies them all, "" applies them all. For tools and tests.

func Atomic

func Atomic(ctx context.Context, fn func(ctx context.Context) error, opts ...TxOption) error

Atomic runs fn in a transaction: committed when fn returns nil, rolled back when it returns an error or panics. Queries take part when they use the ctx fn is given. The first write inside it picks the database (each model's write database, as it routes); reads before it run outside, on the models' write databases, and a write to another database fails: a transaction spans one database. TxOn names it from the start; Retry retries deadlocks. Inside another Atomic it is a savepoint, rolled back alone. OnCommit runs work once it commits.

func AtomicOn

func AtomicOn(ctx context.Context, db *DB, fn func(ctx context.Context) error, opts ...TxOption) error

AtomicOn is Atomic on a given database.

func Check

func Check(models ...AnyManager) error

Check is what keeps the ORM from mapping the program's models — those Register added, and models — joined: run it at start up, so a model it can't map fails there rather than on its first query.

func ConnFor

func ConnFor[T any](name string) func(*Databases) *T

ConnFor declares T (a struct embedding orm.Conn) the handle of the database name, as @orm.Database does, and returns its constructor from a Databases, for a DI container.

type Analytics struct{ orm.Conn }
app.Provide(orm.ConnFor[Analytics]("analytics"))

func CreateTables

func CreateTables(ctx context.Context, models ...AnyManager) error

CreateTables creates the models' tables, and the tables between their many-to-many relations, where they are not already, and the extensions CreateExtension declared for the models' databases: for tests and tools. Foreign keys are made between the tables it makes only. A deployed schema changes through migrations (orm.Migrate).

func Database

func Database(name string) func(*vuka.Type)

Database is a Vuka type decorator naming the database a connection handle (a struct embedding orm.Conn) is on; ormweb provides it, made from the app's Databases, to whatever takes a *T.

@orm.Database("analytics")
type Analytics struct{ orm.Conn }

func Entity

func Entity(t *vuka.Type)

Entity is a type decorator registering a Vuka model at start-up, so Check and the migration commands know it without orm generate; an @orm.Meta{…} on the type is its Meta.

@orm.Meta{Table: "posts", Ordering: []string{"-published"}}
@orm.Entity
type Post struct { orm.Base; … }

func Exec

func Exec(ctx context.Context, sql string, args ...any) (int64, error)

Exec runs sql, a write or DDL, on the default database (or the one an option among args names, as Raw's do), inside ctx's transaction when there is one, and says how many rows it changed. A raw write is the database's alone: OnChange hears nothing of it and no mirror repeats it.

func ExecOn

func ExecOn(ctx context.Context, s DBSchema, sql string, args ...any) (int64, error)

ExecOn is Exec on schema s's database.

func FieldName

func FieldName[T any](p Path[T]) string

FieldName is a field reference's lookup path (Post.Author.Name → "author__name"), for the ORM's string APIs.

func Generated

func Generated[T any]() bool

Generated is whether T's rows are read by a generated scanner: one is registered and reads the model's columns.

func HandleOf

func HandleOf[T any](s *Databases) *T

HandleOf is the handle T on s, of the database T was declared for.

func Handles

func Handles() []reflect.Type

Handles is the connection handle types declared (@orm.Database, ConnFor), for a framework to provide.

func LikePattern

func LikePattern(before, after string) func(any) any

LikePattern is a LookupValue making the value a LIKE pattern matching it literally, between before and after (% for any run): its own %, _ and ! escaped by !, so the lookup's SQL ends with ESCAPE '!'.

func LookupPath

func LookupPath(p vuka.FieldPath) string

LookupPath is a field reference's path as lookups write it: author__name.

func MakeHandle

func MakeHandle(s *Databases, t reflect.Type) reflect.Value

MakeHandle is a *T of the handle type t on s.

func Migrate

func Migrate(ctx context.Context, opts ...MigrateOption) error

Migrate applies the registered migrations to ctx's databases (Default, or WithDatabases's), at start up before the program serves: those not yet recorded in each database's MigrationsTable, each after its dependencies. The program's migrations package registers them: import it.

import _ "example.com/shop/migrations"

if err := orm.Migrate(ctx); err != nil {
	log.Fatal(err)
}

It waits up to MigrateWait for each database to answer. A migration runs in a transaction where the database can roll DDL back (Postgres, SQLite; MySQL commits each statement). Replicas starting together take turns under a lock (Postgres advisory, MySQL GET_LOCK). orm migrate applies them (or unapplies them, back to a migration) on demand.

func MySQLDSN

func MySQLDSN(dsn string) string

MySQLDSN is a go-sql-driver DSN as the ORM reads MySQL: parseTime on, which time fields need, and, unless the DSN names a charset or a collation, charset=utf8mb4 with no collation. The driver otherwise greets the server in utf8mb4_general_ci, while MySQL 8 and later make tables in their default, utf8mb4_0900_ai_ci: a string the server derives in the connection's collation (a JSON_TABLE column) then can't be compared with a column (Error 1267, illegal mix of collations). SET NAMES utf8mb4 takes the server's default collation for utf8mb4, the one its tables have: MySQL 8.0, 8.4 and 9 and MariaDB alike. A DSN's own charset or collation is left as it is.

func Observe

func Observe(fn Observer) (stop func())

Observe has fn see every statement the program's queries run, whatever their context: the hook a framework's tracer or debug toolbar plugs into. It returns what stops it.

func ObserveState

func ObserveState(fn func(StateEvent)) (stop func())

ObserveState calls fn on each change of a supervised database's state: for metrics and alerts (the supervisor logs them too). It returns what stops it.

func OnCommit

func OnCommit(ctx context.Context, fn func(ctx context.Context)) error

OnCommit runs fn once ctx's transaction commits — at once outside one; a rolled back transaction never runs it, nor a savepoint's rolled back part. The way to send mail or enqueue work (an outbox) only for writes that stay.

func OnMirrorError

func OnMirrorError(fn func(ctx context.Context, e MirrorError)) (stop func())

OnMirrorError calls fn for each mirrored write that fails, beside the log: the place to queue the table for a re-sync. It returns what stops it.

func Operand

func Operand(v any) any

Operand is v as the engine takes a value that may be an expression: a typed field or field reference as the field, an Expr as is, an orm.FK its key, an Option its value, anything else as is (bound, and typed by the engine where it must be, as in Case results on Postgres).

func OptValue added in v1.0.0

func OptValue[V any](o vuka.Option[V]) any

OptValue is an Option field's value for the driver, for generated writers: nil for None, else the value.

func Optional added in v1.0.0

func Optional[T any](row T, err error) (vuka.Option[T], error)

Optional is a row First or Get found as an Option, None when ErrNotFound says there is none; other errors stay errors:

p, err := orm.Optional(orm.Objects[Post]().Filter(f.Slug.Eq(slug)).First(ctx))
if err != nil { … }
if post, ok := p.Get(); ok { … }

func Orderings

func Orderings(os ...Ordering) []string

Orderings is the terms as OrderBy takes them.

qs.OrderBy(orm.Orderings(orm.Ordering{Path: []string{"age"}, Desc: true})...)

func P

func P[T any](c Cond) vuka.Pred[T]

P is an ORM condition as a predicate on T, beside field references' own: Q, Or, Exists, Match — whatever has no Vuka form.

Post.Objects.Filter(Post.Views.Gt(10), orm.P[Post](orm.Q{"title__iregex": "^go"}))

func ParseURL

func ParseURL(url string) (dialect, dsn string, err error)

ParseURL is a database URL's dialect and the DSN its driver takes, as ConnectURL reads it.

func PathName

func PathName(v any) string

PathName is a field reference's lookup path, a named term's name, or a string as is; "" for an unnamed term.

func Primary

func Primary(ctx context.Context) context.Context

Primary has ctx's reads go where the models write: never to a replica, nor a read database a Meta or router names. For a read that must see a write just made outside a Scope.

func PtrValue

func PtrValue[V any](p *V) any

PtrValue is a pointer field's value for the driver, for generated writers: nil for nil, the pointer for a driver.Valuer, else what it points to.

func Raw

func Raw[R any](ctx context.Context, sql string, args ...any) ([]R, error)

Raw is the rows sql reads, read into R as Values reads them: a scalar for one column, a struct (a field per column, by orm path tag, db or column tag, name or column; Option or a pointer for a NULL), map[string]any keyed by column, or []any. ? marks each argument (written as the database's, $1 on Postgres; values are always bound), ?? is a literal ?. A column the struct has no field for is an error, unless Loose.

It reads the default database (its replica, outside a transaction or a Scope that wrote it), or the one an option among args names: orm.On("analytics"), or a connection handle (a struct embedding orm.Conn); Timeout bounds it. Using, transactions and observers apply as to any query.

n, err := orm.Raw[int64](ctx, "SELECT COUNT(*) FROM users WHERE is_active = ?", true)
sales, err := orm.Raw[Sale](ctx, "SELECT day, SUM(total) AS total FROM sales GROUP BY day", orm.On("analytics"))

func RawIter

func RawIter[R any](ctx context.Context, sql string, args ...any) iter.Seq2[R, error]

RawIter is Raw one row at a time.

func RawIterOn

func RawIterOn[R any](ctx context.Context, s DBSchema, sql string, args ...any) iter.Seq2[R, error]

RawIterOn is RawOn one row at a time.

func RawOn

func RawOn[R any](ctx context.Context, s DBSchema, sql string, args ...any) ([]R, error)

RawOn is Raw on schema s's database.

n, err := orm.RawOn[int64](ctx, Legacy, "SELECT COUNT(*) FROM auth_user WHERE is_active = ?", true)

func RawRow

func RawRow[R any](ctx context.Context, sql string, args ...any) (R, error)

RawRow is the one row Raw reads: NotFound when there is none (more are dropped).

func Register

func Register[T any]()

Register adds the model T to the program's models: Check reports when the ORM can't map it, and orm makemigrations plans its table without a package-level orm.For. orm generate writes a Register for every type embedding orm.Model in the module's packages (the orm command overlays them when it builds the program as its migration tool); Objects registers too.

func RegisterDriver

func RegisterDriver(dialect, sqlDriver string)

RegisterDriver names the database/sql driver Connect opens for a dialect. The driver packages (orm/postgres, orm/mysql, orm/sqlite) register theirs: blank-import the one the program opens.

import _ "github.com/vuka-lang/orm/postgres"

func RegisterDriverHooks

func RegisterDriverHooks(dialect string, h DriverHooks)

RegisterDriverHooks gives the ORM a driver's hooks for a dialect; the driver packages register theirs.

func RegisterLookup

func RegisterLookup(name string, fn LookupFunc, opts ...LookupOption)

RegisterLookup adds a lookup to Q keys and Where, Django's register_lookup: Q{"title__unaccent_icontains": "cafe"}, orm.Where(e, "unaccent_icontains", "cafe"). Its name is lowercase letters, digits and single underscores; registering a name again replaces it, and a built-in lookup's name panics. Transforms (a function's Transform) go before it as before a built-in lookup: Q{"title__lower__unaccent_icontains": "cafe"}.

orm.RegisterLookup("unaccent_icontains", func(lhs string, rhs func() string, d orm.Dialect) (string, bool) {
	if d.Name() != "postgres" {
		return "", false
	}
	return "unaccent(" + lhs + ") ILIKE unaccent(" + rhs() + ") ESCAPE '!'", true
}, orm.LookupValue(orm.LikePattern("%", "%")))

func RegisterScanner

func RegisterScanner[T any](cols []string, make func() RowScanner[T])

RegisterScanner installs a generated scanner for T, reading cols in order. A scanner whose columns aren't the model's (the model changed after the code was generated) is ignored and rows are read by reflection.

func Scope

func Scope(ctx context.Context) context.Context

Scope starts a read-your-writes scope on ctx, a request's: once a query with it (or a context made from it) writes a model, its reads of that model go to the database written, not a replica or a read database; raw reads of a database written go to its primary. Middleware starts one per request (ormweb does). ReadYourWrites(false) on the Databases turns it off.

func ServeCLI

func ServeCLI()

ServeCLI makes the program the orm command's migration tool when CLIEnv is set: it does what the request asks and exits, before main runs. The command calls it from an init it overlays into the program's main package; programs don't.

func SetFieldOptions deprecated

func SetFieldOptions(fn FieldOptionsFunc)

SetFieldOptions installs fn as the source of field options beside the struct tags. Set it before the first query: a model's metadata is read once. orm generate reads tags only, so a model whose columns fn renames is read by reflection rather than by its generated scanner.

Deprecated: declare a model's field attributes in its Schema method (s.Field(&p.Title, orm.Char{Max: 200})), as values of a FieldAttr of your own where the ORM has none; what only the running program knows (a column a legacy table lacks) a model attribute decides through ModelConfig.Fields.

func Transaction

func Transaction(ctx context.Context, fn func(ctx context.Context) error, opts ...TxOption) error

Transaction is Atomic: fn run in a transaction, committed when it returns nil, rolled back when it returns an error or panics.

err := orm.Transaction(ctx, func(ctx context.Context) error {
	p, err := orm.For[Post]().Get(ctx, PostFields.Slug.Eq(slug))
	if err != nil {
		return err
	}
	p.Published = time.Now()
	return p.Save(ctx)
}, orm.Retry(3))

func Using

func Using(ctx context.Context, name string) context.Context

Using sends ctx's queries to the database added as name, instead of each model's own.

func WatchRepeats

func WatchRepeats(ctx context.Context, n int, warn func(ctx context.Context, sql string, times int)) context.Context

WatchRepeats is ctx warning, through warn, when its queries run the same statement n times: a statement reading related rows one parent at a time, an N+1, which SelectRelated or PrefetchRelated read in one. Give it a request's context; warn is called once per statement.

ctx = orm.WatchRepeats(ctx, 5, func(ctx context.Context, sql string, n int) {
	slog.WarnContext(ctx, "orm: N+1", "sql", sql, "times", n)
})

func WithDB

func WithDB(ctx context.Context, db *DB) context.Context

WithDB has every model in ctx's queries use db, whatever database it is on: for tests and tools.

func WithDatabases

func WithDatabases(ctx context.Context, s *Databases) context.Context

WithDatabases has ctx's queries find their databases in s instead of Default.

func WithMirror

func WithMirror(ctx context.Context, db *DB) context.Context

WithMirror has every model's writes in ctx repeated on db, as Mirror does by name: for tests and tools.

func WithObserver

func WithObserver(ctx context.Context, fn Observer) context.Context

WithObserver has fn see every statement queries with ctx run, after the observers ctx already has.

func WithTimeout

func WithTimeout(ctx context.Context, d time.Duration) context.Context

WithTimeout bounds each statement ctx's queries run by d, over their database's Timeout.

Types

type AfterCreater

type AfterCreater interface {
	AfterCreate(ctx context.Context) error
}

The hooks a model may have, promoted from embedded structs like any method. A Before hook's error stops the write.

type AfterDeleter

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

The hooks a model may have, promoted from embedded structs like any method. A Before hook's error stops the write.

type AfterSaver

type AfterSaver interface {
	AfterSave(ctx context.Context) error
}

The hooks a model may have, promoted from embedded structs like any method. A Before hook's error stops the write.

type Agg

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

Agg is an aggregate: of a field (Count, Sum, Avg, Min, Max), or any aggregate expression (AggOf).

func AggOf

func AggOf(key string, e Expr) Agg

AggOf is an aggregate expression under a key of your own, for the aggregates the database has beyond the five:

var Names = orm.Function("names",
	orm.Template("postgres", "string_agg({0}, ', ')"),
	orm.Template("", "GROUP_CONCAT({0}, ', ')"),               // SQLite
	orm.Template("mysql", "GROUP_CONCAT({0} SEPARATOR ', ')")) // a literal: MySQL takes no argument here
r, _ := Users.Aggregate(ctx, orm.AggOf("names", Names.Of(orm.F("name"))))

func Avg

func Avg(field string) Agg

func Count

func Count(field string) Agg

func Max

func Max(field string) Agg

func Min

func Min(field string) Agg

func Sum

func Sum(field string) Agg

func (Agg) As

func (a Agg) As(key string) Agg

As names the aggregate's Key.

func (Agg) Distinct

func (a Agg) Distinct() Agg

Distinct aggregates each distinct value once, Django's distinct=True: COUNT(DISTINCT …), so a row reached twice through joins counts once.

func (Agg) Filter

func (a Agg) Filter(cond Cond) Agg

Filter aggregates only the rows where cond holds, Django's filter=:

orm.Count("id").Filter(orm.Q{"active": true}).As("active")

It is FILTER (WHERE …) on Postgres and SQLite, and the aggregate of a CASE on MySQL, NULL where cond doesn't hold.

func (Agg) Key

func (a Agg) Key() string

Key is the aggregate's name in a Result, Django's: age__avg.

type Alias

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

Alias is an expression under a name, As's: AnnotateAs annotates the query with it.

func (Alias) Name

func (a Alias) Name() string

Name is the alias.

type Annotation

type Annotation[T any] interface {
	// contains filtered or unexported methods
}

Annotation is a named term of T: what Annotate adds.

type AnyManager

type AnyManager interface {
	Err() error
	// contains filtered or unexported methods
}

AnyManager is a manager of any model, as CreateTables and ormtest take it.

type Arg

type Arg[T, V any] interface{ Get(T) V }

Arg is a field reference of T, or a typed expression over T, of Go type V: Post.Title, Post.Author.Name, fn.Lower(Post.Title). Functions taking one keep V, so their results stay typed.

type Assignment

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

Assignment is a field set to a value or an expression, for Update.

func Assign

func Assign[T, V any](f Arg[T, V], v V) Assignment[T]

Assign sets field f to v; AssignF to a field or term.

func AssignF

func AssignF[T, V any](f Arg[T, V], e Arg[T, V]) Assignment[T]

type AutoNow

type AutoNow struct{}

AutoNow sets the field to the time on every save; AutoNowAdd on create.

func (AutoNow) Apply added in v0.5.0

func (AutoNow) Apply(o *FieldOptions)

type AutoNowAdd

type AutoNowAdd struct{}

The field attributes: what a struct tag says, as typed values given to s.Field in a model's Schema method,

func (p *Post) Schema(s *orm.Schema) {
	s.Field(&p.ID, orm.PK{})
	s.Field(&p.Title, orm.Char{Max: 200, Label: "Title"})
	s.Field(&p.Author, orm.Rel{OnDelete: orm.Cascade, Related: "posts"})
}

or, in Vuka, written after the field:

type Post struct {
	orm.Base
	ID        int64        @orm.PK
	Title     string       @orm.Char{Max: 200, Label: "Title"}
	Status    string       @orm.Char{Max: 20, Default: "draft", Choices: []string{"draft", "published"}}
	Slug      string       @orm.Slug{Unique: true}
	Body      string       @orm.LongText
	Author    orm.FK[User] @orm.Rel{OnDelete: orm.Cascade, Related: "posts"}
	Views     int          @orm.Int{Min: Some(0), Default: 0}
	Published time.Time    @orm.Datetime{DefaultNow: true, Precision: 3}
}

Each is a FieldAttr, applied in order: a later one's set members win, an empty member never undoes an earlier one. They win over the field's tags; SetFieldOptions wins over them. One without members is written orm.PK{} (@orm.PK in Vuka), one of a named type by conversion: orm.Column("headline"), orm.Dims(384).

func (AutoNowAdd) Apply added in v0.5.0

func (AutoNowAdd) Apply(o *FieldOptions)

type Base

type Base[Self any] Model[Self]

Base is a Vuka model's base: Model without its Objects method, so that Post.Objects is the static Vuka gives every type embedding it, the model's manager (Objects[Post]()). Embed it as orm.Base (Vuka fills in the type):

type Post struct {
	orm.Base @orm.Meta{Table: "posts", Ordering: []string{"-published"}}
	ID    int64
	Title string @orm.Char{Max: 200}
}

posts := Post.Objects.Filter(Post.Title.Contains("go")).All(ctx)?
p.Save(ctx)?

The attributes become the model's Schema method; Post.Title is the generated typed field PostFields.Title. Rows have Model's methods: Save, SaveFields, Refresh, Delete, Load, Add, Remove, Set, Using.

func (*Base[Self]) Add

func (b *Base[Self]) Add(ctx context.Context, relation any, related ...any) error

Add, Remove and Set change the row's many-to-many links, the relation named by reference or name. An orm.M2M field has them itself.

func (*Base[Self]) DeclaredFieldAttrs added in v1.0.0

func (*Base[Self]) DeclaredFieldAttrs(t reflect.Type, registered map[string][]any) map[string][]any

DeclaredFieldAttrs is Model's, for a Vuka model.

func (*Base[Self]) Delete

func (b *Base[Self]) Delete(ctx context.Context) error

func (*Base[Self]) Load

func (b *Base[Self]) Load(ctx context.Context, specs ...any) error

Load loads relations into the row, by reference (Post.Tags), name or orm.Prefetch.

func (*Base[Self]) Refresh

func (b *Base[Self]) Refresh(ctx context.Context) error

Refresh reads the row again; Delete removes it.

func (*Base[Self]) Remove

func (b *Base[Self]) Remove(ctx context.Context, relation any, related ...any) error

func (*Base[Self]) Save

func (b *Base[Self]) Save(ctx context.Context) error

Save inserts the row when it is new, else updates it.

func (*Base[Self]) SaveFields

func (b *Base[Self]) SaveFields(ctx context.Context, fields ...any) error

SaveFields updates only the fields named: typed fields (PostFields.Title), references or names.

func (*Base[Self]) Set

func (b *Base[Self]) Set(ctx context.Context, relation any, related ...any) error

func (*Base[Self]) Using

func (b *Base[Self]) Using(sc DBSchema) *Self

type BaseRouter

type BaseRouter struct{}

BaseRouter is a Router with no opinion that allows everything: embed it and override what the router decides.

func (BaseRouter) AllowMigrate

func (BaseRouter) AllowMigrate(string, ModelInfo) bool

func (BaseRouter) AllowRelation

func (BaseRouter) AllowRelation(a, b ModelInfo) bool

func (BaseRouter) ReadDB

func (BaseRouter) WriteDB

type BeforeCreater

type BeforeCreater interface {
	BeforeCreate(ctx context.Context) error
}

The hooks a model may have, promoted from embedded structs like any method. A Before hook's error stops the write.

type BeforeDeleter

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

The hooks a model may have, promoted from embedded structs like any method. A Before hook's error stops the write.

type BeforeSaver

type BeforeSaver interface {
	BeforeSave(ctx context.Context) error
}

The hooks a model may have, promoted from embedded structs like any method. A Before hook's error stops the write.

type Breaker

type Breaker struct {
	Failures int
	Cooldown time.Duration
}

Breaker is the circuit breaker on a database's statements: after Failures connection errors in a row (5) it opens, and statements fail at once, Unavailable, for Cooldown (5s); then one statement probes, and its success closes it. Only connection errors count — a refused or reset connection, a timeout, a dropped one — never a query's own.

type CLIRequest

type CLIRequest struct {
	Command string `json:"command"` // plan, migrate, show, sql
	// DB is the migrations' database ("" the default).
	DB string `json:"db"`
	// Models is plan's: the databases (orm.On names) the models of DB's
	// migrations are on: "" and the default's name for the default.
	Models  []string `json:"models,omitempty"`
	Dialect string   `json:"dialect,omitempty"` // plan, sql
	// Number, Name, Package and Empty name and shape plan's migration;
	// Interactive asks about renames on stdin, and migrate's confirmation
	// before it unapplies migrations.
	Number      int    `json:"number,omitempty"`
	Name        string `json:"name,omitempty"`
	Package     string `json:"package,omitempty"`
	Empty       bool   `json:"empty,omitempty"`
	Interactive bool   `json:"interactive,omitempty"`
	// Target is migrate's: the migration to take DB to ("zero" for
	// none); every database's pending ones when empty. sql's: the
	// migration to print.
	Target    string `json:"target,omitempty"`
	Backwards bool   `json:"backwards,omitempty"` // sql
	// Conns is the URL of each migrations database ("" the default's),
	// for migrate and show to connect (see ConnectURL).
	Conns map[string]string `json:"conns,omitempty"`
	Out   string            `json:"out,omitempty"` // plan: the file its PlanResult goes to
}

CLIRequest is what the orm command asks of a program run as its migration tool.

type CSV

type CSV[E CSVElem] struct{ V []E }

CSV is a column holding a list as comma-separated text ("7,9,11"), for legacy tables that keep lists in a string: read and write V; the field marshals as the list. A nil V is NULL, an empty non-nil V ""; reading trims spaces around the commas.

type Letter struct {
	ID           int64
	PlacementIDs orm.CSV[int64]
}

func CSVOf

func CSVOf[E CSVElem](v ...E) CSV[E]

CSVOf is the CSV column holding v.

func (CSV[E]) MarshalJSON

func (c CSV[E]) MarshalJSON() ([]byte, error)

func (*CSV[E]) Scan

func (c *CSV[E]) Scan(src any) error

func (*CSV[E]) UnmarshalJSON

func (c *CSV[E]) UnmarshalJSON(b []byte) error

func (CSV[E]) Value

func (c CSV[E]) Value() (driver.Value, error)

type CSVElem

type CSVElem interface {
	~int | ~int8 | ~int16 | ~int32 | ~int64 | ~uint | ~uint16 | ~uint32 | ~uint64 | ~string
}

CSVElem is what a CSV column's list may hold.

type CaseExpr

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

CaseExpr is a Case.

func Case

func Case(whens ...WhenClause) *CaseExpr

Case is the first of its branches whose condition holds, Django's Case: each When's result, else Else's, else NULL.

orm.Case(
	orm.When(orm.Q{"orders__total__gte": 1000}, "gold"),
	orm.When(orm.Q{"age__gte": 18}, "silver"),
).Else("bronze")

func (*CaseExpr) Else

func (c *CaseExpr) Else(v any) *CaseExpr

Else is the result when no branch's condition holds: an Expr or a value.

type CastType

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

CastType is a type Cast converts to, spelled for each database.

func AsDecimal

func AsDecimal(precision, scale int) CastType

AsDecimal is a fixed-point number of precision digits, scale of them after the point (NUMERIC on SQLite, which keeps no scale).

func AsType

func AsType(sqlType string) CastType

AsType is a database type written as the database spells it, the same on every database: AsType("CHAR(20)"), AsType("UUID"). It takes a type name and its size only, never anything else. MySQL's CAST knows few types (CHAR, SIGNED, UNSIGNED, DECIMAL, DOUBLE, DATE, DATETIME, TIME, JSON, BINARY): no VARCHAR, no INT.

type Cell

type Cell[V any] struct{ P *V }

Cell is a scan destination for a field of type V, for generated scanners: the common types convert without reflection, any other through the ORM's own conversion, NULL to the zero value.

func (*Cell[V]) Scan

func (c *Cell[V]) Scan(src any) error

type Change

type Change[T any] struct {
	Kind  ChangeKind
	Row   *T
	Count int64
	Keys  []any // BulkUpdated
	Rows  []T   // BulkDeleted
}

Change is a write OnChange hears: the row for Created, Updated and Deleted; for the bulk kinds, how many rows, and which: the keys of the rows an Update matched, the rows a Delete removed, as found just before the write.

type ChangeKind

type ChangeKind int

ChangeKind is what a write did.

const (
	Created     ChangeKind = iota + 1 // a row, by Create or BulkCreate
	Updated                           // a row, by Save
	Deleted                           // a row, by Remove
	BulkUpdated                       // rows, by a QuerySet's Update
	BulkDeleted                       // rows, by a QuerySet's Delete
)

type Char

type Char struct {
	Max, Min             int
	Unique, Index, Null  bool
	Column               string
	Comment, Label, Help string
	Default              any
	Choices              []string
	Pattern              string
	Trim, Lower          bool
	Collation            string
}

Char is a VARCHAR of Max characters (a write holding more is refused), unique or indexed. Min is the fewest characters a write may hold, Choices the values it may take, Pattern a regular expression it matches; Trim and Lower normalize it as it is written; Collation is the column's (utf8mb4_bin, "C", NOCASE).

func (Char) Apply added in v0.5.0

func (x Char) Apply(o *FieldOptions)

type Codec added in v0.6.0

type Codec[G, DB any] struct {
	To   func(G) (DB, error)
	From func(DB) (G, error)
}

Codec adapts a field's value under a names set: its Go value G is written as DB (To) and read back as G (From), so a field keeps one Go type while its database form differs per schema — a status string kept as an integer code on a legacy table, a time kept as text. Unlike a SchemaValuer the field's Go type is untouched: the conversion rides the field, set under a names set, not the type.

orm.Codec[string, int64]{
	To:   func(s string) (int64, error) { return codes[s], nil },
	From: func(c int64) (string, error) { return names[c], nil },
}

A field codec wins over the field type's own SchemaValuer/SchemaScanner and DBValuer: the codec is the field-level instruction, applied in place of the type's.

func CodeMap added in v0.6.0

func CodeMap[S ~string](codes map[S]int64) Codec[S, int64]

CodeMap is a codec mapping a string value to and from an integer code under a names set (a legacy enum kept as a number): codes gives each value its code, and a value or code it doesn't hold is an error.

func TimeAsText added in v0.6.0

func TimeAsText(layout string) Codec[time.Time, string]

TimeAsText is a codec keeping a time.Time as text in layout under a names set (a legacy table whose timestamps are strings), reading it back by the same layout.

type Col

type Col[T, R any] struct {
	// contains filtered or unexported fields
}

Col is a column of a Values or Aggregate result R: Into(R.Field, source).

func Into

func Into[T, R, V any](dst Arg[R, V], src Arg[T, V]) Col[T, R]

Into reads src — a field reference of T or a term — into the field dst of the result R. Both are of type V, so a count goes into an int64.

type Stats struct { Author string; Posts int64 }
stats := Post.Objects.Values[Stats](ctx, orm.Into(Stats.Author, Post.Author.Name), orm.Into(Stats.Posts, fn.Count(Post.ID)))?

type Column

type Column string

Column names the column.

func (Column) Apply added in v0.5.0

func (x Column) Apply(o *FieldOptions)

type ColumnTyper added in v0.5.0

type ColumnTyper interface {
	ColumnType(d Dialect) string
}

ColumnTyper is the column's database type on dialect d, as migrations make it: "" leaves the type the ORM would choose.

type CompareTerm

type CompareTerm[T any, V interface{ Compare(V) int }] struct{ Term[T, V] }

CompareTerm is a Term of a type ordering itself (time.Time).

func CompareTermOf

func CompareTermOf[T any, V interface{ Compare(V) int }](e any) CompareTerm[T, V]

func (CompareTerm[T, V]) As

func (t CompareTerm[T, V]) As(name string) CompareTerm[T, V]

func (CompareTerm[T, V]) Ge

func (t CompareTerm[T, V]) Ge(v V) vuka.Pred[T]

func (CompareTerm[T, V]) Gt

func (t CompareTerm[T, V]) Gt(v V) vuka.Pred[T]

func (CompareTerm[T, V]) Le

func (t CompareTerm[T, V]) Le(v V) vuka.Pred[T]

func (CompareTerm[T, V]) Lt

func (t CompareTerm[T, V]) Lt(v V) vuka.Pred[T]

type Cond

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

Cond is a condition a QuerySet filters by: a Q, or Or / Not / And of conditions.

func And

func And(conds ...Cond) Cond

And holds when every condition does.

func CondOf

func CondOf[T any](p vuka.Pred[T]) Cond

CondOf is a predicate as the engine's condition.

func Match

func Match(d Document, q TextQuery) Cond

Match holds when the document matches the query, Django's SearchVector(…) = SearchQuery(…): on Postgres the tsvector @@ the tsquery, on MySQL MATCH … AGAINST in boolean mode, on SQLite the FTS5 table of a model declaring the document (a LIKE per word otherwise).

Posts.Filter(orm.Match(orm.SearchVector("title", "body"), orm.SearchQuery(`go -java "web server"`)))

func Not

func Not(c Cond) Cond

Not holds when c doesn't.

func Or

func Or(conds ...Cond) Cond

Or holds when any condition does.

func PredCond

func PredCond(p Predicate) Cond

PredCond is p's Pred as a condition.

func Where

func Where(e Expr, lookup string, value any) Cond

Where is a condition on an expression: Where(Lower.Of(F("name")), "exact", "ali"); any Q lookup.

type Conn

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

Conn is a handle on one database of a Databases: embed it in a type of your own to inject that database by type (a framework's DI), and name it with @orm.Database("analytics") in Vuka or ConnFor in Go.

@orm.Database("analytics")
type Analytics struct{ orm.Conn }

rows := orm.Raw[Sales](a.With(ctx), "SELECT day, SUM(total) AS total FROM sales GROUP BY day")?

A Conn is a context.Context too (background, with its database): pass it where a context goes, or With(ctx) for a request's.

func (Conn) DB

func (c Conn) DB() (*DB, error)

DB is the database.

func (Conn) Deadline

func (Conn) Deadline() (time.Time, bool)

func (Conn) Done

func (Conn) Done() <-chan struct{}

func (Conn) Err

func (Conn) Err() error

func (Conn) Exec

func (c Conn) Exec(ctx context.Context, sql string, args ...any) (int64, error)

Exec runs a write on c's database, as Exec does.

func (Conn) Name

func (c Conn) Name() string

Name is the database's name.

func (Conn) Ping

func (c Conn) Ping(ctx context.Context) error

Ping checks c's database answers.

func (Conn) Stats

func (c Conn) Stats() (DBStats, error)

Stats is c's database's pool.

func (Conn) Value

func (c Conn) Value(key any) any

func (Conn) With

func (c Conn) With(ctx context.Context) context.Context

With is ctx with its queries sent to c's database (WithDatabases and Using).

type DB

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

DB is a database the ORM queries: a *sql.DB and its dialect, and, for one Declare opened, the supervisor keeping its pool (see Pool).

func Connect

func Connect(dialect, dsn string) (*DB, error)

Connect opens dsn on dialect ("postgres", "mysql", "sqlite") through the driver its package registered, as the ORM reads it: MySQL as MySQLDSN sets it, in-memory SQLite on one connection (each would be a database of its own), others with Pool's default caps (DefaultMaxOpen, DefaultMaxIdle, DefaultMaxLifetime; the *sql.DB's setters change them). It doesn't ping.

func ConnectURL

func ConnectURL(url string) (*DB, error)

ConnectURL opens a database by URL: postgres://… (or postgresql://), mysql://user:pass@tcp(host:3306)/db?… (the driver's DSN after mysql://), sqlite:path, sqlite://path or sqlite::memory:. Any pool_* (and breaker_*, startup_wait) parameters are read as the pool's, as a declared database reads them, and never reach the driver's DSN.

func DBFrom

func DBFrom(ctx context.Context) (*DB, bool)

DBFrom is the database WithDB put in ctx.

func Open

func Open(db *sql.DB, driver string) *DB

Open is a DB over a *sql.DB of the given dialect ("postgres", "mysql", "sqlite"; "pgx" and "postgresql" read as postgres). Connect opens the *sql.DB too.

func (*DB) Dialect

func (d *DB) Dialect() Dialect

Dialect is how the database speaks SQL.

func (*DB) Name

func (d *DB) Name() string

Name is the name the database was added under.

func (*DB) SQL

func (d *DB) SQL() *sql.DB

SQL is the underlying *sql.DB.

func (*DB) State

func (d *DB) State() State

State is the database's state: Up for one that isn't supervised.

type DBScanner added in v0.5.0

type DBScanner interface {
	FromDB(src any) error
}

DBScanner reads the column's value, as the driver gives it (nil for NULL unless the field is a pointer or an Option, which are nil and None then).

type DBSchema added in v1.0.0

type DBSchema struct {
	DB        string // the database added under that name; "" the default
	Names     string // the names set; "" the default naming
	Unmanaged bool   // the tables are the database's own: never migrated
}

DBSchema is a database the models are queried on and the names they have there: one set of Go models, relations declared on them, over schemas that name their tables and columns apart.

var Legacy = orm.DBSchema{DB: "legacy", Names: "legacy", Unmanaged: true}

type User struct {
	ID      int64
	Phone   string   `legacy:"tel_no"`              // the column there
	Bio     string   `legacy:"-"`                   // no such column there
	Code    string   `gorm:"-" legacy:"code"`       // there only
	City    string   `legacy:"profile__city"`       // read through a relation there
	Profile *Profile `gorm:"foreignKey:UserID"`
	Groups  []Group  `gorm:"many2many:user_groups" legacy:"many2many:auth_user_groups;joinReferences:group_id"`
}
func (User) LegacyTableName() string { return "auth_user" }

users, err := orm.Of[User](Legacy).Filter(orm.Q{"groups__name": "staff"}).All(ctx)

Queries name Go fields, default names and relation paths whatever the schema; the ORM writes them in the schema's names. A field read through a relation is read-only there: Create and Save skip it, and Update refuses it.

func (DBSchema) Verify added in v1.0.0

func (s DBSchema) Verify(ctx context.Context, models ...any) error

Verify checks that the models (values or pointers of their types) map onto the schema — no tag naming no field, relation to a model by no field of it, column read through no relation, or two relations claiming one inverse name — and, when ctx has its database, once it answers (waiting for it as Migrate does), that the database can make their columns, indexes and generated columns (a vector on MySQL can't be) and has their extensions. Run it at start up, after Migrate, which may install what it checks for.

if err := Legacy.Verify(ctx, User{}, Profile{}, Group{}); err != nil {
	log.Fatal(err)
}

type DBStats

type DBStats struct {
	Name       string         `json:"name"`
	Dialect    string         `json:"dialect"`
	Default    bool           `json:"default"`
	Supervised bool           `json:"supervised"` // declared (Declare), not added
	State      State          `json:"state"`
	Breaker    string         `json:"breaker"` // closed, open, half-open
	Failures   int            `json:"failures"`
	LastError  string         `json:"last_error,omitempty"`
	Pool       sql.DBStats    `json:"pool"`
	Replicas   []ReplicaStats `json:"replicas,omitempty"`
}

DBStats is a database's pool, as Stats reports it.

func Stats

func Stats(name string) (DBStats, error)

Stats is a database's pool in Default: database/sql's statistics, its state, breaker and replicas.

type DBType

type DBType string

DBType is the column's database type, written as is (jsonb, CHAR(2)).

func (DBType) Apply added in v0.5.0

func (x DBType) Apply(o *FieldOptions)

type DBValuer added in v0.5.0

type DBValuer interface {
	DBValue(d Dialect) (any, error)
}

DBValuer gives the value written to the column for the dialect d (d.Name() is "postgres", "mysql" or "sqlite"): on Create, Save and Update, and for the conditions comparing the field (Eq, In, Gt, …).

type DatabaseOption

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

DatabaseOption tunes a database: one added (AsDefault) or declared (AsDefault, ReadOnly, Replicas, Pool, Credentials, Session, Timeout, SlowQuery).

func AsDefault

func AsDefault() DatabaseOption

AsDefault makes the database the default: the one models without On query. Without one, the only database is the default.

func Credentials

func Credentials(fn func(ctx context.Context) (user, password string, err error)) DatabaseOption

Credentials supplies the user and password of every new connection (a rotating secret, a cloud IAM token), asked for each one the pool opens; they replace the URL's.

func ReadOnly

func ReadOnly() DatabaseOption

ReadOnly refuses writes to the database: a reporting copy. The ORM refuses its own writes (Create, Save, Update, Delete, Exec), and a declared database's connections are read-only on the server too, so raw SQL (Raw, RawRow) can't write either: MySQL's sessions run SET SESSION TRANSACTION READ ONLY, Postgres's set default_transaction_read_only, SQLite's set PRAGMA query_only. A statement can still undo those for its own connection; only the database's grants (a user allowed SELECT alone) are a boundary an attacker running SQL can't cross. A database added with Add gets the ORM's refusal alone.

func Replicas

func Replicas(urls ...string) DatabaseOption

Replicas gives the database read replicas, by URL: reads outside a transaction (and a Scope that wrote) go to the healthy one least in use, the primary when none is; a replica failing its health ping is left out until it answers again.

type DatabaseSpec

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

DatabaseSpec is a database Declare declared: opened, supervised, by the Databases' Start.

func Declare

func Declare(name, url string, opts ...DatabaseOption) *DatabaseSpec

Declare declares the database name at url (as ConnectURL reads it; pool settings may ride as URL parameters, see Pool), for OpenDatabases or NewDatabases. Its pool is supervised: see Pool.

dbs, err := orm.OpenDatabases(ctx,
	orm.Declare("main", os.Getenv("DATABASE_URL"), orm.AsDefault(),
		orm.Replicas(os.Getenv("REPLICA_URL")), orm.Pool{MaxOpen: 20, Acquire: 2 * time.Second}),
	orm.Declare("analytics", "postgres://…/analytics?pool_max=5", orm.ReadOnly()))

type Databases

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

Databases is the named databases models query: the default, and others a model names with On (or Meta.DB, or a DBSchema). A program has one, Default, which AddDatabase fills; a process serving several apps (tests, a gateway) gives each its own and puts it on the app's contexts with WithDatabases, so one manager queries the database of the app serving the request.

db, err := orm.Connect("postgres", os.Getenv("DATABASE_URL"))
orm.AddDatabase("main", db, orm.AsDefault())

func DatabasesOf

func DatabasesOf(ctx context.Context) *Databases

DatabasesOf is ctx's Databases: WithDatabases's, else Default.

func NewDatabases

func NewDatabases(opts ...OpenOption) *Databases

NewDatabases is a set of databases: empty, or of the databases Declare declares (opened by Start) and the routers and settings the options give.

func OpenDatabases

func OpenDatabases(ctx context.Context, opts ...OpenOption) (*Databases, error)

OpenDatabases is the databases opts declare, opened (NewDatabases, then Start). With none declared it reads DATABASE_URL (the default, "default") and DATABASE_URL_<NAME> (database name, lowercased), as the orm command does.

func (*Databases) Add

func (s *Databases) Add(name string, db *DB, opts ...DatabaseOption)

Add adds db as name, replacing a database of that name.

func (*Databases) AddRouter

func (s *Databases) AddRouter(r Router) *Databases

AddRouter adds r after the routers s has.

func (*Databases) Apply

func (s *Databases) Apply(opts ...OpenOption) *Databases

Apply applies opts to s (before Start, for the databases it declares).

func (*Databases) Check

func (s *Databases) Check() error

Check is what keeps s from serving the program's models: a Meta, For option, Route or connection handle naming a database s doesn't declare, and relations across databases that aren't CrossDB or a router refuses. Start runs it.

func (*Databases) Close

func (s *Databases) Close(ctx context.Context) error

Close closes the databases Start opened, gracefully: new statements fail Unavailable, those running finish until ctx's deadline, then the pools close. Databases added with Add are the caller's to close.

func (*Databases) Conn

func (s *Databases) Conn(name string) Conn

Conn is a handle on the database name.

func (*Databases) Lookup

func (s *Databases) Lookup(name string) (*DB, error)

Lookup is the database name: "" the default (the one AsDefault marked, else the only one).

func (*Databases) Names

func (s *Databases) Names() []string

Names is the databases' names, sorted.

func (*Databases) Remove

func (s *Databases) Remove(name string)

Remove removes the database name; it doesn't close it.

func (*Databases) Route

func (s *Databases) Route(table string, r Route)

Route routes table: its models read and write r.DB, and their writes are repeated on r.Mirror (see Mirror).

orm.Default.Route("categories", orm.Route{DB: "legacy", Mirror: "main"})

func (*Databases) RouteFor added in v0.6.0

func (s *Databases) RouteFor(table string) (Route, bool)

RouteFor is table's route, and whether one is set (Route, Dual).

func (*Databases) Start

func (s *Databases) Start(ctx context.Context) error

Start opens the databases declared and not yet open, supervised, waits up to each one's StartupWait for it to answer (one that doesn't is opened Down, and reconnects in the background), then runs the OnOpen hooks. It runs Check first: a model naming a database no one declared fails it. With nothing declared or added it reads the environment (FromEnv). A framework calls it on start (it is a Starter).

func (*Databases) Stats

func (s *Databases) Stats() []DBStats

Stats is every database's pool, by name.

func (*Databases) StatsOf

func (s *Databases) StatsOf(name string) (DBStats, error)

StatsOf is the database name's pool ("" the default).

func (*Databases) Stop

func (s *Databases) Stop(ctx context.Context) error

Stop closes the databases Start opened (Close): a framework calls it on shutdown (it is a Stopper).

func (*Databases) Unroute

func (s *Databases) Unroute(table string)

Unroute drops table's route.

type DateField added in v0.5.0

type DateField struct {
	AutoNow, AutoNowAdd, DefaultNow bool
	Unique, Index, Null             bool
	Column                          string
	Comment, Label, Help            string
	Default                         any
}

DateField is a DATE column of a time.Time field: the day, without the time (orm.Date is the lookup transform). AutoNow sets it to today on every save, AutoNowAdd on create, DefaultNow on create when it is empty and in the table (DEFAULT CURRENT_DATE).

func (DateField) Apply added in v0.5.0

func (x DateField) Apply(o *FieldOptions)

type Datetime added in v0.5.0

type Datetime struct {
	AutoNow, AutoNowAdd, DefaultNow bool
	Precision                       int
	UTC                             bool
	Unique, Index, Null             bool
	Column                          string
	Comment, Label, Help            string
	Default                         any
}

Datetime is a timestamp column of a time.Time field. AutoNow sets it to now on every save, AutoNowAdd on create, DefaultNow on create when it is empty and in the table (DEFAULT CURRENT_TIMESTAMP). Precision is its fractional digits, 1–6 (0: the database's default, microseconds; times are cut to it as they are written); UTC writes and reads it in UTC.

func (Datetime) Apply added in v0.5.0

func (x Datetime) Apply(o *FieldOptions)

type DefaultValue

type DefaultValue struct {
	SQL string
	// contains filtered or unexported fields
}

DefaultValue is a column default, from DefaultTo or DefaultSQL.

func DefaultSQL

func DefaultSQL(sql string) DefaultValue

DefaultSQL is a column default written in SQL: @orm.DefaultSQL("CURRENT_TIMESTAMP").

func DefaultTo

func DefaultTo(v any) DefaultValue

DefaultTo is the column default v, a Go value written as a SQL literal: @orm.DefaultTo(0), @orm.DefaultTo("draft"), @orm.DefaultTo(false). A row Create writes with the field at its zero value takes v too.

func (DefaultValue) Apply added in v0.5.0

func (x DefaultValue) Apply(o *FieldOptions)

type Defaults

type Defaults map[string]any

Defaults is the fields GetOrCreate sets on a row it creates, beyond those of its lookup.

type Dialect

type Dialect interface {
	Name() string
	// Placeholder is the n-th argument's marker, from 1.
	Placeholder(n int) string
	Quote(ident string) string
	// ILike matches col against a LIKE pattern, ignoring case.
	ILike(col, pattern string) string
	// Returning is whether INSERT … RETURNING gives back the new key.
	Returning() bool
	// NoLimit is the LIMIT to put before an OFFSET with no limit; empty
	// when OFFSET may stand alone.
	NoLimit() string
	// Violation reads a constraint error: unique or foreign key, and the
	// column it names when it names one.
	Violation(err error) (kind violation, column string)
}

Dialect is what differs between the databases the ORM speaks to.

func DialectFor

func DialectFor(driver string) Dialect

DialectFor is the dialect of a driver name: "postgres" (or "postgresql", "pgx"), "mysql" or "sqlite"; a server version after a slash (mysql/9.3.0, as migrations take it) is left out.

type Dims

type Dims int

Dims is a vector field's dimensions.

func (Dims) Apply added in v0.5.0

func (x Dims) Apply(o *FieldOptions)

type Document

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

Document is a SearchVector.

func SearchVector

func SearchVector(fields ...string) Document

SearchVector is fields as a document to search, Django's SearchVector: Match, SearchRank and Headline read it, and an index or a generated column may hold it. On Postgres it is a tsvector; elsewhere the fields' text.

doc := orm.SearchVector("title").Weight("A").Add(orm.SearchVector("body").Weight("B")).Config("english")

func (Document) Add

func (d Document) Add(o Document) Document

Add is the two documents as one, Django's +.

func (Document) Config

func (d Document) Config(c string) Document

Config is the Postgres text search configuration (english, simple): how words are stemmed. An index or a generated column needs one. On SQLite, english stems by FTS5's porter tokenizer.

func (Document) Weight

func (d Document) Weight(w string) Document

Weight ranks the document's fields: A (the highest), B, C or D.

type DriverHooks

type DriverHooks struct {
	Credentials func(dsn, user, password string) (string, error)
	Classify    func(err error) ErrorClass // OtherError for one it doesn't know
}

DriverHooks is what a driver package teaches the ORM about its driver: how to put a user and password into a DSN (for Credentials), and how to read its errors.

type DualOption added in v0.6.0

type DualOption = ForOption

DualOption tunes a Dual route. The mirror options (MirrorUnder, MirrorTable) and Names are the ones that carry over; it is ForOption so the same helpers serve For, Of and Dual.

type Elements added in v0.7.0

type Elements struct {
	Column string
	Ref    string
}

Elements configures an elements relation on a slice field: Column is the owner's column (a JSON array or comma-separated string) holding the related rows' keys, and Ref the related model's field they refer to (its primary key otherwise). The attribute form of orm:"elements:<column>[,<ref>]":

Tags []Tag @orm.Elements{Column: "tag_ids"}

func (Elements) Apply added in v0.7.0

func (x Elements) Apply(o *FieldOptions)

type Error

type Error struct {
	Kind    Kind
	Model   string // the model's name, when there is one
	Message string
	// Fields is the messages of the columns the error is about: a
	// Conflict's column "already exists", an Invalid's "does not exist".
	Fields map[string][]string
	Err    error // the cause: the driver's error, when there is one
}

Error is an error the ORM gives a meaning: its Kind, a message for people, the fields (by column) it is about, and the database's error under it.

func (*Error) Error

func (e *Error) Error() string

func (*Error) Field

func (e *Error) Field(column, msg string) *Error

Field adds msg to the column's messages.

func (*Error) Is

func (e *Error) Is(target error) bool

Is matches e's kind: errors.Is(err, orm.ErrConflict).

func (*Error) Unwrap

func (e *Error) Unwrap() error

Unwrap is the cause.

type ErrorClass

type ErrorClass int

ErrorClass is what an error says about the connection it came on.

const (
	// OtherError: the database answered (a bad query, a constraint).
	OtherError ErrorClass = iota
	// ConnError: the connection failed — refused, reset, timed out,
	// dropped. The breaker counts these; reads are retried once.
	ConnError
	// RetryableError: the database aborted the transaction for a
	// deadlock or a serialization failure; Retry runs it again.
	RetryableError
)

func Classify

func Classify(err error) ErrorClass

Classify is what err says about its connection: the drivers' hooks' reading, else the ORM's own of database/sql's, the network's and the databases' codes.

type ExistsCond

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

ExistsCond is Exists: a condition, and an Expr for Annotate.

func Exists

func Exists(s subquerier) ExistsCond

Exists holds when the query has a row; with OuterRef, a row related to the outer one.

Users.Filter(orm.Exists(Posts.Filter(orm.Q{"author_id": orm.OuterRef("id"), "published": true})))

type Expr

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

Expr is a SQL expression a query computes: a field (F), a function call (a typed Func called, a Function's Of), a template (SQL), a Case or a Switch. Exprs go where values go — a Q's value, Where, Annotate, Update's Set, Aggregate — and nest.

func Cast

func Cast(v any, t CastType) Expr

Cast converts v (a field, an expression, or a value) to type t:

Users.Annotate("age_text", orm.Cast(orm.F("age"), orm.AsText)).
	Filter(orm.Q{"age_text__startswith": "2"})
Orders.Filter(orm.Where(orm.Cast(orm.F("created_at"), orm.AsDate), "exact", day))
orm.Cast("12.5", orm.AsDecimal(10, 2))

func ExprOf

func ExprOf(v any) Expr

ExprOf is v as an expression: a typed field (PostFields.Title) or field reference as the field (F), a typed expression or Expr as itself, anything else a bound value. Expressions and conditions taking values (functions, SQL, Q's values, Case, Set) take typed fields and field references directly, the same way: orm.Lower(PostFields.Title).

func F

func F(name string) Expr

F is a field of the model, or an annotation, as an expression: Q{"age__gt": orm.F("min_age")} compares two columns, and Set{"views": orm.SQL("{0} + 1", orm.F("views"))} adds to one.

func FilteredRelation

func FilteredRelation(relation string, cond Cond) Expr

FilteredRelation is a relation (a path of them, posts__comments) whose related rows are only those where cond holds, Django's FilteredRelation: annotate it under a name, then name the related fields through it. cond names the related model's own fields. Reading through it joins with cond in the ON, so rows without a match keep a row of NULLs; filtering through a relation holding many rows asks EXISTS one matching.

Users.Annotate("active_apps", orm.FilteredRelation("applications", orm.Q{"status": "active"})).
	Annotate("n", orm.Count("active_apps__id")).Values[[]any]("name", "n")

func OuterRef

func OuterRef(name string) Expr

OuterRef is a field of the query a Subquery or Exists stands in.

func SQL

func SQL(template string, args ...any) Expr

SQL is an expression written in SQL: {0}, {1}, … stand for args, each an Expr or a value sent as an argument ({{ and }} are literal braces). It is the escape hatch for what a Func can't say; never build the template from user input.

orm.SQL("{0} + {1}", orm.F("views"), 1)

func Subquery

func Subquery(s subquerier) Expr

Subquery is a query as a value of another: in a filter, an annotation or an update. A QuerySet gives its primary keys, Values its columns; OuterRef reads the query it stands in.

latest := Posts.Filter(orm.Q{"author_id": orm.OuterRef("id")}).OrderBy("-created_at").Limit(1)
Users.Annotate("latest_title", orm.Subquery(latest.Values[string]("title")))
Users.Filter(orm.Q{"id__in": Posts.Filter(orm.Q{"published": true}).Values[int64]("author_id")})

func Value

func Value(v any) Expr

Value is v as an expression, Django's Value(): a constant column ('PLACEMENT' AS category) or a value passed where an expression goes (Case results, Coalesce) — sent as a bound argument, never pasted into the SQL.

Letters.Annotate("category", orm.Value("PLACEMENT"))

type ExprOrder

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

ExprOrder is an expression to order by and its direction.

func Asc

func Asc(e Expr) ExprOrder

Asc orders by e, smallest first.

func Desc

func Desc(e Expr) ExprOrder

Desc orders by e, largest first.

type Extension

type Extension struct {
	Name string
	DB   string // the database's name; "" the default
}

Extension is a Postgres extension CreateExtension declared.

func CreateExtension

func CreateExtension(name string, opts ...ForOption) Extension

CreateExtension declares a Postgres extension the database needs — "vector" for Vector columns and vector indexes, "pg_trgm" for trigram indexes — Django's CreateExtension: orm makemigrations writes it into the next migration (CreateTables makes it too, with the database's models). Declare it package-level, beside the models, where the autodetector finds it; its Verify fails while the database lacks it. orm.On names the database.

var Vectors = orm.CreateExtension("vector")

func (Extension) Verify

func (e Extension) Verify(ctx context.Context) error

Verify fails while ctx's database of the extension lacks it, waiting for the database to answer as Migrate does. Other databases than Postgres have no extensions: it passes.

type FK

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

FK is a foreign key to a T, as one field: the column <field>_id holding the related row's key, and that row once loaded (SelectRelated, PrefetchRelated, Get). It implements vuka.Related[T], so a Vuka field reference goes on into T's fields: Post.Author.Name.

type Post struct {
	orm.Base
	ID     int64
	Author orm.FK[User] @orm.Rel{OnDelete: orm.Cascade, Related: "posts"}
}

p := Post{Author: orm.FKOf(&ada)}
author := p.Author.Get(ctx)?                       // loaded once, then kept
posts := Post.Objects.Filter(Post.Author.Name.Eq("ada")).SelectRelated(Post.Author).All(ctx)?

In JSON it is the key. The column is NOT NULL unless Rel{Null: true} (or @orm.Null): an unset FK is then NULL.

func FKID

func FKID[T any](key any) FK[T]

FKID is a foreign key to the T whose key is key.

func FKOf

func FKOf[T any](row *T) FK[T]

FKOf is a foreign key to row: its key is read from the row when the FK is written, so the row may be saved after the FK is made.

func (*FK[T]) Get

func (f *FK[T]) Get(ctx context.Context) vuka.Result[T]

Get is the related row: the one held, else read and then held. orm.NotFound when the FK is unset or the row is gone.

func (FK[T]) ID

func (f FK[T]) ID() int64

ID is the related row's key as an integer; 0 when unset or not one.

func (FK[T]) IsSet

func (f FK[T]) IsSet() bool

IsSet is whether the FK refers to a row.

func (FK[T]) Key

func (f FK[T]) Key() any

Key is the related row's key; nil when unset.

func (FK[T]) Loaded

func (f FK[T]) Loaded() bool

Loaded is whether the related row is held.

func (FK[T]) MarshalJSON

func (f FK[T]) MarshalJSON() ([]byte, error)

func (FK[T]) Related

func (f FK[T]) Related() *T

Related is the related row when it is loaded, else nil.

func (*FK[T]) Scan

func (f *FK[T]) Scan(src any) error

func (*FK[T]) Set

func (f *FK[T]) Set(row *T)

Set makes the FK refer to row; SetID to the row of key key.

func (*FK[T]) SetID

func (f *FK[T]) SetID(key any)

func (FK[T]) String

func (f FK[T]) String() string

func (*FK[T]) UnmarshalJSON

func (f *FK[T]) UnmarshalJSON(b []byte) error

func (FK[T]) Value

func (f FK[T]) Value() (driver.Value, error)

type Field

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

Field is a model's field as a typed lookup. ormgen writes one per column in each model's typed fields (UserFields.Age, PostFields.Author.Email), so a misspelled field or a value of the wrong type fails to compile instead of failing the query; the methods build the same conditions as Q:

orm.For[User]().Filter(UserFields.Age.Gte(18), UserFields.Name.IContains("al")).
	OrderBy(UserFields.Name.Desc())

A pointer or an Option field is a Field of what it holds (*int and vuka.Option[int] are Field[int]); IsNull matches its NULLs.

func FieldAt

func FieldAt[V any](path string) Field[V]

FieldAt is the field at a lookup path (a column, or relations to one: "author__name"). Generated typed fields call it.

func (Field[V]) Asc

func (f Field[V]) Asc() string

Asc and Desc are the field for OrderBy.

func (Field[V]) Desc

func (f Field[V]) Desc() string

func (Field[V]) Eq

func (f Field[V]) Eq(v V) Cond

func (Field[V]) Expr

func (f Field[V]) Expr() Expr

Expr is the field as an expression: F(name).

func (Field[V]) Gt

func (f Field[V]) Gt(v V) Cond

func (Field[V]) Gte

func (f Field[V]) Gte(v V) Cond

func (Field[V]) In

func (f Field[V]) In(vs ...V) Cond

In matches any of vs; none matches nothing.

func (Field[V]) InQuery

func (f Field[V]) InQuery(sub subquerier) Cond

InQuery matches the values a subquery selects: a QuerySet (its keys) or a Values of one column.

func (Field[V]) IsNull

func (f Field[V]) IsNull(null bool) Cond

IsNull matches a NULL column with true, a set one with false.

func (Field[V]) Lt

func (f Field[V]) Lt(v V) Cond

func (Field[V]) Lte

func (f Field[V]) Lte(v V) Cond

func (Field[V]) Name

func (f Field[V]) Name() string

Name is the field's lookup path.

func (Field[V]) Ne

func (f Field[V]) Ne(v V) Cond

func (Field[V]) Range

func (f Field[V]) Range(lo, hi V) Cond

Range matches from lo to hi, both included.

func (Field[V]) String

func (f Field[V]) String() string

String is the field's lookup path.

type FieldAttr added in v0.5.0

type FieldAttr interface{ Apply(o *FieldOptions) }

FieldAttr is a field attribute: any value that says something about a field's column, given to s.Field in the model's Schema method (or written after the field in Vuka). The ORM applies a field's attributes in order, each over what the ones before it set; the built-in ones (orm.Char, orm.Int, orm.Column, …) are FieldAttrs too, and one of another package is used the same way.

type Email struct{ Unique bool }

func (e Email) Apply(o *orm.FieldOptions) {
	o.MaxLength, o.Lower, o.Trim = 254, true, true
	o.Unique = o.Unique || e.Unique
}

func (u *User) Schema(s *orm.Schema) {
	s.Field(&u.Email, Email{Unique: true})
}

An attribute with a method Validate(v any) error checks each value a write gives the field as well: a non-nil error refuses it (Invalid).

type FieldInfo added in v0.5.0

type FieldInfo struct {
	Name   string // the Go field's name
	Column string
	Type   reflect.Type
	PK     bool
	Null   bool // the column takes NULL
	Unique bool
	Index  bool
	// MaxLength and MinLength bound a text's characters (0: no bound);
	// Min and Max a number; Choices is the values it may take (nil: any);
	// Pattern the regular expression a text matches ("" for none).
	MaxLength, MinLength int
	Min, Max             vuka.Option[int64]
	Choices              []any
	Pattern              string
	// Default is the column's SQL default ("" for none); Initial its value
	// on Create when left empty.
	Default              string
	Initial              any
	Label, Help, Comment string
	AutoNow, AutoNowAdd  bool
}

FieldInfo is a model's column as forms, admin pages and tools read it: what its field attributes, tags and SetFieldOptions declare.

type FieldOptions

type FieldOptions struct {
	Column      string // the column; the field's name in snake_case otherwise
	Skip        bool   // no column (orm:"-")
	PK          bool
	Unique      bool
	Index       bool
	UniqueIndex string // a named unique index: fields sharing it are unique together
	MaxLength   int    // a VARCHAR's size, and the most characters a write may hold
	Type        string // a database type of the program's own
	// Default is the SQL literal a row inserted without the column takes,
	// in the table migrations make ('draft', 0, CURRENT_TIMESTAMP).
	Default string
	// Initial is the Go value Create gives the field when it is left at
	// its zero value: Default as a value (DefaultTo and the attributes'
	// Default set both).
	Initial    any
	AutoNow    bool   // set to the time on every save
	AutoNowAdd bool   // set to the time on create
	OnDelete   string // a foreign key's: cascade, set_null, restrict
	Related    string // the name of a relation's inverse on the related model
	FK         string // a relation's column holding the related row's key
	Null       bool   // the column takes NULL (an orm.FK that may be unset)
	Through    string // a many-to-many's table between
	// Elements names an elements relation's column (a JSON array or
	// comma-separated string holding the related rows' keys), optionally
	// with the related field it refers to as "column,ref": the attribute
	// form of orm:"elements:…". JoinForeignKey and JoinReferences are a
	// many-to-many's columns for this row's key and the related row's.
	Elements                       string
	JoinForeignKey, JoinReferences string
	Dims                           int // a vector's dimensions
	// CrossDB marks a relation to a model on another database: its key
	// column has no foreign key constraint, and queries never join it.
	CrossDB bool

	// Comment is the column's comment in the table (Postgres, MySQL);
	// Label and Help describe the field to forms and tools (FieldInfo).
	Comment, Label, Help string
	// Collation is a text column's collation, as the database names it
	// (utf8mb4_bin, "C", NOCASE).
	Collation string

	// The checks a write makes of the field's value (Create, Save, Update,
	// BulkCreate): an Invalid error naming the column when one fails.
	// Choices is the values it may take; MinLength the fewest characters
	// of a text; Min and Max a number's bounds; Pattern a regular
	// expression (Go's syntax) a text matches; Checks functions of the
	// value, a non-nil error refusing it.
	Choices   []any
	MinLength int
	Min, Max  vuka.Option[int64]
	Pattern   string
	Checks    []func(v any) error
	// Trim and Lower normalize a text as it is written: its surrounding
	// spaces removed, in lower case.
	Trim, Lower bool

	// Size is an integer column's width (Small, Medium, Big), over its Go
	// type's; Unsigned makes it UNSIGNED on MySQL, and refuses negative
	// values on every database.
	Size     IntSize
	Unsigned bool
	// Date makes a time.Time field a DATE column. Precision is a
	// timestamp's fractional digits (1–6; 0 is the database's default,
	// microseconds); UTC writes and reads its times in UTC.
	Date      bool
	Precision int
	UTC       bool
	// Manual is an integer primary key the program sets, not one the
	// database counts; UUID a key the ORM sets to a random UUID on create
	// when it is empty (a string field).
	Manual, UUID bool

	// Schemas overrides the field's column under named schemas (names
	// sets), keyed by the set's name: what a layer above the ORM declares
	// in place of a per-set struct tag (legacy:"column:…;type:…"). The
	// override for a set is applied only when the model is read under it
	// (orm.Names, orm.Of, a mirror under the set); the default schema is
	// untouched. A SchemaField's Codec adapts the value's format there.
	Schemas map[string]SchemaField
	// contains filtered or unexported fields
}

FieldOptions is what a struct tag says about a field, as a value: the way a layer above the ORM (a language's field attributes, a schema file) declares a model's fields without tags. A zero member leaves the tag's setting; a set one wins over it.

FieldOptions is a FieldAttr itself: its set members applied over another's, as SetFieldOptions's are over a field's attributes.

func (FieldOptions) Apply added in v0.5.0

func (o FieldOptions) Apply(dst *FieldOptions)

Apply sets o's set members over dst: a string, number or list that isn't empty, a bool that is true, a bound that is present. Checks add up.

type FieldOptionsFunc

type FieldOptionsFunc func(owner reflect.Type, field reflect.StructField) (opts FieldOptions, ok bool)

FieldOptionsFunc supplies a model's field options: ok false for a field it says nothing about. owner is the struct declaring the field (an embedded one's own type for its fields).

type Fn

type Fn[X interface {
	Expr
	funcDef() *FuncDef
}] func(args ...any) X

Fn is a typed function: called with its arguments — each an Expr (F for a field) or a value, sent as a bound argument — it is an expression of type X. Func, OrderedFunc and TextFunc make one.

func (Fn[X]) Def

func (f Fn[X]) Def() *FuncDef

Def is the function's definition.

func (Fn[X]) Name

func (f Fn[X]) Name() string

Name is the function's name.

func (Fn[X]) Of

func (f Fn[X]) Of(args ...any) Expr

Of is the call as an untyped Expr, as a Function's Of.

func (Fn[X]) Transform

func (f Fn[X]) Transform() Fn[X]

Transform makes the function a step of Q keys, as FuncDef's Transform.

type ForOption

type ForOption func(*forConfig)

ForOption tunes a model.

func Mirror

func Mirror(name string) ForOption

Mirror has every write of the model repeated on the database added as name, after it succeeds on the model's own database: a table moving from one database to another keeps both current while reads stay on the first. Reads never use the mirror.

The mirror is written after the write's transaction commits (at once outside one), with the keys the primary gave new rows. A mirror that fails doesn't fail the write: the primary is the truth, and the failure goes to OnMirrorError (and the log) so the table can be re-synced.

A Route routes a table without a code change, for models naming no databases of their own (a program reads its routes from its configuration):

orm.Default.Route("categories", orm.Route{
	DB:     "legacy", // read, and written first
	Mirror: "main",   // written after
})

Cutting the table over is swapping the two, then dropping the mirror.

func MirrorTable added in v0.6.0

func MirrorTable(name string) ForOption

MirrorTable names the mirror's table, over the model's: the legacy table a row is mirrored into.

func MirrorUnder added in v0.6.0

func MirrorUnder(names string) ForOption

MirrorUnder binds the mirror through a names set: its writes take the columns, types and value codecs the model's fields declare for that set (a legacy schema's). Reads still never use the mirror. It pairs with Mirror (or a Route's) and MirrorTable.

func Names

func Names(set string) ForOption

Names reads the model under a names set (see DBSchema): each field's tag of that key over its default naming, and <Names>TableName (names "legacy": LegacyTableName) over TableName.

func On

func On(name string) ForOption

On puts the model on the database added as name; the default database otherwise.

func Table

func Table(name string) ForOption

Table names the model's table, over TableName and the plural of the type's name.

func Unmanaged

func Unmanaged() ForOption

Unmanaged marks the model's table as the database's own: never created or migrated by the ORM. Every legacy table is one.

type FullText

type FullText struct{ Config string }

FullText is the field's full-text index (Config: Postgres's text search configuration).

func (FullText) Apply added in v1.0.0

func (FullText) Apply(*FieldOptions)

type FuncDef

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

FuncDef is a database function's definition: its name, its SQL on each dialect, how many arguments it takes, whether it aggregates. Function makes an untyped one, called with Of; Func, OrderedFunc and TextFunc make typed ones, called directly.

var Unaccent = orm.Function("unaccent")                 // unaccent(…)
var Year = orm.Function("year",
	orm.Template("postgres", "CAST(EXTRACT(YEAR FROM {0}) AS INTEGER)"),
	orm.Template("sqlite", "CAST(strftime('%Y', {0}) AS INTEGER)"))  // YEAR(…) on MySQL

Users.Filter(orm.Where(Unaccent.Of(orm.F("name")), "icontains", "jose"))

A FuncDef registered with Transform is also a step of Q keys: Q{"name__unaccent__icontains": "jose"}.

func Function

func Function(name string, opts ...FuncOption) *FuncDef

Function defines an untyped database function, called with Of. Without a Template for the dialect a query runs on it is called as name(args…).

func (*FuncDef) Name

func (f *FuncDef) Name() string

Name is the function's name.

func (*FuncDef) Of

func (f *FuncDef) Of(args ...any) Expr

Of calls the function: each argument an Expr (F for a field) or a value, sent as a bound argument, never written into the SQL.

func (*FuncDef) Transform

func (f *FuncDef) Transform() *FuncDef

Transform makes a one-argument function a step of Q keys, by its name, between a field and its lookup: Q{"created_at__year__gte": 2026}. It returns f, so a declaration can end with it.

type FuncOf

type FuncOf[R any] = Fn[TypedExpr[R]]

FuncOf is a function of R values, made by Func.

func Aggregate

func Aggregate[R any](name string, opts ...FuncOption) FuncOf[R]

Aggregate is Func of an aggregate, IsAggregate's: an annotation of it groups, a condition on it is HAVING's, and AggOf takes it to Aggregate. OrderedFunc and TextFunc take IsAggregate for ordered and text aggregates.

var StringAgg = orm.TextFunc("string_agg", orm.IsAggregate(),
	orm.Template("postgres", "string_agg({0}, {1})"),
	orm.Template("sqlite", "group_concat({0}, {1})"))
Users.Annotate("names", StringAgg(orm.F("name"), ", ")).Values[[]any]("team_id", "names")

func Func

func Func[R any](name string, opts ...FuncOption) FuncOf[R]

Func defines a database function whose value is an R, called directly:

var Levenshtein = orm.Func[int]("levenshtein",
	orm.Template("postgres", "levenshtein({0}, {1})"),   // fuzzystrmatch
	orm.Template("sqlite", "editdist({0}, {1})"))         // a Go function: see orm/sqlite's Func

Users.Filter(Levenshtein(orm.F("name"), "jon").Eq(1))

OrderedFunc and TextFunc make functions whose values compare by order and by pattern too. Without a Template it is called as name(args…); with some, on their dialects only.

type FuncOption

type FuncOption func(*FuncDef)

FuncOption shapes a function's definition.

func Arity

func Arity(n int) FuncOption

Arity is how many arguments the function takes: a call with another number fails its query.

func IsAggregate

func IsAggregate() FuncOption

IsAggregate makes the function an aggregate, as Count and Sum are: an annotation of it groups a Values listing it by the other names, and a condition on it is HAVING's.

func Template

func Template(dialect, template string) FuncOption

Template is the function's SQL on a dialect ("postgres", "mysql", "sqlite"; "" for all of them), {0}, {1}, … for its arguments and {*} for all of them. A typed function with Templates is written only where one applies: on another dialect its query fails, naming the function and the dialect.

type Fusion

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

Fusion is a Fuse.

func Fuse

func Fuse(orders ...string) Fusion

Fuse is reciprocal-rank fusion of orderings, for hybrid search: each name (a field or an annotation, a leading - for descending) ranks the rows, and a row scores the sum of w/(k + its rank), k 60 and each w 1 unless K and Weights say — order by it descending. Hybrid fuses a text and a vector search with it.

Docs.Annotate("rank", orm.SearchRank(doc, q)).
	Annotate("distance", orm.CosineDistance("embedding", v)).
	Annotate("score", orm.Fuse("-rank", "distance").Weights(1, 0.5)).OrderBy("-score")

func (Fusion) K

func (f Fusion) K(k int) Fusion

K is the fusion's constant (60 otherwise): a smaller k favours the top ranks more.

func (Fusion) Weights

func (f Fusion) Weights(ws ...float64) Fusion

Weights weighs the orderings, in order: 1 for those not given.

type HNSW

type HNSW struct {
	Metric            Metric
	M, EfConstruction int
}

HNSW and IVFFlat are pgvector indexes of a vector field, serving the distance of their Metric (L2 when not given).

func (HNSW) Apply added in v1.0.0

func (HNSW) Apply(*FieldOptions)

Apply makes the search attributes field attributes: the indexes and the generated document are read from the attributes themselves.

type HeadlineOption

type HeadlineOption func(*headlineOpts)

HeadlineOption shapes a Headline: Marks, MaxWords, HighlightAll.

func HighlightAll

func HighlightAll() HeadlineOption

HighlightAll marks the words in the whole text rather than cutting a headline from it.

func Marks

func Marks(start, stop string) HeadlineOption

Marks are the text put before and after each word found: <b> and </b> otherwise. They are bound arguments, written as given: escape the field's text yourself where it is HTML.

func MaxWords

func MaxWords(n int) HeadlineOption

MaxWords is the most words a headline has: 35 otherwise (at most 64 on SQLite).

type IVFFlat

type IVFFlat struct {
	Metric Metric
	Lists  int
}

The search attributes of a Vuka model's fields:

type Doc struct {
	orm.Base
	ID        int64
	Title     string       @orm.Trigram
	Body      string       @orm.FullText{Config: "english"}
	Search    orm.TSVector @orm.SearchOf{Fields: []string{"title:A", "body:B"}, Config: "english"}
	Embedding orm.Vector   @orm.Dims(3) @orm.HNSW{Metric: orm.Cosine, M: 16}
}

They are what Indexes() and Generated() say in Go: indexes made by migrations (on Postgres; MySQL a FullText; SQLite none, its searches unindexed), and a generated tsvector column of the fields named.

func (IVFFlat) Apply added in v1.0.0

func (IVFFlat) Apply(*FieldOptions)

type Index

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

Index is an index a model declares in its Indexes() method, Django's Meta.indexes, made and dropped by migrations. It is over fields, named as queries name them (their columns under the schema's names), or expressions:

func (Post) Indexes() []orm.Index {
	return []orm.Index{
		orm.GinIndex(orm.SearchVector("title", "body").Config("english")).Name("posts_search"),
		orm.GinIndex("title").Trigram(),
		orm.HnswIndex("embedding").Ops(orm.Cosine).M(16).EfConstruction(64),
		orm.FullTextIndex("title", "body"),
	}
}

MySQL makes a FullTextIndex, and no trigram or vector index (its trigram lookups are LIKE, and it searches no vectors); a model declaring another kind can't map onto it. SQLite makes none of them: a model declaring a document (a FullTextIndex, a GinIndex of a SearchVector, a generated TSVector) has its FTS5 table instead, <table>_fts, and its vector and trigram searches are unindexed.

func FullTextIndex

func FullTextIndex(fields ...string) Index

FullTextIndex is the fields' full-text index: MySQL's FULLTEXT, which MATCH needs, and on Postgres a GIN index of their tsvector (by Config, simple when not given), which the __search lookup of a field indexed alone uses.

func GinIndex

func GinIndex(on ...any) Index

GinIndex is a Postgres GIN index of fields or expressions: a SearchVector, or text fields with Trigram.

func GistIndex

func GistIndex(on ...any) Index

GistIndex is a Postgres GiST index of fields or expressions.

func HnswIndex

func HnswIndex(field string) Index

HnswIndex is pgvector's HNSW index of a vector field.

func IvfflatIndex

func IvfflatIndex(field string) Index

IvfflatIndex is pgvector's IVFFlat index of a vector field.

func (Index) Apply added in v0.5.0

func (Index) Apply(o *FieldOptions)

func (Index) Config

func (i Index) Config(c string) Index

Config is a FullTextIndex's text search configuration on Postgres.

func (Index) EfConstruction

func (i Index) EfConstruction(n int) Index

func (Index) Lists

func (i Index) Lists(n int) Index

func (Index) M

func (i Index) M(n int) Index

M is an HNSW index's connections per layer; EfConstruction its candidate list while building; Lists an IVFFlat index's lists.

func (Index) Name

func (i Index) Name(s string) Index

Name names the index; <table>_<fields>_<kind> otherwise.

func (Index) Ops

func (i Index) Ops(m Metric) Index

Ops is the metric a vector index serves (L2 when not given): only queries by that metric's distance use it.

func (Index) Trigram

func (i Index) Trigram() Index

Trigram indexes text fields by pg_trgm's trigrams (gin_trgm_ops, gist_trgm_ops), for trigram_similar, Similarity and LIKE.

type Int added in v0.5.0

type Int struct {
	Min, Max             any
	Size                 IntSize
	Unsigned             bool
	Choices              []int64
	Unique, Index, Null  bool
	Column               string
	Comment, Label, Help string
	Default              any
}

Int is an integer column: Min and Max bound its values, an integer each, nil for no bound (@orm.Int{Min: 0, Max: 150}: 0 is a bound like any other); Size is its width over its Go type's (Small, Medium, Big); Unsigned makes it UNSIGNED on MySQL and refuses negative values everywhere; Choices is the values it may take.

func (Int) Apply added in v0.5.0

func (x Int) Apply(o *FieldOptions)

type IntSize added in v0.5.0

type IntSize uint8

IntSize is an integer column's width.

const (
	Small IntSize = iota + 1
	Medium
	Big
)

The widths of an integer column: SMALLINT, INTEGER (32 bits) and BIGINT.

type JSON

type JSON[T any] struct{ V T }

JSON is a column holding V as JSON, Django's JSONField: jsonb on Postgres, JSON on MySQL, TEXT on SQLite. Read and write V; the field marshals as V alone, so a response carries the value, not a wrapper. A nil V (slice, map or pointer) is NULL, as a nil []byte is.

type Letter struct {
	ID           int64
	PlacementIDs orm.JSON[[]int64]
}
letter.PlacementIDs.V = append(letter.PlacementIDs.V, 7)

func JSONOf

func JSONOf[T any](v T) JSON[T]

JSONOf is the JSON column holding v.

func (JSON[T]) MarshalJSON

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

func (*JSON[T]) Scan

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

func (*JSON[T]) UnmarshalJSON

func (j *JSON[T]) UnmarshalJSON(b []byte) error

func (JSON[T]) Value

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

type Kind

type Kind uint8

Kind is what an Error means to the code calling the ORM, whatever the database said. A Kind is an error itself, so errors.Is matches an Error against its kind:

if errors.Is(err, orm.ErrNotFound) { … }

A web framework maps kinds onto HTTP statuses, for example:

Kind           HTTP
NotFound       404
MultipleFound  409
Conflict       409 (Fields: the column → "already exists")
Invalid        422 (Fields: the column → "does not exist", or a check's message)
Unavailable    503
(no Error)     500
const (

	// NotFound: no row matched a query that needs one (Get, First,
	// Refresh, Remove of a row gone).
	NotFound Kind
	// MultipleFound: Get matched more than one row.
	MultipleFound
	// Conflict: a write broke a unique constraint.
	Conflict
	// Invalid: a write referred to a row that does not exist (a foreign
	// key violation), or gave a field a value its checks refuse (Max,
	// Choices, Pattern, a Validate method; Fields: the columns' messages).
	Invalid
	// Unavailable: the database didn't answer (Migrate gave up waiting
	// for it).
	Unavailable
)

func KindOf

func KindOf(err error) Kind

KindOf is the Kind of err: its first *Error's, or a Kind in its chain (ErrNotFound and the other kinds as values), 0 when it has neither: an error the ORM gives no meaning (a driver's, a bad query).

func (Kind) Error

func (k Kind) Error() string

func (Kind) String

func (k Kind) String() string

type LongText

type LongText struct{}

LongText is an unbounded text column (what a string is by default).

func (LongText) Apply added in v0.5.0

func (LongText) Apply(o *FieldOptions)

type LookupFunc

type LookupFunc func(lhs string, rhs func() string, d Dialect) (sql string, ok bool)

LookupFunc writes a custom lookup's condition on dialect d: lhs is the left side's SQL (a column, through any transforms of its key), rhs writes the value — a bound argument's marker, or an Expr's SQL — each call binding it once more, so a lookup naming its value twice calls rhs twice and one ignoring its value binds nothing. ok is false where the lookup has no SQL on d: the query fails, naming the lookup and the dialect.

type LookupOption

type LookupOption func(*customLookup)

LookupOption shapes a custom lookup.

func LookupValue

func LookupValue(fn func(v any) any) LookupOption

LookupValue maps a lookup's value before it is bound (an Expr is never passed through it): LikePattern's, or a value of your own.

type M2M

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

M2M is a many-to-many relation to T, as one field: a table between (<table>_<field>, or Rel's Through) holding both keys. Its rows are read with All (held once PrefetchRelated loaded them) or queried with Query; Add, Remove, Set and Clear change the links of a saved row. A field reference goes on into T's fields: Post.Tags.Name.

p.Tags.Add(ctx, &golang, &db)?
tags := p.Tags.All(ctx)?
posts := Post.Objects.Filter(Post.Tags.Name.Eq("go")).PrefetchRelated(Post.Tags).All(ctx)?

func (*M2M[T]) Add

func (m *M2M[T]) Add(ctx context.Context, related ...any) error

Add links the row to related rows (rows or keys), Remove unlinks them, Set makes them its only links, Clear removes every link.

func (M2M[T]) All

func (m M2M[T]) All(ctx context.Context) vuka.Result[[]T]

All is the related rows: those held, else read (and not held).

func (*M2M[T]) Clear

func (m *M2M[T]) Clear(ctx context.Context) error

func (M2M[T]) Loaded

func (m M2M[T]) Loaded() bool

Loaded is whether the related rows are held; Rows is them.

func (M2M[T]) MarshalJSON

func (m M2M[T]) MarshalJSON() ([]byte, error)

func (M2M[T]) Query

func (m M2M[T]) Query() Query[T]

Query is the related rows as a query.

func (M2M[T]) Related

func (m M2M[T]) Related() *T

Related is the first related row held, else nil: what a field reference through the relation reads in memory.

func (*M2M[T]) Remove

func (m *M2M[T]) Remove(ctx context.Context, related ...any) error

func (M2M[T]) Rows

func (m M2M[T]) Rows() []T

func (*M2M[T]) Set

func (m *M2M[T]) Set(ctx context.Context, related ...any) error

type Manager

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

Manager is a model's entry point, Django's Model.objects: a QuerySet of every row (its methods are the QuerySet's) and the writes of single rows. It queries the database its model is on (On, Meta.DB; the default otherwise) in the context's Databases.

var Users = orm.For[User]()

orm.AddDatabase("main", db, orm.AsDefault())
u, err := Users.Get(ctx, orm.Q{"email": e})

func For

func For[T any](opts ...ForOption) *Manager[T]

For is the manager of the model T. Declare it once, package-level; a model the ORM can't map fails every query, and Check (run it at start up) reports it. Its options win over the model's Meta. The first For of a model without Names is its own manager, the one Objects and its rows' writes use.

func Objects

func Objects[T any](s ...DBSchema) *Manager[T]

Objects is the model T's manager: Objects[T]() its own (its package-level orm.For[T]() without Names, else one made from its Meta on first use), Objects[T](s) the one on schema s (orm.Of[T](s)). It registers T (see Register).

func Of

func Of[T any](s DBSchema) *Manager[T]

Of is the manager of the model T on schema s, made once per model and schema. It finds its database as For's managers do: in the context's Databases.

func (*Manager[T]) BulkCreate

func (m *Manager[T]) BulkCreate(ctx context.Context, rows []*T) (err error)

BulkCreate inserts rows in as few statements as it can, setting their primary keys, in batches of up to 500 (fewer for a wide model).

func (*Manager[T]) ColumnValues

func (m *Manager[T]) ColumnValues(row *T) map[string]any

ColumnValues is row's columns and their values as the database is sent them (a nil pointer as nil): a snapshot of the row for code that speaks columns, such as a change feed to another system.

func (*Manager[T]) Columns

func (m *Manager[T]) Columns() []string

Columns is the model's columns, in field order.

func (*Manager[T]) Create

func (m *Manager[T]) Create(ctx context.Context, row *T) (err error)

Create inserts row and sets its new primary key.

func (*Manager[T]) Err

func (m *Manager[T]) Err() error

Err is what keeps the ORM from mapping the model: nil when it can.

func (*Manager[T]) Fields added in v0.5.0

func (m *Manager[T]) Fields() []FieldInfo

Fields is the model's columns, in field order, as forms and tools read them.

func (*Manager[T]) GetOrCreate

func (m *Manager[T]) GetOrCreate(ctx context.Context, lookup Q, defaults Defaults) (row T, created bool, err error)

GetOrCreate is the row matching lookup, or a new one made from lookup's exact fields and defaults; created says which. A row another request inserted first is read rather than duplicated.

func (*Manager[T]) Info

func (m *Manager[T]) Info() ModelInfo

Info is the model as routers see it: its Meta, with For's On and Mirror over it.

func (*Manager[T]) Manager added in v1.0.0

func (m *Manager[T]) Manager() *Manager[T]

Manager is m itself, as Query's Manager was: Post.Objects.Manager() in code written when Post.Objects was a Query.

func (*Manager[T]) OnChange

func (m *Manager[T]) OnChange(fn func(ctx context.Context, c Change[T])) (stop func())

OnChange calls fn after each write of T's rows, once its transaction commits (at once outside one; never for a rolled-back one): the place to tell live pages, with view.Broadcast. It returns what stops it.

Users.OnChange(func(ctx context.Context, c orm.Change[User]) {
	view.Broadcast(ctx, "users", "changed")
})

func (*Manager[T]) PKColumn

func (m *Manager[T]) PKColumn() string

PKColumn is the column of the model's primary key, "" for none.

func (*Manager[T]) Raw

func (m *Manager[T]) Raw(sql string, args ...any) RawQuerySet[T]

Raw is the rows sql reads, as the model's: each column fills the field of that column under the manager's names set (else of that name, or the computed field), and other columns are dropped. ? marks each argument, ?? is a literal ?. The SQL is the schema's own: written for the schema the manager is on.

users, err := orm.Of[User](Legacy).Raw("SELECT * FROM auth_user WHERE tel_no LIKE ?", "255%").All(ctx)

func (*Manager[T]) Remove

func (m *Manager[T]) Remove(ctx context.Context, row *T) error

Remove deletes row's row, found by primary key. A row of a Model is new again after.

func (*Manager[T]) Save

func (m *Manager[T]) Save(ctx context.Context, row *T) error

Save writes every field of row to its row, found by primary key; auto_now fields are set to now.

func (*Manager[T]) TableName

func (m *Manager[T]) TableName() string

TableName is the model's table.

func (*Manager[T]) UpdateOrCreate

func (m *Manager[T]) UpdateOrCreate(ctx context.Context, lookup Q, defaults Defaults) (row T, created bool, err error)

UpdateOrCreate updates the row matching lookup with defaults, or creates one from lookup's exact fields and defaults (as GetOrCreate), in a transaction; created says which.

type Meta

type Meta struct {
	Table     string   // the table; the plural of the type's name in snake_case otherwise
	DB        string   // the database added under that name, orm.On's; the default otherwise
	Read      string   // the database its reads go to, over DB (a replica's own name, a reporting copy)
	Write     string   // the database its writes go to, over DB
	Mirror    string   // a database its writes are repeated on (see orm.Mirror)
	Unmanaged bool     // the database's own table: never created or migrated, orm.Unmanaged's
	Indexes   []Index  // its indexes, as Indexes() returns them
	Ordering  []string // the order of a query that sets none, as OrderBy takes it
}

Meta is what a model declares about itself, Django's class Meta: a model attribute, given to s.Model in the model's Schema method.

func (u *User) Schema(s *orm.Schema) {
	s.Model(orm.Meta{Table: "accounts", Ordering: []string{"-created_at"}})
}

A Meta method, func (User) Meta() orm.Meta, still declares it and wins over the Schema's; it is deprecated, for the Schema method.

It wins over the model's TableName and Indexes methods; For's options (orm.Table, orm.On, orm.Unmanaged) win over it, and under a names set the set's <Names>TableName (LegacyTableName) does.

func (Meta) ApplyModel added in v1.0.0

func (Meta) ApplyModel(*ModelConfig)

ApplyModel makes Meta a model attribute: s.Model(orm.Meta{…}) is the model's Meta, read whole (a Meta method wins over it), so it folds nothing into a ModelConfig.

type Metric

type Metric int

Metric is how vectors are compared: their Euclidean distance (L2), the cosine distance, or the inner product.

const (
	L2 Metric = iota + 1
	Cosine
	IP
)

type MigrateOption

type MigrateOption func(*migrateConfig)

MigrateOption tunes Migrate.

func MigrateLogger

func MigrateLogger(l *slog.Logger) MigrateOption

MigrateLogger has Migrate say what it applies on l; slog.Default() otherwise.

func MigrateOn

func MigrateOn(name string) MigrateOption

MigrateOn applies the migrations of the database added as name ("" the default) only; repeat it for several. Every database with migrations otherwise.

type MirrorError

type MirrorError struct {
	Table  string
	Op     string // create, update, delete
	Mirror string // the mirror's database name ("" for one WithMirror set)
	Err    error
}

MirrorError is a mirrored write that failed: the primary has the change, the mirror doesn't.

func (MirrorError) Error

func (e MirrorError) Error() string

type Model

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

Model is a model's base, Django's models.Model: embedded by value in the model it is of, it gives each row the writes of its own and the model its manager.

type User struct {
	orm.Model[User]
	ID    int64
	Email string
	Roles []Role `gorm:"many2many:user_roles"`
}

u := User{Email: e}
err := u.Save(ctx)                                    // INSERT: u.ID set
u.Email = e2
err = u.Save(ctx)                                     // UPDATE
err = u.Add(ctx, "roles", admin)                      // a user_roles row
users, err := User{}.Objects().Filter(orm.Q{"roles__name": "admin"}).All(ctx)

A row remembers whether it was read from (or written to) the database, Django's _state.adding, and the schema it was: rows read through a QuerySet, Raw, Get or Create come back loaded, related rows SelectRelated and PrefetchRelated load too. Its state is unexported, so GORM and JSON ignore it. Its methods find the row from the embedded field, so embed it exactly once, by value, in the type it names (directly or through an embedded struct); Register and boot fail on anything else.

func (*Model[T]) Add

func (s *Model[T]) Add(ctx context.Context, relation any, related ...any) error

Add links the row to related rows through a many-to-many relation, Django's related manager add(): a row of the table between for each not linked yet. The relation is named by typed field (UserFields.Roles), reference or name; related is rows of the relation's model (values or pointers) or their keys.

err := u.Add(ctx, "roles", admin, editor)

func (*Model[T]) DeclaredFieldAttrs added in v1.0.0

func (*Model[T]) DeclaredFieldAttrs(t reflect.Type, registered map[string][]any) map[string][]any

DeclaredFieldAttrs makes the attributes of a model's Schema method what vuka.FieldAttrsOf reads: T's fields' registered attributes, then its Schema's; its model attributes are on the embedded Model field, as a Vuka model writes them on orm.Base.

func (*Model[T]) Delete

func (s *Model[T]) Delete(ctx context.Context) error

Delete deletes the row by its primary key. The row is new again after: Save inserts it.

func (*Model[T]) Load

func (s *Model[T]) Load(ctx context.Context, specs ...any) error

Load loads relations into the row, as PrefetchRelated loads them into a query's rows: typed fields (UserFields.Roles), references, names, paths (posts__comments) or orm.Prefetch.

err := u.Load(ctx, "roles", "profile")

func (Model[T]) Objects

func (Model[T]) Objects(s ...DBSchema) *Manager[T]

Objects is the model's manager, Django's Model.objects: Objects() its own (the package-level orm.For[T]() without Names it has, else one made from its Meta), Objects(s) the one on schema s (orm.Of[T](s)).

n, err := User{}.Objects().Filter(orm.Q{"active": true}).Count(ctx)

func (*Model[T]) Refresh

func (s *Model[T]) Refresh(ctx context.Context) error

Refresh reads the row again by its primary key, Django's refresh_from_db: every field, and relations emptied.

func (*Model[T]) Remove

func (s *Model[T]) Remove(ctx context.Context, relation any, related ...any) error

Remove unlinks the row from related rows through a many-to-many relation, Django's remove(): their rows of the table between go.

func (*Model[T]) Save

func (s *Model[T]) Save(ctx context.Context) error

Save writes the row: an INSERT when it is new (its key set, and the row loaded, after), else an UPDATE of every field by its primary key.

func (*Model[T]) SaveFields

func (s *Model[T]) SaveFields(ctx context.Context, fields ...any) error

SaveFields updates only the fields named, and the auto_now ones, of a loaded row: Django's save(update_fields=…). A field is a typed field (PostFields.Title), a Go name or a column.

func (*Model[T]) Set

func (s *Model[T]) Set(ctx context.Context, relation any, related ...any) error

Set makes related the rows the row is linked to through a many-to-many relation, Django's set(): links to others are removed, missing ones added, in one transaction.

func (*Model[T]) Using

func (s *Model[T]) Using(sc DBSchema) *T

Using binds the row to schema s and returns it: its writes go there. A row loaded from another schema is new to s, so Save inserts it (a copy of the row there).

type ModelAttr added in v0.7.0

type ModelAttr interface{ ApplyModel(*ModelConfig) }

ModelAttr is a model attribute: a value given to s.Model in the model's Schema method (or written on the embedded orm.Base in Vuka) that configures the whole model's routing and mirror the way orm.Meta and For's options do. The ORM calls ApplyModel on every model attribute but orm.Meta (read whole as the model's Meta), and folds the result into the model.

func (p *Placement) Schema(s *orm.Schema) {
	s.Model(orm.Meta{Table: "placements"}, mirror.Legacy{Table: "tbl_placements"})
	s.Field(&p.ID, orm.PK{})
}

where an app's mirror.Legacy implements ModelAttr:

func (l Legacy) ApplyModel(c *orm.ModelConfig) {
	c.Mirror = cmp.Or(c.Mirror, "legacy")
	c.MirrorNames = cmp.Or(c.MirrorNames, "legacy")
	c.MirrorTable = cmp.Or(c.MirrorTable, l.Table)
}

A ModelConfig.Names rebinds the PRIMARY: orm.For[T]() with no Names option, and the model's own read/write path, build the model under that names set, so the live columns use the Schemas[Names] overrides and codecs — a model whose primary is a legacy schema and whose mirror is the clean one (a migration's live source), the inverse of MirrorNames.

The ORM never names any specific attribute: it folds whatever implements ModelAttr. Precedence, highest first: a For option, an orm.Meta field, a ModelAttr; an empty value from a lower layer never clobbers a set one.

Table names the model's table under Names, as MirrorTable names the mirror's under MirrorNames: wherever the model is built under that names set (For[T](), Of, Names(set), relations into it) it wins over <Names>TableName, @orm.Meta's Table and TableName; with Names empty it is the default-naming table, where @orm.Meta's Table wins over it. For's orm.Table wins over both. A ModelAttr that names a mirror but leaves MirrorTable empty mirrors into the model's table under MirrorNames (the same-named table on the mirror's database when the sets share it).

type ModelConfig added in v0.7.0

type ModelConfig struct {
	DB          string // the model's primary database (On / Meta.DB); the default otherwise
	Names       string // the names set the model's OWN (primary) manager binds under
	Table       string // the model's table under Names (see ModelAttr)
	Read        string // the database its reads go to, over DB (Meta.Read; a replica)
	Write       string // the database its writes go to, over DB (Meta.Write)
	Mirror      string // a database its writes are repeated on (Mirror / Meta.Mirror)
	MirrorNames string // the names set the mirror binds under (MirrorUnder)
	MirrorTable string // the mirror's table, over the model's (MirrorTable)
	Unmanaged   bool   // the database's own table: never created or migrated (Unmanaged)

	// Fields gives the model's fields options as the ORM maps the model,
	// over their attributes: for what only the running program knows,
	// such as the columns a legacy table it read at start lacks. It is
	// asked once per field (owner is the struct declaring it, an embedded
	// one for its fields) each time the model is mapped, under each names
	// set, so it decides before the model's first query. A later
	// attribute's Fields replaces an earlier one's; to keep it, call it.
	Fields FieldOptionsFunc
}

ModelConfig is the model-level configuration a ModelAttr sets: the same routing and mirror an @orm.Meta and For's options set, in one value. A zero member leaves the model's setting; a set one is folded in. Later attributes' set members win over earlier ones, and an empty member never undoes one already set (cmp.Or-style), as field attributes merge.

type ModelInfo

type ModelInfo struct {
	Name  string // the Go type's name
	Table string
	Type  reflect.Type
	Meta  Meta
}

ModelInfo is a model as a Router sees it: its Go type, its table and the databases it declares (Meta's DB, Read, Write and Mirror, with For's On and Mirror over them).

type Null

type Null struct{}

Null lets the column hold NULL, for a field whose Go type has no nil (an orm.FK that may be unset).

func (Null) Apply added in v0.5.0

func (Null) Apply(o *FieldOptions)

type NumExpr added in v1.0.0

type NumExpr[V Number] struct{ OrderedExpr[V] }

NumExpr is an OrderedExpr of numbers, which arithmetic takes further: f.Views.Add(1).Mul(2) is (views + 1) * 2.

func Num added in v1.0.0

func Num[V Number](e Expr) NumExpr[V]

Num is e as an expression of numbers.

orm.Num[int](orm.F("views")).Add(1)

func (NumExpr[V]) Add added in v1.0.0

func (e NumExpr[V]) Add(n V) NumExpr[V]

Add, Sub, Mul and Div are the expression and n combined.

func (NumExpr[V]) Div added in v1.0.0

func (e NumExpr[V]) Div(n V) NumExpr[V]

func (NumExpr[V]) Mul added in v1.0.0

func (e NumExpr[V]) Mul(n V) NumExpr[V]

func (NumExpr[V]) Sub added in v1.0.0

func (e NumExpr[V]) Sub(n V) NumExpr[V]

type Number added in v1.0.0

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

Number is a type a NumberField holds: an integer or a float.

type NumberField added in v1.0.0

type NumberField[V Number] struct{ Field[V] }

NumberField is a number field, which arithmetic takes: an Update's new value computed from the row's own,

Posts.Filter(f.Slug.Eq(slug)).Update(ctx, orm.Set{"views": f.Views.Add(1)})

func NumberFieldAt added in v1.0.0

func NumberFieldAt[V Number](path string) NumberField[V]

NumberFieldAt is the number field at a lookup path. Generated typed fields call it.

func (NumberField[V]) Add added in v1.0.0

func (f NumberField[V]) Add(n V) NumExpr[V]

Add, Sub, Mul and Div are the field and n combined, as an expression: f.Views.Add(1) is views + 1. Div divides as the database does, an integer column by an integer in whole numbers on SQLite and Postgres.

func (NumberField[V]) Div added in v1.0.0

func (f NumberField[V]) Div(n V) NumExpr[V]

func (NumberField[V]) Mul added in v1.0.0

func (f NumberField[V]) Mul(n V) NumExpr[V]

func (NumberField[V]) Sub added in v1.0.0

func (f NumberField[V]) Sub(n V) NumExpr[V]

type Observer

type Observer func(ctx context.Context, q QueryInfo)

Observer sees each statement a context's queries run: for logging, tracing, counting in tests, or catching repeated queries. It runs on the goroutine of the query, after the statement returns.

type OpenOption

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

OpenOption shapes a Databases: the databases Declare declares, Routers, ReadYourWrites, Logger, OnOpen.

func Dual added in v0.6.0

func Dual[T any](primary, mirror string, opts ...DualOption) OpenOption

Dual routes T for a dual write without a code change on the model: its models read and write primary, and every write is repeated on mirror after the primary commits (see Mirror). MirrorUnder and MirrorTable bind the mirror through a names set and table of its own — a legacy schema, whose columns, types and value codecs the model declares for that set — so the clean model stays clean and the mirror's shape rides the route. The table comes from T's Meta. Reads never use the mirror.

orm.Default.Apply(orm.Dual[Placement]("main", "legacy",
	orm.MirrorUnder("legacy"), orm.MirrorTable("tbl_placements")))

It is sugar for a Route; the raw Route (DB, Mirror, Names, MirrorNames, MirrorTable) expresses the same from configuration, and a cutover swaps primary and mirror with their schemas. Keep one of Dual or a Route per table.

func FromEnv

func FromEnv() []OpenOption

FromEnv is the databases DATABASE_URL and DATABASE_URL_<NAME> name.

func Logger

func Logger(l *slog.Logger) OpenOption

Logger is where the supervisors log state changes and slow queries; slog.Default() otherwise.

func OnOpen

func OnOpen(fn func(ctx context.Context, s *Databases) error) OpenOption

OnOpen runs fn once Start opened the databases: migrations, a cache warm-up. Its error fails Start.

func ReadYourWrites

func ReadYourWrites(on bool) OpenOption

ReadYourWrites turns Scope's read-your-writes on (the default) or off.

func Routers

func Routers(rs ...Router) OpenOption

Routers adds routers to the Databases (see Router).

type OptCell added in v1.0.0

type OptCell[V any] struct{ P *vuka.Option[V] }

OptCell is a scan destination for a nullable field of type vuka.Option[V]: None for NULL, else Some of a V read as Cell reads it.

func (*OptCell[V]) Scan added in v1.0.0

func (c *OptCell[V]) Scan(src any) error

type OrderTerm

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

OrderTerm is a term of Order: an Ordering (a field path) or an expression's Asc or Desc.

type Orderable

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

Orderable is a type whose values are ordered: numbers, strings, times.

type OrderedExpr

type OrderedExpr[R Orderable] struct{ TypedExpr[R] }

OrderedExpr is a TypedExpr whose values are ordered.

func CosineDistance

func CosineDistance(field string, v Vector) OrderedExpr[float64]

CosineDistance is the cosine distance from a vector field to v.

func InnerProduct

func InnerProduct(field string, v Vector) OrderedExpr[float64]

InnerProduct is the inner product of a vector field and v, negated as pgvector's <#> is: smaller is nearer, as with the distances.

func L2Distance

func L2Distance(field string, v Vector) OrderedExpr[float64]

L2Distance is the Euclidean distance from a vector field to v.

func Ordered

func Ordered[R Orderable](e Expr) OrderedExpr[R]

Ordered is e as an expression of ordered R values.

orm.Ordered[int](orm.F("age")).Between(18, 30)

func SearchRank

func SearchRank(d Document, q TextQuery) OrderedExpr[float64]

SearchRank is how well the document matches the query, higher better: Postgres's ts_rank, MySQL's relevance, SQLite's FTS5 bm25 negated (a model declaring the document), and elsewhere the weights of the fields each word is found in. The weights of a document's fields are A 1, B 0.4, C 0.2, D and none 0.1, as ts_rank's; bm25 weighs a field the document doesn't hold 0, and, unlike ts_rank, ranks a match in a short text above one in a long.

func Similarity

func Similarity(field, term string) OrderedExpr[float64]

Similarity is the trigram similarity of a field and term, from 0 to 1: pg_trgm's on Postgres, the same computed in Go on SQLite.

func (OrderedExpr[R]) Between

func (t OrderedExpr[R]) Between(lo, hi R) Cond

Between holds where the expression is from lo to hi, both included.

func (OrderedExpr[R]) Ge

func (t OrderedExpr[R]) Ge(v R) Cond

Ge holds where the expression is at least v.

func (OrderedExpr[R]) Gt

func (t OrderedExpr[R]) Gt(v R) Cond

Gt holds where the expression is more than v.

func (OrderedExpr[R]) Le

func (t OrderedExpr[R]) Le(v R) Cond

Le holds where the expression is at most v.

func (OrderedExpr[R]) Lt

func (t OrderedExpr[R]) Lt(v R) Cond

Lt holds where the expression is less than v.

func (OrderedExpr[R]) Over

func (t OrderedExpr[R]) Over(w Window) OrderedExpr[R]

Over is TypedExpr's Over, keeping the expression ordered.

type OrderedFuncOf

type OrderedFuncOf[R Orderable] = Fn[OrderedExpr[R]]

OrderedFuncOf is a function of ordered R values, made by OrderedFunc.

func OrderedFunc

func OrderedFunc[R Orderable](name string, opts ...FuncOption) OrderedFuncOf[R]

OrderedFunc is Func of ordered values: numbers, strings, times.

var Similarity = orm.OrderedFunc[float64]("similarity")
Users.Filter(Similarity(orm.F("name"), "amna").Gt(0.3)).Order(Similarity(orm.F("name"), "amna").Desc())

type OrderedTerm

type OrderedTerm[T any, V cmp.Ordered] struct{ Term[T, V] }

OrderedTerm is a Term of a number (or any cmp.Ordered type).

func OrderedTermOf

func OrderedTermOf[T any, V cmp.Ordered](e any) OrderedTerm[T, V]

func (OrderedTerm[T, V]) Add

func (t OrderedTerm[T, V]) Add(x any) OrderedTerm[T, V]

Add, Sub, Mul and Div are arithmetic with a value, a field or a term.

func (OrderedTerm[T, V]) As

func (t OrderedTerm[T, V]) As(name string) OrderedTerm[T, V]

func (OrderedTerm[T, V]) Div

func (t OrderedTerm[T, V]) Div(x any) OrderedTerm[T, V]

func (OrderedTerm[T, V]) Ge

func (t OrderedTerm[T, V]) Ge(v V) vuka.Pred[T]

func (OrderedTerm[T, V]) GeF

func (t OrderedTerm[T, V]) GeF(o Arg[T, V]) vuka.Pred[T]

func (OrderedTerm[T, V]) Gt

func (t OrderedTerm[T, V]) Gt(v V) vuka.Pred[T]

func (OrderedTerm[T, V]) GtF

func (t OrderedTerm[T, V]) GtF(o Arg[T, V]) vuka.Pred[T]

func (OrderedTerm[T, V]) Le

func (t OrderedTerm[T, V]) Le(v V) vuka.Pred[T]

func (OrderedTerm[T, V]) LeF

func (t OrderedTerm[T, V]) LeF(o Arg[T, V]) vuka.Pred[T]

func (OrderedTerm[T, V]) Lt

func (t OrderedTerm[T, V]) Lt(v V) vuka.Pred[T]

func (OrderedTerm[T, V]) LtF

func (t OrderedTerm[T, V]) LtF(o Arg[T, V]) vuka.Pred[T]

func (OrderedTerm[T, V]) Mul

func (t OrderedTerm[T, V]) Mul(x any) OrderedTerm[T, V]

func (OrderedTerm[T, V]) Range

func (t OrderedTerm[T, V]) Range(lo, hi V) vuka.Pred[T]

Range holds from lo to hi, both included.

func (OrderedTerm[T, V]) Sub

func (t OrderedTerm[T, V]) Sub(x any) OrderedTerm[T, V]

type Ordering

type Ordering struct {
	Path []string
	Desc bool
}

Ordering is an ORDER BY term as plain data, Pred's companion: a field path, descending or not.

func (Ordering) String

func (o Ordering) String() string

String is the term as OrderBy takes it: author__name, or -author__name.

type Outcome

type Outcome[T any] struct {
	Row     T
	Created bool
}

Outcome is GetOrCreate's and UpdateOrCreate's: the row, and whether it was created.

type PK

type PK struct {
	Column               string
	Manual, UUID         bool
	Comment, Label, Help string
	Default              any
}

PK makes the field the primary key. An integer key is counted by the database unless Manual says the program sets it; UUID makes a string key a random UUID the ORM sets on create when it is empty (uuid on Postgres, CHAR(36) elsewhere). A key is never NULL, and is unique and indexed already.

func (PK) Apply added in v0.5.0

func (x PK) Apply(o *FieldOptions)

type PageOption

type PageOption func(*pageConfig)

PageOption shapes Paginate: the fields a client may sort and search by, the default order, the largest page.

func DefaultSize

func DefaultSize(n int) PageOption

func DefaultSort

func DefaultSort(fields ...string) PageOption

DefaultSort is the order when the client asks for none (or none it may have).

func DefaultSortBy

func DefaultSortBy[T any](sorts ...vuka.Order[T]) PageOption

func MaxSize

func MaxSize(n int) PageOption

MaxSize caps the page size (100 by default); DefaultSize is the size asked for none (20).

func Searchable

func Searchable(fields ...string) PageOption

Searchable is the fields Search matches, case-insensitively, any of them.

func SearchableBy

func SearchableBy[T any](fields ...Path[T]) PageOption

func Sortable

func Sortable(fields ...string) PageOption

Sortable is the fields a client may sort by; others are ignored. Paths through foreign keys (author__name) are fields too.

func SortableBy

func SortableBy[T any](fields ...Path[T]) PageOption

SortableBy, SearchableBy and DefaultSortBy are Sortable, Searchable and DefaultSort by field reference.

type PageRequest

type PageRequest struct {
	Page, Size   int
	Sort, Search string
}

PageRequest is a page of a list as a client asks for it: Page from 1, Size, Sort (fields, a leading - for descending, comma-separated) and Search.

func PageFrom

func PageFrom(v url.Values) PageRequest

PageFrom reads a PageRequest from query parameters page, size, sort and q.

type PageResult

type PageResult[T any] struct {
	Items []T
	Total int64
	Page  int
	Size  int
	Pages int
}

PageResult is a page of rows and where it sits in the list.

func Paginate

func Paginate[T any](ctx context.Context, qs QuerySet[T], r PageRequest, opts ...PageOption) (PageResult[T], error)

Paginate is one page of qs as r asks for it, sorted and searched only by the fields opts allow — what a REST list or a view's DataTable shows.

page, err := orm.Paginate(ctx, Users.Filter(orm.Q{"active": true}), orm.PageFrom(c.Request.URL.Query()),
	orm.Sortable("name", "created_at"), orm.Searchable("name", "email"), orm.DefaultSort("-created_at"))

type Path

type Path[T any] interface{ Asc() vuka.Order[T] }

Path is a field reference of T, whatever its type: Post.Author, Post.Tags.

type PlanResult

type PlanResult struct {
	File   string   `json:"file,omitempty"`
	Source []byte   `json:"source,omitempty"`
	Ops    []string `json:"ops"`
	Notes  []string `json:"notes,omitempty"`
}

PlanResult is the migration plan wrote: its file's name and source, what each operation does, and the notes beside them. No operations is no changes.

type Pool

type Pool struct {
	MaxOpen int // connections open at most (20; negative: no limit)
	MaxIdle int // idle connections kept (5; negative: none)
	// MinIdle is the idle connections opened at start and kept open
	// while the database is up.
	MinIdle int
	// MaxLifetime retires a connection after it (1h; negative: never),
	// less up to a fifth at random so they don't all reconnect at once.
	MaxLifetime time.Duration
	MaxIdleTime time.Duration // closes a connection idle this long (negative or 0: never)
	// Acquire bounds the wait for a connection when MaxOpen are in use
	// (database/sql waits as long as the context lets it): past it the
	// statement fails Unavailable. 0 waits.
	Acquire time.Duration
	// Health is how often the supervisor pings the database and its
	// replicas (10s).
	Health  time.Duration
	Breaker Breaker
	// StartupWait is how long Start waits for the database to answer
	// (5s); past it the program starts with it Down and the supervisor
	// keeps reconnecting. Negative doesn't wait.
	StartupWait time.Duration
}

Pool tunes a declared database's connections and the supervisor keeping them. A zero field takes its default; a negative MaxOpen, MaxIdle, MaxLifetime or MaxIdleTime is no limit (no idle connections kept, for MaxIdle). Give it as a Declare option, or as URL parameters (pool_max, pool_max_idle, pool_min_idle, pool_max_lifetime, pool_max_idle_time, pool_acquire, pool_health, breaker_failures, breaker_cooldown, startup_wait), which are taken off the URL before the driver sees it.

func SplitPoolParams

func SplitPoolParams(url string) (string, Pool, error)

SplitPoolParams is url without the Pool parameters it carries, and the Pool they say. Its parameters are those after the last slash's first ?, as a MySQL DSN has them.

type Pred

type Pred struct {
	Op PredOp
	// Path is the field, after the relations leading to it:
	// {"author", "name"} is author__name. Each part is a Go field name or
	// a column, as a Q's key names them.
	Path  []string
	Value any
	Args  []Pred // OpAnd / OpOr: the operands; OpNot: the one it negates
}

Pred is a condition as plain data: a field path, an operator and a value, or And / Or / Not of predicates. It is the adapter point for a layer above the ORM — a language runtime's typed predicates, a filter decoded from a request — that builds conditions without knowing the ORM's lookups: it hands over a Pred (or anything with a Pred method, through PredCond), and the ORM writes it as it writes a Q.

orm.Pred{Op: orm.OpAnd, Args: []orm.Pred{
	{Path: []string{"author", "name"}, Op: orm.OpEq, Value: "Ali"},
	{Op: orm.OpNot, Args: []orm.Pred{{Path: []string{"draft"}, Op: orm.OpEq, Value: true}}},
}}

is orm.And(orm.Q{"author__name": "Ali"}, orm.Not(orm.Q{"draft": true})).

func (Pred) Cond

func (p Pred) Cond() (Cond, error)

Cond is p written with the ORM's own conditions: what Filter does with it.

type PredOp

type PredOp string

PredOp is a Pred's operator: a comparison of the field with the value, or a combination of predicates.

const (
	OpEq          PredOp = "eq" // a nil value: IS NULL
	OpNe          PredOp = "ne" // a nil value: IS NOT NULL
	OpLt          PredOp = "lt"
	OpLte         PredOp = "lte"
	OpGt          PredOp = "gt"
	OpGte         PredOp = "gte"
	OpIn          PredOp = "in"    // the value is a slice
	OpRange       PredOp = "range" // the value is a two-element slice or array
	OpIsNull      PredOp = "isnull"
	OpContains    PredOp = "contains"
	OpIContains   PredOp = "icontains"
	OpIExact      PredOp = "iexact"
	OpStartsWith  PredOp = "startswith"
	OpIStartsWith PredOp = "istartswith"
	OpEndsWith    PredOp = "endswith"
	OpIEndsWith   PredOp = "iendswith"
	OpSearch      PredOp = "search"
	OpAnd         PredOp = "and"
	OpOr          PredOp = "or"
	OpNot         PredOp = "not"
)

type Predicate

type Predicate interface{ Pred() Pred }

Predicate is a condition a layer above the ORM represents its own way and converts to a Pred.

type PrefetchSpec

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

PrefetchSpec is a relation PrefetchRelated loads; see Prefetch.

func Prefetch

func Prefetch(path string, qs prefetcher) PrefetchSpec

Prefetch loads a relation's rows with a query of your own (filtered, ordered, selecting or prefetching further), as Django's Prefetch does:

Users.PrefetchRelated(orm.Prefetch("posts", Posts.Filter(orm.Q{"draft": false}).OrderBy("-id")))

PrefetchRelated("posts") is Prefetch("posts", nil): every related row.

func PrefetchBy

func PrefetchBy[T, R any](rel Path[T], rows Query[R]) PrefetchSpec

PrefetchBy is a relation's rows read by a query: a filtered or ordered prefetch.

Post.Objects.PrefetchRelated(orm.PrefetchBy(Post.Tags, Tag.Objects.Filter(Tag.Name.StartsWith("g"))))

type PtrCell

type PtrCell[V any] struct{ P **V }

PtrCell is a scan destination for a nullable field of type *V: nil for NULL, else a new V read as Cell reads it.

func (*PtrCell[V]) Scan

func (c *PtrCell[V]) Scan(src any) error

type Q

type Q map[string]any

Q is conditions joined by AND, keyed Django's way: a field, then optionally __ and a lookup.

orm.Q{"age__gte": 18, "name__icontains": "a", "email": e}

Lookups: exact (the default), iexact, contains, icontains, startswith, istartswith, endswith, iendswith, in, gt, gte, lt, lte, range, isnull, search (full text, in web search syntax; see SearchQuery) and trigram_similar. A nil value with exact means IS NULL.

type Query deprecated

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

Query is a model's QuerySet as Vuka code reads it: conditions are predicates of field references (Post.Title.Contains("go"), typed by the model, so a User's are refused at compile time), orderings their Asc and Desc, relations and fields named by reference, and the terminals return vuka.Result — First a Result of an Option. Post.Objects is the Query of every Post; each method returns a new Query, and nothing runs until a terminal.

posts := Post.Objects.Filter(Post.Title.Contains("go"), Post.Author.Name.Eq("ada")).
	OrderBy(Post.Views.Desc()).All(ctx)?
match Post.Objects.Get(ctx, Post.Slug.Eq(slug)) {
case Ok(p):              …
case Err(orm.NotFound):  …
case Err(e):             …
}

QuerySet and With reach the engine's string forms.

Deprecated: Vuka now writes Post.Objects as the model's Manager and Post.Title as its typed field, PostFields.Title, so a query is the engine's QuerySet. Query goes in the next minor release.

func QueryOf

func QueryOf[T any](qs QuerySet[T]) Query[T]

QueryOf is qs as a Query.

func (Query[T]) Aggregate

func (q Query[T]) Aggregate[R any](ctx context.Context, cols ...Col[T, R]) vuka.Result[R]

Aggregate is Values of aggregates over every row: one R.

totals := Post.Objects.Aggregate[Totals](ctx, orm.Into(Totals.Views, fn.Sum(Post.Views)))?

func (Query[T]) All

func (q Query[T]) All(ctx context.Context) vuka.Result[[]T]

All runs the query: its rows, relations loaded.

func (Query[T]) Annotate

func (q Query[T]) Annotate(as ...Annotation[T]) Query[T]

Annotate adds named terms (t.As("name")) as computed columns.

func (Query[T]) AnnotateAs

func (q Query[T]) AnnotateAs(as ...Alias) Query[T]

AnnotateAs adds typed expressions named with their As (orm.Func's results).

func (Query[T]) BulkCreate

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

func (Query[T]) Count

func (q Query[T]) Count(ctx context.Context) vuka.Result[int64]

Count, Exists, Delete and Update are the QuerySet's.

func (Query[T]) Create

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

Create inserts row (its key set after); BulkCreate inserts rows in batches.

func (Query[T]) Delete

func (q Query[T]) Delete(ctx context.Context) vuka.Result[int64]

func (Query[T]) Distinct

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

func (Query[T]) Exclude

func (q Query[T]) Exclude(ps ...vuka.Pred[T]) Query[T]

Exclude drops the rows all the predicates hold for.

func (Query[T]) Exists

func (q Query[T]) Exists(ctx context.Context) vuka.Result[bool]

func (Query[T]) Filter

func (q Query[T]) Filter(ps ...vuka.Pred[T]) Query[T]

Filter keeps the rows every predicate holds for.

func (Query[T]) First

func (q Query[T]) First(ctx context.Context) vuka.Result[vuka.Option[T]]

First is the first row, None when there is none.

func (Query[T]) Get

func (q Query[T]) Get(ctx context.Context, ps ...vuka.Pred[T]) vuka.Result[T]

Get is the one row the predicates hold for. It fails with the Kind itself — orm.NotFound, orm.MultipleFound — so match compares it: case Err(orm.NotFound).

func (Query[T]) GetOrCreate

func (q Query[T]) GetOrCreate(ctx context.Context, row T, match ...Path[T]) vuka.Result[Outcome[T]]

GetOrCreate is the row whose match fields equal row's, else row created.

tag := Tag.Objects.GetOrCreate(ctx, Tag{Name: "go"}, Tag.Name)?.Row

func (Query[T]) GroupBy

func (q Query[T]) GroupBy(groups ...any) Query[T]

GroupBy groups by fields (references) and annotations (named terms).

func (Query[T]) Having

func (q Query[T]) Having(ps ...vuka.Pred[T]) Query[T]

Having filters the groups by predicates on aggregates.

func (Query[T]) Hybrid

func (q Query[T]) Hybrid(doc SearchDoc[T], text string, field Path[T], v Vector, opts ...SearchOption) Query[T]

Hybrid ranks by a text match of doc and the distance of field to v, fused by reciprocal rank, as QuerySet.Hybrid.

Post.Objects.Hybrid(orm.Search(Post.Title, Post.Body), "go web", Post.Embedding, v, orm.K(10))

func (Query[T]) Iter

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

Iter yields the rows one at a time.

func (Query[T]) Limit

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

Limit, Offset and Distinct are the QuerySet's.

func (Query[T]) Manager

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

Manager is the model's manager (OnChange, BulkCreate, Raw, …).

func (Query[T]) Nearest

func (q Query[T]) Nearest(field Path[T], v Vector, opts ...SearchOption) Query[T]

Nearest orders by the distance of a vector field to v: K(n), MaxDistance(d), DistanceAs(alias), a Metric, EfSearch(n) and Probes(n) as for QuerySet.Nearest.

func (Query[T]) Offset

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

func (Query[T]) On

func (q Query[T]) On(s DBSchema) Query[T]

On is the model's rows on schema s (a tenant's schema, another database): call it first, the query starts over.

func (Query[T]) OnChange

func (q Query[T]) OnChange(fn func(ctx context.Context, c Change[T])) (stop func())

OnChange calls fn after each write of the model's rows.

func (Query[T]) Order

func (q Query[T]) Order(terms ...OrderTerm) Query[T]

Order orders by the engine's order terms: a typed expression's Asc or Desc (orm.Func's results), orm.Ordering.

func (Query[T]) OrderBy

func (q Query[T]) OrderBy(sorts ...Sort[T]) Query[T]

OrderBy orders by field references' and terms' Asc and Desc.

func (Query[T]) Paginate

func (q Query[T]) Paginate(ctx context.Context, r PageRequest, opts ...PageOption) vuka.Result[PageResult[T]]

Paginate is a page of the rows: PageRequest's page, size, sort and search over the fields SortableBy and SearchableBy allow.

func (Query[T]) PrefetchRelated

func (q Query[T]) PrefetchRelated(specs ...any) Query[T]

PrefetchRelated reads relations' rows in a query each: references, or Prefetch for a filtered one.

func (Query[T]) QuerySet

func (q Query[T]) QuerySet() QuerySet[T]

QuerySet is the query as the engine's QuerySet.

func (Query[T]) Raw

func (q Query[T]) Raw(ctx context.Context, sql string, args ...any) vuka.Result[[]T]

Raw is the rows of a SQL query of the model's table.

func (Query[T]) SelectRelated

func (q Query[T]) SelectRelated(rels ...Path[T]) Query[T]

SelectRelated reads foreign keys' rows in the same query (a join).

func (Query[T]) Unfiltered

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

Unfiltered marks the query as meant for every row (Delete, Update).

func (Query[T]) Update

func (q Query[T]) Update(ctx context.Context, sets ...Assignment[T]) vuka.Result[int64]

Update sets fields of every row: Assign(Post.Views, 0), AssignF(Post.Views, fn.Num(Post.Views).Add(1)).

func (Query[T]) UpdateOrCreate

func (q Query[T]) UpdateOrCreate(ctx context.Context, row T, match ...Path[T]) vuka.Result[Outcome[T]]

UpdateOrCreate updates the row whose match fields equal row's with row's other fields, else creates row.

func (Query[T]) Values

func (q Query[T]) Values[R any](ctx context.Context, cols ...Col[T, R]) vuka.Result[[]R]

Values reads the columns into Rs, Django's values: a source that is a field of the same name is read as is, others are annotated; with an aggregate among them the rows are grouped by the rest.

func (Query[T]) Where

func (q Query[T]) Where(conds ...Cond) Query[T]

Where filters by the ORM's own conditions (Q, Or, Exists, Match).

func (Query[T]) With

func (q Query[T]) With(f func(QuerySet[T]) QuerySet[T]) Query[T]

With applies f to the QuerySet: the way to a string form of the engine.

Post.Objects.With(func(qs orm.QuerySet[Post]) orm.QuerySet[Post] { return qs.OrderBy("-views") })

type QueryInfo

type QueryInfo struct {
	SQL      string
	Args     []any
	Start    time.Time
	Duration time.Duration
	// Rows is the rows an exec affected; -1 for a query, whose rows are
	// read after it returns, and when the driver doesn't say.
	Rows int64
	Err  error
	// Refused is a query the ORM refused before sending it (an argument
	// it can't write for the database, a field the schema lacks): SQL is
	// a comment naming the model, Err why. It never reached the database.
	Refused bool
	Model   string // a refused query's model
	// DB is the name of the database the statement ran on ("" for one
	// WithDB gave without a name), and Replica whether a replica of it.
	DB      string
	Replica bool
}

QueryInfo is a statement the ORM ran, or refused to, as an Observer sees it.

type QuerySet

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

QuerySet is a lazy, immutable query of a model: each method returns a new QuerySet, and nothing runs until a terminal (All, First, Get, Count, Exists, Iter, Delete, Update, Aggregate). Keep one in a package variable and share it freely.

func Related[R any](inst any, relation string) QuerySet[R]

Related is the rows of R related to inst (a model row, or a pointer to one) by its relation named: a foreign key's row, the rows holding its key, or those linked to it many-to-many; queried on inst's schema.

posts, err := orm.Related[Post](&u, "posts").Filter(orm.Q{"draft": false}).All(ctx)

func (QuerySet[T]) Aggregate

func (qs QuerySet[T]) Aggregate(ctx context.Context, aggs ...Agg) (_ Result, err error)

Aggregate computes aggregates over the matching rows.

func (QuerySet[T]) All

func (qs QuerySet[T]) All(ctx context.Context) ([]T, error)

All runs the query and returns its rows, relations loaded.

func (QuerySet[T]) Annotate

func (qs QuerySet[T]) Annotate(name string, e Expr) QuerySet[T]

Annotate adds a computed column, named: Filter, OrderBy and Values use it by name, and a field of the model tagged orm:"computed" with the same name receives it.

Users.Annotate("joined", orm.Year.Of(orm.F("created_at"))).Filter(orm.Q{"joined__gte": 2025})

func (QuerySet[T]) AnnotateAs

func (qs QuerySet[T]) AnnotateAs(as ...Alias) QuerySet[T]

AnnotateAs is Annotate of expressions named by their As.

Users.AnnotateAs(orm.Length(orm.F("name")).As("name_len")).Filter(orm.Q{"name_len__gt": 3})

func (QuerySet[T]) Count

func (qs QuerySet[T]) Count(ctx context.Context) (_ int64, err error)

Count is how many rows match.

func (QuerySet[T]) Delete

func (qs QuerySet[T]) Delete(ctx context.Context) (_ int64, err error)

Delete removes the matching rows and says how many. With no condition it fails with ErrUnfiltered unless the query is Unfiltered.

func (QuerySet[T]) Distinct

func (qs QuerySet[T]) Distinct() QuerySet[T]

Distinct drops duplicate rows.

func (QuerySet[T]) Exclude

func (qs QuerySet[T]) Exclude(conds ...Cond) QuerySet[T]

Exclude drops the rows matching all the conditions.

func (QuerySet[T]) Exists

func (qs QuerySet[T]) Exists(ctx context.Context) (_ bool, err error)

Exists is whether any row matches.

func (QuerySet[T]) Filter

func (qs QuerySet[T]) Filter(conds ...Cond) QuerySet[T]

Filter keeps the rows matching every condition. Keys may follow relations: author__name (a join), posts__title__icontains (a related row exists).

func (QuerySet[T]) First

func (qs QuerySet[T]) First(ctx context.Context) (T, error)

First is the first row, by the order given (or Meta's) or else by primary key; ErrNotFound when there is none.

func (QuerySet[T]) Get

func (qs QuerySet[T]) Get(ctx context.Context, conds ...Cond) (T, error)

Get is the one row matching conds: ErrNotFound when there is none, ErrMultipleFound when there are several.

func (QuerySet[T]) GroupBy

func (qs QuerySet[T]) GroupBy(names ...string) QuerySet[T]

GroupBy groups the rows by names (fields, paths through relations, annotations): the GROUP BY of a Values listing aggregates, over the implicit rule (every other name Values lists). Grouping by a primary key lets Values read the columns depending on it without grouping by them, as Postgres and MySQL allow:

Letters.Annotate("n", orm.Count("placements__id")).GroupBy("id").
	Values[[]any]("id", "reference_number", "status", "n")

func (QuerySet[T]) Having

func (qs QuerySet[T]) Having(conds ...Cond) QuerySet[T]

Having keeps the groups matching every condition: HAVING, run after grouping, where the aggregates exist. A grouped query only (Values listing an aggregate, or GroupBy); Filter on an aggregate annotation routes to HAVING too, as Django's does.

Letters.Annotate("n", orm.Count("placements__id")).Having(orm.Q{"n__gt": 0}).Values[…]

func (QuerySet[T]) Hybrid

func (qs QuerySet[T]) Hybrid(doc Document, text TextQuery, field string, v Vector, opts ...SearchOption) QuerySet[T]

Hybrid is hybrid search: the rows ranked twice, by the document's match of the text query (SearchRank, annotated "rank") and by the distance of a vector field from v (as Nearest, "distance"), the ranks fused by reciprocal rank (Fuse, "score": a row scores w/(k + r) for each ranking, from the text's only where the text matches) and ordered by score, best first. Every row is ranked by distance unless Candidates or MaxDistance narrow them; K keeps the best k. It runs where both searches do: Postgres, and SQLite (FTS5 and distances in Go).

Docs.Hybrid(orm.SearchVector("title", "body"), orm.SearchQuery("web server"), "embedding", v,
	orm.K(10), orm.Candidates(100), orm.HybridWeights(1, 0.5)).All(ctx)

func (QuerySet[T]) Iter

func (qs QuerySet[T]) Iter(ctx context.Context) iter.Seq2[T, error]

Iter runs the query and yields its rows one at a time, without holding them all; relations PrefetchRelated names are not loaded.

func (QuerySet[T]) Limit

func (qs QuerySet[T]) Limit(n int) QuerySet[T]

Limit keeps at most n rows.

func (QuerySet[T]) Nearest

func (qs QuerySet[T]) Nearest(field string, v Vector, opts ...SearchOption) QuerySet[T]

Nearest orders the query by the distance of a vector field from v, nearest first, annotated as "distance" (DistanceAs): K keeps the k nearest, MaxDistance those near enough. The metric is the field's vector index's (Ops), L2 without one, or the option's. On Postgres the ORDER BY is pgvector's operator, so an HNSW or IVFFlat index of that metric serves it (EfSearch, Probes tune it); on SQLite the distance is computed in Go for every row; MySQL has no vector search.

Docs.Nearest("embedding", q, orm.K(10), orm.MaxDistance(0.4)).All(ctx)
Docs.Nearest("embedding", q, orm.Cosine, orm.DistanceAs("d")).Values[Hit]("id", "title", "d")

func (QuerySet[T]) Offset

func (qs QuerySet[T]) Offset(n int) QuerySet[T]

Offset skips the first n rows.

func (QuerySet[T]) Order

func (qs QuerySet[T]) Order(terms ...OrderTerm) QuerySet[T]

Order sorts by terms, as OrderBy: an expression's Asc or Desc, or an Ordering (a field path) — mixed as needed.

Users.Order(Levenshtein(orm.F("name"), "jon").Asc(), orm.Ordering{Path: []string{"age"}, Desc: true})

func (QuerySet[T]) OrderBy

func (qs QuerySet[T]) OrderBy(fields ...string) QuerySet[T]

OrderBy sorts by fields, a leading - for descending: OrderBy("-age", "author__name"). A query that sets no order reads rows in the model's Meta Ordering; OrderBy() sets none, that one included.

func (QuerySet[T]) PrefetchRelated

func (qs QuerySet[T]) PrefetchRelated(specs ...any) QuerySet[T]

PrefetchRelated loads relations of any kind after the rows, one query each (per thousand keys): names, paths (posts__comments) or Prefetch with a query of your own. Only All, First and Get load them; Iter streams rows without.

func (QuerySet[T]) SelectRelated

func (qs QuerySet[T]) SelectRelated(paths ...string) QuerySet[T]

SelectRelated reads foreign keys in the same query, one LEFT JOIN each: SelectRelated("author", "author__profile"). A missing related row leaves the field nil (or zero).

func (QuerySet[T]) Unfiltered

func (qs QuerySet[T]) Unfiltered() QuerySet[T]

Unfiltered lets Update and Delete reach every row the query has: without it, an Update or Delete whose conditions are empty (no Filter, or one of nothing, such as an empty Q) fails with ErrUnfiltered and runs nothing.

Sessions.Unfiltered().Delete(ctx) // every row, on purpose

func (QuerySet[T]) Update

func (qs QuerySet[T]) Update(ctx context.Context, values Set) (_ int64, err error)

Update writes values to the matching rows and says how many; fields marked auto_now are set to now too. With no condition it fails with ErrUnfiltered unless the query is Unfiltered.

func (QuerySet[T]) Values

func (qs QuerySet[T]) Values[R any](fields ...string) Values[T, R]

Values is the query reading some fields into R, Django's values and values_list. A name is a field, an annotation, or a path through relations (team__name), joined in the same query; through rows held by many, a row per related row. R is a scalar for one name (Values[string]("email")); a struct, its fields matched by orm path tag (orm:"team__name"), name or column, and with no names its own fields say what is read; map[string]any keyed by the names as written; or []any in their order. An aggregate annotation among the names groups the rows by the others:

Users.Annotate("n", orm.Count("id")).Values[map[string]any]("team__name", "n")

type RawOption

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

RawOption tunes Raw and Exec, given among their arguments: Loose (and On, Timeout, a connection handle).

func Loose

func Loose() RawOption

Loose has Raw drop the columns its struct has no field for.

type RawQuerySet

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

RawQuerySet is a model's rows read by SQL of your own, Django's RawQuerySet: see Manager.Raw.

func (RawQuerySet[T]) All

func (r RawQuerySet[T]) All(ctx context.Context) ([]T, error)

All runs the SQL and returns its rows, relations loaded.

func (RawQuerySet[T]) First

func (r RawQuerySet[T]) First(ctx context.Context) (T, error)

First is the first row the SQL reads, relations loaded; ErrNotFound when there is none.

func (RawQuerySet[T]) Iter

func (r RawQuerySet[T]) Iter(ctx context.Context) iter.Seq2[T, error]

Iter runs the SQL and yields its rows one at a time; relations PrefetchRelated names are not loaded.

func (RawQuerySet[T]) PrefetchRelated

func (r RawQuerySet[T]) PrefetchRelated(specs ...any) RawQuerySet[T]

PrefetchRelated loads relations of the rows after them, as a QuerySet's does.

type Rel

type Rel struct {
	OnDelete string
	Related  string
	Column   string
	Through  string
	Null     bool
	// CrossDB: the related model is on another database (see
	// ModelInfo): no foreign key constraint, and queries prefetch
	// rather than join it.
	CrossDB bool
}

Rel describes a relation field (orm.FK, orm.M2M): what deleting the related row does (Cascade, SetNull, Restrict), the inverse's name on the related model, the foreign key's column, a many-to-many's table between, and whether the key may be NULL.

func (Rel) Apply added in v0.5.0

func (x Rel) Apply(o *FieldOptions)

type ReplicaStats

type ReplicaStats struct {
	URL     string      `json:"url"`
	Healthy bool        `json:"healthy"`
	Pool    sql.DBStats `json:"pool"`
}

ReplicaStats is a replica's health and pool.

type Result

type Result map[string]any

Result is aggregates by Key: COUNT an int64, AVG a float64, the others what the database gives (nil over no rows).

func (Result) Float

func (r Result) Float(key string) float64

Float is an aggregate as a float64.

func (Result) Int

func (r Result) Int(key string) int64

Int is an aggregate as an int64.

type Retry

type Retry int

Retry runs a transaction again, up to n more times, when the database aborts it for a deadlock or a serialization failure (Postgres 40001 and 40P01, MySQL 1213 and 1205, SQLite's busy), after a short backoff. Only the outermost transaction retries: fn must be safe to run again.

type Route

type Route struct {
	DB     string `toml:"db" json:"db"`
	Mirror string `toml:"mirror" json:"mirror"`
	// Names is the names set the primary (DB) binds through, and MirrorNames
	// the one the mirror binds through (orm.MirrorUnder); MirrorTable the
	// mirror's table, over the model's (orm.MirrorTable). They let a dual
	// write to a legacy schema — a renamed, reformatted table — be
	// expressed from configuration, and a cutover swap primary and mirror
	// with their schemas.
	Names       string `toml:"names" json:"names"`
	MirrorNames string `toml:"mirror_names" json:"mirror_names"`
	MirrorTable string `toml:"mirror_table" json:"mirror_table"`
}

Route is a table's databases, for models that name none themselves (Using, the routers and a model's Meta or On win over it): routing a table moves it without a code change.

type Router

type Router interface {
	ReadDB(ctx context.Context, m ModelInfo) string
	WriteDB(ctx context.Context, m ModelInfo) string
	AllowRelation(a, b ModelInfo) bool
	AllowMigrate(db string, m ModelInfo) bool
}

Router picks the databases of models' queries at run time, Django's database routers: a tenant's database from the context, an analytics model on its own server. Register routers on a Databases (Routers, or AddRouter); the first giving an opinion wins.

ReadDB and WriteDB name the database a model's reads and writes go to, "" for no opinion. AllowRelation says whether a relation between two models may be declared, AllowMigrate whether a model's table is migrated on a database: being bools, they can't abstain, so every router must allow (embed BaseRouter to allow by default).

A router's choice yields to orm.Using; it wins over a model's Meta and a table's Route. Migrations run before main: a router that should move a model's migrations is registered on Default at init.

type RowScanner

type RowScanner[T any] interface {
	// Dest is the scan destinations, one per column; the same slice for
	// every row.
	Dest() []any
	// Bind points the destinations at row's fields.
	Bind(row *T)
}

RowScanner reads a model's rows without reflection: generated per model by orm generate (or ormgen, from go:generate), never written by hand.

type RowWriter

type RowWriter[T any] interface {
	// Values appends row's column values to dst[:0].
	Values(row *T, dst []any) []any
}

RowWriter is a generated scanner's other half: a row's column values for an INSERT, in the scanner's column order, without reflection. ormgen writes it beside the scanner of a model with no pointer embeds.

type Schema

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

Schema is what a model declares beyond its Go types: attributes of the model and of its fields, as values. A model declares them in a Schema method, which the ORM calls once, on a zero value, the first time it maps the model:

type Post struct {
	orm.Model[Post]
	ID        int64
	Title     string
	Slug      string
	Author    orm.FK[User]
	Published time.Time
}

func (p *Post) Schema(s *orm.Schema) {
	s.Model(orm.Meta{Table: "posts", Ordering: []string{"-published"}})
	s.Field(&p.Title, orm.Char{Max: 200})
	s.Field(&p.Slug, orm.Slug{Unique: true})
	s.Field(&p.Author, orm.Rel{OnDelete: orm.Cascade, Related: "posts"})
	s.Field(&p.Published, orm.Datetime{DefaultNow: true})
}

A field is named by its address, so a rename is checked by the compiler; a field with nothing to say isn't listed, its column, NULL-ness and type coming from its name and Go type. Fields of embedded structs are named the same way (&p.Timestamps.Created, or the promoted &p.Created).

The attributes are the ones the ORM declares (PK, Char, Int, Datetime, Rel, Column, Unique, …) and any value of another package implementing FieldAttr or ModelAttr: the ORM treats them alike. Attributes apply in order, a later one's set members over an earlier one's. A Vuka model's field attributes (Title string @orm.Char{Max: 200}) are the same values, and come before its Schema's.

func SchemaOf added in v1.0.0

func SchemaOf(t reflect.Type) (*Schema, error)

SchemaOf is what the struct t declares: its Vuka attributes, then its Schema method's. A model declaring nothing has an empty Schema; one whose Schema method is wrong has a *SchemaError.

func (*Schema) Field added in v1.0.0

func (s *Schema) Field(ptr any, attrs ...FieldAttr)

Field adds attributes to the field ptr points to, a field of the value Schema is called on.

func (*Schema) FieldAttrs added in v1.0.0

func (s *Schema) FieldAttrs(name string) []any

FieldAttrs is the attributes of the model's field name (its Go name, as the model's own or promoted from a struct it embeds), in order.

func (*Schema) Model added in v1.0.0

func (s *Schema) Model(attrs ...ModelAttr)

Model adds model attributes: orm.Meta, and any ModelAttr.

func (*Schema) ModelAttrs added in v1.0.0

func (s *Schema) ModelAttrs() []any

ModelAttrs is the model's attributes, in order.

type SchemaCodec added in v0.6.0

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

SchemaCodec converts a single field's value to and from its database form under a names set, at the field level. orm.Codec is the only implementation: build one with orm.Codec[G, DB]{To, From} and attach it to a field for a names set (SchemaField.Codec, from a FieldAttr's Apply or SetFieldOptions). The methods are unexported so the ORM owns the conversion; a type that converts itself per schema uses SchemaValuer.

type SchemaError added in v1.0.0

type SchemaError struct {
	Code  string
	Model string
	Msg   string
}

SchemaError is a mistake in a model's Schema method, reported by every query of the model and by Check. Code is E0501 for an argument of s.Field that isn't the address of one of the model's fields, E0502 for a nil attribute, E0503 for a Schema method that panicked.

func (*SchemaError) Error added in v1.0.0

func (e *SchemaError) Error() string

type SchemaField added in v0.6.0

type SchemaField struct {
	Column string
	Type   string
	Skip   bool
	Codec  SchemaCodec
}

SchemaField is a field's column under one names set: its name and SQL type there (as a legacy:"column:…;type:…" tag gives them), and a Codec adapting its value's format for that set. A zero member leaves the default; Skip drops the column there (legacy:"-"). The SQL type is the column's documented type and what migrations would make; a legacy (Unmanaged) table is never migrated, so there it only documents the write format.

type SchemaScanner

type SchemaScanner interface {
	ScanFor(names string, src any) error
}

SchemaScanner is SchemaValuer's reading side: ScanFor is given the names set the value was read under ("" the default), so a type never guesses which form it got. The ORM calls it for model rows, Values (into the type, a map or a list) and Raw on a schema; a type without it reads through its Scan.

func (s *LetterStatus) ScanFor(names string, src any) error {
	if names == "legacy" {
		*s = LetterStatus(EmployerLetterStatusFromCode[int(src.(int64))])
		return nil
	}
	*s = LetterStatus(fmt.Sprint(src))
	return nil
}

type SchemaValuer

type SchemaValuer interface {
	ValueFor(names string) (driver.Value, error)
}

SchemaValuer is a column's Go type whose database value depends on the schema it is written under: a status kept as a code on a legacy table and as its name on the new one. The ORM calls ValueFor with the names set of the query ("" the default) for every value it sends of the type — conditions, Update and Save, Create — so one query serves both. Reading is the type's own Scan, which takes either form.

func (s LetterStatus) ValueFor(names string) (driver.Value, error) {
	if names == "legacy" {
		return int64(codeOf[s]), nil
	}
	return string(s), nil
}

A value of another type sent to such a column (an int where the field is a LetterStatus) is refused: it would be right on one schema only.

type SearchDoc

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

SearchDoc is fields of T searched as one document: Matches is the condition, Rank how well a row matches.

doc := orm.Search(Post.Title, Post.Body)
Post.Objects.Filter(doc.Matches("go web")).OrderBy(doc.Rank("go web").Desc())
func Search[T any](fields ...Path[T]) SearchDoc[T]

Search is the document of fields (a SearchOf field's own fields, or any text fields).

func (SearchDoc[T]) Add

func (s SearchDoc[T]) Add(o SearchDoc[T]) SearchDoc[T]

func (SearchDoc[T]) Config

func (s SearchDoc[T]) Config(c string) SearchDoc[T]

func (SearchDoc[T]) Document

func (s SearchDoc[T]) Document() Document

func (SearchDoc[T]) Matches

func (s SearchDoc[T]) Matches(q string) vuka.Pred[T]

Matches holds for the rows whose document matches q, a web search ("go web", "go -rust", `"exact phrase"`); MatchesQuery for a TextQuery.

func (SearchDoc[T]) MatchesQuery

func (s SearchDoc[T]) MatchesQuery(q TextQuery) vuka.Pred[T]

func (SearchDoc[T]) Rank

func (s SearchDoc[T]) Rank(q string) OrderedTerm[T, float64]

Rank is how well the document matches q: order by it descending.

func (SearchDoc[T]) RankQuery

func (s SearchDoc[T]) RankQuery(q TextQuery) OrderedTerm[T, float64]

func (SearchDoc[T]) Weight

func (s SearchDoc[T]) Weight(w string) SearchDoc[T]

Weight, Config and Add are the Document's.

type SearchOf

type SearchOf struct {
	Fields []string
	Config string
}

SearchOf makes an orm.TSVector field a generated column holding the fields' document: each "field" or "field:weight" (A–D).

func (SearchOf) Apply added in v1.0.0

func (SearchOf) Apply(*FieldOptions)

type SearchOption

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

SearchOption shapes Nearest and Hybrid: a Metric, K, MaxDistance, DistanceAs, EfSearch, Probes; and Hybrid's HybridWeights, RRF, Candidates, RankAs and ScoreAs.

func Candidates

func Candidates(n int) SearchOption

Candidates is the n nearest rows Hybrid ranks by distance (all of them otherwise), beside those the text matches: a vector index finds them, where ranking every row by distance reads the table.

func DistanceAs

func DistanceAs(alias string) SearchOption

DistanceAs names the distance's annotation: "distance" otherwise.

func EfSearch

func EfSearch(n int) SearchOption

EfSearch is pgvector's hnsw.ef_search for the query, the candidates an HNSW scan keeps (40 by default: more finds more of the true nearest, slower); Probes is ivfflat.probes, the lists an IVFFlat scan reads (1 by default). Set with SET LOCAL in a transaction of the query's own (a savepoint inside the context's), on Postgres only.

func HybridWeights

func HybridWeights(text, vector float64) SearchOption

HybridWeights weighs Hybrid's text rank and vector distance in their fusion: 1 and 1 otherwise.

func K

func K(k int) SearchOption

K keeps the k nearest (best) rows: the query's Limit.

func MaxDistance

func MaxDistance(d float64) SearchOption

MaxDistance keeps the rows at most d from the vector: Nearest's filter, and the rows Hybrid ranks by distance as well as those the text matches.

func Probes

func Probes(n int) SearchOption

func RRF

func RRF(k int) SearchOption

RRF is reciprocal-rank fusion's constant k: a row ranked r scores 1/(k + r); 60 otherwise. A smaller k favours the top ranks more.

func RankAs

func RankAs(alias string) SearchOption

RankAs and ScoreAs name Hybrid's text rank and fused score annotations: "rank" and "score" otherwise.

func ScoreAs

func ScoreAs(alias string) SearchOption

type Session

type Session map[string]string

Session is settings each new connection takes: MySQL session variables (SET SESSION k = v), Postgres run-time parameters (SET k = v), SQLite pragmas (PRAGMA k = v). Values are plain: numbers and words as they are, anything else quoted.

orm.Session{"statement_timeout": "5s", "search_path": "app"}

type Set

type Set map[string]any

Set is the fields an Update writes, by field name.

type Skip

type Skip struct{}

Skip leaves the field out of the table (orm:"-").

func (Skip) Apply added in v0.5.0

func (Skip) Apply(o *FieldOptions)

type SlowQuery

type SlowQuery time.Duration

SlowQuery logs (warn, through the Databases' Logger) each statement of the database taking d or longer: a Declare option.

type Slug

type Slug struct {
	Max    int
	Unique bool
}

Slug is an indexed VARCHAR (Max, 50 by default) for URL parts.

func (Slug) Apply added in v0.5.0

func (x Slug) Apply(o *FieldOptions)

type Sort

type Sort[T any] interface{ Compare(a, b T) int }

Sort is an ordering of Ts: a field reference's Asc or Desc (vuka.Order[T]), or an expression's.

type State

type State int32

State is a supervised database's health.

const (
	// Connecting: it hasn't answered yet.
	Connecting State = iota
	// Up: it answers, and so do its replicas.
	Up
	// Degraded: it answers, but a replica doesn't, or the breaker is
	// probing it.
	Degraded
	// Down: it doesn't answer — the breaker is open, or it never answered
	// within StartupWait. The supervisor keeps reconnecting.
	Down
)

func (State) MarshalText

func (s State) MarshalText() ([]byte, error)

func (State) String

func (s State) String() string

func (*State) UnmarshalText

func (s *State) UnmarshalText(b []byte) error

type StateEvent

type StateEvent struct {
	DB       string
	Replica  string // the replica's URL, its password hidden
	From, To State
	Err      error // what failed, for a change for the worse
}

StateEvent is a change of a database's state, or of one of its replicas' health (Replica set, State Up or Down), as ObserveState sees it.

type StringTerm

type StringTerm[T any, V ~string] struct{ OrderedTerm[T, V] }

StringTerm is a Term of a string.

func StringTermOf

func StringTermOf[T any, V ~string](e any) StringTerm[T, V]

func (StringTerm[T, V]) As

func (t StringTerm[T, V]) As(name string) StringTerm[T, V]

func (StringTerm[T, V]) Contains

func (t StringTerm[T, V]) Contains(s V) vuka.Pred[T]

func (StringTerm[T, V]) EndsWith

func (t StringTerm[T, V]) EndsWith(s V) vuka.Pred[T]

func (StringTerm[T, V]) IContains

func (t StringTerm[T, V]) IContains(s V) vuka.Pred[T]

func (StringTerm[T, V]) IEndsWith

func (t StringTerm[T, V]) IEndsWith(s V) vuka.Pred[T]

func (StringTerm[T, V]) IExact

func (t StringTerm[T, V]) IExact(s V) vuka.Pred[T]

func (StringTerm[T, V]) IStartsWith

func (t StringTerm[T, V]) IStartsWith(s V) vuka.Pred[T]

func (StringTerm[T, V]) StartsWith

func (t StringTerm[T, V]) StartsWith(s V) vuka.Pred[T]

type SwitchExpr

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

SwitchExpr is a Switch.

func Switch

func Switch(e Expr) *SwitchExpr

Switch is a Case comparing one expression with values: the result of the first Case value it equals, else Else's, else NULL.

orm.Switch(orm.F("status")).Case("paid", 1).Case("refunded", -1).Else(0)

func (*SwitchExpr) Case

func (s *SwitchExpr) Case(value, then any) *SwitchExpr

Case adds a branch: then where the expression equals value. Each is an Expr or a value.

func (*SwitchExpr) Else

func (s *SwitchExpr) Else(v any) *SwitchExpr

Else is the result when the expression equals no value.

type TSVector

type TSVector string

TSVector is a Postgres tsvector column: a generated column holding a SearchVector (orm:"generated"), searched by its __search lookup. Elsewhere the column holds the document's text.

type Term

type Term[T, V any] struct {
	// contains filtered or unexported fields
}

Term is a typed SQL expression over the model T, of Go type V: what the functions of package orm/fn build from field references (fn.Lower(Post.Title), fn.Count(Post.ID)). Its methods are predicates (vuka.Pred[T], so they go in Filter beside field references' own) and orderings; As names it, for Annotate and Values. It is an Expr, so the engine's string-free APIs take it too. OrderedTerm (numbers), StringTerm (strings) and CompareTerm (times) add what their type has, as vuka's references do.

func TermOf

func TermOf[T, V any](e any) Term[T, V]

TermOf, OrderedTermOf, StringTermOf and CompareTermOf type an expression: the way to give a function of your own a result type.

func (Term[T, V]) As

func (t Term[T, V]) As(name string) Term[T, V]

As names the term: Annotate(t) adds it to the query under that name, and the named term then reads it (in Filter, OrderBy, Values).

func (Term[T, V]) Asc

func (t Term[T, V]) Asc() TermOrder[T]

Asc and Desc order by the term.

func (Term[T, V]) Desc

func (t Term[T, V]) Desc() TermOrder[T]

func (Term[T, V]) Eq

func (t Term[T, V]) Eq(v V) vuka.Pred[T]

Eq, Ne and In compare the term with values; EqF and NeF with another field or term (Django's F).

func (Term[T, V]) EqF

func (t Term[T, V]) EqF(o Arg[T, V]) vuka.Pred[T]

func (Term[T, V]) Expr

func (t Term[T, V]) Expr() Expr

Expr is the term as the engine's expression.

func (Term[T, V]) Get

func (t Term[T, V]) Get(T) V

Get makes a Term an Arg; an expression is computed by the database, so it panics.

func (Term[T, V]) In

func (t Term[T, V]) In(vs ...V) vuka.Pred[T]

func (Term[T, V]) IsNull

func (t Term[T, V]) IsNull() vuka.Pred[T]

IsNull and NotNull test for NULL.

func (Term[T, V]) Ne

func (t Term[T, V]) Ne(v V) vuka.Pred[T]

func (Term[T, V]) NeF

func (t Term[T, V]) NeF(o Arg[T, V]) vuka.Pred[T]

func (Term[T, V]) NotNull

func (t Term[T, V]) NotNull() vuka.Pred[T]

type TermOrder

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

TermOrder is an ordering by a term: OrderBy takes it beside field references' orderings.

func (TermOrder[T]) Compare

func (TermOrder[T]) Compare(a, b T) int

Compare makes a TermOrder a Sort; the database orders by it, so it panics.

func (TermOrder[T]) Name

func (o TermOrder[T]) Name() string

Name is the ordering as the engine's OrderBy and Fuse name it: the term's annotation, - first when descending ("" for an unnamed term).

type TextExpr

type TextExpr struct{ OrderedExpr[string] }

TextExpr is an OrderedExpr of text, matched by patterns too.

func Headline

func Headline(field string, q TextQuery, opts ...HeadlineOption) TextExpr

Headline is a field's text about the query's words, marked <b>…</b>: Postgres's ts_headline; on SQLite, where the model declares a document holding the field, FTS5's snippet (highlight with HighlightAll); and elsewhere, or where none of the words is found, the 160 characters about the first word, unmarked. MySQL's is that substring always.

Posts.Annotate("head", orm.Headline("body", q, orm.Marks("<mark>", "</mark>"), orm.MaxWords(20)))

func Text

func Text(e Expr) TextExpr

Text is e as an expression of text.

orm.Text(orm.F("title")).IContains("go")

func (TextExpr) Contains

func (t TextExpr) Contains(s string) Cond

Contains holds where the text contains s, matched literally.

func (TextExpr) EndsWith

func (t TextExpr) EndsWith(s string) Cond

EndsWith holds where the text ends with s.

func (TextExpr) IContains

func (t TextExpr) IContains(s string) Cond

IContains is Contains ignoring case.

func (TextExpr) Like

func (t TextExpr) Like(pattern string) Cond

Like holds where the text matches a LIKE pattern of your own: % any run of characters, _ any one (case-sensitive or not as the database's LIKE is). The pattern is a bound argument.

func (TextExpr) Over

func (t TextExpr) Over(w Window) TextExpr

Over is TypedExpr's Over, keeping the expression text.

func (TextExpr) StartsWith

func (t TextExpr) StartsWith(s string) Cond

StartsWith holds where the text starts with s.

type TextField

type TextField[V ~string] struct{ Field[V] }

TextField is a string field: a Field with the text lookups.

func TextFieldAt

func TextFieldAt[V ~string](path string) TextField[V]

TextFieldAt is the text field at a lookup path.

func (TextField[V]) Contains

func (f TextField[V]) Contains(s string) Cond

func (TextField[V]) EndsWith

func (f TextField[V]) EndsWith(s string) Cond

func (TextField[V]) IContains

func (f TextField[V]) IContains(s string) Cond

func (TextField[V]) IEndsWith

func (f TextField[V]) IEndsWith(s string) Cond

func (TextField[V]) IExact

func (f TextField[V]) IExact(s string) Cond

func (TextField[V]) IStartsWith

func (f TextField[V]) IStartsWith(s string) Cond

func (TextField[V]) StartsWith

func (f TextField[V]) StartsWith(s string) Cond

type TextFuncOf

type TextFuncOf = Fn[TextExpr]

TextFuncOf is a function of text, made by TextFunc.

func TextFunc

func TextFunc(name string, opts ...FuncOption) TextFuncOf

TextFunc is Func of text.

var Unaccent = orm.TextFunc("unaccent", orm.Arity(1)).Transform()
Users.Filter(Unaccent(orm.F("name")).IContains("jose"))   // or Q{"name__unaccent__icontains": "jose"}

type TextQuery

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

TextQuery is a SearchQuery.

func SearchQuery

func SearchQuery(text string) TextQuery

SearchQuery is text to search for in web search syntax, Postgres's websearch_to_tsquery: every word must match, "a phrase" in order, or between alternatives, and -word must not; and, beyond Postgres's syntax, word* matches the words starting with word.

func (TextQuery) Config

func (q TextQuery) Config(c string) TextQuery

Config is the text search configuration the query's words are stemmed by; the document's otherwise.

type Timeout

type Timeout time.Duration

Timeout bounds a statement: a Declare option (each statement of the database), an argument of Raw, RawRow and Exec (that one), or WithTimeout's (each statement of a context).

type Trigram

type Trigram struct{}

Trigram is a pg_trgm index of a text field, for Similarity and LIKE.

func (Trigram) Apply added in v1.0.0

func (Trigram) Apply(*FieldOptions)

type TxOn

type TxOn string

TxOn runs a transaction on the database added as name, from its start, instead of the database its first write picks.

type TxOption

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

TxOption tunes Atomic: TxOn, Retry.

type TypedExpr

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

TypedExpr is an expression whose value is an R: a typed function's call, or any Expr given its type with Typed. Its comparisons take R, so a condition on it type-checks; it is an Expr, so it goes in Annotate, Values (through an annotation), Update's Set, a Case, and Where with any lookup.

Go can't give a generic type's methods to some of its type arguments only, so the comparisons a type has come with the constructor: TypedExpr (Func) has those of every type, OrderedExpr (OrderedFunc) adds Lt, Le, Gt, Ge and Between, and TextExpr (TextFunc) adds the pattern matches. Contains on a number doesn't compile.

func Typed

func Typed[R any](e Expr) TypedExpr[R]

Typed is e as an expression of R values.

orm.Typed[bool](orm.F("active")).Eq(true)

func (TypedExpr[R]) As

func (t TypedExpr[R]) As(alias string) Alias

As names the expression: AnnotateAs's annotation.

func (TypedExpr[R]) Asc

func (t TypedExpr[R]) Asc() ExprOrder

Asc orders by the expression, smallest first: Order's term.

func (TypedExpr[R]) Desc

func (t TypedExpr[R]) Desc() ExprOrder

Desc orders by the expression, largest first.

func (TypedExpr[R]) Eq

func (t TypedExpr[R]) Eq(v R) Cond

Eq holds where the expression equals v.

func (TypedExpr[R]) In

func (t TypedExpr[R]) In(vs ...R) Cond

In holds where the expression equals one of vs; none matches nothing.

func (TypedExpr[R]) IsNull

func (t TypedExpr[R]) IsNull() Cond

IsNull holds where the expression is NULL.

func (TypedExpr[R]) Ne

func (t TypedExpr[R]) Ne(v R) Cond

Ne holds where the expression doesn't equal v: NOT (… = v), unknown where the expression is NULL, as SQL has it.

func (TypedExpr[R]) NotNull

func (t TypedExpr[R]) NotNull() Cond

NotNull holds where the expression isn't NULL.

func (TypedExpr[R]) Over

func (t TypedExpr[R]) Over(w Window) TypedExpr[R]

Over makes the expression a window function's: computed per row over the rows of its partition, in its order — an aggregate over a window no longer groups.

RowNumber := orm.OrderedFunc[int64]("row_number", orm.Arity(0))
Users.AnnotateAs(RowNumber().Over(orm.Window{PartitionBy: []string{"team_id"}, OrderBy: []string{"-age"}}).As("rank"))

type Unique

type Unique struct{}

Unique makes the column unique.

func (Unique) Apply added in v0.5.0

func (Unique) Apply(o *FieldOptions)

type UniqueTogether

type UniqueTogether string

UniqueTogether names a unique index: the fields sharing a name are unique together.

func (UniqueTogether) Apply added in v0.5.0

func (x UniqueTogether) Apply(o *FieldOptions)

type Validator added in v0.5.0

type Validator interface {
	Validate() error
}

Validator checks a value before it is written: a non-nil error refuses the write with an Invalid error naming the column.

type Values

type Values[T, R any] struct {
	// contains filtered or unexported fields
}

Values is a query of some fields of T, read into R.

func (Values[T, R]) All

func (vq Values[T, R]) All(ctx context.Context) ([]R, error)

All runs the query and returns its rows.

func (Values[T, R]) Annotate added in v0.8.0

func (vq Values[T, R]) Annotate(name string, e Expr) Values[T, R]

Annotate adds a computed field (QuerySet.Annotate), e.g. an aggregate the projection groups by.

func (Values[T, R]) Distinct added in v0.8.0

func (vq Values[T, R]) Distinct() Values[T, R]

Distinct removes duplicate rows (QuerySet.Distinct).

func (Values[T, R]) Exclude added in v0.8.0

func (vq Values[T, R]) Exclude(conds ...Cond) Values[T, R]

Exclude drops rows matching every condition (QuerySet.Exclude).

func (Values[T, R]) Filter added in v0.8.0

func (vq Values[T, R]) Filter(conds ...Cond) Values[T, R]

Filter keeps only rows matching every condition (QuerySet.Filter).

func (Values[T, R]) Having added in v0.8.0

func (vq Values[T, R]) Having(conds ...Cond) Values[T, R]

Having filters the grouped rows (QuerySet.Having) — a condition on an aggregate annotation.

func (Values[T, R]) Iter

func (vq Values[T, R]) Iter(ctx context.Context) iter.Seq2[R, error]

Iter runs the query and yields its rows one at a time.

func (Values[T, R]) Limit added in v0.8.0

func (vq Values[T, R]) Limit(n int) Values[T, R]

Limit caps the number of rows (QuerySet.Limit).

func (Values[T, R]) Offset added in v0.8.0

func (vq Values[T, R]) Offset(n int) Values[T, R]

Offset skips the first n rows (QuerySet.Offset).

func (Values[T, R]) OrderBy added in v0.8.0

func (vq Values[T, R]) OrderBy(fields ...string) Values[T, R]

OrderBy sorts by the given fields (QuerySet.OrderBy); a leading "-" is descending.

type Vector

type Vector []float32

Vector is an embedding: a pgvector column on Postgres, its dimensions tagged on the field (orm:"vector:1536"); on MySQL a VECTOR column where the server has the type (MySQL 9, MariaDB 11.7), else a VARBINARY of the same bytes, so the column becomes a VECTOR in place once it does (ALTER TABLE … MODIFY); its text, [1,2,3], on SQLite. MySQL stores and reads vectors but can't search them: distances, Nearest and Hybrid are refused there when the query is written.

func (*Vector) Scan

func (v *Vector) Scan(src any) error

Scan reads pgvector's text, [1,2,3], or MySQL's bytes (bytes): a value that doesn't open with [ and close with ] is bytes, which only a vector whose last element is beyond 1e17 would.

func (Vector) Value

func (v Vector) Value() (driver.Value, error)

Value writes the vector as pgvector's text.

type WhenClause

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

WhenClause is a branch of a Case.

func When

func When(cond Cond, then any) WhenClause

When is a branch of a Case: then where cond, any Cond, holds. then is an Expr (a field, arithmetic, another Case) or a value sent as an argument.

type Window

type Window struct {
	PartitionBy []string
	OrderBy     []string
}

Window is the rows a window function (Over) computes over: those sharing the PartitionBy names' values (all rows without), in OrderBy's order (a leading - for descending). Names are fields, paths through relations or annotations.

Directories

Path Synopsis
cmd
orm command
Command orm is the ORM's tool: it writes the generated code of a module's models and drives its migrations, Django's manage.py commands.
Command orm is the ORM's tool: it writes the generated code of a module's models and drives its migrations, Django's manage.py commands.
ormgen command
Command ormgen writes the ORM's row scanners and model registrations into the tree, for go:generate (orm generate does the same):
Command ormgen writes the ORM's row scanners and model registrations into the tree, for go:generate (orm generate does the same):
Package fn is the ORM's expressions over Vuka field references, typed: each takes field references (Post.Title, Post.Author.Name) or other terms and gives an orm.Term of the Go type the database computes, so only the comparisons that make sense type-check.
Package fn is the ORM's expressions over Vuka field references, typed: each takes field references (Post.Title, Post.Author.Name) or other terms and gives an orm.Term of the Go type the database computes, so only the comparisons that make sense type-check.
internal
schema
Package schema is the ORM's tables as the database needs them, and the SQL that creates and changes them on each dialect.
Package schema is the ORM's tables as the database needs them, and the SQL that creates and changes them on each dialect.
Package migration is the ORM's schema migrations, Django's: Go files orm makemigrations writes into the program (migrations/NNNN_name.go, a package of their own), each registering a Migration of operations from its init.
Package migration is the ORM's schema migrations, Django's: Go files orm makemigrations writes into the program (migrations/NNNN_name.go, a package of their own), each registering a Migration of operations from its init.
Package mysql links the MySQL driver (go-sql-driver/mysql) for the ORM: blank-import it to Connect to "mysql".
Package mysql links the MySQL driver (go-sql-driver/mysql) for the ORM: blank-import it to Connect to "mysql".
Package ormgen writes the row scanners the ORM uses instead of reflection, registers the models embedding orm.Model, and writes each model's typed fields.
Package ormgen writes the row scanners the ORM uses instead of reflection, registers the models embedding orm.Model, and writes each model's typed fields.
Package ormtest opens a database for a test with the models' tables made: in-memory SQLite by default, or, with ORMTEST_DRIVER and ORMTEST_DSN set, a schema (Postgres) or database (MySQL) of its own on a real server, dropped when the test ends.
Package ormtest opens a database for a test with the models' tables made: in-memory SQLite by default, or, with ORMTEST_DRIVER and ORMTEST_DSN set, a schema (Postgres) or database (MySQL) of its own on a real server, dropped when the test ends.
Package ormweb serves an orm.Databases in a github.com/vuka-lang/web app: the databases open when the app starts (migrated in development) and close gracefully when it stops; *orm.Databases and the connection handles (@orm.Database) are dependencies; each request's context carries the databases and a read-your-writes Scope; GET /healthz reports every database's state (and, in development, its pool and errors); a model or handle naming a database the app doesn't declare fails the app's Build.
Package ormweb serves an orm.Databases in a github.com/vuka-lang/web app: the databases open when the app starts (migrated in development) and close gracefully when it stops; *orm.Databases and the connection handles (@orm.Database) are dependencies; each request's context carries the databases and a read-your-writes Scope; GET /healthz reports every database's state (and, in development, its pool and errors); a model or handle naming a database the app doesn't declare fails the app's Build.
Package postgres links the Postgres driver (pgx, through database/sql) for the ORM: blank-import it to Connect to "postgres".
Package postgres links the Postgres driver (pgx, through database/sql) for the ORM: blank-import it to Connect to "postgres".
Package sqlite links the pure-Go SQLite engine (glebarez/go-sqlite, on modernc's) for the ORM: blank-import it to Connect to "sqlite".
Package sqlite links the pure-Go SQLite engine (glebarez/go-sqlite, on modernc's) for the ORM: blank-import it to Connect to "sqlite".

Jump to

Keyboard shortcuts

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