add package gmeta for embedded struct of meta data feature

This commit is contained in:
John Guo
2021-02-07 21:23:09 +08:00
parent 39fb842e9b
commit b4d5335e43
17 changed files with 499 additions and 361 deletions

View File

@ -35,6 +35,7 @@ type DB interface {
// The DB interface is designed not only for
// relational databases but also for NoSQL databases in the future. The name
// "Table" is not proper for that purpose any more.
// Deprecated, use Model instead.
Table(table ...string) *Model
Model(table ...string) *Model
Schema(schema string) *Schema

View File

@ -14,6 +14,7 @@ import (
"github.com/gogf/gf/internal/json"
"github.com/gogf/gf/internal/utils"
"github.com/gogf/gf/os/gtime"
"github.com/gogf/gf/util/gmeta"
"github.com/gogf/gf/util/gutil"
"reflect"
"regexp"
@ -47,10 +48,16 @@ type apiMapStrAny interface {
MapStrAny() map[string]interface{}
}
// apiTableName is the interface for retrieving table name fro struct.
type apiTableName interface {
TableName() string
}
const (
OrmTagForStruct = "orm"
OrmTagForUnique = "unique"
OrmTagForPrimary = "primary"
OrmTagForStruct = "orm"
OrmTagForUnique = "unique"
OrmTagForPrimary = "primary"
metaDataNameForTable = "table"
)
var (
@ -61,6 +68,26 @@ var (
structTagPriority = append([]string{OrmTagForStruct}, gconv.StructTagPriority...)
)
// getTableNameFromObject retrieves and returns the table name from struct object.
func getTableNameFromObject(object interface{}) string {
if r, ok := object.(apiTableName); ok {
// Use the interface value.
return r.TableName()
} else if table := gmeta.Get(object, metaDataNameForTable); !table.IsEmpty() {
// User meta data tag "table".
return table.String()
} else {
// Use the struct name of snake case.
if t, err := structs.StructType(object); err != nil {
panic(err)
} else {
return gstr.CaseSnakeFirstUpper(
gstr.StrEx(t.String(), "."),
)
}
}
}
// ListItemValues retrieves and returns the elements of all item struct/map with key <key>.
// Note that the parameter <list> should be type of slice which contains elements of map or struct,
// or else it returns an empty slice.
@ -752,9 +779,16 @@ func convertMapToStruct(data map[string]interface{}, pointer interface{}) error
return err
}
// It retrieves and returns the mapping between orm tag and the struct attribute name.
mapping := make(map[string]string)
var (
mapping = make(map[string]string)
tagFieldName string
)
for tag, attr := range tagNameMap {
mapping[strings.Split(tag, ",")[0]] = attr
tagFieldName = strings.Split(tag, ",")[0]
if !gregex.IsMatchString(regularFieldNameRegPattern, tagFieldName) {
continue
}
mapping[tagFieldName] = attr
}
return gconv.Struct(data, pointer, mapping)
}

View File

@ -25,6 +25,7 @@ type Model struct {
tables string // Operation table names, which can be more than one table names and aliases, like: "user", "user u", "user u, user_detail ud".
fields string // Operation fields, multiple fields joined using char ','.
fieldsEx string // Excluded operation fields, multiple fields joined using char ','.
withArray []interface{} // Arguments for With feature.
extraArgs []interface{} // Extra custom arguments for sql.
whereHolder []*whereHolder // Condition strings for where operation.
groupBy string // Used for "group by" statement.
@ -53,9 +54,13 @@ type whereHolder struct {
}
const (
OPTION_OMITEMPTY = 1
// Deprecated, use OptionOmitEmpty instead.
OPTION_OMITEMPTY = 1
// Deprecated, use OptionAllowEmpty instead..
OPTION_ALLOWEMPTY = 2
OptionOmitEmpty = 1
OptionAllowEmpty = 2
linkTypeMaster = 1
linkTypeSlave = 2
whereHolderWhere = 1
@ -63,15 +68,22 @@ const (
whereHolderOr = 3
)
// Table creates and returns a new ORM model from given schema.
// The parameter <table> can be more than one table names, and also alias name, like:
// 1. Table names:
// Table("user")
// Table("user u")
// Table("user, user_detail")
// Table("user u, user_detail ud")
// 2. Table name with alias: Table("user", "u")
// Table is alias of Core.Model.
// See Core.Model.
// Deprecated, use Model instead.
func (c *Core) Table(table ...string) *Model {
return c.DB.Model(table...)
}
// Model creates and returns a new ORM model from given schema.
// The parameter <table> can be more than one table names, and also alias name, like:
// 1. Model names:
// Model("user")
// Model("user u")
// Model("user, user_detail")
// Model("user u, user_detail ud")
// 2. Model name with alias: Model("user", "u")
func (c *Core) Model(table ...string) *Model {
tables := ""
if len(table) > 1 {
tables = fmt.Sprintf(
@ -79,8 +91,6 @@ func (c *Core) Table(table ...string) *Model {
)
} else if len(table) == 1 {
tables = c.DB.QuotePrefixTableName(table[0])
} else {
panic("table cannot be empty")
}
return &Model{
db: c.DB,
@ -89,31 +99,25 @@ func (c *Core) Table(table ...string) *Model {
fields: "*",
start: -1,
offset: -1,
option: OPTION_ALLOWEMPTY,
option: OptionAllowEmpty,
}
}
// Model is alias of Core.Table.
// See Core.Table.
func (c *Core) Model(table ...string) *Model {
return c.DB.Table(table...)
// Table is alias of tx.Model.
// Deprecated, use Model instead.
func (tx *TX) Table(table ...string) *Model {
return tx.Model(table...)
}
// Table acts like Core.Table except it operates on transaction.
// See Core.Table.
func (tx *TX) Table(table ...string) *Model {
model := tx.db.Table(table...)
// Model acts like Core.Model except it operates on transaction.
// See Core.Model.
func (tx *TX) Model(table ...string) *Model {
model := tx.db.Model(table...)
model.db = tx.db
model.tx = tx
return model
}
// Model is alias of tx.Table.
// See tx.Table.
func (tx *TX) Model(table ...string) *Model {
return tx.Table(table...)
}
// Ctx sets the context for current operation.
func (m *Model) Ctx(ctx context.Context) *Model {
if ctx == nil {
@ -175,7 +179,7 @@ func (m *Model) Clone() *Model {
newModel = m.db.Table(m.tablesInit)
}
*newModel = *m
// Deep copy slice attributes.
// Shallow copy slice attributes.
if n := len(m.extraArgs); n > 0 {
newModel.extraArgs = make([]interface{}, n)
copy(newModel.extraArgs, m.extraArgs)
@ -184,6 +188,10 @@ func (m *Model) Clone() *Model {
newModel.whereHolder = make([]*whereHolder, n)
copy(newModel.whereHolder, m.whereHolder)
}
if n := len(m.withArray); n > 0 {
newModel.withArray = make([]interface{}, n)
copy(newModel.withArray, m.withArray)
}
return newModel
}

View File

@ -13,15 +13,15 @@ func (m *Model) Option(option int) *Model {
return model
}
// OptionOmitEmpty sets OPTION_OMITEMPTY option for the model, which automatically filers
// OptionOmitEmpty sets OptionOmitEmpty option for the model, which automatically filers
// the data and where attributes for empty values.
// Deprecated, use OmitEmpty instead.
func (m *Model) OptionOmitEmpty() *Model {
return m.Option(OPTION_OMITEMPTY)
return m.Option(OptionOmitEmpty)
}
// OmitEmpty sets OPTION_OMITEMPTY option for the model, which automatically filers
// OmitEmpty sets OptionOmitEmpty option for the model, which automatically filers
// the data and where attributes for empty values.
func (m *Model) OmitEmpty() *Model {
return m.Option(OPTION_OMITEMPTY)
return m.Option(OptionOmitEmpty)
}

View File

@ -92,7 +92,7 @@ func (m *Model) doMappingAndFilterForInsertOrUpdateDataMap(data Map, allowOmitEm
return nil, err
}
// Remove key-value pairs of which the value is empty.
if allowOmitEmpty && m.option&OPTION_OMITEMPTY > 0 {
if allowOmitEmpty && m.option&OptionOmitEmpty > 0 {
tempMap := make(Map, len(data))
for k, v := range data {
if empty.IsEmpty(v) {
@ -206,7 +206,7 @@ func (m *Model) formatCondition(limit1 bool, isCountStatement bool) (conditionWh
case whereHolderWhere:
if conditionWhere == "" {
newWhere, newArgs := formatWhere(
m.db, v.where, v.args, m.option&OPTION_OMITEMPTY > 0,
m.db, v.where, v.args, m.option&OptionOmitEmpty > 0,
)
if len(newWhere) > 0 {
conditionWhere = newWhere
@ -218,7 +218,7 @@ func (m *Model) formatCondition(limit1 bool, isCountStatement bool) (conditionWh
case whereHolderAnd:
newWhere, newArgs := formatWhere(
m.db, v.where, v.args, m.option&OPTION_OMITEMPTY > 0,
m.db, v.where, v.args, m.option&OptionOmitEmpty > 0,
)
if len(newWhere) > 0 {
if len(conditionWhere) == 0 {
@ -233,7 +233,7 @@ func (m *Model) formatCondition(limit1 bool, isCountStatement bool) (conditionWh
case whereHolderOr:
newWhere, newArgs := formatWhere(
m.db, v.where, v.args, m.option&OPTION_OMITEMPTY > 0,
m.db, v.where, v.args, m.option&OptionOmitEmpty > 0,
)
if len(newWhere) > 0 {
if len(conditionWhere) == 0 {
@ -268,7 +268,7 @@ func (m *Model) formatCondition(limit1 bool, isCountStatement bool) (conditionWh
// HAVING.
if len(m.having) > 0 {
havingStr, havingArgs := formatWhere(
m.db, m.having[0], gconv.Interfaces(m.having[1]), m.option&OPTION_OMITEMPTY > 0,
m.db, m.having[0], gconv.Interfaces(m.having[1]), m.option&OptionOmitEmpty > 0,
)
if len(havingStr) > 0 {
conditionExtra += " HAVING " + havingStr

View File

@ -0,0 +1,17 @@
// Copyright GoFrame Author(https://goframe.org). All Rights Reserved.
//
// This Source Code Form is subject to the terms of the MIT License.
// If a copy of the MIT was not distributed with this file,
// You can obtain one at https://github.com/gogf/gf.
package gdb
func (m *Model) With(structAttrPointer interface{}) *Model {
model := m.getModel()
if m.tables == "" {
m.tables = getTableNameFromObject(structAttrPointer)
return model
}
model.withArray = append(model.withArray, structAttrPointer)
return model
}

View File

@ -226,7 +226,6 @@ func (r Result) ScanList(listPointer interface{}, bindToAttrName string, relatio
)
} else {
relationBindToSubAttrName = key
}
}
relationBindToSubAttrNameChecked = true

View File

@ -6,311 +6,103 @@
package gdb_test
//func Test_Table_Relation_With(t *testing.T) {
// var (
// tableUser = "user_" + gtime.TimestampMicroStr()
// tableUserDetail = "user_detail_" + gtime.TimestampMicroStr()
// tableUserScores = "user_scores_" + gtime.TimestampMicroStr()
// )
// if _, err := db.Exec(fmt.Sprintf(`
//CREATE TABLE %s (
// uid int(10) unsigned NOT NULL AUTO_INCREMENT,
// name varchar(45) NOT NULL,
// PRIMARY KEY (uid)
//) ENGINE=InnoDB DEFAULT CHARSET=utf8;
// `, tableUser)); err != nil {
// gtest.Error(err)
// }
// defer dropTable(tableUser)
//
// if _, err := db.Exec(fmt.Sprintf(`
//CREATE TABLE %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 %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 {
// Uid int `json:"uid"`
// Address string `json:"address"`
// }
// type UserScores struct {
// Id int `json:"id"`
// Uid int `json:"uid"`
// Score int `json:"score"`
// }
//
// type User struct {
// Id int `json:"id"`
// Name string `json:"name"`
// UserDetail *UserDetail `orm:"with:uid=uid"`
// 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)
// }
// }
// // MapKeyValue.
// gtest.C(t, func(t *gtest.T) {
// all, err := db.Table(tableUser).Where("uid", g.Slice{3, 4}).Order("uid asc").All()
// t.Assert(err, nil)
// t.Assert(all.Len(), 2)
// t.Assert(len(all.MapKeyValue("uid")), 2)
// t.Assert(all.MapKeyValue("uid")["3"].Map()["uid"], 3)
// t.Assert(all.MapKeyValue("uid")["4"].Map()["uid"], 4)
// all, err = db.Table(tableUserScores).Where("uid", g.Slice{3, 4}).Order("id asc").All()
// t.Assert(err, nil)
// t.Assert(all.Len(), 10)
// t.Assert(len(all.MapKeyValue("uid")), 2)
// t.Assert(len(all.MapKeyValue("uid")["3"].Slice()), 5)
// t.Assert(len(all.MapKeyValue("uid")["4"].Slice()), 5)
// t.Assert(gconv.Map(all.MapKeyValue("uid")["3"].Slice()[0])["uid"], 3)
// t.Assert(gconv.Map(all.MapKeyValue("uid")["3"].Slice()[0])["score"], 1)
// t.Assert(gconv.Map(all.MapKeyValue("uid")["3"].Slice()[4])["uid"], 3)
// t.Assert(gconv.Map(all.MapKeyValue("uid")["3"].Slice()[4])["score"], 5)
// })
// // Result ScanList with struct elements and pointer attributes.
// gtest.C(t, func(t *gtest.T) {
// var users []Entity
// // User
// all, err := db.Table(tableUser).Where("uid", g.Slice{3, 4}).Order("uid asc").All()
// t.Assert(err, nil)
// err = all.ScanList(&users, "User")
// t.Assert(err, nil)
// t.Assert(len(users), 2)
// t.Assert(users[0].User, &EntityUser{3, "name_3"})
// t.Assert(users[1].User, &EntityUser{4, "name_4"})
// // Detail
// all, err = db.Table(tableUserDetail).Where("uid", gdb.ListItemValues(users, "User", "Uid")).Order("uid asc").All()
// gtest.Assert(err, nil)
// err = all.ScanList(&users, "UserDetail", "User", "uid:Uid")
// t.Assert(err, nil)
// t.Assert(users[0].UserDetail, &EntityUserDetail{3, "address_3"})
// t.Assert(users[1].UserDetail, &EntityUserDetail{4, "address_4"})
// // Scores
// all, err = db.Table(tableUserScores).Where("uid", gdb.ListItemValues(users, "User", "Uid")).Order("id asc").All()
// gtest.Assert(err, nil)
// err = all.ScanList(&users, "UserScores", "User", "uid:Uid")
// t.Assert(err, nil)
// t.Assert(len(users[0].UserScores), 5)
// t.Assert(len(users[1].UserScores), 5)
// t.Assert(users[0].UserScores[0].Uid, 3)
// t.Assert(users[0].UserScores[0].Score, 1)
// t.Assert(users[0].UserScores[4].Score, 5)
// t.Assert(users[1].UserScores[0].Uid, 4)
// t.Assert(users[1].UserScores[0].Score, 1)
// t.Assert(users[1].UserScores[4].Score, 5)
// })
//
// // Result ScanList with pointer elements and pointer attributes.
// gtest.C(t, func(t *gtest.T) {
// var users []*Entity
// // User
// all, err := db.Table(tableUser).Where("uid", g.Slice{3, 4}).Order("uid asc").All()
// t.Assert(err, nil)
// err = all.ScanList(&users, "User")
// t.Assert(err, nil)
// t.Assert(len(users), 2)
// t.Assert(users[0].User, &EntityUser{3, "name_3"})
// t.Assert(users[1].User, &EntityUser{4, "name_4"})
// // Detail
// all, err = db.Table(tableUserDetail).Where("uid", gdb.ListItemValues(users, "User", "Uid")).Order("uid asc").All()
// gtest.Assert(err, nil)
// err = all.ScanList(&users, "UserDetail", "User", "uid:Uid")
// t.Assert(err, nil)
// t.Assert(users[0].UserDetail, &EntityUserDetail{3, "address_3"})
// t.Assert(users[1].UserDetail, &EntityUserDetail{4, "address_4"})
// // Scores
// all, err = db.Table(tableUserScores).Where("uid", gdb.ListItemValues(users, "User", "Uid")).Order("id asc").All()
// gtest.Assert(err, nil)
// err = all.ScanList(&users, "UserScores", "User", "uid:Uid")
// t.Assert(err, nil)
// t.Assert(len(users[0].UserScores), 5)
// t.Assert(len(users[1].UserScores), 5)
// t.Assert(users[0].UserScores[0].Uid, 3)
// t.Assert(users[0].UserScores[0].Score, 1)
// t.Assert(users[0].UserScores[4].Score, 5)
// t.Assert(users[1].UserScores[0].Uid, 4)
// t.Assert(users[1].UserScores[0].Score, 1)
// t.Assert(users[1].UserScores[4].Score, 5)
// })
//
// // Result ScanList with struct elements and struct attributes.
// gtest.C(t, func(t *gtest.T) {
// type EntityUser struct {
// Uid int `json:"uid"`
// Name string `json:"name"`
// }
// type EntityUserDetail struct {
// Uid int `json:"uid"`
// Address string `json:"address"`
// }
// type EntityUserScores struct {
// Id int `json:"id"`
// Uid int `json:"uid"`
// Score int `json:"score"`
// }
// type Entity struct {
// User EntityUser
// UserDetail EntityUserDetail
// UserScores []EntityUserScores
// }
// var users []Entity
// // User
// all, err := db.Table(tableUser).Where("uid", g.Slice{3, 4}).Order("uid asc").All()
// t.Assert(err, nil)
// err = all.ScanList(&users, "User")
// t.Assert(err, nil)
// t.Assert(len(users), 2)
// t.Assert(users[0].User, &EntityUser{3, "name_3"})
// t.Assert(users[1].User, &EntityUser{4, "name_4"})
// // Detail
// all, err = db.Table(tableUserDetail).Where("uid", gdb.ListItemValues(users, "User", "Uid")).Order("uid asc").All()
// gtest.Assert(err, nil)
// err = all.ScanList(&users, "UserDetail", "User", "uid:Uid")
// t.Assert(err, nil)
// t.Assert(users[0].UserDetail, &EntityUserDetail{3, "address_3"})
// t.Assert(users[1].UserDetail, &EntityUserDetail{4, "address_4"})
// // Scores
// all, err = db.Table(tableUserScores).Where("uid", gdb.ListItemValues(users, "User", "Uid")).Order("id asc").All()
// gtest.Assert(err, nil)
// err = all.ScanList(&users, "UserScores", "User", "uid:Uid")
// t.Assert(err, nil)
// t.Assert(len(users[0].UserScores), 5)
// t.Assert(len(users[1].UserScores), 5)
// t.Assert(users[0].UserScores[0].Uid, 3)
// t.Assert(users[0].UserScores[0].Score, 1)
// t.Assert(users[0].UserScores[4].Score, 5)
// t.Assert(users[1].UserScores[0].Uid, 4)
// t.Assert(users[1].UserScores[0].Score, 1)
// t.Assert(users[1].UserScores[4].Score, 5)
// })
//
// // Result ScanList with pointer elements and struct attributes.
// gtest.C(t, func(t *gtest.T) {
// type EntityUser struct {
// Uid int `json:"uid"`
// Name string `json:"name"`
// }
// type EntityUserDetail struct {
// Uid int `json:"uid"`
// Address string `json:"address"`
// }
// type EntityUserScores struct {
// Id int `json:"id"`
// Uid int `json:"uid"`
// Score int `json:"score"`
// }
// type Entity struct {
// User EntityUser
// UserDetail EntityUserDetail
// UserScores []EntityUserScores
// }
// var users []*Entity
//
// // User
// all, err := db.Table(tableUser).Where("uid", g.Slice{3, 4}).Order("uid asc").All()
// t.Assert(err, nil)
// err = all.ScanList(&users, "User")
// t.Assert(err, nil)
// t.Assert(len(users), 2)
// t.Assert(users[0].User, &EntityUser{3, "name_3"})
// t.Assert(users[1].User, &EntityUser{4, "name_4"})
// // Detail
// all, err = db.Table(tableUserDetail).Where("uid", gdb.ListItemValues(users, "User", "Uid")).Order("uid asc").All()
// gtest.Assert(err, nil)
// err = all.ScanList(&users, "UserDetail", "User", "uid:Uid")
// t.Assert(err, nil)
// t.Assert(users[0].UserDetail, &EntityUserDetail{3, "address_3"})
// t.Assert(users[1].UserDetail, &EntityUserDetail{4, "address_4"})
// // Scores
// all, err = db.Table(tableUserScores).Where("uid", gdb.ListItemValues(users, "User", "Uid")).Order("id asc").All()
// gtest.Assert(err, nil)
// err = all.ScanList(&users, "UserScores", "User", "uid:Uid")
// t.Assert(err, nil)
// t.Assert(len(users[0].UserScores), 5)
// t.Assert(len(users[1].UserScores), 5)
// t.Assert(users[0].UserScores[0].Uid, 3)
// t.Assert(users[0].UserScores[0].Score, 1)
// t.Assert(users[0].UserScores[4].Score, 5)
// t.Assert(users[1].UserScores[0].Uid, 4)
// t.Assert(users[1].UserScores[0].Score, 1)
// t.Assert(users[1].UserScores[4].Score, 5)
// })
//
// // Model ScanList with pointer elements and pointer attributes.
// gtest.C(t, func(t *gtest.T) {
// var users []*Entity
// // User
// err := db.Table(tableUser).
// Where("uid", g.Slice{3, 4}).
// Order("uid asc").
// ScanList(&users, "User")
// t.Assert(err, nil)
// // Detail
// err = db.Table(tableUserDetail).
// Where("uid", gdb.ListItemValues(users, "User", "Uid")).
// Order("uid asc").
// ScanList(&users, "UserDetail", "User", "uid:Uid")
// gtest.Assert(err, nil)
// // Scores
// err = db.Table(tableUserScores).
// Where("uid", gdb.ListItemValues(users, "User", "Uid")).
// Order("id asc").
// ScanList(&users, "UserScores", "User", "uid:Uid")
// t.Assert(err, nil)
//
// t.Assert(len(users), 2)
// t.Assert(users[0].User, &EntityUser{3, "name_3"})
// t.Assert(users[1].User, &EntityUser{4, "name_4"})
//
// t.Assert(users[0].UserDetail, &EntityUserDetail{3, "address_3"})
// t.Assert(users[1].UserDetail, &EntityUserDetail{4, "address_4"})
//
// t.Assert(len(users[0].UserScores), 5)
// t.Assert(len(users[1].UserScores), 5)
// t.Assert(users[0].UserScores[0].Uid, 3)
// t.Assert(users[0].UserScores[0].Score, 1)
// t.Assert(users[0].UserScores[4].Score, 5)
// t.Assert(users[1].UserScores[0].Uid, 4)
// t.Assert(users[1].UserScores[0].Score, 1)
// t.Assert(users[1].UserScores[4].Score, 5)
// })
//}
import (
"fmt"
"github.com/gogf/gf/frame/g"
"github.com/gogf/gf/test/gtest"
"github.com/gogf/gf/util/gmeta"
"testing"
)
func Test_Table_Relation_With(t *testing.T) {
var (
tableUser = "user_with"
tableUserDetail = "user_detail_with"
tableUserScores = "user_scores_with"
)
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 `table:"user_detail_with"`
Uid int `json:"uid"`
Address string `json:"address"`
}
type UserScores struct {
gmeta.Meta `table:"user_scores_with"`
Id int `json:"id"`
Uid int `json:"uid"`
Score int `json:"score"`
}
type User struct {
gmeta.Meta `table:"user_with"`
Id int `json:"id"`
Name string `json:"name"`
UserDetail *UserDetail `orm:"with:uid=id"`
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)
}
}
gtest.C(t, func(t *gtest.T) {
var user *User
err := db.Model().With(&user).Where("id", 3).Scan(&user)
t.AssertNil(err)
t.Assert(user.Id, 3)
})
}

View File

@ -2013,7 +2013,7 @@ func Test_Model_Option_Map(t *testing.T) {
gtest.C(t, func(t *gtest.T) {
table := createTable()
defer dropTable(table)
r, err := db.Table(table).Option(gdb.OPTION_OMITEMPTY).Data(g.Map{
r, err := db.Table(table).Option(gdb.OptionOmitEmpty).Data(g.Map{
"id": 1,
"passport": 0,
"password": 0,
@ -2033,7 +2033,7 @@ func Test_Model_Option_Map(t *testing.T) {
gtest.C(t, func(t *gtest.T) {
table := createInitTable()
defer dropTable(table)
_, err := db.Table(table).Option(gdb.OPTION_OMITEMPTY).Data(g.Map{
_, err := db.Table(table).Option(gdb.OptionOmitEmpty).Data(g.Map{
"id": 1,
"passport": 0,
"password": 0,
@ -2069,7 +2069,7 @@ func Test_Model_Option_Map(t *testing.T) {
gtest.C(t, func(t *gtest.T) {
table := createTable()
defer dropTable(table)
_, err := db.Table(table).Option(gdb.OPTION_OMITEMPTY).Data(g.Map{
_, err := db.Table(table).Option(gdb.OptionOmitEmpty).Data(g.Map{
"id": 1,
"passport": 0,
"password": 0,
@ -2106,7 +2106,7 @@ func Test_Model_Option_Map(t *testing.T) {
n, _ := r.RowsAffected()
t.Assert(n, 1)
_, err = db.Table(table).Option(gdb.OPTION_OMITEMPTY).Data(g.Map{"nickname": ""}).Where("id", 2).Update()
_, err = db.Table(table).Option(gdb.OptionOmitEmpty).Data(g.Map{"nickname": ""}).Where("id", 2).Update()
t.AssertNE(err, nil)
r, err = db.Table(table).OmitEmpty().Data(g.Map{"nickname": "", "password": "123"}).Where("id", 3).Update()

View File

@ -92,15 +92,17 @@ func DB(name ...string) gdb.DB {
}
// Table is alias of Model.
func Table(tables string, db ...string) *gdb.Model {
return DB(db...).Table(tables)
// The database component is designed not only for
// relational databases but also for NoSQL databases in the future. The name
// "Table" is not proper for that purpose any more.
// Deprecated, use Model instead.
func Table(tables ...string) *gdb.Model {
return DB().Table(tables...)
}
// Model creates and returns a model from specified database or default database configuration.
// The optional parameter <db> specifies the configuration group name of the database,
// which is "default" in default.
func Model(tables string, db ...string) *gdb.Model {
return DB(db...).Model(tables)
// Model creates and returns a model based on configuration of default database group.
func Model(tables ...string) *gdb.Model {
return DB().Model(tables...)
}
// Redis returns an instance of redis client with specified configuration group name.

View File

@ -13,6 +13,11 @@ import (
"reflect"
)
// Type wraps reflect.Type for additional features.
type Type struct {
reflect.Type
}
// Field contains information of a struct field .
type Field struct {
value reflect.Value

View File

@ -0,0 +1,55 @@
// Copyright GoFrame Author(https://goframe.org). All Rights Reserved.
//
// This Source Code Form is subject to the terms of the MIT License.
// If a copy of the MIT was not distributed with this file,
// You can obtain one at https://github.com/gogf/gf.
package structs
import (
"github.com/gogf/gf/errors/gerror"
"reflect"
)
// StructType retrieves and returns the Type of specified struct/*struct.
func StructType(structOrPointer interface{}) (*Type, error) {
var (
reflectValue reflect.Value
reflectKind reflect.Kind
reflectType reflect.Type
)
if rv, ok := structOrPointer.(reflect.Value); ok {
reflectValue = rv
} else {
reflectValue = reflect.ValueOf(structOrPointer)
}
reflectKind = reflectValue.Kind()
for reflectKind == reflect.Ptr {
if !reflectValue.IsValid() || reflectValue.IsNil() {
// If pointer is type of *struct and nil, then automatically create a temporary struct.
reflectValue = reflect.New(reflectValue.Type().Elem()).Elem()
reflectKind = reflectValue.Kind()
} else {
reflectValue = reflectValue.Elem()
reflectKind = reflectValue.Kind()
}
}
for reflectKind == reflect.Ptr {
reflectValue = reflectValue.Elem()
reflectKind = reflectValue.Kind()
}
if reflectKind != reflect.Struct {
return nil, gerror.Newf(
`invalid parameter kind "%s", kind of "struct" is required`,
reflectKind,
)
}
reflectType = reflectValue.Type()
return &Type{
Type: reflectType,
}, nil
}
func (t *Type) Signature() string {
return t.PkgPath() + "/" + t.String()
}

View File

@ -124,3 +124,39 @@ func Test_MapField(t *testing.T) {
t.Assert(ok, true)
})
}
func Test_StructType(t *testing.T) {
gtest.C(t, func(t *gtest.T) {
type B struct {
Name string
}
type A struct {
B
}
r, err := structs.StructType(new(A))
t.AssertNil(err)
t.Assert(r.Signature(), `github.com/gogf/gf/internal/structs_test/structs_test.A`)
})
gtest.C(t, func(t *gtest.T) {
type B struct {
Name string
}
type A struct {
B
}
r, err := structs.StructType(new(A).B)
t.AssertNil(err)
t.Assert(r.Signature(), `github.com/gogf/gf/internal/structs_test/structs_test.B`)
})
// Error.
gtest.C(t, func(t *gtest.T) {
type B struct {
Name string
}
type A struct {
*B
}
_, err := structs.StructType(new(A).B)
t.AssertNE(err, nil)
})
}

View File

@ -472,6 +472,15 @@ func Str(haystack string, needle string) string {
return haystack[idx+len([]byte(needle))-1:]
}
// StrEx returns part of <haystack> string starting from and excluding
// the first occurrence of <needle> to the end of <haystack>.
func StrEx(haystack string, needle string) string {
if s := Str(haystack, needle); s != "" {
return s[1:]
}
return ""
}
// Shuffle randomly shuffles a string.
// It considers parameter <str> as unicode string.
func Shuffle(str string) string {

View File

@ -295,6 +295,14 @@ func Test_Str(t *testing.T) {
})
}
func Test_StrEx(t *testing.T) {
gtest.C(t, func(t *gtest.T) {
t.Assert(gstr.StrEx("name@example.com", "@"), "example.com")
t.Assert(gstr.StrEx("name@example.com", ""), "")
t.Assert(gstr.StrEx("name@example.com", "z"), "")
})
}
func Test_Shuffle(t *testing.T) {
gtest.C(t, func(t *gtest.T) {
t.Assert(len(gstr.Shuffle("123456")), 6)

94
util/gmeta/gmeta.go Normal file
View File

@ -0,0 +1,94 @@
// Copyright GoFrame Author(https://goframe.org). All Rights Reserved.
//
// This Source Code Form is subject to the terms of the MIT License.
// If a copy of the MIT was not distributed with this file,
// You can obtain one at https://github.com/gogf/gf.
// Package gmeta provides embedded meta data feature for struct.
package gmeta
import (
"github.com/gogf/gf/container/gmap"
"github.com/gogf/gf/container/gvar"
"github.com/gogf/gf/internal/structs"
"strconv"
)
// Meta is used as an embedded attribute for struct to enabled meta data feature.
type Meta struct{}
const (
metaAttributeName = "Meta"
)
var (
metaDataCacheMap = gmap.NewStrAnyMap(true)
)
// Data retrieves and returns all meta data from `object`.
// It automatically parses 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 (
key string
tag = field.Tag
data = make(map[string]interface{})
)
for tag != "" {
// Skip leading space.
i := 0
for i < len(tag) && tag[i] == ' ' {
i++
}
tag = tag[i:]
if tag == "" {
break
}
// Scan to colon. A space, a quote or a control character is a syntax error.
// Strictly speaking, control chars include the range [0x7f, 0x9f], not just
// [0x00, 0x1f], but in practice, we ignore the multi-byte control characters
// as it is simpler to inspect the tag's bytes than the tag's runes.
i = 0
for i < len(tag) && tag[i] > ' ' && tag[i] != ':' && tag[i] != '"' && tag[i] != 0x7f {
i++
}
if i == 0 || i+1 >= len(tag) || tag[i] != ':' || tag[i+1] != '"' {
break
}
key = string(tag[:i])
tag = tag[i+1:]
// Scan quoted string to find value.
i = 1
for i < len(tag) && tag[i] != '"' {
if tag[i] == '\\' {
i++
}
i++
}
if i >= len(tag) {
break
}
quotedValue := string(tag[:i+1])
tag = tag[i+1:]
value, err := strconv.Unquote(quotedValue)
if err != nil {
panic(err)
}
data[key] = value
}
return data
}
return map[string]interface{}{}
}).(map[string]interface{})
}
// Get retrieves and returns specified meta data by `key` from `object`.
func Get(object interface{}, key string) *gvar.Var {
return gvar.New(Data(object)[key])
}

78
util/gmeta/gmeta_test.go Normal file
View File

@ -0,0 +1,78 @@
// Copyright GoFrame Author(https://goframe.org). All Rights Reserved.
//
// This Source Code Form is subject to the terms of the MIT License.
// If a copy of the MIT was not distributed with this file,
// You can obtain one at https://github.com/gogf/gf.
package gmeta_test
import (
"github.com/gogf/gf/internal/json"
"github.com/gogf/gf/util/gmeta"
"testing"
"github.com/gogf/gf/test/gtest"
)
type A struct {
gmeta.Meta `tag:"123" orm:"456"`
Id int
Name string
}
var (
a1 A
a2 *A
)
func Benchmark_Data_Struct(b *testing.B) {
for i := 0; i < b.N; i++ {
gmeta.Data(a1)
}
}
func Benchmark_Data_Pointer1(b *testing.B) {
for i := 0; i < b.N; i++ {
gmeta.Data(a2)
}
}
func Benchmark_Data_Pointer2(b *testing.B) {
for i := 0; i < b.N; i++ {
gmeta.Data(&a2)
}
}
func Benchmark_Data_Get_Struct(b *testing.B) {
for i := 0; i < b.N; i++ {
gmeta.Get(a1, "tag")
}
}
func Benchmark_Data_Get_Pointer1(b *testing.B) {
for i := 0; i < b.N; i++ {
gmeta.Get(a2, "tag")
}
}
func Benchmark_Data_Get_Pointer2(b *testing.B) {
for i := 0; i < b.N; i++ {
gmeta.Get(&a2, "tag")
}
}
func TestMeta_Basic(t *testing.T) {
gtest.C(t, func(t *gtest.T) {
a := &A{
Id: 100,
Name: "john",
}
t.Assert(len(gmeta.Data(a)), 2)
t.Assert(gmeta.Get(a, "tag").String(), "123")
t.Assert(gmeta.Get(a, "orm").String(), "456")
b, err := json.Marshal(a)
t.AssertNil(err)
t.Assert(b, `{"Id":100,"Name":"john"}`)
})
}