1package gorm_test
2
3import (
4	"os"
5	"reflect"
6	"sort"
7	"testing"
8)
9
10type Blog struct {
11	ID         uint   `gorm:"primary_key"`
12	Locale     string `gorm:"primary_key"`
13	Subject    string
14	Body       string
15	Tags       []Tag `gorm:"many2many:blog_tags;"`
16	SharedTags []Tag `gorm:"many2many:shared_blog_tags;ForeignKey:id;AssociationForeignKey:id"`
17	LocaleTags []Tag `gorm:"many2many:locale_blog_tags;ForeignKey:id,locale;AssociationForeignKey:id"`
18}
19
20type Tag struct {
21	ID     uint   `gorm:"primary_key"`
22	Locale string `gorm:"primary_key"`
23	Value  string
24	Blogs  []*Blog `gorm:"many2many:blogs_tags"`
25}
26
27func compareTags(tags []Tag, contents []string) bool {
28	var tagContents []string
29	for _, tag := range tags {
30		tagContents = append(tagContents, tag.Value)
31	}
32	sort.Strings(tagContents)
33	sort.Strings(contents)
34	return reflect.DeepEqual(tagContents, contents)
35}
36
37func TestManyToManyWithMultiPrimaryKeys(t *testing.T) {
38	if dialect := os.Getenv("GORM_DIALECT"); dialect != "" && dialect != "sqlite" && dialect != "mssql" {
39		DB.DropTable(&Blog{}, &Tag{})
40		DB.DropTable("blog_tags")
41		DB.CreateTable(&Blog{}, &Tag{})
42		blog := Blog{
43			Locale:  "ZH",
44			Subject: "subject",
45			Body:    "body",
46			Tags: []Tag{
47				{Locale: "ZH", Value: "tag1"},
48				{Locale: "ZH", Value: "tag2"},
49			},
50		}
51
52		DB.Save(&blog)
53		if !compareTags(blog.Tags, []string{"tag1", "tag2"}) {
54			t.Errorf("Blog should has two tags")
55		}
56
57		// Append
58		var tag3 = &Tag{Locale: "ZH", Value: "tag3"}
59		DB.Model(&blog).Association("Tags").Append([]*Tag{tag3})
60		if !compareTags(blog.Tags, []string{"tag1", "tag2", "tag3"}) {
61			t.Errorf("Blog should has three tags after Append")
62		}
63
64		if DB.Model(&blog).Association("Tags").Count() != 3 {
65			t.Errorf("Blog should has three tags after Append")
66		}
67
68		var tags []Tag
69		DB.Model(&blog).Related(&tags, "Tags")
70		if !compareTags(tags, []string{"tag1", "tag2", "tag3"}) {
71			t.Errorf("Should find 3 tags with Related")
72		}
73
74		var blog1 Blog
75		DB.Preload("Tags").Find(&blog1)
76		if !compareTags(blog1.Tags, []string{"tag1", "tag2", "tag3"}) {
77			t.Errorf("Preload many2many relations")
78		}
79
80		// Replace
81		var tag5 = &Tag{Locale: "ZH", Value: "tag5"}
82		var tag6 = &Tag{Locale: "ZH", Value: "tag6"}
83		DB.Model(&blog).Association("Tags").Replace(tag5, tag6)
84		var tags2 []Tag
85		DB.Model(&blog).Related(&tags2, "Tags")
86		if !compareTags(tags2, []string{"tag5", "tag6"}) {
87			t.Errorf("Should find 2 tags after Replace")
88		}
89
90		if DB.Model(&blog).Association("Tags").Count() != 2 {
91			t.Errorf("Blog should has three tags after Replace")
92		}
93
94		// Delete
95		DB.Model(&blog).Association("Tags").Delete(tag5)
96		var tags3 []Tag
97		DB.Model(&blog).Related(&tags3, "Tags")
98		if !compareTags(tags3, []string{"tag6"}) {
99			t.Errorf("Should find 1 tags after Delete")
100		}
101
102		if DB.Model(&blog).Association("Tags").Count() != 1 {
103			t.Errorf("Blog should has three tags after Delete")
104		}
105
106		DB.Model(&blog).Association("Tags").Delete(tag3)
107		var tags4 []Tag
108		DB.Model(&blog).Related(&tags4, "Tags")
109		if !compareTags(tags4, []string{"tag6"}) {
110			t.Errorf("Tag should not be deleted when Delete with a unrelated tag")
111		}
112
113		// Clear
114		DB.Model(&blog).Association("Tags").Clear()
115		if DB.Model(&blog).Association("Tags").Count() != 0 {
116			t.Errorf("All tags should be cleared")
117		}
118	}
119}
120
121func TestManyToManyWithCustomizedForeignKeys(t *testing.T) {
122	if dialect := os.Getenv("GORM_DIALECT"); dialect != "" && dialect != "sqlite" && dialect != "mssql" {
123		DB.DropTable(&Blog{}, &Tag{})
124		DB.DropTable("shared_blog_tags")
125		DB.CreateTable(&Blog{}, &Tag{})
126		blog := Blog{
127			Locale:  "ZH",
128			Subject: "subject",
129			Body:    "body",
130			SharedTags: []Tag{
131				{Locale: "ZH", Value: "tag1"},
132				{Locale: "ZH", Value: "tag2"},
133			},
134		}
135		DB.Save(&blog)
136
137		blog2 := Blog{
138			ID:     blog.ID,
139			Locale: "EN",
140		}
141		DB.Create(&blog2)
142
143		if !compareTags(blog.SharedTags, []string{"tag1", "tag2"}) {
144			t.Errorf("Blog should has two tags")
145		}
146
147		// Append
148		var tag3 = &Tag{Locale: "ZH", Value: "tag3"}
149		DB.Model(&blog).Association("SharedTags").Append([]*Tag{tag3})
150		if !compareTags(blog.SharedTags, []string{"tag1", "tag2", "tag3"}) {
151			t.Errorf("Blog should has three tags after Append")
152		}
153
154		if DB.Model(&blog).Association("SharedTags").Count() != 3 {
155			t.Errorf("Blog should has three tags after Append")
156		}
157
158		if DB.Model(&blog2).Association("SharedTags").Count() != 3 {
159			t.Errorf("Blog should has three tags after Append")
160		}
161
162		var tags []Tag
163		DB.Model(&blog).Related(&tags, "SharedTags")
164		if !compareTags(tags, []string{"tag1", "tag2", "tag3"}) {
165			t.Errorf("Should find 3 tags with Related")
166		}
167
168		DB.Model(&blog2).Related(&tags, "SharedTags")
169		if !compareTags(tags, []string{"tag1", "tag2", "tag3"}) {
170			t.Errorf("Should find 3 tags with Related")
171		}
172
173		var blog1 Blog
174		DB.Preload("SharedTags").Find(&blog1)
175		if !compareTags(blog1.SharedTags, []string{"tag1", "tag2", "tag3"}) {
176			t.Errorf("Preload many2many relations")
177		}
178
179		var tag4 = &Tag{Locale: "ZH", Value: "tag4"}
180		DB.Model(&blog2).Association("SharedTags").Append(tag4)
181
182		DB.Model(&blog).Related(&tags, "SharedTags")
183		if !compareTags(tags, []string{"tag1", "tag2", "tag3", "tag4"}) {
184			t.Errorf("Should find 3 tags with Related")
185		}
186
187		DB.Model(&blog2).Related(&tags, "SharedTags")
188		if !compareTags(tags, []string{"tag1", "tag2", "tag3", "tag4"}) {
189			t.Errorf("Should find 3 tags with Related")
190		}
191
192		// Replace
193		var tag5 = &Tag{Locale: "ZH", Value: "tag5"}
194		var tag6 = &Tag{Locale: "ZH", Value: "tag6"}
195		DB.Model(&blog2).Association("SharedTags").Replace(tag5, tag6)
196		var tags2 []Tag
197		DB.Model(&blog).Related(&tags2, "SharedTags")
198		if !compareTags(tags2, []string{"tag5", "tag6"}) {
199			t.Errorf("Should find 2 tags after Replace")
200		}
201
202		DB.Model(&blog2).Related(&tags2, "SharedTags")
203		if !compareTags(tags2, []string{"tag5", "tag6"}) {
204			t.Errorf("Should find 2 tags after Replace")
205		}
206
207		if DB.Model(&blog).Association("SharedTags").Count() != 2 {
208			t.Errorf("Blog should has three tags after Replace")
209		}
210
211		// Delete
212		DB.Model(&blog).Association("SharedTags").Delete(tag5)
213		var tags3 []Tag
214		DB.Model(&blog).Related(&tags3, "SharedTags")
215		if !compareTags(tags3, []string{"tag6"}) {
216			t.Errorf("Should find 1 tags after Delete")
217		}
218
219		if DB.Model(&blog).Association("SharedTags").Count() != 1 {
220			t.Errorf("Blog should has three tags after Delete")
221		}
222
223		DB.Model(&blog2).Association("SharedTags").Delete(tag3)
224		var tags4 []Tag
225		DB.Model(&blog).Related(&tags4, "SharedTags")
226		if !compareTags(tags4, []string{"tag6"}) {
227			t.Errorf("Tag should not be deleted when Delete with a unrelated tag")
228		}
229
230		// Clear
231		DB.Model(&blog2).Association("SharedTags").Clear()
232		if DB.Model(&blog).Association("SharedTags").Count() != 0 {
233			t.Errorf("All tags should be cleared")
234		}
235	}
236}
237
238func TestManyToManyWithCustomizedForeignKeys2(t *testing.T) {
239	if dialect := os.Getenv("GORM_DIALECT"); dialect != "" && dialect != "sqlite" && dialect != "mssql" {
240		DB.DropTable(&Blog{}, &Tag{})
241		DB.DropTable("locale_blog_tags")
242		DB.CreateTable(&Blog{}, &Tag{})
243		blog := Blog{
244			Locale:  "ZH",
245			Subject: "subject",
246			Body:    "body",
247			LocaleTags: []Tag{
248				{Locale: "ZH", Value: "tag1"},
249				{Locale: "ZH", Value: "tag2"},
250			},
251		}
252		DB.Save(&blog)
253
254		blog2 := Blog{
255			ID:     blog.ID,
256			Locale: "EN",
257		}
258		DB.Create(&blog2)
259
260		// Append
261		var tag3 = &Tag{Locale: "ZH", Value: "tag3"}
262		DB.Model(&blog).Association("LocaleTags").Append([]*Tag{tag3})
263		if !compareTags(blog.LocaleTags, []string{"tag1", "tag2", "tag3"}) {
264			t.Errorf("Blog should has three tags after Append")
265		}
266
267		if DB.Model(&blog).Association("LocaleTags").Count() != 3 {
268			t.Errorf("Blog should has three tags after Append")
269		}
270
271		if DB.Model(&blog2).Association("LocaleTags").Count() != 0 {
272			t.Errorf("EN Blog should has 0 tags after ZH Blog Append")
273		}
274
275		var tags []Tag
276		DB.Model(&blog).Related(&tags, "LocaleTags")
277		if !compareTags(tags, []string{"tag1", "tag2", "tag3"}) {
278			t.Errorf("Should find 3 tags with Related")
279		}
280
281		DB.Model(&blog2).Related(&tags, "LocaleTags")
282		if len(tags) != 0 {
283			t.Errorf("Should find 0 tags with Related for EN Blog")
284		}
285
286		var blog1 Blog
287		DB.Preload("LocaleTags").Find(&blog1, "locale = ? AND id = ?", "ZH", blog.ID)
288		if !compareTags(blog1.LocaleTags, []string{"tag1", "tag2", "tag3"}) {
289			t.Errorf("Preload many2many relations")
290		}
291
292		var tag4 = &Tag{Locale: "ZH", Value: "tag4"}
293		DB.Model(&blog2).Association("LocaleTags").Append(tag4)
294
295		DB.Model(&blog).Related(&tags, "LocaleTags")
296		if !compareTags(tags, []string{"tag1", "tag2", "tag3"}) {
297			t.Errorf("Should find 3 tags with Related for EN Blog")
298		}
299
300		DB.Model(&blog2).Related(&tags, "LocaleTags")
301		if !compareTags(tags, []string{"tag4"}) {
302			t.Errorf("Should find 1 tags with Related for EN Blog")
303		}
304
305		// Replace
306		var tag5 = &Tag{Locale: "ZH", Value: "tag5"}
307		var tag6 = &Tag{Locale: "ZH", Value: "tag6"}
308		DB.Model(&blog2).Association("LocaleTags").Replace(tag5, tag6)
309
310		var tags2 []Tag
311		DB.Model(&blog).Related(&tags2, "LocaleTags")
312		if !compareTags(tags2, []string{"tag1", "tag2", "tag3"}) {
313			t.Errorf("CN Blog's tags should not be changed after EN Blog Replace")
314		}
315
316		var blog11 Blog
317		DB.Preload("LocaleTags").First(&blog11, "id = ? AND locale = ?", blog.ID, blog.Locale)
318		if !compareTags(blog11.LocaleTags, []string{"tag1", "tag2", "tag3"}) {
319			t.Errorf("CN Blog's tags should not be changed after EN Blog Replace")
320		}
321
322		DB.Model(&blog2).Related(&tags2, "LocaleTags")
323		if !compareTags(tags2, []string{"tag5", "tag6"}) {
324			t.Errorf("Should find 2 tags after Replace")
325		}
326
327		var blog21 Blog
328		DB.Preload("LocaleTags").First(&blog21, "id = ? AND locale = ?", blog2.ID, blog2.Locale)
329		if !compareTags(blog21.LocaleTags, []string{"tag5", "tag6"}) {
330			t.Errorf("EN Blog's tags should be changed after Replace")
331		}
332
333		if DB.Model(&blog).Association("LocaleTags").Count() != 3 {
334			t.Errorf("ZH Blog should has three tags after Replace")
335		}
336
337		if DB.Model(&blog2).Association("LocaleTags").Count() != 2 {
338			t.Errorf("EN Blog should has two tags after Replace")
339		}
340
341		// Delete
342		DB.Model(&blog).Association("LocaleTags").Delete(tag5)
343
344		if DB.Model(&blog).Association("LocaleTags").Count() != 3 {
345			t.Errorf("ZH Blog should has three tags after Delete with EN's tag")
346		}
347
348		if DB.Model(&blog2).Association("LocaleTags").Count() != 2 {
349			t.Errorf("EN Blog should has two tags after ZH Blog Delete with EN's tag")
350		}
351
352		DB.Model(&blog2).Association("LocaleTags").Delete(tag5)
353
354		if DB.Model(&blog).Association("LocaleTags").Count() != 3 {
355			t.Errorf("ZH Blog should has three tags after EN Blog Delete with EN's tag")
356		}
357
358		if DB.Model(&blog2).Association("LocaleTags").Count() != 1 {
359			t.Errorf("EN Blog should has 1 tags after EN Blog Delete with EN's tag")
360		}
361
362		// Clear
363		DB.Model(&blog2).Association("LocaleTags").Clear()
364		if DB.Model(&blog).Association("LocaleTags").Count() != 3 {
365			t.Errorf("ZH Blog's tags should not be cleared when clear EN Blog's tags")
366		}
367
368		if DB.Model(&blog2).Association("LocaleTags").Count() != 0 {
369			t.Errorf("EN Blog's tags should be cleared when clear EN Blog's tags")
370		}
371
372		DB.Model(&blog).Association("LocaleTags").Clear()
373		if DB.Model(&blog).Association("LocaleTags").Count() != 0 {
374			t.Errorf("ZH Blog's tags should be cleared when clear ZH Blog's tags")
375		}
376
377		if DB.Model(&blog2).Association("LocaleTags").Count() != 0 {
378			t.Errorf("EN Blog's tags should be cleared")
379		}
380	}
381}
382