Compare commits

...

19 Commits

Author SHA1 Message Date
6a80091fef version updates 2021-05-03 00:00:29 +08:00
742653ce75 improve Model function for struct parameter that can retrieve table name tag from 2021-05-02 23:28:24 +08:00
a8c3d07d9f improve with feature for package gdb 2021-05-02 22:35:47 +08:00
df1ef5db78 add example for package gdb 2021-05-02 17:57:00 +08:00
4b15ab5e99 improve OrderRandom function for package gdb 2021-05-02 16:42:34 +08:00
cdc97e9b2b improve logging for transaction feature for package gdb 2021-05-02 15:58:28 +08:00
bd84b97614 add more Where*/Min/Max/Avg/Sum/CountColumn/Increment/Decrement/OrderAsc/OrderDesc/OrderRandom functions and associated unit testing cases for package gdb 2021-05-02 12:17:06 +08:00
d7eb1cca07 add nested transaction feature for package gdb 2021-05-02 09:35:54 +08:00
5856f74d83 up 2021-05-02 08:10:35 +08:00
563509c4a6 route map dumping updates 2021-05-01 09:04:16 +08:00
d0753fa527 Merge branch 'develop' 2021-04-26 20:38:16 +08:00
524e3bedc7 improve empty value validation for required-with* patterns for package gvalid 2021-04-26 20:37:36 +08:00
30b60754e3 README update 2021-04-16 10:32:55 +08:00
88c49dc2cb README update 2021-04-16 10:27:39 +08:00
bb3dcc2622 Merge branch 'master' of https://github.com/gogf/gf 2021-04-16 10:22:02 +08:00
5cca0640d9 README update 2021-04-16 10:21:42 +08:00
fb48ceeeee Merge pull request #1214 from ansionfor/add-graceful-timeout
add graceful reload timeout config
2021-04-10 08:23:54 +08:00
90f4bba8fd gofmt格式化 2021-04-07 20:52:38 +08:00
802187abc4 add graceful reload timeout 2021-04-07 16:06:37 +08:00
34 changed files with 1337 additions and 214 deletions

View File

@ -0,0 +1,9 @@
package main
import (
"github.com/gogf/gf/frame/g"
)
func main() {
g.DB().Model("user").Distinct().CountColumn("uid,name")
}

View File

@ -0,0 +1,27 @@
package main
import (
"github.com/gogf/gf/frame/g"
)
func main() {
var (
db = g.DB()
table = "user"
)
tx, err := db.Begin()
if err != nil {
panic(err)
}
if err = tx.Begin(); err != nil {
panic(err)
}
_, err = tx.Model(table).Data(g.Map{"id": 1, "name": "john"}).Insert()
if err = tx.Rollback(); err != nil {
panic(err)
}
_, err = tx.Model(table).Data(g.Map{"id": 2, "name": "smith"}).Insert()
if err = tx.Commit(); err != nil {
panic(err)
}
}

View File

@ -0,0 +1,34 @@
package main
import (
"github.com/gogf/gf/database/gdb"
"github.com/gogf/gf/frame/g"
)
func main() {
var (
err error
db = g.DB()
table = "user"
)
if err = db.Transaction(func(tx *gdb.TX) error {
// Nested transaction 1.
if err = tx.Transaction(func(tx *gdb.TX) error {
_, err = tx.Model(table).Data(g.Map{"id": 1, "name": "john"}).Insert()
return err
}); err != nil {
return err
}
// Nested transaction 2, panic.
if err = tx.Transaction(func(tx *gdb.TX) error {
_, err = tx.Model(table).Data(g.Map{"id": 2, "name": "smith"}).Insert()
// Create a panic that can make this transaction rollback automatically.
panic("error")
}); err != nil {
return err
}
return nil
}); err != nil {
panic(err)
}
}

View File

@ -0,0 +1,40 @@
package main
import (
"github.com/gogf/gf/frame/g"
)
func main() {
var (
err error
db = g.DB()
table = "user"
)
tx, err := db.Begin()
if err != nil {
panic(err)
}
defer func() {
if err := recover(); err != nil {
_ = tx.Rollback()
}
}()
if _, err = tx.Model(table).Data(g.Map{"id": 1, "name": "john"}).Insert(); err != nil {
panic(err)
}
if err = tx.SavePoint("MyPoint"); err != nil {
panic(err)
}
if _, err = tx.Model(table).Data(g.Map{"id": 2, "name": "smith"}).Insert(); err != nil {
panic(err)
}
if _, err = tx.Model(table).Data(g.Map{"id": 3, "name": "green"}).Insert(); err != nil {
panic(err)
}
if err = tx.RollbackTo("MyPoint"); err != nil {
panic(err)
}
if err = tx.Commit(); err != nil {
panic(err)
}
}

View File

@ -0,0 +1,66 @@
package main
import (
"fmt"
"github.com/gogf/gf/database/gdb"
"github.com/gogf/gf/frame/g"
"github.com/gogf/gf/util/gmeta"
)
func main() {
type UserDetail struct {
gmeta.Meta `orm:"table:user_detail"`
Uid int `json:"uid"`
Address string `json:"address"`
}
type UserScore struct {
gmeta.Meta `orm:"table:user_score"`
Id int `json:"id"`
Uid int `json:"uid"`
Score int `json:"score"`
}
type User struct {
gmeta.Meta `orm:"table:user"`
Id int `json:"id"`
Name string `json:"name"`
UserDetail *UserDetail `orm:"with:uid=id"`
UserScores []*UserScore `orm:"with:uid=id"`
}
db := g.DB()
db.Transaction(func(tx *gdb.TX) error {
for i := 1; i <= 5; i++ {
// User.
user := User{
Name: fmt.Sprintf(`name_%d`, i),
}
lastInsertId, err := db.Model(user).Data(user).OmitEmpty().InsertAndGetId()
if err != nil {
return err
}
// Detail.
userDetail := UserDetail{
Uid: int(lastInsertId),
Address: fmt.Sprintf(`address_%d`, lastInsertId),
}
_, err = db.Model(userDetail).Data(userDetail).OmitEmpty().Insert()
if err != nil {
return err
}
// Scores.
for j := 1; j <= 5; j++ {
userScore := UserScore{
Uid: int(lastInsertId),
Score: j,
}
_, err = db.Model(userScore).Data(userScore).OmitEmpty().Insert()
if err != nil {
return err
}
}
}
return nil
})
}

View File

@ -0,0 +1,37 @@
package main
import (
"github.com/gogf/gf/frame/g"
"github.com/gogf/gf/util/gmeta"
)
func main() {
type UserDetail struct {
gmeta.Meta `orm:"table:user_detail"`
Uid int `json:"uid"`
Address string `json:"address"`
}
type UserScore struct {
gmeta.Meta `orm:"table:user_score"`
Id int `json:"id"`
Uid int `json:"uid"`
Score int `json:"score"`
}
type User struct {
gmeta.Meta `orm:"table:user"`
Id int `json:"id"`
Name string `json:"name"`
UserDetail *UserDetail `orm:"with:uid=id"`
UserScores []*UserScore `orm:"with:uid=id"`
}
db := g.DB()
var user *User
err := db.Model(user).WithAll().Where("id", 3).Scan(&user)
if err != nil {
panic(err)
}
g.Dump(user)
}

View File

@ -50,17 +50,10 @@ The `Web` component performance of `GoFrame`, please refer to third-party projec
# Documentation
* 中文官网: https://goframe.org
* GoDoc API: https://godoc.org/github.com/gogf/gf
* 中文官网: [https://goframe.org](https://goframe.org/display/gf)
* GoDoc API: [https://pkg.go.dev/github.com/gogf/gf](https://pkg.go.dev/github.com/gogf/gf)
# Discussion
- QQ Group[116707870](//shang.qq.com/wpa/qunwpa?idkey=195f91eceeb5d7fa76009b7cd5a4641f70bf4897b7f5a520635eb26ff17adfe7)
- WX GroupAdd friend`389961817` in WeChat, commenting `GF`
- Issueshttps://github.com/gogf/gf/issues
> It's recommended learning `GoFrame` through its awesome source codes and API reference.
# License
`GF` is licensed under the [MIT License](LICENSE), 100% free and open-source, forever.

View File

@ -13,14 +13,16 @@
> 如果您初识`Go`语言,您可以将`GoFrame`类似于`PHP`中的`Laravel`, `Java`中的`SpringBoot`或者`Python`中的`Django`。
# 特点
* 模块化、松耦合设计;
* 模块丰富、开箱即用
* 简便易用、易于维护;
* 高代码质量、高单元测试覆盖率;
* 社区活跃,大牛谦逊低调脾气好;
* 详尽的开发文档及示例;
* 完善的本地中文化支持;
* 设计为团队及企业使用;
* 模块化、松耦合
* 模块丰富、开箱即用
* 简洁易用、快速接入
* 文档详尽、易于维护
* 自顶向下、体系化设计
* 统一框架、统一组件、降低选择成本
* 开发规范、设计模式、代码分层模型
* 强大便捷的开发工具链
* 完善的本地中文化支持
* 设计为团队及企业使用
# 地址
- **主库**https://github.com/gogf/gf
@ -63,16 +65,10 @@ golang版本 >= 1.11
# 文档
开发文档:https://goframe.org
官方站点:[https://goframe.org](https://goframe.org/display/gf)
接口文档https://godoc.org/github.com/gogf/gf
接口文档:[https://pkg.go.dev/github.com/gogf/gf](https://pkg.go.dev/github.com/gogf/gf)
# 帮助
- QQ交流群[116707870](//shang.qq.com/wpa/qunwpa?idkey=195f91eceeb5d7fa76009b7cd5a4641f70bf4897b7f5a520635eb26ff17adfe7)
- WX交流群微信添加`389961817`备注`GF`
- 主库ISSUEhttps://github.com/gogf/gf/issues
> 建议通过阅读`GoFrame`的源码以及API文档深度学习`GoFrame`,了解更多的精妙设计。
# 协议

View File

@ -32,12 +32,12 @@ type DB interface {
// Model creation.
// ===========================================================================
// Table function is deprecated, use Model instead.
// 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.
// Also see Core.Table.
// Deprecated, use Model instead.
Table(table ...string) *Model
// Also see Core.Table.
Table(tableNameOrStruct ...interface{}) *Model
// 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:
@ -48,7 +48,7 @@ type DB interface {
// Model("user u, user_detail ud")
// 2. Model name with alias: Model("user", "u")
// Also see Core.Model.
Model(table ...string) *Model
Model(tableNameOrStruct ...interface{}) *Model
// Schema creates and returns a schema.
// Also see Core.Schema.
@ -56,7 +56,7 @@ type DB interface {
// With creates and returns an ORM model based on meta data of given object.
// Also see Core.With.
With(object interface{}) *Model
With(objects ...interface{}) *Model
// Open creates a raw connection object for database with given node configuration.
// Note that it is not recommended using the this function manually.
@ -191,6 +191,8 @@ type DB interface {
mappingAndFilterData(schema, table string, data map[string]interface{}, filter bool) (map[string]interface{}, error) // See Core.mappingAndFilterData.
convertFieldValueToLocalValue(fieldValue interface{}, fieldType string) interface{} // See Core.convertFieldValueToLocalValue.
convertRowsToResult(rows *sql.Rows) (Result, error) // See Core.convertRowsToResult.
addSqlToTracing(ctx context.Context, sql *Sql) // See Core.addSqlToTracing.
writeSqlToLogger(v *Sql) // See Core.writeSqlToLogger.
}
// Core is the base struct for database management.

View File

@ -812,13 +812,19 @@ func (c *Core) DoUpdate(link Link, table string, data interface{}, condition str
switch value := v.(type) {
case *Counter:
if value.Value != 0 {
column := c.db.QuoteWord(value.Field)
column := k
if value.Field != "" {
column = c.db.QuoteWord(value.Field)
}
fields = append(fields, fmt.Sprintf("%s=%s+?", column, column))
params = append(params, value.Value)
}
case Counter:
if value.Value != 0 {
column := c.db.QuoteWord(value.Field)
column := k
if value.Field != "" {
column = c.db.QuoteWord(value.Field)
}
fields = append(fields, fmt.Sprintf("%s=%s+?", column, column))
params = append(params, value.Value)
}
@ -829,7 +835,6 @@ func (c *Core) DoUpdate(link Link, table string, data interface{}, condition str
fields = append(fields, c.db.QuoteWord(k)+"=?")
params = append(params, v)
}
}
}
updates = strings.Join(fields, ",")

View File

@ -235,7 +235,7 @@ func DataToMapDeep(value interface{}) map[string]interface{} {
name = ""
fieldTag = rtField.Tag
for _, tag := range structTagPriority {
if s := fieldTag.Get(tag); s != "" {
if s := fieldTag.Get(tag); s != "" && gregex.IsMatchString(regularFieldNameWithoutDotRegPattern, s) {
name = s
break
}

View File

@ -39,6 +39,7 @@ type Model struct {
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.
distinct string // Force the query to only return distinct results.
lockInfo string // Lock for update or in shared lock.
cacheEnabled bool // Enable sql result cache feature.
cacheDuration time.Duration // Cache TTL duration.
@ -69,53 +70,66 @@ const (
// 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...)
func (c *Core) Table(tableNameOrStruct ...interface{}) *Model {
return c.db.Model(tableNameOrStruct...)
}
// 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:
// The parameter `tableNameOrStruct` 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(
`%s AS %s`, c.db.QuotePrefixTableName(table[0]), c.db.QuoteWord(table[1]),
func (c *Core) Model(tableNameOrStruct ...interface{}) *Model {
var (
tableStr = ""
tableName = ""
tableNames = make([]string, len(tableNameOrStruct))
)
for k, v := range tableNameOrStruct {
if s, ok := v.(string); ok {
tableNames[k] = s
} else if tableName = getTableNameFromOrmTag(v); tableName != "" {
tableNames[k] = tableName
}
}
if len(tableNames) > 1 {
tableStr = fmt.Sprintf(
`%s AS %s`, c.db.QuotePrefixTableName(tableNames[0]), c.db.QuoteWord(tableNames[1]),
)
} else if len(table) == 1 {
tables = c.db.QuotePrefixTableName(table[0])
} else if len(tableNames) == 1 {
tableStr = c.db.QuotePrefixTableName(tableNames[0])
}
return &Model{
db: c.db,
tablesInit: tables,
tables: tables,
tablesInit: tableStr,
tables: tableStr,
fields: "*",
start: -1,
offset: -1,
option: OptionAllowEmpty,
filter: true,
}
}
// With creates and returns an ORM model based on meta data of given object.
func (c *Core) With(object interface{}) *Model {
return c.db.Model().With(object)
func (c *Core) With(objects ...interface{}) *Model {
return c.db.Model().With(objects...)
}
// Table is alias of tx.Model.
// Deprecated, use Model instead.
func (tx *TX) Table(table ...string) *Model {
return tx.Model(table...)
func (tx *TX) Table(tableNameOrStruct ...interface{}) *Model {
return tx.Model(tableNameOrStruct...)
}
// 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...)
func (tx *TX) Model(tableNameOrStruct ...interface{}) *Model {
model := tx.db.Model(tableNameOrStruct...)
model.db = tx.db
model.tx = tx
return model

View File

@ -7,6 +7,7 @@
package gdb
import (
"fmt"
"strings"
)
@ -58,7 +59,125 @@ func (m *Model) WherePri(where interface{}, args ...interface{}) *Model {
return m.Where(newWhere[0], newWhere[1:]...)
}
// WhereBetween builds `xxx BETWEEN x AND y` statement.
func (m *Model) WhereBetween(column string, min, max interface{}) *Model {
return m.Where(fmt.Sprintf(`%s BETWEEN ? AND ?`, m.db.QuoteWord(column)), min, max)
}
// WhereLike builds `xxx LIKE x` statement.
func (m *Model) WhereLike(column string, like interface{}) *Model {
return m.Where(fmt.Sprintf(`%s LIKE ?`, m.db.QuoteWord(column)), like)
}
// WhereIn builds `xxx IN (x)` statement.
func (m *Model) WhereIn(column string, in interface{}) *Model {
return m.Where(fmt.Sprintf(`%s IN (?)`, m.db.QuoteWord(column)), in)
}
// WhereNull builds `xxx IS NULL` statement.
func (m *Model) WhereNull(columns ...string) *Model {
model := m
for _, column := range columns {
model = m.Where(fmt.Sprintf(`%s IS NULL`, m.db.QuoteWord(column)))
}
return model
}
// WhereNotBetween builds `xxx NOT BETWEEN x AND y` statement.
func (m *Model) WhereNotBetween(column string, min, max interface{}) *Model {
return m.Where(fmt.Sprintf(`%s NOT BETWEEN ? AND ?`, m.db.QuoteWord(column)), min, max)
}
// WhereNotLike builds `xxx NOT LIKE x` statement.
func (m *Model) WhereNotLike(column string, like interface{}) *Model {
return m.Where(fmt.Sprintf(`%s NOT LIKE ?`, m.db.QuoteWord(column)), like)
}
// WhereNotIn builds `xxx NOT IN (x)` statement.
func (m *Model) WhereNotIn(column string, in interface{}) *Model {
return m.Where(fmt.Sprintf(`%s NOT IN (?)`, m.db.QuoteWord(column)), in)
}
// WhereNotNull builds `xxx IS NOT NULL` statement.
func (m *Model) WhereNotNull(columns ...string) *Model {
model := m
for _, column := range columns {
model = m.Where(fmt.Sprintf(`%s IS NOT NULL`, m.db.QuoteWord(column)))
}
return model
}
// WhereOr adds "OR" condition to the where statement.
func (m *Model) WhereOr(where interface{}, args ...interface{}) *Model {
model := m.getModel()
if model.whereHolder == nil {
model.whereHolder = make([]*whereHolder, 0)
}
model.whereHolder = append(model.whereHolder, &whereHolder{
operator: whereHolderOr,
where: where,
args: args,
})
return model
}
// WhereOrBetween builds `xxx BETWEEN x AND y` statement in `OR` conditions.
func (m *Model) WhereOrBetween(column string, min, max interface{}) *Model {
return m.WhereOr(fmt.Sprintf(`%s BETWEEN ? AND ?`, m.db.QuoteWord(column)), min, max)
}
// WhereOrLike builds `xxx LIKE x` statement in `OR` conditions.
func (m *Model) WhereOrLike(column string, like interface{}) *Model {
return m.WhereOr(fmt.Sprintf(`%s LIKE ?`, m.db.QuoteWord(column)), like)
}
// WhereOrIn builds `xxx IN (x)` statement in `OR` conditions.
func (m *Model) WhereOrIn(column string, in interface{}) *Model {
return m.WhereOr(fmt.Sprintf(`%s IN (?)`, m.db.QuoteWord(column)), in)
}
// WhereOrNull builds `xxx IS NULL` statement in `OR` conditions.
func (m *Model) WhereOrNull(columns ...string) *Model {
model := m
for _, column := range columns {
model = m.WhereOr(fmt.Sprintf(`%s IS NULL`, m.db.QuoteWord(column)))
}
return model
}
// WhereOrNotBetween builds `xxx NOT BETWEEN x AND y` statement in `OR` conditions.
func (m *Model) WhereOrNotBetween(column string, min, max interface{}) *Model {
return m.WhereOr(fmt.Sprintf(`%s NOT BETWEEN ? AND ?`, m.db.QuoteWord(column)), min, max)
}
// WhereOrNotLike builds `xxx NOT LIKE x` statement in `OR` conditions.
func (m *Model) WhereOrNotLike(column string, like interface{}) *Model {
return m.WhereOr(fmt.Sprintf(`%s NOT LIKE ?`, m.db.QuoteWord(column)), like)
}
// WhereOrNotIn builds `xxx NOT IN (x)` statement.
func (m *Model) WhereOrNotIn(column string, in interface{}) *Model {
return m.WhereOr(fmt.Sprintf(`%s NOT IN (?)`, m.db.QuoteWord(column)), in)
}
// WhereOrNotNull builds `xxx IS NOT NULL` statement in `OR` conditions.
func (m *Model) WhereOrNotNull(columns ...string) *Model {
model := m
for _, column := range columns {
model = m.WhereOr(fmt.Sprintf(`%s IS NOT NULL`, m.db.QuoteWord(column)))
}
return model
}
// Group sets the "GROUP BY" statement for the model.
func (m *Model) Group(groupBy string) *Model {
model := m.getModel()
model.groupBy = m.db.QuoteString(groupBy)
return model
}
// And adds "AND" condition to the where statement.
// Deprecated, use Where instead.
func (m *Model) And(where interface{}, args ...interface{}) *Model {
model := m.getModel()
if model.whereHolder == nil {
@ -73,24 +192,9 @@ func (m *Model) And(where interface{}, args ...interface{}) *Model {
}
// Or adds "OR" condition to the where statement.
// Deprecated, use WhereOr instead.
func (m *Model) Or(where interface{}, args ...interface{}) *Model {
model := m.getModel()
if model.whereHolder == nil {
model.whereHolder = make([]*whereHolder, 0)
}
model.whereHolder = append(model.whereHolder, &whereHolder{
operator: whereHolderOr,
where: where,
args: args,
})
return model
}
// Group sets the "GROUP BY" statement for the model.
func (m *Model) Group(groupBy string) *Model {
model := m.getModel()
model.groupBy = m.db.QuoteString(groupBy)
return model
return m.WhereOr(where, args...)
}
// GroupBy is alias of Model.Group.
@ -102,11 +206,41 @@ func (m *Model) GroupBy(groupBy string) *Model {
// Order sets the "ORDER BY" statement for the model.
func (m *Model) Order(orderBy ...string) *Model {
if len(orderBy) == 0 {
return m
}
model := m.getModel()
model.orderBy = m.db.QuoteString(strings.Join(orderBy, " "))
return model
}
// OrderAsc sets the "ORDER BY xxx ASC" statement for the model.
func (m *Model) OrderAsc(column string) *Model {
if len(column) == 0 {
return m
}
model := m.getModel()
model.orderBy = m.db.QuoteWord(column) + " ASC"
return model
}
// OrderDesc sets the "ORDER BY xxx DESC" statement for the model.
func (m *Model) OrderDesc(column string) *Model {
if len(column) == 0 {
return m
}
model := m.getModel()
model.orderBy = m.db.QuoteWord(column) + " DESC"
return model
}
// OrderRandom sets the "ORDER BY RANDOM()" statement for the model.
func (m *Model) OrderRandom() *Model {
model := m.getModel()
model.orderBy = "RAND()"
return model
}
// OrderBy is alias of Model.Order.
// See Model.Order.
// Deprecated, use Order instead.
@ -138,6 +272,13 @@ func (m *Model) Offset(offset int) *Model {
return model
}
// Distinct forces the query to only return distinct results.
func (m *Model) Distinct() *Model {
model := m.getModel()
model.distinct = "DISTINCT "
return model
}
// Page sets the paging number for the model.
// The parameter `page` is started from 1 for paging.
// Note that, it differs that the Limit function starts from 0 for "LIMIT" statement.

View File

@ -14,17 +14,6 @@ import (
"github.com/gogf/gf/util/gutil"
)
// Filter marks filtering the fields which does not exist in the fields of the operated table.
// Note that this function supports only single table operations.
func (m *Model) Filter() *Model {
if gstr.Contains(m.tables, " ") {
panic("function Filter supports only single table operations")
}
model := m.getModel()
model.filter = true
return model
}
// Fields sets the operation fields of the model, multiple fields joined using char ','.
// The parameter `fieldNamesOrMapStruct` can be type of string/map/*map/struct/*struct.
func (m *Model) Fields(fieldNamesOrMapStruct ...interface{}) *Model {
@ -81,13 +70,26 @@ func (m *Model) FieldsEx(fieldNamesOrMapStruct ...interface{}) *Model {
return m
}
// Filter marks filtering the fields which does not exist in the fields of the operated table.
// Note that this function supports only single table operations.
// Deprecated, filter feature is automatically enabled from GoFrame v1.16.0, it is so no longer used.
func (m *Model) Filter() *Model {
if gstr.Contains(m.tables, " ") {
panic("function Filter supports only single table operations")
}
model := m.getModel()
model.filter = true
return model
}
// FieldsStr retrieves and returns all fields from the table, joined with char ','.
// The optional parameter `prefix` specifies the prefix for each field, eg: FieldsStr("u.").
// Deprecated, use GetFieldsStr instead.
// This function name confuses the user that it was a chaining function.
func (m *Model) FieldsStr(prefix ...string) string {
return m.GetFieldsStr(prefix...)
}
// FieldsStr retrieves and returns all fields from the table, joined with char ','.
// GetFieldsStr retrieves and returns all fields from the table, joined with char ','.
// The optional parameter `prefix` specifies the prefix for each field, eg: FieldsStr("u.").
func (m *Model) GetFieldsStr(prefix ...string) string {
prefixStr := ""
@ -116,13 +118,16 @@ func (m *Model) GetFieldsStr(prefix ...string) string {
return newFields
}
// FieldsExStr retrieves and returns fields which are not in parameter `fields` from the table,
// joined with char ','.
// The parameter `fields` specifies the fields that are excluded.
// The optional parameter `prefix` specifies the prefix for each field, eg: FieldsExStr("id", "u.").
// Deprecated, use GetFieldsExStr instead.
// This function name confuses the user that it was a chaining function.
func (m *Model) FieldsExStr(fields string, prefix ...string) string {
return m.GetFieldsExStr(fields, prefix...)
}
// FieldsExStr retrieves and returns fields which are not in parameter `fields` from the table,
// GetFieldsExStr retrieves and returns fields which are not in parameter `fields` from the table,
// joined with char ','.
// The parameter `fields` specifies the fields that are excluded.
// The optional parameter `prefix` specifies the prefix for each field, eg: FieldsExStr("id", "u.").

View File

@ -109,6 +109,18 @@ func (m *Model) Insert(data ...interface{}) (result sql.Result, err error) {
return m.doInsertWithOption(insertOptionDefault)
}
// InsertAndGetId performs action Insert and returns the last insert id that automatically generated.
func (m *Model) InsertAndGetId(data ...interface{}) (lastInsertId int64, err error) {
if len(data) > 0 {
return m.Data(data...).InsertAndGetId()
}
result, err := m.doInsertWithOption(insertOptionDefault)
if err != nil {
return 0, err
}
return result.LastInsertId()
}
// InsertIgnore does "INSERT IGNORE INTO ..." statement for the model.
// The optional parameter `data` is the same as the parameter of Model.Data function,
// see Model.Data.

View File

@ -19,7 +19,7 @@ import (
// Select is alias of Model.All.
// See Model.All.
// Deprecated.
// Deprecated, use All instead.
func (m *Model) Select(where ...interface{}) (Result, error) {
return m.All(where...)
}
@ -50,7 +50,8 @@ func (m *Model) doGetAll(limit1 bool, where ...interface{}) (Result, error) {
// DISTINCT t.user_id uid
return m.doGetAllBySql(
fmt.Sprintf(
"SELECT %s FROM %s%s",
"SELECT %s%s FROM %s%s",
m.distinct,
m.getFieldsFiltered(),
m.tables,
conditionWhere+conditionExtra,
@ -182,7 +183,7 @@ func (m *Model) Value(fieldsAndWhere ...interface{}) (Value, error) {
}
// Array queries and returns data values as slice from database.
// Note that if there're multiple columns in the result, it returns just one column values randomly.
// Note that if there are multiple columns in the result, it returns just one column values randomly.
//
// If the optional parameter `fieldsAndWhere` is given, the fieldsAndWhere[0] is the selected fields
// and fieldsAndWhere[1:] is treated as where condition fields.
@ -334,7 +335,7 @@ func (m *Model) Count(where ...interface{}) (int, error) {
if m.fields != "" && m.fields != "*" {
// DO NOT quote the m.fields here, in case of fields like:
// DISTINCT t.user_id uid
countFields = fmt.Sprintf(`COUNT(%s)`, m.fields)
countFields = fmt.Sprintf(`COUNT(%s%s)`, m.distinct, m.fields)
}
conditionWhere, conditionExtra, conditionArgs := m.formatCondition(false, true)
s := fmt.Sprintf("SELECT %s FROM %s%s", countFields, m.tables, conditionWhere+conditionExtra)
@ -353,6 +354,62 @@ func (m *Model) Count(where ...interface{}) (int, error) {
return 0, nil
}
// CountColumn does "SELECT COUNT(x) FROM ..." statement for the model.
func (m *Model) CountColumn(column string) (int, error) {
if len(column) == 0 {
return 0, nil
}
return m.Fields(column).Count()
}
// Min does "SELECT MIN(x) FROM ..." statement for the model.
func (m *Model) Min(column string) (float64, error) {
if len(column) == 0 {
return 0, nil
}
value, err := m.Fields(fmt.Sprintf(`MIN(%s)`, m.db.QuoteWord(column))).Value()
if err != nil {
return 0, err
}
return value.Float64(), err
}
// Max does "SELECT MAX(x) FROM ..." statement for the model.
func (m *Model) Max(column string) (float64, error) {
if len(column) == 0 {
return 0, nil
}
value, err := m.Fields(fmt.Sprintf(`MAX(%s)`, m.db.QuoteWord(column))).Value()
if err != nil {
return 0, err
}
return value.Float64(), err
}
// Avg does "SELECT AVG(x) FROM ..." statement for the model.
func (m *Model) Avg(column string) (float64, error) {
if len(column) == 0 {
return 0, nil
}
value, err := m.Fields(fmt.Sprintf(`AVG(%s)`, m.db.QuoteWord(column))).Value()
if err != nil {
return 0, err
}
return value.Float64(), err
}
// Sum does "SELECT SUM(x) FROM ..." statement for the model.
func (m *Model) Sum(column string) (float64, error) {
if len(column) == 0 {
return 0, nil
}
value, err := m.Fields(fmt.Sprintf(`SUM(%s)`, m.db.QuoteWord(column))).Value()
if err != nil {
return 0, err
}
return value.Float64(), err
}
// FindOne retrieves and returns a single Record by Model.WherePri and Model.One.
// Also see Model.WherePri and Model.One.
func (m *Model) FindOne(where ...interface{}) (Record, error) {

View File

@ -89,3 +89,19 @@ func (m *Model) Update(dataAndWhere ...interface{}) (result sql.Result, err erro
m.mergeArguments(conditionArgs)...,
)
}
// Increment increments a column's value by a given amount.
func (m *Model) Increment(column string, amount float64) (sql.Result, error) {
return m.getModel().Data(column, &Counter{
Field: column,
Value: amount,
}).Update()
}
// Decrement decrements a column's value by a given amount.
func (m *Model) Decrement(column string, amount float64) (sql.Result, error) {
return m.getModel().Data(column, &Counter{
Field: column,
Value: -amount,
}).Update()
}

View File

@ -18,16 +18,6 @@ import (
"time"
)
// getModel creates and returns a cloned model of current model if `safe` is true, or else it returns
// the current model.
func (m *Model) getModel() *Model {
if !m.safe {
return m
} else {
return m.Clone()
}
}
// TableFields retrieves and returns the fields information of specified table of current
// schema.
//
@ -47,6 +37,16 @@ func (m *Model) TableFields(table string, schema ...string) (fields map[string]*
return m.db.TableFields(link, table, schema...)
}
// getModel creates and returns a cloned model of current model if `safe` is true, or else it returns
// the current model.
func (m *Model) getModel() *Model {
if !m.safe {
return m
} else {
return m.Clone()
}
}
// mappingAndFilterToTableFields mappings and changes given field name to really table field name.
// Eg:
// ID -> id

View File

@ -32,13 +32,17 @@ import (
// db.With(User{}.UserDetail).With(User{}.UserDetail).Scan(xxx)
// Or:
// db.With(UserDetail{}).With(UserDetail{}).Scan(xxx)
func (m *Model) With(object interface{}) *Model {
// Or:
// db.With(UserDetail{}, UserDetail{}).Scan(xxx)
func (m *Model) With(objects ...interface{}) *Model {
model := m.getModel()
if m.tables == "" {
m.tables = m.db.QuotePrefixTableName(getTableNameFromOrmTag(object))
return model
for _, object := range objects {
if m.tables == "" {
m.tables = m.db.QuotePrefixTableName(getTableNameFromOrmTag(object))
return model
}
model.withArray = append(model.withArray, object)
}
model.withArray = append(model.withArray, object)
return model
}

View File

@ -9,6 +9,8 @@ package gdb
import (
"database/sql"
"fmt"
"github.com/gogf/gf/os/gtime"
"github.com/gogf/gf/util/gconv"
"reflect"
"github.com/gogf/gf/text/gregex"
@ -16,19 +18,139 @@ import (
// TX is the struct for transaction management.
type TX struct {
db DB
tx *sql.Tx
master *sql.DB
db DB // db is the current gdb database manager.
tx *sql.Tx // tx is the raw and underlying transaction manager.
master *sql.DB // master is the raw and underlying database manager.
transactionCount int // transactionCount marks the times that Begins.
}
// Commit commits the transaction.
const (
transactionPointerPrefix = "transaction"
)
// Commit commits current transaction.
// Note that it releases previous saved transaction point if it's in a nested transaction procedure,
// or else it commits the hole transaction.
func (tx *TX) Commit() error {
return tx.tx.Commit()
if tx.transactionCount > 0 {
tx.transactionCount--
_, err := tx.Exec("RELEASE SAVEPOINT " + tx.transactionKey())
return err
}
var (
sqlStr = "COMMIT"
mTime1 = gtime.TimestampMilli()
err = tx.tx.Commit()
mTime2 = gtime.TimestampMilli()
sqlObj = &Sql{
Sql: sqlStr,
Type: "TX.Commit",
Args: nil,
Format: sqlStr,
Error: err,
Start: mTime1,
End: mTime2,
Group: tx.db.GetGroup(),
}
)
tx.db.addSqlToTracing(tx.db.GetCtx(), sqlObj)
if tx.db.GetDebug() {
tx.db.writeSqlToLogger(sqlObj)
}
return err
}
// Rollback aborts the transaction.
// Rollback aborts current transaction.
// Note that it aborts current transaction if it's in a nested transaction procedure,
// or else it aborts the hole transaction.
func (tx *TX) Rollback() error {
return tx.tx.Rollback()
if tx.transactionCount > 0 {
tx.transactionCount--
_, err := tx.Exec("ROLLBACK TO SAVEPOINT " + tx.transactionKey())
return err
}
var (
sqlStr = "ROLLBACK"
mTime1 = gtime.TimestampMilli()
err = tx.tx.Rollback()
mTime2 = gtime.TimestampMilli()
sqlObj = &Sql{
Sql: sqlStr,
Type: "TX.Rollback",
Args: nil,
Format: sqlStr,
Error: err,
Start: mTime1,
End: mTime2,
Group: tx.db.GetGroup(),
}
)
tx.db.addSqlToTracing(tx.db.GetCtx(), sqlObj)
if tx.db.GetDebug() {
tx.db.writeSqlToLogger(sqlObj)
}
return err
}
// Begin starts a nested transaction procedure.
func (tx *TX) Begin() error {
_, err := tx.Exec("SAVEPOINT " + tx.transactionKey())
if err != nil {
return err
}
tx.transactionCount++
return nil
}
// SavePoint performs `SAVEPOINT xxx` SQL statement that saves transaction at current point.
// The parameter `point` specifies the point name that will be saved to server.
func (tx *TX) SavePoint(point string) error {
_, err := tx.Exec("SAVEPOINT " + tx.db.QuoteWord(point))
return err
}
// RollbackTo performs `ROLLBACK TO SAVEPOINT xxx` SQL statement that rollbacks to specified saved transaction.
// The parameter `point` specifies the point name that was saved previously.
func (tx *TX) RollbackTo(point string) error {
_, err := tx.Exec("ROLLBACK TO SAVEPOINT " + tx.db.QuoteWord(point))
return err
}
// transactionKey forms and returns the transaction key at current save point.
func (tx *TX) transactionKey() string {
return tx.db.QuoteWord(transactionPointerPrefix + gconv.String(tx.transactionCount))
}
// Transaction wraps the transaction logic using function `f`.
// It rollbacks the transaction and returns the error from function `f` if
// it returns non-nil error. It commits the transaction and returns nil if
// function `f` returns nil.
//
// Note that, you should not Commit or Rollback the transaction in function `f`
// as it is automatically handled by this function.
func (tx *TX) Transaction(f func(tx *TX) error) (err error) {
err = tx.Begin()
if err != nil {
return err
}
defer func() {
if err == nil {
if e := recover(); e != nil {
err = fmt.Errorf("%v", e)
}
}
if err != nil {
if e := tx.Rollback(); e != nil {
err = e
}
} else {
if e := tx.Commit(); e != nil {
err = e
}
}
}()
err = f(tx)
return
}
// Query does query operation on transaction.
@ -221,7 +343,7 @@ func (tx *TX) BatchInsert(table string, list interface{}, batch ...int) (sql.Res
return tx.Model(table).Data(list).Insert()
}
// BatchInsert batch inserts data with ignore option.
// BatchInsertIgnore batch inserts data with ignore option.
// The parameter `list` must be type of slice of map or struct.
func (tx *TX) BatchInsertIgnore(table string, list interface{}, batch ...int) (sql.Result, error) {
if len(batch) > 0 {

View File

@ -18,7 +18,7 @@ func Test_Table_Relation_With_Scan(t *testing.T) {
var (
tableUser = "user"
tableUserDetail = "user_detail"
tableUserScores = "user_scores"
tableUserScores = "user_score"
)
if _, err := db.Exec(fmt.Sprintf(`
CREATE TABLE IF NOT EXISTS %s (
@ -60,8 +60,8 @@ PRIMARY KEY (id)
Address string `json:"address"`
}
type UserScores struct {
gmeta.Meta `orm:"table:user_scores"`
type UserScore struct {
gmeta.Meta `orm:"table:user_score"`
Id int `json:"id"`
Uid int `json:"uid"`
Score int `json:"score"`
@ -69,34 +69,61 @@ PRIMARY KEY (id)
type User struct {
gmeta.Meta `orm:"table:user"`
Id int `json:"id"`
Name string `json:"name"`
UserDetail *UserDetail `orm:"with:uid=id"`
UserScores []*UserScores `orm:"with:uid=id"`
Id int `json:"id"`
Name string `json:"name"`
UserDetail *UserDetail `orm:"with:uid=id"`
UserScores []*UserScore `orm:"with:uid=id"`
}
// Initialize the data.
var err error
gtest.C(t, func(t *gtest.T) {
for i := 1; i <= 5; i++ {
// User.
user := User{
Name: fmt.Sprintf(`name_%d`, i),
}
lastInsertId, err := db.Model(user).Data(user).OmitEmpty().InsertAndGetId()
t.AssertNil(err)
// Detail.
userDetail := UserDetail{
Uid: int(lastInsertId),
Address: fmt.Sprintf(`address_%d`, lastInsertId),
}
_, err = db.Model(userDetail).Data(userDetail).OmitEmpty().Insert()
t.AssertNil(err)
// Scores.
for j := 1; j <= 5; j++ {
userScore := UserScore{
Uid: int(lastInsertId),
Score: j,
}
_, err = db.Model(userScore).Data(userScore).OmitEmpty().Insert()
t.AssertNil(err)
}
}
})
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)
user := User{
Name: fmt.Sprintf(`name_%d`, i),
}
lastInsertId, err := db.Model(user).Data(user).OmitEmpty().InsertAndGetId()
gtest.AssertNil(err)
// Detail.
_, err = db.Insert(tableUserDetail, g.Map{
"uid": i,
"address": fmt.Sprintf(`address_%d`, i),
})
gtest.Assert(err, nil)
userDetail := UserDetail{
Uid: int(lastInsertId),
Address: fmt.Sprintf(`address_%d`, lastInsertId),
}
_, err = db.Model(userDetail).Data(userDetail).Insert()
gtest.AssertNil(err)
// Scores.
for j := 1; j <= 5; j++ {
_, err = db.Insert(tableUserScores, g.Map{
"uid": i,
"score": j,
})
gtest.Assert(err, nil)
userScore := UserScore{
Uid: int(lastInsertId),
Score: j,
}
_, err = db.Model(userScore).Data(userScore).Insert()
gtest.AssertNil(err)
}
}
gtest.C(t, func(t *gtest.T) {
@ -139,7 +166,7 @@ PRIMARY KEY (id)
var user *User
err := db.With(User{}).
With(UserDetail{}).
With(UserScores{}).
With(UserScore{}).
Where("id", 4).
Scan(&user)
t.AssertNil(err)

View File

@ -274,30 +274,31 @@ func Test_DB_Upadte_KeyFieldNameMapping(t *testing.T) {
})
}
func Test_DB_Insert_KeyFieldNameMapping_Error(t *testing.T) {
table := createTable()
defer dropTable(table)
gtest.C(t, func(t *gtest.T) {
type User struct {
Id int
Passport string
Password string
Nickname string
CreateTime string
NoneExistField string
}
data := User{
Id: 1,
Passport: "user_1",
Password: "pass_1",
Nickname: "name_1",
CreateTime: "2020-10-10 12:00:01",
}
_, err := db.Insert(table, data)
t.AssertNE(err, nil)
})
}
// This is no longer used as the filter feature is automatically enabled from GoFrame v1.16.0.
//func Test_DB_Insert_KeyFieldNameMapping_Error(t *testing.T) {
// table := createTable()
// defer dropTable(table)
//
// gtest.C(t, func(t *gtest.T) {
// type User struct {
// Id int
// Passport string
// Password string
// Nickname string
// CreateTime string
// NoneExistField string
// }
// data := User{
// Id: 1,
// Passport: "user_1",
// Password: "pass_1",
// Nickname: "name_1",
// CreateTime: "2020-10-10 12:00:01",
// }
// _, err := db.Insert(table, data)
// t.AssertNE(err, nil)
// })
//}
func Test_DB_InsertIgnore(t *testing.T) {
table := createInitTable()

View File

@ -240,30 +240,31 @@ func Test_Model_Update_KeyFieldNameMapping(t *testing.T) {
})
}
func Test_Model_Insert_KeyFieldNameMapping_Error(t *testing.T) {
table := createTable()
defer dropTable(table)
gtest.C(t, func(t *gtest.T) {
type User struct {
Id int
Passport string
Password string
Nickname string
CreateTime string
NoneExistFiled string
}
data := User{
Id: 1,
Passport: "user_1",
Password: "pass_1",
Nickname: "name_1",
CreateTime: "2020-10-10 12:00:01",
}
_, err := db.Model(table).Data(data).Insert()
t.AssertNE(err, nil)
})
}
// This is no longer used as the filter feature is automatically enabled from GoFrame v1.16.0.
//func Test_Model_Insert_KeyFieldNameMapping_Error(t *testing.T) {
// table := createTable()
// defer dropTable(table)
//
// gtest.C(t, func(t *gtest.T) {
// type User struct {
// Id int
// Passport string
// Password string
// Nickname string
// CreateTime string
// NoneExistFiled string
// }
// data := User{
// Id: 1,
// Passport: "user_1",
// Password: "pass_1",
// Nickname: "name_1",
// CreateTime: "2020-10-10 12:00:01",
// }
// _, err := db.Model(table).Data(data).Insert()
// t.AssertNE(err, nil)
// })
//}
func Test_Model_Insert_Time(t *testing.T) {
table := createTable()
@ -2778,6 +2779,11 @@ func Test_Model_Distinct(t *testing.T) {
t.AssertNil(err)
t.Assert(len(all), 2)
})
gtest.C(t, func(t *gtest.T) {
count, err := db.Model(table).Where("id > 1").Distinct().Count()
t.AssertNil(err)
t.Assert(count, 9)
})
}
func Test_Model_Min_Max(t *testing.T) {
@ -3321,3 +3327,292 @@ func Test_Model_Fields_AutoFilterInJoinStatement(t *testing.T) {
t.Assert(one["number"].String(), "n")
})
}
func Test_Model_WhereIn(t *testing.T) {
table := createInitTable()
defer dropTable(table)
gtest.C(t, func(t *gtest.T) {
result, err := db.Model(table).WhereIn("id", g.Slice{1, 2, 3, 4}).WhereIn("id", g.Slice{3, 4, 5}).OrderAsc("id").All()
t.AssertNil(err)
t.Assert(len(result), 2)
t.Assert(result[0]["id"], 3)
t.Assert(result[1]["id"], 4)
})
}
func Test_Model_WhereNotIn(t *testing.T) {
table := createInitTable()
defer dropTable(table)
gtest.C(t, func(t *gtest.T) {
result, err := db.Model(table).WhereNotIn("id", g.Slice{1, 2, 3, 4}).WhereNotIn("id", g.Slice{3, 4, 5}).OrderAsc("id").All()
t.AssertNil(err)
t.Assert(len(result), 5)
t.Assert(result[0]["id"], 6)
t.Assert(result[1]["id"], 7)
})
}
func Test_Model_WhereOrIn(t *testing.T) {
table := createInitTable()
defer dropTable(table)
gtest.C(t, func(t *gtest.T) {
result, err := db.Model(table).WhereOrIn("id", g.Slice{1, 2, 3, 4}).WhereOrIn("id", g.Slice{3, 4, 5}).OrderAsc("id").All()
t.AssertNil(err)
t.Assert(len(result), 5)
t.Assert(result[0]["id"], 1)
t.Assert(result[4]["id"], 5)
})
}
func Test_Model_WhereOrNotIn(t *testing.T) {
table := createInitTable()
defer dropTable(table)
gtest.C(t, func(t *gtest.T) {
result, err := db.Model(table).WhereOrNotIn("id", g.Slice{1, 2, 3, 4}).WhereOrNotIn("id", g.Slice{3, 4, 5}).OrderAsc("id").All()
t.AssertNil(err)
t.Assert(len(result), 8)
t.Assert(result[0]["id"], 1)
t.Assert(result[4]["id"], 7)
})
}
func Test_Model_WhereBetween(t *testing.T) {
table := createInitTable()
defer dropTable(table)
gtest.C(t, func(t *gtest.T) {
result, err := db.Model(table).WhereBetween("id", 1, 4).WhereBetween("id", 3, 5).OrderAsc("id").All()
t.AssertNil(err)
t.Assert(len(result), 2)
t.Assert(result[0]["id"], 3)
t.Assert(result[1]["id"], 4)
})
}
func Test_Model_WhereNotBetween(t *testing.T) {
table := createInitTable()
defer dropTable(table)
gtest.C(t, func(t *gtest.T) {
result, err := db.Model(table).WhereNotBetween("id", 2, 8).WhereNotBetween("id", 3, 100).OrderAsc("id").All()
t.AssertNil(err)
t.Assert(len(result), 1)
t.Assert(result[0]["id"], 1)
})
}
func Test_Model_WhereOrBetween(t *testing.T) {
table := createInitTable()
defer dropTable(table)
gtest.C(t, func(t *gtest.T) {
result, err := db.Model(table).WhereOrBetween("id", 1, 4).WhereOrBetween("id", 3, 5).OrderDesc("id").All()
t.AssertNil(err)
t.Assert(len(result), 5)
t.Assert(result[0]["id"], 5)
t.Assert(result[4]["id"], 1)
})
}
func Test_Model_WhereOrNotBetween(t *testing.T) {
table := createInitTable()
defer dropTable(table)
//db.SetDebug(true)
gtest.C(t, func(t *gtest.T) {
result, err := db.Model(table).WhereOrNotBetween("id", 1, 4).WhereOrNotBetween("id", 3, 5).OrderDesc("id").All()
t.AssertNil(err)
t.Assert(len(result), 8)
t.Assert(result[0]["id"], 10)
t.Assert(result[4]["id"], 6)
})
}
func Test_Model_WhereLike(t *testing.T) {
table := createInitTable()
defer dropTable(table)
gtest.C(t, func(t *gtest.T) {
result, err := db.Model(table).WhereLike("nickname", "name%").OrderAsc("id").All()
t.AssertNil(err)
t.Assert(len(result), TableSize)
t.Assert(result[0]["id"], 1)
t.Assert(result[TableSize-1]["id"], TableSize)
})
}
func Test_Model_WhereNotLike(t *testing.T) {
table := createInitTable()
defer dropTable(table)
gtest.C(t, func(t *gtest.T) {
result, err := db.Model(table).WhereNotLike("nickname", "name%").OrderAsc("id").All()
t.AssertNil(err)
t.Assert(len(result), 0)
})
}
func Test_Model_WhereOrLike(t *testing.T) {
table := createInitTable()
defer dropTable(table)
gtest.C(t, func(t *gtest.T) {
result, err := db.Model(table).WhereOrLike("nickname", "namexxx%").WhereOrLike("nickname", "name%").OrderAsc("id").All()
t.AssertNil(err)
t.Assert(len(result), TableSize)
t.Assert(result[0]["id"], 1)
t.Assert(result[TableSize-1]["id"], TableSize)
})
}
func Test_Model_WhereOrNotLike(t *testing.T) {
table := createInitTable()
defer dropTable(table)
gtest.C(t, func(t *gtest.T) {
result, err := db.Model(table).WhereOrNotLike("nickname", "namexxx%").WhereOrNotLike("nickname", "name%").OrderAsc("id").All()
t.AssertNil(err)
t.Assert(len(result), TableSize)
t.Assert(result[0]["id"], 1)
t.Assert(result[TableSize-1]["id"], TableSize)
})
}
func Test_Model_WhereNull(t *testing.T) {
table := createInitTable()
defer dropTable(table)
gtest.C(t, func(t *gtest.T) {
result, err := db.Model(table).WhereNull("nickname").WhereNull("passport").OrderAsc("id").All()
t.AssertNil(err)
t.Assert(len(result), 0)
})
}
func Test_Model_WhereNotNull(t *testing.T) {
table := createInitTable()
defer dropTable(table)
gtest.C(t, func(t *gtest.T) {
result, err := db.Model(table).WhereNotNull("nickname").WhereNotNull("passport").OrderAsc("id").All()
t.AssertNil(err)
t.Assert(len(result), TableSize)
t.Assert(result[0]["id"], 1)
t.Assert(result[TableSize-1]["id"], TableSize)
})
}
func Test_Model_WhereOrNull(t *testing.T) {
table := createInitTable()
defer dropTable(table)
gtest.C(t, func(t *gtest.T) {
result, err := db.Model(table).WhereOrNull("nickname").WhereOrNull("passport").OrderAsc("id").OrderRandom().All()
t.AssertNil(err)
t.Assert(len(result), 0)
})
}
func Test_Model_WhereOrNotNull(t *testing.T) {
table := createInitTable()
defer dropTable(table)
gtest.C(t, func(t *gtest.T) {
result, err := db.Model(table).WhereOrNotNull("nickname").WhereOrNotNull("passport").OrderAsc("id").All()
t.AssertNil(err)
t.Assert(len(result), TableSize)
t.Assert(result[0]["id"], 1)
t.Assert(result[TableSize-1]["id"], TableSize)
})
}
func Test_Model_Min_Max_Avg_Sum(t *testing.T) {
table := createInitTable()
defer dropTable(table)
gtest.C(t, func(t *gtest.T) {
result, err := db.Model(table).Min("id")
t.AssertNil(err)
t.Assert(result, 1)
})
gtest.C(t, func(t *gtest.T) {
result, err := db.Model(table).Max("id")
t.AssertNil(err)
t.Assert(result, TableSize)
})
gtest.C(t, func(t *gtest.T) {
result, err := db.Model(table).Avg("id")
t.AssertNil(err)
t.Assert(result, 5.5)
})
gtest.C(t, func(t *gtest.T) {
result, err := db.Model(table).Sum("id")
t.AssertNil(err)
t.Assert(result, 55)
})
}
func Test_Model_CountColumn(t *testing.T) {
table := createInitTable()
defer dropTable(table)
gtest.C(t, func(t *gtest.T) {
result, err := db.Model(table).CountColumn("id")
t.AssertNil(err)
t.Assert(result, TableSize)
})
gtest.C(t, func(t *gtest.T) {
result, err := db.Model(table).WhereIn("id", g.Slice{1, 2, 3}).CountColumn("id")
t.AssertNil(err)
t.Assert(result, 3)
})
}
func Test_Model_InsertAndGetId(t *testing.T) {
table := createTable()
defer dropTable(table)
gtest.C(t, func(t *gtest.T) {
id, err := db.Model(table).Data(g.Map{
"id": 1,
"passport": "user_1",
"password": "pass_1",
"nickname": "name_1",
}).InsertAndGetId()
t.AssertNil(err)
t.Assert(id, 1)
})
gtest.C(t, func(t *gtest.T) {
id, err := db.Model(table).Data(g.Map{
"passport": "user_2",
"password": "pass_2",
"nickname": "name_2",
}).InsertAndGetId()
t.AssertNil(err)
t.Assert(id, 2)
})
}
func Test_Model_Increment_Decrement(t *testing.T) {
table := createInitTable()
defer dropTable(table)
gtest.C(t, func(t *gtest.T) {
result, err := db.Model(table).Where("id", 1).Increment("id", 100)
t.AssertNil(err)
rows, _ := result.RowsAffected()
t.Assert(rows, 1)
})
gtest.C(t, func(t *gtest.T) {
result, err := db.Model(table).Where("id", 101).Decrement("id", 10)
t.AssertNil(err)
rows, _ := result.RowsAffected()
t.Assert(rows, 1)
})
gtest.C(t, func(t *gtest.T) {
count, err := db.Model(table).Where("id", 91).Count()
t.AssertNil(err)
t.Assert(count, 1)
})
}

View File

@ -789,3 +789,137 @@ func Test_Transaction_Panic(t *testing.T) {
}
})
}
func Test_Transaction_Nested_Begin_Rollback_Commit(t *testing.T) {
table := createTable()
defer dropTable(table)
gtest.C(t, func(t *gtest.T) {
tx, err := db.Begin()
t.AssertNil(err)
// tx begin.
err = tx.Begin()
t.AssertNil(err)
// tx rollback.
_, err = tx.Model(table).Data(g.Map{
"id": 1,
"passport": "user_1",
"password": "pass_1",
"nickname": "name_1",
}).Insert()
err = tx.Rollback()
t.AssertNil(err)
// tx commit.
_, err = tx.Model(table).Data(g.Map{
"id": 2,
"passport": "user_2",
"password": "pass_2",
"nickname": "name_2",
}).Insert()
err = tx.Commit()
t.AssertNil(err)
// check data.
all, err := db.Model(table).All()
t.AssertNil(err)
t.Assert(len(all), 1)
t.Assert(all[0]["id"], 2)
})
}
func Test_Transaction_Nested_TX_Transaction(t *testing.T) {
table := createTable()
defer dropTable(table)
gtest.C(t, func(t *gtest.T) {
var err error
err = db.Transaction(func(tx *gdb.TX) error {
// commit
err = tx.Transaction(func(tx *gdb.TX) error {
err = tx.Transaction(func(tx *gdb.TX) error {
err = tx.Transaction(func(tx *gdb.TX) error {
err = tx.Transaction(func(tx *gdb.TX) error {
err = tx.Transaction(func(tx *gdb.TX) error {
_, err = tx.Model(table).Data(g.Map{
"id": 1,
"passport": "USER_1",
"password": "PASS_1",
"nickname": "NAME_1",
"create_time": gtime.Now().String(),
}).Insert()
t.AssertNil(err)
return err
})
t.AssertNil(err)
return err
})
t.AssertNil(err)
return err
})
t.AssertNil(err)
return err
})
t.AssertNil(err)
return err
})
t.AssertNil(err)
// rollback
err = tx.Transaction(func(tx *gdb.TX) error {
_, err = tx.Model(table).Data(g.Map{
"id": 2,
"passport": "USER_2",
"password": "PASS_2",
"nickname": "NAME_2",
"create_time": gtime.Now().String(),
}).Insert()
t.AssertNil(err)
panic("error")
return err
})
t.AssertNE(err, nil)
return nil
})
t.AssertNil(err)
all, err := db.Model(table).All()
t.AssertNil(err)
t.Assert(len(all), 1)
t.Assert(all[0]["id"], 1)
})
}
func Test_Transaction_Nested_SavePoint_RollbackTo(t *testing.T) {
table := createTable()
defer dropTable(table)
gtest.C(t, func(t *gtest.T) {
tx, err := db.Begin()
t.AssertNil(err)
// tx save point.
_, err = tx.Model(table).Data(g.Map{
"id": 1,
"passport": "user_1",
"password": "pass_1",
"nickname": "name_1",
}).Insert()
err = tx.SavePoint("MyPoint")
t.AssertNil(err)
_, err = tx.Model(table).Data(g.Map{
"id": 2,
"passport": "user_2",
"password": "pass_2",
"nickname": "name_2",
}).Insert()
// tx rollback to.
err = tx.RollbackTo("MyPoint")
t.AssertNil(err)
// tx commit.
err = tx.Commit()
t.AssertNil(err)
// check data.
all, err := db.Model(table).All()
t.AssertNil(err)
t.Assert(len(all), 1)
t.Assert(all[0]["id"], 1)
})
}

View File

@ -97,18 +97,13 @@ func DB(name ...string) gdb.DB {
// 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().Model(tables...)
func Table(tableNameOrStruct ...interface{}) *gdb.Model {
return DB().Model(tableNameOrStruct...)
}
// Model creates and returns a model based on configuration of default database group.
func Model(tables ...string) *gdb.Model {
return DB().Model(tables...)
}
// With creates and returns an ORM model based on meta data of given object.
func With(object interface{}) *gdb.Model {
return DB().With(object)
func Model(tableNameOrStruct ...interface{}) *gdb.Model {
return DB().Model(tableNameOrStruct...)
}
// Redis returns an instance of redis client with specified configuration group name.

View File

@ -9,6 +9,7 @@ package empty
import (
"reflect"
"time"
)
// apiString is used for type assert api for String().
@ -26,6 +27,11 @@ type apiMapStrAny interface {
MapStrAny() map[string]interface{}
}
type apiTime interface {
Date() (year int, month time.Month, day int)
IsZero() bool
}
// IsEmpty checks whether given `value` empty.
// It returns true if `value` is in: 0, nil, false, "", len(slice/map/chan) == 0,
// or else it returns false.
@ -80,6 +86,12 @@ func IsEmpty(value interface{}) bool {
return len(value) == 0
default:
// Common interfaces checks.
if f, ok := value.(apiTime); ok {
if f == nil {
return true
}
return f.IsZero()
}
if f, ok := value.(apiString); ok {
if f == nil {
return true

View File

@ -193,7 +193,7 @@ func (s *Server) Start() error {
// If this is a child process, it then notifies its parent exit.
if gproc.IsChild() {
gtimer.SetTimeout(2*time.Second, func() {
gtimer.SetTimeout(time.Duration(s.config.GracefulTimeout)*time.Second, func() {
if err := gproc.Send(gproc.PPid(), []byte("exit"), adminGProcCommGroup); err != nil {
//glog.Error("server error in process communication:", err)
}
@ -268,7 +268,7 @@ func (s *Server) GetRouterArray() []RouterItem {
item.Middleware += gdebug.FuncName(v)
}
}
// If the domain does not exist in the dump map, it create the map.
// If the domain does not exist in the dump map, it creates the map.
// The value of the map is a custom sorted array.
if _, ok := m[item.Domain]; !ok {
// Sort in ASC order.

View File

@ -218,6 +218,9 @@ type ServerConfig struct {
// Graceful enables graceful reload feature for all servers of the process.
Graceful bool `json:"graceful"`
// GracefulTimeout set the maximum survival time (seconds) of the parent process.
GracefulTimeout uint8 `json:"gracefulTimeout"`
}
// Deprecated. Use NewConfig instead.
@ -265,6 +268,7 @@ func NewConfig() ServerConfig {
FormParsingMemory: 1024 * 1024, // 1MB
Rewrites: make(map[string]string),
Graceful: false,
GracefulTimeout: 2, // seconds
}
}

View File

@ -80,9 +80,9 @@ func (s *Server) ServeHTTP(w http.ResponseWriter, r *http.Request) {
// Close the request and response body
// to release the file descriptor in time.
request.Request.Body.Close()
_ = request.Request.Body.Close()
if request.Request.Response != nil {
request.Request.Response.Body.Close()
_ = request.Request.Response.Body.Close()
}
}()
@ -168,7 +168,8 @@ func (s *Server) ServeHTTP(w http.ResponseWriter, r *http.Request) {
}
// Automatically set the session id to cookie
// if it creates a new session id in this request.
// if it creates a new session id in this request
// and SessionCookieOutput is enabled.
if s.config.SessionCookieOutput &&
request.Session.IsDirty() &&
request.Session.Id() != request.GetSessionId() {

View File

@ -220,6 +220,15 @@ func (t *Time) String() string {
return t.Format("Y-m-d H:i:s")
}
// IsZero reports whether t represents the zero time instant,
// January 1, year 1, 00:00:00 UTC.
func (t *Time) IsZero() bool {
if t == nil {
return true
}
return t.Time.IsZero()
}
// Clone returns a new Time object which is a clone of current time object.
func (t *Time) Clone() *Time {
return New(t.Time)

View File

@ -47,13 +47,11 @@ func (v *Validator) doCheck(key string, value interface{}, rules string, message
// It converts value to string and then does the validation.
var (
// Do not trim it as the space is also part of the value.
data = make(map[string]string)
data = make(map[string]interface{})
errorMsgArray = make(map[string]string)
)
if len(params) > 0 {
for k, v := range gconv.Map(params[0]) {
data[k] = gconv.String(v)
}
data = gconv.Map(params[0])
}
// Custom error messages handling.
var (
@ -150,7 +148,7 @@ func (v *Validator) doCheckBuildInRules(
ruleKey string,
rulePattern string,
ruleItems []string,
dataMap map[string]string,
dataMap map[string]interface{},
customMsgMap map[string]string,
) (match bool, err error) {
valueStr := gconv.String(value)
@ -235,7 +233,7 @@ func (v *Validator) doCheckBuildInRules(
// Values of two fields should be equal as string.
case "same":
if v, ok := dataMap[rulePattern]; ok {
if strings.Compare(valueStr, v) == 0 {
if strings.Compare(valueStr, gconv.String(v)) == 0 {
match = true
}
}
@ -250,7 +248,7 @@ func (v *Validator) doCheckBuildInRules(
case "different":
match = true
if v, ok := dataMap[rulePattern]; ok {
if strings.Compare(valueStr, v) == 0 {
if strings.Compare(valueStr, gconv.String(v)) == 0 {
match = false
}
}

View File

@ -7,6 +7,7 @@
package gvalid
import (
"github.com/gogf/gf/internal/empty"
"github.com/gogf/gf/util/gconv"
"reflect"
"strings"
@ -14,7 +15,7 @@ import (
// checkRequired checks `value` using required rules.
// It also supports require checks for `value` of type: slice, map.
func (v *Validator) checkRequired(value interface{}, ruleKey, rulePattern string, params map[string]string) bool {
func (v *Validator) checkRequired(value interface{}, ruleKey, rulePattern string, dataMap map[string]interface{}) bool {
required := false
switch ruleKey {
// Required.
@ -31,8 +32,8 @@ func (v *Validator) checkRequired(value interface{}, ruleKey, rulePattern string
for i := 0; i < len(array); {
tk := array[i]
tv := array[i+1]
if v, ok := params[tk]; ok {
if strings.Compare(tv, v) == 0 {
if v, ok := dataMap[tk]; ok {
if strings.Compare(tv, gconv.String(v)) == 0 {
required = true
break
}
@ -51,8 +52,8 @@ func (v *Validator) checkRequired(value interface{}, ruleKey, rulePattern string
for i := 0; i < len(array); {
tk := array[i]
tv := array[i+1]
if v, ok := params[tk]; ok {
if strings.Compare(tv, v) == 0 {
if v, ok := dataMap[tk]; ok {
if strings.Compare(tv, gconv.String(v)) == 0 {
required = false
break
}
@ -67,7 +68,7 @@ func (v *Validator) checkRequired(value interface{}, ruleKey, rulePattern string
required = false
array := strings.Split(rulePattern, ",")
for i := 0; i < len(array); i++ {
if params[array[i]] != "" {
if !empty.IsEmpty(dataMap[array[i]]) {
required = true
break
}
@ -79,7 +80,7 @@ func (v *Validator) checkRequired(value interface{}, ruleKey, rulePattern string
required = true
array := strings.Split(rulePattern, ",")
for i := 0; i < len(array); i++ {
if params[array[i]] == "" {
if empty.IsEmpty(dataMap[array[i]]) {
required = false
break
}
@ -91,7 +92,7 @@ func (v *Validator) checkRequired(value interface{}, ruleKey, rulePattern string
required = false
array := strings.Split(rulePattern, ",")
for i := 0; i < len(array); i++ {
if params[array[i]] == "" {
if empty.IsEmpty(dataMap[array[i]]) {
required = true
break
}
@ -103,7 +104,7 @@ func (v *Validator) checkRequired(value interface{}, ruleKey, rulePattern string
required = true
array := strings.Split(rulePattern, ",")
for i := 0; i < len(array); i++ {
if params[array[i]] != "" {
if !empty.IsEmpty(dataMap[array[i]]) {
required = false
break
}

View File

@ -88,6 +88,72 @@ func Test_RequiredWith(t *testing.T) {
t.AssertNE(err2, nil)
t.AssertNE(err3, nil)
})
// time.Time
gtest.C(t, func(t *gtest.T) {
rule := "required-with:id,time"
val1 := ""
params1 := g.Map{
"age": 18,
}
params2 := g.Map{
"id": 100,
}
params3 := g.Map{
"time": time.Time{},
}
err1 := gvalid.Check(val1, rule, nil, params1)
err2 := gvalid.Check(val1, rule, nil, params2)
err3 := gvalid.Check(val1, rule, nil, params3)
t.Assert(err1, nil)
t.AssertNE(err2, nil)
t.Assert(err3, nil)
})
gtest.C(t, func(t *gtest.T) {
rule := "required-with:id,time"
val1 := ""
params1 := g.Map{
"age": 18,
}
params2 := g.Map{
"id": 100,
}
params3 := g.Map{
"time": time.Now(),
}
err1 := gvalid.Check(val1, rule, nil, params1)
err2 := gvalid.Check(val1, rule, nil, params2)
err3 := gvalid.Check(val1, rule, nil, params3)
t.Assert(err1, nil)
t.AssertNE(err2, nil)
t.AssertNE(err3, nil)
})
// gtime.Time
gtest.C(t, func(t *gtest.T) {
type UserApiSearch struct {
Uid int64 `json:"uid"`
Nickname string `json:"nickname" v:"required-with:Uid"`
StartTime *gtime.Time `json:"start_time" v:"required-with:EndTime"`
EndTime *gtime.Time `json:"end_time" v:"required-with:StartTime"`
}
data := UserApiSearch{
StartTime: nil,
EndTime: nil,
}
t.Assert(gvalid.CheckStruct(data, nil), nil)
})
gtest.C(t, func(t *gtest.T) {
type UserApiSearch struct {
Uid int64 `json:"uid"`
Nickname string `json:"nickname" v:"required-with:Uid"`
StartTime *gtime.Time `json:"start_time" v:"required-with:EndTime"`
EndTime *gtime.Time `json:"end_time" v:"required-with:StartTime"`
}
data := UserApiSearch{
StartTime: nil,
EndTime: gtime.Now(),
}
t.AssertNE(gvalid.CheckStruct(data, nil), nil)
})
}
func Test_RequiredWithAll(t *testing.T) {

View File

@ -1,4 +1,4 @@
package gf
const VERSION = "v1.15.6"
const VERSION = "v1.15.7"
const AUTHORS = "john<john@goframe.org>"