mirror of
https://gitee.com/johng/gf
synced 2026-06-07 02:12:11 +08:00
## Summary - Fixed a bug where `Raw()` with `Where()` and `Count()`/`ScanAndCount()` was ignoring the Where conditions in Count queries - The issue was in `getFormattedSqlAndArgs()` which returned `nil` for `conditionArgs` without calling `formatCondition()` for Raw SQL in `SelectTypeCount` case ## Changes - Modified `database/gdb/gdb_model_select.go` to call `formatCondition()` for Raw SQL Count queries - Added comprehensive test cases for MySQL and PostgreSQL drivers - Fixed incorrect test expectation in `Test_Model_Raw` ## Test plan - [x] Added `Test_Issue4500` with 6 edge cases covering: - Raw SQL with WHERE + external Where condition - Raw SQL without WHERE + external Where condition - Raw + Where + ScanAndCount - Raw + multiple Where conditions - Raw SQL with no external Where (baseline) - Verify All() still works correctly - [x] All tests pass on PostgreSQL Closes #4500
383 lines
9.9 KiB
Go
383 lines
9.9 KiB
Go
// 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 pgsql_test
|
|
|
|
import (
|
|
"fmt"
|
|
"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"
|
|
)
|
|
|
|
// https://github.com/gogf/gf/issues/3330
|
|
func Test_Issue3330(t *testing.T) {
|
|
var (
|
|
table = fmt.Sprintf(`%s_%d`, TablePrefix+"test", gtime.TimestampNano())
|
|
uniqueName = fmt.Sprintf(`%s_%d`, TablePrefix+"test_unique", gtime.TimestampNano())
|
|
)
|
|
if _, err := db.Exec(ctx, fmt.Sprintf(`
|
|
CREATE TABLE %s (
|
|
id bigserial NOT NULL,
|
|
passport varchar(45) NOT NULL,
|
|
password varchar(32) NOT NULL,
|
|
nickname varchar(45) NOT NULL,
|
|
create_time timestamp NOT NULL,
|
|
PRIMARY KEY (id),
|
|
CONSTRAINT %s unique ("password")
|
|
) ;`, table, uniqueName,
|
|
)); err != nil {
|
|
gtest.Fatal(err)
|
|
}
|
|
defer dropTable(table)
|
|
|
|
gtest.C(t, func(t *gtest.T) {
|
|
var (
|
|
list []map[string]any
|
|
one gdb.Record
|
|
err error
|
|
)
|
|
|
|
fields, err := db.TableFields(ctx, table)
|
|
t.AssertNil(err)
|
|
|
|
t.Assert(fields["id"].Key, "pri")
|
|
t.Assert(fields["password"].Key, "uni")
|
|
|
|
for i := 1; i <= 10; i++ {
|
|
list = append(list, g.Map{
|
|
"id": i,
|
|
"passport": fmt.Sprintf("p%d", i),
|
|
"password": fmt.Sprintf("pw%d", i),
|
|
"nickname": fmt.Sprintf("n%d", i),
|
|
"create_time": "2016-06-01 00:00:00",
|
|
})
|
|
}
|
|
|
|
_, err = db.Model(table).Data(list).Insert()
|
|
t.AssertNil(err)
|
|
|
|
for i := 1; i <= 10; i++ {
|
|
one, err = db.Model(table).WherePri(i).One()
|
|
t.AssertNil(err)
|
|
t.Assert(one["id"], list[i-1]["id"])
|
|
t.Assert(one["passport"], list[i-1]["passport"])
|
|
t.Assert(one["password"], list[i-1]["password"])
|
|
t.Assert(one["nickname"], list[i-1]["nickname"])
|
|
}
|
|
})
|
|
}
|
|
|
|
// https://github.com/gogf/gf/issues/3632
|
|
func Test_Issue3632(t *testing.T) {
|
|
type Member struct {
|
|
One []int64 `json:"one" orm:"one"`
|
|
Two [][]string `json:"two" orm:"two"`
|
|
}
|
|
var (
|
|
sqlText = gtest.DataContent("issues", "issue3632.sql")
|
|
table = fmt.Sprintf(`%s_%d`, TablePrefix+"issue3632", gtime.TimestampNano())
|
|
)
|
|
if _, err := db.Exec(ctx, fmt.Sprintf(sqlText, table)); err != nil {
|
|
gtest.Fatal(err)
|
|
}
|
|
defer dropTable(table)
|
|
|
|
gtest.C(t, func(t *gtest.T) {
|
|
var (
|
|
dao = db.Model(table)
|
|
member = Member{
|
|
One: []int64{1, 2, 3},
|
|
Two: [][]string{{"a", "b"}, {"c", "d"}},
|
|
}
|
|
)
|
|
|
|
_, err := dao.Ctx(ctx).Data(&member).Insert()
|
|
t.AssertNil(err)
|
|
})
|
|
}
|
|
|
|
// https://github.com/gogf/gf/issues/3671
|
|
func Test_Issue3671(t *testing.T) {
|
|
type SubMember struct {
|
|
Seven string
|
|
Eight int64
|
|
}
|
|
type Member struct {
|
|
One []int64 `json:"one" orm:"one"`
|
|
Two [][]string `json:"two" orm:"two"`
|
|
Three []string `json:"three" orm:"three"`
|
|
Four []int64 `json:"four" orm:"four"`
|
|
Five []SubMember `json:"five" orm:"five"`
|
|
}
|
|
var (
|
|
sqlText = gtest.DataContent("issues", "issue3671.sql")
|
|
table = fmt.Sprintf(`%s_%d`, TablePrefix+"issue3632", gtime.TimestampNano())
|
|
)
|
|
if _, err := db.Exec(ctx, fmt.Sprintf(sqlText, table)); err != nil {
|
|
gtest.Fatal(err)
|
|
}
|
|
defer dropTable(table)
|
|
|
|
gtest.C(t, func(t *gtest.T) {
|
|
var (
|
|
dao = db.Model(table)
|
|
member = Member{
|
|
One: []int64{1, 2, 3},
|
|
Two: [][]string{{"a", "b"}, {"c", "d"}},
|
|
Three: []string{"x", "y", "z"},
|
|
Four: []int64{1, 2, 3},
|
|
Five: []SubMember{{Seven: "1", Eight: 2}, {Seven: "3", Eight: 4}},
|
|
}
|
|
)
|
|
|
|
_, err := dao.Ctx(ctx).Data(&member).Insert()
|
|
t.AssertNil(err)
|
|
})
|
|
}
|
|
|
|
// https://github.com/gogf/gf/issues/3668
|
|
func Test_Issue3668(t *testing.T) {
|
|
type Issue3668 struct {
|
|
Text any
|
|
Number any
|
|
}
|
|
var (
|
|
sqlText = gtest.DataContent("issues", "issue3668.sql")
|
|
table = fmt.Sprintf(`%s_%d`, TablePrefix+"issue3668", gtime.TimestampNano())
|
|
)
|
|
if _, err := db.Exec(ctx, fmt.Sprintf(sqlText, table)); err != nil {
|
|
gtest.Fatal(err)
|
|
}
|
|
defer dropTable(table)
|
|
|
|
gtest.C(t, func(t *gtest.T) {
|
|
var (
|
|
dao = db.Model(table)
|
|
data = Issue3668{
|
|
Text: "我们都是自然的婴儿,卧在宇宙的摇篮里",
|
|
Number: nil,
|
|
}
|
|
)
|
|
_, err := dao.Ctx(ctx).
|
|
Data(data).
|
|
Insert()
|
|
t.AssertNil(err)
|
|
})
|
|
}
|
|
|
|
type Issue4033Status int
|
|
|
|
const (
|
|
Issue4033StatusA Issue4033Status = 1
|
|
)
|
|
|
|
func (s Issue4033Status) String() string {
|
|
return "somevalue"
|
|
}
|
|
|
|
func (s Issue4033Status) Int64() int64 {
|
|
return int64(s)
|
|
}
|
|
|
|
// https://github.com/gogf/gf/issues/4033
|
|
func Test_Issue4033(t *testing.T) {
|
|
var (
|
|
sqlText = gtest.DataContent("issues", "issue4033.sql")
|
|
table = "test_enum"
|
|
)
|
|
if _, err := db.Exec(ctx, sqlText); err != nil {
|
|
gtest.Fatal(err)
|
|
}
|
|
defer dropTable(table)
|
|
|
|
gtest.C(t, func(t *gtest.T) {
|
|
query := g.Map{
|
|
"status": g.Slice{Issue4033StatusA},
|
|
}
|
|
_, err := db.Model(table).Ctx(ctx).Where(query).All()
|
|
t.AssertNil(err)
|
|
})
|
|
}
|
|
|
|
// https://github.com/gogf/gf/issues/4500
|
|
// Raw() Count ignores Where condition
|
|
func Test_Issue4500(t *testing.T) {
|
|
table := createInitTable()
|
|
defer dropTable(table)
|
|
|
|
// Test 1: Raw SQL with WHERE + external Where condition + Count
|
|
// This tests that formatCondition correctly uses AND when Raw SQL already has WHERE
|
|
gtest.C(t, func(t *gtest.T) {
|
|
count, err := db.
|
|
Raw(fmt.Sprintf("SELECT * FROM %s WHERE id IN (?)", table), g.Slice{1, 5, 7, 8, 9, 10}).
|
|
WhereLT("id", 8).
|
|
Count()
|
|
t.AssertNil(err)
|
|
// Raw SQL: id IN (1,5,7,8,9,10) = 6 records
|
|
// Where: id < 8 filters to {1,5,7} = 3 records
|
|
t.Assert(count, 3)
|
|
})
|
|
|
|
// Test 2: Raw SQL without WHERE + external Where condition + Count
|
|
// This tests that formatCondition correctly adds WHERE
|
|
gtest.C(t, func(t *gtest.T) {
|
|
count, err := db.
|
|
Raw(fmt.Sprintf("SELECT * FROM %s", table)).
|
|
WhereLT("id", 5).
|
|
Count()
|
|
t.AssertNil(err)
|
|
// Raw SQL: all 10 records
|
|
// Where: id < 5 = {1,2,3,4} = 4 records
|
|
t.Assert(count, 4)
|
|
})
|
|
|
|
// Test 3: Raw + Where + ScanAndCount
|
|
gtest.C(t, func(t *gtest.T) {
|
|
type User struct {
|
|
Id int
|
|
Passport string
|
|
}
|
|
var users []User
|
|
var total int
|
|
err := db.
|
|
Raw(fmt.Sprintf("SELECT * FROM %s WHERE id IN (?)", table), g.Slice{1, 5, 7, 8, 9, 10}).
|
|
WhereLT("id", 8).
|
|
ScanAndCount(&users, &total, false)
|
|
t.AssertNil(err)
|
|
// Both scan result and count should respect Where condition
|
|
t.Assert(len(users), 3)
|
|
t.Assert(total, 3)
|
|
})
|
|
|
|
// Test 4: Raw + multiple Where conditions + Count
|
|
gtest.C(t, func(t *gtest.T) {
|
|
count, err := db.
|
|
Raw(fmt.Sprintf("SELECT * FROM %s WHERE id > ?", table), 0).
|
|
WhereLT("id", 5).
|
|
WhereGTE("id", 2).
|
|
Count()
|
|
t.AssertNil(err)
|
|
// Raw: id > 0 (all 10 records)
|
|
// Where: id < 5 AND id >= 2 = {2, 3, 4} = 3 records
|
|
t.Assert(count, 3)
|
|
})
|
|
|
|
// Test 5: Raw SQL with no external Where + Count (baseline test)
|
|
gtest.C(t, func(t *gtest.T) {
|
|
count, err := db.
|
|
Raw(fmt.Sprintf("SELECT * FROM %s WHERE id IN (?)", table), g.Slice{1, 2, 3}).
|
|
Count()
|
|
t.AssertNil(err)
|
|
// Should count 3 records
|
|
t.Assert(count, 3)
|
|
})
|
|
|
|
// Test 6: Verify All() still works correctly with Raw + Where
|
|
gtest.C(t, func(t *gtest.T) {
|
|
all, err := db.
|
|
Raw(fmt.Sprintf("SELECT * FROM %s WHERE id IN (?)", table), g.Slice{1, 5, 7, 8, 9, 10}).
|
|
WhereLT("id", 8).
|
|
All()
|
|
t.AssertNil(err)
|
|
t.Assert(len(all), 3)
|
|
})
|
|
}
|
|
|
|
// https://github.com/gogf/gf/issues/4595
|
|
// FieldsPrefix silently drops fields when using table alias before LeftJoin.
|
|
func Test_Issue4595(t *testing.T) {
|
|
var (
|
|
tableUser = fmt.Sprintf(`%s_%d`, TablePrefix+"issue4595_user", gtime.TimestampNano())
|
|
tableUserDetail = fmt.Sprintf(`%s_%d`, TablePrefix+"issue4595_user_detail", gtime.TimestampNano())
|
|
)
|
|
|
|
// Create user table
|
|
if _, err := db.Exec(ctx, fmt.Sprintf(`
|
|
CREATE TABLE %s (
|
|
id bigserial PRIMARY KEY,
|
|
name varchar(100),
|
|
email varchar(100)
|
|
);`, tableUser,
|
|
)); err != nil {
|
|
gtest.Fatal(err)
|
|
}
|
|
defer dropTable(tableUser)
|
|
|
|
// Create user_detail table
|
|
if _, err := db.Exec(ctx, fmt.Sprintf(`
|
|
CREATE TABLE %s (
|
|
id bigserial PRIMARY KEY,
|
|
user_id bigint,
|
|
phone varchar(20),
|
|
address varchar(200)
|
|
);`, tableUserDetail,
|
|
)); err != nil {
|
|
gtest.Fatal(err)
|
|
}
|
|
defer dropTable(tableUserDetail)
|
|
|
|
// Insert test data
|
|
if _, err := db.Exec(ctx, fmt.Sprintf(`
|
|
INSERT INTO %s (id, name, email) VALUES (1, 'john', 'john@example.com');
|
|
INSERT INTO %s (id, user_id, phone, address) VALUES (1, 1, '1234567890', '123 Main St');
|
|
`, tableUser, tableUserDetail)); err != nil {
|
|
gtest.Fatal(err)
|
|
}
|
|
|
|
gtest.C(t, func(t *gtest.T) {
|
|
// Test case 1: FieldsPrefix called before LeftJoin
|
|
// Both t1 and t2 fields should be present
|
|
r, err := db.Model(tableUser).As("t1").
|
|
FieldsPrefix("t2", "phone", "address").
|
|
FieldsPrefix("t1", "id", "name", "email").
|
|
LeftJoin(tableUserDetail, "t2", "t1.id=t2.user_id").
|
|
All()
|
|
|
|
t.AssertNil(err)
|
|
t.Assert(len(r), 1)
|
|
t.Assert(r[0]["id"], 1)
|
|
t.Assert(r[0]["name"], "john")
|
|
t.Assert(r[0]["email"], "john@example.com")
|
|
t.Assert(r[0]["phone"], "1234567890")
|
|
t.Assert(r[0]["address"], "123 Main St")
|
|
})
|
|
|
|
gtest.C(t, func(t *gtest.T) {
|
|
// Test case 2: Using Fields() with prefix
|
|
r, err := db.Model(tableUser).As("t1").
|
|
Fields("t2.phone", "t2.address", "t1.id", "t1.name", "t1.email").
|
|
LeftJoin(tableUserDetail, "t2", "t1.id=t2.user_id").
|
|
All()
|
|
t.AssertNil(err)
|
|
t.Assert(len(r), 1)
|
|
t.Assert(r[0]["id"], 1)
|
|
t.Assert(r[0]["name"], "john")
|
|
t.Assert(r[0]["email"], "john@example.com")
|
|
t.Assert(r[0]["phone"], "1234567890")
|
|
t.Assert(r[0]["address"], "123 Main St")
|
|
})
|
|
|
|
gtest.C(t, func(t *gtest.T) {
|
|
// Test case 3: FieldsPrefix called after LeftJoin
|
|
r, err := db.Model(tableUser).As("t1").
|
|
LeftJoin(tableUserDetail, "t2", "t1.id=t2.user_id").
|
|
FieldsPrefix("t2", "phone", "address").
|
|
FieldsPrefix("t1", "id", "name", "email").
|
|
All()
|
|
t.AssertNil(err)
|
|
t.Assert(len(r), 1)
|
|
t.Assert(r[0]["id"], 1)
|
|
t.Assert(r[0]["name"], "john")
|
|
t.Assert(r[0]["email"], "john@example.com")
|
|
t.Assert(r[0]["phone"], "1234567890")
|
|
t.Assert(r[0]["address"], "123 Main St")
|
|
})
|
|
}
|