gorm/callbacks/associations.go

381 lines
11 KiB
Go
Raw Normal View History

package callbacks
import (
"reflect"
"strings"
2020-06-02 04:16:07 +03:00
"gorm.io/gorm"
"gorm.io/gorm/clause"
"gorm.io/gorm/schema"
)
func SaveBeforeAssociations(db *gorm.DB) {
2020-05-31 18:55:56 +03:00
if db.Error == nil && db.Statement.Schema != nil {
2020-06-30 11:53:54 +03:00
selectColumns, restricted := db.Statement.SelectAndOmitColumns(true, false)
2020-04-19 18:11:56 +03:00
2020-04-17 03:23:47 +03:00
// Save Belongs To associations
for _, rel := range db.Statement.Schema.Relationships.BelongsTo {
2020-07-02 05:14:30 +03:00
if v, ok := selectColumns[rel.Name]; (ok && !v) || (!ok && restricted) {
2020-04-17 03:23:47 +03:00
continue
}
2020-04-20 06:47:29 +03:00
setupReferences := func(obj reflect.Value, elem reflect.Value) {
for _, ref := range rel.References {
if !ref.OwnPrimaryKey {
pv, _ := ref.PrimaryKey.ValueOf(elem)
2020-07-16 06:27:04 +03:00
db.AddError(ref.ForeignKey.Set(obj, pv))
2020-06-01 14:41:33 +03:00
if dest, ok := db.Statement.Dest.(map[string]interface{}); ok {
dest[ref.ForeignKey.DBName] = pv
if _, ok := dest[rel.Name]; ok {
dest[rel.Name] = elem.Interface()
}
}
2020-04-20 06:47:29 +03:00
}
}
}
switch db.Statement.ReflectValue.Kind() {
2020-05-20 18:44:50 +03:00
case reflect.Slice, reflect.Array:
2020-04-19 18:11:56 +03:00
var (
objs []reflect.Value
fieldType = rel.Field.FieldType
isPtr = fieldType.Kind() == reflect.Ptr
)
2020-04-19 18:11:56 +03:00
if !isPtr {
fieldType = reflect.PtrTo(fieldType)
}
2020-11-10 13:38:24 +03:00
elems := reflect.MakeSlice(reflect.SliceOf(fieldType), 0, 10)
2020-04-19 18:11:56 +03:00
for i := 0; i < db.Statement.ReflectValue.Len(); i++ {
obj := db.Statement.ReflectValue.Index(i)
2020-08-17 12:41:36 +03:00
if reflect.Indirect(obj).Kind() == reflect.Struct {
if _, zero := rel.Field.ValueOf(obj); !zero { // check belongs to relation value
rv := rel.Field.ReflectValueOf(obj) // relation reflect value
objs = append(objs, obj)
if isPtr {
elems = reflect.Append(elems, rv)
} else {
elems = reflect.Append(elems, rv.Addr())
}
}
2020-08-17 12:41:36 +03:00
} else {
break
2020-04-19 18:11:56 +03:00
}
}
if elems.Len() > 0 {
if saveAssociations(db, rel, elems.Interface(), selectColumns, restricted, nil) == nil {
2020-04-19 18:11:56 +03:00
for i := 0; i < elems.Len(); i++ {
2020-04-20 06:47:29 +03:00
setupReferences(objs[i], elems.Index(i))
}
}
2020-04-19 18:11:56 +03:00
}
case reflect.Struct:
if _, zero := rel.Field.ValueOf(db.Statement.ReflectValue); !zero {
rv := rel.Field.ReflectValueOf(db.Statement.ReflectValue) // relation reflect value
2020-04-20 06:47:29 +03:00
if rv.Kind() != reflect.Ptr {
rv = rv.Addr()
}
if saveAssociations(db, rel, rv.Interface(), selectColumns, restricted, nil) == nil {
setupReferences(db.Statement.ReflectValue, rv)
}
}
}
}
}
}
2020-04-17 03:23:47 +03:00
func SaveAfterAssociations(db *gorm.DB) {
2020-05-31 18:55:56 +03:00
if db.Error == nil && db.Statement.Schema != nil {
2020-06-30 11:53:54 +03:00
selectColumns, restricted := db.Statement.SelectAndOmitColumns(true, false)
2020-04-19 18:11:56 +03:00
// Save Has One associations
for _, rel := range db.Statement.Schema.Relationships.HasOne {
2020-07-02 05:14:30 +03:00
if v, ok := selectColumns[rel.Name]; (ok && !v) || (!ok && restricted) {
2020-04-19 18:11:56 +03:00
continue
}
switch db.Statement.ReflectValue.Kind() {
2020-05-20 18:44:50 +03:00
case reflect.Slice, reflect.Array:
2020-04-19 18:11:56 +03:00
var (
fieldType = rel.Field.FieldType
isPtr = fieldType.Kind() == reflect.Ptr
)
if !isPtr {
fieldType = reflect.PtrTo(fieldType)
}
2020-04-17 03:23:47 +03:00
2020-11-10 13:38:24 +03:00
elems := reflect.MakeSlice(reflect.SliceOf(fieldType), 0, 10)
2020-04-19 18:11:56 +03:00
for i := 0; i < db.Statement.ReflectValue.Len(); i++ {
obj := db.Statement.ReflectValue.Index(i)
2020-04-20 06:47:29 +03:00
2020-08-17 12:41:36 +03:00
if reflect.Indirect(obj).Kind() == reflect.Struct {
if _, zero := rel.Field.ValueOf(obj); !zero {
rv := rel.Field.ReflectValueOf(obj)
if rv.Kind() != reflect.Ptr {
rv = rv.Addr()
}
2020-04-20 06:47:29 +03:00
2020-08-17 12:41:36 +03:00
for _, ref := range rel.References {
if ref.OwnPrimaryKey {
fv, _ := ref.PrimaryKey.ValueOf(obj)
db.AddError(ref.ForeignKey.Set(rv, fv))
} else if ref.PrimaryValue != "" {
db.AddError(ref.ForeignKey.Set(rv, ref.PrimaryValue))
}
2020-04-19 18:11:56 +03:00
}
2020-08-17 12:41:36 +03:00
elems = reflect.Append(elems, rv)
}
2020-04-19 18:11:56 +03:00
}
}
if elems.Len() > 0 {
assignmentColumns := []string{}
for _, ref := range rel.References {
assignmentColumns = append(assignmentColumns, ref.ForeignKey.DBName)
}
saveAssociations(db, rel, elems.Interface(), selectColumns, restricted, assignmentColumns)
2020-04-19 18:11:56 +03:00
}
case reflect.Struct:
if _, zero := rel.Field.ValueOf(db.Statement.ReflectValue); !zero {
f := rel.Field.ReflectValueOf(db.Statement.ReflectValue)
2020-04-20 06:47:29 +03:00
if f.Kind() != reflect.Ptr {
f = f.Addr()
}
2020-04-17 03:23:47 +03:00
assignmentColumns := []string{}
2020-04-17 03:23:47 +03:00
for _, ref := range rel.References {
if ref.OwnPrimaryKey {
fv, _ := ref.PrimaryKey.ValueOf(db.Statement.ReflectValue)
ref.ForeignKey.Set(f, fv)
2020-04-17 03:40:07 +03:00
} else if ref.PrimaryValue != "" {
ref.ForeignKey.Set(f, ref.PrimaryValue)
2020-04-17 03:23:47 +03:00
}
assignmentColumns = append(assignmentColumns, ref.ForeignKey.DBName)
2020-04-17 03:23:47 +03:00
}
2020-04-19 18:11:56 +03:00
saveAssociations(db, rel, f.Interface(), selectColumns, restricted, assignmentColumns)
2020-04-17 03:23:47 +03:00
}
2020-04-19 18:11:56 +03:00
}
}
2020-04-17 03:23:47 +03:00
2020-04-19 18:11:56 +03:00
// Save Has Many associations
for _, rel := range db.Statement.Schema.Relationships.HasMany {
2020-07-02 05:14:30 +03:00
if v, ok := selectColumns[rel.Name]; (ok && !v) || (!ok && restricted) {
2020-04-19 18:11:56 +03:00
continue
}
2020-04-17 03:23:47 +03:00
2020-04-19 18:11:56 +03:00
fieldType := rel.Field.IndirectFieldType.Elem()
2020-04-20 06:47:29 +03:00
isPtr := fieldType.Kind() == reflect.Ptr
if !isPtr {
2020-04-19 18:11:56 +03:00
fieldType = reflect.PtrTo(fieldType)
}
2020-11-10 13:38:24 +03:00
elems := reflect.MakeSlice(reflect.SliceOf(fieldType), 0, 10)
2020-04-19 18:11:56 +03:00
appendToElems := func(v reflect.Value) {
if _, zero := rel.Field.ValueOf(v); !zero {
f := reflect.Indirect(rel.Field.ReflectValueOf(v))
for i := 0; i < f.Len(); i++ {
elem := f.Index(i)
for _, ref := range rel.References {
if ref.OwnPrimaryKey {
pv, _ := ref.PrimaryKey.ValueOf(v)
ref.ForeignKey.Set(elem, pv)
} else if ref.PrimaryValue != "" {
ref.ForeignKey.Set(elem, ref.PrimaryValue)
}
}
if isPtr {
elems = reflect.Append(elems, elem)
2020-05-24 17:52:16 +03:00
} else {
elems = reflect.Append(elems, elem.Addr())
2020-04-19 18:11:56 +03:00
}
2020-04-17 03:23:47 +03:00
}
}
}
2020-04-19 09:29:31 +03:00
2020-04-19 18:11:56 +03:00
switch db.Statement.ReflectValue.Kind() {
2020-05-20 18:44:50 +03:00
case reflect.Slice, reflect.Array:
2020-04-19 18:11:56 +03:00
for i := 0; i < db.Statement.ReflectValue.Len(); i++ {
2020-08-17 12:41:36 +03:00
obj := db.Statement.ReflectValue.Index(i)
if reflect.Indirect(obj).Kind() == reflect.Struct {
appendToElems(obj)
}
2020-04-19 18:11:56 +03:00
}
case reflect.Struct:
appendToElems(db.Statement.ReflectValue)
}
2020-04-19 09:29:31 +03:00
2020-04-19 18:11:56 +03:00
if elems.Len() > 0 {
assignmentColumns := []string{}
for _, ref := range rel.References {
assignmentColumns = append(assignmentColumns, ref.ForeignKey.DBName)
}
saveAssociations(db, rel, elems.Interface(), selectColumns, restricted, assignmentColumns)
2020-04-19 18:11:56 +03:00
}
2020-04-19 09:29:31 +03:00
}
2020-04-19 18:11:56 +03:00
// Save Many2Many associations
for _, rel := range db.Statement.Schema.Relationships.Many2Many {
2020-07-02 05:14:30 +03:00
if v, ok := selectColumns[rel.Name]; (ok && !v) || (!ok && restricted) {
2020-04-19 18:11:56 +03:00
continue
2020-04-19 09:29:31 +03:00
}
2020-04-19 18:11:56 +03:00
fieldType := rel.Field.IndirectFieldType.Elem()
2020-04-20 06:47:29 +03:00
isPtr := fieldType.Kind() == reflect.Ptr
if !isPtr {
2020-04-19 18:11:56 +03:00
fieldType = reflect.PtrTo(fieldType)
}
2020-11-10 13:38:24 +03:00
elems := reflect.MakeSlice(reflect.SliceOf(fieldType), 0, 10)
joins := reflect.MakeSlice(reflect.SliceOf(reflect.PtrTo(rel.JoinTable.ModelType)), 0, 10)
2020-04-20 06:47:29 +03:00
objs := []reflect.Value{}
appendToJoins := func(obj reflect.Value, elem reflect.Value) {
joinValue := reflect.New(rel.JoinTable.ModelType)
for _, ref := range rel.References {
if ref.OwnPrimaryKey {
fv, _ := ref.PrimaryKey.ValueOf(obj)
ref.ForeignKey.Set(joinValue, fv)
} else if ref.PrimaryValue != "" {
ref.ForeignKey.Set(joinValue, ref.PrimaryValue)
} else {
fv, _ := ref.PrimaryKey.ValueOf(elem)
ref.ForeignKey.Set(joinValue, fv)
}
2020-04-19 18:11:56 +03:00
}
2020-04-20 06:47:29 +03:00
joins = reflect.Append(joins, joinValue)
}
appendToElems := func(v reflect.Value) {
if _, zero := rel.Field.ValueOf(v); !zero {
f := reflect.Indirect(rel.Field.ReflectValueOf(v))
2020-04-19 18:11:56 +03:00
for i := 0; i < f.Len(); i++ {
elem := f.Index(i)
2020-04-19 09:29:31 +03:00
objs = append(objs, v)
if isPtr {
elems = reflect.Append(elems, elem)
2020-04-20 06:47:29 +03:00
} else {
elems = reflect.Append(elems, elem.Addr())
2020-04-19 09:29:31 +03:00
}
}
}
}
2020-04-20 06:47:29 +03:00
switch db.Statement.ReflectValue.Kind() {
2020-05-20 18:44:50 +03:00
case reflect.Slice, reflect.Array:
2020-04-20 06:47:29 +03:00
for i := 0; i < db.Statement.ReflectValue.Len(); i++ {
2020-08-17 12:41:36 +03:00
obj := db.Statement.ReflectValue.Index(i)
if reflect.Indirect(obj).Kind() == reflect.Struct {
appendToElems(obj)
}
2020-04-20 06:47:29 +03:00
}
case reflect.Struct:
appendToElems(db.Statement.ReflectValue)
}
2020-04-19 18:11:56 +03:00
if elems.Len() > 0 {
if v, ok := selectColumns[rel.Name+".*"]; !ok || v {
saveAssociations(db, rel, elems.Interface(), selectColumns, restricted, nil)
}
2020-04-20 06:47:29 +03:00
for i := 0; i < elems.Len(); i++ {
appendToJoins(objs[i], elems.Index(i))
}
}
if joins.Len() > 0 {
db.AddError(db.Session(&gorm.Session{NewDB: true}).Clauses(clause.OnConflict{DoNothing: true}).Session(&gorm.Session{
SkipHooks: db.Statement.SkipHooks,
DisableNestedTransaction: true,
}).Create(joins.Interface()).Error)
2020-04-19 18:11:56 +03:00
}
2020-04-19 09:29:31 +03:00
}
}
2020-04-17 03:23:47 +03:00
}
func onConflictOption(stmt *gorm.Statement, s *schema.Schema, selectColumns map[string]bool, restricted bool, defaultUpdatingColumns []string) clause.OnConflict {
if stmt.DB.FullSaveAssociations {
defaultUpdatingColumns = make([]string, 0, len(s.DBNames))
for _, dbName := range s.DBNames {
if v, ok := selectColumns[dbName]; (ok && !v) || (!ok && restricted) {
continue
}
if !s.LookUpField(dbName).PrimaryKey {
defaultUpdatingColumns = append(defaultUpdatingColumns, dbName)
}
}
}
if len(defaultUpdatingColumns) > 0 {
var columns []clause.Column
for _, dbName := range s.PrimaryFieldDBNames {
columns = append(columns, clause.Column{Name: dbName})
}
return clause.OnConflict{
Columns: columns,
DoUpdates: clause.AssignmentColumns(defaultUpdatingColumns),
}
}
return clause.OnConflict{DoNothing: true}
}
func saveAssociations(db *gorm.DB, rel *schema.Relationship, values interface{}, selectColumns map[string]bool, restricted bool, defaultUpdatingColumns []string) error {
var (
selects, omits []string
onConflict = onConflictOption(db.Statement, rel.FieldSchema, selectColumns, restricted, defaultUpdatingColumns)
refName = rel.Name + "."
)
for name, ok := range selectColumns {
columnName := ""
if strings.HasPrefix(name, refName) {
columnName = strings.TrimPrefix(name, refName)
} else if strings.HasPrefix(name, clause.Associations) {
columnName = name
}
if columnName != "" {
if ok {
selects = append(selects, columnName)
} else {
omits = append(omits, columnName)
}
}
}
tx := db.Session(&gorm.Session{NewDB: true}).Clauses(onConflict).Session(&gorm.Session{
SkipHooks: db.Statement.SkipHooks,
DisableNestedTransaction: true,
})
db.Statement.Settings.Range(func(k, v interface{}) bool {
tx.Statement.Settings.Store(k, v)
return true
})
if len(selects) > 0 {
tx = tx.Select(selects)
}
if len(omits) > 0 {
tx = tx.Omit(omits...)
}
return db.AddError(tx.Create(values).Error)
}