Documentation
¶
Overview ¶
Package orm is a generic query builder and model mapper over database/sql. Queries are typed (Query[T] returns T values), always use bound parameters, and validate column names against the model so input can never become SQL (A05). Models are plain structs; persistence functions take an Executor (*database.DB or *database.Tx), so there is no Active Record handle hidden in models.
Index ¶
- Variables
- func Attach[T any](ctx context.Context, ex database.Executor, model *T, relation string, ...) error
- func Columns[T any]() []string
- func Create[T any](ctx context.Context, ex database.Executor, model *T) error
- func Delete[T any](ctx context.Context, ex database.Executor, model *T) error
- func Detach[T any](ctx context.Context, ex database.Executor, model *T, relation string, ...) error
- func ForceDelete[T any](ctx context.Context, ex database.Executor, model *T) error
- func Restore[T any](ctx context.Context, ex database.Executor, model *T) error
- func Sync[T any](ctx context.Context, ex database.Executor, model *T, relation string, ...) error
- func TableOf[T any]() string
- func Update[T any](ctx context.Context, ex database.Executor, model *T) error
- func Validate(ctx context.Context, ex database.Executor, rules ...Rule) error
- func WithClock(ctx context.Context, now func() time.Time) context.Context
- type AfterCreater
- type AfterUpdater
- type BeforeCreater
- type BeforeDeleter
- type BeforeUpdater
- type BelongsTo
- type CursorPage
- type Direction
- type HasMany
- type HasOne
- type JSON
- type Keyer
- type ManyToMany
- type Model
- type NullTime
- type Page
- type Query
- func (q Query[T]) Chunk(ctx context.Context, size int, fn func([]T) error) error
- func (q Query[T]) Count(ctx context.Context) (int64, error)
- func (q Query[T]) CursorPaginate(ctx context.Context, cursor string, perPage int, dir Direction) (CursorPage[T], error)
- func (q Query[T]) Delete(ctx context.Context) (int64, error)
- func (q Query[T]) Each(ctx context.Context) iter.Seq2[T, error]
- func (q Query[T]) Exists(ctx context.Context) (bool, error)
- func (q Query[T]) Find(ctx context.Context, id any) (T, error)
- func (q Query[T]) First(ctx context.Context) (T, error)
- func (q Query[T]) Get(ctx context.Context) ([]T, error)
- func (q Query[T]) Latest() Query[T]
- func (q Query[T]) Limit(n int) Query[T]
- func (q Query[T]) Offset(n int) Query[T]
- func (q Query[T]) OnlyTrashed() Query[T]
- func (q Query[T]) OrWhere(col string, args ...any) Query[T]
- func (q Query[T]) OrderBy(colName string, dir Direction) Query[T]
- func (q Query[T]) Paginate(ctx context.Context, page, perPage int) (Page[T], error)
- func (q Query[T]) Pluck[V any](ctx context.Context, colName string) ([]V, error)
- func (q Query[T]) SQL() (string, []any, error)
- func (q Query[T]) Scope(fns ...func(Query[T]) Query[T]) Query[T]
- func (q Query[T]) Select(cols ...string) Query[T]
- func (q Query[T]) Update(ctx context.Context, set Set) (int64, error)
- func (q Query[T]) Where(col string, args ...any) Query[T]
- func (q Query[T]) WhereBetween(colName string, low, high any) Query[T]
- func (q Query[T]) WhereGroup(fn func(g Query[T]) Query[T]) Query[T]
- func (q Query[T]) WhereIn(col string, values ...any) Query[T]
- func (q Query[T]) WhereNotIn(col string, values ...any) Query[T]
- func (q Query[T]) WhereNotNull(col string) Query[T]
- func (q Query[T]) WhereNull(col string) Query[T]
- func (q Query[T]) WhereRaw(sqlFrag string, args ...any) Query[T]
- func (q Query[T]) With(relations ...string) Query[T]
- func (q Query[T]) WithTrashed() Query[T]
- type Rule
- type Set
- type SoftDeletes
- type Tabler
Constants ¶
This section is empty.
Variables ¶
var ErrConflict error = &conflictError{}
ErrConflict is returned (wrapped) when a write violates a unique constraint; it reports HTTP status 409 so handlers answer Conflict.
var ErrInvalidCursor = &errs.HTTPError{Status: 400, Message: "Invalid pagination cursor."}
ErrInvalidCursor is returned (400) when a cursor cannot be decoded.
var ErrNotFound = &errs.HTTPError{Status: 404, Message: "Record not found."}
ErrNotFound is returned when First/Find match no row. It matches errs.ErrNotFound, so handlers answer 404 by returning it.
var ErrNotLoaded = errors.New("orm: relation not loaded (use Query.With)")
ErrNotLoaded is returned when a relation is read without being eager loaded with Query.With. Relations never query lazily, so there are no hidden N+1 queries (strict mode).
Functions ¶
func Attach ¶ added in v0.4.0
func Attach[T any](ctx context.Context, ex database.Executor, model *T, relation string, ids ...any) error
Attach links model to the related ids through the pivot table of the many-to-many relation (field name). Ids already attached are skipped.
func Detach ¶ added in v0.4.0
func Detach[T any](ctx context.Context, ex database.Executor, model *T, relation string, ids ...any) error
Detach unlinks the related ids, or every related row when ids is empty.
func ForceDelete ¶
ForceDelete removes model even when it uses soft deletes.
func Sync ¶ added in v0.4.0
func Sync[T any](ctx context.Context, ex database.Executor, model *T, relation string, ids ...any) error
Sync makes ids the exact set of related rows (in a transaction): missing ids are attached, others detached.
func Update ¶
Update writes every column of model (except the primary key and created_at) and refreshes updated_at.
Types ¶
type AfterCreater ¶
AfterCreater runs after INSERT.
type AfterUpdater ¶
AfterUpdater runs after UPDATE.
type BeforeCreater ¶
BeforeCreater runs before INSERT.
type BeforeDeleter ¶
BeforeDeleter runs before DELETE (or soft delete).
type BeforeUpdater ¶
BeforeUpdater runs before UPDATE.
type BelongsTo ¶ added in v0.4.0
type BelongsTo[T any] struct { // contains filtered or unexported fields }
BelongsTo is a relation to the model referenced by a foreign key on this model: Post.User with column user_id. Override the column with the tag `orm:"fk:author_id"`.
func (BelongsTo[T]) Get ¶ added in v0.4.0
Get returns the related model, nil when the foreign key points nowhere, or ErrNotLoaded.
func (BelongsTo[T]) IsZero ¶ added in v0.4.0
IsZero makes `json:",omitzero"` drop relations that were not loaded.
func (BelongsTo[T]) MarshalJSON ¶ added in v0.4.0
MarshalJSON encodes the related model (null when none or not loaded).
type CursorPage ¶
type CursorPage[T any] struct { Items []T `json:"data"` PerPage int `json:"per_page"` NextCursor string `json:"next_cursor,omitempty"` }
CursorPage is one page of keyset pagination. NextCursor is empty on the last page.
type HasMany ¶ added in v0.4.0
type HasMany[T any] struct { // contains filtered or unexported fields }
HasMany is a relation to the models holding a foreign key to this one: User.Posts with posts.user_id. Tag: `orm:"fk:author_id"`.
func (HasMany[T]) IsZero ¶ added in v0.4.0
IsZero makes `json:",omitzero"` drop relations that were not loaded.
func (HasMany[T]) MarshalJSON ¶ added in v0.4.0
MarshalJSON encodes the related models ([] when loaded and empty).
type HasOne ¶ added in v0.4.0
type HasOne[T any] struct { // contains filtered or unexported fields }
HasOne is a relation to one model holding a foreign key to this one: User.Profile with profiles.user_id. Tag: `orm:"fk:owner_id"`.
func (HasOne[T]) Get ¶ added in v0.4.0
Get returns the related model (nil when none) or ErrNotLoaded.
func (HasOne[T]) IsZero ¶ added in v0.4.0
IsZero makes `json:",omitzero"` drop relations that were not loaded.
func (HasOne[T]) MarshalJSON ¶ added in v0.4.0
MarshalJSON encodes the related model (null when none or not loaded).
type JSON ¶
type JSON[T any] struct { V T }
JSON stores T as JSON text in one column.
func (JSON[T]) MarshalJSON ¶
MarshalJSON encodes the inner value.
func (*JSON[T]) UnmarshalJSON ¶
UnmarshalJSON decodes into the inner value.
type Keyer ¶
type Keyer interface{ PrimaryKey() string }
Keyer overrides the primary key column (default "id").
type ManyToMany ¶ added in v0.4.0
type ManyToMany[T any] struct { // contains filtered or unexported fields }
ManyToMany relates models through a pivot table: Post.Tags with table post_tag (the two singular table names, sorted) and columns post_id and tag_id. Tag: `orm:"pivot:taggings,fk:post_id,rk:tag_id"`.
func (ManyToMany[T]) Get ¶ added in v0.4.0
func (r ManyToMany[T]) Get() ([]T, error)
Get returns the related models or ErrNotLoaded.
func (ManyToMany[T]) IsZero ¶ added in v0.4.0
func (r ManyToMany[T]) IsZero() bool
IsZero makes `json:",omitzero"` drop relations that were not loaded.
func (ManyToMany[T]) Loaded ¶ added in v0.4.0
func (r ManyToMany[T]) Loaded() bool
Loaded reports whether the relation was eager loaded.
func (ManyToMany[T]) MarshalJSON ¶ added in v0.4.0
func (r ManyToMany[T]) MarshalJSON() ([]byte, error)
MarshalJSON encodes the related models ([] when loaded and empty).
type Model ¶
type Model struct {
ID int64 `db:"id" json:"id"`
CreatedAt time.Time `db:"created_at" json:"created_at"`
UpdatedAt time.Time `db:"updated_at" json:"updated_at"`
}
Model provides an auto-increment id and timestamps; embed it in models.
type NullTime ¶ added in v0.4.0
NullTime is a nullable timestamp that encodes as JSON null or an RFC 3339 time (sql.Null would encode as {"V":...,"Valid":...}).
func (NullTime) IsZero ¶ added in v0.4.0
IsZero reports a NULL value, so `json:",omitzero"` omits it.
func (NullTime) MarshalJSON ¶ added in v0.4.0
MarshalJSON implements json.Marshaler.
func (*NullTime) UnmarshalJSON ¶ added in v0.4.0
UnmarshalJSON implements json.Unmarshaler.
type Page ¶
type Page[T any] struct { Items []T `json:"data"` Total int64 `json:"total"` Page int `json:"page"` PerPage int `json:"per_page"` LastPage int `json:"last_page"` }
Page is one page of results.
type Query ¶
type Query[T any] struct { // contains filtered or unexported fields }
Query builds a SELECT for model T. Builders are values: every method returns a modified copy, so a base query can be shared and refined.
func (Query[T]) Chunk ¶
Chunk processes rows in batches of size ordered by primary key; fn may stop early by returning an error.
func (Query[T]) CursorPaginate ¶
func (q Query[T]) CursorPaginate( ctx context.Context, cursor string, perPage int, dir Direction, ) (CursorPage[T], error)
CursorPaginate returns perPage rows after cursor, ordered by the primary key in dir. Unlike Paginate it needs no COUNT and stays fast and stable on large or changing tables. Pass "" for the first page and the returned NextCursor for the following ones. The query must not have its own OrderBy: the keyset order is the primary key.
func (Query[T]) Delete ¶
Delete removes every matching row (soft-deleting for SoftDeletes models unless WithTrashed/OnlyTrashed was used, which deletes for real) and returns the count.
func (Query[T]) Each ¶
Each streams rows without loading them all; breaking out of the loop closes the rows.
for post, err := range orm.QueryOf[Post](db).Each(ctx) { ... }
func (Query[T]) OnlyTrashed ¶
OnlyTrashed returns only soft-deleted rows.
func (Query[T]) Update ¶
Update sets columns on every matching row and returns the count. updated_at is refreshed unless set explicitly.
func (Query[T]) Where ¶
Where adds "col = value", or "col op value" when three arguments are given: Where("age", ">=", 18). Operators are allow-listed.
func (Query[T]) WhereBetween ¶
WhereBetween adds "col BETWEEN low AND high".
func (Query[T]) WhereGroup ¶
WhereGroup adds a parenthesised group built by fn.
func (Query[T]) WhereNotIn ¶
WhereNotIn adds "col NOT IN (...)". An empty list matches everything.
func (Query[T]) WhereNotNull ¶
WhereNotNull adds "col IS NOT NULL".
func (Query[T]) WhereRaw ¶
WhereRaw adds a raw condition. It must use "?" placeholders for every value; never concatenate input into sql.
func (Query[T]) With ¶ added in v0.4.0
With eager loads relations (by field name) for Get, First, Find, Paginate and CursorPaginate: one query per relation, batched with IN, so N rows cost 1 + len(relations) queries. Each does not eager load.
func (Query[T]) WithTrashed ¶
WithTrashed includes soft-deleted rows.
type Rule ¶ added in v0.4.0
type Rule struct {
// contains filtered or unexported fields
}
Rule is a validation rule that needs the database (unique, exists). z schemas validate shape and format; Rules run after parsing:
in, err := web.Parse(c, requests.RegisterSchema)
if err != nil {
return err
}
if err := orm.Validate(c.Context(), db, orm.Unique[models.User]("email", in.Email)); err != nil {
return err // 422 (or flashed errors for forms), like any validation error
}
func Unique ¶ added in v0.4.0
Unique passes when no row of T has column = value (soft-deleted rows count, as the database constraint does). Use Ignore(id) when updating.
type SoftDeletes ¶
type SoftDeletes struct {
DeletedAt NullTime `db:"deleted_at" json:"deleted_at,omitzero"`
}
SoftDeletes makes Delete set deleted_at instead of removing rows; queries exclude deleted rows unless WithTrashed or OnlyTrashed is used.