mirror of
https://gitee.com/johng/gf
synced 2026-07-06 13:42:46 +08:00
test(contrib/drivers/mysql): enhance complex query tests (#4707)
## Summary - Add comprehensive JOIN tests (Inner/Left/Right Join) - Add SubQuery tests - Add complex WHERE condition tests (Or/Group/Having) - Add advanced query builder tests **Test coverage added:** ~26 test functions across 3 files Ref #4689 ## Test plan ```bash cd contrib/drivers/mysql go test -v -run "TestModel_Join|TestModel_SubQuery|TestModel_Where.*Complex" ```
This commit is contained in:
@ -175,3 +175,339 @@ func Test_Model_FieldsPrefix(t *testing.T) {
|
||||
t.Assert(r[0]["nickname"], "name_1")
|
||||
})
|
||||
}
|
||||
|
||||
// Test_Model_Join_FiveTables tests complex join with 5+ tables
|
||||
func Test_Model_Join_FiveTables(t *testing.T) {
|
||||
var (
|
||||
table1 = gtime.TimestampNanoStr() + "_table1"
|
||||
table2 = gtime.TimestampNanoStr() + "_table2"
|
||||
table3 = gtime.TimestampNanoStr() + "_table3"
|
||||
table4 = gtime.TimestampNanoStr() + "_table4"
|
||||
table5 = gtime.TimestampNanoStr() + "_table5"
|
||||
)
|
||||
createInitTable(table1)
|
||||
defer dropTable(table1)
|
||||
createInitTable(table2)
|
||||
defer dropTable(table2)
|
||||
createInitTable(table3)
|
||||
defer dropTable(table3)
|
||||
createInitTable(table4)
|
||||
defer dropTable(table4)
|
||||
createInitTable(table5)
|
||||
defer dropTable(table5)
|
||||
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
r, err := db.Model(table1).As("t1").
|
||||
FieldsPrefix("t1", "id", "nickname").
|
||||
FieldsPrefix("t2", "passport").
|
||||
InnerJoin(table2+" AS t2", "t1.id = t2.id").
|
||||
InnerJoin(table3+" AS t3", "t2.id = t3.id").
|
||||
InnerJoin(table4+" AS t4", "t3.id = t4.id").
|
||||
InnerJoin(table5+" AS t5", "t4.id = t5.id").
|
||||
Where("t1.id IN(?)", g.Slice{1, 2, 3}).
|
||||
Order("t1.id asc").
|
||||
All()
|
||||
t.AssertNil(err)
|
||||
|
||||
t.Assert(len(r), 3)
|
||||
t.Assert(r[0]["id"], "1")
|
||||
t.Assert(r[0]["nickname"], "name_1")
|
||||
t.Assert(r[0]["passport"], "user_1")
|
||||
t.Assert(r[2]["id"], "3")
|
||||
})
|
||||
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
// 6 tables with mixed join types
|
||||
table6 := gtime.TimestampNanoStr() + "_table6"
|
||||
createInitTable(table6)
|
||||
defer dropTable(table6)
|
||||
|
||||
r, err := db.Model(table1).As("t1").
|
||||
Fields("t1.id").
|
||||
InnerJoin(table2+" AS t2", "t1.id = t2.id").
|
||||
LeftJoin(table3+" AS t3", "t2.id = t3.id").
|
||||
InnerJoin(table4+" AS t4", "t3.id = t4.id").
|
||||
RightJoin(table5+" AS t5", "t4.id = t5.id").
|
||||
LeftJoin(table6+" AS t6", "t5.id = t6.id").
|
||||
Where("t1.id", 5).
|
||||
One()
|
||||
t.AssertNil(err)
|
||||
|
||||
t.Assert(r["id"], "5")
|
||||
})
|
||||
}
|
||||
|
||||
// Test_Model_Join_SelfJoin tests self-join scenarios
|
||||
func Test_Model_Join_SelfJoin(t *testing.T) {
|
||||
table := createInitTable()
|
||||
defer dropTable(table)
|
||||
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
// Self-join to find pairs where a.id < b.id
|
||||
r, err := db.Model(table).As("a").
|
||||
Fields("a.id AS a_id", "b.id AS b_id").
|
||||
InnerJoin(table+" AS b", "a.id < b.id").
|
||||
Where("a.id", 1).
|
||||
Where("b.id <=", 3).
|
||||
Order("b.id asc").
|
||||
All()
|
||||
t.AssertNil(err)
|
||||
|
||||
t.Assert(len(r), 2)
|
||||
t.Assert(r[0]["a_id"], "1")
|
||||
t.Assert(r[0]["b_id"], "2")
|
||||
t.Assert(r[1]["b_id"], "3")
|
||||
})
|
||||
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
// Self-join with multiple conditions
|
||||
r, err := db.Model(table).As("a").
|
||||
Fields("a.id", "a.nickname", "b.nickname AS other_nickname").
|
||||
LeftJoin(table+" AS b", "a.id = b.id - 1").
|
||||
Where("a.id IN(?)", g.Slice{1, 2}).
|
||||
Order("a.id asc").
|
||||
All()
|
||||
t.AssertNil(err)
|
||||
|
||||
t.Assert(len(r), 2)
|
||||
t.Assert(r[0]["id"], "1")
|
||||
t.Assert(r[0]["nickname"], "name_1")
|
||||
t.Assert(r[0]["other_nickname"], "name_2")
|
||||
t.Assert(r[1]["id"], "2")
|
||||
t.Assert(r[1]["other_nickname"], "name_3")
|
||||
})
|
||||
}
|
||||
|
||||
// Test_Model_Join_LeftJoinNull tests LEFT JOIN NULL handling
|
||||
func Test_Model_Join_LeftJoinNull(t *testing.T) {
|
||||
var (
|
||||
table1 = gtime.TimestampNanoStr() + "_table1"
|
||||
table2 = gtime.TimestampNanoStr() + "_table2"
|
||||
)
|
||||
createInitTable(table1)
|
||||
defer dropTable(table1)
|
||||
|
||||
// Create table2 with only partial data
|
||||
createTable(table2)
|
||||
defer dropTable(table2)
|
||||
_, err := db.Insert(ctx, table2, g.List{
|
||||
{"id": 1, "passport": "user_1", "nickname": "name_1"},
|
||||
{"id": 2, "passport": "user_2", "nickname": "name_2"},
|
||||
})
|
||||
if err != nil {
|
||||
gtest.Fatal(err)
|
||||
}
|
||||
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
// LEFT JOIN - table1 has all records, table2 only has id 1,2
|
||||
r, err := db.Model(table1).As("t1").
|
||||
FieldsPrefix("t1", "id").
|
||||
FieldsPrefix("t2", "nickname").
|
||||
LeftJoin(table2+" AS t2", "t1.id = t2.id").
|
||||
Where("t1.id IN(?)", g.Slice{1, 2, 3}).
|
||||
Order("t1.id asc").
|
||||
All()
|
||||
t.AssertNil(err)
|
||||
|
||||
t.Assert(len(r), 3)
|
||||
t.Assert(r[0]["id"], "1")
|
||||
t.Assert(r[0]["nickname"], "name_1") // matched
|
||||
t.Assert(r[1]["id"], "2")
|
||||
t.Assert(r[1]["nickname"], "name_2") // matched
|
||||
t.Assert(r[2]["id"], "3")
|
||||
// r[2]["nickname"] should be NULL/empty from t2
|
||||
})
|
||||
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
// Find records where RIGHT table is NULL
|
||||
r, err := db.Model(table1).As("t1").
|
||||
FieldsPrefix("t1", "id", "nickname").
|
||||
LeftJoin(table2+" AS t2", "t1.id = t2.id").
|
||||
Where("t2.id IS NULL").
|
||||
Where("t1.id IN(?)", g.Slice{1, 2, 3, 4}).
|
||||
Order("t1.id asc").
|
||||
All()
|
||||
t.AssertNil(err)
|
||||
|
||||
// Should return id 3,4 (not in table2)
|
||||
t.Assert(len(r), 2)
|
||||
t.Assert(r[0]["id"], "3")
|
||||
t.Assert(r[0]["nickname"], "name_3")
|
||||
t.Assert(r[1]["id"], "4")
|
||||
})
|
||||
}
|
||||
|
||||
// Test_Model_Join_RightJoinNull tests RIGHT JOIN NULL handling
|
||||
func Test_Model_Join_RightJoinNull(t *testing.T) {
|
||||
var (
|
||||
table1 = gtime.TimestampNanoStr() + "_table1"
|
||||
table2 = gtime.TimestampNanoStr() + "_table2"
|
||||
)
|
||||
// table1 has partial data
|
||||
createTable(table1)
|
||||
defer dropTable(table1)
|
||||
_, err := db.Insert(ctx, table1, g.List{
|
||||
{"id": 1, "passport": "user_1", "nickname": "name_1"},
|
||||
{"id": 2, "passport": "user_2", "nickname": "name_2"},
|
||||
})
|
||||
if err != nil {
|
||||
gtest.Fatal(err)
|
||||
}
|
||||
|
||||
// table2 has all data
|
||||
createInitTable(table2)
|
||||
defer dropTable(table2)
|
||||
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
// RIGHT JOIN - table1 only has id 1,2, table2 has all
|
||||
r, err := db.Model(table1).As("t1").
|
||||
FieldsPrefix("t2", "id").
|
||||
FieldsPrefix("t1", "nickname").
|
||||
RightJoin(table2+" AS t2", "t1.id = t2.id").
|
||||
Where("t2.id IN(?)", g.Slice{1, 2, 3}).
|
||||
Order("t2.id asc").
|
||||
All()
|
||||
t.AssertNil(err)
|
||||
|
||||
t.Assert(len(r), 3)
|
||||
t.Assert(r[0]["id"], "1")
|
||||
t.Assert(r[0]["nickname"], "name_1") // matched
|
||||
t.Assert(r[1]["id"], "2")
|
||||
t.Assert(r[1]["nickname"], "name_2") // matched
|
||||
t.Assert(r[2]["id"], "3")
|
||||
// r[2]["nickname"] should be NULL/empty from t1
|
||||
})
|
||||
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
// Find records where LEFT table is NULL
|
||||
r, err := db.Model(table1).As("t1").
|
||||
FieldsPrefix("t2", "id", "nickname").
|
||||
RightJoin(table2+" AS t2", "t1.id = t2.id").
|
||||
Where("t1.id IS NULL").
|
||||
Where("t2.id IN(?)", g.Slice{1, 2, 3, 4}).
|
||||
Order("t2.id asc").
|
||||
All()
|
||||
t.AssertNil(err)
|
||||
|
||||
// Should return id 3,4 (not in table1)
|
||||
t.Assert(len(r), 2)
|
||||
t.Assert(r[0]["id"], "3")
|
||||
t.Assert(r[0]["nickname"], "name_3")
|
||||
t.Assert(r[1]["id"], "4")
|
||||
})
|
||||
}
|
||||
|
||||
// Test_Model_Join_OnVsWhere tests difference between ON and WHERE conditions
|
||||
func Test_Model_Join_OnVsWhere(t *testing.T) {
|
||||
var (
|
||||
table1 = gtime.TimestampNanoStr() + "_table1"
|
||||
table2 = gtime.TimestampNanoStr() + "_table2"
|
||||
)
|
||||
createInitTable(table1)
|
||||
defer dropTable(table1)
|
||||
createInitTable(table2)
|
||||
defer dropTable(table2)
|
||||
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
// INNER JOIN: ON and WHERE behave the same
|
||||
r1, err := db.Model(table1).As("t1").
|
||||
Fields("t1.id").
|
||||
InnerJoin(table2+" AS t2", "t1.id = t2.id AND t2.id <= 3").
|
||||
Order("t1.id asc").
|
||||
All()
|
||||
t.AssertNil(err)
|
||||
|
||||
r2, err := db.Model(table1).As("t1").
|
||||
Fields("t1.id").
|
||||
InnerJoin(table2+" AS t2", "t1.id = t2.id").
|
||||
Where("t2.id <=", 3).
|
||||
Order("t1.id asc").
|
||||
All()
|
||||
t.AssertNil(err)
|
||||
|
||||
// For INNER JOIN, results should be identical
|
||||
t.Assert(len(r1), 3)
|
||||
t.Assert(len(r2), 3)
|
||||
t.Assert(r1[0]["id"], r2[0]["id"])
|
||||
})
|
||||
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
// LEFT JOIN: ON filter in join condition vs WHERE filter after join
|
||||
// ON condition: filters t2 before join (keeps all t1 rows)
|
||||
r1, err := db.Model(table1).As("t1").
|
||||
FieldsPrefix("t1", "id").
|
||||
FieldsPrefix("t2", "nickname").
|
||||
LeftJoin(table2+" AS t2", "t1.id = t2.id AND t2.id <= 2").
|
||||
Where("t1.id <=", 4).
|
||||
Order("t1.id asc").
|
||||
All()
|
||||
t.AssertNil(err)
|
||||
|
||||
// WHERE condition: filters result after join (removes t1 rows where t2 is NULL)
|
||||
r2, err := db.Model(table1).As("t1").
|
||||
FieldsPrefix("t1", "id").
|
||||
FieldsPrefix("t2", "nickname").
|
||||
LeftJoin(table2+" AS t2", "t1.id = t2.id").
|
||||
Where("t1.id <=", 4).
|
||||
Where("t2.id <=", 2).
|
||||
Order("t1.id asc").
|
||||
All()
|
||||
t.AssertNil(err)
|
||||
|
||||
// r1: all t1 rows (1,2,3,4), t2 data only for id 1,2
|
||||
t.Assert(len(r1), 4)
|
||||
t.Assert(r1[0]["id"], "1")
|
||||
t.Assert(r1[0]["nickname"], "name_1")
|
||||
t.Assert(r1[2]["id"], "3")
|
||||
// r1[2]["nickname"] is NULL from t2
|
||||
|
||||
// r2: only rows where t2.id <= 2, so only id 1,2
|
||||
t.Assert(len(r2), 2)
|
||||
t.Assert(r2[0]["id"], "1")
|
||||
t.Assert(r2[1]["id"], "2")
|
||||
})
|
||||
}
|
||||
|
||||
// Test_Model_Join_ComplexConditions tests joins with complex ON conditions
|
||||
func Test_Model_Join_ComplexConditions(t *testing.T) {
|
||||
var (
|
||||
table1 = gtime.TimestampNanoStr() + "_table1"
|
||||
table2 = gtime.TimestampNanoStr() + "_table2"
|
||||
)
|
||||
createInitTable(table1)
|
||||
defer dropTable(table1)
|
||||
createInitTable(table2)
|
||||
defer dropTable(table2)
|
||||
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
// Multiple AND conditions in ON clause
|
||||
r, err := db.Model(table1).As("t1").
|
||||
Fields("t1.id", "t1.nickname").
|
||||
InnerJoin(
|
||||
table2+" AS t2",
|
||||
"t1.id = t2.id AND t1.nickname = t2.nickname AND t1.id BETWEEN 2 AND 4",
|
||||
).
|
||||
Order("t1.id asc").
|
||||
All()
|
||||
t.AssertNil(err)
|
||||
|
||||
t.Assert(len(r), 3)
|
||||
t.Assert(r[0]["id"], "2")
|
||||
t.Assert(r[2]["id"], "4")
|
||||
})
|
||||
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
// OR conditions in ON clause (need to use Where for OR in join)
|
||||
r, err := db.Model(table1).As("t1").
|
||||
Fields("t1.id").
|
||||
InnerJoin(table2+" AS t2", "t1.id = t2.id").
|
||||
Where("t2.id = 1 OR t2.id = 5").
|
||||
Order("t1.id asc").
|
||||
All()
|
||||
t.AssertNil(err)
|
||||
|
||||
t.Assert(len(r), 2)
|
||||
t.Assert(r[0]["id"], "1")
|
||||
t.Assert(r[1]["id"], "5")
|
||||
})
|
||||
}
|
||||
|
||||
@ -7,6 +7,7 @@
|
||||
package mysql_test
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"testing"
|
||||
|
||||
"github.com/gogf/gf/v2/frame/g"
|
||||
@ -64,3 +65,247 @@ func Test_Model_SubQuery_Model(t *testing.T) {
|
||||
t.Assert(r[0]["id"], 5)
|
||||
})
|
||||
}
|
||||
|
||||
// Test_Model_SubQuery_Correlated tests scalar subquery and correlated subquery with EXISTS
|
||||
func Test_Model_SubQuery_Correlated(t *testing.T) {
|
||||
table := createInitTable()
|
||||
defer dropTable(table)
|
||||
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
// Scalar subquery: find users whose id is greater than average id
|
||||
subQuery := db.Model(table + " AS inner_table").Fields("AVG(id)")
|
||||
r, err := db.Model(table).Where(
|
||||
"id > (?)",
|
||||
subQuery,
|
||||
).OrderAsc("id").All()
|
||||
t.AssertNil(err)
|
||||
|
||||
// Average of 1-10 is 5.5, so expect ids 6-10
|
||||
t.Assert(len(r), 5)
|
||||
t.Assert(r[0]["id"], 6)
|
||||
t.Assert(r[4]["id"], 10)
|
||||
})
|
||||
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
// Correlated subquery with EXISTS: find users with id matching their own id
|
||||
r, err := db.Model(table+" AS outer_table").
|
||||
Where(
|
||||
fmt.Sprintf("EXISTS (SELECT 1 FROM %s AS inner_table WHERE inner_table.id = outer_table.id AND inner_table.id <= ?)", table),
|
||||
3,
|
||||
).
|
||||
OrderAsc("id").
|
||||
All()
|
||||
t.AssertNil(err)
|
||||
|
||||
t.Assert(len(r), 3)
|
||||
t.Assert(r[0]["id"], 1)
|
||||
t.Assert(r[2]["id"], 3)
|
||||
})
|
||||
}
|
||||
|
||||
// Test_Model_SubQuery_From tests subquery in FROM clause
|
||||
func Test_Model_SubQuery_From(t *testing.T) {
|
||||
table := createInitTable()
|
||||
defer dropTable(table)
|
||||
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
// Subquery in FROM clause
|
||||
subQuery := db.Model(table).Where("id <=", 5)
|
||||
r, err := db.Model("(?) AS sub", subQuery).
|
||||
Fields("sub.id", "sub.nickname").
|
||||
Where("sub.id >", 2).
|
||||
OrderAsc("id").
|
||||
All()
|
||||
t.AssertNil(err)
|
||||
|
||||
t.Assert(len(r), 3)
|
||||
t.Assert(r[0]["id"], 3)
|
||||
t.Assert(r[0]["nickname"], "name_3")
|
||||
t.Assert(r[2]["id"], 5)
|
||||
})
|
||||
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
// Multiple subqueries in FROM clause with JOIN
|
||||
subQuery1 := db.Model(table).Fields("id", "nickname").Where("id <=", 3)
|
||||
subQuery2 := db.Model(table).Fields("id", "passport").Where("id >=", 3)
|
||||
|
||||
r, err := db.Model("? AS a, ? AS b", subQuery1, subQuery2).
|
||||
Fields("a.id", "a.nickname", "b.passport").
|
||||
Where("a.id = b.id").
|
||||
OrderAsc("id").
|
||||
All()
|
||||
t.AssertNil(err)
|
||||
|
||||
t.Assert(len(r), 1)
|
||||
t.Assert(r[0]["id"], 3)
|
||||
t.Assert(r[0]["nickname"], "name_3")
|
||||
t.Assert(r[0]["passport"], "user_3")
|
||||
})
|
||||
}
|
||||
|
||||
// Test_Model_SubQuery_Select tests subquery in SELECT clause
|
||||
func Test_Model_SubQuery_Select(t *testing.T) {
|
||||
table := createInitTable()
|
||||
defer dropTable(table)
|
||||
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
// Subquery in SELECT clause for scalar value
|
||||
r, err := db.Model(table).
|
||||
Fields("id", "nickname", fmt.Sprintf("(SELECT MAX(id) FROM %s) AS max_id", table)).
|
||||
Where("id", 1).
|
||||
One()
|
||||
t.AssertNil(err)
|
||||
|
||||
t.Assert(r["id"], 1)
|
||||
t.Assert(r["nickname"], "name_1")
|
||||
t.Assert(r["max_id"], 10)
|
||||
})
|
||||
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
// Multiple subqueries in SELECT clause
|
||||
r, err := db.Model(table).
|
||||
Fields(
|
||||
"id",
|
||||
fmt.Sprintf("(SELECT MAX(id) FROM %s) AS max_id", table),
|
||||
fmt.Sprintf("(SELECT MIN(id) FROM %s) AS min_id", table),
|
||||
).
|
||||
Where("id", 5).
|
||||
One()
|
||||
t.AssertNil(err)
|
||||
|
||||
t.Assert(r["id"], 5)
|
||||
t.Assert(r["max_id"], 10)
|
||||
t.Assert(r["min_id"], 1)
|
||||
})
|
||||
}
|
||||
|
||||
// Test_Model_SubQuery_Nested tests multi-level nested subqueries (3+ levels)
|
||||
func Test_Model_SubQuery_Nested(t *testing.T) {
|
||||
table := createInitTable()
|
||||
defer dropTable(table)
|
||||
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
// 3-level nested subquery
|
||||
// Level 3: innermost - get ids <= 8
|
||||
level3 := db.Model(table).Fields("id").Where("id <=", 8)
|
||||
|
||||
// Level 2: middle - filter from level 3 where id >= 3
|
||||
level2 := db.Model("(?) AS l3", level3).Fields("l3.id").Where("l3.id >=", 3)
|
||||
|
||||
// Level 1: outermost - filter from level 2 where id <= 6
|
||||
r, err := db.Model(table).
|
||||
Where("id IN (?)", level2).
|
||||
Where("id <=", 6).
|
||||
OrderAsc("id").
|
||||
All()
|
||||
t.AssertNil(err)
|
||||
|
||||
t.Assert(len(r), 4)
|
||||
t.Assert(r[0]["id"], 3)
|
||||
t.Assert(r[3]["id"], 6)
|
||||
})
|
||||
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
// 4-level nested subquery with aggregates
|
||||
// Level 4: get all ids
|
||||
level4 := db.Model(table).Fields("id")
|
||||
|
||||
// Level 3: get ids > 5 from level 4
|
||||
level3 := db.Model("(?) AS l4", level4).Fields("l4.id").Where("l4.id >", 5)
|
||||
|
||||
// Level 2: get MIN(id) from level 3
|
||||
level2 := db.Model("(?) AS l3", level3).Fields("MIN(l3.id)")
|
||||
|
||||
// Level 1: find records >= the minimum from level 2
|
||||
r, err := db.Model(table).
|
||||
Where("id >= (?)", level2).
|
||||
OrderAsc("id").
|
||||
All()
|
||||
t.AssertNil(err)
|
||||
|
||||
// MIN(id) from level 3 should be 6, so expect ids 6-10
|
||||
t.Assert(len(r), 5)
|
||||
t.Assert(r[0]["id"], 6)
|
||||
t.Assert(r[4]["id"], 10)
|
||||
})
|
||||
}
|
||||
|
||||
// Test_Model_SubQuery_WhereIn tests subquery with WHERE IN
|
||||
func Test_Model_SubQuery_WhereIn(t *testing.T) {
|
||||
table := createInitTable()
|
||||
defer dropTable(table)
|
||||
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
// Simple WHERE IN with subquery
|
||||
subQuery := db.Model(table).Fields("id").Where("id IN(?)", g.Slice{2, 4, 6})
|
||||
r, err := db.Model(table).
|
||||
Where("id IN(?)", subQuery).
|
||||
OrderAsc("id").
|
||||
All()
|
||||
t.AssertNil(err)
|
||||
|
||||
t.Assert(len(r), 3)
|
||||
t.Assert(r[0]["id"], 2)
|
||||
t.Assert(r[1]["id"], 4)
|
||||
t.Assert(r[2]["id"], 6)
|
||||
})
|
||||
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
// Multiple WHERE IN subqueries combined
|
||||
subQuery1 := db.Model(table).Fields("id").Where("id <=", 5)
|
||||
subQuery2 := db.Model(table).Fields("id").Where("id >=", 3)
|
||||
|
||||
r, err := db.Model(table).
|
||||
Where("id IN(?)", subQuery1).
|
||||
Where("id IN(?)", subQuery2).
|
||||
OrderAsc("id").
|
||||
All()
|
||||
t.AssertNil(err)
|
||||
|
||||
t.Assert(len(r), 3)
|
||||
t.Assert(r[0]["id"], 3)
|
||||
t.Assert(r[2]["id"], 5)
|
||||
})
|
||||
}
|
||||
|
||||
// Test_Model_SubQuery_Complex tests complex subquery combinations
|
||||
func Test_Model_SubQuery_Complex(t *testing.T) {
|
||||
table := createInitTable()
|
||||
defer dropTable(table)
|
||||
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
// Combine subquery in WHERE, FROM, and SELECT
|
||||
whereSubQuery := db.Model(table).Fields("AVG(id)")
|
||||
fromSubQuery := db.Model(table).Where("id <=", 7)
|
||||
|
||||
r, err := db.Model("(?) AS sub", fromSubQuery).
|
||||
Fields("sub.id", "sub.nickname").
|
||||
Where("sub.id > (?)", whereSubQuery).
|
||||
OrderAsc("id").
|
||||
All()
|
||||
t.AssertNil(err)
|
||||
|
||||
// AVG(1-10) = 5.5, filter sub.id > 5.5 from ids 1-7
|
||||
t.Assert(len(r), 2)
|
||||
t.Assert(r[0]["id"], 6)
|
||||
t.Assert(r[1]["id"], 7)
|
||||
})
|
||||
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
// Subquery with GROUP BY and HAVING
|
||||
subQuery := db.Model(table).
|
||||
Fields("id % 3 AS mod_group", "COUNT(*) AS cnt").
|
||||
Group("mod_group").
|
||||
Having("COUNT(*) >=", 3)
|
||||
|
||||
r, err := db.Model(table).
|
||||
Where("id % 3 IN(?)", db.Model("(?) AS sub", subQuery).Fields("sub.mod_group")).
|
||||
OrderAsc("id").
|
||||
All()
|
||||
t.AssertNil(err)
|
||||
|
||||
// id % 3: 0(3,6,9), 1(1,4,7,10), 2(2,5,8)
|
||||
// Groups with count >= 3: 0(3 items), 1(4 items), 2(3 items) - all qualify
|
||||
t.Assert(len(r), 10)
|
||||
})
|
||||
}
|
||||
|
||||
@ -1242,3 +1242,463 @@ CREATE TABLE %s (
|
||||
t.Assert(r[0]["id"].Int(), 1) // Should include id 1
|
||||
})
|
||||
}
|
||||
|
||||
// Test_Model_WherePrefixIn tests WherePrefix with IN clause
|
||||
func Test_Model_WherePrefixIn(t *testing.T) {
|
||||
var (
|
||||
table1 = gtime.TimestampNanoStr() + "_table1"
|
||||
table2 = gtime.TimestampNanoStr() + "_table2"
|
||||
)
|
||||
createInitTable(table1)
|
||||
defer dropTable(table1)
|
||||
createInitTable(table2)
|
||||
defer dropTable(table2)
|
||||
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
r, err := db.Model(table1).As("t1").
|
||||
FieldsPrefix("t1", "id").
|
||||
LeftJoin(table2+" AS t2", "t1.id = t2.id").
|
||||
WherePrefixIn("t1", "id", g.Slice{1, 2, 3}).
|
||||
Order("t1.id asc").
|
||||
All()
|
||||
t.AssertNil(err)
|
||||
|
||||
t.Assert(len(r), 3)
|
||||
t.Assert(r[0]["id"], "1")
|
||||
t.Assert(r[2]["id"], "3")
|
||||
})
|
||||
}
|
||||
|
||||
// Test_Model_WherePrefixNotIn tests WherePrefix with NOT IN clause
|
||||
func Test_Model_WherePrefixNotIn(t *testing.T) {
|
||||
var (
|
||||
table1 = gtime.TimestampNanoStr() + "_table1"
|
||||
table2 = gtime.TimestampNanoStr() + "_table2"
|
||||
)
|
||||
createInitTable(table1)
|
||||
defer dropTable(table1)
|
||||
createInitTable(table2)
|
||||
defer dropTable(table2)
|
||||
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
r, err := db.Model(table1).As("t1").
|
||||
FieldsPrefix("t1", "id").
|
||||
LeftJoin(table2+" AS t2", "t1.id = t2.id").
|
||||
WherePrefixNotIn("t1", "id", g.Slice{1, 2, 3, 4, 5, 6, 7}).
|
||||
Order("t1.id asc").
|
||||
All()
|
||||
t.AssertNil(err)
|
||||
|
||||
t.Assert(len(r), 3)
|
||||
t.Assert(r[0]["id"], "8")
|
||||
t.Assert(r[2]["id"], "10")
|
||||
})
|
||||
}
|
||||
|
||||
// Test_Model_WherePrefixBetween tests WherePrefix with BETWEEN clause
|
||||
func Test_Model_WherePrefixBetween(t *testing.T) {
|
||||
var (
|
||||
table1 = gtime.TimestampNanoStr() + "_table1"
|
||||
table2 = gtime.TimestampNanoStr() + "_table2"
|
||||
)
|
||||
createInitTable(table1)
|
||||
defer dropTable(table1)
|
||||
createInitTable(table2)
|
||||
defer dropTable(table2)
|
||||
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
r, err := db.Model(table1).As("t1").
|
||||
FieldsPrefix("t1", "id", "nickname").
|
||||
LeftJoin(table2+" AS t2", "t1.id = t2.id").
|
||||
WherePrefixBetween("t1", "id", 3, 6).
|
||||
Order("t1.id asc").
|
||||
All()
|
||||
t.AssertNil(err)
|
||||
|
||||
t.Assert(len(r), 4)
|
||||
t.Assert(r[0]["id"], "3")
|
||||
t.Assert(r[3]["id"], "6")
|
||||
})
|
||||
}
|
||||
|
||||
// Test_Model_WherePrefixNotBetween tests WherePrefix with NOT BETWEEN clause
|
||||
func Test_Model_WherePrefixNotBetween(t *testing.T) {
|
||||
var (
|
||||
table1 = gtime.TimestampNanoStr() + "_table1"
|
||||
table2 = gtime.TimestampNanoStr() + "_table2"
|
||||
)
|
||||
createInitTable(table1)
|
||||
defer dropTable(table1)
|
||||
createInitTable(table2)
|
||||
defer dropTable(table2)
|
||||
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
r, err := db.Model(table1).As("t1").
|
||||
FieldsPrefix("t1", "id").
|
||||
LeftJoin(table2+" AS t2", "t1.id = t2.id").
|
||||
WherePrefixNotBetween("t1", "id", 3, 7).
|
||||
Order("t1.id asc").
|
||||
All()
|
||||
t.AssertNil(err)
|
||||
|
||||
// NOT BETWEEN 3 AND 7 returns: 1, 2, 8, 9, 10 (5 records)
|
||||
t.Assert(len(r), 5)
|
||||
t.Assert(r[0]["id"], "1")
|
||||
t.Assert(r[1]["id"], "2")
|
||||
t.Assert(r[2]["id"], "8")
|
||||
t.Assert(r[3]["id"], "9")
|
||||
t.Assert(r[4]["id"], "10")
|
||||
})
|
||||
}
|
||||
|
||||
// Test_Model_WherePrefixLT tests WherePrefix with < operator
|
||||
func Test_Model_WherePrefixLT(t *testing.T) {
|
||||
var (
|
||||
table1 = gtime.TimestampNanoStr() + "_table1"
|
||||
table2 = gtime.TimestampNanoStr() + "_table2"
|
||||
)
|
||||
createInitTable(table1)
|
||||
defer dropTable(table1)
|
||||
createInitTable(table2)
|
||||
defer dropTable(table2)
|
||||
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
r, err := db.Model(table1).As("t1").
|
||||
FieldsPrefix("t1", "id").
|
||||
LeftJoin(table2+" AS t2", "t1.id = t2.id").
|
||||
WherePrefixLT("t1", "id", 4).
|
||||
Order("t1.id asc").
|
||||
All()
|
||||
t.AssertNil(err)
|
||||
|
||||
t.Assert(len(r), 3)
|
||||
t.Assert(r[0]["id"], "1")
|
||||
t.Assert(r[2]["id"], "3")
|
||||
})
|
||||
}
|
||||
|
||||
// Test_Model_WherePrefixLTE tests WherePrefix with <= operator
|
||||
func Test_Model_WherePrefixLTE(t *testing.T) {
|
||||
var (
|
||||
table1 = gtime.TimestampNanoStr() + "_table1"
|
||||
table2 = gtime.TimestampNanoStr() + "_table2"
|
||||
)
|
||||
createInitTable(table1)
|
||||
defer dropTable(table1)
|
||||
createInitTable(table2)
|
||||
defer dropTable(table2)
|
||||
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
r, err := db.Model(table1).As("t1").
|
||||
FieldsPrefix("t1", "id").
|
||||
LeftJoin(table2+" AS t2", "t1.id = t2.id").
|
||||
WherePrefixLTE("t1", "id", 4).
|
||||
Order("t1.id asc").
|
||||
All()
|
||||
t.AssertNil(err)
|
||||
|
||||
t.Assert(len(r), 4)
|
||||
t.Assert(r[0]["id"], "1")
|
||||
t.Assert(r[3]["id"], "4")
|
||||
})
|
||||
}
|
||||
|
||||
// Test_Model_WherePrefixGT tests WherePrefix with > operator
|
||||
func Test_Model_WherePrefixGT(t *testing.T) {
|
||||
var (
|
||||
table1 = gtime.TimestampNanoStr() + "_table1"
|
||||
table2 = gtime.TimestampNanoStr() + "_table2"
|
||||
)
|
||||
createInitTable(table1)
|
||||
defer dropTable(table1)
|
||||
createInitTable(table2)
|
||||
defer dropTable(table2)
|
||||
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
r, err := db.Model(table1).As("t1").
|
||||
FieldsPrefix("t1", "id").
|
||||
LeftJoin(table2+" AS t2", "t1.id = t2.id").
|
||||
WherePrefixGT("t1", "id", 7).
|
||||
Order("t1.id asc").
|
||||
All()
|
||||
t.AssertNil(err)
|
||||
|
||||
t.Assert(len(r), 3)
|
||||
t.Assert(r[0]["id"], "8")
|
||||
t.Assert(r[2]["id"], "10")
|
||||
})
|
||||
}
|
||||
|
||||
// Test_Model_WherePrefixGTE tests WherePrefix with >= operator
|
||||
func Test_Model_WherePrefixGTE(t *testing.T) {
|
||||
var (
|
||||
table1 = gtime.TimestampNanoStr() + "_table1"
|
||||
table2 = gtime.TimestampNanoStr() + "_table2"
|
||||
)
|
||||
createInitTable(table1)
|
||||
defer dropTable(table1)
|
||||
createInitTable(table2)
|
||||
defer dropTable(table2)
|
||||
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
r, err := db.Model(table1).As("t1").
|
||||
FieldsPrefix("t1", "id").
|
||||
LeftJoin(table2+" AS t2", "t1.id = t2.id").
|
||||
WherePrefixGTE("t1", "id", 7).
|
||||
Order("t1.id asc").
|
||||
All()
|
||||
t.AssertNil(err)
|
||||
|
||||
t.Assert(len(r), 4)
|
||||
t.Assert(r[0]["id"], "7")
|
||||
t.Assert(r[3]["id"], "10")
|
||||
})
|
||||
}
|
||||
|
||||
// Test_Model_WherePrefixNotLike tests WherePrefix with NOT LIKE operator
|
||||
func Test_Model_WherePrefixNotLike(t *testing.T) {
|
||||
var (
|
||||
table1 = gtime.TimestampNanoStr() + "_table1"
|
||||
table2 = gtime.TimestampNanoStr() + "_table2"
|
||||
)
|
||||
createInitTable(table1)
|
||||
defer dropTable(table1)
|
||||
createInitTable(table2)
|
||||
defer dropTable(table2)
|
||||
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
r, err := db.Model(table1).As("t1").
|
||||
FieldsPrefix("t1", "id", "nickname").
|
||||
LeftJoin(table2+" AS t2", "t1.id = t2.id").
|
||||
WherePrefixNotLike("t1", "nickname", "name_1%").
|
||||
Where("t1.id <", 5).
|
||||
Order("t1.id asc").
|
||||
All()
|
||||
t.AssertNil(err)
|
||||
|
||||
// Should exclude name_1 and name_10 (but id 10 filtered by WHERE anyway)
|
||||
t.Assert(len(r), 3)
|
||||
t.Assert(r[0]["id"], "2")
|
||||
t.Assert(r[0]["nickname"], "name_2")
|
||||
t.Assert(r[2]["id"], "4")
|
||||
})
|
||||
}
|
||||
|
||||
// Test_Model_WherePrefixNull tests WherePrefix with IS NULL
|
||||
func Test_Model_WherePrefixNull(t *testing.T) {
|
||||
var (
|
||||
table1 = gtime.TimestampNanoStr() + "_table1"
|
||||
table2 = gtime.TimestampNanoStr() + "_table2"
|
||||
)
|
||||
createInitTable(table1)
|
||||
defer dropTable(table1)
|
||||
// table2 has partial data, will cause NULLs in LEFT JOIN
|
||||
createTable(table2)
|
||||
defer dropTable(table2)
|
||||
_, _ = db.Insert(ctx, table2, g.List{
|
||||
{"id": 1, "passport": "user_1", "nickname": "name_1"},
|
||||
})
|
||||
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
r, err := db.Model(table1).As("t1").
|
||||
FieldsPrefix("t1", "id").
|
||||
LeftJoin(table2+" AS t2", "t1.id = t2.id").
|
||||
WherePrefixNull("t2", "nickname").
|
||||
Where("t1.id <=", 3).
|
||||
Order("t1.id asc").
|
||||
All()
|
||||
t.AssertNil(err)
|
||||
|
||||
// t2 only has id=1, so id 2,3 should have NULL in t2.nickname
|
||||
t.Assert(len(r), 2)
|
||||
t.Assert(r[0]["id"], "2")
|
||||
t.Assert(r[1]["id"], "3")
|
||||
})
|
||||
}
|
||||
|
||||
// Test_Model_WherePrefixNotNull tests WherePrefix with IS NOT NULL
|
||||
func Test_Model_WherePrefixNotNull(t *testing.T) {
|
||||
var (
|
||||
table1 = gtime.TimestampNanoStr() + "_table1"
|
||||
table2 = gtime.TimestampNanoStr() + "_table2"
|
||||
)
|
||||
createInitTable(table1)
|
||||
defer dropTable(table1)
|
||||
// table2 has partial data
|
||||
createTable(table2)
|
||||
defer dropTable(table2)
|
||||
_, _ = db.Insert(ctx, table2, g.List{
|
||||
{"id": 1, "passport": "user_1", "nickname": "name_1"},
|
||||
{"id": 2, "passport": "user_2", "nickname": "name_2"},
|
||||
})
|
||||
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
r, err := db.Model(table1).As("t1").
|
||||
FieldsPrefix("t1", "id").
|
||||
LeftJoin(table2+" AS t2", "t1.id = t2.id").
|
||||
WherePrefixNotNull("t2", "nickname").
|
||||
Where("t1.id <=", 4).
|
||||
Order("t1.id asc").
|
||||
All()
|
||||
t.AssertNil(err)
|
||||
|
||||
// t2 has id 1,2, so only these should have NOT NULL in t2.nickname
|
||||
t.Assert(len(r), 2)
|
||||
t.Assert(r[0]["id"], "1")
|
||||
t.Assert(r[1]["id"], "2")
|
||||
})
|
||||
}
|
||||
|
||||
// Test_Model_WhereOrPrefixIn tests WhereOrPrefix with IN clause
|
||||
func Test_Model_WhereOrPrefixIn(t *testing.T) {
|
||||
var (
|
||||
table1 = gtime.TimestampNanoStr() + "_table1"
|
||||
table2 = gtime.TimestampNanoStr() + "_table2"
|
||||
)
|
||||
createInitTable(table1)
|
||||
defer dropTable(table1)
|
||||
createInitTable(table2)
|
||||
defer dropTable(table2)
|
||||
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
r, err := db.Model(table1).As("t1").
|
||||
FieldsPrefix("t1", "id").
|
||||
LeftJoin(table2+" AS t2", "t1.id = t2.id").
|
||||
WhereOrPrefixIn("t1", "id", g.Slice{1, 2}).
|
||||
WhereOrPrefixIn("t2", "id", g.Slice{8, 9}).
|
||||
Order("t1.id asc").
|
||||
All()
|
||||
t.AssertNil(err)
|
||||
|
||||
t.Assert(len(r), 4)
|
||||
t.Assert(r[0]["id"], "1")
|
||||
t.Assert(r[1]["id"], "2")
|
||||
t.Assert(r[2]["id"], "8")
|
||||
t.Assert(r[3]["id"], "9")
|
||||
})
|
||||
}
|
||||
|
||||
// Test_Model_WhereOrPrefixNotIn tests WhereOrPrefix with NOT IN clause
|
||||
func Test_Model_WhereOrPrefixNotIn(t *testing.T) {
|
||||
var (
|
||||
table1 = gtime.TimestampNanoStr() + "_table1"
|
||||
table2 = gtime.TimestampNanoStr() + "_table2"
|
||||
)
|
||||
createInitTable(table1)
|
||||
defer dropTable(table1)
|
||||
createInitTable(table2)
|
||||
defer dropTable(table2)
|
||||
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
r, err := db.Model(table1).As("t1").
|
||||
FieldsPrefix("t1", "id").
|
||||
LeftJoin(table2+" AS t2", "t1.id = t2.id").
|
||||
WhereOrPrefixNotIn("t1", "id", g.Slice{1}).
|
||||
WhereOrPrefixNotIn("t2", "id", g.Slice{2, 3, 4, 5, 6, 7, 8, 9, 10}).
|
||||
Order("t1.id asc").
|
||||
All()
|
||||
t.AssertNil(err)
|
||||
|
||||
t.Assert(len(r), 10) // All records match one OR condition
|
||||
t.Assert(r[0]["id"], "1")
|
||||
})
|
||||
}
|
||||
|
||||
// Test_Model_Having_Aggregate tests HAVING clause with aggregate functions
|
||||
func Test_Model_Having_Aggregate(t *testing.T) {
|
||||
table := createInitTable()
|
||||
defer dropTable(table)
|
||||
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
// HAVING with COUNT
|
||||
r, err := db.Model(table).
|
||||
Fields("id % 3 AS mod_group", "COUNT(*) AS cnt").
|
||||
Group("mod_group").
|
||||
Having("COUNT(*) >= ?", 3).
|
||||
Order("mod_group asc").
|
||||
All()
|
||||
t.AssertNil(err)
|
||||
|
||||
// mod 0: 3,6,9 (3 items)
|
||||
// mod 1: 1,4,7,10 (4 items)
|
||||
// mod 2: 2,5,8 (3 items)
|
||||
t.Assert(len(r), 3)
|
||||
t.Assert(r[0]["mod_group"], "0")
|
||||
t.Assert(r[0]["cnt"], "3")
|
||||
t.Assert(r[1]["cnt"], "4")
|
||||
})
|
||||
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
// HAVING with SUM
|
||||
r, err := db.Model(table).
|
||||
Fields("id % 2 AS parity", "SUM(id) AS total").
|
||||
Group("parity").
|
||||
Having("SUM(id) > ?", 25).
|
||||
Order("parity asc").
|
||||
All()
|
||||
t.AssertNil(err)
|
||||
|
||||
// even (2,4,6,8,10): sum=30
|
||||
// odd (1,3,5,7,9): sum=25
|
||||
t.Assert(len(r), 1)
|
||||
t.Assert(r[0]["parity"], "0")
|
||||
t.Assert(r[0]["total"], "30")
|
||||
})
|
||||
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
// HAVING with AVG
|
||||
r, err := db.Model(table).
|
||||
Fields("id DIV 5 AS group_key", "AVG(id) AS avg_id").
|
||||
Group("group_key").
|
||||
Having("AVG(id) >= ?", 5).
|
||||
Order("group_key asc").
|
||||
All()
|
||||
t.AssertNil(err)
|
||||
|
||||
// group 0 (id 1-4): avg=2.5
|
||||
// group 1 (id 5-9): avg=7
|
||||
// group 2 (id 10): avg=10
|
||||
t.Assert(len(r), 2)
|
||||
t.Assert(r[0]["group_key"], "1")
|
||||
})
|
||||
}
|
||||
|
||||
// Test_Model_Having_MultipleConditions tests HAVING with multiple conditions
|
||||
func Test_Model_Having_MultipleConditions(t *testing.T) {
|
||||
table := createInitTable()
|
||||
defer dropTable(table)
|
||||
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
// Multiple HAVING conditions
|
||||
r, err := db.Model(table).
|
||||
Fields("id % 3 AS mod_group", "COUNT(*) AS cnt", "SUM(id) AS total").
|
||||
Group("mod_group").
|
||||
Having("COUNT(*) >= ?", 3).
|
||||
Having("SUM(id) < ?", 30).
|
||||
Order("mod_group asc").
|
||||
All()
|
||||
t.AssertNil(err)
|
||||
|
||||
// mod 0: cnt=3, sum=18 (3+6+9) ✓
|
||||
// mod 1: cnt=4, sum=22 (1+4+7+10) ✓
|
||||
// mod 2: cnt=3, sum=15 (2+5+8) ✓
|
||||
t.Assert(len(r), 3)
|
||||
})
|
||||
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
// HAVING with complex expression
|
||||
r, err := db.Model(table).
|
||||
Fields("id DIV 3 AS group_key", "MAX(id) - MIN(id) AS range_val").
|
||||
Group("group_key").
|
||||
Having("MAX(id) - MIN(id) >= ?", 2).
|
||||
Order("group_key asc").
|
||||
All()
|
||||
t.AssertNil(err)
|
||||
|
||||
// group 0 (1,2): range=1
|
||||
// group 1 (3,4,5): range=2 ✓
|
||||
// group 2 (6,7,8): range=2 ✓
|
||||
// group 3 (9,10): range=1
|
||||
t.Assert(len(r), 2)
|
||||
})
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user