fix issue missing org tag value retrieving when using struct in Fields for package gdb

This commit is contained in:
John Guo
2021-09-28 22:02:08 +08:00
parent 750972e7c6
commit 02478371f2
8 changed files with 178 additions and 43 deletions

View File

@ -667,7 +667,7 @@ func (c *Core) MarshalJSON() ([]byte, error) {
return []byte(fmt.Sprintf(`%+v`, c)), nil
}
// writeSqlToLogger outputs the sql object to logger.
// writeSqlToLogger outputs the Sql object to logger.
// It is enabled only if configuration "debug" is true.
func (c *Core) writeSqlToLogger(ctx context.Context, sql *Sql) {
var transactionIdStr string

View File

@ -328,6 +328,26 @@ func doQuoteString(s, charLeft, charRight string) string {
return gstr.Join(array1, ",")
}
func getFieldsFromStructOrMap(structOrMap interface{}) (fields []string) {
fields = []string{}
if utils.IsStruct(structOrMap) {
structFields, _ := structs.Fields(structs.FieldsInput{
Pointer: structOrMap,
RecursiveOption: structs.RecursiveOptionEmbeddedNoTag,
})
for _, structField := range structFields {
if tag := structField.Tag(OrmTagForStruct); tag != "" && gregex.IsMatchString(regularFieldNameRegPattern, tag) {
fields = append(fields, tag)
} else {
fields = append(fields, structField.Name())
}
}
} else {
fields = gutil.Keys(structOrMap)
}
return
}
// GetWhereConditionOfStruct returns the where condition sql and arguments by given struct pointer.
// This function automatically retrieves primary or unique field and its attribute value as condition.
func GetWhereConditionOfStruct(pointer interface{}) (where string, args []interface{}, err error) {

View File

@ -13,7 +13,6 @@ import (
"github.com/gogf/gf/errors/gerror"
"github.com/gogf/gf/text/gstr"
"github.com/gogf/gf/util/gconv"
"github.com/gogf/gf/util/gutil"
)
// Fields appends `fieldNamesOrMapStruct` to the operation fields of the model, multiple fields joined using char ','.
@ -43,7 +42,7 @@ func (m *Model) Fields(fieldNamesOrMapStruct ...interface{}) *Model {
))
default:
return m.appendFieldsByStr(gstr.Join(
m.mappingAndFilterToTableFields(gutil.Keys(r), true), ",",
m.mappingAndFilterToTableFields(getFieldsFromStructOrMap(r), true), ",",
))
}
}
@ -71,7 +70,7 @@ func (m *Model) FieldsEx(fieldNamesOrMapStruct ...interface{}) *Model {
case []string:
model.fieldsEx = gstr.Join(m.mappingAndFilterToTableFields(r, true), ",")
default:
model.fieldsEx = gstr.Join(m.mappingAndFilterToTableFields(gutil.Keys(r), true), ",")
model.fieldsEx = gstr.Join(m.mappingAndFilterToTableFields(getFieldsFromStructOrMap(r), true), ",")
}
return model
}

View File

@ -33,6 +33,7 @@ var (
db gdb.DB
dbPrefix gdb.DB
dbCtxStrict gdb.DB
dbInvalid gdb.DB
configNode gdb.ConfigNode
)
@ -62,9 +63,13 @@ func init() {
nodeCtxStrict := configNode
nodeCtxStrict.CtxStrict = true
nodeInvalid := configNode
nodeInvalid.Port = "3307"
gdb.AddConfigNode("test", configNode)
gdb.AddConfigNode("prefix", nodePrefix)
gdb.AddConfigNode("ctxstrict", nodeCtxStrict)
gdb.AddConfigNode("nodeinvalid", nodeInvalid)
gdb.AddConfigNode(gdb.DefaultGroupName, configNode)
// Default db.
@ -109,6 +114,14 @@ func init() {
gtest.Error(err)
}
dbCtxStrict.SetSchema(TestSchema1)
// Invalid db.
if r, err := gdb.New("nodeinvalid"); err != nil {
gtest.Error(err)
} else {
dbInvalid = r
}
dbInvalid.SetSchema(TestSchema1)
}
func createTable(table ...string) string {

View File

@ -7,10 +7,13 @@
package gdb_test
import (
"bytes"
"context"
"database/sql"
"fmt"
"github.com/gogf/gf/encoding/gjson"
"github.com/gogf/gf/text/gstr"
"os"
"testing"
"time"
@ -1086,35 +1089,53 @@ func Test_Model_Structs(t *testing.T) {
})
}
// JSON tag is only used for JSON Marshal/Unmarshal, DO NOT use it in multiple purposes!
//func Test_Model_StructsWithJsonTag(t *testing.T) {
// table := createInitTable()
// defer dropTable(table)
//
// db.SetDebug(true)
// gtest.C(t, func(t *gtest.T) {
// type User struct {
// Uid int `json:"id"`
// Passport string
// Password string
// Name string `json:"nick_name"`
// Time gtime.Time `json:"create_time"`
// }
// var users []User
// err := db.Model(table).Order("id asc").Scan(&users)
// if err != nil {
// gtest.Error(err)
// }
// t.Assert(len(users), TableSize)
// t.Assert(users[0].Uid, 1)
// t.Assert(users[1].Uid, 2)
// t.Assert(users[2].Uid, 3)
// t.Assert(users[0].Name, "name_1")
// t.Assert(users[1].Name, "name_2")
// t.Assert(users[2].Name, "name_3")
// t.Assert(users[0].Time.String(), "2018-10-24 10:00:00")
// })
//}
func Test_Model_StructsWithOrmTag(t *testing.T) {
table := createInitTable()
defer dropTable(table)
dbInvalid.SetDebug(true)
defer dbInvalid.SetDebug(false)
gtest.C(t, func(t *gtest.T) {
type User struct {
Uid int `orm:"id"`
Passport string
Password string `orm:"password"`
Name string `orm:"nick_name"`
Time gtime.Time `orm:"create_time"`
}
var (
users []User
buffer = bytes.NewBuffer(nil)
)
dbInvalid.GetLogger().SetWriter(buffer)
defer dbInvalid.GetLogger().SetWriter(os.Stdout)
dbInvalid.Model(table).Order("id asc").Scan(&users)
t.Assert(
gstr.Contains(buffer.String(), "SELECT `id`,`Passport`,`password`,`nick_name`,`create_time` FROM `user"),
true,
)
})
//db.SetDebug(true)
//defer db.SetDebug(false)
gtest.C(t, func(t *gtest.T) {
type A struct {
Passport string
Password string
}
type B struct {
A
NickName string
}
one, err := db.Model(table).Fields(&B{}).Where("id", 2).One()
t.AssertNil(err)
t.Assert(len(one), 3)
t.Assert(one["nickname"], "name_2")
t.Assert(one["passport"], "user_2")
t.Assert(one["password"], "pass_2")
})
}
func Test_Model_Scan(t *testing.T) {
table := createInitTable()

View File

@ -75,6 +75,15 @@ const (
RecursiveOptionEmbeddedNoTag = 2 // Recursively retrieving fields as map if the field is an embedded struct and the field has no tag.
)
type FieldsInput struct {
// Pointer should be type of struct/*struct.
Pointer interface{}
// RecursiveOption specifies the way retrieving the fields recursively if the attribute
// is an embedded struct. It is RecursiveOptionNone in default.
RecursiveOption int
}
type FieldMapInput struct {
// Pointer should be type of struct/*struct.
Pointer interface{}
@ -88,6 +97,52 @@ type FieldMapInput struct {
RecursiveOption int
}
// Fields retrieves and returns the fields of `pointer` as slice.
func Fields(in FieldsInput) ([]*Field, error) {
var (
fieldFilterMap = make(map[string]struct{})
retrievedFields = make([]*Field, 0)
)
rangeFields, err := getFieldValues(in.Pointer)
if err != nil {
return nil, err
}
for i := 0; i < len(rangeFields); i++ {
field := rangeFields[i]
if _, ok := fieldFilterMap[field.Name()]; ok {
continue
}
// Only retrieve exported attributes.
if !field.IsExported() {
continue
}
if field.IsEmbedded() {
if in.RecursiveOption != RecursiveOptionNone {
switch in.RecursiveOption {
case RecursiveOptionEmbeddedNoTag:
if field.TagStr() != "" {
break
}
fallthrough
case RecursiveOptionEmbedded:
structFields, err := Fields(FieldsInput{
Pointer: field.Value,
RecursiveOption: in.RecursiveOption,
})
if err != nil {
return nil, err
}
rangeFields = append(rangeFields, structFields...)
}
}
continue
}
fieldFilterMap[field.Name()] = struct{}{}
retrievedFields = append(retrievedFields, field)
}
return retrievedFields, nil
}
// FieldMap retrieves and returns struct field as map[name/tag]*Field from `pointer`.
//
// The parameter `pointer` should be type of struct/*struct.
@ -99,8 +154,8 @@ type FieldMapInput struct {
// is an embedded struct.
//
// Note that it only retrieves the exported attributes with first letter up-case from struct.
func FieldMap(input FieldMapInput) (map[string]*Field, error) {
fields, err := getFieldValues(input.Pointer)
func FieldMap(in FieldMapInput) (map[string]*Field, error) {
fields, err := getFieldValues(in.Pointer)
if err != nil {
return nil, err
}
@ -114,7 +169,7 @@ func FieldMap(input FieldMapInput) (map[string]*Field, error) {
continue
}
tagValue = ""
for _, p := range input.PriorityTagArray {
for _, p := range in.PriorityTagArray {
tagValue = field.Tag(p)
if tagValue != "" && tagValue != "-" {
break
@ -125,8 +180,8 @@ func FieldMap(input FieldMapInput) (map[string]*Field, error) {
if tagValue != "" {
mapField[tagValue] = tempField
} else {
if input.RecursiveOption != RecursiveOptionNone && field.IsEmbedded() {
switch input.RecursiveOption {
if in.RecursiveOption != RecursiveOptionNone && field.IsEmbedded() {
switch in.RecursiveOption {
case RecursiveOptionEmbeddedNoTag:
if field.TagStr() != "" {
mapField[field.Name()] = tempField
@ -136,8 +191,8 @@ func FieldMap(input FieldMapInput) (map[string]*Field, error) {
case RecursiveOptionEmbedded:
m, err := FieldMap(FieldMapInput{
Pointer: field.Value,
PriorityTagArray: input.PriorityTagArray,
RecursiveOption: input.RecursiveOption,
PriorityTagArray: in.PriorityTagArray,
RecursiveOption: in.RecursiveOption,
})
if err != nil {
return nil, err

View File

@ -13,6 +13,7 @@ import (
)
// ParseTag parses tag string into map.
// For example, tag ParseTag(`v:"required" p:"id" d:"1"`) => map[v:required p:id d:1].
func ParseTag(tag string) map[string]string {
var (
key string
@ -68,7 +69,9 @@ func ParseTag(tag string) map[string]string {
//
// The parameter `pointer` should be type of struct/*struct.
//
// Note that it only retrieves the exported attributes with first letter up-case from struct.
// Note that,
// 1. It only retrieves the exported attributes with first letter up-case from struct.
// 2. The parameter `priority` should be given, it only retrieves fields that has given tag.
func TagFields(pointer interface{}, priority []string) ([]*Field, error) {
return getFieldValuesByTagPriority(pointer, priority, map[string]struct{}{})
}
@ -77,7 +80,9 @@ func TagFields(pointer interface{}, priority []string) ([]*Field, error) {
//
// The parameter `pointer` should be type of struct/*struct.
//
// Note that it only retrieves the exported attributes with first letter up-case from struct.
// Note that,
// 1. It only retrieves the exported attributes with first letter up-case from struct.
// 2. The parameter `priority` should be given, it only retrieves fields that has given tag.
func TagMapName(pointer interface{}, priority []string) (map[string]string, error) {
fields, err := TagFields(pointer, priority)
if err != nil {
@ -93,7 +98,9 @@ func TagMapName(pointer interface{}, priority []string) (map[string]string, erro
// TagMapField retrieves struct tags as map[tag]*Field from `pointer`, and returns it.
// The parameter `object` should be either type of struct/*struct/[]struct/[]*struct.
//
// Note that it only retrieves the exported attributes with first letter up-case from struct.
// Note that,
// 1. It only retrieves the exported attributes with first letter up-case from struct.
// 2. The parameter `priority` should be given, it only retrieves fields that has given tag.
func TagMapField(object interface{}, priority []string) (map[string]*Field, error) {
fields, err := TagFields(object, priority)
if err != nil {

View File

@ -102,6 +102,26 @@ func Test_StructOfNilPointer(t *testing.T) {
})
}
func Test_Fields(t *testing.T) {
gtest.C(t, func(t *gtest.T) {
type User struct {
Id int
Name string `params:"name"`
Pass string `my-tag1:"pass1" my-tag2:"pass2" params:"pass"`
}
var user *User
fields, _ := structs.Fields(user)
t.Assert(len(fields), 3)
t.Assert(fields[0].Name(), "Id")
t.Assert(fields[1].Name(), "Name")
t.Assert(fields[1].Tag("params"), "name")
t.Assert(fields[2].Name(), "Pass")
t.Assert(fields[2].Tag("my-tag1"), "pass1")
t.Assert(fields[2].Tag("my-tag2"), "pass2")
t.Assert(fields[2].Tag("params"), "pass")
})
}
func Test_FieldMap(t *testing.T) {
gtest.C(t, func(t *gtest.T) {
type User struct {