orm

package
v0.11.0 Latest Latest
Warning

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

Go to latest
Published: Jun 19, 2026 License: Apache-2.0 Imports: 15 Imported by: 0

Documentation

Overview

Package orm is liteorm's declarative front-end: convention + struct-tag driven models over the SAME core (Conn, Tx, dialect, scanner) as the explicit `query` front-end, so a value fetched via one feeds the other on the same transaction. Its design is generics-first (no reflection/interface{} on the hot path), value-oriented (immutable handles, not shared mutable state), and explicit (no lazy loading, explicit soft-delete scopes, hard errors instead of silent guesses). Models may be annotated with native `orm:"..."` tags or `gorm:"..."` tags — both lower into the same schema.

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

func Attach

func Attach[P any, C any](ctx context.Context, sess liteorm.Session, field string, owner *P, targets ...*C) error

Attach inserts many-to-many links from owner to each target in the relation's join table. It is idempotent: re-attaching an existing pair is a no-op (the duplicate-key insert is caught and ignored).

func AutoMigrate

func AutoMigrate[T any](ctx context.Context, sess liteorm.Session, opts ...MigrateOption) error

AutoMigrate brings the table for T into being and in sync, additively. It is introspection-gated: a non-existent table is CREATEd (with its unique indexes and any m2m junction tables); an existing one is synced by ADD COLUMN for anything the model gained — it never drops or alters types (those are a reviewable migration via GenerateMigration). Iterating the model (never the live DB) is what makes "never drop" structural. Unique indexes get the soft-delete fix: a partial unique index (... WHERE deleted_at IS NULL) on SQLite/Postgres/MSSQL, or a functional unique index on MySQL (which lacks partial indexes) — so a soft-deleted row stops occupying the unique key.

func AutoMigrateAll

func AutoMigrateAll(ctx context.Context, sess liteorm.Session, models ...any) error

AutoMigrateAll runs AutoMigrate for several models in one call, in the order given — a one-liner for bringing a whole set of tables into sync:

orm.AutoMigrateAll(ctx, db, Org{}, Member{}, Project{})

Each model is identified by a zero value of its struct type (a value or a pointer both work). Migration runs in argument order, so list a referenced table before the table that points at it. The options that AutoMigrate[T] takes are not applied here (Go has no variadic type parameters to mix them cleanly); when you need WithForeignKeys, call the generic AutoMigrate[T] per model.

func Detach

func Detach[P any, C any](ctx context.Context, sess liteorm.Session, field string, owner *P, targets ...*C) error

Detach removes many-to-many links from owner to each target. Removing a link that does not exist is a no-op.

func DropSearchIndexes added in v0.9.0

func DropSearchIndexes[T any](ctx context.Context, sess liteorm.Session) error

DropSearchIndexes drops the search sidecars declared by T (idempotent). It is a no-op on a backend without dialect.SearchProvisioner.

func GenerateMigration

func GenerateMigration[T any](ctx context.Context, sess liteorm.Session) (up, down string, err error)

GenerateMigration computes the diff for T and returns reviewable up/down SQL — it does NOT execute anything (the two-track model: additive changes auto-apply via AutoMigrate; destructive ones are emitted here for review). Added columns become ADD/DROP; removed columns become a commented destructive DROP.

func IntrospectAllColumnsFull

func IntrospectAllColumnsFull(ctx context.Context, sess liteorm.Session) (map[string][]ColumnInfo, bool, error)

IntrospectAllColumnsFull returns rich column metadata for EVERY base table in a single query via the dialect's dialect.AllColumnsIntrospector, keyed by table name — so a schema browser scales to hundreds of tables with one round trip. The bool is false (with no error) when the dialect does not implement the capability, signalling the caller to fall back to per-table IntrospectColumnsFull.

func IntrospectForeignKeys

func IntrospectForeignKeys(ctx context.Context, sess liteorm.Session) (map[string][]ForeignKey, error)

IntrospectForeignKeys returns the foreign keys of EVERY base table in a single query via the dialect's dialect.ForeignKeyIntrospector, keyed by referencing table name. Returns an empty map (no error) when the dialect does not implement the capability — foreign-key overlays are additive.

func IntrospectIndexes

func IntrospectIndexes(ctx context.Context, sess liteorm.Session, table string) ([]string, error)

IntrospectIndexes lists the names of the indexes on table via the dialect's IndexIntrospector capability. It errors when the dialect cannot list indexes.

func IntrospectTables

func IntrospectTables(ctx context.Context, sess liteorm.Session) ([]string, error)

IntrospectTables lists the base tables of the connected database via the dialect's dialect.TableLister capability.

func Load

func Load[P any, C any](ctx context.Context, sess liteorm.Session, parents []P, field string, opts ...LoadOption) error

Load eager-loads the has-many / belongs-to relation `field` (whose target type is C) for the given parents in ONE batched query (SELECT ... WHERE TargetKey IN (ownerKeys...)), then assigns the results into each parent's field. N+1-safe by construction: the query count is exactly 1 per Load call, never O(len(parents)). There is no lazy load — you eager-load explicitly or you don't have the data.

Optional LoadWhere/LoadOrderBy options filter and order the loaded children (still one query); they are not yet supported for many-to-many (a clear error). Nested relations are loaded by chaining Load calls one level at a time. A belongs-to into a non-pointer struct field cannot represent "no matching row" (the field stays its zero value); use a pointer field if that distinction matters. An fk override resolves against the target type for has-many and the owner type for belongs-to.

func LoadPath

func LoadPath[Root any](ctx context.Context, sess liteorm.Session, roots []Root, path string) error

LoadPath eager-loads a dotted relation path off roots, running exactly ONE batched query per path segment — N+1-safe at every level, with no lazy loading. Each segment is a Go relation field name on the type the previous segment produced, so "Author.Company" loads each root's Author, then each of those authors' Company. A self-referential relation may repeat ("Children.Children") for a bounded-depth tree load; depth is exactly the number of segments, so there is no unbounded recursion. It is the typed analogue of gorm's Preload("A.B").

func RegisterPlural added in v0.9.0

func RegisterPlural(singular, plural string)

RegisterPlural overrides the pluralization of a single irregular word for the pluralized table-name convention — e.g. RegisterPlural("quiz", "quizzes"). Only the last snake_case segment of a table name is inflected, so register the bare word, not a compound (register "person", which also covers "blog_person"). Common irregulars (person, child, man, …) are built in; use this for the rest, or for domain words the rule-based pluralizer gets wrong.

func SearchSpecs added in v0.9.0

func SearchSpecs[T any]() ([]dialect.SearchSpec, error)

SearchSpecs returns the resolved, dialect-neutral search specifications for T — sidecar names defaulted, field names resolved to columns, sync mode resolved. The typed search helpers use it to locate a model's sidecars without re-deriving them. The result is in the same order as Schema.SearchIndexes.

func UsePluralTableNames added in v0.9.0

func UsePluralTableNames(on bool)

UsePluralTableNames switches the default table-name convention to pluralized snake_case — User -> "users", Category -> "categories", UserProfile -> "user_profiles" — matching gorm's default, to ease porting. It is off by default: liteorm otherwise uses the exact snake_case of the type name. A model's own TableName() method always wins, regardless of this setting.

It applies to both the orm and query front-ends (they share the naming resolver). Call it once at startup, before any schema is resolved; it resets the orm schema cache so a toggle takes effect, but other components may have already captured a name. Register irregular plurals the rules miss with RegisterPlural.

Types

type AfterCreateHook

type AfterCreateHook[T any] interface {
	AfterCreate(ctx context.Context, ev *Event[T]) error
}

Hook interfaces. A model opts in by implementing the ones it needs on *T:

func (u *User) BeforeCreate(ctx context.Context, ev *orm.Event[User]) error { ... }

type AfterDeleteHook

type AfterDeleteHook[T any] interface {
	AfterDelete(ctx context.Context, ev *Event[T]) error
}

Hook interfaces. A model opts in by implementing the ones it needs on *T:

func (u *User) BeforeCreate(ctx context.Context, ev *orm.Event[User]) error { ... }

type AfterUpdateHook

type AfterUpdateHook[T any] interface {
	AfterUpdate(ctx context.Context, ev *Event[T]) error
}

Hook interfaces. A model opts in by implementing the ones it needs on *T:

func (u *User) BeforeCreate(ctx context.Context, ev *orm.Event[User]) error { ... }

type Association

type Association[P any, C any] struct {
	// contains filtered or unexported fields
}

Association is a typed write handle over the relation `field` of one owner *P whose target type is C. It mirrors gorm's db.Model(&owner).Association("Rel") for every relation whose foreign key is not on the owner — has-many, has-one, and many-to-many — exposing Append, Replace, Delete, Clear, and Count. (For a to-one has-one, Replace is the natural setter: it detaches the old target and points the new one at the owner.)

It never cascade-saves target rows: targets are linked by their existing primary keys, so create them first with a Repo. For has-many/has-one, Delete and Clear detach by setting the target's foreign key to NULL (the column must be nullable); they never delete target rows. These methods write the database; call orm.Load to refresh the owner's field afterwards.

func Assoc

func Assoc[P any, C any](sess liteorm.Session, field string, owner *P) (*Association[P, C], error)

Assoc opens an association handle for owner's `field`. It errors when the field is not a has-many, has-one, or many-to-many relation whose target type is C. Belongs-to is a single foreign key on the owner — set that field and Update the owner instead of using an association handle.

func (*Association[P, C]) Append

func (a *Association[P, C]) Append(ctx context.Context, targets ...*C) error

Append links targets to the owner. For many-to-many it inserts junction rows (idempotent, like Attach); for has-many it points each target's foreign key at the owner. Targets must already exist (have primary keys).

func (*Association[P, C]) Clear

func (a *Association[P, C]) Clear(ctx context.Context) error

Clear unlinks every target from the owner: all junction rows for many-to-many, or nulling the foreign key of every owned row for has-many.

func (*Association[P, C]) Count

func (a *Association[P, C]) Count(ctx context.Context) (int64, error)

Count returns how many targets are currently linked to the owner.

func (*Association[P, C]) Delete

func (a *Association[P, C]) Delete(ctx context.Context, targets ...*C) error

Delete unlinks the given targets from the owner. For many-to-many it removes the junction rows; for has-many it nulls the target's foreign key (only for rows that currently belong to this owner). It never deletes target rows.

func (*Association[P, C]) Replace

func (a *Association[P, C]) Replace(ctx context.Context, targets ...*C) error

Replace clears the association and then appends targets — the set becomes exactly targets.

type BeforeCreateHook

type BeforeCreateHook[T any] interface {
	BeforeCreate(ctx context.Context, ev *Event[T]) error
}

Hook interfaces. A model opts in by implementing the ones it needs on *T:

func (u *User) BeforeCreate(ctx context.Context, ev *orm.Event[User]) error { ... }

type BeforeDeleteHook

type BeforeDeleteHook[T any] interface {
	BeforeDelete(ctx context.Context, ev *Event[T]) error
}

Hook interfaces. A model opts in by implementing the ones it needs on *T:

func (u *User) BeforeCreate(ctx context.Context, ev *orm.Event[User]) error { ... }

type BeforeUpdateHook

type BeforeUpdateHook[T any] interface {
	BeforeUpdate(ctx context.Context, ev *Event[T]) error
}

Hook interfaces. A model opts in by implementing the ones it needs on *T:

func (u *User) BeforeCreate(ctx context.Context, ev *orm.Event[User]) error { ... }

type Changes

type Changes struct {
	Table   string
	Added   []*Field       // in the model, missing in the DB (additive)
	Removed []ColumnMeta   // in the DB, missing from the model (destructive to drop)
	Changed []ColumnChange // present on both sides with a different type (reviewable)
}

Changes is a schema diff between the model T and the live table.

func Diff

func Diff[T any](ctx context.Context, sess liteorm.Session) (Changes, error)

Diff compares the model T against the live table's columns.

func (Changes) Empty

func (c Changes) Empty() bool

Empty reports whether the model and the live table already agree.

type ColumnChange

type ColumnChange struct {
	Column string
	From   string
	To     string
}

ColumnChange is a column whose type the model changed relative to the live table. From is the live (catalog) type, To is the model's type. Type changes are always reviewable-only — never auto-applied.

type ColumnInfo

type ColumnInfo struct {
	Name    string
	Type    string
	NotNull bool
	Default *string // nil when the column has no default
	PKPos   int
}

ColumnInfo is richer live-column metadata than ColumnMeta, for schema browsers and admin tooling (liteorm.org/studio). PKPos is 0 when the column is not part of the primary key, else its 1-based position within the key.

func IntrospectColumnsFull

func IntrospectColumnsFull(ctx context.Context, sess liteorm.Session, table string) ([]ColumnInfo, error)

IntrospectColumnsFull lists rich column metadata (nullability, default, primary key position) via the dialect's dialect.ColumnIntrospector. Dialects that do not implement it degrade to name+type only (via IntrospectColumns).

type ColumnMeta

type ColumnMeta struct {
	Name string
	Type string
}

ColumnMeta describes an existing database column.

func IntrospectColumns

func IntrospectColumns(ctx context.Context, sess liteorm.Session, table string) ([]ColumnMeta, error)

IntrospectColumns lists the existing columns of table via the dialect's Introspector capability. Returns an empty slice if the table does not exist.

type DeletedScope

type DeletedScope int

DeletedScope controls how soft-deleted rows are treated by reads. The default (WithoutDeleted) excludes them; the scope is an explicit, nameable enum — a clear opt-out without a double negative.

const (
	WithoutDeleted DeletedScope = iota
	IncludeDeleted
	OnlyDeleted
)

type Event added in v0.9.0

type Event[T any] struct {
	Sess  liteorm.Session
	Model *T
}

Event is the narrow, explicit handle passed to a hook — the executing session and the typed model. It is not a mutable shared *DB: control flow and capabilities are visible at the signature, and hook errors propagate and abort (they are never swallowed). Wrong hook signatures are a compile error, not a silently-dead hook, because the interfaces are typed on T.

type Field

type Field struct {
	GoName     string
	Column     string
	Index      []int
	PK         bool
	Auto       bool
	NotNull    bool
	Unique     bool
	HasIndex   bool   // non-unique secondary index (orm/gorm "index")
	IndexName  string // optional explicit index name
	HasDefault bool
	Default    string
	SoftDelete bool
	AutoCreate bool   // set to now() on Create (autoCreateTime)
	AutoUpdate bool   // set to now() on Create and Update (autoUpdateTime)
	Check      string // CHECK constraint expression, if any
	Readable   bool   // included in SELECT column lists
	Writable   bool   // included in INSERT/UPDATE column lists
	Size       int
	// contains filtered or unexported fields
}

Field is a scalar column in a model schema.

type ForeignKey

type ForeignKey struct {
	FromColumn string
	RefTable   string
	RefColumn  string
}

ForeignKey describes one foreign-key column discovered from the live catalog: FromColumn (in the referencing table) references RefTable(RefColumn).

type LoadOption

type LoadOption func(*loadOpts)

LoadOption customizes the batched children query of Load — a filter and/or an order on the related rows. Options keep the load N+1-safe (still one query); they are raw SQL fragments with "?" placeholders (renumbered per dialect), the same escape-hatch style as Repo.Where/OrderBy.

func LoadOrderBy

func LoadOrderBy(terms ...string) LoadOption

LoadOrderBy orders the loaded children, e.g. LoadOrderBy("created_at DESC").

func LoadWhere

func LoadWhere(frag string, args ...any) LoadOption

LoadWhere restricts the loaded children with a raw predicate fragment, e.g. LoadWhere("published_at IS NOT NULL") or LoadWhere("status = ?", "active").

type Metric added in v0.9.0

type Metric int

Metric is a vector distance function.

const (
	// L2 is Euclidean distance (the vec0 default).
	L2 Metric = iota
	// Cosine is cosine distance — the usual choice for normalized embeddings.
	Cosine
	// L1 is Manhattan distance.
	L1
	// Hamming is bit-vector Hamming distance.
	Hamming
)

func (Metric) String added in v0.9.0

func (m Metric) String() string

type MigrateOption

type MigrateOption func(*migrateConfig)

MigrateOption configures AutoMigrate. Options are opt-in; the zero config preserves the historical behavior (no foreign-key constraints).

func WithForeignKeys

func WithForeignKeys() MigrateOption

WithForeignKeys makes AutoMigrate emit a FOREIGN KEY constraint for every belongs-to relation on a NEWLY created table (referencing the target's primary key). It is off by default — liteorm ships relations as plain columns so additive migration and bulk loads stay simple. Adding a constraint to an already-existing table is never automatic (it can fail on dirty data); do that with a reviewable migration. A single relation can opt in without this global flag via the `orm:"constraint:fk"` tag. Migrate referenced tables first, so the target exists when the owner's constraint is created.

type Preloader

type Preloader[Root any] struct {
	// contains filtered or unexported fields
}

Preloader plans several relation paths off one root slice; Load runs each path with one batched query per segment. It is the fluent form of repeated LoadPath calls (gorm's chained Preload).

func NewPreloader

func NewPreloader[Root any](sess liteorm.Session) *Preloader[Root]

NewPreloader starts a preload plan for roots of type Root on sess.

func (*Preloader[Root]) Load

func (p *Preloader[Root]) Load(ctx context.Context, roots []Root) error

Load executes every planned path against roots, in order.

func (*Preloader[Root]) With

func (p *Preloader[Root]) With(path string) *Preloader[Root]

With adds a dotted relation path to the plan and returns the preloader for chaining.

type RelKind

type RelKind int

RelKind is the kind of an association.

const (
	// RelHasMany: the FK lives on the target (e.g. User.Orders []Order, order.user_id).
	RelHasMany RelKind = iota
	// RelBelongsTo: the FK lives on the owner (e.g. User.Company Company, user.company_id).
	RelBelongsTo
	// RelManyToMany: a junction table links owner and target (e.g. User.Roles []Role
	// via user_roles(user_id, role_id)).
	RelManyToMany
	// RelHasOne: a single target whose FK lives on the target (e.g. User.Profile
	// *Profile, profile.user_id). Same shape as has-many but to-one — loaded by the
	// same query direction and assigned as one value.
	RelHasOne
)

type Relation

type Relation struct {
	GoName     string
	Kind       RelKind
	Target     reflect.Type
	OwnerKey   string
	TargetKey  string
	Index      []int
	IsSlice    bool
	Constraint bool // belongs-to: emit an FK constraint (opt-in via `constraint:fk`)

	// many-to-many only:
	JoinTable string
	OwnerFK   string // column on JoinTable referencing OwnerKey
	TargetFK  string // column on JoinTable referencing TargetKey

	// polymorphic has-many / has-one only: the target also carries a type column
	// (PolymorphicType) constrained to PolymorphicValue, so one table can be owned
	// by several owner types. TargetKey is then the owner-id column. Empty for a
	// non-polymorphic relation.
	PolymorphicType  string
	PolymorphicValue string
}

Relation is an association. For has-many/belongs-to, OwnerKey/TargetKey are the join columns and eager loading runs one batched "SELECT * FROM target WHERE TargetKey IN (ownerKeys)". For many-to-many, JoinTable links OwnerKey<-OwnerFK and TargetFK->TargetKey, loaded in one JOIN query.

type Repo

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

Repo is the declarative repository for T over a liteorm.Session. It reuses the `query` front-end for reads (proving shared-core interop) and adds hooks and soft-delete scoping.

func NewRepo

func NewRepo[T any](sess liteorm.Session) *Repo[T]

NewRepo constructs a Repo[T] bound to sess (a *DB or a transaction).

orm.Repo uses model-oriented verbs (Create, Delete(*T)) so hooks and the soft-delete scope can fire; this differs by design from query.Repo's SQL-oriented surface (Insert, Delete(id)). Reads compose a thin filter layer (Where/Filter/OrderBy/Limit/Offset/Scopes) over the query builder; drop to a full query.Select[T] on the same Session for joins, unions, and projections.

func (*Repo[T]) Count

func (r *Repo[T]) Count(ctx context.Context) (int64, error)

Count returns how many rows match the current scopes.

func (*Repo[T]) Create

func (r *Repo[T]) Create(ctx context.Context, v *T) error

Create inserts v, firing Before/AfterCreate hooks; the generated PK is read back via RETURNING where supported.

func (*Repo[T]) CreateInBatches

func (r *Repo[T]) CreateInBatches(ctx context.Context, vs []*T, batchSize int) error

CreateInBatches inserts vs in chunks of batchSize, firing Before/AfterCreate hooks and stamping auto timestamps for every row, and reading generated primary keys back into each element where the dialect supports RETURNING/OUTPUT (a non-RETURNING dialect inserts the batch without reading keys back). Each chunk is one multi-row INSERT — far fewer round trips than Create per row. Mirrors gorm's CreateInBatches. A batchSize <= 0 inserts everything in a single batch.

func (*Repo[T]) Delete

func (r *Repo[T]) Delete(ctx context.Context, v *T) error

Delete removes v: a soft delete (UPDATE deleted_at = now) when the model has a soft-delete column, else a hard DELETE. Always scoped by PK (no WHERE-less delete). Fires Before/AfterDelete hooks.

func (*Repo[T]) Exists

func (r *Repo[T]) Exists(ctx context.Context) (bool, error)

Exists reports whether any row matches the current scopes.

func (*Repo[T]) Filter

func (r *Repo[T]) Filter(preds ...query.Predicate) *Repo[T]

Filter adds typed predicates (query.Col[V]…) to the read scope.

func (*Repo[T]) Find

func (r *Repo[T]) Find(ctx context.Context) ([]T, error)

Find returns all rows matching the soft-delete scope and any read scopes composed via Where/Filter/OrderBy/Limit/Offset/Scopes.

func (*Repo[T]) FindInBatches

func (r *Repo[T]) FindInBatches(ctx context.Context, batchSize int, fn func(batch []T) error) error

FindInBatches processes every row matching the current scopes in chunks of batchSize, calling fn once per non-empty batch. It walks the table by keyset on the primary key (WHERE pk > cursor ORDER BY pk LIMIT batchSize), so memory stays bounded regardless of total rows — the batched-callback scan gorm and xorm ship, for backfills and exports. It honors the soft-delete scope and any composed Where/Filter/Scopes, but imposes its own primary-key ordering, so do not combine it with OrderBy; it requires a single-column primary key. Iteration stops when a short batch is returned or fn returns an error (which is propagated). A batchSize <= 0 defaults to 100.

func (*Repo[T]) First

func (r *Repo[T]) First(ctx context.Context) (T, error)

First returns the first row matching the current scopes, or liteorm.ErrNoRows.

func (*Repo[T]) FirstOrCreate

func (r *Repo[T]) FirstOrCreate(ctx context.Context, v *T, conds ...query.Predicate) (created bool, err error)

FirstOrCreate looks up the first row matching conds; if one exists it is loaded into *v and created is false. Otherwise v is inserted as-is (Create, firing hooks) and created is true. It honors the Repo's soft-delete scope. Mirrors gorm's FirstOrCreate (the conds are the lookup; v supplies the new row).

func (*Repo[T]) FirstOrInit

func (r *Repo[T]) FirstOrInit(ctx context.Context, v *T, conds ...query.Predicate) (found bool, err error)

FirstOrInit looks up the first row matching conds; if one exists it is loaded into *v and found is true. Otherwise *v is left exactly as the caller supplied it (its default/attribute values) and NOTHING is written — found is false. It is the non-persisting sibling of FirstOrCreate (gorm's FirstOrInit): use it to load-or-prepare a value, then decide whether to Save it yourself.

func (*Repo[T]) ForceDelete

func (r *Repo[T]) ForceDelete(ctx context.Context, v *T) error

ForceDelete always issues a hard DELETE, even for soft-delete models.

func (*Repo[T]) Get

func (r *Repo[T]) Get(ctx context.Context, keys ...any) (T, error)

Get fetches the row whose primary key equals the given key (honoring the soft-delete scope), or liteorm.ErrNoRows. For a composite primary key, pass one value per key column, in declaration order: Get(ctx, tenantID, code).

func (*Repo[T]) GetByKeys

func (r *Repo[T]) GetByKeys(ctx context.Context, keys ...any) ([]T, error)

GetByKeys fetches the rows whose primary key is one of keys, in a single query (honoring the soft-delete scope) — the batch form of Get. It requires a single-column primary key (a clear error on a composite key). Rows come back in no particular order, and a key with no row is simply absent.

func (*Repo[T]) IncludeDeleted

func (r *Repo[T]) IncludeDeleted() *Repo[T]

IncludeDeleted returns a Repo view that includes soft-deleted rows.

func (*Repo[T]) Limit

func (r *Repo[T]) Limit(n int) *Repo[T]

Limit caps the number of rows a read returns.

func (*Repo[T]) Offset

func (r *Repo[T]) Offset(n int) *Repo[T]

Offset skips the first n rows of a read.

func (*Repo[T]) Omit

func (r *Repo[T]) Omit(cols ...string) *Repo[T]

Omit returns a Repo view whose writes never touch the named columns (matched by column name or Go field name). Mirrors gorm's Omit.

func (*Repo[T]) OnlyDeleted

func (r *Repo[T]) OnlyDeleted() *Repo[T]

OnlyDeleted returns a Repo view that returns only soft-deleted rows.

func (*Repo[T]) OrderBy

func (r *Repo[T]) OrderBy(terms ...string) *Repo[T]

OrderBy adds ORDER BY terms to the read scope.

func (*Repo[T]) Restore

func (r *Repo[T]) Restore(ctx context.Context, v *T) error

Restore clears the soft-delete timestamp of v's row, bringing a soft-deleted row back into the live set — the symmetric partner to Delete. It targets the row by primary key regardless of the delete scope (so it can reach an already-deleted row), fires Before/AfterUpdate, and returns liteorm.ErrNoRows if no row matches. It errors on a model without a soft-delete column.

func (*Repo[T]) Save

func (r *Repo[T]) Save(ctx context.Context, v *T) error

Save inserts v when its primary key is zero and updates it otherwise — the upsert-by-identity convenience (gorm's Save). It fires the matching Before/After Create or Update hooks of the path it takes.

func (*Repo[T]) Scopes

func (r *Repo[T]) Scopes(scopes ...Scope[T]) *Repo[T]

Scopes appends reusable scopes to the read, in order. Each scope is a function over the query builder, so teams can package and share common filters.

func (*Repo[T]) Select

func (r *Repo[T]) Select(cols ...string) *Repo[T]

Select returns a Repo view whose writes (Create/Update/Save) touch only the named columns. Tokens match a column name or a Go field name. The primary key and auto timestamps are still handled. Mirrors gorm's Select for writes.

func (*Repo[T]) Update

func (r *Repo[T]) Update(ctx context.Context, v *T) error

Update writes v's non-key columns to the row identified by its PK, firing Before/AfterUpdate hooks.

func (*Repo[T]) Updates

func (r *Repo[T]) Updates(ctx context.Context, v *T, cols ...string) error

Updates writes only the named columns (matched by column or Go field name) of v to its row, firing Before/AfterUpdate hooks — a partial update (gorm's Updates). With no columns named it updates the full writable set, like Update.

func (*Repo[T]) Upsert

func (r *Repo[T]) Upsert(ctx context.Context, v *T, oc query.OnConflictSpec) error

Upsert inserts v, or on a conflict with oc's columns updates the existing row — INSERT ... ON CONFLICT DO UPDATE (gorm/xorm upsert) in a single statement, unlike FirstOrCreate's lookup-then-insert. It fires Before/AfterCreate hooks and stamps auto timestamps, then delegates to the query front-end (which reads a generated key back where the dialect supports it). The update path overwrites the columns oc updates; narrow them with OnConflict(cols...).DoUpdate(cols...) to preserve a column like created_at.

func (*Repo[T]) Where

func (r *Repo[T]) Where(frag string, args ...any) *Repo[T]

Where adds a raw predicate fragment (with "?" placeholders) to the read scope. It returns a Repo view, so reads stay the orm convenience layer while delegating to the query builder underneath.

type Schema

type Schema struct {
	Type       reflect.Type
	Table      string
	Fields     []*Field
	PKs        []*Field // the primary-key columns, in declaration order (composite = >1)
	PK         *Field   // the single PK when there is exactly one; nil for a composite key
	SoftDelete *Field
	Relations  map[string]*Relation
	// SearchIndexes are the model's full-text / vector sidecars, collected from
	// `vector`/`fts` struct tags and the optional SearchIndexes method. Empty for
	// a model with no search indexes.
	SearchIndexes []SearchIndex
}

Schema is a model's resolved metadata.

func SchemaOf

func SchemaOf[T any]() (*Schema, error)

SchemaOf returns the resolved schema for T, building and caching it once.

func SchemaOfType

func SchemaOfType(t reflect.Type) (*Schema, error)

SchemaOfType resolves the schema for a model whose type is only known at runtime — for tools that hold models as reflect.Type or interface{} (schema browsers, generic admin UIs) rather than a type parameter. Pointer types are dereferenced to their struct element, so SchemaOfType(reflect.TypeOf(User{})) and SchemaOf[User]() return the identical cached *Schema. It errors if t does not resolve to a struct.

func (*Schema) WriteColumns

func (s *Schema) WriteColumns(forUpdate bool) []string

WriteColumns returns the columns to write: writable, non-auto-increment, and (for updates) non-primary-key. Respects read-only fields (orm "readonly" / gorm "<-:false").

type Scope

type Scope[T any] func(*query.SelectBuilder[T]) *query.SelectBuilder[T]

Scope is a reusable, named read transformer over the query builder — the unit teams package common filters in (ActiveOnly, OwnedBy(user), …) and pass to Repo.Scopes, mirroring gorm's Scopes. It receives and returns the builder, so scopes compose by chaining.

type SearchIndex added in v0.9.0

type SearchIndex struct {
	Kind dialect.SearchKind
	// Name is the sidecar table name. Empty means the default: <table>_fts for a
	// full-text index, <table>_vec for a vector index. With more than one index of
	// the same kind on a model, give each an explicit name.
	Name string
	// Fields are the model's Go field names the index covers — the text columns
	// for a full-text index, the single embedding field for a vector index.
	Fields []string
	// Sync selects the synchronization strategy (see [SyncMode]).
	Sync SyncMode

	// Full-text options.
	Tokenizer string    // FTS5 tokenizer, e.g. "porter unicode61" (default "unicode61")
	Prefix    []int     // FTS5 prefix-index lengths
	Detail    string    // FTS5 detail level: "full" (default), "column", or "none"
	Content   string    // FTS5 content mode; only external (the default, "") is supported today
	Weights   []float64 // per-column BM25 weights; len must equal len(Fields) when set

	// Vector options.
	Dim      int    // embedding dimension (required, > 0)
	Metric   Metric // distance function (default Cosine)
	Encoding string // "float32" (default), "int8", or "bit"
}

SearchIndex declares a search sidecar (full-text or vector) attached to a model. It is the single representation that both the struct-tag sugar and the SearchIndexes method lower into; the migrator, the sync layer, and the typed search helpers all read it. The zero value is not useful — construct one with FullText or Vector.

func FullText added in v0.9.0

func FullText(fields ...string) SearchIndex

FullText declares a full-text (FTS5) index over the named model fields. Refine it with the With* methods; for the common single-field case the defaults (unicode61 tokenizer, external content, trigger sync) are usually right.

func Vector added in v0.9.0

func Vector(field string, dim int) SearchIndex

Vector declares a vector (sqlite-vec) index over the named embedding field with the given dimension. The default metric is Cosine; override with SearchIndex.WithMetric.

func (SearchIndex) Named added in v0.9.0

func (i SearchIndex) Named(name string) SearchIndex

Named overrides the sidecar table name.

func (SearchIndex) WithDetail added in v0.9.0

func (i SearchIndex) WithDetail(d string) SearchIndex

WithDetail sets the FTS5 detail level (full-text only).

func (SearchIndex) WithEncoding added in v0.9.0

func (i SearchIndex) WithEncoding(e string) SearchIndex

WithEncoding sets the vector storage encoding (vector only).

func (SearchIndex) WithMetric added in v0.9.0

func (i SearchIndex) WithMetric(m Metric) SearchIndex

WithMetric sets the distance function (vector only).

func (SearchIndex) WithPrefix added in v0.9.0

func (i SearchIndex) WithPrefix(lengths ...int) SearchIndex

WithPrefix sets FTS5 prefix-index lengths (full-text only).

func (SearchIndex) WithSync added in v0.9.0

func (i SearchIndex) WithSync(m SyncMode) SearchIndex

WithSync sets the synchronization strategy.

func (SearchIndex) WithTokenizer added in v0.9.0

func (i SearchIndex) WithTokenizer(t string) SearchIndex

WithTokenizer sets the FTS5 tokenizer (full-text only).

func (SearchIndex) WithWeights added in v0.9.0

func (i SearchIndex) WithWeights(w ...float64) SearchIndex

WithWeights sets per-column BM25 weights; the count must match the field count (full-text only).

type SearchIndexer added in v0.9.0

type SearchIndexer interface {
	SearchIndexes() []SearchIndex
}

SearchIndexer is the optional interface a model implements to declare its search indexes in typed Go rather than struct tags. Tag-declared and method-declared indexes are merged; on a sidecar-name collision the method-declared one wins.

type SyncMode added in v0.9.0

type SyncMode int

SyncMode chooses how a sidecar index is kept current with its base table.

const (
	// SyncAuto lets the backend pick: triggers for full-text (the indexed text
	// already lives on the base table, so it is free and catches every write) and
	// hooks for vectors (the embedding need not be duplicated on the base table).
	SyncAuto SyncMode = iota
	// SyncTriggers keeps the sidecar current with SQL triggers, so bulk and raw
	// (non-ORM) writes stay in sync. A vector index in trigger mode stores the
	// embedding as a column on the base table.
	SyncTriggers
	// SyncHooks keeps the sidecar current from the ORM write path only (no base
	// column duplication for vectors); writes that bypass the ORM are not indexed.
	SyncHooks
)

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL