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 ¶
- func Attach[P any, C any](ctx context.Context, sess liteorm.Session, field string, owner *P, ...) error
- func AutoMigrate[T any](ctx context.Context, sess liteorm.Session, opts ...MigrateOption) error
- func AutoMigrateAll(ctx context.Context, sess liteorm.Session, models ...any) error
- func Detach[P any, C any](ctx context.Context, sess liteorm.Session, field string, owner *P, ...) error
- func DropSearchIndexes[T any](ctx context.Context, sess liteorm.Session) error
- func GenerateMigration[T any](ctx context.Context, sess liteorm.Session) (up, down string, err error)
- func IntrospectAllColumnsFull(ctx context.Context, sess liteorm.Session) (map[string][]ColumnInfo, bool, error)
- func IntrospectForeignKeys(ctx context.Context, sess liteorm.Session) (map[string][]ForeignKey, error)
- func IntrospectIndexes(ctx context.Context, sess liteorm.Session, table string) ([]string, error)
- func IntrospectTables(ctx context.Context, sess liteorm.Session) ([]string, error)
- func Load[P any, C any](ctx context.Context, sess liteorm.Session, parents []P, field string, ...) error
- func LoadPath[Root any](ctx context.Context, sess liteorm.Session, roots []Root, path string) error
- func RegisterPlural(singular, plural string)
- func SearchSpecs[T any]() ([]dialect.SearchSpec, error)
- func UsePluralTableNames(on bool)
- type AfterCreateHook
- type AfterDeleteHook
- type AfterUpdateHook
- type Association
- func (a *Association[P, C]) Append(ctx context.Context, targets ...*C) error
- func (a *Association[P, C]) Clear(ctx context.Context) error
- func (a *Association[P, C]) Count(ctx context.Context) (int64, error)
- func (a *Association[P, C]) Delete(ctx context.Context, targets ...*C) error
- func (a *Association[P, C]) Replace(ctx context.Context, targets ...*C) error
- type BeforeCreateHook
- type BeforeDeleteHook
- type BeforeUpdateHook
- type Changes
- type ColumnChange
- type ColumnInfo
- type ColumnMeta
- type DeletedScope
- type Event
- type Field
- type ForeignKey
- type LoadOption
- type Metric
- type MigrateOption
- type Preloader
- type RelKind
- type Relation
- type Repo
- func (r *Repo[T]) Count(ctx context.Context) (int64, error)
- func (r *Repo[T]) Create(ctx context.Context, v *T) error
- func (r *Repo[T]) CreateInBatches(ctx context.Context, vs []*T, batchSize int) error
- func (r *Repo[T]) Delete(ctx context.Context, v *T) error
- func (r *Repo[T]) Exists(ctx context.Context) (bool, error)
- func (r *Repo[T]) Filter(preds ...query.Predicate) *Repo[T]
- func (r *Repo[T]) Find(ctx context.Context) ([]T, error)
- func (r *Repo[T]) FindInBatches(ctx context.Context, batchSize int, fn func(batch []T) error) error
- func (r *Repo[T]) First(ctx context.Context) (T, error)
- func (r *Repo[T]) FirstOrCreate(ctx context.Context, v *T, conds ...query.Predicate) (created bool, err error)
- func (r *Repo[T]) FirstOrInit(ctx context.Context, v *T, conds ...query.Predicate) (found bool, err error)
- func (r *Repo[T]) ForceDelete(ctx context.Context, v *T) error
- func (r *Repo[T]) Get(ctx context.Context, keys ...any) (T, error)
- func (r *Repo[T]) GetByKeys(ctx context.Context, keys ...any) ([]T, error)
- func (r *Repo[T]) IncludeDeleted() *Repo[T]
- func (r *Repo[T]) Limit(n int) *Repo[T]
- func (r *Repo[T]) Offset(n int) *Repo[T]
- func (r *Repo[T]) Omit(cols ...string) *Repo[T]
- func (r *Repo[T]) OnlyDeleted() *Repo[T]
- func (r *Repo[T]) OrderBy(terms ...string) *Repo[T]
- func (r *Repo[T]) Restore(ctx context.Context, v *T) error
- func (r *Repo[T]) Save(ctx context.Context, v *T) error
- func (r *Repo[T]) Scopes(scopes ...Scope[T]) *Repo[T]
- func (r *Repo[T]) Select(cols ...string) *Repo[T]
- func (r *Repo[T]) Update(ctx context.Context, v *T) error
- func (r *Repo[T]) Updates(ctx context.Context, v *T, cols ...string) error
- func (r *Repo[T]) Upsert(ctx context.Context, v *T, oc query.OnConflictSpec) error
- func (r *Repo[T]) Where(frag string, args ...any) *Repo[T]
- type Schema
- type Scope
- type SearchIndex
- func (i SearchIndex) Named(name string) SearchIndex
- func (i SearchIndex) WithDetail(d string) SearchIndex
- func (i SearchIndex) WithEncoding(e string) SearchIndex
- func (i SearchIndex) WithMetric(m Metric) SearchIndex
- func (i SearchIndex) WithPrefix(lengths ...int) SearchIndex
- func (i SearchIndex) WithSync(m SyncMode) SearchIndex
- func (i SearchIndex) WithTokenizer(t string) SearchIndex
- func (i SearchIndex) WithWeights(w ...float64) SearchIndex
- type SearchIndexer
- type SyncMode
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 ¶
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 ¶
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
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 ¶
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 ¶
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 ¶
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 ¶
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 ¶
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 ¶
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 ¶
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 ¶
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.
type BeforeCreateHook ¶
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 ¶
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 ¶
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.
type ColumnChange ¶
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 ¶
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
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 ¶
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 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 ¶
NewPreloader starts a preload plan for roots of type Root on sess.
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 ¶
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]) Create ¶
Create inserts v, firing Before/AfterCreate hooks; the generated PK is read back via RETURNING where supported.
func (*Repo[T]) CreateInBatches ¶
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 ¶
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]) Find ¶
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 ¶
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 ¶
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 ¶
ForceDelete always issues a hard DELETE, even for soft-delete models.
func (*Repo[T]) Get ¶
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 ¶
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 ¶
IncludeDeleted returns a Repo view that includes soft-deleted rows.
func (*Repo[T]) Omit ¶
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 ¶
OnlyDeleted returns a Repo view that returns only soft-deleted rows.
func (*Repo[T]) Restore ¶
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 ¶
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 ¶
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 ¶
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 ¶
Update writes v's non-key columns to the row identified by its PK, firing Before/AfterUpdate hooks.
func (*Repo[T]) Updates ¶
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 ¶
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.
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 SchemaOfType ¶
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 ¶
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 )