gorm/association.go
2020-05-20 02:03:43 +08:00

167 lines
4.6 KiB
Go

package gorm
import (
"fmt"
"reflect"
"github.com/jinzhu/gorm/clause"
"github.com/jinzhu/gorm/schema"
"github.com/jinzhu/gorm/utils"
)
// Association Mode contains some helper methods to handle relationship things easily.
type Association struct {
DB *DB
Relationship *schema.Relationship
Error error
}
func (db *DB) Association(column string) *Association {
association := &Association{DB: db}
if err := db.Statement.Parse(db.Statement.Model); err == nil {
association.Relationship = db.Statement.Schema.Relationships.Relations[column]
if association.Relationship == nil {
association.Error = fmt.Errorf("%w: %v", ErrUnsupportedRelation, column)
}
} else {
association.Error = err
}
return association
}
func (association *Association) Find(out interface{}, conds ...interface{}) error {
if association.Error == nil {
}
return association.Error
}
func (association *Association) Append(values ...interface{}) error {
return association.Error
}
func (association *Association) Replace(values ...interface{}) error {
return association.Error
}
func (association *Association) Delete(values ...interface{}) error {
if association.Error == nil {
var (
tx = association.DB
rel = association.Relationship
reflectValue = tx.Statement.ReflectValue
conds = rel.ToQueryConditions(reflectValue)
relFields []*schema.Field
foreignKeys []string
updateAttrs = map[string]interface{}{}
)
for _, ref := range rel.References {
if ref.PrimaryValue == "" {
if rel.JoinTable == nil || !ref.OwnPrimaryKey {
if ref.OwnPrimaryKey {
relFields = append(relFields, ref.ForeignKey)
} else {
relFields = append(relFields, ref.PrimaryKey)
}
foreignKeys = append(foreignKeys, ref.ForeignKey.DBName)
updateAttrs[ref.ForeignKey.DBName] = nil
}
}
}
relValuesMap, relQueryValues := schema.GetIdentityFieldValuesMapFromValues(values, relFields)
column, values := schema.ToQueryValues(foreignKeys, relQueryValues)
tx.Where(clause.IN{Column: column, Values: values})
switch association.Relationship.Type {
case schema.HasOne, schema.HasMany:
modelValue := reflect.New(rel.FieldSchema.ModelType).Interface()
tx.Model(modelValue).Clauses(clause.Where{Exprs: conds}).UpdateColumns(updateAttrs)
case schema.BelongsTo:
tx.Clauses(clause.Where{Exprs: conds}).UpdateColumns(updateAttrs)
case schema.Many2Many:
modelValue := reflect.New(rel.JoinTable.ModelType).Interface()
tx.Clauses(clause.Where{Exprs: conds}).Delete(modelValue)
}
if tx.Error == nil {
cleanUpDeletedRelations := func(data reflect.Value) {
if _, zero := rel.Field.ValueOf(data); !zero {
fieldValue := reflect.Indirect(rel.Field.ReflectValueOf(data))
fieldValues := make([]reflect.Value, len(relFields))
switch fieldValue.Kind() {
case reflect.Slice, reflect.Array:
validFieldValues := reflect.Zero(rel.Field.FieldType)
for i := 0; i < fieldValue.Len(); i++ {
for idx, field := range relFields {
fieldValues[idx] = field.ReflectValueOf(fieldValue.Index(i))
}
if _, ok := relValuesMap[utils.ToStringKey(fieldValues...)]; !ok {
validFieldValues = reflect.Append(validFieldValues, fieldValue.Index(i))
}
}
rel.Field.Set(data, validFieldValues)
case reflect.Struct:
for idx, field := range relFields {
fieldValues[idx] = field.ReflectValueOf(data)
}
if _, ok := relValuesMap[utils.ToStringKey(fieldValues...)]; ok {
rel.Field.Set(data, reflect.Zero(rel.FieldSchema.ModelType))
}
}
}
}
switch reflectValue.Kind() {
case reflect.Slice, reflect.Array:
for i := 0; i < reflectValue.Len(); i++ {
cleanUpDeletedRelations(reflect.Indirect(reflectValue.Index(i)))
}
case reflect.Struct:
cleanUpDeletedRelations(reflectValue)
}
} else {
association.Error = tx.Error
}
}
return association.Error
}
func (association *Association) Clear() error {
return association.Replace()
}
func (association *Association) Count() (count int) {
if association.Error == nil {
var (
tx = association.DB
conds = association.Relationship.ToQueryConditions(tx.Statement.ReflectValue)
)
if association.Relationship.JoinTable != nil {
for _, queryClause := range association.Relationship.JoinTable.QueryClauses {
tx.Clauses(queryClause)
}
tx.Clauses(clause.From{Joins: []clause.Join{{
Table: clause.Table{Name: association.Relationship.JoinTable.Table},
ON: clause.Where{Exprs: conds},
}}})
} else {
tx.Clauses(clause.Where{Exprs: conds})
}
association.Error = tx.Count(&count).Error
}
return
}