mirror of
https://gitee.com/johng/gf
synced 2026-06-06 02:25:47 +08:00
fix issue #1325
This commit is contained in:
@ -63,7 +63,11 @@ func (m *Model) doWithScanStruct(pointer interface{}) error {
|
||||
err error
|
||||
allowedTypeStrArray = make([]string, 0)
|
||||
)
|
||||
fieldMap, err := structs.FieldMap(pointer, nil, false)
|
||||
fieldMap, err := structs.FieldMap(structs.FieldMapInput{
|
||||
Pointer: pointer,
|
||||
PriorityTagArray: nil,
|
||||
RecursiveOption: structs.RecursiveOptionEmbeddedNoTag,
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@ -79,7 +83,7 @@ func (m *Model) doWithScanStruct(pointer interface{}) error {
|
||||
fieldTypeStr = gstr.TrimAll(field.Type().String(), "*[]")
|
||||
withItemReflectValueTypeStr = gstr.TrimAll(withItemReflectValueType.String(), "*[]")
|
||||
)
|
||||
// It does select operation if the field type is in the specified with type array.
|
||||
// It does select operation if the field type is in the specified "with" type array.
|
||||
if gstr.Compare(fieldTypeStr, withItemReflectValueTypeStr) == 0 {
|
||||
allowedTypeStrArray = append(allowedTypeStrArray, fieldTypeStr)
|
||||
}
|
||||
@ -94,6 +98,7 @@ func (m *Model) doWithScanStruct(pointer interface{}) error {
|
||||
if parsedTagOutput.With == "" {
|
||||
continue
|
||||
}
|
||||
// Just handler "with" type attribute struct.
|
||||
if !m.withAll && !gstr.InArray(allowedTypeStrArray, fieldTypeStr) {
|
||||
continue
|
||||
}
|
||||
@ -120,8 +125,8 @@ func (m *Model) doWithScanStruct(pointer interface{}) error {
|
||||
if relatedFieldValue == nil {
|
||||
return gerror.NewCodef(
|
||||
gerror.CodeInvalidParameter,
|
||||
`cannot find the related value for attribute name "%s" of with tag "%s"`,
|
||||
relatedAttrName, parsedTagOutput.With,
|
||||
`cannot find the related value of attribute name "%s" in with tag "%s" for attribute "%s.%s"`,
|
||||
relatedAttrName, parsedTagOutput.With, reflect.TypeOf(pointer).Elem(), field.Name(),
|
||||
)
|
||||
}
|
||||
bindToReflectValue := field.Value
|
||||
@ -173,7 +178,11 @@ func (m *Model) doWithScanStructs(pointer interface{}) error {
|
||||
err error
|
||||
allowedTypeStrArray = make([]string, 0)
|
||||
)
|
||||
fieldMap, err := structs.FieldMap(pointer, nil, false)
|
||||
fieldMap, err := structs.FieldMap(structs.FieldMapInput{
|
||||
Pointer: pointer,
|
||||
PriorityTagArray: nil,
|
||||
RecursiveOption: structs.RecursiveOptionEmbeddedNoTag,
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@ -874,7 +874,7 @@ PRIMARY KEY (id)
|
||||
})
|
||||
}
|
||||
|
||||
func Test_Table_Relation_WithAll_Embedded(t *testing.T) {
|
||||
func Test_Table_Relation_WithAll_Embedded_With_SelfMaintained_Attributes(t *testing.T) {
|
||||
var (
|
||||
tableUser = "user"
|
||||
tableUserDetail = "user_detail"
|
||||
@ -989,6 +989,129 @@ PRIMARY KEY (id)
|
||||
})
|
||||
}
|
||||
|
||||
func Test_Table_Relation_WithAll_Embedded_Without_SelfMaintained_Attributes(t *testing.T) {
|
||||
var (
|
||||
tableUser = "user"
|
||||
tableUserDetail = "user_detail"
|
||||
tableUserScores = "user_scores"
|
||||
)
|
||||
if _, err := db.Exec(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(fmt.Sprintf(`
|
||||
CREATE TABLE IF NOT EXISTS %s (
|
||||
uid int(10) unsigned NOT NULL AUTO_INCREMENT,
|
||||
address varchar(45) NOT NULL,
|
||||
PRIMARY KEY (uid)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
|
||||
`, tableUserDetail)); err != nil {
|
||||
gtest.Error(err)
|
||||
}
|
||||
defer dropTable(tableUserDetail)
|
||||
|
||||
if _, err := db.Exec(fmt.Sprintf(`
|
||||
CREATE TABLE IF NOT EXISTS %s (
|
||||
id int(10) unsigned NOT NULL AUTO_INCREMENT,
|
||||
uid int(10) unsigned NOT NULL,
|
||||
score int(10) unsigned NOT NULL,
|
||||
PRIMARY KEY (id)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
|
||||
`, tableUserScores)); err != nil {
|
||||
gtest.Error(err)
|
||||
}
|
||||
defer dropTable(tableUserScores)
|
||||
|
||||
type UserDetail struct {
|
||||
gmeta.Meta `orm:"table:user_detail"`
|
||||
Uid int `json:"uid"`
|
||||
Address string `json:"address"`
|
||||
}
|
||||
|
||||
type UserScores struct {
|
||||
gmeta.Meta `orm:"table:user_scores"`
|
||||
Id int `json:"id"`
|
||||
Uid int `json:"uid"`
|
||||
Score int `json:"score"`
|
||||
}
|
||||
|
||||
// For Test Only
|
||||
type UserEmbedded struct {
|
||||
Id int `json:"id"`
|
||||
Name string `json:"name"`
|
||||
}
|
||||
|
||||
type User struct {
|
||||
gmeta.Meta `orm:"table:user"`
|
||||
*UserDetail `orm:"with:uid=id"`
|
||||
UserEmbedded
|
||||
UserScores []*UserScores `orm:"with:uid=id"`
|
||||
}
|
||||
|
||||
// Initialize the data.
|
||||
var err error
|
||||
for i := 1; i <= 5; i++ {
|
||||
// User.
|
||||
_, err = db.Insert(tableUser, g.Map{
|
||||
"id": i,
|
||||
"name": fmt.Sprintf(`name_%d`, i),
|
||||
})
|
||||
gtest.Assert(err, nil)
|
||||
// Detail.
|
||||
_, err = db.Insert(tableUserDetail, g.Map{
|
||||
"uid": i,
|
||||
"address": fmt.Sprintf(`address_%d`, i),
|
||||
})
|
||||
gtest.Assert(err, nil)
|
||||
// Scores.
|
||||
for j := 1; j <= 5; j++ {
|
||||
_, err = db.Insert(tableUserScores, g.Map{
|
||||
"uid": i,
|
||||
"score": j,
|
||||
})
|
||||
gtest.Assert(err, nil)
|
||||
}
|
||||
}
|
||||
db.SetDebug(true)
|
||||
defer db.SetDebug(false)
|
||||
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
var user *User
|
||||
err := db.Model(tableUser).WithAll().Where("id", 3).Scan(&user)
|
||||
t.AssertNil(err)
|
||||
t.Assert(user.Id, 3)
|
||||
t.AssertNE(user.UserDetail, nil)
|
||||
t.Assert(user.UserDetail.Uid, 3)
|
||||
t.Assert(user.UserDetail.Address, `address_3`)
|
||||
t.Assert(len(user.UserScores), 5)
|
||||
t.Assert(user.UserScores[0].Uid, 3)
|
||||
t.Assert(user.UserScores[0].Score, 1)
|
||||
t.Assert(user.UserScores[4].Uid, 3)
|
||||
t.Assert(user.UserScores[4].Score, 5)
|
||||
})
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
var user User
|
||||
err := db.Model(tableUser).WithAll().Where("id", 4).Scan(&user)
|
||||
t.AssertNil(err)
|
||||
t.Assert(user.Id, 4)
|
||||
t.AssertNE(user.UserDetail, nil)
|
||||
t.Assert(user.UserDetail.Uid, 4)
|
||||
t.Assert(user.UserDetail.Address, `address_4`)
|
||||
t.Assert(len(user.UserScores), 5)
|
||||
t.Assert(user.UserScores[0].Uid, 4)
|
||||
t.Assert(user.UserScores[0].Score, 1)
|
||||
t.Assert(user.UserScores[4].Uid, 4)
|
||||
t.Assert(user.UserScores[4].Score, 5)
|
||||
})
|
||||
}
|
||||
|
||||
func Test_Table_Relation_WithAll_Embedded_WithoutMeta(t *testing.T) {
|
||||
var (
|
||||
tableUser = "user"
|
||||
|
||||
@ -29,6 +29,11 @@ func (f *Field) IsEmbedded() bool {
|
||||
return f.Field.Anonymous
|
||||
}
|
||||
|
||||
// TagStr returns the tag string of the field.
|
||||
func (f *Field) TagStr() string {
|
||||
return string(f.Field.Tag)
|
||||
}
|
||||
|
||||
// IsExported returns true if the given field is exported.
|
||||
func (f *Field) IsExported() bool {
|
||||
return f.Field.PkgPath == ""
|
||||
@ -64,6 +69,25 @@ func (f *Field) OriginalKind() reflect.Kind {
|
||||
return kind
|
||||
}
|
||||
|
||||
const (
|
||||
RecursiveOptionNone = 0 // No recursively retrieving fields as map if the field is an embedded struct.
|
||||
RecursiveOptionEmbedded = 1 // Recursively retrieving fields as map if the field is an embedded struct.
|
||||
RecursiveOptionEmbeddedNoTag = 2 // Recursively retrieving fields as map if the field is an embedded struct and the field has no tag.
|
||||
)
|
||||
|
||||
type FieldMapInput struct {
|
||||
// Pointer should be type of struct/*struct.
|
||||
Pointer interface{}
|
||||
|
||||
// PriorityTagArray specifies the priority tag array for retrieving from high to low.
|
||||
// If it's given `nil`, it returns map[name]*Field, of which the `name` is attribute name.
|
||||
PriorityTagArray []string
|
||||
|
||||
// RecursiveOption specifies the way retrieving the fields recursively if the attribute
|
||||
// is an embedded struct. It is RecursiveOptionNone in default.
|
||||
RecursiveOption int
|
||||
}
|
||||
|
||||
// FieldMap retrieves and returns struct field as map[name/tag]*Field from `pointer`.
|
||||
//
|
||||
// The parameter `pointer` should be type of struct/*struct.
|
||||
@ -75,8 +99,8 @@ func (f *Field) OriginalKind() reflect.Kind {
|
||||
// is an embedded struct.
|
||||
//
|
||||
// Note that it only retrieves the exported attributes with first letter up-case from struct.
|
||||
func FieldMap(pointer interface{}, priority []string, recursive bool) (map[string]*Field, error) {
|
||||
fields, err := getFieldValues(pointer)
|
||||
func FieldMap(input FieldMapInput) (map[string]*Field, error) {
|
||||
fields, err := getFieldValues(input.Pointer)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@ -90,7 +114,7 @@ func FieldMap(pointer interface{}, priority []string, recursive bool) (map[strin
|
||||
continue
|
||||
}
|
||||
tagValue = ""
|
||||
for _, p := range priority {
|
||||
for _, p := range input.PriorityTagArray {
|
||||
tagValue = field.Tag(p)
|
||||
if tagValue != "" && tagValue != "-" {
|
||||
break
|
||||
@ -101,15 +125,28 @@ func FieldMap(pointer interface{}, priority []string, recursive bool) (map[strin
|
||||
if tagValue != "" {
|
||||
mapField[tagValue] = tempField
|
||||
} else {
|
||||
if recursive && field.IsEmbedded() {
|
||||
m, err := FieldMap(field.Value, priority, recursive)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
for k, v := range m {
|
||||
if _, ok := mapField[k]; !ok {
|
||||
tempV := v
|
||||
mapField[k] = tempV
|
||||
if input.RecursiveOption != RecursiveOptionNone && field.IsEmbedded() {
|
||||
switch input.RecursiveOption {
|
||||
case RecursiveOptionEmbeddedNoTag:
|
||||
if field.TagStr() != "" {
|
||||
mapField[field.Name()] = tempField
|
||||
break
|
||||
}
|
||||
fallthrough
|
||||
case RecursiveOptionEmbedded:
|
||||
m, err := FieldMap(FieldMapInput{
|
||||
Pointer: field.Value,
|
||||
PriorityTagArray: input.PriorityTagArray,
|
||||
RecursiveOption: input.RecursiveOption,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
for k, v := range m {
|
||||
if _, ok := mapField[k]; !ok {
|
||||
tempV := v
|
||||
mapField[k] = tempV
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
|
||||
@ -110,7 +110,11 @@ func Test_FieldMap(t *testing.T) {
|
||||
Pass string `my-tag1:"pass1" my-tag2:"pass2" params:"pass"`
|
||||
}
|
||||
var user *User
|
||||
m, _ := structs.FieldMap(user, []string{"params"}, true)
|
||||
m, _ := structs.FieldMap(structs.FieldMapInput{
|
||||
Pointer: user,
|
||||
PriorityTagArray: []string{"params"},
|
||||
RecursiveOption: structs.RecursiveOptionEmbedded,
|
||||
})
|
||||
t.Assert(len(m), 3)
|
||||
_, ok := m["Id"]
|
||||
t.Assert(ok, true)
|
||||
@ -130,7 +134,11 @@ func Test_FieldMap(t *testing.T) {
|
||||
Pass string `my-tag1:"pass1" my-tag2:"pass2" params:"pass"`
|
||||
}
|
||||
var user *User
|
||||
m, _ := structs.FieldMap(user, nil, true)
|
||||
m, _ := structs.FieldMap(structs.FieldMapInput{
|
||||
Pointer: user,
|
||||
PriorityTagArray: nil,
|
||||
RecursiveOption: structs.RecursiveOptionEmbedded,
|
||||
})
|
||||
t.Assert(len(m), 3)
|
||||
_, ok := m["Id"]
|
||||
t.Assert(ok, true)
|
||||
|
||||
@ -25,7 +25,11 @@ func (v *Validator) doCheckStruct(object interface{}) Error {
|
||||
errorMaps = make(map[string]map[string]string) // Returning error.
|
||||
fieldToAliasNameMap = make(map[string]string) // Field name to alias name map.
|
||||
)
|
||||
fieldMap, err := structs.FieldMap(object, aliasNameTagPriority, true)
|
||||
fieldMap, err := structs.FieldMap(structs.FieldMapInput{
|
||||
Pointer: object,
|
||||
PriorityTagArray: aliasNameTagPriority,
|
||||
RecursiveOption: structs.RecursiveOptionEmbedded,
|
||||
})
|
||||
if err != nil {
|
||||
return newErrorStr(internalObjectErrRuleName, err.Error())
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user