This commit is contained in:
John Guo
2021-08-03 22:21:20 +08:00
parent a4497ed547
commit 685bf56a30
5 changed files with 202 additions and 21 deletions

View File

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

View File

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

View File

@ -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 {

View File

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

View File

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