improve With feature for package gdb

This commit is contained in:
John Guo
2021-08-27 20:16:29 +08:00
parent f9eaa8f930
commit fa7a3e987d
6 changed files with 187 additions and 65 deletions

View File

@ -18,7 +18,7 @@ import (
"github.com/gogf/gf/text/gstr"
)
// With creates and returns an ORM model based on meta data of given object.
// With creates and returns an ORM model based on metadata of given object.
// It also enables model association operations feature on given `object`.
// It can be called multiple times to add one or more objects to model and enable
// their mode association operations feature.
@ -64,7 +64,7 @@ func (m *Model) doWithScanStruct(pointer interface{}) error {
err error
allowedTypeStrArray = make([]string, 0)
)
fieldMap, err := structs.FieldMap(structs.FieldMapInput{
currentStructFieldMap, err := structs.FieldMap(structs.FieldMapInput{
Pointer: pointer,
PriorityTagArray: nil,
RecursiveOption: structs.RecursiveOptionEmbeddedNoTag,
@ -74,7 +74,7 @@ func (m *Model) doWithScanStruct(pointer interface{}) error {
}
// It checks the with array and automatically calls the ScanList to complete association querying.
if !m.withAll {
for _, field := range fieldMap {
for _, field := range currentStructFieldMap {
for _, withItem := range m.withArray {
withItemReflectValueType, err := structs.StructType(withItem)
if err != nil {
@ -91,7 +91,7 @@ func (m *Model) doWithScanStruct(pointer interface{}) error {
}
}
}
for _, field := range fieldMap {
for _, field := range currentStructFieldMap {
var (
fieldTypeStr = gstr.TrimAll(field.Type().String(), "*[]")
parsedTagOutput = m.parseWithTagInFieldStruct(field)
@ -99,43 +99,40 @@ func (m *Model) doWithScanStruct(pointer interface{}) error {
if parsedTagOutput.With == "" {
continue
}
// Just handler "with" type attribute struct.
// It just handlers "with" type attribute struct, so it ignores other struct types.
if !m.withAll && !gstr.InArray(allowedTypeStrArray, fieldTypeStr) {
continue
}
array := gstr.SplitAndTrim(parsedTagOutput.With, "=")
if len(array) == 1 {
// It supports using only one column name
// It also supports using only one column name
// if both tables associates using the same column name.
array = append(array, parsedTagOutput.With)
}
var (
model *Model
fieldKeys []string
relatedFieldName = array[0]
relatedAttrName = array[1]
relatedFieldValue interface{}
model *Model
fieldKeys []string
relatedSourceName = array[0]
relatedTargetName = array[1]
relatedTargetValue interface{}
)
// Find the value of related attribute from `pointer`.
for attributeName, attributeValue := range fieldMap {
if utils.EqualFoldWithoutChars(attributeName, relatedAttrName) {
relatedFieldValue = attributeValue.Value.Interface()
for attributeName, attributeValue := range currentStructFieldMap {
if utils.EqualFoldWithoutChars(attributeName, relatedTargetName) {
relatedTargetValue = attributeValue.Value.Interface()
break
}
}
if relatedFieldValue == nil {
if relatedTargetValue == nil {
return gerror.NewCodef(
gcode.CodeInvalidParameter,
`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(),
`cannot find the target related value of name "%s" in with tag "%s" for attribute "%s.%s"`,
relatedTargetName, parsedTagOutput.With, reflect.TypeOf(pointer).Elem(), field.Name(),
)
}
bindToReflectValue := field.Value
switch bindToReflectValue.Kind() {
case reflect.Array, reflect.Slice:
if bindToReflectValue.CanAddr() {
bindToReflectValue = bindToReflectValue.Addr()
}
if bindToReflectValue.Kind() != reflect.Ptr && bindToReflectValue.CanAddr() {
bindToReflectValue = bindToReflectValue.Addr()
}
// It automatically retrieves struct field names from current attribute struct/slice.
@ -159,7 +156,7 @@ func (m *Model) doWithScanStruct(pointer interface{}) error {
model = model.Order(parsedTagOutput.Order)
}
err = model.Fields(fieldKeys).Where(relatedFieldName, relatedFieldValue).Scan(bindToReflectValue)
err = model.Fields(fieldKeys).Where(relatedSourceName, relatedTargetValue).Scan(bindToReflectValue)
if err != nil {
return err
}
@ -179,7 +176,7 @@ func (m *Model) doWithScanStructs(pointer interface{}) error {
err error
allowedTypeStrArray = make([]string, 0)
)
fieldMap, err := structs.FieldMap(structs.FieldMapInput{
currentStructFieldMap, err := structs.FieldMap(structs.FieldMapInput{
Pointer: pointer,
PriorityTagArray: nil,
RecursiveOption: structs.RecursiveOptionEmbeddedNoTag,
@ -189,7 +186,7 @@ func (m *Model) doWithScanStructs(pointer interface{}) error {
}
// It checks the with array and automatically calls the ScanList to complete association querying.
if !m.withAll {
for _, field := range fieldMap {
for _, field := range currentStructFieldMap {
for _, withItem := range m.withArray {
withItemReflectValueType, err := structs.StructType(withItem)
if err != nil {
@ -207,7 +204,7 @@ func (m *Model) doWithScanStructs(pointer interface{}) error {
}
}
for fieldName, field := range fieldMap {
for fieldName, field := range currentStructFieldMap {
var (
fieldTypeStr = gstr.TrimAll(field.Type().String(), "*[]")
parsedTagOutput = m.parseWithTagInFieldStruct(field)
@ -225,24 +222,24 @@ func (m *Model) doWithScanStructs(pointer interface{}) error {
array = append(array, parsedTagOutput.With)
}
var (
model *Model
fieldKeys []string
relatedFieldName = array[0]
relatedAttrName = array[1]
relatedFieldValue interface{}
model *Model
fieldKeys []string
relatedSourceName = array[0]
relatedTargetName = array[1]
relatedTargetValue interface{}
)
// Find the value slice of related attribute from `pointer`.
for attributeName, _ := range fieldMap {
if utils.EqualFoldWithoutChars(attributeName, relatedAttrName) {
relatedFieldValue = ListItemValuesUnique(pointer, attributeName)
for attributeName, _ := range currentStructFieldMap {
if utils.EqualFoldWithoutChars(attributeName, relatedTargetName) {
relatedTargetValue = ListItemValuesUnique(pointer, attributeName)
break
}
}
if relatedFieldValue == nil {
if relatedTargetValue == nil {
return gerror.NewCodef(
gcode.CodeInvalidParameter,
`cannot find the related value for attribute name "%s" of with tag "%s"`,
relatedAttrName, parsedTagOutput.With,
relatedTargetName, parsedTagOutput.With,
)
}
@ -267,7 +264,9 @@ func (m *Model) doWithScanStructs(pointer interface{}) error {
model = model.Order(parsedTagOutput.Order)
}
err = model.Fields(fieldKeys).Where(relatedFieldName, relatedFieldValue).ScanList(pointer, fieldName, parsedTagOutput.With)
err = model.Fields(fieldKeys).
Where(relatedSourceName, relatedTargetValue).
ScanList(pointer, fieldName, parsedTagOutput.With)
if err != nil {
return err
}

View File

@ -1867,3 +1867,127 @@ func Test_Table_Relation_With_MultipleDepends_Embedded(t *testing.T) {
t.Assert(tableA[1].TableB.TableC.Id, 300)
})
}
func Test_Table_Relation_WithAll_Embedded_Meta_NameMatchingRule(t *testing.T) {
var (
tableUser = "user1"
tableUserDetail = "user_detail1"
tableUserScores = "user_scores1"
)
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 (
user_id int(10) unsigned NOT NULL,
address varchar(45) NOT NULL,
PRIMARY KEY (user_id)
) 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,
user_id 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_detail1"`
UserID int `json:"user_id"`
Address string `json:"address"`
}
type UserScores struct {
gmeta.Meta `orm:"table:user_scores1"`
ID int `json:"id"`
UserID int `json:"user_id"`
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:user1"`
UserEmbedded
UserDetail UserDetail `orm:"with:user_id=id"`
UserScores []*UserScores `orm:"with:user_id=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.AssertNil(err)
// Detail.
_, err = db.Insert(tableUserDetail, g.Map{
"user_id": i,
"address": fmt.Sprintf(`address_%d`, i),
})
gtest.AssertNil(err)
// Scores.
for j := 1; j <= 5; j++ {
_, err = db.Insert(tableUserScores, g.Map{
"user_id": i,
"score": j,
})
gtest.AssertNil(err)
}
}
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.UserID, 3)
t.Assert(user.UserDetail.Address, `address_3`)
t.Assert(len(user.UserScores), 5)
t.Assert(user.UserScores[0].UserID, 3)
t.Assert(user.UserScores[0].Score, 1)
t.Assert(user.UserScores[4].UserID, 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.UserID, 4)
t.Assert(user.UserDetail.Address, `address_4`)
t.Assert(len(user.UserScores), 5)
t.Assert(user.UserScores[0].UserID, 4)
t.Assert(user.UserScores[0].Score, 1)
t.Assert(user.UserScores[4].UserID, 4)
t.Assert(user.UserScores[4].Score, 5)
})
}

View File

@ -133,11 +133,13 @@ func Entries() []*Entry {
}
// Start starts running the specified timed task named `name`.
func Start(name string) {
defaultCron.Start(name)
// If no`name` specified, it starts the entire cron.
func Start(name ...string) {
defaultCron.Start(name...)
}
// Stop stops running the specified timed task named `name`.
func Stop(name string) {
defaultCron.Stop(name)
// If no`name` specified, it stops the entire cron.
func Stop(name ...string) {
defaultCron.Stop(name...)
}

View File

@ -184,6 +184,7 @@ func (c *Cron) Search(name string) *Entry {
}
// Start starts running the specified timed task named `name`.
// If no`name` specified, it starts the entire cron.
func (c *Cron) Start(name ...string) {
if len(name) > 0 {
for _, v := range name {
@ -197,6 +198,7 @@ func (c *Cron) Start(name ...string) {
}
// Stop stops running the specified timed task named `name`.
// If no`name` specified, it stops the entire cron.
func (c *Cron) Stop(name ...string) {
if len(name) > 0 {
for _, v := range name {

View File

@ -8,7 +8,6 @@
package gmeta
import (
"github.com/gogf/gf/container/gmap"
"github.com/gogf/gf/container/gvar"
"github.com/gogf/gf/internal/structs"
)
@ -21,34 +20,27 @@ const (
metaAttributeName = "Meta"
)
var (
// metaDataCacheMap is a cache map for struct type to enhance the performance.
metaDataCacheMap = gmap.NewStrAnyMap(true)
)
// Data retrieves and returns all meta data from `object`.
// Data retrieves and returns all metadata from `object`.
// It automatically parses and caches the tag string from "Mata" attribute as its meta data.
func Data(object interface{}) map[string]interface{} {
reflectType, err := structs.StructType(object)
if err != nil {
panic(err)
}
return metaDataCacheMap.GetOrSetFuncLock(reflectType.Signature(), func() interface{} {
if field, ok := reflectType.FieldByName(metaAttributeName); ok {
var (
tags = structs.ParseTag(string(field.Tag))
data = make(map[string]interface{}, len(tags))
)
for k, v := range tags {
data[k] = v
}
return data
if field, ok := reflectType.FieldByName(metaAttributeName); ok {
var (
tags = structs.ParseTag(string(field.Tag))
data = make(map[string]interface{}, len(tags))
)
for k, v := range tags {
data[k] = v
}
return map[string]interface{}{}
}).(map[string]interface{})
return data
}
return map[string]interface{}{}
}
// Get retrieves and returns specified meta data by `key` from `object`.
// Get retrieves and returns specified metadata by `key` from `object`.
func Get(object interface{}, key string) *gvar.Var {
return gvar.New(Data(object)[key])
}

View File

@ -17,12 +17,15 @@ import (
"time"
)
var (
sequence gtype.Uint32 // Sequence for unique purpose of current process.
const (
sequenceMax = uint32(46655) // Sequence max("zzz").
randomStrBase = "0123456789abcdefghijklmnopqrstuvwxyz" // Random chars string(36 bytes).
macAddrStr = "0000000" // MAC addresses hash result in 7 bytes.
processIdStr = "0000" // Process id in 4 bytes.
)
var (
sequence gtype.Uint32 // Sequence for unique purpose of current process.
macAddrStr = "0000000" // MAC addresses hash result in 7 bytes.
processIdStr = "0000" // Process id in 4 bytes.
)
// init initializes several fixed local variable.