Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions contracts/database/db/db.go
Original file line number Diff line number Diff line change
Expand Up @@ -215,6 +215,15 @@ type Result struct {
RowsAffected int64
}

// SyncResult reports the per-id outcome of a Sync / SyncWithoutDetaching / Toggle operation on
// a many-to-many (or polymorphic many-to-many) pivot table. Each slice carries the related ids
// in the order they were processed.
type SyncResult struct {
Attached []any
Detached []any
Updated []any
}

type Builder interface {
CommonBuilder
Beginx() (*sqlx.Tx, error)
Expand Down
17 changes: 17 additions & 0 deletions contracts/database/orm/morph.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
package orm

// ModelWithMorphClass lets a model override the value written to and matched against polymorphic
// `*_type` columns. The override takes precedence over both the global morph map (registered via
// orm.MorphMap) and GORM's default of using the parent's table name.
//
// A model that wants to be aliased as e.g. "post" in polymorphic relations declares:
//
// func (Post) MorphClass() string { return "post" }
//
// This is the recommended primary mechanism for aliasing morph types because it co-locates the
// alias with the model definition. The global morph map is provided as a fallback for models the
// caller cannot modify (e.g. third-party types) or for teams that prefer a single boot-time
// registration.
type ModelWithMorphClass interface {
MorphClass() string
}
92 changes: 72 additions & 20 deletions contracts/database/orm/orm.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,27 @@ type Orm interface {
DatabaseName() string
// Name gets the current connection name.
Name() string
// Related returns a Query pre-scoped to the related rows for the given parent and relation
// name. parent must be a non-nil pointer to a struct. The returned Query is a fresh chain β€”
// call any of Where / OrderBy / Get / First / Count / etc. on it.
//
// Per-kind shape:
// - HasOne / HasMany: Query().Model(related).Where("<id_col>", parent.<local_key>)
// - BelongsTo: Query().Model(related).Where("<owner_key>", parent.<fk_col>)
// - MorphOne / MorphMany: HasMany shape + Where("<type_col>", desc.morphValue)
// - MorphTo: type resolved from morph map; Query().Model(<resolved>).Where("<owner_key>", parent.<id_col>)
// - Many2Many / MorphToMany / MorphedByMany: Query().Table(related).Joins("INNER JOIN <pivot> ON ...").Where("<pivot>.<parent_fk>", parent.<pk>)
// - HasOneThrough / HasManyThrough: Query().Table(related).Joins("INNER JOIN <through> ON ...").Where("<through>.<first_key>", parent.<local_key>)
//
// Mirrors fedaco's model.NewRelation('foo') for the read path. Write operations live on
// RelationWriter (see Orm.Relation) β€” they are not chained off this Query.
Related(parent any, relation string) Query
// Relation returns a RelationWriter bound to (parent, name) for FK-safe write operations.
// All write methods (Save / Create / UpdateOrCreate / Attach / Sync / Detach / Toggle /
// Associate / Dissociate / etc.) are reached via this builder rather than as flat methods on
// Orm β€” the (parent, name) pair binds once.
Relation(parent any, name string) RelationWriter

// Observe registers an observer with the Orm.
Observe(model any, observer Observer)
// Query gets a new query builder instance.
Expand All @@ -36,8 +57,18 @@ type Orm interface {
}

type Query interface {
// Association gets an association instance by name.
Association(association string) Association
// QueryWithRelations exposes the QueriesRelationships surface (Has / WhereHas / WithCount /
// HasMorph / etc.). Embedding it into Query lets users chain relationship queries with the
// rest of the builder: q.Where(...).Has("Books", ">=", 3).Get(&users).
QueryWithRelations
// Related returns a Query pre-scoped to the related rows for parent.name. Mirrors Orm.Related
// but lives on Query so it can be used inside a Transaction callback. parent must be a
// non-nil pointer to a struct.
Related(parent any, name string) Query
// Relation returns a RelationWriter bound to (parent, name) for FK-safe write operations.
// Mirrors Orm.Relation but lives on Query so writes inside a Transaction callback honor the
// transaction.
Relation(parent any, name string) RelationWriter
// Begin begins a new transaction
// DEPRECATED Use BeginTransaction instead.
Begin() (Query, error)
Expand Down Expand Up @@ -101,14 +132,31 @@ type Query interface {
Join(query string, args ...any) Query
// Limit the number of records returned.
Limit(limit int) Query
// Load loads a relationship for the model.
// Load loads a relationship for the model. args may be a callback or other
// shapes accepted by With (e.g. "Books:id,name" column pruning is supported by
// embedding the column list in relation; a callback may be passed via args).
Load(dest any, relation string, args ...any) error
// LoadMissing loads a relationship for the model that is not already loaded.
// args follow the same shapes as Load.
LoadMissing(dest any, relation string, args ...any) error
// LockForUpdate locks the selected rows in the table for updating.
LockForUpdate() Query
// Model sets the model instance to be queried.
Model(value any) Query
// OfMany configures a HasOne or MorphOne relation to return the row whose value of column
// matches the given SQL aggregate ("MAX" / "MIN") within each parent. Intended for use
// inside a With(...) callback so the rewrite is local to a single relation:
//
// q.With("LatestImage", func(q orm.Query) orm.Query {
// return q.OfMany("created_at", "MAX")
// })
OfMany(column, aggregate string) Query
// LatestOfMany is shorthand for OfMany(column, "MAX") with column defaulting to "id" when
// empty. Mirrors fedaco's latestOfMany().
LatestOfMany(column ...string) Query
// OldestOfMany is shorthand for OfMany(column, "MIN") with column defaulting to "id" when
// empty. Mirrors fedaco's oldestOfMany().
OldestOfMany(column ...string) Query
// Offset specifies the number of records to skip before starting to return the records.
Offset(offset int) Query
// Omit specifies columns that should be omitted from the query.
Expand Down Expand Up @@ -221,10 +269,29 @@ type Query interface {
WithoutEvents() Query
// WithoutGlobalScopes disables all global scopes for the query.
WithoutGlobalScopes(names ...string) Query
// Without removes the given relations from the eager-load list set by With.
// Mirrors fedaco's without().
Without(relations ...string) Query
// WithTrashed allows soft deleted models to be included in the results.
WithTrashed() Query
// With returns a new query instance with the given relationships eager loaded.
With(query string, args ...any) Query
// With eagerly loads the given relationships using Goravel's own loader (does not
// delegate to GORM Preload). Accepts the union of fedaco's with(...) shapes:
//
// q.With("Books")
// q.With("Books", cb) // string + callback
// q.With("Books", "Roles", "Address") // multiple strings
// q.With("Books:id,name") // column pruning
// q.With(map[string]orm.RelationCallback{"Books": cb}) // map of name -> callback
// q.With([]any{"Books", map[string]orm.RelationCallback{"Roles": cb}})
// q.With("Books.Author") // nested
// q.With("Books.Author", cb) // nested + callback
//
// Supports HasOne, HasMany, BelongsTo, BelongsToMany, MorphOne, MorphMany,
// HasOneThrough and HasManyThrough.
With(args ...any) Query
// WithOnly clears the eager-load list set by With, then adds the given
// relations. Mirrors fedaco's withOnly().
WithOnly(args ...any) Query
}

type QueryWithContext interface {
Expand All @@ -235,21 +302,6 @@ type QueryWithObserver interface {
Observe(model any, observer Observer)
}

type Association interface {
// Find finds records that match given conditions.
Find(out any, conds ...any) error
// Append appending a model to the association.
Append(values ...any) error
// Replace replaces the association with the given value.
Replace(values ...any) error
// Delete deletes the given value from the association.
Delete(values ...any) error
// Clear clears the association.
Clear() error
// Count returns the number of records in the association.
Count() int64
}

type ModelWithConnection interface {
// Connection gets the connection name for the model.
Connection() string
Expand Down
Loading
Loading