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:
Jack Ling
2026-02-27 15:52:41 +08:00
committed by GitHub
parent bac637570d
commit 307c6ec307
3 changed files with 1041 additions and 0 deletions

View File

@ -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")
})
}

View File

@ -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)
})
}

View File

@ -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)
})
}