diff --git a/callback_query_preload.go b/callback_query_preload.go index d9ec8bdd..c9bfa866 100644 --- a/callback_query_preload.go +++ b/callback_query_preload.go @@ -186,6 +186,13 @@ func (scope *Scope) handleHasManyPreload(field *Field, conditions []interface{}) for j := 0; j < indirectScopeValue.Len(); j++ { object := indirect(indirectScopeValue.Index(j)) objectRealValue := getValueFromFields(object, relation.AssociationForeignFieldNames) + if j > 0 { + prevObject := indirect(indirectScopeValue.Index(j - 1)) + prevObjectRealValue := getValueFromFields(prevObject, relation.AssociationForeignFieldNames) + if toString(prevObjectRealValue) == toString(objectRealValue) { + continue + } + } if results, ok := preloadMap[toString(objectRealValue)]; ok { f := object.FieldByName(field.Name) f.Set(reflect.Append(f, results...)) diff --git a/preload_test.go b/preload_test.go index da3ee38f..fd5b3af6 100644 --- a/preload_test.go +++ b/preload_test.go @@ -1509,6 +1509,84 @@ func TestNilPointerSlice2(t *testing.T) { } } +func TestPrefixedPreloadDuplication(t *testing.T) { + type ( + Level4 struct { + ID uint + Level3ID uint + } + Level3 struct { + ID uint + Level4s []*Level4 + } + Level2 struct { + ID uint + Level3ID sql.NullInt64 `sql:"index"` + Level3 *Level3 + } + Level1 struct { + ID uint + Level2ID sql.NullInt64 `sql:"index"` + Level2 *Level2 + } + ) + + DB.DropTableIfExists(new(Level3)) + DB.DropTableIfExists(new(Level4)) + DB.DropTableIfExists(new(Level2)) + DB.DropTableIfExists(new(Level1)) + + if err := DB.AutoMigrate(new(Level3), new(Level4), new(Level2), new(Level1)).Error; err != nil { + t.Error(err) + } + + lvl := new(Level3) + if err := DB.Save(lvl).Error; err != nil { + t.Error(err) + } + + sublvl1 := &Level4{Level3ID: lvl.ID} + if err := DB.Save(sublvl1).Error; err != nil { + t.Error(err) + } + sublvl2 := &Level4{Level3ID: lvl.ID} + if err := DB.Save(sublvl2).Error; err != nil { + t.Error(err) + } + + lvl.Level4s = []*Level4{sublvl1, sublvl2} + + want1 := Level1{ + Level2: &Level2{ + Level3: lvl, + }, + } + if err := DB.Save(&want1).Error; err != nil { + t.Error(err) + } + + want2 := Level1{ + Level2: &Level2{ + Level3: lvl, + }, + } + if err := DB.Save(&want2).Error; err != nil { + t.Error(err) + } + + want := []Level1{want1, want2} + + var got []Level1 + err := DB.Preload("Level2.Level3.Level4s").Find(&got).Error + if err != nil { + t.Error(err) + } + + if !reflect.DeepEqual(got, want) { + t.Errorf("got %s; want %s", toJSONString(got), toJSONString(want)) + } +} + func toJSONString(v interface{}) []byte { r, _ := json.MarshalIndent(v, "", " ") return r