181 lines
5.7 KiB
Go
181 lines
5.7 KiB
Go
package schemas
|
|
|
|
import (
|
|
"fmt"
|
|
"reflect"
|
|
"xorm.io/builder"
|
|
)
|
|
|
|
// Association is the representation of an association
|
|
type Association struct {
|
|
OwnTable *Table
|
|
OwnColumn *Column
|
|
OwnPkType reflect.Type
|
|
RefTable *Table
|
|
RefPkType reflect.Type
|
|
JoinTable *Table // many_to_many
|
|
SourceCol string // belongs_to, many_to_many
|
|
TargetCol string // has_one, has_many, many_to_many
|
|
}
|
|
|
|
// NewJoinSlice creates a slice to hold the intermediate result of a many-to-many association query
|
|
func (a *Association) NewJoinSlice() reflect.Value {
|
|
return reflect.New(reflect.SliceOf(a.JoinTable.Type))
|
|
}
|
|
|
|
// MakeJoinMap creates a map to hold the intermediate result of a many-to-many association
|
|
func (a *Association) MakeJoinMap() reflect.Value {
|
|
return reflect.MakeMap(reflect.MapOf(a.RefPkType, reflect.SliceOf(a.OwnPkType)))
|
|
}
|
|
|
|
// MakeRefMap creates a map to hold the result of an association query
|
|
func (a *Association) MakeRefMap() reflect.Value {
|
|
return reflect.MakeMap(reflect.MapOf(a.RefPkType, reflect.PtrTo(a.RefTable.Type)))
|
|
}
|
|
|
|
// ValidateOwnMap validates the type of the owner map (parent of an association)
|
|
func (a *Association) ValidateOwnMap(ownMap reflect.Value) error {
|
|
if ownMap.Type() != reflect.MapOf(a.OwnPkType, reflect.PtrTo(a.OwnTable.Type)) {
|
|
return fmt.Errorf("wrong map type: %v", ownMap.Type())
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// GetCond gets a where condition to use in an association query
|
|
func (a *Association) GetCond(ownMap reflect.Value) builder.Cond {
|
|
if a.JoinTable != nil {
|
|
return a.condManyToMany(ownMap)
|
|
}
|
|
if len(a.SourceCol) > 0 {
|
|
return a.condBelongsTo(ownMap)
|
|
}
|
|
return a.condHasOneOrMany(ownMap)
|
|
}
|
|
|
|
// condBelongsTo gets a where condition to use in a belongs-to association query
|
|
func (a *Association) condBelongsTo(ownMap reflect.Value) builder.Cond {
|
|
pkMap := make(map[interface{}]bool)
|
|
fkCol := a.OwnTable.GetColumn(a.SourceCol)
|
|
iter := ownMap.MapRange()
|
|
for iter.Next() {
|
|
structPtr := iter.Value()
|
|
fk, _ := fkCol.ValueOfV(&structPtr)
|
|
if fk.Type().Kind() == reflect.Ptr {
|
|
if fk.IsNil() {
|
|
continue
|
|
}
|
|
*fk = fk.Elem()
|
|
}
|
|
// don't check fk.IsZero(), because it might be a valid fk value
|
|
pkMap[fk.Interface()] = true
|
|
}
|
|
pks := make([]interface{}, 0, len(pkMap))
|
|
for pk := range pkMap {
|
|
pks = append(pks, pk)
|
|
}
|
|
return builder.In(a.RefTable.PrimaryKeys[0], pks)
|
|
}
|
|
|
|
// condHasOneOrMany gets a where condition to use in a has-one or has-many association query
|
|
func (a *Association) condHasOneOrMany(ownMap reflect.Value) builder.Cond {
|
|
var pks []interface{}
|
|
iter := ownMap.MapRange()
|
|
for iter.Next() {
|
|
pks = append(pks, iter.Key().Interface())
|
|
}
|
|
return builder.In(a.TargetCol, pks)
|
|
}
|
|
|
|
// condHasOneOrMany gets a where condition to use in a many-to-many association query
|
|
func (a *Association) condManyToMany(ownMap reflect.Value) builder.Cond {
|
|
var pks []interface{}
|
|
iter := ownMap.MapRange()
|
|
for iter.Next() {
|
|
pks = append(pks, iter.Key().Interface())
|
|
}
|
|
return builder.In(a.SourceCol, pks)
|
|
}
|
|
|
|
// Link links the owner (parent) values with the referenced association values
|
|
func (a *Association) Link(ownMap, refMap, pruneMap, joinMap reflect.Value) {
|
|
if a.JoinTable != nil {
|
|
a.linkManyToMany(ownMap, refMap, pruneMap, joinMap)
|
|
} else if len(a.SourceCol) > 0 {
|
|
a.linkBelongsTo(ownMap, refMap, pruneMap)
|
|
} else {
|
|
a.linkHasOneOrMany(ownMap, refMap, pruneMap)
|
|
}
|
|
}
|
|
|
|
// linkBelongsTo links the owner (parent) values with the referenced belongs-to association values
|
|
func (a *Association) linkBelongsTo(ownMap, refMap, pruneMap reflect.Value) {
|
|
fkCol := a.OwnTable.GetColumn(a.SourceCol)
|
|
iter := ownMap.MapRange()
|
|
for iter.Next() {
|
|
structPtr := iter.Value()
|
|
fk, _ := fkCol.ValueOfV(&structPtr)
|
|
if fk.Type().Kind() == reflect.Ptr {
|
|
if fk.IsNil() {
|
|
continue
|
|
}
|
|
*fk = fk.Elem()
|
|
}
|
|
// don't check fk.IsZero(), because it might be a valid fk value
|
|
refStructPtr := refMap.MapIndex(*fk)
|
|
if refStructPtr.IsValid() {
|
|
refField, _ := a.OwnColumn.ValueOfV(&structPtr)
|
|
refField.Set(refStructPtr)
|
|
if pruneMap.IsValid() {
|
|
pruneMap.SetMapIndex(iter.Key(), reflect.Value{}) // do not prune this key
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// linkBelongsTo links the owner (parent) values with the referenced has-one or has-many association values
|
|
func (a *Association) linkHasOneOrMany(ownMap, refMap, pruneMap reflect.Value) {
|
|
hasMany := a.OwnColumn.FieldType.Kind() == reflect.Slice
|
|
fkCol := a.RefTable.GetColumn(a.TargetCol)
|
|
iter := refMap.MapRange()
|
|
for iter.Next() {
|
|
refStructPtr := iter.Value()
|
|
fk, _ := fkCol.ValueOfV(&refStructPtr)
|
|
if fk.Type().Kind() == reflect.Ptr {
|
|
if fk.IsNil() {
|
|
continue
|
|
}
|
|
*fk = fk.Elem()
|
|
}
|
|
// don't check fk.IsZero(), because it might be a valid fk value
|
|
structPtr := ownMap.MapIndex(*fk) // structPtr should be valid at this point
|
|
refField, _ := a.OwnColumn.ValueOfV(&structPtr)
|
|
if hasMany {
|
|
refField.Set(reflect.Append(*refField, refStructPtr))
|
|
} else {
|
|
refField.Set(refStructPtr)
|
|
}
|
|
if pruneMap.IsValid() {
|
|
pruneMap.SetMapIndex(*fk, reflect.Value{}) // do not prune this key
|
|
}
|
|
}
|
|
}
|
|
|
|
// linkManyToMany links the owner (parent) values with the referenced many-to-many association values
|
|
func (a *Association) linkManyToMany(ownMap, refMap, pruneMap, joinMap reflect.Value) {
|
|
iter := refMap.MapRange()
|
|
for iter.Next() {
|
|
refStructPtr := iter.Value()
|
|
refPk := iter.Key() // refPk should not be a pointer
|
|
pkSlice := joinMap.MapIndex(refPk) // pkSlice should be valid at this point
|
|
for i := 0; i < pkSlice.Len(); i++ {
|
|
pk := pkSlice.Index(i)
|
|
structPtr := ownMap.MapIndex(pk) // structPtr should be valid at this point
|
|
refField, _ := a.OwnColumn.ValueOfV(&structPtr)
|
|
refField.Set(reflect.Append(*refField, refStructPtr))
|
|
if pruneMap.IsValid() {
|
|
pruneMap.SetMapIndex(pk, reflect.Value{}) // do not prune this key
|
|
}
|
|
}
|
|
}
|
|
}
|