diff --git a/.example/frame/mvc/app/model/defaults/user.go b/.example/frame/mvc/app/model/defaults/user.go deleted file mode 100644 index 25cac1542..000000000 --- a/.example/frame/mvc/app/model/defaults/user.go +++ /dev/null @@ -1,60 +0,0 @@ -// This is auto-generated by gf cli tool. You may not really want to edit it. - -package defaults - -import ( - "database/sql" - "github.com/gogf/gf/database/gdb" - "github.com/gogf/gf/frame/g" -) - -import ( - "github.com/gogf/gf/os/gtime" -) - -// User is the golang structure for table user. -type User struct { - Id int `orm:"id,primary" json:"id"` - Passport string `orm:"passport" json:"passport"` - Password string `orm:"password" json:"password"` - Nickname string `orm:"nickname,unique" json:"nickname"` - CreateTime *gtime.Time `orm:"create_time" json:"create_time"` -} - -var ( - // TableUser is the table name of user. - TableUser = "user" - // ModelUser is the model object of user. - ModelUser = g.DB("default").Table(TableUser).Safe() -) - -// Inserts does "INSERT...INTO..." statement for inserting current object into table. -func (r *User) Insert() (result sql.Result, err error) { - return ModelUser.Data(r).Insert() -} - -// Replace does "REPLACE...INTO..." statement for inserting current object into table. -// If there's already another same record in the table (it checks using primary key or unique index), -// it deletes it and insert this one. -func (r *User) Replace() (result sql.Result, err error) { - return ModelUser.Data(r).Replace() -} - -// Save does "INSERT...INTO..." statement for inserting/updating current object into table. -// It updates the record if there's already another same record in the table -// (it checks using primary key or unique index). -func (r *User) Save() (result sql.Result, err error) { - return ModelUser.Data(r).Save() -} - -// Update does "UPDATE...WHERE..." statement for updating current object from table. -// It updates the record if there's already another same record in the table -// (it checks using primary key or unique index). -func (r *User) Update() (result sql.Result, err error) { - return ModelUser.Data(r).Where(gdb.GetWhereConditionOfStruct(r)).Update() -} - -// Delete does "DELETE FROM...WHERE..." statement for deleting current object from table. -func (r *User) Delete() (result sql.Result, err error) { - return ModelUser.Where(gdb.GetWhereConditionOfStruct(r)).Delete() -} diff --git a/.example/frame/mvc/config.toml b/.example/frame/mvc/config.toml index 0713b840b..eb8a109e3 100644 --- a/.example/frame/mvc/config.toml +++ b/.example/frame/mvc/config.toml @@ -3,7 +3,7 @@ viewpath = "/home/www/templates" # MySQL数据库配置 [database] debug = true - link = "mysql:root:12345678@tcp(127.0.0.1:3306)/test" + link = "mysql:root:12345678@tcp(127.0.0.1:3306)/gf" [redis] diff --git a/database/gdb/gdb.go b/database/gdb/gdb.go index 9136a94c9..2f73303af 100644 --- a/database/gdb/gdb.go +++ b/database/gdb/gdb.go @@ -141,13 +141,14 @@ type Sql struct { // 表字段结构信息 type TableField struct { - Index int // 用于字段排序(map类型是无序的) + Index int // 用于字段排序(因为map类型是无序的) Name string // 字段名称 Type string // 字段类型 Null bool // 是否可为null Key string // 索引信息 Default interface{} // 默认值 Extra string // 其他信息 + Comment string // 字段描述 } // 返回数据表记录值 diff --git a/database/gdb/gdb_func.go b/database/gdb/gdb_func.go index 4cfd22533..79ee47cfe 100644 --- a/database/gdb/gdb_func.go +++ b/database/gdb/gdb_func.go @@ -24,17 +24,17 @@ import ( "github.com/gogf/gf/util/gconv" ) -// Type assert api for String. +// apiString is the type assert api for String. type apiString interface { String() string } -// Type assert api for Iterator. +// apiIterator is the type assert api for Iterator. type apiIterator interface { Iterator(f func(key, value interface{}) bool) } -// Type assert api for Interfaces. +// apiInterfacesis the type assert api for Interfaces. type apiInterfaces interface { Interfaces() []interface{} } @@ -48,15 +48,15 @@ const ( // 获得struct对象对应的where查询条件 func GetWhereConditionOfStruct(pointer interface{}) (where string, args []interface{}) { array := ([]string)(nil) - for tag, field := range structs.TagMapField(pointer, []string{ORM_TAG_FOR_STRUCT}, true) { - array = strings.Split(tag, ",") + for _, field := range structs.TagFields(pointer, []string{ORM_TAG_FOR_STRUCT}, true) { + array = strings.Split(field.Tag, ",") if len(array) > 1 && gstr.InArray([]string{ORM_TAG_FOR_UNIQUE, ORM_TAG_FOR_PRIMARY}, array[1]) { return array[0], []interface{}{field.Value()} } if len(where) > 0 { where += " " } - where += tag + "=?" + where += field.Tag + "=?" args = append(args, field.Value()) } return diff --git a/database/gdb/gdb_model.go b/database/gdb/gdb_model.go index fca9a7b09..982dd5c7f 100644 --- a/database/gdb/gdb_model.go +++ b/database/gdb/gdb_model.go @@ -22,30 +22,30 @@ import ( "github.com/gogf/gf/util/gconv" ) -// 数据库链式操作模型对象 +// Model is the DAO for ORM. type Model struct { - db DB // 数据库操作对象 - tx *TX // 数据库事务对象 - linkType int // 连接对象类型(用于主从集群时开发者自定义操作对象) - tablesInit string // 初始化Model时的表名称(可以是多个) - tables string // 数据库操作表 - fields string // 操作字段 - fieldsEx string // 操作字段(排除) - whereArgs []interface{} // 操作条件参数 - whereHolder []*whereHolder // 操作条件预处理 - groupBy string // 分组语句 - orderBy string // 排序语句 - start int // 分页开始 - limit int // 分页条数 - option int // 操作选项 - offset int // 查询偏移量(OFFSET语法) - data interface{} // 操作数据(注意仅支持Map/List/string类型) - batch int // 批量操作条数 - filter bool // 是否按照表字段过滤data参数 - cacheEnabled bool // 当前SQL操作是否开启查询缓存功能 - cacheExpire time.Duration // 查询缓存时间 - cacheName string // 查询缓存名称 - safe bool // 当前模型是否安全模式(默认非安全表示链式操作直接修改当前模型属性;否则每一次链式操作都是返回新的模型对象) + db DB // Underlying DB interface. + tx *TX // Underlying TX interface. + linkType int // Mark for operation on master or slave. + tablesInit string // Table names when model initialization. + 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 ','. + whereArgs []interface{} // Arguments for where operation. + whereHolder []*whereHolder // Condition strings for where operation. + groupBy string // Used for "group by" statement. + orderBy string // Used for "order by" statement. + start int // Used for "select ... start, limit ..." statement. + limit int // Used for "select ... start, limit ..." statement. + option int // Option for extra operation features. + offset int // Offset statement for some databases grammar. + data interface{} // Data for operation, which can be type of map/[]map/struct/*struct/string, etc. + batch int // Batch number for batch Insert/Replace/Save operations. + filter bool // Filter data and where key-value pairs according to the fields of the table. + cacheEnabled bool // Enable sql result cache feature. + cacheDuration time.Duration // Cache TTL duration. + cacheName string // Cache name for custom operation. + safe bool // If true, it clones and returns a new model object whenever operation done; or else it changes the attribute of current model. } // whereHolder is the holder for where condition preparing. @@ -65,7 +65,9 @@ const ( OPTION_ALLOWEMPTY ) -// 链式操作,数据表字段,可支持多个表,以半角逗号连接 +// Table creates and returns a new ORM model. +// The parameter can be more than one table names, like : +// "user", "user u", "user, user_detail", "user u, user_detail ud" func (bs *dbBase) Table(tables string) *Model { return &Model{ db: bs.db, @@ -79,12 +81,14 @@ func (bs *dbBase) Table(tables string) *Model { } } -// 链式操作,数据表字段,可支持多个表,以半角逗号连接 +// From is alias of dbBase.Table. +// See dbBase.Table. func (bs *dbBase) From(tables string) *Model { return bs.db.Table(tables) } -// (事务)链式操作,数据表字段,可支持多个表,以半角逗号连接 +// Table acts like dbBase.Table except it operates on transaction. +// See dbBase.Table. func (tx *TX) Table(tables string) *Model { return &Model{ db: tx.db, @@ -99,105 +103,111 @@ func (tx *TX) Table(tables string) *Model { } } -// (事务)链式操作,数据表字段,可支持多个表,以半角逗号连接 +// From is alias of tx.Table. +// See tx.Table. func (tx *TX) From(tables string) *Model { return tx.Table(tables) } -// 克隆一个当前对象 -func (md *Model) Clone() *Model { +// TX sets the transaction for current operation. +func (m *Model) TX(tx *TX) *Model { + model := m.getModel() + model.tx = tx + return model +} + +// Clone creates and returns a new model which is a clone of current model. +// Note that it uses deep-copy for the clone. +func (m *Model) Clone() *Model { newModel := (*Model)(nil) - if md.tx != nil { - newModel = md.tx.Table(md.tablesInit) + if m.tx != nil { + newModel = m.tx.Table(m.tablesInit) } else { - newModel = md.db.Table(md.tablesInit) + newModel = m.db.Table(m.tablesInit) } - *newModel = *md + *newModel = *m // Deep copy slice attributes. - if n := len(md.whereArgs); n > 0 { + if n := len(m.whereArgs); n > 0 { newModel.whereArgs = make([]interface{}, n) - copy(newModel.whereArgs, md.whereArgs) + copy(newModel.whereArgs, m.whereArgs) } - if n := len(md.whereHolder); n > 0 { + if n := len(m.whereHolder); n > 0 { newModel.whereHolder = make([]*whereHolder, n) - copy(newModel.whereHolder, md.whereHolder) + copy(newModel.whereHolder, m.whereHolder) } return newModel } -// 设置本次链式操作在主节点上 -func (md *Model) Master() *Model { - model := md.getModel() +// Master marks the following operation on master node. +func (m *Model) Master() *Model { + model := m.getModel() model.linkType = gLINK_TYPE_MASTER return model } -// 设置本次链式操作在从节点上 -func (md *Model) Slave() *Model { - model := md.getModel() +// Slave marks the following operation on slave node. +// Note that it makes sense only if there's any slave node configured. +func (m *Model) Slave() *Model { + model := m.getModel() model.linkType = gLINK_TYPE_SLAVE return model } -// 标识当前对象运行安全模式(可被修改)。 -// 1. 默认情况下,模型对象的对象属性无法被修改, -// 每一次链式操作都是克隆一个新的模型对象,这样所有的操作都不会污染模型对象。 -// 但是链式操作如果需要分开执行,那么需要将新的克隆对象赋值给旧的模型对象继续操作。 -// 2. 当标识模型对象为可修改,那么在当前模型对象的所有链式操作均会影响下一次的链式操作, -// 即使是链式操作分开执行。 -// 3. 大部分ORM框架默认模型对象是可修改的,但是GF框架的ORM提供给开发者更灵活,更安全的链式操作选项。 -func (md *Model) Safe(safe ...bool) *Model { +// Safe marks this model safe or unsafe. If safe is true, it clones and returns a new model object +// whenever the operation done, or else it changes the attribute of current model. +func (m *Model) Safe(safe ...bool) *Model { if len(safe) > 0 { - md.safe = safe[0] + m.safe = safe[0] } else { - md.safe = true + m.safe = true } - return md + return m } -// 返回操作的模型对象,可能是当前对象,也可能是新的克隆对象,根据alterable决定。 -func (md *Model) getModel() *Model { - if !md.safe { - return md +// getModel creates and returns a cloned model of current model if is true, or else it returns +// the current model. +func (m *Model) getModel() *Model { + if !m.safe { + return m } else { - return md.Clone() + return m.Clone() } } -// 链式操作,左联表 -func (md *Model) LeftJoin(joinTable string, on string) *Model { - model := md.getModel() +// LeftJoin does "LEFT JOIN ... ON ..." statement on the model. +func (m *Model) LeftJoin(joinTable string, on string) *Model { + model := m.getModel() model.tables += fmt.Sprintf(" LEFT JOIN %s ON (%s)", joinTable, on) return model } -// 链式操作,右联表 -func (md *Model) RightJoin(joinTable string, on string) *Model { - model := md.getModel() +// RightJoin does "RIGHT JOIN ... ON ..." statement on the model. +func (m *Model) RightJoin(joinTable string, on string) *Model { + model := m.getModel() model.tables += fmt.Sprintf(" RIGHT JOIN %s ON (%s)", joinTable, on) return model } -// 链式操作,内联表 -func (md *Model) InnerJoin(joinTable string, on string) *Model { - model := md.getModel() +// InnerJoin does "INNER JOIN ... ON ..." statement on the model. +func (m *Model) InnerJoin(joinTable string, on string) *Model { + model := m.getModel() model.tables += fmt.Sprintf(" INNER JOIN %s ON (%s)", joinTable, on) return model } -// 链式操作,查询字段 -func (md *Model) Fields(fields string) *Model { - model := md.getModel() +// Fields sets the operation fields of the model, multiple fields joined using char ','. +func (m *Model) Fields(fields string) *Model { + model := m.getModel() model.fields = fields return model } -// 链式操作,查询字段(排除) -func (md *Model) FieldsEx(fields string) *Model { - model := md.getModel() +// FieldsEx sets the excluded operation fields of the model, multiple fields joined using char ','. +func (m *Model) FieldsEx(fields string) *Model { + model := m.getModel() model.fieldsEx = fields fieldsExSet := gset.NewStrSetFrom(gstr.SplitAndTrim(fields, ",")) - if m, err := md.db.TableFields(md.tables); err == nil { + if m, err := m.db.TableFields(m.tables); err == nil { model.fields = "" for k, _ := range m { if fieldsExSet.Contains(k) { @@ -212,29 +222,46 @@ func (md *Model) FieldsEx(fields string) *Model { return model } -// 链式操作,选项设置 -func (md *Model) Option(option int) *Model { - model := md.getModel() +// Option sets the extra operation option for the model. +func (m *Model) Option(option int) *Model { + model := m.getModel() model.option = option return model } -// 链式操作,设置 OPTION_OMITEMPTY 常用选项 -func (md *Model) OptionOmitEmpty() *Model { - return md.Option(OPTION_OMITEMPTY) +// OptionOmitEmpty sets OPTION_OMITEMPTY 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) } -// 链式操作,过滤字段 -func (md *Model) Filter() *Model { - model := md.getModel() +// OmitEmpty sets OPTION_OMITEMPTY 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) +} + +// Filter marks filtering the fields which does not exist in the fields of the operated table. +func (m *Model) Filter() *Model { + model := m.getModel() model.filter = true return model } -// 链式操作,condition,支持string/map/gmap/struct/*struct. -// 注意,多个Where调用时,会自动转换为And条件调用。 -func (md *Model) Where(where interface{}, args ...interface{}) *Model { - model := md.getModel() +// Where sets the condition statement for the model. The parameter can be type of +// string/map/gmap/slice/struct/*struct, etc. Note that, if it's called more than one times, +// multiple conditions will be joined into where statement using "AND". +// Eg: +// Where("uid=10000") +// Where("uid", 10000) +// Where("money>? AND name like ?", 99999, "vip_%") +// Where("uid", 1).Where("name", "john") +// Where("status IN (?)", g.Slice{1,2,3}) +// Where("age IN(?,?)", 18, 50) +// Where(User{ Id : 1, UserName : "john"}) +func (m *Model) Where(where interface{}, args ...interface{}) *Model { + model := m.getModel() if model.whereHolder == nil { model.whereHolder = make([]*whereHolder, 0) } @@ -246,9 +273,9 @@ func (md *Model) Where(where interface{}, args ...interface{}) *Model { return model } -// 链式操作,添加AND条件到Where中 -func (md *Model) And(where interface{}, args ...interface{}) *Model { - model := md.getModel() +// And adds "AND" condition to the where statement. +func (m *Model) And(where interface{}, args ...interface{}) *Model { + model := m.getModel() if model.whereHolder == nil { model.whereHolder = make([]*whereHolder, 0) } @@ -260,9 +287,9 @@ func (md *Model) And(where interface{}, args ...interface{}) *Model { return model } -// 链式操作,添加OR条件到Where中 -func (md *Model) Or(where interface{}, args ...interface{}) *Model { - model := md.getModel() +// Or adds "OR" condition to the where statement. +func (m *Model) Or(where interface{}, args ...interface{}) *Model { + model := m.getModel() if model.whereHolder == nil { model.whereHolder = make([]*whereHolder, 0) } @@ -274,29 +301,28 @@ func (md *Model) Or(where interface{}, args ...interface{}) *Model { return model } -// 链式操作,group by -func (md *Model) GroupBy(groupBy string) *Model { - model := md.getModel() +// GroupBy sets the "GROUP BY" statement for the model. +func (m *Model) GroupBy(groupBy string) *Model { + model := m.getModel() model.groupBy = groupBy return model } -// 链式操作,order by -func (md *Model) OrderBy(orderBy string) *Model { - model := md.getModel() +// OrderBy sets the "ORDER BY" statement for the model. +func (m *Model) OrderBy(orderBy string) *Model { + model := m.getModel() array := strings.Split(orderBy, " ") - array[0] = md.db.quoteWord(array[0]) + array[0] = m.db.quoteWord(array[0]) model.orderBy = strings.Join(array, " ") return model } -// 链式操作,limit。 -// -// 如果给定一个参数,那么生成的SQL为:LIMIT limit[0] -// -// 如果给定两个参数,那么生成的SQL为:LIMIT limit[0], limit[1] -func (md *Model) Limit(limit ...int) *Model { - model := md.getModel() +// Limit sets the "LIMIT" statement for the model. +// The parameter can be either one or two number, if passed two number is passed, +// it then sets "LIMIT limit[0],limit[1]" statement for the model, or else it sets "LIMIT limit[0]" +// statement. +func (m *Model) Limit(limit ...int) *Model { + model := m.getModel() switch len(limit) { case 1: model.limit = limit[0] @@ -307,51 +333,65 @@ func (md *Model) Limit(limit ...int) *Model { return model } -// 链式操作,OFFSET语法(部分数据库支持)。 -// 注意:可以使用Limit方法调用替换该方法特性,底层不同数据库将会自动替换LIMIT语法为OFFSET语法。 -func (md *Model) Offset(offset int) *Model { - model := md.getModel() +// Offset sets the "OFFSET" statement for the model. +// It only makes sense for some databases like SQLServer, PostgreSQL, etc. +func (m *Model) Offset(offset int) *Model { + model := m.getModel() model.offset = offset return model } -// 链式操作,翻页,注意分页页码从1开始,而Limit方法从0开始。 -func (md *Model) ForPage(page, limit int) *Model { - model := md.getModel() +// ForPage sets the paging number for the model. +// The parameter is started from 1 for paging. +// Note that, it differs that the Limit function start from 0 for "LIMIT" statement. +func (m *Model) ForPage(page, limit int) *Model { + model := m.getModel() model.start = (page - 1) * limit model.limit = limit return model } -// 设置批处理的大小 -func (md *Model) Batch(batch int) *Model { - model := md.getModel() +// Batch sets the batch operation number for the model. +func (m *Model) Batch(batch int) *Model { + model := m.getModel() model.batch = batch return model } -// 查询缓存/清除缓存操作,需要注意的是,事务查询不支持缓存。 -// 1. 当 expire < 0时表示清除缓存,expire=0 时表示不过期, expire > 0时表示过期时间。 -// 2. expire参数类型为interface{},这是一个兼容旧版本的方式,该参数支持 int/time.Duration 类型,当传递类型为int时,表示缓存多少秒。 -// 3. name表示自定义的缓存名称(注意不要出现重复),便于业务层精准定位缓存项(如果业务层需要手动清理时,必须指定缓存名称), -// 例如:查询缓存时设置名称,在特定的业务逻辑中清理缓存时可以给定缓存名称进行精准清理。 -func (md *Model) Cache(expire time.Duration, name ...string) *Model { - model := md.getModel() - model.cacheExpire = expire +// Cache sets the cache feature for the model. It caches the result of the sql, which means +// if there's another same sql request, it just reads and returns the result from cache, it +// but not committed and executed into the database. +// +// If the parameter < 0, which means it clear the cache with given . +// If the parameter = 0, which means it never expires. +// If the parameter > 0, which means it expires after . +// +// The optional parameter is used to bind a name to the cache, which means you can later +// control the cache like changing the or clearing the cache with specified . +// +// Note that, the cache feature is disabled if the model is operating on a transaction. +func (m *Model) Cache(duration time.Duration, name ...string) *Model { + model := m.getModel() + model.cacheDuration = duration if len(name) > 0 { model.cacheName = name[0] } - // 查询缓存特性不支持事务操作 + // It does not support cache on transaction. if model.tx == nil { model.cacheEnabled = true } return model } -// 链式操作,操作数据项,参数data类型支持 string/map/slice/struct/*struct , -// 也可以是:key,value,key,value,...。 -func (md *Model) Data(data ...interface{}) *Model { - model := md.getModel() +// Data sets the operation data for the model. +// The parameter can be type of string/map/gmap/slice/struct/*struct, etc. +// Eg: +// Data("uid=10000") +// Data("uid", 10000) +// Data(g.Map{"uid": 10000, "name":"john"}) +// Data(g.Slice{g.Map{"uid": 10000, "name":"john"}, g.Map{"uid": 20000, "name":"smith"}) +func (m *Model) Data(data ...interface{}) *Model { + model := m.getModel() if len(data) > 1 { m := make(map[string]interface{}) for i := 0; i < len(data); i += 2 { @@ -394,201 +434,205 @@ func (md *Model) Data(data ...interface{}) *Model { // filterDataForInsertOrUpdate does filter feature with data for inserting/updating operations. // Note that, it does not filter list item, which is also type of map, for "omit empty" feature. -func (md *Model) filterDataForInsertOrUpdate(data interface{}) interface{} { - if list, ok := md.data.(List); ok { - for k, m := range list { - list[k] = md.doFilterDataMapForInsertOrUpdate(m, false) +func (m *Model) filterDataForInsertOrUpdate(data interface{}) interface{} { + if list, ok := m.data.(List); ok { + for k, item := range list { + list[k] = m.doFilterDataMapForInsertOrUpdate(item, false) } return list - } else if m, ok := md.data.(Map); ok { - return md.doFilterDataMapForInsertOrUpdate(m, true) + } else if item, ok := m.data.(Map); ok { + return m.doFilterDataMapForInsertOrUpdate(item, true) } return data } // doFilterDataMapForInsertOrUpdate does the filter features for map. // Note that, it does not filter list item, which is also type of map, for "omit empty" feature. -func (md *Model) doFilterDataMapForInsertOrUpdate(data Map, allowOmitEmpty bool) Map { - if md.filter { - data = md.db.filterFields(md.tables, data) +func (m *Model) doFilterDataMapForInsertOrUpdate(data Map, allowOmitEmpty bool) Map { + if m.filter { + data = m.db.filterFields(m.tables, data) } // Remove key-value pairs of which the value is empty. - if allowOmitEmpty && md.option&OPTION_OMITEMPTY > 0 { + if allowOmitEmpty && m.option&OPTION_OMITEMPTY > 0 { m := gmap.NewStrAnyMapFrom(data) m.FilterEmpty() data = m.Map() } - if len(md.fields) > 0 && md.fields != "*" { + if len(m.fields) > 0 && m.fields != "*" { // Keep specified fields. - set := gset.NewStrSetFrom(gstr.SplitAndTrim(md.fields, ",")) + set := gset.NewStrSetFrom(gstr.SplitAndTrim(m.fields, ",")) for k := range data { if !set.Contains(k) { delete(data, k) } } - } else if len(md.fieldsEx) > 0 { + } else if len(m.fieldsEx) > 0 { // Filter specified fields. - for _, v := range gstr.SplitAndTrim(md.fieldsEx, ",") { + for _, v := range gstr.SplitAndTrim(m.fieldsEx, ",") { delete(data, v) } } return data } -// 链式操作, CURD - Insert/BatchInsert。 -// 根据Data方法传递的参数类型决定该操作是单条操作还是批量操作, -// 如果Data方法传递的是slice类型,那么为批量操作。 -func (md *Model) Insert() (result sql.Result, err error) { +// Insert does "INSERT INTO ..." statement for the model. +func (m *Model) Insert() (result sql.Result, err error) { defer func() { if err == nil { - md.checkAndRemoveCache() + m.checkAndRemoveCache() } }() - if md.data == nil { + if m.data == nil { return nil, errors.New("inserting into table with empty data") } - // 批量操作 - if list, ok := md.data.(List); ok { + + if list, ok := m.data.(List); ok { + // Batch insert. batch := 10 - if md.batch > 0 { - batch = md.batch + if m.batch > 0 { + batch = m.batch } - return md.db.doBatchInsert( - md.getLink(), - md.tables, - md.filterDataForInsertOrUpdate(list), + return m.db.doBatchInsert( + m.getLink(), + m.tables, + m.filterDataForInsertOrUpdate(list), gINSERT_OPTION_DEFAULT, batch, ) - } else if data, ok := md.data.(Map); ok { - return md.db.doInsert( - md.getLink(), - md.tables, - md.filterDataForInsertOrUpdate(data), + } else if data, ok := m.data.(Map); ok { + // Single insert. + return m.db.doInsert( + m.getLink(), + m.tables, + m.filterDataForInsertOrUpdate(data), gINSERT_OPTION_DEFAULT, ) } return nil, errors.New("inserting into table with invalid data type") } -// 链式操作, CURD - Replace/BatchReplace。 -// 根据Data方法传递的参数类型决定该操作是单条操作还是批量操作, -// 如果Data方法传递的是slice类型,那么为批量操作。 -func (md *Model) Replace() (result sql.Result, err error) { +// Replace does "REPLACE INTO ..." statement for the model. +func (m *Model) Replace() (result sql.Result, err error) { defer func() { if err == nil { - md.checkAndRemoveCache() + m.checkAndRemoveCache() } }() - if md.data == nil { + if m.data == nil { return nil, errors.New("replacing into table with empty data") } - // 批量操作 - if list, ok := md.data.(List); ok { + if list, ok := m.data.(List); ok { + // Batch replace. batch := 10 - if md.batch > 0 { - batch = md.batch + if m.batch > 0 { + batch = m.batch } - return md.db.doBatchInsert( - md.getLink(), - md.tables, - md.filterDataForInsertOrUpdate(list), + return m.db.doBatchInsert( + m.getLink(), + m.tables, + m.filterDataForInsertOrUpdate(list), gINSERT_OPTION_REPLACE, batch, ) - } else if data, ok := md.data.(Map); ok { - return md.db.doInsert( - md.getLink(), - md.tables, - md.filterDataForInsertOrUpdate(data), + } else if data, ok := m.data.(Map); ok { + // Single insert. + return m.db.doInsert( + m.getLink(), + m.tables, + m.filterDataForInsertOrUpdate(data), gINSERT_OPTION_REPLACE, ) } return nil, errors.New("replacing into table with invalid data type") } -// 链式操作, CURD - Save/BatchSave。 -// 根据Data方法传递的参数类型决定该操作是单条操作还是批量操作, -// 如果Data方法传递的是slice类型,那么为批量操作。 -func (md *Model) Save() (result sql.Result, err error) { +// Save does "INSERT INTO ... ON DUPLICATE KEY UPDATE..." statement for the model. +// It updates the record if there's primary or unique index in the saving data, +// or else it inserts a new record into the table. +func (m *Model) Save() (result sql.Result, err error) { defer func() { if err == nil { - md.checkAndRemoveCache() + m.checkAndRemoveCache() } }() - if md.data == nil { - return nil, errors.New("replacing into table with empty data") + if m.data == nil { + return nil, errors.New("saving into table with empty data") } - // 批量操作 - if list, ok := md.data.(List); ok { + if list, ok := m.data.(List); ok { + // Batch save. batch := gDEFAULT_BATCH_NUM - if md.batch > 0 { - batch = md.batch + if m.batch > 0 { + batch = m.batch } - return md.db.doBatchInsert( - md.getLink(), - md.tables, - md.filterDataForInsertOrUpdate(list), + return m.db.doBatchInsert( + m.getLink(), + m.tables, + m.filterDataForInsertOrUpdate(list), gINSERT_OPTION_SAVE, batch, ) - } else if data, ok := md.data.(Map); ok { - return md.db.doInsert( - md.getLink(), - md.tables, - md.filterDataForInsertOrUpdate(data), + } else if data, ok := m.data.(Map); ok { + // Single save. + return m.db.doInsert( + m.getLink(), + m.tables, + m.filterDataForInsertOrUpdate(data), gINSERT_OPTION_SAVE, ) } return nil, errors.New("saving into table with invalid data type") } -// 链式操作, CURD - Update -func (md *Model) Update() (result sql.Result, err error) { +// Update does "UPDATE ... " statement for the model. +func (m *Model) Update() (result sql.Result, err error) { defer func() { if err == nil { - md.checkAndRemoveCache() + m.checkAndRemoveCache() } }() - if md.data == nil { + if m.data == nil { return nil, errors.New("updating table with empty data") } - condition, conditionArgs := md.formatCondition() - return md.db.doUpdate( - md.getLink(), - md.tables, - md.filterDataForInsertOrUpdate(md.data), + condition, conditionArgs := m.formatCondition() + return m.db.doUpdate( + m.getLink(), + m.tables, + m.filterDataForInsertOrUpdate(m.data), condition, conditionArgs..., ) } -// 链式操作, CURD - Delete -func (md *Model) Delete() (result sql.Result, err error) { +// Delete does "DELETE FROM ... " statement for the model. +func (m *Model) Delete() (result sql.Result, err error) { defer func() { if err == nil { - md.checkAndRemoveCache() + m.checkAndRemoveCache() } }() - condition, conditionArgs := md.formatCondition() - return md.db.doDelete(md.getLink(), md.tables, condition, conditionArgs...) + condition, conditionArgs := m.formatCondition() + return m.db.doDelete(m.getLink(), m.tables, condition, conditionArgs...) } -// 链式操作,select -func (md *Model) Select() (Result, error) { - return md.All() +// Select is alias of All. +// See All. +func (m *Model) Select() (Result, error) { + return m.All() } -// 链式操作,查询所有记录 -func (md *Model) All() (Result, error) { - condition, conditionArgs := md.formatCondition() - return md.getAll(fmt.Sprintf("SELECT %s FROM %s%s", md.fields, md.tables, condition), conditionArgs...) +// All does "SELECT FROM ..." statement for the model. +// It retrieves the records from table and returns the result as slice type. +// It returns nil if there's no record retrieved with the given conditions from table. +func (m *Model) All() (Result, error) { + condition, conditionArgs := m.formatCondition() + return m.getAll(fmt.Sprintf("SELECT %s FROM %s%s", m.fields, m.tables, condition), conditionArgs...) } -// 链式操作,查询单条记录 -func (md *Model) One() (Record, error) { - list, err := md.All() +// One retrieves one record from table and returns the result as map type. +// It returns nil if there's no record retrieved with the given conditions from table. +func (m *Model) One() (Record, error) { + list, err := m.All() if err != nil { return nil, err } @@ -598,9 +642,10 @@ func (md *Model) One() (Record, error) { return nil, nil } -// 链式操作,查询字段值 -func (md *Model) Value() (Value, error) { - one, err := md.One() +// Value retrieves a specified record value from table and returns the result as interface type. +// It returns nil if there's no record found with the given conditions from table. +func (m *Model) Value() (Value, error) { + one, err := m.One() if err != nil { return nil, err } @@ -610,9 +655,21 @@ func (md *Model) Value() (Value, error) { return nil, nil } -// 链式操作,查询单条记录,并自动转换为struct对象, 参数必须为对象的指针,不能为空指针。 -func (md *Model) Struct(pointer interface{}) error { - one, err := md.One() +// Struct retrieves one record from table and converts it into given struct. +// The parameter should be type of *struct/**struct. If type **struct is given, +// it can create the struct internally during converting. +// +// Note that it returns sql.ErrNoRows if there's no record retrieved with the given conditions +// from table. +// +// Eg: +// user := new(User) +// err := db.Table("user").Where("id", 1).Struct(user) +// +// user := (*User)(nil) +// err := db.Table("user").Where("id", 1).Struct(&user) +func (m *Model) Struct(pointer interface{}) error { + one, err := m.One() if err != nil { return err } @@ -622,9 +679,21 @@ func (md *Model) Struct(pointer interface{}) error { return one.Struct(pointer) } -// 链式操作,查询多条记录,并自动转换为指定的slice对象, 如: []struct/[]*struct。 -func (md *Model) Structs(pointer interface{}) error { - all, err := md.All() +// Structs retrieves records from table and converts them into given struct slice. +// The parameter should be type of *[]struct/*[]*struct. It can create and fill the struct +// slice internally during converting. +// +// Note that it returns sql.ErrNoRows if there's no record retrieved with the given conditions +// from table. +// +// Eg: +// users := ([]User)(nil) +// err := db.Table("user").Structs(&users) +// +// users := ([]*User)(nil) +// err := db.Table("user").Structs(&users) +func (m *Model) Structs(pointer interface{}) error { + all, err := m.All() if err != nil { return err } @@ -634,10 +703,26 @@ func (md *Model) Structs(pointer interface{}) error { return all.Structs(pointer) } -// 链式操作,将结果转换为指定的struct/*struct/[]struct/[]*struct, -// 参数应该为指针类型,否则返回失败。 -// 该方法自动识别参数类型,调用Struct/Structs方法。 -func (md *Model) Scan(pointer interface{}) error { +// Scan automatically calls Struct or Structs function according to the type of parameter . +// It calls function Struct if is type of *struct/**struct. +// It calls function Structs if is type of *[]struct/*[]*struct. +// +// Note that it returns sql.ErrNoRows if there's no record retrieved with the given conditions +// from table. +// +// Eg: +// user := new(User) +// err := db.Table("user").Where("id", 1).Struct(user) +// +// user := (*User)(nil) +// err := db.Table("user").Where("id", 1).Struct(&user) +// +// users := ([]User)(nil) +// err := db.Table("user").Structs(&users) +// +// users := ([]*User)(nil) +// err := db.Table("user").Structs(&users) +func (m *Model) Scan(pointer interface{}) error { t := reflect.TypeOf(pointer) k := t.Kind() if k != reflect.Ptr { @@ -646,30 +731,29 @@ func (md *Model) Scan(pointer interface{}) error { switch t.Elem().Kind() { case reflect.Array: case reflect.Slice: - return md.Structs(pointer) + return m.Structs(pointer) default: - return md.Struct(pointer) + return m.Struct(pointer) } return nil } -// 链式操作,查询数量,fields可以为空,也可以自定义查询字段, -// 当给定自定义查询字段时,该字段必须为数量结果,否则会引起歧义,使用如:md.Fields("COUNT(id)") -func (md *Model) Count() (int, error) { +// Count does "SELECT COUNT(x) FROM ..." statement for the model. +func (m *Model) Count() (int, error) { defer func(fields string) { - md.fields = fields - }(md.fields) - if md.fields == "" || md.fields == "*" { - md.fields = "COUNT(1)" + m.fields = fields + }(m.fields) + if m.fields == "" || m.fields == "*" { + m.fields = "COUNT(1)" } else { - md.fields = fmt.Sprintf(`COUNT(%s)`, md.fields) + m.fields = fmt.Sprintf(`COUNT(%s)`, m.fields) } - condition, conditionArgs := md.formatCondition() - s := fmt.Sprintf("SELECT %s FROM %s %s", md.fields, md.tables, condition) - if len(md.groupBy) > 0 { + condition, conditionArgs := m.formatCondition() + s := fmt.Sprintf("SELECT %s FROM %s %s", m.fields, m.tables, condition) + if len(m.groupBy) > 0 { s = fmt.Sprintf("SELECT COUNT(1) FROM (%s) count_alias", s) } - list, err := md.getAll(s, conditionArgs...) + list, err := m.getAll(s, conditionArgs...) if err != nil { return 0, err } @@ -681,64 +765,64 @@ func (md *Model) Count() (int, error) { return 0, nil } -// 获得操作的连接对象 -func (md *Model) getLink() dbLink { - if md.tx != nil { - return md.tx.tx +// getLink returns the underlying database link object with configured attribute. +func (m *Model) getLink() dbLink { + if m.tx != nil { + return m.tx.tx } - switch md.linkType { + switch m.linkType { case gLINK_TYPE_MASTER: - link, _ := md.db.Master() + link, _ := m.db.Master() return link case gLINK_TYPE_SLAVE: - link, _ := md.db.Slave() + link, _ := m.db.Slave() return link } return nil } -// 查询操作,对底层SQL操作的封装 -func (md *Model) getAll(query string, args ...interface{}) (result Result, err error) { +// getAll does the query from database. +func (m *Model) getAll(query string, args ...interface{}) (result Result, err error) { cacheKey := "" - // 查询缓存查询处理 - if md.cacheEnabled { - cacheKey = md.cacheName + // Retrieve from cache. + if m.cacheEnabled { + cacheKey = m.cacheName if len(cacheKey) == 0 { cacheKey = query + "/" + gconv.String(args) } - if v := md.db.getCache().Get(cacheKey); v != nil { + if v := m.db.getCache().Get(cacheKey); v != nil { return v.(Result), nil } } - result, err = md.db.doGetAll(md.getLink(), query, args...) - // 查询缓存保存处理 + result, err = m.db.doGetAll(m.getLink(), query, args...) + // Cache the result. if len(cacheKey) > 0 && err == nil { - if md.cacheExpire < 0 { - md.db.getCache().Remove(cacheKey) + if m.cacheDuration < 0 { + m.db.getCache().Remove(cacheKey) } else { - md.db.getCache().Set(cacheKey, result, md.cacheExpire) + m.db.getCache().Set(cacheKey, result, m.cacheDuration) } } return result, err } -// 检查是否需要查询查询缓存 -func (md *Model) checkAndRemoveCache() { - if md.cacheEnabled && md.cacheExpire < 0 && len(md.cacheName) > 0 { - md.db.getCache().Remove(md.cacheName) +// checkAndRemoveCache checks and remove the cache if necessary. +func (m *Model) checkAndRemoveCache() { + if m.cacheEnabled && m.cacheDuration < 0 && len(m.cacheName) > 0 { + m.db.getCache().Remove(m.cacheName) } } // formatCondition formats where arguments of the model and returns a new condition sql and its arguments. // Note that this function does not change any of the attribute value of the mode. -func (md *Model) formatCondition() (condition string, conditionArgs []interface{}) { +func (m *Model) formatCondition() (condition string, conditionArgs []interface{}) { var where string - if len(md.whereHolder) > 0 { - for _, v := range md.whereHolder { + if len(m.whereHolder) > 0 { + for _, v := range m.whereHolder { switch v.operator { case gWHERE_HOLDER_WHERE: if where == "" { - newWhere, newArgs := formatWhere(md.db, v.where, v.args, md.option&OPTION_OMITEMPTY > 0) + newWhere, newArgs := formatWhere(m.db, v.where, v.args, m.option&OPTION_OMITEMPTY > 0) if len(newWhere) > 0 { where = newWhere conditionArgs = newArgs @@ -748,7 +832,7 @@ func (md *Model) formatCondition() (condition string, conditionArgs []interface{ fallthrough case gWHERE_HOLDER_AND: - newWhere, newArgs := formatWhere(md.db, v.where, v.args, md.option&OPTION_OMITEMPTY > 0) + newWhere, newArgs := formatWhere(m.db, v.where, v.args, m.option&OPTION_OMITEMPTY > 0) if len(newWhere) > 0 { if where[0] == '(' { where = fmt.Sprintf(`%s AND (%s)`, where, newWhere) @@ -759,7 +843,7 @@ func (md *Model) formatCondition() (condition string, conditionArgs []interface{ } case gWHERE_HOLDER_OR: - newWhere, newArgs := formatWhere(md.db, v.where, v.args, md.option&OPTION_OMITEMPTY > 0) + newWhere, newArgs := formatWhere(m.db, v.where, v.args, m.option&OPTION_OMITEMPTY > 0) if len(newWhere) > 0 { if where[0] == '(' { where = fmt.Sprintf(`%s OR (%s)`, where, newWhere) @@ -774,29 +858,32 @@ func (md *Model) formatCondition() (condition string, conditionArgs []interface{ if where != "" { condition += " WHERE " + where } - if md.groupBy != "" { - condition += " GROUP BY " + md.groupBy + if m.groupBy != "" { + condition += " GROUP BY " + m.groupBy } - if md.orderBy != "" { - condition += " ORDER BY " + md.orderBy + if m.orderBy != "" { + condition += " ORDER BY " + m.orderBy } - if md.limit != 0 { - if md.start >= 0 { - condition += fmt.Sprintf(" LIMIT %d,%d", md.start, md.limit) + if m.limit != 0 { + if m.start >= 0 { + condition += fmt.Sprintf(" LIMIT %d,%d", m.start, m.limit) } else { - condition += fmt.Sprintf(" LIMIT %d", md.limit) + condition += fmt.Sprintf(" LIMIT %d", m.limit) } } - if md.offset >= 0 { - condition += fmt.Sprintf(" OFFSET %d", md.offset) + if m.offset >= 0 { + condition += fmt.Sprintf(" OFFSET %d", m.offset) } return } -// 组块结果集。 -func (md *Model) Chunk(limit int, callback func(result Result, err error) bool) { - page := 1 - model := md +// Chunk iterates the table with given size and callback function. +func (m *Model) Chunk(limit int, callback func(result Result, err error) bool) { + page := m.start + if page == 0 { + page = 1 + } + model := m for { model = model.ForPage(page, limit) data, err := model.All() diff --git a/database/gdb/gdb_structure.go b/database/gdb/gdb_structure.go index b4400334b..9a30948e8 100644 --- a/database/gdb/gdb_structure.go +++ b/database/gdb/gdb_structure.go @@ -131,7 +131,7 @@ func (bs *dbBase) TableFields(table string) (fields map[string]*TableField, err // 缓存不存在时会查询数据表结构,缓存后不过期,直至程序重启(重新部署) v := bs.cache.GetOrSetFunc("table_fields_"+table, func() interface{} { result := (Result)(nil) - result, err = bs.GetAll(fmt.Sprintf(`SHOW COLUMNS FROM %s`, bs.db.quoteWord(table))) + result, err = bs.GetAll(fmt.Sprintf(`SHOW FULL COLUMNS FROM %s`, bs.db.quoteWord(table))) if err != nil { return nil } @@ -145,6 +145,7 @@ func (bs *dbBase) TableFields(table string) (fields map[string]*TableField, err Key: m["Key"].String(), Default: m["Default"].Val(), Extra: m["Extra"].String(), + Comment: m["Comment"].String(), } } return fields diff --git a/frame/gmvc/model.go b/frame/gmvc/model.go index 38fa333bb..afc8a72f5 100644 --- a/frame/gmvc/model.go +++ b/frame/gmvc/model.go @@ -6,6 +6,6 @@ package gmvc -// MVC模型基类 +// Model is the base struct for model. type Model struct { } diff --git a/internal/structs/structs.go b/internal/structs/structs.go index fbac653ad..242f0ecfd 100644 --- a/internal/structs/structs.go +++ b/internal/structs/structs.go @@ -7,9 +7,12 @@ // Package structs provides functions for struct conversion. package structs -import ( - "github.com/fatih/structs" -) +import "github.com/fatih/structs" // Field is alias of structs.Field. -type Field = structs.Field +type Field struct { + *structs.Field + // Retrieved tag name. There might be more than one tags in the field, + // but only one can be retrieved according to calling function rules. + Tag string +} diff --git a/internal/structs/structs_map.go b/internal/structs/structs_map.go index 9eeae6cc4..9e830af56 100644 --- a/internal/structs/structs_map.go +++ b/internal/structs/structs_map.go @@ -33,7 +33,10 @@ func MapField(pointer interface{}, priority []string, recursive bool) map[string if name[0] < byte('A') || name[0] > byte('Z') { continue } - fieldMap[name] = field + fieldMap[name] = &Field{ + Field: field, + Tag: tag, + } tag = "" for _, p := range priority { tag = field.Tag(p) @@ -42,7 +45,10 @@ func MapField(pointer interface{}, priority []string, recursive bool) map[string } } if tag != "" { - fieldMap[tag] = field + fieldMap[tag] = &Field{ + Field: field, + Tag: tag, + } } if recursive { rv := reflect.ValueOf(field.Value()) diff --git a/internal/structs/structs_tag.go b/internal/structs/structs_tag.go index 60d3466b9..1fbfc2a54 100644 --- a/internal/structs/structs_tag.go +++ b/internal/structs/structs_tag.go @@ -12,31 +12,19 @@ import ( "github.com/fatih/structs" ) -// TagMapName retrieves struct tags as map[tag]attribute from , and returns it. +// TagFields retrieves struct tags as []*Field from , and returns it. // // The parameter specifies whether retrieving the struct field recursively. // // Note that it only retrieves the exported attributes with first letter up-case from struct. -func TagMapName(pointer interface{}, priority []string, recursive bool) map[string]string { - tagMap := TagMapField(pointer, priority, recursive) - if len(tagMap) > 0 { - m := make(map[string]string, len(tagMap)) - for k, v := range tagMap { - m[k] = v.Name() - } - return m - } - return nil +func TagFields(pointer interface{}, priority []string, recursive bool) []*Field { + return doTagFields(pointer, priority, recursive, map[string]struct{}{}) } -// TagMapField retrieves struct tags as map[tag]*Field from , and returns it. -// -// The parameter specifies whether retrieving the struct field recursively. -// -// Note that it only retrieves the exported attributes with first letter up-case from struct. -func TagMapField(pointer interface{}, priority []string, recursive bool) map[string]*Field { - tagMap := make(map[string]*Field) - fields := ([]*structs.Field)(nil) +// doTagFields retrieves the tag and corresponding attribute name from . It also filters repeated +// tag internally. +func doTagFields(pointer interface{}, priority []string, recursive bool, tagMap map[string]struct{}) []*Field { + var fields []*structs.Field if v, ok := pointer.(reflect.Value); ok { fields = structs.Fields(v.Interface()) } else { @@ -56,13 +44,13 @@ func TagMapField(pointer interface{}, priority []string, recursive bool) map[str } tag := "" name := "" + tagFields := make([]*Field, 0) for _, field := range fields { name = field.Name() // Only retrieve exported attributes. if name[0] < byte('A') || name[0] > byte('Z') { continue } - tag = "" for _, p := range priority { tag = field.Tag(p) @@ -71,7 +59,14 @@ func TagMapField(pointer interface{}, priority []string, recursive bool) map[str } } if tag != "" { - tagMap[tag] = field + // Filter repeated tag. + if _, ok := tagMap[tag]; ok { + continue + } + tagFields = append(tagFields, &Field{ + Field: field, + Tag: tag, + }) } if recursive { rv := reflect.ValueOf(field.Value()) @@ -81,13 +76,37 @@ func TagMapField(pointer interface{}, priority []string, recursive bool) map[str kind = rv.Kind() } if kind == reflect.Struct { - for k, v := range TagMapField(rv, priority, true) { - if _, ok := tagMap[k]; !ok { - tagMap[k] = v - } - } + tagFields = append(tagFields, doTagFields(rv, priority, recursive, tagMap)...) } } } + return tagFields +} + +// TagMapName retrieves struct tags as map[tag]attribute from , and returns it. +// +// The parameter specifies whether retrieving the struct field recursively. +// +// Note that it only retrieves the exported attributes with first letter up-case from struct. +func TagMapName(pointer interface{}, priority []string, recursive bool) map[string]string { + fields := TagFields(pointer, priority, recursive) + tagMap := make(map[string]string, len(fields)) + for _, v := range fields { + tagMap[v.Tag] = v.Name() + } + return tagMap +} + +// TagMapField retrieves struct tags as map[tag]*Field from , and returns it. +// +// The parameter specifies whether retrieving the struct field recursively. +// +// Note that it only retrieves the exported attributes with first letter up-case from struct. +func TagMapField(pointer interface{}, priority []string, recursive bool) map[string]*Field { + fields := TagFields(pointer, priority, recursive) + tagMap := make(map[string]*Field, len(fields)) + for _, v := range fields { + tagMap[v.Tag] = v + } return tagMap }