From fa7a3e987d51aa3d9839672682b8f0b0714b56d9 Mon Sep 17 00:00:00 2001 From: John Guo Date: Fri, 27 Aug 2021 20:16:29 +0800 Subject: [PATCH] improve With feature for package gdb --- database/gdb/gdb_model_with.go | 73 +++++------ .../gdb/gdb_z_mysql_association_with_test.go | 124 ++++++++++++++++++ os/gcron/gcron.go | 10 +- os/gcron/gcron_cron.go | 2 + util/gmeta/gmeta.go | 32 ++--- util/guid/guid_string.go | 11 +- 6 files changed, 187 insertions(+), 65 deletions(-) diff --git a/database/gdb/gdb_model_with.go b/database/gdb/gdb_model_with.go index 542f302a9..36782fffc 100644 --- a/database/gdb/gdb_model_with.go +++ b/database/gdb/gdb_model_with.go @@ -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 } diff --git a/database/gdb/gdb_z_mysql_association_with_test.go b/database/gdb/gdb_z_mysql_association_with_test.go index d1b98727e..7345221d1 100644 --- a/database/gdb/gdb_z_mysql_association_with_test.go +++ b/database/gdb/gdb_z_mysql_association_with_test.go @@ -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) + }) +} diff --git a/os/gcron/gcron.go b/os/gcron/gcron.go index aeb63bc8b..66c5692aa 100644 --- a/os/gcron/gcron.go +++ b/os/gcron/gcron.go @@ -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...) } diff --git a/os/gcron/gcron_cron.go b/os/gcron/gcron_cron.go index cace246d2..4b954810d 100644 --- a/os/gcron/gcron_cron.go +++ b/os/gcron/gcron_cron.go @@ -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 { diff --git a/util/gmeta/gmeta.go b/util/gmeta/gmeta.go index 4969537c2..c522fcd7b 100644 --- a/util/gmeta/gmeta.go +++ b/util/gmeta/gmeta.go @@ -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]) } diff --git a/util/guid/guid_string.go b/util/guid/guid_string.go index 2927b9e62..666d0ec8d 100644 --- a/util/guid/guid_string.go +++ b/util/guid/guid_string.go @@ -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.