Skip to content

Commit 2d1c086

Browse files
committed
test(mysql): 添加复杂条件和软删除功能测试
- 实现大数据量级复杂条件查询测试 - 验证软删除功能与unscoped参数的正确性 - 测试复杂关联查询中的where和order条件 - 添加大批量数据分批查询功能验证 - 实现边界和异常情况的测试场景
1 parent f10bbda commit 2d1c086

File tree

1 file changed

+347
-0
lines changed

1 file changed

+347
-0
lines changed
Lines changed: 347 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,347 @@
1+
// Copyright GoFrame Author(https://goframe.org). All Rights Reserved.
2+
//
3+
// This Source Code Form is subject to the terms of the MIT License.
4+
// If a copy of the MIT was not distributed with this file,
5+
// You can obtain one at https://github.com/gogf/gf.
6+
7+
package mysql_test
8+
9+
import (
10+
"fmt"
11+
"testing"
12+
13+
"github.com/gogf/gf/v2/database/gdb"
14+
"github.com/gogf/gf/v2/frame/g"
15+
"github.com/gogf/gf/v2/os/gtime"
16+
"github.com/gogf/gf/v2/test/gtest"
17+
"github.com/gogf/gf/v2/util/gmeta"
18+
)
19+
20+
// Test_WithAll_Complex 大数据量级复杂条件及软删除测试
21+
func Test_WithAll_Complex(t *testing.T) {
22+
var (
23+
tableUser = "user_complex"
24+
tableUserDetail = "user_detail_complex"
25+
tableUserScores = "user_scores_complex"
26+
)
27+
28+
// 1. 软删除相关数据结构
29+
type UserDetailSoft struct {
30+
gmeta.Meta `orm:"table:user_detail_complex"`
31+
Uid int `json:"uid"`
32+
Address string `json:"address"`
33+
DeleteAt *gtime.Time `json:"delete_at" orm:"delete_at"`
34+
}
35+
36+
type UserScoresSoft struct {
37+
gmeta.Meta `orm:"table:user_scores_complex"`
38+
Id int `json:"id"`
39+
Uid int `json:"uid"`
40+
Score int `json:"score"`
41+
DeleteAt *gtime.Time `json:"delete_at" orm:"delete_at"`
42+
}
43+
44+
type UserSoft struct {
45+
gmeta.Meta `orm:"table:user_complex"`
46+
Id int `json:"id"`
47+
Name string `json:"name"`
48+
UserDetail *UserDetailSoft `orm:"with:uid=id, unscoped:true"`
49+
UserScores []*UserScoresSoft `orm:"with:uid=id, unscoped:true"`
50+
}
51+
52+
// 2. 复杂条件筛选数据结构
53+
type UserDetailCond struct {
54+
gmeta.Meta `orm:"table:user_detail_complex"`
55+
Uid int `json:"uid"`
56+
Address string `json:"address"`
57+
}
58+
59+
type UserScoresCond struct {
60+
gmeta.Meta `orm:"table:user_scores_complex"`
61+
Id int `json:"id"`
62+
Uid int `json:"uid"`
63+
Score int `json:"score"`
64+
}
65+
66+
type UserCond struct {
67+
gmeta.Meta `orm:"table:user_complex"`
68+
Id int `json:"id"`
69+
Name string `json:"name"`
70+
UserDetail *UserDetailCond `orm:"with:uid=id, where:uid > 3"`
71+
UserScores []*UserScoresCond `orm:"with:uid=id, where:score>1 and score<5, order:score desc"`
72+
}
73+
74+
// 初始化表结构
75+
dropTable(tableUser)
76+
dropTable(tableUserDetail)
77+
dropTable(tableUserScores)
78+
db.SetDebug(true)
79+
defer db.SetDebug(false)
80+
81+
_, err := db.Exec(ctx, fmt.Sprintf(`
82+
CREATE TABLE %s (
83+
id int(10) unsigned NOT NULL AUTO_INCREMENT,
84+
name varchar(45) NOT NULL,
85+
PRIMARY KEY (id)
86+
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
87+
`, tableUser))
88+
gtest.AssertNil(err)
89+
90+
_, err = db.Exec(ctx, fmt.Sprintf(`
91+
CREATE TABLE %s (
92+
uid int(10) unsigned NOT NULL,
93+
address varchar(100) NOT NULL,
94+
delete_at datetime DEFAULT NULL,
95+
PRIMARY KEY (uid)
96+
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
97+
`, tableUserDetail))
98+
gtest.AssertNil(err)
99+
100+
_, err = db.Exec(ctx, fmt.Sprintf(`
101+
CREATE TABLE %s (
102+
id int(10) unsigned NOT NULL AUTO_INCREMENT,
103+
uid int(10) unsigned NOT NULL,
104+
score int(10) NOT NULL,
105+
delete_at datetime DEFAULT NULL,
106+
PRIMARY KEY (id)
107+
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
108+
`, tableUserScores))
109+
gtest.AssertNil(err)
110+
111+
defer dropTable(tableUser)
112+
defer dropTable(tableUserDetail)
113+
defer dropTable(tableUserScores)
114+
115+
db.SetDebug(true)
116+
defer db.SetDebug(false)
117+
118+
// ========================================
119+
// 数据初始化
120+
// ========================================
121+
const (
122+
userCount = 100
123+
scorePerUser = 10
124+
)
125+
126+
fmt.Println("\n========== 开始初始化数据 ==========")
127+
128+
// 1. 插入用户
129+
usersData := make(g.List, 0, userCount)
130+
for i := 1; i <= userCount; i++ {
131+
usersData = append(usersData, g.Map{"id": i, "name": fmt.Sprintf("user_%d", i)})
132+
}
133+
_, err = db.Model(tableUser).Data(usersData).Insert()
134+
gtest.AssertNil(err)
135+
136+
// 2. 插入详情(部分标记为删除)
137+
detailsData := make(g.List, 0, userCount)
138+
for i := 1; i <= userCount; i++ {
139+
deleteAt := interface{}(nil)
140+
if i%2 == 0 { // 偶数用户详情被软删除
141+
deleteAt = gtime.Now()
142+
}
143+
detailsData = append(detailsData, g.Map{
144+
"uid": i,
145+
"address": fmt.Sprintf("address_%d", i),
146+
"delete_at": deleteAt,
147+
})
148+
}
149+
_, err = db.Model(tableUserDetail).Data(detailsData).Insert()
150+
gtest.AssertNil(err)
151+
152+
// 3. 插入成绩(部分标记为删除,且分数各异)
153+
scoresData := make(g.List, 0, userCount*scorePerUser)
154+
for i := 1; i <= userCount; i++ {
155+
for j := 1; j <= scorePerUser; j++ {
156+
deleteAt := interface{}(nil)
157+
if j%2 == 0 { // 每个用户的偶数项成绩被软删除
158+
deleteAt = gtime.Now()
159+
}
160+
scoresData = append(scoresData, g.Map{
161+
"uid": i,
162+
"score": j, // score 从 1 到 10
163+
"delete_at": deleteAt,
164+
})
165+
}
166+
}
167+
_, err = db.Model(tableUserScores).Data(scoresData).Batch(500).Insert()
168+
gtest.AssertNil(err)
169+
170+
fmt.Println("========== 数据初始化完成 ==========")
171+
172+
// ========================================
173+
// Scenario 1: 验证 unscoped:true
174+
// ========================================
175+
gtest.C(t, func(t *gtest.T) {
176+
fmt.Println("\nScenario 1: 验证 unscoped:true (包含已软删除数据)")
177+
var users []*UserSoft
178+
err := db.Model(tableUser).WithAll().Where("id", 1).Scan(&users)
179+
t.AssertNil(err)
180+
t.Assert(len(users), 1)
181+
182+
// user := users[0]
183+
// UserDetail 虽然 i=1 是奇数没删,但我们验证 i=2 的
184+
var usersAll []*UserSoft
185+
err = db.Model(tableUser).WithAll().Where("id IN (1,2)").Order("id").Scan(&usersAll)
186+
t.AssertNil(err)
187+
t.Assert(len(usersAll), 2)
188+
189+
// user 1: detail not deleted
190+
t.AssertNE(usersAll[0].UserDetail, nil)
191+
t.AssertNil(usersAll[0].UserDetail.DeleteAt)
192+
193+
// user 2: detail deleted, but unscoped:true should find it
194+
t.AssertNE(usersAll[1].UserDetail, nil)
195+
t.AssertNE(usersAll[1].UserDetail.DeleteAt, nil)
196+
197+
// scores: half deleted, but unscoped:true should find all 10
198+
t.Assert(len(usersAll[0].UserScores), 10)
199+
deletedCount := 0
200+
for _, s := range usersAll[0].UserScores {
201+
if s.DeleteAt != nil {
202+
deletedCount++
203+
}
204+
}
205+
t.Assert(deletedCount, 5)
206+
})
207+
208+
// ========================================
209+
// Scenario 2: 验证 where 和 order 条件
210+
// ========================================
211+
gtest.C(t, func(t *gtest.T) {
212+
fmt.Println("\nScenario 2: 验证 where 和 order 条件")
213+
// UserDetailCond: where:uid > 3
214+
// UserScoresCond: where:score>1 and score<5, order:score desc
215+
// Note: Normal scan will respect soft delete by default if not unscoped.
216+
// So for UserScores, score 2 and 4 are deleted (even items).
217+
// score 1, 3, 5, 7, 9 are NOT deleted.
218+
// where: score > 1 and score < 5 => scores 2, 3, 4
219+
// But 2 and 4 are deleted. So only score 3 should remain.
220+
221+
var users []*UserCond
222+
err := db.Model(tableUser).WithAll().Where("id", 4).Scan(&users)
223+
t.AssertNil(err)
224+
t.Assert(len(users), 1)
225+
226+
user := users[0]
227+
// Detail: uid=4 > 3, should be there.
228+
// BUT wait, user 4's detail is deleted (4%2==0).
229+
// Since UserDetailCond DOES NOT have unscoped:true, it should be nil!
230+
t.Assert(user.UserDetail, nil)
231+
232+
// User 5
233+
users = nil
234+
err = db.Model(tableUser).WithAll().Where("id", 5).Scan(&users)
235+
t.AssertNil(err)
236+
user = users[0]
237+
// uid=5 > 3 and not deleted.
238+
t.AssertNE(user.UserDetail, nil)
239+
t.Assert(user.UserDetail.Uid, 5)
240+
241+
// User 3
242+
users = nil
243+
err = db.Model(tableUser).WithAll().Where("id", 3).Scan(&users)
244+
t.AssertNil(err)
245+
user = users[0]
246+
// uid=3 NOT > 3.
247+
t.Assert(user.UserDetail, nil)
248+
249+
// UserScores: score > 1 and score < 5 => 2, 3, 4.
250+
// 2 and 4 are deleted. Only 3 remains.
251+
t.Assert(len(user.UserScores), 1)
252+
t.Assert(user.UserScores[0].Score, 3)
253+
})
254+
255+
// ========================================
256+
// Scenario 3: 大数据量分批查询与复杂条件结合
257+
// ========================================
258+
gtest.C(t, func(t *gtest.T) {
259+
fmt.Println("\nScenario 3: 大数据量分批查询与复杂条件结合")
260+
261+
// 增加数据量以测试分批
262+
const largeUserCount = 500
263+
usersData := make(g.List, 0, largeUserCount)
264+
for i := 101; i <= 101+largeUserCount; i++ {
265+
usersData = append(usersData, g.Map{"id": i, "name": fmt.Sprintf("user_%d", i)})
266+
}
267+
_, err = db.Model(tableUser).Data(usersData).Insert()
268+
t.AssertNil(err)
269+
270+
detailsData := make(g.List, 0, largeUserCount)
271+
for i := 101; i <= 101+largeUserCount; i++ {
272+
detailsData = append(detailsData, g.Map{
273+
"uid": i,
274+
"address": fmt.Sprintf("address_%d", i),
275+
})
276+
}
277+
_, err = db.Model(tableUserDetail).Data(detailsData).Insert()
278+
t.AssertNil(err)
279+
280+
scoresData := make(g.List, 0, largeUserCount*scorePerUser)
281+
for i := 101; i <= 101+largeUserCount; i++ {
282+
for j := 1; j <= scorePerUser; j++ {
283+
scoresData = append(scoresData, g.Map{
284+
"uid": i,
285+
"score": j,
286+
})
287+
}
288+
}
289+
_, err = db.Model(tableUserScores).Data(scoresData).Batch(1000).Insert()
290+
t.AssertNil(err)
291+
292+
// 执行分批查询
293+
var users []*UserCond
294+
err = db.Model(tableUser).WithAll().
295+
WithBatchOption(gdb.WithBatchOption{
296+
Layer: 0,
297+
Enabled: true,
298+
BatchThreshold: 0,
299+
BatchSize: 100,
300+
}).
301+
WithBatch().
302+
Where("id > ?", 100).
303+
Scan(&users)
304+
305+
t.AssertNil(err)
306+
t.Assert(len(users), largeUserCount+1)
307+
308+
// 验证复杂条件在分批下是否依然有效
309+
// UserScoresCond: where:score>1 and score<5 => 2, 3, 4
310+
// 对于新插入的数据,没有标记删除,所以应该有 3 条 (2, 3, 4)
311+
// 且 order:score desc => 4, 3, 2
312+
for _, u := range users {
313+
t.Assert(len(u.UserScores), 3)
314+
t.Assert(u.UserScores[0].Score, 4)
315+
t.Assert(u.UserScores[1].Score, 3)
316+
t.Assert(u.UserScores[2].Score, 2)
317+
318+
if u.Id > 3 {
319+
t.AssertNE(u.UserDetail, nil)
320+
} else {
321+
t.Assert(u.UserDetail, nil)
322+
}
323+
}
324+
})
325+
326+
// ========================================
327+
// Scenario 4: 边界与异常情况
328+
// ========================================
329+
gtest.C(t, func(t *gtest.T) {
330+
fmt.Println("\nScenario 4: 边界与异常情况")
331+
332+
// 1. 查询不存在的用户
333+
var users []*UserCond
334+
err := db.Model(tableUser).WithAll().Where("id", 99999).Scan(&users)
335+
t.AssertNil(err)
336+
t.Assert(len(users), 0)
337+
338+
// 2. 关联表完全没数据 (清空 scores)
339+
_, err = db.Exec(ctx, fmt.Sprintf("TRUNCATE TABLE %s", tableUserScores))
340+
t.AssertNil(err)
341+
342+
err = db.Model(tableUser).WithAll().Where("id", 1).Scan(&users)
343+
t.AssertNil(err)
344+
t.Assert(len(users), 1)
345+
t.Assert(len(users[0].UserScores), 0)
346+
})
347+
}

0 commit comments

Comments
 (0)