fix(database/gdb): support multiple order fields in gdb_model_with and merged #4272 fix scanning functionality for deep slice types (#4320)

Fix:#4311
Merged: #4272
This commit is contained in:
hailaz
2025-06-20 21:09:41 +08:00
committed by GitHub
4 changed files with 141 additions and 4 deletions

View File

@ -1888,3 +1888,111 @@ PRIMARY KEY (user_id)
t.AssertNil(user3.UserDetail)
})
}
func Test_Table_Relation_WithAll_Order(t *testing.T) {
var (
tableUser = "user101"
tableUserDetail = "user_detail101"
)
if _, err := db.Exec(ctx, fmt.Sprintf(`
CREATE TABLE IF NOT EXISTS %s (
id int(10) unsigned NOT NULL AUTO_INCREMENT,
name varchar(45) NOT NULL,
PRIMARY KEY (id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
`, tableUser)); err != nil {
gtest.Error(err)
}
defer dropTable(tableUser)
if _, err := db.Exec(ctx, fmt.Sprintf(`
CREATE TABLE IF NOT EXISTS %s (
user_id int(10) unsigned NOT NULL,
address varchar(45) NOT NULL,
deleted_at datetime default NULL ,
PRIMARY KEY (user_id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
`, tableUserDetail)); err != nil {
gtest.Error(err)
}
defer dropTable(tableUserDetail)
type UserDetail struct {
gmeta.Meta `orm:"table:user_detail101"`
UserID int `json:"user_id"`
Address string `json:"address"`
DeletedAt *gtime.Time `json:"deleted_at"`
}
// For Test Only
type UserEmbedded struct {
ID int `json:"id"`
Name string `json:"name"`
}
type User struct {
gmeta.Meta `orm:"table:user101"`
UserEmbedded
UserDetail *UserDetail `orm:"with:user_id=id"`
}
type UserWithDeletedDetail struct {
gmeta.Meta `orm:"table:user101"`
UserEmbedded
UserDetail *UserDetail `orm:"with:user_id=id, order:user_id asc,address desc, unscoped:true"`
}
// Initialize the data.
var err error
for i := 1; i <= 5; i++ {
// User.
_, err = db.Insert(ctx, tableUser, g.Map{
"id": i,
"name": fmt.Sprintf(`name_%d`, i),
})
gtest.AssertNil(err)
// Detail.
_, err = db.Insert(ctx, tableUserDetail, g.Map{
"user_id": i,
"address": fmt.Sprintf(`address_%d`, i),
})
// Delete detail where i = 3
if i == 3 {
_, err = db.Delete(ctx, tableUserDetail, g.Map{
"user_id": i,
})
}
gtest.AssertNil(err)
}
gtest.C(t, func(t *gtest.T) {
var user0 User
err := db.Model(tableUser).WithAll().Where("id", 4).Scan(&user0)
t.AssertNil(err)
t.Assert(user0.ID, 4)
t.AssertNE(user0.UserDetail, nil)
t.AssertNil(user0.UserDetail.DeletedAt)
t.Assert(user0.UserDetail.UserID, 4)
t.Assert(user0.UserDetail.Address, `address_4`)
var user1 User
err = db.Model(tableUser).WithAll().Where("id", 3).Scan(&user1)
t.AssertNil(err)
t.Assert(user1.ID, 3)
t.AssertNil(user1.UserDetail)
var user2 UserWithDeletedDetail
err = db.Model(tableUser).WithAll().Where("id", 3).Scan(&user2)
t.AssertNil(err)
t.Assert(user2.ID, 3)
t.AssertNE(user2.UserDetail, nil)
t.AssertNE(user2.UserDetail.DeletedAt, nil)
t.Assert(user2.UserDetail.UserID, 3)
t.Assert(user2.UserDetail.Address, `address_3`)
// Unscoped outside test
var user3 User
err = db.Model(tableUser).Unscoped().WithAll().Where("id", 3).Scan(&user3)
t.AssertNil(err)
t.Assert(user3.ID, 3)
t.AssertNil(user3.UserDetail)
})
}

View File

@ -333,7 +333,12 @@ func (m *Model) parseWithTagInFieldStruct(field gstructs.Field) (output parseWit
key = array[0]
data[key] = gstr.Trim(array[1])
} else {
data[key] += " " + gstr.Trim(v)
if key == OrmTagForWithOrder {
// supporting multiple order fields
data[key] += "," + gstr.Trim(v)
} else {
data[key] += " " + gstr.Trim(v)
}
}
}
output.With = data[OrmTagForWith]

View File

@ -11,6 +11,7 @@ import (
"testing"
"github.com/gogf/gf/v2/container/gvar"
"github.com/gogf/gf/v2/encoding/gjson"
"github.com/gogf/gf/v2/errors/gerror"
"github.com/gogf/gf/v2/frame/g"
"github.com/gogf/gf/v2/os/gtime"
@ -367,10 +368,10 @@ func TestScan(t *testing.T) {
t.Assert(test["Name"], scanExpects.structSub.Place)
t.Assert(test["Place"], scanExpects.structSub.Name)
//t.Logf("%#v", test)
// t.Logf("%#v", test)
err = gconv.Scan(test, &scanExpects.structSubPtr, mapParameter)
t.AssertNil(err)
//t.Logf("%#v", scanExpects.structSubPtr)
// t.Logf("%#v", scanExpects.structSubPtr)
t.Assert(test["Name"], scanExpects.structSubPtr.Place)
t.Assert(test["Place"], scanExpects.structSubPtr.Name)
}
@ -421,3 +422,22 @@ func TestScanEmptyStringToCustomType(t *testing.T) {
t.Assert(len(req.Types), 0)
})
}
func TestScanDeepSlice(t *testing.T) {
gtest.C(t, func(t *gtest.T) {
var (
req [][]int
req2 [][][]int
data1 = gjson.New("[[1,2,3],[4,5,6]]")
data2 = gjson.New("[[[1,2,3]],[[4,5,6]]]")
)
err := data1.Scan(&req)
t.AssertNil(err)
err = gconv.Scan(data1.String(), &req)
t.AssertNil(err)
err = data2.Scan(&req2)
t.AssertNil(err)
t.Assert(len(req), 2)
t.Assert(len(req2), 2)
})
}

View File

@ -159,6 +159,7 @@ func (c *Converter) Scan(srcValue any, dstPointer any, option ...ScanOption) (er
dstElemType = dstPointerReflectValueElem.Type().Elem()
dstElemKind = dstElemType.Kind()
)
// The slice element might be a pointer type
if dstElemKind == reflect.Ptr {
dstElemType = dstElemType.Elem()
@ -209,9 +210,12 @@ func (c *Converter) Scan(srcValue any, dstPointer any, option ...ScanOption) (er
}
newSlice.Index(i).SetBool(v)
default:
return c.Scan(
err = c.Scan(
srcElem, newSlice.Index(i).Addr().Interface(), option...,
)
if err != nil && !scanOption.ContinueOnError {
return err
}
}
}
dstPointerReflectValueElem.Set(newSlice)