model

package
v0.44.1 Latest Latest
Warning

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

Go to latest
Published: Oct 1, 2026 License: MIT Imports: 21 Imported by: 11

Documentation

Overview

Package model holds the Model, its query Builder, its Collection and soft deletes.

There is no dynamic attribute, and that is the whole design

A model here is the application's own struct, and the machinery works over it with a type parameter:

type User struct {
	model.Model[User]

	ID        int64      `db:"id"`
	Name      string     `db:"name"`
	Email     string     `db:"email"`
	CreatedAt time.Time  `db:"created_at"`
	UpdatedAt time.Time  `db:"updated_at"`
	DeletedAt *time.Time `db:"deleted_at"`
}

users := model.Query[User](db)

user, err := users.Where("email", "=", email).First(ctx, g)
if err != nil {
	return err
}
user.Name = "Ada"        // a struct field, checked by the compiler
_, err = user.Save(ctx, g)

The row is the model

A terminal hands back *T -- the application's own struct -- and not a wrapper over it. Reading a column is reading a field, and the methods a row is saved and deleted through are the ones Go promotes out of the embedded Model[T]. Collection is the same thing for a set of rows: []*T.

The embedding is what makes the second half work, and it is worth saying what a T that does not embed Model[T] gets instead. Everything still runs: the query, the hydration, the eager load, the write. What that row cannot do is answer for itself -- there is no field in it pointing at the model that hydrated it, so Save, GetAttribute and the loaded relations are not reachable from the value a terminal returned. The columns are all there, and nothing else is. See ModelOf.

A column is a field. The tag `db:"..."` names it; without a tag the name is the field name in snake case. A field tagged `db:"-"` is not a column.

Reading a relation back, for each of the two shapes

With marks a relation to eager load, and the terminal attaches what it matched to the model behind each row. Reading it back takes the row:

users, err := model.Query[User](db).With("posts").Get(ctx, g)
posts, ok := model.Related[User, Post](users[0], "posts")

and loading one afterwards is Load, promoted out of the embedded model:

err := users[0].Load(ctx, g, "posts")

Both of those reach the model through the row, so both need a T that embeds Model[T]. A T that does not still eager loads -- the query runs, the rows are matched, the relation is attached -- but it is attached to a model beside the row rather than inside it, and no terminal hands that model back. Related answers false there, and Collection.Load, Collection.LoadMissing, Collection.LoadAggregate and Builder.EagerLoadRelations report ErrRowHasNoModel rather than reporting success having loaded onto nothing. The way to read a relation off a row is to give the entity the embedded model.

Query is the entry point when the defaults are right: it works the table out of the type and takes the grammar and the processor off the connection. NewModel is the one to reach for when they are not -- another table, another key, soft deletes, a table with no tenant column -- and the model it returns answers NewQuery.

There is no mass-assignment allowlist, and nothing replaces one

The allowlist already exists and the compiler enforces it: reflection cannot write an unexported field, from any package, ever.

type User struct {
	Name  string `db:"name"`  // Fill sets this
	admin bool   `db:"admin"` // nothing outside the package can, at all
}

So Fill writes the exported fields it finds and drops keys it does not know. ForceFill keeps the unknown keys as raw attributes instead. Neither can reach an unexported field.

The cost is stated plainly: an unexported field is not a column at all, so it is not persisted either. A value that must be stored but must never come from a request is an exported field the caller does not put in the map -- in this framework a request never becomes a model, it becomes a validated struct first.

Every read carries the Grant

All, Find, First, Get, Value, Pluck, Paginate, Chunk and Cursor take an auth.Grant and filter by auth.Tenant(g), exactly as Insert, Update and Delete do. A query builder reached without a Grant compiles SQL and cannot run it, on a read as much as on a write.

What the Grant settles here is the tenant, not the Policy. auth.Authorize is the path a Policy answered on, and it is not the only exported way to obtain a Grant: auth.SystemGrant issues one for work that has no subject. So these methods can be reached holding a Grant nothing was ever asked about, and what reports that is `aru doctor` -- a lint, not the type system.

The tenant filter is on by default and comes off only by naming it: a model whose table has no tenant column sets TenantColumn to the empty string, in its constructor, where a reader sees it. A Grant carrying no tenant is refused with ErrNoTenant before any SQL is built, and ErrNoTenant names which Grants those are.

The tenant written on insert and matched on select is always auth.Tenant(g). A value the caller put in the struct for that column is overwritten: the tenant comes from the Grant and from nowhere else.

What the signatures do NOT carry, and why

There is no context.Context. The connection contract in this component is query.Connection, which takes none; declaring a second, context-carrying connection interface here would be a second way to reach the database. A signature that accepted a context it could not pass on would be worse than one that does not accept it.

Column order on insert

Values reach the grammar as a map, and a Go map has no order, so columns and their bindings go in sorted order on every insert. The grammar must sort identically -- both sides sort by column name, which is the only ordering either side can derive from the values alone.

What is not here

Relations live in model/relations. The Relation interface in relation.go is what this package asks of one, and it is the contract declared there plus the one method the builder needs that the tree does not put in its own interface. It is not a second contract: a relation satisfies it by being a relation, and the constructors in relationsof.go are how an application builds one.

A relation is registered by name in Model.RelationResolvers, in its Unconstrained form -- the builder resolves it from the model it queries through, which carries no key, and narrows it to the batch. relationsof.go says why the split exists.

There is no automatic eager loading. Reading an unloaded relation would run a query behind the caller, and that query carries no auth.Grant. PreventLazyLoading is what is left of the pair, and its doc comment says what it means here.

Index

Constants

View Source
const SoftDeletingScopeName = "SoftDeletingScope"

SoftDeletingScopeName is the identifier the SoftDeletingScope is registered under.

It is a constant rather than a computed name, so that WithTrashed can name the same scope the model registered without holding a reference to it.

Variables

View Source
var ErrEmptyCollection = errors.New("model: unable to create query for empty collection")

ErrEmptyCollection is what ToQuery returns for an empty collection: there is no model to take the table from.

View Source
var ErrJSONEncoding = errors.New("model: json encoding failed")

ErrJSONEncoding is the sentinel every failure to turn a model, an attribute or a resource into JSON wraps.

The wrapping is done by ForModel, ForAttribute and the resource helper below, each of which adds what encoding failed and on which row -- a cast that produced a value encoding/json refuses, an attribute holding something with no JSON form. Callers match on this with errors.Is when they want to answer one status for any encoding failure; the message says which row to look at.

View Source
var ErrLazyLoadingViolation = errors.New("model: attempted to lazy load a relation that is not loaded")

ErrLazyLoadingViolation is what reading a relation that was never loaded means once PreventLazyLoading is on. See PreventLazyLoading for what "lazy loading" can still mean in a framework that has none.

View Source
var ErrMassAssignment = errors.New("model: attribute does not exist on the model")

ErrMassAssignment is what Fill returns once PreventSilentlyDiscardingAttributes is on and a key has no column behind it.

View Source
var ErrMissingAttribute = errors.New("model: attribute does not exist on the model")

ErrMissingAttribute names reading an attribute a persisted model does not have -- a key that is neither a column nor a raw attribute -- once PreventAccessingMissingAttributes is on.

Nothing in this package returns it. GetAttribute returns one value and has no error to return, so the violation is reported through the callback HandleMissingAttributeViolationUsing registered, and this is the error that callback has to report or wrap. Without a callback the read is simply nil, which is why the switch alone changes nothing.

View Source
var ErrMixedQueueableConnections = errors.New("model: queueing collections with multiple model connections is not supported")

ErrMixedQueueableConnections is what GetQueueableConnection returns for a queued collection whose models are not all on one connection: it cannot be restored, because the job records one connection name.

View Source
var ErrModelNotFound = relations.ErrModelNotFound

ErrModelNotFound is what FindOrFail and its neighbours return when no row matched.

It is returned wrapped with the table and the ids that were looked for, so errors.Is keeps working and the message still says which row:

fmt.Errorf("%w: users [7]", ErrModelNotFound)

It is a sentinel and not a type for the reason database.ErrNotFound is one: a caller comparing against it should not have to name a struct. It is the same value as relations.ErrModelNotFound, so a failing read on a relation matches it too, and errors.Is answers true for database.ErrRecordNotFound as well -- a routing layer that answers a missing row with 404 answers this one the same way.

View Source
var ErrMultipleRecordsFound = errors.New("model: multiple records found")

ErrMultipleRecordsFound is what Sole returns when the query matched more than one row.

View Source
var ErrNamedScopeNotFound = errors.New("model: call to undefined named scope")

ErrNamedScopeNotFound is what CallNamedScope reports for a scope the model never registered.

A scope is an entry in Model.NamedScopes, so the miss is a missing key rather than a missing method.

View Source
var ErrNoKey = errors.New("model: no primary key defined on model")

ErrNoKey is what Delete returns when the model has no primary key defined.

View Source
var ErrNoTenant = errors.New("model: the grant carries no tenant, and a query without one reads every tenant (call auth.Authorize, or auth.SystemGrant with the tenant this work belongs to)")

ErrNoTenant is what every executing method returns when the Grant carries no tenant.

auth.Tenant reads the empty string off three different Grants: the zero one, whose caller never authorized anything; the one auth.SystemGrant returns when it is asked for no tenant; and the one it returns when the tenant it was asked for cannot be one. They are three different mistakes, and this error does not tell them apart -- auth.Grant.Check does, because a refused system grant carries the reason it was refused.

What they share is the consequence, which is why one error covers all three: a query with no tenant in its where clause reads every customer of the system. So it does not run.

View Source
var ErrRelationNotFound = errors.New("model: call to undefined relationship")

ErrRelationNotFound is returned when a query names a relation the model never registered: an eager load of "author" on a model that declares no author, a nested path whose second segment does not exist on the model the first segment reaches, or a route binding through a relation that is not there.

Wrapping it always carries the name and the table, because the useful part of the message is which relation was asked for on which model. It is a programming mistake rather than a runtime condition -- the fix is the declaration, not a retry -- so callers usually let it travel rather than matching on it.

View Source
var ErrRowHasNoModel = errors.New("model: these rows carry no model to load a relation onto (embed Model[T] in the entity, or read the rows back through a query)")

ErrRowHasNoModel is what a relation load reports when the rows it was handed carry no model to attach anything to.

A relation is attached to the model behind a row, and a row reaches its model only when T embeds Model[T]: a plain struct has no field to point back with, and a struct written as a literal has a zero model inside it. Loading onto either would run the query, match the rows and attach the result to nothing, so it says so instead -- a silent success here is a relation the next line reads and does not find.

It is wrapped with how many of the rows were unreachable, because one literal in a collection of hydrated rows and a collection that is entirely the plain shape are different mistakes.

View Source
var ErrUnwired = errors.New("model: this value was not built by the framework, so it has no connection to save through -- make one with Model.NewInstance, insert one with Builder.Create, or read one back with Find, First or Get")

ErrUnwired is a terminal called on an entity the framework did not build.

A struct written as a literal has a zero Model[T] inside it: no connection, no back pointer to itself, no table. It is the one difference a Laravel developer meets at this layer, because in PHP $this is free and here it is not, so the error says what to do rather than what went wrong.

The three it names are the three ways to get a wired row: an empty one from the model, a stored one from the query, and one read back. It named a fourth, Create(ctx, g, entity), and no such call exists -- Create takes the columns as a map, never a struct the caller already filled.

Functions

func BelongsTo

func BelongsTo(child, related relations.Model, foreignKey, ownerKey, relation string) *relations.BelongsTo

BelongsTo returns a belongs-to relation from child to related.

relation is the name the relation is read under, and it is required rather than guessed from the call site. It is not decoration: associate and dissociate write the loaded relation under it.

func BelongsToMany

func BelongsToMany(parent, related relations.Model, table, foreignPivotKey, relatedPivotKey, parentKey, relatedKey, relation string) *relations.BelongsToMany

BelongsToMany returns a many-to-many relation from parent to related.

func BelongsToManyOf

func BelongsToManyOf[P, C any](parent *Model[P], related *Model[C], table, foreignPivotKey, relatedPivotKey, parentKey, relatedKey, relation string) *relations.BelongsToMany

BelongsToManyOf returns a many-to-many from parent to related.

An empty table is the conventional intermediate name, which is the two table names in the singular, sorted, joined by an underscore.

func BelongsToManyOfUnconstrained added in v0.18.0

func BelongsToManyOfUnconstrained[P, C any](parent *Model[P], related *Model[C], table, foreignPivotKey, relatedPivotKey, parentKey, relatedKey, relation string) *relations.BelongsToMany

BelongsToManyOfUnconstrained is BelongsToManyOf without the narrowing to parent. The join onto the intermediate table stays: it is how the related table is reached at all, for one parent or for a hundred.

func BelongsToModel

func BelongsToModel[C, P any](child *Model[C], related *Model[P], foreignKey, ownerKey, relation string) *relations.BelongsTo

BelongsToModel returns a belongs-to from child to related.

The relation name is what the error message says when the key is missing, so it is the name the method has: "user", not "userRelation".

func BelongsToModelUnconstrained added in v0.18.0

func BelongsToModelUnconstrained[C, P any](child *Model[C], related *Model[P], foreignKey, ownerKey, relation string) *relations.BelongsTo

BelongsToModelUnconstrained is BelongsToModel without the narrowing to the row child points at.

func ChildRouteBindingRelationshipName

func ChildRouteBindingRelationshipName(childType string) string

ChildRouteBindingRelationshipName returns the relation a scoped nested resource resolves through: the child type, camel cased and pluralised.

func CountBy

func CountBy[T any, K comparable](c Collection[T], countBy func(row *T, key int) K) map[K]int

CountBy counts the rows by the key countBy returns for each one.

It is a function rather than a method because the result is a map keyed by K, not a Collection[T]: the key type is the caller's, and Go names it in the signature rather than discovering it at run time.

func ForAttribute

func ForAttribute(table, key, message string) error

ForAttribute returns the ErrJSONEncoding wrapped with the model's table and the attribute's key, when it is an attribute that failed to encode.

func ForModel

func ForModel(table string, key any, message string) error

ForModel returns the ErrJSONEncoding wrapped with the model's table and key, when it is the model itself that failed to encode.

It is a package function because a Go error value has no type to hang a constructor method on, and it names the model by its table rather than its type: the table is what a reader of the log can look up.

func ForResource

func ForResource(resource, table string, key any, message string) error

ForResource returns the ErrJSONEncoding wrapped with the resource's name, the model's table and key, when it is a resource that failed to encode.

A resource here is a value an http handler names, and http/resources does not reach into this package, so the resource arrives as its own name rather than a value this package reads it from.

func GetMorphs

func GetMorphs(name, typ, id string) (string, string)

GetMorphs returns the pair of column names a polymorphic relation reads.

func HandleDiscardedAttributeViolationUsing

func HandleDiscardedAttributeViolationUsing(callback func(model any, keys []string))

HandleDiscardedAttributeViolationUsing registers what to do about a discarded-attribute violation.

A registered callback replaces the error: Fill reports the keys to it and then succeeds.

func HandleLazyLoadingViolationUsing

func HandleLazyLoadingViolationUsing(callback func(model any, key string))

HandleLazyLoadingViolationUsing registers what to do about a lazy loading violation.

GetRelation cannot return an error without becoming a second way to read a relation, so the callback is where the violation goes. An application that wants it to be loud registers one:

model.HandleLazyLoadingViolationUsing(func(model any, key string) {
	panic(fmt.Sprintf("%v: %s was not loaded", model, key))
})

A nil callback unregisters it.

func HandleMissingAttributeViolationUsing

func HandleMissingAttributeViolationUsing(callback func(model any, key string))

HandleMissingAttributeViolationUsing registers what to do about a missing-attribute violation.

GetAttribute returns one value and has no error to return, so the callback is the whole of the switch; see HandleLazyLoadingViolationUsing.

func HasMany

func HasMany(parent, related relations.Model, foreignKey, localKey string) *relations.HasMany

HasMany returns a has-many relation from parent to related.

func HasManyOf

func HasManyOf[P, C any](parent *Model[P], related *Model[C], foreignKey, localKey string) *relations.HasMany

HasManyOf returns a has-many from parent to related.

func HasManyOfUnconstrained added in v0.18.0

func HasManyOfUnconstrained[P, C any](parent *Model[P], related *Model[C], foreignKey, localKey string) *relations.HasMany

HasManyOfUnconstrained is HasManyOf without the narrowing to parent.

func HasManyThrough

func HasManyThrough(farParent, through, related relations.Model, firstKey, secondKey, localKey, secondLocalKey string) *relations.HasManyThrough

HasManyThrough returns a has-many-through relation from farParent to related.

func HasManyThroughOf

func HasManyThroughOf[F, T2, C any](farParent *Model[F], through *Model[T2], related *Model[C], firstKey, secondKey, localKey, secondLocalKey string) *relations.HasManyThrough

HasManyThroughOf returns a has-many-through from farParent to related, by way of through.

func HasManyThroughOfUnconstrained added in v0.18.0

func HasManyThroughOfUnconstrained[F, T2, C any](farParent *Model[F], through *Model[T2], related *Model[C], firstKey, secondKey, localKey, secondLocalKey string) *relations.HasManyThrough

HasManyThroughOfUnconstrained is HasManyThroughOf without the narrowing to farParent. The join onto the intermediate table stays, for the reason BelongsToManyOfUnconstrained keeps its own.

func HasOne

func HasOne(parent, related relations.Model, foreignKey, localKey string) *relations.HasOne

HasOne returns a has-one relation from parent to related.

An empty foreignKey defaults to the parent's conventional foreign key, user_id for a model whose alias is user.

func HasOneOf

func HasOneOf[P, C any](parent *Model[P], related *Model[C], foreignKey, localKey string) *relations.HasOne

HasOneOf returns a has-one from parent to related.

func HasOneOfUnconstrained added in v0.18.0

func HasOneOfUnconstrained[P, C any](parent *Model[P], related *Model[C], foreignKey, localKey string) *relations.HasOne

HasOneOfUnconstrained is HasOneOf without the narrowing to parent.

func HasOneThrough

func HasOneThrough(farParent, through, related relations.Model, firstKey, secondKey, localKey, secondLocalKey string) *relations.HasOneThrough

HasOneThrough returns a has-one-through relation from farParent to related.

func HasOneThroughOf

func HasOneThroughOf[F, T2, C any](farParent *Model[F], through *Model[T2], related *Model[C], firstKey, secondKey, localKey, secondLocalKey string) *relations.HasOneThrough

HasOneThroughOf returns a has-one-through from farParent to related, by way of through.

func HasOneThroughOfUnconstrained added in v0.18.0

func HasOneThroughOfUnconstrained[F, T2, C any](farParent *Model[F], through *Model[T2], related *Model[C], firstKey, secondKey, localKey, secondLocalKey string) *relations.HasOneThrough

HasOneThroughOfUnconstrained is HasOneThroughOf without the narrowing to farParent.

func JoiningTable

func JoiningTable(parent, related relations.Model) string

JoiningTable returns the conventional name of an intermediate table, the two model names in alphabetical order.

Alphabetical is what makes it the same table from both sides -- role_user whether you start at the user or at the role -- and it is why a many-to-many declared on both models needs no configuration at all.

func Map

func Map[T, R any](c Collection[T], callback func(row *T, key int) R) collections.Collection[R]

Map returns the result of calling callback on every row, as a collections.Collection[R].

The compiler decides the result type: a callback returning *T gives back exactly what Collection[T] holds, and any other R gives a collection of R.

func MapWithKeys

func MapWithKeys[T any, K comparable, V any](c Collection[T], callback func(row *T, key int) (K, V)) map[K]V

MapWithKeys returns the key/value pairs callback returns for every row, as a map. See Map for how the value type is decided.

func MorphMany

func MorphMany(parent, related relations.Model, name, typ, id, localKey string) *relations.MorphMany

MorphMany returns a morph-many relation from parent to related.

func MorphManyOf

func MorphManyOf[P, C any](parent *Model[P], related *Model[C], name, typ, id, localKey string) *relations.MorphMany

MorphManyOf returns a morph-many from parent to related.

func MorphManyOfUnconstrained added in v0.18.0

func MorphManyOfUnconstrained[P, C any](parent *Model[P], related *Model[C], name, typ, id, localKey string) *relations.MorphMany

MorphManyOfUnconstrained is MorphManyOf without the narrowing to parent.

func MorphOne

func MorphOne(parent, related relations.Model, name, typ, id, localKey string) *relations.MorphOne

MorphOne returns a morph-one relation from parent to related.

func MorphOneOf

func MorphOneOf[P, C any](parent *Model[P], related *Model[C], name, typ, id, localKey string) *relations.MorphOne

MorphOneOf returns a morph-one from parent to related.

func MorphOneOfUnconstrained added in v0.18.0

func MorphOneOfUnconstrained[P, C any](parent *Model[P], related *Model[C], name, typ, id, localKey string) *relations.MorphOne

MorphOneOfUnconstrained is MorphOneOf without the narrowing to parent.

func MorphTo

func MorphTo(parent, related relations.Model, name, typ, id, ownerKey string) *relations.MorphTo

MorphTo returns a morph-to relation on parent.

related only carries a connection to start from, and is replaced per type once the type column is read: Go needs something concrete to start with, where the query is otherwise built fresh per type.

func MorphToMany

func MorphToMany(parent, related relations.Model, name, table, foreignPivotKey, relatedPivotKey, parentKey, relatedKey, relation string, inverse bool) *relations.MorphToMany

MorphToMany returns a polymorphic many-to-many relation from parent to related.

func MorphToManyOf

func MorphToManyOf[P, C any](parent *Model[P], related *Model[C], name, table, foreignPivotKey, relatedPivotKey, parentKey, relatedKey, relation string, inverse bool) *relations.MorphToMany

MorphToManyOf returns a polymorphic many-to-many from parent to related.

func MorphToManyOfUnconstrained added in v0.18.0

func MorphToManyOfUnconstrained[P, C any](parent *Model[P], related *Model[C], name, table, foreignPivotKey, relatedPivotKey, parentKey, relatedKey, relation string, inverse bool) *relations.MorphToMany

MorphToManyOfUnconstrained is MorphToManyOf without the narrowing to parent.

func MorphToOf

func MorphToOf[P, C any](parent *Model[P], related *Model[C], name, typ, id, ownerKey string) *relations.MorphTo

MorphToOf returns a morph-to on parent.

related is the model whose connection the relation starts from; the table it finally reads is resolved from the type column, through the morph map.

func MorphToOfUnconstrained added in v0.18.0

func MorphToOfUnconstrained[P, C any](parent *Model[P], related *Model[C], name, typ, id, ownerKey string) *relations.MorphTo

MorphToOfUnconstrained is MorphToOf without the narrowing to parent.

func MorphedByMany

func MorphedByMany(parent, related relations.Model, name, table, foreignPivotKey, relatedPivotKey, parentKey, relatedKey, relation string) *relations.MorphToMany

MorphedByMany returns the other side of a MorphToMany, where this model is what the intermediate table points at.

func MorphedByManyOf

func MorphedByManyOf[P, C any](parent *Model[P], related *Model[C], name, table, foreignPivotKey, relatedPivotKey, parentKey, relatedKey, relation string) *relations.MorphToMany

MorphedByManyOf returns the other side of a MorphToManyOf.

func MorphedByManyOfUnconstrained added in v0.18.0

func MorphedByManyOfUnconstrained[P, C any](parent *Model[P], related *Model[C], name, table, foreignPivotKey, relatedPivotKey, parentKey, relatedKey, relation string) *relations.MorphToMany

MorphedByManyOfUnconstrained is MorphedByManyOf without the narrowing to parent.

func PreventAccessingMissingAttributes

func PreventAccessingMissingAttributes(value ...bool)

PreventAccessingMissingAttributes turns reading an absent attribute into a reported violation.

GetAttribute returns nil for a key that is neither a column nor a raw attribute. With this on, the read is reported through the callback HandleMissingAttributeViolationUsing registered.

Most of what this would catch the compiler catches first: a row is a struct, and found.Entity.Naem does not build. What is left is the read by name -- GetAttribute takes a string -- which is where a typo still survives to run time.

func PreventLazyLoading

func PreventLazyLoading(value ...bool)

PreventLazyLoading turns reading an unloaded relation into a reported violation.

What it does not do

It does not stop a query, because there is no query to stop: nothing here loads a relation behind the caller, since such a query would carry no auth.Grant.

What is left is reading a relation that is not there. With the switch off, GetRelation returns false and a caller that ignored the second value reads a nil it will blame on the database. With it on, the read is reported through the callback HandleLazyLoadingViolationUsing registered.

A model that does not exist yet, and one that was just created, are exempt -- they have nothing to have loaded.

func PreventSilentlyDiscardingAttributes

func PreventSilentlyDiscardingAttributes(value ...bool)

PreventSilentlyDiscardingAttributes changes what Fill does with a key that has no column behind it: dropped when this is off, refused with ErrMassAssignment when it is on.

func PreventsAccessingMissingAttributes

func PreventsAccessingMissingAttributes() bool

PreventsAccessingMissingAttributes reports whether PreventAccessingMissingAttributes is on.

func PreventsLazyLoading

func PreventsLazyLoading() bool

PreventsLazyLoading reports whether PreventLazyLoading is on.

func PreventsSilentlyDiscardingAttributes

func PreventsSilentlyDiscardingAttributes() bool

PreventsSilentlyDiscardingAttributes reports whether PreventSilentlyDiscardingAttributes is on.

func ResolveChildRouteBinding

func ResolveChildRouteBinding[T, R any](ctx context.Context, parent *Model[T], children *Builder[R], g auth.Grant, childType string, value any, field string) (*R, error)

ResolveChildRouteBinding returns the row a URL segment stands for, restricted to the children of parent.

It is a function and not a method because the child is a model of another type, and a method on Model[T] cannot name Model[R]. childType names the relation on the parent; children is that relation as the typed query it stands for, because the Relation a resolver hands back is narrowed to what the eager loader asks of it and cannot produce a Builder[R]. That is the same reason RelationResolvers exists: Go makes no type from a string.

The Grant is filtered on by First, on the child's own table. A nested resource scoped only by its parent is still a row somebody else owns when the parent was reached without a tenant filter, so both ends carry it.

func ResolveSoftDeletableChildRouteBinding

func ResolveSoftDeletableChildRouteBinding[T, R any](ctx context.Context, parent *Model[T], children *Builder[R], g auth.Grant, childType string, value any, field string) (*R, error)

ResolveSoftDeletableChildRouteBinding is the same lookup as ResolveChildRouteBinding, trashed rows included.

func ShouldBeStrict

func ShouldBeStrict(shouldBeStrict ...bool)

ShouldBeStrict turns the three switches below on or off together.

value is variadic because Go has no default argument; an empty call turns them all on.

func WithoutTouching

func WithoutTouching[T any](callback func() error) error

WithoutTouching suspends touch propagation for T for the length of callback: a relation whose owner would have had its updated_at bumped is left alone until callback returns.

It is a package-level generic function rather than a method, because no model instance is needed -- only the type identifies which relations to suspend.

func WithoutTouchingOn

func WithoutTouchingOn(models []reflect.Type, callback func() error) error

WithoutTouchingOn suspends touch propagation for every type in models for the length of callback, restoring the previous list via defer once callback returns -- even when it returns an error.

It takes reflect.Type values rather than name strings, because Go cannot reach a type from a name in a string.

func Zip

func Zip[T any](c Collection[T], items ...[]*T) collections.Collection[collections.Collection[*T]]

Zip pairs up c with each of items, position by position.

It is a function and not a method for the reason collections.Zip is one: the result no longer holds rows, which the return type already says.

Types

type BelongsToRelation

type BelongsToRelation interface {
	Relation

	// GetQualifiedForeignKeyName returns the foreign key column on this
	// table, qualified with the table name.
	GetQualifiedForeignKeyName() string

	// GetOwnerKeyName returns the column on the related table the foreign
	// key points at.
	GetOwnerKeyName() string
}

BelongsToRelation is the part of a relation that WhereBelongsTo needs: the column on this table, and the column on the other one it points at.

type Builder

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

Builder is the query builder that hands back models instead of rows.

Everything that runs takes an auth.Grant and filters by auth.Tenant(g) -- reads exactly like writes. Everything that only builds does not: a Where or an OrderBy is a fragment of SQL, and a fragment authorizes nothing.

func NewBuilder

func NewBuilder[T any](q *query.Builder) *Builder[T]

NewBuilder creates a Builder for q. The model is attached afterward, through SetModel.

func Query added in v0.17.0

func Query[T any](db DB) *Builder[T]

Query returns a query over T, on db, with T's global scopes registered.

user, err := model.Query[User](db).Where("email", "=", email).First(ctx, g)

What it does that NewModel(table, connection, grammar, processor).NewQuery() does not is refuse to be told what is already known. The table is the name of T, pluralised and snake cased, which is what GetTable falls back to; the grammar and the processor are the connection's own. NewModel cannot work any of the three out, because all three arrive as arguments -- and an argument that repeats what a value already carries is an argument that can disagree with it.

NewModel stays for the model that is not the default: another table, another key, a table with no tenant column, soft deletes. Configure that model once, keep it, and ask it for its query.

It does not take a Grant, and that is the decision rather than an omission. Every terminal takes one already, because authorization belongs to the statement that runs and not to the sentence that builds it: a Grant held on the builder would be a second place a tenant could come from, and the two could differ. A builder authorizes nothing, which is why it can be built, stored and passed around without one.

func WhereBelongsTo

func WhereBelongsTo[T, R any](b *Builder[T], relationshipName string, related ...*Model[R]) *Builder[T]

WhereBelongsTo adds a filter requiring the named belongs-to relation to point at one of related.

It is a function and not a method because the related models are of another type and a Go method cannot introduce a type parameter.

func (*Builder[T]) AddSelect

func (b *Builder[T]) AddSelect(columns ...any) *Builder[T]

AddSelect adds columns to the ones already selected.

func (*Builder[T]) AfterQuery

func (b *Builder[T]) AfterQuery(callback func(Collection[T]) Collection[T]) *Builder[T]

AfterQuery registers a callback run on the result of Get, allowed to replace it.

It runs wherever a Collection is handed to a caller: Get, Chunk and the walks built on it, and the terminals that are a Get with a limit -- First, Sole, Find and their neighbours, whose row the callback sees as a result of one.

It does not run on the reads this package makes for itself, and the reason is the signature. A callback takes rows and answers rows, and a row is not a way back to the model behind it, so a read whose next step is on the model cannot pass through one: the row Refresh reads back into a model it already holds, the rows Destroy loads in order to delete them, the aggregate LoadCount fills in, everything the relation tree reads through its own seam. Nor does it run on a paginator, whose page is not a Collection but items of whatever type the page was asked for.

func (*Builder[T]) Aggregate

func (b *Builder[T]) Aggregate(ctx context.Context, g auth.Grant, function string, columns ...any) (any, error)

Aggregate runs function (count, sum, min, max, avg) over columns and returns the result.

func (*Builder[T]) ApplyAfterQueryCallbacks

func (b *Builder[T]) ApplyAfterQueryCallbacks(result Collection[T]) Collection[T]

ApplyAfterQueryCallbacks runs the registered AfterQuery callbacks over result in order, threading each callback's replacement into the next.

func (*Builder[T]) ApplyScopes

func (b *Builder[T]) ApplyScopes() *Builder[T]

ApplyScopes returns a copy of the builder with every registered scope applied.

The wheres a scope adds are wrapped in a group when either side carries an or: without that, a scope's filter joins an or chain and stops filtering.

func (*Builder[T]) Avg

func (b *Builder[T]) Avg(ctx context.Context, g auth.Grant, column any) (any, error)

Avg returns the average of column over the matching rows.

func (*Builder[T]) Chunk

func (b *Builder[T]) Chunk(ctx context.Context, g auth.Grant, count int, callback func(Collection[T], int) (bool, error)) error

Chunk walks the query count rows at a time, calling callback for each chunk.

The callback stops the walk by returning false, and stops it with a reason by returning an error.

func (*Builder[T]) ChunkById

func (b *Builder[T]) ChunkById(ctx context.Context, g auth.Grant, count int, callback func(Collection[T], int) (bool, error), column ...string) error

ChunkById walks the query count rows at a time, ordered and paged by the key rather than by offset, so that a row inserted between two chunks cannot shift the window and hide a row.

func (*Builder[T]) Clone

func (b *Builder[T]) Clone() *Builder[T]

Clone returns a copy of b, safe to mutate independently.

func (*Builder[T]) Count

func (b *Builder[T]) Count(ctx context.Context, g auth.Grant, columns ...any) (int64, error)

Count returns the row count of the query, or the count of columns when given.

func (*Builder[T]) Create

func (b *Builder[T]) Create(ctx context.Context, g auth.Grant, attributes map[string]any) (*T, error)

Create returns a new model, filled with attributes and saved.

A tenant in attributes is ignored, as Fill ignores it. The row is written with the Grant's tenant, and the entity returned carries that tenant.

func (*Builder[T]) CreateOrFirst

func (b *Builder[T]) CreateOrFirst(ctx context.Context, g auth.Grant, attributes, values map[string]any) (*T, error)

CreateOrFirst inserts a row from attributes and values, and if a unique index says somebody got there first, reads theirs instead.

Nothing here classifies a driver error yet, so any insert failure sends it looking for the row, and the insert error is returned when there is none -- which keeps the race safe and never swallows a real failure.

func (*Builder[T]) CreateOrRestore

func (b *Builder[T]) CreateOrRestore(ctx context.Context, g auth.Grant, attributes, values map[string]any) (*T, error)

CreateOrRestore finds the first row matching attributes, including trashed, and restores it if trashed; otherwise it creates one from attributes and values.

func (*Builder[T]) Cursor

func (b *Builder[T]) Cursor(ctx context.Context, g auth.Grant, err *error) func(func(*T) bool)

Cursor walks the result one model at a time.

query.Connection hands back the rows it read, so this is a walk over one result set rather than a second way to run a query -- and it stays lazy from the caller's side, which is what the method is for.

func (*Builder[T]) CursorPaginate

func (b *Builder[T]) CursorPaginate(ctx context.Context, g auth.Grant, perPage int, cursor *pagination.Cursor, opts pagination.Options, columns ...any) (*pagination.CursorPaginator[*T], error)

CursorPaginate returns the page after (or before) a boundary named by cursor rather than by offset.

It is the paginator to reach for past the first few pages: ForPage makes the engine count and discard every row it skips, and a row inserted between two requests shifts the boundary so that a row is never shown.

cursor is nil for the first page. The columns the query orders by are the cursor's parameters, so every one of them has to be selected -- the cursor is built out of the rows that come back.

func (*Builder[T]) Decrement

func (b *Builder[T]) Decrement(ctx context.Context, g auth.Grant, column string, amount any, extra map[string]any) (int64, error)

Decrement subtracts amount from column, plus any extra columns to set, and returns the number of rows affected.

func (*Builder[T]) DefaultKeyName

func (b *Builder[T]) DefaultKeyName() string

DefaultKeyName returns the model's primary key name.

func (*Builder[T]) Delete

func (b *Builder[T]) Delete(ctx context.Context, g auth.Grant) (int64, error)

Delete removes the rows matching the query, or runs the registered onDelete callback instead.

A model that soft deletes has an onDelete callback on its builder, so this runs the update the SoftDeletingScope registered instead of a delete.

func (*Builder[T]) DoesntExist

func (b *Builder[T]) DoesntExist(ctx context.Context, g auth.Grant) (bool, error)

DoesntExist reports whether the query matches no rows.

It is Exists read the other way round, and it exists as its own method for the reason the base builder has one: `if !exists` after a call that also returns an error reads as a mistake even when it is not.

func (*Builder[T]) DoesntHave

func (b *Builder[T]) DoesntHave(relation, boolean string, callback func(*query.Builder)) *Builder[T]

DoesntHave adds a filter requiring relation to not exist.

func (*Builder[T]) Each

func (b *Builder[T]) Each(ctx context.Context, g auth.Grant, count int, callback func(*T, int) (bool, error)) error

Each walks the query count rows at a time, calling callback for every row with its overall index.

func (*Builder[T]) EagerLoadRelations

func (b *Builder[T]) EagerLoadRelations(ctx context.Context, g auth.Grant, rows Collection[T]) error

EagerLoadRelations loads every top-level relation marked with With, and attaches the matches to the model behind each row.

A row reaches its model only when T embeds Model[T], so rows that carry none are ErrRowHasNoModel: attaching a relation to nothing and reporting success would be a load the next line cannot read back.

func (*Builder[T]) Exists

func (b *Builder[T]) Exists(ctx context.Context, g auth.Grant) (bool, error)

Exists reports whether the query matches any row.

func (*Builder[T]) FillAndInsert

func (b *Builder[T]) FillAndInsert(ctx context.Context, g auth.Grant, values []map[string]any) (bool, error)

FillAndInsert runs FillForInsert and inserts the result.

func (*Builder[T]) FillAndInsertGetID

func (b *Builder[T]) FillAndInsertGetID(ctx context.Context, g auth.Grant, values map[string]any) (int64, error)

FillAndInsertGetID runs FillForInsert for one row, inserts it, and returns the value generated for the primary key.

func (*Builder[T]) FillAndInsertOrIgnore

func (b *Builder[T]) FillAndInsertOrIgnore(ctx context.Context, g auth.Grant, values []map[string]any) (bool, error)

FillAndInsertOrIgnore runs FillForInsert and inserts the result, dropping rows that violate a unique index.

func (*Builder[T]) FillForInsert

func (b *Builder[T]) FillForInsert(values []map[string]any) ([]map[string]any, error)

FillForInsert returns the rows, enriched with whatever the model would have put on them -- its defaults and its timestamps -- without making a model per row on the way to the database.

It is the path a seeder and an importer take: one statement for a thousand rows, with the columns a save would have written -- the generated key of a model that uses unique ids among them.

func (*Builder[T]) Find

func (b *Builder[T]) Find(ctx context.Context, g auth.Grant, id any, columns ...any) (*T, error)

Find returns the row with the given primary key, or the rows for a slice of keys.

func (*Builder[T]) FindMany

func (b *Builder[T]) FindMany(ctx context.Context, g auth.Grant, ids []any, columns ...any) (Collection[T], error)

FindMany returns the rows matching any of ids.

func (*Builder[T]) FindOrFail

func (b *Builder[T]) FindOrFail(ctx context.Context, g auth.Grant, id any, columns ...any) (*T, error)

FindOrFail returns the row with the given primary key, or an error when there is none.

Given a list it also fails when one id is missing: asking for three rows and getting two is not a shorter result, it is a wrong one.

func (*Builder[T]) FindOrNew

func (b *Builder[T]) FindOrNew(ctx context.Context, g auth.Grant, id any, columns ...any) (*T, error)

FindOrNew returns the row with the given primary key, or a new unsaved model when there is none.

func (*Builder[T]) FindSole

func (b *Builder[T]) FindSole(ctx context.Context, g auth.Grant, id any, columns ...any) (*T, error)

FindSole returns the row with the given primary key, and fails unless it is the only one.

func (*Builder[T]) First

func (b *Builder[T]) First(ctx context.Context, g auth.Grant, columns ...any) (*T, error)

First returns the first row matching the query, or (nil, nil) when there is none: no row is not a failure, and FirstOrFail is the spelling for when it is.

func (*Builder[T]) FirstOr

func (b *Builder[T]) FirstOr(ctx context.Context, g auth.Grant, callback func() (*T, error), columns ...any) (*T, error)

FirstOr returns the first row matching the query, or what callback makes when there is none.

func (*Builder[T]) FirstOrCreate

func (b *Builder[T]) FirstOrCreate(ctx context.Context, g auth.Grant, attributes, values map[string]any) (*T, error)

FirstOrCreate returns the first row matching attributes, or creates and returns one from attributes and values when there is none.

func (*Builder[T]) FirstOrFail

func (b *Builder[T]) FirstOrFail(ctx context.Context, g auth.Grant, columns ...any) (*T, error)

FirstOrFail returns the first row matching the query, or an error when there is none.

func (*Builder[T]) FirstOrNew

func (b *Builder[T]) FirstOrNew(ctx context.Context, g auth.Grant, attributes, values map[string]any) (*T, error)

FirstOrNew returns the first row matching attributes, or a new unsaved model built from attributes and values when there is none.

func (*Builder[T]) FirstWhere

func (b *Builder[T]) FirstWhere(ctx context.Context, g auth.Grant, column any, args ...any) (*T, error)

FirstWhere adds a where clause and returns the first matching row.

func (*Builder[T]) ForPage

func (b *Builder[T]) ForPage(page, perPage int) *Builder[T]

ForPage sets the limit and offset for page, perPage rows at a time.

func (*Builder[T]) ForceCreate

func (b *Builder[T]) ForceCreate(ctx context.Context, g auth.Grant, attributes map[string]any) (*T, error)

ForceCreate returns a new model, filled with attributes via ForceFill and saved.

There is no mass-assignment guard to turn off (see the package comment); what ForceCreate keeps from Create is ForceFill's behavior: an attribute the entity does not declare is carried through as a raw attribute instead of dropped.

func (*Builder[T]) ForceDelete

func (b *Builder[T]) ForceDelete(ctx context.Context, g auth.Grant) (int64, error)

ForceDelete removes the row, whatever the scope would have done.

It still applies the tenant filter. "Force" is about the soft delete, never about the tenant.

func (*Builder[T]) FromQuery

func (b *Builder[T]) FromQuery(ctx context.Context, g auth.Grant, sql string, bindings []any) (Collection[T], error)

FromQuery returns models from SQL somebody wrote by hand.

It takes the Grant like every other read. The SQL is the caller's, so the tenant cannot be added to it -- which is exactly why the Grant is still required: a query nobody authorized does not run, and the where clause that scopes it is the caller's to write.

func (*Builder[T]) Get

func (b *Builder[T]) Get(ctx context.Context, g auth.Grant, columns ...any) (Collection[T], error)

Get runs the query and returns the matching models, with their eager loads applied.

func (*Builder[T]) GetCountForPagination

func (b *Builder[T]) GetCountForPagination(ctx context.Context, g auth.Grant) (int64, error)

GetCountForPagination returns the row count of the query, ignoring its order, limit and offset.

The orders, the limit and the offset come off before the count, because a count with a limit on it counts the page rather than the result set.

func (*Builder[T]) GetEagerLoads

func (b *Builder[T]) GetEagerLoads() []string

GetEagerLoads returns the names marked to eager load, sorted.

func (*Builder[T]) GetLimit

func (b *Builder[T]) GetLimit() *int

GetLimit returns the row limit, or nil when none is set.

func (*Builder[T]) GetModel

func (b *Builder[T]) GetModel() *Model[T]

GetModel returns the model this builder queries.

func (*Builder[T]) GetModels

func (b *Builder[T]) GetModels(ctx context.Context, g auth.Grant, columns ...any) (Collection[T], error)

GetModels returns the rows, hydrated, with nothing eager loaded.

It is already prepared when Get calls it; called on its own it prepares itself, so there is no way to reach the rows without the Grant.

func (*Builder[T]) GetOffset

func (b *Builder[T]) GetOffset() *int

GetOffset returns the row offset, or nil when none is set.

func (*Builder[T]) GetQuery

func (b *Builder[T]) GetQuery() *query.Builder

GetQuery returns the underlying query.Builder.

func (*Builder[T]) GetRelationWithoutConstraints

func (b *Builder[T]) GetRelationWithoutConstraints(name string) (Relation, error)

GetRelationWithoutConstraints resolves relation by calling its registered resolver.

What comes back must not be narrowed to one parent, and it is the resolver that promises so: this is called with the model the builder queries through, which for a list query is a prototype carrying no key at all. The Unconstrained constructors in relationsof.go are what a resolver registers, and the note at the top of that file is why.

func (*Builder[T]) GroupBy

func (b *Builder[T]) GroupBy(groups ...any) *Builder[T]

GroupBy groups the rows.

func (*Builder[T]) Has

func (b *Builder[T]) Has(relation, operator string, count int, boolean string, callback func(*query.Builder)) *Builder[T]

Has adds a filter on the count of relation matching operator and count: relation exists (">=" 1), does not exist ("<" 1), or any other comparison.

Go has no default arguments, so the full form is spelled out here and the short ones -- WhereHas, DoesntHave, OrHas -- are the ones to reach for. callback may be nil.

func (*Builder[T]) HasNamedScope

func (b *Builder[T]) HasNamedScope(scope string) bool

HasNamedScope reports whether the builder's model has a scope registered under this name.

func (*Builder[T]) Hydrate

func (b *Builder[T]) Hydrate(items []query.Record) (Collection[T], error)

Hydrate turns rows into models: rows in, models out.

func (*Builder[T]) Increment

func (b *Builder[T]) Increment(ctx context.Context, g auth.Grant, column string, amount any, extra map[string]any) (int64, error)

Increment adds amount to column, plus any extra columns to set, and returns the number of rows affected.

func (*Builder[T]) IncrementOrCreate

func (b *Builder[T]) IncrementOrCreate(ctx context.Context, g auth.Grant, attributes map[string]any, column string, def, step any) (*T, error)

IncrementOrCreate returns the row matching attributes with column set to def, or increments column by step on the row that was already there.

func (*Builder[T]) Insert

func (b *Builder[T]) Insert(ctx context.Context, g auth.Grant, values ...map[string]any) (bool, error)

Insert writes values as new rows.

The tenant column is written from the Grant on every row, overwriting whatever the caller put there: the tenant comes from the Grant and from nowhere else.

func (*Builder[T]) InsertGetID

func (b *Builder[T]) InsertGetID(ctx context.Context, g auth.Grant, values map[string]any, sequence string) (int64, error)

InsertGetID inserts values as one new row and returns the value generated for sequence.

func (*Builder[T]) InsertOrIgnore

func (b *Builder[T]) InsertOrIgnore(ctx context.Context, g auth.Grant, values ...map[string]any) (bool, error)

InsertOrIgnore writes values as new rows, dropping the ones that violate a unique index rather than failing the statement.

func (*Builder[T]) Join

func (b *Builder[T]) Join(table any, first any, args ...any) *Builder[T]

Join adds an inner join.

func (*Builder[T]) Latest

func (b *Builder[T]) Latest(column ...string) *Builder[T]

Latest orders the query by column, or the model's created-at column when none is given, newest first.

func (*Builder[T]) Lazy

func (b *Builder[T]) Lazy(ctx context.Context, g auth.Grant, chunkSize int, err *error) func(func(*T) bool)

Lazy returns an iterator over the rows, fetched a chunk at a time.

It returns a range-over-func iterator directly. The error the walk stopped on is reported through the pointer the caller passes, because an iterator has nowhere else to put one.

func (*Builder[T]) LazyById

func (b *Builder[T]) LazyById(ctx context.Context, g auth.Grant, chunkSize int, err *error, column ...string) func(func(*T) bool)

LazyById returns an iterator over the rows, fetched a chunk at a time and paged by the key rather than by offset.

func (*Builder[T]) Limit

func (b *Builder[T]) Limit(value int) *Builder[T]

Limit sets the row limit, forwarded to the underlying query.

func (*Builder[T]) Max

func (b *Builder[T]) Max(ctx context.Context, g auth.Grant, column any) (any, error)

Max returns the largest value of column over the matching rows.

func (*Builder[T]) Min

func (b *Builder[T]) Min(ctx context.Context, g auth.Grant, column any) (any, error)

Min returns the smallest value of column over the matching rows.

func (*Builder[T]) NewModelInstance

func (b *Builder[T]) NewModelInstance(attributes map[string]any) (*Model[T], error)

NewModelInstance returns a new, unsaved model of the builder's type, with attributes merged over any pending attributes from WithAttributes.

func (*Builder[T]) Offset

func (b *Builder[T]) Offset(value int) *Builder[T]

Offset sets the row offset, forwarded to the underlying query.

func (*Builder[T]) Oldest

func (b *Builder[T]) Oldest(column ...string) *Builder[T]

Oldest orders the query by column, or the model's created-at column when none is given, oldest first.

func (*Builder[T]) OnClone

func (b *Builder[T]) OnClone(callback func(*Builder[T])) *Builder[T]

OnClone registers a callback that runs on every copy this builder makes.

func (*Builder[T]) OnDelete

func (b *Builder[T]) OnDelete(callback func(context.Context, *Builder[T], auth.Grant) (int64, error)) *Builder[T]

OnDelete registers callback as the override Delete runs instead of a plain delete.

func (*Builder[T]) OnlyTrashed

func (b *Builder[T]) OnlyTrashed() *Builder[T]

OnlyTrashed restricts the query to soft-deleted rows only.

func (*Builder[T]) OrDoesntHave

func (b *Builder[T]) OrDoesntHave(relation string) *Builder[T]

OrDoesntHave is DoesntHave joined with or.

func (*Builder[T]) OrHas

func (b *Builder[T]) OrHas(relation, operator string, count int) *Builder[T]

OrHas is Has joined with or.

func (*Builder[T]) OrWhere

func (b *Builder[T]) OrWhere(column any, args ...any) *Builder[T]

OrWhere adds an or-where clause. Passing a func(*Builder[T]) instead of a column name adds a group built by calling that function with a fresh builder.

func (*Builder[T]) OrWhereDoesntHave

func (b *Builder[T]) OrWhereDoesntHave(relation string, callback func(*query.Builder)) *Builder[T]

OrWhereDoesntHave is WhereDoesntHave joined with or.

func (*Builder[T]) OrWhereHas

func (b *Builder[T]) OrWhereHas(relation string, callback func(*query.Builder)) *Builder[T]

OrWhereHas is WhereHas joined with or.

func (*Builder[T]) OrWhereNot

func (b *Builder[T]) OrWhereNot(column any, args ...any) *Builder[T]

OrWhereNot adds an or-joined, negated group: OR NOT (column args...).

func (*Builder[T]) OrWhereRelation

func (b *Builder[T]) OrWhereRelation(relation string, column any, args ...any) *Builder[T]

OrWhereRelation is WhereRelation joined with or.

func (*Builder[T]) OrderBy

func (b *Builder[T]) OrderBy(column any, direction ...string) *Builder[T]

OrderBy adds an ordering, forwarded to the underlying query.

func (*Builder[T]) OrderByDesc

func (b *Builder[T]) OrderByDesc(column any) *Builder[T]

OrderByDesc adds a descending ordering, forwarded to the underlying query.

func (*Builder[T]) Paginate

func (b *Builder[T]) Paginate(ctx context.Context, g auth.Grant, perPage, page int, opts pagination.Options, columns ...any) (*pagination.LengthAwarePaginator[*T], error)

Paginate runs the query for one page and returns a length-aware paginator.

The page number is an argument: no request is reachable from here, and the caller reads it with pagination.ResolveCurrentPage.

perPage of zero means the model's own.

func (*Builder[T]) Pluck

func (b *Builder[T]) Pluck(ctx context.Context, g auth.Grant, column string) ([]any, error)

Pluck returns one column of every row matching the query.

func (*Builder[T]) Qualify

func (b *Builder[T]) Qualify(column string) string

Qualify returns column qualified with the model's table.

func (*Builder[T]) Ref

func (b *Builder[T]) Ref() concerns.Builder

Ref returns b as the builder a relation takes.

func (*Builder[T]) RemovedScopes

func (b *Builder[T]) RemovedScopes() []string

RemovedScopes returns the identifiers of the scopes removed from this builder.

func (*Builder[T]) Restore

func (b *Builder[T]) Restore(ctx context.Context, g auth.Grant) (int64, error)

Restore un-deletes every row the query matches, in one statement.

func (*Builder[T]) RestoreOrCreate

func (b *Builder[T]) RestoreOrCreate(ctx context.Context, g auth.Grant, attributes, values map[string]any) (*T, error)

RestoreOrCreate finds the first trashed-or-not row matching attributes and restores it, or creates one from attributes and values if none matches.

func (*Builder[T]) Scopes

func (b *Builder[T]) Scopes(scopes ...string) *Builder[T]

Scopes applies the named scopes in order, each one's wheres grouped so that an or inside a scope cannot escape it.

This takes only names; CallNamedScope takes the parameters -- a scope that needs arguments is called directly rather than listed here.

func (*Builder[T]) Select

func (b *Builder[T]) Select(columns ...any) *Builder[T]

Select sets the columns to return, forwarded to the underlying query.

func (*Builder[T]) SelectRaw

func (b *Builder[T]) SelectRaw(expression string, bindings ...any) *Builder[T]

SelectRaw adds a raw select expression, forwarded to the underlying query.

func (*Builder[T]) SetEagerLoads

func (b *Builder[T]) SetEagerLoads(relations ...string) *Builder[T]

SetEagerLoads replaces the eager load list with relations.

func (*Builder[T]) SetModel

func (b *Builder[T]) SetModel(model *Model[T]) *Builder[T]

SetModel attaches model to b, and points the query at its table.

func (*Builder[T]) SetQuery

func (b *Builder[T]) SetQuery(q *query.Builder) *Builder[T]

SetQuery replaces the underlying query.Builder.

func (*Builder[T]) SimplePaginate

func (b *Builder[T]) SimplePaginate(ctx context.Context, g auth.Grant, perPage, page int, opts pagination.Options, columns ...any) (*pagination.Paginator[*T], error)

SimplePaginate returns one page and whether there is another, without the count.

func (*Builder[T]) Sole

func (b *Builder[T]) Sole(ctx context.Context, g auth.Grant, columns ...any) (*T, error)

Sole returns the row matching the query, and fails unless it is the only one.

func (*Builder[T]) SoleValue

func (b *Builder[T]) SoleValue(ctx context.Context, g auth.Grant, column string) (any, error)

SoleValue returns one column of the row matching the query, and fails unless it is the only one.

func (*Builder[T]) Sum

func (b *Builder[T]) Sum(ctx context.Context, g auth.Grant, column any) (any, error)

Sum returns the sum of column over the matching rows.

func (*Builder[T]) ToBase

func (b *Builder[T]) ToBase(ctx context.Context, g auth.Grant) (*query.Builder, error)

ToBase returns the underlying query.Builder with the scopes applied.

It takes the Grant because applying the scopes is also where the tenant filter goes on, and a base builder handed out without it is a query somebody will run.

func (*Builder[T]) Touch

func (b *Builder[T]) Touch(ctx context.Context, g auth.Grant, column ...string) (int64, error)

Touch sets column, or the model's updated-at column when none is given, to the current time on every row matching the query.

func (*Builder[T]) TouchQuietly

func (b *Builder[T]) TouchQuietly(ctx context.Context, g auth.Grant, column ...string) (touched int64, err error)

TouchQuietly touches the query's rows without firing model events.

func (*Builder[T]) Update

func (b *Builder[T]) Update(ctx context.Context, g auth.Grant, values map[string]any) (int64, error)

Update runs an UPDATE with values over the query as it stands, and returns the number of rows affected.

A value under the model's tenant column, spelled any way query.NamesColumn recognises, is replaced by the Grant's tenant rather than written: the row stays with the tenant whose Grant reached it. It holds for Save, Increment and every other write that ends here.

func (*Builder[T]) UpdateOrCreate

func (b *Builder[T]) UpdateOrCreate(ctx context.Context, g auth.Grant, attributes, values map[string]any) (*T, error)

UpdateOrCreate finds or creates a row matching attributes, then fills it with values and saves it.

func (*Builder[T]) Upsert

func (b *Builder[T]) Upsert(ctx context.Context, g auth.Grant, values []map[string]any, uniqueBy, update []string) (int64, error)

Upsert inserts values, updating the columns in update on any row that conflicts on uniqueBy. With no update columns given, every column is updated.

func (*Builder[T]) UseWritePDO

func (b *Builder[T]) UseWritePDO() *Builder[T]

UseWritePDO points this builder's statement at the write connection, even though it reads.

It is how a read that has to see what was just written avoids the replica lag that would otherwise make a fresh row look missing.

func (*Builder[T]) Value

func (b *Builder[T]) Value(ctx context.Context, g auth.Grant, column string) (any, error)

Value returns one column of the first row matching the query.

func (*Builder[T]) ValueOrFail

func (b *Builder[T]) ValueOrFail(ctx context.Context, g auth.Grant, column string) (any, error)

ValueOrFail returns one column of the first row matching the query, or an error when there is none.

func (*Builder[T]) Where

func (b *Builder[T]) Where(column any, args ...any) *Builder[T]

Where adds a where clause. Passing a func(*Builder[T]) instead of a column name adds a group built by calling that function with a fresh builder.

func (*Builder[T]) WhereBetween

func (b *Builder[T]) WhereBetween(column any, from, to any) *Builder[T]

WhereBetween adds a where-between clause.

func (*Builder[T]) WhereColumn

func (b *Builder[T]) WhereColumn(first any, args ...any) *Builder[T]

WhereColumn compares two columns.

func (*Builder[T]) WhereDoesntHave

func (b *Builder[T]) WhereDoesntHave(relation string, callback func(*query.Builder)) *Builder[T]

WhereDoesntHave adds a filter requiring relation to not exist, constrained by callback.

func (*Builder[T]) WhereDoesntHaveRelation

func (b *Builder[T]) WhereDoesntHaveRelation(relation string, column any, args ...any) *Builder[T]

WhereDoesntHaveRelation adds a filter requiring relation to have no row matching column args.

func (*Builder[T]) WhereExists

func (b *Builder[T]) WhereExists(callback func(*query.Builder)) *Builder[T]

WhereExists adds a where-exists clause built by callback.

The callback takes the base builder rather than this one, and that is not an oversight: the subquery of an exists names another table, so a builder typed on T would be the wrong type for it. Has and WhereHas are the typed way to ask the same question about a relation, and they are what most callers want.

func (*Builder[T]) WhereHas

func (b *Builder[T]) WhereHas(relation string, callback func(*query.Builder)) *Builder[T]

WhereHas adds a filter requiring relation to exist, constrained by callback.

func (*Builder[T]) WhereHasCount

func (b *Builder[T]) WhereHasCount(relation string, callback func(*query.Builder), operator string, count int) *Builder[T]

WhereHasCount is WhereHas with an explicit operator and count instead of ">=" and 1.

func (*Builder[T]) WhereIn

func (b *Builder[T]) WhereIn(column any, values []any) *Builder[T]

WhereIn adds a where-in clause.

func (*Builder[T]) WhereKey

func (b *Builder[T]) WhereKey(id any) *Builder[T]

WhereKey filters by the model's primary key. A slice of ids adds a WHERE IN instead of an equality.

func (*Builder[T]) WhereKeyNot

func (b *Builder[T]) WhereKeyNot(id any) *Builder[T]

WhereKeyNot excludes the model's primary key. A slice of ids adds a WHERE NOT IN instead of an inequality.

func (*Builder[T]) WhereMorphRelation

func (b *Builder[T]) WhereMorphRelation(relation string, types []string, column any, args ...any) *Builder[T]

WhereMorphRelation adds a filter requiring relation, constrained to any of types, to have a row matching column args.

Go cannot make a type from a string, so the relation resolves its own types through RelationForMorphType, one branch per type, ored together.

A single type of "*" is refused rather than resolved: resolving it means selecting the distinct morph column first, which is a query, and this method builds SQL and runs nothing.

func (*Builder[T]) WhereNot

func (b *Builder[T]) WhereNot(column any, args ...any) *Builder[T]

WhereNot adds a where clause wrapped in a negated group: NOT (column args...).

func (*Builder[T]) WhereNotExists

func (b *Builder[T]) WhereNotExists(callback func(*query.Builder)) *Builder[T]

WhereNotExists adds a where-not-exists clause built by callback.

func (*Builder[T]) WhereNotIn

func (b *Builder[T]) WhereNotIn(column any, values []any) *Builder[T]

WhereNotIn adds a where-not-in clause.

func (*Builder[T]) WhereNotNull

func (b *Builder[T]) WhereNotNull(columns ...any) *Builder[T]

WhereNotNull adds a where-not-null clause for each column named.

func (*Builder[T]) WhereNull

func (b *Builder[T]) WhereNull(columns ...any) *Builder[T]

WhereNull adds a where-null clause for each column named.

func (*Builder[T]) WhereRelation

func (b *Builder[T]) WhereRelation(relation string, column any, args ...any) *Builder[T]

WhereRelation adds a filter requiring relation to have a row matching column args.

func (*Builder[T]) With

func (b *Builder[T]) With(relations ...string) *Builder[T]

With marks relations to eager load.

func (*Builder[T]) WithAggregate

func (b *Builder[T]) WithAggregate(relations []string, column, function string) *Builder[T]

WithAggregate adds a subselect per relation, aliased onto the row.

A name may carry an alias -- "posts as recent_posts".

The subselect goes in through query.SelectSub, and the exists form through query.SelectExistsSub, which are the methods that record a subquery so the tenant can be put on it when the Grant arrives. This used to write the column itself, with AddSelect and SelectRaw over sub.ToSQL(), and a subquery compiled into a raw column is a subquery nothing can scope: Users.WithCount("posts").Get(auth.SystemGrant( "user.list", "acme")) gave every row the number of posts EVERY tenant had, and WithSum("orders", "total") handed one tenant another tenant's revenue as a scalar.

func (*Builder[T]) WithAttributes

func (b *Builder[T]) WithAttributes(attributes map[string]any, asConditions ...bool) *Builder[T]

WithAttributes records values that filter the query and then fill whatever the query creates.

asConditions defaults to true; passing false keeps the values for NewModelInstance without adding the where clauses -- which is what a relation does with a foreign key it already constrained another way.

There is no separate single-column form: a map with one entry is that call.

func (*Builder[T]) WithAvg

func (b *Builder[T]) WithAvg(relation, column string) *Builder[T]

WithAvg adds the average of column over relation, aliased onto the row.

func (*Builder[T]) WithConstraints

func (b *Builder[T]) WithConstraints(relation string, constraints func(*query.Builder)) *Builder[T]

WithConstraints marks relation to eager load, constrained by callback.

The callback takes a query.Builder rather than a Builder[T], because the relation it constrains belongs to another model type and this signature has no type parameter to spell that other model's builder with.

func (*Builder[T]) WithCount

func (b *Builder[T]) WithCount(relations ...string) *Builder[T]

WithCount adds the row count of each relation, aliased onto the row.

func (*Builder[T]) WithExists

func (b *Builder[T]) WithExists(relation string) *Builder[T]

WithExists adds whether relation exists, aliased onto the row.

func (*Builder[T]) WithGlobalScope

func (b *Builder[T]) WithGlobalScope(identifier string, scope Scope[T]) *Builder[T]

WithGlobalScope registers scope under identifier, and extends b immediately if scope implements ScopeExtender.

func (*Builder[T]) WithMax

func (b *Builder[T]) WithMax(relation, column string) *Builder[T]

WithMax adds the max of column over relation, aliased onto the row.

func (*Builder[T]) WithMin

func (b *Builder[T]) WithMin(relation, column string) *Builder[T]

WithMin adds the min of column over relation, aliased onto the row.

func (*Builder[T]) WithOnly

func (b *Builder[T]) WithOnly(relations ...string) *Builder[T]

WithOnly replaces the eager load list with relations.

func (*Builder[T]) WithSavepointIfNeeded

func (b *Builder[T]) WithSavepointIfNeeded(scope func() error) error

WithSavepointIfNeeded runs scope inside a savepoint when a transaction is already open, and plainly when none is.

query.Connection does not declare a transaction level -- see Transactor for why this component does not widen it -- so the capability is asked for by a type assertion, and a connection that does not implement it runs the callback as if no transaction were open, the same as a level of zero.

func (*Builder[T]) WithSum

func (b *Builder[T]) WithSum(relation, column string) *Builder[T]

WithSum adds the sum of column over relation, aliased onto the row.

func (*Builder[T]) WithTrashed

func (b *Builder[T]) WithTrashed(withTrashed ...bool) *Builder[T]

WithTrashed includes the soft-deleted rows in the query.

On a model that does not soft delete it is an error rather than a query that quietly means something else.

func (*Builder[T]) Without

func (b *Builder[T]) Without(relations ...string) *Builder[T]

Without removes relations from the eager load list.

func (*Builder[T]) WithoutEagerLoad

func (b *Builder[T]) WithoutEagerLoad(relations ...string) *Builder[T]

WithoutEagerLoad removes these relations, and the ones nested under them, from the eager load list.

func (*Builder[T]) WithoutEagerLoads

func (b *Builder[T]) WithoutEagerLoads() *Builder[T]

WithoutEagerLoads clears the eager load list.

func (*Builder[T]) WithoutGlobalScope

func (b *Builder[T]) WithoutGlobalScope(identifier string) *Builder[T]

WithoutGlobalScope removes the scope registered under identifier, and records it as removed.

func (*Builder[T]) WithoutGlobalScopes

func (b *Builder[T]) WithoutGlobalScopes(identifiers ...string) *Builder[T]

WithoutGlobalScopes removes the named scopes. With no argument it removes them all.

func (*Builder[T]) WithoutGlobalScopesExcept

func (b *Builder[T]) WithoutGlobalScopesExcept(identifiers ...string) *Builder[T]

WithoutGlobalScopesExcept removes every registered scope except the named ones.

func (*Builder[T]) WithoutTrashed

func (b *Builder[T]) WithoutTrashed() *Builder[T]

WithoutTrashed removes the global soft-delete scope and re-adds an explicit not-deleted filter, so trashed rows stay excluded even after the scope is gone.

type Collection

type Collection[T any] []*T

Collection is the rows a query came back with.

The items are the application's own struct -- the same value a terminal hands back one of -- so reading a column off one is reading a field, and there is nothing to unwrap first.

The general collection vocabulary lives in hesape/collections, and ToBase converts to it. Only the methods that know the items are rows of a table are here: keyed by their key, reloaded from their table, hidden and appended per row. Those go through the model behind each row, so they need a T that embeds Model[T] -- see models for what a T that does not gets instead.

func Related[T, R any](row *T, name string) (Collection[R], bool)

Related reads a loaded relation off the row, as the type it was loaded as.

The relation was loaded as rows of another type, which Go cannot spell as a field, so the read is a generic function rather than a method:

posts, ok := model.Related[User, Post](user, "posts")

It reports false when the relation was not loaded, when it was loaded as something else, and when the row carries no model to read it off.

That last one is the shape of T. A T that embeds Model[T] carries its model inside itself, so the relation an eager load attached is on the row this takes. A T that does not has no field pointing back: the relation is on a model beside the row, which no terminal hands back, so there is nothing here to read and the answer is false rather than a dereference of nothing.

func (Collection[T]) All

func (c Collection[T]) All() []*T

All returns the rows as a plain slice.

func (Collection[T]) Append

func (c Collection[T]) Append(attributes ...string) Collection[T]

Append calls Model.Append on every row.

func (Collection[T]) Contains

func (c Collection[T]) Contains(key any) bool

Contains reports whether key -- a key value, or a row to compare by key -- matches one of the rows.

func (Collection[T]) Count

func (c Collection[T]) Count() int

Count returns the number of rows.

func (Collection[T]) Diff

func (c Collection[T]) Diff(items Collection[T]) Collection[T]

Diff returns the rows that are not in items.

func (Collection[T]) DoesntContain

func (c Collection[T]) DoesntContain(key any) bool

DoesntContain reports the opposite of Contains.

func (Collection[T]) Except

func (c Collection[T]) Except(keys ...any) Collection[T]

Except returns the rows without these keys.

func (Collection[T]) Find

func (c Collection[T]) Find(key any) *T

Find returns the row with this key, out of the ones already in hand.

func (Collection[T]) FindOrFail

func (c Collection[T]) FindOrFail(key any) (*T, error)

FindOrFail returns the row with this key, or an error when none matches.

func (Collection[T]) First

func (c Collection[T]) First() *T

First returns the first row, or nil when there is none.

func (Collection[T]) Flatten

func (c Collection[T]) Flatten(depth ...int) collections.Collection[any]

Flatten returns the same rows as a collection of any.

The rows are the leaves -- a row is not a list -- so flattening them changes only the element type. The depth is optional and unlimited when omitted.

func (Collection[T]) Flip

func (c Collection[T]) Flip() map[*T]int

Flip returns the rows as keys and their positions as values.

The keys of a Collection[T] are positions, so flipping gives row to position, and a row that repeats keeps the last position.

func (Collection[T]) Fresh

func (c Collection[T]) Fresh(ctx context.Context, g auth.Grant, with ...string) (Collection[T], error)

Fresh returns the same rows, read again.

A row that has since been deleted drops out of the result.

func (Collection[T]) GetDictionary

func (c Collection[T]) GetDictionary() map[any]*T

GetDictionary returns the rows keyed by their key, which is how every set operation here compares them.

func (Collection[T]) GetQueueableClass

func (c Collection[T]) GetQueueableClass() string

GetQueueableClass returns the type name of the models being queued.

A Collection[T] cannot hold two model types, so there is no mixed-type case left to refuse.

It returns the empty string for an empty collection: there is no model to take the name from.

func (Collection[T]) GetQueueableConnection

func (c Collection[T]) GetQueueableConnection() (string, error)

GetQueueableConnection returns the connection name shared by every model, or ErrMixedQueueableConnections when they disagree. An empty collection returns the empty string.

func (Collection[T]) GetQueueableIDs

func (c Collection[T]) GetQueueableIDs() []any

GetQueueableIDs returns the queueable id of every model.

func (Collection[T]) GetQueueableRelations

func (c Collection[T]) GetQueueableRelations() []string

GetQueueableRelations returns the relations every model in the collection has loaded.

It is the intersection and not the union: a relation loaded on one row and not on another cannot be restored for the whole collection.

func (Collection[T]) Intersect

func (c Collection[T]) Intersect(items Collection[T]) Collection[T]

Intersect returns the rows that are also in items.

func (Collection[T]) IsEmpty

func (c Collection[T]) IsEmpty() bool

IsEmpty reports whether there are no rows.

func (Collection[T]) IsNotEmpty

func (c Collection[T]) IsNotEmpty() bool

IsNotEmpty reports the opposite of IsEmpty.

func (Collection[T]) Load

func (c Collection[T]) Load(ctx context.Context, g auth.Grant, relations ...string) error

Load eager loads these relations onto every row.

It reaches the model behind each row, so it needs a T that embeds Model[T]; rows that carry no model are ErrRowHasNoModel rather than a load that quietly attaches nothing.

func (Collection[T]) LoadAggregate

func (c Collection[T]) LoadAggregate(ctx context.Context, g auth.Grant, relations []string, column, function string) error

LoadAggregate loads function over column of each relation onto every row. See Load for the shape of T it needs.

func (Collection[T]) LoadAvg

func (c Collection[T]) LoadAvg(ctx context.Context, g auth.Grant, relations []string, column string) error

LoadAvg loads the average of column over each relation onto every row.

func (Collection[T]) LoadCount

func (c Collection[T]) LoadCount(ctx context.Context, g auth.Grant, relations ...string) error

LoadCount loads the count of each relation onto every row.

func (Collection[T]) LoadExists

func (c Collection[T]) LoadExists(ctx context.Context, g auth.Grant, relations ...string) error

LoadExists loads whether each relation exists onto every row.

func (Collection[T]) LoadMax

func (c Collection[T]) LoadMax(ctx context.Context, g auth.Grant, relations []string, column string) error

LoadMax loads the max of column over each relation onto every row.

func (Collection[T]) LoadMin

func (c Collection[T]) LoadMin(ctx context.Context, g auth.Grant, relations []string, column string) error

LoadMin loads the min of column over each relation onto every row.

func (Collection[T]) LoadMissing

func (c Collection[T]) LoadMissing(ctx context.Context, g auth.Grant, relations ...string) error

LoadMissing eager loads these relations onto every row, skipping the ones already loaded. See Load for the shape of T it needs.

func (Collection[T]) LoadMorph

func (c Collection[T]) LoadMorph(ctx context.Context, g auth.Grant, relation string, relations map[string][]string) error

LoadMorph eager loads relation for every model in the collection, per the target row's own type.

A []MorphLoadable cannot become a Collection[R] -- R is only known at run time -- so the load runs per row instead: the same rows, more queries. A caller on a hot path loads the concrete relation by its type instead, where the grouping is the compiler's.

func (Collection[T]) LoadMorphCount

func (c Collection[T]) LoadMorphCount(ctx context.Context, g auth.Grant, relation string, relations map[string][]string) error

LoadMorphCount loads the count of relation for every model in the collection. See LoadMorph for why it is one query per row rather than one per class.

func (Collection[T]) LoadSum

func (c Collection[T]) LoadSum(ctx context.Context, g auth.Grant, relations []string, column string) error

LoadSum loads the sum of column over each relation onto every row.

func (Collection[T]) MakeHidden

func (c Collection[T]) MakeHidden(attributes ...string) Collection[T]

MakeHidden calls Model.MakeHidden on every row.

func (Collection[T]) MakeVisible

func (c Collection[T]) MakeVisible(attributes ...string) Collection[T]

MakeVisible calls Model.MakeVisible on every row.

func (Collection[T]) Merge

func (c Collection[T]) Merge(items Collection[T]) Collection[T]

Merge returns the other rows added, with a key that is already here replaced rather than repeated.

func (Collection[T]) ModelKeys

func (c Collection[T]) ModelKeys() []any

ModelKeys returns the primary key of every row.

func (Collection[T]) Only

func (c Collection[T]) Only(keys ...any) Collection[T]

Only returns the rows with these keys.

func (Collection[T]) Pad

func (c Collection[T]) Pad(size int, value *T) collections.Collection[*T]

Pad returns the rows padded with value to size elements.

A positive size pads on the right, a negative size on the left, and a size no larger than the count returns the rows unchanged.

func (Collection[T]) Partition

func (c Collection[T]) Partition(callback func(row *T, key int) bool) (passed, failed collections.Collection[*T])

Partition returns the rows passing callback, then the ones failing it.

func (Collection[T]) Pluck

func (c Collection[T]) Pluck(column string) []any

Pluck returns one attribute of every row, as a slice of any: the value is whatever that column holds.

func (Collection[T]) Push

func (c Collection[T]) Push(ctx context.Context, g auth.Grant) (bool, error)

Push calls Model.Push on every row, which is what makes a loaded relation pushable.

func (Collection[T]) SetAppends

func (c Collection[T]) SetAppends(appends ...string) Collection[T]

SetAppends calls Model.SetAppends on every row.

func (Collection[T]) SetHidden

func (c Collection[T]) SetHidden(hidden ...string) Collection[T]

SetHidden calls Model.SetHidden on every row.

func (Collection[T]) SetVisible

func (c Collection[T]) SetVisible(visible ...string) Collection[T]

SetVisible calls Model.SetVisible on every row.

func (Collection[T]) ToArray

func (c Collection[T]) ToArray() []map[string]any

ToArray returns every row, serialised.

func (Collection[T]) ToBase

func (c Collection[T]) ToBase() collections.Collection[*T]

ToBase returns the rows as a collections.Collection.

func (Collection[T]) ToQuery

func (c Collection[T]) ToQuery() (*Builder[T], error)

ToQuery returns a query over exactly these rows.

A Go collection cannot hold two model types, so the only refusal is the empty one -- with no row there is no table to query.

func (Collection[T]) Unique

func (c Collection[T]) Unique() Collection[T]

Unique returns one row per key, the first one seen.

type DB added in v0.17.0

type DB interface {
	query.Connection

	// GetQueryGrammar returns the grammar statements are compiled through.
	GetQueryGrammar() query.Grammar

	// GetPostProcessor returns the processor results are read back through.
	GetPostProcessor() query.Processor
}

DB is a connection that also carries the grammar its statements compile through and the processor their results are read back through.

query.Connection is the five verbs and nothing else, on purpose: it is the contract a driver implements. The two below are not a driver's job, they are what a connection already knows about itself -- which grammar quotes its identifiers, which processor reads an inserted key back -- and asking for them by assertion is how Go spells a capability a narrow contract does not carry. Transactor is the same shape for the same reason.

type Event

type Event string

Event is one of the model events HasEvents fires.

A callback is registered on the model that will fire it, and NewInstance carries the registrations onto every instance made from that model.

const (
	Retrieved     Event = "retrieved"
	Creating      Event = "creating"
	Created       Event = "created"
	Updating      Event = "updating"
	Updated       Event = "updated"
	Saving        Event = "saving"
	Saved         Event = "saved"
	Deleting      Event = "deleting"
	Deleted       Event = "deleted"
	Trashed       Event = "trashed"
	Restoring     Event = "restoring"
	Restored      Event = "restored"
	ForceDeleting Event = "forceDeleting"
	ForceDeleted  Event = "forceDeleted"
	Replicating   Event = "replicating"
)

The events Model fires, in the order it fires them.

There is no booting event: a Go value has no class initialisation to hook.

type Model

type Model[T any] struct {
	// Entity is the row: a struct the compiler checks, not a dynamic map.
	//
	// It is a pointer rather than a value, and that is what lets the
	// application's own struct embed the model instead of being wrapped by it.
	// A Model[User] holding a User inside a User that holds a Model[User] is a
	// type of infinite size, and Go refuses it by name: "invalid recursive
	// type". A pointer makes the same shape finite.
	//
	// It is what a terminal hands back, and for a T that embeds Model[T] it
	// points at the value this model is inside of: the two are one allocation
	// seen from two sides. The model always has one -- NewModel and NewInstance
	// both allocate it -- so it is never nil on a model this package built.
	Entity *T

	// Table is the table name. Empty means the snake-cased plural of the type
	// name, which is what GetTable falls back to.
	Table string

	// PrimaryKey is the name of the primary key column.
	PrimaryKey string

	// KeyType is the primary key's type ("int", "string", etc.), recorded so
	// a model whose key is a uuid says so here rather than in a comment.
	KeyType string

	// Incrementing says whether the primary key is generated by the database
	// on insert.
	Incrementing bool

	// Timestamps says whether Save stamps created_at and updated_at.
	Timestamps bool

	// SoftDeletes says whether the SoftDeletingScope is registered: a
	// boolean field rather than a marker type, and NewQuery registers the
	// scope when it is set.
	SoftDeletes bool

	// TenantColumn is the column every statement is scoped by, from
	// auth.Tenant(g). It defaults to tenant_id.
	//
	// The empty string turns the scoping off, and it is spelled out at the
	// construction of the model so that a reader sees a table that is deliberately
	// global rather than a filter somebody forgot.
	//
	// It turns off the filter and nothing else. Every query method still takes
	// an auth.Grant and still refuses without one, so a table declared global is
	// a table every tenant may be allowed to read -- not a table reachable
	// without authorization. The two are separate guarantees and only this one
	// is configurable.
	TenantColumn string

	// PerPage is the default page size for Paginate.
	PerPage int

	// ConnectionName is the name of the connection this model uses.
	ConnectionName string

	// CreatedAtColumn, UpdatedAtColumn and DeletedAtColumn name the timestamp
	// columns Save and soft delete write to.
	CreatedAtColumn string
	UpdatedAtColumn string
	DeletedAtColumn string

	// Exists says whether the row exists in the database.
	Exists bool

	// WasRecentlyCreated says whether this model was created by the current
	// request.
	WasRecentlyCreated bool

	Grammar   query.Grammar
	Processor query.Processor

	// RelationResolvers holds one entry per relation, registered by name. Go
	// cannot look a method up by name and stay type safe, so With, Load, Has
	// and WithCount resolve relations through this map instead.
	RelationResolvers map[string]func(*Model[T]) Relation

	// NamedScopes holds one entry per scope, registered under its bare name.
	// Go cannot look a method up by name, so Scopes and CallNamedScope
	// resolve through this map instead.
	NamedScopes map[string]NamedScope[T]
	// contains filtered or unexported fields
}

Model is one row of a table, over the application's own struct.

T is that struct, and its exported fields are the columns. The configuration fields below are copied onto every instance NewInstance makes.

They are exported because a Go value has no subtype to override them in. Read the package comment before setting TenantColumn to the empty string.

func ModelOf added in v0.15.1

func ModelOf[T any](entity *T) *Model[T]

ModelOf returns the model embedded in entity.

It is how a caller holding the application's own struct reaches the model's configuration -- the table, the key, the tenant column -- without the struct having to expose them again.

It answers nil for a T that does not embed Model[T], and for a value the framework did not build: a User written as a literal has a zero Model[User] inside it, with no connection and no back pointer, and calling a terminal on it returns ErrUnwired rather than panicking. The ways to make one are the three ErrUnwired names.

func NewModel

func NewModel[T any](table string, connection query.Connection, grammar query.Grammar, processor query.Processor) *Model[T]

NewModel builds a Model over T.

The three arguments after the table are handed over once, here, because there is no resolver to look them up in later.

The defaults are id, an int key, incrementing, timestamps on, 15 per page and created_at / updated_at / deleted_at. TenantColumn starts at tenant_id: there is no default that leaves the scoping off.

func Unref

func Unref[T any](m concerns.Model) (*Model[T], bool)

Unref is the way back: the typed model behind a ref, and whether the ref was over this entity at all.

A ref over another entity answers false rather than panicking, because the question "is this relation's model a Post" is one a caller is entitled to ask and get no for.

func (*Model[T]) AddGlobalScope

func (m *Model[T]) AddGlobalScope(identifier string, scope Scope[T]) *Model[T]

AddGlobalScope registers scope under identifier: every query on the model carries it until WithoutGlobalScope removes it by that name.

The identifier is given rather than derived, because a closure scope (see ScopeFunc) has no name of its own.

func (*Model[T]) All

func (m *Model[T]) All(ctx context.Context, g auth.Grant, columns ...any) (Collection[T], error)

All returns every row of the model's table, subject to its global scopes.

func (*Model[T]) Append

func (m *Model[T]) Append(attributes ...string) *Model[T]

Append adds a name that is serialised with the row without being a column.

The value comes from a raw attribute or a loaded relation -- the two things a model can hold that the entity struct does not declare.

func (*Model[T]) AttributesToArray

func (m *Model[T]) AttributesToArray() map[string]any

AttributesToArray returns the row as it is serialised, with the hidden columns removed and the appended ones added.

func (*Model[T]) CallNamedScope

func (m *Model[T]) CallNamedScope(scope string, b *Builder[T], parameters ...any) *Builder[T]

CallNamedScope calls the named scope with b and parameters, and returns what it returns. It fails the builder when scope is not registered.

The builder is its own argument rather than the first entry of parameters: an []any that must have a *Builder[T] in slot zero is a signature that documents nothing and fails at run time.

func (*Model[T]) Create

func (m *Model[T]) Create(ctx context.Context, g auth.Grant, attributes map[string]any) (*T, error)

Create calls Create on a fresh query for the model.

func (*Model[T]) Delete

func (m *Model[T]) Delete(ctx context.Context, g auth.Grant) (bool, error)

Delete removes the row, and reports whether it was deleted.

A model that does not exist returns false with no error: a Go bool has no third state, and "there was nothing to delete" is not a failure.

func (*Model[T]) DeleteOrFail

func (m *Model[T]) DeleteOrFail(ctx context.Context, g auth.Grant) (bool, error)

DeleteOrFail is Delete, inside a transaction.

func (*Model[T]) DeleteQuietly

func (m *Model[T]) DeleteQuietly(ctx context.Context, g auth.Grant) (deleted bool, err error)

DeleteQuietly deletes the model without firing model events.

func (*Model[T]) Destroy

func (m *Model[T]) Destroy(ctx context.Context, g auth.Grant, ids ...any) (int, error)

Destroy deletes the rows with the given primary keys, and returns how many were deleted.

It loads the rows and deletes them one at a time, so that a soft delete stays a soft delete and every event fires with the row it is about.

func (*Model[T]) DiscardChanges

func (m *Model[T]) DiscardChanges() error

DiscardChanges resets the row to its original values and clears the recorded changes.

func (*Model[T]) DoesntExist added in v0.44.0

func (m *Model[T]) DoesntExist(ctx context.Context, g auth.Grant) (bool, error)

DoesntExist calls DoesntExist on a fresh query for the model: whether the tenant's table holds no row the global scopes let through, which is the question a seeder asks before it fills one.

It has no Exists counterpart on the model, because Exists is the field that says whether this instance is stored, and a type cannot carry a field and a method of the same name. NewQuery().Exists asks the positive question.

func (*Model[T]) Except

func (m *Model[T]) Except(attributes ...string) map[string]any

Except returns the row without the named columns.

func (*Model[T]) Fill

func (m *Model[T]) Fill(attributes map[string]any) error

Fill writes the columns the entity declares and drops the keys it does not know.

It never writes the tenant column, and never the primary key of a row that already exists: a form posted back into Update or UpdateOrCreate carries whatever keys its sender added, and neither of those is the sender's to choose. Both are skipped without error, like an unknown key. ForceFill writes them.

There is no allowlist to consult beyond the struct itself: an unexported field is unreachable to reflection, so the allowlist is the initial letter of the field and the compiler keeps it (see the package comment).

The error here is the other failure a typed model can have -- a value that does not fit the field.

func (*Model[T]) Find

func (m *Model[T]) Find(ctx context.Context, g auth.Grant, id any, columns ...any) (*T, error)

Find calls Find on a fresh query for the model.

func (*Model[T]) FindMany

func (m *Model[T]) FindMany(ctx context.Context, g auth.Grant, ids []any, columns ...any) (Collection[T], error)

FindMany calls FindMany on a fresh query for the model.

func (*Model[T]) FindOrFail

func (m *Model[T]) FindOrFail(ctx context.Context, g auth.Grant, id any, columns ...any) (*T, error)

FindOrFail calls FindOrFail on a fresh query for the model.

func (*Model[T]) FindOrNew

func (m *Model[T]) FindOrNew(ctx context.Context, g auth.Grant, id any, columns ...any) (*T, error)

FindOrNew calls FindOrNew on a fresh query for the model.

func (*Model[T]) First

func (m *Model[T]) First(ctx context.Context, g auth.Grant, columns ...any) (*T, error)

First calls First on a fresh query for the model.

func (*Model[T]) FirstOrCreate

func (m *Model[T]) FirstOrCreate(ctx context.Context, g auth.Grant, attributes, values map[string]any) (*T, error)

FirstOrCreate calls FirstOrCreate on a fresh query for the model.

func (*Model[T]) FirstOrNew

func (m *Model[T]) FirstOrNew(ctx context.Context, g auth.Grant, attributes, values map[string]any) (*T, error)

FirstOrNew calls FirstOrNew on a fresh query for the model.

func (*Model[T]) ForceCreate

func (m *Model[T]) ForceCreate(ctx context.Context, g auth.Grant, attributes map[string]any) (*T, error)

ForceCreate calls ForceCreate on a fresh query for the model.

func (*Model[T]) ForceDelete

func (m *Model[T]) ForceDelete(ctx context.Context, g auth.Grant) (bool, error)

ForceDelete removes the row even if the model soft deletes. On a model that does not soft delete it is a plain delete.

func (*Model[T]) ForceDeleteQuietly

func (m *Model[T]) ForceDeleteQuietly(ctx context.Context, g auth.Grant) (deleted bool, err error)

ForceDeleteQuietly removes the row even if the model soft deletes, without firing model events.

func (*Model[T]) ForceDeleted

func (m *Model[T]) ForceDeleted(callback func(*Model[T]) error) *Model[T]

ForceDeleted registers a callback for the moment a row has been force deleted.

func (*Model[T]) ForceDeleting

func (m *Model[T]) ForceDeleting(callback func(*Model[T]) error) *Model[T]

ForceDeleting registers a callback for the moment a row is about to be force deleted.

func (*Model[T]) ForceDestroy

func (m *Model[T]) ForceDestroy(ctx context.Context, g auth.Grant, ids ...any) (int, error)

ForceDestroy loads the models with the given keys, including trashed ones, and removes each row even if the model soft deletes. It returns the number removed.

func (*Model[T]) ForceFill

func (m *Model[T]) ForceFill(attributes map[string]any) error

ForceFill writes the columns the entity declares, and keeps the keys it does not know as raw attributes instead of dropping them the way Fill does. It still cannot reach an unexported field, because nothing can.

func (*Model[T]) Fresh

func (m *Model[T]) Fresh(ctx context.Context, g auth.Grant, with ...string) (*T, error)

Fresh returns the same row, read again, as a new model.

It queries without the global scopes, which is what makes it able to find a row that has since been soft deleted.

func (*Model[T]) FreshTimestamp

func (m *Model[T]) FreshTimestamp() time.Time

FreshTimestamp returns the current time in UTC.

func (*Model[T]) GetAppends

func (m *Model[T]) GetAppends() []string

GetAppends returns the names appended to serialisation.

func (*Model[T]) GetAttribute

func (m *Model[T]) GetAttribute(key string) any

GetAttribute returns the value for key: a column value if key names a field, else a raw attribute, else a loaded relation.

A key that matches none of those reads as nil. PreventAccessingMissingAttributes turns that into a reported violation, though it catches much less here than it would need to elsewhere: a typo like found.Entity.Naem fails to compile, so it never reaches this check at all.

func (*Model[T]) GetAttributes

func (m *Model[T]) GetAttributes() map[string]any

GetAttributes returns every column of the row, as the database sees it.

The row lives in the entity struct, so the map is built from it -- plus the raw attributes a column with no field behind it left behind (a withCount alias, a column a migration added and the struct has not caught up with).

func (*Model[T]) GetChanges

func (m *Model[T]) GetChanges() map[string]any

GetChanges returns what changed on the last save.

func (*Model[T]) GetConnectionName

func (m *Model[T]) GetConnectionName() string

GetConnectionName returns the name of the connection this model uses.

func (*Model[T]) GetCreatedAtColumn

func (m *Model[T]) GetCreatedAtColumn() string

GetCreatedAtColumn returns the name of the created-at column.

func (*Model[T]) GetDeletedAtColumn

func (m *Model[T]) GetDeletedAtColumn() string

GetDeletedAtColumn returns the name of the column that marks a row deleted, defaulting to "deleted_at".

func (*Model[T]) GetDirty

func (m *Model[T]) GetDirty() map[string]any

GetDirty returns the columns that differ from the original.

func (*Model[T]) GetForeignKey

func (m *Model[T]) GetForeignKey() string

GetForeignKey returns the name this model has when another table points at it.

func (*Model[T]) GetGlobalScopes

func (m *Model[T]) GetGlobalScopes() map[string]Scope[T]

GetGlobalScopes returns every scope registered on the model.

func (*Model[T]) GetHidden

func (m *Model[T]) GetHidden() []string

GetHidden returns the columns hidden from serialisation.

func (*Model[T]) GetIncrementing

func (m *Model[T]) GetIncrementing() bool

GetIncrementing reports whether the primary key is generated by the database on insert.

func (*Model[T]) GetKey

func (m *Model[T]) GetKey() any

GetKey returns the value of the primary key column.

func (*Model[T]) GetKeyName

func (m *Model[T]) GetKeyName() string

GetKeyName returns the name of the primary key column.

func (*Model[T]) GetKeyType

func (m *Model[T]) GetKeyType() string

GetKeyType returns the primary key's type.

func (*Model[T]) GetMorphClass

func (m *Model[T]) GetMorphClass() string

GetMorphClass returns the name a polymorphic column writes down for a row of this model: the unaliased type name of T.

A relation can register a short alias for that name instead, through the morph map in model/relations, which imports this package and applies the alias there.

func (*Model[T]) GetOriginal

func (m *Model[T]) GetOriginal() map[string]any

GetOriginal returns the row as it was when it was last synced.

func (*Model[T]) GetPerPage

func (m *Model[T]) GetPerPage() int

GetPerPage returns the default page size for Paginate, or 15 when PerPage is not set to a positive value.

func (*Model[T]) GetPrevious

func (m *Model[T]) GetPrevious() map[string]any

GetPrevious returns what the changed columns held before the last save.

func (*Model[T]) GetQualifiedCreatedAtColumn

func (m *Model[T]) GetQualifiedCreatedAtColumn() string

GetQualifiedCreatedAtColumn returns the created-at column qualified with the model's table.

func (*Model[T]) GetQualifiedDeletedAtColumn

func (m *Model[T]) GetQualifiedDeletedAtColumn() string

GetQualifiedDeletedAtColumn returns the deleted_at column name qualified with the model's table.

func (*Model[T]) GetQualifiedKeyName

func (m *Model[T]) GetQualifiedKeyName() string

GetQualifiedKeyName returns the primary key column qualified with the model's table.

func (*Model[T]) GetQualifiedUpdatedAtColumn

func (m *Model[T]) GetQualifiedUpdatedAtColumn() string

GetQualifiedUpdatedAtColumn returns the updated-at column qualified with the model's table.

func (*Model[T]) GetQueueableConnection

func (m *Model[T]) GetQueueableConnection() string

GetQueueableConnection returns the name of the connection this model uses, for a queued job to restore it on.

func (*Model[T]) GetQueueableID

func (m *Model[T]) GetQueueableID() any

GetQueueableID returns what a queued job writes down so it can find this row again.

func (*Model[T]) GetQueueableRelations

func (m *Model[T]) GetQueueableRelations() []string

GetQueueableRelations returns the loaded relations a job restores along with the row.

A loaded relation with no registered resolver is skipped, since a relation with no resolver cannot be loaded again on the other side of the queue.

The order is sorted rather than insertion order: a Go map has none, and a job payload that differs between two runs over the same row is a payload nobody can diff.

func (*Model[T]) GetRawOriginal

func (m *Model[T]) GetRawOriginal(key string) any

GetRawOriginal returns the original value for one key, uncast.

It is the same value GetOriginal would give for that key, because the cast is the field's type and the original was already cast when it was read. The two methods are kept apart anyway, because a caller that asks for the raw one is saying something about intent.

func (*Model[T]) GetRelation

func (m *Model[T]) GetRelation(name string) (any, bool)

GetRelation returns the value loaded for a relation, and whether it was loaded at all.

The value is an any because the related rows are models of another type, and a Go field cannot hold "some other model". Related is the typed way to read it.

A name the model declares a relation for, asked for before anything loaded it, is the lazy load this framework does not do: it reports false rather than running a query, and PreventLazyLoading is what makes that silence loud.

func (*Model[T]) GetRelations

func (m *Model[T]) GetRelations() map[string]any

GetRelations returns every loaded relation.

func (*Model[T]) GetRouteKey

func (m *Model[T]) GetRouteKey() any

GetRouteKey returns the value that stands for this row in a URL: whatever the key column holds.

It is an any because that value can be any type the key column holds. routing.UrlRoutable asks for a string, because a path segment is one; a model type that is bound in a route formats this value itself.

func (*Model[T]) GetRouteKeyName

func (m *Model[T]) GetRouteKeyName() string

GetRouteKeyName returns the column the route binds on.

func (*Model[T]) GetTable

func (m *Model[T]) GetTable() string

GetTable returns the table name: Table when it is set, or else the name of T, pluralised and snake cased, so a struct called User reads from users.

func (*Model[T]) GetTouchedRelations

func (m *Model[T]) GetTouchedRelations() []string

GetTouchedRelations returns the relations whose owner is stamped on save.

func (*Model[T]) GetUpdatedAtColumn

func (m *Model[T]) GetUpdatedAtColumn() string

GetUpdatedAtColumn returns the name of the updated-at column.

func (*Model[T]) GetVisible

func (m *Model[T]) GetVisible() []string

GetVisible returns the columns allowed in serialisation, when the visible list is in use.

func (*Model[T]) HasAppended

func (m *Model[T]) HasAppended(attribute string) bool

HasAppended reports whether attribute is in the appended list.

func (*Model[T]) HasGlobalScope

func (m *Model[T]) HasGlobalScope(identifier string) bool

HasGlobalScope reports whether a scope is registered under identifier.

func (*Model[T]) HasNamedScope

func (m *Model[T]) HasNamedScope(scope string) bool

HasNamedScope reports whether the model has a scope registered under this name.

A scope is registered under its bare name -- Active, not scopeActive -- and this is a lookup in that map.

func (*Model[T]) Is

func (m *Model[T]) Is(other *T) bool

Is reports whether other is the same row: the same key, on the same table, on the same connection.

It takes the row, which is what a terminal hands back, and what it can answer depends on the shape of T. A T that embeds Model[T] carries its model inside itself, so a.Is(b) on two rows reaches the key and the table on both. A T that does not has no field pointing back at a model, and this answers false: a table and a connection are not columns, so a plain row does not carry them.

func (*Model[T]) IsClean

func (m *Model[T]) IsClean(attributes ...string) bool

IsClean reports the opposite of IsDirty.

func (*Model[T]) IsDirty

func (m *Model[T]) IsDirty(attributes ...string) bool

IsDirty reports whether the given columns differ from the original. With no argument it asks about the whole row.

func (*Model[T]) IsForceDeleting

func (m *Model[T]) IsForceDeleting() bool

IsForceDeleting reports whether the current delete bypasses soft deletes.

func (*Model[T]) IsIgnoringTouch

func (m *Model[T]) IsIgnoringTouch() bool

IsIgnoringTouch reports whether touch propagation is currently suspended for T.

A model with no updated_at column, or with timestamps switched off, reports true without consulting the suspended list, because neither has anything a touch could update. Otherwise a type counts as suspended only when it is itself in the list -- there is no supertype or subtype relationship to walk.

func (*Model[T]) IsNot

func (m *Model[T]) IsNot(other *T) bool

IsNot reports the opposite of Is.

func (*Model[T]) IsRelation

func (m *Model[T]) IsRelation(key string) bool

IsRelation reports whether key names a relation this model declares.

It reads the resolvers rather than the loaded values, so it answers true for a relation that has not been loaded yet -- which is the question being asked: "is this a relation" and not "is this relation here".

func (*Model[T]) IsSoftDeletable

func (m *Model[T]) IsSoftDeletable() bool

IsSoftDeletable reports whether the model soft deletes.

func (*Model[T]) Latest added in v0.44.0

func (m *Model[T]) Latest(column ...string) *Builder[T]

Latest calls Latest on a fresh query for the model: newest first, by column or by the created-at column when none is given.

func (*Model[T]) Load

func (m *Model[T]) Load(ctx context.Context, g auth.Grant, relations ...string) error

Load eager loads these relations onto this model.

func (*Model[T]) LoadAggregate

func (m *Model[T]) LoadAggregate(ctx context.Context, g auth.Grant, relations []string, column, function string) error

LoadAggregate loads function over column of each relation onto this model.

func (*Model[T]) LoadCount

func (m *Model[T]) LoadCount(ctx context.Context, g auth.Grant, relations ...string) error

LoadCount loads the count of each relation onto this model.

func (*Model[T]) LoadMissing

func (m *Model[T]) LoadMissing(ctx context.Context, g auth.Grant, relations ...string) error

LoadMissing eager loads these relations onto this model, skipping the ones already loaded.

func (*Model[T]) LoadMorph

func (m *Model[T]) LoadMorph(ctx context.Context, g auth.Grant, relation string, relations map[string][]string) error

LoadMorph eager loads, on the row a polymorphic relation points at, the relations named for that row's own type.

relation is the morph relation on this model; relations maps a morph class to what to load on it. A morph class with no entry loads nothing.

func (*Model[T]) LoadMorphAggregate

func (m *Model[T]) LoadMorphAggregate(ctx context.Context, g auth.Grant, relation string, relations map[string][]string, column, function string) error

LoadMorphAggregate loads function over column of each relation named for the target row's type, on the row a polymorphic relation points at.

func (*Model[T]) LoadMorphAvg

func (m *Model[T]) LoadMorphAvg(ctx context.Context, g auth.Grant, relation string, relations map[string][]string, column string) error

LoadMorphAvg loads the average of column over each relation named for the target row's type, on the row a polymorphic relation points at.

func (*Model[T]) LoadMorphCount

func (m *Model[T]) LoadMorphCount(ctx context.Context, g auth.Grant, relation string, relations map[string][]string) error

LoadMorphCount loads the count of each relation named for the target row's type, on the row a polymorphic relation points at.

func (*Model[T]) LoadMorphMax

func (m *Model[T]) LoadMorphMax(ctx context.Context, g auth.Grant, relation string, relations map[string][]string, column string) error

LoadMorphMax loads the max of column over each relation named for the target row's type, on the row a polymorphic relation points at.

func (*Model[T]) LoadMorphMin

func (m *Model[T]) LoadMorphMin(ctx context.Context, g auth.Grant, relation string, relations map[string][]string, column string) error

LoadMorphMin loads the min of column over each relation named for the target row's type, on the row a polymorphic relation points at.

func (*Model[T]) LoadMorphSum

func (m *Model[T]) LoadMorphSum(ctx context.Context, g auth.Grant, relation string, relations map[string][]string, column string) error

LoadMorphSum loads the sum of column over each relation named for the target row's type, on the row a polymorphic relation points at.

func (*Model[T]) MakeHidden

func (m *Model[T]) MakeHidden(attributes ...string) *Model[T]

MakeHidden adds the named columns to the hidden list.

func (*Model[T]) MakeVisible

func (m *Model[T]) MakeVisible(attributes ...string) *Model[T]

MakeVisible takes the named columns out of the hidden list, and adds them to the visible list when that list is in use.

func (*Model[T]) NewBaseQueryBuilder

func (m *Model[T]) NewBaseQueryBuilder() *query.Builder

NewBaseQueryBuilder returns a plain query.Builder scoped to m's table, with no model-level behavior attached.

func (*Model[T]) NewCollection

func (m *Model[T]) NewCollection(rows ...*T) Collection[T]

NewCollection builds the Collection a query's rows are handed back in.

There is one collection type and no automatic relation loading, so this is the construction and nothing else.

func (*Model[T]) NewFromBuilder

func (m *Model[T]) NewFromBuilder(attributes map[string]any) (*Model[T], error)

NewFromBuilder returns the model a row becomes on the way out of the database, already existing and already synced.

func (*Model[T]) NewInstance

func (m *Model[T]) NewInstance(attributes map[string]any, exists bool) (*Model[T], error)

NewInstance returns a fresh model of the same shape, on the same table and connection, filled with the given attributes.

func (*Model[T]) NewModelQuery

func (m *Model[T]) NewModelQuery() *Builder[T]

NewModelQuery returns a builder with no global scopes and no eager loads.

func (*Model[T]) NewQuery

func (m *Model[T]) NewQuery() *Builder[T]

NewQuery returns the model's query with its global scopes on.

func (*Model[T]) NewQueryForRestoration

func (m *Model[T]) NewQueryForRestoration(ids ...any) *Builder[T]

NewQueryForRestoration returns the query a route model binding uses to find a soft deleted row.

func (*Model[T]) NewQueryWithoutRelationships

func (m *Model[T]) NewQueryWithoutRelationships() *Builder[T]

NewQueryWithoutRelationships returns the model's query with its global scopes on.

func (*Model[T]) NewQueryWithoutScope

func (m *Model[T]) NewQueryWithoutScope(identifier string) *Builder[T]

NewQueryWithoutScope returns the model's query with its global scopes on, except the one named by identifier.

func (*Model[T]) NewQueryWithoutScopes

func (m *Model[T]) NewQueryWithoutScopes() *Builder[T]

NewQueryWithoutScopes returns a builder for m with no global scopes applied.

func (*Model[T]) NewTypedBuilder

func (m *Model[T]) NewTypedBuilder(q *query.Builder) *Builder[T]

NewTypedBuilder returns the Builder used for m's queries.

A model that wants a wider query writes a type that embeds Builder[T] and overrides this method. It does not set the model, because NewModelQuery does that afterward.

func (*Model[T]) Oldest added in v0.44.0

func (m *Model[T]) Oldest(column ...string) *Builder[T]

Oldest calls Oldest on a fresh query for the model: oldest first, by column or by the created-at column when none is given.

func (*Model[T]) On

func (m *Model[T]) On(name string, connection query.Connection) *Builder[T]

On returns the model's query, run against another connection.

The connection comes with the name: there is no resolver to look one up in.

func (*Model[T]) OnWriteConnection

func (m *Model[T]) OnWriteConnection() *Builder[T]

OnWriteConnection returns a query pointed at the write connection.

func (*Model[T]) Only

func (m *Model[T]) Only(attributes ...string) map[string]any

Only returns a subset of the row, by column.

func (*Model[T]) OnlyTrashed

func (m *Model[T]) OnlyTrashed() *Builder[T]

OnlyTrashed calls OnlyTrashed on a fresh query for the model.

func (*Model[T]) OriginalIsEquivalent

func (m *Model[T]) OriginalIsEquivalent(key string) bool

OriginalIsEquivalent reports whether key's current value equals its original value.

The field has one static type, and both the current and the original value went through assign to reach it, so this is a plain comparison rather than a ladder of type coercions. The one case a plain comparison cannot handle is an uncomparable field (a slice, a map), which reflect.DeepEqual handles instead.

func (*Model[T]) Push

func (m *Model[T]) Push(ctx context.Context, g auth.Grant) (bool, error)

Push saves the model and everything loaded on it.

A loaded relation is held as an any, so Push recurses into whatever implements Pushable -- *Model[R] and Collection[R] both do.

func (*Model[T]) PushQuietly

func (m *Model[T]) PushQuietly(ctx context.Context, g auth.Grant) (pushed bool, err error)

PushQuietly pushes the model without firing model events.

func (*Model[T]) QualifyColumn

func (m *Model[T]) QualifyColumn(column string) string

QualifyColumn returns column qualified with the model's table, unless it already contains a dot.

func (*Model[T]) QualifyColumns

func (m *Model[T]) QualifyColumns(columns []string) []string

QualifyColumns returns columns, each qualified with the model's table.

func (*Model[T]) Query

func (m *Model[T]) Query() *Builder[T]

Query returns the model's query with its global scopes on -- the same as NewQuery. Go has no static form of a generic method, so there is only one way to ask a model for its query.

func (*Model[T]) Ref

func (m *Model[T]) Ref() concerns.Model

Ref returns m as the model a relation takes.

The value is cached on the model, so two calls answer the same one. Nothing keys a map by a model today, and a ref that was a new value every call would be a trap waiting for the first thing that did.

func (*Model[T]) Refresh

func (m *Model[T]) Refresh(ctx context.Context, g auth.Grant) error

Refresh reads the same row again, into this model.

func (*Model[T]) RegisterGlobalScopes

func (m *Model[T]) RegisterGlobalScopes(b *Builder[T]) *Builder[T]

RegisterGlobalScopes copies the model's global scopes onto b, and returns b.

It also registers the SoftDeletingScope when the model soft deletes. A model has no construction-time hook to do this once up front, so the registration happens here, every time the scopes are collected.

func (*Model[T]) RegisterModelEvent

func (m *Model[T]) RegisterModelEvent(event Event, callback func(*Model[T]) error) *Model[T]

RegisterModelEvent registers callback to run when event fires.

A callback that returns an error stops the operation, and the error says why. A saving callback that fails means nothing was written.

func (*Model[T]) RelationLoaded

func (m *Model[T]) RelationLoaded(name string) bool

RelationLoaded reports whether name has a loaded value.

func (*Model[T]) Replicate

func (m *Model[T]) Replicate(except ...string) (*T, error)

Replicate returns the same row as a new, unsaved model.

The key, the timestamps and anything named in except are left out.

func (*Model[T]) ReplicateQuietly

func (m *Model[T]) ReplicateQuietly(except ...string) (copied *T, err error)

ReplicateQuietly replicates the model without firing model events.

func (*Model[T]) ResolveRouteBinding

func (m *Model[T]) ResolveRouteBinding(ctx context.Context, g auth.Grant, value any, field string) (*T, error)

ResolveRouteBinding returns the row a URL segment stands for.

This is the line a multi-tenant application leaks on

/invoices/9 arrives, 9 is read off the path, a row is loaded by that id alone, and the reader is handed another customer's invoice. No policy was violated -- none was consulted. So the Grant is a parameter and not something read out of a context or a global: a resolver written without it does not compile, and the query it builds refuses a Grant carrying no tenant before any SQL exists.

Nothing matched is a nil model with a nil error, which is a 404 and not a failure.

func (*Model[T]) ResolveRouteBindingQuery

func (m *Model[T]) ResolveRouteBindingQuery(b *Builder[T], value any, field string) *Builder[T]

ResolveRouteBindingQuery returns the where clause a bound value becomes.

It only builds, so it takes no Grant. Nothing it returns can run without one -- First and Get both refuse a Grant with no tenant -- which is the whole reason this is the query and ResolveRouteBinding is the lookup.

func (*Model[T]) ResolveSoftDeletableRouteBinding

func (m *Model[T]) ResolveSoftDeletableRouteBinding(ctx context.Context, g auth.Grant, value any, field string) (*T, error)

ResolveSoftDeletableRouteBinding is the same lookup as ResolveRouteBinding, trashed rows included.

func (*Model[T]) Restore

func (m *Model[T]) Restore(ctx context.Context, g auth.Grant) (bool, error)

Restore clears the deleted_at column and saves the model: the row comes back.

func (*Model[T]) RestoreQuietly

func (m *Model[T]) RestoreQuietly(ctx context.Context, g auth.Grant) (restored bool, err error)

RestoreQuietly restores the model without firing model events.

func (*Model[T]) Restored

func (m *Model[T]) Restored(callback func(*Model[T]) error) *Model[T]

Restored registers a callback for the moment a row has been restored.

func (*Model[T]) Restoring

func (m *Model[T]) Restoring(callback func(*Model[T]) error) *Model[T]

Restoring registers a callback for the moment a row is about to be restored.

func (*Model[T]) Save

func (m *Model[T]) Save(ctx context.Context, g auth.Grant) (bool, error)

Save inserts or updates the row, and reports whether anything was written.

A model that exists and is clean is a true with no statement: there is nothing to write.

func (*Model[T]) SaveOrFail

func (m *Model[T]) SaveOrFail(ctx context.Context, g auth.Grant) (bool, error)

SaveOrFail is the save, inside a transaction.

query.Connection has no transaction on it, so the connection is asked whether it can open one, and a connection that cannot says so instead of writing outside a transaction the caller believes it is in.

func (*Model[T]) SaveQuietly

func (m *Model[T]) SaveQuietly(ctx context.Context, g auth.Grant) (saved bool, err error)

SaveQuietly saves the model without firing model events.

func (*Model[T]) SetAppends

func (m *Model[T]) SetAppends(appends ...string) *Model[T]

SetAppends replaces the names appended to serialisation.

func (*Model[T]) SetAttribute

func (m *Model[T]) SetAttribute(key string, value any) error

SetAttribute converts value to the field's type and assigns it, or reports the conversion error. A column the entity does not declare is kept as a raw attribute instead.

func (*Model[T]) SetConnection

func (m *Model[T]) SetConnection(name string, connection query.Connection) *Model[T]

SetConnection replaces the connection name and, when connection is not nil, the connection itself.

It takes the connection as well as the name because there is no resolver to look one up in: a name on its own reaches nothing.

func (*Model[T]) SetHidden

func (m *Model[T]) SetHidden(hidden ...string) *Model[T]

SetHidden replaces the columns hidden from serialisation.

func (*Model[T]) SetIncrementing

func (m *Model[T]) SetIncrementing(value bool) *Model[T]

SetIncrementing replaces whether the primary key is generated by the database on insert.

func (*Model[T]) SetKeyName

func (m *Model[T]) SetKeyName(key string) *Model[T]

SetKeyName replaces the name of the primary key column.

func (*Model[T]) SetKeyType

func (m *Model[T]) SetKeyType(keyType string) *Model[T]

SetKeyType replaces the primary key's type.

func (*Model[T]) SetPerPage

func (m *Model[T]) SetPerPage(perPage int) *Model[T]

SetPerPage replaces the default page size for Paginate.

func (*Model[T]) SetRawAttributes

func (m *Model[T]) SetRawAttributes(attributes map[string]any, sync bool) error

SetRawAttributes replaces the row without checking anything, and optionally syncs the original.

It is what Hydrate uses, and it is the only path that puts a value the caller did not declare on the model: a key with no field behind it is kept as a raw attribute rather than dropped, because a select the caller wrote has a reason for every column in it.

func (*Model[T]) SetRelation

func (m *Model[T]) SetRelation(name string, value any) *Model[T]

SetRelation records value as the loaded relation named name.

func (*Model[T]) SetRelations

func (m *Model[T]) SetRelations(relations map[string]any) *Model[T]

SetRelations replaces every loaded relation.

func (*Model[T]) SetTable

func (m *Model[T]) SetTable(table string) *Model[T]

SetTable replaces the table name.

func (*Model[T]) SetTouchedRelations

func (m *Model[T]) SetTouchedRelations(relations []string) *Model[T]

SetTouchedRelations replaces them, and returns the model so it can be set at construction with everything else.

func (*Model[T]) SetVisible

func (m *Model[T]) SetVisible(visible ...string) *Model[T]

SetVisible replaces the columns allowed in serialisation.

func (*Model[T]) SimplePaginate added in v0.44.0

func (m *Model[T]) SimplePaginate(ctx context.Context, g auth.Grant, perPage, page int, opts pagination.Options, columns ...any) (*pagination.Paginator[*T], error)

SimplePaginate calls SimplePaginate on a fresh query for the model: one page, in the order the table returns it, and whether there is another.

func (*Model[T]) SoftDeleted

func (m *Model[T]) SoftDeleted(callback func(*Model[T]) error) *Model[T]

SoftDeleted registers a callback for the moment a row is marked deleted.

func (*Model[T]) SyncChanges

func (m *Model[T]) SyncChanges() *Model[T]

SyncChanges records the current dirty columns as the last save's changes, and captures what each one held before it.

func (*Model[T]) SyncOriginal

func (m *Model[T]) SyncOriginal() *Model[T]

SyncOriginal replaces the original snapshot with the row's current values.

func (*Model[T]) SyncOriginalAttribute

func (m *Model[T]) SyncOriginalAttribute(attribute string) *Model[T]

SyncOriginalAttribute replaces the original snapshot for one column with its current value.

func (*Model[T]) SyncOriginalAttributes

func (m *Model[T]) SyncOriginalAttributes(attributes ...string) *Model[T]

SyncOriginalAttributes replaces the original snapshot for the named columns with their current values.

func (*Model[T]) ToArray

func (m *Model[T]) ToArray() map[string]any

ToArray returns the serialised row together with the loaded relations.

func (*Model[T]) ToJSON

func (m *Model[T]) ToJSON() ([]byte, error)

ToJSON encodes the serialised row as JSON. Go initialisms are upper case, hence ToJSON rather than ToJson, and it returns bytes rather than a string.

func (*Model[T]) ToPrettyJSON

func (m *Model[T]) ToPrettyJSON() ([]byte, error)

ToPrettyJSON encodes the serialised row as indented JSON.

func (*Model[T]) Touch

func (m *Model[T]) Touch(ctx context.Context, g auth.Grant) error

Touch stamps the model's updated-at column and saves it.

A model that does not use timestamps, or has no updated-at column, is not an error: it is a model there is nothing to stamp on, and the call is a no-op rather than a failure. The Builder's Touch is the same idea over a set of rows.

func (*Model[T]) Touches

func (m *Model[T]) Touches(relation string) bool

Touches reports whether saving this model stamps the owner of relation.

The list is the model's own, set by the application. An empty list means the model touches nothing, which is the default: a save that silently stamped a parent would be a write the caller did not ask for.

func (*Model[T]) Trashed

func (m *Model[T]) Trashed() bool

Trashed reports whether the model has been soft deleted.

func (*Model[T]) UnsetAttribute

func (m *Model[T]) UnsetAttribute(key string)

UnsetAttribute removes a raw attribute.

It reaches only the attributes a column has no field behind: a struct field cannot be removed, and setting it to its zero value would be a different thing said with the same word. A pivot row, whose columns are not known until run time, is what needs this.

func (*Model[T]) UnsetRelation

func (m *Model[T]) UnsetRelation(name string) *Model[T]

UnsetRelation removes the loaded relation named name.

func (*Model[T]) UnsetRelations

func (m *Model[T]) UnsetRelations() *Model[T]

UnsetRelations removes every loaded relation.

func (*Model[T]) Update

func (m *Model[T]) Update(ctx context.Context, g auth.Grant, attributes map[string]any) (bool, error)

Update fills the model with attributes, then saves it. A model that does not exist yet is false and no statement.

func (*Model[T]) UpdateOrCreate

func (m *Model[T]) UpdateOrCreate(ctx context.Context, g auth.Grant, attributes, values map[string]any) (*T, error)

UpdateOrCreate calls UpdateOrCreate on a fresh query for the model.

func (*Model[T]) UpdateOrFail

func (m *Model[T]) UpdateOrFail(ctx context.Context, g auth.Grant, attributes map[string]any) (bool, error)

UpdateOrFail is Update, inside a transaction.

func (*Model[T]) UpdateQuietly

func (m *Model[T]) UpdateQuietly(ctx context.Context, g auth.Grant, attributes map[string]any) (saved bool, err error)

UpdateQuietly updates the model without firing model events.

func (*Model[T]) UpdateTimestamps

func (m *Model[T]) UpdateTimestamps()

UpdateTimestamps stamps the updated-at column, and the created-at column when the model does not yet exist.

A column the entity does not declare is skipped rather than written as a raw attribute: a model without a created_at field is a table without the column, and inserting one would fail on the first row.

func (*Model[T]) UseUniqueIDs added in v0.44.0

func (m *Model[T]) UseUniqueIDs() *Model[T]

UseUniqueIDs makes the primary key an identifier the model generates: text, not incremented by the database, and filled on insert when it is empty.

func Invoices(db *data.DB) *model.Model[Invoice] {
	return model.NewModel[Invoice]("invoices", db, db.GetQueryGrammar(), db.GetPostProcessor()).UseUniqueIDs()
}

It sets KeyType to "string" and Incrementing to false, and from then on Save, Create and FillForInsert give a row whose key is empty a fresh database.NewOrderedID -- a version 7 UUID, the same text as the version 4 a key column already holds, which sorts by the time it was made. A key that is already set is kept, so a caller that has to choose the id still can.

The key is filled before the Creating event, so a listener sees the id the row is about to be written with.

It returns m, so it chains onto NewModel, and every instance the model makes carries it.

func (*Model[T]) UsesTimestamps

func (m *Model[T]) UsesTimestamps() bool

UsesTimestamps reports whether Save stamps created_at and updated_at.

func (*Model[T]) UsesUniqueIDs added in v0.44.0

func (m *Model[T]) UsesUniqueIDs() bool

UsesUniqueIDs reports whether UseUniqueIDs was called on the model.

func (*Model[T]) WasChanged

func (m *Model[T]) WasChanged(attributes ...string) bool

WasChanged reports whether the last save touched these columns.

func (*Model[T]) Where

func (m *Model[T]) Where(column any, args ...any) *Builder[T]

Where calls Where on a fresh query for the model.

func (*Model[T]) WhereKey

func (m *Model[T]) WhereKey(id any) *Builder[T]

WhereKey calls WhereKey on a fresh query for the model.

func (*Model[T]) With

func (m *Model[T]) With(relations ...string) *Builder[T]

With calls With on a fresh query for the model.

func (*Model[T]) WithTrashed

func (m *Model[T]) WithTrashed() *Builder[T]

WithTrashed calls WithTrashed on a fresh query for the model.

func (*Model[T]) WithoutEvents

func (m *Model[T]) WithoutEvents(callback func() error) error

WithoutEvents runs callback with model events muted on this model, and restores the previous setting however it ends.

It mutes the model it is called on, and the instances it makes while muted, because they are made from it.

func (*Model[T]) WithoutRelations

func (m *Model[T]) WithoutRelations() (*T, error)

WithoutRelations returns the same row, with nothing loaded hanging off it.

func (*Model[T]) WithoutTimestamps

func (m *Model[T]) WithoutTimestamps(callback func() error) error

WithoutTimestamps runs callback with the timestamps switched off, and restores the previous setting however it ends.

type ModelNotFoundError

type ModelNotFoundError struct {
	// Model is the table the query was on: what identifies a row on this
	// side.
	Model string

	// IDs is the primary keys that were looked for.
	IDs []any
}

ModelNotFoundError carries what ModelNotFoundException records beyond its message: the model that was looked for and the ids it was looked for by.

It unwraps to ErrModelNotFound, so errors.Is keeps working and a caller that only wants the 404 never has to name this type. A caller that wants the ids -- a log line, a retry that drops the missing rows -- reaches them with errors.As.

func (*ModelNotFoundError) Error

func (e *ModelNotFoundError) Error() string

Error formats the message naming the table and, when there are any, the ids that were not found.

func (*ModelNotFoundError) GetIDs

func (e *ModelNotFoundError) GetIDs() []any

GetIDs returns the ids that were looked for.

func (*ModelNotFoundError) GetModel

func (e *ModelNotFoundError) GetModel() string

GetModel returns the table the query was on.

func (*ModelNotFoundError) SetModel

func (e *ModelNotFoundError) SetModel(model string, ids ...any) *ModelNotFoundError

SetModel records the table and ids a not-found error is about, and returns e.

func (*ModelNotFoundError) Unwrap

func (e *ModelNotFoundError) Unwrap() error

Unwrap makes errors.Is(err, ErrModelNotFound) true. See ErrModelNotFound for why the sentinel is what callers compare against.

type MorphLoadable

type MorphLoadable interface {
	// GetMorphClass returns the name this value is stored under in the
	// morph column.
	GetMorphClass() string
	// Load eager loads these relations onto the value.
	Load(ctx context.Context, g auth.Grant, relations ...string) error
	// LoadAggregate loads function over column of each relation onto the
	// value.
	LoadAggregate(ctx context.Context, g auth.Grant, relations []string, column, function string) error
}

MorphLoadable is the loaded value of a polymorphic relation: a row of whatever model the morph column named.

Nothing in Go can turn a stored name back into a type, so the value identifies itself instead: *Model[R] satisfies this for every R, and the morph map is keyed by GetMorphClass.

type MorphRelation

type MorphRelation interface {
	Relation

	// GetMorphType returns the column holding the type of the related row.
	GetMorphType() string

	// RelationForMorphType returns the concrete relation for one of the
	// types a polymorphic relation can point at.
	//
	// Go cannot make a type from a string, so the relation resolves its
	// own map of type to model instead of a caller building one
	// generically.
	RelationForMorphType(morphType string) (Relation, error)
}

MorphRelation is the polymorphic relation WhereMorphRelation walks.

type NamedScope

type NamedScope[T any] func(builder *Builder[T], parameters ...any) *Builder[T]

NamedScope is one entry of Model.NamedScopes: a filter the caller applies by name, through Scopes or CallNamedScope.

It is a func and not a method because Go cannot look a method up by name, which is the same reason RelationResolvers is a map.

type NestedRelation

type NestedRelation interface {
	Relation

	// Nested returns the relation the next segment of a dotted path names,
	// on the related model.
	Nested(name string) (Relation, error)
}

NestedRelation is the relation a dotted existence check walks through. See hasNested.

type Pushable

type Pushable interface {
	Push(ctx context.Context, g auth.Grant) (bool, error)
}

Pushable is what Push recurses into. See Push.

type Queueable

type Queueable interface {
	// GetQueueableRelations returns the names of this value's own loaded
	// relations that a queued job restores along with it.
	GetQueueableRelations() []string
}

Queueable is what GetQueueableRelations recurses into: a value hanging off a loaded relation that can name its own.

It is one method, declared where it is consumed.

type Relation

type Relation interface {
	relations.Relation

	// GetRelationExistenceQuery returns the correlated subquery over the
	// related table, selecting the given expression and constrained to the
	// parent row.
	//
	// It is what Has, WhereHas and WithCount compile into an exists() or a
	// scalar subselect. q is the query it narrows, which is a fresh one on the
	// related model rather than the relation's own; parentQuery is what it
	// correlates against, and is read to tell a relation pointing back at its
	// own table apart from one pointing elsewhere.
	GetRelationExistenceQuery(q relations.Builder, parentQuery relations.Builder, columns ...any) relations.Builder
}

Relation is what the builder and the eager loader ask of a relation.

It is the contract the relations tree implements, plus the one method the builder needs that the tree does not declare: every relation there has GetRelationExistenceQuery, but as a method on the shared body rather than as part of its interface, and Has cannot call a method the type it holds does not promise.

It is declared here and satisfied there

This used to be a smaller interface of this package's own -- a Match that took the Grant and a batch of keys, and returned a dictionary the builder assigned from. It was written against a stand-in while this slice was built, and nothing in model/relations ever satisfied it: relations declare Match(models, results, relation), so the two collided on the name and no relation could be registered at all.

The shape that went is the one that could not have worked. Keys are not enough to eager load: a has-many on a local key that is not the primary key needs that column's value and not the row's id, and a morph-to is keyed by a type resolved per row and does its own matching. A dictionary of key to value also has nothing to say for the parent that matched nothing, where the tree seeds every parent with an empty collection first -- so a childless parent read back as a relation that was never loaded.

A relation is registered on the model by name, in RelationResolvers, because Go cannot look up a method by name and stay type safe.

type Savepointer

type Savepointer interface {
	Transactor

	// TransactionLevel returns how many transactions are currently nested.
	TransactionLevel() int
}

Savepointer is a Transactor that also reports how deep it already is.

It is what Builder.WithSavepointIfNeeded asks for, and it is separate from Transactor because a connection can be able to open a transaction without being able to say whether one is open -- and guessing wrong there is either a savepoint nobody asked for or a rollback that takes the caller's work with it.

type Scope

type Scope[T any] interface {
	// Apply adds the scope's filter to builder's query.
	Apply(builder *Builder[T], model *Model[T])
}

Scope is a filter every query on a model carries until somebody removes it by name.

func ScopeFunc

func ScopeFunc[T any](apply func(*Builder[T])) Scope[T]

ScopeFunc wraps a function as a Scope, for the closure form of addGlobalScope.

type ScopeExtender

type ScopeExtender[T any] interface {
	// Extend adds whatever methods or state this scope hangs off builder.
	Extend(builder *Builder[T])
}

ScopeExtender is the optional half of a Scope: it is called when the scope implements it, which is how SoftDeletingScope hangs WithTrashed and OnlyTrashed off the builder.

type SoftDeletingScope

type SoftDeletingScope[T any] struct{}

SoftDeletingScope filters the deleted rows out of every query, and replaces the delete with an update.

The model turns it on by setting SoftDeletes, and NewQuery registers it.

The deleted_at field on the entity has to be able to hold a null: a *time.Time, or another type that writes NULL when it is empty. A plain time.Time has no null, so restoring would write the zero date and the row would read as deleted at the year one.

func (*SoftDeletingScope[T]) Apply

func (s *SoftDeletingScope[T]) Apply(builder *Builder[T], model *Model[T])

Apply adds the not-deleted filter for model to builder's query.

func (*SoftDeletingScope[T]) Extend

func (s *SoftDeletingScope[T]) Extend(builder *Builder[T])

Extend registers the delete override that turns a hard delete into a timestamp update.

WithTrashed, WithoutTrashed, OnlyTrashed and Restore already exist as methods on Builder, so what Extend does is the part that is not a name: it points the builder's delete at an update.

type Transactor

type Transactor interface {
	// Transaction runs callback inside a database transaction.
	Transaction(callback func() error) error
}

Transactor is a connection that can open a transaction.

The narrow query.Connection this component builds on does not declare one, and widening it would change a contract this package does not own. So the capability is asked for by assertion, which is how Go spells an optional one.

Directories

Path Synopsis
Package attributes declares nothing, and will not.
Package attributes declares nothing, and will not.
Package factories builds rows of an entity for tests and for seeding.
Package factories builds rows of an entity for tests and for seeding.
Package relations holds the sixteen relation types, and the eager loading that keeps them from being N+1 queries.
Package relations holds the sixteen relation types, and the eager loading that keeps them from being N+1 queries.
concerns
Package concerns holds the shared halves the sixteen relation types are assembled from: AsPivot, CanBeOneOfMany, ComparesRelatedModels, InteractsWithDictionary, InteractsWithPivotTable, SupportsDefaultModels and SupportsInverseRelations.
Package concerns holds the shared halves the sixteen relation types are assembled from: AsPivot, CanBeOneOfMany, ComparesRelatedModels, InteractsWithDictionary, InteractsWithPivotTable, SupportsDefaultModels and SupportsInverseRelations.

Jump to

Keyboard shortcuts

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