mirror of
https://gitee.com/johng/gf
synced 2026-06-07 02:12:11 +08:00
improve With feature for package gdb
This commit is contained in:
@ -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
|
||||
}
|
||||
|
||||
@ -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)
|
||||
})
|
||||
}
|
||||
|
||||
@ -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...)
|
||||
}
|
||||
|
||||
@ -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 {
|
||||
|
||||
@ -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])
|
||||
}
|
||||
|
||||
@ -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.
|
||||
|
||||
Reference in New Issue
Block a user