mirror of
https://gitee.com/johng/gf
synced 2026-06-11 11:51:46 +08:00
Compare commits
63 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| dd5cd31ef5 | |||
| de92e804fe | |||
| 7725d9aaaf | |||
| bd3e25adea | |||
| 0b1d49af4b | |||
| 7efa9e351e | |||
| 0b0141954b | |||
| 82b531fbfb | |||
| 67fb626339 | |||
| 9044d5f05d | |||
| 47663aa1f1 | |||
| 63c0aab19c | |||
| 261216f5e4 | |||
| f88b799d67 | |||
| 9c48dd172c | |||
| 09ce105eee | |||
| 651aa895f8 | |||
| 0509e41198 | |||
| 1b40d6a53a | |||
| f9189d48d1 | |||
| 849874a247 | |||
| 3f6510bae7 | |||
| a585a26c39 | |||
| 9943966a86 | |||
| 3617e51c01 | |||
| c931032f08 | |||
| 37b286eaa4 | |||
| 619287c273 | |||
| aeb9b68298 | |||
| bae8f6315b | |||
| a6f70f8935 | |||
| acca6f4009 | |||
| 727fdd2391 | |||
| 1d174e00c0 | |||
| a5e3e2f5ba | |||
| da43c2d52f | |||
| 262f27748c | |||
| a29aef7e1c | |||
| 1671391195 | |||
| 28b0d59c61 | |||
| 86433cef25 | |||
| e96ccd5f71 | |||
| 50a087bb3d | |||
| 1ec049c52f | |||
| ec38805542 | |||
| 4c8517d075 | |||
| edf06da6ea | |||
| eb43a2040e | |||
| 9d0ecc7d3e | |||
| ad943c5e02 | |||
| bdb4fd0d25 | |||
| 2440e05457 | |||
| 1337c6c0d1 | |||
| 957689e07c | |||
| 3952d74f87 | |||
| 6dc4b81693 | |||
| 9cd953b7be | |||
| 631810dda2 | |||
| 8c12bc5506 | |||
| d4091a4826 | |||
| a7c269886b | |||
| f54593037b | |||
| 0415cf6a08 |
@ -1,10 +1,15 @@
|
||||
|
||||
# MySQL数据库配置
|
||||
# MySQL.
|
||||
[database]
|
||||
debug = true
|
||||
link = "mysql:root:12345678@tcp(127.0.0.1:3306)/test?parseTime=true&loc=Local"
|
||||
MaxOpen = 100
|
||||
|
||||
# Redis.
|
||||
[redis]
|
||||
default = "127.0.0.1:6379,0"
|
||||
cache = "127.0.0.1:6379,1"
|
||||
|
||||
#[database]
|
||||
# [[database.default]]
|
||||
# type = "mysql"
|
||||
|
||||
@ -3,6 +3,7 @@ package main
|
||||
import (
|
||||
"github.com/gogf/gf/database/gdb"
|
||||
"github.com/gogf/gf/util/gutil"
|
||||
"time"
|
||||
)
|
||||
|
||||
func main() {
|
||||
@ -10,7 +11,7 @@ func main() {
|
||||
Host: "127.0.0.1",
|
||||
Port: "3306",
|
||||
User: "root",
|
||||
Pass: "123456",
|
||||
Pass: "12345678",
|
||||
Name: "test",
|
||||
Type: "mysql",
|
||||
Role: "master",
|
||||
@ -20,19 +21,20 @@ func main() {
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
//db.GetCache().SetAdapter(adapter.NewRedis(g.Redis()))
|
||||
// 开启调试模式,以便于记录所有执行的SQL
|
||||
db.SetDebug(true)
|
||||
|
||||
// 执行2次查询并将查询结果缓存3秒,并可执行缓存名称(可选)
|
||||
for i := 0; i < 2; i++ {
|
||||
r, _ := db.Table("user").Cache(3, "vip-user").Where("uid=?", 1).One()
|
||||
gutil.Dump(r.ToMap())
|
||||
for i := 0; i < 3; i++ {
|
||||
r, _ := db.Table("user").Cache(3000*time.Second).Where("id=?", 1).One()
|
||||
gutil.Dump(r.Map())
|
||||
}
|
||||
|
||||
// 执行更新操作,并清理指定名称的查询缓存
|
||||
db.Table("user").Cache(-1, "vip-user").Data(gdb.Map{"name": "smith"}).Where("uid=?", 1).Update()
|
||||
//db.Table("user").Cache(-1, "vip-user").Data(gdb.Map{"name": "smith"}).Where("id=?", 1).Update()
|
||||
|
||||
// 再次执行查询,启用查询缓存特性
|
||||
r, _ := db.Table("user").Cache(3, "vip-user").Where("uid=?", 1).One()
|
||||
gutil.Dump(r.ToMap())
|
||||
//r, _ := db.Table("user").Cache(300000*time.Second, "vip-user").Where("id=?", 1).One()
|
||||
//gutil.Dump(r.Map())
|
||||
}
|
||||
|
||||
@ -8,9 +8,6 @@ import (
|
||||
|
||||
func main() {
|
||||
db := g.DB()
|
||||
db.SetMaxIdleConnCount(10)
|
||||
db.SetMaxOpenConnCount(10)
|
||||
db.SetMaxConnLifetime(time.Minute)
|
||||
|
||||
// 开启调试模式,以便于记录所有执行的SQL
|
||||
db.SetDebug(true)
|
||||
@ -19,7 +16,7 @@ func main() {
|
||||
for i := 0; i < 10; i++ {
|
||||
go db.Table("user").All()
|
||||
}
|
||||
time.Sleep(time.Second)
|
||||
time.Sleep(time.Millisecond * 100)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@ -1,12 +1,13 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"github.com/gogf/gf/frame/g"
|
||||
"github.com/gogf/gf/net/ghttp"
|
||||
)
|
||||
|
||||
func main() {
|
||||
s := ghttp.GetServer()
|
||||
s.EnablePProf()
|
||||
s := g.Server()
|
||||
s.Domain("localhost").EnablePProf()
|
||||
s.BindHandler("/", func(r *ghttp.Request) {
|
||||
r.Response.Writeln("哈喽世界!")
|
||||
})
|
||||
|
||||
@ -2,19 +2,18 @@ package main
|
||||
|
||||
import (
|
||||
"github.com/gogf/gf/frame/g"
|
||||
"github.com/gogf/gf/frame/gmvc"
|
||||
"github.com/gogf/gf/net/ghttp"
|
||||
)
|
||||
|
||||
type Controller struct {
|
||||
gmvc.Controller
|
||||
}
|
||||
|
||||
func (c *Controller) Test() {
|
||||
c.View.Display("layout.html")
|
||||
}
|
||||
func main() {
|
||||
s := g.Server()
|
||||
s.BindControllerMethod("/", new(Controller), "Test")
|
||||
s.BindHandler("/", func(r *ghttp.Request) {
|
||||
r.Response.WriteTpl("layout.html", g.Map{
|
||||
"header": "This is header",
|
||||
"container": "This is container",
|
||||
"footer": "This is footer",
|
||||
})
|
||||
})
|
||||
s.SetPort(8199)
|
||||
s.Run()
|
||||
}
|
||||
|
||||
@ -1,3 +1,3 @@
|
||||
{{define "container"}}
|
||||
<h1>CONTAINER</h1>
|
||||
<h1>{{.container}}</h1>
|
||||
{{end}}
|
||||
@ -1,3 +1,3 @@
|
||||
{{define "footer"}}
|
||||
<h1>FOOTER</h1>
|
||||
<h1>{{.footer}}</h1>
|
||||
{{end}}
|
||||
@ -1,3 +1,3 @@
|
||||
{{define "header"}}
|
||||
<h1>HEADER</h1>
|
||||
<h1>{{.header}}</h1>
|
||||
{{end}}
|
||||
@ -2,14 +2,14 @@
|
||||
<html>
|
||||
<head>
|
||||
<title>GoFrame Layout</title>
|
||||
{{template "header"}}
|
||||
{{template "header" .}}
|
||||
</head>
|
||||
<body>
|
||||
<div class="container">
|
||||
{{template "container"}}
|
||||
{{template "container" .}}
|
||||
</div>
|
||||
<div class="footer">
|
||||
{{template "footer"}}
|
||||
{{template "footer" .}}
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
@ -9,11 +9,13 @@ func main() {
|
||||
s := g.Server()
|
||||
s.BindHandler("/main1", func(r *ghttp.Request) {
|
||||
r.Response.WriteTpl("layout.html", g.Map{
|
||||
"name": "smith",
|
||||
"mainTpl": "main/main1.html",
|
||||
})
|
||||
})
|
||||
s.BindHandler("/main2", func(r *ghttp.Request) {
|
||||
r.Response.WriteTpl("layout.html", g.Map{
|
||||
"name": "john",
|
||||
"mainTpl": "main/main2.html",
|
||||
})
|
||||
})
|
||||
|
||||
@ -115,6 +115,10 @@ please note your github/gitee account in your payment bill. All the donations wi
|
||||
|[hyuant](https://github.com/hyuant)|alipay|¥66.66|
|
||||
|*庆|alipay|¥9.99| 支持一下,gf越来越好
|
||||
|**君|alipay|¥10.00| 加油
|
||||
|向回走的闹钟|wechat|¥20.00| 越来越好
|
||||
|金毛|wechat|¥100.00|
|
||||
|莫失莫忘|wechat|¥100.00|
|
||||
|**航|alipay|¥20.00|
|
||||
|
||||
|
||||
|
||||
|
||||
@ -457,6 +457,12 @@ func (m *AnyAnyMap) Merge(other *AnyAnyMap) {
|
||||
}
|
||||
}
|
||||
|
||||
// String returns the map as a string.
|
||||
func (m *AnyAnyMap) String() string {
|
||||
b, _ := m.MarshalJSON()
|
||||
return gconv.UnsafeBytesToStr(b)
|
||||
}
|
||||
|
||||
// MarshalJSON implements the interface MarshalJSON for json.Marshal.
|
||||
func (m *AnyAnyMap) MarshalJSON() ([]byte, error) {
|
||||
return json.Marshal(gconv.Map(m.Map()))
|
||||
|
||||
@ -455,6 +455,12 @@ func (m *IntAnyMap) Merge(other *IntAnyMap) {
|
||||
}
|
||||
}
|
||||
|
||||
// String returns the map as a string.
|
||||
func (m *IntAnyMap) String() string {
|
||||
b, _ := m.MarshalJSON()
|
||||
return gconv.UnsafeBytesToStr(b)
|
||||
}
|
||||
|
||||
// MarshalJSON implements the interface MarshalJSON for json.Marshal.
|
||||
func (m *IntAnyMap) MarshalJSON() ([]byte, error) {
|
||||
m.mu.RLock()
|
||||
|
||||
@ -426,6 +426,12 @@ func (m *IntIntMap) Merge(other *IntIntMap) {
|
||||
}
|
||||
}
|
||||
|
||||
// String returns the map as a string.
|
||||
func (m *IntIntMap) String() string {
|
||||
b, _ := m.MarshalJSON()
|
||||
return gconv.UnsafeBytesToStr(b)
|
||||
}
|
||||
|
||||
// MarshalJSON implements the interface MarshalJSON for json.Marshal.
|
||||
func (m *IntIntMap) MarshalJSON() ([]byte, error) {
|
||||
m.mu.RLock()
|
||||
|
||||
@ -426,6 +426,12 @@ func (m *IntStrMap) Merge(other *IntStrMap) {
|
||||
}
|
||||
}
|
||||
|
||||
// String returns the map as a string.
|
||||
func (m *IntStrMap) String() string {
|
||||
b, _ := m.MarshalJSON()
|
||||
return gconv.UnsafeBytesToStr(b)
|
||||
}
|
||||
|
||||
// MarshalJSON implements the interface MarshalJSON for json.Marshal.
|
||||
func (m *IntStrMap) MarshalJSON() ([]byte, error) {
|
||||
m.mu.RLock()
|
||||
|
||||
@ -451,6 +451,12 @@ func (m *StrAnyMap) Merge(other *StrAnyMap) {
|
||||
}
|
||||
}
|
||||
|
||||
// String returns the map as a string.
|
||||
func (m *StrAnyMap) String() string {
|
||||
b, _ := m.MarshalJSON()
|
||||
return gconv.UnsafeBytesToStr(b)
|
||||
}
|
||||
|
||||
// MarshalJSON implements the interface MarshalJSON for json.Marshal.
|
||||
func (m *StrAnyMap) MarshalJSON() ([]byte, error) {
|
||||
m.mu.RLock()
|
||||
|
||||
@ -429,6 +429,12 @@ func (m *StrIntMap) Merge(other *StrIntMap) {
|
||||
}
|
||||
}
|
||||
|
||||
// String returns the map as a string.
|
||||
func (m *StrIntMap) String() string {
|
||||
b, _ := m.MarshalJSON()
|
||||
return gconv.UnsafeBytesToStr(b)
|
||||
}
|
||||
|
||||
// MarshalJSON implements the interface MarshalJSON for json.Marshal.
|
||||
func (m *StrIntMap) MarshalJSON() ([]byte, error) {
|
||||
m.mu.RLock()
|
||||
|
||||
@ -429,6 +429,12 @@ func (m *StrStrMap) Merge(other *StrStrMap) {
|
||||
}
|
||||
}
|
||||
|
||||
// String returns the map as a string.
|
||||
func (m *StrStrMap) String() string {
|
||||
b, _ := m.MarshalJSON()
|
||||
return gconv.UnsafeBytesToStr(b)
|
||||
}
|
||||
|
||||
// MarshalJSON implements the interface MarshalJSON for json.Marshal.
|
||||
func (m *StrStrMap) MarshalJSON() ([]byte, error) {
|
||||
m.mu.RLock()
|
||||
|
||||
@ -510,6 +510,12 @@ func (m *ListMap) Merge(other *ListMap) {
|
||||
})
|
||||
}
|
||||
|
||||
// String returns the map as a string.
|
||||
func (m *ListMap) String() string {
|
||||
b, _ := m.MarshalJSON()
|
||||
return gconv.UnsafeBytesToStr(b)
|
||||
}
|
||||
|
||||
// MarshalJSON implements the interface MarshalJSON for json.Marshal.
|
||||
func (m *ListMap) MarshalJSON() ([]byte, error) {
|
||||
return json.Marshal(gconv.Map(m.Map()))
|
||||
|
||||
@ -156,7 +156,7 @@ type DB interface {
|
||||
// Internal methods.
|
||||
// ===========================================================================
|
||||
|
||||
filterFields(schema, table string, data map[string]interface{}) map[string]interface{}
|
||||
mappingAndFilterData(schema, table string, data map[string]interface{}, filter bool) (map[string]interface{}, error)
|
||||
convertValue(fieldValue interface{}, fieldType string) interface{}
|
||||
rowsToResult(rows *sql.Rows) (Result, error)
|
||||
}
|
||||
@ -166,7 +166,7 @@ type Core struct {
|
||||
DB DB // DB interface object.
|
||||
group string // Configuration group name.
|
||||
debug *gtype.Bool // Enable debug mode for the database.
|
||||
cache *gcache.Cache // Cache manager.
|
||||
cache *gcache.Cache // Cache manager, SQL result cache only.
|
||||
schema *gtype.String // Custom schema for this object.
|
||||
dryrun *gtype.Bool // Dry run.
|
||||
prefix string // Table prefix.
|
||||
@ -283,10 +283,10 @@ func Register(name string, driver Driver) error {
|
||||
// New creates and returns an ORM object with global configurations.
|
||||
// The parameter <name> specifies the configuration group name,
|
||||
// which is DEFAULT_GROUP_NAME in default.
|
||||
func New(name ...string) (db DB, err error) {
|
||||
group := configs.group
|
||||
if len(name) > 0 && name[0] != "" {
|
||||
group = name[0]
|
||||
func New(group ...string) (db DB, err error) {
|
||||
groupName := configs.group
|
||||
if len(group) > 0 && group[0] != "" {
|
||||
groupName = group[0]
|
||||
}
|
||||
configs.RLock()
|
||||
defer configs.RUnlock()
|
||||
@ -294,10 +294,10 @@ func New(name ...string) (db DB, err error) {
|
||||
if len(configs.config) < 1 {
|
||||
return nil, errors.New("empty database configuration")
|
||||
}
|
||||
if _, ok := configs.config[group]; ok {
|
||||
if node, err := getConfigNodeByGroup(group, true); err == nil {
|
||||
if _, ok := configs.config[groupName]; ok {
|
||||
if node, err := getConfigNodeByGroup(groupName, true); err == nil {
|
||||
c := &Core{
|
||||
group: group,
|
||||
group: groupName,
|
||||
debug: gtype.NewBool(),
|
||||
cache: gcache.New(),
|
||||
schema: gtype.NewString(),
|
||||
@ -320,7 +320,7 @@ func New(name ...string) (db DB, err error) {
|
||||
return nil, err
|
||||
}
|
||||
} else {
|
||||
return nil, errors.New(fmt.Sprintf(`database configuration node "%s" is not found`, group))
|
||||
return nil, errors.New(fmt.Sprintf(`database configuration node "%s" is not found`, groupName))
|
||||
}
|
||||
}
|
||||
|
||||
@ -438,11 +438,11 @@ func (c *Core) getSqlDb(master bool, schema ...string) (sqlDb *sql.DB, err error
|
||||
node = &n
|
||||
}
|
||||
// Cache the underlying connection pool object by node.
|
||||
v := c.cache.GetOrSetFuncLock(node.String(), func() interface{} {
|
||||
v, _ := gcache.GetOrSetFuncLock(node.String(), func() (interface{}, error) {
|
||||
sqlDb, err = c.DB.Open(node)
|
||||
if err != nil {
|
||||
intlog.Printf("DB open failed: %v, %+v", err, node)
|
||||
return nil
|
||||
return nil, err
|
||||
}
|
||||
if c.maxIdleConnCount > 0 {
|
||||
sqlDb.SetMaxIdleConns(c.maxIdleConnCount)
|
||||
@ -461,7 +461,7 @@ func (c *Core) getSqlDb(master bool, schema ...string) (sqlDb *sql.DB, err error
|
||||
} else if node.MaxConnLifetime > 0 {
|
||||
sqlDb.SetConnMaxLifetime(node.MaxConnLifetime * time.Second)
|
||||
}
|
||||
return sqlDb
|
||||
return sqlDb, nil
|
||||
}, 0)
|
||||
if v != nil && sqlDb == nil {
|
||||
sqlDb = v.(*sql.DB)
|
||||
|
||||
@ -310,6 +310,11 @@ func (c *Core) Transaction(f func(tx *TX) error) (err error) {
|
||||
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
|
||||
@ -334,7 +339,10 @@ func (c *Core) Transaction(f func(tx *TX) error) (err error) {
|
||||
//
|
||||
// The parameter <batch> specifies the batch operation count when given data is slice.
|
||||
func (c *Core) Insert(table string, data interface{}, batch ...int) (sql.Result, error) {
|
||||
return c.DB.DoInsert(nil, table, data, gINSERT_OPTION_DEFAULT, batch...)
|
||||
if len(batch) > 0 {
|
||||
return c.Model(table).Data(data).Batch(batch[0]).Insert()
|
||||
}
|
||||
return c.Model(table).Data(data).Insert()
|
||||
}
|
||||
|
||||
// InsertIgnore does "INSERT IGNORE INTO ..." statement for the table.
|
||||
@ -347,7 +355,10 @@ func (c *Core) Insert(table string, data interface{}, batch ...int) (sql.Result,
|
||||
//
|
||||
// The parameter <batch> specifies the batch operation count when given data is slice.
|
||||
func (c *Core) InsertIgnore(table string, data interface{}, batch ...int) (sql.Result, error) {
|
||||
return c.DB.DoInsert(nil, table, data, gINSERT_OPTION_IGNORE, batch...)
|
||||
if len(batch) > 0 {
|
||||
return c.Model(table).Data(data).Batch(batch[0]).InsertIgnore()
|
||||
}
|
||||
return c.Model(table).Data(data).InsertIgnore()
|
||||
}
|
||||
|
||||
// Replace does "REPLACE INTO ..." statement for the table.
|
||||
@ -363,7 +374,10 @@ func (c *Core) InsertIgnore(table string, data interface{}, batch ...int) (sql.R
|
||||
// If given data is type of slice, it then does batch replacing, and the optional parameter
|
||||
// <batch> specifies the batch operation count.
|
||||
func (c *Core) Replace(table string, data interface{}, batch ...int) (sql.Result, error) {
|
||||
return c.DB.DoInsert(nil, table, data, gINSERT_OPTION_REPLACE, batch...)
|
||||
if len(batch) > 0 {
|
||||
return c.Model(table).Data(data).Batch(batch[0]).Replace()
|
||||
}
|
||||
return c.Model(table).Data(data).Replace()
|
||||
}
|
||||
|
||||
// Save does "INSERT INTO ... ON DUPLICATE KEY UPDATE..." statement for the table.
|
||||
@ -378,11 +392,14 @@ func (c *Core) Replace(table string, data interface{}, batch ...int) (sql.Result
|
||||
// If given data is type of slice, it then does batch saving, and the optional parameter
|
||||
// <batch> specifies the batch operation count.
|
||||
func (c *Core) Save(table string, data interface{}, batch ...int) (sql.Result, error) {
|
||||
return c.DB.DoInsert(nil, table, data, gINSERT_OPTION_SAVE, batch...)
|
||||
if len(batch) > 0 {
|
||||
return c.Model(table).Data(data).Batch(batch[0]).Save()
|
||||
}
|
||||
return c.Model(table).Data(data).Save()
|
||||
}
|
||||
|
||||
// doInsert inserts or updates data for given table.
|
||||
//
|
||||
// This function is usually used for custom interface definition, you do not need call it manually.
|
||||
// The parameter <data> can be type of map/gmap/struct/*struct/[]map/[]struct, etc.
|
||||
// Eg:
|
||||
// Data(g.Map{"uid": 10000, "name":"john"})
|
||||
@ -410,8 +427,14 @@ func (c *Core) DoInsert(link Link, table string, data interface{}, option int, b
|
||||
switch reflectKind {
|
||||
case reflect.Slice, reflect.Array:
|
||||
return c.DB.DoBatchInsert(link, table, data, option, batch...)
|
||||
case reflect.Map, reflect.Struct:
|
||||
dataMap = DataToMapDeep(data)
|
||||
case reflect.Struct:
|
||||
if _, ok := data.(apiInterfaces); ok {
|
||||
return c.DB.DoBatchInsert(link, table, data, option, batch...)
|
||||
} else {
|
||||
dataMap = ConvertDataForTableRecord(data)
|
||||
}
|
||||
case reflect.Map:
|
||||
dataMap = ConvertDataForTableRecord(data)
|
||||
default:
|
||||
return result, errors.New(fmt.Sprint("unsupported data type:", reflectKind))
|
||||
}
|
||||
@ -465,35 +488,48 @@ func (c *Core) DoInsert(link Link, table string, data interface{}, option int, b
|
||||
// BatchInsert batch inserts data.
|
||||
// The parameter <list> must be type of slice of map or struct.
|
||||
func (c *Core) BatchInsert(table string, list interface{}, batch ...int) (sql.Result, error) {
|
||||
return c.DB.DoBatchInsert(nil, table, list, gINSERT_OPTION_DEFAULT, batch...)
|
||||
if len(batch) > 0 {
|
||||
return c.Model(table).Data(list).Batch(batch[0]).Insert()
|
||||
}
|
||||
return c.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 (c *Core) BatchInsertIgnore(table string, list interface{}, batch ...int) (sql.Result, error) {
|
||||
return c.DB.DoBatchInsert(nil, table, list, gINSERT_OPTION_IGNORE, batch...)
|
||||
if len(batch) > 0 {
|
||||
return c.Model(table).Data(list).Batch(batch[0]).InsertIgnore()
|
||||
}
|
||||
return c.Model(table).Data(list).InsertIgnore()
|
||||
}
|
||||
|
||||
// BatchReplace batch replaces data.
|
||||
// The parameter <list> must be type of slice of map or struct.
|
||||
func (c *Core) BatchReplace(table string, list interface{}, batch ...int) (sql.Result, error) {
|
||||
return c.DB.DoBatchInsert(nil, table, list, gINSERT_OPTION_REPLACE, batch...)
|
||||
if len(batch) > 0 {
|
||||
return c.Model(table).Data(list).Batch(batch[0]).Replace()
|
||||
}
|
||||
return c.Model(table).Data(list).Replace()
|
||||
}
|
||||
|
||||
// BatchSave batch replaces data.
|
||||
// The parameter <list> must be type of slice of map or struct.
|
||||
func (c *Core) BatchSave(table string, list interface{}, batch ...int) (sql.Result, error) {
|
||||
return c.DB.DoBatchInsert(nil, table, list, gINSERT_OPTION_SAVE, batch...)
|
||||
if len(batch) > 0 {
|
||||
return c.Model(table).Data(list).Batch(batch[0]).Save()
|
||||
}
|
||||
return c.Model(table).Data(list).Save()
|
||||
}
|
||||
|
||||
// DoBatchInsert batch inserts/replaces/saves data.
|
||||
// This function is usually used for custom interface definition, you do not need call it manually.
|
||||
func (c *Core) DoBatchInsert(link Link, table string, list interface{}, option int, batch ...int) (result sql.Result, err error) {
|
||||
table = c.DB.QuotePrefixTableName(table)
|
||||
var (
|
||||
keys []string
|
||||
values []string
|
||||
params []interface{}
|
||||
listMap List
|
||||
keys []string // Field names.
|
||||
values []string // Value holder string array, like: (?,?,?)
|
||||
params []interface{} // Values that will be committed to underlying database driver.
|
||||
listMap List // The data list that passed from caller.
|
||||
)
|
||||
switch value := list.(type) {
|
||||
case Result:
|
||||
@ -518,10 +554,10 @@ func (c *Core) DoBatchInsert(link Link, table string, list interface{}, option i
|
||||
case reflect.Slice, reflect.Array:
|
||||
listMap = make(List, rv.Len())
|
||||
for i := 0; i < rv.Len(); i++ {
|
||||
listMap[i] = DataToMapDeep(rv.Index(i).Interface())
|
||||
listMap[i] = ConvertDataForTableRecord(rv.Index(i).Interface())
|
||||
}
|
||||
case reflect.Map:
|
||||
listMap = List{DataToMapDeep(value)}
|
||||
listMap = List{ConvertDataForTableRecord(value)}
|
||||
case reflect.Struct:
|
||||
if v, ok := value.(apiInterfaces); ok {
|
||||
var (
|
||||
@ -529,11 +565,11 @@ func (c *Core) DoBatchInsert(link Link, table string, list interface{}, option i
|
||||
list = make(List, len(array))
|
||||
)
|
||||
for i := 0; i < len(array); i++ {
|
||||
list[i] = DataToMapDeep(array[i])
|
||||
list[i] = ConvertDataForTableRecord(array[i])
|
||||
}
|
||||
listMap = list
|
||||
} else {
|
||||
listMap = List{DataToMapDeep(value)}
|
||||
listMap = List{ConvertDataForTableRecord(value)}
|
||||
}
|
||||
default:
|
||||
return result, errors.New(fmt.Sprint("unsupported list type:", kind))
|
||||
@ -636,15 +672,11 @@ func (c *Core) DoBatchInsert(link Link, table string, list interface{}, option i
|
||||
// "age IN(?,?)", 18, 50
|
||||
// User{ Id : 1, UserName : "john"}
|
||||
func (c *Core) Update(table string, data interface{}, condition interface{}, args ...interface{}) (sql.Result, error) {
|
||||
newWhere, newArgs := formatWhere(c.DB, condition, args, false)
|
||||
if newWhere != "" {
|
||||
newWhere = " WHERE " + newWhere
|
||||
}
|
||||
return c.DB.DoUpdate(nil, table, data, newWhere, newArgs...)
|
||||
return c.Model(table).Data(data).Where(condition, args...).Update()
|
||||
}
|
||||
|
||||
// doUpdate does "UPDATE ... " statement for the table.
|
||||
// Also see Update.
|
||||
// This function is usually used for custom interface definition, you do not need call it manually.
|
||||
func (c *Core) DoUpdate(link Link, table string, data interface{}, condition string, args ...interface{}) (result sql.Result, err error) {
|
||||
table = c.DB.QuotePrefixTableName(table)
|
||||
var (
|
||||
@ -663,7 +695,7 @@ func (c *Core) DoUpdate(link Link, table string, data interface{}, condition str
|
||||
case reflect.Map, reflect.Struct:
|
||||
var (
|
||||
fields []string
|
||||
dataMap = DataToMapDeep(data)
|
||||
dataMap = ConvertDataForTableRecord(data)
|
||||
)
|
||||
for k, v := range dataMap {
|
||||
fields = append(fields, c.DB.QuoteWord(k)+"=?")
|
||||
@ -704,15 +736,11 @@ func (c *Core) DoUpdate(link Link, table string, data interface{}, condition str
|
||||
// "age IN(?,?)", 18, 50
|
||||
// User{ Id : 1, UserName : "john"}
|
||||
func (c *Core) Delete(table string, condition interface{}, args ...interface{}) (result sql.Result, err error) {
|
||||
newWhere, newArgs := formatWhere(c.DB, condition, args, false)
|
||||
if newWhere != "" {
|
||||
newWhere = " WHERE " + newWhere
|
||||
}
|
||||
return c.DB.DoDelete(nil, table, newWhere, newArgs...)
|
||||
return c.Model(table).Where(condition, args...).Delete()
|
||||
}
|
||||
|
||||
// DoDelete does "DELETE FROM ... " statement for the table.
|
||||
// Also see Delete.
|
||||
// This function is usually used for custom interface definition, you do not need call it manually.
|
||||
func (c *Core) DoDelete(link Link, table string, condition string, args ...interface{}) (result sql.Result, err error) {
|
||||
if link == nil {
|
||||
if link, err = c.DB.Master(); err != nil {
|
||||
|
||||
@ -115,6 +115,14 @@ func GetDefaultGroup() string {
|
||||
return configs.group
|
||||
}
|
||||
|
||||
// IsConfigured checks and returns whether the database configured.
|
||||
// It returns true if any configuration exists.
|
||||
func IsConfigured() bool {
|
||||
configs.RLock()
|
||||
defer configs.RUnlock()
|
||||
return len(configs.config) > 0
|
||||
}
|
||||
|
||||
// SetLogger sets the logger for orm.
|
||||
func (c *Core) SetLogger(logger *glog.Logger) {
|
||||
c.logger = logger
|
||||
|
||||
@ -7,6 +7,8 @@
|
||||
package gdb
|
||||
|
||||
import (
|
||||
"github.com/gogf/gf/errors/gerror"
|
||||
"github.com/gogf/gf/util/gutil"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
@ -145,15 +147,50 @@ func (c *Core) convertValue(fieldValue interface{}, fieldType string) interface{
|
||||
}
|
||||
|
||||
// filterFields removes all key-value pairs which are not the field of given table.
|
||||
func (c *Core) filterFields(schema, table string, data map[string]interface{}) map[string]interface{} {
|
||||
// It must use data copy here to avoid its changing the origin data map.
|
||||
newDataMap := make(map[string]interface{}, len(data))
|
||||
if fields, err := c.DB.TableFields(table, schema); err == nil {
|
||||
for k, v := range data {
|
||||
if _, ok := fields[k]; ok {
|
||||
newDataMap[k] = v
|
||||
func (c *Core) mappingAndFilterData(schema, table string, data map[string]interface{}, filter bool) (map[string]interface{}, error) {
|
||||
if fieldsMap, err := c.DB.TableFields(table, schema); err == nil {
|
||||
fieldsKeyMap := make(map[string]interface{}, len(fieldsMap))
|
||||
for k, _ := range fieldsMap {
|
||||
fieldsKeyMap[k] = nil
|
||||
}
|
||||
// Automatic data key to table field name mapping.
|
||||
var foundKey string
|
||||
for dataKey, dataValue := range data {
|
||||
if _, ok := fieldsKeyMap[dataKey]; !ok {
|
||||
foundKey, _ = gutil.MapPossibleItemByKey(fieldsKeyMap, dataKey)
|
||||
if foundKey != "" {
|
||||
data[foundKey] = dataValue
|
||||
delete(data, dataKey)
|
||||
} else if !filter {
|
||||
if schema != "" {
|
||||
return nil, gerror.Newf(`no column of name "%s" found for table "%s" in schema "%s"`, dataKey, table, schema)
|
||||
}
|
||||
return nil, gerror.Newf(`no column of name "%s" found for table "%s"`, dataKey, table)
|
||||
}
|
||||
}
|
||||
}
|
||||
// Data filtering.
|
||||
if filter {
|
||||
for dataKey, _ := range data {
|
||||
if _, ok := fieldsMap[dataKey]; !ok {
|
||||
delete(data, dataKey)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return newDataMap
|
||||
return data, nil
|
||||
}
|
||||
|
||||
//// filterFields removes all key-value pairs which are not the field of given table.
|
||||
//func (c *Core) filterFields(schema, table string, data map[string]interface{}) map[string]interface{} {
|
||||
// // It must use data copy here to avoid its changing the origin data map.
|
||||
// newDataMap := make(map[string]interface{}, len(data))
|
||||
// if fields, err := c.DB.TableFields(table, schema); err == nil {
|
||||
// for k, v := range data {
|
||||
// if _, ok := fields[k]; ok {
|
||||
// newDataMap[k] = v
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// return newDataMap
|
||||
//}
|
||||
@ -15,6 +15,7 @@ import (
|
||||
"database/sql"
|
||||
"errors"
|
||||
"fmt"
|
||||
"github.com/gogf/gf/os/gcache"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
@ -198,47 +199,58 @@ func (d *DriverMssql) TableFields(table string, schema ...string) (fields map[st
|
||||
if len(schema) > 0 && schema[0] != "" {
|
||||
checkSchema = schema[0]
|
||||
}
|
||||
v := d.DB.GetCache().GetOrSetFunc(
|
||||
fmt.Sprintf(`mssql_table_fields_%s_%s`, table, checkSchema), func() interface{} {
|
||||
var result Result
|
||||
var link *sql.DB
|
||||
v, _ := gcache.GetOrSetFunc(
|
||||
fmt.Sprintf(`mssql_table_fields_%s_%s`, table, checkSchema),
|
||||
func() (interface{}, error) {
|
||||
var (
|
||||
result Result
|
||||
link *sql.DB
|
||||
)
|
||||
link, err = d.DB.GetSlave(checkSchema)
|
||||
if err != nil {
|
||||
return nil
|
||||
return nil, err
|
||||
}
|
||||
result, err = d.DB.DoGetAll(link, fmt.Sprintf(`
|
||||
SELECT a.name Field,
|
||||
structureSql := fmt.Sprintf(`
|
||||
SELECT
|
||||
a.name Field,
|
||||
CASE b.name
|
||||
WHEN 'datetime' THEN 'datetime'
|
||||
WHEN 'numeric' THEN b.name + '(' + convert(varchar(20),a.xprec) + ',' + convert(varchar(20),a.xscale) + ')'
|
||||
WHEN 'char' THEN b.name + '(' + convert(varchar(20),a.length)+ ')'
|
||||
WHEN 'varchar' THEN b.name + '(' + convert(varchar(20),a.length)+ ')'
|
||||
ELSE b.name + '(' + convert(varchar(20),a.length)+ ')' END as TYPE,
|
||||
case when a.isnullable=1 then 'YES'else 'NO' end as [Null],
|
||||
case when exists(SELECT 1 FROM sysobjects where xtype='PK' and name in (
|
||||
SELECT name FROM sysindexes WHERE indid in(
|
||||
SELECT indid FROM sysindexkeys WHERE id = a.id AND colid=a.colid
|
||||
))) then 'PRI' else '' end AS [Key],
|
||||
case when COLUMNPROPERTY(a.id,a.name,'IsIdentity')=1 then 'auto_increment'else '' end Extra,
|
||||
isnull(e.text,'') as [Default],
|
||||
WHEN 'numeric' THEN b.name + '(' + convert(varchar(20), a.xprec) + ',' + convert(varchar(20), a.xscale) + ')'
|
||||
WHEN 'char' THEN b.name + '(' + convert(varchar(20), a.length)+ ')'
|
||||
WHEN 'varchar' THEN b.name + '(' + convert(varchar(20), a.length)+ ')'
|
||||
ELSE b.name + '(' + convert(varchar(20),a.length)+ ')' END AS Type,
|
||||
CASE WHEN a.isnullable=1 THEN 'YES' ELSE 'NO' end AS [Null],
|
||||
CASE WHEN exists (
|
||||
SELECT 1 FROM sysobjects WHERE xtype='PK' AND name IN (
|
||||
SELECT name FROM sysindexes WHERE indid IN (
|
||||
SELECT indid FROM sysindexkeys WHERE id = a.id AND colid=a.colid
|
||||
)
|
||||
)
|
||||
) THEN 'PRI' ELSE '' END AS [Key],
|
||||
CASE WHEN COLUMNPROPERTY(a.id,a.name,'IsIdentity')=1 THEN 'auto_increment' ELSE '' END Extra,
|
||||
isnull(e.text,'') AS [Default],
|
||||
isnull(g.[value],'') AS [Comment]
|
||||
FROM syscolumns a
|
||||
left join systypes b on a.xtype=b.xusertype
|
||||
inner join sysobjects d on a.id=d.id and d.xtype='U' and d.name<>'dtproperties'
|
||||
left join syscomments e on a.cdefault=e.id
|
||||
left join sys.extended_properties g on a.id=g.major_id and a.colid=g.minor_id
|
||||
left join sys.extended_properties f on d.id=f.major_id and f.minor_id =0
|
||||
where d.name='%s'
|
||||
order by a.id,a.colorder`, strings.ToUpper(table)))
|
||||
FROM syscolumns a
|
||||
LEFT JOIN systypes b ON a.xtype=b.xtype AND a.xusertype=b.xusertype
|
||||
INNER JOIN sysobjects d ON a.id=d.id AND d.xtype='U' AND d.name<>'dtproperties'
|
||||
LEFT JOIN syscomments e ON a.cdefault=e.id
|
||||
LEFT JOIN sys.extended_properties g ON a.id=g.major_id AND a.colid=g.minor_id
|
||||
LEFT JOIN sys.extended_properties f ON d.id=f.major_id AND f.minor_id =0
|
||||
WHERE d.name='%s'
|
||||
ORDER BY a.id,a.colorder`,
|
||||
strings.ToUpper(table),
|
||||
)
|
||||
structureSql, _ = gregex.ReplaceString(`[\n\r\s]+`, " ", gstr.Trim(structureSql))
|
||||
result, err = d.DB.DoGetAll(link, structureSql)
|
||||
if err != nil {
|
||||
return nil
|
||||
return nil, err
|
||||
}
|
||||
fields = make(map[string]*TableField)
|
||||
for i, m := range result {
|
||||
fields[strings.ToLower(m["FIELD"].String())] = &TableField{
|
||||
fields[strings.ToLower(m["Field"].String())] = &TableField{
|
||||
Index: i,
|
||||
Name: strings.ToLower(m["FIELD"].String()),
|
||||
Type: strings.ToLower(m["TYPE"].String()),
|
||||
Name: strings.ToLower(m["Field"].String()),
|
||||
Type: strings.ToLower(m["Type"].String()),
|
||||
Null: m["Null"].Bool(),
|
||||
Key: m["Key"].String(),
|
||||
Default: m["Default"].Val(),
|
||||
@ -246,7 +258,7 @@ func (d *DriverMssql) TableFields(table string, schema ...string) (fields map[st
|
||||
Comment: m["Comment"].String(),
|
||||
}
|
||||
}
|
||||
return fields
|
||||
return fields, nil
|
||||
}, 0)
|
||||
if err == nil {
|
||||
fields = v.(map[string]*TableField)
|
||||
|
||||
@ -11,6 +11,7 @@ import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"github.com/gogf/gf/internal/intlog"
|
||||
"github.com/gogf/gf/os/gcache"
|
||||
"github.com/gogf/gf/text/gregex"
|
||||
"github.com/gogf/gf/text/gstr"
|
||||
|
||||
@ -102,21 +103,23 @@ func (d *DriverMysql) TableFields(table string, schema ...string) (fields map[st
|
||||
if len(schema) > 0 && schema[0] != "" {
|
||||
checkSchema = schema[0]
|
||||
}
|
||||
v := d.cache.GetOrSetFunc(
|
||||
v, _ := gcache.GetOrSetFunc(
|
||||
fmt.Sprintf(`mysql_table_fields_%s_%s`, table, checkSchema),
|
||||
func() interface{} {
|
||||
var result Result
|
||||
var link *sql.DB
|
||||
func() (interface{}, error) {
|
||||
var (
|
||||
result Result
|
||||
link *sql.DB
|
||||
)
|
||||
link, err = d.DB.GetSlave(checkSchema)
|
||||
if err != nil {
|
||||
return nil
|
||||
return nil, err
|
||||
}
|
||||
result, err = d.DB.DoGetAll(
|
||||
link,
|
||||
fmt.Sprintf(`SHOW FULL COLUMNS FROM %s`, d.DB.QuoteWord(table)),
|
||||
)
|
||||
if err != nil {
|
||||
return nil
|
||||
return nil, err
|
||||
}
|
||||
fields = make(map[string]*TableField)
|
||||
for i, m := range result {
|
||||
@ -131,7 +134,7 @@ func (d *DriverMysql) TableFields(table string, schema ...string) (fields map[st
|
||||
Comment: m["Comment"].String(),
|
||||
}
|
||||
}
|
||||
return fields
|
||||
return fields, nil
|
||||
}, 0)
|
||||
if err == nil {
|
||||
fields = v.(map[string]*TableField)
|
||||
|
||||
@ -16,6 +16,7 @@ import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"github.com/gogf/gf/internal/intlog"
|
||||
"github.com/gogf/gf/os/gcache"
|
||||
"github.com/gogf/gf/text/gstr"
|
||||
"reflect"
|
||||
"strconv"
|
||||
@ -78,44 +79,45 @@ func (d *DriverOracle) HandleSqlBeforeCommit(link Link, sql string, args []inter
|
||||
// parseSql does some replacement of the sql before commits it to underlying driver,
|
||||
// for support of oracle server.
|
||||
func (d *DriverOracle) parseSql(sql string) string {
|
||||
patten := `^\s*(?i)(SELECT)|(LIMIT\s*(\d+)\s*,\s*(\d+))`
|
||||
if gregex.IsMatchString(patten, sql) == false {
|
||||
var (
|
||||
patten = `^\s*(?i)(SELECT)|(LIMIT\s*(\d+)\s*,{0,1}\s*(\d*))`
|
||||
allMatch, _ = gregex.MatchAllString(patten, sql)
|
||||
)
|
||||
if len(allMatch) == 0 {
|
||||
return sql
|
||||
}
|
||||
|
||||
res, err := gregex.MatchAllString(patten, sql)
|
||||
if err != nil {
|
||||
return ""
|
||||
}
|
||||
|
||||
var (
|
||||
index = 0
|
||||
keyword = strings.ToUpper(strings.TrimSpace(res[index][0]))
|
||||
keyword = strings.ToUpper(strings.TrimSpace(allMatch[index][0]))
|
||||
)
|
||||
index++
|
||||
switch keyword {
|
||||
case "SELECT":
|
||||
if len(res) < 2 || (strings.HasPrefix(res[index][0], "LIMIT") == false &&
|
||||
strings.HasPrefix(res[index][0], "limit") == false) {
|
||||
if len(allMatch) < 2 || strings.HasPrefix(allMatch[index][0], "LIMIT") == false {
|
||||
break
|
||||
}
|
||||
if gregex.IsMatchString("((?i)SELECT)(.+)((?i)LIMIT)", sql) == false {
|
||||
break
|
||||
}
|
||||
queryExpr, _ := gregex.MatchString("((?i)SELECT)(.+)((?i)LIMIT)", sql)
|
||||
if len(queryExpr) != 4 || strings.EqualFold(queryExpr[1], "SELECT") == false ||
|
||||
if len(queryExpr) != 4 ||
|
||||
strings.EqualFold(queryExpr[1], "SELECT") == false ||
|
||||
strings.EqualFold(queryExpr[3], "LIMIT") == false {
|
||||
break
|
||||
}
|
||||
first, limit := 0, 0
|
||||
for i := 1; i < len(res[index]); i++ {
|
||||
if len(strings.TrimSpace(res[index][i])) == 0 {
|
||||
for i := 1; i < len(allMatch[index]); i++ {
|
||||
if len(strings.TrimSpace(allMatch[index][i])) == 0 {
|
||||
continue
|
||||
}
|
||||
|
||||
if strings.HasPrefix(res[index][i], "LIMIT") || strings.HasPrefix(res[index][i], "limit") {
|
||||
first, _ = strconv.Atoi(res[index][i+1])
|
||||
limit, _ = strconv.Atoi(res[index][i+2])
|
||||
if strings.HasPrefix(allMatch[index][i], "LIMIT") {
|
||||
if allMatch[index][i+2] != "" {
|
||||
first, _ = strconv.Atoi(allMatch[index][i+1])
|
||||
limit, _ = strconv.Atoi(allMatch[index][i+2])
|
||||
} else {
|
||||
limit, _ = strconv.Atoi(allMatch[index][i+1])
|
||||
}
|
||||
break
|
||||
}
|
||||
}
|
||||
@ -157,18 +159,24 @@ func (d *DriverOracle) TableFields(table string, schema ...string) (fields map[s
|
||||
if len(schema) > 0 && schema[0] != "" {
|
||||
checkSchema = schema[0]
|
||||
}
|
||||
v := d.DB.GetCache().GetOrSetFunc(
|
||||
v, _ := gcache.GetOrSetFunc(
|
||||
fmt.Sprintf(`oracle_table_fields_%s_%s`, table, checkSchema),
|
||||
func() interface{} {
|
||||
func() (interface{}, error) {
|
||||
result := (Result)(nil)
|
||||
result, err = d.DB.GetAll(fmt.Sprintf(`
|
||||
SELECT COLUMN_NAME AS FIELD, CASE DATA_TYPE
|
||||
WHEN 'NUMBER' THEN DATA_TYPE||'('||DATA_PRECISION||','||DATA_SCALE||')'
|
||||
WHEN 'FLOAT' THEN DATA_TYPE||'('||DATA_PRECISION||','||DATA_SCALE||')'
|
||||
ELSE DATA_TYPE||'('||DATA_LENGTH||')' END AS TYPE
|
||||
FROM USER_TAB_COLUMNS WHERE TABLE_NAME = '%s' ORDER BY COLUMN_ID`, strings.ToUpper(table)))
|
||||
structureSql := fmt.Sprintf(`
|
||||
SELECT
|
||||
COLUMN_NAME AS FIELD,
|
||||
CASE DATA_TYPE
|
||||
WHEN 'NUMBER' THEN DATA_TYPE||'('||DATA_PRECISION||','||DATA_SCALE||')'
|
||||
WHEN 'FLOAT' THEN DATA_TYPE||'('||DATA_PRECISION||','||DATA_SCALE||')'
|
||||
ELSE DATA_TYPE||'('||DATA_LENGTH||')' END AS TYPE
|
||||
FROM USER_TAB_COLUMNS WHERE TABLE_NAME = '%s' ORDER BY COLUMN_ID`,
|
||||
strings.ToUpper(table),
|
||||
)
|
||||
structureSql, _ = gregex.ReplaceString(`[\n\r\s]+`, " ", gstr.Trim(structureSql))
|
||||
result, err = d.DB.GetAll(structureSql)
|
||||
if err != nil {
|
||||
return nil
|
||||
return nil, err
|
||||
}
|
||||
fields = make(map[string]*TableField)
|
||||
for i, m := range result {
|
||||
@ -178,7 +186,7 @@ func (d *DriverOracle) TableFields(table string, schema ...string) (fields map[s
|
||||
Type: strings.ToLower(m["TYPE"].String()),
|
||||
}
|
||||
}
|
||||
return fields
|
||||
return fields, nil
|
||||
}, 0)
|
||||
if err == nil {
|
||||
fields = v.(map[string]*TableField)
|
||||
@ -188,24 +196,26 @@ func (d *DriverOracle) TableFields(table string, schema ...string) (fields map[s
|
||||
|
||||
func (d *DriverOracle) getTableUniqueIndex(table string) (fields map[string]map[string]string, err error) {
|
||||
table = strings.ToUpper(table)
|
||||
v := d.DB.GetCache().GetOrSetFunc("table_unique_index_"+table, func() interface{} {
|
||||
res := (Result)(nil)
|
||||
res, err = d.DB.GetAll(fmt.Sprintf(`
|
||||
v, _ := gcache.GetOrSetFunc(
|
||||
"table_unique_index_"+table,
|
||||
func() (interface{}, error) {
|
||||
res := (Result)(nil)
|
||||
res, err = d.DB.GetAll(fmt.Sprintf(`
|
||||
SELECT INDEX_NAME,COLUMN_NAME,CHAR_LENGTH FROM USER_IND_COLUMNS
|
||||
WHERE TABLE_NAME = '%s'
|
||||
AND INDEX_NAME IN(SELECT INDEX_NAME FROM USER_INDEXES WHERE TABLE_NAME='%s' AND UNIQUENESS='UNIQUE')
|
||||
ORDER BY INDEX_NAME,COLUMN_POSITION`, table, table))
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
fields := make(map[string]map[string]string)
|
||||
for _, v := range res {
|
||||
mm := make(map[string]string)
|
||||
mm[v["COLUMN_NAME"].String()] = v["CHAR_LENGTH"].String()
|
||||
fields[v["INDEX_NAME"].String()] = mm
|
||||
}
|
||||
return fields
|
||||
}, 0)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
fields := make(map[string]map[string]string)
|
||||
for _, v := range res {
|
||||
mm := make(map[string]string)
|
||||
mm[v["COLUMN_NAME"].String()] = v["CHAR_LENGTH"].String()
|
||||
fields[v["INDEX_NAME"].String()] = mm
|
||||
}
|
||||
return fields, nil
|
||||
}, 0)
|
||||
if err == nil {
|
||||
fields = v.(map[string]map[string]string)
|
||||
}
|
||||
@ -231,7 +241,7 @@ func (d *DriverOracle) DoInsert(link Link, table string, data interface{}, optio
|
||||
case reflect.Map:
|
||||
fallthrough
|
||||
case reflect.Struct:
|
||||
dataMap = DataToMapDeep(data)
|
||||
dataMap = ConvertDataForTableRecord(data)
|
||||
default:
|
||||
return result, errors.New(fmt.Sprint("unsupported data type:", kind))
|
||||
}
|
||||
@ -351,12 +361,12 @@ func (d *DriverOracle) DoBatchInsert(link Link, table string, list interface{},
|
||||
case reflect.Array:
|
||||
listMap = make(List, rv.Len())
|
||||
for i := 0; i < rv.Len(); i++ {
|
||||
listMap[i] = DataToMapDeep(rv.Index(i).Interface())
|
||||
listMap[i] = ConvertDataForTableRecord(rv.Index(i).Interface())
|
||||
}
|
||||
case reflect.Map:
|
||||
fallthrough
|
||||
case reflect.Struct:
|
||||
listMap = List{Map(DataToMapDeep(list))}
|
||||
listMap = List{Map(ConvertDataForTableRecord(list))}
|
||||
default:
|
||||
return result, errors.New(fmt.Sprint("unsupported list type:", kind))
|
||||
}
|
||||
|
||||
@ -16,6 +16,7 @@ import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"github.com/gogf/gf/internal/intlog"
|
||||
"github.com/gogf/gf/os/gcache"
|
||||
"github.com/gogf/gf/text/gstr"
|
||||
"strings"
|
||||
|
||||
@ -107,21 +108,28 @@ func (d *DriverPgsql) TableFields(table string, schema ...string) (fields map[st
|
||||
if len(schema) > 0 && schema[0] != "" {
|
||||
checkSchema = schema[0]
|
||||
}
|
||||
v := d.DB.GetCache().GetOrSetFunc(
|
||||
fmt.Sprintf(`pgsql_table_fields_%s_%s`, table, checkSchema), func() interface{} {
|
||||
var result Result
|
||||
var link *sql.DB
|
||||
v, _ := gcache.GetOrSetFunc(
|
||||
fmt.Sprintf(`pgsql_table_fields_%s_%s`, table, checkSchema),
|
||||
func() (interface{}, error) {
|
||||
var (
|
||||
result Result
|
||||
link *sql.DB
|
||||
)
|
||||
link, err = d.DB.GetSlave(checkSchema)
|
||||
if err != nil {
|
||||
return nil
|
||||
return nil, err
|
||||
}
|
||||
result, err = d.DB.DoGetAll(link, fmt.Sprintf(`
|
||||
SELECT a.attname AS field, t.typname AS type FROM pg_class c, pg_attribute a
|
||||
LEFT OUTER JOIN pg_description b ON a.attrelid=b.objoid AND a.attnum = b.objsubid,pg_type t
|
||||
WHERE c.relname = '%s' and a.attnum > 0 and a.attrelid = c.oid and a.atttypid = t.oid
|
||||
ORDER BY a.attnum`, strings.ToLower(table)))
|
||||
structureSql := fmt.Sprintf(`
|
||||
SELECT a.attname AS field, t.typname AS type FROM pg_class c, pg_attribute a
|
||||
LEFT OUTER JOIN pg_description b ON a.attrelid=b.objoid AND a.attnum = b.objsubid,pg_type t
|
||||
WHERE c.relname = '%s' and a.attnum > 0 and a.attrelid = c.oid and a.atttypid = t.oid
|
||||
ORDER BY a.attnum`,
|
||||
strings.ToLower(table),
|
||||
)
|
||||
structureSql, _ = gregex.ReplaceString(`[\n\r\s]+`, " ", gstr.Trim(structureSql))
|
||||
result, err = d.DB.DoGetAll(link, structureSql)
|
||||
if err != nil {
|
||||
return nil
|
||||
return nil, err
|
||||
}
|
||||
|
||||
fields = make(map[string]*TableField)
|
||||
@ -132,7 +140,7 @@ func (d *DriverPgsql) TableFields(table string, schema ...string) (fields map[st
|
||||
Type: m["type"].String(),
|
||||
}
|
||||
}
|
||||
return fields
|
||||
return fields, nil
|
||||
}, 0)
|
||||
if err == nil {
|
||||
fields = v.(map[string]*TableField)
|
||||
|
||||
@ -15,6 +15,7 @@ import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"github.com/gogf/gf/internal/intlog"
|
||||
"github.com/gogf/gf/os/gcache"
|
||||
"github.com/gogf/gf/os/gfile"
|
||||
"github.com/gogf/gf/text/gstr"
|
||||
"strings"
|
||||
@ -98,17 +99,20 @@ func (d *DriverSqlite) TableFields(table string, schema ...string) (fields map[s
|
||||
if len(schema) > 0 && schema[0] != "" {
|
||||
checkSchema = schema[0]
|
||||
}
|
||||
v := d.DB.GetCache().GetOrSetFunc(
|
||||
fmt.Sprintf(`sqlite_table_fields_%s_%s`, table, checkSchema), func() interface{} {
|
||||
var result Result
|
||||
var link *sql.DB
|
||||
v, _ := gcache.GetOrSetFunc(
|
||||
fmt.Sprintf(`sqlite_table_fields_%s_%s`, table, checkSchema),
|
||||
func() (interface{}, error) {
|
||||
var (
|
||||
result Result
|
||||
link *sql.DB
|
||||
)
|
||||
link, err = d.DB.GetSlave(checkSchema)
|
||||
if err != nil {
|
||||
return nil
|
||||
return nil, err
|
||||
}
|
||||
result, err = d.DB.DoGetAll(link, fmt.Sprintf(`PRAGMA TABLE_INFO(%s)`, table))
|
||||
if err != nil {
|
||||
return nil
|
||||
return nil, err
|
||||
}
|
||||
fields = make(map[string]*TableField)
|
||||
for i, m := range result {
|
||||
@ -118,7 +122,7 @@ func (d *DriverSqlite) TableFields(table string, schema ...string) (fields map[s
|
||||
Type: strings.ToLower(m["type"].String()),
|
||||
}
|
||||
}
|
||||
return fields
|
||||
return fields, nil
|
||||
}, 0)
|
||||
if err == nil {
|
||||
fields = v.(map[string]*TableField)
|
||||
|
||||
@ -11,6 +11,7 @@ import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"github.com/gogf/gf/internal/empty"
|
||||
"github.com/gogf/gf/internal/json"
|
||||
"github.com/gogf/gf/internal/utils"
|
||||
"github.com/gogf/gf/os/gtime"
|
||||
"github.com/gogf/gf/util/gutil"
|
||||
@ -97,14 +98,56 @@ func GetInsertOperationByOption(option int) string {
|
||||
return operator
|
||||
}
|
||||
|
||||
// DataToMapDeep converts struct object to map type recursively.
|
||||
// ConvertDataForTableRecord is a very important function, which does converting for any data that
|
||||
// will be inserted into table as a record.
|
||||
//
|
||||
// The parameter <obj> should be type of *map/map/*struct/struct.
|
||||
// It supports inherit struct definition for struct.
|
||||
func ConvertDataForTableRecord(value interface{}) map[string]interface{} {
|
||||
var (
|
||||
rvValue reflect.Value
|
||||
rvKind reflect.Kind
|
||||
data = DataToMapDeep(value)
|
||||
)
|
||||
for k, v := range data {
|
||||
rvValue = reflect.ValueOf(v)
|
||||
rvKind = rvValue.Kind()
|
||||
for rvKind == reflect.Ptr {
|
||||
rvValue = rvValue.Elem()
|
||||
rvKind = rvValue.Kind()
|
||||
}
|
||||
switch rvKind {
|
||||
case reflect.Slice, reflect.Array, reflect.Map:
|
||||
// It should ignore the bytes type.
|
||||
if _, ok := v.([]byte); !ok {
|
||||
// Convert the value to JSON.
|
||||
data[k], _ = json.Marshal(v)
|
||||
}
|
||||
case reflect.Struct:
|
||||
switch v.(type) {
|
||||
case time.Time, *time.Time, gtime.Time, *gtime.Time:
|
||||
continue
|
||||
default:
|
||||
// Use string conversion in default.
|
||||
if s, ok := v.(apiString); ok {
|
||||
data[k] = s.String()
|
||||
} else {
|
||||
// Convert the value to JSON.
|
||||
data[k], _ = json.Marshal(v)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return data
|
||||
}
|
||||
|
||||
// DataToMapDeep converts <value> to map type recursively.
|
||||
// The parameter <value> should be type of *map/map/*struct/struct.
|
||||
// It supports inherit struct definition for struct.
|
||||
func DataToMapDeep(value interface{}) map[string]interface{} {
|
||||
if v, ok := value.(apiMapStrAny); ok {
|
||||
return v.MapStrAny()
|
||||
}
|
||||
|
||||
var (
|
||||
rvValue reflect.Value
|
||||
rvField reflect.Value
|
||||
@ -182,7 +225,7 @@ func DataToMapDeep(value interface{}) map[string]interface{} {
|
||||
// The underlying driver supports time.Time/*time.Time types.
|
||||
fieldValue := rvField.Interface()
|
||||
switch fieldValue.(type) {
|
||||
case time.Time, *time.Time:
|
||||
case time.Time, *time.Time, gtime.Time, *gtime.Time:
|
||||
data[name] = fieldValue
|
||||
default:
|
||||
// Use string conversion in default.
|
||||
@ -317,8 +360,10 @@ func GetPrimaryKeyCondition(primary string, where ...interface{}) (newWhereCondi
|
||||
return where
|
||||
}
|
||||
if len(where) == 1 {
|
||||
rv := reflect.ValueOf(where[0])
|
||||
kind := rv.Kind()
|
||||
var (
|
||||
rv = reflect.ValueOf(where[0])
|
||||
kind = rv.Kind()
|
||||
)
|
||||
if kind == reflect.Ptr {
|
||||
rv = rv.Elem()
|
||||
kind = rv.Kind()
|
||||
@ -341,9 +386,10 @@ func GetPrimaryKeyCondition(primary string, where ...interface{}) (newWhereCondi
|
||||
// The internal handleArguments function might be called twice during the SQL procedure,
|
||||
// but do not worry about it, it's safe and efficient.
|
||||
func formatSql(sql string, args []interface{}) (newSql string, newArgs []interface{}) {
|
||||
sql = gstr.Trim(sql)
|
||||
sql = gstr.Replace(sql, "\n", " ")
|
||||
sql, _ = gregex.ReplaceString(`\s{2,}`, ` `, sql)
|
||||
// DO NOT do this as there may be multiple lines and comments in the sql.
|
||||
// sql = gstr.Trim(sql)
|
||||
// sql = gstr.Replace(sql, "\n", " ")
|
||||
// sql, _ = gregex.ReplaceString(`\s{2,}`, ` `, sql)
|
||||
return handleArguments(sql, args)
|
||||
}
|
||||
|
||||
|
||||
@ -12,13 +12,6 @@ import (
|
||||
"github.com/gogf/gf/os/gtime"
|
||||
)
|
||||
|
||||
// Unscoped disables the soft deleting feature.
|
||||
func (m *Model) Unscoped() *Model {
|
||||
model := m.getModel()
|
||||
model.unscoped = true
|
||||
return model
|
||||
}
|
||||
|
||||
// Delete does "DELETE FROM ... " statement for the model.
|
||||
// The optional parameter <where> is the same as the parameter of Model.Where function,
|
||||
// see Model.Where.
|
||||
|
||||
@ -24,49 +24,35 @@ func (m *Model) Filter() *Model {
|
||||
}
|
||||
|
||||
// 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 (m *Model) Fields(fields ...string) *Model {
|
||||
if len(fields) > 0 {
|
||||
model := m.getModel()
|
||||
model.fields = gstr.Join(fields, ",")
|
||||
return model
|
||||
}
|
||||
return m
|
||||
}
|
||||
|
||||
// FieldsEx sets the excluded operation fields of the model, multiple fields joined using char ','.
|
||||
// Note that this function supports only single table operations.
|
||||
func (m *Model) FieldsEx(fields string) *Model {
|
||||
if gstr.Contains(m.tables, " ") {
|
||||
panic("function FieldsEx supports only single table operations")
|
||||
func (m *Model) FieldsEx(fields ...string) *Model {
|
||||
if len(fields) > 0 {
|
||||
model := m.getModel()
|
||||
model.fieldsEx = gstr.Join(fields, ",")
|
||||
return model
|
||||
}
|
||||
tableFields, err := m.db.TableFields(m.tables)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
if len(tableFields) == 0 {
|
||||
panic(fmt.Sprintf(`empty table fields for table "%s"`, m.tables))
|
||||
}
|
||||
model := m.getModel()
|
||||
model.fieldsEx = fields
|
||||
fieldsExSet := gset.NewStrSetFrom(gstr.SplitAndTrim(fields, ","))
|
||||
fieldsArray := make([]string, len(tableFields))
|
||||
for k, v := range tableFields {
|
||||
fieldsArray[v.Index] = k
|
||||
}
|
||||
model.fields = ""
|
||||
for _, k := range fieldsArray {
|
||||
if fieldsExSet.Contains(k) {
|
||||
continue
|
||||
}
|
||||
if len(model.fields) > 0 {
|
||||
model.fields += ","
|
||||
}
|
||||
model.fields += k
|
||||
}
|
||||
model.fields = model.db.QuoteString(model.fields)
|
||||
return model
|
||||
return m
|
||||
}
|
||||
|
||||
// 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 ','.
|
||||
// The optional parameter <prefix> specifies the prefix for each field, eg: FieldsStr("u.").
|
||||
func (m *Model) FieldsStr(prefix ...string) string {
|
||||
func (m *Model) GetFieldsStr(prefix ...string) string {
|
||||
prefixStr := ""
|
||||
if len(prefix) > 0 {
|
||||
prefixStr = prefix[0]
|
||||
@ -93,11 +79,17 @@ func (m *Model) FieldsStr(prefix ...string) string {
|
||||
return newFields
|
||||
}
|
||||
|
||||
// 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,
|
||||
// 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.").
|
||||
func (m *Model) FieldsExStr(fields string, prefix ...string) string {
|
||||
func (m *Model) GetFieldsExStr(fields string, prefix ...string) string {
|
||||
prefixStr := ""
|
||||
if len(prefix) > 0 {
|
||||
prefixStr = prefix[0]
|
||||
|
||||
@ -56,8 +56,10 @@ func (m *Model) Data(data ...interface{}) *Model {
|
||||
case Map:
|
||||
model.data = params
|
||||
default:
|
||||
rv := reflect.ValueOf(params)
|
||||
kind := rv.Kind()
|
||||
var (
|
||||
rv = reflect.ValueOf(params)
|
||||
kind = rv.Kind()
|
||||
)
|
||||
if kind == reflect.Ptr {
|
||||
rv = rv.Elem()
|
||||
kind = rv.Kind()
|
||||
@ -66,11 +68,11 @@ func (m *Model) Data(data ...interface{}) *Model {
|
||||
case reflect.Slice, reflect.Array:
|
||||
list := make(List, rv.Len())
|
||||
for i := 0; i < rv.Len(); i++ {
|
||||
list[i] = DataToMapDeep(rv.Index(i).Interface())
|
||||
list[i] = ConvertDataForTableRecord(rv.Index(i).Interface())
|
||||
}
|
||||
model.data = list
|
||||
case reflect.Map:
|
||||
model.data = DataToMapDeep(data[0])
|
||||
model.data = ConvertDataForTableRecord(data[0])
|
||||
case reflect.Struct:
|
||||
if v, ok := data[0].(apiInterfaces); ok {
|
||||
var (
|
||||
@ -78,11 +80,11 @@ func (m *Model) Data(data ...interface{}) *Model {
|
||||
list = make(List, len(array))
|
||||
)
|
||||
for i := 0; i < len(array); i++ {
|
||||
list[i] = DataToMapDeep(array[i])
|
||||
list[i] = ConvertDataForTableRecord(array[i])
|
||||
}
|
||||
model.data = list
|
||||
} else {
|
||||
model.data = DataToMapDeep(data[0])
|
||||
model.data = ConvertDataForTableRecord(data[0])
|
||||
}
|
||||
default:
|
||||
model.data = data[0]
|
||||
@ -170,10 +172,14 @@ func (m *Model) doInsertWithOption(option int, data ...interface{}) (result sql.
|
||||
list[k] = v
|
||||
}
|
||||
}
|
||||
newData, err := m.filterDataForInsertOrUpdate(list)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return m.db.DoBatchInsert(
|
||||
m.getLink(true),
|
||||
m.tables,
|
||||
m.filterDataForInsertOrUpdate(list),
|
||||
newData,
|
||||
option,
|
||||
batch,
|
||||
)
|
||||
@ -190,10 +196,14 @@ func (m *Model) doInsertWithOption(option int, data ...interface{}) (result sql.
|
||||
data[fieldNameUpdate] = nowString
|
||||
}
|
||||
}
|
||||
newData, err := m.filterDataForInsertOrUpdate(data)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return m.db.DoInsert(
|
||||
m.getLink(true),
|
||||
m.tables,
|
||||
m.filterDataForInsertOrUpdate(data),
|
||||
newData,
|
||||
option,
|
||||
)
|
||||
}
|
||||
|
||||
@ -6,7 +6,21 @@
|
||||
|
||||
package gdb
|
||||
|
||||
import "fmt"
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/gogf/gf/text/gstr"
|
||||
)
|
||||
|
||||
// isSubQuery checks and returns whether given string a sub-query sql string.
|
||||
func isSubQuery(s string) bool {
|
||||
s = gstr.TrimLeft(s)
|
||||
if p := gstr.Pos(s, " "); p != -1 {
|
||||
if gstr.Equal(s[:p], "select") {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// LeftJoin does "LEFT JOIN ... ON ..." statement on the model.
|
||||
// The parameter <table> can be joined table and its joined condition,
|
||||
@ -14,19 +28,32 @@ import "fmt"
|
||||
// Table("user").LeftJoin("user_detail", "user_detail.uid=user.uid")
|
||||
// Table("user", "u").LeftJoin("user_detail", "ud", "ud.uid=u.uid")
|
||||
func (m *Model) LeftJoin(table ...string) *Model {
|
||||
model := m.getModel()
|
||||
var (
|
||||
model = m.getModel()
|
||||
joinStr = ""
|
||||
)
|
||||
if len(table) > 0 {
|
||||
if isSubQuery(table[0]) {
|
||||
joinStr = "(" + table[0] + ")"
|
||||
} else {
|
||||
joinStr = m.db.QuotePrefixTableName(table[0])
|
||||
}
|
||||
}
|
||||
if len(table) > 2 {
|
||||
model.tables += fmt.Sprintf(
|
||||
" LEFT JOIN %s AS %s ON (%s)",
|
||||
m.db.QuotePrefixTableName(table[0]), m.db.QuoteWord(table[1]), table[2],
|
||||
joinStr, m.db.QuoteWord(table[1]), table[2],
|
||||
)
|
||||
} else if len(table) == 2 {
|
||||
model.tables += fmt.Sprintf(
|
||||
" LEFT JOIN %s ON (%s)",
|
||||
m.db.QuotePrefixTableName(table[0]), table[1],
|
||||
joinStr, table[1],
|
||||
)
|
||||
} else if len(table) == 1 {
|
||||
model.tables += fmt.Sprintf(
|
||||
" LEFT JOIN %s",
|
||||
joinStr,
|
||||
)
|
||||
} else {
|
||||
panic("invalid join table parameter")
|
||||
}
|
||||
return model
|
||||
}
|
||||
@ -37,19 +64,32 @@ func (m *Model) LeftJoin(table ...string) *Model {
|
||||
// Table("user").RightJoin("user_detail", "user_detail.uid=user.uid")
|
||||
// Table("user", "u").RightJoin("user_detail", "ud", "ud.uid=u.uid")
|
||||
func (m *Model) RightJoin(table ...string) *Model {
|
||||
model := m.getModel()
|
||||
var (
|
||||
model = m.getModel()
|
||||
joinStr = ""
|
||||
)
|
||||
if len(table) > 0 {
|
||||
if isSubQuery(table[0]) {
|
||||
joinStr = "(" + table[0] + ")"
|
||||
} else {
|
||||
joinStr = m.db.QuotePrefixTableName(table[0])
|
||||
}
|
||||
}
|
||||
if len(table) > 2 {
|
||||
model.tables += fmt.Sprintf(
|
||||
" RIGHT JOIN %s AS %s ON (%s)",
|
||||
m.db.QuotePrefixTableName(table[0]), m.db.QuoteWord(table[1]), table[2],
|
||||
joinStr, m.db.QuoteWord(table[1]), table[2],
|
||||
)
|
||||
} else if len(table) == 2 {
|
||||
model.tables += fmt.Sprintf(
|
||||
" RIGHT JOIN %s ON (%s)",
|
||||
m.db.QuotePrefixTableName(table[0]), table[1],
|
||||
joinStr, table[1],
|
||||
)
|
||||
} else if len(table) == 1 {
|
||||
model.tables += fmt.Sprintf(
|
||||
" RIGHT JOIN %s",
|
||||
joinStr,
|
||||
)
|
||||
} else {
|
||||
panic("invalid join table parameter")
|
||||
}
|
||||
return model
|
||||
}
|
||||
@ -60,19 +100,32 @@ func (m *Model) RightJoin(table ...string) *Model {
|
||||
// Table("user").InnerJoin("user_detail", "user_detail.uid=user.uid")
|
||||
// Table("user", "u").InnerJoin("user_detail", "ud", "ud.uid=u.uid")
|
||||
func (m *Model) InnerJoin(table ...string) *Model {
|
||||
model := m.getModel()
|
||||
var (
|
||||
model = m.getModel()
|
||||
joinStr = ""
|
||||
)
|
||||
if len(table) > 0 {
|
||||
if isSubQuery(table[0]) {
|
||||
joinStr = "(" + table[0] + ")"
|
||||
} else {
|
||||
joinStr = m.db.QuotePrefixTableName(table[0])
|
||||
}
|
||||
}
|
||||
if len(table) > 2 {
|
||||
model.tables += fmt.Sprintf(
|
||||
" INNER JOIN %s AS %s ON (%s)",
|
||||
m.db.QuotePrefixTableName(table[0]), m.db.QuoteWord(table[1]), table[2],
|
||||
joinStr, m.db.QuoteWord(table[1]), table[2],
|
||||
)
|
||||
} else if len(table) == 2 {
|
||||
model.tables += fmt.Sprintf(
|
||||
" INNER JOIN %s ON (%s)",
|
||||
m.db.QuotePrefixTableName(table[0]), table[1],
|
||||
joinStr, table[1],
|
||||
)
|
||||
} else if len(table) == 1 {
|
||||
model.tables += fmt.Sprintf(
|
||||
" INNER JOIN %s",
|
||||
joinStr,
|
||||
)
|
||||
} else {
|
||||
panic("invalid join table parameter")
|
||||
}
|
||||
return model
|
||||
}
|
||||
|
||||
@ -8,7 +8,10 @@ package gdb
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/gogf/gf/container/gset"
|
||||
"github.com/gogf/gf/container/gvar"
|
||||
"github.com/gogf/gf/internal/json"
|
||||
"github.com/gogf/gf/text/gstr"
|
||||
"github.com/gogf/gf/util/gconv"
|
||||
"reflect"
|
||||
)
|
||||
@ -53,12 +56,13 @@ func (m *Model) doGetAll(limit1 bool, where ...interface{}) (Result, error) {
|
||||
}
|
||||
conditionWhere += softDeletingCondition
|
||||
}
|
||||
|
||||
// DO NOT quote the m.fields where, in case of fields like:
|
||||
// DISTINCT t.user_id uid
|
||||
return m.doGetAllBySql(
|
||||
fmt.Sprintf(
|
||||
"SELECT %s FROM %s%s",
|
||||
m.fields,
|
||||
m.getFieldsFiltered(),
|
||||
m.tables,
|
||||
conditionWhere+conditionExtra,
|
||||
),
|
||||
@ -66,6 +70,53 @@ func (m *Model) doGetAll(limit1 bool, where ...interface{}) (Result, error) {
|
||||
)
|
||||
}
|
||||
|
||||
// getFieldsFiltered checks the fields and fieldsEx attributes, filters and returns the fields that will
|
||||
// really be committed to underlying database driver.
|
||||
func (m *Model) getFieldsFiltered() string {
|
||||
if m.fieldsEx == "" {
|
||||
// No filtering.
|
||||
return m.fields
|
||||
}
|
||||
var (
|
||||
fieldsArray []string
|
||||
fieldsExSet = gset.NewStrSetFrom(gstr.SplitAndTrim(m.fieldsEx, ","))
|
||||
)
|
||||
if m.fields != "*" {
|
||||
// Filter custom fields with fieldEx.
|
||||
fieldsArray = make([]string, 0, 8)
|
||||
for _, v := range gstr.SplitAndTrim(m.fields, ",") {
|
||||
fieldsArray = append(fieldsArray, v[gstr.PosR(v, "-")+1:])
|
||||
}
|
||||
} else {
|
||||
if gstr.Contains(m.tables, " ") {
|
||||
panic("function FieldsEx supports only single table operations")
|
||||
}
|
||||
// Filter table fields with fieldEx.
|
||||
tableFields, err := m.db.TableFields(m.tables)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
if len(tableFields) == 0 {
|
||||
panic(fmt.Sprintf(`empty table fields for table "%s"`, m.tables))
|
||||
}
|
||||
fieldsArray = make([]string, len(tableFields))
|
||||
for k, v := range tableFields {
|
||||
fieldsArray[v.Index] = k
|
||||
}
|
||||
}
|
||||
newFields := ""
|
||||
for _, k := range fieldsArray {
|
||||
if fieldsExSet.Contains(k) {
|
||||
continue
|
||||
}
|
||||
if len(newFields) > 0 {
|
||||
newFields += ","
|
||||
}
|
||||
newFields += k
|
||||
}
|
||||
return newFields
|
||||
}
|
||||
|
||||
// Chunk iterates the query result with given size and callback function.
|
||||
func (m *Model) Chunk(limit int, callback func(result Result, err error) bool) {
|
||||
page := m.start
|
||||
@ -381,23 +432,35 @@ func (m *Model) FindScan(pointer interface{}, where ...interface{}) error {
|
||||
// doGetAllBySql does the select statement on the database.
|
||||
func (m *Model) doGetAllBySql(sql string, args ...interface{}) (result Result, err error) {
|
||||
cacheKey := ""
|
||||
cacheObj := m.db.GetCache()
|
||||
// Retrieve from cache.
|
||||
if m.cacheEnabled && m.tx == nil {
|
||||
cacheKey = m.cacheName
|
||||
if len(cacheKey) == 0 {
|
||||
cacheKey = sql + "/" + gconv.String(args)
|
||||
cacheKey = sql + ", @PARAMS:" + gconv.String(args)
|
||||
}
|
||||
if v := m.db.GetCache().Get(cacheKey); v != nil {
|
||||
return v.(Result), nil
|
||||
if v, _ := cacheObj.GetVar(cacheKey); !v.IsNil() {
|
||||
if result, ok := v.Val().(Result); ok {
|
||||
// In-memory cache.
|
||||
return result, nil
|
||||
} else {
|
||||
// Other cache, it needs conversion.
|
||||
var result Result
|
||||
if err = json.Unmarshal(v.Bytes(), &result); err != nil {
|
||||
return nil, err
|
||||
} else {
|
||||
return result, nil
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
result, err = m.db.DoGetAll(m.getLink(false), sql, m.mergeArguments(args)...)
|
||||
// Cache the result.
|
||||
if cacheKey != "" && err == nil {
|
||||
if m.cacheDuration < 0 {
|
||||
m.db.GetCache().Remove(cacheKey)
|
||||
cacheObj.Remove(cacheKey)
|
||||
} else {
|
||||
m.db.GetCache().Set(cacheKey, result, m.cacheDuration)
|
||||
cacheObj.Set(cacheKey, result, m.cacheDuration)
|
||||
}
|
||||
}
|
||||
return result, err
|
||||
|
||||
@ -21,6 +21,13 @@ const (
|
||||
gSOFT_FIELD_NAME_DELETE = "delete_at"
|
||||
)
|
||||
|
||||
// Unscoped disables the auto-update time feature for insert, update and delete options.
|
||||
func (m *Model) Unscoped() *Model {
|
||||
model := m.getModel()
|
||||
model.unscoped = true
|
||||
return model
|
||||
}
|
||||
|
||||
// getSoftFieldNameCreate checks and returns the field name for record creating time.
|
||||
// If there's no field name for storing creating time, it returns an empty string.
|
||||
// It checks the key with or without cases or chars '-'/'_'/'.'/' '.
|
||||
@ -86,8 +93,8 @@ func (m *Model) getConditionForSoftDeleting() string {
|
||||
// Base table.
|
||||
match, _ := gregex.MatchString(`(.+?) [A-Z]+ JOIN`, m.tables)
|
||||
conditionArray.Append(m.getConditionOfTableStringForSoftDeleting(match[1]))
|
||||
// Multiple joined tables.
|
||||
matches, _ := gregex.MatchAllString(`JOIN (.+?) ON`, m.tables)
|
||||
// Multiple joined tables, exclude the sub query sql which contains char '(' and ')'.
|
||||
matches, _ := gregex.MatchAllString(`JOIN ([^()]+?) ON`, m.tables)
|
||||
for _, match := range matches {
|
||||
conditionArray.Append(m.getConditionOfTableStringForSoftDeleting(match[1]))
|
||||
}
|
||||
|
||||
@ -59,7 +59,7 @@ func (m *Model) Update(dataAndWhere ...interface{}) (result sql.Result, err erro
|
||||
}
|
||||
switch refKind {
|
||||
case reflect.Map, reflect.Struct:
|
||||
dataMap := DataToMapDeep(m.data)
|
||||
dataMap := ConvertDataForTableRecord(m.data)
|
||||
gutil.MapDelete(dataMap, fieldNameCreate, fieldNameUpdate, fieldNameDelete)
|
||||
if fieldNameUpdate != "" {
|
||||
dataMap[fieldNameUpdate] = gtime.Now().String()
|
||||
@ -73,10 +73,14 @@ func (m *Model) Update(dataAndWhere ...interface{}) (result sql.Result, err erro
|
||||
updateData = updates
|
||||
}
|
||||
}
|
||||
newData, err := m.filterDataForInsertOrUpdate(updateData)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return m.db.DoUpdate(
|
||||
m.getLink(true),
|
||||
m.tables,
|
||||
m.filterDataForInsertOrUpdate(updateData),
|
||||
newData,
|
||||
conditionWhere+conditionExtra,
|
||||
m.mergeArguments(conditionArgs)...,
|
||||
)
|
||||
|
||||
@ -28,27 +28,33 @@ func (m *Model) getModel() *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 (m *Model) filterDataForInsertOrUpdate(data interface{}) interface{} {
|
||||
func (m *Model) filterDataForInsertOrUpdate(data interface{}) (interface{}, error) {
|
||||
var err error
|
||||
switch value := data.(type) {
|
||||
case List:
|
||||
for k, item := range value {
|
||||
value[k] = m.doFilterDataMapForInsertOrUpdate(item, false)
|
||||
value[k], err = m.doMappingAndFilterForInsertOrUpdateDataMap(item, false)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
return value
|
||||
return value, nil
|
||||
|
||||
case Map:
|
||||
return m.doFilterDataMapForInsertOrUpdate(value, true)
|
||||
return m.doMappingAndFilterForInsertOrUpdateDataMap(value, true)
|
||||
|
||||
default:
|
||||
return data
|
||||
return data, nil
|
||||
}
|
||||
}
|
||||
|
||||
// doFilterDataMapForInsertOrUpdate does the filter features for map.
|
||||
// doMappingAndFilterForInsertOrUpdateDataMap 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 (m *Model) doFilterDataMapForInsertOrUpdate(data Map, allowOmitEmpty bool) Map {
|
||||
if m.filter {
|
||||
data = m.db.filterFields(m.schema, m.tables, data)
|
||||
func (m *Model) doMappingAndFilterForInsertOrUpdateDataMap(data Map, allowOmitEmpty bool) (Map, error) {
|
||||
var err error
|
||||
data, err = m.db.mappingAndFilterData(m.schema, m.tables, data, m.filter)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// Remove key-value pairs of which the value is empty.
|
||||
if allowOmitEmpty && m.option&OPTION_OMITEMPTY > 0 {
|
||||
@ -103,7 +109,7 @@ func (m *Model) doFilterDataMapForInsertOrUpdate(data Map, allowOmitEmpty bool)
|
||||
delete(data, v)
|
||||
}
|
||||
}
|
||||
return data
|
||||
return data, nil
|
||||
}
|
||||
|
||||
// getLink returns the underlying database link object with configured <linkType> attribute.
|
||||
|
||||
@ -115,7 +115,6 @@ func (tx *TX) GetScan(objPointer interface{}, sql string, args ...interface{}) e
|
||||
default:
|
||||
return fmt.Errorf("element type should be type of struct/slice, unsupported: %v", k)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetValue queries and returns the field value from database.
|
||||
@ -154,7 +153,10 @@ func (tx *TX) GetCount(sql string, args ...interface{}) (int, error) {
|
||||
//
|
||||
// The parameter <batch> specifies the batch operation count when given data is slice.
|
||||
func (tx *TX) Insert(table string, data interface{}, batch ...int) (sql.Result, error) {
|
||||
return tx.db.DoInsert(tx.tx, table, data, gINSERT_OPTION_DEFAULT, batch...)
|
||||
if len(batch) > 0 {
|
||||
return tx.Model(table).Data(data).Batch(batch[0]).Insert()
|
||||
}
|
||||
return tx.Model(table).Data(data).Insert()
|
||||
}
|
||||
|
||||
// InsertIgnore does "INSERT IGNORE INTO ..." statement for the table.
|
||||
@ -167,7 +169,10 @@ func (tx *TX) Insert(table string, data interface{}, batch ...int) (sql.Result,
|
||||
//
|
||||
// The parameter <batch> specifies the batch operation count when given data is slice.
|
||||
func (tx *TX) InsertIgnore(table string, data interface{}, batch ...int) (sql.Result, error) {
|
||||
return tx.db.DoInsert(tx.tx, table, data, gINSERT_OPTION_IGNORE, batch...)
|
||||
if len(batch) > 0 {
|
||||
return tx.Model(table).Data(data).Batch(batch[0]).InsertIgnore()
|
||||
}
|
||||
return tx.Model(table).Data(data).InsertIgnore()
|
||||
}
|
||||
|
||||
// Replace does "REPLACE INTO ..." statement for the table.
|
||||
@ -183,7 +188,10 @@ func (tx *TX) InsertIgnore(table string, data interface{}, batch ...int) (sql.Re
|
||||
// If given data is type of slice, it then does batch replacing, and the optional parameter
|
||||
// <batch> specifies the batch operation count.
|
||||
func (tx *TX) Replace(table string, data interface{}, batch ...int) (sql.Result, error) {
|
||||
return tx.db.DoInsert(tx.tx, table, data, gINSERT_OPTION_REPLACE, batch...)
|
||||
if len(batch) > 0 {
|
||||
return tx.Model(table).Data(data).Batch(batch[0]).Replace()
|
||||
}
|
||||
return tx.Model(table).Data(data).Replace()
|
||||
}
|
||||
|
||||
// Save does "INSERT INTO ... ON DUPLICATE KEY UPDATE..." statement for the table.
|
||||
@ -198,31 +206,46 @@ func (tx *TX) Replace(table string, data interface{}, batch ...int) (sql.Result,
|
||||
// If given data is type of slice, it then does batch saving, and the optional parameter
|
||||
// <batch> specifies the batch operation count.
|
||||
func (tx *TX) Save(table string, data interface{}, batch ...int) (sql.Result, error) {
|
||||
return tx.db.DoInsert(tx.tx, table, data, gINSERT_OPTION_SAVE, batch...)
|
||||
if len(batch) > 0 {
|
||||
return tx.Model(table).Data(data).Batch(batch[0]).Save()
|
||||
}
|
||||
return tx.Model(table).Data(data).Save()
|
||||
}
|
||||
|
||||
// BatchInsert batch inserts data.
|
||||
// The parameter <list> must be type of slice of map or struct.
|
||||
func (tx *TX) BatchInsert(table string, list interface{}, batch ...int) (sql.Result, error) {
|
||||
return tx.db.DoBatchInsert(tx.tx, table, list, gINSERT_OPTION_DEFAULT, batch...)
|
||||
if len(batch) > 0 {
|
||||
return tx.Model(table).Data(list).Batch(batch[0]).Insert()
|
||||
}
|
||||
return tx.Model(table).Data(list).Insert()
|
||||
}
|
||||
|
||||
// BatchInsert 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) {
|
||||
return tx.db.DoBatchInsert(tx.tx, table, list, gINSERT_OPTION_IGNORE, batch...)
|
||||
if len(batch) > 0 {
|
||||
return tx.Model(table).Data(list).Batch(batch[0]).InsertIgnore()
|
||||
}
|
||||
return tx.Model(table).Data(list).InsertIgnore()
|
||||
}
|
||||
|
||||
// BatchReplace batch replaces data.
|
||||
// The parameter <list> must be type of slice of map or struct.
|
||||
func (tx *TX) BatchReplace(table string, list interface{}, batch ...int) (sql.Result, error) {
|
||||
return tx.db.DoBatchInsert(tx.tx, table, list, gINSERT_OPTION_REPLACE, batch...)
|
||||
if len(batch) > 0 {
|
||||
return tx.Model(table).Data(list).Batch(batch[0]).Replace()
|
||||
}
|
||||
return tx.Model(table).Data(list).Replace()
|
||||
}
|
||||
|
||||
// BatchSave batch replaces data.
|
||||
// The parameter <list> must be type of slice of map or struct.
|
||||
func (tx *TX) BatchSave(table string, list interface{}, batch ...int) (sql.Result, error) {
|
||||
return tx.db.DoBatchInsert(tx.tx, table, list, gINSERT_OPTION_SAVE, batch...)
|
||||
if len(batch) > 0 {
|
||||
return tx.Model(table).Data(list).Batch(batch[0]).Save()
|
||||
}
|
||||
return tx.Model(table).Data(list).Save()
|
||||
}
|
||||
|
||||
// Update does "UPDATE ... " statement for the table.
|
||||
@ -240,11 +263,7 @@ func (tx *TX) BatchSave(table string, list interface{}, batch ...int) (sql.Resul
|
||||
// "age IN(?,?)", 18, 50
|
||||
// User{ Id : 1, UserName : "john"}
|
||||
func (tx *TX) Update(table string, data interface{}, condition interface{}, args ...interface{}) (sql.Result, error) {
|
||||
newWhere, newArgs := formatWhere(tx.db, condition, args, false)
|
||||
if newWhere != "" {
|
||||
newWhere = " WHERE " + newWhere
|
||||
}
|
||||
return tx.db.DoUpdate(tx.tx, table, data, newWhere, newArgs...)
|
||||
return tx.Model(table).Data(data).Where(condition, args...).Update()
|
||||
}
|
||||
|
||||
// Delete does "DELETE FROM ... " statement for the table.
|
||||
@ -259,9 +278,5 @@ func (tx *TX) Update(table string, data interface{}, condition interface{}, args
|
||||
// "age IN(?,?)", 18, 50
|
||||
// User{ Id : 1, UserName : "john"}
|
||||
func (tx *TX) Delete(table string, condition interface{}, args ...interface{}) (sql.Result, error) {
|
||||
newWhere, newArgs := formatWhere(tx.db, condition, args, false)
|
||||
if newWhere != "" {
|
||||
newWhere = " WHERE " + newWhere
|
||||
}
|
||||
return tx.db.DoDelete(tx.tx, table, newWhere, newArgs...)
|
||||
return tx.Model(table).Where(condition, args...).Delete()
|
||||
}
|
||||
|
||||
@ -181,7 +181,7 @@ func (r Result) ScanList(listPointer interface{}, attributeName string, relation
|
||||
}
|
||||
}
|
||||
if len(relationDataMap) > 0 && !relationValue.IsValid() {
|
||||
return fmt.Errorf(`invalid relation: %s, %s`, relation[0], relation[1])
|
||||
return fmt.Errorf(`invalid relation: "%s:%s"`, relation[0], relation[1])
|
||||
}
|
||||
switch attrKind {
|
||||
case reflect.Array, reflect.Slice:
|
||||
@ -196,7 +196,7 @@ func (r Result) ScanList(listPointer interface{}, attributeName string, relation
|
||||
}
|
||||
} else {
|
||||
// May be the attribute does not exist yet.
|
||||
return fmt.Errorf(`invalid relation: %s, %s`, relation[0], relation[1])
|
||||
return fmt.Errorf(`invalid relation: "%s:%s"`, relation[0], relation[1])
|
||||
}
|
||||
} else {
|
||||
return fmt.Errorf(`relationKey should not be empty as field "%s" is slice`, attributeName)
|
||||
@ -207,15 +207,25 @@ func (r Result) ScanList(listPointer interface{}, attributeName string, relation
|
||||
if len(relationDataMap) > 0 {
|
||||
relationField = relationValue.FieldByName(relationAttrName)
|
||||
if relationField.IsValid() {
|
||||
if err = gconv.Struct(relationDataMap[gconv.String(relationField.Interface())], e); err != nil {
|
||||
v := relationDataMap[gconv.String(relationField.Interface())]
|
||||
if v == nil {
|
||||
// There's no relational data.
|
||||
continue
|
||||
}
|
||||
if err = gconv.Struct(v, e); err != nil {
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
// May be the attribute does not exist yet.
|
||||
return fmt.Errorf(`invalid relation: %s, %s`, relation[0], relation[1])
|
||||
return fmt.Errorf(`invalid relation: "%s:%s"`, relation[0], relation[1])
|
||||
}
|
||||
} else {
|
||||
if err = gconv.Struct(r[i], e); err != nil {
|
||||
v := r[i]
|
||||
if v == nil {
|
||||
// There's no relational data.
|
||||
continue
|
||||
}
|
||||
if err = gconv.Struct(v, e); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
@ -226,15 +236,25 @@ func (r Result) ScanList(listPointer interface{}, attributeName string, relation
|
||||
if len(relationDataMap) > 0 {
|
||||
relationField = relationValue.FieldByName(relationAttrName)
|
||||
if relationField.IsValid() {
|
||||
if err = gconv.Struct(relationDataMap[gconv.String(relationField.Interface())], e); err != nil {
|
||||
v := relationDataMap[gconv.String(relationField.Interface())]
|
||||
if v == nil {
|
||||
// There's no relational data.
|
||||
continue
|
||||
}
|
||||
if err = gconv.Struct(v, e); err != nil {
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
// May be the attribute does not exist yet.
|
||||
return fmt.Errorf(`invalid relation: %s, %s`, relation[0], relation[1])
|
||||
return fmt.Errorf(`invalid relation: "%s:%s"`, relation[0], relation[1])
|
||||
}
|
||||
} else {
|
||||
if err = gconv.Struct(r[i], e); err != nil {
|
||||
v := r[i]
|
||||
if v == nil {
|
||||
// There's no relational data.
|
||||
continue
|
||||
}
|
||||
if err = gconv.Struct(v, e); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
@ -17,7 +17,152 @@ import (
|
||||
"github.com/gogf/gf/test/gtest"
|
||||
)
|
||||
|
||||
func Test_Table_Relation(t *testing.T) {
|
||||
func Test_Table_Relation_One(t *testing.T) {
|
||||
var (
|
||||
tableUser = "user_" + gtime.TimestampMicroStr()
|
||||
tableUserDetail = "user_detail_" + gtime.TimestampMicroStr()
|
||||
tableUserScores = "user_scores_" + gtime.TimestampMicroStr()
|
||||
)
|
||||
if _, err := db.Exec(fmt.Sprintf(`
|
||||
CREATE TABLE %s (
|
||||
uid int(10) unsigned NOT NULL AUTO_INCREMENT,
|
||||
name varchar(45) NOT NULL,
|
||||
PRIMARY KEY (uid)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
|
||||
`, tableUser)); err != nil {
|
||||
gtest.Error(err)
|
||||
}
|
||||
defer dropTable(tableUser)
|
||||
|
||||
if _, err := db.Exec(fmt.Sprintf(`
|
||||
CREATE TABLE %s (
|
||||
uid int(10) unsigned NOT NULL AUTO_INCREMENT,
|
||||
address varchar(45) NOT NULL,
|
||||
PRIMARY KEY (uid)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
|
||||
`, tableUserDetail)); err != nil {
|
||||
gtest.Error(err)
|
||||
}
|
||||
defer dropTable(tableUserDetail)
|
||||
|
||||
if _, err := db.Exec(fmt.Sprintf(`
|
||||
CREATE TABLE %s (
|
||||
id int(10) unsigned NOT NULL AUTO_INCREMENT,
|
||||
uid int(10) unsigned NOT NULL,
|
||||
score int(10) unsigned NOT NULL,
|
||||
course varchar(45) NOT NULL,
|
||||
PRIMARY KEY (id)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
|
||||
`, tableUserScores)); err != nil {
|
||||
gtest.Error(err)
|
||||
}
|
||||
defer dropTable(tableUserScores)
|
||||
|
||||
type EntityUser struct {
|
||||
Uid int `orm:"uid"`
|
||||
Name string `orm:"name"`
|
||||
}
|
||||
|
||||
type EntityUserDetail struct {
|
||||
Uid int `orm:"uid"`
|
||||
Address string `orm:"address"`
|
||||
}
|
||||
|
||||
type EntityUserScores struct {
|
||||
Id int `orm:"id"`
|
||||
Uid int `orm:"uid"`
|
||||
Score int `orm:"score"`
|
||||
Course string `orm:"course"`
|
||||
}
|
||||
|
||||
type Entity struct {
|
||||
User *EntityUser
|
||||
UserDetail *EntityUserDetail
|
||||
UserScores []*EntityUserScores
|
||||
}
|
||||
|
||||
// Initialize the data.
|
||||
var err error
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
err = db.Transaction(func(tx *gdb.TX) error {
|
||||
r, err := tx.Table(tableUser).Save(EntityUser{
|
||||
Name: "john",
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
uid, err := r.LastInsertId()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
_, err = tx.Table(tableUserDetail).Save(EntityUserDetail{
|
||||
Uid: int(uid),
|
||||
Address: "Beijing DongZhiMen #66",
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
_, err = tx.Table(tableUserScores).Save(g.Slice{
|
||||
EntityUserScores{Uid: int(uid), Score: 100, Course: "math"},
|
||||
EntityUserScores{Uid: int(uid), Score: 99, Course: "physics"},
|
||||
})
|
||||
return err
|
||||
})
|
||||
t.Assert(err, nil)
|
||||
})
|
||||
// Data check.
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
r, err := db.Table(tableUser).All()
|
||||
t.Assert(err, nil)
|
||||
t.Assert(r.Len(), 1)
|
||||
t.Assert(r[0]["uid"].Int(), 1)
|
||||
t.Assert(r[0]["name"].String(), "john")
|
||||
|
||||
r, err = db.Table(tableUserDetail).Where("uid", r[0]["uid"].Int()).All()
|
||||
t.Assert(err, nil)
|
||||
t.Assert(r.Len(), 1)
|
||||
t.Assert(r[0]["uid"].Int(), 1)
|
||||
t.Assert(r[0]["address"].String(), `Beijing DongZhiMen #66`)
|
||||
|
||||
r, err = db.Table(tableUserScores).Where("uid", r[0]["uid"].Int()).All()
|
||||
t.Assert(err, nil)
|
||||
t.Assert(r.Len(), 2)
|
||||
t.Assert(r[0]["uid"].Int(), 1)
|
||||
t.Assert(r[1]["uid"].Int(), 1)
|
||||
t.Assert(r[0]["course"].String(), `math`)
|
||||
t.Assert(r[1]["course"].String(), `physics`)
|
||||
})
|
||||
// Entity query.
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
var user Entity
|
||||
// SELECT * FROM `user` WHERE `name`='john'
|
||||
err := db.Table(tableUser).Scan(&user.User, "name", "john")
|
||||
t.Assert(err, nil)
|
||||
|
||||
// SELECT * FROM `user_detail` WHERE `uid`=1
|
||||
err = db.Table(tableUserDetail).Scan(&user.UserDetail, "uid", user.User.Uid)
|
||||
t.Assert(err, nil)
|
||||
|
||||
// SELECT * FROM `user_scores` WHERE `uid`=1
|
||||
err = db.Table(tableUserScores).Scan(&user.UserScores, "uid", user.User.Uid)
|
||||
t.Assert(err, nil)
|
||||
|
||||
t.Assert(user.User, EntityUser{
|
||||
Uid: 1,
|
||||
Name: "john",
|
||||
})
|
||||
t.Assert(user.UserDetail, EntityUserDetail{
|
||||
Uid: 1,
|
||||
Address: "Beijing DongZhiMen #66",
|
||||
})
|
||||
t.Assert(user.UserScores, []EntityUserScores{
|
||||
{Id: 1, Uid: 1, Course: "math", Score: 100},
|
||||
{Id: 2, Uid: 1, Course: "physics", Score: 99},
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
func Test_Table_Relation_Many(t *testing.T) {
|
||||
var (
|
||||
tableUser = "user_" + gtime.TimestampMicroStr()
|
||||
tableUserDetail = "user_detail_" + gtime.TimestampMicroStr()
|
||||
@ -292,14 +292,23 @@ CREATE TABLE %s (
|
||||
}
|
||||
|
||||
// Fix issue: https://github.com/gogf/gf/issues/819
|
||||
func Test_Func_DataToMapDeep(t *testing.T) {
|
||||
func Test_Func_ConvertDataForTableRecord(t *testing.T) {
|
||||
type Test struct {
|
||||
ResetPasswordTokenAt mysql.NullTime `orm:"reset_password_token_at"`
|
||||
}
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
m := DataToMapDeep(new(Test))
|
||||
m := ConvertDataForTableRecord(new(Test))
|
||||
t.Assert(len(m), 1)
|
||||
t.AssertNE(m["reset_password_token_at"], nil)
|
||||
t.Assert(m["reset_password_token_at"], new(mysql.NullTime))
|
||||
})
|
||||
}
|
||||
|
||||
func Test_isSubQuery(t *testing.T) {
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
t.Assert(isSubQuery("user"), false)
|
||||
t.Assert(isSubQuery("user.uid"), false)
|
||||
t.Assert(isSubQuery("u, user.uid"), false)
|
||||
t.Assert(isSubQuery("select 1"), true)
|
||||
})
|
||||
}
|
||||
|
||||
@ -9,6 +9,7 @@ package gdb_test
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/gogf/gf/container/garray"
|
||||
"github.com/gogf/gf/encoding/gparser"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
@ -183,6 +184,119 @@ func Test_DB_Insert(t *testing.T) {
|
||||
})
|
||||
}
|
||||
|
||||
// Fix issue: https://github.com/gogf/gf/issues/819
|
||||
func Test_DB_Insert_WithStructAndSliceAttribute(t *testing.T) {
|
||||
table := createTable()
|
||||
defer dropTable(table)
|
||||
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
type Password struct {
|
||||
Salt string `json:"salt"`
|
||||
Pass string `json:"pass"`
|
||||
}
|
||||
data := g.Map{
|
||||
"id": 1,
|
||||
"passport": "t1",
|
||||
"password": &Password{"123", "456"},
|
||||
"nickname": []string{"A", "B", "C"},
|
||||
"create_time": gtime.Now().String(),
|
||||
}
|
||||
_, err := db.Insert(table, data)
|
||||
t.Assert(err, nil)
|
||||
|
||||
one, err := db.GetOne(fmt.Sprintf("SELECT * FROM %s WHERE id=?", table), 1)
|
||||
t.Assert(err, nil)
|
||||
t.Assert(one["passport"], data["passport"])
|
||||
t.Assert(one["create_time"], data["create_time"])
|
||||
t.Assert(one["nickname"], gparser.MustToJson(data["nickname"]))
|
||||
})
|
||||
}
|
||||
|
||||
func Test_DB_Insert_KeyFieldNameMapping(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
|
||||
}
|
||||
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.Assert(err, nil)
|
||||
|
||||
one, err := db.GetOne(fmt.Sprintf("SELECT * FROM %s WHERE id=?", table), 1)
|
||||
t.Assert(err, nil)
|
||||
t.Assert(one["passport"], data.Passport)
|
||||
t.Assert(one["create_time"], data.CreateTime)
|
||||
t.Assert(one["nickname"], data.Nickname)
|
||||
})
|
||||
}
|
||||
|
||||
func Test_DB_Upadte_KeyFieldNameMapping(t *testing.T) {
|
||||
table := createInitTable()
|
||||
defer dropTable(table)
|
||||
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
type User struct {
|
||||
Id int
|
||||
Passport string
|
||||
Password string
|
||||
Nickname string
|
||||
CreateTime string
|
||||
}
|
||||
data := User{
|
||||
Id: 1,
|
||||
Passport: "user_10",
|
||||
Password: "pass_10",
|
||||
Nickname: "name_10",
|
||||
CreateTime: "2020-10-10 12:00:01",
|
||||
}
|
||||
_, err := db.Update(table, data, "id=1")
|
||||
t.Assert(err, nil)
|
||||
|
||||
one, err := db.GetOne(fmt.Sprintf("SELECT * FROM %s WHERE id=?", table), 1)
|
||||
t.Assert(err, nil)
|
||||
t.Assert(one["passport"], data.Passport)
|
||||
t.Assert(one["create_time"], data.CreateTime)
|
||||
t.Assert(one["nickname"], data.Nickname)
|
||||
})
|
||||
}
|
||||
|
||||
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()
|
||||
defer dropTable(table)
|
||||
|
||||
@ -11,6 +11,7 @@ import (
|
||||
"fmt"
|
||||
"github.com/gogf/gf/container/garray"
|
||||
"github.com/gogf/gf/container/gmap"
|
||||
"github.com/gogf/gf/encoding/gparser"
|
||||
"github.com/gogf/gf/util/gutil"
|
||||
"testing"
|
||||
"time"
|
||||
@ -52,20 +53,20 @@ func Test_Model_Insert(t *testing.T) {
|
||||
t.Assert(n, 1)
|
||||
|
||||
type User struct {
|
||||
Id int `gconv:"id"`
|
||||
Uid int `gconv:"uid"`
|
||||
Passport string `json:"passport"`
|
||||
Password string `gconv:"password"`
|
||||
Nickname string `gconv:"nickname"`
|
||||
CreateTime string `json:"create_time"`
|
||||
Id int `gconv:"id"`
|
||||
Uid int `gconv:"uid"`
|
||||
Passport string `json:"passport"`
|
||||
Password string `gconv:"password"`
|
||||
Nickname string `gconv:"nickname"`
|
||||
CreateTime *gtime.Time `json:"create_time"`
|
||||
}
|
||||
// Model inserting.
|
||||
result, err = db.Table(table).Filter().Data(User{
|
||||
Id: 3,
|
||||
Uid: 3,
|
||||
Passport: "t3",
|
||||
Password: "25d55ad283aa400af464c76d713c07ad",
|
||||
Nickname: "name_3",
|
||||
CreateTime: gtime.Now().String(),
|
||||
Id: 3,
|
||||
Uid: 3,
|
||||
Passport: "t3",
|
||||
Password: "25d55ad283aa400af464c76d713c07ad",
|
||||
Nickname: "name_3",
|
||||
}).Insert()
|
||||
t.Assert(err, nil)
|
||||
n, _ = result.RowsAffected()
|
||||
@ -80,7 +81,7 @@ func Test_Model_Insert(t *testing.T) {
|
||||
Passport: "t4",
|
||||
Password: "25d55ad283aa400af464c76d713c07ad",
|
||||
Nickname: "T4",
|
||||
CreateTime: gtime.Now().String(),
|
||||
CreateTime: gtime.Now(),
|
||||
}).Insert()
|
||||
t.Assert(err, nil)
|
||||
n, _ = result.RowsAffected()
|
||||
@ -94,7 +95,164 @@ func Test_Model_Insert(t *testing.T) {
|
||||
n, _ = result.RowsAffected()
|
||||
t.Assert(n, 3)
|
||||
})
|
||||
}
|
||||
|
||||
// Fix issue: https://github.com/gogf/gf/issues/819
|
||||
func Test_Model_Insert_WithStructAndSliceAttribute(t *testing.T) {
|
||||
table := createTable()
|
||||
defer dropTable(table)
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
type Password struct {
|
||||
Salt string `json:"salt"`
|
||||
Pass string `json:"pass"`
|
||||
}
|
||||
data := g.Map{
|
||||
"id": 1,
|
||||
"passport": "t1",
|
||||
"password": &Password{"123", "456"},
|
||||
"nickname": []string{"A", "B", "C"},
|
||||
"create_time": gtime.Now().String(),
|
||||
}
|
||||
_, err := db.Table(table).Data(data).Insert()
|
||||
t.Assert(err, nil)
|
||||
|
||||
one, err := db.Table(table).One("id", 1)
|
||||
t.Assert(err, nil)
|
||||
t.Assert(one["passport"], data["passport"])
|
||||
t.Assert(one["create_time"], data["create_time"])
|
||||
t.Assert(one["nickname"], gparser.MustToJson(data["nickname"]))
|
||||
})
|
||||
}
|
||||
|
||||
func Test_Model_Insert_KeyFieldNameMapping(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
|
||||
}
|
||||
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.Assert(err, nil)
|
||||
|
||||
one, err := db.Model(table).FindOne(1)
|
||||
t.Assert(err, nil)
|
||||
t.Assert(one["passport"], data.Passport)
|
||||
t.Assert(one["create_time"], data.CreateTime)
|
||||
t.Assert(one["nickname"], data.Nickname)
|
||||
})
|
||||
}
|
||||
|
||||
func Test_Model_Update_KeyFieldNameMapping(t *testing.T) {
|
||||
table := createInitTable()
|
||||
defer dropTable(table)
|
||||
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
type User struct {
|
||||
Id int
|
||||
Passport string
|
||||
Password string
|
||||
Nickname string
|
||||
CreateTime string
|
||||
}
|
||||
data := User{
|
||||
Id: 1,
|
||||
Passport: "user_10",
|
||||
Password: "pass_10",
|
||||
Nickname: "name_10",
|
||||
CreateTime: "2020-10-10 12:00:01",
|
||||
}
|
||||
_, err := db.Model(table).Data(data).WherePri(1).Update()
|
||||
t.Assert(err, nil)
|
||||
|
||||
one, err := db.Model(table).FindOne(1)
|
||||
t.Assert(err, nil)
|
||||
t.Assert(one["passport"], data.Passport)
|
||||
t.Assert(one["create_time"], data.CreateTime)
|
||||
t.Assert(one["nickname"], data.Nickname)
|
||||
})
|
||||
}
|
||||
|
||||
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()
|
||||
defer dropTable(table)
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
data := g.Map{
|
||||
"id": 1,
|
||||
"passport": "t1",
|
||||
"password": "p1",
|
||||
"nickname": "n1",
|
||||
"create_time": "2020-10-10 20:09:18.334",
|
||||
}
|
||||
_, err := db.Table(table).Data(data).Insert()
|
||||
t.Assert(err, nil)
|
||||
|
||||
one, err := db.Table(table).One("id", 1)
|
||||
t.Assert(err, nil)
|
||||
t.Assert(one["passport"], data["passport"])
|
||||
t.Assert(one["create_time"], "2020-10-10 20:09:18")
|
||||
t.Assert(one["nickname"], data["nickname"])
|
||||
})
|
||||
}
|
||||
|
||||
func Test_Model_BatchInsertWithArrayStruct(t *testing.T) {
|
||||
table := createTable()
|
||||
defer dropTable(table)
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
user := db.Table(table)
|
||||
array := garray.New()
|
||||
for i := 1; i <= SIZE; i++ {
|
||||
array.Append(g.Map{
|
||||
"id": i,
|
||||
"uid": i,
|
||||
"passport": fmt.Sprintf("t%d", i),
|
||||
"password": "25d55ad283aa400af464c76d713c07ad",
|
||||
"nickname": fmt.Sprintf("name_%d", i),
|
||||
"create_time": gtime.Now().String(),
|
||||
})
|
||||
}
|
||||
|
||||
result, err := user.Filter().Data(array).Insert()
|
||||
t.Assert(err, nil)
|
||||
n, _ := result.LastInsertId()
|
||||
t.Assert(n, SIZE)
|
||||
})
|
||||
}
|
||||
|
||||
func Test_Model_InsertIgnore(t *testing.T) {
|
||||
@ -2243,6 +2401,19 @@ func Test_Model_DryRun(t *testing.T) {
|
||||
})
|
||||
}
|
||||
|
||||
func Test_Model_Join_SubQuery(t *testing.T) {
|
||||
table := createInitTable()
|
||||
defer dropTable(table)
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
subQuery := fmt.Sprintf("select * from `%s`", table)
|
||||
r, err := db.Table(table, "t1").Fields("t2.id").LeftJoin(subQuery, "t2", "t2.id=t1.id").Array()
|
||||
t.Assert(err, nil)
|
||||
t.Assert(len(r), SIZE)
|
||||
t.Assert(r[0], "1")
|
||||
t.Assert(r[SIZE-1], SIZE)
|
||||
})
|
||||
}
|
||||
|
||||
func Test_Model_Cache(t *testing.T) {
|
||||
table := createInitTable()
|
||||
defer dropTable(table)
|
||||
|
||||
@ -151,7 +151,7 @@ CREATE TABLE %s (
|
||||
}
|
||||
|
||||
func Test_SoftUpdateTime(t *testing.T) {
|
||||
table := "time_test_table"
|
||||
table := "time_test_table_" + gtime.TimestampNanoStr()
|
||||
if _, err := db.Exec(fmt.Sprintf(`
|
||||
CREATE TABLE %s (
|
||||
id int(11) NOT NULL,
|
||||
|
||||
@ -764,3 +764,31 @@ func Test_Transaction(t *testing.T) {
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func Test_Transaction_Panic(t *testing.T) {
|
||||
table := createInitTable()
|
||||
defer dropTable(table)
|
||||
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
err := db.Transaction(func(tx *gdb.TX) error {
|
||||
if _, err := tx.Replace(table, g.Map{
|
||||
"id": 1,
|
||||
"passport": "USER_1",
|
||||
"password": "PASS_1",
|
||||
"nickname": "NAME_1",
|
||||
"create_time": gtime.Now().String(),
|
||||
}); err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
panic("error")
|
||||
return nil
|
||||
})
|
||||
t.AssertNE(err, nil)
|
||||
|
||||
if value, err := db.Table(table).Fields("nickname").Where("id", 1).Value(); err != nil {
|
||||
gtest.Error(err)
|
||||
} else {
|
||||
t.Assert(value.String(), "name_1")
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
49
database/gdb/gdb_z_oracle_internal_test.go
Normal file
49
database/gdb/gdb_z_oracle_internal_test.go
Normal file
@ -0,0 +1,49 @@
|
||||
// Copyright 2019 gf Author(https://github.com/gogf/gf). All Rights Reserved.
|
||||
//
|
||||
// This Source Code Form is subject to the terms of the MIT License.
|
||||
// If a copy of the MIT was not distributed with this file,
|
||||
// You can obtain one at https://github.com/gogf/gf.
|
||||
|
||||
package gdb
|
||||
|
||||
import (
|
||||
"github.com/gogf/gf/test/gtest"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func Test_Oracle_parseSql(t *testing.T) {
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
o := new(DriverOracle)
|
||||
sql := `UPDATE user SET name='john'`
|
||||
newSql := o.parseSql(sql)
|
||||
t.Assert(newSql, sql)
|
||||
})
|
||||
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
o := new(DriverOracle)
|
||||
sql := `SELECT * FROM user`
|
||||
newSql := o.parseSql(sql)
|
||||
t.Assert(newSql, sql)
|
||||
})
|
||||
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
o := new(DriverOracle)
|
||||
sql := `SELECT * FROM user LIMIT 0, 10`
|
||||
newSql := o.parseSql(sql)
|
||||
t.Assert(newSql, `SELECT * FROM (SELECT GFORM.*, ROWNUM ROWNUM_ FROM (SELECT * FROM user ) GFORM WHERE ROWNUM <= 10) WHERE ROWNUM_ >= 0`)
|
||||
})
|
||||
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
o := new(DriverOracle)
|
||||
sql := `SELECT * FROM user LIMIT 1`
|
||||
newSql := o.parseSql(sql)
|
||||
t.Assert(newSql, `SELECT * FROM (SELECT GFORM.*, ROWNUM ROWNUM_ FROM (SELECT * FROM user ) GFORM WHERE ROWNUM <= 1) WHERE ROWNUM_ >= 0`)
|
||||
})
|
||||
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
o := new(DriverOracle)
|
||||
sql := `SELECT ENAME FROM USER_INFO WHERE ID=2 LIMIT 1`
|
||||
newSql := o.parseSql(sql)
|
||||
t.Assert(newSql, `SELECT * FROM (SELECT GFORM.*, ROWNUM ROWNUM_ FROM (SELECT ENAME FROM USER_INFO WHERE ID=2 ) GFORM WHERE ROWNUM <= 1) WHERE ROWNUM_ >= 0`)
|
||||
})
|
||||
}
|
||||
@ -68,6 +68,7 @@ func StackWithFilters(filters []string, skip ...int) string {
|
||||
file[0:len(goRootForFilter)] == goRootForFilter {
|
||||
continue
|
||||
}
|
||||
// Custom filtering.
|
||||
filtered = false
|
||||
for _, filter := range filters {
|
||||
if filter != "" && strings.Contains(file, filter) {
|
||||
|
||||
@ -55,8 +55,10 @@ func NewWithTag(data interface{}, tags string, safe ...bool) *Json {
|
||||
}
|
||||
}
|
||||
default:
|
||||
rv := reflect.ValueOf(data)
|
||||
kind := rv.Kind()
|
||||
var (
|
||||
rv = reflect.ValueOf(data)
|
||||
kind = rv.Kind()
|
||||
)
|
||||
if kind == reflect.Ptr {
|
||||
rv = rv.Elem()
|
||||
kind = rv.Kind()
|
||||
@ -72,9 +74,7 @@ func NewWithTag(data interface{}, tags string, safe ...bool) *Json {
|
||||
}
|
||||
case reflect.Map, reflect.Struct:
|
||||
i := interface{}(nil)
|
||||
// Note that it uses Map function implementing the converting.
|
||||
// Note that it here should not use MapDeep function if you really know what it means.
|
||||
i = gconv.Map(data, tags)
|
||||
i = gconv.MapDeep(data, tags)
|
||||
j = &Json{
|
||||
p: &i,
|
||||
c: byte(gDEFAULT_SPLIT_CHAR),
|
||||
@ -207,6 +207,21 @@ func LoadContentType(dataType string, data interface{}, safe ...bool) (*Json, er
|
||||
return doLoadContent(dataType, content, safe...)
|
||||
}
|
||||
|
||||
// IsValidDataType checks and returns whether given <dataType> a valid data type for loading.
|
||||
func IsValidDataType(dataType string) bool {
|
||||
if dataType == "" {
|
||||
return false
|
||||
}
|
||||
if dataType[0] == '.' {
|
||||
dataType = dataType[1:]
|
||||
}
|
||||
switch dataType {
|
||||
case "json", "js", "xml", "yaml", "yml", "toml", "ini":
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// checkDataType automatically checks and returns the data type for <content>.
|
||||
// Note that it uses regular expression for loose checking, you can use LoadXXX/LoadContentType
|
||||
// functions to load the content for certain content type.
|
||||
@ -215,8 +230,9 @@ func checkDataType(content []byte) string {
|
||||
return "json"
|
||||
} else if gregex.IsMatch(`^<.+>[\S\s]+<.+>$`, content) {
|
||||
return "xml"
|
||||
} else if (gregex.IsMatch(`^[\n\r]*[\w\-\s\t]+\s*:\s*".+"`, content) || gregex.IsMatch(`^[\n\r]*[\w\-\s\t]+\s*:\s*\w+`, content)) ||
|
||||
(gregex.IsMatch(`[\n\r]+[\w\-\s\t]+\s*:\s*".+"`, content) || gregex.IsMatch(`[\n\r]+[\w\-\s\t]+\s*:\s*\w+`, content)) {
|
||||
} else if !gregex.IsMatch(`[\n\r]*[\s\t\w\-\."]+\s*=\s*"""[\s\S]+"""`, content) && !gregex.IsMatch(`[\n\r]*[\s\t\w\-\."]+\s*=\s*'''[\s\S]+'''`, content) &&
|
||||
((gregex.IsMatch(`^[\n\r]*[\w\-\s\t]+\s*:\s*".+"`, content) || gregex.IsMatch(`^[\n\r]*[\w\-\s\t]+\s*:\s*\w+`, content)) ||
|
||||
(gregex.IsMatch(`[\n\r]+[\w\-\s\t]+\s*:\s*".+"`, content) || gregex.IsMatch(`[\n\r]+[\w\-\s\t]+\s*:\s*\w+`, content))) {
|
||||
return "yml"
|
||||
} else if !gregex.IsMatch(`^[\s\t\n\r]*;.+`, content) &&
|
||||
!gregex.IsMatch(`[\s\t\n\r]+;.+`, content) &&
|
||||
|
||||
@ -112,4 +112,22 @@ app_conf = ./config/app.ini
|
||||
`)
|
||||
t.Assert(checkDataType(data), "ini")
|
||||
})
|
||||
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
data := []byte(`
|
||||
# API Server
|
||||
[server]
|
||||
address = ":8199"
|
||||
|
||||
# Jenkins
|
||||
[jenkins]
|
||||
url = "https://jenkins-swimlane.com"
|
||||
nodeJsStaticBuildCmdTpl = """
|
||||
npm i --registry=https://registry.npm.taobao.org
|
||||
wget http://consul.infra:8500/v1/kv/app_{{.SwimlaneName}}/{{.RepoName}}/.env.qa?raw=true -O ./env.qa
|
||||
npm run build:qa
|
||||
"""
|
||||
`)
|
||||
t.Assert(checkDataType(data), "toml")
|
||||
})
|
||||
}
|
||||
|
||||
@ -47,3 +47,25 @@ func Test_Load_NewWithTag(t *testing.T) {
|
||||
t.Assert(j.Get("addr-json"), nil)
|
||||
})
|
||||
}
|
||||
|
||||
func Test_Load_New_CustomStruct(t *testing.T) {
|
||||
type Base struct {
|
||||
Id int
|
||||
}
|
||||
type User struct {
|
||||
Base
|
||||
Name string
|
||||
}
|
||||
user := new(User)
|
||||
user.Id = 1
|
||||
user.Name = "john"
|
||||
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
j := gjson.New(user)
|
||||
t.AssertNE(j, nil)
|
||||
|
||||
s, err := j.ToJsonString()
|
||||
t.Assert(err, nil)
|
||||
t.Assert(s == `{"Id":1,"Name":"john"}` || s == `{"Name":"john","Id":1}`, true)
|
||||
})
|
||||
}
|
||||
|
||||
@ -10,7 +10,7 @@ import (
|
||||
"github.com/gogf/gf/encoding/gjson"
|
||||
)
|
||||
|
||||
// New creates a Parser object with any variable type of <data>, but <data> should be a map or
|
||||
// New creates a Parser object with any variable type of <data>, but <data> should be a map, struct or
|
||||
// slice for data access reason, or it will make no sense.
|
||||
//
|
||||
// The parameter <safe> specifies whether using this Json object in concurrent-safe context, which
|
||||
|
||||
@ -26,6 +26,12 @@ type ApiCause interface {
|
||||
Cause() error
|
||||
}
|
||||
|
||||
// ApiLevel is the interface for Current/Next feature.
|
||||
type ApiLevel interface {
|
||||
Current() error
|
||||
Next() error
|
||||
}
|
||||
|
||||
// New creates and returns an error which is formatted from given text.
|
||||
func New(text string) error {
|
||||
if text == "" {
|
||||
@ -120,3 +126,27 @@ func Stack(err error) string {
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
// Current creates and returns the current level error.
|
||||
// It returns nil if current level error is nil.
|
||||
func Current(err error) error {
|
||||
if err == nil {
|
||||
return nil
|
||||
}
|
||||
if e, ok := err.(ApiLevel); ok {
|
||||
return e.Current()
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Next returns the next level error.
|
||||
// It returns nil if current level error or the next level error is nil.
|
||||
func Next(err error) error {
|
||||
if err == nil {
|
||||
return nil
|
||||
}
|
||||
if e, ok := err.(ApiLevel); ok {
|
||||
return e.Next()
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
@ -27,6 +27,7 @@ const (
|
||||
|
||||
var (
|
||||
// goRootForFilter is used for stack filtering purpose.
|
||||
// Mainly for development environment.
|
||||
goRootForFilter = runtime.GOROOT()
|
||||
)
|
||||
|
||||
@ -36,8 +37,11 @@ func init() {
|
||||
}
|
||||
}
|
||||
|
||||
// Error implements the interface of Error, it returns the error as string.
|
||||
// Error implements the interface of Error, it returns all the error as string.
|
||||
func (err *Error) Error() string {
|
||||
if err == nil {
|
||||
return ""
|
||||
}
|
||||
if err.text != "" {
|
||||
if err.error != nil {
|
||||
return err.text + ": " + err.error.Error()
|
||||
@ -49,6 +53,9 @@ func (err *Error) Error() string {
|
||||
|
||||
// Cause returns the root cause error.
|
||||
func (err *Error) Cause() error {
|
||||
if err == nil {
|
||||
return nil
|
||||
}
|
||||
loop := err
|
||||
for loop != nil {
|
||||
if loop.error != nil {
|
||||
@ -66,8 +73,8 @@ func (err *Error) Cause() error {
|
||||
|
||||
// Format formats the frame according to the fmt.Formatter interface.
|
||||
//
|
||||
// %v, %s : Print the error string;
|
||||
// %-v, %-s : Print current error string;
|
||||
// %v, %s : Print all the error string;
|
||||
// %-v, %-s : Print current level error string;
|
||||
// %+s : Print full stack error list;
|
||||
// %+v : Print the error string and full stack error list;
|
||||
func (err *Error) Format(s fmt.State, verb rune) {
|
||||
@ -120,6 +127,28 @@ func (err *Error) Stack() string {
|
||||
return buffer.String()
|
||||
}
|
||||
|
||||
// Current creates and returns the current level error.
|
||||
// It returns nil if current level error is nil.
|
||||
func (err *Error) Current() error {
|
||||
if err == nil {
|
||||
return nil
|
||||
}
|
||||
return &Error{
|
||||
error: nil,
|
||||
stack: err.stack,
|
||||
text: err.text,
|
||||
}
|
||||
}
|
||||
|
||||
// Next returns the next level error.
|
||||
// It returns nil if current level error or the next level error is nil.
|
||||
func (err *Error) Next() error {
|
||||
if err == nil {
|
||||
return nil
|
||||
}
|
||||
return err.error
|
||||
}
|
||||
|
||||
// formatSubStack formats the stack for error.
|
||||
func formatSubStack(st stack, buffer *bytes.Buffer) {
|
||||
index := 1
|
||||
|
||||
@ -127,3 +127,31 @@ func Test_Stack(t *testing.T) {
|
||||
//fmt.Printf("%+v", err)
|
||||
})
|
||||
}
|
||||
|
||||
func Test_Current(t *testing.T) {
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
err := errors.New("1")
|
||||
err = gerror.Wrap(err, "2")
|
||||
err = gerror.Wrap(err, "3")
|
||||
t.Assert(err.Error(), "3: 2: 1")
|
||||
t.Assert(gerror.Current(err).Error(), "3")
|
||||
})
|
||||
}
|
||||
|
||||
func Test_Next(t *testing.T) {
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
err := errors.New("1")
|
||||
err = gerror.Wrap(err, "2")
|
||||
err = gerror.Wrap(err, "3")
|
||||
t.Assert(err.Error(), "3: 2: 1")
|
||||
|
||||
err = gerror.Next(err)
|
||||
t.Assert(err.Error(), "2: 1")
|
||||
|
||||
err = gerror.Next(err)
|
||||
t.Assert(err.Error(), "1")
|
||||
|
||||
err = gerror.Next(err)
|
||||
t.Assert(err, nil)
|
||||
})
|
||||
}
|
||||
|
||||
@ -10,17 +10,14 @@ import (
|
||||
"github.com/gogf/gf/os/glog"
|
||||
)
|
||||
|
||||
// SetDebug disables/enables debug level for logging component globally.
|
||||
func SetDebug(debug bool) {
|
||||
glog.SetDebug(debug)
|
||||
}
|
||||
|
||||
// SetLogLevel sets the logging level globally.
|
||||
// Deprecated, use functions of package glog or g.Log() instead.
|
||||
func SetLogLevel(level int) {
|
||||
glog.SetLevel(level)
|
||||
}
|
||||
|
||||
// GetLogLevel returns the global logging level.
|
||||
// Deprecated, use functions of package glog or g.Log() instead.
|
||||
func GetLogLevel() int {
|
||||
return glog.GetLevel()
|
||||
}
|
||||
|
||||
@ -6,7 +6,17 @@
|
||||
|
||||
package g
|
||||
|
||||
import "github.com/gogf/gf/net/ghttp"
|
||||
import (
|
||||
"github.com/gogf/gf/internal/intlog"
|
||||
"github.com/gogf/gf/net/ghttp"
|
||||
)
|
||||
|
||||
// SetEnabled enables/disables the GoFrame internal logging manually.
|
||||
// Note that this function is not concurrent safe, be aware of the DATA RACE,
|
||||
// which means you should call this function in your boot but not the runtime.
|
||||
func SetDebug(enabled bool) {
|
||||
intlog.SetEnabled(enabled)
|
||||
}
|
||||
|
||||
// SetServerGraceful enables/disables graceful reload feature of http Web Server.
|
||||
// This feature is disabled in default.
|
||||
|
||||
@ -31,26 +31,26 @@ func Database(name ...string) gdb.DB {
|
||||
}
|
||||
instanceKey := fmt.Sprintf("%s.%s", gFRAME_CORE_COMPONENT_NAME_DATABASE, group)
|
||||
db := instances.GetOrSetFuncLock(instanceKey, func() interface{} {
|
||||
// If configuration file does not exist but the gdb configurations are already set.
|
||||
if !Config().Available() && gdb.GetConfig(group) != nil {
|
||||
db, err := gdb.Instance(group)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return db
|
||||
}
|
||||
// Check the configuration file.
|
||||
var m map[string]interface{}
|
||||
var (
|
||||
configMap map[string]interface{}
|
||||
configNodeKey string
|
||||
)
|
||||
// It firstly searches the configuration of the instance name.
|
||||
nodeKey, _ := gutil.MapPossibleItemByKey(Config().GetMap("."), gDATABASE_NODE_NAME)
|
||||
if nodeKey == "" {
|
||||
nodeKey = gDATABASE_NODE_NAME
|
||||
if Config().Available() {
|
||||
configNodeKey, _ = gutil.MapPossibleItemByKey(Config().GetMap("."), gDATABASE_NODE_NAME)
|
||||
if configNodeKey == "" {
|
||||
configNodeKey = gDATABASE_NODE_NAME
|
||||
}
|
||||
configMap = Config().GetMap(configNodeKey)
|
||||
}
|
||||
if m = Config().GetMap(nodeKey); len(m) == 0 {
|
||||
if len(configMap) == 0 && !gdb.IsConfigured() {
|
||||
panic(fmt.Sprintf(`database init failed: "%s" node not found, is config file or configuration missing?`, gDATABASE_NODE_NAME))
|
||||
}
|
||||
if len(configMap) == 0 {
|
||||
configMap = make(map[string]interface{})
|
||||
}
|
||||
// Parse <m> as map-slice and adds it to gdb's global configurations.
|
||||
for group, groupConfig := range m {
|
||||
for g, groupConfig := range configMap {
|
||||
cg := gdb.ConfigGroup{}
|
||||
switch value := groupConfig.(type) {
|
||||
case []interface{}:
|
||||
@ -65,37 +65,51 @@ func Database(name ...string) gdb.DB {
|
||||
}
|
||||
}
|
||||
if len(cg) > 0 {
|
||||
intlog.Printf("%s, %#v", group, cg)
|
||||
gdb.SetConfigGroup(group, cg)
|
||||
if gdb.GetConfig(group) == nil {
|
||||
intlog.Printf("add configuration for group: %s, %#v", g, cg)
|
||||
gdb.SetConfigGroup(g, cg)
|
||||
} else {
|
||||
intlog.Printf("ignore configuration as it already exists for group: %s, %#v", g, cg)
|
||||
intlog.Printf("%s, %#v", g, cg)
|
||||
}
|
||||
}
|
||||
}
|
||||
// Parse <m> as a single node configuration,
|
||||
// which is the default group configuration.
|
||||
if node := parseDBConfigNode(m); node != nil {
|
||||
if node := parseDBConfigNode(configMap); node != nil {
|
||||
cg := gdb.ConfigGroup{}
|
||||
if node.LinkInfo != "" || node.Host != "" {
|
||||
cg = append(cg, *node)
|
||||
}
|
||||
|
||||
if len(cg) > 0 {
|
||||
intlog.Printf("%s, %#v", gdb.DEFAULT_GROUP_NAME, cg)
|
||||
gdb.SetConfigGroup(gdb.DEFAULT_GROUP_NAME, cg)
|
||||
if gdb.GetConfig(group) == nil {
|
||||
intlog.Printf("add configuration for group: %s, %#v", gdb.DEFAULT_GROUP_NAME, cg)
|
||||
gdb.SetConfigGroup(gdb.DEFAULT_GROUP_NAME, cg)
|
||||
} else {
|
||||
intlog.Printf("ignore configuration as it already exists for group: %s, %#v", gdb.DEFAULT_GROUP_NAME, cg)
|
||||
intlog.Printf("%s, %#v", gdb.DEFAULT_GROUP_NAME, cg)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Create a new ORM object with given configurations.
|
||||
if db, err := gdb.New(name...); err == nil {
|
||||
// Initialize logger for ORM.
|
||||
var m map[string]interface{}
|
||||
m = Config().GetMap(fmt.Sprintf("%s.%s", nodeKey, gLOGGER_NODE_NAME))
|
||||
if len(m) == 0 {
|
||||
m = Config().GetMap(nodeKey)
|
||||
}
|
||||
if len(m) > 0 {
|
||||
if err := db.GetLogger().SetConfigWithMap(m); err != nil {
|
||||
panic(err)
|
||||
if Config().Available() {
|
||||
// Initialize logger for ORM.
|
||||
var loggerConfigMap map[string]interface{}
|
||||
loggerConfigMap = Config().GetMap(fmt.Sprintf("%s.%s", configNodeKey, gLOGGER_NODE_NAME))
|
||||
if len(loggerConfigMap) == 0 {
|
||||
loggerConfigMap = Config().GetMap(configNodeKey)
|
||||
}
|
||||
if len(loggerConfigMap) > 0 {
|
||||
if err := db.GetLogger().SetConfigWithMap(loggerConfigMap); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
}
|
||||
return db
|
||||
} else {
|
||||
// It panics often because it dose not find its configuration for given group.
|
||||
panic(err)
|
||||
}
|
||||
return nil
|
||||
|
||||
3
go.mod
3
go.mod
@ -5,7 +5,7 @@ go 1.11
|
||||
require (
|
||||
github.com/BurntSushi/toml v0.3.1
|
||||
github.com/clbanning/mxj v1.8.5-0.20200714211355-ff02cfb8ea28
|
||||
github.com/fsnotify/fsnotify v1.4.7
|
||||
github.com/fsnotify/fsnotify v1.4.9
|
||||
github.com/go-sql-driver/mysql v1.5.0
|
||||
github.com/gomodule/redigo v2.0.0+incompatible
|
||||
github.com/gorilla/websocket v1.4.1
|
||||
@ -15,7 +15,6 @@ require (
|
||||
github.com/mattn/go-runewidth v0.0.9 // indirect
|
||||
github.com/olekukonko/tablewriter v0.0.1
|
||||
golang.org/x/net v0.0.0-20200602114024-627f9648deb9
|
||||
golang.org/x/sys v0.0.0-20200625212154-ddb9806d33ae // indirect
|
||||
golang.org/x/text v0.3.2
|
||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c
|
||||
)
|
||||
|
||||
@ -33,9 +33,12 @@ func init() {
|
||||
}
|
||||
|
||||
// SetEnabled enables/disables the internal logging manually.
|
||||
// Note that this function is not current safe, be aware of the DATA RACE.
|
||||
// Note that this function is not concurrent safe, be aware of the DATA RACE.
|
||||
func SetEnabled(enabled bool) {
|
||||
isGFDebug = enabled
|
||||
// If they're the same, it does not write the <isGFDebug> but only reading operation.
|
||||
if isGFDebug != enabled {
|
||||
isGFDebug = enabled
|
||||
}
|
||||
}
|
||||
|
||||
// IsEnabled checks and returns whether current process is in GF development.
|
||||
|
||||
@ -149,3 +149,14 @@ func (c *Client) Proxy(proxyURL string) *Client {
|
||||
newClient.SetProxy(proxyURL)
|
||||
return c
|
||||
}
|
||||
|
||||
// RedirectLimit is a chaining function,
|
||||
// which sets the redirect limit the number of jumps for the request.
|
||||
func (c *Client) RedirectLimit(redirectLimit int) *Client {
|
||||
newClient := c
|
||||
if c.parent == nil {
|
||||
newClient = c.Clone()
|
||||
}
|
||||
newClient.SetRedirectLimit(redirectLimit)
|
||||
return c
|
||||
}
|
||||
|
||||
@ -276,6 +276,10 @@ func (r *Request) parseBody() {
|
||||
return
|
||||
}
|
||||
r.parsedBody = true
|
||||
// There's no data posted.
|
||||
if r.ContentLength == 0 {
|
||||
return
|
||||
}
|
||||
if body := r.GetBody(); len(body) > 0 {
|
||||
// Trim space/new line characters.
|
||||
body = bytes.TrimSpace(body)
|
||||
@ -306,6 +310,10 @@ func (r *Request) parseForm() {
|
||||
return
|
||||
}
|
||||
r.parsedForm = true
|
||||
// There's no data posted.
|
||||
if r.ContentLength == 0 {
|
||||
return
|
||||
}
|
||||
if contentType := r.Header.Get("Content-Type"); contentType != "" {
|
||||
var err error
|
||||
if gstr.Contains(contentType, "multipart/") {
|
||||
|
||||
@ -267,11 +267,6 @@ func (s *Server) Start() error {
|
||||
return errors.New("[ghttp] server is already running")
|
||||
}
|
||||
|
||||
// If there's no route registered and no static service enabled,
|
||||
// it then returns an error of invalid usage of server.
|
||||
if len(s.routesMap) == 0 && !s.config.FileServerEnabled {
|
||||
return errors.New(`[ghttp] there's no route set or static feature enabled, did you forget import the router?`)
|
||||
}
|
||||
// Logging path setting check.
|
||||
if s.config.LogPath != "" {
|
||||
if err := s.config.Logger.SetPath(s.config.LogPath); err != nil {
|
||||
@ -316,6 +311,12 @@ func (s *Server) Start() error {
|
||||
// Check the group routes again.
|
||||
s.handlePreBindItems()
|
||||
|
||||
// If there's no route registered and no static service enabled,
|
||||
// it then returns an error of invalid usage of server.
|
||||
if len(s.routesMap) == 0 && !s.config.FileServerEnabled {
|
||||
return errors.New(`[ghttp] there's no route set or static feature enabled, did you forget import the router?`)
|
||||
}
|
||||
|
||||
// Start the HTTP server.
|
||||
reloaded := false
|
||||
fdMapStr := genv.Get(gADMIN_ACTION_RELOAD_ENVKEY)
|
||||
|
||||
@ -92,7 +92,7 @@ type ServerConfig struct {
|
||||
// size of the request body.
|
||||
//
|
||||
// It can be configured in configuration file using string like: 1m, 10m, 500kb etc.
|
||||
// It's 1024 bytes in default.
|
||||
// It's 10240 bytes in default.
|
||||
MaxHeaderBytes int
|
||||
|
||||
// KeepAlive enables HTTP keep-alive.
|
||||
@ -157,6 +157,9 @@ type ServerConfig struct {
|
||||
// SessionIdName specifies the session id name.
|
||||
SessionIdName string
|
||||
|
||||
// SessionCookieOutput specifies whether automatic outputting session id to cookie.
|
||||
SessionCookieOutput bool
|
||||
|
||||
// SessionPath specifies the session storage directory path for storing session files.
|
||||
// It only makes sense if the session storage is type of file storage.
|
||||
SessionPath string
|
||||
@ -231,50 +234,56 @@ type ServerConfig struct {
|
||||
Graceful bool
|
||||
}
|
||||
|
||||
// Config creates and returns a ServerConfig object with default configurations.
|
||||
// Deprecated. Use NewConfig instead.
|
||||
func Config() ServerConfig {
|
||||
return NewConfig()
|
||||
}
|
||||
|
||||
// NewConfig creates and returns a ServerConfig object with default configurations.
|
||||
// Note that, do not define this default configuration to local package variable, as there're
|
||||
// some pointer attributes that may be shared in different servers.
|
||||
func Config() ServerConfig {
|
||||
func NewConfig() ServerConfig {
|
||||
return ServerConfig{
|
||||
Address: "",
|
||||
HTTPSAddr: "",
|
||||
Handler: nil,
|
||||
ReadTimeout: 60 * time.Second,
|
||||
WriteTimeout: 0, // No timeout.
|
||||
IdleTimeout: 60 * time.Second,
|
||||
MaxHeaderBytes: 10240, // 10KB
|
||||
KeepAlive: true,
|
||||
IndexFiles: []string{"index.html", "index.htm"},
|
||||
IndexFolder: false,
|
||||
ServerAgent: "GF HTTP Server",
|
||||
ServerRoot: "",
|
||||
StaticPaths: make([]staticPathItem, 0),
|
||||
FileServerEnabled: false,
|
||||
CookieMaxAge: time.Hour * 24 * 365,
|
||||
CookiePath: "/",
|
||||
CookieDomain: "",
|
||||
SessionMaxAge: time.Hour * 24,
|
||||
SessionIdName: "gfsessionid",
|
||||
SessionPath: gsession.DefaultStorageFilePath,
|
||||
Logger: glog.New(),
|
||||
LogStdout: true,
|
||||
ErrorStack: true,
|
||||
ErrorLogEnabled: true,
|
||||
ErrorLogPattern: "error-{Ymd}.log",
|
||||
AccessLogEnabled: false,
|
||||
AccessLogPattern: "access-{Ymd}.log",
|
||||
DumpRouterMap: true,
|
||||
ClientMaxBodySize: 8 * 1024 * 1024, // 8MB
|
||||
FormParsingMemory: 1024 * 1024, // 1MB
|
||||
Rewrites: make(map[string]string),
|
||||
Graceful: false,
|
||||
Address: "",
|
||||
HTTPSAddr: "",
|
||||
Handler: nil,
|
||||
ReadTimeout: 60 * time.Second,
|
||||
WriteTimeout: 0, // No timeout.
|
||||
IdleTimeout: 60 * time.Second,
|
||||
MaxHeaderBytes: 10240, // 10KB
|
||||
KeepAlive: true,
|
||||
IndexFiles: []string{"index.html", "index.htm"},
|
||||
IndexFolder: false,
|
||||
ServerAgent: "GF HTTP Server",
|
||||
ServerRoot: "",
|
||||
StaticPaths: make([]staticPathItem, 0),
|
||||
FileServerEnabled: false,
|
||||
CookieMaxAge: time.Hour * 24 * 365,
|
||||
CookiePath: "/",
|
||||
CookieDomain: "",
|
||||
SessionMaxAge: time.Hour * 24,
|
||||
SessionIdName: "gfsessionid",
|
||||
SessionPath: gsession.DefaultStorageFilePath,
|
||||
SessionCookieOutput: true,
|
||||
Logger: glog.New(),
|
||||
LogStdout: true,
|
||||
ErrorStack: true,
|
||||
ErrorLogEnabled: true,
|
||||
ErrorLogPattern: "error-{Ymd}.log",
|
||||
AccessLogEnabled: false,
|
||||
AccessLogPattern: "access-{Ymd}.log",
|
||||
DumpRouterMap: true,
|
||||
ClientMaxBodySize: 8 * 1024 * 1024, // 8MB
|
||||
FormParsingMemory: 1024 * 1024, // 1MB
|
||||
Rewrites: make(map[string]string),
|
||||
Graceful: false,
|
||||
}
|
||||
}
|
||||
|
||||
// ConfigFromMap creates and returns a ServerConfig object with given map and
|
||||
// default configuration object.
|
||||
func ConfigFromMap(m map[string]interface{}) (ServerConfig, error) {
|
||||
config := Config()
|
||||
config := NewConfig()
|
||||
if err := gconv.Struct(m, &config); err != nil {
|
||||
return config, err
|
||||
}
|
||||
|
||||
@ -27,6 +27,11 @@ func (s *Server) SetSessionStorage(storage gsession.Storage) {
|
||||
s.config.SessionStorage = storage
|
||||
}
|
||||
|
||||
// SetSessionCookieOutput sets the SetSessionCookieOutput for server.
|
||||
func (s *Server) SetSessionCookieOutput(enabled bool) {
|
||||
s.config.SessionCookieOutput = enabled
|
||||
}
|
||||
|
||||
// GetSessionMaxAge returns the SessionMaxAge of server.
|
||||
func (s *Server) GetSessionMaxAge() time.Duration {
|
||||
return s.config.SessionMaxAge
|
||||
|
||||
@ -161,7 +161,9 @@ 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 request.Session.IsDirty() && request.Session.Id() != request.GetSessionId() {
|
||||
if s.config.SessionCookieOutput &&
|
||||
request.Session.IsDirty() &&
|
||||
request.Session.Id() != request.GetSessionId() {
|
||||
request.Cookie.SetSessionId(request.Session.Id())
|
||||
}
|
||||
// Output the cookie content to client.
|
||||
|
||||
@ -24,14 +24,19 @@ const (
|
||||
|
||||
// EnablePProf enables PProf feature for server.
|
||||
func (s *Server) EnablePProf(pattern ...string) {
|
||||
s.Domain(gDEFAULT_DOMAIN).EnablePProf(pattern...)
|
||||
}
|
||||
|
||||
// EnablePProf enables PProf feature for server of specified domain.
|
||||
func (d *Domain) EnablePProf(pattern ...string) {
|
||||
p := gDEFAULT_PPROF_PATTERN
|
||||
if len(pattern) > 0 && pattern[0] != "" {
|
||||
p = pattern[0]
|
||||
}
|
||||
up := &utilPProf{}
|
||||
_, _, uri, _ := s.parsePattern(p)
|
||||
_, _, uri, _ := d.server.parsePattern(p)
|
||||
uri = strings.TrimRight(uri, "/")
|
||||
s.Group(uri, func(group *RouterGroup) {
|
||||
d.Group(uri, func(group *RouterGroup) {
|
||||
group.ALL("/*action", up.Index)
|
||||
group.ALL("/cmdline", up.Cmdline)
|
||||
group.ALL("/profile", up.Profile)
|
||||
|
||||
@ -44,13 +44,15 @@ func (s *Server) getHandlersWithCache(r *Request) (parsedItems []*handlerParsedI
|
||||
}
|
||||
}
|
||||
// Search and cache the router handlers.
|
||||
value := s.serveCache.GetOrSetFunc(s.serveHandlerKey(method, r.URL.Path, r.GetHost()), func() interface{} {
|
||||
parsedItems, hasHook, hasServe = s.searchHandlers(method, r.URL.Path, r.GetHost())
|
||||
if parsedItems != nil {
|
||||
return &handlerCacheItem{parsedItems, hasHook, hasServe}
|
||||
}
|
||||
return nil
|
||||
}, gROUTE_CACHE_DURATION)
|
||||
value, _ := s.serveCache.GetOrSetFunc(
|
||||
s.serveHandlerKey(method, r.URL.Path, r.GetHost()),
|
||||
func() (interface{}, error) {
|
||||
parsedItems, hasHook, hasServe = s.searchHandlers(method, r.URL.Path, r.GetHost())
|
||||
if parsedItems != nil {
|
||||
return &handlerCacheItem{parsedItems, hasHook, hasServe}, nil
|
||||
}
|
||||
return nil, nil
|
||||
}, gROUTE_CACHE_DURATION)
|
||||
if value != nil {
|
||||
item := value.(*handlerCacheItem)
|
||||
return item.parsedItems, item.hasHook, item.hasServe
|
||||
|
||||
@ -152,3 +152,47 @@ func Test_Session_StorageFile(t *testing.T) {
|
||||
t.Assert(client.GetContent("/get?k=key1"), "")
|
||||
})
|
||||
}
|
||||
|
||||
func Test_Session_Custom_Id(t *testing.T) {
|
||||
var (
|
||||
sessionId = "1234567890"
|
||||
key = "key"
|
||||
value = "value"
|
||||
p, _ = ports.PopRand()
|
||||
s = g.Server(p)
|
||||
)
|
||||
s.BindHandler("/id", func(r *ghttp.Request) {
|
||||
if err := r.Session.SetId(sessionId); err != nil {
|
||||
r.Response.WriteExit(err.Error())
|
||||
}
|
||||
if err := r.Session.Set(key, value); err != nil {
|
||||
r.Response.WriteExit(err.Error())
|
||||
}
|
||||
r.Response.WriteExit(r.Session.Id())
|
||||
})
|
||||
s.BindHandler("/value", func(r *ghttp.Request) {
|
||||
r.Response.WriteExit(r.Session.Get(key))
|
||||
})
|
||||
s.SetPort(p)
|
||||
s.SetDumpRouterMap(false)
|
||||
s.Start()
|
||||
defer s.Shutdown()
|
||||
|
||||
time.Sleep(100 * time.Millisecond)
|
||||
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
client := g.Client()
|
||||
client.SetPrefix(fmt.Sprintf("http://127.0.0.1:%d", p))
|
||||
r, err := client.Get("/id")
|
||||
t.Assert(err, nil)
|
||||
defer r.Close()
|
||||
t.Assert(r.ReadAllString(), sessionId)
|
||||
t.Assert(r.GetCookie(s.GetSessionIdName()), sessionId)
|
||||
})
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
client := g.Client()
|
||||
client.SetPrefix(fmt.Sprintf("http://127.0.0.1:%d", p))
|
||||
client.SetHeader(s.GetSessionIdName(), sessionId)
|
||||
t.Assert(client.GetContent("/value"), value)
|
||||
})
|
||||
}
|
||||
|
||||
@ -4,7 +4,8 @@
|
||||
// If a copy of the MIT was not distributed with this file,
|
||||
// You can obtain one at https://github.com/gogf/gf.
|
||||
|
||||
// Package gcache provides high performance and concurrent-safe in-memory cache for process.
|
||||
// Package gcache provides kinds of cache management for process.
|
||||
// It default provides a concurrent-safe in-memory cache adapter for process.
|
||||
package gcache
|
||||
|
||||
import (
|
||||
@ -21,33 +22,27 @@ func Set(key interface{}, value interface{}, duration time.Duration) {
|
||||
defaultCache.Set(key, value, duration)
|
||||
}
|
||||
|
||||
// Update updates the value of <key> without changing its expiration and returns the old value.
|
||||
// The returned <exist> value is false if the <key> does not exist in the cache.
|
||||
func Update(key interface{}, value interface{}) (oldValue interface{}, exist bool) {
|
||||
return defaultCache.Update(key, value)
|
||||
}
|
||||
|
||||
// SetIfNotExist sets cache with <key>-<value> pair if <key> does not exist in the cache,
|
||||
// which is expired after <duration>. It does not expire if <duration> == 0.
|
||||
func SetIfNotExist(key interface{}, value interface{}, duration time.Duration) bool {
|
||||
func SetIfNotExist(key interface{}, value interface{}, duration time.Duration) (bool, error) {
|
||||
return defaultCache.SetIfNotExist(key, value, duration)
|
||||
}
|
||||
|
||||
// Sets batch sets cache with key-value pairs by <data>, which is expired after <duration>.
|
||||
//
|
||||
// It does not expire if <duration> == 0.
|
||||
func Sets(data map[interface{}]interface{}, duration time.Duration) {
|
||||
defaultCache.Sets(data, duration)
|
||||
func Sets(data map[interface{}]interface{}, duration time.Duration) error {
|
||||
return defaultCache.Sets(data, duration)
|
||||
}
|
||||
|
||||
// Get returns the value of <key>.
|
||||
// It returns nil if it does not exist or its value is nil.
|
||||
func Get(key interface{}) interface{} {
|
||||
func Get(key interface{}) (interface{}, error) {
|
||||
return defaultCache.Get(key)
|
||||
}
|
||||
|
||||
// GetVar retrieves and returns the value of <key> as gvar.Var.
|
||||
func GetVar(key interface{}) *gvar.Var {
|
||||
func GetVar(key interface{}) (*gvar.Var, error) {
|
||||
return defaultCache.GetVar(key)
|
||||
}
|
||||
|
||||
@ -56,14 +51,14 @@ func GetVar(key interface{}) *gvar.Var {
|
||||
// The key-value pair expires after <duration>.
|
||||
//
|
||||
// It does not expire if <duration> == 0.
|
||||
func GetOrSet(key interface{}, value interface{}, duration time.Duration) interface{} {
|
||||
func GetOrSet(key interface{}, value interface{}, duration time.Duration) (interface{}, error) {
|
||||
return defaultCache.GetOrSet(key, value, duration)
|
||||
}
|
||||
|
||||
// GetOrSetFunc returns the value of <key>, or sets <key> with result of function <f>
|
||||
// and returns its result if <key> does not exist in the cache. The key-value pair expires
|
||||
// after <duration>. It does not expire if <duration> == 0.
|
||||
func GetOrSetFunc(key interface{}, f func() interface{}, duration time.Duration) interface{} {
|
||||
func GetOrSetFunc(key interface{}, f func() (interface{}, error), duration time.Duration) (interface{}, error) {
|
||||
return defaultCache.GetOrSetFunc(key, f, duration)
|
||||
}
|
||||
|
||||
@ -72,18 +67,18 @@ func GetOrSetFunc(key interface{}, f func() interface{}, duration time.Duration)
|
||||
// after <duration>. It does not expire if <duration> == 0.
|
||||
//
|
||||
// Note that the function <f> is executed within writing mutex lock.
|
||||
func GetOrSetFuncLock(key interface{}, f func() interface{}, duration time.Duration) interface{} {
|
||||
func GetOrSetFuncLock(key interface{}, f func() (interface{}, error), duration time.Duration) (interface{}, error) {
|
||||
return defaultCache.GetOrSetFuncLock(key, f, duration)
|
||||
}
|
||||
|
||||
// Contains returns true if <key> exists in the cache, or else returns false.
|
||||
func Contains(key interface{}) bool {
|
||||
func Contains(key interface{}) (bool, error) {
|
||||
return defaultCache.Contains(key)
|
||||
}
|
||||
|
||||
// Remove deletes the one or more keys from cache, and returns its value.
|
||||
// If multiple keys are given, it returns the value of the deleted last item.
|
||||
func Remove(keys ...interface{}) (value interface{}) {
|
||||
func Remove(keys ...interface{}) (value interface{}, err error) {
|
||||
return defaultCache.Remove(keys...)
|
||||
}
|
||||
|
||||
@ -94,38 +89,44 @@ func Removes(keys []interface{}) {
|
||||
}
|
||||
|
||||
// Data returns a copy of all key-value pairs in the cache as map type.
|
||||
func Data() map[interface{}]interface{} {
|
||||
func Data() (map[interface{}]interface{}, error) {
|
||||
return defaultCache.Data()
|
||||
}
|
||||
|
||||
// Keys returns all keys in the cache as slice.
|
||||
func Keys() []interface{} {
|
||||
func Keys() ([]interface{}, error) {
|
||||
return defaultCache.Keys()
|
||||
}
|
||||
|
||||
// KeyStrings returns all keys in the cache as string slice.
|
||||
func KeyStrings() []string {
|
||||
func KeyStrings() ([]string, error) {
|
||||
return defaultCache.KeyStrings()
|
||||
}
|
||||
|
||||
// Values returns all values in the cache as slice.
|
||||
func Values() []interface{} {
|
||||
func Values() ([]interface{}, error) {
|
||||
return defaultCache.Values()
|
||||
}
|
||||
|
||||
// Size returns the size of the cache.
|
||||
func Size() int {
|
||||
func Size() (int, error) {
|
||||
return defaultCache.Size()
|
||||
}
|
||||
|
||||
// GetExpire retrieves and returns the expiration of <key>.
|
||||
// It returns -1 if the <key> does not exist in the cache.
|
||||
func GetExpire(key interface{}) time.Duration {
|
||||
func GetExpire(key interface{}) (time.Duration, error) {
|
||||
return defaultCache.GetExpire(key)
|
||||
}
|
||||
|
||||
// Update updates the value of <key> without changing its expiration and returns the old value.
|
||||
// The returned <exist> value is false if the <key> does not exist in the cache.
|
||||
func Update(key interface{}, value interface{}) (oldValue interface{}, exist bool, err error) {
|
||||
return defaultCache.Update(key, value)
|
||||
}
|
||||
|
||||
// UpdateExpire updates the expiration of <key> and returns the old expiration duration value.
|
||||
// It returns -1 if the <key> does not exist in the cache.
|
||||
func UpdateExpire(key interface{}, duration time.Duration) (oldDuration time.Duration) {
|
||||
func UpdateExpire(key interface{}, duration time.Duration) (oldDuration time.Duration, err error) {
|
||||
return defaultCache.UpdateExpire(key, duration)
|
||||
}
|
||||
|
||||
117
os/gcache/gcache_adapter.go
Normal file
117
os/gcache/gcache_adapter.go
Normal file
@ -0,0 +1,117 @@
|
||||
// Copyright 2020 gf Author(https://github.com/gogf/gf). All Rights Reserved.
|
||||
//
|
||||
// This Source Code Form is subject to the terms of the MIT License.
|
||||
// If a copy of the MIT was not distributed with this file,
|
||||
// You can obtain one at https://github.com/gogf/gf.
|
||||
|
||||
package gcache
|
||||
|
||||
import (
|
||||
"time"
|
||||
)
|
||||
|
||||
// Adapter is the adapter for cache features implements.
|
||||
type Adapter interface {
|
||||
// Set sets cache with <key>-<value> pair, which is expired after <duration>.
|
||||
//
|
||||
// It does not expire if <duration> == 0.
|
||||
// It deletes the <key> if <duration> < 0.
|
||||
Set(key interface{}, value interface{}, duration time.Duration) error
|
||||
|
||||
// Sets batch sets cache with key-value pairs by <data>, which is expired after <duration>.
|
||||
//
|
||||
// It does not expire if <duration> == 0.
|
||||
// It deletes the keys of <data> if <duration> < 0 or given <value> is nil.
|
||||
Sets(data map[interface{}]interface{}, duration time.Duration) error
|
||||
|
||||
// SetIfNotExist sets cache with <key>-<value> pair which is expired after <duration>
|
||||
// if <key> does not exist in the cache. It returns true the <key> dose not exist in the
|
||||
// cache and it sets <value> successfully to the cache, or else it returns false.
|
||||
//
|
||||
// The parameter <value> can be type of <func() interface{}>, but it dose nothing if its
|
||||
// result is nil.
|
||||
//
|
||||
// It does not expire if <duration> == 0.
|
||||
// It deletes the <key> if <duration> < 0 or given <value> is nil.
|
||||
SetIfNotExist(key interface{}, value interface{}, duration time.Duration) (bool, error)
|
||||
|
||||
// Get retrieves and returns the associated value of given <key>.
|
||||
// It returns nil if it does not exist or its value is nil.
|
||||
Get(key interface{}) (interface{}, error)
|
||||
|
||||
// GetOrSet retrieves and returns the value of <key>, or sets <key>-<value> pair and
|
||||
// returns <value> if <key> does not exist in the cache. The key-value pair expires
|
||||
// after <duration>.
|
||||
//
|
||||
// It does not expire if <duration> == 0.
|
||||
// It deletes the <key> if <duration> < 0 or given <value> is nil, but it does nothing
|
||||
// if <value> is a function and the function result is nil.
|
||||
GetOrSet(key interface{}, value interface{}, duration time.Duration) (interface{}, error)
|
||||
|
||||
// GetOrSetFunc retrieves and returns the value of <key>, or sets <key> with result of
|
||||
// function <f> and returns its result if <key> does not exist in the cache. The key-value
|
||||
// pair expires after <duration>.
|
||||
//
|
||||
// It does not expire if <duration> == 0.
|
||||
// It deletes the <key> if <duration> < 0 or given <value> is nil, but it does nothing
|
||||
// if <value> is a function and the function result is nil.
|
||||
GetOrSetFunc(key interface{}, f func() (interface{}, error), duration time.Duration) (interface{}, error)
|
||||
|
||||
// GetOrSetFuncLock retrieves and returns the value of <key>, or sets <key> with result of
|
||||
// function <f> and returns its result if <key> does not exist in the cache. The key-value
|
||||
// pair expires after <duration>.
|
||||
//
|
||||
// It does not expire if <duration> == 0.
|
||||
// It does nothing if function <f> returns nil.
|
||||
//
|
||||
// Note that the function <f> should be executed within writing mutex lock for concurrent
|
||||
// safety purpose.
|
||||
GetOrSetFuncLock(key interface{}, f func() (interface{}, error), duration time.Duration) (interface{}, error)
|
||||
|
||||
// Contains returns true if <key> exists in the cache, or else returns false.
|
||||
Contains(key interface{}) (bool, error)
|
||||
|
||||
// GetExpire retrieves and returns the expiration of <key> in the cache.
|
||||
//
|
||||
// It returns 0 if the <key> does not expire.
|
||||
// It returns -1 if the <key> does not exist in the cache.
|
||||
GetExpire(key interface{}) (time.Duration, error)
|
||||
|
||||
// Remove deletes one or more keys from cache, and returns its value.
|
||||
// If multiple keys are given, it returns the value of the last deleted item.
|
||||
Remove(keys ...interface{}) (value interface{}, err error)
|
||||
|
||||
// Update updates the value of <key> without changing its expiration and returns the old value.
|
||||
// The returned value <exist> is false if the <key> does not exist in the cache.
|
||||
//
|
||||
// It deletes the <key> if given <value> is nil.
|
||||
// It does nothing if <key> does not exist in the cache.
|
||||
Update(key interface{}, value interface{}) (oldValue interface{}, exist bool, err error)
|
||||
|
||||
// UpdateExpire updates the expiration of <key> and returns the old expiration duration value.
|
||||
//
|
||||
// It returns -1 and does nothing if the <key> does not exist in the cache.
|
||||
// It deletes the <key> if <duration> < 0.
|
||||
UpdateExpire(key interface{}, duration time.Duration) (oldDuration time.Duration, err error)
|
||||
|
||||
// Size returns the number of items in the cache.
|
||||
Size() (size int, err error)
|
||||
|
||||
// Data returns a copy of all key-value pairs in the cache as map type.
|
||||
// Note that this function may leads lots of memory usage, you can implement this function
|
||||
// if necessary.
|
||||
Data() (map[interface{}]interface{}, error)
|
||||
|
||||
// Keys returns all keys in the cache as slice.
|
||||
Keys() ([]interface{}, error)
|
||||
|
||||
// Values returns all values in the cache as slice.
|
||||
Values() ([]interface{}, error)
|
||||
|
||||
// Clear clears all data of the cache.
|
||||
// Note that this function is sensitive and should be carefully used.
|
||||
Clear() error
|
||||
|
||||
// Close closes the cache if necessary.
|
||||
Close() error
|
||||
}
|
||||
@ -7,7 +7,6 @@
|
||||
package gcache
|
||||
|
||||
import (
|
||||
"github.com/gogf/gf/container/gvar"
|
||||
"math"
|
||||
"sync"
|
||||
"time"
|
||||
@ -17,11 +16,10 @@ import (
|
||||
"github.com/gogf/gf/container/gtype"
|
||||
"github.com/gogf/gf/os/gtime"
|
||||
"github.com/gogf/gf/os/gtimer"
|
||||
"github.com/gogf/gf/util/gconv"
|
||||
)
|
||||
|
||||
// Internal cache object.
|
||||
type memCache struct {
|
||||
type adapterMemory struct {
|
||||
// dataMu ensures the concurrent safety of underlying data map.
|
||||
dataMu sync.RWMutex
|
||||
|
||||
@ -38,7 +36,7 @@ type memCache struct {
|
||||
cap int
|
||||
|
||||
// data is the underlying cache data which is stored in a hash table.
|
||||
data map[interface{}]memCacheItem
|
||||
data map[interface{}]adapterMemoryItem
|
||||
|
||||
// expireTimes is the expiring key to its timestamp mapping,
|
||||
// which is used for quick indexing and deleting.
|
||||
@ -49,7 +47,7 @@ type memCache struct {
|
||||
expireSets map[int64]*gset.Set
|
||||
|
||||
// lru is the LRU manager, which is enabled when attribute cap > 0.
|
||||
lru *memCacheLru
|
||||
lru *adapterMemoryLru
|
||||
|
||||
// lruGetList is the LRU history according with Get function.
|
||||
lruGetList *glist.List
|
||||
@ -62,13 +60,13 @@ type memCache struct {
|
||||
}
|
||||
|
||||
// Internal cache item.
|
||||
type memCacheItem struct {
|
||||
type adapterMemoryItem struct {
|
||||
v interface{} // Value.
|
||||
e int64 // Expire timestamp in milliseconds.
|
||||
}
|
||||
|
||||
// Internal event item.
|
||||
type memCacheEvent struct {
|
||||
type adapterMemoryEvent struct {
|
||||
k interface{} // Key.
|
||||
e int64 // Expire time in milliseconds.
|
||||
}
|
||||
@ -79,11 +77,11 @@ const (
|
||||
gDEFAULT_MAX_EXPIRE = 9223372036854
|
||||
)
|
||||
|
||||
// newMemCache creates and returns a new memory cache object.
|
||||
func newMemCache(lruCap ...int) *memCache {
|
||||
c := &memCache{
|
||||
// newAdapterMemory creates and returns a new memory cache object.
|
||||
func newAdapterMemory(lruCap ...int) *adapterMemory {
|
||||
c := &adapterMemory{
|
||||
lruGetList: glist.New(true),
|
||||
data: make(map[interface{}]memCacheItem),
|
||||
data: make(map[interface{}]adapterMemoryItem),
|
||||
expireTimes: make(map[interface{}]int64),
|
||||
expireSets: make(map[int64]*gset.Set),
|
||||
eventList: glist.New(true),
|
||||
@ -97,96 +95,330 @@ func newMemCache(lruCap ...int) *memCache {
|
||||
}
|
||||
|
||||
// Set sets cache with <key>-<value> pair, which is expired after <duration>.
|
||||
//
|
||||
// It does not expire if <duration> == 0.
|
||||
func (c *memCache) Set(key interface{}, value interface{}, duration time.Duration) {
|
||||
// It deletes the <key> if <duration> < 0.
|
||||
func (c *adapterMemory) Set(key interface{}, value interface{}, duration time.Duration) error {
|
||||
expireTime := c.getInternalExpire(duration)
|
||||
c.dataMu.Lock()
|
||||
c.data[key] = memCacheItem{
|
||||
c.data[key] = adapterMemoryItem{
|
||||
v: value,
|
||||
e: expireTime,
|
||||
}
|
||||
c.dataMu.Unlock()
|
||||
c.eventList.PushBack(&memCacheEvent{
|
||||
c.eventList.PushBack(&adapterMemoryEvent{
|
||||
k: key,
|
||||
e: expireTime,
|
||||
})
|
||||
return nil
|
||||
}
|
||||
|
||||
// Update updates the value of <key> without changing its expiration and returns the old value.
|
||||
// The returned <exist> value is false if the <key> does not exist in the cache.
|
||||
func (c *memCache) Update(key interface{}, value interface{}) (oldValue interface{}, exist bool) {
|
||||
// The returned value <exist> is false if the <key> does not exist in the cache.
|
||||
//
|
||||
// It deletes the <key> if given <value> is nil.
|
||||
// It does nothing if <key> does not exist in the cache.
|
||||
func (c *adapterMemory) Update(key interface{}, value interface{}) (oldValue interface{}, exist bool, err error) {
|
||||
c.dataMu.Lock()
|
||||
defer c.dataMu.Unlock()
|
||||
if item, ok := c.data[key]; ok {
|
||||
c.data[key] = memCacheItem{
|
||||
c.data[key] = adapterMemoryItem{
|
||||
v: value,
|
||||
e: item.e,
|
||||
}
|
||||
return item.v, true
|
||||
return item.v, true, nil
|
||||
}
|
||||
return nil, false
|
||||
return nil, false, nil
|
||||
}
|
||||
|
||||
// UpdateExpire updates the expiration of <key> and returns the old expiration duration value.
|
||||
// It returns -1 if the <key> does not exist in the cache.
|
||||
func (c *memCache) UpdateExpire(key interface{}, duration time.Duration) (oldDuration time.Duration) {
|
||||
//
|
||||
// It returns -1 and does nothing if the <key> does not exist in the cache.
|
||||
// It deletes the <key> if <duration> < 0.
|
||||
func (c *adapterMemory) UpdateExpire(key interface{}, duration time.Duration) (oldDuration time.Duration, err error) {
|
||||
newExpireTime := c.getInternalExpire(duration)
|
||||
c.dataMu.Lock()
|
||||
defer c.dataMu.Unlock()
|
||||
if item, ok := c.data[key]; ok {
|
||||
c.data[key] = memCacheItem{
|
||||
c.data[key] = adapterMemoryItem{
|
||||
v: item.v,
|
||||
e: newExpireTime,
|
||||
}
|
||||
c.eventList.PushBack(&memCacheEvent{
|
||||
c.eventList.PushBack(&adapterMemoryEvent{
|
||||
k: key,
|
||||
e: newExpireTime,
|
||||
})
|
||||
return time.Duration(item.e-gtime.TimestampMilli()) * time.Millisecond
|
||||
return time.Duration(item.e-gtime.TimestampMilli()) * time.Millisecond, nil
|
||||
}
|
||||
return -1
|
||||
return -1, nil
|
||||
}
|
||||
|
||||
// GetExpire retrieves and returns the expiration of <key>.
|
||||
// GetExpire retrieves and returns the expiration of <key> in the cache.
|
||||
//
|
||||
// It returns 0 if the <key> does not expire.
|
||||
// It returns -1 if the <key> does not exist in the cache.
|
||||
func (c *memCache) GetExpire(key interface{}) time.Duration {
|
||||
func (c *adapterMemory) GetExpire(key interface{}) (time.Duration, error) {
|
||||
c.dataMu.RLock()
|
||||
defer c.dataMu.RUnlock()
|
||||
if item, ok := c.data[key]; ok {
|
||||
return time.Duration(item.e-gtime.TimestampMilli()) * time.Millisecond
|
||||
return time.Duration(item.e-gtime.TimestampMilli()) * time.Millisecond, nil
|
||||
}
|
||||
return -1
|
||||
return -1, nil
|
||||
}
|
||||
|
||||
// SetIfNotExist sets cache with <key>-<value> pair which is expired after <duration>
|
||||
// if <key> does not exist in the cache. It returns true the <key> dose not exist in the
|
||||
// cache and it sets <value> successfully to the cache, or else it returns false.
|
||||
// The parameter <value> can be type of <func() interface{}>, but it dose nothing if its
|
||||
// result is nil.
|
||||
//
|
||||
// It does not expire if <duration> == 0.
|
||||
// It deletes the <key> if <duration> < 0 or given <value> is nil.
|
||||
func (c *adapterMemory) SetIfNotExist(key interface{}, value interface{}, duration time.Duration) (bool, error) {
|
||||
isContained, err := c.Contains(key)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
if !isContained {
|
||||
_, err := c.doSetWithLockCheck(key, value, duration)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
return true, nil
|
||||
}
|
||||
return false, nil
|
||||
}
|
||||
|
||||
// Sets batch sets cache with key-value pairs by <data>, which is expired after <duration>.
|
||||
//
|
||||
// It does not expire if <duration> == 0.
|
||||
// It deletes the keys of <data> if <duration> < 0 or given <value> is nil.
|
||||
func (c *adapterMemory) Sets(data map[interface{}]interface{}, duration time.Duration) error {
|
||||
expireTime := c.getInternalExpire(duration)
|
||||
for k, v := range data {
|
||||
c.dataMu.Lock()
|
||||
c.data[k] = adapterMemoryItem{
|
||||
v: v,
|
||||
e: expireTime,
|
||||
}
|
||||
c.dataMu.Unlock()
|
||||
c.eventList.PushBack(&adapterMemoryEvent{
|
||||
k: k,
|
||||
e: expireTime,
|
||||
})
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Get retrieves and returns the associated value of given <key>.
|
||||
// It returns nil if it does not exist or its value is nil.
|
||||
func (c *adapterMemory) Get(key interface{}) (interface{}, error) {
|
||||
c.dataMu.RLock()
|
||||
item, ok := c.data[key]
|
||||
c.dataMu.RUnlock()
|
||||
if ok && !item.IsExpired() {
|
||||
// Adding to LRU history if LRU feature is enabled.
|
||||
if c.cap > 0 {
|
||||
c.lruGetList.PushBack(key)
|
||||
}
|
||||
return item.v, nil
|
||||
}
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
// GetOrSet retrieves and returns the value of <key>, or sets <key>-<value> pair and
|
||||
// returns <value> if <key> does not exist in the cache. The key-value pair expires
|
||||
// after <duration>.
|
||||
//
|
||||
// It does not expire if <duration> == 0.
|
||||
// It deletes the <key> if <duration> < 0 or given <value> is nil, but it does nothing
|
||||
// if <value> is a function and the function result is nil.
|
||||
func (c *adapterMemory) GetOrSet(key interface{}, value interface{}, duration time.Duration) (interface{}, error) {
|
||||
v, err := c.Get(key)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if v == nil {
|
||||
return c.doSetWithLockCheck(key, value, duration)
|
||||
} else {
|
||||
return v, nil
|
||||
}
|
||||
}
|
||||
|
||||
// GetOrSetFunc retrieves and returns the value of <key>, or sets <key> with result of
|
||||
// function <f> and returns its result if <key> does not exist in the cache. The key-value
|
||||
// pair expires after <duration>.
|
||||
//
|
||||
// It does not expire if <duration> == 0.
|
||||
// It deletes the <key> if <duration> < 0 or given <value> is nil, but it does nothing
|
||||
// if <value> is a function and the function result is nil.
|
||||
func (c *adapterMemory) GetOrSetFunc(key interface{}, f func() (interface{}, error), duration time.Duration) (interface{}, error) {
|
||||
v, err := c.Get(key)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if v == nil {
|
||||
value, err := f()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if value == nil {
|
||||
return nil, nil
|
||||
}
|
||||
return c.doSetWithLockCheck(key, value, duration)
|
||||
} else {
|
||||
return v, nil
|
||||
}
|
||||
}
|
||||
|
||||
// GetOrSetFuncLock retrieves and returns the value of <key>, or sets <key> with result of
|
||||
// function <f> and returns its result if <key> does not exist in the cache. The key-value
|
||||
// pair expires after <duration>.
|
||||
//
|
||||
// It does not expire if <duration> == 0.
|
||||
// It does nothing if function <f> returns nil.
|
||||
//
|
||||
// Note that the function <f> should be executed within writing mutex lock for concurrent
|
||||
// safety purpose.
|
||||
func (c *adapterMemory) GetOrSetFuncLock(key interface{}, f func() (interface{}, error), duration time.Duration) (interface{}, error) {
|
||||
v, err := c.Get(key)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if v == nil {
|
||||
return c.doSetWithLockCheck(key, f, duration)
|
||||
} else {
|
||||
return v, nil
|
||||
}
|
||||
}
|
||||
|
||||
// Contains returns true if <key> exists in the cache, or else returns false.
|
||||
func (c *adapterMemory) Contains(key interface{}) (bool, error) {
|
||||
v, err := c.Get(key)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
return v != nil, nil
|
||||
}
|
||||
|
||||
// Remove deletes the one or more keys from cache, and returns its value.
|
||||
// If multiple keys are given, it returns the value of the deleted last item.
|
||||
func (c *adapterMemory) Remove(keys ...interface{}) (value interface{}, err error) {
|
||||
c.dataMu.Lock()
|
||||
defer c.dataMu.Unlock()
|
||||
for _, key := range keys {
|
||||
item, ok := c.data[key]
|
||||
if ok {
|
||||
value = item.v
|
||||
delete(c.data, key)
|
||||
c.eventList.PushBack(&adapterMemoryEvent{
|
||||
k: key,
|
||||
e: gtime.TimestampMilli() - 1000,
|
||||
})
|
||||
}
|
||||
}
|
||||
return value, nil
|
||||
}
|
||||
|
||||
// Data returns a copy of all key-value pairs in the cache as map type.
|
||||
func (c *adapterMemory) Data() (map[interface{}]interface{}, error) {
|
||||
m := make(map[interface{}]interface{})
|
||||
c.dataMu.RLock()
|
||||
for k, v := range c.data {
|
||||
if !v.IsExpired() {
|
||||
m[k] = v.v
|
||||
}
|
||||
}
|
||||
c.dataMu.RUnlock()
|
||||
return m, nil
|
||||
}
|
||||
|
||||
// Keys returns all keys in the cache as slice.
|
||||
func (c *adapterMemory) Keys() ([]interface{}, error) {
|
||||
keys := make([]interface{}, 0)
|
||||
c.dataMu.RLock()
|
||||
for k, v := range c.data {
|
||||
if !v.IsExpired() {
|
||||
keys = append(keys, k)
|
||||
}
|
||||
}
|
||||
c.dataMu.RUnlock()
|
||||
return keys, nil
|
||||
}
|
||||
|
||||
// Values returns all values in the cache as slice.
|
||||
func (c *adapterMemory) Values() ([]interface{}, error) {
|
||||
values := make([]interface{}, 0)
|
||||
c.dataMu.RLock()
|
||||
for _, v := range c.data {
|
||||
if !v.IsExpired() {
|
||||
values = append(values, v.v)
|
||||
}
|
||||
}
|
||||
c.dataMu.RUnlock()
|
||||
return values, nil
|
||||
}
|
||||
|
||||
// Size returns the size of the cache.
|
||||
func (c *adapterMemory) Size() (size int, err error) {
|
||||
c.dataMu.RLock()
|
||||
size = len(c.data)
|
||||
c.dataMu.RUnlock()
|
||||
return size, nil
|
||||
}
|
||||
|
||||
// Clear clears all data of the cache.
|
||||
// Note that this function is sensitive and should be carefully used.
|
||||
func (c *adapterMemory) Clear() error {
|
||||
c.dataMu.Lock()
|
||||
defer c.dataMu.Unlock()
|
||||
c.data = make(map[interface{}]adapterMemoryItem)
|
||||
return nil
|
||||
}
|
||||
|
||||
// Close closes the cache.
|
||||
func (c *adapterMemory) Close() error {
|
||||
if c.cap > 0 {
|
||||
c.lru.Close()
|
||||
}
|
||||
c.closed.Set(true)
|
||||
return nil
|
||||
}
|
||||
|
||||
// doSetWithLockCheck sets cache with <key>-<value> pair if <key> does not exist in the
|
||||
// cache, which is expired after <duration>.
|
||||
//
|
||||
// It does not expire if <duration> == 0.
|
||||
// The parameter <value> can be type of <func() interface{}>, but it dose nothing if its
|
||||
// result is nil.
|
||||
// The parameter <value> can be type of <func() interface{}>, but it dose nothing if the
|
||||
// function result is nil.
|
||||
//
|
||||
// It doubly checks the <key> whether exists in the cache using mutex writing lock
|
||||
// before setting it to the cache.
|
||||
func (c *memCache) doSetWithLockCheck(key interface{}, value interface{}, duration time.Duration) interface{} {
|
||||
func (c *adapterMemory) doSetWithLockCheck(key interface{}, value interface{}, duration time.Duration) (interface{}, error) {
|
||||
expireTimestamp := c.getInternalExpire(duration)
|
||||
c.dataMu.Lock()
|
||||
defer c.dataMu.Unlock()
|
||||
if v, ok := c.data[key]; ok && !v.IsExpired() {
|
||||
return v.v
|
||||
return v.v, nil
|
||||
}
|
||||
if f, ok := value.(func() interface{}); ok {
|
||||
value = f()
|
||||
if value == nil {
|
||||
return nil
|
||||
if f, ok := value.(func() (interface{}, error)); ok {
|
||||
v, err := f()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if v == nil {
|
||||
return nil, nil
|
||||
} else {
|
||||
value = v
|
||||
}
|
||||
}
|
||||
c.data[key] = memCacheItem{v: value, e: expireTimestamp}
|
||||
c.eventList.PushBack(&memCacheEvent{k: key, e: expireTimestamp})
|
||||
return value
|
||||
c.data[key] = adapterMemoryItem{v: value, e: expireTimestamp}
|
||||
c.eventList.PushBack(&adapterMemoryEvent{k: key, e: expireTimestamp})
|
||||
return value, nil
|
||||
}
|
||||
|
||||
// getInternalExpire converts and returns the expire time with given expired duration in milliseconds.
|
||||
func (c *memCache) getInternalExpire(duration time.Duration) int64 {
|
||||
func (c *adapterMemory) getInternalExpire(duration time.Duration) int64 {
|
||||
if duration == 0 {
|
||||
return gDEFAULT_MAX_EXPIRE
|
||||
} else {
|
||||
@ -195,12 +427,12 @@ func (c *memCache) getInternalExpire(duration time.Duration) int64 {
|
||||
}
|
||||
|
||||
// makeExpireKey groups the <expire> in milliseconds to its according seconds.
|
||||
func (c *memCache) makeExpireKey(expire int64) int64 {
|
||||
func (c *adapterMemory) makeExpireKey(expire int64) int64 {
|
||||
return int64(math.Ceil(float64(expire/1000)+1) * 1000)
|
||||
}
|
||||
|
||||
// getExpireSet returns the expire set for given <expire> in seconds.
|
||||
func (c *memCache) getExpireSet(expire int64) (expireSet *gset.Set) {
|
||||
func (c *adapterMemory) getExpireSet(expire int64) (expireSet *gset.Set) {
|
||||
c.expireSetMu.RLock()
|
||||
expireSet, _ = c.expireSets[expire]
|
||||
c.expireSetMu.RUnlock()
|
||||
@ -209,7 +441,7 @@ func (c *memCache) getExpireSet(expire int64) (expireSet *gset.Set) {
|
||||
|
||||
// getOrNewExpireSet returns the expire set for given <expire> in seconds.
|
||||
// It creates and returns a new set for <expire> if it does not exist.
|
||||
func (c *memCache) getOrNewExpireSet(expire int64) (expireSet *gset.Set) {
|
||||
func (c *adapterMemory) getOrNewExpireSet(expire int64) (expireSet *gset.Set) {
|
||||
if expireSet = c.getExpireSet(expire); expireSet == nil {
|
||||
expireSet = gset.New(true)
|
||||
c.expireSetMu.Lock()
|
||||
@ -223,202 +455,17 @@ func (c *memCache) getOrNewExpireSet(expire int64) (expireSet *gset.Set) {
|
||||
return
|
||||
}
|
||||
|
||||
// SetIfNotExist sets cache with <key>-<value> pair if <key> does not exist in the cache,
|
||||
// which is expired after <duration>. It does not expire if <duration> == 0.
|
||||
func (c *memCache) SetIfNotExist(key interface{}, value interface{}, duration time.Duration) bool {
|
||||
if !c.Contains(key) {
|
||||
c.doSetWithLockCheck(key, value, duration)
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// Sets batch sets cache with key-value pairs by <data>, which is expired after <duration>.
|
||||
//
|
||||
// It does not expire if <duration> == 0.
|
||||
func (c *memCache) Sets(data map[interface{}]interface{}, duration time.Duration) {
|
||||
expireTime := c.getInternalExpire(duration)
|
||||
for k, v := range data {
|
||||
c.dataMu.Lock()
|
||||
c.data[k] = memCacheItem{
|
||||
v: v,
|
||||
e: expireTime,
|
||||
}
|
||||
c.dataMu.Unlock()
|
||||
c.eventList.PushBack(&memCacheEvent{
|
||||
k: k,
|
||||
e: expireTime,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// Get returns the value of <key>.
|
||||
// It returns nil if it does not exist or its value is nil.
|
||||
func (c *memCache) Get(key interface{}) interface{} {
|
||||
c.dataMu.RLock()
|
||||
item, ok := c.data[key]
|
||||
c.dataMu.RUnlock()
|
||||
if ok && !item.IsExpired() {
|
||||
// Adding to LRU history if LRU feature is enabled.
|
||||
if c.cap > 0 {
|
||||
c.lruGetList.PushBack(key)
|
||||
}
|
||||
return item.v
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetVar retrieves and returns the value of <key> as gvar.Var.
|
||||
func (c *memCache) GetVar(key interface{}) *gvar.Var {
|
||||
return gvar.New(c.Get(key))
|
||||
}
|
||||
|
||||
// GetOrSet returns the value of <key>, or sets <key>-<value> pair and returns <value> if <key>
|
||||
// does not exist in the cache. The key-value pair expires after <duration>. It does not expire
|
||||
// if <duration> == 0.
|
||||
func (c *memCache) GetOrSet(key interface{}, value interface{}, duration time.Duration) interface{} {
|
||||
if v := c.Get(key); v == nil {
|
||||
return c.doSetWithLockCheck(key, value, duration)
|
||||
} else {
|
||||
return v
|
||||
}
|
||||
}
|
||||
|
||||
// GetOrSetFunc returns the value of <key>, or sets <key> with result of function <f>
|
||||
// and returns its result if <key> does not exist in the cache. The key-value pair expires
|
||||
// after <duration>.
|
||||
//
|
||||
// It does not expire if <duration> == 0.
|
||||
// It does nothing if function <f> returns nil.
|
||||
func (c *memCache) GetOrSetFunc(key interface{}, f func() interface{}, duration time.Duration) interface{} {
|
||||
if v := c.Get(key); v == nil {
|
||||
value := f()
|
||||
if value == nil {
|
||||
return nil
|
||||
}
|
||||
return c.doSetWithLockCheck(key, value, duration)
|
||||
} else {
|
||||
return v
|
||||
}
|
||||
}
|
||||
|
||||
// GetOrSetFuncLock returns the value of <key>, or sets <key> with result of function <f>
|
||||
// and returns its result if <key> does not exist in the cache. The key-value pair expires
|
||||
// after <duration>.
|
||||
//
|
||||
// It does not expire if <duration> == 0.
|
||||
// It does nothing if function <f> returns nil.
|
||||
//
|
||||
// Note that the function <f> is executed within writing mutex lock.
|
||||
func (c *memCache) GetOrSetFuncLock(key interface{}, f func() interface{}, duration time.Duration) interface{} {
|
||||
if v := c.Get(key); v == nil {
|
||||
return c.doSetWithLockCheck(key, f, duration)
|
||||
} else {
|
||||
return v
|
||||
}
|
||||
}
|
||||
|
||||
// Contains returns true if <key> exists in the cache, or else returns false.
|
||||
func (c *memCache) Contains(key interface{}) bool {
|
||||
return c.Get(key) != nil
|
||||
}
|
||||
|
||||
// Remove deletes the one or more keys from cache, and returns its value.
|
||||
// If multiple keys are given, it returns the value of the deleted last item.
|
||||
func (c *memCache) Remove(keys ...interface{}) (value interface{}) {
|
||||
c.dataMu.Lock()
|
||||
defer c.dataMu.Unlock()
|
||||
for _, key := range keys {
|
||||
item, ok := c.data[key]
|
||||
if ok {
|
||||
value = item.v
|
||||
delete(c.data, key)
|
||||
c.eventList.PushBack(&memCacheEvent{
|
||||
k: key,
|
||||
e: gtime.TimestampMilli() - 1000,
|
||||
})
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// Removes deletes <keys> in the cache.
|
||||
// Deprecated, use Remove instead.
|
||||
func (c *memCache) Removes(keys []interface{}) {
|
||||
c.Remove(keys...)
|
||||
}
|
||||
|
||||
// Data returns a copy of all key-value pairs in the cache as map type.
|
||||
func (c *memCache) Data() map[interface{}]interface{} {
|
||||
m := make(map[interface{}]interface{})
|
||||
c.dataMu.RLock()
|
||||
for k, v := range c.data {
|
||||
if !v.IsExpired() {
|
||||
m[k] = v.v
|
||||
}
|
||||
}
|
||||
c.dataMu.RUnlock()
|
||||
return m
|
||||
}
|
||||
|
||||
// Keys returns all keys in the cache as slice.
|
||||
func (c *memCache) Keys() []interface{} {
|
||||
keys := make([]interface{}, 0)
|
||||
c.dataMu.RLock()
|
||||
for k, v := range c.data {
|
||||
if !v.IsExpired() {
|
||||
keys = append(keys, k)
|
||||
}
|
||||
}
|
||||
c.dataMu.RUnlock()
|
||||
return keys
|
||||
}
|
||||
|
||||
// KeyStrings returns all keys in the cache as string slice.
|
||||
func (c *memCache) KeyStrings() []string {
|
||||
return gconv.Strings(c.Keys())
|
||||
}
|
||||
|
||||
// Values returns all values in the cache as slice.
|
||||
func (c *memCache) Values() []interface{} {
|
||||
values := make([]interface{}, 0)
|
||||
c.dataMu.RLock()
|
||||
for _, v := range c.data {
|
||||
if !v.IsExpired() {
|
||||
values = append(values, v.v)
|
||||
}
|
||||
}
|
||||
c.dataMu.RUnlock()
|
||||
return values
|
||||
}
|
||||
|
||||
// Size returns the size of the cache.
|
||||
func (c *memCache) Size() (size int) {
|
||||
c.dataMu.RLock()
|
||||
size = len(c.data)
|
||||
c.dataMu.RUnlock()
|
||||
return
|
||||
}
|
||||
|
||||
// Close closes the cache.
|
||||
func (c *memCache) Close() {
|
||||
if c.cap > 0 {
|
||||
c.lru.Close()
|
||||
}
|
||||
c.closed.Set(true)
|
||||
}
|
||||
|
||||
// syncEventAndClearExpired does the asynchronous task loop:
|
||||
// 1. Asynchronously process the data in the event list,
|
||||
// and synchronize the results to the <expireTimes> and <expireSets> properties.
|
||||
// 2. Clean up the expired key-value pair data.
|
||||
func (c *memCache) syncEventAndClearExpired() {
|
||||
func (c *adapterMemory) syncEventAndClearExpired() {
|
||||
if c.closed.Val() {
|
||||
gtimer.Exit()
|
||||
return
|
||||
}
|
||||
var (
|
||||
event *memCacheEvent
|
||||
event *adapterMemoryEvent
|
||||
oldExpireTime int64
|
||||
newExpireTime int64
|
||||
)
|
||||
@ -430,7 +477,7 @@ func (c *memCache) syncEventAndClearExpired() {
|
||||
if v == nil {
|
||||
break
|
||||
}
|
||||
event = v.(*memCacheEvent)
|
||||
event = v.(*adapterMemoryEvent)
|
||||
// Fetching the old expire set.
|
||||
c.expireTimeMu.RLock()
|
||||
oldExpireTime = c.expireTimes[event.k]
|
||||
@ -487,7 +534,7 @@ func (c *memCache) syncEventAndClearExpired() {
|
||||
|
||||
// clearByKey deletes the key-value pair with given <key>.
|
||||
// The parameter <force> specifies whether doing this deleting forcibly.
|
||||
func (c *memCache) clearByKey(key interface{}, force ...bool) {
|
||||
func (c *adapterMemory) clearByKey(key interface{}, force ...bool) {
|
||||
c.dataMu.Lock()
|
||||
// Doubly check before really deleting it from cache.
|
||||
if item, ok := c.data[key]; (ok && item.IsExpired()) || (len(force) > 0 && force[0]) {
|
||||
@ -11,7 +11,7 @@ import (
|
||||
)
|
||||
|
||||
// IsExpired checks whether <item> is expired.
|
||||
func (item *memCacheItem) IsExpired() bool {
|
||||
func (item *adapterMemoryItem) IsExpired() bool {
|
||||
// Note that it should use greater than or equal judgement here
|
||||
// imagining that the cache time is only 1 millisecond.
|
||||
if item.e >= gtime.TimestampMilli() {
|
||||
@ -17,17 +17,17 @@ import (
|
||||
|
||||
// LRU cache object.
|
||||
// It uses list.List from stdlib for its underlying doubly linked list.
|
||||
type memCacheLru struct {
|
||||
cache *memCache // Parent cache object.
|
||||
data *gmap.Map // Key mapping to the item of the list.
|
||||
list *glist.List // Key list.
|
||||
rawList *glist.List // History for key adding.
|
||||
closed *gtype.Bool // Closed or not.
|
||||
type adapterMemoryLru struct {
|
||||
cache *adapterMemory // Parent cache object.
|
||||
data *gmap.Map // Key mapping to the item of the list.
|
||||
list *glist.List // Key list.
|
||||
rawList *glist.List // History for key adding.
|
||||
closed *gtype.Bool // Closed or not.
|
||||
}
|
||||
|
||||
// newMemCacheLru creates and returns a new LRU object.
|
||||
func newMemCacheLru(cache *memCache) *memCacheLru {
|
||||
lru := &memCacheLru{
|
||||
func newMemCacheLru(cache *adapterMemory) *adapterMemoryLru {
|
||||
lru := &adapterMemoryLru{
|
||||
cache: cache,
|
||||
data: gmap.New(true),
|
||||
list: glist.New(true),
|
||||
@ -39,12 +39,12 @@ func newMemCacheLru(cache *memCache) *memCacheLru {
|
||||
}
|
||||
|
||||
// Close closes the LRU object.
|
||||
func (lru *memCacheLru) Close() {
|
||||
func (lru *adapterMemoryLru) Close() {
|
||||
lru.closed.Set(true)
|
||||
}
|
||||
|
||||
// Remove deletes the <key> FROM <lru>.
|
||||
func (lru *memCacheLru) Remove(key interface{}) {
|
||||
func (lru *adapterMemoryLru) Remove(key interface{}) {
|
||||
if v := lru.data.Get(key); v != nil {
|
||||
lru.data.Remove(key)
|
||||
lru.list.Remove(v.(*glist.Element))
|
||||
@ -52,17 +52,17 @@ func (lru *memCacheLru) Remove(key interface{}) {
|
||||
}
|
||||
|
||||
// Size returns the size of <lru>.
|
||||
func (lru *memCacheLru) Size() int {
|
||||
func (lru *adapterMemoryLru) Size() int {
|
||||
return lru.data.Size()
|
||||
}
|
||||
|
||||
// Push pushes <key> to the tail of <lru>.
|
||||
func (lru *memCacheLru) Push(key interface{}) {
|
||||
func (lru *adapterMemoryLru) Push(key interface{}) {
|
||||
lru.rawList.PushBack(key)
|
||||
}
|
||||
|
||||
// Pop deletes and returns the key from tail of <lru>.
|
||||
func (lru *memCacheLru) Pop() interface{} {
|
||||
func (lru *adapterMemoryLru) Pop() interface{} {
|
||||
if v := lru.list.PopBack(); v != nil {
|
||||
lru.data.Remove(v)
|
||||
return v
|
||||
@ -71,7 +71,7 @@ func (lru *memCacheLru) Pop() interface{} {
|
||||
}
|
||||
|
||||
// Print is used for test only.
|
||||
//func (lru *memCacheLru) Print() {
|
||||
//func (lru *adapterMemoryLru) Print() {
|
||||
// for _, v := range lru.list.FrontAll() {
|
||||
// fmt.Printf("%v ", v)
|
||||
// }
|
||||
@ -80,7 +80,7 @@ func (lru *memCacheLru) Pop() interface{} {
|
||||
|
||||
// SyncAndClear synchronizes the keys from <rawList> to <list> and <data>
|
||||
// using Least Recently Used algorithm.
|
||||
func (lru *memCacheLru) SyncAndClear() {
|
||||
func (lru *adapterMemoryLru) SyncAndClear() {
|
||||
if lru.closed.Val() {
|
||||
gtimer.Exit()
|
||||
return
|
||||
@ -7,31 +7,55 @@
|
||||
package gcache
|
||||
|
||||
import (
|
||||
"sync/atomic"
|
||||
"time"
|
||||
"unsafe"
|
||||
|
||||
"github.com/gogf/gf/container/gvar"
|
||||
"github.com/gogf/gf/os/gtimer"
|
||||
"github.com/gogf/gf/util/gconv"
|
||||
"time"
|
||||
)
|
||||
|
||||
// Cache struct.
|
||||
type Cache struct {
|
||||
*memCache
|
||||
Adapter // Adapter for cache features.
|
||||
}
|
||||
|
||||
// New creates and returns a new cache object.
|
||||
// New creates and returns a new cache object using default memory adapter.
|
||||
// Note that the LRU feature is only available using memory adapter.
|
||||
func New(lruCap ...int) *Cache {
|
||||
memAdapter := newAdapterMemory(lruCap...)
|
||||
c := &Cache{
|
||||
memCache: newMemCache(lruCap...),
|
||||
Adapter: memAdapter,
|
||||
}
|
||||
gtimer.AddSingleton(time.Second, c.syncEventAndClearExpired)
|
||||
// Here may be a "timer leak" if adapter is manually changed from memory adapter.
|
||||
// Do not worry about this, as adapter is less changed and it dose nothing if it's not used.
|
||||
gtimer.AddSingleton(time.Second, memAdapter.syncEventAndClearExpired)
|
||||
return c
|
||||
}
|
||||
|
||||
// Clear clears all data of the cache.
|
||||
func (c *Cache) Clear() {
|
||||
// atomic swap to ensure atomicity.
|
||||
old := atomic.SwapPointer((*unsafe.Pointer)(unsafe.Pointer(&c.memCache)), unsafe.Pointer(newMemCache()))
|
||||
// close the old cache object.
|
||||
(*memCache)(old).Close()
|
||||
// SetAdapter changes the adapter for this cache.
|
||||
// Be very note that, this setting function is not concurrent-safe, which means you should not call
|
||||
// this setting function concurrently in multiple goroutines.
|
||||
func (c *Cache) SetAdapter(adapter Adapter) {
|
||||
c.Adapter = adapter
|
||||
}
|
||||
|
||||
// GetVar retrieves and returns the value of <key> as gvar.Var.
|
||||
func (c *Cache) GetVar(key interface{}) (*gvar.Var, error) {
|
||||
v, err := c.Get(key)
|
||||
return gvar.New(v), err
|
||||
}
|
||||
|
||||
// Removes deletes <keys> in the cache.
|
||||
// Deprecated, use Remove instead.
|
||||
func (c *Cache) Removes(keys []interface{}) error {
|
||||
_, err := c.Remove(keys...)
|
||||
return err
|
||||
}
|
||||
|
||||
// KeyStrings returns all keys in the cache as string slice.
|
||||
func (c *Cache) KeyStrings() ([]string, error) {
|
||||
keys, err := c.Keys()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return gconv.Strings(keys), nil
|
||||
}
|
||||
|
||||
@ -25,8 +25,10 @@ func TestCache_GCache_Set(t *testing.T) {
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
gcache.Set(1, 11, 0)
|
||||
defer gcache.Removes(g.Slice{1, 2, 3})
|
||||
t.Assert(gcache.Get(1), 11)
|
||||
t.Assert(gcache.Contains(1), true)
|
||||
v, _ := gcache.Get(1)
|
||||
t.Assert(v, 11)
|
||||
b, _ := gcache.Contains(1)
|
||||
t.Assert(b, true)
|
||||
})
|
||||
}
|
||||
|
||||
@ -34,44 +36,57 @@ func TestCache_Set(t *testing.T) {
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
c := gcache.New()
|
||||
defer c.Close()
|
||||
c.Set(1, 11, 0)
|
||||
t.Assert(c.Get(1), 11)
|
||||
t.Assert(c.Contains(1), true)
|
||||
t.Assert(c.Set(1, 11, 0), nil)
|
||||
v, _ := c.Get(1)
|
||||
t.Assert(v, 11)
|
||||
b, _ := c.Contains(1)
|
||||
t.Assert(b, true)
|
||||
})
|
||||
}
|
||||
|
||||
func TestCache_GetVar(t *testing.T) {
|
||||
c := gcache.New()
|
||||
defer c.Close()
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
c := gcache.New()
|
||||
defer c.Close()
|
||||
c.Set(1, 11, 0)
|
||||
t.Assert(c.Get(1), 11)
|
||||
t.Assert(c.Contains(1), true)
|
||||
t.Assert(c.GetVar(1).Int(), 11)
|
||||
t.Assert(c.GetVar(2).Int(), 0)
|
||||
t.Assert(c.GetVar(2).IsNil(), true)
|
||||
t.Assert(c.GetVar(2).IsEmpty(), true)
|
||||
t.Assert(c.Set(1, 11, 0), nil)
|
||||
v, _ := c.Get(1)
|
||||
t.Assert(v, 11)
|
||||
b, _ := c.Contains(1)
|
||||
t.Assert(b, true)
|
||||
})
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
v, _ := c.GetVar(1)
|
||||
t.Assert(v.Int(), 11)
|
||||
v, _ = c.GetVar(2)
|
||||
t.Assert(v.Int(), 0)
|
||||
t.Assert(v.IsNil(), true)
|
||||
t.Assert(v.IsEmpty(), true)
|
||||
})
|
||||
}
|
||||
|
||||
func TestCache_Set_Expire(t *testing.T) {
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
cache := gcache.New()
|
||||
cache.Set(2, 22, 100*time.Millisecond)
|
||||
t.Assert(cache.Get(2), 22)
|
||||
t.Assert(cache.Set(2, 22, 100*time.Millisecond), nil)
|
||||
v, _ := cache.Get(2)
|
||||
t.Assert(v, 22)
|
||||
time.Sleep(200 * time.Millisecond)
|
||||
t.Assert(cache.Get(2), nil)
|
||||
v, _ = cache.Get(2)
|
||||
t.Assert(v, nil)
|
||||
time.Sleep(3 * time.Second)
|
||||
t.Assert(cache.Size(), 0)
|
||||
cache.Close()
|
||||
n, _ := cache.Size()
|
||||
t.Assert(n, 0)
|
||||
t.Assert(cache.Close(), nil)
|
||||
})
|
||||
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
cache := gcache.New()
|
||||
cache.Set(1, 11, 100*time.Millisecond)
|
||||
t.Assert(cache.Get(1), 11)
|
||||
t.Assert(cache.Set(1, 11, 100*time.Millisecond), nil)
|
||||
v, _ := cache.Get(1)
|
||||
t.Assert(v, 11)
|
||||
time.Sleep(200 * time.Millisecond)
|
||||
t.Assert(cache.Get(1), nil)
|
||||
v, _ = cache.Get(1)
|
||||
t.Assert(v, nil)
|
||||
})
|
||||
}
|
||||
|
||||
@ -80,20 +95,22 @@ func TestCache_Update_GetExpire(t *testing.T) {
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
key := guid.S()
|
||||
gcache.Set(key, 11, 3*time.Second)
|
||||
expire1 := gcache.GetExpire(key)
|
||||
expire1, _ := gcache.GetExpire(key)
|
||||
gcache.Update(key, 12)
|
||||
expire2 := gcache.GetExpire(key)
|
||||
t.Assert(gcache.GetVar(key), 12)
|
||||
expire2, _ := gcache.GetExpire(key)
|
||||
v, _ := gcache.GetVar(key)
|
||||
t.Assert(v, 12)
|
||||
t.Assert(math.Ceil(expire1.Seconds()), math.Ceil(expire2.Seconds()))
|
||||
})
|
||||
// gcache.Cache
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
cache := gcache.New()
|
||||
cache.Set(1, 11, 3*time.Second)
|
||||
expire1 := cache.GetExpire(1)
|
||||
expire1, _ := cache.GetExpire(1)
|
||||
cache.Update(1, 12)
|
||||
expire2 := cache.GetExpire(1)
|
||||
t.Assert(cache.GetVar(1), 12)
|
||||
expire2, _ := cache.GetExpire(1)
|
||||
v, _ := cache.GetVar(1)
|
||||
t.Assert(v, 12)
|
||||
t.Assert(math.Ceil(expire1.Seconds()), math.Ceil(expire2.Seconds()))
|
||||
})
|
||||
}
|
||||
@ -104,34 +121,43 @@ func TestCache_UpdateExpire(t *testing.T) {
|
||||
key := guid.S()
|
||||
gcache.Set(key, 11, 3*time.Second)
|
||||
defer gcache.Remove(key)
|
||||
oldExpire := gcache.GetExpire(key)
|
||||
oldExpire, _ := gcache.GetExpire(key)
|
||||
newExpire := 10 * time.Second
|
||||
gcache.UpdateExpire(key, newExpire)
|
||||
t.AssertNE(gcache.GetExpire(key), oldExpire)
|
||||
t.Assert(math.Ceil(gcache.GetExpire(key).Seconds()), 10)
|
||||
e, _ := gcache.GetExpire(key)
|
||||
t.AssertNE(e, oldExpire)
|
||||
e, _ = gcache.GetExpire(key)
|
||||
t.Assert(math.Ceil(e.Seconds()), 10)
|
||||
})
|
||||
// gcache.Cache
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
cache := gcache.New()
|
||||
cache.Set(1, 11, 3*time.Second)
|
||||
oldExpire := cache.GetExpire(1)
|
||||
oldExpire, _ := cache.GetExpire(1)
|
||||
newExpire := 10 * time.Second
|
||||
cache.UpdateExpire(1, newExpire)
|
||||
t.AssertNE(cache.GetExpire(1), oldExpire)
|
||||
t.Assert(math.Ceil(cache.GetExpire(1).Seconds()), 10)
|
||||
e, _ := cache.GetExpire(1)
|
||||
t.AssertNE(e, oldExpire)
|
||||
|
||||
e, _ = cache.GetExpire(1)
|
||||
t.Assert(math.Ceil(e.Seconds()), 10)
|
||||
})
|
||||
}
|
||||
|
||||
func TestCache_Keys_Values(t *testing.T) {
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
cache := gcache.New()
|
||||
c := gcache.New()
|
||||
for i := 0; i < 10; i++ {
|
||||
cache.Set(i, i*10, 0)
|
||||
t.Assert(c.Set(i, i*10, 0), nil)
|
||||
}
|
||||
t.Assert(len(cache.Keys()), 10)
|
||||
t.Assert(len(cache.Values()), 10)
|
||||
t.AssertIN(0, cache.Keys())
|
||||
t.AssertIN(90, cache.Values())
|
||||
var (
|
||||
keys, _ = c.Keys()
|
||||
values, _ = c.Values()
|
||||
)
|
||||
t.Assert(len(keys), 10)
|
||||
t.Assert(len(values), 10)
|
||||
t.AssertIN(0, keys)
|
||||
t.AssertIN(90, values)
|
||||
})
|
||||
}
|
||||
|
||||
@ -141,22 +167,30 @@ func TestCache_LRU(t *testing.T) {
|
||||
for i := 0; i < 10; i++ {
|
||||
cache.Set(i, i, 0)
|
||||
}
|
||||
t.Assert(cache.Size(), 10)
|
||||
t.Assert(cache.Get(6), 6)
|
||||
n, _ := cache.Size()
|
||||
t.Assert(n, 10)
|
||||
v, _ := cache.Get(6)
|
||||
t.Assert(v, 6)
|
||||
time.Sleep(4 * time.Second)
|
||||
t.Assert(cache.Size(), 2)
|
||||
t.Assert(cache.Get(6), 6)
|
||||
t.Assert(cache.Get(1), nil)
|
||||
cache.Close()
|
||||
n, _ = cache.Size()
|
||||
t.Assert(n, 2)
|
||||
v, _ = cache.Get(6)
|
||||
t.Assert(v, 6)
|
||||
v, _ = cache.Get(1)
|
||||
t.Assert(v, nil)
|
||||
t.Assert(cache.Close(), nil)
|
||||
})
|
||||
}
|
||||
|
||||
func TestCache_LRU_expire(t *testing.T) {
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
cache := gcache.New(2)
|
||||
cache.Set(1, nil, 1000)
|
||||
t.Assert(cache.Size(), 1)
|
||||
t.Assert(cache.Get(1), nil)
|
||||
t.Assert(cache.Set(1, nil, 1000), nil)
|
||||
n, _ := cache.Size()
|
||||
t.Assert(n, 1)
|
||||
v, _ := cache.Get(1)
|
||||
|
||||
t.Assert(v, nil)
|
||||
})
|
||||
}
|
||||
|
||||
@ -164,17 +198,22 @@ func TestCache_SetIfNotExist(t *testing.T) {
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
cache := gcache.New()
|
||||
cache.SetIfNotExist(1, 11, 0)
|
||||
t.Assert(cache.Get(1), 11)
|
||||
v, _ := cache.Get(1)
|
||||
t.Assert(v, 11)
|
||||
cache.SetIfNotExist(1, 22, 0)
|
||||
t.Assert(cache.Get(1), 11)
|
||||
v, _ = cache.Get(1)
|
||||
t.Assert(v, 11)
|
||||
cache.SetIfNotExist(2, 22, 0)
|
||||
t.Assert(cache.Get(2), 22)
|
||||
v, _ = cache.Get(2)
|
||||
t.Assert(v, 22)
|
||||
|
||||
gcache.Removes(g.Slice{1, 2, 3})
|
||||
gcache.SetIfNotExist(1, 11, 0)
|
||||
t.Assert(gcache.Get(1), 11)
|
||||
v, _ = gcache.Get(1)
|
||||
t.Assert(v, 11)
|
||||
gcache.SetIfNotExist(1, 22, 0)
|
||||
t.Assert(gcache.Get(1), 11)
|
||||
v, _ = gcache.Get(1)
|
||||
t.Assert(v, 11)
|
||||
})
|
||||
}
|
||||
|
||||
@ -182,11 +221,13 @@ func TestCache_Sets(t *testing.T) {
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
cache := gcache.New()
|
||||
cache.Sets(g.MapAnyAny{1: 11, 2: 22}, 0)
|
||||
t.Assert(cache.Get(1), 11)
|
||||
v, _ := cache.Get(1)
|
||||
t.Assert(v, 11)
|
||||
|
||||
gcache.Removes(g.Slice{1, 2, 3})
|
||||
gcache.Sets(g.MapAnyAny{1: 11, 2: 22}, 0)
|
||||
t.Assert(gcache.Get(1), 11)
|
||||
v, _ = cache.Get(1)
|
||||
t.Assert(v, 11)
|
||||
})
|
||||
}
|
||||
|
||||
@ -194,63 +235,82 @@ func TestCache_GetOrSet(t *testing.T) {
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
cache := gcache.New()
|
||||
cache.GetOrSet(1, 11, 0)
|
||||
t.Assert(cache.Get(1), 11)
|
||||
v, _ := cache.Get(1)
|
||||
t.Assert(v, 11)
|
||||
cache.GetOrSet(1, 111, 0)
|
||||
t.Assert(cache.Get(1), 11)
|
||||
|
||||
v, _ = cache.Get(1)
|
||||
t.Assert(v, 11)
|
||||
gcache.Removes(g.Slice{1, 2, 3})
|
||||
gcache.GetOrSet(1, 11, 0)
|
||||
t.Assert(gcache.Get(1), 11)
|
||||
|
||||
v, _ = cache.Get(1)
|
||||
t.Assert(v, 11)
|
||||
|
||||
gcache.GetOrSet(1, 111, 0)
|
||||
t.Assert(gcache.Get(1), 11)
|
||||
v, _ = cache.Get(1)
|
||||
t.Assert(v, 11)
|
||||
})
|
||||
}
|
||||
|
||||
func TestCache_GetOrSetFunc(t *testing.T) {
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
cache := gcache.New()
|
||||
cache.GetOrSetFunc(1, func() interface{} {
|
||||
return 11
|
||||
cache.GetOrSetFunc(1, func() (interface{}, error) {
|
||||
return 11, nil
|
||||
}, 0)
|
||||
t.Assert(cache.Get(1), 11)
|
||||
cache.GetOrSetFunc(1, func() interface{} {
|
||||
return 111
|
||||
v, _ := cache.Get(1)
|
||||
t.Assert(v, 11)
|
||||
|
||||
cache.GetOrSetFunc(1, func() (interface{}, error) {
|
||||
return 111, nil
|
||||
}, 0)
|
||||
t.Assert(cache.Get(1), 11)
|
||||
v, _ = cache.Get(1)
|
||||
t.Assert(v, 11)
|
||||
|
||||
gcache.Removes(g.Slice{1, 2, 3})
|
||||
gcache.GetOrSetFunc(1, func() interface{} {
|
||||
return 11
|
||||
|
||||
gcache.GetOrSetFunc(1, func() (interface{}, error) {
|
||||
return 11, nil
|
||||
}, 0)
|
||||
t.Assert(gcache.Get(1), 11)
|
||||
gcache.GetOrSetFunc(1, func() interface{} {
|
||||
return 111
|
||||
v, _ = cache.Get(1)
|
||||
t.Assert(v, 11)
|
||||
|
||||
gcache.GetOrSetFunc(1, func() (interface{}, error) {
|
||||
return 111, nil
|
||||
}, 0)
|
||||
t.Assert(gcache.Get(1), 11)
|
||||
v, _ = cache.Get(1)
|
||||
t.Assert(v, 11)
|
||||
})
|
||||
}
|
||||
|
||||
func TestCache_GetOrSetFuncLock(t *testing.T) {
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
cache := gcache.New()
|
||||
cache.GetOrSetFuncLock(1, func() interface{} {
|
||||
return 11
|
||||
cache.GetOrSetFuncLock(1, func() (interface{}, error) {
|
||||
return 11, nil
|
||||
}, 0)
|
||||
t.Assert(cache.Get(1), 11)
|
||||
cache.GetOrSetFuncLock(1, func() interface{} {
|
||||
return 111
|
||||
v, _ := cache.Get(1)
|
||||
t.Assert(v, 11)
|
||||
|
||||
cache.GetOrSetFuncLock(1, func() (interface{}, error) {
|
||||
return 111, nil
|
||||
}, 0)
|
||||
t.Assert(cache.Get(1), 11)
|
||||
v, _ = cache.Get(1)
|
||||
t.Assert(v, 11)
|
||||
|
||||
gcache.Removes(g.Slice{1, 2, 3})
|
||||
gcache.GetOrSetFuncLock(1, func() interface{} {
|
||||
return 11
|
||||
gcache.GetOrSetFuncLock(1, func() (interface{}, error) {
|
||||
return 11, nil
|
||||
}, 0)
|
||||
t.Assert(gcache.Get(1), 11)
|
||||
gcache.GetOrSetFuncLock(1, func() interface{} {
|
||||
return 111
|
||||
v, _ = cache.Get(1)
|
||||
t.Assert(v, 11)
|
||||
|
||||
gcache.GetOrSetFuncLock(1, func() (interface{}, error) {
|
||||
return 111, nil
|
||||
}, 0)
|
||||
t.Assert(gcache.Get(1), 11)
|
||||
v, _ = cache.Get(1)
|
||||
t.Assert(v, 11)
|
||||
})
|
||||
}
|
||||
|
||||
@ -259,7 +319,8 @@ func TestCache_Clear(t *testing.T) {
|
||||
cache := gcache.New()
|
||||
cache.Sets(g.MapAnyAny{1: 11, 2: 22}, 0)
|
||||
cache.Clear()
|
||||
t.Assert(cache.Size(), 0)
|
||||
n, _ := cache.Size()
|
||||
t.Assert(n, 0)
|
||||
})
|
||||
}
|
||||
|
||||
@ -298,47 +359,57 @@ func TestCache_Basic(t *testing.T) {
|
||||
{
|
||||
cache := gcache.New()
|
||||
cache.Sets(g.MapAnyAny{1: 11, 2: 22}, 0)
|
||||
t.Assert(cache.Contains(1), true)
|
||||
t.Assert(cache.Get(1), 11)
|
||||
data := cache.Data()
|
||||
b, _ := cache.Contains(1)
|
||||
t.Assert(b, true)
|
||||
v, _ := cache.Get(1)
|
||||
t.Assert(v, 11)
|
||||
data, _ := cache.Data()
|
||||
t.Assert(data[1], 11)
|
||||
t.Assert(data[2], 22)
|
||||
t.Assert(data[3], nil)
|
||||
t.Assert(cache.Size(), 2)
|
||||
keys := cache.Keys()
|
||||
n, _ := cache.Size()
|
||||
t.Assert(n, 2)
|
||||
keys, _ := cache.Keys()
|
||||
t.Assert(gset.NewFrom(g.Slice{1, 2}).Equal(gset.NewFrom(keys)), true)
|
||||
keyStrs := cache.KeyStrings()
|
||||
keyStrs, _ := cache.KeyStrings()
|
||||
t.Assert(gset.NewFrom(g.Slice{"1", "2"}).Equal(gset.NewFrom(keyStrs)), true)
|
||||
values := cache.Values()
|
||||
values, _ := cache.Values()
|
||||
t.Assert(gset.NewFrom(g.Slice{11, 22}).Equal(gset.NewFrom(values)), true)
|
||||
removeData1 := cache.Remove(1)
|
||||
removeData1, _ := cache.Remove(1)
|
||||
t.Assert(removeData1, 11)
|
||||
t.Assert(cache.Size(), 1)
|
||||
n, _ = cache.Size()
|
||||
t.Assert(n, 1)
|
||||
cache.Removes(g.Slice{2})
|
||||
t.Assert(cache.Size(), 0)
|
||||
n, _ = cache.Size()
|
||||
t.Assert(n, 0)
|
||||
}
|
||||
|
||||
gcache.Remove(g.Slice{1, 2, 3}...)
|
||||
{
|
||||
gcache.Sets(g.MapAnyAny{1: 11, 2: 22}, 0)
|
||||
t.Assert(gcache.Contains(1), true)
|
||||
t.Assert(gcache.Get(1), 11)
|
||||
data := gcache.Data()
|
||||
b, _ := gcache.Contains(1)
|
||||
t.Assert(b, true)
|
||||
v, _ := gcache.Get(1)
|
||||
t.Assert(v, 11)
|
||||
data, _ := gcache.Data()
|
||||
t.Assert(data[1], 11)
|
||||
t.Assert(data[2], 22)
|
||||
t.Assert(data[3], nil)
|
||||
t.Assert(gcache.Size(), 2)
|
||||
keys := gcache.Keys()
|
||||
n, _ := gcache.Size()
|
||||
t.Assert(n, 2)
|
||||
keys, _ := gcache.Keys()
|
||||
t.Assert(gset.NewFrom(g.Slice{1, 2}).Equal(gset.NewFrom(keys)), true)
|
||||
keyStrs := gcache.KeyStrings()
|
||||
keyStrs, _ := gcache.KeyStrings()
|
||||
t.Assert(gset.NewFrom(g.Slice{"1", "2"}).Equal(gset.NewFrom(keyStrs)), true)
|
||||
values := gcache.Values()
|
||||
values, _ := gcache.Values()
|
||||
t.Assert(gset.NewFrom(g.Slice{11, 22}).Equal(gset.NewFrom(values)), true)
|
||||
removeData1 := gcache.Remove(1)
|
||||
removeData1, _ := gcache.Remove(1)
|
||||
t.Assert(removeData1, 11)
|
||||
t.Assert(gcache.Size(), 1)
|
||||
n, _ = gcache.Size()
|
||||
t.Assert(n, 1)
|
||||
gcache.Removes(g.Slice{2})
|
||||
t.Assert(gcache.Size(), 0)
|
||||
n, _ = gcache.Size()
|
||||
t.Assert(n, 0)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
@ -334,7 +334,10 @@ func (c *Config) getJson(file ...string) *gjson.Json {
|
||||
content = ""
|
||||
filePath = ""
|
||||
)
|
||||
// The configured content can be any kind of data type different from its file type.
|
||||
isFromConfigContent := true
|
||||
if content = GetContent(name); content == "" {
|
||||
isFromConfigContent = false
|
||||
filePath = c.filePath(name)
|
||||
if filePath == "" {
|
||||
return nil
|
||||
@ -346,7 +349,17 @@ func (c *Config) getJson(file ...string) *gjson.Json {
|
||||
}
|
||||
}
|
||||
// Note that the underlying configuration json object operations are concurrent safe.
|
||||
if j, err := gjson.LoadContent(content, true); err == nil {
|
||||
var (
|
||||
j *gjson.Json
|
||||
err error
|
||||
)
|
||||
dataType := gfile.ExtName(name)
|
||||
if gjson.IsValidDataType(dataType) && !isFromConfigContent {
|
||||
j, err = gjson.LoadContentType(dataType, content, true)
|
||||
} else {
|
||||
j, err = gjson.LoadContent(content, true)
|
||||
}
|
||||
if err == nil {
|
||||
j.SetViolenceCheck(c.vc)
|
||||
// Add monitor for this configuration file,
|
||||
// any changes of this file will refresh its cache in Config object.
|
||||
|
||||
@ -231,21 +231,16 @@ func Test_SetFileName(t *testing.T) {
|
||||
|
||||
func Test_Instance(t *testing.T) {
|
||||
config := `
|
||||
{
|
||||
"array": [
|
||||
1,
|
||||
2,
|
||||
3
|
||||
],
|
||||
"redis": {
|
||||
"cache": "127.0.0.1:6379,1",
|
||||
"disk": "127.0.0.1:6379,0"
|
||||
},
|
||||
"v1": 1,
|
||||
"v2": "true",
|
||||
"v3": "off",
|
||||
"v4": "1.234"
|
||||
}
|
||||
array = [1.0, 2.0, 3.0]
|
||||
v1 = 1.0
|
||||
v2 = "true"
|
||||
v3 = "off"
|
||||
v4 = "1.234"
|
||||
|
||||
[redis]
|
||||
cache = "127.0.0.1:6379,1"
|
||||
disk = "127.0.0.1:6379,0"
|
||||
|
||||
`
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
path := gcfg.DEFAULT_CONFIG_FILE
|
||||
|
||||
@ -39,7 +39,7 @@ func GetBytesWithCache(path string, duration ...time.Duration) []byte {
|
||||
if len(duration) > 0 {
|
||||
expire = duration[0]
|
||||
}
|
||||
r := gcache.GetOrSetFuncLock(key, func() interface{} {
|
||||
r, _ := gcache.GetOrSetFuncLock(key, func() (interface{}, error) {
|
||||
b := GetBytes(path)
|
||||
if b != nil {
|
||||
// Adding this <path> to gfsnotify,
|
||||
@ -49,7 +49,7 @@ func GetBytesWithCache(path string, duration ...time.Duration) []byte {
|
||||
gfsnotify.Exit()
|
||||
})
|
||||
}
|
||||
return b
|
||||
return b, nil
|
||||
}, expire)
|
||||
if r != nil {
|
||||
return r.([]byte)
|
||||
|
||||
@ -7,12 +7,18 @@
|
||||
package gfile
|
||||
|
||||
import (
|
||||
"github.com/gogf/gf/errors/gerror"
|
||||
"github.com/gogf/gf/text/gstr"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"sort"
|
||||
)
|
||||
|
||||
const (
|
||||
// Max recursive depth for directory scanning.
|
||||
gMAX_SCAN_DEPTH = 100000
|
||||
)
|
||||
|
||||
// ScanDir returns all sub-files with absolute paths of given <path>,
|
||||
// It scans directory recursively if given parameter <recursive> is true.
|
||||
//
|
||||
@ -23,7 +29,7 @@ func ScanDir(path string, pattern string, recursive ...bool) ([]string, error) {
|
||||
if len(recursive) > 0 {
|
||||
isRecursive = recursive[0]
|
||||
}
|
||||
list, err := doScanDir(path, pattern, isRecursive, nil)
|
||||
list, err := doScanDir(0, path, pattern, isRecursive, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@ -47,7 +53,7 @@ func ScanDir(path string, pattern string, recursive ...bool) ([]string, error) {
|
||||
// the <path> and its sub-folders. It ignores the sub-file path if <handler> returns an empty
|
||||
// string, or else it appends the sub-file path to result slice.
|
||||
func ScanDirFunc(path string, pattern string, recursive bool, handler func(path string) string) ([]string, error) {
|
||||
list, err := doScanDir(path, pattern, recursive, handler)
|
||||
list, err := doScanDir(0, path, pattern, recursive, handler)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@ -69,7 +75,7 @@ func ScanDirFile(path string, pattern string, recursive ...bool) ([]string, erro
|
||||
if len(recursive) > 0 {
|
||||
isRecursive = recursive[0]
|
||||
}
|
||||
list, err := doScanDir(path, pattern, isRecursive, func(path string) string {
|
||||
list, err := doScanDir(0, path, pattern, isRecursive, func(path string) string {
|
||||
if IsDir(path) {
|
||||
return ""
|
||||
}
|
||||
@ -84,6 +90,38 @@ func ScanDirFile(path string, pattern string, recursive ...bool) ([]string, erro
|
||||
return list, nil
|
||||
}
|
||||
|
||||
// ScanDirFileFunc returns all sub-files with absolute paths of given <path>,
|
||||
// It scans directory recursively if given parameter <recursive> is true.
|
||||
//
|
||||
// The pattern parameter <pattern> supports multiple file name patterns, using the ','
|
||||
// symbol to separate multiple patterns.
|
||||
//
|
||||
// The parameter <recursive> specifies whether scanning the <path> recursively, which
|
||||
// means it scans its sub-files and appends the files path to result array if the sub-file
|
||||
// is also a folder. It is false in default.
|
||||
//
|
||||
// The parameter <handler> specifies the callback function handling each sub-file path of
|
||||
// the <path> and its sub-folders. It ignores the sub-file path if <handler> returns an empty
|
||||
// string, or else it appends the sub-file path to result slice.
|
||||
//
|
||||
// Note that the parameter <path> for <handler> is not a directory but a file.
|
||||
// It returns only files, exclusive of directories.
|
||||
func ScanDirFileFunc(path string, pattern string, recursive bool, handler func(path string) string) ([]string, error) {
|
||||
list, err := doScanDir(0, path, pattern, recursive, func(path string) string {
|
||||
if IsDir(path) {
|
||||
return ""
|
||||
}
|
||||
return handler(path)
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if len(list) > 0 {
|
||||
sort.Strings(list)
|
||||
}
|
||||
return list, nil
|
||||
}
|
||||
|
||||
// doScanDir is an internal method which scans directory and returns the absolute path
|
||||
// list of files that are not sorted.
|
||||
//
|
||||
@ -97,7 +135,10 @@ func ScanDirFile(path string, pattern string, recursive ...bool) ([]string, erro
|
||||
// The parameter <handler> specifies the callback function handling each sub-file path of
|
||||
// the <path> and its sub-folders. It ignores the sub-file path if <handler> returns an empty
|
||||
// string, or else it appends the sub-file path to result slice.
|
||||
func doScanDir(path string, pattern string, recursive bool, handler func(path string) string) ([]string, error) {
|
||||
func doScanDir(depth int, path string, pattern string, recursive bool, handler func(path string) string) ([]string, error) {
|
||||
if depth >= gMAX_SCAN_DEPTH {
|
||||
return nil, gerror.Newf("directory scanning exceeds max recursive depth: %d", gMAX_SCAN_DEPTH)
|
||||
}
|
||||
list := ([]string)(nil)
|
||||
file, err := os.Open(path)
|
||||
if err != nil {
|
||||
@ -115,7 +156,7 @@ func doScanDir(path string, pattern string, recursive bool, handler func(path st
|
||||
for _, name := range names {
|
||||
filePath = path + Separator + name
|
||||
if IsDir(filePath) && recursive {
|
||||
array, _ := doScanDir(filePath, pattern, true, handler)
|
||||
array, _ := doScanDir(depth+1, filePath, pattern, true, handler)
|
||||
if len(array) > 0 {
|
||||
list = append(list, array...)
|
||||
}
|
||||
|
||||
@ -7,6 +7,7 @@
|
||||
package gfile_test
|
||||
|
||||
import (
|
||||
"github.com/gogf/gf/container/garray"
|
||||
"github.com/gogf/gf/debug/gdebug"
|
||||
"testing"
|
||||
|
||||
@ -65,3 +66,30 @@ func Test_ScanDirFile(t *testing.T) {
|
||||
t.AssertIN(teatPath+gfile.Separator+"dir2"+gfile.Separator+"file2", files)
|
||||
})
|
||||
}
|
||||
|
||||
func Test_ScanDirFileFunc(t *testing.T) {
|
||||
teatPath := gdebug.TestDataPath()
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
array := garray.New()
|
||||
files, err := gfile.ScanDirFileFunc(teatPath, "*", false, func(path string) string {
|
||||
array.Append(1)
|
||||
return path
|
||||
})
|
||||
t.Assert(err, nil)
|
||||
t.Assert(len(files), 0)
|
||||
t.Assert(array.Len(), 0)
|
||||
})
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
array := garray.New()
|
||||
files, err := gfile.ScanDirFileFunc(teatPath, "*", true, func(path string) string {
|
||||
array.Append(1)
|
||||
if gfile.Basename(path) == "file1" {
|
||||
return path
|
||||
}
|
||||
return ""
|
||||
})
|
||||
t.Assert(err, nil)
|
||||
t.Assert(len(files), 1)
|
||||
t.Assert(array.Len(), 3)
|
||||
})
|
||||
}
|
||||
|
||||
@ -12,6 +12,7 @@ import (
|
||||
"fmt"
|
||||
"github.com/gogf/gf/container/gset"
|
||||
"github.com/gogf/gf/internal/intlog"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/fsnotify/fsnotify"
|
||||
@ -67,20 +68,12 @@ const (
|
||||
)
|
||||
|
||||
var (
|
||||
mu sync.Mutex // Mutex for concurrent safety of defaultWatcher.
|
||||
defaultWatcher *Watcher // Default watcher.
|
||||
callbackIdMap = gmap.NewIntAnyMap(true) // Id to callback mapping.
|
||||
callbackIdGenerator = gtype.NewInt() // Atomic id generator for callback.
|
||||
)
|
||||
|
||||
func init() {
|
||||
var err error
|
||||
defaultWatcher, err = New()
|
||||
if err != nil {
|
||||
// Default watcher object must be created, or else it panics.
|
||||
panic(fmt.Sprintf(`creating default fsnotify watcher failed: %s`, err.Error()))
|
||||
}
|
||||
}
|
||||
|
||||
// New creates and returns a new watcher.
|
||||
// Note that the watcher number is limited by the file handle setting of the system.
|
||||
// Eg: fs.inotify.max_user_instances system variable in linux systems.
|
||||
@ -106,7 +99,11 @@ func New() (*Watcher, error) {
|
||||
// Add monitors <path> using default watcher with callback function <callbackFunc>.
|
||||
// The optional parameter <recursive> specifies whether monitoring the <path> recursively, which is true in default.
|
||||
func Add(path string, callbackFunc func(event *Event), recursive ...bool) (callback *Callback, err error) {
|
||||
return defaultWatcher.Add(path, callbackFunc, recursive...)
|
||||
w, err := getDefaultWatcher()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return w.Add(path, callbackFunc, recursive...)
|
||||
}
|
||||
|
||||
// AddOnce monitors <path> using default watcher with callback function <callbackFunc> only once using unique name <name>.
|
||||
@ -115,16 +112,28 @@ func Add(path string, callbackFunc func(event *Event), recursive ...bool) (callb
|
||||
//
|
||||
// The optional parameter <recursive> specifies whether monitoring the <path> recursively, which is true in default.
|
||||
func AddOnce(name, path string, callbackFunc func(event *Event), recursive ...bool) (callback *Callback, err error) {
|
||||
return defaultWatcher.AddOnce(name, path, callbackFunc, recursive...)
|
||||
w, err := getDefaultWatcher()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return w.AddOnce(name, path, callbackFunc, recursive...)
|
||||
}
|
||||
|
||||
// Remove removes all monitoring callbacks of given <path> from watcher recursively.
|
||||
func Remove(path string) error {
|
||||
return defaultWatcher.Remove(path)
|
||||
w, err := getDefaultWatcher()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return w.Remove(path)
|
||||
}
|
||||
|
||||
// RemoveCallback removes specified callback with given id from watcher.
|
||||
func RemoveCallback(callbackId int) error {
|
||||
w, err := getDefaultWatcher()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
callback := (*Callback)(nil)
|
||||
if r := callbackIdMap.Get(callbackId); r != nil {
|
||||
callback = r.(*Callback)
|
||||
@ -132,7 +141,7 @@ func RemoveCallback(callbackId int) error {
|
||||
if callback == nil {
|
||||
return errors.New(fmt.Sprintf(`callback for id %d not found`, callbackId))
|
||||
}
|
||||
defaultWatcher.RemoveCallback(callbackId)
|
||||
w.RemoveCallback(callbackId)
|
||||
return nil
|
||||
}
|
||||
|
||||
@ -141,3 +150,16 @@ func RemoveCallback(callbackId int) error {
|
||||
func Exit() {
|
||||
panic(callbackExitEventPanicStr)
|
||||
}
|
||||
|
||||
// getDefaultWatcher creates and returns the default watcher.
|
||||
// This is used for lazy initialization purpose.
|
||||
func getDefaultWatcher() (*Watcher, error) {
|
||||
mu.Lock()
|
||||
defer mu.Unlock()
|
||||
if defaultWatcher != nil {
|
||||
return defaultWatcher, nil
|
||||
}
|
||||
var err error
|
||||
defaultWatcher, err = New()
|
||||
return defaultWatcher, err
|
||||
}
|
||||
|
||||
@ -23,14 +23,14 @@ func (w *Watcher) startWatchLoop() {
|
||||
// Event listening.
|
||||
case ev := <-w.watcher.Events:
|
||||
// Filter the repeated event in custom duration.
|
||||
w.cache.SetIfNotExist(ev.String(), func() interface{} {
|
||||
w.cache.SetIfNotExist(ev.String(), func() (interface{}, error) {
|
||||
w.events.Push(&Event{
|
||||
event: ev,
|
||||
Path: ev.Name,
|
||||
Op: Op(ev.Op),
|
||||
Watcher: w,
|
||||
})
|
||||
return struct{}{}
|
||||
return struct{}{}, nil
|
||||
}, repeatEventFilterDuration)
|
||||
|
||||
case err := <-w.watcher.Errors:
|
||||
|
||||
@ -230,7 +230,8 @@ func (l *Logger) printToWriter(now time.Time, std io.Writer, buffer *bytes.Buffe
|
||||
}
|
||||
} else {
|
||||
if _, err := l.config.Writer.Write(buffer.Bytes()); err != nil {
|
||||
panic(err)
|
||||
// panic(err)
|
||||
intlog.Error(err)
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -249,7 +250,9 @@ func (l *Logger) printToFile(now time.Time, buffer *bytes.Buffer) {
|
||||
stat, err := file.Stat()
|
||||
if err != nil {
|
||||
file.Close()
|
||||
panic(err)
|
||||
// panic(err)
|
||||
intlog.Error(err)
|
||||
return
|
||||
}
|
||||
if stat.Size() > l.config.RotateSize {
|
||||
l.rotateFileBySize(now)
|
||||
@ -258,7 +261,9 @@ func (l *Logger) printToFile(now time.Time, buffer *bytes.Buffer) {
|
||||
}
|
||||
if _, err := file.Write(buffer.Bytes()); err != nil {
|
||||
file.Close()
|
||||
panic(err)
|
||||
// panic(err)
|
||||
intlog.Error(err)
|
||||
return
|
||||
}
|
||||
file.Close()
|
||||
}
|
||||
@ -272,7 +277,8 @@ func (l *Logger) getFilePointer(path string) *gfpool.File {
|
||||
gDEFAULT_FILE_EXPIRE,
|
||||
)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
// panic(err)
|
||||
intlog.Error(err)
|
||||
}
|
||||
return file
|
||||
}
|
||||
|
||||
@ -8,6 +8,7 @@ package glog
|
||||
|
||||
import (
|
||||
"context"
|
||||
"github.com/gogf/gf/internal/intlog"
|
||||
"io"
|
||||
|
||||
"github.com/gogf/gf/os/gfile"
|
||||
@ -55,7 +56,8 @@ func (l *Logger) Path(path string) *Logger {
|
||||
}
|
||||
if path != "" {
|
||||
if err := logger.SetPath(path); err != nil {
|
||||
panic(err)
|
||||
// panic(err)
|
||||
intlog.Error(err)
|
||||
}
|
||||
}
|
||||
return logger
|
||||
@ -73,7 +75,8 @@ func (l *Logger) Cat(category string) *Logger {
|
||||
}
|
||||
if logger.config.Path != "" {
|
||||
if err := logger.SetPath(gfile.Join(logger.config.Path, category)); err != nil {
|
||||
panic(err)
|
||||
// panic(err)
|
||||
intlog.Error(err)
|
||||
}
|
||||
}
|
||||
return logger
|
||||
@ -115,7 +118,8 @@ func (l *Logger) LevelStr(levelStr string) *Logger {
|
||||
logger = l
|
||||
}
|
||||
if err := logger.SetLevelStr(levelStr); err != nil {
|
||||
panic(err)
|
||||
// panic(err)
|
||||
intlog.Error(err)
|
||||
}
|
||||
return logger
|
||||
}
|
||||
|
||||
@ -28,7 +28,8 @@ func (l *Logger) rotateFileBySize(now time.Time) {
|
||||
l.rmu.Lock()
|
||||
defer l.rmu.Unlock()
|
||||
if err := l.doRotateFile(l.getFilePath(now)); err != nil {
|
||||
panic(err)
|
||||
// panic(err)
|
||||
intlog.Error(err)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -40,7 +40,7 @@ func (s *Session) init() {
|
||||
if s.id != "" {
|
||||
var err error
|
||||
// Retrieve memory session data from manager.
|
||||
if r := s.manager.sessionData.Get(s.id); r != nil {
|
||||
if r, _ := s.manager.sessionData.Get(s.id); r != nil {
|
||||
s.data = r.(*gmap.StrAnyMap)
|
||||
intlog.Print("session init data:", s.data)
|
||||
}
|
||||
@ -50,11 +50,6 @@ func (s *Session) init() {
|
||||
intlog.Errorf("session restoring failed for id '%s': %v", s.id, err)
|
||||
}
|
||||
}
|
||||
// If it's an invalid or expired session id,
|
||||
// it should create a new session id.
|
||||
if s.data == nil {
|
||||
s.id = ""
|
||||
}
|
||||
}
|
||||
// Use custom session id creating function.
|
||||
if s.id == "" && s.idFunc != nil {
|
||||
|
||||
@ -17,13 +17,21 @@ type Time struct {
|
||||
TimeWrapper
|
||||
}
|
||||
|
||||
// apiUnixNano is an interface definition commonly for custom time.Time wrapper.
|
||||
type apiUnixNano interface {
|
||||
UnixNano() int64
|
||||
}
|
||||
|
||||
// New creates and returns a Time object with given parameter.
|
||||
// The optional parameter can be type of: time.Time, string or integer.
|
||||
// The optional parameter can be type of: time.Time/*time.Time, string or integer.
|
||||
func New(param ...interface{}) *Time {
|
||||
if len(param) > 0 {
|
||||
switch r := param[0].(type) {
|
||||
case time.Time:
|
||||
r.Nanosecond()
|
||||
return NewFromTime(r)
|
||||
case *time.Time:
|
||||
return NewFromTime(*r)
|
||||
case string:
|
||||
return NewFromStr(r)
|
||||
case []byte:
|
||||
@ -32,6 +40,10 @@ func New(param ...interface{}) *Time {
|
||||
return NewFromTimeStamp(int64(r))
|
||||
case int64:
|
||||
return NewFromTimeStamp(r)
|
||||
default:
|
||||
if v, ok := r.(apiUnixNano); ok {
|
||||
return NewFromTimeStamp(v.UnixNano())
|
||||
}
|
||||
}
|
||||
}
|
||||
return &Time{
|
||||
@ -84,6 +96,7 @@ func NewFromStrLayout(str string, layout string) *Time {
|
||||
|
||||
// NewFromTimeStamp creates and returns a Time object with given timestamp,
|
||||
// which can be in seconds to nanoseconds.
|
||||
// Eg: 1600443866 and 1600443866199266000 are both considered as valid timestamp number.
|
||||
func NewFromTimeStamp(timestamp int64) *Time {
|
||||
if timestamp == 0 {
|
||||
return &Time{}
|
||||
@ -189,31 +202,31 @@ func (t *Time) Clone() *Time {
|
||||
|
||||
// Add adds the duration to current time.
|
||||
func (t *Time) Add(d time.Duration) *Time {
|
||||
t.Time = t.Time.Add(d)
|
||||
return t
|
||||
newTime := t.Clone()
|
||||
newTime.Time = newTime.Time.Add(d)
|
||||
return newTime
|
||||
}
|
||||
|
||||
// AddStr parses the given duration as string and adds it to current time.
|
||||
func (t *Time) AddStr(duration string) error {
|
||||
func (t *Time) AddStr(duration string) (*Time, error) {
|
||||
if d, err := time.ParseDuration(duration); err != nil {
|
||||
return err
|
||||
return nil, err
|
||||
} else {
|
||||
t.Time = t.Time.Add(d)
|
||||
return t.Add(d), nil
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// ToLocation converts current time to specified location.
|
||||
func (t *Time) ToLocation(location *time.Location) *Time {
|
||||
t.Time = t.Time.In(location)
|
||||
return t
|
||||
newTime := t.Clone()
|
||||
newTime.Time = newTime.Time.In(location)
|
||||
return newTime
|
||||
}
|
||||
|
||||
// ToZone converts current time to specified zone like: Asia/Shanghai.
|
||||
func (t *Time) ToZone(zone string) (*Time, error) {
|
||||
if l, err := time.LoadLocation(zone); err == nil {
|
||||
t.Time = t.Time.In(l)
|
||||
return t, nil
|
||||
return t.ToLocation(l), nil
|
||||
} else {
|
||||
return nil, err
|
||||
}
|
||||
@ -221,8 +234,9 @@ func (t *Time) ToZone(zone string) (*Time, error) {
|
||||
|
||||
// UTC converts current time to UTC timezone.
|
||||
func (t *Time) UTC() *Time {
|
||||
t.Time = t.Time.UTC()
|
||||
return t
|
||||
newTime := t.Clone()
|
||||
newTime.Time = newTime.Time.UTC()
|
||||
return newTime
|
||||
}
|
||||
|
||||
// ISO8601 formats the time as ISO8601 and returns it as string.
|
||||
@ -237,14 +251,16 @@ func (t *Time) RFC822() string {
|
||||
|
||||
// Local converts the time to local timezone.
|
||||
func (t *Time) Local() *Time {
|
||||
t.Time = t.Time.Local()
|
||||
return t
|
||||
newTime := t.Clone()
|
||||
newTime.Time = newTime.Time.Local()
|
||||
return newTime
|
||||
}
|
||||
|
||||
// AddDate adds year, month and day to the time.
|
||||
func (t *Time) AddDate(years int, months int, days int) *Time {
|
||||
t.Time = t.Time.AddDate(years, months, days)
|
||||
return t
|
||||
newTime := t.Clone()
|
||||
newTime.Time = newTime.Time.AddDate(years, months, days)
|
||||
return newTime
|
||||
}
|
||||
|
||||
// Round returns the result of rounding t to the nearest multiple of d (since the zero time).
|
||||
@ -256,8 +272,9 @@ func (t *Time) AddDate(years int, months int, days int) *Time {
|
||||
// time. Thus, Round(Hour) may return a time with a non-zero
|
||||
// minute, depending on the time's Location.
|
||||
func (t *Time) Round(d time.Duration) *Time {
|
||||
t.Time = t.Time.Round(d)
|
||||
return t
|
||||
newTime := t.Clone()
|
||||
newTime.Time = newTime.Time.Round(d)
|
||||
return newTime
|
||||
}
|
||||
|
||||
// Truncate returns the result of rounding t down to a multiple of d (since the zero time).
|
||||
@ -268,8 +285,9 @@ func (t *Time) Round(d time.Duration) *Time {
|
||||
// time. Thus, Truncate(Hour) may return a time with a non-zero
|
||||
// minute, depending on the time's Location.
|
||||
func (t *Time) Truncate(d time.Duration) *Time {
|
||||
t.Time = t.Time.Truncate(d)
|
||||
return t
|
||||
newTime := t.Clone()
|
||||
newTime.Time = newTime.Time.Truncate(d)
|
||||
return newTime
|
||||
}
|
||||
|
||||
// Equal reports whether t and u represent the same time instant.
|
||||
|
||||
@ -188,7 +188,7 @@ func Test_ToTime(t *testing.T) {
|
||||
func Test_Add(t *testing.T) {
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
timeTemp := gtime.NewFromStr("2006-01-02 15:04:05")
|
||||
timeTemp.Add(time.Second)
|
||||
timeTemp = timeTemp.Add(time.Second)
|
||||
t.Assert(timeTemp.Format("Y-m-d H:i:s"), "2006-01-02 15:04:06")
|
||||
})
|
||||
}
|
||||
@ -196,15 +196,14 @@ func Test_Add(t *testing.T) {
|
||||
func Test_ToZone(t *testing.T) {
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
timeTemp := gtime.Now()
|
||||
//
|
||||
timeTemp.ToZone("America/Los_Angeles")
|
||||
timeTemp, _ = timeTemp.ToZone("America/Los_Angeles")
|
||||
t.Assert(timeTemp.Time.Location().String(), "America/Los_Angeles")
|
||||
|
||||
loc, err := time.LoadLocation("Asia/Shanghai")
|
||||
if err != nil {
|
||||
t.Error("test fail")
|
||||
}
|
||||
timeTemp.ToLocation(loc)
|
||||
timeTemp = timeTemp.ToLocation(loc)
|
||||
t.Assert(timeTemp.Time.Location().String(), "Asia/Shanghai")
|
||||
|
||||
timeTemp1, _ := timeTemp.ToZone("errZone")
|
||||
@ -217,7 +216,7 @@ func Test_ToZone(t *testing.T) {
|
||||
func Test_AddDate(t *testing.T) {
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
timeTemp := gtime.NewFromStr("2006-01-02 15:04:05")
|
||||
timeTemp.AddDate(1, 2, 3)
|
||||
timeTemp = timeTemp.AddDate(1, 2, 3)
|
||||
t.Assert(timeTemp.Format("Y-m-d H:i:s"), "2007-03-05 15:04:05")
|
||||
})
|
||||
}
|
||||
@ -244,7 +243,7 @@ func Test_Round(t *testing.T) {
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
timeTemp := gtime.Now()
|
||||
timeTemp1 := timeTemp.Time
|
||||
timeTemp.Round(time.Hour)
|
||||
timeTemp = timeTemp.Round(time.Hour)
|
||||
t.Assert(timeTemp.UnixNano(), timeTemp1.Round(time.Hour).UnixNano())
|
||||
})
|
||||
}
|
||||
@ -253,7 +252,7 @@ func Test_Truncate(t *testing.T) {
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
timeTemp := gtime.Now()
|
||||
timeTemp1 := timeTemp.Time
|
||||
timeTemp.Truncate(time.Hour)
|
||||
timeTemp = timeTemp.Truncate(time.Hour)
|
||||
t.Assert(timeTemp.UnixNano(), timeTemp1.Truncate(time.Hour).UnixNano())
|
||||
})
|
||||
}
|
||||
|
||||
@ -24,8 +24,7 @@ import (
|
||||
)
|
||||
|
||||
var (
|
||||
numberSequence = regexp.MustCompile(`([a-zA-Z])(\d+)([a-zA-Z]?)`)
|
||||
numberReplacement = []byte(`$1 $2 $3`)
|
||||
numberSequence = regexp.MustCompile(`([a-zA-Z]{0,1})(\d+)([a-zA-Z]{0,1})`)
|
||||
firstCamelCaseStart = regexp.MustCompile(`([A-Z]+)([A-Z]?[_a-z\d]+)|$`)
|
||||
firstCamelCaseEnd = regexp.MustCompile(`([\w\W]*?)([_]?[A-Z]+)$`)
|
||||
)
|
||||
@ -139,9 +138,21 @@ func DelimitedScreamingCase(s string, del uint8, screaming bool) string {
|
||||
}
|
||||
|
||||
func addWordBoundariesToNumbers(s string) string {
|
||||
b := []byte(s)
|
||||
b = numberSequence.ReplaceAll(b, numberReplacement)
|
||||
return string(b)
|
||||
r := numberSequence.ReplaceAllFunc([]byte(s), func(bytes []byte) []byte {
|
||||
var result []byte
|
||||
match := numberSequence.FindSubmatch(bytes)
|
||||
if len(match[1]) > 0 {
|
||||
result = append(result, match[1]...)
|
||||
result = append(result, []byte(" ")...)
|
||||
}
|
||||
result = append(result, match[2]...)
|
||||
if len(match[3]) > 0 {
|
||||
result = append(result, []byte(" ")...)
|
||||
result = append(result, match[3]...)
|
||||
}
|
||||
return result
|
||||
})
|
||||
return string(r)
|
||||
}
|
||||
|
||||
// Converts a string to CamelCase
|
||||
|
||||
@ -130,6 +130,7 @@ func Test_SnakeScreamingCase(t *testing.T) {
|
||||
func Test_KebabCase(t *testing.T) {
|
||||
cases := [][]string{
|
||||
{"testCase", "test-case"},
|
||||
{"optimization1.0.0", "optimization-1-0-0"},
|
||||
}
|
||||
for _, i := range cases {
|
||||
in := i[0]
|
||||
|
||||
@ -381,8 +381,10 @@ func String(i interface{}) string {
|
||||
return f.Error()
|
||||
}
|
||||
// Reflect checks.
|
||||
rv := reflect.ValueOf(value)
|
||||
kind := rv.Kind()
|
||||
var (
|
||||
rv = reflect.ValueOf(value)
|
||||
kind = rv.Kind()
|
||||
)
|
||||
switch kind {
|
||||
case reflect.Chan,
|
||||
reflect.Map,
|
||||
@ -394,6 +396,8 @@ func String(i interface{}) string {
|
||||
if rv.IsNil() {
|
||||
return ""
|
||||
}
|
||||
case reflect.String:
|
||||
return rv.String()
|
||||
}
|
||||
if kind == reflect.Ptr {
|
||||
return String(rv.Elem().Interface())
|
||||
|
||||
@ -37,7 +37,7 @@ func MapDeep(value interface{}, tags ...string) map[string]interface{} {
|
||||
// doMapConvert implements the map converting.
|
||||
// It automatically checks and converts json string to map if <value> is string/[]byte.
|
||||
//
|
||||
// TODO completely implement the recursive converting for all types.
|
||||
// TODO completely implement the recursive converting for all types, especially the map.
|
||||
func doMapConvert(value interface{}, recursive bool, tags ...string) map[string]interface{} {
|
||||
if value == nil {
|
||||
return nil
|
||||
|
||||
@ -31,6 +31,7 @@ func SliceStructDeep(params interface{}, pointer interface{}, mapping ...map[str
|
||||
}
|
||||
|
||||
// Maps converts <i> to []map[string]interface{}.
|
||||
// Note that it automatically checks and converts json string to []map if <value> is string/[]byte.
|
||||
func Maps(value interface{}, tags ...string) []map[string]interface{} {
|
||||
if value == nil {
|
||||
return nil
|
||||
|
||||
@ -50,7 +50,8 @@ func StructDeep(params interface{}, pointer interface{}, mapping ...map[string]s
|
||||
// doStruct is the core internal converting function for any data to struct recursively or not.
|
||||
func doStruct(params interface{}, pointer interface{}, recursive bool, mapping ...map[string]string) (err error) {
|
||||
if params == nil {
|
||||
return gerror.New("params cannot be nil")
|
||||
// If <params> is nil, no conversion.
|
||||
return nil
|
||||
}
|
||||
if pointer == nil {
|
||||
return gerror.New("object pointer cannot be nil")
|
||||
@ -73,7 +74,7 @@ func doStruct(params interface{}, pointer interface{}, recursive bool, mapping .
|
||||
// DO NOT use MapDeep here.
|
||||
paramsMap := Map(params)
|
||||
if paramsMap == nil {
|
||||
return gerror.Newf("invalid params: %v", params)
|
||||
return gerror.Newf("convert params to map failed: %v", params)
|
||||
}
|
||||
|
||||
// Using reflect to do the converting,
|
||||
|
||||
@ -23,14 +23,15 @@ func StructsDeep(params interface{}, pointer interface{}, mapping ...map[string]
|
||||
|
||||
// doStructs converts any slice to given struct slice.
|
||||
//
|
||||
// The parameter <params> should be type of slice.
|
||||
// It automatically checks and converts json string to []map if <params> is string/[]byte.
|
||||
//
|
||||
// The parameter <pointer> should be type of pointer to slice of struct.
|
||||
// Note that if <pointer> is a pointer to another pointer of type of slice of struct,
|
||||
// it will create the struct/pointer internally.
|
||||
func doStructs(params interface{}, pointer interface{}, deep bool, mapping ...map[string]string) (err error) {
|
||||
if params == nil {
|
||||
return gerror.New("params cannot be nil")
|
||||
// If <params> is nil, no conversion.
|
||||
return nil
|
||||
}
|
||||
if pointer == nil {
|
||||
return gerror.New("object pointer cannot be nil")
|
||||
@ -48,57 +49,45 @@ func doStructs(params interface{}, pointer interface{}, deep bool, mapping ...ma
|
||||
return gerror.Newf("pointer should be type of pointer, but got: %v", kind)
|
||||
}
|
||||
}
|
||||
params = Maps(params)
|
||||
var (
|
||||
reflectValue = reflect.ValueOf(params)
|
||||
reflectKind = reflectValue.Kind()
|
||||
)
|
||||
for reflectKind == reflect.Ptr {
|
||||
reflectValue = reflectValue.Elem()
|
||||
reflectKind = reflectValue.Kind()
|
||||
}
|
||||
switch reflectKind {
|
||||
case reflect.Slice, reflect.Array:
|
||||
// If <params> is an empty slice, no conversion.
|
||||
if reflectValue.Len() == 0 {
|
||||
return nil
|
||||
}
|
||||
var (
|
||||
array = reflect.MakeSlice(pointerRv.Type().Elem(), reflectValue.Len(), reflectValue.Len())
|
||||
itemType = array.Index(0).Type()
|
||||
)
|
||||
for i := 0; i < reflectValue.Len(); i++ {
|
||||
if itemType.Kind() == reflect.Ptr {
|
||||
// Slice element is type pointer.
|
||||
e := reflect.New(itemType.Elem()).Elem()
|
||||
if deep {
|
||||
if err = StructDeep(reflectValue.Index(i).Interface(), e, mapping...); err != nil {
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
if err = Struct(reflectValue.Index(i).Interface(), e, mapping...); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
array.Index(i).Set(e.Addr())
|
||||
} else {
|
||||
// Slice element is not type of pointer.
|
||||
e := reflect.New(itemType).Elem()
|
||||
if deep {
|
||||
if err = StructDeep(reflectValue.Index(i).Interface(), e, mapping...); err != nil {
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
if err = Struct(reflectValue.Index(i).Interface(), e, mapping...); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
array.Index(i).Set(e)
|
||||
}
|
||||
}
|
||||
pointerRv.Elem().Set(array)
|
||||
paramsMaps := Maps(params)
|
||||
// If <params> is an empty slice, no conversion.
|
||||
if len(paramsMaps) == 0 {
|
||||
return nil
|
||||
default:
|
||||
return gerror.Newf("params should be type of slice, but got: %v", reflectKind)
|
||||
}
|
||||
var (
|
||||
array = reflect.MakeSlice(pointerRv.Type().Elem(), len(paramsMaps), len(paramsMaps))
|
||||
itemType = array.Index(0).Type()
|
||||
)
|
||||
for i := 0; i < len(paramsMaps); i++ {
|
||||
if itemType.Kind() == reflect.Ptr {
|
||||
// Slice element is type pointer.
|
||||
e := reflect.New(itemType.Elem()).Elem()
|
||||
if deep {
|
||||
if err = StructDeep(paramsMaps[i], e, mapping...); err != nil {
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
if err = Struct(paramsMaps[i], e, mapping...); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
array.Index(i).Set(e.Addr())
|
||||
} else {
|
||||
// Slice element is not type of pointer.
|
||||
e := reflect.New(itemType).Elem()
|
||||
if deep {
|
||||
if err = StructDeep(paramsMaps[i], e, mapping...); err != nil {
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
if err = Struct(paramsMaps[i], e, mapping...); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
array.Index(i).Set(e)
|
||||
}
|
||||
}
|
||||
pointerRv.Elem().Set(array)
|
||||
return nil
|
||||
|
||||
}
|
||||
|
||||
@ -7,6 +7,7 @@
|
||||
package gvalid
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"github.com/gogf/gf/encoding/gjson"
|
||||
"github.com/gogf/gf/net/gipv4"
|
||||
"github.com/gogf/gf/net/gipv6"
|
||||
@ -124,9 +125,8 @@ func doCheck(key string, value interface{}, rules string, messages interface{},
|
||||
// It converts value to string and then does the validation.
|
||||
var (
|
||||
// Do not trim it as the space is also part of the value.
|
||||
val = gconv.String(value)
|
||||
data = make(map[string]string)
|
||||
errorMsgs = make(map[string]string)
|
||||
data = make(map[string]string)
|
||||
errorMsgArray = make(map[string]string)
|
||||
)
|
||||
if len(params) > 0 {
|
||||
for k, v := range gconv.Map(params[0]) {
|
||||
@ -151,7 +151,8 @@ func doCheck(key string, value interface{}, rules string, messages interface{},
|
||||
ruleItems := strings.Split(strings.TrimSpace(rules), "|")
|
||||
for i := 0; ; {
|
||||
array := strings.Split(ruleItems[i], ":")
|
||||
if _, ok := allSupportedRules[array[0]]; !ok {
|
||||
_, ok := allSupportedRules[array[0]]
|
||||
if !ok && customRuleFuncMap[array[0]] == nil {
|
||||
if i > 0 && ruleItems[i-1][:5] == "regex" {
|
||||
ruleItems[i-1] += "|" + ruleItems[i]
|
||||
ruleItems = append(ruleItems[:i], ruleItems[i+1:]...)
|
||||
@ -170,303 +171,36 @@ func doCheck(key string, value interface{}, rules string, messages interface{},
|
||||
}
|
||||
for index := 0; index < len(ruleItems); {
|
||||
var (
|
||||
item = ruleItems[index]
|
||||
match = false
|
||||
results = ruleRegex.FindStringSubmatch(item)
|
||||
ruleKey = strings.TrimSpace(results[1])
|
||||
ruleVal = strings.TrimSpace(results[2])
|
||||
err error
|
||||
match = false
|
||||
results = ruleRegex.FindStringSubmatch(ruleItems[index])
|
||||
ruleKey = strings.TrimSpace(results[1])
|
||||
rulePattern = strings.TrimSpace(results[2])
|
||||
)
|
||||
if len(msgArray) > index {
|
||||
customMsgMap[ruleKey] = strings.TrimSpace(msgArray[index])
|
||||
}
|
||||
switch ruleKey {
|
||||
// Required rules.
|
||||
case
|
||||
"required",
|
||||
"required-if",
|
||||
"required-unless",
|
||||
"required-with",
|
||||
"required-with-all",
|
||||
"required-without",
|
||||
"required-without-all":
|
||||
match = checkRequired(val, ruleKey, ruleVal, data)
|
||||
|
||||
// Length rules.
|
||||
// It also supports length of unicode string.
|
||||
case
|
||||
"length",
|
||||
"min-length",
|
||||
"max-length":
|
||||
if msg := checkLength(val, ruleKey, ruleVal, customMsgMap); msg != "" {
|
||||
errorMsgs[ruleKey] = msg
|
||||
if f, ok := customRuleFuncMap[ruleKey]; ok {
|
||||
// It checks custom validation rules with most priority.
|
||||
var (
|
||||
dataMap map[string]interface{}
|
||||
message = getErrorMessageByRule(ruleKey, customMsgMap)
|
||||
)
|
||||
if len(params) > 0 {
|
||||
dataMap = gconv.Map(params[0])
|
||||
}
|
||||
if err := f(ruleItems[index], value, message, dataMap); err != nil {
|
||||
match = false
|
||||
errorMsgArray[ruleKey] = err.Error()
|
||||
} else {
|
||||
match = true
|
||||
}
|
||||
|
||||
// Range rules.
|
||||
case
|
||||
"min",
|
||||
"max",
|
||||
"between":
|
||||
if msg := checkRange(val, ruleKey, ruleVal, customMsgMap); msg != "" {
|
||||
errorMsgs[ruleKey] = msg
|
||||
} else {
|
||||
match = true
|
||||
}
|
||||
|
||||
// Custom regular expression.
|
||||
case "regex":
|
||||
// It here should check the rule as there might be special char '|' in it.
|
||||
for i := index + 1; i < len(ruleItems); i++ {
|
||||
if !gregex.IsMatchString(gSINGLE_RULE_PATTERN, ruleItems[i]) {
|
||||
ruleVal += "|" + ruleItems[i]
|
||||
index++
|
||||
}
|
||||
}
|
||||
match = gregex.IsMatchString(ruleVal, val)
|
||||
|
||||
// Date rules.
|
||||
case "date":
|
||||
// Standard date string, which must contain char '-' or '.'.
|
||||
if _, err := gtime.StrToTime(val); err == nil {
|
||||
match = true
|
||||
break
|
||||
}
|
||||
// Date that not contains char '-' or '.'.
|
||||
if _, err := gtime.StrToTime(val, "Ymd"); err == nil {
|
||||
match = true
|
||||
break
|
||||
}
|
||||
|
||||
// Date rule with specified format.
|
||||
case "date-format":
|
||||
if _, err := gtime.StrToTimeFormat(val, ruleVal); err == nil {
|
||||
match = true
|
||||
} else {
|
||||
var msg string
|
||||
msg = getErrorMessageByRule(ruleKey, customMsgMap)
|
||||
msg = strings.Replace(msg, ":format", ruleVal, -1)
|
||||
errorMsgs[ruleKey] = msg
|
||||
}
|
||||
|
||||
// Values of two fields should be equal as string.
|
||||
case "same":
|
||||
if v, ok := data[ruleVal]; ok {
|
||||
if strings.Compare(val, v) == 0 {
|
||||
match = true
|
||||
}
|
||||
}
|
||||
if !match {
|
||||
var msg string
|
||||
msg = getErrorMessageByRule(ruleKey, customMsgMap)
|
||||
msg = strings.Replace(msg, ":field", ruleVal, -1)
|
||||
errorMsgs[ruleKey] = msg
|
||||
}
|
||||
|
||||
// Values of two fields should not be equal as string.
|
||||
case "different":
|
||||
match = true
|
||||
if v, ok := data[ruleVal]; ok {
|
||||
if strings.Compare(val, v) == 0 {
|
||||
match = false
|
||||
}
|
||||
}
|
||||
if !match {
|
||||
var msg string
|
||||
msg = getErrorMessageByRule(ruleKey, customMsgMap)
|
||||
msg = strings.Replace(msg, ":field", ruleVal, -1)
|
||||
errorMsgs[ruleKey] = msg
|
||||
}
|
||||
|
||||
// Field value should be in range of.
|
||||
case "in":
|
||||
array := strings.Split(ruleVal, ",")
|
||||
for _, v := range array {
|
||||
if strings.Compare(val, strings.TrimSpace(v)) == 0 {
|
||||
match = true
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
// Field value should not be in range of.
|
||||
case "not-in":
|
||||
match = true
|
||||
array := strings.Split(ruleVal, ",")
|
||||
for _, v := range array {
|
||||
if strings.Compare(val, strings.TrimSpace(v)) == 0 {
|
||||
match = false
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
// Phone format validation.
|
||||
// 1. China Mobile:
|
||||
// 134, 135, 136, 137, 138, 139, 150, 151, 152, 157, 158, 159, 182, 183, 184, 187, 188,
|
||||
// 178(4G), 147(Net);
|
||||
//
|
||||
// 2. China Unicom:
|
||||
// 130, 131, 132, 155, 156, 185, 186 ,176(4G), 145(Net), 175
|
||||
//
|
||||
// 3. China Telecom:
|
||||
// 133, 153, 180, 181, 189, 177(4G)
|
||||
//
|
||||
// 4. Satelite:
|
||||
// 1349
|
||||
//
|
||||
// 5. Virtual:
|
||||
// 170, 173
|
||||
//
|
||||
// 6. 2018:
|
||||
// 16x, 19x
|
||||
case "phone":
|
||||
match = gregex.IsMatchString(`^13[\d]{9}$|^14[5,7]{1}\d{8}$|^15[^4]{1}\d{8}$|^16[\d]{9}$|^17[0,3,5,6,7,8]{1}\d{8}$|^18[\d]{9}$|^19[\d]{9}$`, val)
|
||||
|
||||
// Telephone number:
|
||||
// "XXXX-XXXXXXX"
|
||||
// "XXXX-XXXXXXXX"
|
||||
// "XXX-XXXXXXX"
|
||||
// "XXX-XXXXXXXX"
|
||||
// "XXXXXXX"
|
||||
// "XXXXXXXX"
|
||||
case "telephone":
|
||||
match = gregex.IsMatchString(`^((\d{3,4})|\d{3,4}-)?\d{7,8}$`, val)
|
||||
|
||||
// QQ number: from 10000.
|
||||
case "qq":
|
||||
match = gregex.IsMatchString(`^[1-9][0-9]{4,}$`, val)
|
||||
|
||||
// Postcode number.
|
||||
case "postcode":
|
||||
match = gregex.IsMatchString(`^\d{6}$`, val)
|
||||
|
||||
// China resident id number.
|
||||
//
|
||||
// xxxxxx yyyy MM dd 375 0 十八位
|
||||
// xxxxxx yy MM dd 75 0 十五位
|
||||
//
|
||||
// 地区: [1-9]\d{5}
|
||||
// 年的前两位:(18|19|([23]\d)) 1800-2399
|
||||
// 年的后两位:\d{2}
|
||||
// 月份: ((0[1-9])|(10|11|12))
|
||||
// 天数: (([0-2][1-9])|10|20|30|31) 闰年不能禁止29+
|
||||
//
|
||||
// 三位顺序码:\d{3}
|
||||
// 两位顺序码:\d{2}
|
||||
// 校验码: [0-9Xx]
|
||||
//
|
||||
// 十八位:^[1-9]\d{5}(18|19|([23]\d))\d{2}((0[1-9])|(10|11|12))(([0-2][1-9])|10|20|30|31)\d{3}[0-9Xx]$
|
||||
// 十五位:^[1-9]\d{5}\d{2}((0[1-9])|(10|11|12))(([0-2][1-9])|10|20|30|31)\d{3}$
|
||||
//
|
||||
// 总:
|
||||
// (^[1-9]\d{5}(18|19|([23]\d))\d{2}((0[1-9])|(10|11|12))(([0-2][1-9])|10|20|30|31)\d{3}[0-9Xx]$)|(^[1-9]\d{5}\d{2}((0[1-9])|(10|11|12))(([0-2][1-9])|10|20|30|31)\d{3}$)
|
||||
case "resident-id":
|
||||
match = checkResidentId(val)
|
||||
|
||||
// Bank card number using LUHN algorithm.
|
||||
case "bank-card":
|
||||
match = checkLuHn(val)
|
||||
|
||||
// Universal passport format rule:
|
||||
// Starting with letter, containing only numbers or underscores, length between 6 and 18.
|
||||
case "passport":
|
||||
match = gregex.IsMatchString(`^[a-zA-Z]{1}\w{5,17}$`, val)
|
||||
|
||||
// Universal password format rule1:
|
||||
// Containing any visible chars, length between 6 and 18.
|
||||
case "password":
|
||||
match = gregex.IsMatchString(`^[\w\S]{6,18}$`, val)
|
||||
|
||||
// Universal password format rule2:
|
||||
// Must meet password rule1, must contain lower and upper letters and numbers.
|
||||
case "password2":
|
||||
if gregex.IsMatchString(`^[\w\S]{6,18}$`, val) &&
|
||||
gregex.IsMatchString(`[a-z]+`, val) &&
|
||||
gregex.IsMatchString(`[A-Z]+`, val) &&
|
||||
gregex.IsMatchString(`\d+`, val) {
|
||||
match = true
|
||||
}
|
||||
|
||||
// Universal password format rule3:
|
||||
// Must meet password rule1, must contain lower and upper letters, numbers and special chars.
|
||||
case "password3":
|
||||
if gregex.IsMatchString(`^[\w\S]{6,18}$`, val) &&
|
||||
gregex.IsMatchString(`[a-z]+`, val) &&
|
||||
gregex.IsMatchString(`[A-Z]+`, val) &&
|
||||
gregex.IsMatchString(`\d+`, val) &&
|
||||
gregex.IsMatchString(`[^a-zA-Z0-9]+`, val) {
|
||||
match = true
|
||||
}
|
||||
|
||||
// Json.
|
||||
case "json":
|
||||
if _, err := gjson.Decode([]byte(val)); err == nil {
|
||||
match = true
|
||||
}
|
||||
|
||||
// Integer.
|
||||
case "integer":
|
||||
if _, err := strconv.Atoi(val); err == nil {
|
||||
match = true
|
||||
}
|
||||
|
||||
// Float.
|
||||
case "float":
|
||||
if _, err := strconv.ParseFloat(val, 10); err == nil {
|
||||
match = true
|
||||
}
|
||||
|
||||
// Boolean(1,true,on,yes:true | 0,false,off,no,"":false).
|
||||
case "boolean":
|
||||
match = false
|
||||
if _, ok := boolMap[strings.ToLower(val)]; ok {
|
||||
match = true
|
||||
}
|
||||
|
||||
// Email.
|
||||
case "email":
|
||||
match = gregex.IsMatchString(`^[a-zA-Z0-9_\-\.]+@[a-zA-Z0-9_\-]+(\.[a-zA-Z0-9_\-]+)+$`, val)
|
||||
|
||||
// URL
|
||||
case "url":
|
||||
match = gregex.IsMatchString(`(https?|ftp|file)://[-A-Za-z0-9+&@#/%?=~_|!:,.;]+[-A-Za-z0-9+&@#/%=~_|]`, val)
|
||||
|
||||
// Domain
|
||||
case "domain":
|
||||
match = gregex.IsMatchString(`^([0-9a-zA-Z][0-9a-zA-Z\-]{0,62}\.)+([a-zA-Z]{0,62})$`, val)
|
||||
|
||||
// IP(IPv4/IPv6).
|
||||
case "ip":
|
||||
match = gipv4.Validate(val) || gipv6.Validate(val)
|
||||
|
||||
// IPv4.
|
||||
case "ipv4":
|
||||
match = gipv4.Validate(val)
|
||||
|
||||
// IPv6.
|
||||
case "ipv6":
|
||||
match = gipv6.Validate(val)
|
||||
|
||||
// MAC.
|
||||
case "mac":
|
||||
match = gregex.IsMatchString(`^([0-9A-Fa-f]{2}[\-:]){5}[0-9A-Fa-f]{2}$`, val)
|
||||
|
||||
default:
|
||||
if f, ok := customRuleFuncMap[ruleKey]; ok {
|
||||
var (
|
||||
dataMap map[string]interface{}
|
||||
message = getErrorMessageByRule(ruleKey, customMsgMap)
|
||||
)
|
||||
if len(params) > 0 {
|
||||
dataMap = gconv.Map(params[0])
|
||||
}
|
||||
if err := f(value, message, dataMap); err != nil {
|
||||
match = false
|
||||
errorMsgs[ruleKey] = err.Error()
|
||||
} else {
|
||||
match = true
|
||||
}
|
||||
} else {
|
||||
errorMsgs[ruleKey] = "Invalid rule name: " + ruleKey
|
||||
} else {
|
||||
// It checks build-in validation rules if there's no custom rule.
|
||||
match, err = doCheckBuildInRules(index, value, ruleKey, rulePattern, ruleItems, data, customMsgMap)
|
||||
if !match && err != nil {
|
||||
errorMsgArray[ruleKey] = err.Error()
|
||||
}
|
||||
}
|
||||
|
||||
@ -474,16 +208,303 @@ func doCheck(key string, value interface{}, rules string, messages interface{},
|
||||
if !match {
|
||||
// It does nothing if the error message for this rule
|
||||
// is already set in previous validation.
|
||||
if _, ok := errorMsgs[ruleKey]; !ok {
|
||||
errorMsgs[ruleKey] = getErrorMessageByRule(ruleKey, customMsgMap)
|
||||
if _, ok := errorMsgArray[ruleKey]; !ok {
|
||||
errorMsgArray[ruleKey] = getErrorMessageByRule(ruleKey, customMsgMap)
|
||||
}
|
||||
}
|
||||
index++
|
||||
}
|
||||
if len(errorMsgs) > 0 {
|
||||
if len(errorMsgArray) > 0 {
|
||||
return newError([]string{rules}, ErrorMap{
|
||||
key: errorMsgs,
|
||||
key: errorMsgArray,
|
||||
})
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func doCheckBuildInRules(
|
||||
index int,
|
||||
value interface{},
|
||||
ruleKey string,
|
||||
rulePattern string,
|
||||
ruleItems []string,
|
||||
dataMap map[string]string,
|
||||
customMsgMap map[string]string,
|
||||
) (match bool, err error) {
|
||||
valueStr := gconv.String(value)
|
||||
switch ruleKey {
|
||||
// Required rules.
|
||||
case
|
||||
"required",
|
||||
"required-if",
|
||||
"required-unless",
|
||||
"required-with",
|
||||
"required-with-all",
|
||||
"required-without",
|
||||
"required-without-all":
|
||||
match = checkRequired(valueStr, ruleKey, rulePattern, dataMap)
|
||||
|
||||
// Length rules.
|
||||
// It also supports length of unicode string.
|
||||
case
|
||||
"length",
|
||||
"min-length",
|
||||
"max-length":
|
||||
if msg := checkLength(valueStr, ruleKey, rulePattern, customMsgMap); msg != "" {
|
||||
return match, errors.New(msg)
|
||||
} else {
|
||||
match = true
|
||||
}
|
||||
|
||||
// Range rules.
|
||||
case
|
||||
"min",
|
||||
"max",
|
||||
"between":
|
||||
if msg := checkRange(valueStr, ruleKey, rulePattern, customMsgMap); msg != "" {
|
||||
return match, errors.New(msg)
|
||||
} else {
|
||||
match = true
|
||||
}
|
||||
|
||||
// Custom regular expression.
|
||||
case "regex":
|
||||
// It here should check the rule as there might be special char '|' in it.
|
||||
for i := index + 1; i < len(ruleItems); i++ {
|
||||
if !gregex.IsMatchString(gSINGLE_RULE_PATTERN, ruleItems[i]) {
|
||||
rulePattern += "|" + ruleItems[i]
|
||||
index++
|
||||
}
|
||||
}
|
||||
match = gregex.IsMatchString(rulePattern, valueStr)
|
||||
|
||||
// Date rules.
|
||||
case "date":
|
||||
// Standard date string, which must contain char '-' or '.'.
|
||||
if _, err := gtime.StrToTime(valueStr); err == nil {
|
||||
match = true
|
||||
break
|
||||
}
|
||||
// Date that not contains char '-' or '.'.
|
||||
if _, err := gtime.StrToTime(valueStr, "Ymd"); err == nil {
|
||||
match = true
|
||||
break
|
||||
}
|
||||
|
||||
// Date rule with specified format.
|
||||
case "date-format":
|
||||
if _, err := gtime.StrToTimeFormat(valueStr, rulePattern); err == nil {
|
||||
match = true
|
||||
} else {
|
||||
var msg string
|
||||
msg = getErrorMessageByRule(ruleKey, customMsgMap)
|
||||
msg = strings.Replace(msg, ":format", rulePattern, -1)
|
||||
return match, errors.New(msg)
|
||||
}
|
||||
|
||||
// Values of two fields should be equal as string.
|
||||
case "same":
|
||||
if v, ok := dataMap[rulePattern]; ok {
|
||||
if strings.Compare(valueStr, v) == 0 {
|
||||
match = true
|
||||
}
|
||||
}
|
||||
if !match {
|
||||
var msg string
|
||||
msg = getErrorMessageByRule(ruleKey, customMsgMap)
|
||||
msg = strings.Replace(msg, ":field", rulePattern, -1)
|
||||
return match, errors.New(msg)
|
||||
}
|
||||
|
||||
// Values of two fields should not be equal as string.
|
||||
case "different":
|
||||
match = true
|
||||
if v, ok := dataMap[rulePattern]; ok {
|
||||
if strings.Compare(valueStr, v) == 0 {
|
||||
match = false
|
||||
}
|
||||
}
|
||||
if !match {
|
||||
var msg string
|
||||
msg = getErrorMessageByRule(ruleKey, customMsgMap)
|
||||
msg = strings.Replace(msg, ":field", rulePattern, -1)
|
||||
return match, errors.New(msg)
|
||||
}
|
||||
|
||||
// Field value should be in range of.
|
||||
case "in":
|
||||
array := strings.Split(rulePattern, ",")
|
||||
for _, v := range array {
|
||||
if strings.Compare(valueStr, strings.TrimSpace(v)) == 0 {
|
||||
match = true
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
// Field value should not be in range of.
|
||||
case "not-in":
|
||||
match = true
|
||||
array := strings.Split(rulePattern, ",")
|
||||
for _, v := range array {
|
||||
if strings.Compare(valueStr, strings.TrimSpace(v)) == 0 {
|
||||
match = false
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
// Phone format validation.
|
||||
// 1. China Mobile:
|
||||
// 134, 135, 136, 137, 138, 139, 150, 151, 152, 157, 158, 159, 182, 183, 184, 187, 188,
|
||||
// 178(4G), 147(Net);
|
||||
//
|
||||
// 2. China Unicom:
|
||||
// 130, 131, 132, 155, 156, 185, 186 ,176(4G), 145(Net), 175
|
||||
//
|
||||
// 3. China Telecom:
|
||||
// 133, 153, 180, 181, 189, 177(4G)
|
||||
//
|
||||
// 4. Satelite:
|
||||
// 1349
|
||||
//
|
||||
// 5. Virtual:
|
||||
// 170, 173
|
||||
//
|
||||
// 6. 2018:
|
||||
// 16x, 19x
|
||||
case "phone":
|
||||
match = gregex.IsMatchString(`^13[\d]{9}$|^14[5,7]{1}\d{8}$|^15[^4]{1}\d{8}$|^16[\d]{9}$|^17[0,3,5,6,7,8]{1}\d{8}$|^18[\d]{9}$|^19[\d]{9}$`, valueStr)
|
||||
|
||||
// Telephone number:
|
||||
// "XXXX-XXXXXXX"
|
||||
// "XXXX-XXXXXXXX"
|
||||
// "XXX-XXXXXXX"
|
||||
// "XXX-XXXXXXXX"
|
||||
// "XXXXXXX"
|
||||
// "XXXXXXXX"
|
||||
case "telephone":
|
||||
match = gregex.IsMatchString(`^((\d{3,4})|\d{3,4}-)?\d{7,8}$`, valueStr)
|
||||
|
||||
// QQ number: from 10000.
|
||||
case "qq":
|
||||
match = gregex.IsMatchString(`^[1-9][0-9]{4,}$`, valueStr)
|
||||
|
||||
// Postcode number.
|
||||
case "postcode":
|
||||
match = gregex.IsMatchString(`^\d{6}$`, valueStr)
|
||||
|
||||
// China resident id number.
|
||||
//
|
||||
// xxxxxx yyyy MM dd 375 0 十八位
|
||||
// xxxxxx yy MM dd 75 0 十五位
|
||||
//
|
||||
// 地区: [1-9]\d{5}
|
||||
// 年的前两位:(18|19|([23]\d)) 1800-2399
|
||||
// 年的后两位:\d{2}
|
||||
// 月份: ((0[1-9])|(10|11|12))
|
||||
// 天数: (([0-2][1-9])|10|20|30|31) 闰年不能禁止29+
|
||||
//
|
||||
// 三位顺序码:\d{3}
|
||||
// 两位顺序码:\d{2}
|
||||
// 校验码: [0-9Xx]
|
||||
//
|
||||
// 十八位:^[1-9]\d{5}(18|19|([23]\d))\d{2}((0[1-9])|(10|11|12))(([0-2][1-9])|10|20|30|31)\d{3}[0-9Xx]$
|
||||
// 十五位:^[1-9]\d{5}\d{2}((0[1-9])|(10|11|12))(([0-2][1-9])|10|20|30|31)\d{3}$
|
||||
//
|
||||
// 总:
|
||||
// (^[1-9]\d{5}(18|19|([23]\d))\d{2}((0[1-9])|(10|11|12))(([0-2][1-9])|10|20|30|31)\d{3}[0-9Xx]$)|(^[1-9]\d{5}\d{2}((0[1-9])|(10|11|12))(([0-2][1-9])|10|20|30|31)\d{3}$)
|
||||
case "resident-id":
|
||||
match = checkResidentId(valueStr)
|
||||
|
||||
// Bank card number using LUHN algorithm.
|
||||
case "bank-card":
|
||||
match = checkLuHn(valueStr)
|
||||
|
||||
// Universal passport format rule:
|
||||
// Starting with letter, containing only numbers or underscores, length between 6 and 18.
|
||||
case "passport":
|
||||
match = gregex.IsMatchString(`^[a-zA-Z]{1}\w{5,17}$`, valueStr)
|
||||
|
||||
// Universal password format rule1:
|
||||
// Containing any visible chars, length between 6 and 18.
|
||||
case "password":
|
||||
match = gregex.IsMatchString(`^[\w\S]{6,18}$`, valueStr)
|
||||
|
||||
// Universal password format rule2:
|
||||
// Must meet password rule1, must contain lower and upper letters and numbers.
|
||||
case "password2":
|
||||
if gregex.IsMatchString(`^[\w\S]{6,18}$`, valueStr) &&
|
||||
gregex.IsMatchString(`[a-z]+`, valueStr) &&
|
||||
gregex.IsMatchString(`[A-Z]+`, valueStr) &&
|
||||
gregex.IsMatchString(`\d+`, valueStr) {
|
||||
match = true
|
||||
}
|
||||
|
||||
// Universal password format rule3:
|
||||
// Must meet password rule1, must contain lower and upper letters, numbers and special chars.
|
||||
case "password3":
|
||||
if gregex.IsMatchString(`^[\w\S]{6,18}$`, valueStr) &&
|
||||
gregex.IsMatchString(`[a-z]+`, valueStr) &&
|
||||
gregex.IsMatchString(`[A-Z]+`, valueStr) &&
|
||||
gregex.IsMatchString(`\d+`, valueStr) &&
|
||||
gregex.IsMatchString(`[^a-zA-Z0-9]+`, valueStr) {
|
||||
match = true
|
||||
}
|
||||
|
||||
// Json.
|
||||
case "json":
|
||||
if _, err := gjson.Decode([]byte(valueStr)); err == nil {
|
||||
match = true
|
||||
}
|
||||
|
||||
// Integer.
|
||||
case "integer":
|
||||
if _, err := strconv.Atoi(valueStr); err == nil {
|
||||
match = true
|
||||
}
|
||||
|
||||
// Float.
|
||||
case "float":
|
||||
if _, err := strconv.ParseFloat(valueStr, 10); err == nil {
|
||||
match = true
|
||||
}
|
||||
|
||||
// Boolean(1,true,on,yes:true | 0,false,off,no,"":false).
|
||||
case "boolean":
|
||||
match = false
|
||||
if _, ok := boolMap[strings.ToLower(valueStr)]; ok {
|
||||
match = true
|
||||
}
|
||||
|
||||
// Email.
|
||||
case "email":
|
||||
match = gregex.IsMatchString(`^[a-zA-Z0-9_\-\.]+@[a-zA-Z0-9_\-]+(\.[a-zA-Z0-9_\-]+)+$`, valueStr)
|
||||
|
||||
// URL
|
||||
case "url":
|
||||
match = gregex.IsMatchString(`(https?|ftp|file)://[-A-Za-z0-9+&@#/%?=~_|!:,.;]+[-A-Za-z0-9+&@#/%=~_|]`, valueStr)
|
||||
|
||||
// Domain
|
||||
case "domain":
|
||||
match = gregex.IsMatchString(`^([0-9a-zA-Z][0-9a-zA-Z\-]{0,62}\.)+([a-zA-Z]{0,62})$`, valueStr)
|
||||
|
||||
// IP(IPv4/IPv6).
|
||||
case "ip":
|
||||
match = gipv4.Validate(valueStr) || gipv6.Validate(valueStr)
|
||||
|
||||
// IPv4.
|
||||
case "ipv4":
|
||||
match = gipv4.Validate(valueStr)
|
||||
|
||||
// IPv6.
|
||||
case "ipv6":
|
||||
match = gipv6.Validate(valueStr)
|
||||
|
||||
// MAC.
|
||||
case "mac":
|
||||
match = gregex.IsMatchString(`^([0-9A-Fa-f]{2}[\-:]){5}[0-9A-Fa-f]{2}$`, valueStr)
|
||||
|
||||
default:
|
||||
return match, errors.New("Invalid rule name: " + ruleKey)
|
||||
}
|
||||
return match, nil
|
||||
}
|
||||
|
||||
@ -6,15 +6,13 @@
|
||||
|
||||
package gvalid
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
)
|
||||
|
||||
// RuleFunc is the custom function for data validation.
|
||||
// The parameter <rule> specifies the validation rule string, like "required", "between:1,100", etc.
|
||||
// The parameter <value> specifies the value for this rule to validate.
|
||||
// The parameter <message> specifies the custom error message or configured i18n message for this rule.
|
||||
// The parameter <params> specifies all the parameters that needs .
|
||||
type RuleFunc func(value interface{}, message string, params map[string]interface{}) error
|
||||
// The parameter <params> specifies all the parameters that needs. You can ignore parameter <params> if
|
||||
// you do not really need it in your custom validation rule.
|
||||
type RuleFunc func(rule string, value interface{}, message string, params map[string]interface{}) error
|
||||
|
||||
var (
|
||||
// customRuleFuncMap stores the custom rule functions.
|
||||
@ -24,10 +22,11 @@ var (
|
||||
// RegisterRule registers custom validation rule and function for package.
|
||||
// It returns error if there's already the same rule registered previously.
|
||||
func RegisterRule(rule string, f RuleFunc) error {
|
||||
if _, ok := allSupportedRules[rule]; ok {
|
||||
return fmt.Errorf(`validation rule "%s" is already registered`, rule)
|
||||
}
|
||||
allSupportedRules[rule] = struct{}{}
|
||||
customRuleFuncMap[rule] = f
|
||||
return nil
|
||||
}
|
||||
|
||||
// DeleteRule deletes custom defined validation rule and its function from global package.
|
||||
func DeleteRule(rule string) {
|
||||
delete(customRuleFuncMap, rule)
|
||||
}
|
||||
|
||||
@ -49,17 +49,26 @@ func newErrorStr(key, err string) *Error {
|
||||
|
||||
// Map returns the first error message as map.
|
||||
func (e *Error) Map() map[string]string {
|
||||
if e == nil {
|
||||
return map[string]string{}
|
||||
}
|
||||
_, m := e.FirstItem()
|
||||
return m
|
||||
}
|
||||
|
||||
// Maps returns all error messages as map.
|
||||
func (e *Error) Maps() ErrorMap {
|
||||
if e == nil {
|
||||
return nil
|
||||
}
|
||||
return e.errors
|
||||
}
|
||||
|
||||
// FirstItem returns the field name and error messages for the first validation rule error.
|
||||
func (e *Error) FirstItem() (key string, messages map[string]string) {
|
||||
if e == nil {
|
||||
return "", map[string]string{}
|
||||
}
|
||||
if e.firstItem != nil {
|
||||
return e.firstKey, e.firstItem
|
||||
}
|
||||
@ -85,6 +94,9 @@ func (e *Error) FirstItem() (key string, messages map[string]string) {
|
||||
|
||||
// FirstRule returns the first error rule and message string.
|
||||
func (e *Error) FirstRule() (rule string, err string) {
|
||||
if e == nil {
|
||||
return "", ""
|
||||
}
|
||||
// By sequence.
|
||||
if len(e.rules) > 0 {
|
||||
for _, v := range e.rules {
|
||||
@ -112,22 +124,34 @@ func (e *Error) FirstRule() (rule string, err string) {
|
||||
// FirstString returns the first error message as string.
|
||||
// Note that the returned message might be different if it has no sequence.
|
||||
func (e *Error) FirstString() (err string) {
|
||||
if e == nil {
|
||||
return ""
|
||||
}
|
||||
_, err = e.FirstRule()
|
||||
return
|
||||
}
|
||||
|
||||
// String returns all error messages as string, multiple error messages joined using char ';'.
|
||||
func (e *Error) String() string {
|
||||
if e == nil {
|
||||
return ""
|
||||
}
|
||||
return strings.Join(e.Strings(), "; ")
|
||||
}
|
||||
|
||||
// Error implements interface of error.Error.
|
||||
func (e *Error) Error() string {
|
||||
if e == nil {
|
||||
return ""
|
||||
}
|
||||
return e.String()
|
||||
}
|
||||
|
||||
// Strings returns all error messages as string array.
|
||||
func (e *Error) Strings() (errs []string) {
|
||||
if e == nil {
|
||||
return []string{}
|
||||
}
|
||||
errs = make([]string, 0)
|
||||
// By sequence.
|
||||
if len(e.rules) > 0 {
|
||||
|
||||
@ -57,6 +57,7 @@ var defaultMessages = map[string]string{
|
||||
"in": "The :attribute value is not in acceptable range",
|
||||
"not-in": "The :attribute value is not in acceptable range",
|
||||
"regex": "The :attribute value is invalid",
|
||||
"__default__": "The :attribute value is invalid",
|
||||
}
|
||||
|
||||
// getErrorMessageByRule retrieves and returns the error message for specified rule.
|
||||
@ -71,5 +72,12 @@ func getErrorMessageByRule(ruleKey string, customMsgMap map[string]string) strin
|
||||
if content == "" {
|
||||
content = defaultMessages[ruleKey]
|
||||
}
|
||||
// If there's no configured rule message, it uses default one.
|
||||
if content == "" {
|
||||
content = gi18n.GetContent(`gf.gvalid.rule.__default__`)
|
||||
if content == "" {
|
||||
content = defaultMessages["__default__"]
|
||||
}
|
||||
}
|
||||
return content
|
||||
}
|
||||
|
||||
@ -16,9 +16,9 @@ import (
|
||||
"github.com/gogf/gf/util/gvalid"
|
||||
)
|
||||
|
||||
func Test_CustomRule(t *testing.T) {
|
||||
func Test_CustomRule1(t *testing.T) {
|
||||
rule := "custom"
|
||||
err := gvalid.RegisterRule(rule, func(value interface{}, message string, params map[string]interface{}) error {
|
||||
err := gvalid.RegisterRule(rule, func(rule string, value interface{}, message string, params map[string]interface{}) error {
|
||||
pass := gconv.String(value)
|
||||
if len(pass) != 6 {
|
||||
return errors.New(message)
|
||||
@ -62,3 +62,47 @@ func Test_CustomRule(t *testing.T) {
|
||||
t.Assert(err, nil)
|
||||
})
|
||||
}
|
||||
|
||||
func Test_CustomRule2(t *testing.T) {
|
||||
rule := "required-map"
|
||||
err := gvalid.RegisterRule(rule, func(rule string, value interface{}, message string, params map[string]interface{}) error {
|
||||
m := gconv.Map(value)
|
||||
if len(m) == 0 {
|
||||
return errors.New(message)
|
||||
}
|
||||
return nil
|
||||
})
|
||||
gtest.Assert(err, nil)
|
||||
// Check.
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
errStr := "data map should not be empty"
|
||||
t.Assert(gvalid.Check(g.Map{}, rule, errStr).String(), errStr)
|
||||
t.Assert(gvalid.Check(g.Map{"k": "v"}, rule, errStr).String(), nil)
|
||||
})
|
||||
// Error with struct validation.
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
type T struct {
|
||||
Value map[string]string `v:"uid@required-map#自定义错误"`
|
||||
Data string `p:"data"`
|
||||
}
|
||||
st := &T{
|
||||
Value: map[string]string{},
|
||||
Data: "123456",
|
||||
}
|
||||
err := gvalid.CheckStruct(st, nil)
|
||||
t.Assert(err.String(), "自定义错误")
|
||||
})
|
||||
// No error with struct validation.
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
type T struct {
|
||||
Value map[string]string `v:"uid@required-map#自定义错误"`
|
||||
Data string `p:"data"`
|
||||
}
|
||||
st := &T{
|
||||
Value: map[string]string{"k": "v"},
|
||||
Data: "123456",
|
||||
}
|
||||
err := gvalid.CheckStruct(st, nil)
|
||||
t.Assert(err, nil)
|
||||
})
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user