-
-
Notifications
You must be signed in to change notification settings - Fork 1.7k
fix(database/gdb): Resolve column ambiguity in GROUP BY/ORDER BY with MySQL JOIN #4521
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
hailaz
merged 7 commits into
gogf:master
from
ljluestc:ambiguous_group_mysql_join_queries
Nov 24, 2025
Merged
Changes from all commits
Commits
Show all changes
7 commits
Select commit
Hold shift + click to select a range
741a485
fix tests
ljluestc afdde7a
fix tests
ljluestc 0839207
fix: 还原文件
hailaz 55c6908
fix: 修复模型分组时的列前缀处理逻辑
hailaz 6f651cf
feat: 添加支持JOIN的GROUP BY和ORDER BY测试用例
hailaz 973dc7a
feat: 添加获取字段前缀的方法以支持JOIN查询的字段处理
hailaz 04b4c64
feat: 添加解析表别名的方法以简化表别名处理逻辑
hailaz File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
236 changes: 236 additions & 0 deletions
236
contrib/drivers/mysql/mysql_z_unit_feature_model_join_group_order_test.go
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,236 @@ | ||
| // Copyright GoFrame Author(https://goframe.org). All Rights Reserved. | ||
| // | ||
| // This Source Code Form is subject to the terms of the MIT License. | ||
| // If a copy of the MIT was not distributed with this file, | ||
| // You can obtain one at https://github.com/gogf/gf. | ||
|
|
||
| package mysql_test | ||
|
|
||
| import ( | ||
| "testing" | ||
|
|
||
| "github.com/gogf/gf/v2/database/gdb" | ||
| "github.com/gogf/gf/v2/frame/g" | ||
| "github.com/gogf/gf/v2/os/gtime" | ||
| "github.com/gogf/gf/v2/test/gtest" | ||
| ) | ||
|
|
||
| // Test_Model_Group_WithJoin tests GROUP BY with JOIN queries | ||
| func Test_Model_Group_WithJoin(t *testing.T) { | ||
| var ( | ||
| table1 = gtime.TimestampNanoStr() + "_user" | ||
| table2 = gtime.TimestampNanoStr() + "_user_detail" | ||
| ) | ||
| createInitTable(table1) | ||
| defer dropTable(table1) | ||
| createInitTable(table2) | ||
| defer dropTable(table2) | ||
|
|
||
| gtest.C(t, func(t *gtest.T) { | ||
| // Test basic GROUP BY with JOIN - unqualified column should be auto-prefixed | ||
| // This prevents "Column 'id' in group statement is ambiguous" error | ||
| r, err := db.Model(table1+" u"). | ||
| Fields("u.id", "u.nickname", "COUNT(*) as count"). | ||
| LeftJoin(table2+" ud", "u.id = ud.id"). | ||
| Where("u.id", g.Slice{1, 2}). | ||
| Group("id"). | ||
| Order("u.id asc").All() | ||
| t.AssertNil(err) | ||
| t.Assert(len(r), 2) | ||
| t.Assert(r[0]["id"], "1") | ||
| t.Assert(r[1]["id"], "2") | ||
|
|
||
| // Test GROUP BY with already qualified column | ||
| r, err = db.Model(table1+" u"). | ||
| Fields("u.id", "u.nickname", "COUNT(*) as count"). | ||
| LeftJoin(table2+" ud", "u.id = ud.id"). | ||
| Where("u.id", g.Slice{1, 2}). | ||
| Group("u.id"). | ||
| Order("u.id asc").All() | ||
| t.AssertNil(err) | ||
| t.Assert(len(r), 2) | ||
| t.Assert(r[0]["id"], "1") | ||
| t.Assert(r[1]["id"], "2") | ||
|
|
||
| // Test GROUP BY with multiple columns | ||
| r, err = db.Model(table1+" u"). | ||
| Fields("u.id", "u.nickname", "COUNT(*) as count"). | ||
| LeftJoin(table2+" ud", "u.id = ud.id"). | ||
| Where("u.id", g.Slice{1, 2}). | ||
| Group("id", "nickname"). | ||
| Order("u.id asc").All() | ||
| t.AssertNil(err) | ||
| t.Assert(len(r), 2) | ||
|
|
||
| // Test GROUP BY with Raw expression | ||
| r, err = db.Model(table1+" u"). | ||
| Fields("u.id", "u.nickname", "COUNT(*) as count"). | ||
| LeftJoin(table2+" ud", "u.id = ud.id"). | ||
| Where("u.id", g.Slice{1, 2}). | ||
| Group(gdb.Raw("u.id")). | ||
| Order("u.id asc").All() | ||
| t.AssertNil(err) | ||
| t.Assert(len(r), 2) | ||
| t.Assert(r[0]["id"], "1") | ||
| t.Assert(r[1]["id"], "2") | ||
|
|
||
| // Test GROUP BY on non-primary table should work correctly | ||
| r, err = db.Model(table1+" u"). | ||
| Fields("ud.id", "COUNT(*) as count"). | ||
| LeftJoin(table2+" ud", "u.id = ud.id"). | ||
| Where("u.id", g.Slice{1, 2}). | ||
| Group("ud.id"). | ||
| Order("ud.id asc").All() | ||
| t.AssertNil(err) | ||
| // Should have results from the joined table | ||
| t.Assert(len(r) > 0, true) | ||
| }) | ||
| } | ||
|
|
||
| // Test_Model_Order_WithJoin tests ORDER BY with JOIN queries | ||
| func Test_Model_Order_WithJoin(t *testing.T) { | ||
| var ( | ||
| table1 = gtime.TimestampNanoStr() + "_user" | ||
| table2 = gtime.TimestampNanoStr() + "_user_detail" | ||
| ) | ||
| createInitTable(table1) | ||
| defer dropTable(table1) | ||
| createInitTable(table2) | ||
| defer dropTable(table2) | ||
|
|
||
| gtest.C(t, func(t *gtest.T) { | ||
| // Test ORDER BY with JOIN - unqualified column should be auto-prefixed | ||
| r, err := db.Model(table1+" u"). | ||
| LeftJoin(table2+" ud", "u.id = ud.id"). | ||
| Where("u.id", g.Slice{1, 2}). | ||
| Order("id desc").All() | ||
| t.AssertNil(err) | ||
| t.Assert(len(r), 2) | ||
| t.Assert(r[0]["id"], "2") | ||
| t.Assert(r[1]["id"], "1") | ||
|
|
||
| // Test ORDER BY with already qualified column | ||
| r, err = db.Model(table1+" u"). | ||
| LeftJoin(table2+" ud", "u.id = ud.id"). | ||
| Where("u.id", g.Slice{1, 2}). | ||
| Order("u.id asc").All() | ||
| t.AssertNil(err) | ||
| t.Assert(len(r), 2) | ||
| t.Assert(r[0]["id"], "1") | ||
| t.Assert(r[1]["id"], "2") | ||
|
|
||
| // Test ORDER BY with Raw expression | ||
| r, err = db.Model(table1+" u"). | ||
| LeftJoin(table2+" ud", "u.id = ud.id"). | ||
| Where("u.id", g.Slice{1, 2}). | ||
| Order(gdb.Raw("u.id asc")).All() | ||
| t.AssertNil(err) | ||
| t.Assert(len(r), 2) | ||
| t.Assert(r[0]["id"], "1") | ||
| t.Assert(r[1]["id"], "2") | ||
|
|
||
| // Test multiple ORDER BY clauses with JOIN | ||
| r, err = db.Model(table1+" u"). | ||
| LeftJoin(table2+" ud", "u.id = ud.id"). | ||
| Order("id asc").Order("nickname asc").All() | ||
| t.AssertNil(err) | ||
| t.Assert(len(r) > 0, true) | ||
|
|
||
| // Test ORDER BY with asc/desc keywords | ||
| r, err = db.Model(table1+" u"). | ||
| LeftJoin(table2+" ud", "u.id = ud.id"). | ||
| Where("u.id", g.Slice{1, 2}). | ||
| Order("id asc").All() | ||
| t.AssertNil(err) | ||
| t.Assert(len(r), 2) | ||
| t.Assert(r[0]["id"], "1") | ||
| t.Assert(r[1]["id"], "2") | ||
| }) | ||
| } | ||
|
|
||
| // Test_Model_Group_And_Order_WithJoin tests combined GROUP BY and ORDER BY with JOINs | ||
| func Test_Model_Group_And_Order_WithJoin(t *testing.T) { | ||
| var ( | ||
| table1 = gtime.TimestampNanoStr() + "_user" | ||
| table2 = gtime.TimestampNanoStr() + "_user_detail" | ||
| ) | ||
| createInitTable(table1) | ||
| defer dropTable(table1) | ||
| createInitTable(table2) | ||
| defer dropTable(table2) | ||
|
|
||
| gtest.C(t, func(t *gtest.T) { | ||
| // Test combined GROUP BY and ORDER BY with JOIN | ||
| r, err := db.Model(table1+" u"). | ||
| Fields("u.id", "COUNT(*) as count"). | ||
| LeftJoin(table2+" ud", "u.id = ud.id"). | ||
| Where("u.id", g.Slice{1, 2}). | ||
| Group("id"). | ||
| Order("id desc").All() | ||
| t.AssertNil(err) | ||
| t.Assert(len(r), 2) | ||
| t.Assert(r[0]["id"], "2") | ||
| t.Assert(r[1]["id"], "1") | ||
|
|
||
| // Test with already qualified GROUP BY and unqualified ORDER BY | ||
| r, err = db.Model(table1+" u"). | ||
| Fields("u.id", "COUNT(*) as count"). | ||
| LeftJoin(table2+" ud", "u.id = ud.id"). | ||
| Where("u.id", g.Slice{1, 2}). | ||
| Group("u.id"). | ||
| Order("id asc").All() | ||
| t.AssertNil(err) | ||
| t.Assert(len(r), 2) | ||
| t.Assert(r[0]["id"], "1") | ||
| t.Assert(r[1]["id"], "2") | ||
|
|
||
| // Test with unqualified GROUP BY and qualified ORDER BY | ||
| r, err = db.Model(table1+" u"). | ||
| Fields("u.id", "COUNT(*) as count"). | ||
| LeftJoin(table2+" ud", "u.id = ud.id"). | ||
| Where("u.id", g.Slice{1, 2}). | ||
| Group("id"). | ||
| Order("u.id desc").All() | ||
| t.AssertNil(err) | ||
| t.Assert(len(r), 2) | ||
| t.Assert(r[0]["id"], "2") | ||
| t.Assert(r[1]["id"], "1") | ||
|
|
||
| // Test with both unqualified | ||
| r, err = db.Model(table1+" u"). | ||
| Fields("u.id", "COUNT(*) as count"). | ||
| LeftJoin(table2+" ud", "u.id = ud.id"). | ||
| Where("u.id", g.Slice{1, 2}). | ||
| Group("id"). | ||
| Order("id").All() | ||
| t.AssertNil(err) | ||
| t.Assert(len(r), 2) | ||
| }) | ||
| } | ||
|
|
||
| // Test_Model_Join_Without_Alias tests JOIN without table aliases | ||
| func Test_Model_Join_Without_Alias(t *testing.T) { | ||
| var ( | ||
| table1 = gtime.TimestampNanoStr() + "_user" | ||
| table2 = gtime.TimestampNanoStr() + "_user_detail" | ||
| ) | ||
| createInitTable(table1) | ||
| defer dropTable(table1) | ||
| createInitTable(table2) | ||
| defer dropTable(table2) | ||
|
|
||
| gtest.C(t, func(t *gtest.T) { | ||
| // Test GROUP BY and ORDER BY with JOIN but without aliases | ||
| // This should still work correctly | ||
| r, err := db.Model(table1). | ||
| Fields(table1+".id", "COUNT(*) as count"). | ||
| LeftJoin(table2, table1+".id = "+table2+".id"). | ||
| Where(table1+".id", g.Slice{1, 2}). | ||
| Group(table1 + ".id"). | ||
| Order(table1 + ".id asc").All() | ||
| t.AssertNil(err) | ||
| t.Assert(len(r), 2) | ||
| t.Assert(r[0]["id"], "1") | ||
| t.Assert(r[1]["id"], "2") | ||
| }) | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.