diff --git a/.example/database/gdb/driver/driver/driver.go b/.example/database/gdb/driver/driver/driver.go index 97948b80c..71af8399d 100644 --- a/.example/database/gdb/driver/driver/driver.go +++ b/.example/database/gdb/driver/driver/driver.go @@ -1,12 +1,7 @@ -// Copyright 2017 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 driver import ( + "context" "database/sql" "github.com/gogf/gf/database/gdb" "github.com/gogf/gf/os/gtime" @@ -47,9 +42,9 @@ func (d *MyDriver) New(core *gdb.Core, node *gdb.ConfigNode) (gdb.DB, error) { // DoQuery commits the sql string and its arguments to underlying driver // through given link object and returns the execution result. -func (d *MyDriver) DoQuery(link gdb.Link, sql string, args ...interface{}) (rows *sql.Rows, err error) { +func (d *MyDriver) DoQuery(ctx context.Context, link gdb.Link, sql string, args ...interface{}) (rows *sql.Rows, err error) { tsMilli := gtime.TimestampMilli() - rows, err = d.DriverMysql.DoQuery(link, sql, args...) + rows, err = d.DriverMysql.DoQuery(ctx, link, sql, args...) link.Exec( "INSERT INTO `monitor`(`sql`,`cost`,`time`,`error`) VALUES(?,?,?,?)", gdb.FormatSqlWithArgs(sql, args), @@ -62,9 +57,9 @@ func (d *MyDriver) DoQuery(link gdb.Link, sql string, args ...interface{}) (rows // DoExec commits the query string and its arguments to underlying driver // through given link object and returns the execution result. -func (d *MyDriver) DoExec(link gdb.Link, sql string, args ...interface{}) (result sql.Result, err error) { +func (d *MyDriver) DoExec(ctx context.Context, link gdb.Link, sql string, args ...interface{}) (result sql.Result, err error) { tsMilli := gtime.TimestampMilli() - result, err = d.DriverMysql.DoExec(link, sql, args...) + result, err = d.DriverMysql.DoExec(ctx, link, sql, args...) link.Exec( "INSERT INTO `monitor`(`sql`,`cost`,`time`,`error`) VALUES(?,?,?,?)", gdb.FormatSqlWithArgs(sql, args), diff --git a/.example/database/gdb/mysql/config.toml b/.example/database/gdb/mysql/config.toml index 48a479b62..beea9dcb5 100644 --- a/.example/database/gdb/mysql/config.toml +++ b/.example/database/gdb/mysql/config.toml @@ -1,9 +1,13 @@ # MySQL. [database] - debug = true - link = "mysql:root:12345678@tcp(127.0.0.1:3306)/test?parseTime=true" - MaxOpen = 100 + [database.logger] + Level = "all" + Stdout = true + CtxKeys = ["Trace-Id"] + [database.default] + link = "mysql:root:12345678@tcp(127.0.0.1:3306)/test" + debug = true # Redis. [redis] diff --git a/.example/database/gdb/mysql/gdb_batch_insert.go b/.example/database/gdb/mysql/gdb_batch_insert.go new file mode 100644 index 000000000..171bb9fb4 --- /dev/null +++ b/.example/database/gdb/mysql/gdb_batch_insert.go @@ -0,0 +1,24 @@ +package main + +import ( + "fmt" + "github.com/gogf/gf/frame/g" +) + +func main() { + db := g.DB() + db.SetDebug(true) + list := make(g.List, 0) + for i := 0; i < 100; i++ { + list = append(list, g.Map{ + "name": fmt.Sprintf(`name_%d`, i), + }) + } + r, e := db.Table("user").Data(list).Batch(2).Insert() + if e != nil { + panic(e) + } + if r != nil { + fmt.Println(r.LastInsertId()) + } +} diff --git a/.example/database/gdb/mysql/gdb_ctx.go b/.example/database/gdb/mysql/gdb_ctx.go new file mode 100644 index 000000000..c296b59ba --- /dev/null +++ b/.example/database/gdb/mysql/gdb_ctx.go @@ -0,0 +1,14 @@ +package main + +import ( + "context" + "github.com/gogf/gf/frame/g" +) + +func main() { + ctx := context.WithValue(context.Background(), "Trace-Id", "123456789") + _, err := g.DB().Ctx(ctx).Query("SELECT 1") + if err != nil { + panic(err) + } +} diff --git a/.example/database/gdb/mysql/gdb_ctx_model.go b/.example/database/gdb/mysql/gdb_ctx_model.go new file mode 100644 index 000000000..ba7b83c02 --- /dev/null +++ b/.example/database/gdb/mysql/gdb_ctx_model.go @@ -0,0 +1,14 @@ +package main + +import ( + "context" + "github.com/gogf/gf/frame/g" +) + +func main() { + ctx := context.WithValue(context.Background(), "Trace-Id", "123456789") + _, err := g.DB().Model("user").Ctx(ctx).All() + if err != nil { + panic(err) + } +} diff --git a/.example/database/gdb/mysql/gdb_distinct.go b/.example/database/gdb/mysql/gdb_distinct.go new file mode 100644 index 000000000..189422f6b --- /dev/null +++ b/.example/database/gdb/mysql/gdb_distinct.go @@ -0,0 +1,9 @@ +package main + +import ( + "github.com/gogf/gf/frame/g" +) + +func main() { + g.DB().Model("user").Distinct().CountColumn("uid,name") +} diff --git a/.example/database/gdb/mysql/gdb_insert.go b/.example/database/gdb/mysql/gdb_insert.go index ee345b370..a6afa8491 100644 --- a/.example/database/gdb/mysql/gdb_insert.go +++ b/.example/database/gdb/mysql/gdb_insert.go @@ -10,9 +10,9 @@ func main() { //db := g.DB() gdb.AddDefaultConfigNode(gdb.ConfigNode{ - LinkInfo: "root:12345678@tcp(127.0.0.1:3306)/test?parseTime=true&loc=Local", - Type: "mysql", - Charset: "utf8", + Link: "root:12345678@tcp(127.0.0.1:3306)/test?parseTime=true&loc=Local", + Type: "mysql", + Charset: "utf8", }) db, _ := gdb.New() diff --git a/.example/database/gdb/mysql/gdb_transaction.go b/.example/database/gdb/mysql/gdb_transaction.go new file mode 100644 index 000000000..fe5642ca2 --- /dev/null +++ b/.example/database/gdb/mysql/gdb_transaction.go @@ -0,0 +1,27 @@ +package main + +import ( + "github.com/gogf/gf/frame/g" +) + +func main() { + var ( + db = g.DB() + table = "user" + ) + tx, err := db.Begin() + if err != nil { + panic(err) + } + if err = tx.Begin(); err != nil { + panic(err) + } + _, err = tx.Model(table).Data(g.Map{"id": 1, "name": "john"}).Insert() + if err = tx.Rollback(); err != nil { + panic(err) + } + _, err = tx.Model(table).Data(g.Map{"id": 2, "name": "smith"}).Insert() + if err = tx.Commit(); err != nil { + panic(err) + } +} diff --git a/.example/database/gdb/mysql/gdb_transaction_closure.go b/.example/database/gdb/mysql/gdb_transaction_closure.go new file mode 100644 index 000000000..aa05d48d4 --- /dev/null +++ b/.example/database/gdb/mysql/gdb_transaction_closure.go @@ -0,0 +1,34 @@ +package main + +import ( + "github.com/gogf/gf/database/gdb" + "github.com/gogf/gf/frame/g" +) + +func main() { + var ( + err error + db = g.DB() + table = "user" + ) + if err = db.Transaction(func(tx *gdb.TX) error { + // Nested transaction 1. + if err = tx.Transaction(func(tx *gdb.TX) error { + _, err = tx.Model(table).Data(g.Map{"id": 1, "name": "john"}).Insert() + return err + }); err != nil { + return err + } + // Nested transaction 2, panic. + if err = tx.Transaction(func(tx *gdb.TX) error { + _, err = tx.Model(table).Data(g.Map{"id": 2, "name": "smith"}).Insert() + // Create a panic that can make this transaction rollback automatically. + panic("error") + }); err != nil { + return err + } + return nil + }); err != nil { + panic(err) + } +} diff --git a/.example/database/gdb/mysql/gdb_transaction_savepoint.go b/.example/database/gdb/mysql/gdb_transaction_savepoint.go new file mode 100644 index 000000000..e677f15ab --- /dev/null +++ b/.example/database/gdb/mysql/gdb_transaction_savepoint.go @@ -0,0 +1,40 @@ +package main + +import ( + "github.com/gogf/gf/frame/g" +) + +func main() { + var ( + err error + db = g.DB() + table = "user" + ) + tx, err := db.Begin() + if err != nil { + panic(err) + } + defer func() { + if err := recover(); err != nil { + _ = tx.Rollback() + } + }() + if _, err = tx.Model(table).Data(g.Map{"id": 1, "name": "john"}).Insert(); err != nil { + panic(err) + } + if err = tx.SavePoint("MyPoint"); err != nil { + panic(err) + } + if _, err = tx.Model(table).Data(g.Map{"id": 2, "name": "smith"}).Insert(); err != nil { + panic(err) + } + if _, err = tx.Model(table).Data(g.Map{"id": 3, "name": "green"}).Insert(); err != nil { + panic(err) + } + if err = tx.RollbackTo("MyPoint"); err != nil { + panic(err) + } + if err = tx.Commit(); err != nil { + panic(err) + } +} diff --git a/.example/database/gdb/mysql/gdb_with_insert.go b/.example/database/gdb/mysql/gdb_with_insert.go new file mode 100644 index 000000000..669ab3ce6 --- /dev/null +++ b/.example/database/gdb/mysql/gdb_with_insert.go @@ -0,0 +1,66 @@ +package main + +import ( + "fmt" + "github.com/gogf/gf/database/gdb" + "github.com/gogf/gf/frame/g" + "github.com/gogf/gf/util/gmeta" +) + +func main() { + type UserDetail struct { + gmeta.Meta `orm:"table:user_detail"` + Uid int `json:"uid"` + Address string `json:"address"` + } + + type UserScore struct { + gmeta.Meta `orm:"table:user_score"` + Id int `json:"id"` + Uid int `json:"uid"` + Score int `json:"score"` + } + + type User struct { + gmeta.Meta `orm:"table:user"` + Id int `json:"id"` + Name string `json:"name"` + UserDetail *UserDetail `orm:"with:uid=id"` + UserScores []*UserScore `orm:"with:uid=id"` + } + + db := g.DB() + db.Transaction(func(tx *gdb.TX) error { + for i := 1; i <= 5; i++ { + // User. + user := User{ + Name: fmt.Sprintf(`name_%d`, i), + } + lastInsertId, err := db.Model(user).Data(user).OmitEmpty().InsertAndGetId() + if err != nil { + return err + } + // Detail. + userDetail := UserDetail{ + Uid: int(lastInsertId), + Address: fmt.Sprintf(`address_%d`, lastInsertId), + } + _, err = db.Model(userDetail).Data(userDetail).OmitEmpty().Insert() + if err != nil { + return err + } + // Scores. + for j := 1; j <= 5; j++ { + userScore := UserScore{ + Uid: int(lastInsertId), + Score: j, + } + _, err = db.Model(userScore).Data(userScore).OmitEmpty().Insert() + if err != nil { + return err + } + } + } + return nil + }) +} diff --git a/.example/database/gdb/mysql/gdb_with_slect.go b/.example/database/gdb/mysql/gdb_with_slect.go new file mode 100644 index 000000000..e15cf40f9 --- /dev/null +++ b/.example/database/gdb/mysql/gdb_with_slect.go @@ -0,0 +1,37 @@ +package main + +import ( + "github.com/gogf/gf/frame/g" + "github.com/gogf/gf/util/gmeta" +) + +func main() { + type UserDetail struct { + gmeta.Meta `orm:"table:user_detail"` + Uid int `json:"uid"` + Address string `json:"address"` + } + + type UserScore struct { + gmeta.Meta `orm:"table:user_score"` + Id int `json:"id"` + Uid int `json:"uid"` + Score int `json:"score"` + } + + type User struct { + gmeta.Meta `orm:"table:user"` + Id int `json:"id"` + Name string `json:"name"` + UserDetail *UserDetail `orm:"with:uid=id"` + UserScores []*UserScore `orm:"with:uid=id"` + } + + db := g.DB() + var user *User + err := db.Model(user).WithAll().Where("id", 3).Scan(&user) + if err != nil { + panic(err) + } + g.Dump(user) +} diff --git a/.example/database/gredis/gredis.go b/.example/database/gredis/gredis.go index 0ba2a4b2c..a276da6f9 100644 --- a/.example/database/gredis/gredis.go +++ b/.example/database/gredis/gredis.go @@ -9,10 +9,11 @@ import ( // 使用原生gredis.New操作redis,但是注意需要自己调用Close方法关闭redis链接池 func main() { - redis := gredis.New(gredis.Config{ + config := &gredis.Config{ Host: "127.0.0.1", Port: 6379, - }) + } + redis := gredis.New(config) defer redis.Close() redis.Do("SET", "k", "v") v, _ := redis.Do("GET", "k") diff --git a/.example/i18n/gi18n/gi18n-dir.go b/.example/i18n/gi18n/gi18n-dir.go index e9359c5ea..552298176 100644 --- a/.example/i18n/gi18n/gi18n-dir.go +++ b/.example/i18n/gi18n/gi18n-dir.go @@ -1,6 +1,7 @@ package main import ( + "context" "fmt" "github.com/gogf/gf/i18n/gi18n" @@ -13,6 +14,6 @@ func main() { if err != nil { panic(err) } - fmt.Println(t.Translate(`hello`)) - fmt.Println(t.Translate(`{#hello}{#world}!`)) + fmt.Println(t.Translate(context.TODO(), `hello`)) + fmt.Println(t.Translate(context.TODO(), `{#hello}{#world}!`)) } diff --git a/.example/i18n/gi18n/gi18n-file.go b/.example/i18n/gi18n/gi18n-file.go index 1a6d337c3..f1a4423ee 100644 --- a/.example/i18n/gi18n/gi18n-file.go +++ b/.example/i18n/gi18n/gi18n-file.go @@ -1,6 +1,7 @@ package main import ( + "context" "fmt" "github.com/gogf/gf/i18n/gi18n" @@ -13,6 +14,6 @@ func main() { if err != nil { panic(err) } - fmt.Println(t.Translate(`hello`)) - fmt.Println(t.Translate(`{#hello}{#world}!`)) + fmt.Println(t.Translate(context.TODO(), `hello`)) + fmt.Println(t.Translate(context.TODO(), `{#hello}{#world}!`)) } diff --git a/.example/i18n/gi18n/gi18n.go b/.example/i18n/gi18n/gi18n.go index 630d5a954..17d830c0d 100644 --- a/.example/i18n/gi18n/gi18n.go +++ b/.example/i18n/gi18n/gi18n.go @@ -1,8 +1,10 @@ package main import ( + "context" "fmt" "github.com/gogf/gf/frame/g" + "github.com/gogf/gf/i18n/gi18n" ) func main() { @@ -10,6 +12,12 @@ func main() { orderId = 865271654 orderAmount = 99.8 ) - fmt.Println(g.I18n().Tfl(`en`, `{#OrderPaid}`, orderId, orderAmount)) - fmt.Println(g.I18n().Tfl(`zh-CN`, `{#OrderPaid}`, orderId, orderAmount)) + fmt.Println(g.I18n().Tf( + gi18n.WithLanguage(context.TODO(), `en`), + `{#OrderPaid}`, orderId, orderAmount, + )) + fmt.Println(g.I18n().Tf( + gi18n.WithLanguage(context.TODO(), `zh-CN`), + `{#OrderPaid}`, orderId, orderAmount, + )) } diff --git a/.example/i18n/gi18n/http_view_i18n.go b/.example/i18n/gi18n/http_view_i18n.go index ca5956c7b..cbf0b6e36 100644 --- a/.example/i18n/gi18n/http_view_i18n.go +++ b/.example/i18n/gi18n/http_view_i18n.go @@ -2,15 +2,19 @@ package main import ( "github.com/gogf/gf/frame/g" + "github.com/gogf/gf/i18n/gi18n" "github.com/gogf/gf/net/ghttp" ) func main() { - g.I18n().SetPath("/Users/john/Workspace/Go/GOPATH/src/github.com/gogf/gf/.example/i18n/gi18n/i18n") s := g.Server() - s.BindHandler("/", func(r *ghttp.Request) { - r.Response.WriteTplContent(`{#hello}{#world}!`, g.Map{ - "I18nLanguage": r.Get("lang", "zh-CN"), + s.Group("/", func(group *ghttp.RouterGroup) { + group.Middleware(func(r *ghttp.Request) { + r.SetCtx(gi18n.WithLanguage(r.Context(), r.GetString("lang", "zh-CN"))) + r.Middleware.Next() + }) + group.ALL("/", func(r *ghttp.Request) { + r.Response.WriteTplContent(`{#hello}{#world}!`) }) }) s.SetPort(8199) diff --git a/.example/i18n/gi18n/i18n/zh-CN.toml b/.example/i18n/gi18n/i18n/zh-CN.toml index 80acf06de..20406d93a 100644 --- a/.example/i18n/gi18n/i18n/zh-CN.toml +++ b/.example/i18n/gi18n/i18n/zh-CN.toml @@ -1 +1,3 @@ -OrderPaid = "您已成功完成订单号 #%d 支付,支付金额¥%.2f。" \ No newline at end of file +OrderPaid = "您已成功完成订单号 #%d 支付,支付金额¥%.2f。" +hello = "你好" +world = "世界" \ No newline at end of file diff --git a/.example/net/ghttp/client/middleware/client.go b/.example/net/ghttp/client/middleware/client.go new file mode 100644 index 000000000..7ba07b4bf --- /dev/null +++ b/.example/net/ghttp/client/middleware/client.go @@ -0,0 +1,64 @@ +package main + +import ( + "bytes" + "fmt" + "github.com/gogf/gf/container/garray" + "github.com/gogf/gf/crypto/gmd5" + "github.com/gogf/gf/frame/g" + "github.com/gogf/gf/internal/json" + "github.com/gogf/gf/net/ghttp" + "github.com/gogf/gf/os/gtime" + "github.com/gogf/gf/util/gconv" + "github.com/gogf/gf/util/guid" + "github.com/gogf/gf/util/gutil" + "io/ioutil" + "net/http" +) + +const ( + appId = "123" + appSecret = "456" +) + +// 注入统一的接口签名参数 +func injectSignature(jsonContent []byte) []byte { + var m map[string]interface{} + _ = json.Unmarshal(jsonContent, &m) + if len(m) > 0 { + m["appid"] = appId + m["nonce"] = guid.S() + m["timestamp"] = gtime.Timestamp() + var ( + keyArray = garray.NewSortedStrArrayFrom(gutil.Keys(m)) + sigContent string + ) + keyArray.Iterator(func(k int, v string) bool { + sigContent += v + sigContent += gconv.String(m[v]) + return true + }) + m["signature"] = gmd5.MustEncryptString(gmd5.MustEncryptString(sigContent) + appSecret) + jsonContent, _ = json.Marshal(m) + } + return jsonContent +} + +func main() { + c := g.Client() + c.Use(func(c *ghttp.Client, r *http.Request) (resp *ghttp.ClientResponse, err error) { + bodyBytes, _ := ioutil.ReadAll(r.Body) + if len(bodyBytes) > 0 { + // 注入签名相关参数,修改Request原有的提交参数 + bodyBytes = injectSignature(bodyBytes) + r.Body = ioutil.NopCloser(bytes.NewBuffer(bodyBytes)) + r.ContentLength = int64(len(bodyBytes)) + } + return c.Next(r) + }) + content := c.ContentJson().PostContent("http://127.0.0.1:8199/", g.Map{ + "name": "goframe", + "site": "https://goframe.org", + }) + fmt.Println(content) +} diff --git a/.example/net/ghttp/client/middleware/server.go b/.example/net/ghttp/client/middleware/server.go new file mode 100644 index 000000000..8770dd634 --- /dev/null +++ b/.example/net/ghttp/client/middleware/server.go @@ -0,0 +1,17 @@ +package main + +import ( + "github.com/gogf/gf/frame/g" + "github.com/gogf/gf/net/ghttp" +) + +func main() { + s := g.Server() + s.Group("/", func(group *ghttp.RouterGroup) { + group.ALL("/", func(r *ghttp.Request) { + r.Response.Write(r.GetMap()) + }) + }) + s.SetPort(8199) + s.Run() +} diff --git a/.example/net/ghttp/server/redirect/back.go b/.example/net/ghttp/server/redirect/redirect_back.go similarity index 100% rename from .example/net/ghttp/server/redirect/back.go rename to .example/net/ghttp/server/redirect/redirect_back.go diff --git a/.example/net/ghttp/server/redirect/redirect_to.go b/.example/net/ghttp/server/redirect/redirect_to.go new file mode 100644 index 000000000..f7b8f859d --- /dev/null +++ b/.example/net/ghttp/server/redirect/redirect_to.go @@ -0,0 +1,18 @@ +package main + +import ( + "github.com/gogf/gf/frame/g" + "github.com/gogf/gf/net/ghttp" +) + +func main() { + s := g.Server() + s.BindHandler("/", func(r *ghttp.Request) { + r.Response.RedirectTo("/login") + }) + s.BindHandler("/login", func(r *ghttp.Request) { + r.Response.Writeln("Login First") + }) + s.SetPort(8199) + s.Run() +} diff --git a/.example/net/ghttp/server/request/json-xml/test2.go b/.example/net/ghttp/server/request/json-xml/test2.go index 5e3aed050..ced00e20d 100644 --- a/.example/net/ghttp/server/request/json-xml/test2.go +++ b/.example/net/ghttp/server/request/json-xml/test2.go @@ -25,7 +25,7 @@ func main() { //fmt.Println(r.GetBody()) if err := r.Parse(&req); err != nil { // Validation error. - if v, ok := err.(*gvalid.Error); ok { + if v, ok := err.(gvalid.Error); ok { r.Response.WriteJsonExit(RegisterRes{ Code: 1, Error: v.FirstString(), diff --git a/.example/net/ghttp/server/request/request_validation.go b/.example/net/ghttp/server/request/request_validation.go index fa23190b2..2e180ad77 100644 --- a/.example/net/ghttp/server/request/request_validation.go +++ b/.example/net/ghttp/server/request/request_validation.go @@ -18,13 +18,13 @@ func main() { s.Group("/", func(rgroup *ghttp.RouterGroup) { rgroup.ALL("/user", func(r *ghttp.Request) { user := new(User) - if err := r.GetToStruct(user); err != nil { + if err := r.GetStruct(user); err != nil { r.Response.WriteJsonExit(g.Map{ "message": err, "errcode": 1, }) } - if err := gvalid.CheckStruct(user, nil); err != nil { + if err := gvalid.CheckStruct(r.Context(), user, nil); err != nil { r.Response.WriteJsonExit(g.Map{ "message": err.Maps(), "errcode": 1, diff --git a/.example/net/ghttp/server/request/validation/validation1.go b/.example/net/ghttp/server/request/validation/validation1/validation1.go similarity index 100% rename from .example/net/ghttp/server/request/validation/validation1.go rename to .example/net/ghttp/server/request/validation/validation1/validation1.go diff --git a/.example/net/ghttp/server/request/validation/validation2.go b/.example/net/ghttp/server/request/validation/validation2/validation2.go similarity index 96% rename from .example/net/ghttp/server/request/validation/validation2.go rename to .example/net/ghttp/server/request/validation/validation2/validation2.go index ccee1e3f9..2800feab1 100644 --- a/.example/net/ghttp/server/request/validation/validation2.go +++ b/.example/net/ghttp/server/request/validation/validation2/validation2.go @@ -24,7 +24,7 @@ func main() { var req *RegisterReq if err := r.Parse(&req); err != nil { // Validation error. - if v, ok := err.(*gvalid.Error); ok { + if v, ok := err.(gvalid.Error); ok { r.Response.WriteJsonExit(RegisterRes{ Code: 1, Error: v.FirstString(), diff --git a/.example/net/ghttp/server/router/duplicated/duplicated.go b/.example/net/ghttp/server/router/duplicated/duplicated.go new file mode 100644 index 000000000..9402a2a4d --- /dev/null +++ b/.example/net/ghttp/server/router/duplicated/duplicated.go @@ -0,0 +1,20 @@ +package main + +import ( + "github.com/gogf/gf/frame/g" + "github.com/gogf/gf/net/ghttp" +) + +func main() { + s := g.Server() + s.Group("/", func(group *ghttp.RouterGroup) { + group.ALL("/test", func(r *ghttp.Request) { + r.Response.Writeln(1) + }) + group.ALL("/test", func(r *ghttp.Request) { + r.Response.Writeln(2) + }) + }) + s.SetPort(8199) + s.Run() +} diff --git a/.example/net/ghttp/server/servefile/servefile.go b/.example/net/ghttp/server/servefile/servefile.go new file mode 100644 index 000000000..761cfbe03 --- /dev/null +++ b/.example/net/ghttp/server/servefile/servefile.go @@ -0,0 +1,15 @@ +package main + +import ( + "github.com/gogf/gf/frame/g" + "github.com/gogf/gf/net/ghttp" +) + +func main() { + s := g.Server() + s.BindHandler("/", func(r *ghttp.Request) { + r.Response.ServeFile("test.txt") + }) + s.SetPort(8999) + s.Run() +} diff --git a/.example/net/ghttp/server/servefile/servefiledownload.go b/.example/net/ghttp/server/servefile/servefiledownload.go new file mode 100644 index 000000000..a8e734b9b --- /dev/null +++ b/.example/net/ghttp/server/servefile/servefiledownload.go @@ -0,0 +1,15 @@ +package main + +import ( + "github.com/gogf/gf/frame/g" + "github.com/gogf/gf/net/ghttp" +) + +func main() { + s := g.Server() + s.BindHandler("/", func(r *ghttp.Request) { + r.Response.ServeFileDownload("test.txt") + }) + s.SetPort(8999) + s.Run() +} diff --git a/.example/net/ghttp/server/servefile/test.txt b/.example/net/ghttp/server/servefile/test.txt new file mode 100644 index 000000000..30d74d258 --- /dev/null +++ b/.example/net/ghttp/server/servefile/test.txt @@ -0,0 +1 @@ +test \ No newline at end of file diff --git a/.example/net/ghttp/server/session/redis/redis_bigint.go b/.example/net/ghttp/server/session/redis/redis_bigint.go new file mode 100644 index 000000000..b8a5b3460 --- /dev/null +++ b/.example/net/ghttp/server/session/redis/redis_bigint.go @@ -0,0 +1,36 @@ +package main + +import ( + "github.com/gogf/gf/frame/g" + "github.com/gogf/gf/net/ghttp" + "github.com/gogf/gf/os/gsession" +) + +func main() { + type User struct { + Id int64 + Name string + } + s := g.Server() + s.SetSessionStorage(gsession.NewStorageRedis(g.Redis())) + s.Group("/", func(group *ghttp.RouterGroup) { + group.GET("/set", func(r *ghttp.Request) { + user := &User{ + Id: 1265476890672672808, + Name: "john", + } + if err := r.Session.Set("user", user); err != nil { + panic(err) + } + r.Response.Write("ok") + }) + group.GET("/get", func(r *ghttp.Request) { + r.Response.WriteJson(r.Session.Get("user")) + }) + group.GET("/clear", func(r *ghttp.Request) { + r.Session.Clear() + }) + }) + s.SetPort(8199) + s.Run() +} diff --git a/.example/net/gsmtp/gsmtp_sendMail.go b/.example/net/gsmtp/gsmtp_sendMail.go index 8496c43e9..f32cd7f38 100644 --- a/.example/net/gsmtp/gsmtp_sendMail.go +++ b/.example/net/gsmtp/gsmtp_sendMail.go @@ -1,4 +1,4 @@ -// Copyright 2017 gf Author(https://github.com/gogf/gf). All Rights Reserved. +// Copyright GoFrame Author(https://goframe.org). All Rights Reserved. // // This Source Code Form is subject to the terms of the MIT License. // If a copy of the MIT was not distributed with this file, diff --git a/.example/net/gtcp/gtcp_conn.go b/.example/net/gtcp/gtcp_conn.go index a312c4ead..323595751 100644 --- a/.example/net/gtcp/gtcp_conn.go +++ b/.example/net/gtcp/gtcp_conn.go @@ -16,7 +16,7 @@ func main() { } defer conn.Close() - if err := conn.Send([]byte("GET / HTTP/1.1\n\n")); err != nil { + if err := conn.Send([]byte("GET / HTTP/1.1\r\n\r\n")); err != nil { panic(err) } @@ -30,13 +30,14 @@ func main() { array := bytes.Split(data, []byte(": ")) // 获得页面内容长度 if contentLength == 0 && len(array) == 2 && bytes.EqualFold([]byte("Content-Length"), array[0]) { - contentLength = gconv.Int(array[1]) + // http 以\r\n换行,需要把\r也去掉 + contentLength = gconv.Int(string(array[1][:len(array[1])-1])) } header = append(header, data...) header = append(header, '\n') } - // header读取完毕,读取文本内容 - if contentLength > 0 && len(data) == 0 { + // header读取完毕,读取文本内容, 1为\r + if contentLength > 0 && len(data) == 1 { content, _ = conn.Recv(contentLength) break } diff --git a/.example/os/gcache/getorset_func_lock.go b/.example/os/gcache/getorset_func_lock.go new file mode 100644 index 000000000..b5e3760bc --- /dev/null +++ b/.example/os/gcache/getorset_func_lock.go @@ -0,0 +1,28 @@ +package main + +import ( + "fmt" + "github.com/gogf/gf/os/gcache" + "github.com/gogf/gf/os/gctx" + "time" +) + +func main() { + var ( + ch = make(chan struct{}, 0) + ctx = gctx.New() + key = `key` + value = `value` + ) + for i := 0; i < 10; i++ { + go func(index int) { + <-ch + _, _ = gcache.Ctx(ctx).GetOrSetFuncLock(key, func() (interface{}, error) { + fmt.Println(index, "entered") + return value, nil + }, 0) + }(i) + } + close(ch) + time.Sleep(time.Second) +} diff --git a/.example/os/gcache/note_interface_key.go b/.example/os/gcache/note_interface_key.go new file mode 100644 index 000000000..4f5e1ee44 --- /dev/null +++ b/.example/os/gcache/note_interface_key.go @@ -0,0 +1,19 @@ +package main + +import ( + "fmt" + "github.com/gogf/gf/os/gcache" + "github.com/gogf/gf/os/gctx" +) + +func main() { + var ( + ctx = gctx.New() + key1 int32 = 1 + key2 float64 = 1 + value = `value` + ) + _ = gcache.Ctx(ctx).Set(key1, value, 0) + fmt.Println(gcache.Ctx(ctx).Get(key1)) + fmt.Println(gcache.Ctx(ctx).Get(key2)) +} diff --git a/.example/os/gcache/note_interface_value.go b/.example/os/gcache/note_interface_value.go new file mode 100644 index 000000000..b102e86f0 --- /dev/null +++ b/.example/os/gcache/note_interface_value.go @@ -0,0 +1,29 @@ +package main + +import ( + "fmt" + "github.com/gogf/gf/os/gcache" + "github.com/gogf/gf/os/gctx" +) + +func main() { + type User struct { + Id int + Name string + Site string + } + var ( + ctx = gctx.New() + user *User + key = `UserKey` + value = &User{ + Id: 1, + Name: "GoFrame", + Site: "https://goframe.org", + } + ) + _ = gcache.Ctx(ctx).Set(key, value, 0) + v, _ := gcache.Ctx(ctx).GetVar(key) + _ = v.Scan(&user) + fmt.Printf(`%#v`, user) +} diff --git a/.example/os/glog/glog_CtxKeys.go b/.example/os/glog/glog_CtxKeys.go new file mode 100644 index 000000000..3eb6fcc13 --- /dev/null +++ b/.example/os/glog/glog_CtxKeys.go @@ -0,0 +1,14 @@ +package main + +import ( + "context" + "github.com/gogf/gf/frame/g" +) + +func main() { + g.Log().SetCtxKeys("TraceId", "SpanId", "Test") + ctx := context.WithValue(context.Background(), "TraceId", "1234567890") + ctx = context.WithValue(ctx, "SpanId", "abcdefg") + + g.Log().Ctx(ctx).Print(1, 2, 3) +} diff --git a/.example/os/glog/glog_config1.go b/.example/os/glog/glog_SetConfigWithMap.go similarity index 81% rename from .example/os/glog/glog_config1.go rename to .example/os/glog/glog_SetConfigWithMap.go index fc3c5a3cb..06361b583 100644 --- a/.example/os/glog/glog_config1.go +++ b/.example/os/glog/glog_SetConfigWithMap.go @@ -6,7 +6,7 @@ import ( ) func main() { - err := glog.SetConfigWithMap(g.Map{ + err := g.Log().SetConfigWithMap(g.Map{ "prefix": "[TEST]", }) if err != nil { diff --git a/.example/os/glog/glog_async1.go b/.example/os/glog/glog_async_chaining.go similarity index 60% rename from .example/os/glog/glog_async1.go rename to .example/os/glog/glog_async_chaining.go index 1f17d1c25..a3dda3cd6 100644 --- a/.example/os/glog/glog_async1.go +++ b/.example/os/glog/glog_async_chaining.go @@ -1,14 +1,13 @@ package main import ( + "github.com/gogf/gf/frame/g" "time" - - "github.com/gogf/gf/os/glog" ) func main() { for i := 0; i < 10; i++ { - glog.Async().Print("async log", i) + g.Log().Async().Print("async log", i) } time.Sleep(time.Second) } diff --git a/.example/os/glog/glog_async2.go b/.example/os/glog/glog_async_configure.go similarity index 54% rename from .example/os/glog/glog_async2.go rename to .example/os/glog/glog_async_configure.go index 63d6f4278..e9cc86bd6 100644 --- a/.example/os/glog/glog_async2.go +++ b/.example/os/glog/glog_async_configure.go @@ -1,15 +1,14 @@ package main import ( + "github.com/gogf/gf/frame/g" "time" - - "github.com/gogf/gf/os/glog" ) func main() { - glog.SetAsync(true) + g.Log().SetAsync(true) for i := 0; i < 10; i++ { - glog.Async().Print("async log", i) + g.Log().Print("async log", i) } time.Sleep(time.Second) } diff --git a/.example/os/glog/glog_category.go b/.example/os/glog/glog_category.go index 47c56ccf4..2be6ee80b 100644 --- a/.example/os/glog/glog_category.go +++ b/.example/os/glog/glog_category.go @@ -3,13 +3,12 @@ package main import ( "github.com/gogf/gf/frame/g" "github.com/gogf/gf/os/gfile" - "github.com/gogf/gf/os/glog" ) func main() { path := "/tmp/glog-cat" - glog.SetPath(path) - glog.Stdout(false).Cat("cat1").Cat("cat2").Println("test") + g.Log().SetPath(path) + g.Log().Stdout(false).Cat("cat1").Cat("cat2").Println("test") list, err := gfile.ScanDir(path, "*", true) g.Dump(err) g.Dump(list) diff --git a/.example/os/glog/glog_color.go b/.example/os/glog/glog_color.go new file mode 100644 index 000000000..b178db5e0 --- /dev/null +++ b/.example/os/glog/glog_color.go @@ -0,0 +1,15 @@ +package main + +import ( + "github.com/gogf/gf/frame/g" +) + +func main() { + g.Log().Print("Print") + g.Log().Debug("Debug") + g.Log().Info("Info") + g.Log().Notice("Notice") + g.Log().Warning("Warning") + g.Log().Error("Error") + g.Log().Critical("Critical") +} diff --git a/.example/os/glog/glog_ctx.go b/.example/os/glog/glog_ctx.go deleted file mode 100644 index b4ca21ea2..000000000 --- a/.example/os/glog/glog_ctx.go +++ /dev/null @@ -1,14 +0,0 @@ -package main - -import ( - "context" - "github.com/gogf/gf/os/glog" -) - -func main() { - glog.SetCtxKeys("Trace-Id", "Span-Id", "Test") - ctx := context.WithValue(context.Background(), "Trace-Id", "1234567890") - ctx = context.WithValue(ctx, "Span-Id", "abcdefg") - - glog.Ctx(ctx).Print(1, 2, 3) -} diff --git a/.example/os/glog/glog_debug.go b/.example/os/glog/glog_debug.go index 64ec9c277..579aefe3b 100644 --- a/.example/os/glog/glog_debug.go +++ b/.example/os/glog/glog_debug.go @@ -1,19 +1,19 @@ package main import ( + "github.com/gogf/gf/frame/g" "time" - "github.com/gogf/gf/os/glog" "github.com/gogf/gf/os/gtime" "github.com/gogf/gf/os/gtimer" ) func main() { gtimer.SetTimeout(3*time.Second, func() { - glog.SetDebug(false) + g.Log().SetDebug(false) }) for { - glog.Debug(gtime.Datetime()) + g.Log().Debug(gtime.Datetime()) time.Sleep(time.Second) } } diff --git a/.example/os/glog/glog_file.go b/.example/os/glog/glog_file.go index 6a0843dac..9e4f6b858 100644 --- a/.example/os/glog/glog_file.go +++ b/.example/os/glog/glog_file.go @@ -3,24 +3,25 @@ package main import ( "github.com/gogf/gf/frame/g" "github.com/gogf/gf/os/gfile" - "github.com/gogf/gf/os/glog" ) // 设置日志等级 func main() { - l := glog.New() path := "/tmp/glog" - l.SetPath(path) - l.SetStdoutPrint(false) + g.Log().SetPath(path) + g.Log().SetStdoutPrint(false) + // 使用默认文件名称格式 - l.Println("标准文件名称格式,使用当前时间时期") + g.Log().Println("标准文件名称格式,使用当前时间时期") + // 通过SetFile设置文件名称格式 - l.SetFile("stdout.log") - l.Println("设置日志输出文件名称格式为同一个文件") + g.Log().SetFile("stdout.log") + g.Log().Println("设置日志输出文件名称格式为同一个文件") + // 链式操作设置文件名称格式 - l.File("stderr.log").Println("支持链式操作") - l.File("error-{Ymd}.log").Println("文件名称支持带gtime日期格式") - l.File("access-{Ymd}.log").Println("文件名称支持带gtime日期格式") + g.Log().File("stderr.log").Println("支持链式操作") + g.Log().File("error-{Ymd}.log").Println("文件名称支持带gtime日期格式") + g.Log().File("access-{Ymd}.log").Println("文件名称支持带gtime日期格式") list, err := gfile.ScanDir(path, "*") g.Dump(err) diff --git a/.example/os/glog/glog_flags.go b/.example/os/glog/glog_flags.go index ebb3894c3..f3ed32ce3 100644 --- a/.example/os/glog/glog_flags.go +++ b/.example/os/glog/glog_flags.go @@ -1,15 +1,15 @@ package main import ( + "github.com/gogf/gf/frame/g" "github.com/gogf/gf/os/glog" ) func main() { - l := glog.New() - l.SetFlags(glog.F_TIME_TIME | glog.F_FILE_SHORT) - l.Println("time and short line number") - l.SetFlags(glog.F_TIME_MILLI | glog.F_FILE_LONG) - l.Println("time with millisecond and long line number") - l.SetFlags(glog.F_TIME_STD | glog.F_FILE_LONG) - l.Println("standard time format and long line number") + g.Log().SetFlags(glog.F_TIME_TIME | glog.F_FILE_SHORT) + g.Log().Println("time and short line number") + g.Log().SetFlags(glog.F_TIME_MILLI | glog.F_FILE_LONG) + g.Log().Println("time with millisecond and long line number") + g.Log().SetFlags(glog.F_TIME_STD | glog.F_FILE_LONG) + g.Log().Println("standard time format and long line number") } diff --git a/.example/os/glog/glog_gerror.go b/.example/os/glog/glog_gerror.go index cbcf405c0..6fb54f726 100644 --- a/.example/os/glog/glog_gerror.go +++ b/.example/os/glog/glog_gerror.go @@ -2,9 +2,9 @@ package main import ( "errors" + "github.com/gogf/gf/frame/g" "github.com/gogf/gf/errors/gerror" - "github.com/gogf/gf/os/glog" ) func MakeError() error { @@ -18,8 +18,8 @@ func MakeGError() error { func TestGError() { err1 := MakeError() err2 := MakeGError() - glog.Error(err1) - glog.Error(err2) + g.Log().Error(err1) + g.Log().Error(err2) } func main() { diff --git a/.example/os/glog/glog_json.go b/.example/os/glog/glog_json.go index f1e28944e..1075bf056 100644 --- a/.example/os/glog/glog_json.go +++ b/.example/os/glog/glog_json.go @@ -2,15 +2,14 @@ package main import ( "github.com/gogf/gf/frame/g" - "github.com/gogf/gf/os/glog" ) func main() { - glog.Debug(g.Map{"uid": 100, "name": "john"}) + g.Log().Debug(g.Map{"uid": 100, "name": "john"}) type User struct { Uid int `json:"uid"` Name string `json:"name"` } - glog.Debug(User{100, "john"}) + g.Log().Debug(User{100, "john"}) } diff --git a/.example/os/glog/glog_level.go b/.example/os/glog/glog_level.go index c1e07f736..e8c7b917b 100644 --- a/.example/os/glog/glog_level.go +++ b/.example/os/glog/glog_level.go @@ -1,13 +1,13 @@ package main import ( + "github.com/gogf/gf/frame/g" "github.com/gogf/gf/os/glog" ) // 设置日志等级,过滤掉Info日志信息 func main() { - l := glog.New() - l.Info("info1") - l.SetLevel(glog.LEVEL_ALL ^ glog.LEVEL_INFO) - l.Info("info2") + g.Log().Info("info1") + g.Log().SetLevel(glog.LEVEL_ALL ^ glog.LEVEL_INFO) + g.Log().Info("info2") } diff --git a/.example/os/glog/glog_level_prefix.go b/.example/os/glog/glog_level_prefix.go index cd9a7e9f7..014c7c288 100644 --- a/.example/os/glog/glog_level_prefix.go +++ b/.example/os/glog/glog_level_prefix.go @@ -1,11 +1,11 @@ package main import ( + "github.com/gogf/gf/frame/g" "github.com/gogf/gf/os/glog" ) func main() { - l := glog.New() - l.SetLevelPrefix(glog.LEVEL_DEBU, "debug") - l.Debug("test") + g.Log().SetLevelPrefix(glog.LEVEL_DEBU, "debug") + g.Log().Debug("test") } diff --git a/.example/os/glog/glog_line.go b/.example/os/glog/glog_line.go index 4ee2ad1a1..5326cf867 100644 --- a/.example/os/glog/glog_line.go +++ b/.example/os/glog/glog_line.go @@ -1,10 +1,10 @@ package main import ( - "github.com/gogf/gf/os/glog" + "github.com/gogf/gf/frame/g" ) func main() { - glog.Line().Debug("this is the short file name with its line number") - glog.Line(true).Debug("lone file name with line number") + g.Log().Line().Debug("this is the short file name with its line number") + g.Log().Line(true).Debug("lone file name with line number") } diff --git a/.example/os/glog/glog_line2.go b/.example/os/glog/glog_line2.go index b694f7b92..9fd8a60fc 100644 --- a/.example/os/glog/glog_line2.go +++ b/.example/os/glog/glog_line2.go @@ -1,12 +1,12 @@ package main import ( - "github.com/gogf/gf/os/glog" + "github.com/gogf/gf/frame/g" ) func PrintLog(content string) { - glog.Skip(0).Line().Println("line number with skip:", content) - glog.Line(true).Println("line number without skip:", content) + g.Log().Skip(0).Line().Println("line number with skip:", content) + g.Log().Line(true).Println("line number without skip:", content) } func main() { diff --git a/.example/os/glog/glog_path.go b/.example/os/glog/glog_path.go index 913a037ab..a7d0e4367 100644 --- a/.example/os/glog/glog_path.go +++ b/.example/os/glog/glog_path.go @@ -3,14 +3,13 @@ package main import ( "github.com/gogf/gf/frame/g" "github.com/gogf/gf/os/gfile" - "github.com/gogf/gf/os/glog" ) // 设置日志输出路径 func main() { path := "/tmp/glog" - glog.SetPath(path) - glog.Println("日志内容") + g.Log().SetPath(path) + g.Log().Println("日志内容") list, err := gfile.ScanDir(path, "*") g.Dump(err) g.Dump(list) diff --git a/.example/os/glog/glog_pool.go b/.example/os/glog/glog_pool.go index 383b74367..960e85557 100644 --- a/.example/os/glog/glog_pool.go +++ b/.example/os/glog/glog_pool.go @@ -1,18 +1,18 @@ package main import ( + "github.com/gogf/gf/frame/g" "time" - "github.com/gogf/gf/os/glog" "github.com/gogf/gf/os/gtime" ) // 测试删除日志文件是否会重建日志文件 func main() { path := "/Users/john/Temp/test" - glog.SetPath(path) + g.Log().SetPath(path) for { - glog.Println(gtime.Now().String()) + g.Log().Println(gtime.Now().String()) time.Sleep(time.Second) } } diff --git a/.example/os/glog/glog_prefix.go b/.example/os/glog/glog_prefix.go index 502467128..b803b9a4e 100644 --- a/.example/os/glog/glog_prefix.go +++ b/.example/os/glog/glog_prefix.go @@ -1,12 +1,11 @@ package main import ( - "github.com/gogf/gf/os/glog" + "github.com/gogf/gf/frame/g" ) func main() { - l := glog.New() - l.SetPrefix("[API]") - l.Println("hello world") - l.Error("error occurred") + g.Log().SetPrefix("[API]") + g.Log().Println("hello world") + g.Log().Error("error occurred") } diff --git a/.example/os/glog/glog_stack.go b/.example/os/glog/glog_stack.go index 1232c2bec..1dced6e69 100644 --- a/.example/os/glog/glog_stack.go +++ b/.example/os/glog/glog_stack.go @@ -2,15 +2,11 @@ package main import ( "fmt" - - "github.com/gogf/gf/os/glog" + "github.com/gogf/gf/frame/g" ) func main() { + g.Log().PrintStack() - glog.PrintStack() - glog.New().PrintStack() - - fmt.Println(glog.GetStack()) - fmt.Println(glog.New().GetStack()) + fmt.Println(g.Log().GetStack()) } diff --git a/.example/os/glog/glog_stdout.go b/.example/os/glog/glog_stdout.go index 4d2200028..c4ae4d6ba 100644 --- a/.example/os/glog/glog_stdout.go +++ b/.example/os/glog/glog_stdout.go @@ -1,22 +1,23 @@ package main import ( + "github.com/gogf/gf/frame/g" "sync" - - "github.com/gogf/gf/os/glog" ) func main() { - wg := sync.WaitGroup{} - c := make(chan struct{}) + var ( + wg = sync.WaitGroup{} + ch = make(chan struct{}) + ) wg.Add(3000) for i := 0; i < 3000; i++ { go func() { - <-c - glog.Println("abcdefghijklmnopqrstuvwxyz1234567890") + <-ch + g.Log().Println("abcdefghijklmnopqrstuvwxyz1234567890") wg.Done() }() } - close(c) + close(ch) wg.Wait() } diff --git a/.example/os/glog/glog_writer_hook.go b/.example/os/glog/glog_writer_hook.go index 67e8863ca..365ec28f0 100644 --- a/.example/os/glog/glog_writer_hook.go +++ b/.example/os/glog/glog_writer_hook.go @@ -2,8 +2,8 @@ package main import ( "fmt" + "github.com/gogf/gf/frame/g" - "github.com/gogf/gf/net/ghttp" "github.com/gogf/gf/os/glog" "github.com/gogf/gf/text/gregex" ) @@ -16,7 +16,7 @@ func (w *MyWriter) Write(p []byte) (n int, err error) { s := string(p) if gregex.IsMatchString(`\[(PANI|FATA)\]`, s) { fmt.Println("SERIOUS ISSUE OCCURRED!! I'd better tell monitor in first time!") - ghttp.PostContent("http://monitor.mydomain.com", s) + g.Client().PostContent("http://monitor.mydomain.com", s) } return w.logger.Write(p) } diff --git a/.example/os/glog/handler/glog_handler_greylog.go b/.example/os/glog/handler/glog_handler_greylog.go new file mode 100644 index 000000000..e690ac309 --- /dev/null +++ b/.example/os/glog/handler/glog_handler_greylog.go @@ -0,0 +1,31 @@ +package main + +//import ( +// "context" +// "github.com/gogf/gf/frame/g" +// "github.com/gogf/gf/os/glog" +// "github.com/robertkowalski/graylog-golang" +//) +// +//var greyLogClient = gelf.New(gelf.Config{ +// GraylogPort: 80, +// GraylogHostname: "graylog-host.com", +// Connection: "wan", +// MaxChunkSizeWan: 42, +// MaxChunkSizeLan: 1337, +//}) +// +//// LoggingGreyLogHandler is an example handler for logging content to remote GreyLog service. +//var LoggingGreyLogHandler glog.Handler = func(ctx context.Context, in *glog.HandlerInput) { +// in.Next() +// greyLogClient.Log(in.Buffer.String()) +//} +// +//func main() { +// g.Log().SetHandlers(LoggingGreyLogHandler) +// +// g.Log().Debug("Debugging...") +// g.Log().Warning("It is warning info") +// g.Log().Error("Error occurs, please have a check") +// glog.Println("test log") +//} diff --git a/.example/os/glog/handler/glog_handler_json.go b/.example/os/glog/handler/glog_handler_json.go new file mode 100644 index 000000000..49dc0750b --- /dev/null +++ b/.example/os/glog/handler/glog_handler_json.go @@ -0,0 +1,42 @@ +package main + +import ( + "context" + "github.com/gogf/gf/frame/g" + "github.com/gogf/gf/internal/json" + "github.com/gogf/gf/os/glog" + "github.com/gogf/gf/text/gstr" + "os" +) + +// JsonOutputsForLogger is for JSON marshaling in sequence. +type JsonOutputsForLogger struct { + Time string `json:"time"` + Level string `json:"level"` + Content string `json:"content"` +} + +// LoggingJsonHandler is an example handler for logging JSON format content. +var LoggingJsonHandler glog.Handler = func(ctx context.Context, in *glog.HandlerInput) { + jsonForLogger := JsonOutputsForLogger{ + Time: in.TimeFormat, + Level: in.LevelFormat, + Content: gstr.Trim(in.String()), + } + jsonBytes, err := json.Marshal(jsonForLogger) + if err != nil { + _, _ = os.Stderr.WriteString(err.Error()) + return + } + in.Buffer.Write(jsonBytes) + in.Buffer.WriteString("\n") + in.Next() +} + +func main() { + g.Log().SetHandlers(LoggingJsonHandler) + + g.Log().Debug("Debugging...") + g.Log().Warning("It is warning info") + g.Log().Error("Error occurs, please have a check") +} diff --git a/.example/os/gmlock/locker1.go b/.example/os/gmlock/1.lock&unlock.go similarity index 86% rename from .example/os/gmlock/locker1.go rename to .example/os/gmlock/1.lock&unlock.go index 946ae72b4..71a4d3534 100644 --- a/.example/os/gmlock/locker1.go +++ b/.example/os/gmlock/1.lock&unlock.go @@ -10,8 +10,10 @@ import ( // 内存锁基本使用 func main() { - key := "lock" - wg := sync.WaitGroup{} + var ( + key = "lock" + wg = sync.WaitGroup{} + ) for i := 0; i < 10; i++ { wg.Add(1) go func(i int) { diff --git a/.example/os/gmlock/locker3.go b/.example/os/gmlock/2.trylock.go similarity index 100% rename from .example/os/gmlock/locker3.go rename to .example/os/gmlock/2.trylock.go diff --git a/.example/os/gmlock/locker4.go b/.example/os/gmlock/3.lock_conflicts.go similarity index 100% rename from .example/os/gmlock/locker4.go rename to .example/os/gmlock/3.lock_conflicts.go diff --git a/.example/os/gmlock/test_locker.go b/.example/os/gmlock/4.test_deadlock.go similarity index 91% rename from .example/os/gmlock/test_locker.go rename to .example/os/gmlock/4.test_deadlock.go index 3600e36b6..b67cf7ddd 100644 --- a/.example/os/gmlock/test_locker.go +++ b/.example/os/gmlock/4.test_deadlock.go @@ -11,12 +11,13 @@ import ( // 测试Locker是否会产生死锁 func main() { - l := gmlock.New() - wg := sync.WaitGroup{} - key := "test" - event := make(chan int) - number := 100000 - + var ( + l = gmlock.New() + wg = sync.WaitGroup{} + key = "test" + event = make(chan int) + number = 100000 + ) for i := 0; i < number; i++ { wg.Add(1) go func() { diff --git a/.example/os/gmlock/locker2.go b/.example/os/gmlock/locker2.go deleted file mode 100644 index f340a25c9..000000000 --- a/.example/os/gmlock/locker2.go +++ /dev/null @@ -1,23 +0,0 @@ -package main - -import ( - "sync" - - "github.com/gogf/gf/os/glog" - "github.com/gogf/gf/os/gmlock" -) - -// 内存锁 - 给定过期时间 -func main() { - key := "lock" - wg := sync.WaitGroup{} - for i := 0; i < 10; i++ { - wg.Add(1) - go func(i int) { - gmlock.Lock(key, 1000) - glog.Println(i) - wg.Done() - }(i) - } - wg.Wait() -} diff --git a/.example/os/gmlock/test_mutex.go b/.example/os/gmlock/test_mutex.go deleted file mode 100644 index c03874e70..000000000 --- a/.example/os/gmlock/test_mutex.go +++ /dev/null @@ -1,95 +0,0 @@ -package main - -import ( - "fmt" - "math/rand" - "sync" - "time" - - "github.com/gogf/gf/os/gmlock" -) - -// 测试是否会产生死锁 -func main() { - mu := gmlock.NewMutex() - wg := sync.WaitGroup{} - event := make(chan int) - number := 100000 - - for i := 0; i < number; i++ { - wg.Add(1) - go func() { - <-event - mu.Lock() - //fmt.Println("get lock") - mu.Unlock() - wg.Done() - }() - } - - for i := 0; i < number; i++ { - wg.Add(1) - go func() { - <-event - mu.RLock() - //fmt.Println("get rlock") - mu.RUnlock() - wg.Done() - }() - } - - for i := 0; i < number; i++ { - wg.Add(1) - go func() { - <-event - if mu.TryLock() { - //fmt.Println("get lock") - mu.Unlock() - } - wg.Done() - }() - } - - for i := 0; i < number; i++ { - wg.Add(1) - go func() { - <-event - if mu.TryRLock() { - //fmt.Println("get rlock") - mu.RUnlock() - } - wg.Done() - }() - } - - for i := 0; i < number; i++ { - wg.Add(1) - go func() { - <-event - if mu.TryLock() { - // 模拟业务逻辑的随机处理间隔 - time.Sleep(time.Duration(rand.Intn(100)) * time.Millisecond) - mu.Unlock() - } - wg.Done() - }() - } - - for i := 0; i < number; i++ { - wg.Add(1) - go func() { - <-event - if mu.TryRLock() { - // 模拟业务逻辑的随机处理间隔 - time.Sleep(time.Duration(rand.Intn(100)) * time.Millisecond) - mu.RUnlock() - } - wg.Done() - }() - } - // 使用chan作为事件发送测试指令,让所有的goroutine同时执行 - close(event) - wg.Wait() - - fmt.Println("done!") -} diff --git a/.example/os/gproc/signal/signal_handler.go b/.example/os/gproc/signal/signal_handler.go new file mode 100644 index 000000000..a1d495347 --- /dev/null +++ b/.example/os/gproc/signal/signal_handler.go @@ -0,0 +1,54 @@ +package main + +import ( + "fmt" + "os" + "os/signal" + "syscall" + "time" +) + +func signalHandlerForMQ() { + var ( + sig os.Signal + receivedChan = make(chan os.Signal) + ) + signal.Notify( + receivedChan, + syscall.SIGINT, + syscall.SIGQUIT, + syscall.SIGKILL, + syscall.SIGTERM, + syscall.SIGABRT, + ) + for { + sig = <-receivedChan + fmt.Println("MQ is shutting down due to signal:", sig.String()) + time.Sleep(time.Second) + fmt.Println("MQ is shut down smoothly") + return + } +} + +func main() { + fmt.Println("Process start, pid:", os.Getpid()) + go signalHandlerForMQ() + + var ( + sig os.Signal + receivedChan = make(chan os.Signal) + ) + signal.Notify( + receivedChan, + syscall.SIGINT, + syscall.SIGQUIT, + syscall.SIGKILL, + syscall.SIGTERM, + syscall.SIGABRT, + ) + for { + sig = <-receivedChan + fmt.Println("MainProcess is shutting down due to signal:", sig.String()) + return + } +} diff --git a/.example/os/gproc/signal/signal_handler_gproc.go b/.example/os/gproc/signal/signal_handler_gproc.go new file mode 100644 index 000000000..e8e473484 --- /dev/null +++ b/.example/os/gproc/signal/signal_handler_gproc.go @@ -0,0 +1,27 @@ +package main + +import ( + "fmt" + "github.com/gogf/gf/os/gproc" + "os" + "time" +) + +func signalHandlerForMQ(sig os.Signal) { + fmt.Println("MQ is shutting down due to signal:", sig.String()) + time.Sleep(time.Second) + fmt.Println("MQ is shut down smoothly") +} + +func signalHandlerForMain(sig os.Signal) { + fmt.Println("MainProcess is shutting down due to signal:", sig.String()) +} + +func main() { + fmt.Println("Process start, pid:", os.Getpid()) + gproc.AddSigHandlerShutdown( + signalHandlerForMQ, + signalHandlerForMain, + ) + gproc.Listen() +} diff --git a/.example/util/gvalid/config.toml b/.example/util/gvalid/config.toml new file mode 100644 index 000000000..26beac3a7 --- /dev/null +++ b/.example/util/gvalid/config.toml @@ -0,0 +1,14 @@ + + + +# MySQL. +[database] + [database.default] + link = "mysql:root:12345678@tcp(127.0.0.1:3306)/test" + debug = true + + + + + + diff --git a/.example/util/gvalid/gvalid.go b/.example/util/gvalid/gvalid.go index 9975bd443..4d6212876 100644 --- a/.example/util/gvalid/gvalid.go +++ b/.example/util/gvalid/gvalid.go @@ -1,31 +1,32 @@ package main import ( + "context" "github.com/gogf/gf/frame/g" "github.com/gogf/gf/util/gvalid" ) func main() { //rule := "length:6,16" - //if m := gvalid.Check("123456", rule, nil); m != nil { + //if m := gvalid.Check(context.TODO(), "123456", rule, nil); m != nil { // fmt.Println(m) //} - //if m := gvalid.Check("12345", rule, nil); m != nil { + //if m := gvalid.Check(context.TODO(), "12345", rule, nil); m != nil { // fmt.Println(m) // // map[length:字段长度为6到16个字符] //} //rule := "integer|between:6,16" //msgs := "请输入一个整数|参数大小不对啊老铁" - //fmt.Println(gvalid.Check("5.66", rule, msgs)) + //fmt.Println(gvalid.Check(context.TODO(), "5.66", rule, msgs)) //// map[integer:请输入一个整数 between:参数大小不对啊老铁] //// 参数长度至少为6个数字或者6个字母,但是总长度不能超过16个字符 //rule := `regex:\d{6,}|\D{6,}|max-length:16` - //if m := gvalid.Check("123456", rule, nil); m != nil { + //if m := gvalid.Check(context.TODO(), "123456", rule, nil); m != nil { // fmt.Println(m) //} - //if m := gvalid.Check("abcde6", rule, nil); m != nil { + //if m := gvalid.Check(context.TODO(), "abcde6", rule, nil); m != nil { // fmt.Println(m) // // map[regex:字段值不合法] //} @@ -40,18 +41,20 @@ func main() { // "password" : "required|length:6,16|same:password2", // "password2" : "required|length:6,16", //} - //fmt.Println(gvalid.CheckMap(params, rules)) + //fmt.Println(gvalid.CheckMap(context.TODO(), params, rules)) //// map[passport:map[length:字段长度为6到16个字符] password:map[same:字段值不合法]] params := map[string]interface{}{ "passport": "john", "password": "123456", "password2": "1234567", + "name": "gf", } rules := map[string]string{ "passport": "required|length:6,16", "password": "required|length:6,16|same:password2", "password2": "required|length:6,16", + "name": "size:5", } msgs := map[string]interface{}{ "passport": "账号不能为空|账号长度应当在:min到:max之间", @@ -59,8 +62,9 @@ func main() { "required": "密码不能为空", "same": "两次密码输入不相等", }, + "name": "名字长度必须为:size", } - if e := gvalid.CheckMap(params, rules, msgs); e != nil { + if e := gvalid.CheckMap(context.TODO(), params, rules, msgs); e != nil { g.Dump(e.Maps()) } // map[passport:map[length:账号长度应当在6到16之间] password:map[same:两次密码输入不相等]] diff --git a/.example/util/gvalid/gvalid_checkstructwithdata.go b/.example/util/gvalid/gvalid_checkstructwithdata.go new file mode 100644 index 000000000..7e1636f88 --- /dev/null +++ b/.example/util/gvalid/gvalid_checkstructwithdata.go @@ -0,0 +1,28 @@ +package main + +import ( + "context" + "github.com/gogf/gf/frame/g" + "github.com/gogf/gf/util/gconv" + "github.com/gogf/gf/util/gvalid" +) + +func main() { + type User struct { + Name string `v:"required#请输入用户姓名"` + Type int `v:"required#请选择用户类型"` + } + data := g.Map{ + "name": "john", + } + user := User{} + if err := gconv.Scan(data, &user); err != nil { + panic(err) + } + err := gvalid.CheckStructWithData(context.TODO(), user, data, nil) + // 也可以使用 + // err := g.Validator().Data(data).CheckStruct(user) + if err != nil { + g.Dump(err.Items()) + } +} diff --git a/.example/util/gvalid/gvalid_custom_message.go b/.example/util/gvalid/gvalid_custom_message.go index 816749f10..3c8798693 100644 --- a/.example/util/gvalid/gvalid_custom_message.go +++ b/.example/util/gvalid/gvalid_custom_message.go @@ -1,6 +1,7 @@ package main import ( + "context" "fmt" "github.com/gogf/gf/frame/g" "github.com/gogf/gf/util/gvalid" @@ -8,6 +9,6 @@ import ( func main() { g.I18n().SetLanguage("cn") - err := gvalid.Check("", "required", nil) + err := gvalid.Check(context.TODO(), "", "required", nil) fmt.Println(err.String()) } diff --git a/.example/util/gvalid/gvalid_error.go b/.example/util/gvalid/gvalid_error.go index fe5c0a2f2..1f5d350d4 100644 --- a/.example/util/gvalid/gvalid_error.go +++ b/.example/util/gvalid/gvalid_error.go @@ -1,6 +1,7 @@ package main import ( + "context" "github.com/gogf/gf/frame/g" "github.com/gogf/gf/util/gvalid" ) @@ -17,7 +18,7 @@ func main() { ConfiemPassword: "", } - e := gvalid.CheckStruct(user, nil) + e := gvalid.CheckStruct(context.TODO(), user, nil) g.Dump(e.Map()) g.Dump(e.Maps()) g.Dump(e.String()) diff --git a/.example/util/gvalid/gvalid_i18n.go b/.example/util/gvalid/gvalid_i18n.go new file mode 100644 index 000000000..cb2a5e905 --- /dev/null +++ b/.example/util/gvalid/gvalid_i18n.go @@ -0,0 +1,38 @@ +package main + +import ( + "context" + + "github.com/gogf/gf/frame/g" + "github.com/gogf/gf/i18n/gi18n" + "github.com/gogf/gf/util/gconv" +) + +func main() { + type User struct { + Name string `v:"required#ReuiredUserName"` + Type int `v:"required#ReuiredUserType"` + Project string `v:"size:10#MustSize"` + } + var ( + data = g.Map{ + "name": "john", + "project": "gf", + } + user = User{} + ctxEn = gi18n.WithLanguage(context.TODO(), "en") + ctxCh = gi18n.WithLanguage(context.TODO(), "zh-CN") + ) + + if err := gconv.Scan(data, &user); err != nil { + panic(err) + } + // 英文 + if err := g.Validator().Ctx(ctxEn).Data(data).CheckStruct(user); err != nil { + g.Dump(err.String()) + } + // 中文 + if err := g.Validator().Ctx(ctxCh).Data(data).CheckStruct(user); err != nil { + g.Dump(err.String()) + } +} diff --git a/.example/util/gvalid/gvalid_i18n_http.go b/.example/util/gvalid/gvalid_i18n_http.go new file mode 100644 index 000000000..cba5062fc --- /dev/null +++ b/.example/util/gvalid/gvalid_i18n_http.go @@ -0,0 +1,35 @@ +package main + +import ( + "github.com/gogf/gf/net/ghttp" + + "github.com/gogf/gf/frame/g" + "github.com/gogf/gf/i18n/gi18n" +) + +func main() { + type User struct { + Name string `v:"required#ReuiredUserName"` + Type int `v:"required#ReuiredUserType"` + Project string `v:"size:10#MustSize"` + } + s := g.Server() + s.Group("/", func(group *ghttp.RouterGroup) { + group.Middleware(func(r *ghttp.Request) { + lang := r.GetString("lang", "zh-CN") + r.SetCtx(gi18n.WithLanguage(r.Context(), lang)) + r.Middleware.Next() + }) + group.GET("/validate", func(r *ghttp.Request) { + var ( + err error + user = User{} + ) + if err = r.Parse(&user); err != nil { + r.Response.WriteExit(err) + } + r.Response.WriteExit(user) + }) + }) + s.SetPort(8199) +} diff --git a/.example/util/gvalid/gvalid_result.go b/.example/util/gvalid/gvalid_result.go index 0190f0df4..ff9e01e6d 100644 --- a/.example/util/gvalid/gvalid_result.go +++ b/.example/util/gvalid/gvalid_result.go @@ -1,6 +1,7 @@ package main import ( + "context" "github.com/gogf/gf/frame/g" "github.com/gogf/gf/util/gvalid" ) @@ -18,7 +19,7 @@ func main() { Pass2: "123", } - e := gvalid.CheckStruct(user, nil) + e := gvalid.CheckStruct(context.TODO(), user, nil) g.Dump(e.String()) g.Dump(e.FirstString()) } diff --git a/.example/util/gvalid/gvalid_sequence.go b/.example/util/gvalid/gvalid_sequence.go index 8274cd1c9..0296a4ab5 100644 --- a/.example/util/gvalid/gvalid_sequence.go +++ b/.example/util/gvalid/gvalid_sequence.go @@ -1,6 +1,7 @@ package main import ( + "context" "fmt" "github.com/gogf/gf/util/gvalid" @@ -17,7 +18,7 @@ func main() { "password@required|length:6,16|same:password2#密码不能为空}|两次密码输入不相等", "password2@required|length:6,16#", } - if e := gvalid.CheckMap(params, rules); e != nil { + if e := gvalid.CheckMap(context.TODO(), params, rules); e != nil { fmt.Println(e.Map()) fmt.Println(e.FirstItem()) fmt.Println(e.FirstString()) diff --git a/.example/util/gvalid/gvalid_struct1.go b/.example/util/gvalid/gvalid_struct1.go index d992860ee..6995ec2be 100644 --- a/.example/util/gvalid/gvalid_struct1.go +++ b/.example/util/gvalid/gvalid_struct1.go @@ -1,6 +1,7 @@ package main import ( + "context" "github.com/gogf/gf/frame/g" "github.com/gogf/gf/util/gvalid" ) @@ -20,7 +21,7 @@ func main() { } // 使用结构体定义的校验规则和错误提示进行校验 - g.Dump(gvalid.CheckStruct(user, nil).Map()) + g.Dump(gvalid.CheckStruct(context.TODO(), user, nil).Map()) // 自定义校验规则和错误提示,对定义的特定校验规则和错误提示进行覆盖 rules := map[string]string{ @@ -31,5 +32,5 @@ func main() { "password3": "名称不能为空", }, } - g.Dump(gvalid.CheckStruct(user, rules, msgs).Map()) + g.Dump(gvalid.CheckStruct(context.TODO(), user, rules, msgs).Map()) } diff --git a/.example/util/gvalid/gvalid_struct2.go b/.example/util/gvalid/gvalid_struct2.go index a4c9442fb..8e6c11601 100644 --- a/.example/util/gvalid/gvalid_struct2.go +++ b/.example/util/gvalid/gvalid_struct2.go @@ -1,6 +1,7 @@ package main import ( + "context" "github.com/gogf/gf/frame/g" "github.com/gogf/gf/util/gvalid" ) @@ -13,5 +14,5 @@ func main() { user := &User{} - g.Dump(gvalid.CheckStruct(user, nil)) + g.Dump(gvalid.CheckStruct(context.TODO(), user, nil)) } diff --git a/.example/util/gvalid/gvalid_struct3.go b/.example/util/gvalid/gvalid_struct3.go index de9980ca3..fdd0ea965 100644 --- a/.example/util/gvalid/gvalid_struct3.go +++ b/.example/util/gvalid/gvalid_struct3.go @@ -1,6 +1,7 @@ package main import ( + "context" "github.com/gogf/gf/frame/g" "github.com/gogf/gf/util/gvalid" ) @@ -15,5 +16,5 @@ func main() { Pass: "1", } - g.Dump(gvalid.CheckStruct(user, nil).Maps()) + g.Dump(gvalid.CheckStruct(context.TODO(), user, nil).Maps()) } diff --git a/.example/util/gvalid/gvalid_struct_meta.go b/.example/util/gvalid/gvalid_struct_meta.go new file mode 100644 index 000000000..a08e8d8ca --- /dev/null +++ b/.example/util/gvalid/gvalid_struct_meta.go @@ -0,0 +1,40 @@ +package main + +import ( + "context" + "fmt" + "github.com/gogf/gf/errors/gerror" + "github.com/gogf/gf/frame/g" + "github.com/gogf/gf/util/gmeta" +) + +type UserCreateReq struct { + gmeta.Meta `v:"UserCreateReq"` + Name string + Pass string +} + +func UserCreateReqChecker(ctx context.Context, rule string, value interface{}, message string, data interface{}) error { + user := &UserCreateReq{} + if v, ok := data.(*UserCreateReq); ok { + user = v + } + // SELECT COUNT(*) FROM `user` WHERE `name` = xxx + count, err := g.Model("user").Ctx(ctx).Where("name", user.Name).Count() + if err != nil { + return err + } + if count > 0 { + return gerror.Newf(`The name "%s" is already token`, user.Name) + } + return nil +} + +func main() { + user := &UserCreateReq{ + Name: "john", + Pass: "123456", + } + err := g.Validator().RuleFunc("UserCreateReq", UserCreateReqChecker).CheckStruct(user) + fmt.Println(err) +} diff --git a/.example/util/gvalid/i18n/cn.toml b/.example/util/gvalid/i18n/cn.toml deleted file mode 100644 index 498f60e24..000000000 --- a/.example/util/gvalid/i18n/cn.toml +++ /dev/null @@ -1,14 +0,0 @@ - - -"gf.gvalid.required" = "字段不能为空" - - - - - - - - - - - diff --git a/.example/util/gvalid/i18n/en.toml b/.example/util/gvalid/i18n/en.toml new file mode 100644 index 000000000..7574fd595 --- /dev/null +++ b/.example/util/gvalid/i18n/en.toml @@ -0,0 +1,7 @@ +"gf.gvalid.required" = "字段不能为空" + + +"ReuiredUserName" = "Please input user name" +"ReuiredUserType" = "Please select user type" +"MustSize" = "Size of :attribute must be :size" + diff --git a/.example/util/gvalid/i18n/zh-CN.toml b/.example/util/gvalid/i18n/zh-CN.toml new file mode 100644 index 000000000..3db393e20 --- /dev/null +++ b/.example/util/gvalid/i18n/zh-CN.toml @@ -0,0 +1,16 @@ + + +"gf.gvalid.required" = "字段不能为空" + +"ReuiredUserName" = "请输入用户名称" +"ReuiredUserType" = "请选择用户类型" +"MustSize" = ":attribute长度必须为:size" + + + + + + + + + diff --git a/.gitee/ISSUE_TEMPLATE.MD b/.gitee/ISSUE_TEMPLATE.MD index 1197badda..4d24425c3 100644 --- a/.gitee/ISSUE_TEMPLATE.MD +++ b/.gitee/ISSUE_TEMPLATE.MD @@ -1,4 +1,7 @@ - + + + + ### 1. 您当前使用的`Go`版本,及系统版本、系统架构? diff --git a/.github/ISSUE_TEMPLATE.MD b/.github/ISSUE_TEMPLATE.MD index 09e907f4e..364044d16 100644 --- a/.github/ISSUE_TEMPLATE.MD +++ b/.github/ISSUE_TEMPLATE.MD @@ -1,5 +1,10 @@ + + + + + ### 1. What version of `Go` and system type/arch are you using? -### 3. Can this issue be reproduced with the latest release? +### 3. Can this issue be re-produced with the latest release? diff --git a/.github/workflows/go.yml b/.github/workflows/go.yml new file mode 100644 index 000000000..86fb55226 --- /dev/null +++ b/.github/workflows/go.yml @@ -0,0 +1,71 @@ +name: GoFrame CI + +on: + push: + branches: + - master + - develop + pull_request: + branches: [master, develop] +env: + GF_DEBUG: 1 + +jobs: + code-test: + runs-on: ubuntu-latest + # Service containers to run with `code-test` + services: + redis: + image : redis + options: >- + --health-cmd "redis-cli ping" + --health-interval 10s + --health-timeout 5s + --health-retries 5 + ports: + # Maps tcp port 6379 on service container to the host + - 6379:6379 + mysql: + image: mysql:5.7 + env: + MYSQL_DATABASE : test + MYSQL_ROOT_PASSWORD: 12345678 + ports: + # Maps tcp port 3306 on service container to the host + - 3306:3306 + + # strategy set + strategy: + matrix: + go: ["1.14", "1.15", "1.16"] + + steps: + - name: Set Up Timezone + uses: szenius/set-timezone@v1.0 + with: + timezoneLinux: "Asia/Shanghai" + + - name: Checkout Repositary + uses: actions/checkout@v2 + + - name: Set Up Go + uses: actions/setup-go@v2 + with: + go-version: ${{ matrix.go }} + + - name: Before Script + run: | + date + find . -name "*.go" | xargs gofmt -w + git diff --name-only --exit-code || exit 1 + sudo echo "127.0.0.1 local" | sudo tee -a /etc/hosts + + - name: Run i386 Arch Test + run: GOARCH=386 go test -v ./... || exit 1 + + - name: Run amd64 Arch Test + run: GOARCH=amd64 go test -v ./... -race -coverprofile=coverage.txt -covermode=atomic + + - name: Report Coverage + run: bash <(curl -s https://codecov.io/bash) + diff --git a/.gitignore b/.gitignore index a21ea7259..fdb500de8 100644 --- a/.gitignore +++ b/.gitignore @@ -14,5 +14,6 @@ bin/ cbuild **/.DS_Store .vscode/ -go.sum .example/other/ +main +gf \ No newline at end of file diff --git a/.travis.yml b/.travis.yml index ee2f52ac1..d11a3c960 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,11 +1,12 @@ +os: linux +arch: arm64-graviton2 + language: go go: - - "1.11.x" - - "1.12.x" - - "1.13.x" - "1.14.x" - "1.15.x" + - "1.16.x" branches: only: @@ -14,7 +15,7 @@ branches: - staging env: -- GF_DEBUG=1 GO111MODULE=on +- TZ=Asia/Shanghai GF_DEBUG=1 GO111MODULE=on services: - mysql diff --git a/README.MD b/README.MD index cdc2e6ac1..dda9ad1a5 100644 --- a/README.MD +++ b/README.MD @@ -1,7 +1,7 @@ # GoFrame [![Go Doc](https://godoc.org/github.com/gogf/gf?status.svg)](https://godoc.org/github.com/gogf/gf) -[![Build Status](https://travis-ci.org/gogf/gf.svg?branch=master)](https://travis-ci.org/gogf/gf) +[![GoFrame CI](https://github.com/gogf/gf/actions/workflows/go.yml/badge.svg)](https://github.com/gogf/gf/actions/workflows/go.yml) [![Go Report](https://goreportcard.com/badge/github.com/gogf/gf?v=1)](https://goreportcard.com/report/github.com/gogf/gf) [![Code Coverage](https://codecov.io/gh/gogf/gf/branch/master/graph/badge.svg)](https://codecov.io/gh/gogf/gf/branch/master) [![Production Ready](https://img.shields.io/badge/production-ready-blue.svg)](https://github.com/gogf/gf) @@ -9,11 +9,8 @@ English | [简体中文](README_ZH.MD) -`GF(GoFrame)` is a modular, powerful, high-performance and enterprise-class application development framework -of Golang. Providing a series of core components and dozens of practical modules, such as: -cache, logging, containers, timer, resource, validator, database orm, etc. -Supporting web server integrated with router, cookie, session, middleware, logger, configure, -template, https, hooks, rewrites and many more features. +`GoFrame` is a modular, powerful, high-performance and enterprise-class application development framework +of Golang. > If you're a newbie to `Go`, you may consider `GoFrame` easy and great as `Laravel` in `PHP`, `SpringBoot` in `Java` or `Django` in `Python`. @@ -33,7 +30,7 @@ golang version >= 1.11 # Architecture
- +
# Packages @@ -53,17 +50,10 @@ The `Web` component performance of `GoFrame`, please refer to third-party projec # Documentation -* 中文官网: https://goframe.org -* GoDoc API: https://godoc.org/github.com/gogf/gf +* 中文官网: [https://goframe.org](https://goframe.org/display/gf) +* GoDoc API: [https://pkg.go.dev/github.com/gogf/gf](https://pkg.go.dev/github.com/gogf/gf) -# Discussion -- QQ Group:[116707870](//shang.qq.com/wpa/qunwpa?idkey=195f91eceeb5d7fa76009b7cd5a4641f70bf4897b7f5a520635eb26ff17adfe7) -- WX Group:Add friend`389961817` in WeChat, commenting `GF` -- Issues:https://github.com/gogf/gf/issues - -> It's recommended learning `GoFrame` through its awesome source codes and API reference. - # License `GF` is licensed under the [MIT License](LICENSE), 100% free and open-source, forever. @@ -80,7 +70,7 @@ The `Web` component performance of `GoFrame`, please refer to third-party projec - [XiMaLaYa](https://www.ximalaya.com) - [ZYBang](https://www.zybang.com/) -> We list part of the users here, if your company or products are using `GoFrame`, please let us know [here](https://github.com/gogf/gf/issues/168). +> We list part of the users here, if your company or products are using `GoFrame`, please let us know [here](https://goframe.org/pages/viewpage.action?pageId=1114415). # Contributors @@ -90,7 +80,7 @@ This project exists thanks to all the people who contribute. [[Contributors](htt # Donators -If you love `GF`, why not [buy developer a cup of coffee](https://itician.org/pages/viewpage.action?pageId=1115633)? +If you love `GF`, why not [buy developer a cup of coffee](https://goframe.org/pages/viewpage.action?pageId=1115633)? # Sponsors We appreciate any kind of sponsorship for `GF` development. If you've got some interesting, please contact WeChat `389961817` / Email `john@goframe.org`. @@ -98,8 +88,8 @@ We appreciate any kind of sponsorship for `GF` development. If you've got some i # Thanks -JetBrains -Atlassian +JetBrains +Atlassian diff --git a/README_ZH.MD b/README_ZH.MD index 77f472620..79b22cfde 100644 --- a/README_ZH.MD +++ b/README_ZH.MD @@ -1,6 +1,6 @@ # GoFrame [![Go Doc](https://godoc.org/github.com/gogf/gf?status.svg)](https://godoc.org/github.com/gogf/gf) -[![Build Status](https://travis-ci.org/gogf/gf.svg?branch=master)](https://travis-ci.org/gogf/gf) +[![Build Status](https://travis-ci.com/gogf/gf.svg?branch=master)](https://travis-ci.org/gogf/gf) [![Go Report](https://goreportcard.com/badge/github.com/gogf/gf?v=1)](https://goreportcard.com/report/github.com/gogf/gf) [![Code Coverage](https://codecov.io/gh/gogf/gf/branch/master/graph/badge.svg)](https://codecov.io/gh/gogf/gf/branch/master) [![Production Ready](https://img.shields.io/badge/production-ready-blue.svg)](https://github.com/gogf/gf) @@ -8,24 +8,21 @@ [English](README.MD) | 简体中文 -`GF(Go Frame)`是一款模块化、高性能、企业级的Go基础开发框架。 -实现了比较完善的基础设施建设以及开发工具链,提供了常用的基础开发模块, -如:缓存、日志、队列、数组、集合、容器、定时器、命令行、内存锁、对象池、 -配置管理、资源管理、数据校验、数据编码、定时任务、数据库ORM、TCP/UDP组件、进程管理/通信等等。 -并提供了Web服务开发的系列核心组件,如:Router、Cookie、Session、Middleware、服务注册、模板引擎等等, -支持热重启、热更新、域名绑定、TLS/HTTPS、Rewrite等特性。 +`GoFrame`是一款模块化、高性能、企业级的Go基础开发框架。 > 如果您初识`Go`语言,您可以将`GoFrame`类似于`PHP`中的`Laravel`, `Java`中的`SpringBoot`或者`Python`中的`Django`。 # 特点 -* 模块化、松耦合设计; -* 模块丰富、开箱即用; -* 简便易用、易于维护; -* 高代码质量、高单元测试覆盖率; -* 社区活跃,大牛谦逊低调脾气好; -* 详尽的开发文档及示例; -* 完善的本地中文化支持; -* 设计为团队及企业使用; +* 模块化、松耦合 +* 模块丰富、开箱即用 +* 简洁易用、快速接入 +* 文档详尽、易于维护 +* 自顶向下、体系化设计 +* 统一框架、统一组件、降低选择成本 +* 开发规范、设计模式、代码分层模型 +* 强大便捷的开发工具链 +* 完善的本地中文化支持 +* 设计为团队及企业使用 # 地址 - **主库**:https://github.com/gogf/gf @@ -49,7 +46,7 @@ golang版本 >= 1.11 # 架构
- +
# 模块 @@ -64,21 +61,14 @@ golang版本 >= 1.11 # 性能 - -`Web`组件的性能测试,请参考第三方性能测试评估:https://github.com/the-benchmarker/web-frameworks +大家较为感兴趣的`Web`组件性能测试,请参考第三方性能测试评估:https://github.com/the-benchmarker/web-frameworks # 文档 -开发文档:https://goframe.org +官方站点:[https://goframe.org](https://goframe.org/display/gf) -接口文档:https://godoc.org/github.com/gogf/gf +接口文档:[https://pkg.go.dev/github.com/gogf/gf](https://pkg.go.dev/github.com/gogf/gf) -# 帮助 -- QQ交流群:[116707870](//shang.qq.com/wpa/qunwpa?idkey=195f91eceeb5d7fa76009b7cd5a4641f70bf4897b7f5a520635eb26ff17adfe7) -- WX交流群:微信添加`389961817`备注`GF` -- 主库ISSUE:https://github.com/gogf/gf/issues - -> 建议通过阅读`GoFrame`的源码以及API文档深度学习`GoFrame`,了解更多的精妙设计。 # 协议 @@ -87,7 +77,7 @@ golang版本 >= 1.11 # 用户 - [腾讯科技](https://www.tencent.com/) -- [中兴科技](https://www.zte.com.cn/china/) +- [中兴通讯](https://www.zte.com.cn/china/) - [蚂蚁金服](https://www.antfin.com/) - [医联科技](https://www.medlinker.com/) - [库币科技](https://www.kucoin.io/) @@ -96,7 +86,7 @@ golang版本 >= 1.11 - [喜马拉雅](https://www.ximalaya.com) - [作业帮](https://www.zybang.com/) -> 在这里只列举了部分知名的用户,如果您的企业或者产品正在使用`GoFrame`,欢迎到 [这里](https://github.com/gogf/gf/issues/168) 留言。 +> 在这里只列举了部分知名的用户,如果您的企业或者产品正在使用`GoFrame`,欢迎到 [这里](https://goframe.org/pages/viewpage.action?pageId=1114415) 留言。 # 贡献 @@ -106,7 +96,7 @@ golang版本 >= 1.11 # 捐赠 -如果您喜欢`GF`,要不给开发者 [来杯咖啡](https://itician.org/pages/viewpage.action?pageId=1115633) 吧! +如果您喜欢`GF`,要不给开发者 [来杯咖啡](https://goframe.org/pages/viewpage.action?pageId=1115633) 吧! 请在捐赠时备注您的`github`/`gitee`账号名称。 # 赞助 @@ -114,5 +104,6 @@ golang版本 >= 1.11 赞助支持`GF`框架的快速研发,如果您感兴趣,请联系 微信 `389961817` / 邮件 `john@goframe.org`。 # 感谢 -JetBrains -Atlassian +JetBrains +Atlassian + diff --git a/container/garray/garray.go b/container/garray/garray.go index 8cc88e71b..08e9ece89 100644 --- a/container/garray/garray.go +++ b/container/garray/garray.go @@ -1,4 +1,4 @@ -// Copyright 2018 gf Author(https://github.com/gogf/gf). All Rights Reserved. +// Copyright GoFrame Author(https://goframe.org). All Rights Reserved. // // This Source Code Form is subject to the terms of the MIT License. // If a copy of the MIT was not distributed with this file, diff --git a/container/garray/garray_func.go b/container/garray/garray_func.go index 572cba444..d3e7b7795 100644 --- a/container/garray/garray_func.go +++ b/container/garray/garray_func.go @@ -1,4 +1,4 @@ -// Copyright 2019 gf Author(https://github.com/gogf/gf). All Rights Reserved. +// Copyright GoFrame Author(https://goframe.org). All Rights Reserved. // // This Source Code Form is subject to the terms of the MIT License. // If a copy of the MIT was not distributed with this file, diff --git a/container/garray/garray_normal_any.go b/container/garray/garray_normal_any.go index 4c41cc2d4..07b33c813 100644 --- a/container/garray/garray_normal_any.go +++ b/container/garray/garray_normal_any.go @@ -1,4 +1,4 @@ -// Copyright 2018 gf Author(https://github.com/gogf/gf). All Rights Reserved. +// Copyright GoFrame Author(https://goframe.org). All Rights Reserved. // // This Source Code Form is subject to the terms of the MIT License. // If a copy of the MIT was not distributed with this file, @@ -8,8 +8,9 @@ package garray import ( "bytes" - "errors" "fmt" + "github.com/gogf/gf/errors/gcode" + "github.com/gogf/gf/errors/gerror" "github.com/gogf/gf/internal/empty" "github.com/gogf/gf/internal/json" "github.com/gogf/gf/text/gstr" @@ -30,19 +31,19 @@ type Array struct { } // New creates and returns an empty array. -// The parameter is used to specify whether using array in concurrent-safety, +// The parameter `safe` is used to specify whether using array in concurrent-safety, // which is false in default. func New(safe ...bool) *Array { return NewArraySize(0, 0, safe...) } -// See New. +// NewArray is alias of New, please see New. func NewArray(safe ...bool) *Array { return NewArraySize(0, 0, safe...) } // NewArraySize create and returns an array with given size and cap. -// The parameter is used to specify whether using array in concurrent-safety, +// The parameter `safe` is used to specify whether using array in concurrent-safety, // which is false in default. func NewArraySize(size int, cap int, safe ...bool) *Array { return &Array{ @@ -51,8 +52,8 @@ func NewArraySize(size int, cap int, safe ...bool) *Array { } } -// NewArrayRange creates and returns a array by a range from to -// with step value . +// NewArrayRange creates and returns a array by a range from `start` to `end` +// with step value `step`. func NewArrayRange(start, end, step int, safe ...bool) *Array { if step == 0 { panic(fmt.Sprintf(`invalid step value: %d`, step)) @@ -66,18 +67,20 @@ func NewArrayRange(start, end, step int, safe ...bool) *Array { return NewArrayFrom(slice, safe...) } +// NewFrom is alias of NewArrayFrom. // See NewArrayFrom. func NewFrom(array []interface{}, safe ...bool) *Array { return NewArrayFrom(array, safe...) } +// NewFromCopy is alias of NewArrayFromCopy. // See NewArrayFromCopy. func NewFromCopy(array []interface{}, safe ...bool) *Array { return NewArrayFromCopy(array, safe...) } -// NewArrayFrom creates and returns an array with given slice . -// The parameter is used to specify whether using array in concurrent-safety, +// NewArrayFrom creates and returns an array with given slice `array`. +// The parameter `safe` is used to specify whether using array in concurrent-safety, // which is false in default. func NewArrayFrom(array []interface{}, safe ...bool) *Array { return &Array{ @@ -86,8 +89,8 @@ func NewArrayFrom(array []interface{}, safe ...bool) *Array { } } -// NewArrayFromCopy creates and returns an array from a copy of given slice . -// The parameter is used to specify whether using array in concurrent-safety, +// NewArrayFromCopy creates and returns an array from a copy of given slice `array`. +// The parameter `safe` is used to specify whether using array in concurrent-safety, // which is false in default. func NewArrayFromCopy(array []interface{}, safe ...bool) *Array { newArray := make([]interface{}, len(array)) @@ -98,8 +101,15 @@ func NewArrayFromCopy(array []interface{}, safe ...bool) *Array { } } +// At returns the value by the specified index. +// If the given `index` is out of range of the array, it returns `nil`. +func (a *Array) At(index int) (value interface{}) { + value, _ = a.Get(index) + return +} + // Get returns the value by the specified index. -// If the given is out of range of the array, the is false. +// If the given `index` is out of range of the array, the `found` is false. func (a *Array) Get(index int) (value interface{}, found bool) { a.mu.RLock() defer a.mu.RUnlock() @@ -114,13 +124,13 @@ func (a *Array) Set(index int, value interface{}) error { a.mu.Lock() defer a.mu.Unlock() if index < 0 || index >= len(a.array) { - return errors.New(fmt.Sprintf("index %d out of array range %d", index, len(a.array))) + return gerror.NewCodef(gcode.CodeInvalidParameter, "index %d out of array range %d", index, len(a.array)) } a.array[index] = value return nil } -// SetArray sets the underlying slice array with the given . +// SetArray sets the underlying slice array with the given `array`. func (a *Array) SetArray(array []interface{}) *Array { a.mu.Lock() defer a.mu.Unlock() @@ -128,7 +138,7 @@ func (a *Array) SetArray(array []interface{}) *Array { return a } -// Replace replaces the array items by given from the beginning of array. +// Replace replaces the array items by given `array` from the beginning of array. func (a *Array) Replace(array []interface{}) *Array { a.mu.Lock() defer a.mu.Unlock() @@ -152,7 +162,7 @@ func (a *Array) Sum() (sum int) { return } -// SortFunc sorts the array by custom function . +// SortFunc sorts the array by custom function `less`. func (a *Array) SortFunc(less func(v1, v2 interface{}) bool) *Array { a.mu.Lock() defer a.mu.Unlock() @@ -162,12 +172,12 @@ func (a *Array) SortFunc(less func(v1, v2 interface{}) bool) *Array { return a } -// InsertBefore inserts the to the front of . +// InsertBefore inserts the `value` to the front of `index`. func (a *Array) InsertBefore(index int, value interface{}) error { a.mu.Lock() defer a.mu.Unlock() if index < 0 || index >= len(a.array) { - return errors.New(fmt.Sprintf("index %d out of array range %d", index, len(a.array))) + return gerror.NewCodef(gcode.CodeInvalidParameter, "index %d out of array range %d", index, len(a.array)) } rear := append([]interface{}{}, a.array[index:]...) a.array = append(a.array[0:index], value) @@ -175,12 +185,12 @@ func (a *Array) InsertBefore(index int, value interface{}) error { return nil } -// InsertAfter inserts the to the back of . +// InsertAfter inserts the `value` to the back of `index`. func (a *Array) InsertAfter(index int, value interface{}) error { a.mu.Lock() defer a.mu.Unlock() if index < 0 || index >= len(a.array) { - return errors.New(fmt.Sprintf("index %d out of array range %d", index, len(a.array))) + return gerror.NewCodef(gcode.CodeInvalidParameter, "index %d out of array range %d", index, len(a.array)) } rear := append([]interface{}{}, a.array[index+1:]...) a.array = append(a.array[0:index+1], value) @@ -189,7 +199,7 @@ func (a *Array) InsertAfter(index int, value interface{}) error { } // Remove removes an item by index. -// If the given is out of range of the array, the is false. +// If the given `index` is out of range of the array, the `found` is false. func (a *Array) Remove(index int) (value interface{}, found bool) { a.mu.Lock() defer a.mu.Unlock() @@ -247,14 +257,14 @@ func (a *Array) PushRight(value ...interface{}) *Array { } // PopRand randomly pops and return an item out of array. -// Note that if the array is empty, the is false. +// Note that if the array is empty, the `found` is false. func (a *Array) PopRand() (value interface{}, found bool) { a.mu.Lock() defer a.mu.Unlock() return a.doRemoveWithoutLock(grand.Intn(len(a.array))) } -// PopRands randomly pops and returns items out of array. +// PopRands randomly pops and returns `size` items out of array. func (a *Array) PopRands(size int) []interface{} { a.mu.Lock() defer a.mu.Unlock() @@ -272,7 +282,7 @@ func (a *Array) PopRands(size int) []interface{} { } // PopLeft pops and returns an item from the beginning of array. -// Note that if the array is empty, the is false. +// Note that if the array is empty, the `found` is false. func (a *Array) PopLeft() (value interface{}, found bool) { a.mu.Lock() defer a.mu.Unlock() @@ -285,7 +295,7 @@ func (a *Array) PopLeft() (value interface{}, found bool) { } // PopRight pops and returns an item from the end of array. -// Note that if the array is empty, the is false. +// Note that if the array is empty, the `found` is false. func (a *Array) PopRight() (value interface{}, found bool) { a.mu.Lock() defer a.mu.Unlock() @@ -298,7 +308,7 @@ func (a *Array) PopRight() (value interface{}, found bool) { return value, true } -// PopLefts pops and returns items from the beginning of array. +// PopLefts pops and returns `size` items from the beginning of array. func (a *Array) PopLefts(size int) []interface{} { a.mu.Lock() defer a.mu.Unlock() @@ -315,7 +325,7 @@ func (a *Array) PopLefts(size int) []interface{} { return value } -// PopRights pops and returns items from the end of array. +// PopRights pops and returns `size` items from the end of array. func (a *Array) PopRights(size int) []interface{} { a.mu.Lock() defer a.mu.Unlock() @@ -337,8 +347,8 @@ func (a *Array) PopRights(size int) []interface{} { // Notice, if in concurrent-safe usage, it returns a copy of slice; // else a pointer to the underlying data. // -// If is negative, then the offset will start from the end of array. -// If is omitted, then the sequence will have everything from start up +// If `end` is negative, then the offset will start from the end of array. +// If `end` is omitted, then the sequence will have everything from start up // until the end of the array. func (a *Array) Range(start int, end ...int) []interface{} { a.mu.RLock() @@ -364,7 +374,7 @@ func (a *Array) Range(start int, end ...int) []interface{} { } // SubSlice returns a slice of elements from the array as specified -// by the and parameters. +// by the `offset` and `size` parameters. // If in concurrent safe usage, it returns a copy of the slice; else a pointer. // // If offset is non-negative, the sequence will start at that offset in the array. @@ -413,7 +423,7 @@ func (a *Array) SubSlice(offset int, length ...int) []interface{} { } } -// See PushRight. +// Append is alias of PushRight, please See PushRight. func (a *Array) Append(value ...interface{}) *Array { a.PushRight(value...) return a @@ -471,7 +481,7 @@ func (a *Array) Contains(value interface{}) bool { return a.Search(value) != -1 } -// Search searches array by , returns the index of , +// Search searches array by `value`, returns the index of `value`, // or returns -1 if not exists. func (a *Array) Search(value interface{}) int { a.mu.RLock() @@ -506,7 +516,7 @@ func (a *Array) Unique() *Array { return a } -// LockFunc locks writing by callback function . +// LockFunc locks writing by callback function `f`. func (a *Array) LockFunc(f func(array []interface{})) *Array { a.mu.Lock() defer a.mu.Unlock() @@ -514,7 +524,7 @@ func (a *Array) LockFunc(f func(array []interface{})) *Array { return a } -// RLockFunc locks reading by callback function . +// RLockFunc locks reading by callback function `f`. func (a *Array) RLockFunc(f func(array []interface{})) *Array { a.mu.RLock() defer a.mu.RUnlock() @@ -522,21 +532,21 @@ func (a *Array) RLockFunc(f func(array []interface{})) *Array { return a } -// Merge merges into current array. -// The parameter can be any garray or slice type. +// Merge merges `array` into current array. +// The parameter `array` can be any garray or slice type. // The difference between Merge and Append is Append supports only specified slice type, // but Merge supports more parameter types. func (a *Array) Merge(array interface{}) *Array { return a.Append(gconv.Interfaces(array)...) } -// Fill fills an array with num entries of the value , -// keys starting at the parameter. +// Fill fills an array with num entries of the value `value`, +// keys starting at the `startIndex` parameter. func (a *Array) Fill(startIndex int, num int, value interface{}) error { a.mu.Lock() defer a.mu.Unlock() if startIndex < 0 || startIndex > len(a.array) { - return errors.New(fmt.Sprintf("index %d out of array range %d", startIndex, len(a.array))) + return gerror.NewCodef(gcode.CodeInvalidParameter, "index %d out of array range %d", startIndex, len(a.array)) } for i := startIndex; i < startIndex+num; i++ { if i > len(a.array)-1 { @@ -549,7 +559,7 @@ func (a *Array) Fill(startIndex int, num int, value interface{}) error { } // Chunk splits an array into multiple arrays, -// the size of each array is determined by . +// the size of each array is determined by `size`. // The last chunk may contain less than size elements. func (a *Array) Chunk(size int) [][]interface{} { if size < 1 { @@ -571,9 +581,9 @@ func (a *Array) Chunk(size int) [][]interface{} { return n } -// Pad pads array to the specified length with . +// Pad pads array to the specified length with `value`. // If size is positive then the array is padded on the right, or negative on the left. -// If the absolute value of is less than or equal to the length of the array +// If the absolute value of `size` is less than or equal to the length of the array // then no padding takes place. func (a *Array) Pad(size int, val interface{}) *Array { a.mu.Lock() @@ -608,7 +618,7 @@ func (a *Array) Rand() (value interface{}, found bool) { return a.array[grand.Intn(len(a.array))], true } -// Rands randomly returns items from array(no deleting). +// Rands randomly returns `size` items from array(no deleting). func (a *Array) Rands(size int) []interface{} { a.mu.RLock() defer a.mu.RUnlock() @@ -642,7 +652,7 @@ func (a *Array) Reverse() *Array { return a } -// Join joins array elements with a string . +// Join joins array elements with a string `glue`. func (a *Array) Join(glue string) string { a.mu.RLock() defer a.mu.RUnlock() @@ -675,8 +685,8 @@ func (a *Array) Iterator(f func(k int, v interface{}) bool) { a.IteratorAsc(f) } -// IteratorAsc iterates the array readonly in ascending order with given callback function . -// If returns true, then it continues iterating; or false to stop. +// IteratorAsc iterates the array readonly in ascending order with given callback function `f`. +// If `f` returns true, then it continues iterating; or false to stop. func (a *Array) IteratorAsc(f func(k int, v interface{}) bool) { a.mu.RLock() defer a.mu.RUnlock() @@ -687,8 +697,8 @@ func (a *Array) IteratorAsc(f func(k int, v interface{}) bool) { } } -// IteratorDesc iterates the array readonly in descending order with given callback function . -// If returns true, then it continues iterating; or false to stop. +// IteratorDesc iterates the array readonly in descending order with given callback function `f`. +// If `f` returns true, then it continues iterating; or false to stop. func (a *Array) IteratorDesc(f func(k int, v interface{}) bool) { a.mu.RLock() defer a.mu.RUnlock() @@ -736,7 +746,7 @@ func (a *Array) UnmarshalJSON(b []byte) error { } a.mu.Lock() defer a.mu.Unlock() - if err := json.Unmarshal(b, &a.array); err != nil { + if err := json.UnmarshalUseNumber(b, &a.array); err != nil { return err } return nil @@ -748,7 +758,7 @@ func (a *Array) UnmarshalValue(value interface{}) error { defer a.mu.Unlock() switch value.(type) { case string, []byte: - return json.Unmarshal(gconv.Bytes(value), &a.array) + return json.UnmarshalUseNumber(gconv.Bytes(value), &a.array) default: a.array = gconv.SliceAny(value) } @@ -784,7 +794,7 @@ func (a *Array) FilterEmpty() *Array { return a } -// Walk applies a user supplied function to every item of array. +// Walk applies a user supplied function `f` to every item of array. func (a *Array) Walk(f func(value interface{}) interface{}) *Array { a.mu.Lock() defer a.mu.Unlock() diff --git a/container/garray/garray_normal_int.go b/container/garray/garray_normal_int.go index 69d4c157a..ee3d173ba 100644 --- a/container/garray/garray_normal_int.go +++ b/container/garray/garray_normal_int.go @@ -1,4 +1,4 @@ -// Copyright 2018 gf Author(https://github.com/gogf/gf). All Rights Reserved. +// Copyright GoFrame Author(https://goframe.org). All Rights Reserved. // // This Source Code Form is subject to the terms of the MIT License. // If a copy of the MIT was not distributed with this file, @@ -8,8 +8,9 @@ package garray import ( "bytes" - "errors" "fmt" + "github.com/gogf/gf/errors/gcode" + "github.com/gogf/gf/errors/gerror" "github.com/gogf/gf/internal/json" "math" "sort" @@ -28,14 +29,14 @@ type IntArray struct { } // NewIntArray creates and returns an empty array. -// The parameter is used to specify whether using array in concurrent-safety, +// The parameter `safe` is used to specify whether using array in concurrent-safety, // which is false in default. func NewIntArray(safe ...bool) *IntArray { return NewIntArraySize(0, 0, safe...) } // NewIntArraySize create and returns an array with given size and cap. -// The parameter is used to specify whether using array in concurrent-safety, +// The parameter `safe` is used to specify whether using array in concurrent-safety, // which is false in default. func NewIntArraySize(size int, cap int, safe ...bool) *IntArray { return &IntArray{ @@ -44,8 +45,8 @@ func NewIntArraySize(size int, cap int, safe ...bool) *IntArray { } } -// NewIntArrayRange creates and returns a array by a range from to -// with step value . +// NewIntArrayRange creates and returns a array by a range from `start` to `end` +// with step value `step`. func NewIntArrayRange(start, end, step int, safe ...bool) *IntArray { if step == 0 { panic(fmt.Sprintf(`invalid step value: %d`, step)) @@ -59,8 +60,8 @@ func NewIntArrayRange(start, end, step int, safe ...bool) *IntArray { return NewIntArrayFrom(slice, safe...) } -// NewIntArrayFrom creates and returns an array with given slice . -// The parameter is used to specify whether using array in concurrent-safety, +// NewIntArrayFrom creates and returns an array with given slice `array`. +// The parameter `safe` is used to specify whether using array in concurrent-safety, // which is false in default. func NewIntArrayFrom(array []int, safe ...bool) *IntArray { return &IntArray{ @@ -69,8 +70,8 @@ func NewIntArrayFrom(array []int, safe ...bool) *IntArray { } } -// NewIntArrayFromCopy creates and returns an array from a copy of given slice . -// The parameter is used to specify whether using array in concurrent-safety, +// NewIntArrayFromCopy creates and returns an array from a copy of given slice `array`. +// The parameter `safe` is used to specify whether using array in concurrent-safety, // which is false in default. func NewIntArrayFromCopy(array []int, safe ...bool) *IntArray { newArray := make([]int, len(array)) @@ -81,8 +82,15 @@ func NewIntArrayFromCopy(array []int, safe ...bool) *IntArray { } } +// At returns the value by the specified index. +// If the given `index` is out of range of the array, it returns `0`. +func (a *IntArray) At(index int) (value int) { + value, _ = a.Get(index) + return +} + // Get returns the value by the specified index. -// If the given is out of range of the array, the is false. +// If the given `index` is out of range of the array, the `found` is false. func (a *IntArray) Get(index int) (value int, found bool) { a.mu.RLock() defer a.mu.RUnlock() @@ -97,13 +105,13 @@ func (a *IntArray) Set(index int, value int) error { a.mu.Lock() defer a.mu.Unlock() if index < 0 || index >= len(a.array) { - return errors.New(fmt.Sprintf("index %d out of array range %d", index, len(a.array))) + return gerror.NewCodef(gcode.CodeInvalidParameter, "index %d out of array range %d", index, len(a.array)) } a.array[index] = value return nil } -// SetArray sets the underlying slice array with the given . +// SetArray sets the underlying slice array with the given `array`. func (a *IntArray) SetArray(array []int) *IntArray { a.mu.Lock() defer a.mu.Unlock() @@ -111,7 +119,7 @@ func (a *IntArray) SetArray(array []int) *IntArray { return a } -// Replace replaces the array items by given from the beginning of array. +// Replace replaces the array items by given `array` from the beginning of array. func (a *IntArray) Replace(array []int) *IntArray { a.mu.Lock() defer a.mu.Unlock() @@ -136,16 +144,13 @@ func (a *IntArray) Sum() (sum int) { } // Sort sorts the array in increasing order. -// The parameter controls whether sort in increasing order(default) or decreasing order. +// The parameter `reverse` controls whether sort in increasing order(default) or decreasing order. func (a *IntArray) Sort(reverse ...bool) *IntArray { a.mu.Lock() defer a.mu.Unlock() if len(reverse) > 0 && reverse[0] { sort.Slice(a.array, func(i, j int) bool { - if a.array[i] < a.array[j] { - return false - } - return true + return a.array[i] >= a.array[j] }) } else { sort.Ints(a.array) @@ -153,7 +158,7 @@ func (a *IntArray) Sort(reverse ...bool) *IntArray { return a } -// SortFunc sorts the array by custom function . +// SortFunc sorts the array by custom function `less`. func (a *IntArray) SortFunc(less func(v1, v2 int) bool) *IntArray { a.mu.Lock() defer a.mu.Unlock() @@ -163,12 +168,12 @@ func (a *IntArray) SortFunc(less func(v1, v2 int) bool) *IntArray { return a } -// InsertBefore inserts the to the front of . +// InsertBefore inserts the `value` to the front of `index`. func (a *IntArray) InsertBefore(index int, value int) error { a.mu.Lock() defer a.mu.Unlock() if index < 0 || index >= len(a.array) { - return errors.New(fmt.Sprintf("index %d out of array range %d", index, len(a.array))) + return gerror.NewCodef(gcode.CodeInvalidParameter, "index %d out of array range %d", index, len(a.array)) } rear := append([]int{}, a.array[index:]...) a.array = append(a.array[0:index], value) @@ -176,12 +181,12 @@ func (a *IntArray) InsertBefore(index int, value int) error { return nil } -// InsertAfter inserts the to the back of . +// InsertAfter inserts the `value` to the back of `index`. func (a *IntArray) InsertAfter(index int, value int) error { a.mu.Lock() defer a.mu.Unlock() if index < 0 || index >= len(a.array) { - return errors.New(fmt.Sprintf("index %d out of array range %d", index, len(a.array))) + return gerror.NewCodef(gcode.CodeInvalidParameter, "index %d out of array range %d", index, len(a.array)) } rear := append([]int{}, a.array[index+1:]...) a.array = append(a.array[0:index+1], value) @@ -190,7 +195,7 @@ func (a *IntArray) InsertAfter(index int, value int) error { } // Remove removes an item by index. -// If the given is out of range of the array, the is false. +// If the given `index` is out of range of the array, the `found` is false. func (a *IntArray) Remove(index int) (value int, found bool) { a.mu.Lock() defer a.mu.Unlock() @@ -248,7 +253,7 @@ func (a *IntArray) PushRight(value ...int) *IntArray { } // PopLeft pops and returns an item from the beginning of array. -// Note that if the array is empty, the is false. +// Note that if the array is empty, the `found` is false. func (a *IntArray) PopLeft() (value int, found bool) { a.mu.Lock() defer a.mu.Unlock() @@ -261,7 +266,7 @@ func (a *IntArray) PopLeft() (value int, found bool) { } // PopRight pops and returns an item from the end of array. -// Note that if the array is empty, the is false. +// Note that if the array is empty, the `found` is false. func (a *IntArray) PopRight() (value int, found bool) { a.mu.Lock() defer a.mu.Unlock() @@ -275,16 +280,16 @@ func (a *IntArray) PopRight() (value int, found bool) { } // PopRand randomly pops and return an item out of array. -// Note that if the array is empty, the is false. +// Note that if the array is empty, the `found` is false. func (a *IntArray) PopRand() (value int, found bool) { a.mu.Lock() defer a.mu.Unlock() return a.doRemoveWithoutLock(grand.Intn(len(a.array))) } -// PopRands randomly pops and returns items out of array. -// If the given is greater than size of the array, it returns all elements of the array. -// Note that if given <= 0 or the array is empty, it returns nil. +// PopRands randomly pops and returns `size` items out of array. +// If the given `size` is greater than size of the array, it returns all elements of the array. +// Note that if given `size` <= 0 or the array is empty, it returns nil. func (a *IntArray) PopRands(size int) []int { a.mu.Lock() defer a.mu.Unlock() @@ -301,9 +306,9 @@ func (a *IntArray) PopRands(size int) []int { return array } -// PopLefts pops and returns items from the beginning of array. -// If the given is greater than size of the array, it returns all elements of the array. -// Note that if given <= 0 or the array is empty, it returns nil. +// PopLefts pops and returns `size` items from the beginning of array. +// If the given `size` is greater than size of the array, it returns all elements of the array. +// Note that if given `size` <= 0 or the array is empty, it returns nil. func (a *IntArray) PopLefts(size int) []int { a.mu.Lock() defer a.mu.Unlock() @@ -320,9 +325,9 @@ func (a *IntArray) PopLefts(size int) []int { return value } -// PopRights pops and returns items from the end of array. -// If the given is greater than size of the array, it returns all elements of the array. -// Note that if given <= 0 or the array is empty, it returns nil. +// PopRights pops and returns `size` items from the end of array. +// If the given `size` is greater than size of the array, it returns all elements of the array. +// Note that if given `size` <= 0 or the array is empty, it returns nil. func (a *IntArray) PopRights(size int) []int { a.mu.Lock() defer a.mu.Unlock() @@ -344,8 +349,8 @@ func (a *IntArray) PopRights(size int) []int { // Notice, if in concurrent-safe usage, it returns a copy of slice; // else a pointer to the underlying data. // -// If is negative, then the offset will start from the end of array. -// If is omitted, then the sequence will have everything from start up +// If `end` is negative, then the offset will start from the end of array. +// If `end` is omitted, then the sequence will have everything from start up // until the end of the array. func (a *IntArray) Range(start int, end ...int) []int { a.mu.RLock() @@ -371,7 +376,7 @@ func (a *IntArray) Range(start int, end ...int) []int { } // SubSlice returns a slice of elements from the array as specified -// by the and parameters. +// by the `offset` and `size` parameters. // If in concurrent safe usage, it returns a copy of the slice; else a pointer. // // If offset is non-negative, the sequence will start at that offset in the array. @@ -420,7 +425,7 @@ func (a *IntArray) SubSlice(offset int, length ...int) []int { } } -// See PushRight. +// Append is alias of PushRight,please See PushRight. func (a *IntArray) Append(value ...int) *IntArray { a.mu.Lock() a.array = append(a.array, value...) @@ -487,7 +492,7 @@ func (a *IntArray) Contains(value int) bool { return a.Search(value) != -1 } -// Search searches array by , returns the index of , +// Search searches array by `value`, returns the index of `value`, // or returns -1 if not exists. func (a *IntArray) Search(value int) int { a.mu.RLock() @@ -522,7 +527,7 @@ func (a *IntArray) Unique() *IntArray { return a } -// LockFunc locks writing by callback function . +// LockFunc locks writing by callback function `f`. func (a *IntArray) LockFunc(f func(array []int)) *IntArray { a.mu.Lock() defer a.mu.Unlock() @@ -530,7 +535,7 @@ func (a *IntArray) LockFunc(f func(array []int)) *IntArray { return a } -// RLockFunc locks reading by callback function . +// RLockFunc locks reading by callback function `f`. func (a *IntArray) RLockFunc(f func(array []int)) *IntArray { a.mu.RLock() defer a.mu.RUnlock() @@ -538,21 +543,21 @@ func (a *IntArray) RLockFunc(f func(array []int)) *IntArray { return a } -// Merge merges into current array. -// The parameter can be any garray or slice type. +// Merge merges `array` into current array. +// The parameter `array` can be any garray or slice type. // The difference between Merge and Append is Append supports only specified slice type, // but Merge supports more parameter types. func (a *IntArray) Merge(array interface{}) *IntArray { return a.Append(gconv.Ints(array)...) } -// Fill fills an array with num entries of the value , -// keys starting at the parameter. +// Fill fills an array with num entries of the value `value`, +// keys starting at the `startIndex` parameter. func (a *IntArray) Fill(startIndex int, num int, value int) error { a.mu.Lock() defer a.mu.Unlock() if startIndex < 0 || startIndex > len(a.array) { - return errors.New(fmt.Sprintf("index %d out of array range %d", startIndex, len(a.array))) + return gerror.NewCodef(gcode.CodeInvalidParameter, "index %d out of array range %d", startIndex, len(a.array)) } for i := startIndex; i < startIndex+num; i++ { if i > len(a.array)-1 { @@ -565,7 +570,7 @@ func (a *IntArray) Fill(startIndex int, num int, value int) error { } // Chunk splits an array into multiple arrays, -// the size of each array is determined by . +// the size of each array is determined by `size`. // The last chunk may contain less than size elements. func (a *IntArray) Chunk(size int) [][]int { if size < 1 { @@ -587,9 +592,9 @@ func (a *IntArray) Chunk(size int) [][]int { return n } -// Pad pads array to the specified length with . +// Pad pads array to the specified length with `value`. // If size is positive then the array is padded on the right, or negative on the left. -// If the absolute value of is less than or equal to the length of the array +// If the absolute value of `size` is less than or equal to the length of the array // then no padding takes place. func (a *IntArray) Pad(size int, value int) *IntArray { a.mu.Lock() @@ -624,7 +629,7 @@ func (a *IntArray) Rand() (value int, found bool) { return a.array[grand.Intn(len(a.array))], true } -// Rands randomly returns items from array(no deleting). +// Rands randomly returns `size` items from array(no deleting). func (a *IntArray) Rands(size int) []int { a.mu.RLock() defer a.mu.RUnlock() @@ -658,7 +663,7 @@ func (a *IntArray) Reverse() *IntArray { return a } -// Join joins array elements with a string . +// Join joins array elements with a string `glue`. func (a *IntArray) Join(glue string) string { a.mu.RLock() defer a.mu.RUnlock() @@ -691,8 +696,8 @@ func (a *IntArray) Iterator(f func(k int, v int) bool) { a.IteratorAsc(f) } -// IteratorAsc iterates the array readonly in ascending order with given callback function . -// If returns true, then it continues iterating; or false to stop. +// IteratorAsc iterates the array readonly in ascending order with given callback function `f`. +// If `f` returns true, then it continues iterating; or false to stop. func (a *IntArray) IteratorAsc(f func(k int, v int) bool) { a.mu.RLock() defer a.mu.RUnlock() @@ -703,8 +708,8 @@ func (a *IntArray) IteratorAsc(f func(k int, v int) bool) { } } -// IteratorDesc iterates the array readonly in descending order with given callback function . -// If returns true, then it continues iterating; or false to stop. +// IteratorDesc iterates the array readonly in descending order with given callback function `f`. +// If `f` returns true, then it continues iterating; or false to stop. func (a *IntArray) IteratorDesc(f func(k int, v int) bool) { a.mu.RLock() defer a.mu.RUnlock() @@ -735,7 +740,7 @@ func (a *IntArray) UnmarshalJSON(b []byte) error { } a.mu.Lock() defer a.mu.Unlock() - if err := json.Unmarshal(b, &a.array); err != nil { + if err := json.UnmarshalUseNumber(b, &a.array); err != nil { return err } return nil @@ -747,7 +752,7 @@ func (a *IntArray) UnmarshalValue(value interface{}) error { defer a.mu.Unlock() switch value.(type) { case string, []byte: - return json.Unmarshal(gconv.Bytes(value), &a.array) + return json.UnmarshalUseNumber(gconv.Bytes(value), &a.array) default: a.array = gconv.SliceInt(value) } @@ -768,7 +773,7 @@ func (a *IntArray) FilterEmpty() *IntArray { return a } -// Walk applies a user supplied function to every item of array. +// Walk applies a user supplied function `f` to every item of array. func (a *IntArray) Walk(f func(value int) int) *IntArray { a.mu.Lock() defer a.mu.Unlock() diff --git a/container/garray/garray_normal_str.go b/container/garray/garray_normal_str.go index 889a19a09..11cb6551b 100644 --- a/container/garray/garray_normal_str.go +++ b/container/garray/garray_normal_str.go @@ -1,4 +1,4 @@ -// Copyright 2018 gf Author(https://github.com/gogf/gf). All Rights Reserved. +// Copyright GoFrame Author(https://goframe.org). All Rights Reserved. // // This Source Code Form is subject to the terms of the MIT License. // If a copy of the MIT was not distributed with this file, @@ -8,8 +8,8 @@ package garray import ( "bytes" - "errors" - "fmt" + "github.com/gogf/gf/errors/gcode" + "github.com/gogf/gf/errors/gerror" "github.com/gogf/gf/internal/json" "github.com/gogf/gf/text/gstr" "math" @@ -30,14 +30,14 @@ type StrArray struct { } // NewStrArray creates and returns an empty array. -// The parameter is used to specify whether using array in concurrent-safety, +// The parameter `safe` is used to specify whether using array in concurrent-safety, // which is false in default. func NewStrArray(safe ...bool) *StrArray { return NewStrArraySize(0, 0, safe...) } // NewStrArraySize create and returns an array with given size and cap. -// The parameter is used to specify whether using array in concurrent-safety, +// The parameter `safe` is used to specify whether using array in concurrent-safety, // which is false in default. func NewStrArraySize(size int, cap int, safe ...bool) *StrArray { return &StrArray{ @@ -46,8 +46,8 @@ func NewStrArraySize(size int, cap int, safe ...bool) *StrArray { } } -// NewStrArrayFrom creates and returns an array with given slice . -// The parameter is used to specify whether using array in concurrent-safety, +// NewStrArrayFrom creates and returns an array with given slice `array`. +// The parameter `safe` is used to specify whether using array in concurrent-safety, // which is false in default. func NewStrArrayFrom(array []string, safe ...bool) *StrArray { return &StrArray{ @@ -56,8 +56,8 @@ func NewStrArrayFrom(array []string, safe ...bool) *StrArray { } } -// NewStrArrayFromCopy creates and returns an array from a copy of given slice . -// The parameter is used to specify whether using array in concurrent-safety, +// NewStrArrayFromCopy creates and returns an array from a copy of given slice `array`. +// The parameter `safe` is used to specify whether using array in concurrent-safety, // which is false in default. func NewStrArrayFromCopy(array []string, safe ...bool) *StrArray { newArray := make([]string, len(array)) @@ -68,8 +68,15 @@ func NewStrArrayFromCopy(array []string, safe ...bool) *StrArray { } } +// At returns the value by the specified index. +// If the given `index` is out of range of the array, it returns an empty string. +func (a *StrArray) At(index int) (value string) { + value, _ = a.Get(index) + return +} + // Get returns the value by the specified index. -// If the given is out of range of the array, the is false. +// If the given `index` is out of range of the array, the `found` is false. func (a *StrArray) Get(index int) (value string, found bool) { a.mu.RLock() defer a.mu.RUnlock() @@ -84,13 +91,13 @@ func (a *StrArray) Set(index int, value string) error { a.mu.Lock() defer a.mu.Unlock() if index < 0 || index >= len(a.array) { - return errors.New(fmt.Sprintf("index %d out of array range %d", index, len(a.array))) + return gerror.NewCodef(gcode.CodeInvalidParameter, "index %d out of array range %d", index, len(a.array)) } a.array[index] = value return nil } -// SetArray sets the underlying slice array with the given . +// SetArray sets the underlying slice array with the given `array`. func (a *StrArray) SetArray(array []string) *StrArray { a.mu.Lock() defer a.mu.Unlock() @@ -98,7 +105,7 @@ func (a *StrArray) SetArray(array []string) *StrArray { return a } -// Replace replaces the array items by given from the beginning of array. +// Replace replaces the array items by given `array` from the beginning of array. func (a *StrArray) Replace(array []string) *StrArray { a.mu.Lock() defer a.mu.Unlock() @@ -123,17 +130,14 @@ func (a *StrArray) Sum() (sum int) { } // Sort sorts the array in increasing order. -// The parameter controls whether sort +// The parameter `reverse` controls whether sort // in increasing order(default) or decreasing order func (a *StrArray) Sort(reverse ...bool) *StrArray { a.mu.Lock() defer a.mu.Unlock() if len(reverse) > 0 && reverse[0] { sort.Slice(a.array, func(i, j int) bool { - if strings.Compare(a.array[i], a.array[j]) < 0 { - return false - } - return true + return strings.Compare(a.array[i], a.array[j]) >= 0 }) } else { sort.Strings(a.array) @@ -141,7 +145,7 @@ func (a *StrArray) Sort(reverse ...bool) *StrArray { return a } -// SortFunc sorts the array by custom function . +// SortFunc sorts the array by custom function `less`. func (a *StrArray) SortFunc(less func(v1, v2 string) bool) *StrArray { a.mu.Lock() defer a.mu.Unlock() @@ -151,12 +155,12 @@ func (a *StrArray) SortFunc(less func(v1, v2 string) bool) *StrArray { return a } -// InsertBefore inserts the to the front of . +// InsertBefore inserts the `value` to the front of `index`. func (a *StrArray) InsertBefore(index int, value string) error { a.mu.Lock() defer a.mu.Unlock() if index < 0 || index >= len(a.array) { - return errors.New(fmt.Sprintf("index %d out of array range %d", index, len(a.array))) + return gerror.NewCodef(gcode.CodeInvalidParameter, "index %d out of array range %d", index, len(a.array)) } rear := append([]string{}, a.array[index:]...) a.array = append(a.array[0:index], value) @@ -164,12 +168,12 @@ func (a *StrArray) InsertBefore(index int, value string) error { return nil } -// InsertAfter inserts the to the back of . +// InsertAfter inserts the `value` to the back of `index`. func (a *StrArray) InsertAfter(index int, value string) error { a.mu.Lock() defer a.mu.Unlock() if index < 0 || index >= len(a.array) { - return errors.New(fmt.Sprintf("index %d out of array range %d", index, len(a.array))) + return gerror.NewCodef(gcode.CodeInvalidParameter, "index %d out of array range %d", index, len(a.array)) } rear := append([]string{}, a.array[index+1:]...) a.array = append(a.array[0:index+1], value) @@ -178,7 +182,7 @@ func (a *StrArray) InsertAfter(index int, value string) error { } // Remove removes an item by index. -// If the given is out of range of the array, the is false. +// If the given `index` is out of range of the array, the `found` is false. func (a *StrArray) Remove(index int) (value string, found bool) { a.mu.Lock() defer a.mu.Unlock() @@ -236,7 +240,7 @@ func (a *StrArray) PushRight(value ...string) *StrArray { } // PopLeft pops and returns an item from the beginning of array. -// Note that if the array is empty, the is false. +// Note that if the array is empty, the `found` is false. func (a *StrArray) PopLeft() (value string, found bool) { a.mu.Lock() defer a.mu.Unlock() @@ -249,7 +253,7 @@ func (a *StrArray) PopLeft() (value string, found bool) { } // PopRight pops and returns an item from the end of array. -// Note that if the array is empty, the is false. +// Note that if the array is empty, the `found` is false. func (a *StrArray) PopRight() (value string, found bool) { a.mu.Lock() defer a.mu.Unlock() @@ -263,16 +267,16 @@ func (a *StrArray) PopRight() (value string, found bool) { } // PopRand randomly pops and return an item out of array. -// Note that if the array is empty, the is false. +// Note that if the array is empty, the `found` is false. func (a *StrArray) PopRand() (value string, found bool) { a.mu.Lock() defer a.mu.Unlock() return a.doRemoveWithoutLock(grand.Intn(len(a.array))) } -// PopRands randomly pops and returns items out of array. -// If the given is greater than size of the array, it returns all elements of the array. -// Note that if given <= 0 or the array is empty, it returns nil. +// PopRands randomly pops and returns `size` items out of array. +// If the given `size` is greater than size of the array, it returns all elements of the array. +// Note that if given `size` <= 0 or the array is empty, it returns nil. func (a *StrArray) PopRands(size int) []string { a.mu.Lock() defer a.mu.Unlock() @@ -289,9 +293,9 @@ func (a *StrArray) PopRands(size int) []string { return array } -// PopLefts pops and returns items from the beginning of array. -// If the given is greater than size of the array, it returns all elements of the array. -// Note that if given <= 0 or the array is empty, it returns nil. +// PopLefts pops and returns `size` items from the beginning of array. +// If the given `size` is greater than size of the array, it returns all elements of the array. +// Note that if given `size` <= 0 or the array is empty, it returns nil. func (a *StrArray) PopLefts(size int) []string { a.mu.Lock() defer a.mu.Unlock() @@ -308,9 +312,9 @@ func (a *StrArray) PopLefts(size int) []string { return value } -// PopRights pops and returns items from the end of array. -// If the given is greater than size of the array, it returns all elements of the array. -// Note that if given <= 0 or the array is empty, it returns nil. +// PopRights pops and returns `size` items from the end of array. +// If the given `size` is greater than size of the array, it returns all elements of the array. +// Note that if given `size` <= 0 or the array is empty, it returns nil. func (a *StrArray) PopRights(size int) []string { a.mu.Lock() defer a.mu.Unlock() @@ -332,8 +336,8 @@ func (a *StrArray) PopRights(size int) []string { // Notice, if in concurrent-safe usage, it returns a copy of slice; // else a pointer to the underlying data. // -// If is negative, then the offset will start from the end of array. -// If is omitted, then the sequence will have everything from start up +// If `end` is negative, then the offset will start from the end of array. +// If `end` is omitted, then the sequence will have everything from start up // until the end of the array. func (a *StrArray) Range(start int, end ...int) []string { a.mu.RLock() @@ -359,7 +363,7 @@ func (a *StrArray) Range(start int, end ...int) []string { } // SubSlice returns a slice of elements from the array as specified -// by the and parameters. +// by the `offset` and `size` parameters. // If in concurrent safe usage, it returns a copy of the slice; else a pointer. // // If offset is non-negative, the sequence will start at that offset in the array. @@ -408,7 +412,7 @@ func (a *StrArray) SubSlice(offset int, length ...int) []string { } } -// See PushRight. +// Append is alias of PushRight,please See PushRight. func (a *StrArray) Append(value ...string) *StrArray { a.mu.Lock() a.array = append(a.array, value...) @@ -491,7 +495,7 @@ func (a *StrArray) ContainsI(value string) bool { return false } -// Search searches array by , returns the index of , +// Search searches array by `value`, returns the index of `value`, // or returns -1 if not exists. func (a *StrArray) Search(value string) int { a.mu.RLock() @@ -526,7 +530,7 @@ func (a *StrArray) Unique() *StrArray { return a } -// LockFunc locks writing by callback function . +// LockFunc locks writing by callback function `f`. func (a *StrArray) LockFunc(f func(array []string)) *StrArray { a.mu.Lock() defer a.mu.Unlock() @@ -534,7 +538,7 @@ func (a *StrArray) LockFunc(f func(array []string)) *StrArray { return a } -// RLockFunc locks reading by callback function . +// RLockFunc locks reading by callback function `f`. func (a *StrArray) RLockFunc(f func(array []string)) *StrArray { a.mu.RLock() defer a.mu.RUnlock() @@ -542,21 +546,21 @@ func (a *StrArray) RLockFunc(f func(array []string)) *StrArray { return a } -// Merge merges into current array. -// The parameter can be any garray or slice type. +// Merge merges `array` into current array. +// The parameter `array` can be any garray or slice type. // The difference between Merge and Append is Append supports only specified slice type, // but Merge supports more parameter types. func (a *StrArray) Merge(array interface{}) *StrArray { return a.Append(gconv.Strings(array)...) } -// Fill fills an array with num entries of the value , -// keys starting at the parameter. +// Fill fills an array with num entries of the value `value`, +// keys starting at the `startIndex` parameter. func (a *StrArray) Fill(startIndex int, num int, value string) error { a.mu.Lock() defer a.mu.Unlock() if startIndex < 0 || startIndex > len(a.array) { - return errors.New(fmt.Sprintf("index %d out of array range %d", startIndex, len(a.array))) + return gerror.NewCodef(gcode.CodeInvalidParameter, "index %d out of array range %d", startIndex, len(a.array)) } for i := startIndex; i < startIndex+num; i++ { if i > len(a.array)-1 { @@ -569,7 +573,7 @@ func (a *StrArray) Fill(startIndex int, num int, value string) error { } // Chunk splits an array into multiple arrays, -// the size of each array is determined by . +// the size of each array is determined by `size`. // The last chunk may contain less than size elements. func (a *StrArray) Chunk(size int) [][]string { if size < 1 { @@ -591,9 +595,9 @@ func (a *StrArray) Chunk(size int) [][]string { return n } -// Pad pads array to the specified length with . +// Pad pads array to the specified length with `value`. // If size is positive then the array is padded on the right, or negative on the left. -// If the absolute value of is less than or equal to the length of the array +// If the absolute value of `size` is less than or equal to the length of the array // then no padding takes place. func (a *StrArray) Pad(size int, value string) *StrArray { a.mu.Lock() @@ -628,7 +632,7 @@ func (a *StrArray) Rand() (value string, found bool) { return a.array[grand.Intn(len(a.array))], true } -// Rands randomly returns items from array(no deleting). +// Rands randomly returns `size` items from array(no deleting). func (a *StrArray) Rands(size int) []string { a.mu.RLock() defer a.mu.RUnlock() @@ -662,7 +666,7 @@ func (a *StrArray) Reverse() *StrArray { return a } -// Join joins array elements with a string . +// Join joins array elements with a string `glue`. func (a *StrArray) Join(glue string) string { a.mu.RLock() defer a.mu.RUnlock() @@ -695,8 +699,8 @@ func (a *StrArray) Iterator(f func(k int, v string) bool) { a.IteratorAsc(f) } -// IteratorAsc iterates the array readonly in ascending order with given callback function . -// If returns true, then it continues iterating; or false to stop. +// IteratorAsc iterates the array readonly in ascending order with given callback function `f`. +// If `f` returns true, then it continues iterating; or false to stop. func (a *StrArray) IteratorAsc(f func(k int, v string) bool) { a.mu.RLock() defer a.mu.RUnlock() @@ -707,8 +711,8 @@ func (a *StrArray) IteratorAsc(f func(k int, v string) bool) { } } -// IteratorDesc iterates the array readonly in descending order with given callback function . -// If returns true, then it continues iterating; or false to stop. +// IteratorDesc iterates the array readonly in descending order with given callback function `f`. +// If `f` returns true, then it continues iterating; or false to stop. func (a *StrArray) IteratorDesc(f func(k int, v string) bool) { a.mu.RLock() defer a.mu.RUnlock() @@ -750,7 +754,7 @@ func (a *StrArray) UnmarshalJSON(b []byte) error { } a.mu.Lock() defer a.mu.Unlock() - if err := json.Unmarshal(b, &a.array); err != nil { + if err := json.UnmarshalUseNumber(b, &a.array); err != nil { return err } return nil @@ -762,7 +766,7 @@ func (a *StrArray) UnmarshalValue(value interface{}) error { defer a.mu.Unlock() switch value.(type) { case string, []byte: - return json.Unmarshal(gconv.Bytes(value), &a.array) + return json.UnmarshalUseNumber(gconv.Bytes(value), &a.array) default: a.array = gconv.SliceStr(value) } @@ -783,7 +787,7 @@ func (a *StrArray) FilterEmpty() *StrArray { return a } -// Walk applies a user supplied function to every item of array. +// Walk applies a user supplied function `f` to every item of array. func (a *StrArray) Walk(f func(value string) string) *StrArray { a.mu.Lock() defer a.mu.Unlock() diff --git a/container/garray/garray_sorted_any.go b/container/garray/garray_sorted_any.go index 3122be023..f120809a5 100644 --- a/container/garray/garray_sorted_any.go +++ b/container/garray/garray_sorted_any.go @@ -1,4 +1,4 @@ -// Copyright 2018 gf Author(https://github.com/gogf/gf). All Rights Reserved. +// Copyright GoFrame Author(https://goframe.org). All Rights Reserved. // // This Source Code Form is subject to the terms of the MIT License. // If a copy of the MIT was not distributed with this file, @@ -34,8 +34,8 @@ type SortedArray struct { } // NewSortedArray creates and returns an empty sorted array. -// The parameter is used to specify whether using array in concurrent-safety, which is false in default. -// The parameter used to compare values to sort in array, +// The parameter `safe` is used to specify whether using array in concurrent-safety, which is false in default. +// The parameter `comparator` used to compare values to sort in array, // if it returns value < 0, means v1 < v2; the v1 will be inserted before v2; // if it returns value = 0, means v1 = v2; the v1 will be replaced by v2; // if it returns value > 0, means v1 > v2; the v1 will be inserted after v2; @@ -44,7 +44,7 @@ func NewSortedArray(comparator func(a, b interface{}) int, safe ...bool) *Sorted } // NewSortedArraySize create and returns an sorted array with given size and cap. -// The parameter is used to specify whether using array in concurrent-safety, +// The parameter `safe` is used to specify whether using array in concurrent-safety, // which is false in default. func NewSortedArraySize(cap int, comparator func(a, b interface{}) int, safe ...bool) *SortedArray { return &SortedArray{ @@ -54,8 +54,8 @@ func NewSortedArraySize(cap int, comparator func(a, b interface{}) int, safe ... } } -// NewSortedArrayRange creates and returns a array by a range from to -// with step value . +// NewSortedArrayRange creates and returns a array by a range from `start` to `end` +// with step value `step`. func NewSortedArrayRange(start, end, step int, comparator func(a, b interface{}) int, safe ...bool) *SortedArray { if step == 0 { panic(fmt.Sprintf(`invalid step value: %d`, step)) @@ -69,8 +69,8 @@ func NewSortedArrayRange(start, end, step int, comparator func(a, b interface{}) return NewSortedArrayFrom(slice, comparator, safe...) } -// NewSortedArrayFrom creates and returns an sorted array with given slice . -// The parameter is used to specify whether using array in concurrent-safety, +// NewSortedArrayFrom creates and returns an sorted array with given slice `array`. +// The parameter `safe` is used to specify whether using array in concurrent-safety, // which is false in default. func NewSortedArrayFrom(array []interface{}, comparator func(a, b interface{}) int, safe ...bool) *SortedArray { a := NewSortedArraySize(0, comparator, safe...) @@ -81,8 +81,8 @@ func NewSortedArrayFrom(array []interface{}, comparator func(a, b interface{}) i return a } -// NewSortedArrayFromCopy creates and returns an sorted array from a copy of given slice . -// The parameter is used to specify whether using array in concurrent-safety, +// NewSortedArrayFromCopy creates and returns an sorted array from a copy of given slice `array`. +// The parameter `safe` is used to specify whether using array in concurrent-safety, // which is false in default. func NewSortedArrayFromCopy(array []interface{}, comparator func(a, b interface{}) int, safe ...bool) *SortedArray { newArray := make([]interface{}, len(array)) @@ -90,7 +90,14 @@ func NewSortedArrayFromCopy(array []interface{}, comparator func(a, b interface{ return NewSortedArrayFrom(newArray, comparator, safe...) } -// SetArray sets the underlying slice array with the given . +// At returns the value by the specified index. +// If the given `index` is out of range of the array, it returns `nil`. +func (a *SortedArray) At(index int) (value interface{}) { + value, _ = a.Get(index) + return +} + +// SetArray sets the underlying slice array with the given `array`. func (a *SortedArray) SetArray(array []interface{}) *SortedArray { a.mu.Lock() defer a.mu.Unlock() @@ -113,7 +120,7 @@ func (a *SortedArray) SetComparator(comparator func(a, b interface{}) int) { } // Sort sorts the array in increasing order. -// The parameter controls whether sort +// The parameter `reverse` controls whether sort // in increasing order(default) or decreasing order func (a *SortedArray) Sort() *SortedArray { a.mu.Lock() @@ -149,15 +156,13 @@ func (a *SortedArray) Append(values ...interface{}) *SortedArray { if cmp > 0 { index++ } - rear := append([]interface{}{}, a.array[index:]...) - a.array = append(a.array[0:index], value) - a.array = append(a.array, rear...) + a.array = append(a.array[:index], append([]interface{}{value}, a.array[index:]...)...) } return a } // Get returns the value by the specified index. -// If the given is out of range of the array, the is false. +// If the given `index` is out of range of the array, the `found` is false. func (a *SortedArray) Get(index int) (value interface{}, found bool) { a.mu.RLock() defer a.mu.RUnlock() @@ -168,7 +173,7 @@ func (a *SortedArray) Get(index int) (value interface{}, found bool) { } // Remove removes an item by index. -// If the given is out of range of the array, the is false. +// If the given `index` is out of range of the array, the `found` is false. func (a *SortedArray) Remove(index int) (value interface{}, found bool) { a.mu.Lock() defer a.mu.Unlock() @@ -209,7 +214,7 @@ func (a *SortedArray) RemoveValue(value interface{}) bool { } // PopLeft pops and returns an item from the beginning of array. -// Note that if the array is empty, the is false. +// Note that if the array is empty, the `found` is false. func (a *SortedArray) PopLeft() (value interface{}, found bool) { a.mu.Lock() defer a.mu.Unlock() @@ -222,7 +227,7 @@ func (a *SortedArray) PopLeft() (value interface{}, found bool) { } // PopRight pops and returns an item from the end of array. -// Note that if the array is empty, the is false. +// Note that if the array is empty, the `found` is false. func (a *SortedArray) PopRight() (value interface{}, found bool) { a.mu.Lock() defer a.mu.Unlock() @@ -236,14 +241,14 @@ func (a *SortedArray) PopRight() (value interface{}, found bool) { } // PopRand randomly pops and return an item out of array. -// Note that if the array is empty, the is false. +// Note that if the array is empty, the `found` is false. func (a *SortedArray) PopRand() (value interface{}, found bool) { a.mu.Lock() defer a.mu.Unlock() return a.doRemoveWithoutLock(grand.Intn(len(a.array))) } -// PopRands randomly pops and returns items out of array. +// PopRands randomly pops and returns `size` items out of array. func (a *SortedArray) PopRands(size int) []interface{} { a.mu.Lock() defer a.mu.Unlock() @@ -260,7 +265,7 @@ func (a *SortedArray) PopRands(size int) []interface{} { return array } -// PopLefts pops and returns items from the beginning of array. +// PopLefts pops and returns `size` items from the beginning of array. func (a *SortedArray) PopLefts(size int) []interface{} { a.mu.Lock() defer a.mu.Unlock() @@ -277,7 +282,7 @@ func (a *SortedArray) PopLefts(size int) []interface{} { return value } -// PopRights pops and returns items from the end of array. +// PopRights pops and returns `size` items from the end of array. func (a *SortedArray) PopRights(size int) []interface{} { a.mu.Lock() defer a.mu.Unlock() @@ -299,8 +304,8 @@ func (a *SortedArray) PopRights(size int) []interface{} { // Notice, if in concurrent-safe usage, it returns a copy of slice; // else a pointer to the underlying data. // -// If is negative, then the offset will start from the end of array. -// If is omitted, then the sequence will have everything from start up +// If `end` is negative, then the offset will start from the end of array. +// If `end` is omitted, then the sequence will have everything from start up // until the end of the array. func (a *SortedArray) Range(start int, end ...int) []interface{} { a.mu.RLock() @@ -326,7 +331,7 @@ func (a *SortedArray) Range(start int, end ...int) []interface{} { } // SubSlice returns a slice of elements from the array as specified -// by the and parameters. +// by the `offset` and `size` parameters. // If in concurrent safe usage, it returns a copy of the slice; else a pointer. // // If offset is non-negative, the sequence will start at that offset in the array. @@ -419,7 +424,7 @@ func (a *SortedArray) Contains(value interface{}) bool { return a.Search(value) != -1 } -// Search searches array by , returns the index of , +// Search searches array by `value`, returns the index of `value`, // or returns -1 if not exists. func (a *SortedArray) Search(value interface{}) (index int) { if i, r := a.binSearch(value, true); r == 0 { @@ -430,9 +435,9 @@ func (a *SortedArray) Search(value interface{}) (index int) { // Binary search. // It returns the last compared index and the result. -// If equals to 0, it means the value at is equals to . -// If lesser than 0, it means the value at is lesser than . -// If greater than 0, it means the value at is greater than . +// If `result` equals to 0, it means the value at `index` is equals to `value`. +// If `result` lesser than 0, it means the value at `index` is lesser than `value`. +// If `result` greater than 0, it means the value at `index` is greater than `value`. func (a *SortedArray) binSearch(value interface{}, lock bool) (index int, result int) { if lock { a.mu.RLock() @@ -446,7 +451,7 @@ func (a *SortedArray) binSearch(value interface{}, lock bool) (index int, result mid := 0 cmp := -2 for min <= max { - mid = min + int((max-min)/2) + mid = min + (max-min)/2 cmp = a.getComparator()(value, a.array[mid]) switch { case cmp < 0: @@ -512,7 +517,7 @@ func (a *SortedArray) Clear() *SortedArray { return a } -// LockFunc locks writing by callback function . +// LockFunc locks writing by callback function `f`. func (a *SortedArray) LockFunc(f func(array []interface{})) *SortedArray { a.mu.Lock() defer a.mu.Unlock() @@ -526,7 +531,7 @@ func (a *SortedArray) LockFunc(f func(array []interface{})) *SortedArray { return a } -// RLockFunc locks reading by callback function . +// RLockFunc locks reading by callback function `f`. func (a *SortedArray) RLockFunc(f func(array []interface{})) *SortedArray { a.mu.RLock() defer a.mu.RUnlock() @@ -534,8 +539,8 @@ func (a *SortedArray) RLockFunc(f func(array []interface{})) *SortedArray { return a } -// Merge merges into current array. -// The parameter can be any garray or slice type. +// Merge merges `array` into current array. +// The parameter `array` can be any garray or slice type. // The difference between Merge and Append is Append supports only specified slice type, // but Merge supports more parameter types. func (a *SortedArray) Merge(array interface{}) *SortedArray { @@ -543,7 +548,7 @@ func (a *SortedArray) Merge(array interface{}) *SortedArray { } // Chunk splits an array into multiple arrays, -// the size of each array is determined by . +// the size of each array is determined by `size`. // The last chunk may contain less than size elements. func (a *SortedArray) Chunk(size int) [][]interface{} { if size < 1 { @@ -575,7 +580,7 @@ func (a *SortedArray) Rand() (value interface{}, found bool) { return a.array[grand.Intn(len(a.array))], true } -// Rands randomly returns items from array(no deleting). +// Rands randomly returns `size` items from array(no deleting). func (a *SortedArray) Rands(size int) []interface{} { a.mu.RLock() defer a.mu.RUnlock() @@ -589,7 +594,7 @@ func (a *SortedArray) Rands(size int) []interface{} { return array } -// Join joins array elements with a string . +// Join joins array elements with a string `glue`. func (a *SortedArray) Join(glue string) string { a.mu.RLock() defer a.mu.RUnlock() @@ -622,8 +627,8 @@ func (a *SortedArray) Iterator(f func(k int, v interface{}) bool) { a.IteratorAsc(f) } -// IteratorAsc iterates the array readonly in ascending order with given callback function . -// If returns true, then it continues iterating; or false to stop. +// IteratorAsc iterates the array readonly in ascending order with given callback function `f`. +// If `f` returns true, then it continues iterating; or false to stop. func (a *SortedArray) IteratorAsc(f func(k int, v interface{}) bool) { a.mu.RLock() defer a.mu.RUnlock() @@ -634,8 +639,8 @@ func (a *SortedArray) IteratorAsc(f func(k int, v interface{}) bool) { } } -// IteratorDesc iterates the array readonly in descending order with given callback function . -// If returns true, then it continues iterating; or false to stop. +// IteratorDesc iterates the array readonly in descending order with given callback function `f`. +// If `f` returns true, then it continues iterating; or false to stop. func (a *SortedArray) IteratorDesc(f func(k int, v interface{}) bool) { a.mu.RLock() defer a.mu.RUnlock() @@ -685,7 +690,7 @@ func (a *SortedArray) UnmarshalJSON(b []byte) error { } a.mu.Lock() defer a.mu.Unlock() - if err := json.Unmarshal(b, &a.array); err != nil { + if err := json.UnmarshalUseNumber(b, &a.array); err != nil { return err } if a.comparator != nil && a.array != nil { @@ -706,7 +711,7 @@ func (a *SortedArray) UnmarshalValue(value interface{}) (err error) { defer a.mu.Unlock() switch value.(type) { case string, []byte: - err = json.Unmarshal(gconv.Bytes(value), &a.array) + err = json.UnmarshalUseNumber(gconv.Bytes(value), &a.array) default: a.array = gconv.SliceAny(value) } @@ -761,7 +766,7 @@ func (a *SortedArray) FilterEmpty() *SortedArray { return a } -// Walk applies a user supplied function to every item of array. +// Walk applies a user supplied function `f` to every item of array. func (a *SortedArray) Walk(f func(value interface{}) interface{}) *SortedArray { a.mu.Lock() defer a.mu.Unlock() diff --git a/container/garray/garray_sorted_int.go b/container/garray/garray_sorted_int.go index fa44ed534..f250d989c 100644 --- a/container/garray/garray_sorted_int.go +++ b/container/garray/garray_sorted_int.go @@ -1,4 +1,4 @@ -// Copyright 2018 gf Author(https://github.com/gogf/gf). All Rights Reserved. +// Copyright GoFrame Author(https://goframe.org). All Rights Reserved. // // This Source Code Form is subject to the terms of the MIT License. // If a copy of the MIT was not distributed with this file, @@ -31,14 +31,14 @@ type SortedIntArray struct { } // NewSortedIntArray creates and returns an empty sorted array. -// The parameter is used to specify whether using array in concurrent-safety, +// The parameter `safe` is used to specify whether using array in concurrent-safety, // which is false in default. func NewSortedIntArray(safe ...bool) *SortedIntArray { return NewSortedIntArraySize(0, safe...) } // NewSortedIntArrayComparator creates and returns an empty sorted array with specified comparator. -// The parameter is used to specify whether using array in concurrent-safety which is false in default. +// The parameter `safe` is used to specify whether using array in concurrent-safety which is false in default. func NewSortedIntArrayComparator(comparator func(a, b int) int, safe ...bool) *SortedIntArray { array := NewSortedIntArray(safe...) array.comparator = comparator @@ -46,7 +46,7 @@ func NewSortedIntArrayComparator(comparator func(a, b int) int, safe ...bool) *S } // NewSortedIntArraySize create and returns an sorted array with given size and cap. -// The parameter is used to specify whether using array in concurrent-safety, +// The parameter `safe` is used to specify whether using array in concurrent-safety, // which is false in default. func NewSortedIntArraySize(cap int, safe ...bool) *SortedIntArray { return &SortedIntArray{ @@ -56,8 +56,8 @@ func NewSortedIntArraySize(cap int, safe ...bool) *SortedIntArray { } } -// NewSortedIntArrayRange creates and returns a array by a range from to -// with step value . +// NewSortedIntArrayRange creates and returns a array by a range from `start` to `end` +// with step value `step`. func NewSortedIntArrayRange(start, end, step int, safe ...bool) *SortedIntArray { if step == 0 { panic(fmt.Sprintf(`invalid step value: %d`, step)) @@ -71,8 +71,8 @@ func NewSortedIntArrayRange(start, end, step int, safe ...bool) *SortedIntArray return NewSortedIntArrayFrom(slice, safe...) } -// NewIntArrayFrom creates and returns an sorted array with given slice . -// The parameter is used to specify whether using array in concurrent-safety, +// NewSortedIntArrayFrom creates and returns an sorted array with given slice `array`. +// The parameter `safe` is used to specify whether using array in concurrent-safety, // which is false in default. func NewSortedIntArrayFrom(array []int, safe ...bool) *SortedIntArray { a := NewSortedIntArraySize(0, safe...) @@ -81,8 +81,8 @@ func NewSortedIntArrayFrom(array []int, safe ...bool) *SortedIntArray { return a } -// NewSortedIntArrayFromCopy creates and returns an sorted array from a copy of given slice . -// The parameter is used to specify whether using array in concurrent-safety, +// NewSortedIntArrayFromCopy creates and returns an sorted array from a copy of given slice `array`. +// The parameter `safe` is used to specify whether using array in concurrent-safety, // which is false in default. func NewSortedIntArrayFromCopy(array []int, safe ...bool) *SortedIntArray { newArray := make([]int, len(array)) @@ -90,7 +90,14 @@ func NewSortedIntArrayFromCopy(array []int, safe ...bool) *SortedIntArray { return NewSortedIntArrayFrom(newArray, safe...) } -// SetArray sets the underlying slice array with the given . +// At returns the value by the specified index. +// If the given `index` is out of range of the array, it returns `0`. +func (a *SortedIntArray) At(index int) (value int) { + value, _ = a.Get(index) + return +} + +// SetArray sets the underlying slice array with the given `array`. func (a *SortedIntArray) SetArray(array []int) *SortedIntArray { a.mu.Lock() defer a.mu.Unlock() @@ -100,7 +107,7 @@ func (a *SortedIntArray) SetArray(array []int) *SortedIntArray { } // Sort sorts the array in increasing order. -// The parameter controls whether sort +// The parameter `reverse` controls whether sort // in increasing order(default) or decreasing order. func (a *SortedIntArray) Sort() *SortedIntArray { a.mu.Lock() @@ -142,7 +149,7 @@ func (a *SortedIntArray) Append(values ...int) *SortedIntArray { } // Get returns the value by the specified index. -// If the given is out of range of the array, the is false. +// If the given `index` is out of range of the array, the `found` is false. func (a *SortedIntArray) Get(index int) (value int, found bool) { a.mu.RLock() defer a.mu.RUnlock() @@ -153,7 +160,7 @@ func (a *SortedIntArray) Get(index int) (value int, found bool) { } // Remove removes an item by index. -// If the given is out of range of the array, the is false. +// If the given `index` is out of range of the array, the `found` is false. func (a *SortedIntArray) Remove(index int) (value int, found bool) { a.mu.Lock() defer a.mu.Unlock() @@ -194,7 +201,7 @@ func (a *SortedIntArray) RemoveValue(value int) bool { } // PopLeft pops and returns an item from the beginning of array. -// Note that if the array is empty, the is false. +// Note that if the array is empty, the `found` is false. func (a *SortedIntArray) PopLeft() (value int, found bool) { a.mu.Lock() defer a.mu.Unlock() @@ -207,7 +214,7 @@ func (a *SortedIntArray) PopLeft() (value int, found bool) { } // PopRight pops and returns an item from the end of array. -// Note that if the array is empty, the is false. +// Note that if the array is empty, the `found` is false. func (a *SortedIntArray) PopRight() (value int, found bool) { a.mu.Lock() defer a.mu.Unlock() @@ -221,16 +228,16 @@ func (a *SortedIntArray) PopRight() (value int, found bool) { } // PopRand randomly pops and return an item out of array. -// Note that if the array is empty, the is false. +// Note that if the array is empty, the `found` is false. func (a *SortedIntArray) PopRand() (value int, found bool) { a.mu.Lock() defer a.mu.Unlock() return a.doRemoveWithoutLock(grand.Intn(len(a.array))) } -// PopRands randomly pops and returns items out of array. -// If the given is greater than size of the array, it returns all elements of the array. -// Note that if given <= 0 or the array is empty, it returns nil. +// PopRands randomly pops and returns `size` items out of array. +// If the given `size` is greater than size of the array, it returns all elements of the array. +// Note that if given `size` <= 0 or the array is empty, it returns nil. func (a *SortedIntArray) PopRands(size int) []int { a.mu.Lock() defer a.mu.Unlock() @@ -247,9 +254,9 @@ func (a *SortedIntArray) PopRands(size int) []int { return array } -// PopLefts pops and returns items from the beginning of array. -// If the given is greater than size of the array, it returns all elements of the array. -// Note that if given <= 0 or the array is empty, it returns nil. +// PopLefts pops and returns `size` items from the beginning of array. +// If the given `size` is greater than size of the array, it returns all elements of the array. +// Note that if given `size` <= 0 or the array is empty, it returns nil. func (a *SortedIntArray) PopLefts(size int) []int { a.mu.Lock() defer a.mu.Unlock() @@ -266,9 +273,9 @@ func (a *SortedIntArray) PopLefts(size int) []int { return value } -// PopRights pops and returns items from the end of array. -// If the given is greater than size of the array, it returns all elements of the array. -// Note that if given <= 0 or the array is empty, it returns nil. +// PopRights pops and returns `size` items from the end of array. +// If the given `size` is greater than size of the array, it returns all elements of the array. +// Note that if given `size` <= 0 or the array is empty, it returns nil. func (a *SortedIntArray) PopRights(size int) []int { a.mu.Lock() defer a.mu.Unlock() @@ -290,8 +297,8 @@ func (a *SortedIntArray) PopRights(size int) []int { // Notice, if in concurrent-safe usage, it returns a copy of slice; // else a pointer to the underlying data. // -// If is negative, then the offset will start from the end of array. -// If is omitted, then the sequence will have everything from start up +// If `end` is negative, then the offset will start from the end of array. +// If `end` is omitted, then the sequence will have everything from start up // until the end of the array. func (a *SortedIntArray) Range(start int, end ...int) []int { a.mu.RLock() @@ -317,7 +324,7 @@ func (a *SortedIntArray) Range(start int, end ...int) []int { } // SubSlice returns a slice of elements from the array as specified -// by the and parameters. +// by the `offset` and `size` parameters. // If in concurrent safe usage, it returns a copy of the slice; else a pointer. // // If offset is non-negative, the sequence will start at that offset in the array. @@ -416,7 +423,7 @@ func (a *SortedIntArray) Contains(value int) bool { return a.Search(value) != -1 } -// Search searches array by , returns the index of , +// Search searches array by `value`, returns the index of `value`, // or returns -1 if not exists. func (a *SortedIntArray) Search(value int) (index int) { if i, r := a.binSearch(value, true); r == 0 { @@ -427,9 +434,9 @@ func (a *SortedIntArray) Search(value int) (index int) { // Binary search. // It returns the last compared index and the result. -// If equals to 0, it means the value at is equals to . -// If lesser than 0, it means the value at is lesser than . -// If greater than 0, it means the value at is greater than . +// If `result` equals to 0, it means the value at `index` is equals to `value`. +// If `result` lesser than 0, it means the value at `index` is lesser than `value`. +// If `result` greater than 0, it means the value at `index` is greater than `value`. func (a *SortedIntArray) binSearch(value int, lock bool) (index int, result int) { if lock { a.mu.RLock() @@ -509,7 +516,7 @@ func (a *SortedIntArray) Clear() *SortedIntArray { return a } -// LockFunc locks writing by callback function . +// LockFunc locks writing by callback function `f`. func (a *SortedIntArray) LockFunc(f func(array []int)) *SortedIntArray { a.mu.Lock() defer a.mu.Unlock() @@ -517,7 +524,7 @@ func (a *SortedIntArray) LockFunc(f func(array []int)) *SortedIntArray { return a } -// RLockFunc locks reading by callback function . +// RLockFunc locks reading by callback function `f`. func (a *SortedIntArray) RLockFunc(f func(array []int)) *SortedIntArray { a.mu.RLock() defer a.mu.RUnlock() @@ -525,8 +532,8 @@ func (a *SortedIntArray) RLockFunc(f func(array []int)) *SortedIntArray { return a } -// Merge merges into current array. -// The parameter can be any garray or slice type. +// Merge merges `array` into current array. +// The parameter `array` can be any garray or slice type. // The difference between Merge and Append is Append supports only specified slice type, // but Merge supports more parameter types. func (a *SortedIntArray) Merge(array interface{}) *SortedIntArray { @@ -534,7 +541,7 @@ func (a *SortedIntArray) Merge(array interface{}) *SortedIntArray { } // Chunk splits an array into multiple arrays, -// the size of each array is determined by . +// the size of each array is determined by `size`. // The last chunk may contain less than size elements. func (a *SortedIntArray) Chunk(size int) [][]int { if size < 1 { @@ -566,7 +573,7 @@ func (a *SortedIntArray) Rand() (value int, found bool) { return a.array[grand.Intn(len(a.array))], true } -// Rands randomly returns items from array(no deleting). +// Rands randomly returns `size` items from array(no deleting). func (a *SortedIntArray) Rands(size int) []int { a.mu.RLock() defer a.mu.RUnlock() @@ -580,7 +587,7 @@ func (a *SortedIntArray) Rands(size int) []int { return array } -// Join joins array elements with a string . +// Join joins array elements with a string `glue`. func (a *SortedIntArray) Join(glue string) string { a.mu.RLock() defer a.mu.RUnlock() @@ -613,8 +620,8 @@ func (a *SortedIntArray) Iterator(f func(k int, v int) bool) { a.IteratorAsc(f) } -// IteratorAsc iterates the array readonly in ascending order with given callback function . -// If returns true, then it continues iterating; or false to stop. +// IteratorAsc iterates the array readonly in ascending order with given callback function `f`. +// If `f` returns true, then it continues iterating; or false to stop. func (a *SortedIntArray) IteratorAsc(f func(k int, v int) bool) { a.mu.RLock() defer a.mu.RUnlock() @@ -625,8 +632,8 @@ func (a *SortedIntArray) IteratorAsc(f func(k int, v int) bool) { } } -// IteratorDesc iterates the array readonly in descending order with given callback function . -// If returns true, then it continues iterating; or false to stop. +// IteratorDesc iterates the array readonly in descending order with given callback function `f`. +// If `f` returns true, then it continues iterating; or false to stop. func (a *SortedIntArray) IteratorDesc(f func(k int, v int) bool) { a.mu.RLock() defer a.mu.RUnlock() @@ -658,7 +665,7 @@ func (a *SortedIntArray) UnmarshalJSON(b []byte) error { } a.mu.Lock() defer a.mu.Unlock() - if err := json.Unmarshal(b, &a.array); err != nil { + if err := json.UnmarshalUseNumber(b, &a.array); err != nil { return err } if a.array != nil { @@ -676,7 +683,7 @@ func (a *SortedIntArray) UnmarshalValue(value interface{}) (err error) { defer a.mu.Unlock() switch value.(type) { case string, []byte: - err = json.Unmarshal(gconv.Bytes(value), &a.array) + err = json.UnmarshalUseNumber(gconv.Bytes(value), &a.array) default: a.array = gconv.SliceInt(value) } @@ -707,7 +714,7 @@ func (a *SortedIntArray) FilterEmpty() *SortedIntArray { return a } -// Walk applies a user supplied function to every item of array. +// Walk applies a user supplied function `f` to every item of array. func (a *SortedIntArray) Walk(f func(value int) int) *SortedIntArray { a.mu.Lock() defer a.mu.Unlock() diff --git a/container/garray/garray_sorted_str.go b/container/garray/garray_sorted_str.go index 762017d72..896030e65 100644 --- a/container/garray/garray_sorted_str.go +++ b/container/garray/garray_sorted_str.go @@ -1,4 +1,4 @@ -// Copyright 2018 gf Author(https://github.com/gogf/gf). All Rights Reserved. +// Copyright GoFrame Author(https://goframe.org). All Rights Reserved. // // This Source Code Form is subject to the terms of the MIT License. // If a copy of the MIT was not distributed with this file, @@ -32,14 +32,14 @@ type SortedStrArray struct { } // NewSortedStrArray creates and returns an empty sorted array. -// The parameter is used to specify whether using array in concurrent-safety, +// The parameter `safe` is used to specify whether using array in concurrent-safety, // which is false in default. func NewSortedStrArray(safe ...bool) *SortedStrArray { return NewSortedStrArraySize(0, safe...) } // NewSortedStrArrayComparator creates and returns an empty sorted array with specified comparator. -// The parameter is used to specify whether using array in concurrent-safety which is false in default. +// The parameter `safe` is used to specify whether using array in concurrent-safety which is false in default. func NewSortedStrArrayComparator(comparator func(a, b string) int, safe ...bool) *SortedStrArray { array := NewSortedStrArray(safe...) array.comparator = comparator @@ -47,7 +47,7 @@ func NewSortedStrArrayComparator(comparator func(a, b string) int, safe ...bool) } // NewSortedStrArraySize create and returns an sorted array with given size and cap. -// The parameter is used to specify whether using array in concurrent-safety, +// The parameter `safe` is used to specify whether using array in concurrent-safety, // which is false in default. func NewSortedStrArraySize(cap int, safe ...bool) *SortedStrArray { return &SortedStrArray{ @@ -57,8 +57,8 @@ func NewSortedStrArraySize(cap int, safe ...bool) *SortedStrArray { } } -// NewSortedStrArrayFrom creates and returns an sorted array with given slice . -// The parameter is used to specify whether using array in concurrent-safety, +// NewSortedStrArrayFrom creates and returns an sorted array with given slice `array`. +// The parameter `safe` is used to specify whether using array in concurrent-safety, // which is false in default. func NewSortedStrArrayFrom(array []string, safe ...bool) *SortedStrArray { a := NewSortedStrArraySize(0, safe...) @@ -67,8 +67,8 @@ func NewSortedStrArrayFrom(array []string, safe ...bool) *SortedStrArray { return a } -// NewSortedStrArrayFromCopy creates and returns an sorted array from a copy of given slice . -// The parameter is used to specify whether using array in concurrent-safety, +// NewSortedStrArrayFromCopy creates and returns an sorted array from a copy of given slice `array`. +// The parameter `safe` is used to specify whether using array in concurrent-safety, // which is false in default. func NewSortedStrArrayFromCopy(array []string, safe ...bool) *SortedStrArray { newArray := make([]string, len(array)) @@ -76,7 +76,7 @@ func NewSortedStrArrayFromCopy(array []string, safe ...bool) *SortedStrArray { return NewSortedStrArrayFrom(newArray, safe...) } -// SetArray sets the underlying slice array with the given . +// SetArray sets the underlying slice array with the given `array`. func (a *SortedStrArray) SetArray(array []string) *SortedStrArray { a.mu.Lock() defer a.mu.Unlock() @@ -85,8 +85,15 @@ func (a *SortedStrArray) SetArray(array []string) *SortedStrArray { return a } +// At returns the value by the specified index. +// If the given `index` is out of range of the array, it returns an empty string. +func (a *SortedStrArray) At(index int) (value string) { + value, _ = a.Get(index) + return +} + // Sort sorts the array in increasing order. -// The parameter controls whether sort +// The parameter `reverse` controls whether sort // in increasing order(default) or decreasing order. func (a *SortedStrArray) Sort() *SortedStrArray { a.mu.Lock() @@ -128,7 +135,7 @@ func (a *SortedStrArray) Append(values ...string) *SortedStrArray { } // Get returns the value by the specified index. -// If the given is out of range of the array, the is false. +// If the given `index` is out of range of the array, the `found` is false. func (a *SortedStrArray) Get(index int) (value string, found bool) { a.mu.RLock() defer a.mu.RUnlock() @@ -139,7 +146,7 @@ func (a *SortedStrArray) Get(index int) (value string, found bool) { } // Remove removes an item by index. -// If the given is out of range of the array, the is false. +// If the given `index` is out of range of the array, the `found` is false. func (a *SortedStrArray) Remove(index int) (value string, found bool) { a.mu.Lock() defer a.mu.Unlock() @@ -180,7 +187,7 @@ func (a *SortedStrArray) RemoveValue(value string) bool { } // PopLeft pops and returns an item from the beginning of array. -// Note that if the array is empty, the is false. +// Note that if the array is empty, the `found` is false. func (a *SortedStrArray) PopLeft() (value string, found bool) { a.mu.Lock() defer a.mu.Unlock() @@ -193,7 +200,7 @@ func (a *SortedStrArray) PopLeft() (value string, found bool) { } // PopRight pops and returns an item from the end of array. -// Note that if the array is empty, the is false. +// Note that if the array is empty, the `found` is false. func (a *SortedStrArray) PopRight() (value string, found bool) { a.mu.Lock() defer a.mu.Unlock() @@ -207,16 +214,16 @@ func (a *SortedStrArray) PopRight() (value string, found bool) { } // PopRand randomly pops and return an item out of array. -// Note that if the array is empty, the is false. +// Note that if the array is empty, the `found` is false. func (a *SortedStrArray) PopRand() (value string, found bool) { a.mu.Lock() defer a.mu.Unlock() return a.doRemoveWithoutLock(grand.Intn(len(a.array))) } -// PopRands randomly pops and returns items out of array. -// If the given is greater than size of the array, it returns all elements of the array. -// Note that if given <= 0 or the array is empty, it returns nil. +// PopRands randomly pops and returns `size` items out of array. +// If the given `size` is greater than size of the array, it returns all elements of the array. +// Note that if given `size` <= 0 or the array is empty, it returns nil. func (a *SortedStrArray) PopRands(size int) []string { a.mu.Lock() defer a.mu.Unlock() @@ -233,9 +240,9 @@ func (a *SortedStrArray) PopRands(size int) []string { return array } -// PopLefts pops and returns items from the beginning of array. -// If the given is greater than size of the array, it returns all elements of the array. -// Note that if given <= 0 or the array is empty, it returns nil. +// PopLefts pops and returns `size` items from the beginning of array. +// If the given `size` is greater than size of the array, it returns all elements of the array. +// Note that if given `size` <= 0 or the array is empty, it returns nil. func (a *SortedStrArray) PopLefts(size int) []string { a.mu.Lock() defer a.mu.Unlock() @@ -252,9 +259,9 @@ func (a *SortedStrArray) PopLefts(size int) []string { return value } -// PopRights pops and returns items from the end of array. -// If the given is greater than size of the array, it returns all elements of the array. -// Note that if given <= 0 or the array is empty, it returns nil. +// PopRights pops and returns `size` items from the end of array. +// If the given `size` is greater than size of the array, it returns all elements of the array. +// Note that if given `size` <= 0 or the array is empty, it returns nil. func (a *SortedStrArray) PopRights(size int) []string { a.mu.Lock() defer a.mu.Unlock() @@ -276,8 +283,8 @@ func (a *SortedStrArray) PopRights(size int) []string { // Notice, if in concurrent-safe usage, it returns a copy of slice; // else a pointer to the underlying data. // -// If is negative, then the offset will start from the end of array. -// If is omitted, then the sequence will have everything from start up +// If `end` is negative, then the offset will start from the end of array. +// If `end` is omitted, then the sequence will have everything from start up // until the end of the array. func (a *SortedStrArray) Range(start int, end ...int) []string { a.mu.RLock() @@ -303,7 +310,7 @@ func (a *SortedStrArray) Range(start int, end ...int) []string { } // SubSlice returns a slice of elements from the array as specified -// by the and parameters. +// by the `offset` and `size` parameters. // If in concurrent safe usage, it returns a copy of the slice; else a pointer. // // If offset is non-negative, the sequence will start at that offset in the array. @@ -418,7 +425,7 @@ func (a *SortedStrArray) ContainsI(value string) bool { return false } -// Search searches array by , returns the index of , +// Search searches array by `value`, returns the index of `value`, // or returns -1 if not exists. func (a *SortedStrArray) Search(value string) (index int) { if i, r := a.binSearch(value, true); r == 0 { @@ -429,9 +436,9 @@ func (a *SortedStrArray) Search(value string) (index int) { // Binary search. // It returns the last compared index and the result. -// If equals to 0, it means the value at is equals to . -// If lesser than 0, it means the value at is lesser than . -// If greater than 0, it means the value at is greater than . +// If `result` equals to 0, it means the value at `index` is equals to `value`. +// If `result` lesser than 0, it means the value at `index` is lesser than `value`. +// If `result` greater than 0, it means the value at `index` is greater than `value`. func (a *SortedStrArray) binSearch(value string, lock bool) (index int, result int) { if lock { a.mu.RLock() @@ -511,7 +518,7 @@ func (a *SortedStrArray) Clear() *SortedStrArray { return a } -// LockFunc locks writing by callback function . +// LockFunc locks writing by callback function `f`. func (a *SortedStrArray) LockFunc(f func(array []string)) *SortedStrArray { a.mu.Lock() defer a.mu.Unlock() @@ -519,7 +526,7 @@ func (a *SortedStrArray) LockFunc(f func(array []string)) *SortedStrArray { return a } -// RLockFunc locks reading by callback function . +// RLockFunc locks reading by callback function `f`. func (a *SortedStrArray) RLockFunc(f func(array []string)) *SortedStrArray { a.mu.RLock() defer a.mu.RUnlock() @@ -527,8 +534,8 @@ func (a *SortedStrArray) RLockFunc(f func(array []string)) *SortedStrArray { return a } -// Merge merges into current array. -// The parameter can be any garray or slice type. +// Merge merges `array` into current array. +// The parameter `array` can be any garray or slice type. // The difference between Merge and Append is Append supports only specified slice type, // but Merge supports more parameter types. func (a *SortedStrArray) Merge(array interface{}) *SortedStrArray { @@ -536,7 +543,7 @@ func (a *SortedStrArray) Merge(array interface{}) *SortedStrArray { } // Chunk splits an array into multiple arrays, -// the size of each array is determined by . +// the size of each array is determined by `size`. // The last chunk may contain less than size elements. func (a *SortedStrArray) Chunk(size int) [][]string { if size < 1 { @@ -568,7 +575,7 @@ func (a *SortedStrArray) Rand() (value string, found bool) { return a.array[grand.Intn(len(a.array))], true } -// Rands randomly returns items from array(no deleting). +// Rands randomly returns `size` items from array(no deleting). func (a *SortedStrArray) Rands(size int) []string { a.mu.RLock() defer a.mu.RUnlock() @@ -582,7 +589,7 @@ func (a *SortedStrArray) Rands(size int) []string { return array } -// Join joins array elements with a string . +// Join joins array elements with a string `glue`. func (a *SortedStrArray) Join(glue string) string { a.mu.RLock() defer a.mu.RUnlock() @@ -615,8 +622,8 @@ func (a *SortedStrArray) Iterator(f func(k int, v string) bool) { a.IteratorAsc(f) } -// IteratorAsc iterates the array readonly in ascending order with given callback function . -// If returns true, then it continues iterating; or false to stop. +// IteratorAsc iterates the array readonly in ascending order with given callback function `f`. +// If `f` returns true, then it continues iterating; or false to stop. func (a *SortedStrArray) IteratorAsc(f func(k int, v string) bool) { a.mu.RLock() defer a.mu.RUnlock() @@ -627,8 +634,8 @@ func (a *SortedStrArray) IteratorAsc(f func(k int, v string) bool) { } } -// IteratorDesc iterates the array readonly in descending order with given callback function . -// If returns true, then it continues iterating; or false to stop. +// IteratorDesc iterates the array readonly in descending order with given callback function `f`. +// If `f` returns true, then it continues iterating; or false to stop. func (a *SortedStrArray) IteratorDesc(f func(k int, v string) bool) { a.mu.RLock() defer a.mu.RUnlock() @@ -671,7 +678,7 @@ func (a *SortedStrArray) UnmarshalJSON(b []byte) error { } a.mu.Lock() defer a.mu.Unlock() - if err := json.Unmarshal(b, &a.array); err != nil { + if err := json.UnmarshalUseNumber(b, &a.array); err != nil { return err } if a.array != nil { @@ -689,7 +696,7 @@ func (a *SortedStrArray) UnmarshalValue(value interface{}) (err error) { defer a.mu.Unlock() switch value.(type) { case string, []byte: - err = json.Unmarshal(gconv.Bytes(value), &a.array) + err = json.UnmarshalUseNumber(gconv.Bytes(value), &a.array) default: a.array = gconv.SliceStr(value) } @@ -720,7 +727,7 @@ func (a *SortedStrArray) FilterEmpty() *SortedStrArray { return a } -// Walk applies a user supplied function to every item of array. +// Walk applies a user supplied function `f` to every item of array. func (a *SortedStrArray) Walk(f func(value string) string) *SortedStrArray { a.mu.Lock() defer a.mu.Unlock() diff --git a/container/garray/garray_z_bench_any_test.go b/container/garray/garray_z_bench_any_test.go new file mode 100644 index 000000000..477c18a19 --- /dev/null +++ b/container/garray/garray_z_bench_any_test.go @@ -0,0 +1,39 @@ +// Copyright GoFrame Author(https://goframe.org). All Rights Reserved. +// +// This Source Code Form is subject to the terms of the MIT License. +// If a copy of the MIT was not distributed with this file, +// You can obtain one at https://github.com/gogf/gf. + +package garray_test + +import ( + "github.com/gogf/gf/container/garray" + "testing" +) + +type anySortedArrayItem struct { + priority int64 + value interface{} +} + +var ( + anyArray = garray.NewArray() + anySortedArray = garray.NewSortedArray(func(a, b interface{}) int { + return int(a.(anySortedArrayItem).priority - b.(anySortedArrayItem).priority) + }) +) + +func Benchmark_AnyArray_Add(b *testing.B) { + for i := 0; i < b.N; i++ { + anyArray.Append(i) + } +} + +func Benchmark_AnySortedArray_Add(b *testing.B) { + for i := 0; i < b.N; i++ { + anySortedArray.Add(anySortedArrayItem{ + priority: int64(i), + value: i, + }) + } +} diff --git a/container/garray/garray_z_example_any_test.go b/container/garray/garray_z_example_any_test.go index e7bdcfe4b..fccf99716 100644 --- a/container/garray/garray_z_example_any_test.go +++ b/container/garray/garray_z_example_any_test.go @@ -1,4 +1,4 @@ -// Copyright 2018 gf Author(https://github.com/gogf/gf). All Rights Reserved. +// Copyright GoFrame Author(https://goframe.org). All Rights Reserved. // // This Source Code Form is subject to the terms of the MIT License. // If a copy of the MIT was not distributed with this file, @@ -75,14 +75,14 @@ func ExampleNew() { func ExampleArray_Iterator() { array := garray.NewArrayFrom(g.Slice{"a", "b", "c"}) // Iterator is alias of IteratorAsc, which iterates the array readonly in ascending order - // with given callback function . - // If returns true, then it continues iterating; or false to stop. + // with given callback function `f`. + // If `f` returns true, then it continues iterating; or false to stop. array.Iterator(func(k int, v interface{}) bool { fmt.Println(k, v) return true }) - // IteratorDesc iterates the array readonly in descending order with given callback function . - // If returns true, then it continues iterating; or false to stop. + // IteratorDesc iterates the array readonly in descending order with given callback function `f`. + // If `f` returns true, then it continues iterating; or false to stop. array.IteratorDesc(func(k int, v interface{}) bool { fmt.Println(k, v) return true @@ -150,7 +150,7 @@ func ExampleArray_Chunk() { array := garray.NewFrom(g.Slice{1, 2, 3, 4, 5, 6, 7, 8, 9}) // Chunk splits an array into multiple arrays, - // the size of each array is determined by . + // the size of each array is determined by `size`. // The last chunk may contain less than size elements. fmt.Println(array.Chunk(2)) diff --git a/container/garray/garray_z_example_str_test.go b/container/garray/garray_z_example_str_test.go index 2d1a522f8..733322c6e 100644 --- a/container/garray/garray_z_example_str_test.go +++ b/container/garray/garray_z_example_str_test.go @@ -1,4 +1,4 @@ -// Copyright 2018 gf Author(https://github.com/gogf/gf). All Rights Reserved. +// Copyright GoFrame Author(https://goframe.org). All Rights Reserved. // // This Source Code Form is subject to the terms of the MIT License. // If a copy of the MIT was not distributed with this file, diff --git a/container/garray/garray_z_unit_all_basic_test.go b/container/garray/garray_z_unit_all_basic_test.go index b9da2fc95..fddebe271 100644 --- a/container/garray/garray_z_unit_all_basic_test.go +++ b/container/garray/garray_z_unit_all_basic_test.go @@ -1,4 +1,4 @@ -// Copyright 2018 gf Author(https://github.com/gogf/gf). All Rights Reserved. +// Copyright GoFrame Author(https://goframe.org). All Rights Reserved. // // This Source Code Form is subject to the terms of the MIT License. // If a copy of the MIT was not distributed with this file, diff --git a/container/garray/garray_z_unit_normal_any_array_test.go b/container/garray/garray_z_unit_normal_any_array_test.go index 19fb8aa8d..004e9bfd3 100644 --- a/container/garray/garray_z_unit_normal_any_array_test.go +++ b/container/garray/garray_z_unit_normal_any_array_test.go @@ -1,4 +1,4 @@ -// Copyright 2018 gf Author(https://github.com/gogf/gf). All Rights Reserved. +// Copyright GoFrame Author(https://goframe.org). All Rights Reserved. // // This Source Code Form is subject to the terms of the MIT License. // If a copy of the MIT was not distributed with this file, @@ -570,12 +570,12 @@ func TestArray_Json(t *testing.T) { t.Assert(err1, err2) a2 := garray.New() - err2 = json.Unmarshal(b2, &a2) + err2 = json.UnmarshalUseNumber(b2, &a2) t.Assert(err2, nil) t.Assert(a2.Slice(), s1) var a3 garray.Array - err := json.Unmarshal(b2, &a3) + err := json.UnmarshalUseNumber(b2, &a3) t.Assert(err, nil) t.Assert(a3.Slice(), s1) }) @@ -589,12 +589,12 @@ func TestArray_Json(t *testing.T) { t.Assert(err1, err2) a2 := garray.New() - err2 = json.Unmarshal(b2, &a2) + err2 = json.UnmarshalUseNumber(b2, &a2) t.Assert(err2, nil) t.Assert(a2.Slice(), s1) var a3 garray.Array - err := json.Unmarshal(b2, &a3) + err := json.UnmarshalUseNumber(b2, &a3) t.Assert(err, nil) t.Assert(a3.Slice(), s1) }) @@ -612,7 +612,7 @@ func TestArray_Json(t *testing.T) { t.Assert(err, nil) user := new(User) - err = json.Unmarshal(b, user) + err = json.UnmarshalUseNumber(b, user) t.Assert(err, nil) t.Assert(user.Name, data["Name"]) t.Assert(user.Scores, data["Scores"]) @@ -631,7 +631,7 @@ func TestArray_Json(t *testing.T) { t.Assert(err, nil) user := new(User) - err = json.Unmarshal(b, user) + err = json.UnmarshalUseNumber(b, user) t.Assert(err, nil) t.Assert(user.Name, data["Name"]) t.Assert(user.Scores, data["Scores"]) diff --git a/container/garray/garray_z_unit_normal_int_array_test.go b/container/garray/garray_z_unit_normal_int_array_test.go index f107ba201..ef1e763ef 100644 --- a/container/garray/garray_z_unit_normal_int_array_test.go +++ b/container/garray/garray_z_unit_normal_int_array_test.go @@ -1,4 +1,4 @@ -// Copyright 2018 gf Author(https://github.com/gogf/gf). All Rights Reserved. +// Copyright GoFrame Author(https://goframe.org). All Rights Reserved. // // This Source Code Form is subject to the terms of the MIT License. // If a copy of the MIT was not distributed with this file, @@ -615,11 +615,11 @@ func TestIntArray_Json(t *testing.T) { t.Assert(err1, err2) a2 := garray.NewIntArray() - err1 = json.Unmarshal(b2, &a2) + err1 = json.UnmarshalUseNumber(b2, &a2) t.Assert(a2.Slice(), s1) var a3 garray.IntArray - err := json.Unmarshal(b2, &a3) + err := json.UnmarshalUseNumber(b2, &a3) t.Assert(err, nil) t.Assert(a3.Slice(), s1) }) @@ -633,11 +633,11 @@ func TestIntArray_Json(t *testing.T) { t.Assert(err1, err2) a2 := garray.NewIntArray() - err1 = json.Unmarshal(b2, &a2) + err1 = json.UnmarshalUseNumber(b2, &a2) t.Assert(a2.Slice(), s1) var a3 garray.IntArray - err := json.Unmarshal(b2, &a3) + err := json.UnmarshalUseNumber(b2, &a3) t.Assert(err, nil) t.Assert(a3.Slice(), s1) }) @@ -655,7 +655,7 @@ func TestIntArray_Json(t *testing.T) { t.Assert(err, nil) user := new(User) - err = json.Unmarshal(b, user) + err = json.UnmarshalUseNumber(b, user) t.Assert(err, nil) t.Assert(user.Name, data["Name"]) t.Assert(user.Scores, data["Scores"]) @@ -674,7 +674,7 @@ func TestIntArray_Json(t *testing.T) { t.Assert(err, nil) user := new(User) - err = json.Unmarshal(b, user) + err = json.UnmarshalUseNumber(b, user) t.Assert(err, nil) t.Assert(user.Name, data["Name"]) t.Assert(user.Scores, data["Scores"]) @@ -758,16 +758,16 @@ func TestIntArray_UnmarshalValue(t *testing.T) { t.Assert(v.Array.Slice(), g.Slice{1, 2, 3}) }) // Map - gtest.C(t, func(t *gtest.T) { - var v *V - err := gconv.Struct(g.Map{ - "name": "john", - "array": g.Slice{1, 2, 3}, - }, &v) - t.Assert(err, nil) - t.Assert(v.Name, "john") - t.Assert(v.Array.Slice(), g.Slice{1, 2, 3}) - }) + //gtest.C(t, func(t *gtest.T) { + // var v *V + // err := gconv.Struct(g.Map{ + // "name": "john", + // "array": g.Slice{1, 2, 3}, + // }, &v) + // t.Assert(err, nil) + // t.Assert(v.Name, "john") + // t.Assert(v.Array.Slice(), g.Slice{1, 2, 3}) + //}) } func TestIntArray_FilterEmpty(t *testing.T) { diff --git a/container/garray/garray_z_unit_normal_str_array_test.go b/container/garray/garray_z_unit_normal_str_array_test.go index 28777dc28..be165863b 100644 --- a/container/garray/garray_z_unit_normal_str_array_test.go +++ b/container/garray/garray_z_unit_normal_str_array_test.go @@ -1,4 +1,4 @@ -// Copyright 2018 gf Author(https://github.com/gogf/gf). All Rights Reserved. +// Copyright GoFrame Author(https://goframe.org). All Rights Reserved. // // This Source Code Form is subject to the terms of the MIT License. // If a copy of the MIT was not distributed with this file, @@ -614,11 +614,11 @@ func TestStrArray_Json(t *testing.T) { t.Assert(err1, err2) a2 := garray.NewStrArray() - err1 = json.Unmarshal(b2, &a2) + err1 = json.UnmarshalUseNumber(b2, &a2) t.Assert(a2.Slice(), s1) var a3 garray.StrArray - err := json.Unmarshal(b2, &a3) + err := json.UnmarshalUseNumber(b2, &a3) t.Assert(err, nil) t.Assert(a3.Slice(), s1) }) @@ -632,11 +632,11 @@ func TestStrArray_Json(t *testing.T) { t.Assert(err1, err2) a2 := garray.NewStrArray() - err1 = json.Unmarshal(b2, &a2) + err1 = json.UnmarshalUseNumber(b2, &a2) t.Assert(a2.Slice(), s1) var a3 garray.StrArray - err := json.Unmarshal(b2, &a3) + err := json.UnmarshalUseNumber(b2, &a3) t.Assert(err, nil) t.Assert(a3.Slice(), s1) }) @@ -654,7 +654,7 @@ func TestStrArray_Json(t *testing.T) { t.Assert(err, nil) user := new(User) - err = json.Unmarshal(b, user) + err = json.UnmarshalUseNumber(b, user) t.Assert(err, nil) t.Assert(user.Name, data["Name"]) t.Assert(user.Scores, data["Scores"]) @@ -673,7 +673,7 @@ func TestStrArray_Json(t *testing.T) { t.Assert(err, nil) user := new(User) - err = json.Unmarshal(b, user) + err = json.UnmarshalUseNumber(b, user) t.Assert(err, nil) t.Assert(user.Name, data["Name"]) t.Assert(user.Scores, data["Scores"]) diff --git a/container/garray/garray_z_unit_sorted_any_array_test.go b/container/garray/garray_z_unit_sorted_any_array_test.go index 3cf4cc185..baecec1b5 100644 --- a/container/garray/garray_z_unit_sorted_any_array_test.go +++ b/container/garray/garray_z_unit_sorted_any_array_test.go @@ -1,4 +1,4 @@ -// Copyright 2018 gf Author(https://github.com/gogf/gf). All Rights Reserved. +// Copyright GoFrame Author(https://goframe.org). All Rights Reserved. // // This Source Code Form is subject to the terms of the MIT License. // If a copy of the MIT was not distributed with this file, @@ -656,11 +656,11 @@ func TestSortedArray_Json(t *testing.T) { t.Assert(err1, err2) a2 := garray.NewSortedArray(gutil.ComparatorString) - err1 = json.Unmarshal(b2, &a2) + err1 = json.UnmarshalUseNumber(b2, &a2) t.Assert(a2.Slice(), s2) var a3 garray.SortedArray - err := json.Unmarshal(b2, &a3) + err := json.UnmarshalUseNumber(b2, &a3) t.Assert(err, nil) t.Assert(a3.Slice(), s1) t.Assert(a3.Interfaces(), s1) @@ -676,11 +676,11 @@ func TestSortedArray_Json(t *testing.T) { t.Assert(err1, err2) a2 := garray.NewSortedArray(gutil.ComparatorString) - err1 = json.Unmarshal(b2, &a2) + err1 = json.UnmarshalUseNumber(b2, &a2) t.Assert(a2.Slice(), s2) var a3 garray.SortedArray - err := json.Unmarshal(b2, &a3) + err := json.UnmarshalUseNumber(b2, &a3) t.Assert(err, nil) t.Assert(a3.Slice(), s1) t.Assert(a3.Interfaces(), s1) @@ -699,7 +699,7 @@ func TestSortedArray_Json(t *testing.T) { t.Assert(err, nil) user := new(User) - err = json.Unmarshal(b, user) + err = json.UnmarshalUseNumber(b, user) t.Assert(err, nil) t.Assert(user.Name, data["Name"]) t.AssertNE(user.Scores, nil) @@ -735,7 +735,7 @@ func TestSortedArray_Json(t *testing.T) { t.Assert(err, nil) user := new(User) - err = json.Unmarshal(b, user) + err = json.UnmarshalUseNumber(b, user) t.Assert(err, nil) t.Assert(user.Name, data["Name"]) t.AssertNE(user.Scores, nil) diff --git a/container/garray/garray_z_unit_sorted_int_array_test.go b/container/garray/garray_z_unit_sorted_int_array_test.go index 3d7e18490..56b873f47 100644 --- a/container/garray/garray_z_unit_sorted_int_array_test.go +++ b/container/garray/garray_z_unit_sorted_int_array_test.go @@ -1,4 +1,4 @@ -// Copyright 2018 gf Author(https://github.com/gogf/gf). All Rights Reserved. +// Copyright GoFrame Author(https://goframe.org). All Rights Reserved. // // This Source Code Form is subject to the terms of the MIT License. // If a copy of the MIT was not distributed with this file, @@ -557,11 +557,11 @@ func TestSortedIntArray_Json(t *testing.T) { t.Assert(err1, err2) a2 := garray.NewSortedIntArray() - err1 = json.Unmarshal(b2, &a2) + err1 = json.UnmarshalUseNumber(b2, &a2) t.Assert(a2.Slice(), s2) var a3 garray.SortedIntArray - err := json.Unmarshal(b2, &a3) + err := json.UnmarshalUseNumber(b2, &a3) t.Assert(err, nil) t.Assert(a3.Slice(), s1) }) @@ -576,11 +576,11 @@ func TestSortedIntArray_Json(t *testing.T) { t.Assert(err1, err2) a2 := garray.NewSortedIntArray() - err1 = json.Unmarshal(b2, &a2) + err1 = json.UnmarshalUseNumber(b2, &a2) t.Assert(a2.Slice(), s2) var a3 garray.SortedIntArray - err := json.Unmarshal(b2, &a3) + err := json.UnmarshalUseNumber(b2, &a3) t.Assert(err, nil) t.Assert(a3.Slice(), s1) }) @@ -598,7 +598,7 @@ func TestSortedIntArray_Json(t *testing.T) { t.Assert(err, nil) user := new(User) - err = json.Unmarshal(b, user) + err = json.UnmarshalUseNumber(b, user) t.Assert(err, nil) t.Assert(user.Name, data["Name"]) t.Assert(user.Scores, []int{98, 99, 100}) @@ -617,7 +617,7 @@ func TestSortedIntArray_Json(t *testing.T) { t.Assert(err, nil) user := new(User) - err = json.Unmarshal(b, user) + err = json.UnmarshalUseNumber(b, user) t.Assert(err, nil) t.Assert(user.Name, data["Name"]) t.Assert(user.Scores, []int{98, 99, 100}) diff --git a/container/garray/garray_z_unit_sorted_str_array_test.go b/container/garray/garray_z_unit_sorted_str_array_test.go index 4dbabc3c0..bab5c539e 100644 --- a/container/garray/garray_z_unit_sorted_str_array_test.go +++ b/container/garray/garray_z_unit_sorted_str_array_test.go @@ -1,4 +1,4 @@ -// Copyright 2018 gf Author(https://github.com/gogf/gf). All Rights Reserved. +// Copyright GoFrame Author(https://goframe.org). All Rights Reserved. // // This Source Code Form is subject to the terms of the MIT License. // If a copy of the MIT was not distributed with this file, @@ -577,12 +577,12 @@ func TestSortedStrArray_Json(t *testing.T) { t.Assert(err1, err2) a2 := garray.NewSortedStrArray() - err1 = json.Unmarshal(b2, &a2) + err1 = json.UnmarshalUseNumber(b2, &a2) t.Assert(a2.Slice(), s2) t.Assert(a2.Interfaces(), s2) var a3 garray.SortedStrArray - err := json.Unmarshal(b2, &a3) + err := json.UnmarshalUseNumber(b2, &a3) t.Assert(err, nil) t.Assert(a3.Slice(), s1) t.Assert(a3.Interfaces(), s1) @@ -598,12 +598,12 @@ func TestSortedStrArray_Json(t *testing.T) { t.Assert(err1, err2) a2 := garray.NewSortedStrArray() - err1 = json.Unmarshal(b2, &a2) + err1 = json.UnmarshalUseNumber(b2, &a2) t.Assert(a2.Slice(), s2) t.Assert(a2.Interfaces(), s2) var a3 garray.SortedStrArray - err := json.Unmarshal(b2, &a3) + err := json.UnmarshalUseNumber(b2, &a3) t.Assert(err, nil) t.Assert(a3.Slice(), s1) t.Assert(a3.Interfaces(), s1) @@ -622,7 +622,7 @@ func TestSortedStrArray_Json(t *testing.T) { t.Assert(err, nil) user := new(User) - err = json.Unmarshal(b, user) + err = json.UnmarshalUseNumber(b, user) t.Assert(err, nil) t.Assert(user.Name, data["Name"]) t.Assert(user.Scores, []string{"A", "A", "A+"}) @@ -641,7 +641,7 @@ func TestSortedStrArray_Json(t *testing.T) { t.Assert(err, nil) user := new(User) - err = json.Unmarshal(b, user) + err = json.UnmarshalUseNumber(b, user) t.Assert(err, nil) t.Assert(user.Name, data["Name"]) t.Assert(user.Scores, []string{"A", "A", "A+"}) diff --git a/container/glist/glist.go b/container/glist/glist.go index 2b4b68bdd..94319e50e 100644 --- a/container/glist/glist.go +++ b/container/glist/glist.go @@ -1,4 +1,4 @@ -// Copyright 2017 gf Author(https://github.com/gogf/gf). All Rights Reserved. +// Copyright GoFrame Author(https://goframe.org). All Rights Reserved. // // This Source Code Form is subject to the terms of the MIT License. // If a copy of the MIT was not distributed with l file, @@ -519,7 +519,7 @@ func (l *List) UnmarshalJSON(b []byte) error { l.list = list.New() } var array []interface{} - if err := json.Unmarshal(b, &array); err != nil { + if err := json.UnmarshalUseNumber(b, &array); err != nil { return err } l.PushBacks(array) @@ -536,7 +536,7 @@ func (l *List) UnmarshalValue(value interface{}) (err error) { var array []interface{} switch value.(type) { case string, []byte: - err = json.Unmarshal(gconv.Bytes(value), &array) + err = json.UnmarshalUseNumber(gconv.Bytes(value), &array) default: array = gconv.SliceAny(value) } diff --git a/container/glist/glist_example_test.go b/container/glist/glist_example_test.go index af1118c89..c2a00fd04 100644 --- a/container/glist/glist_example_test.go +++ b/container/glist/glist_example_test.go @@ -1,4 +1,4 @@ -// Copyright 2018 gf Author(https://github.com/gogf/gf). All Rights Reserved. +// Copyright GoFrame Author(https://goframe.org). All Rights Reserved. // // This Source Code Form is subject to the terms of the MIT License. // If a copy of the MIT was not distributed with this file, diff --git a/container/glist/glist_z_bench_test.go b/container/glist/glist_z_bench_test.go index 48889e11e..f9d1b749b 100644 --- a/container/glist/glist_z_bench_test.go +++ b/container/glist/glist_z_bench_test.go @@ -1,4 +1,4 @@ -// Copyright 2018 gf Author(https://github.com/gogf/gf). All Rights Reserved. +// Copyright GoFrame Author(https://goframe.org). All Rights Reserved. // // This Source Code Form is subject to the terms of the MIT License. // If a copy of the MIT was not distributed with this file, diff --git a/container/glist/glist_z_unit_test.go b/container/glist/glist_z_unit_test.go index 2c04169ac..5e1da47c8 100644 --- a/container/glist/glist_z_unit_test.go +++ b/container/glist/glist_z_unit_test.go @@ -1,4 +1,4 @@ -// Copyright 2019 gf Author(https://github.com/gogf/gf). All Rights Reserved. +// Copyright GoFrame Author(https://goframe.org). All Rights Reserved. // // This Source Code Form is subject to the terms of the MIT License. // If a copy of the MIT was not distributed with this file, @@ -711,7 +711,7 @@ func TestList_Json(t *testing.T) { b, err := json.Marshal(a) t.Assert(err, nil) - err = json.Unmarshal(b, l) + err = json.UnmarshalUseNumber(b, l) t.Assert(err, nil) t.Assert(l.FrontAll(), a) }) @@ -721,7 +721,7 @@ func TestList_Json(t *testing.T) { b, err := json.Marshal(a) t.Assert(err, nil) - err = json.Unmarshal(b, &l) + err = json.UnmarshalUseNumber(b, &l) t.Assert(err, nil) t.Assert(l.FrontAll(), a) }) diff --git a/container/gmap/gmap.go b/container/gmap/gmap.go index e3e22c190..b5e887e62 100644 --- a/container/gmap/gmap.go +++ b/container/gmap/gmap.go @@ -1,4 +1,4 @@ -// Copyright 2017 gf Author(https://github.com/gogf/gf). All Rights Reserved. +// Copyright GoFrame Author(https://goframe.org). All Rights Reserved. // // This Source Code Form is subject to the terms of the MIT License. // If a copy of the MIT was not distributed with gm file, diff --git a/container/gmap/gmap_hash_any_any_map.go b/container/gmap/gmap_hash_any_any_map.go index 28859ed45..a42cf26ea 100644 --- a/container/gmap/gmap_hash_any_any_map.go +++ b/container/gmap/gmap_hash_any_any_map.go @@ -1,4 +1,4 @@ -// Copyright 2017 gf Author(https://github.com/gogf/gf). All Rights Reserved. +// Copyright GoFrame Author(https://goframe.org). All Rights Reserved. // // This Source Code Form is subject to the terms of the MIT License. // If a copy of the MIT was not distributed with gm file, @@ -476,7 +476,7 @@ func (m *AnyAnyMap) UnmarshalJSON(b []byte) error { m.data = make(map[interface{}]interface{}) } var data map[string]interface{} - if err := json.Unmarshal(b, &data); err != nil { + if err := json.UnmarshalUseNumber(b, &data); err != nil { return err } for k, v := range data { diff --git a/container/gmap/gmap_hash_int_any_map.go b/container/gmap/gmap_hash_int_any_map.go index 52df0cebc..f99fd77d4 100644 --- a/container/gmap/gmap_hash_int_any_map.go +++ b/container/gmap/gmap_hash_int_any_map.go @@ -1,4 +1,4 @@ -// Copyright 2017 gf Author(https://github.com/gogf/gf). All Rights Reserved. +// Copyright GoFrame Author(https://goframe.org). All Rights Reserved. // // This Source Code Form is subject to the terms of the MIT License. // If a copy of the MIT was not distributed with gm file, @@ -475,7 +475,7 @@ func (m *IntAnyMap) UnmarshalJSON(b []byte) error { if m.data == nil { m.data = make(map[int]interface{}) } - if err := json.Unmarshal(b, &m.data); err != nil { + if err := json.UnmarshalUseNumber(b, &m.data); err != nil { return err } return nil @@ -490,7 +490,7 @@ func (m *IntAnyMap) UnmarshalValue(value interface{}) (err error) { } switch value.(type) { case string, []byte: - return json.Unmarshal(gconv.Bytes(value), &m.data) + return json.UnmarshalUseNumber(gconv.Bytes(value), &m.data) default: for k, v := range gconv.Map(value) { m.data[gconv.Int(k)] = v diff --git a/container/gmap/gmap_hash_int_int_map.go b/container/gmap/gmap_hash_int_int_map.go index 4d546661e..3f8b8d411 100644 --- a/container/gmap/gmap_hash_int_int_map.go +++ b/container/gmap/gmap_hash_int_int_map.go @@ -1,4 +1,4 @@ -// Copyright 2017 gf Author(https://github.com/gogf/gf). All Rights Reserved. +// Copyright GoFrame Author(https://goframe.org). All Rights Reserved. // // This Source Code Form is subject to the terms of the MIT License. // If a copy of the MIT was not distributed with gm file, @@ -446,7 +446,7 @@ func (m *IntIntMap) UnmarshalJSON(b []byte) error { if m.data == nil { m.data = make(map[int]int) } - if err := json.Unmarshal(b, &m.data); err != nil { + if err := json.UnmarshalUseNumber(b, &m.data); err != nil { return err } return nil @@ -461,7 +461,7 @@ func (m *IntIntMap) UnmarshalValue(value interface{}) (err error) { } switch value.(type) { case string, []byte: - return json.Unmarshal(gconv.Bytes(value), &m.data) + return json.UnmarshalUseNumber(gconv.Bytes(value), &m.data) default: for k, v := range gconv.Map(value) { m.data[gconv.Int(k)] = gconv.Int(v) diff --git a/container/gmap/gmap_hash_int_str_map.go b/container/gmap/gmap_hash_int_str_map.go index 8d1f1edfd..c5f5ac323 100644 --- a/container/gmap/gmap_hash_int_str_map.go +++ b/container/gmap/gmap_hash_int_str_map.go @@ -1,4 +1,4 @@ -// Copyright 2017 gf Author(https://github.com/gogf/gf). All Rights Reserved. +// Copyright GoFrame Author(https://goframe.org). All Rights Reserved. // // This Source Code Form is subject to the terms of the MIT License. // If a copy of the MIT was not distributed with gm file, @@ -446,7 +446,7 @@ func (m *IntStrMap) UnmarshalJSON(b []byte) error { if m.data == nil { m.data = make(map[int]string) } - if err := json.Unmarshal(b, &m.data); err != nil { + if err := json.UnmarshalUseNumber(b, &m.data); err != nil { return err } return nil @@ -461,7 +461,7 @@ func (m *IntStrMap) UnmarshalValue(value interface{}) (err error) { } switch value.(type) { case string, []byte: - return json.Unmarshal(gconv.Bytes(value), &m.data) + return json.UnmarshalUseNumber(gconv.Bytes(value), &m.data) default: for k, v := range gconv.Map(value) { m.data[gconv.Int(k)] = gconv.String(v) diff --git a/container/gmap/gmap_hash_str_any_map.go b/container/gmap/gmap_hash_str_any_map.go index 423923e2c..04d70f664 100644 --- a/container/gmap/gmap_hash_str_any_map.go +++ b/container/gmap/gmap_hash_str_any_map.go @@ -1,4 +1,4 @@ -// Copyright 2017 gf Author(https://github.com/gogf/gf). All Rights Reserved. +// Copyright GoFrame Author(https://goframe.org). All Rights Reserved. // // This Source Code Form is subject to the terms of the MIT License. // If a copy of the MIT was not distributed with gm file, @@ -471,7 +471,7 @@ func (m *StrAnyMap) UnmarshalJSON(b []byte) error { if m.data == nil { m.data = make(map[string]interface{}) } - if err := json.Unmarshal(b, &m.data); err != nil { + if err := json.UnmarshalUseNumber(b, &m.data); err != nil { return err } return nil diff --git a/container/gmap/gmap_hash_str_int_map.go b/container/gmap/gmap_hash_str_int_map.go index 58f98f017..7bc03cd34 100644 --- a/container/gmap/gmap_hash_str_int_map.go +++ b/container/gmap/gmap_hash_str_int_map.go @@ -1,4 +1,4 @@ -// Copyright 2017 gf Author(https://github.com/gogf/gf). All Rights Reserved. +// Copyright GoFrame Author(https://goframe.org). All Rights Reserved. // // This Source Code Form is subject to the terms of the MIT License. // If a copy of the MIT was not distributed with gm file, @@ -449,7 +449,7 @@ func (m *StrIntMap) UnmarshalJSON(b []byte) error { if m.data == nil { m.data = make(map[string]int) } - if err := json.Unmarshal(b, &m.data); err != nil { + if err := json.UnmarshalUseNumber(b, &m.data); err != nil { return err } return nil @@ -464,7 +464,7 @@ func (m *StrIntMap) UnmarshalValue(value interface{}) (err error) { } switch value.(type) { case string, []byte: - return json.Unmarshal(gconv.Bytes(value), &m.data) + return json.UnmarshalUseNumber(gconv.Bytes(value), &m.data) default: for k, v := range gconv.Map(value) { m.data[k] = gconv.Int(v) diff --git a/container/gmap/gmap_hash_str_str_map.go b/container/gmap/gmap_hash_str_str_map.go index 5565b28a7..ae68640c8 100644 --- a/container/gmap/gmap_hash_str_str_map.go +++ b/container/gmap/gmap_hash_str_str_map.go @@ -1,4 +1,4 @@ -// Copyright 2017 gf Author(https://github.com/gogf/gf). All Rights Reserved. +// Copyright GoFrame Author(https://goframe.org). All Rights Reserved. // // This Source Code Form is subject to the terms of the MIT License. // If a copy of the MIT was not distributed with gm file, @@ -449,7 +449,7 @@ func (m *StrStrMap) UnmarshalJSON(b []byte) error { if m.data == nil { m.data = make(map[string]string) } - if err := json.Unmarshal(b, &m.data); err != nil { + if err := json.UnmarshalUseNumber(b, &m.data); err != nil { return err } return nil diff --git a/container/gmap/gmap_list_map.go b/container/gmap/gmap_list_map.go index 823cd7a54..790998da7 100644 --- a/container/gmap/gmap_list_map.go +++ b/container/gmap/gmap_list_map.go @@ -1,4 +1,4 @@ -// Copyright 2017 gf Author(https://github.com/gogf/gf). All Rights Reserved. +// Copyright GoFrame Author(https://goframe.org). All Rights Reserved. // // This Source Code Form is subject to the terms of the MIT License. // If a copy of the MIT was not distributed with gm file, @@ -530,7 +530,7 @@ func (m *ListMap) UnmarshalJSON(b []byte) error { m.list = glist.New() } var data map[string]interface{} - if err := json.Unmarshal(b, &data); err != nil { + if err := json.UnmarshalUseNumber(b, &data); err != nil { return err } for key, value := range data { diff --git a/container/gmap/gmap_tree_map.go b/container/gmap/gmap_tree_map.go index 9bcce1b62..1b9f02783 100644 --- a/container/gmap/gmap_tree_map.go +++ b/container/gmap/gmap_tree_map.go @@ -1,4 +1,4 @@ -// Copyright 2017 gf Author(https://github.com/gogf/gf). All Rights Reserved. +// Copyright GoFrame Author(https://goframe.org). All Rights Reserved. // // This Source Code Form is subject to the terms of the MIT License. // If a copy of the MIT was not distributed with gm file, diff --git a/container/gmap/gmap_z_basic_test.go b/container/gmap/gmap_z_basic_test.go index 534ccc610..5ffcdebf3 100644 --- a/container/gmap/gmap_z_basic_test.go +++ b/container/gmap/gmap_z_basic_test.go @@ -1,4 +1,4 @@ -// Copyright 2017-2019 gf Author(https://github.com/gogf/gf). All Rights Reserved. +// Copyright GoFrame Author(https://goframe.org). All Rights Reserved. // // This Source Code Form is subject to the terms of the MIT License. // If a copy of the MIT was not distributed with gm file, diff --git a/container/gmap/gmap_z_bench_maps_test.go b/container/gmap/gmap_z_bench_maps_test.go index 54dc6968c..b5e3b22ff 100644 --- a/container/gmap/gmap_z_bench_maps_test.go +++ b/container/gmap/gmap_z_bench_maps_test.go @@ -1,4 +1,4 @@ -// Copyright 2017 gf Author(https://github.com/gogf/gf). All Rights Reserved. +// Copyright GoFrame Author(https://goframe.org). All Rights Reserved. // // This Source Code Form is subject to the terms of the MIT License. // If a copy of the MIT was not distributed with gm file, diff --git a/container/gmap/gmap_z_bench_safe_test.go b/container/gmap/gmap_z_bench_safe_test.go index 2e84d15ca..46bbc1354 100644 --- a/container/gmap/gmap_z_bench_safe_test.go +++ b/container/gmap/gmap_z_bench_safe_test.go @@ -1,4 +1,4 @@ -// Copyright 2017 gf Author(https://github.com/gogf/gf). All Rights Reserved. +// Copyright GoFrame Author(https://goframe.org). All Rights Reserved. // // This Source Code Form is subject to the terms of the MIT License. // If a copy of the MIT was not distributed with gm file, diff --git a/container/gmap/gmap_z_bench_syncmap_test.go b/container/gmap/gmap_z_bench_syncmap_test.go index 64c252299..889edbe06 100644 --- a/container/gmap/gmap_z_bench_syncmap_test.go +++ b/container/gmap/gmap_z_bench_syncmap_test.go @@ -1,4 +1,4 @@ -// Copyright 2017 gf Author(https://github.com/gogf/gf). All Rights Reserved. +// Copyright GoFrame Author(https://goframe.org). All Rights Reserved. // // This Source Code Form is subject to the terms of the MIT License. // If a copy of the MIT was not distributed with gm file, diff --git a/container/gmap/gmap_z_bench_unsafe_test.go b/container/gmap/gmap_z_bench_unsafe_test.go index c63c88a8b..2a6f4853f 100644 --- a/container/gmap/gmap_z_bench_unsafe_test.go +++ b/container/gmap/gmap_z_bench_unsafe_test.go @@ -1,4 +1,4 @@ -// Copyright 2017 gf Author(https://github.com/gogf/gf). All Rights Reserved. +// Copyright GoFrame Author(https://goframe.org). All Rights Reserved. // // This Source Code Form is subject to the terms of the MIT License. // If a copy of the MIT was not distributed with gm file, diff --git a/container/gmap/gmap_z_example_any_any_test.go b/container/gmap/gmap_z_example_any_any_test.go index 68675b72a..73ad8d233 100644 --- a/container/gmap/gmap_z_example_any_any_test.go +++ b/container/gmap/gmap_z_example_any_any_test.go @@ -1,4 +1,4 @@ -// Copyright 2019 gf Author(https://github.com/gogf/gf). All Rights Reserved. +// Copyright GoFrame Author(https://goframe.org). All Rights Reserved. // // This Source Code Form is subject to the terms of the MIT License. // If a copy of the MIT was not distributed with gm file, diff --git a/container/gmap/gmap_z_unit_any_any_test.go b/container/gmap/gmap_z_unit_any_any_test.go index 53cc0bfb5..3a5c78592 100644 --- a/container/gmap/gmap_z_unit_any_any_test.go +++ b/container/gmap/gmap_z_unit_any_any_test.go @@ -1,4 +1,4 @@ -// Copyright 2017-2019 gf Author(https://github.com/gogf/gf). All Rights Reserved. +// Copyright GoFrame Author(https://goframe.org). All Rights Reserved. // // This Source Code Form is subject to the terms of the MIT License. // If a copy of the MIT was not distributed with gm file, @@ -255,7 +255,7 @@ func Test_AnyAnyMap_Json(t *testing.T) { t.Assert(err, nil) m := gmap.New() - err = json.Unmarshal(b, m) + err = json.UnmarshalUseNumber(b, m) t.Assert(err, nil) t.Assert(m.Get("k1"), data["k1"]) t.Assert(m.Get("k2"), data["k2"]) @@ -269,7 +269,7 @@ func Test_AnyAnyMap_Json(t *testing.T) { t.Assert(err, nil) var m gmap.Map - err = json.Unmarshal(b, &m) + err = json.UnmarshalUseNumber(b, &m) t.Assert(err, nil) t.Assert(m.Get("k1"), data["k1"]) t.Assert(m.Get("k2"), data["k2"]) diff --git a/container/gmap/gmap_z_unit_int_any_test.go b/container/gmap/gmap_z_unit_int_any_test.go index cc7f8e57f..2c6bc25ea 100644 --- a/container/gmap/gmap_z_unit_int_any_test.go +++ b/container/gmap/gmap_z_unit_int_any_test.go @@ -1,4 +1,4 @@ -// Copyright 2017-2019 gf Author(https://github.com/gogf/gf). All Rights Reserved. +// Copyright GoFrame Author(https://goframe.org). All Rights Reserved. // // This Source Code Form is subject to the terms of the MIT License. // If a copy of the MIT was not distributed with gm file, @@ -245,7 +245,7 @@ func Test_IntAnyMap_Json(t *testing.T) { t.Assert(err, nil) m := gmap.NewIntAnyMap() - err = json.Unmarshal(b, m) + err = json.UnmarshalUseNumber(b, m) t.Assert(err, nil) t.Assert(m.Get(1), data[1]) t.Assert(m.Get(2), data[2]) diff --git a/container/gmap/gmap_z_unit_int_int_test.go b/container/gmap/gmap_z_unit_int_int_test.go index f4763be3a..762ba223f 100644 --- a/container/gmap/gmap_z_unit_int_int_test.go +++ b/container/gmap/gmap_z_unit_int_int_test.go @@ -1,4 +1,4 @@ -// Copyright 2017-2019 gf Author(https://github.com/gogf/gf). All Rights Reserved. +// Copyright GoFrame Author(https://goframe.org). All Rights Reserved. // // This Source Code Form is subject to the terms of the MIT License. // If a copy of the MIT was not distributed with gm file, @@ -251,7 +251,7 @@ func Test_IntIntMap_Json(t *testing.T) { t.Assert(err, nil) m := gmap.NewIntIntMap() - err = json.Unmarshal(b, m) + err = json.UnmarshalUseNumber(b, m) t.Assert(err, nil) t.Assert(m.Get(1), data[1]) t.Assert(m.Get(2), data[2]) diff --git a/container/gmap/gmap_z_unit_int_str_test.go b/container/gmap/gmap_z_unit_int_str_test.go index 45f4d0bb9..6a91b00f6 100644 --- a/container/gmap/gmap_z_unit_int_str_test.go +++ b/container/gmap/gmap_z_unit_int_str_test.go @@ -1,4 +1,4 @@ -// Copyright 2017-2019 gf Author(https://github.com/gogf/gf). All Rights Reserved. +// Copyright GoFrame Author(https://goframe.org). All Rights Reserved. // // This Source Code Form is subject to the terms of the MIT License. // If a copy of the MIT was not distributed with gm file, @@ -249,7 +249,7 @@ func Test_IntStrMap_Json(t *testing.T) { t.Assert(err, nil) m := gmap.NewIntStrMap() - err = json.Unmarshal(b, m) + err = json.UnmarshalUseNumber(b, m) t.Assert(err, nil) t.Assert(m.Get(1), data[1]) t.Assert(m.Get(2), data[2]) diff --git a/container/gmap/gmap_z_unit_list_map_test.go b/container/gmap/gmap_z_unit_list_map_test.go index 08f60d7e5..4a226cfca 100644 --- a/container/gmap/gmap_z_unit_list_map_test.go +++ b/container/gmap/gmap_z_unit_list_map_test.go @@ -1,4 +1,4 @@ -// Copyright 2017-2019 gf Author(https://github.com/gogf/gf). All Rights Reserved. +// Copyright GoFrame Author(https://goframe.org). All Rights Reserved. // // This Source Code Form is subject to the terms of the MIT License. // If a copy of the MIT was not distributed with gm file, @@ -204,7 +204,7 @@ func Test_ListMap_Json(t *testing.T) { t.Assert(err, nil) m := gmap.NewListMap() - err = json.Unmarshal(b, m) + err = json.UnmarshalUseNumber(b, m) t.Assert(err, nil) t.Assert(m.Get("k1"), data["k1"]) t.Assert(m.Get("k2"), data["k2"]) @@ -219,7 +219,7 @@ func Test_ListMap_Json(t *testing.T) { t.Assert(err, nil) var m gmap.ListMap - err = json.Unmarshal(b, &m) + err = json.UnmarshalUseNumber(b, &m) t.Assert(err, nil) t.Assert(m.Get("k1"), data["k1"]) t.Assert(m.Get("k2"), data["k2"]) diff --git a/container/gmap/gmap_z_unit_str_any_test.go b/container/gmap/gmap_z_unit_str_any_test.go index 7f915a90a..1cee92de2 100644 --- a/container/gmap/gmap_z_unit_str_any_test.go +++ b/container/gmap/gmap_z_unit_str_any_test.go @@ -1,4 +1,4 @@ -// Copyright 2017-2019 gf Author(https://github.com/gogf/gf). All Rights Reserved. +// Copyright GoFrame Author(https://goframe.org). All Rights Reserved. // // This Source Code Form is subject to the terms of the MIT License. // If a copy of the MIT was not distributed with gm file, @@ -243,7 +243,7 @@ func Test_StrAnyMap_Json(t *testing.T) { t.Assert(err, nil) m := gmap.NewStrAnyMap() - err = json.Unmarshal(b, m) + err = json.UnmarshalUseNumber(b, m) t.Assert(err, nil) t.Assert(m.Get("k1"), data["k1"]) t.Assert(m.Get("k2"), data["k2"]) @@ -257,7 +257,7 @@ func Test_StrAnyMap_Json(t *testing.T) { t.Assert(err, nil) var m gmap.StrAnyMap - err = json.Unmarshal(b, &m) + err = json.UnmarshalUseNumber(b, &m) t.Assert(err, nil) t.Assert(m.Get("k1"), data["k1"]) t.Assert(m.Get("k2"), data["k2"]) diff --git a/container/gmap/gmap_z_unit_str_int_test.go b/container/gmap/gmap_z_unit_str_int_test.go index b806292c2..eb80c7760 100644 --- a/container/gmap/gmap_z_unit_str_int_test.go +++ b/container/gmap/gmap_z_unit_str_int_test.go @@ -1,4 +1,4 @@ -// Copyright 2017-2019 gf Author(https://github.com/gogf/gf). All Rights Reserved. +// Copyright GoFrame Author(https://goframe.org). All Rights Reserved. // // This Source Code Form is subject to the terms of the MIT License. // If a copy of the MIT was not distributed with gm file, @@ -247,7 +247,7 @@ func Test_StrIntMap_Json(t *testing.T) { t.Assert(err, nil) m := gmap.NewStrIntMap() - err = json.Unmarshal(b, m) + err = json.UnmarshalUseNumber(b, m) t.Assert(err, nil) t.Assert(m.Get("k1"), data["k1"]) t.Assert(m.Get("k2"), data["k2"]) @@ -261,7 +261,7 @@ func Test_StrIntMap_Json(t *testing.T) { t.Assert(err, nil) var m gmap.StrIntMap - err = json.Unmarshal(b, &m) + err = json.UnmarshalUseNumber(b, &m) t.Assert(err, nil) t.Assert(m.Get("k1"), data["k1"]) t.Assert(m.Get("k2"), data["k2"]) diff --git a/container/gmap/gmap_z_unit_str_str_test.go b/container/gmap/gmap_z_unit_str_str_test.go index 002ae13bc..e64a36af1 100644 --- a/container/gmap/gmap_z_unit_str_str_test.go +++ b/container/gmap/gmap_z_unit_str_str_test.go @@ -1,4 +1,4 @@ -// Copyright 2017-2019 gf Author(https://github.com/gogf/gf). All Rights Reserved. +// Copyright GoFrame Author(https://goframe.org). All Rights Reserved. // // This Source Code Form is subject to the terms of the MIT License. // If a copy of the MIT was not distributed with gm file, @@ -244,7 +244,7 @@ func Test_StrStrMap_Json(t *testing.T) { t.Assert(err, nil) m := gmap.NewStrStrMap() - err = json.Unmarshal(b, m) + err = json.UnmarshalUseNumber(b, m) t.Assert(err, nil) t.Assert(m.Get("k1"), data["k1"]) t.Assert(m.Get("k2"), data["k2"]) @@ -258,7 +258,7 @@ func Test_StrStrMap_Json(t *testing.T) { t.Assert(err, nil) var m gmap.StrStrMap - err = json.Unmarshal(b, &m) + err = json.UnmarshalUseNumber(b, &m) t.Assert(err, nil) t.Assert(m.Get("k1"), data["k1"]) t.Assert(m.Get("k2"), data["k2"]) diff --git a/container/gmap/gmap_z_unit_tree_map_test.go b/container/gmap/gmap_z_unit_tree_map_test.go index bcdec51b6..470cf40f0 100644 --- a/container/gmap/gmap_z_unit_tree_map_test.go +++ b/container/gmap/gmap_z_unit_tree_map_test.go @@ -1,4 +1,4 @@ -// Copyright 2017-2019 gf Author(https://github.com/gogf/gf). All Rights Reserved. +// Copyright GoFrame Author(https://goframe.org). All Rights Reserved. // // This Source Code Form is subject to the terms of the MIT License. // If a copy of the MIT was not distributed with gm file, @@ -188,7 +188,7 @@ func Test_TreeMap_Json(t *testing.T) { t.Assert(err, nil) m := gmap.NewTreeMap(gutil.ComparatorString) - err = json.Unmarshal(b, m) + err = json.UnmarshalUseNumber(b, m) t.Assert(err, nil) t.Assert(m.Get("k1"), data["k1"]) t.Assert(m.Get("k2"), data["k2"]) @@ -202,7 +202,7 @@ func Test_TreeMap_Json(t *testing.T) { t.Assert(err, nil) var m gmap.TreeMap - err = json.Unmarshal(b, &m) + err = json.UnmarshalUseNumber(b, &m) t.Assert(err, nil) t.Assert(m.Get("k1"), data["k1"]) t.Assert(m.Get("k2"), data["k2"]) diff --git a/container/gpool/gpool.go b/container/gpool/gpool.go index e7a59905d..4b7e5e583 100644 --- a/container/gpool/gpool.go +++ b/container/gpool/gpool.go @@ -1,4 +1,4 @@ -// Copyright GoFrame gf Author(https://github.com/gogf/gf). All Rights Reserved. +// Copyright GoFrame Author(https://goframe.org). All Rights Reserved. // // This Source Code Form is subject to the terms of the MIT License. // If a copy of the MIT was not distributed with this file, @@ -8,7 +8,8 @@ package gpool import ( - "errors" + "github.com/gogf/gf/errors/gcode" + "github.com/gogf/gf/errors/gerror" "time" "github.com/gogf/gf/container/glist" @@ -66,7 +67,7 @@ func New(ttl time.Duration, newFunc NewFunc, expireFunc ...ExpireFunc) *Pool { // Put puts an item to pool. func (p *Pool) Put(value interface{}) error { if p.closed.Val() { - return errors.New("pool is closed") + return gerror.NewCode(gcode.CodeInvalidOperation, "pool is closed") } item := &poolItem{ value: value, @@ -117,7 +118,7 @@ func (p *Pool) Get() (interface{}, error) { if p.NewFunc != nil { return p.NewFunc() } - return nil, errors.New("pool is empty") + return nil, gerror.NewCode(gcode.CodeInvalidOperation, "pool is empty") } // Size returns the count of available items of pool. diff --git a/container/gpool/gpool_bench_test.go b/container/gpool/gpool_bench_test.go index 4dc1346cf..f43cada2c 100644 --- a/container/gpool/gpool_bench_test.go +++ b/container/gpool/gpool_bench_test.go @@ -1,4 +1,4 @@ -// Copyright 2018 gf Author(https://github.com/gogf/gf). All Rights Reserved. +// Copyright GoFrame Author(https://goframe.org). All Rights Reserved. // // This Source Code Form is subject to the terms of the MIT License. // If a copy of the MIT was not distributed with this file, diff --git a/container/gpool/gpool_z_unit_test.go b/container/gpool/gpool_z_unit_test.go index cca547339..b8cfd61fa 100644 --- a/container/gpool/gpool_z_unit_test.go +++ b/container/gpool/gpool_z_unit_test.go @@ -1,4 +1,4 @@ -// Copyright 2019 gf Author(https://github.com/gogf/gf). All Rights Reserved. +// Copyright GoFrame Author(https://goframe.org). All Rights Reserved. // // This Source Code Form is subject to the terms of the MIT License. // If a copy of the MIT was not distributed with this file, diff --git a/container/gqueue/gqueue.go b/container/gqueue/gqueue.go index 1ecfc9231..b8a2dfbaa 100644 --- a/container/gqueue/gqueue.go +++ b/container/gqueue/gqueue.go @@ -1,4 +1,4 @@ -// Copyright 2017 gf Author(https://github.com/gogf/gf). All Rights Reserved. +// Copyright GoFrame Author(https://goframe.org). All Rights Reserved. // // This Source Code Form is subject to the terms of the MIT License. // If a copy of the MIT was not distributed with this file, @@ -35,10 +35,8 @@ type Queue struct { } const ( - // Size for queue buffer. - gDEFAULT_QUEUE_SIZE = 10000 - // Max batch size per-fetching from list. - gDEFAULT_MAX_BATCH_SIZE = 10 + defaultQueueSize = 10000 // Size for queue buffer. + defaultBatchSize = 10 // Max batch size per-fetching from list. ) // New returns an empty queue object. @@ -54,7 +52,7 @@ func New(limit ...int) *Queue { } else { q.list = glist.New(true) q.events = make(chan struct{}, math.MaxInt32) - q.C = make(chan interface{}, gDEFAULT_QUEUE_SIZE) + q.C = make(chan interface{}, defaultQueueSize) go q.asyncLoopFromListToChannel() } return q @@ -72,8 +70,8 @@ func (q *Queue) asyncLoopFromListToChannel() { <-q.events for !q.closed.Val() { if length := q.list.Len(); length > 0 { - if length > gDEFAULT_MAX_BATCH_SIZE { - length = gDEFAULT_MAX_BATCH_SIZE + if length > defaultBatchSize { + length = defaultBatchSize } for _, v := range q.list.PopFronts(length) { // When q.C is closed, it will panic here, especially q.C is being blocked for writing. @@ -101,7 +99,7 @@ func (q *Queue) Push(v interface{}) { q.C <- v } else { q.list.PushBack(v) - if len(q.events) < gDEFAULT_QUEUE_SIZE { + if len(q.events) < defaultQueueSize { q.events <- struct{}{} } } @@ -124,14 +122,14 @@ func (q *Queue) Close() { if q.limit > 0 { close(q.C) } - for i := 0; i < gDEFAULT_MAX_BATCH_SIZE; i++ { + for i := 0; i < defaultBatchSize; i++ { q.Pop() } } // Len returns the length of the queue. // Note that the result might not be accurate as there's a -// asynchronize channel reading the list constantly. +// asynchronous channel reading the list constantly. func (q *Queue) Len() (length int) { if q.list != nil { length += q.list.Len() diff --git a/container/gqueue/gqueue_bench_test.go b/container/gqueue/gqueue_z_bench_test.go similarity index 92% rename from container/gqueue/gqueue_bench_test.go rename to container/gqueue/gqueue_z_bench_test.go index cdddbc57e..cf12624e2 100644 --- a/container/gqueue/gqueue_bench_test.go +++ b/container/gqueue/gqueue_z_bench_test.go @@ -1,4 +1,4 @@ -// Copyright 2017 gf Author(https://github.com/gogf/gf). All Rights Reserved. +// Copyright GoFrame Author(https://goframe.org). All Rights Reserved. // // This Source Code Form is subject to the terms of the MIT License. // If a copy of the MIT was not distributed with this file, diff --git a/container/gqueue/gqueue_unit_test.go b/container/gqueue/gqueue_z_unit_test.go similarity index 93% rename from container/gqueue/gqueue_unit_test.go rename to container/gqueue/gqueue_z_unit_test.go index cc4d75b6b..565028dd7 100644 --- a/container/gqueue/gqueue_unit_test.go +++ b/container/gqueue/gqueue_z_unit_test.go @@ -1,4 +1,4 @@ -// Copyright 2017 gf Author(https://github.com/gogf/gf). All Rights Reserved. +// Copyright GoFrame Author(https://goframe.org). All Rights Reserved. // // This Source Code Form is subject to the terms of the MIT License. // If a copy of the MIT was not distributed with this file, diff --git a/container/gring/gring.go b/container/gring/gring.go index fc75335a5..ac308f56c 100644 --- a/container/gring/gring.go +++ b/container/gring/gring.go @@ -1,4 +1,4 @@ -// Copyright 2018 gf Author(https://github.com/gogf/gf). All Rights Reserved. +// Copyright GoFrame Author(https://goframe.org). All Rights Reserved. // // This Source Code Form is subject to the terms of the MIT License. // If a copy of the MIT was not distributed with this file, diff --git a/container/gring/gring_bench_test.go b/container/gring/gring_bench_test.go index dd9f69b22..5835af5bc 100644 --- a/container/gring/gring_bench_test.go +++ b/container/gring/gring_bench_test.go @@ -1,4 +1,4 @@ -// Copyright 2018 gf Author(https://github.com/gogf/gf). All Rights Reserved. +// Copyright GoFrame Author(https://goframe.org). All Rights Reserved. // // This Source Code Form is subject to the terms of the MIT License. // If a copy of the MIT was not distributed with this file, diff --git a/container/gring/gring_unit_test.go b/container/gring/gring_unit_test.go index 144a0d565..706f273a3 100644 --- a/container/gring/gring_unit_test.go +++ b/container/gring/gring_unit_test.go @@ -1,4 +1,4 @@ -// Copyright 2018 gf Author(https://github.com/gogf/gf). All Rights Reserved. +// Copyright GoFrame Author(https://goframe.org). All Rights Reserved. // // This Source Code Form is subject to the terms of the MIT License. // If a copy of the MIT was not distributed with this file, diff --git a/container/gset/gset_any_set.go b/container/gset/gset_any_set.go index 77b339bea..9db79a47a 100644 --- a/container/gset/gset_any_set.go +++ b/container/gset/gset_any_set.go @@ -1,4 +1,4 @@ -// Copyright 2017 gf Author(https://github.com/gogf/gf). All Rights Reserved. +// Copyright GoFrame Author(https://goframe.org). All Rights Reserved. // // This Source Code Form is subject to the terms of the MIT License. // If a copy of the MIT was not distributed with this file, @@ -477,7 +477,7 @@ func (set *Set) UnmarshalJSON(b []byte) error { set.data = make(map[interface{}]struct{}) } var array []interface{} - if err := json.Unmarshal(b, &array); err != nil { + if err := json.UnmarshalUseNumber(b, &array); err != nil { return err } for _, v := range array { @@ -496,7 +496,7 @@ func (set *Set) UnmarshalValue(value interface{}) (err error) { var array []interface{} switch value.(type) { case string, []byte: - err = json.Unmarshal(gconv.Bytes(value), &array) + err = json.UnmarshalUseNumber(gconv.Bytes(value), &array) default: array = gconv.SliceAny(value) } diff --git a/container/gset/gset_int_set.go b/container/gset/gset_int_set.go index 6d006bfa2..39a98d9f3 100644 --- a/container/gset/gset_int_set.go +++ b/container/gset/gset_int_set.go @@ -1,4 +1,4 @@ -// Copyright 2017 gf Author(https://github.com/gogf/gf). All Rights Reserved. +// Copyright GoFrame Author(https://goframe.org). All Rights Reserved. // // This Source Code Form is subject to the terms of the MIT License. // If a copy of the MIT was not distributed with this file, @@ -437,7 +437,7 @@ func (set *IntSet) UnmarshalJSON(b []byte) error { set.data = make(map[int]struct{}) } var array []int - if err := json.Unmarshal(b, &array); err != nil { + if err := json.UnmarshalUseNumber(b, &array); err != nil { return err } for _, v := range array { @@ -456,7 +456,7 @@ func (set *IntSet) UnmarshalValue(value interface{}) (err error) { var array []int switch value.(type) { case string, []byte: - err = json.Unmarshal(gconv.Bytes(value), &array) + err = json.UnmarshalUseNumber(gconv.Bytes(value), &array) default: array = gconv.SliceInt(value) } diff --git a/container/gset/gset_str_set.go b/container/gset/gset_str_set.go index 23a8dd32f..24323c3e7 100644 --- a/container/gset/gset_str_set.go +++ b/container/gset/gset_str_set.go @@ -1,4 +1,4 @@ -// Copyright 2017 gf Author(https://github.com/gogf/gf). All Rights Reserved. +// Copyright GoFrame Author(https://goframe.org). All Rights Reserved. // // This Source Code Form is subject to the terms of the MIT License. // If a copy of the MIT was not distributed with this file, @@ -21,7 +21,7 @@ type StrSet struct { data map[string]struct{} } -// New create and returns a new set, which contains un-repeated items. +// NewStrSet create and returns a new set, which contains un-repeated items. // The parameter is used to specify whether using set in concurrent-safety, // which is false in default. func NewStrSet(safe ...bool) *StrSet { @@ -465,7 +465,7 @@ func (set *StrSet) UnmarshalJSON(b []byte) error { set.data = make(map[string]struct{}) } var array []string - if err := json.Unmarshal(b, &array); err != nil { + if err := json.UnmarshalUseNumber(b, &array); err != nil { return err } for _, v := range array { @@ -484,7 +484,7 @@ func (set *StrSet) UnmarshalValue(value interface{}) (err error) { var array []string switch value.(type) { case string, []byte: - err = json.Unmarshal(gconv.Bytes(value), &array) + err = json.UnmarshalUseNumber(gconv.Bytes(value), &array) default: array = gconv.SliceStr(value) } diff --git a/container/gset/gset_z_bench_test.go b/container/gset/gset_z_bench_test.go index 378cbae83..ed7a1c9cd 100644 --- a/container/gset/gset_z_bench_test.go +++ b/container/gset/gset_z_bench_test.go @@ -1,4 +1,4 @@ -// Copyright 2017 gf Author(https://github.com/gogf/gf). All Rights Reserved. +// Copyright GoFrame Author(https://goframe.org). All Rights Reserved. // // This Source Code Form is subject to the terms of the MIT License. // If a copy of the MIT was not distributed with this file, diff --git a/container/gset/gset_z_example_any_test.go b/container/gset/gset_z_example_any_test.go index 2fa07cd00..f1a08309d 100644 --- a/container/gset/gset_z_example_any_test.go +++ b/container/gset/gset_z_example_any_test.go @@ -1,4 +1,4 @@ -// Copyright 2020 gf Author(https://github.com/gogf/gf). All Rights Reserved. +// Copyright GoFrame Author(https://goframe.org). All Rights Reserved. // // This Source Code Form is subject to the terms of the MIT License. // If a copy of the MIT was not distributed with gm file, diff --git a/container/gset/gset_z_example_int_test.go b/container/gset/gset_z_example_int_test.go index cc14b4ec8..1118042d5 100644 --- a/container/gset/gset_z_example_int_test.go +++ b/container/gset/gset_z_example_int_test.go @@ -1,4 +1,4 @@ -// Copyright 2020 gf Author(https://github.com/gogf/gf). All Rights Reserved. +// Copyright GoFrame Author(https://goframe.org). All Rights Reserved. // // This Source Code Form is subject to the terms of the MIT License. // If a copy of the MIT was not distributed with gm file, diff --git a/container/gset/gset_z_example_str_test.go b/container/gset/gset_z_example_str_test.go index f511e2ceb..d0c714bd5 100644 --- a/container/gset/gset_z_example_str_test.go +++ b/container/gset/gset_z_example_str_test.go @@ -1,4 +1,4 @@ -// Copyright 2020 gf Author(https://github.com/gogf/gf). All Rights Reserved. +// Copyright GoFrame Author(https://goframe.org). All Rights Reserved. // // This Source Code Form is subject to the terms of the MIT License. // If a copy of the MIT was not distributed with gm file, diff --git a/container/gset/gset_z_unit_any_test.go b/container/gset/gset_z_unit_any_test.go index 50fa0417a..bef113955 100644 --- a/container/gset/gset_z_unit_any_test.go +++ b/container/gset/gset_z_unit_any_test.go @@ -1,4 +1,4 @@ -// Copyright 2018 gf Author(https://github.com/gogf/gf). All Rights Reserved. +// Copyright GoFrame Author(https://goframe.org). All Rights Reserved. // // This Source Code Form is subject to the terms of the MIT License. // If a copy of the MIT was not distributed with this file, @@ -327,7 +327,7 @@ func TestSet_Json(t *testing.T) { t.Assert(err1, err2) a2 := gset.New() - err2 = json.Unmarshal(b2, &a2) + err2 = json.UnmarshalUseNumber(b2, &a2) t.Assert(err2, nil) t.Assert(a2.Contains("a"), true) t.Assert(a2.Contains("b"), true) @@ -336,7 +336,7 @@ func TestSet_Json(t *testing.T) { t.Assert(a2.Contains("e"), false) var a3 gset.Set - err := json.Unmarshal(b2, &a3) + err := json.UnmarshalUseNumber(b2, &a3) t.Assert(err, nil) t.Assert(a3.Contains("a"), true) t.Assert(a3.Contains("b"), true) diff --git a/container/gset/gset_z_unit_int_test.go b/container/gset/gset_z_unit_int_test.go index 4ba7ad449..618200691 100644 --- a/container/gset/gset_z_unit_int_test.go +++ b/container/gset/gset_z_unit_int_test.go @@ -1,4 +1,4 @@ -// Copyright 2018 gf Author(https://github.com/gogf/gf). All Rights Reserved. +// Copyright GoFrame Author(https://goframe.org). All Rights Reserved. // // This Source Code Form is subject to the terms of the MIT License. // If a copy of the MIT was not distributed with this file, @@ -358,7 +358,7 @@ func TestIntSet_Json(t *testing.T) { t.Assert(err1, err2) a2 := gset.NewIntSet() - err2 = json.Unmarshal(b2, &a2) + err2 = json.UnmarshalUseNumber(b2, &a2) t.Assert(err2, nil) t.Assert(a2.Contains(1), true) t.Assert(a2.Contains(2), true) @@ -367,7 +367,7 @@ func TestIntSet_Json(t *testing.T) { t.Assert(a2.Contains(5), false) var a3 gset.IntSet - err := json.Unmarshal(b2, &a3) + err := json.UnmarshalUseNumber(b2, &a3) t.Assert(err, nil) t.Assert(a2.Contains(1), true) t.Assert(a2.Contains(2), true) diff --git a/container/gset/gset_z_unit_str_test.go b/container/gset/gset_z_unit_str_test.go index c283b8cdd..ab57cf6ca 100644 --- a/container/gset/gset_z_unit_str_test.go +++ b/container/gset/gset_z_unit_str_test.go @@ -1,4 +1,4 @@ -// Copyright 2018 gf Author(https://github.com/gogf/gf). All Rights Reserved. +// Copyright GoFrame Author(https://goframe.org). All Rights Reserved. // // This Source Code Form is subject to the terms of the MIT License. // If a copy of the MIT was not distributed with this file, @@ -404,7 +404,7 @@ func TestStrSet_Json(t *testing.T) { t.Assert(err1, err2) a2 := gset.NewStrSet() - err2 = json.Unmarshal(b2, &a2) + err2 = json.UnmarshalUseNumber(b2, &a2) t.Assert(err2, nil) t.Assert(a2.Contains("a"), true) t.Assert(a2.Contains("b"), true) @@ -413,7 +413,7 @@ func TestStrSet_Json(t *testing.T) { t.Assert(a2.Contains("e"), false) var a3 gset.StrSet - err := json.Unmarshal(b2, &a3) + err := json.UnmarshalUseNumber(b2, &a3) t.Assert(err, nil) t.Assert(a3.Contains("a"), true) t.Assert(a3.Contains("b"), true) diff --git a/container/gtree/gtree.go b/container/gtree/gtree.go index 56f69268d..2cb7b25ee 100644 --- a/container/gtree/gtree.go +++ b/container/gtree/gtree.go @@ -1,4 +1,4 @@ -// Copyright 2019 gf Author(https://github.com/gogf/gf). All Rights Reserved. +// Copyright GoFrame Author(https://goframe.org). All Rights Reserved. // // This Source Code Form is subject to the terms of the MIT License. // If a copy of the MIT was not distributed with this file, @@ -7,5 +7,4 @@ // Package gtree provides concurrent-safe/unsafe tree containers. // // Some implements are from: https://github.com/emirpasic/gods -// Thanks! package gtree diff --git a/container/gtree/gtree_avltree.go b/container/gtree/gtree_avltree.go index eb8e72d05..b5ee031dd 100644 --- a/container/gtree/gtree_avltree.go +++ b/container/gtree/gtree_avltree.go @@ -1,4 +1,4 @@ -// Copyright 2019 gf Author(https://github.com/gogf/gf). All Rights Reserved. +// Copyright GoFrame Author(https://goframe.org). All Rights Reserved. // // This Source Code Form is subject to the terms of the MIT License. // If a copy of the MIT was not distributed with this file, diff --git a/container/gtree/gtree_btree.go b/container/gtree/gtree_btree.go index a8c1bbb3b..45a3068b9 100644 --- a/container/gtree/gtree_btree.go +++ b/container/gtree/gtree_btree.go @@ -1,4 +1,4 @@ -// Copyright 2019 gf Author(https://github.com/gogf/gf). All Rights Reserved. +// Copyright GoFrame Author(https://goframe.org). All Rights Reserved. // // This Source Code Form is subject to the terms of the MIT License. // If a copy of the MIT was not distributed with this file, diff --git a/container/gtree/gtree_redblacktree.go b/container/gtree/gtree_redblacktree.go index f73c9e568..7ccf0fd1c 100644 --- a/container/gtree/gtree_redblacktree.go +++ b/container/gtree/gtree_redblacktree.go @@ -1,4 +1,4 @@ -// Copyright 2019 gf Author(https://github.com/gogf/gf). All Rights Reserved. +// Copyright GoFrame Author(https://goframe.org). All Rights Reserved. // // This Source Code Form is subject to the terms of the MIT License. // If a copy of the MIT was not distributed with this file, @@ -937,7 +937,7 @@ func (tree *RedBlackTree) UnmarshalJSON(b []byte) error { tree.comparator = gutil.ComparatorString } var data map[string]interface{} - if err := json.Unmarshal(b, &data); err != nil { + if err := json.UnmarshalUseNumber(b, &data); err != nil { return err } for k, v := range data { diff --git a/container/gtree/gtree_z_avl_tree_test.go b/container/gtree/gtree_z_avl_tree_test.go index aa5b0b981..32ee443fe 100644 --- a/container/gtree/gtree_z_avl_tree_test.go +++ b/container/gtree/gtree_z_avl_tree_test.go @@ -1,4 +1,4 @@ -// Copyright 2017-2019 gf Author(https://github.com/gogf/gf). All Rights Reserved. +// Copyright GoFrame Author(https://goframe.org). All Rights Reserved. // // This Source Code Form is subject to the terms of the MIT License. // If a copy of the MIT was not distributed with gm file, diff --git a/container/gtree/gtree_z_b_tree_test.go b/container/gtree/gtree_z_b_tree_test.go index 469226de2..92dedccc2 100644 --- a/container/gtree/gtree_z_b_tree_test.go +++ b/container/gtree/gtree_z_b_tree_test.go @@ -1,4 +1,4 @@ -// Copyright 2017-2019 gf Author(https://github.com/gogf/gf). All Rights Reserved. +// Copyright GoFrame Author(https://goframe.org). All Rights Reserved. // // This Source Code Form is subject to the terms of the MIT License. // If a copy of the MIT was not distributed with gm file, diff --git a/container/gtree/gtree_z_redblack_tree_test.go b/container/gtree/gtree_z_redblack_tree_test.go index 37dee0c9c..7af80b05b 100644 --- a/container/gtree/gtree_z_redblack_tree_test.go +++ b/container/gtree/gtree_z_redblack_tree_test.go @@ -1,4 +1,4 @@ -// Copyright 2017-2019 gf Author(https://github.com/gogf/gf). All Rights Reserved. +// Copyright GoFrame Author(https://goframe.org). All Rights Reserved. // // This Source Code Form is subject to the terms of the MIT License. // If a copy of the MIT was not distributed with gm file, diff --git a/container/gtype/bool.go b/container/gtype/bool.go index f24fc8661..adb49db20 100644 --- a/container/gtype/bool.go +++ b/container/gtype/bool.go @@ -1,4 +1,4 @@ -// Copyright 2018 gf Author(https://github.com/gogf/gf). All Rights Reserved. +// Copyright GoFrame Author(https://goframe.org). All Rights Reserved. // // This Source Code Form is subject to the terms of the MIT License. // If a copy of the MIT was not distributed with this file, diff --git a/container/gtype/byte.go b/container/gtype/byte.go index 0fdeb0ab4..3b2a8d09b 100644 --- a/container/gtype/byte.go +++ b/container/gtype/byte.go @@ -1,4 +1,4 @@ -// Copyright 2018 gf Author(https://github.com/gogf/gf). All Rights Reserved. +// Copyright GoFrame Author(https://goframe.org). All Rights Reserved. // // This Source Code Form is subject to the terms of the MIT License. // If a copy of the MIT was not distributed with this file, diff --git a/container/gtype/bytes.go b/container/gtype/bytes.go index 75ae554b4..9574558cb 100644 --- a/container/gtype/bytes.go +++ b/container/gtype/bytes.go @@ -1,4 +1,4 @@ -// Copyright 2018 gf Author(https://github.com/gogf/gf). All Rights Reserved. +// Copyright GoFrame Author(https://goframe.org). All Rights Reserved. // // This Source Code Form is subject to the terms of the MIT License. // If a copy of the MIT was not distributed with this file, diff --git a/container/gtype/float32.go b/container/gtype/float32.go index 087a35d88..2eaeb8960 100644 --- a/container/gtype/float32.go +++ b/container/gtype/float32.go @@ -1,4 +1,4 @@ -// Copyright 2018 gf Author(https://github.com/gogf/gf). All Rights Reserved. +// Copyright GoFrame Author(https://goframe.org). All Rights Reserved. // // This Source Code Form is subject to the terms of the MIT License. // If a copy of the MIT was not distributed with this file, diff --git a/container/gtype/float64.go b/container/gtype/float64.go index 749f1b622..ecc80facc 100644 --- a/container/gtype/float64.go +++ b/container/gtype/float64.go @@ -1,4 +1,4 @@ -// Copyright 2018 gf Author(https://github.com/gogf/gf). All Rights Reserved. +// Copyright GoFrame Author(https://goframe.org). All Rights Reserved. // // This Source Code Form is subject to the terms of the MIT License. // If a copy of the MIT was not distributed with this file, diff --git a/container/gtype/gtype.go b/container/gtype/gtype.go index 795d36beb..b43e7f7d6 100644 --- a/container/gtype/gtype.go +++ b/container/gtype/gtype.go @@ -1,4 +1,4 @@ -// Copyright 2018 gf Author(https://github.com/gogf/gf). All Rights Reserved. +// Copyright GoFrame Author(https://goframe.org). All Rights Reserved. // // This Source Code Form is subject to the terms of the MIT License. // If a copy of the MIT was not distributed with this file, diff --git a/container/gtype/int.go b/container/gtype/int.go index da649b946..c1b2543f0 100644 --- a/container/gtype/int.go +++ b/container/gtype/int.go @@ -1,4 +1,4 @@ -// Copyright 2018 gf Author(https://github.com/gogf/gf). All Rights Reserved. +// Copyright GoFrame Author(https://goframe.org). All Rights Reserved. // // This Source Code Form is subject to the terms of the MIT License. // If a copy of the MIT was not distributed with this file, diff --git a/container/gtype/int32.go b/container/gtype/int32.go index 75aba6adb..63d4a5d23 100644 --- a/container/gtype/int32.go +++ b/container/gtype/int32.go @@ -1,4 +1,4 @@ -// Copyright 2018 gf Author(https://github.com/gogf/gf). All Rights Reserved. +// Copyright GoFrame Author(https://goframe.org). All Rights Reserved. // // This Source Code Form is subject to the terms of the MIT License. // If a copy of the MIT was not distributed with this file, diff --git a/container/gtype/int64.go b/container/gtype/int64.go index 9d0f9448f..b9c356ebd 100644 --- a/container/gtype/int64.go +++ b/container/gtype/int64.go @@ -1,4 +1,4 @@ -// Copyright 2018 gf Author(https://github.com/gogf/gf). All Rights Reserved. +// Copyright GoFrame Author(https://goframe.org). All Rights Reserved. // // This Source Code Form is subject to the terms of the MIT License. // If a copy of the MIT was not distributed with this file, diff --git a/container/gtype/interface.go b/container/gtype/interface.go index 0c9734e3f..8cd554dde 100644 --- a/container/gtype/interface.go +++ b/container/gtype/interface.go @@ -1,4 +1,4 @@ -// Copyright 2018 gf Author(https://github.com/gogf/gf). All Rights Reserved. +// Copyright GoFrame Author(https://goframe.org). All Rights Reserved. // // This Source Code Form is subject to the terms of the MIT License. // If a copy of the MIT was not distributed with this file, @@ -58,7 +58,7 @@ func (v *Interface) MarshalJSON() ([]byte, error) { // UnmarshalJSON implements the interface UnmarshalJSON for json.Unmarshal. func (v *Interface) UnmarshalJSON(b []byte) error { var i interface{} - err := json.Unmarshal(b, &i) + err := json.UnmarshalUseNumber(b, &i) if err != nil { return err } diff --git a/container/gtype/string.go b/container/gtype/string.go index 58798140d..0e1483b6f 100644 --- a/container/gtype/string.go +++ b/container/gtype/string.go @@ -1,4 +1,4 @@ -// Copyright 2018 gf Author(https://github.com/gogf/gf). All Rights Reserved. +// Copyright GoFrame Author(https://goframe.org). All Rights Reserved. // // This Source Code Form is subject to the terms of the MIT License. // If a copy of the MIT was not distributed with this file, diff --git a/container/gtype/uint.go b/container/gtype/uint.go index d8802265d..b9203a9af 100644 --- a/container/gtype/uint.go +++ b/container/gtype/uint.go @@ -1,4 +1,4 @@ -// Copyright 2018 gf Author(https://github.com/gogf/gf). All Rights Reserved. +// Copyright GoFrame Author(https://goframe.org). All Rights Reserved. // // This Source Code Form is subject to the terms of the MIT License. // If a copy of the MIT was not distributed with this file, diff --git a/container/gtype/uint32.go b/container/gtype/uint32.go index 403b7de59..2fbdcb030 100644 --- a/container/gtype/uint32.go +++ b/container/gtype/uint32.go @@ -1,4 +1,4 @@ -// Copyright 2018 gf Author(https://github.com/gogf/gf). All Rights Reserved. +// Copyright GoFrame Author(https://goframe.org). All Rights Reserved. // // This Source Code Form is subject to the terms of the MIT License. // If a copy of the MIT was not distributed with this file, diff --git a/container/gtype/uint64.go b/container/gtype/uint64.go index d3f8b36eb..580163bb5 100644 --- a/container/gtype/uint64.go +++ b/container/gtype/uint64.go @@ -1,4 +1,4 @@ -// Copyright 2018 gf Author(https://github.com/gogf/gf). All Rights Reserved. +// Copyright GoFrame Author(https://goframe.org). All Rights Reserved. // // This Source Code Form is subject to the terms of the MIT License. // If a copy of the MIT was not distributed with this file, diff --git a/container/gtype/z_bench_basic_test.go b/container/gtype/z_bench_basic_test.go index 8a0d6f932..705d04f0c 100644 --- a/container/gtype/z_bench_basic_test.go +++ b/container/gtype/z_bench_basic_test.go @@ -1,4 +1,4 @@ -// Copyright 2018 gf Author(https://github.com/gogf/gf). All Rights Reserved. +// Copyright GoFrame Author(https://goframe.org). All Rights Reserved. // // This Source Code Form is subject to the terms of the MIT License. // If a copy of the MIT was not distributed with this file, diff --git a/container/gtype/z_bench_json_test.go b/container/gtype/z_bench_json_test.go index c4eeefeb5..b2830fae5 100644 --- a/container/gtype/z_bench_json_test.go +++ b/container/gtype/z_bench_json_test.go @@ -1,4 +1,4 @@ -// Copyright 2018 gf Author(https://github.com/gogf/gf). All Rights Reserved. +// Copyright GoFrame Author(https://goframe.org). All Rights Reserved. // // This Source Code Form is subject to the terms of the MIT License. // If a copy of the MIT was not distributed with this file, diff --git a/container/gtype/z_unit_bool_test.go b/container/gtype/z_unit_bool_test.go index 858868140..b30738a84 100644 --- a/container/gtype/z_unit_bool_test.go +++ b/container/gtype/z_unit_bool_test.go @@ -1,4 +1,4 @@ -// Copyright 2018 gf Author(https://github.com/gogf/gf). All Rights Reserved. +// Copyright GoFrame Author(https://goframe.org). All Rights Reserved. // // This Source Code Form is subject to the terms of the MIT License. // If a copy of the MIT was not distributed with this file, @@ -55,16 +55,16 @@ func Test_Bool_JSON(t *testing.T) { gtest.C(t, func(t *gtest.T) { var err error i := gtype.NewBool() - err = json.Unmarshal([]byte("true"), &i) + err = json.UnmarshalUseNumber([]byte("true"), &i) t.Assert(err, nil) t.Assert(i.Val(), true) - err = json.Unmarshal([]byte("false"), &i) + err = json.UnmarshalUseNumber([]byte("false"), &i) t.Assert(err, nil) t.Assert(i.Val(), false) - err = json.Unmarshal([]byte("1"), &i) + err = json.UnmarshalUseNumber([]byte("1"), &i) t.Assert(err, nil) t.Assert(i.Val(), true) - err = json.Unmarshal([]byte("0"), &i) + err = json.UnmarshalUseNumber([]byte("0"), &i) t.Assert(err, nil) t.Assert(i.Val(), false) }) @@ -78,7 +78,7 @@ func Test_Bool_JSON(t *testing.T) { t.Assert(b1, b2) i2 := gtype.NewBool() - err := json.Unmarshal(b2, &i2) + err := json.UnmarshalUseNumber(b2, &i2) t.Assert(err, nil) t.Assert(i2.Val(), i.Val()) }) @@ -91,7 +91,7 @@ func Test_Bool_JSON(t *testing.T) { t.Assert(b1, b2) i2 := gtype.NewBool() - err := json.Unmarshal(b2, &i2) + err := json.UnmarshalUseNumber(b2, &i2) t.Assert(err, nil) t.Assert(i2.Val(), i.Val()) }) diff --git a/container/gtype/z_unit_byte_test.go b/container/gtype/z_unit_byte_test.go index ce171432a..86ba6cfb9 100644 --- a/container/gtype/z_unit_byte_test.go +++ b/container/gtype/z_unit_byte_test.go @@ -1,4 +1,4 @@ -// Copyright 2018 gf Author(https://github.com/gogf/gf). All Rights Reserved. +// Copyright GoFrame Author(https://goframe.org). All Rights Reserved. // // This Source Code Form is subject to the terms of the MIT License. // If a copy of the MIT was not distributed with this file, @@ -53,7 +53,7 @@ func Test_Byte_JSON(t *testing.T) { gtest.C(t, func(t *gtest.T) { var err error i := gtype.NewByte() - err = json.Unmarshal([]byte("49"), &i) + err = json.UnmarshalUseNumber([]byte("49"), &i) t.Assert(err, nil) t.Assert(i.Val(), "49") }) diff --git a/container/gtype/z_unit_bytes_test.go b/container/gtype/z_unit_bytes_test.go index b0be15489..49145d290 100644 --- a/container/gtype/z_unit_bytes_test.go +++ b/container/gtype/z_unit_bytes_test.go @@ -1,4 +1,4 @@ -// Copyright 2018 gf Author(https://github.com/gogf/gf). All Rights Reserved. +// Copyright GoFrame Author(https://goframe.org). All Rights Reserved. // // This Source Code Form is subject to the terms of the MIT License. // If a copy of the MIT was not distributed with this file, @@ -39,7 +39,7 @@ func Test_Bytes_JSON(t *testing.T) { t.Assert(b1, b2) i2 := gtype.NewBytes() - err := json.Unmarshal(b2, &i2) + err := json.UnmarshalUseNumber(b2, &i2) t.Assert(err, nil) t.Assert(i2.Val(), b) }) diff --git a/container/gtype/z_unit_float32_test.go b/container/gtype/z_unit_float32_test.go index fb88f01ae..7aff7f79c 100644 --- a/container/gtype/z_unit_float32_test.go +++ b/container/gtype/z_unit_float32_test.go @@ -1,4 +1,4 @@ -// Copyright 2018 gf Author(https://github.com/gogf/gf). All Rights Reserved. +// Copyright GoFrame Author(https://goframe.org). All Rights Reserved. // // This Source Code Form is subject to the terms of the MIT License. // If a copy of the MIT was not distributed with this file, @@ -40,7 +40,7 @@ func Test_Float32_JSON(t *testing.T) { t.Assert(b1, b2) i2 := gtype.NewFloat32() - err := json.Unmarshal(b2, &i2) + err := json.UnmarshalUseNumber(b2, &i2) t.Assert(err, nil) t.Assert(i2.Val(), v) }) diff --git a/container/gtype/z_unit_float64_test.go b/container/gtype/z_unit_float64_test.go index 12079cb5c..1ba6cfa1e 100644 --- a/container/gtype/z_unit_float64_test.go +++ b/container/gtype/z_unit_float64_test.go @@ -1,4 +1,4 @@ -// Copyright 2018 gf Author(https://github.com/gogf/gf). All Rights Reserved. +// Copyright GoFrame Author(https://goframe.org). All Rights Reserved. // // This Source Code Form is subject to the terms of the MIT License. // If a copy of the MIT was not distributed with this file, @@ -38,7 +38,7 @@ func Test_Float64_JSON(t *testing.T) { t.Assert(b1, b2) i2 := gtype.NewFloat64() - err := json.Unmarshal(b2, &i2) + err := json.UnmarshalUseNumber(b2, &i2) t.Assert(err, nil) t.Assert(i2.Val(), v) }) diff --git a/container/gtype/z_unit_int32_test.go b/container/gtype/z_unit_int32_test.go index 53c268c7f..cf2250ae5 100644 --- a/container/gtype/z_unit_int32_test.go +++ b/container/gtype/z_unit_int32_test.go @@ -1,4 +1,4 @@ -// Copyright 2018 gf Author(https://github.com/gogf/gf). All Rights Reserved. +// Copyright GoFrame Author(https://goframe.org). All Rights Reserved. // // This Source Code Form is subject to the terms of the MIT License. // If a copy of the MIT was not distributed with this file, @@ -51,7 +51,7 @@ func Test_Int32_JSON(t *testing.T) { t.Assert(b1, b2) i2 := gtype.NewInt32() - err := json.Unmarshal(b2, &i2) + err := json.UnmarshalUseNumber(b2, &i2) t.Assert(err, nil) t.Assert(i2.Val(), v) }) diff --git a/container/gtype/z_unit_int64_test.go b/container/gtype/z_unit_int64_test.go index 21110b8bf..8b5c6a299 100644 --- a/container/gtype/z_unit_int64_test.go +++ b/container/gtype/z_unit_int64_test.go @@ -1,4 +1,4 @@ -// Copyright 2018 gf Author(https://github.com/gogf/gf). All Rights Reserved. +// Copyright GoFrame Author(https://goframe.org). All Rights Reserved. // // This Source Code Form is subject to the terms of the MIT License. // If a copy of the MIT was not distributed with this file, @@ -50,7 +50,7 @@ func Test_Int64_JSON(t *testing.T) { t.Assert(b1, b2) i2 := gtype.NewInt64() - err := json.Unmarshal(b2, &i2) + err := json.UnmarshalUseNumber(b2, &i2) t.Assert(err, nil) t.Assert(i2.Val(), i) }) diff --git a/container/gtype/z_unit_int_test.go b/container/gtype/z_unit_int_test.go index eccdffbd3..939ef81e9 100644 --- a/container/gtype/z_unit_int_test.go +++ b/container/gtype/z_unit_int_test.go @@ -1,4 +1,4 @@ -// Copyright 2018 gf Author(https://github.com/gogf/gf). All Rights Reserved. +// Copyright GoFrame Author(https://goframe.org). All Rights Reserved. // // This Source Code Form is subject to the terms of the MIT License. // If a copy of the MIT was not distributed with this file, @@ -50,7 +50,7 @@ func Test_Int_JSON(t *testing.T) { t.Assert(b1, b2) i2 := gtype.NewInt() - err := json.Unmarshal(b2, &i2) + err := json.UnmarshalUseNumber(b2, &i2) t.Assert(err, nil) t.Assert(i2.Val(), v) }) diff --git a/container/gtype/z_unit_interface_test.go b/container/gtype/z_unit_interface_test.go index 05e124a79..130102f63 100644 --- a/container/gtype/z_unit_interface_test.go +++ b/container/gtype/z_unit_interface_test.go @@ -1,4 +1,4 @@ -// Copyright 2018 gf Author(https://github.com/gogf/gf). All Rights Reserved. +// Copyright GoFrame Author(https://goframe.org). All Rights Reserved. // // This Source Code Form is subject to the terms of the MIT License. // If a copy of the MIT was not distributed with this file, @@ -40,7 +40,7 @@ func Test_Interface_JSON(t *testing.T) { t.Assert(b1, b2) i2 := gtype.New() - err := json.Unmarshal(b2, &i2) + err := json.UnmarshalUseNumber(b2, &i2) t.Assert(err, nil) t.Assert(i2.Val(), s) }) diff --git a/container/gtype/z_unit_string_test.go b/container/gtype/z_unit_string_test.go index bb1362a6a..3b42cca41 100644 --- a/container/gtype/z_unit_string_test.go +++ b/container/gtype/z_unit_string_test.go @@ -1,4 +1,4 @@ -// Copyright 2018 gf Author(https://github.com/gogf/gf). All Rights Reserved. +// Copyright GoFrame Author(https://goframe.org). All Rights Reserved. // // This Source Code Form is subject to the terms of the MIT License. // If a copy of the MIT was not distributed with this file, @@ -38,7 +38,7 @@ func Test_String_JSON(t *testing.T) { t.Assert(b1, b2) i2 := gtype.NewString() - err := json.Unmarshal(b2, &i2) + err := json.UnmarshalUseNumber(b2, &i2) t.Assert(err, nil) t.Assert(i2.Val(), s) }) diff --git a/container/gtype/z_unit_uint32_test.go b/container/gtype/z_unit_uint32_test.go index 650d4a97c..abe188dad 100644 --- a/container/gtype/z_unit_uint32_test.go +++ b/container/gtype/z_unit_uint32_test.go @@ -1,4 +1,4 @@ -// Copyright 2018 gf Author(https://github.com/gogf/gf). All Rights Reserved. +// Copyright GoFrame Author(https://goframe.org). All Rights Reserved. // // This Source Code Form is subject to the terms of the MIT License. // If a copy of the MIT was not distributed with this file, @@ -50,7 +50,7 @@ func Test_Uint32_JSON(t *testing.T) { t.Assert(b1, b2) i2 := gtype.NewUint32() - err := json.Unmarshal(b2, &i2) + err := json.UnmarshalUseNumber(b2, &i2) t.Assert(err, nil) t.Assert(i2.Val(), i) }) diff --git a/container/gtype/z_unit_uint64_test.go b/container/gtype/z_unit_uint64_test.go index a4e5c02a7..ab05217ff 100644 --- a/container/gtype/z_unit_uint64_test.go +++ b/container/gtype/z_unit_uint64_test.go @@ -1,4 +1,4 @@ -// Copyright 2018 gf Author(https://github.com/gogf/gf). All Rights Reserved. +// Copyright GoFrame Author(https://goframe.org). All Rights Reserved. // // This Source Code Form is subject to the terms of the MIT License. // If a copy of the MIT was not distributed with this file, @@ -55,7 +55,7 @@ func Test_Uint64_JSON(t *testing.T) { t.Assert(b1, b2) i2 := gtype.NewUint64() - err := json.Unmarshal(b2, &i2) + err := json.UnmarshalUseNumber(b2, &i2) t.Assert(err, nil) t.Assert(i2.Val(), i) }) diff --git a/container/gtype/z_unit_uint_test.go b/container/gtype/z_unit_uint_test.go index 5524031c4..4f314856e 100644 --- a/container/gtype/z_unit_uint_test.go +++ b/container/gtype/z_unit_uint_test.go @@ -1,4 +1,4 @@ -// Copyright 2018 gf Author(https://github.com/gogf/gf). All Rights Reserved. +// Copyright GoFrame Author(https://goframe.org). All Rights Reserved. // // This Source Code Form is subject to the terms of the MIT License. // If a copy of the MIT was not distributed with this file, @@ -49,7 +49,7 @@ func Test_Uint_JSON(t *testing.T) { t.Assert(b1, b2) i2 := gtype.NewUint() - err := json.Unmarshal(b2, &i2) + err := json.UnmarshalUseNumber(b2, &i2) t.Assert(err, nil) t.Assert(i2.Val(), i) }) diff --git a/container/gvar/gvar.go b/container/gvar/gvar.go index 3ee988702..aa4dcba31 100644 --- a/container/gvar/gvar.go +++ b/container/gvar/gvar.go @@ -1,4 +1,4 @@ -// Copyright GoFrame Author(https://github.com/gogf/gf). All Rights Reserved. +// Copyright GoFrame Author(https://goframe.org). All Rights Reserved. // // This Source Code Form is subject to the terms of the MIT License. // If a copy of the MIT was not distributed with this file, @@ -22,8 +22,8 @@ type Var struct { safe bool // Concurrent safe or not. } -// New creates and returns a new Var with given . -// The optional parameter specifies whether Var is used in concurrent-safety, +// New creates and returns a new Var with given `value`. +// The optional parameter `safe` specifies whether Var is used in concurrent-safety, // which is false in default. func New(value interface{}, safe ...bool) *Var { v := Var{} @@ -36,8 +36,8 @@ func New(value interface{}, safe ...bool) *Var { return &v } -// Create creates and returns a new Var with given . -// The optional parameter specifies whether Var is used in concurrent-safety, +// Create creates and returns a new Var with given `value`. +// The optional parameter `safe` specifies whether Var is used in concurrent-safety, // which is false in default. func Create(value interface{}, safe ...bool) Var { v := Var{} @@ -55,7 +55,7 @@ func (v *Var) Clone() *Var { return New(v.Val(), v.safe) } -// Set sets to , and returns the old value. +// Set sets `value` to `v`, and returns the old value. func (v *Var) Set(value interface{}) (old interface{}) { if v.safe { if t, ok := v.value.(*gtype.Interface); ok { @@ -68,7 +68,7 @@ func (v *Var) Set(value interface{}) (old interface{}) { return } -// Val returns the current value of . +// Val returns the current value of `v`. func (v *Var) Val() interface{} { if v == nil { return nil @@ -86,144 +86,96 @@ func (v *Var) Interface() interface{} { return v.Val() } -// Bytes converts and returns as []byte. +// Bytes converts and returns `v` as []byte. func (v *Var) Bytes() []byte { return gconv.Bytes(v.Val()) } -// String converts and returns as string. +// String converts and returns `v` as string. func (v *Var) String() string { return gconv.String(v.Val()) } -// Bool converts and returns as bool. +// Bool converts and returns `v` as bool. func (v *Var) Bool() bool { return gconv.Bool(v.Val()) } -// Int converts and returns as int. +// Int converts and returns `v` as int. func (v *Var) Int() int { return gconv.Int(v.Val()) } -// Ints converts and returns as []int. -func (v *Var) Ints() []int { - return gconv.Ints(v.Val()) -} - -// Int8 converts and returns as int8. +// Int8 converts and returns `v` as int8. func (v *Var) Int8() int8 { return gconv.Int8(v.Val()) } -// Int16 converts and returns as int16. +// Int16 converts and returns `v` as int16. func (v *Var) Int16() int16 { return gconv.Int16(v.Val()) } -// Int32 converts and returns as int32. +// Int32 converts and returns `v` as int32. func (v *Var) Int32() int32 { return gconv.Int32(v.Val()) } -// Int64 converts and returns as int64. +// Int64 converts and returns `v` as int64. func (v *Var) Int64() int64 { return gconv.Int64(v.Val()) } -// Uint converts and returns as uint. +// Uint converts and returns `v` as uint. func (v *Var) Uint() uint { return gconv.Uint(v.Val()) } -// Uints converts and returns as []uint. -func (v *Var) Uints() []uint { - return gconv.Uints(v.Val()) -} - -// Uint8 converts and returns as uint8. +// Uint8 converts and returns `v` as uint8. func (v *Var) Uint8() uint8 { return gconv.Uint8(v.Val()) } -// Uint16 converts and returns as uint16. +// Uint16 converts and returns `v` as uint16. func (v *Var) Uint16() uint16 { return gconv.Uint16(v.Val()) } -// Uint32 converts and returns as uint32. +// Uint32 converts and returns `v` as uint32. func (v *Var) Uint32() uint32 { return gconv.Uint32(v.Val()) } -// Uint64 converts and returns as uint64. +// Uint64 converts and returns `v` as uint64. func (v *Var) Uint64() uint64 { return gconv.Uint64(v.Val()) } -// Float32 converts and returns as float32. +// Float32 converts and returns `v` as float32. func (v *Var) Float32() float32 { return gconv.Float32(v.Val()) } -// Float64 converts and returns as float64. +// Float64 converts and returns `v` as float64. func (v *Var) Float64() float64 { return gconv.Float64(v.Val()) } -// Floats converts and returns as []float64. -func (v *Var) Floats() []float64 { - return gconv.Floats(v.Val()) -} - -// Strings converts and returns as []string. -func (v *Var) Strings() []string { - return gconv.Strings(v.Val()) -} - -// Interfaces converts and returns as []interfaces{}. -func (v *Var) Interfaces() []interface{} { - return gconv.Interfaces(v.Val()) -} - -// Slice is alias of Interfaces. -func (v *Var) Slice() []interface{} { - return v.Interfaces() -} - -// Array is alias of Interfaces. -func (v *Var) Array() []interface{} { - return v.Interfaces() -} - -// Vars converts and returns as []Var. -func (v *Var) Vars() []*Var { - array := gconv.Interfaces(v.Val()) - if len(array) == 0 { - return nil - } - vars := make([]*Var, len(array)) - for k, v := range array { - vars[k] = New(v) - } - return vars -} - -// Time converts and returns as time.Time. -// The parameter specifies the format of the time string using gtime, +// Time converts and returns `v` as time.Time. +// The parameter `format` specifies the format of the time string using gtime, // eg: Y-m-d H:i:s. func (v *Var) Time(format ...string) time.Time { return gconv.Time(v.Val(), format...) } -// Duration converts and returns as time.Duration. -// If value of is string, then it uses time.ParseDuration for conversion. +// Duration converts and returns `v` as time.Duration. +// If value of `v` is string, then it uses time.ParseDuration for conversion. func (v *Var) Duration() time.Duration { return gconv.Duration(v.Val()) } -// GTime converts and returns as *gtime.Time. -// The parameter specifies the format of the time string using gtime, +// GTime converts and returns `v` as *gtime.Time. +// The parameter `format` specifies the format of the time string using gtime, // eg: Y-m-d H:i:s. func (v *Var) GTime(format ...string) *gtime.Time { return gconv.GTime(v.Val(), format...) @@ -237,7 +189,7 @@ func (v *Var) MarshalJSON() ([]byte, error) { // UnmarshalJSON implements the interface UnmarshalJSON for json.Unmarshal. func (v *Var) UnmarshalJSON(b []byte) error { var i interface{} - err := json.Unmarshal(b, &i) + err := json.UnmarshalUseNumber(b, &i) if err != nil { return err } diff --git a/container/gvar/gvar_is.go b/container/gvar/gvar_is.go index 396b4e40e..b7ac9af48 100644 --- a/container/gvar/gvar_is.go +++ b/container/gvar/gvar_is.go @@ -1,4 +1,4 @@ -// Copyright GoFrame Author(https://github.com/gogf/gf). All Rights Reserved. +// Copyright GoFrame Author(https://goframe.org). All Rights Reserved. // // This Source Code Form is subject to the terms of the MIT License. // If a copy of the MIT was not distributed with this file, @@ -7,92 +7,45 @@ package gvar import ( - "github.com/gogf/gf/internal/empty" - "reflect" + "github.com/gogf/gf/internal/utils" ) -// IsNil checks whether is nil. +// IsNil checks whether `v` is nil. func (v *Var) IsNil() bool { - return v.Val() == nil + return utils.IsNil(v.Val()) } -// IsEmpty checks whether is empty. +// IsEmpty checks whether `v` is empty. func (v *Var) IsEmpty() bool { - return empty.IsEmpty(v.Val()) + return utils.IsEmpty(v.Val()) } -// IsInt checks whether is type of int. +// IsInt checks whether `v` is type of int. func (v *Var) IsInt() bool { - switch v.Val().(type) { - case int, *int, int8, *int8, int16, *int16, int32, *int32, int64, *int64: - return true - } - return false + return utils.IsInt(v.Val()) } -// IsUint checks whether is type of uint. +// IsUint checks whether `v` is type of uint. func (v *Var) IsUint() bool { - switch v.Val().(type) { - case uint, *uint, uint8, *uint8, uint16, *uint16, uint32, *uint32, uint64, *uint64: - return true - } - return false + return utils.IsUint(v.Val()) } -// IsFloat checks whether is type of float. +// IsFloat checks whether `v` is type of float. func (v *Var) IsFloat() bool { - switch v.Val().(type) { - case float32, *float32, float64, *float64: - return true - } - return false + return utils.IsFloat(v.Val()) } -// IsSlice checks whether is type of slice. +// IsSlice checks whether `v` is type of slice. func (v *Var) IsSlice() bool { - var ( - reflectValue = reflect.ValueOf(v.Val()) - reflectKind = reflectValue.Kind() - ) - for reflectKind == reflect.Ptr { - reflectValue = reflectValue.Elem() - } - switch reflectKind { - case reflect.Slice, reflect.Array: - return true - } - return false + return utils.IsSlice(v.Val()) } -// IsMap checks whether is type of map. +// IsMap checks whether `v` is type of map. func (v *Var) IsMap() bool { - var ( - reflectValue = reflect.ValueOf(v.Val()) - reflectKind = reflectValue.Kind() - ) - for reflectKind == reflect.Ptr { - reflectValue = reflectValue.Elem() - } - switch reflectKind { - case reflect.Map: - return true - } - return false + return utils.IsMap(v.Val()) } -// IsStruct checks whether is type of struct. +// IsStruct checks whether `v` is type of struct. func (v *Var) IsStruct() bool { - var ( - reflectValue = reflect.ValueOf(v.Val()) - reflectKind = reflectValue.Kind() - ) - for reflectKind == reflect.Ptr { - reflectValue = reflectValue.Elem() - reflectKind = reflectValue.Kind() - } - switch reflectKind { - case reflect.Struct: - return true - } - return false + return utils.IsStruct(v.Val()) } diff --git a/container/gvar/gvar_list.go b/container/gvar/gvar_list.go index bc6d74188..06b6f4ab0 100644 --- a/container/gvar/gvar_list.go +++ b/container/gvar/gvar_list.go @@ -1,4 +1,4 @@ -// Copyright 2020 gf Author(https://github.com/gogf/gf). All Rights Reserved. +// Copyright GoFrame Author(https://goframe.org). All Rights Reserved. // // This Source Code Form is subject to the terms of the MIT License. // If a copy of the MIT was not distributed with this file, @@ -10,15 +10,15 @@ import ( "github.com/gogf/gf/util/gutil" ) -// ListItemValues retrieves and returns the elements of all item struct/map with key . -// Note that the parameter should be type of slice which contains elements of map or struct, +// ListItemValues retrieves and returns the elements of all item struct/map with key `key`. +// Note that the parameter `list` should be type of slice which contains elements of map or struct, // or else it returns an empty slice. func (v *Var) ListItemValues(key interface{}) (values []interface{}) { return gutil.ListItemValues(v.Val(), key) } -// ListItemValuesUnique retrieves and returns the unique elements of all struct/map with key . -// Note that the parameter should be type of slice which contains elements of map or struct, +// ListItemValuesUnique retrieves and returns the unique elements of all struct/map with key `key`. +// Note that the parameter `list` should be type of slice which contains elements of map or struct, // or else it returns an empty slice. func (v *Var) ListItemValuesUnique(key string) []interface{} { return gutil.ListItemValuesUnique(v.Val(), key) diff --git a/container/gvar/gvar_map.go b/container/gvar/gvar_map.go index 56bace487..732bae265 100644 --- a/container/gvar/gvar_map.go +++ b/container/gvar/gvar_map.go @@ -1,4 +1,4 @@ -// Copyright 2020 gf Author(https://github.com/gogf/gf). All Rights Reserved. +// Copyright GoFrame Author(https://goframe.org). All Rights Reserved. // // This Source Code Form is subject to the terms of the MIT License. // If a copy of the MIT was not distributed with this file, @@ -8,7 +8,7 @@ package gvar import "github.com/gogf/gf/util/gconv" -// Map converts and returns as map[string]interface{}. +// Map converts and returns `v` as map[string]interface{}. func (v *Var) Map(tags ...string) map[string]interface{} { return gconv.Map(v.Val(), tags...) } @@ -18,12 +18,12 @@ func (v *Var) MapStrAny() map[string]interface{} { return v.Map() } -// MapStrStr converts and returns as map[string]string. +// MapStrStr converts and returns `v` as map[string]string. func (v *Var) MapStrStr(tags ...string) map[string]string { return gconv.MapStrStr(v.Val(), tags...) } -// MapStrVar converts and returns as map[string]Var. +// MapStrVar converts and returns `v` as map[string]Var. func (v *Var) MapStrVar(tags ...string) map[string]*Var { m := v.Map(tags...) if len(m) > 0 { @@ -36,17 +36,17 @@ func (v *Var) MapStrVar(tags ...string) map[string]*Var { return nil } -// MapDeep converts and returns as map[string]interface{} recursively. +// MapDeep converts and returns `v` as map[string]interface{} recursively. func (v *Var) MapDeep(tags ...string) map[string]interface{} { return gconv.MapDeep(v.Val(), tags...) } -// MapDeep converts and returns as map[string]string recursively. +// MapStrStrDeep converts and returns `v` as map[string]string recursively. func (v *Var) MapStrStrDeep(tags ...string) map[string]string { return gconv.MapStrStrDeep(v.Val(), tags...) } -// MapStrVarDeep converts and returns as map[string]*Var recursively. +// MapStrVarDeep converts and returns `v` as map[string]*Var recursively. func (v *Var) MapStrVarDeep(tags ...string) map[string]*Var { m := v.MapDeep(tags...) if len(m) > 0 { @@ -59,34 +59,33 @@ func (v *Var) MapStrVarDeep(tags ...string) map[string]*Var { return nil } -// Maps converts and returns as map[string]string. +// Maps converts and returns `v` as map[string]string. // See gconv.Maps. func (v *Var) Maps(tags ...string) []map[string]interface{} { return gconv.Maps(v.Val(), tags...) } -// MapToMap converts any map type variable to another map type variable . +// MapsDeep converts `value` to []map[string]interface{} recursively. +// See gconv.MapsDeep. +func (v *Var) MapsDeep(tags ...string) []map[string]interface{} { + return gconv.MapsDeep(v.Val(), tags...) +} + +// MapToMap converts any map type variable `params` to another map type variable `pointer`. // See gconv.MapToMap. func (v *Var) MapToMap(pointer interface{}, mapping ...map[string]string) (err error) { return gconv.MapToMap(v.Val(), pointer, mapping...) } -// MapToMapDeep converts any map type variable to another map type variable -// recursively. -// See gconv.MapToMapDeep. -func (v *Var) MapToMapDeep(pointer interface{}, mapping ...map[string]string) (err error) { - return gconv.MapToMapDeep(v.Val(), pointer, mapping...) -} - -// MapToMaps converts any map type variable to another map type variable . +// MapToMaps converts any map type variable `params` to another map type variable `pointer`. // See gconv.MapToMaps. func (v *Var) MapToMaps(pointer interface{}, mapping ...map[string]string) (err error) { return gconv.MapToMaps(v.Val(), pointer, mapping...) } -// MapToMapsDeep converts any map type variable to another map type variable -// recursively. +// MapToMapsDeep converts any map type variable `params` to another map type variable +// `pointer` recursively. // See gconv.MapToMapsDeep. func (v *Var) MapToMapsDeep(pointer interface{}, mapping ...map[string]string) (err error) { - return gconv.MapToMapsDeep(v.Val(), pointer, mapping...) + return gconv.MapToMaps(v.Val(), pointer, mapping...) } diff --git a/container/gvar/gvar_slice.go b/container/gvar/gvar_slice.go new file mode 100644 index 000000000..c9c6556ce --- /dev/null +++ b/container/gvar/gvar_slice.go @@ -0,0 +1,77 @@ +// Copyright GoFrame Author(https://goframe.org). All Rights Reserved. +// +// This Source Code Form is subject to the terms of the MIT License. +// If a copy of the MIT was not distributed with this file, +// You can obtain one at https://github.com/gogf/gf. + +package gvar + +import "github.com/gogf/gf/util/gconv" + +// Ints converts and returns `v` as []int. +func (v *Var) Ints() []int { + return gconv.Ints(v.Val()) +} + +// Int64s converts and returns `v` as []int64. +func (v *Var) Int64s() []int64 { + return gconv.Int64s(v.Val()) +} + +// Uints converts and returns `v` as []uint. +func (v *Var) Uints() []uint { + return gconv.Uints(v.Val()) +} + +// Uint64s converts and returns `v` as []uint64. +func (v *Var) Uint64s() []uint64 { + return gconv.Uint64s(v.Val()) +} + +// Floats is alias of Float64s. +func (v *Var) Floats() []float64 { + return gconv.Floats(v.Val()) +} + +// Float32s converts and returns `v` as []float32. +func (v *Var) Float32s() []float32 { + return gconv.Float32s(v.Val()) +} + +// Float64s converts and returns `v` as []float64. +func (v *Var) Float64s() []float64 { + return gconv.Float64s(v.Val()) +} + +// Strings converts and returns `v` as []string. +func (v *Var) Strings() []string { + return gconv.Strings(v.Val()) +} + +// Interfaces converts and returns `v` as []interfaces{}. +func (v *Var) Interfaces() []interface{} { + return gconv.Interfaces(v.Val()) +} + +// Slice is alias of Interfaces. +func (v *Var) Slice() []interface{} { + return v.Interfaces() +} + +// Array is alias of Interfaces. +func (v *Var) Array() []interface{} { + return v.Interfaces() +} + +// Vars converts and returns `v` as []Var. +func (v *Var) Vars() []*Var { + array := gconv.Interfaces(v.Val()) + if len(array) == 0 { + return nil + } + vars := make([]*Var, len(array)) + for k, v := range array { + vars[k] = New(v) + } + return vars +} diff --git a/container/gvar/gvar_struct.go b/container/gvar/gvar_struct.go index d215e4df1..26814fcd8 100644 --- a/container/gvar/gvar_struct.go +++ b/container/gvar/gvar_struct.go @@ -1,4 +1,4 @@ -// Copyright 2020 gf Author(https://github.com/gogf/gf). All Rights Reserved. +// Copyright GoFrame Author(https://goframe.org). All Rights Reserved. // // This Source Code Form is subject to the terms of the MIT License. // If a copy of the MIT was not distributed with this file, @@ -10,44 +10,48 @@ import ( "github.com/gogf/gf/util/gconv" ) -// Struct maps value of to . -// The parameter should be a pointer to a struct instance. -// The parameter is used to specify the key-to-attribute mapping rules. +// Struct maps value of `v` to `pointer`. +// The parameter `pointer` should be a pointer to a struct instance. +// The parameter `mapping` is used to specify the key-to-attribute mapping rules. func (v *Var) Struct(pointer interface{}, mapping ...map[string]string) error { return gconv.Struct(v.Val(), pointer, mapping...) } -// Struct maps value of to recursively. -// The parameter should be a pointer to a struct instance. -// The parameter is used to specify the key-to-attribute mapping rules. +// StructDeep maps value of `v` to `pointer` recursively. +// The parameter `pointer` should be a pointer to a struct instance. +// The parameter `mapping` is used to specify the key-to-attribute mapping rules. // Deprecated, use Struct instead. func (v *Var) StructDeep(pointer interface{}, mapping ...map[string]string) error { return gconv.StructDeep(v.Val(), pointer, mapping...) } -// Structs converts and returns as given struct slice. +// Structs converts and returns `v` as given struct slice. func (v *Var) Structs(pointer interface{}, mapping ...map[string]string) error { return gconv.Structs(v.Val(), pointer, mapping...) } -// StructsDeep converts and returns as given struct slice recursively. +// StructsDeep converts and returns `v` as given struct slice recursively. // Deprecated, use Struct instead. func (v *Var) StructsDeep(pointer interface{}, mapping ...map[string]string) error { return gconv.StructsDeep(v.Val(), pointer, mapping...) } // Scan automatically calls Struct or Structs function according to the type of parameter -// to implement the converting. -// It calls function Struct if is type of *struct/**struct to do the converting. -// It calls function Structs if is type of *[]struct/*[]*struct to do the converting. +// `pointer` to implement the converting. +// +// It calls function Struct if `pointer` is type of *struct/**struct to do the converting. +// It calls function Structs if `pointer` is type of *[]struct/*[]*struct to do the converting. func (v *Var) Scan(pointer interface{}, mapping ...map[string]string) error { return gconv.Scan(v.Val(), pointer, mapping...) } // ScanDeep automatically calls StructDeep or StructsDeep function according to the type of -// parameter to implement the converting. -// It calls function StructDeep if is type of *struct/**struct to do the converting. -// It calls function StructsDeep if is type of *[]struct/*[]*struct to do the converting. +// parameter `pointer` to implement the converting. +// +// It calls function StructDeep if `pointer` is type of *struct/**struct to do the converting. +// It calls function StructsDeep if `pointer` is type of *[]struct/*[]*struct to do the converting. +// +// Deprecated, use Scan instead. func (v *Var) ScanDeep(pointer interface{}, mapping ...map[string]string) error { return gconv.ScanDeep(v.Val(), pointer, mapping...) } diff --git a/container/gvar/gvar_z_bench_obj_test.go b/container/gvar/gvar_z_bench_obj_test.go index 895518338..8053ce174 100644 --- a/container/gvar/gvar_z_bench_obj_test.go +++ b/container/gvar/gvar_z_bench_obj_test.go @@ -1,4 +1,4 @@ -// Copyright 2018 gf Author(https://github.com/gogf/gf). All Rights Reserved. +// Copyright GoFrame Author(https://goframe.org). All Rights Reserved. // // This Source Code Form is subject to the terms of the MIT License. // If a copy of the MIT was not distributed with this file, diff --git a/container/gvar/gvar_z_bench_ptr_test.go b/container/gvar/gvar_z_bench_ptr_test.go index 619fafc37..815e8cabf 100644 --- a/container/gvar/gvar_z_bench_ptr_test.go +++ b/container/gvar/gvar_z_bench_ptr_test.go @@ -1,4 +1,4 @@ -// Copyright 2018 gf Author(https://github.com/gogf/gf). All Rights Reserved. +// Copyright GoFrame Author(https://goframe.org). All Rights Reserved. // // This Source Code Form is subject to the terms of the MIT License. // If a copy of the MIT was not distributed with this file, diff --git a/container/gvar/gvar_z_unit_basic_test.go b/container/gvar/gvar_z_unit_basic_test.go index a3ae8cff4..fd20aeeb5 100644 --- a/container/gvar/gvar_z_unit_basic_test.go +++ b/container/gvar/gvar_z_unit_basic_test.go @@ -1,4 +1,4 @@ -// Copyright 2019 gf Author(https://github.com/gogf/gf). All Rights Reserved. +// Copyright GoFrame Author(https://goframe.org). All Rights Reserved. // // This Source Code Form is subject to the terms of the MIT License. // If a copy of the MIT was not distributed with this file, @@ -221,62 +221,6 @@ func Test_Float64(t *testing.T) { }) } -func Test_Ints(t *testing.T) { - gtest.C(t, func(t *gtest.T) { - var arr = []int{1, 2, 3, 4, 5} - objOne := gvar.New(arr, true) - t.Assert(objOne.Ints()[0], arr[0]) - }) -} -func Test_Floats(t *testing.T) { - gtest.C(t, func(t *gtest.T) { - var arr = []float64{1, 2, 3, 4, 5} - objOne := gvar.New(arr, true) - t.Assert(objOne.Floats()[0], arr[0]) - }) -} -func Test_Strings(t *testing.T) { - gtest.C(t, func(t *gtest.T) { - var arr = []string{"hello", "world"} - objOne := gvar.New(arr, true) - t.Assert(objOne.Strings()[0], arr[0]) - }) -} - -func Test_Interfaces(t *testing.T) { - gtest.C(t, func(t *gtest.T) { - var arr = []int{1, 2, 3, 4, 5} - objOne := gvar.New(arr, true) - t.Assert(objOne.Interfaces(), arr) - }) -} - -func Test_Slice(t *testing.T) { - gtest.C(t, func(t *gtest.T) { - var arr = []int{1, 2, 3, 4, 5} - objOne := gvar.New(arr, true) - t.Assert(objOne.Slice(), arr) - }) -} - -func Test_Array(t *testing.T) { - gtest.C(t, func(t *gtest.T) { - var arr = []int{1, 2, 3, 4, 5} - objOne := gvar.New(arr, false) - t.Assert(objOne.Array(), arr) - }) -} - -func Test_Vars(t *testing.T) { - gtest.C(t, func(t *gtest.T) { - var arr = []int{1, 2, 3, 4, 5} - objOne := gvar.New(arr, false) - t.Assert(len(objOne.Vars()), 5) - t.Assert(objOne.Vars()[0].Int(), 1) - t.Assert(objOne.Vars()[4].Int(), 5) - }) -} - func Test_Time(t *testing.T) { gtest.C(t, func(t *gtest.T) { var timeUnix int64 = 1556242660 @@ -301,12 +245,57 @@ func Test_Duration(t *testing.T) { }) } -func Test_UnmarshalValue(t *testing.T) { - type V struct { - Name string - Var *gvar.Var - } +func Test_UnmarshalJson(t *testing.T) { gtest.C(t, func(t *gtest.T) { + type V struct { + Name string + Var *gvar.Var + } + var v *V + err := gconv.Struct(map[string]interface{}{ + "name": "john", + "var": "v", + }, &v) + t.Assert(err, nil) + t.Assert(v.Name, "john") + t.Assert(v.Var.String(), "v") + }) + gtest.C(t, func(t *gtest.T) { + type V struct { + Name string + Var gvar.Var + } + var v *V + err := gconv.Struct(map[string]interface{}{ + "name": "john", + "var": "v", + }, &v) + t.Assert(err, nil) + t.Assert(v.Name, "john") + t.Assert(v.Var.String(), "v") + }) +} + +func Test_UnmarshalValue(t *testing.T) { + gtest.C(t, func(t *gtest.T) { + type V struct { + Name string + Var *gvar.Var + } + var v *V + err := gconv.Struct(map[string]interface{}{ + "name": "john", + "var": "v", + }, &v) + t.Assert(err, nil) + t.Assert(v.Name, "john") + t.Assert(v.Var.String(), "v") + }) + gtest.C(t, func(t *gtest.T) { + type V struct { + Name string + Var gvar.Var + } var v *V err := gconv.Struct(map[string]interface{}{ "name": "john", diff --git a/container/gvar/gvar_z_unit_is_test.go b/container/gvar/gvar_z_unit_is_test.go index c1656e044..d485b2af7 100644 --- a/container/gvar/gvar_z_unit_is_test.go +++ b/container/gvar/gvar_z_unit_is_test.go @@ -1,4 +1,4 @@ -// Copyright GoFrame Author(https://github.com/gogf/gf). All Rights Reserved. +// Copyright GoFrame Author(https://goframe.org). All Rights Reserved. // // This Source Code Form is subject to the terms of the MIT License. // If a copy of the MIT was not distributed with this file, diff --git a/container/gvar/gvar_z_unit_json_test.go b/container/gvar/gvar_z_unit_json_test.go index 97d556ca6..dde33a732 100644 --- a/container/gvar/gvar_z_unit_json_test.go +++ b/container/gvar/gvar_z_unit_json_test.go @@ -1,4 +1,4 @@ -// Copyright 2020 gf Author(https://github.com/gogf/gf). All Rights Reserved. +// Copyright GoFrame Author(https://goframe.org). All Rights Reserved. // // This Source Code Form is subject to the terms of the MIT License. // If a copy of the MIT was not distributed with this file, @@ -14,7 +14,7 @@ import ( "testing" ) -func Test_Json(t *testing.T) { +func TestVar_Json(t *testing.T) { // Marshal gtest.C(t, func(t *gtest.T) { s := "i love gf" @@ -41,7 +41,7 @@ func Test_Json(t *testing.T) { b, err := json.Marshal(s) t.Assert(err, nil) - err = json.Unmarshal(b, v) + err = json.UnmarshalUseNumber(b, v) t.Assert(err, nil) t.Assert(v.String(), s) }) @@ -52,7 +52,7 @@ func Test_Json(t *testing.T) { b, err := json.Marshal(s) t.Assert(err, nil) - err = json.Unmarshal(b, &v) + err = json.UnmarshalUseNumber(b, &v) t.Assert(err, nil) t.Assert(v.String(), s) }) diff --git a/container/gvar/gvar_z_unit_list_test.go b/container/gvar/gvar_z_unit_list_test.go index fba1fd3bb..eb2cd2314 100644 --- a/container/gvar/gvar_z_unit_list_test.go +++ b/container/gvar/gvar_z_unit_list_test.go @@ -1,4 +1,4 @@ -// Copyright 2020 gf Author(https://github.com/gogf/gf). All Rights Reserved. +// Copyright GoFrame Author(https://goframe.org). All Rights Reserved. // // This Source Code Form is subject to the terms of the MIT License. // If a copy of the MIT was not distributed with this file, @@ -13,7 +13,7 @@ import ( "testing" ) -func Test_ListItemValues_Map(t *testing.T) { +func TestVar_ListItemValues_Map(t *testing.T) { gtest.C(t, func(t *gtest.T) { listMap := g.List{ g.Map{"id": 1, "score": 100}, @@ -34,7 +34,7 @@ func Test_ListItemValues_Map(t *testing.T) { }) } -func Test_ListItemValues_Struct(t *testing.T) { +func TestVar_ListItemValues_Struct(t *testing.T) { gtest.C(t, func(t *gtest.T) { type T struct { Id int @@ -78,7 +78,7 @@ func Test_ListItemValues_Struct(t *testing.T) { }) } -func Test_ListItemValuesUnique(t *testing.T) { +func TestVar_ListItemValuesUnique(t *testing.T) { gtest.C(t, func(t *gtest.T) { listMap := g.List{ g.Map{"id": 1, "score": 100}, diff --git a/container/gvar/gvar_z_unit_map_test.go b/container/gvar/gvar_z_unit_map_test.go index 181344761..d0b96deb6 100644 --- a/container/gvar/gvar_z_unit_map_test.go +++ b/container/gvar/gvar_z_unit_map_test.go @@ -1,4 +1,4 @@ -// Copyright 2020 gf Author(https://github.com/gogf/gf). All Rights Reserved. +// Copyright GoFrame Author(https://goframe.org). All Rights Reserved. // // This Source Code Form is subject to the terms of the MIT License. // If a copy of the MIT was not distributed with this file, @@ -13,7 +13,7 @@ import ( "testing" ) -func Test_Map(t *testing.T) { +func TestVar_Map(t *testing.T) { gtest.C(t, func(t *gtest.T) { m := g.Map{ "k1": "v1", @@ -24,3 +24,58 @@ func Test_Map(t *testing.T) { t.Assert(objOne.Map()["k2"], m["k2"]) }) } + +func TestVar_MapToMap(t *testing.T) { + // map[int]int -> map[string]string + // empty original map. + gtest.C(t, func(t *gtest.T) { + m1 := g.MapIntInt{} + m2 := g.MapStrStr{} + t.Assert(gvar.New(m1).MapToMap(&m2), nil) + t.Assert(len(m1), len(m2)) + }) + // map[int]int -> map[string]string + gtest.C(t, func(t *gtest.T) { + m1 := g.MapIntInt{ + 1: 100, + 2: 200, + } + m2 := g.MapStrStr{} + t.Assert(gvar.New(m1).MapToMap(&m2), nil) + t.Assert(m2["1"], m1[1]) + t.Assert(m2["2"], m1[2]) + }) + // map[string]interface{} -> map[string]string + gtest.C(t, func(t *gtest.T) { + m1 := g.Map{ + "k1": "v1", + "k2": "v2", + } + m2 := g.MapStrStr{} + t.Assert(gvar.New(m1).MapToMap(&m2), nil) + t.Assert(m2["k1"], m1["k1"]) + t.Assert(m2["k2"], m1["k2"]) + }) + // map[string]string -> map[string]interface{} + gtest.C(t, func(t *gtest.T) { + m1 := g.MapStrStr{ + "k1": "v1", + "k2": "v2", + } + m2 := g.Map{} + t.Assert(gvar.New(m1).MapToMap(&m2), nil) + t.Assert(m2["k1"], m1["k1"]) + t.Assert(m2["k2"], m1["k2"]) + }) + // map[string]interface{} -> map[interface{}]interface{} + gtest.C(t, func(t *gtest.T) { + m1 := g.MapStrStr{ + "k1": "v1", + "k2": "v2", + } + m2 := g.MapAnyAny{} + t.Assert(gvar.New(m1).MapToMap(&m2), nil) + t.Assert(m2["k1"], m1["k1"]) + t.Assert(m2["k2"], m1["k2"]) + }) +} diff --git a/container/gvar/gvar_z_unit_maptomap_test.go b/container/gvar/gvar_z_unit_maptomap_test.go deleted file mode 100644 index 708d0a3d1..000000000 --- a/container/gvar/gvar_z_unit_maptomap_test.go +++ /dev/null @@ -1,69 +0,0 @@ -// 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 gvar_test - -import ( - "github.com/gogf/gf/container/gvar" - "github.com/gogf/gf/frame/g" - "github.com/gogf/gf/test/gtest" - "testing" -) - -func Test_MapToMap(t *testing.T) { - // map[int]int -> map[string]string - // empty original map. - gtest.C(t, func(t *gtest.T) { - m1 := g.MapIntInt{} - m2 := g.MapStrStr{} - t.Assert(gvar.New(m1).MapToMap(&m2), nil) - t.Assert(len(m1), len(m2)) - }) - // map[int]int -> map[string]string - gtest.C(t, func(t *gtest.T) { - m1 := g.MapIntInt{ - 1: 100, - 2: 200, - } - m2 := g.MapStrStr{} - t.Assert(gvar.New(m1).MapToMap(&m2), nil) - t.Assert(m2["1"], m1[1]) - t.Assert(m2["2"], m1[2]) - }) - // map[string]interface{} -> map[string]string - gtest.C(t, func(t *gtest.T) { - m1 := g.Map{ - "k1": "v1", - "k2": "v2", - } - m2 := g.MapStrStr{} - t.Assert(gvar.New(m1).MapToMap(&m2), nil) - t.Assert(m2["k1"], m1["k1"]) - t.Assert(m2["k2"], m1["k2"]) - }) - // map[string]string -> map[string]interface{} - gtest.C(t, func(t *gtest.T) { - m1 := g.MapStrStr{ - "k1": "v1", - "k2": "v2", - } - m2 := g.Map{} - t.Assert(gvar.New(m1).MapToMap(&m2), nil) - t.Assert(m2["k1"], m1["k1"]) - t.Assert(m2["k2"], m1["k2"]) - }) - // map[string]interface{} -> map[interface{}]interface{} - gtest.C(t, func(t *gtest.T) { - m1 := g.MapStrStr{ - "k1": "v1", - "k2": "v2", - } - m2 := g.MapAnyAny{} - t.Assert(gvar.New(m1).MapToMap(&m2), nil) - t.Assert(m2["k1"], m1["k1"]) - t.Assert(m2["k2"], m1["k2"]) - }) -} diff --git a/container/gvar/gvar_z_unit_slice_test.go b/container/gvar/gvar_z_unit_slice_test.go new file mode 100644 index 000000000..585b17bbd --- /dev/null +++ b/container/gvar/gvar_z_unit_slice_test.go @@ -0,0 +1,111 @@ +// Copyright GoFrame Author(https://goframe.org). All Rights Reserved. +// +// This Source Code Form is subject to the terms of the MIT License. +// If a copy of the MIT was not distributed with this file, +// You can obtain one at https://github.com/gogf/gf. + +package gvar_test + +import ( + "github.com/gogf/gf/container/gvar" + "github.com/gogf/gf/test/gtest" + "testing" +) + +func TestVar_Ints(t *testing.T) { + gtest.C(t, func(t *gtest.T) { + var arr = []int{1, 2, 3, 4, 5} + objOne := gvar.New(arr, true) + t.Assert(objOne.Ints()[0], arr[0]) + }) +} + +func TestVar_Uints(t *testing.T) { + gtest.C(t, func(t *gtest.T) { + var arr = []int{1, 2, 3, 4, 5} + objOne := gvar.New(arr, true) + t.Assert(objOne.Uints()[0], arr[0]) + }) +} + +func TestVar_Int64s(t *testing.T) { + gtest.C(t, func(t *gtest.T) { + var arr = []int{1, 2, 3, 4, 5} + objOne := gvar.New(arr, true) + t.Assert(objOne.Int64s()[0], arr[0]) + }) +} + +func TestVar_Uint64s(t *testing.T) { + gtest.C(t, func(t *gtest.T) { + var arr = []int{1, 2, 3, 4, 5} + objOne := gvar.New(arr, true) + t.Assert(objOne.Uint64s()[0], arr[0]) + }) +} + +func TestVar_Floats(t *testing.T) { + gtest.C(t, func(t *gtest.T) { + var arr = []float64{1, 2, 3, 4, 5} + objOne := gvar.New(arr, true) + t.Assert(objOne.Floats()[0], arr[0]) + }) +} + +func TestVar_Float32s(t *testing.T) { + gtest.C(t, func(t *gtest.T) { + var arr = []float32{1, 2, 3, 4, 5} + objOne := gvar.New(arr, true) + t.AssertEQ(objOne.Float32s(), arr) + }) +} + +func TestVar_Float64s(t *testing.T) { + gtest.C(t, func(t *gtest.T) { + var arr = []float64{1, 2, 3, 4, 5} + objOne := gvar.New(arr, true) + t.AssertEQ(objOne.Float64s(), arr) + }) +} + +func TestVar_Strings(t *testing.T) { + gtest.C(t, func(t *gtest.T) { + var arr = []string{"hello", "world"} + objOne := gvar.New(arr, true) + t.Assert(objOne.Strings()[0], arr[0]) + }) +} + +func TestVar_Interfaces(t *testing.T) { + gtest.C(t, func(t *gtest.T) { + var arr = []int{1, 2, 3, 4, 5} + objOne := gvar.New(arr, true) + t.Assert(objOne.Interfaces(), arr) + }) +} + +func TestVar_Slice(t *testing.T) { + gtest.C(t, func(t *gtest.T) { + var arr = []int{1, 2, 3, 4, 5} + objOne := gvar.New(arr, true) + t.Assert(objOne.Slice(), arr) + }) +} + +func TestVar_Array(t *testing.T) { + gtest.C(t, func(t *gtest.T) { + var arr = []int{1, 2, 3, 4, 5} + objOne := gvar.New(arr, false) + t.Assert(objOne.Array(), arr) + }) +} + +func TestVar_Vars(t *testing.T) { + gtest.C(t, func(t *gtest.T) { + var arr = []int{1, 2, 3, 4, 5} + objOne := gvar.New(arr, false) + t.Assert(len(objOne.Vars()), 5) + t.Assert(objOne.Vars()[0].Int(), 1) + t.Assert(objOne.Vars()[4].Int(), 5) + }) +} diff --git a/container/gvar/gvar_z_unit_struct_test.go b/container/gvar/gvar_z_unit_struct_test.go index 64f7f0d40..2164e5ad6 100644 --- a/container/gvar/gvar_z_unit_struct_test.go +++ b/container/gvar/gvar_z_unit_struct_test.go @@ -1,4 +1,4 @@ -// Copyright 2020 gf Author(https://github.com/gogf/gf). All Rights Reserved. +// Copyright GoFrame Author(https://goframe.org). All Rights Reserved. // // This Source Code Form is subject to the terms of the MIT License. // If a copy of the MIT was not distributed with this file, @@ -14,7 +14,7 @@ import ( "testing" ) -func Test_Struct(t *testing.T) { +func TestVar_Struct(t *testing.T) { gtest.C(t, func(t *gtest.T) { type StTest struct { Test int @@ -42,7 +42,7 @@ func Test_Struct(t *testing.T) { }) } -func Test_Var_Attribute_Struct(t *testing.T) { +func TestVar_Var_Attribute_Struct(t *testing.T) { gtest.C(t, func(t *gtest.T) { type User struct { Uid int diff --git a/crypto/gaes/gaes.go b/crypto/gaes/gaes.go index d0ddb9ae7..43bc0600a 100644 --- a/crypto/gaes/gaes.go +++ b/crypto/gaes/gaes.go @@ -1,4 +1,4 @@ -// Copyright 2017 gf Author(https://github.com/gogf/gf). All Rights Reserved. +// Copyright GoFrame Author(https://goframe.org). All Rights Reserved. // // This Source Code Form is subject to the terms of the MIT License. // If a copy of the MIT was not distributed with this file, @@ -11,7 +11,8 @@ import ( "bytes" "crypto/aes" "crypto/cipher" - "errors" + "github.com/gogf/gf/errors/gcode" + "github.com/gogf/gf/errors/gerror" ) var ( @@ -63,7 +64,7 @@ func DecryptCBC(cipherText []byte, key []byte, iv ...[]byte) ([]byte, error) { } blockSize := block.BlockSize() if len(cipherText) < blockSize { - return nil, errors.New("cipherText too short") + return nil, gerror.NewCode(gcode.CodeInvalidParameter, "cipherText too short") } ivValue := ([]byte)(nil) if len(iv) > 0 { @@ -72,7 +73,7 @@ func DecryptCBC(cipherText []byte, key []byte, iv ...[]byte) ([]byte, error) { ivValue = []byte(IVDefaultValue) } if len(cipherText)%blockSize != 0 { - return nil, errors.New("cipherText is not a multiple of the block size") + return nil, gerror.NewCode(gcode.CodeInvalidParameter, "cipherText is not a multiple of the block size") } blockModel := cipher.NewCBCDecrypter(block, ivValue) plainText := make([]byte, len(cipherText)) @@ -93,22 +94,22 @@ func PKCS5Padding(src []byte, blockSize int) []byte { func PKCS5UnPadding(src []byte, blockSize int) ([]byte, error) { length := len(src) if blockSize <= 0 { - return nil, errors.New("invalid blocklen") + return nil, gerror.NewCode(gcode.CodeInvalidParameter, "invalid blocklen") } if length%blockSize != 0 || length == 0 { - return nil, errors.New("invalid data len") + return nil, gerror.NewCode(gcode.CodeInvalidParameter, "invalid data len") } unpadding := int(src[length-1]) if unpadding > blockSize || unpadding == 0 { - return nil, errors.New("invalid padding") + return nil, gerror.NewCode(gcode.CodeInvalidParameter, "invalid padding") } padding := src[length-unpadding:] for i := 0; i < unpadding; i++ { if padding[i] != byte(unpadding) { - return nil, errors.New("invalid padding") + return nil, gerror.NewCode(gcode.CodeInvalidParameter, "invalid padding") } } @@ -146,7 +147,7 @@ func DecryptCFB(cipherText []byte, key []byte, unPadding int, iv ...[]byte) ([]b return nil, err } if len(cipherText) < aes.BlockSize { - return nil, errors.New("cipherText too short") + return nil, gerror.NewCode(gcode.CodeInvalidParameter, "cipherText too short") } ivValue := ([]byte)(nil) if len(iv) > 0 { diff --git a/crypto/gaes/gaes_test.go b/crypto/gaes/gaes_test.go index 58b1fc3c7..eeb94c21a 100644 --- a/crypto/gaes/gaes_test.go +++ b/crypto/gaes/gaes_test.go @@ -1,4 +1,4 @@ -// Copyright 2019 gf Author(https://github.com/gogf/gf). All Rights Reserved. +// Copyright GoFrame Author(https://goframe.org). All Rights Reserved. // // This Source Code Form is subject to the terms of the MIT License. // If a copy of the MIT was not distributed with this file, diff --git a/crypto/gcrc32/gcrc32.go b/crypto/gcrc32/gcrc32.go index 5ef54cbca..59b193bd2 100644 --- a/crypto/gcrc32/gcrc32.go +++ b/crypto/gcrc32/gcrc32.go @@ -1,4 +1,4 @@ -// Copyright 2017 gf Author(https://github.com/gogf/gf). All Rights Reserved. +// Copyright GoFrame Author(https://goframe.org). All Rights Reserved. // // This Source Code Form is subject to the terms of the MIT License. // If a copy of the MIT was not distributed with this file, diff --git a/crypto/gcrc32/gcrc32_test.go b/crypto/gcrc32/gcrc32_test.go index f5682e439..8bd33bf39 100644 --- a/crypto/gcrc32/gcrc32_test.go +++ b/crypto/gcrc32/gcrc32_test.go @@ -1,4 +1,4 @@ -// Copyright 2019 gf Author(https://github.com/gogf/gf). All Rights Reserved. +// Copyright GoFrame Author(https://goframe.org). All Rights Reserved. // // This Source Code Form is subject to the terms of the MIT License. // If a copy of the MIT was not distributed with this file, diff --git a/crypto/gdes/gdes.go b/crypto/gdes/gdes.go index 7f1b59e94..8b00e1a7c 100644 --- a/crypto/gdes/gdes.go +++ b/crypto/gdes/gdes.go @@ -1,4 +1,4 @@ -// Copyright 2018 gf Author(https://github.com/gogf/gf). All Rights Reserved. +// Copyright GoFrame Author(https://goframe.org). All Rights Reserved. // // This Source Code Form is subject to the terms of the MIT License. // If a copy of the MIT was not distributed with this file, @@ -11,7 +11,8 @@ import ( "bytes" "crypto/cipher" "crypto/des" - "errors" + "github.com/gogf/gf/errors/gcode" + "github.com/gogf/gf/errors/gerror" ) const ( @@ -66,7 +67,7 @@ func DecryptECB(cipherText []byte, key []byte, padding int) ([]byte, error) { // The length of the should be either 16 or 24 bytes. func EncryptECBTriple(plainText []byte, key []byte, padding int) ([]byte, error) { if len(key) != 16 && len(key) != 24 { - return nil, errors.New("key length error") + return nil, gerror.NewCode(gcode.CodeInvalidParameter, "key length error") } text, err := Padding(plainText, padding) @@ -100,7 +101,7 @@ func EncryptECBTriple(plainText []byte, key []byte, padding int) ([]byte, error) // The length of the should be either 16 or 24 bytes. func DecryptECBTriple(cipherText []byte, key []byte, padding int) ([]byte, error) { if len(key) != 16 && len(key) != 24 { - return nil, errors.New("key length error") + return nil, gerror.NewCode(gcode.CodeInvalidParameter, "key length error") } var newKey []byte @@ -138,7 +139,7 @@ func EncryptCBC(plainText []byte, key []byte, iv []byte, padding int) ([]byte, e } if len(iv) != block.BlockSize() { - return nil, errors.New("iv length invalid") + return nil, gerror.NewCode(gcode.CodeInvalidParameter, "iv length invalid") } text, err := Padding(plainText, padding) @@ -161,7 +162,7 @@ func DecryptCBC(cipherText []byte, key []byte, iv []byte, padding int) ([]byte, } if len(iv) != block.BlockSize() { - return nil, errors.New("iv length invalid") + return nil, gerror.NewCode(gcode.CodeInvalidParameter, "iv length invalid") } text := make([]byte, len(cipherText)) @@ -179,7 +180,7 @@ func DecryptCBC(cipherText []byte, key []byte, iv []byte, padding int) ([]byte, // EncryptCBCTriple encrypts using TripleDES and CBC mode. func EncryptCBCTriple(plainText []byte, key []byte, iv []byte, padding int) ([]byte, error) { if len(key) != 16 && len(key) != 24 { - return nil, errors.New("key length invalid") + return nil, gerror.NewCode(gcode.CodeInvalidParameter, "key length invalid") } var newKey []byte @@ -196,7 +197,7 @@ func EncryptCBCTriple(plainText []byte, key []byte, iv []byte, padding int) ([]b } if len(iv) != block.BlockSize() { - return nil, errors.New("iv length invalid") + return nil, gerror.NewCode(gcode.CodeInvalidParameter, "iv length invalid") } text, err := Padding(plainText, padding) @@ -214,7 +215,7 @@ func EncryptCBCTriple(plainText []byte, key []byte, iv []byte, padding int) ([]b // DecryptCBCTriple decrypts <cipherText> using TripleDES and CBC mode. func DecryptCBCTriple(cipherText []byte, key []byte, iv []byte, padding int) ([]byte, error) { if len(key) != 16 && len(key) != 24 { - return nil, errors.New("key length invalid") + return nil, gerror.NewCode(gcode.CodeInvalidParameter, "key length invalid") } var newKey []byte @@ -231,7 +232,7 @@ func DecryptCBCTriple(cipherText []byte, key []byte, iv []byte, padding int) ([] } if len(iv) != block.BlockSize() { - return nil, errors.New("iv length invalid") + return nil, gerror.NewCode(gcode.CodeInvalidParameter, "iv length invalid") } text := make([]byte, len(cipherText)) @@ -262,12 +263,12 @@ func Padding(text []byte, padding int) ([]byte, error) { switch padding { case NOPADDING: if len(text)%8 != 0 { - return nil, errors.New("text length invalid") + return nil, gerror.NewCode(gcode.CodeInvalidParameter, "text length invalid") } case PKCS5PADDING: return PaddingPKCS5(text, 8), nil default: - return nil, errors.New("padding type error") + return nil, gerror.NewCode(gcode.CodeInvalidParameter, "padding type error") } return text, nil @@ -277,12 +278,12 @@ func UnPadding(text []byte, padding int) ([]byte, error) { switch padding { case NOPADDING: if len(text)%8 != 0 { - return nil, errors.New("text length invalid") + return nil, gerror.NewCode(gcode.CodeInvalidParameter, "text length invalid") } case PKCS5PADDING: return UnPaddingPKCS5(text), nil default: - return nil, errors.New("padding type error") + return nil, gerror.NewCode(gcode.CodeInvalidParameter, "padding type error") } return text, nil } diff --git a/crypto/gdes/gdes_test.go b/crypto/gdes/gdes_test.go index 384480713..c4f05303c 100644 --- a/crypto/gdes/gdes_test.go +++ b/crypto/gdes/gdes_test.go @@ -1,4 +1,4 @@ -// Copyright 2018 gf Author(https://github.com/gogf/gf). All Rights Reserved. +// Copyright GoFrame Author(https://goframe.org). All Rights Reserved. // // This Source Code Form is subject to the terms of the MIT License. // If a copy of the MIT was not distributed with this file, diff --git a/crypto/gmd5/gmd5.go b/crypto/gmd5/gmd5.go index f73a2ca27..ab16f1cf7 100644 --- a/crypto/gmd5/gmd5.go +++ b/crypto/gmd5/gmd5.go @@ -1,4 +1,4 @@ -// Copyright 2017 gf Author(https://github.com/gogf/gf). All Rights Reserved. +// Copyright GoFrame Author(https://goframe.org). All Rights Reserved. // // This Source Code Form is subject to the terms of the MIT License. // If a copy of the MIT was not distributed with this file, diff --git a/crypto/gmd5/gmd5_test.go b/crypto/gmd5/gmd5_test.go index 286308373..a2f3b8e28 100644 --- a/crypto/gmd5/gmd5_test.go +++ b/crypto/gmd5/gmd5_test.go @@ -1,4 +1,4 @@ -// Copyright 2019 gf Author(https://github.com/gogf/gf). All Rights Reserved. +// Copyright GoFrame Author(https://goframe.org). All Rights Reserved. // // This Source Code Form is subject to the terms of the MIT License. // If a copy of the MIT was not distributed with this file, diff --git a/crypto/gsha1/gsha1.go b/crypto/gsha1/gsha1.go index caa43d008..7f8b6347f 100644 --- a/crypto/gsha1/gsha1.go +++ b/crypto/gsha1/gsha1.go @@ -1,4 +1,4 @@ -// Copyright 2017 gf Author(https://github.com/gogf/gf). All Rights Reserved. +// Copyright GoFrame Author(https://goframe.org). All Rights Reserved. // // This Source Code Form is subject to the terms of the MIT License. // If a copy of the MIT was not distributed with this file, diff --git a/crypto/gsha1/gsha1_test.go b/crypto/gsha1/gsha1_test.go index 775a43ac9..8fa22b096 100644 --- a/crypto/gsha1/gsha1_test.go +++ b/crypto/gsha1/gsha1_test.go @@ -1,4 +1,4 @@ -// Copyright 2019 gf Author(https://github.com/gogf/gf). All Rights Reserved. +// Copyright GoFrame Author(https://goframe.org). All Rights Reserved. // // This Source Code Form is subject to the terms of the MIT License. // If a copy of the MIT was not distributed with this file, diff --git a/database/gdb/gdb.go b/database/gdb/gdb.go index 41846facd..35e392caa 100644 --- a/database/gdb/gdb.go +++ b/database/gdb/gdb.go @@ -1,4 +1,4 @@ -// Copyright GoFrame Author(https://github.com/gogf/gf). All Rights Reserved. +// Copyright GoFrame Author(https://goframe.org). All Rights Reserved. // // This Source Code Form is subject to the terms of the MIT License. // If a copy of the MIT was not distributed with this file, @@ -10,10 +10,11 @@ package gdb import ( "context" "database/sql" - "fmt" + "github.com/gogf/gf/errors/gcode" + "time" + "github.com/gogf/gf/errors/gerror" "github.com/gogf/gf/os/gcmd" - "time" "github.com/gogf/gf/container/gvar" "github.com/gogf/gf/internal/intlog" @@ -32,154 +33,164 @@ type DB interface { // Model creation. // =========================================================================== + // Table function is deprecated, use Model instead. // The DB interface is designed not only for // relational databases but also for NoSQL databases in the future. The name // "Table" is not proper for that purpose any more. - Table(table ...string) *Model - Model(table ...string) *Model + // Also see Core.Table. + // Deprecated. + Table(tableNameOrStruct ...interface{}) *Model + + // Model creates and returns a new ORM model from given schema. + // The parameter `table` can be more than one table names, and also alias name, like: + // 1. Model names: + // Model("user") + // Model("user u") + // Model("user, user_detail") + // Model("user u, user_detail ud") + // 2. Model name with alias: Model("user", "u") + // Also see Core.Model. + Model(tableNameOrStruct ...interface{}) *Model + + // Raw creates and returns a model based on a raw sql not a table. + Raw(rawSql string, args ...interface{}) *Model + + // Schema creates and returns a schema. + // Also see Core.Schema. Schema(schema string) *Schema + // With creates and returns an ORM model based on meta data of given object. + // Also see Core.With. + With(objects ...interface{}) *Model + // Open creates a raw connection object for database with given node configuration. // Note that it is not recommended using the this function manually. + // Also see DriverMysql.Open. Open(config *ConfigNode) (*sql.DB, error) // Ctx is a chaining function, which creates and returns a new DB that is a shallow copy // of current DB object and with given context in it. - // Note that this returned DB object can be used only once, so do not assign it to - // a global or package variable for long using. + // Also see Core.Ctx. Ctx(ctx context.Context) DB + // Close closes the database and prevents new queries from starting. + // Close then waits for all queries that have started processing on the server + // to finish. + // + // It is rare to Close a DB, as the DB handle is meant to be + // long-lived and shared between many goroutines. + Close(ctx context.Context) error + // =========================================================================== // Query APIs. // =========================================================================== - Query(sql string, args ...interface{}) (*sql.Rows, error) - Exec(sql string, args ...interface{}) (sql.Result, error) - Prepare(sql string, execOnMaster ...bool) (*sql.Stmt, error) + Query(sql string, args ...interface{}) (*sql.Rows, error) // See Core.Query. + Exec(sql string, args ...interface{}) (sql.Result, error) // See Core.Exec. + Prepare(sql string, execOnMaster ...bool) (*Stmt, error) // See Core.Prepare. // =========================================================================== // Common APIs for CURD. // =========================================================================== - Insert(table string, data interface{}, batch ...int) (sql.Result, error) - InsertIgnore(table string, data interface{}, batch ...int) (sql.Result, error) - Replace(table string, data interface{}, batch ...int) (sql.Result, error) - Save(table string, data interface{}, batch ...int) (sql.Result, error) - - BatchInsert(table string, list interface{}, batch ...int) (sql.Result, error) - BatchReplace(table string, list interface{}, batch ...int) (sql.Result, error) - BatchSave(table string, list interface{}, batch ...int) (sql.Result, error) - - Update(table string, data interface{}, condition interface{}, args ...interface{}) (sql.Result, error) - Delete(table string, condition interface{}, args ...interface{}) (sql.Result, error) + Insert(table string, data interface{}, batch ...int) (sql.Result, error) // See Core.Insert. + InsertIgnore(table string, data interface{}, batch ...int) (sql.Result, error) // See Core.InsertIgnore. + InsertAndGetId(table string, data interface{}, batch ...int) (int64, error) // See Core.InsertAndGetId. + Replace(table string, data interface{}, batch ...int) (sql.Result, error) // See Core.Replace. + Save(table string, data interface{}, batch ...int) (sql.Result, error) // See Core.Save. + Update(table string, data interface{}, condition interface{}, args ...interface{}) (sql.Result, error) // See Core.Update. + Delete(table string, condition interface{}, args ...interface{}) (sql.Result, error) // See Core.Delete. // =========================================================================== - // Internal APIs for CURD, which can be overwrote for custom CURD implements. + // Internal APIs for CURD, which can be overwritten by custom CURD implements. // =========================================================================== - DoQuery(link Link, sql string, args ...interface{}) (rows *sql.Rows, err error) - DoGetAll(link Link, sql string, args ...interface{}) (result Result, err error) - DoExec(link Link, sql string, args ...interface{}) (result sql.Result, err error) - DoPrepare(link Link, sql string) (*sql.Stmt, error) - DoInsert(link Link, table string, data interface{}, option int, batch ...int) (result sql.Result, err error) - DoBatchInsert(link Link, table string, list interface{}, option int, batch ...int) (result sql.Result, err error) - DoUpdate(link Link, table string, data interface{}, condition string, args ...interface{}) (result sql.Result, err error) - DoDelete(link Link, table string, condition string, args ...interface{}) (result sql.Result, err error) + DoGetAll(ctx context.Context, link Link, sql string, args ...interface{}) (result Result, err error) // See Core.DoGetAll. + DoInsert(ctx context.Context, link Link, table string, data List, option DoInsertOption) (result sql.Result, err error) // See Core.DoInsert. + DoUpdate(ctx context.Context, link Link, table string, data interface{}, condition string, args ...interface{}) (result sql.Result, err error) // See Core.DoUpdate. + DoDelete(ctx context.Context, link Link, table string, condition string, args ...interface{}) (result sql.Result, err error) // See Core.DoDelete. + DoQuery(ctx context.Context, link Link, sql string, args ...interface{}) (rows *sql.Rows, err error) // See Core.DoQuery. + DoExec(ctx context.Context, link Link, sql string, args ...interface{}) (result sql.Result, err error) // See Core.DoExec. + DoCommit(ctx context.Context, link Link, sql string, args []interface{}) (newSql string, newArgs []interface{}, err error) // See Core.DoCommit. + DoPrepare(ctx context.Context, link Link, sql string) (*Stmt, error) // See Core.DoPrepare. // =========================================================================== // Query APIs for convenience purpose. // =========================================================================== - GetAll(sql string, args ...interface{}) (Result, error) - GetOne(sql string, args ...interface{}) (Record, error) - GetValue(sql string, args ...interface{}) (Value, error) - GetArray(sql string, args ...interface{}) ([]Value, error) - GetCount(sql string, args ...interface{}) (int, error) - GetStruct(objPointer interface{}, sql string, args ...interface{}) error - GetStructs(objPointerSlice interface{}, sql string, args ...interface{}) error - GetScan(objPointer interface{}, sql string, args ...interface{}) error + GetAll(sql string, args ...interface{}) (Result, error) // See Core.GetAll. + GetOne(sql string, args ...interface{}) (Record, error) // See Core.GetOne. + GetValue(sql string, args ...interface{}) (Value, error) // See Core.GetValue. + GetArray(sql string, args ...interface{}) ([]Value, error) // See Core.GetArray. + GetCount(sql string, args ...interface{}) (int, error) // See Core.GetCount. + GetScan(objPointer interface{}, sql string, args ...interface{}) error // See Core.GetScan. + Union(unions ...*Model) *Model // See Core.Union. + UnionAll(unions ...*Model) *Model // See Core.UnionAll. // =========================================================================== // Master/Slave specification support. // =========================================================================== - Master() (*sql.DB, error) - Slave() (*sql.DB, error) + Master(schema ...string) (*sql.DB, error) // See Core.Master. + Slave(schema ...string) (*sql.DB, error) // See Core.Slave. // =========================================================================== // Ping-Pong. // =========================================================================== - PingMaster() error - PingSlave() error + PingMaster() error // See Core.PingMaster. + PingSlave() error // See Core.PingSlave. // =========================================================================== // Transaction. // =========================================================================== - Begin() (*TX, error) - Transaction(f func(tx *TX) error) (err error) + Begin() (*TX, error) // See Core.Begin. + Transaction(ctx context.Context, f func(ctx context.Context, tx *TX) error) error // See Core.Transaction. // =========================================================================== // Configuration methods. // =========================================================================== - GetCache() *gcache.Cache - SetDebug(debug bool) - GetDebug() bool - SetSchema(schema string) - GetSchema() string - GetPrefix() string - GetGroup() string - SetDryRun(dryrun bool) - GetDryRun() bool - SetLogger(logger *glog.Logger) - GetLogger() *glog.Logger - GetConfig() *ConfigNode - SetMaxIdleConnCount(n int) - SetMaxOpenConnCount(n int) - SetMaxConnLifetime(d time.Duration) + GetCache() *gcache.Cache // See Core.GetCache. + SetDebug(debug bool) // See Core.SetDebug. + GetDebug() bool // See Core.GetDebug. + SetSchema(schema string) // See Core.SetSchema. + GetSchema() string // See Core.GetSchema. + GetPrefix() string // See Core.GetPrefix. + GetGroup() string // See Core.GetGroup. + SetDryRun(enabled bool) // See Core.SetDryRun. + GetDryRun() bool // See Core.GetDryRun. + SetLogger(logger *glog.Logger) // See Core.SetLogger. + GetLogger() *glog.Logger // See Core.GetLogger. + GetConfig() *ConfigNode // See Core.GetConfig. + SetMaxIdleConnCount(n int) // See Core.SetMaxIdleConnCount. + SetMaxOpenConnCount(n int) // See Core.SetMaxOpenConnCount. + SetMaxConnLifeTime(d time.Duration) // See Core.SetMaxConnLifeTime. // =========================================================================== // Utility methods. // =========================================================================== - GetCtx() context.Context - GetChars() (charLeft string, charRight string) - GetMaster(schema ...string) (*sql.DB, error) - GetSlave(schema ...string) (*sql.DB, error) - QuoteWord(s string) string - QuoteString(s string) string - QuotePrefixTableName(table string) string - Tables(schema ...string) (tables []string, err error) - TableFields(table string, schema ...string) (map[string]*TableField, error) - HasTable(name string) (bool, error) - - // HandleSqlBeforeCommit is a hook function, which deals with the sql string before - // it's committed to underlying driver. The parameter <link> specifies the current - // database connection operation object. You can modify the sql string <sql> and its - // arguments <args> as you wish before they're committed to driver. - HandleSqlBeforeCommit(link Link, sql string, args []interface{}) (string, []interface{}) - - // =========================================================================== - // Internal methods, for internal usage purpose, you do not need consider it. - // =========================================================================== - - mappingAndFilterData(schema, table string, data map[string]interface{}, filter bool) (map[string]interface{}, error) - convertFieldValueToLocalValue(fieldValue interface{}, fieldType string) interface{} - convertRowsToResult(rows *sql.Rows) (Result, error) + GetCtx() context.Context // See Core.GetCtx. + GetCore() *Core // See Core.GetCore + GetChars() (charLeft string, charRight string) // See Core.GetChars. + Tables(ctx context.Context, schema ...string) (tables []string, err error) // See Core.Tables. + TableFields(ctx context.Context, table string, schema ...string) (map[string]*TableField, error) // See Core.TableFields. + FilteredLink() string // FilteredLink is used for filtering sensitive information in `Link` configuration before output it to tracing server. } // Core is the base struct for database management. type Core struct { - DB DB // DB interface object. + db DB // DB interface object. + ctx context.Context // Context for chaining operation only. Do not set a default value in Core initialization. group string // Configuration group name. debug *gtype.Bool // Enable debug mode for the database, which can be changed in runtime. cache *gcache.Cache // Cache manager, SQL result cache only. + links *gmap.StrAnyMap // links caches all created links by node. schema *gtype.String // Custom schema for this object. - logger *glog.Logger // Logger. + logger *glog.Logger // Logger for logging functionality. config *ConfigNode // Current config node. - ctx context.Context // Context for chaining operation only. } // Driver is the interface for integrating sql drivers into package gdb. @@ -188,15 +199,42 @@ type Driver interface { New(core *Core, node *ConfigNode) (DB, error) } +// Link is a common database function wrapper interface. +type Link interface { + Query(sql string, args ...interface{}) (*sql.Rows, error) + Exec(sql string, args ...interface{}) (sql.Result, error) + Prepare(sql string) (*sql.Stmt, error) + QueryContext(ctx context.Context, sql string, args ...interface{}) (*sql.Rows, error) + ExecContext(ctx context.Context, sql string, args ...interface{}) (sql.Result, error) + PrepareContext(ctx context.Context, sql string) (*sql.Stmt, error) + IsTransaction() bool +} + +// Logger is the logging interface for DB. +type Logger interface { + Error(ctx context.Context, s string) + Debug(ctx context.Context, s string) +} + // Sql is the sql recording struct. type Sql struct { - Sql string // SQL string(may contain reserved char '?'). - Args []interface{} // Arguments for this sql. - Format string // Formatted sql which contains arguments in the sql. - Error error // Execution result. - Start int64 // Start execution timestamp in milliseconds. - End int64 // End execution timestamp in milliseconds. - Group string // Group is the group name of the configuration that the sql is executed from. + Sql string // SQL string(may contain reserved char '?'). + Type string // SQL operation type. + Args []interface{} // Arguments for this sql. + Format string // Formatted sql which contains arguments in the sql. + Error error // Execution result. + Start int64 // Start execution timestamp in milliseconds. + End int64 // End execution timestamp in milliseconds. + Group string // Group is the group name of the configuration that the sql is executed from. + IsTransaction bool // IsTransaction marks whether this sql is executed in transaction. +} + +// DoInsertOption is the input struct for function DoInsert. +type DoInsertOption struct { + OnDuplicateStr string + OnDuplicateMap map[string]interface{} + InsertOption int // Insert operation. + BatchCount int // Batch count for batch inserting. } // TableField is the struct for table field. @@ -211,13 +249,6 @@ type TableField struct { Comment string // Comment. } -// Link is a common database function wrapper interface. -type Link interface { - Query(sql string, args ...interface{}) (*sql.Rows, error) - Exec(sql string, args ...interface{}) (sql.Result, error) - Prepare(sql string) (*sql.Stmt, error) -} - // Counter is the type for update count. type Counter struct { Field string @@ -234,6 +265,10 @@ type ( ) const ( + queryTypeNormal = 0 + queryTypeCount = 1 + unionTypeNormal = 0 + unionTypeAll = 1 insertOptionDefault = 0 insertOptionReplace = 1 insertOptionSave = 2 @@ -242,12 +277,15 @@ const ( defaultMaxIdleConnCount = 10 // Max idle connection count in pool. defaultMaxOpenConnCount = 100 // Max open connection count in pool. defaultMaxConnLifeTime = 30 * time.Second // Max life time for per connection in pool in seconds. + ctxTimeoutTypeExec = iota + ctxTimeoutTypeQuery + ctxTimeoutTypePrepare + commandEnvKeyForDryRun = "gf.gdb.dryrun" + ctxStrictKeyName = "gf.gdb.CtxStrictEnabled" + ctxStrictErrorStr = "context is required for database operation, did you missing call function Ctx" ) var ( - // ErrNoRows is alias of sql.ErrNoRows. - ErrNoRows = sql.ErrNoRows - // instances is the management map for instances. instances = gmap.NewStrAnyMap(true) @@ -266,10 +304,15 @@ var ( // regularFieldNameRegPattern is the regular expression pattern for a string // which is a regular field name of table. - regularFieldNameRegPattern = `^[\w\.\-\_]+$` + regularFieldNameRegPattern = `^[\w\.\-]+$` - // internalCache is the memory cache for internal usage. - internalCache = gcache.New() + // regularFieldNameWithoutDotRegPattern is similar to regularFieldNameRegPattern but not allows '.'. + // Note that, although some databases allow char '.' in the field name, but it here does not allow '.' + // in the field name as it conflicts with "db.table.field" pattern in SOME situations. + regularFieldNameWithoutDotRegPattern = `^[\w\-]+$` + + // tableFieldsMap caches the table information retrived from database. + tableFieldsMap = gmap.New(true) // allDryRun sets dry-run feature for all database connections. // It is commonly used for command options for convenience. @@ -278,7 +321,7 @@ var ( func init() { // allDryRun is initialized from environment or command options. - allDryRun = gcmd.GetWithEnv("gf.gdb.dryrun", false).Bool() + allDryRun = gcmd.GetOptWithEnv(commandEnvKeyForDryRun, false).Bool() } // Register registers custom database driver to gdb. @@ -288,7 +331,7 @@ 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, +// The parameter `name` specifies the configuration group name, // which is DefaultGroupName in default. func New(group ...string) (db DB, err error) { groupName := configs.group @@ -299,7 +342,10 @@ func New(group ...string) (db DB, err error) { defer configs.RUnlock() if len(configs.config) < 1 { - return nil, gerror.New("empty database configuration") + return nil, gerror.NewCode( + gcode.CodeInvalidConfiguration, + "database configuration is empty, please set the database configuration before using", + ) } if _, ok := configs.config[groupName]; ok { if node, err := getConfigNodeByGroup(groupName, true); err == nil { @@ -307,29 +353,38 @@ func New(group ...string) (db DB, err error) { group: groupName, debug: gtype.NewBool(), cache: gcache.New(), + links: gmap.NewStrAnyMap(true), schema: gtype.NewString(), logger: glog.New(), config: node, } if v, ok := driverMap[node.Type]; ok { - c.DB, err = v.New(c, node) + c.db, err = v.New(c, node) if err != nil { return nil, err } - return c.DB, nil + return c.db, nil } else { - return nil, gerror.New(fmt.Sprintf(`unsupported database type "%s"`, node.Type)) + return nil, gerror.NewCodef( + gcode.CodeInvalidConfiguration, + `cannot find database driver for specified database type "%s", did you misspell type name "%s" or forget importing the database driver?`, + node.Type, node.Type, + ) } } else { return nil, err } } else { - return nil, gerror.New(fmt.Sprintf(`database configuration node "%s" is not found`, groupName)) + return nil, gerror.NewCodef( + gcode.CodeInvalidConfiguration, + `database configuration node "%s" is not found, did you misspell group name "%s" or miss the database configuration?`, + groupName, groupName, + ) } } // Instance returns an instance for DB operations. -// The parameter <name> specifies the configuration group name, +// The parameter `name` specifies the configuration group name, // which is DefaultGroupName in default. func Instance(name ...string) (db DB, err error) { group := configs.group @@ -349,7 +404,7 @@ func Instance(name ...string) (db DB, err error) { // getConfigNodeByGroup calculates and returns a configuration node of given group. It // calculates the value internally using weight algorithm for load balance. // -// The parameter <master> specifies whether retrieving a master node, or else a slave node +// The parameter `master` specifies whether retrieving a master node, or else a slave node // if master-slave configured. func getConfigNodeByGroup(group string, master bool) (*ConfigNode, error) { if list, ok := configs.config[group]; ok { @@ -364,7 +419,7 @@ func getConfigNodeByGroup(group string, master bool) (*ConfigNode, error) { } } if len(masterList) < 1 { - return nil, gerror.New("at least one master node configuration's need to make sense") + return nil, gerror.NewCode(gcode.CodeInvalidConfiguration, "at least one master node configuration's need to make sense") } if len(slaveList) < 1 { slaveList = masterList @@ -375,7 +430,7 @@ func getConfigNodeByGroup(group string, master bool) (*ConfigNode, error) { return getConfigNodeByWeight(slaveList), nil } } else { - return nil, gerror.New(fmt.Sprintf("empty database configuration for item name '%s'", group)) + return nil, gerror.NewCodef(gcode.CodeInvalidConfiguration, "empty database configuration for item name '%s'", group) } } @@ -393,7 +448,7 @@ func getConfigNodeByWeight(cg ConfigGroup) *ConfigNode { for i := 0; i < len(cg); i++ { total += cg[i].Weight * 100 } - // If total is 0 means all of the nodes have no weight attribute configured. + // If total is 0 means all the nodes have no weight attribute configured. // It then defaults each node's weight attribute to 1. if total == 0 { for i := 0; i < len(cg); i++ { @@ -418,7 +473,7 @@ func getConfigNodeByWeight(cg ConfigGroup) *ConfigNode { } // getSqlDb retrieves and returns a underlying database connection object. -// The parameter <master> specifies whether retrieves master node connection if +// The parameter `master` specifies whether retrieves master node connection if // master-slave nodes are configured. func (c *Core) getSqlDb(master bool, schema ...string) (sqlDb *sql.DB, err error) { // Load balance. @@ -442,12 +497,29 @@ func (c *Core) getSqlDb(master bool, schema ...string) (sqlDb *sql.DB, err error node = &n } // Cache the underlying connection pool object by node. - v, _ := internalCache.GetOrSetFuncLock(node.String(), func() (interface{}, error) { - sqlDb, err = c.DB.Open(node) + v := c.links.GetOrSetFuncLock(node.String(), func() interface{} { + intlog.Printf( + c.db.GetCtx(), + `open new connection, master:%#v, config:%#v, node:%#v`, + master, c.config, node, + ) + defer func() { + if err != nil { + intlog.Printf(c.db.GetCtx(), `open new connection failed: %v, %#v`, err, node) + } else { + intlog.Printf( + c.db.GetCtx(), + `open new connection success, master:%#v, config:%#v, node:%#v`, + master, c.config, node, + ) + } + }() + + sqlDb, err = c.db.Open(node) if err != nil { - intlog.Printf("DB open failed: %v, %+v", err, node) - return nil, err + return nil } + if c.config.MaxIdleConnCount > 0 { sqlDb.SetMaxIdleConns(c.config.MaxIdleConnCount) } else { @@ -458,27 +530,27 @@ func (c *Core) getSqlDb(master bool, schema ...string) (sqlDb *sql.DB, err error } else { sqlDb.SetMaxOpenConns(defaultMaxOpenConnCount) } - if c.config.MaxConnLifetime > 0 { + if c.config.MaxConnLifeTime > 0 { // Automatically checks whether MaxConnLifetime is configured using string like: "30s", "60s", etc. // Or else it is configured just using number, which means value in seconds. - if c.config.MaxConnLifetime > time.Second { - sqlDb.SetConnMaxLifetime(c.config.MaxConnLifetime) + if c.config.MaxConnLifeTime > time.Second { + sqlDb.SetConnMaxLifetime(c.config.MaxConnLifeTime) } else { - sqlDb.SetConnMaxLifetime(c.config.MaxConnLifetime * time.Second) + sqlDb.SetConnMaxLifetime(c.config.MaxConnLifeTime * time.Second) } } else { sqlDb.SetConnMaxLifetime(defaultMaxConnLifeTime) } - return sqlDb, nil - }, 0) + return sqlDb + }) if v != nil && sqlDb == nil { sqlDb = v.(*sql.DB) } if node.Debug { - c.DB.SetDebug(node.Debug) + c.db.SetDebug(node.Debug) } - if node.Debug { - c.DB.SetDryRun(node.DryRun) + if node.DryRun { + c.db.SetDryRun(node.DryRun) } return } diff --git a/database/gdb/gdb_core.go b/database/gdb/gdb_core.go index 14cab14f1..58519e927 100644 --- a/database/gdb/gdb_core.go +++ b/database/gdb/gdb_core.go @@ -1,4 +1,4 @@ -// Copyright GoFrame Author(https://github.com/gogf/gf). All Rights Reserved. +// Copyright GoFrame Author(https://goframe.org). All Rights Reserved. // // This Source Code Form is subject to the terms of the MIT License. // If a copy of the MIT was not distributed with this file, @@ -11,195 +11,151 @@ import ( "context" "database/sql" "fmt" - "github.com/gogf/gf/errors/gerror" - "github.com/gogf/gf/text/gstr" + "github.com/gogf/gf/errors/gcode" + "github.com/gogf/gf/internal/intlog" "reflect" "strings" + "github.com/gogf/gf/errors/gerror" "github.com/gogf/gf/internal/utils" + "github.com/gogf/gf/text/gstr" "github.com/gogf/gf/container/gvar" - "github.com/gogf/gf/os/gtime" "github.com/gogf/gf/text/gregex" "github.com/gogf/gf/util/gconv" ) +// GetCore returns the underlying *Core object. +func (c *Core) GetCore() *Core { + return c +} + // Ctx is a chaining function, which creates and returns a new DB that is a shallow copy // of current DB object and with given context in it. // Note that this returned DB object can be used only once, so do not assign it to // a global or package variable for long using. func (c *Core) Ctx(ctx context.Context) DB { if ctx == nil { - return c.DB + return c.db } + ctx = context.WithValue(ctx, ctxStrictKeyName, 1) + // It makes a shallow copy of current db and changes its context for next chaining operation. var ( err error newCore = &Core{} - configNode = c.DB.GetConfig() + configNode = c.db.GetConfig() ) *newCore = *c newCore.ctx = ctx - newCore.DB, err = driverMap[configNode.Type].New(newCore, configNode) - // Seldom error, just log it. + // It creates a new DB object, which is commonly a wrapper for object `Core`. + newCore.db, err = driverMap[configNode.Type].New(newCore, configNode) if err != nil { - c.DB.GetLogger().Ctx(ctx).Error(err) + // It is really a serious error here. + // Do not let it continue. + panic(err) } - return newCore.DB + return newCore.db } // GetCtx returns the context for current DB. -// Note that it might be nil. +// It returns `context.Background()` is there's no context previously set. func (c *Core) GetCtx() context.Context { - return c.ctx + if c.ctx != nil { + return c.ctx + } + return context.TODO() +} + +// GetCtxTimeout returns the context and cancel function for specified timeout type. +func (c *Core) GetCtxTimeout(timeoutType int, ctx context.Context) (context.Context, context.CancelFunc) { + if ctx == nil { + ctx = c.GetCtx() + } else { + ctx = context.WithValue(ctx, "WrappedByGetCtxTimeout", nil) + } + switch timeoutType { + case ctxTimeoutTypeExec: + if c.db.GetConfig().ExecTimeout > 0 { + return context.WithTimeout(ctx, c.db.GetConfig().ExecTimeout) + } + case ctxTimeoutTypeQuery: + if c.db.GetConfig().QueryTimeout > 0 { + return context.WithTimeout(ctx, c.db.GetConfig().QueryTimeout) + } + case ctxTimeoutTypePrepare: + if c.db.GetConfig().PrepareTimeout > 0 { + return context.WithTimeout(ctx, c.db.GetConfig().PrepareTimeout) + } + default: + panic(gerror.NewCodef(gcode.CodeInvalidParameter, "invalid context timeout type: %d", timeoutType)) + } + return ctx, func() {} +} + +// Close closes the database and prevents new queries from starting. +// Close then waits for all queries that have started processing on the server +// to finish. +// +// It is rare to Close a DB, as the DB handle is meant to be +// long-lived and shared between many goroutines. +func (c *Core) Close(ctx context.Context) (err error) { + c.links.LockFunc(func(m map[string]interface{}) { + for k, v := range m { + if db, ok := v.(*sql.DB); ok { + err = db.Close() + intlog.Printf(ctx, `close link: %s, err: %v`, k, err) + if err != nil { + return + } + delete(m, k) + } + } + }) + return } // Master creates and returns a connection from master node if master-slave configured. // It returns the default connection if master-slave not configured. -func (c *Core) Master() (*sql.DB, error) { - return c.getSqlDb(true, c.schema.Val()) +func (c *Core) Master(schema ...string) (*sql.DB, error) { + useSchema := "" + if len(schema) > 0 && schema[0] != "" { + useSchema = schema[0] + } else { + useSchema = c.schema.Val() + } + return c.getSqlDb(true, useSchema) } // Slave creates and returns a connection from slave node if master-slave configured. // It returns the default connection if master-slave not configured. -func (c *Core) Slave() (*sql.DB, error) { - return c.getSqlDb(false, c.schema.Val()) -} - -// Query commits one query SQL to underlying driver and returns the execution result. -// It is most commonly used for data querying. -func (c *Core) Query(sql string, args ...interface{}) (rows *sql.Rows, err error) { - link, err := c.DB.Slave() - if err != nil { - return nil, err - } - return c.DB.DoQuery(link, sql, args...) -} - -// DoQuery commits the sql string and its arguments to underlying driver -// through given link object and returns the execution result. -func (c *Core) DoQuery(link Link, sql string, args ...interface{}) (rows *sql.Rows, err error) { - sql, args = formatSql(sql, args) - sql, args = c.DB.HandleSqlBeforeCommit(link, sql, args) - if c.DB.GetDebug() { - mTime1 := gtime.TimestampMilli() - rows, err = link.Query(sql, args...) - mTime2 := gtime.TimestampMilli() - s := &Sql{ - Sql: sql, - Args: args, - Format: FormatSqlWithArgs(sql, args), - Error: err, - Start: mTime1, - End: mTime2, - Group: c.DB.GetGroup(), - } - c.writeSqlToLogger(s) +func (c *Core) Slave(schema ...string) (*sql.DB, error) { + useSchema := "" + if len(schema) > 0 && schema[0] != "" { + useSchema = schema[0] } else { - rows, err = link.Query(sql, args...) + useSchema = c.schema.Val() } - if err == nil { - return rows, nil - } else { - err = formatError(err, sql, args...) - } - return nil, err -} - -// Exec commits one query SQL to underlying driver and returns the execution result. -// It is most commonly used for data inserting and updating. -func (c *Core) Exec(sql string, args ...interface{}) (result sql.Result, err error) { - link, err := c.DB.Master() - if err != nil { - return nil, err - } - return c.DB.DoExec(link, sql, args...) -} - -// DoExec commits the sql string and its arguments to underlying driver -// through given link object and returns the execution result. -func (c *Core) DoExec(link Link, sql string, args ...interface{}) (result sql.Result, err error) { - sql, args = formatSql(sql, args) - sql, args = c.DB.HandleSqlBeforeCommit(link, sql, args) - if c.DB.GetDebug() { - mTime1 := gtime.TimestampMilli() - if !c.DB.GetDryRun() { - result, err = link.Exec(sql, args...) - } else { - result = new(SqlResult) - } - mTime2 := gtime.TimestampMilli() - s := &Sql{ - Sql: sql, - Args: args, - Format: FormatSqlWithArgs(sql, args), - Error: err, - Start: mTime1, - End: mTime2, - Group: c.DB.GetGroup(), - } - c.writeSqlToLogger(s) - } else { - if !c.DB.GetDryRun() { - result, err = link.Exec(sql, args...) - } else { - result = new(SqlResult) - } - } - return result, formatError(err, sql, args...) -} - -// Prepare creates a prepared statement for later queries or executions. -// Multiple queries or executions may be run concurrently from the -// returned statement. -// The caller must call the statement's Close method -// when the statement is no longer needed. -// -// The parameter <execOnMaster> specifies whether executing the sql on master node, -// or else it executes the sql on slave node if master-slave configured. -func (c *Core) Prepare(sql string, execOnMaster ...bool) (*sql.Stmt, error) { - err := (error)(nil) - link := (Link)(nil) - if len(execOnMaster) > 0 && execOnMaster[0] { - if link, err = c.DB.Master(); err != nil { - return nil, err - } - } else { - if link, err = c.DB.Slave(); err != nil { - return nil, err - } - } - return c.DB.DoPrepare(link, sql) -} - -// doPrepare calls prepare function on given link object and returns the statement object. -func (c *Core) DoPrepare(link Link, sql string) (*sql.Stmt, error) { - return link.Prepare(sql) + return c.getSqlDb(false, useSchema) } // GetAll queries and returns data records from database. func (c *Core) GetAll(sql string, args ...interface{}) (Result, error) { - return c.DB.DoGetAll(nil, sql, args...) + return c.db.DoGetAll(c.GetCtx(), nil, sql, args...) } // DoGetAll queries and returns data records from database. -func (c *Core) DoGetAll(link Link, sql string, args ...interface{}) (result Result, err error) { - if link == nil { - link, err = c.DB.Slave() - if err != nil { - return nil, err - } - } - rows, err := c.DB.DoQuery(link, sql, args...) +func (c *Core) DoGetAll(ctx context.Context, link Link, sql string, args ...interface{}) (result Result, err error) { + rows, err := c.db.DoQuery(ctx, link, sql, args...) if err != nil || rows == nil { return nil, err } defer rows.Close() - return c.DB.convertRowsToResult(rows) + return c.convertRowsToResult(rows) } // GetOne queries and returns one record from database. func (c *Core) GetOne(sql string, args ...interface{}) (Record, error) { - list, err := c.DB.GetAll(sql, args...) + list, err := c.db.GetAll(sql, args...) if err != nil { return nil, err } @@ -210,9 +166,9 @@ func (c *Core) GetOne(sql string, args ...interface{}) (Record, error) { } // GetArray queries and returns data values as slice from database. -// Note that if there're multiple columns in the result, it returns just one column values randomly. +// Note that if there are multiple columns in the result, it returns just one column values randomly. func (c *Core) GetArray(sql string, args ...interface{}) ([]Value, error) { - all, err := c.DB.DoGetAll(nil, sql, args...) + all, err := c.db.DoGetAll(c.GetCtx(), nil, sql, args...) if err != nil { return nil, err } @@ -220,9 +176,9 @@ func (c *Core) GetArray(sql string, args ...interface{}) ([]Value, error) { } // GetStruct queries one record from database and converts it to given struct. -// The parameter <pointer> should be a pointer to struct. +// The parameter `pointer` should be a pointer to struct. func (c *Core) GetStruct(pointer interface{}, sql string, args ...interface{}) error { - one, err := c.DB.GetOne(sql, args...) + one, err := c.db.GetOne(sql, args...) if err != nil { return err } @@ -230,9 +186,9 @@ func (c *Core) GetStruct(pointer interface{}, sql string, args ...interface{}) e } // GetStructs queries records from database and converts them to given struct. -// The parameter <pointer> should be type of struct slice: []struct/[]*struct. +// The parameter `pointer` should be type of struct slice: []struct/[]*struct. func (c *Core) GetStructs(pointer interface{}, sql string, args ...interface{}) error { - all, err := c.DB.GetAll(sql, args...) + all, err := c.db.GetAll(sql, args...) if err != nil { return err } @@ -242,8 +198,8 @@ func (c *Core) GetStructs(pointer interface{}, sql string, args ...interface{}) // GetScan queries one or more records from database and converts them to given struct or // struct array. // -// If parameter <pointer> is type of struct pointer, it calls GetStruct internally for -// the conversion. If parameter <pointer> is type of slice, it calls GetStructs internally +// If parameter `pointer` is type of struct pointer, it calls GetStruct internally for +// the conversion. If parameter `pointer` is type of slice, it calls GetStructs internally // for conversion. func (c *Core) GetScan(pointer interface{}, sql string, args ...interface{}) error { t := reflect.TypeOf(pointer) @@ -254,9 +210,9 @@ func (c *Core) GetScan(pointer interface{}, sql string, args ...interface{}) err k = t.Elem().Kind() switch k { case reflect.Array, reflect.Slice: - return c.DB.GetStructs(pointer, sql, args...) + return c.db.GetCore().GetStructs(pointer, sql, args...) case reflect.Struct: - return c.DB.GetStruct(pointer, sql, args...) + return c.db.GetCore().GetStruct(pointer, sql, args...) } return fmt.Errorf("element type should be type of struct/slice, unsupported: %v", k) } @@ -265,7 +221,7 @@ func (c *Core) GetScan(pointer interface{}, sql string, args ...interface{}) err // The sql should queries only one field from database, or else it returns only one // field of the result. func (c *Core) GetValue(sql string, args ...interface{}) (Value, error) { - one, err := c.DB.GetOne(sql, args...) + one, err := c.db.GetOne(sql, args...) if err != nil { return gvar.New(nil), err } @@ -282,16 +238,49 @@ func (c *Core) GetCount(sql string, args ...interface{}) (int, error) { if !gregex.IsMatchString(`(?i)SELECT\s+COUNT\(.+\)\s+FROM`, sql) { sql, _ = gregex.ReplaceString(`(?i)(SELECT)\s+(.+)\s+(FROM)`, `$1 COUNT($2) $3`, sql) } - value, err := c.DB.GetValue(sql, args...) + value, err := c.db.GetValue(sql, args...) if err != nil { return 0, err } return value.Int(), nil } +// Union does "(SELECT xxx FROM xxx) UNION (SELECT xxx FROM xxx) ..." statement. +func (c *Core) Union(unions ...*Model) *Model { + return c.doUnion(unionTypeNormal, unions...) +} + +// UnionAll does "(SELECT xxx FROM xxx) UNION ALL (SELECT xxx FROM xxx) ..." statement. +func (c *Core) UnionAll(unions ...*Model) *Model { + return c.doUnion(unionTypeAll, unions...) +} + +func (c *Core) doUnion(unionType int, unions ...*Model) *Model { + var ( + unionTypeStr string + composedSqlStr string + composedArgs = make([]interface{}, 0) + ) + if unionType == unionTypeAll { + unionTypeStr = "UNION ALL" + } else { + unionTypeStr = "UNION" + } + for _, v := range unions { + sqlWithHolder, holderArgs := v.getFormattedSqlAndArgs(queryTypeNormal, false) + if composedSqlStr == "" { + composedSqlStr += fmt.Sprintf(`(%s)`, sqlWithHolder) + } else { + composedSqlStr += fmt.Sprintf(` %s (%s)`, unionTypeStr, sqlWithHolder) + } + composedArgs = append(composedArgs, holderArgs...) + } + return c.db.Raw(composedSqlStr, composedArgs...) +} + // PingMaster pings the master node to check authentication or keeps the connection alive. func (c *Core) PingMaster() error { - if master, err := c.DB.Master(); err != nil { + if master, err := c.db.Master(); err != nil { return err } else { return master.Ping() @@ -300,75 +289,22 @@ func (c *Core) PingMaster() error { // PingSlave pings the slave node to check authentication or keeps the connection alive. func (c *Core) PingSlave() error { - if slave, err := c.DB.Slave(); err != nil { + if slave, err := c.db.Slave(); err != nil { return err } else { return slave.Ping() } } -// Begin starts and returns the transaction object. -// You should call Commit or Rollback functions of the transaction object -// if you no longer use the transaction. Commit or Rollback functions will also -// close the transaction automatically. -func (c *Core) Begin() (*TX, error) { - if master, err := c.DB.Master(); err != nil { - return nil, err - } else { - if tx, err := master.Begin(); err == nil { - return &TX{ - db: c.DB, - tx: tx, - master: master, - }, nil - } else { - return nil, err - } - } -} - -// Transaction wraps the transaction logic using function <f>. -// It rollbacks the transaction and returns the error from function <f> if -// it returns non-nil error. It commits the transaction and returns nil if -// function <f> returns nil. -// -// Note that, you should not Commit or Rollback the transaction in function <f> -// as it is automatically handled by this function. -func (c *Core) Transaction(f func(tx *TX) error) (err error) { - var tx *TX - tx, err = c.DB.Begin() - if err != nil { - return err - } - defer func() { - if err == nil { - if e := recover(); e != nil { - err = fmt.Errorf("%v", e) - } - } - if err != nil { - if e := tx.Rollback(); e != nil { - err = e - } - } else { - if e := tx.Commit(); e != nil { - err = e - } - } - }() - err = f(tx) - return -} - // Insert does "INSERT INTO ..." statement for the table. // If there's already one unique record of the data in the table, it returns error. // -// The parameter <data> can be type of map/gmap/struct/*struct/[]map/[]struct, etc. +// The parameter `data` can be type of map/gmap/struct/*struct/[]map/[]struct, etc. // Eg: // Data(g.Map{"uid": 10000, "name":"john"}) // Data(g.Slice{g.Map{"uid": 10000, "name":"john"}, g.Map{"uid": 20000, "name":"smith"}) // -// The parameter <batch> specifies the batch operation count when given data is slice. +// 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) { if len(batch) > 0 { return c.Model(table).Data(data).Batch(batch[0]).Insert() @@ -379,12 +315,12 @@ func (c *Core) Insert(table string, data interface{}, batch ...int) (sql.Result, // InsertIgnore does "INSERT IGNORE INTO ..." statement for the table. // If there's already one unique record of the data in the table, it ignores the inserting. // -// The parameter <data> can be type of map/gmap/struct/*struct/[]map/[]struct, etc. +// The parameter `data` can be type of map/gmap/struct/*struct/[]map/[]struct, etc. // Eg: // Data(g.Map{"uid": 10000, "name":"john"}) // Data(g.Slice{g.Map{"uid": 10000, "name":"john"}, g.Map{"uid": 20000, "name":"smith"}) // -// The parameter <batch> specifies the batch operation count when given data is slice. +// 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) { if len(batch) > 0 { return c.Model(table).Data(data).Batch(batch[0]).InsertIgnore() @@ -392,18 +328,26 @@ func (c *Core) InsertIgnore(table string, data interface{}, batch ...int) (sql.R return c.Model(table).Data(data).InsertIgnore() } +// InsertAndGetId performs action Insert and returns the last insert id that automatically generated. +func (c *Core) InsertAndGetId(table string, data interface{}, batch ...int) (int64, error) { + if len(batch) > 0 { + return c.Model(table).Data(data).Batch(batch[0]).InsertAndGetId() + } + return c.Model(table).Data(data).InsertAndGetId() +} + // Replace does "REPLACE INTO ..." statement for the table. // If there's already one unique record of the data in the table, it deletes the record // and inserts a new one. // -// The parameter <data> can be type of map/gmap/struct/*struct/[]map/[]struct, etc. +// The parameter `data` can be type of map/gmap/struct/*struct/[]map/[]struct, etc. // Eg: // Data(g.Map{"uid": 10000, "name":"john"}) // Data(g.Slice{g.Map{"uid": 10000, "name":"john"}, g.Map{"uid": 20000, "name":"smith"}) // -// The parameter <data> can be type of map/gmap/struct/*struct/[]map/[]struct, etc. +// The parameter `data` can be type of map/gmap/struct/*struct/[]map/[]struct, etc. // If given data is type of slice, it then does batch replacing, and the optional parameter -// <batch> specifies the batch operation count. +// `batch` specifies the batch operation count. func (c *Core) Replace(table string, data interface{}, batch ...int) (sql.Result, error) { if len(batch) > 0 { return c.Model(table).Data(data).Batch(batch[0]).Replace() @@ -415,13 +359,13 @@ func (c *Core) Replace(table string, data interface{}, batch ...int) (sql.Result // It updates the record if there's primary or unique index in the saving data, // or else it inserts a new record into the table. // -// The parameter <data> can be type of map/gmap/struct/*struct/[]map/[]struct, etc. +// The parameter `data` can be type of map/gmap/struct/*struct/[]map/[]struct, etc. // Eg: // Data(g.Map{"uid": 10000, "name":"john"}) // Data(g.Slice{g.Map{"uid": 10000, "name":"john"}, g.Map{"uid": 20000, "name":"smith"}) // // If given data is type of slice, it then does batch saving, and the optional parameter -// <batch> specifies the batch operation count. +// `batch` specifies the batch operation count. func (c *Core) Save(table string, data interface{}, batch ...int) (sql.Result, error) { if len(batch) > 0 { return c.Model(table).Data(data).Batch(batch[0]).Save() @@ -429,257 +373,64 @@ func (c *Core) Save(table string, data interface{}, batch ...int) (sql.Result, e return c.Model(table).Data(data).Save() } -// doInsert inserts or updates data for given table. +// DoInsert inserts or updates data forF 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. +// The parameter `data` can be type of map/gmap/struct/*struct/[]map/[]struct, etc. // Eg: // Data(g.Map{"uid": 10000, "name":"john"}) // Data(g.Slice{g.Map{"uid": 10000, "name":"john"}, g.Map{"uid": 20000, "name":"smith"}) // -// The parameter <option> values are as follows: +// The parameter `option` values are as follows: // 0: insert: just insert, if there's unique/primary key in the data, it returns error; // 1: replace: if there's unique/primary key in the data, it deletes it from table and inserts a new one; // 2: save: if there's unique/primary key in the data, it updates it or else inserts a new one; // 3: ignore: if there's unique/primary key in the data, it ignores the inserting; -func (c *Core) DoInsert(link Link, table string, data interface{}, option int, batch ...int) (result sql.Result, err error) { - table = c.DB.QuotePrefixTableName(table) +func (c *Core) DoInsert(ctx context.Context, link Link, table string, list List, option DoInsertOption) (result sql.Result, err error) { var ( - fields []string - values []string - params []interface{} - dataMap Map - reflectValue = reflect.ValueOf(data) - reflectKind = reflectValue.Kind() + keys []string // Field names. + values []string // Value holder string array, like: (?,?,?) + params []interface{} // Values that will be committed to underlying database driver. + onDuplicateStr string // onDuplicateStr is used in "ON DUPLICATE KEY UPDATE" statement. ) - if reflectKind == reflect.Ptr { - reflectValue = reflectValue.Elem() - reflectKind = reflectValue.Kind() - } - switch reflectKind { - case reflect.Slice, reflect.Array: - return c.DB.DoBatchInsert(link, table, data, option, batch...) - 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, gerror.New(fmt.Sprint("unsupported data type:", reflectKind)) - } - if len(dataMap) == 0 { - return nil, gerror.New("data cannot be empty") - } - var ( - charL, charR = c.DB.GetChars() - operation = GetInsertOperationByOption(option) - updateStr = "" - ) - for k, v := range dataMap { - fields = append(fields, charL+k+charR) - if s, ok := v.(Raw); ok { - values = append(values, gconv.String(s)) - } else { - values = append(values, "?") - params = append(params, v) - } - } - if option == insertOptionSave { - for k, _ := range dataMap { - // If it's SAVE operation, - // do not automatically update the creating time. - if c.isSoftCreatedFiledName(k) { - continue - } - if len(updateStr) > 0 { - updateStr += "," - } - updateStr += fmt.Sprintf( - "%s%s%s=VALUES(%s%s%s)", - charL, k, charR, - charL, k, charR, - ) - } - updateStr = fmt.Sprintf("ON DUPLICATE KEY UPDATE %s", updateStr) - } - if link == nil { - if link, err = c.DB.Master(); err != nil { - return nil, err - } - } - return c.DB.DoExec( - link, - fmt.Sprintf( - "%s INTO %s(%s) VALUES(%s) %s", - operation, table, strings.Join(fields, ","), - strings.Join(values, ","), updateStr, - ), - params..., - ) -} - -// 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) { - if len(batch) > 0 { - return c.Model(table).Data(list).Batch(batch[0]).Insert() - } - return c.Model(table).Data(list).Insert() -} - -// 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) { - 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) { - 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) { - 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 // 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: - listMap = value.List() - case Record: - listMap = List{value.Map()} - case List: - listMap = value - case Map: - listMap = List{value} - default: - var ( - rv = reflect.ValueOf(list) - kind = rv.Kind() - ) - if kind == reflect.Ptr { - rv = rv.Elem() - kind = rv.Kind() - } - switch kind { - // If it's slice type, it then converts it to List type. - case reflect.Slice, reflect.Array: - listMap = make(List, rv.Len()) - for i := 0; i < rv.Len(); i++ { - listMap[i] = ConvertDataForTableRecord(rv.Index(i).Interface()) - } - case reflect.Map: - listMap = List{ConvertDataForTableRecord(value)} - case reflect.Struct: - if v, ok := value.(apiInterfaces); ok { - var ( - array = v.Interfaces() - list = make(List, len(array)) - ) - for i := 0; i < len(array); i++ { - list[i] = ConvertDataForTableRecord(array[i]) - } - listMap = list - } else { - listMap = List{ConvertDataForTableRecord(value)} - } - default: - return result, gerror.New(fmt.Sprint("unsupported list type:", kind)) - } - } - if len(listMap) < 1 { - return result, gerror.New("data list cannot be empty") - } - if link == nil { - if link, err = c.DB.Master(); err != nil { - return - } - } // Handle the field names and place holders. - for k, _ := range listMap[0] { + for k, _ := range list[0] { keys = append(keys, k) } // Prepare the batch result pointer. var ( - charL, charR = c.DB.GetChars() + charL, charR = c.db.GetChars() batchResult = new(SqlResult) keysStr = charL + strings.Join(keys, charR+","+charL) + charR - operation = GetInsertOperationByOption(option) - updateStr = "" + operation = GetInsertOperationByOption(option.InsertOption) ) - if option == insertOptionSave { - for _, k := range keys { - // If it's SAVE operation, - // do not automatically update the creating time. - if c.isSoftCreatedFiledName(k) { - continue - } - if len(updateStr) > 0 { - updateStr += "," - } - updateStr += fmt.Sprintf( - "%s%s%s=VALUES(%s%s%s)", - charL, k, charR, - charL, k, charR, - ) - } - updateStr = fmt.Sprintf("ON DUPLICATE KEY UPDATE %s", updateStr) - } - batchNum := defaultBatchNumber - if len(batch) > 0 && batch[0] > 0 { - batchNum = batch[0] + if option.InsertOption == insertOptionSave { + onDuplicateStr = c.formatOnDuplicate(keys, option) } var ( - listMapLen = len(listMap) + listLength = len(list) valueHolder = make([]string, 0) ) - for i := 0; i < listMapLen; i++ { + for i := 0; i < listLength; i++ { values = values[:0] // Note that the map type is unordered, // so it should use slice+key to retrieve the value. for _, k := range keys { - if s, ok := listMap[i][k].(Raw); ok { + if s, ok := list[i][k].(Raw); ok { values = append(values, gconv.String(s)) } else { values = append(values, "?") - params = append(params, listMap[i][k]) + params = append(params, list[i][k]) } } valueHolder = append(valueHolder, "("+gstr.Join(values, ",")+")") - if len(values) == batchNum || (i == listMapLen-1 && len(values) > 0) { - r, err := c.DB.DoExec( - link, - fmt.Sprintf( - "%s INTO %s(%s) VALUES%s %s", - operation, table, keysStr, - gstr.Join(valueHolder, ","), - updateStr, - ), - params..., - ) + // Batch package checks: It meets the batch number or it is the last element. + if len(valueHolder) == option.BatchCount || (i == listLength-1 && len(valueHolder) > 0) { + r, err := c.db.DoExec(ctx, link, fmt.Sprintf( + "%s INTO %s(%s) VALUES%s %s", + operation, c.QuotePrefixTableName(table), keysStr, + gstr.Join(valueHolder, ","), + onDuplicateStr, + ), params...) if err != nil { return r, err } @@ -696,13 +447,58 @@ func (c *Core) DoBatchInsert(link Link, table string, list interface{}, option i return batchResult, nil } +func (c *Core) formatOnDuplicate(columns []string, option DoInsertOption) string { + var ( + onDuplicateStr string + ) + if option.OnDuplicateStr != "" { + onDuplicateStr = option.OnDuplicateStr + } else if len(option.OnDuplicateMap) > 0 { + for k, v := range option.OnDuplicateMap { + if len(onDuplicateStr) > 0 { + onDuplicateStr += "," + } + switch v.(type) { + case Raw, *Raw: + onDuplicateStr += fmt.Sprintf( + "%s=%s", + c.QuoteWord(k), + v, + ) + default: + onDuplicateStr += fmt.Sprintf( + "%s=VALUES(%s)", + c.QuoteWord(k), + c.QuoteWord(gconv.String(v)), + ) + } + } + } else { + for _, column := range columns { + // If it's SAVE operation, do not automatically update the creating time. + if c.isSoftCreatedFieldName(column) { + continue + } + if len(onDuplicateStr) > 0 { + onDuplicateStr += "," + } + onDuplicateStr += fmt.Sprintf( + "%s=VALUES(%s)", + c.QuoteWord(column), + c.QuoteWord(column), + ) + } + } + return fmt.Sprintf("ON DUPLICATE KEY UPDATE %s", onDuplicateStr) +} + // Update does "UPDATE ... " statement for the table. // -// The parameter <data> can be type of string/map/gmap/struct/*struct, etc. +// The parameter `data` can be type of string/map/gmap/struct/*struct, etc. // Eg: "uid=10000", "uid", 10000, g.Map{"uid": 10000, "name":"john"} // -// The parameter <condition> can be type of string/map/gmap/slice/struct/*struct, etc. -// It is commonly used with parameter <args>. +// The parameter `condition` can be type of string/map/gmap/slice/struct/*struct, etc. +// It is commonly used with parameter `args`. // Eg: // "uid=10000", // "uid", 10000 @@ -714,10 +510,10 @@ func (c *Core) Update(table string, data interface{}, condition interface{}, arg return c.Model(table).Data(data).Where(condition, args...).Update() } -// doUpdate does "UPDATE ... " statement for the table. +// DoUpdate does "UPDATE ... " statement for the table. // 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) +func (c *Core) DoUpdate(ctx context.Context, link Link, table string, data interface{}, condition string, args ...interface{}) (result sql.Result, err error) { + table = c.QuotePrefixTableName(table) var ( rv = reflect.ValueOf(data) kind = rv.Kind() @@ -733,60 +529,67 @@ func (c *Core) DoUpdate(link Link, table string, data interface{}, condition str switch kind { case reflect.Map, reflect.Struct: var ( - fields []string - dataMap = ConvertDataForTableRecord(data) + fields []string + dataMap = ConvertDataForTableRecord(data) + counterHandler = func(column string, counter Counter) { + if counter.Value != 0 { + var ( + column = c.QuoteWord(column) + columnRef = c.QuoteWord(counter.Field) + columnVal = counter.Value + operator = "+" + ) + if columnVal < 0 { + operator = "-" + columnVal = -columnVal + } + fields = append(fields, fmt.Sprintf("%s=%s%s?", column, columnRef, operator)) + params = append(params, columnVal) + } + } ) + for k, v := range dataMap { switch value := v.(type) { case *Counter: - if value.Value != 0 { - column := c.DB.QuoteWord(value.Field) - fields = append(fields, fmt.Sprintf("%s=%s+?", column, column)) - params = append(params, value.Value) - } + counterHandler(k, *value) + case Counter: - if value.Value != 0 { - column := c.DB.QuoteWord(value.Field) - fields = append(fields, fmt.Sprintf("%s=%s+?", column, column)) - params = append(params, value.Value) - } + counterHandler(k, value) + default: if s, ok := v.(Raw); ok { - fields = append(fields, c.DB.QuoteWord(k)+"="+gconv.String(s)) + fields = append(fields, c.QuoteWord(k)+"="+gconv.String(s)) } else { - fields = append(fields, c.DB.QuoteWord(k)+"=?") + fields = append(fields, c.QuoteWord(k)+"=?") params = append(params, v) } - } } updates = strings.Join(fields, ",") + default: updates = gconv.String(data) } if len(updates) == 0 { - return nil, gerror.New("data cannot be empty") + return nil, gerror.NewCode(gcode.CodeMissingParameter, "data cannot be empty") } if len(params) > 0 { args = append(params, args...) } // If no link passed, it then uses the master link. if link == nil { - if link, err = c.DB.Master(); err != nil { + if link, err = c.MasterLink(); err != nil { return nil, err } } - return c.DB.DoExec( - link, - fmt.Sprintf("UPDATE %s SET %s%s", table, updates, condition), - args..., - ) + return c.db.DoExec(ctx, link, fmt.Sprintf("UPDATE %s SET %s%s", table, updates, condition), args...) } // Delete does "DELETE FROM ... " statement for the table. // -// The parameter <condition> can be type of string/map/gmap/slice/struct/*struct, etc. -// It is commonly used with parameter <args>. +// The parameter `condition` can be type of string/map/gmap/slice/struct/*struct, etc. +// It is commonly used with parameter `args`. // Eg: // "uid=10000", // "uid", 10000 @@ -800,14 +603,14 @@ func (c *Core) Delete(table string, condition interface{}, args ...interface{}) // DoDelete does "DELETE FROM ... " statement for the table. // 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) { +func (c *Core) DoDelete(ctx context.Context, link Link, table string, condition string, args ...interface{}) (result sql.Result, err error) { if link == nil { - if link, err = c.DB.Master(); err != nil { + if link, err = c.MasterLink(); err != nil { return nil, err } } - table = c.DB.QuotePrefixTableName(table) - return c.DB.DoExec(link, fmt.Sprintf("DELETE FROM %s%s", table, condition), args...) + table = c.QuotePrefixTableName(table) + return c.db.DoExec(ctx, link, fmt.Sprintf("DELETE FROM %s%s", table, condition), args...) } // convertRowsToResult converts underlying data record type sql.Rows to Result type. @@ -828,7 +631,7 @@ func (c *Core) convertRowsToResult(rows *sql.Rows) (Result, error) { } var ( values = make([]interface{}, len(columnNames)) - records = make(Result, 0) + result = make(Result, 0) scanArgs = make([]interface{}, len(values)) ) for i := range values { @@ -836,22 +639,22 @@ func (c *Core) convertRowsToResult(rows *sql.Rows) (Result, error) { } for { if err := rows.Scan(scanArgs...); err != nil { - return records, err + return result, err } - row := make(Record) + record := Record{} for i, value := range values { if value == nil { - row[columnNames[i]] = gvar.New(nil) + record[columnNames[i]] = gvar.New(nil) } else { - row[columnNames[i]] = gvar.New(c.DB.convertFieldValueToLocalValue(value, columnTypes[i])) + record[columnNames[i]] = gvar.New(c.convertFieldValueToLocalValue(value, columnTypes[i])) } } - records = append(records, row) + result = append(result, record) if !rows.Next() { break } } - return records, nil + return result, nil } // MarshalJSON implements the interface MarshalJSON for json.Marshal. @@ -865,19 +668,25 @@ func (c *Core) MarshalJSON() ([]byte, error) { // writeSqlToLogger outputs the sql object to logger. // It is enabled only if configuration "debug" is true. -func (c *Core) writeSqlToLogger(v *Sql) { - s := fmt.Sprintf("[%3d ms] [%s] %s", v.End-v.Start, v.Group, v.Format) - if v.Error != nil { - s += "\nError: " + v.Error.Error() - c.logger.Ctx(c.DB.GetCtx()).Error(s) +func (c *Core) writeSqlToLogger(ctx context.Context, sql *Sql) { + var transactionIdStr string + if sql.IsTransaction { + if v := ctx.Value(transactionIdForLoggerCtx); v != nil { + transactionIdStr = fmt.Sprintf(`[%d] `, v.(uint64)) + } + } + s := fmt.Sprintf("[%3d ms] [%s] %s%s", sql.End-sql.Start, sql.Group, transactionIdStr, sql.Format) + if sql.Error != nil { + s += "\nError: " + sql.Error.Error() + c.logger.Ctx(ctx).Error(s) } else { - c.logger.Ctx(c.DB.GetCtx()).Debug(s) + c.logger.Ctx(ctx).Debug(s) } } // HasTable determine whether the table name exists in the database. func (c *Core) HasTable(name string) (bool, error) { - tableList, err := c.DB.Tables() + tableList, err := c.db.Tables(c.GetCtx()) if err != nil { return false, err } @@ -889,12 +698,12 @@ func (c *Core) HasTable(name string) (bool, error) { return false, nil } -// isSoftCreatedFiledName checks and returns whether given filed name is an automatic-filled created time. -func (c *Core) isSoftCreatedFiledName(fieldName string) bool { +// isSoftCreatedFieldName checks and returns whether given filed name is an automatic-filled created time. +func (c *Core) isSoftCreatedFieldName(fieldName string) bool { if fieldName == "" { return false } - if config := c.DB.GetConfig(); config.CreatedAt != "" { + if config := c.db.GetConfig(); config.CreatedAt != "" { if utils.EqualFoldWithoutChars(fieldName, config.CreatedAt) { return true } diff --git a/database/gdb/gdb_core_config.go b/database/gdb/gdb_core_config.go index 85a9ee888..7a8e45443 100644 --- a/database/gdb/gdb_core_config.go +++ b/database/gdb/gdb_core_config.go @@ -1,4 +1,4 @@ -// Copyright GoFrame Author(https://github.com/gogf/gf). All Rights Reserved. +// Copyright GoFrame Author(https://goframe.org). All Rights Reserved. // // This Source Code Form is subject to the terms of the MIT License. // If a copy of the MIT was not distributed with this file, @@ -8,16 +8,11 @@ package gdb import ( "fmt" - "github.com/gogf/gf/os/gcache" + "github.com/gogf/gf/os/glog" "sync" "time" - "github.com/gogf/gf/os/glog" -) - -const ( - DEFAULT_GROUP_NAME = "default" // Deprecated, use DefaultGroupName instead. - DefaultGroupName = "default" // Default group name. + "github.com/gogf/gf/os/gcache" ) // Config is the configuration management object. @@ -28,28 +23,38 @@ type ConfigGroup []ConfigNode // ConfigNode is configuration for one node. type ConfigNode struct { - Host string // Host of server, ip or domain like: 127.0.0.1, localhost - Port string // Port, it's commonly 3306. - User string // Authentication username. - Pass string // Authentication password. - Name string // Default used database name. - Type string // Database type: mysql, sqlite, mssql, pgsql, oracle. - Role string // (Optional, "master" in default) Node role, used for master-slave mode: master, slave. - Debug bool // (Optional) Debug mode enables debug information logging and output. - Prefix string // (Optional) Table prefix. - DryRun bool // (Optional) Dry run, which does SELECT but no INSERT/UPDATE/DELETE statements. - Weight int // (Optional) Weight for load balance calculating, it's useless if there's just one node. - Charset string // (Optional, "utf8mb4" in default) Custom charset when operating on database. - LinkInfo string `json:"link"` // (Optional) Custom link information, when it is used, configuration Host/Port/User/Pass/Name are ignored. - MaxIdleConnCount int `json:"maxidle"` // (Optional) Max idle connection configuration for underlying connection pool. - MaxOpenConnCount int `json:"maxopen"` // (Optional) Max open connection configuration for underlying connection pool. - MaxConnLifetime time.Duration `json:"maxlifetime"` // (Optional) Max connection TTL configuration for underlying connection pool. - CreatedAt string // (Optional) The filed name of table for automatic-filled created datetime. - UpdatedAt string // (Optional) The filed name of table for automatic-filled updated datetime. - DeletedAt string // (Optional) The filed name of table for automatic-filled updated datetime. - TimeMaintainDisabled bool // (Optional) Disable the automatic time maintaining feature. + Host string `json:"host"` // Host of server, ip or domain like: 127.0.0.1, localhost + Port string `json:"port"` // Port, it's commonly 3306. + User string `json:"user"` // Authentication username. + Pass string `json:"pass"` // Authentication password. + Name string `json:"name"` // Default used database name. + Type string `json:"type"` // Database type: mysql, sqlite, mssql, pgsql, oracle. + Link string `json:"link"` // (Optional) Custom link information, when it is used, configuration Host/Port/User/Pass/Name are ignored. + Role string `json:"role"` // (Optional, "master" in default) Node role, used for master-slave mode: master, slave. + Debug bool `json:"debug"` // (Optional) Debug mode enables debug information logging and output. + Prefix string `json:"prefix"` // (Optional) Table prefix. + DryRun bool `json:"dryRun"` // (Optional) Dry run, which does SELECT but no INSERT/UPDATE/DELETE statements. + Weight int `json:"weight"` // (Optional) Weight for load balance calculating, it's useless if there's just one node. + Charset string `json:"charset"` // (Optional, "utf8mb4" in default) Custom charset when operating on database. + Timezone string `json:"timezone"` // (Optional) Sets the time zone for displaying and interpreting time stamps. + MaxIdleConnCount int `json:"maxIdle"` // (Optional) Max idle connection configuration for underlying connection pool. + MaxOpenConnCount int `json:"maxOpen"` // (Optional) Max open connection configuration for underlying connection pool. + MaxConnLifeTime time.Duration `json:"maxLifeTime"` // (Optional) Max amount of time a connection may be idle before being closed. + QueryTimeout time.Duration `json:"queryTimeout"` // (Optional) Max query time for per dql. + ExecTimeout time.Duration `json:"execTimeout"` // (Optional) Max exec time for dml. + TranTimeout time.Duration `json:"tranTimeout"` // (Optional) Max exec time time for a transaction. + PrepareTimeout time.Duration `json:"prepareTimeout"` // (Optional) Max exec time time for prepare operation. + CreatedAt string `json:"createdAt"` // (Optional) The filed name of table for automatic-filled created datetime. + UpdatedAt string `json:"updatedAt"` // (Optional) The filed name of table for automatic-filled updated datetime. + DeletedAt string `json:"deletedAt"` // (Optional) The filed name of table for automatic-filled updated datetime. + TimeMaintainDisabled bool `json:"timeMaintainDisabled"` // (Optional) Disable the automatic time maintaining feature. + CtxStrict bool `json:"ctxStrict"` // (Optional) Strictly require context input for all database operations. } +const ( + DefaultGroupName = "default" // Default group name. +) + // configs is internal used configuration object. var configs struct { sync.RWMutex @@ -133,25 +138,44 @@ func (c *Core) SetLogger(logger *glog.Logger) { c.logger = logger } -// GetLogger returns the logger of the orm. +// GetLogger returns the (logger) of the orm. func (c *Core) GetLogger() *glog.Logger { return c.logger } -// SetMaxIdleConnCount sets the max idle connection count for underlying connection pool. +// SetMaxIdleConnCount sets the maximum number of connections in the idle +// connection pool. +// +// If MaxOpenConns is greater than 0 but less than the new MaxIdleConns, +// then the new MaxIdleConns will be reduced to match the MaxOpenConns limit. +// +// If n <= 0, no idle connections are retained. +// +// The default max idle connections is currently 2. This may change in +// a future release. func (c *Core) SetMaxIdleConnCount(n int) { c.config.MaxIdleConnCount = n } -// SetMaxOpenConnCount sets the max open connection count for underlying connection pool. +// SetMaxOpenConnCount sets the maximum number of open connections to the database. +// +// If MaxIdleConns is greater than 0 and the new MaxOpenConns is less than +// MaxIdleConns, then MaxIdleConns will be reduced to match the new +// MaxOpenConns limit. +// +// If n <= 0, then there is no limit on the number of open connections. +// The default is 0 (unlimited). func (c *Core) SetMaxOpenConnCount(n int) { c.config.MaxOpenConnCount = n } -// SetMaxConnLifetime sets the connection TTL for underlying connection pool. -// If parameter <d> <= 0, it means the connection never expires. -func (c *Core) SetMaxConnLifetime(d time.Duration) { - c.config.MaxConnLifetime = d +// SetMaxConnLifeTime sets the maximum amount of time a connection may be reused. +// +// Expired connections may be closed lazily before reuse. +// +// If d <= 0, connections are not closed due to a connection's age. +func (c *Core) SetMaxConnLifeTime(d time.Duration) { + c.config.MaxConnLifeTime = d } // String returns the node as string. @@ -162,8 +186,8 @@ func (node *ConfigNode) String() string { node.Name, node.Type, node.Role, node.Charset, node.Debug, node.MaxIdleConnCount, node.MaxOpenConnCount, - node.MaxConnLifetime, - node.LinkInfo, + node.MaxConnLifeTime, + node.Link, ) } @@ -201,7 +225,7 @@ func (c *Core) SetDryRun(enabled bool) { // GetDryRun returns the DryRun value. // Deprecated, use GetConfig instead. func (c *Core) GetDryRun() bool { - return c.config.DryRun + return c.config.DryRun || allDryRun } // GetPrefix returns the table prefix string configured. diff --git a/database/gdb/gdb_core_link.go b/database/gdb/gdb_core_link.go new file mode 100644 index 000000000..06ea3190b --- /dev/null +++ b/database/gdb/gdb_core_link.go @@ -0,0 +1,31 @@ +// Copyright GoFrame Author(https://goframe.org). All Rights Reserved. +// +// This Source Code Form is subject to the terms of the MIT License. +// If a copy of the MIT was not distributed with this file, +// You can obtain one at https://github.com/gogf/gf. + +package gdb + +import ( + "database/sql" +) + +// dbLink is used to implement interface Link for DB. +type dbLink struct { + *sql.DB +} + +// txLink is used to implement interface Link for TX. +type txLink struct { + *sql.Tx +} + +// IsTransaction returns if current Link is a transaction. +func (*dbLink) IsTransaction() bool { + return false +} + +// IsTransaction returns if current Link is a transaction. +func (*txLink) IsTransaction() bool { + return true +} diff --git a/database/gdb/gdb_core_structure.go b/database/gdb/gdb_core_structure.go index 27fedb2a1..e7aa76b67 100644 --- a/database/gdb/gdb_core_structure.go +++ b/database/gdb/gdb_core_structure.go @@ -1,4 +1,4 @@ -// Copyright GoFrame Author(https://github.com/gogf/gf). All Rights Reserved. +// Copyright GoFrame Author(https://goframe.org). All Rights Reserved. // // This Source Code Form is subject to the terms of the MIT License. // If a copy of the MIT was not distributed with this file, @@ -7,11 +7,11 @@ package gdb import ( - "github.com/gogf/gf/errors/gerror" - "github.com/gogf/gf/util/gutil" "strings" "time" + "github.com/gogf/gf/util/gutil" + "github.com/gogf/gf/text/gstr" "github.com/gogf/gf/os/gtime" @@ -25,8 +25,8 @@ import ( // convertFieldValueToLocalValue automatically checks and converts field value from database type // to golang variable type. func (c *Core) convertFieldValueToLocalValue(fieldValue interface{}, fieldType string) interface{} { - // If there's no type retrieved, it returns the <fieldValue> directly - // to use its original data type, as <fieldValue> is type of interface{}. + // If there's no type retrieved, it returns the `fieldValue` directly + // to use its original data type, as `fieldValue` is type of interface{}. if fieldType == "" { return fieldValue } @@ -56,6 +56,7 @@ func (c *Core) convertFieldValueToLocalValue(fieldValue interface{}, fieldType s return gconv.Int(gconv.String(fieldValue)) case + "int8", // For pgsql, int8 = bigint. "big_int", "bigint", "bigserial": @@ -99,7 +100,8 @@ func (c *Core) convertFieldValueToLocalValue(fieldValue interface{}, fieldType s case "datetime", - "timestamp": + "timestamp", + "timestamptz": if t, ok := fieldValue.(time.Time); ok { return gtime.NewFromTime(t) } @@ -149,7 +151,7 @@ func (c *Core) convertFieldValueToLocalValue(fieldValue interface{}, fieldType s // mappingAndFilterData automatically mappings the map key to table field and removes // all key-value pairs that are not the field of given table. 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 { + if fieldsMap, err := c.db.TableFields(c.GetCtx(), table, schema); err == nil { fieldsKeyMap := make(map[string]interface{}, len(fieldsMap)) for k, _ := range fieldsMap { fieldsKeyMap[k] = nil @@ -162,15 +164,11 @@ func (c *Core) mappingAndFilterData(schema, table string, data map[string]interf 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. + // It deletes all key-value pairs that has incorrect field name. if filter { for dataKey, _ := range data { if _, ok := fieldsMap[dataKey]; !ok { @@ -186,7 +184,7 @@ func (c *Core) mappingAndFilterData(schema, table string, data map[string]interf //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 { +// if fields, err := c.db.TableFields(table, schema); err == nil { // for k, v := range data { // if _, ok := fields[k]; ok { // newDataMap[k] = v diff --git a/database/gdb/gdb_core_tracing.go b/database/gdb/gdb_core_tracing.go new file mode 100644 index 000000000..0b0bc6d59 --- /dev/null +++ b/database/gdb/gdb_core_tracing.go @@ -0,0 +1,80 @@ +// Copyright GoFrame Author(https://goframe.org). All Rights Reserved. +// +// This Source Code Form is subject to the terms of the MIT License. +// If a copy of the MIT was not distributed with this file, +// You can obtain one at https://github.com/gogf/gf. +// + +package gdb + +import ( + "context" + "fmt" + "github.com/gogf/gf" + "github.com/gogf/gf/net/gtrace" + "go.opentelemetry.io/otel" + "go.opentelemetry.io/otel/attribute" + "go.opentelemetry.io/otel/codes" + "go.opentelemetry.io/otel/trace" +) + +const ( + tracingInstrumentName = "github.com/gogf/gf/database/gdb" + tracingAttrDbType = "db.type" + tracingAttrDbHost = "db.host" + tracingAttrDbPort = "db.port" + tracingAttrDbName = "db.name" + tracingAttrDbUser = "db.user" + tracingAttrDbLink = "db.link" + tracingAttrDbGroup = "db.group" + tracingEventDbExecution = "db.execution" + tracingEventDbExecutionSql = "db.execution.sql" + tracingEventDbExecutionCost = "db.execution.cost" + tracingEventDbExecutionType = "db.execution.type" +) + +// addSqlToTracing adds sql information to tracer if it's enabled. +func (c *Core) addSqlToTracing(ctx context.Context, sql *Sql) { + if !gtrace.IsTracingInternal() || !gtrace.IsActivated(ctx) { + return + } + tr := otel.GetTracerProvider().Tracer( + tracingInstrumentName, + trace.WithInstrumentationVersion(gf.VERSION), + ) + ctx, span := tr.Start(ctx, sql.Type, trace.WithSpanKind(trace.SpanKindInternal)) + defer span.End() + + if sql.Error != nil { + span.SetStatus(codes.Error, fmt.Sprintf(`%+v`, sql.Error)) + } + labels := make([]attribute.KeyValue, 0) + labels = append(labels, gtrace.CommonLabels()...) + labels = append(labels, + attribute.String(tracingAttrDbType, c.db.GetConfig().Type), + ) + if c.db.GetConfig().Host != "" { + labels = append(labels, attribute.String(tracingAttrDbHost, c.db.GetConfig().Host)) + } + if c.db.GetConfig().Port != "" { + labels = append(labels, attribute.String(tracingAttrDbPort, c.db.GetConfig().Port)) + } + if c.db.GetConfig().Name != "" { + labels = append(labels, attribute.String(tracingAttrDbName, c.db.GetConfig().Name)) + } + if c.db.GetConfig().User != "" { + labels = append(labels, attribute.String(tracingAttrDbUser, c.db.GetConfig().User)) + } + if filteredLink := c.db.FilteredLink(); filteredLink != "" { + labels = append(labels, attribute.String(tracingAttrDbLink, c.db.FilteredLink())) + } + if group := c.db.GetGroup(); group != "" { + labels = append(labels, attribute.String(tracingAttrDbGroup, group)) + } + span.SetAttributes(labels...) + span.AddEvent(tracingEventDbExecution, trace.WithAttributes( + attribute.String(tracingEventDbExecutionSql, sql.Format), + attribute.String(tracingEventDbExecutionCost, fmt.Sprintf(`%d ms`, sql.End-sql.Start)), + attribute.String(tracingEventDbExecutionType, sql.Type), + )) +} diff --git a/database/gdb/gdb_core_transaction.go b/database/gdb/gdb_core_transaction.go new file mode 100644 index 000000000..0252f1096 --- /dev/null +++ b/database/gdb/gdb_core_transaction.go @@ -0,0 +1,548 @@ +// Copyright GoFrame Author(https://goframe.org). All Rights Reserved. +// +// This Source Code Form is subject to the terms of the MIT License. +// If a copy of the MIT was not distributed with this file, +// You can obtain one at https://github.com/gogf/gf. + +package gdb + +import ( + "context" + "database/sql" + "fmt" + "reflect" + + "github.com/gogf/gf/container/gtype" + "github.com/gogf/gf/os/gtime" + "github.com/gogf/gf/util/gconv" + "github.com/gogf/gf/util/guid" + + "github.com/gogf/gf/text/gregex" +) + +// TX is the struct for transaction management. +type TX struct { + db DB // db is the current gdb database manager. + tx *sql.Tx // tx is the raw and underlying transaction manager. + ctx context.Context // ctx is the context for this transaction only. + master *sql.DB // master is the raw and underlying database manager. + transactionId string // transactionId is an unique id generated by this object for this transaction. + transactionCount int // transactionCount marks the times that Begins. + isClosed bool // isClosed marks this transaction has already been committed or rolled back. +} + +const ( + transactionPointerPrefix = "transaction" + contextTransactionKeyPrefix = "TransactionObjectForGroup_" + transactionIdForLoggerCtx = "TransactionId" +) + +var ( + transactionIdGenerator = gtype.NewUint64() +) + +// Begin starts and returns the transaction object. +// You should call Commit or Rollback functions of the transaction object +// if you no longer use the transaction. Commit or Rollback functions will also +// close the transaction automatically. +func (c *Core) Begin() (tx *TX, err error) { + return c.doBeginCtx(c.GetCtx()) +} + +func (c *Core) doBeginCtx(ctx context.Context) (*TX, error) { + if master, err := c.db.Master(); err != nil { + return nil, err + } else { + var ( + tx *TX + sqlStr = "BEGIN" + mTime1 = gtime.TimestampMilli() + rawTx, err = master.Begin() + mTime2 = gtime.TimestampMilli() + sqlObj = &Sql{ + Sql: sqlStr, + Type: "DB.Begin", + Args: nil, + Format: sqlStr, + Error: err, + Start: mTime1, + End: mTime2, + Group: c.db.GetGroup(), + IsTransaction: true, + } + ) + if err == nil { + tx = &TX{ + db: c.db, + tx: rawTx, + ctx: context.WithValue(ctx, transactionIdForLoggerCtx, transactionIdGenerator.Add(1)), + master: master, + transactionId: guid.S(), + } + ctx = tx.ctx + } + // Tracing and logging. + c.addSqlToTracing(ctx, sqlObj) + if c.db.GetDebug() { + c.writeSqlToLogger(ctx, sqlObj) + } + return tx, err + } +} + +// Transaction wraps the transaction logic using function `f`. +// It rollbacks the transaction and returns the error from function `f` if +// it returns non-nil error. It commits the transaction and returns nil if +// function `f` returns nil. +// +// Note that, you should not Commit or Rollback the transaction in function `f` +// as it is automatically handled by this function. +func (c *Core) Transaction(ctx context.Context, f func(ctx context.Context, tx *TX) error) (err error) { + var ( + tx *TX + ) + if ctx == nil { + ctx = c.GetCtx() + } + // Check transaction object from context. + tx = TXFromCtx(ctx, c.db.GetGroup()) + if tx != nil { + return tx.Transaction(ctx, f) + } + tx, err = c.doBeginCtx(ctx) + if err != nil { + return err + } + // Inject transaction object into context. + tx.ctx = WithTX(tx.ctx, tx) + defer func() { + if err == nil { + if e := recover(); e != nil { + err = fmt.Errorf("%v", e) + } + } + if err != nil { + if e := tx.Rollback(); e != nil { + err = e + } + } else { + if e := tx.Commit(); e != nil { + err = e + } + } + }() + err = f(tx.ctx, tx) + return +} + +// WithTX injects given transaction object into context and returns a new context. +func WithTX(ctx context.Context, tx *TX) context.Context { + if tx == nil { + return ctx + } + // Check repeat injection from given. + group := tx.db.GetGroup() + if tx := TXFromCtx(ctx, group); tx != nil && tx.db.GetGroup() == group { + return ctx + } + dbCtx := tx.db.GetCtx() + if tx := TXFromCtx(dbCtx, group); tx != nil && tx.db.GetGroup() == group { + return dbCtx + } + // Inject transaction object and id into context. + ctx = context.WithValue(ctx, transactionKeyForContext(group), tx) + return ctx +} + +// TXFromCtx retrieves and returns transaction object from context. +// It is usually used in nested transaction feature, and it returns nil if it is not set previously. +func TXFromCtx(ctx context.Context, group string) *TX { + if ctx == nil { + return nil + } + v := ctx.Value(transactionKeyForContext(group)) + if v != nil { + tx := v.(*TX) + if tx.IsClosed() { + return nil + } + tx.ctx = ctx + return tx + } + return nil +} + +// transactionKeyForContext forms and returns a string for storing transaction object of certain database group into context. +func transactionKeyForContext(group string) string { + return contextTransactionKeyPrefix + group +} + +// transactionKeyForNestedPoint forms and returns the transaction key at current save point. +func (tx *TX) transactionKeyForNestedPoint() string { + return tx.db.GetCore().QuoteWord(transactionPointerPrefix + gconv.String(tx.transactionCount)) +} + +// Ctx sets the context for current transaction. +func (tx *TX) Ctx(ctx context.Context) *TX { + tx.ctx = ctx + return tx +} + +// Commit commits current transaction. +// Note that it releases previous saved transaction point if it's in a nested transaction procedure, +// or else it commits the hole transaction. +func (tx *TX) Commit() error { + if tx.transactionCount > 0 { + tx.transactionCount-- + _, err := tx.Exec("RELEASE SAVEPOINT " + tx.transactionKeyForNestedPoint()) + return err + } + var ( + sqlStr = "COMMIT" + mTime1 = gtime.TimestampMilli() + err = tx.tx.Commit() + mTime2 = gtime.TimestampMilli() + sqlObj = &Sql{ + Sql: sqlStr, + Type: "TX.Commit", + Args: nil, + Format: sqlStr, + Error: err, + Start: mTime1, + End: mTime2, + Group: tx.db.GetGroup(), + IsTransaction: true, + } + ) + tx.isClosed = true + tx.db.GetCore().addSqlToTracing(tx.ctx, sqlObj) + if tx.db.GetDebug() { + tx.db.GetCore().writeSqlToLogger(tx.ctx, sqlObj) + } + return err +} + +// Rollback aborts current transaction. +// Note that it aborts current transaction if it's in a nested transaction procedure, +// or else it aborts the hole transaction. +func (tx *TX) Rollback() error { + if tx.transactionCount > 0 { + tx.transactionCount-- + _, err := tx.Exec("ROLLBACK TO SAVEPOINT " + tx.transactionKeyForNestedPoint()) + return err + } + var ( + sqlStr = "ROLLBACK" + mTime1 = gtime.TimestampMilli() + err = tx.tx.Rollback() + mTime2 = gtime.TimestampMilli() + sqlObj = &Sql{ + Sql: sqlStr, + Type: "TX.Rollback", + Args: nil, + Format: sqlStr, + Error: err, + Start: mTime1, + End: mTime2, + Group: tx.db.GetGroup(), + IsTransaction: true, + } + ) + tx.isClosed = true + tx.db.GetCore().addSqlToTracing(tx.ctx, sqlObj) + if tx.db.GetDebug() { + tx.db.GetCore().writeSqlToLogger(tx.ctx, sqlObj) + } + return err +} + +// IsClosed checks and returns this transaction has already been committed or rolled back. +func (tx *TX) IsClosed() bool { + return tx.isClosed +} + +// Begin starts a nested transaction procedure. +func (tx *TX) Begin() error { + _, err := tx.Exec("SAVEPOINT " + tx.transactionKeyForNestedPoint()) + if err != nil { + return err + } + tx.transactionCount++ + return nil +} + +// SavePoint performs `SAVEPOINT xxx` SQL statement that saves transaction at current point. +// The parameter `point` specifies the point name that will be saved to server. +func (tx *TX) SavePoint(point string) error { + _, err := tx.Exec("SAVEPOINT " + tx.db.GetCore().QuoteWord(point)) + return err +} + +// RollbackTo performs `ROLLBACK TO SAVEPOINT xxx` SQL statement that rollbacks to specified saved transaction. +// The parameter `point` specifies the point name that was saved previously. +func (tx *TX) RollbackTo(point string) error { + _, err := tx.Exec("ROLLBACK TO SAVEPOINT " + tx.db.GetCore().QuoteWord(point)) + return err +} + +// Transaction wraps the transaction logic using function `f`. +// It rollbacks the transaction and returns the error from function `f` if +// it returns non-nil error. It commits the transaction and returns nil if +// function `f` returns nil. +// +// Note that, you should not Commit or Rollback the transaction in function `f` +// as it is automatically handled by this function. +func (tx *TX) Transaction(ctx context.Context, f func(ctx context.Context, tx *TX) error) (err error) { + if ctx != nil { + tx.ctx = ctx + } + // Check transaction object from context. + if TXFromCtx(tx.ctx, tx.db.GetGroup()) == nil { + // Inject transaction object into context. + tx.ctx = WithTX(tx.ctx, tx) + } + err = tx.Begin() + if err != nil { + return err + } + defer func() { + if err == nil { + if e := recover(); e != nil { + err = fmt.Errorf("%v", e) + } + } + if err != nil { + if e := tx.Rollback(); e != nil { + err = e + } + } else { + if e := tx.Commit(); e != nil { + err = e + } + } + }() + err = f(tx.ctx, tx) + return +} + +// Query does query operation on transaction. +// See Core.Query. +func (tx *TX) Query(sql string, args ...interface{}) (rows *sql.Rows, err error) { + return tx.db.DoQuery(tx.ctx, &txLink{tx.tx}, sql, args...) +} + +// Exec does none query operation on transaction. +// See Core.Exec. +func (tx *TX) Exec(sql string, args ...interface{}) (sql.Result, error) { + return tx.db.DoExec(tx.ctx, &txLink{tx.tx}, sql, args...) +} + +// Prepare creates a prepared statement for later queries or executions. +// Multiple queries or executions may be run concurrently from the +// returned statement. +// The caller must call the statement's Close method +// when the statement is no longer needed. +func (tx *TX) Prepare(sql string) (*Stmt, error) { + return tx.db.DoPrepare(tx.ctx, &txLink{tx.tx}, sql) +} + +// GetAll queries and returns data records from database. +func (tx *TX) GetAll(sql string, args ...interface{}) (Result, error) { + rows, err := tx.Query(sql, args...) + if err != nil || rows == nil { + return nil, err + } + defer rows.Close() + return tx.db.GetCore().convertRowsToResult(rows) +} + +// GetOne queries and returns one record from database. +func (tx *TX) GetOne(sql string, args ...interface{}) (Record, error) { + list, err := tx.GetAll(sql, args...) + if err != nil { + return nil, err + } + if len(list) > 0 { + return list[0], nil + } + return nil, nil +} + +// GetStruct queries one record from database and converts it to given struct. +// The parameter `pointer` should be a pointer to struct. +func (tx *TX) GetStruct(obj interface{}, sql string, args ...interface{}) error { + one, err := tx.GetOne(sql, args...) + if err != nil { + return err + } + return one.Struct(obj) +} + +// GetStructs queries records from database and converts them to given struct. +// The parameter `pointer` should be type of struct slice: []struct/[]*struct. +func (tx *TX) GetStructs(objPointerSlice interface{}, sql string, args ...interface{}) error { + all, err := tx.GetAll(sql, args...) + if err != nil { + return err + } + return all.Structs(objPointerSlice) +} + +// GetScan queries one or more records from database and converts them to given struct or +// struct array. +// +// If parameter `pointer` is type of struct pointer, it calls GetStruct internally for +// the conversion. If parameter `pointer` is type of slice, it calls GetStructs internally +// for conversion. +func (tx *TX) GetScan(pointer interface{}, sql string, args ...interface{}) error { + t := reflect.TypeOf(pointer) + k := t.Kind() + if k != reflect.Ptr { + return fmt.Errorf("params should be type of pointer, but got: %v", k) + } + k = t.Elem().Kind() + switch k { + case reflect.Array, reflect.Slice: + return tx.GetStructs(pointer, sql, args...) + case reflect.Struct: + return tx.GetStruct(pointer, sql, args...) + default: + return fmt.Errorf("element type should be type of struct/slice, unsupported: %v", k) + } +} + +// GetValue queries and returns the field value from database. +// The sql should queries only one field from database, or else it returns only one +// field of the result. +func (tx *TX) GetValue(sql string, args ...interface{}) (Value, error) { + one, err := tx.GetOne(sql, args...) + if err != nil { + return nil, err + } + for _, v := range one { + return v, nil + } + return nil, nil +} + +// GetCount queries and returns the count from database. +func (tx *TX) GetCount(sql string, args ...interface{}) (int, error) { + if !gregex.IsMatchString(`(?i)SELECT\s+COUNT\(.+\)\s+FROM`, sql) { + sql, _ = gregex.ReplaceString(`(?i)(SELECT)\s+(.+)\s+(FROM)`, `$1 COUNT($2) $3`, sql) + } + value, err := tx.GetValue(sql, args...) + if err != nil { + return 0, err + } + return value.Int(), nil +} + +// Insert does "INSERT INTO ..." statement for the table. +// If there's already one unique record of the data in the table, it returns error. +// +// The parameter `data` can be type of map/gmap/struct/*struct/[]map/[]struct, etc. +// Eg: +// Data(g.Map{"uid": 10000, "name":"john"}) +// Data(g.Slice{g.Map{"uid": 10000, "name":"john"}, g.Map{"uid": 20000, "name":"smith"}) +// +// 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) { + if len(batch) > 0 { + return tx.Model(table).Ctx(tx.ctx).Data(data).Batch(batch[0]).Insert() + } + return tx.Model(table).Ctx(tx.ctx).Data(data).Insert() +} + +// InsertIgnore does "INSERT IGNORE INTO ..." statement for the table. +// If there's already one unique record of the data in the table, it ignores the inserting. +// +// The parameter `data` can be type of map/gmap/struct/*struct/[]map/[]struct, etc. +// Eg: +// Data(g.Map{"uid": 10000, "name":"john"}) +// Data(g.Slice{g.Map{"uid": 10000, "name":"john"}, g.Map{"uid": 20000, "name":"smith"}) +// +// 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) { + if len(batch) > 0 { + return tx.Model(table).Ctx(tx.ctx).Data(data).Batch(batch[0]).InsertIgnore() + } + return tx.Model(table).Ctx(tx.ctx).Data(data).InsertIgnore() +} + +// InsertAndGetId performs action Insert and returns the last insert id that automatically generated. +func (tx *TX) InsertAndGetId(table string, data interface{}, batch ...int) (int64, error) { + if len(batch) > 0 { + return tx.Model(table).Ctx(tx.ctx).Data(data).Batch(batch[0]).InsertAndGetId() + } + return tx.Model(table).Ctx(tx.ctx).Data(data).InsertAndGetId() +} + +// Replace does "REPLACE INTO ..." statement for the table. +// If there's already one unique record of the data in the table, it deletes the record +// and inserts a new one. +// +// The parameter `data` can be type of map/gmap/struct/*struct/[]map/[]struct, etc. +// Eg: +// Data(g.Map{"uid": 10000, "name":"john"}) +// Data(g.Slice{g.Map{"uid": 10000, "name":"john"}, g.Map{"uid": 20000, "name":"smith"}) +// +// The parameter `data` can be type of map/gmap/struct/*struct/[]map/[]struct, etc. +// 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) { + if len(batch) > 0 { + return tx.Model(table).Ctx(tx.ctx).Data(data).Batch(batch[0]).Replace() + } + return tx.Model(table).Ctx(tx.ctx).Data(data).Replace() +} + +// Save does "INSERT INTO ... ON DUPLICATE KEY UPDATE..." statement for the table. +// It updates the record if there's primary or unique index in the saving data, +// or else it inserts a new record into the table. +// +// The parameter `data` can be type of map/gmap/struct/*struct/[]map/[]struct, etc. +// Eg: +// Data(g.Map{"uid": 10000, "name":"john"}) +// Data(g.Slice{g.Map{"uid": 10000, "name":"john"}, g.Map{"uid": 20000, "name":"smith"}) +// +// 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) { + if len(batch) > 0 { + return tx.Model(table).Ctx(tx.ctx).Data(data).Batch(batch[0]).Save() + } + return tx.Model(table).Ctx(tx.ctx).Data(data).Save() +} + +// Update does "UPDATE ... " statement for the table. +// +// The parameter `data` can be type of string/map/gmap/struct/*struct, etc. +// Eg: "uid=10000", "uid", 10000, g.Map{"uid": 10000, "name":"john"} +// +// The parameter `condition` can be type of string/map/gmap/slice/struct/*struct, etc. +// It is commonly used with parameter `args`. +// Eg: +// "uid=10000", +// "uid", 10000 +// "money>? AND name like ?", 99999, "vip_%" +// "status IN (?)", g.Slice{1,2,3} +// "age IN(?,?)", 18, 50 +// User{ Id : 1, UserName : "john"} +func (tx *TX) Update(table string, data interface{}, condition interface{}, args ...interface{}) (sql.Result, error) { + return tx.Model(table).Ctx(tx.ctx).Data(data).Where(condition, args...).Update() +} + +// Delete does "DELETE FROM ... " statement for the table. +// +// The parameter `condition` can be type of string/map/gmap/slice/struct/*struct, etc. +// It is commonly used with parameter `args`. +// Eg: +// "uid=10000", +// "uid", 10000 +// "money>? AND name like ?", 99999, "vip_%" +// "status IN (?)", g.Slice{1,2,3} +// "age IN(?,?)", 18, 50 +// User{ Id : 1, UserName : "john"} +func (tx *TX) Delete(table string, condition interface{}, args ...interface{}) (sql.Result, error) { + return tx.Model(table).Ctx(tx.ctx).Where(condition, args...).Delete() +} diff --git a/database/gdb/gdb_core_underlying.go b/database/gdb/gdb_core_underlying.go new file mode 100644 index 000000000..036ed3494 --- /dev/null +++ b/database/gdb/gdb_core_underlying.go @@ -0,0 +1,240 @@ +// Copyright GoFrame Author(https://goframe.org). All Rights Reserved. +// +// This Source Code Form is subject to the terms of the MIT License. +// If a copy of the MIT was not distributed with this file, +// You can obtain one at https://github.com/gogf/gf. +// + +package gdb + +import ( + "context" + "database/sql" + "github.com/gogf/gf/errors/gcode" + "github.com/gogf/gf/errors/gerror" + + "github.com/gogf/gf/os/gtime" +) + +// Query commits one query SQL to underlying driver and returns the execution result. +// It is most commonly used for data querying. +func (c *Core) Query(sql string, args ...interface{}) (rows *sql.Rows, err error) { + return c.db.DoQuery(c.GetCtx(), nil, sql, args...) +} + +// DoQuery commits the sql string and its arguments to underlying driver +// through given link object and returns the execution result. +func (c *Core) DoQuery(ctx context.Context, link Link, sql string, args ...interface{}) (rows *sql.Rows, err error) { + // Transaction checks. + if link == nil { + if tx := TXFromCtx(ctx, c.db.GetGroup()); tx != nil { + // Firstly, check and retrieve transaction link from context. + link = &txLink{tx.tx} + } else if link, err = c.SlaveLink(); err != nil { + // Or else it creates one from master node. + return nil, err + } + } else if !link.IsTransaction() { + // If current link is not transaction link, it checks and retrieves transaction from context. + if tx := TXFromCtx(ctx, c.db.GetGroup()); tx != nil { + link = &txLink{tx.tx} + } + } + + if c.GetConfig().QueryTimeout > 0 { + ctx, _ = context.WithTimeout(ctx, c.GetConfig().QueryTimeout) + } + + // Link execution. + sql, args = formatSql(sql, args) + sql, args, err = c.db.DoCommit(ctx, link, sql, args) + if err != nil { + return nil, err + } + mTime1 := gtime.TimestampMilli() + rows, err = link.QueryContext(ctx, sql, args...) + mTime2 := gtime.TimestampMilli() + sqlObj := &Sql{ + Sql: sql, + Type: "DB.QueryContext", + Args: args, + Format: FormatSqlWithArgs(sql, args), + Error: err, + Start: mTime1, + End: mTime2, + Group: c.db.GetGroup(), + IsTransaction: link.IsTransaction(), + } + // Tracing and logging. + c.addSqlToTracing(ctx, sqlObj) + if c.db.GetDebug() { + c.writeSqlToLogger(ctx, sqlObj) + } + if err == nil { + return rows, nil + } else { + err = formatError(err, sql, args...) + } + return nil, err +} + +// Exec commits one query SQL to underlying driver and returns the execution result. +// It is most commonly used for data inserting and updating. +func (c *Core) Exec(sql string, args ...interface{}) (result sql.Result, err error) { + return c.db.DoExec(c.GetCtx(), nil, sql, args...) +} + +// DoExec commits the sql string and its arguments to underlying driver +// through given link object and returns the execution result. +func (c *Core) DoExec(ctx context.Context, link Link, sql string, args ...interface{}) (result sql.Result, err error) { + // Transaction checks. + if link == nil { + if tx := TXFromCtx(ctx, c.db.GetGroup()); tx != nil { + // Firstly, check and retrieve transaction link from context. + link = &txLink{tx.tx} + } else if link, err = c.MasterLink(); err != nil { + // Or else it creates one from master node. + return nil, err + } + } else if !link.IsTransaction() { + // If current link is not transaction link, it checks and retrieves transaction from context. + if tx := TXFromCtx(ctx, c.db.GetGroup()); tx != nil { + link = &txLink{tx.tx} + } + } + + if c.GetConfig().ExecTimeout > 0 { + var cancelFunc context.CancelFunc + ctx, cancelFunc = context.WithTimeout(ctx, c.GetConfig().ExecTimeout) + defer cancelFunc() + } + + // Link execution. + sql, args = formatSql(sql, args) + sql, args, err = c.db.DoCommit(ctx, link, sql, args) + if err != nil { + return nil, err + } + mTime1 := gtime.TimestampMilli() + if !c.db.GetDryRun() { + result, err = link.ExecContext(ctx, sql, args...) + } else { + result = new(SqlResult) + } + mTime2 := gtime.TimestampMilli() + sqlObj := &Sql{ + Sql: sql, + Type: "DB.ExecContext", + Args: args, + Format: FormatSqlWithArgs(sql, args), + Error: err, + Start: mTime1, + End: mTime2, + Group: c.db.GetGroup(), + IsTransaction: link.IsTransaction(), + } + // Tracing and logging. + c.addSqlToTracing(ctx, sqlObj) + if c.db.GetDebug() { + c.writeSqlToLogger(ctx, sqlObj) + } + return result, formatError(err, sql, args...) +} + +// DoCommit is a hook function, which deals with the sql string before it's committed to underlying driver. +// The parameter `link` specifies the current database connection operation object. You can modify the sql +// string `sql` and its arguments `args` as you wish before they're committed to driver. +func (c *Core) DoCommit(ctx context.Context, link Link, sql string, args []interface{}) (newSql string, newArgs []interface{}, err error) { + if c.db.GetConfig().CtxStrict { + if v := ctx.Value(ctxStrictKeyName); v == nil { + return sql, args, gerror.NewCode(gcode.CodeMissingParameter, ctxStrictErrorStr) + } + } + return sql, args, nil +} + +// Prepare creates a prepared statement for later queries or executions. +// Multiple queries or executions may be run concurrently from the +// returned statement. +// The caller must call the statement's Close method +// when the statement is no longer needed. +// +// The parameter `execOnMaster` specifies whether executing the sql on master node, +// or else it executes the sql on slave node if master-slave configured. +func (c *Core) Prepare(sql string, execOnMaster ...bool) (*Stmt, error) { + var ( + err error + link Link + ) + if len(execOnMaster) > 0 && execOnMaster[0] { + if link, err = c.MasterLink(); err != nil { + return nil, err + } + } else { + if link, err = c.SlaveLink(); err != nil { + return nil, err + } + } + return c.db.DoPrepare(c.GetCtx(), link, sql) +} + +// DoPrepare calls prepare function on given link object and returns the statement object. +func (c *Core) DoPrepare(ctx context.Context, link Link, sql string) (*Stmt, error) { + // Transaction checks. + if link == nil { + if tx := TXFromCtx(ctx, c.db.GetGroup()); tx != nil { + // Firstly, check and retrieve transaction link from context. + link = &txLink{tx.tx} + } else { + // Or else it creates one from master node. + var err error + if link, err = c.MasterLink(); err != nil { + return nil, err + } + } + } else if !link.IsTransaction() { + // If current link is not transaction link, it checks and retrieves transaction from context. + if tx := TXFromCtx(ctx, c.db.GetGroup()); tx != nil { + link = &txLink{tx.tx} + } + } + + if c.GetConfig().PrepareTimeout > 0 { + // DO NOT USE cancel function in prepare statement. + ctx, _ = context.WithTimeout(ctx, c.GetConfig().PrepareTimeout) + } + + if c.db.GetConfig().CtxStrict { + if v := ctx.Value(ctxStrictKeyName); v == nil { + return nil, gerror.NewCode(gcode.CodeMissingParameter, ctxStrictErrorStr) + } + } + + var ( + mTime1 = gtime.TimestampMilli() + stmt, err = link.PrepareContext(ctx, sql) + mTime2 = gtime.TimestampMilli() + sqlObj = &Sql{ + Sql: sql, + Type: "DB.PrepareContext", + Args: nil, + Format: FormatSqlWithArgs(sql, nil), + Error: err, + Start: mTime1, + End: mTime2, + Group: c.db.GetGroup(), + IsTransaction: link.IsTransaction(), + } + ) + // Tracing and logging. + c.addSqlToTracing(ctx, sqlObj) + if c.db.GetDebug() { + c.writeSqlToLogger(ctx, sqlObj) + } + return &Stmt{ + Stmt: stmt, + core: c, + link: link, + sql: sql, + }, err +} diff --git a/database/gdb/gdb_core_utility.go b/database/gdb/gdb_core_utility.go index c86086d99..5b0f1973a 100644 --- a/database/gdb/gdb_core_utility.go +++ b/database/gdb/gdb_core_utility.go @@ -1,4 +1,4 @@ -// Copyright GoFrame Author(https://github.com/gogf/gf). All Rights Reserved. +// Copyright GoFrame Author(https://goframe.org). All Rights Reserved. // // This Source Code Form is subject to the terms of the MIT License. // If a copy of the MIT was not distributed with this file, @@ -7,35 +7,41 @@ package gdb -import ( - "database/sql" -) - -// GetMaster acts like function Master but with additional <schema> parameter specifying +// MasterLink acts like function Master but with additional `schema` parameter specifying // the schema for the connection. It is defined for internal usage. // Also see Master. -func (c *Core) GetMaster(schema ...string) (*sql.DB, error) { - return c.getSqlDb(true, schema...) +func (c *Core) MasterLink(schema ...string) (Link, error) { + db, err := c.db.Master(schema...) + if err != nil { + return nil, err + } + return &dbLink{db}, nil } -// GetSlave acts like function Slave but with additional <schema> parameter specifying +// SlaveLink acts like function Slave but with additional `schema` parameter specifying // the schema for the connection. It is defined for internal usage. // Also see Slave. -func (c *Core) GetSlave(schema ...string) (*sql.DB, error) { - return c.getSqlDb(false, schema...) +func (c *Core) SlaveLink(schema ...string) (Link, error) { + db, err := c.db.Slave(schema...) + if err != nil { + return nil, err + } + return &dbLink{db}, nil } -// QuoteWord checks given string <s> a word, if true quotes it with security chars of the database -// and returns the quoted string; or else return <s> without any change. +// QuoteWord checks given string `s` a word, if true quotes it with security chars of the database +// and returns the quoted string; or else return `s` without any change. +// The meaning of a `word` can be considered as a column name. func (c *Core) QuoteWord(s string) string { - charLeft, charRight := c.DB.GetChars() + charLeft, charRight := c.db.GetChars() return doQuoteWord(s, charLeft, charRight) } // QuoteString quotes string with quote chars. Strings like: // "user", "user u", "user,user_detail", "user u, user_detail ut", "u.id asc". +// The meaning of a `string` can be considered as part of a statement string including columns. func (c *Core) QuoteString(s string) string { - charLeft, charRight := c.DB.GetChars() + charLeft, charRight := c.db.GetChars() return doQuoteString(s, charLeft, charRight) } @@ -49,8 +55,8 @@ func (c *Core) QuoteString(s string) string { // Note that, this will automatically checks the table prefix whether already added, // if true it does nothing to the table name, or else adds the prefix to the table name. func (c *Core) QuotePrefixTableName(table string) string { - charLeft, charRight := c.DB.GetChars() - return doHandleTableName(table, c.DB.GetPrefix(), charLeft, charRight) + charLeft, charRight := c.db.GetChars() + return doHandleTableName(table, c.db.GetPrefix(), charLeft, charRight) } // GetChars returns the security char for current database. @@ -59,12 +65,6 @@ func (c *Core) GetChars() (charLeft string, charRight string) { return "", "" } -// HandleSqlBeforeCommit handles the sql before posts it to database. -// It does nothing in default. -func (c *Core) HandleSqlBeforeCommit(sql string) string { - return sql -} - // Tables retrieves and returns the tables of current schema. // It's mainly used in cli tool chain for automatically generating the models. // diff --git a/database/gdb/gdb_driver_mssql.go b/database/gdb/gdb_driver_mssql.go index b3718e333..48a83590e 100644 --- a/database/gdb/gdb_driver_mssql.go +++ b/database/gdb/gdb_driver_mssql.go @@ -1,4 +1,4 @@ -// Copyright GoFrame Author(https://github.com/gogf/gf). All Rights Reserved. +// Copyright GoFrame Author(https://goframe.org). All Rights Reserved. // // This Source Code Form is subject to the terms of the MIT License. // If a copy of the MIT was not distributed with this file, @@ -12,12 +12,15 @@ package gdb import ( + "context" "database/sql" "fmt" - "github.com/gogf/gf/errors/gerror" + "github.com/gogf/gf/errors/gcode" "strconv" "strings" + "github.com/gogf/gf/errors/gerror" + "github.com/gogf/gf/internal/intlog" "github.com/gogf/gf/text/gstr" @@ -40,15 +43,15 @@ func (d *DriverMssql) New(core *Core, node *ConfigNode) (DB, error) { // Open creates and returns a underlying sql.DB object for mssql. func (d *DriverMssql) Open(config *ConfigNode) (*sql.DB, error) { source := "" - if config.LinkInfo != "" { - source = config.LinkInfo + if config.Link != "" { + source = config.Link } else { source = fmt.Sprintf( "user id=%s;password=%s;server=%s;port=%s;database=%s;encrypt=disable", config.User, config.Pass, config.Host, config.Port, config.Name, ) } - intlog.Printf("Open: %s", source) + intlog.Printf(d.GetCtx(), "Open: %s", source) if db, err := sql.Open("sqlserver", source); err == nil { return db, nil } else { @@ -56,13 +59,31 @@ func (d *DriverMssql) Open(config *ConfigNode) (*sql.DB, error) { } } +// FilteredLink retrieves and returns filtered `linkInfo` that can be using for +// logging or tracing purpose. +func (d *DriverMssql) FilteredLink() string { + linkInfo := d.GetConfig().Link + if linkInfo == "" { + return "" + } + s, _ := gregex.ReplaceString( + `(.+);\s*password=(.+);\s*server=(.+)`, + `$1;password=xxx;server=$3`, + d.GetConfig().Link, + ) + return s +} + // GetChars returns the security char for this type of database. func (d *DriverMssql) GetChars() (charLeft string, charRight string) { return "\"", "\"" } -// HandleSqlBeforeCommit deals with the sql string before commits it to underlying sql driver. -func (d *DriverMssql) HandleSqlBeforeCommit(link Link, sql string, args []interface{}) (string, []interface{}) { +// DoCommit deals with the sql string before commits it to underlying sql driver. +func (d *DriverMssql) DoCommit(ctx context.Context, link Link, sql string, args []interface{}) (newSql string, newArgs []interface{}, err error) { + defer func() { + newSql, newArgs, err = d.Core.DoCommit(ctx, link, newSql, newArgs) + }() var index int // Convert place holder char '?' to string "@px". str, _ := gregex.ReplaceStringFunc("\\?", sql, func(s string) string { @@ -70,7 +91,7 @@ func (d *DriverMssql) HandleSqlBeforeCommit(link Link, sql string, args []interf return fmt.Sprintf("@p%d", index) }) str, _ = gregex.ReplaceString("\"", "", str) - return d.parseSql(str), args + return d.parseSql(str), args, nil } // parseSql does some replacement of the sql before commits it to underlying driver, @@ -168,14 +189,14 @@ func (d *DriverMssql) parseSql(sql string) string { // Tables retrieves and returns the tables of current schema. // It's mainly used in cli tool chain for automatically generating the models. -func (d *DriverMssql) Tables(schema ...string) (tables []string, err error) { +func (d *DriverMssql) Tables(ctx context.Context, schema ...string) (tables []string, err error) { var result Result - link, err := d.DB.GetSlave(schema...) + link, err := d.SlaveLink(schema...) if err != nil { return nil, err } - result, err = d.DB.DoGetAll(link, `SELECT NAME FROM SYSOBJECTS WHERE XTYPE='U' AND STATUS >= 0 ORDER BY NAME`) + result, err = d.DoGetAll(ctx, link, `SELECT NAME FROM SYSOBJECTS WHERE XTYPE='U' AND STATUS >= 0 ORDER BY NAME`) if err != nil { return } @@ -188,28 +209,31 @@ func (d *DriverMssql) Tables(schema ...string) (tables []string, err error) { } // TableFields retrieves and returns the fields information of specified table of current schema. -func (d *DriverMssql) TableFields(table string, schema ...string) (fields map[string]*TableField, err error) { +// +// Also see DriverMysql.TableFields. +func (d *DriverMssql) TableFields(ctx context.Context, table string, schema ...string) (fields map[string]*TableField, err error) { charL, charR := d.GetChars() table = gstr.Trim(table, charL+charR) if gstr.Contains(table, " ") { - return nil, gerror.New("function TableFields supports only single table operations") + return nil, gerror.NewCode(gcode.CodeInvalidParameter, "function TableFields supports only single table operations") } - checkSchema := d.DB.GetSchema() + useSchema := d.db.GetSchema() if len(schema) > 0 && schema[0] != "" { - checkSchema = schema[0] + useSchema = schema[0] } - v, _ := internalCache.GetOrSetFunc( - fmt.Sprintf(`mssql_table_fields_%s_%s@group:%s`, table, checkSchema, d.GetGroup()), - func() (interface{}, error) { - var ( - result Result - link *sql.DB - ) - link, err = d.DB.GetSlave(checkSchema) - if err != nil { - return nil, err - } - structureSql := fmt.Sprintf(` + tableFieldsCacheKey := fmt.Sprintf( + `mssql_table_fields_%s_%s@group:%s`, + table, useSchema, d.GetGroup(), + ) + v := tableFieldsMap.GetOrSetFuncLock(tableFieldsCacheKey, func() interface{} { + var ( + result Result + link, err = d.SlaveLink(useSchema) + ) + if err != nil { + return nil + } + structureSql := fmt.Sprintf(` SELECT a.name Field, CASE b.name @@ -237,30 +261,44 @@ 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, err + strings.ToUpper(table), + ) + structureSql, _ = gregex.ReplaceString(`[\n\r\s]+`, " ", gstr.Trim(structureSql)) + result, err = d.DoGetAll(ctx, link, structureSql) + if err != nil { + return nil + } + fields = make(map[string]*TableField) + for i, m := range result { + fields[strings.ToLower(m["Field"].String())] = &TableField{ + Index: i, + Name: strings.ToLower(m["Field"].String()), + Type: strings.ToLower(m["Type"].String()), + Null: m["Null"].Bool(), + Key: m["Key"].String(), + Default: m["Default"].Val(), + Extra: m["Extra"].String(), + Comment: m["Comment"].String(), } - fields = make(map[string]*TableField) - for i, m := range result { - fields[strings.ToLower(m["Field"].String())] = &TableField{ - Index: i, - Name: strings.ToLower(m["Field"].String()), - Type: strings.ToLower(m["Type"].String()), - Null: m["Null"].Bool(), - Key: m["Key"].String(), - Default: m["Default"].Val(), - Extra: m["Extra"].String(), - Comment: m["Comment"].String(), - } - } - return fields, nil - }, 0) - if err == nil { + } + return fields + }) + if v != nil { fields = v.(map[string]*TableField) } return } + +// DoInsert is not supported in mssql. +func (d *DriverMssql) DoInsert(ctx context.Context, link Link, table string, list List, option DoInsertOption) (result sql.Result, err error) { + switch option.InsertOption { + case insertOptionSave: + return nil, gerror.NewCode(gcode.CodeNotSupported, `Save operation is not supported by mssql driver`) + + case insertOptionReplace: + return nil, gerror.NewCode(gcode.CodeNotSupported, `Replace operation is not supported by mssql driver`) + + default: + return d.Core.DoInsert(ctx, link, table, list, option) + } +} diff --git a/database/gdb/gdb_driver_mysql.go b/database/gdb/gdb_driver_mysql.go index 509152a58..7caeefdbc 100644 --- a/database/gdb/gdb_driver_mysql.go +++ b/database/gdb/gdb_driver_mysql.go @@ -1,4 +1,4 @@ -// Copyright GoFrame Author(https://github.com/gogf/gf). All Rights Reserved. +// Copyright GoFrame Author(https://goframe.org). All Rights Reserved. // // This Source Code Form is subject to the terms of the MIT License. // If a copy of the MIT was not distributed with this file, @@ -7,12 +7,15 @@ package gdb import ( + "context" "database/sql" "fmt" + "github.com/gogf/gf/errors/gcode" "github.com/gogf/gf/errors/gerror" "github.com/gogf/gf/internal/intlog" "github.com/gogf/gf/text/gregex" "github.com/gogf/gf/text/gstr" + "net/url" _ "github.com/go-sql-driver/mysql" ) @@ -30,23 +33,26 @@ func (d *DriverMysql) New(core *Core, node *ConfigNode) (DB, error) { }, nil } -// Open creates and returns a underlying sql.DB object for mysql. +// Open creates and returns an underlying sql.DB object for mysql. // Note that it converts time.Time argument to local timezone in default. func (d *DriverMysql) Open(config *ConfigNode) (*sql.DB, error) { var source string - if config.LinkInfo != "" { - source = config.LinkInfo + if config.Link != "" { + source = config.Link // Custom changing the schema in runtime. if config.Name != "" { source, _ = gregex.ReplaceString(`/([\w\.\-]+)+`, "/"+config.Name, source) } } else { source = fmt.Sprintf( - "%s:%s@tcp(%s:%s)/%s?charset=%s&multiStatements=true&parseTime=true", + "%s:%s@tcp(%s:%s)/%s?charset=%s", config.User, config.Pass, config.Host, config.Port, config.Name, config.Charset, ) + if config.Timezone != "" { + source = fmt.Sprintf("%s&loc=%s", source, url.QueryEscape(config.Timezone)) + } } - intlog.Printf("Open: %s", source) + intlog.Printf(d.GetCtx(), "Open: %s", source) if db, err := sql.Open("mysql", source); err == nil { return db, nil } else { @@ -54,25 +60,40 @@ func (d *DriverMysql) Open(config *ConfigNode) (*sql.DB, error) { } } +// FilteredLink retrieves and returns filtered `linkInfo` that can be using for +// logging or tracing purpose. +func (d *DriverMysql) FilteredLink() string { + linkInfo := d.GetConfig().Link + if linkInfo == "" { + return "" + } + s, _ := gregex.ReplaceString( + `(.+?):(.+)@tcp(.+)`, + `$1:xxx@tcp$3`, + linkInfo, + ) + return s +} + // GetChars returns the security char for this type of database. func (d *DriverMysql) GetChars() (charLeft string, charRight string) { return "`", "`" } -// HandleSqlBeforeCommit handles the sql before posts it to database. -func (d *DriverMysql) HandleSqlBeforeCommit(link Link, sql string, args []interface{}) (string, []interface{}) { - return sql, args +// DoCommit handles the sql before posts it to database. +func (d *DriverMysql) DoCommit(ctx context.Context, link Link, sql string, args []interface{}) (newSql string, newArgs []interface{}, err error) { + return d.Core.DoCommit(ctx, link, sql, args) } // Tables retrieves and returns the tables of current schema. // It's mainly used in cli tool chain for automatically generating the models. -func (d *DriverMysql) Tables(schema ...string) (tables []string, err error) { +func (d *DriverMysql) Tables(ctx context.Context, schema ...string) (tables []string, err error) { var result Result - link, err := d.DB.GetSlave(schema...) + link, err := d.SlaveLink(schema...) if err != nil { return nil, err } - result, err = d.DB.DoGetAll(link, `SHOW TABLES`) + result, err = d.DoGetAll(ctx, link, `SHOW TABLES`) if err != nil { return } @@ -87,56 +108,57 @@ func (d *DriverMysql) Tables(schema ...string) (tables []string, err error) { // TableFields retrieves and returns the fields information of specified table of current // schema. // +// The parameter `link` is optional, if given nil it automatically retrieves a raw sql connection +// as its link to proceed necessary sql query. +// // Note that it returns a map containing the field name and its corresponding fields. // As a map is unsorted, the TableField struct has a "Index" field marks its sequence in // the fields. // // It's using cache feature to enhance the performance, which is never expired util the // process restarts. -func (d *DriverMysql) TableFields(table string, schema ...string) (fields map[string]*TableField, err error) { +func (d *DriverMysql) TableFields(ctx context.Context, table string, schema ...string) (fields map[string]*TableField, err error) { charL, charR := d.GetChars() table = gstr.Trim(table, charL+charR) if gstr.Contains(table, " ") { - return nil, gerror.New("function TableFields supports only single table operations") + return nil, gerror.NewCode(gcode.CodeInvalidParameter, "function TableFields supports only single table operations") } - checkSchema := d.schema.Val() + useSchema := d.schema.Val() if len(schema) > 0 && schema[0] != "" { - checkSchema = schema[0] + useSchema = schema[0] } - v, _ := internalCache.GetOrSetFunc( - fmt.Sprintf(`mysql_table_fields_%s_%s@group:%s`, table, checkSchema, d.GetGroup()), - func() (interface{}, error) { - var ( - result Result - link *sql.DB - ) - link, err = d.DB.GetSlave(checkSchema) - if err != nil { - return nil, err + tableFieldsCacheKey := fmt.Sprintf( + `mysql_table_fields_%s_%s@group:%s`, + table, useSchema, d.GetGroup(), + ) + v := tableFieldsMap.GetOrSetFuncLock(tableFieldsCacheKey, func() interface{} { + var ( + result Result + link, err = d.SlaveLink(useSchema) + ) + if err != nil { + return nil + } + result, err = d.DoGetAll(ctx, link, fmt.Sprintf(`SHOW FULL COLUMNS FROM %s`, d.QuoteWord(table))) + if err != nil { + return nil + } + fields = make(map[string]*TableField) + for i, m := range result { + fields[m["Field"].String()] = &TableField{ + Index: i, + Name: m["Field"].String(), + Type: m["Type"].String(), + Null: m["Null"].Bool(), + Key: m["Key"].String(), + Default: m["Default"].Val(), + Extra: m["Extra"].String(), + Comment: m["Comment"].String(), } - result, err = d.DB.DoGetAll( - link, - fmt.Sprintf(`SHOW FULL COLUMNS FROM %s`, d.DB.QuoteWord(table)), - ) - if err != nil { - return nil, err - } - fields = make(map[string]*TableField) - for i, m := range result { - fields[m["Field"].String()] = &TableField{ - Index: i, - Name: m["Field"].String(), - Type: m["Type"].String(), - Null: m["Null"].Bool(), - Key: m["Key"].String(), - Default: m["Default"].Val(), - Extra: m["Extra"].String(), - Comment: m["Comment"].String(), - } - } - return fields, nil - }, 0) - if err == nil { + } + return fields + }) + if v != nil { fields = v.(map[string]*TableField) } return diff --git a/database/gdb/gdb_driver_oracle.go b/database/gdb/gdb_driver_oracle.go index 1e0ee3c98..cc308b687 100644 --- a/database/gdb/gdb_driver_oracle.go +++ b/database/gdb/gdb_driver_oracle.go @@ -1,4 +1,4 @@ -// Copyright GoFrame Author(https://github.com/gogf/gf). All Rights Reserved. +// Copyright GoFrame Author(https://goframe.org). All Rights Reserved. // // This Source Code Form is subject to the terms of the MIT License. // If a copy of the MIT was not distributed with this file, @@ -12,17 +12,20 @@ package gdb import ( + "context" "database/sql" "fmt" - "github.com/gogf/gf/errors/gerror" - "github.com/gogf/gf/internal/intlog" - "github.com/gogf/gf/text/gstr" - "github.com/gogf/gf/util/gconv" + "github.com/gogf/gf/errors/gcode" "reflect" "strconv" "strings" + "time" + "github.com/gogf/gf/errors/gerror" + "github.com/gogf/gf/internal/intlog" "github.com/gogf/gf/text/gregex" + "github.com/gogf/gf/text/gstr" + "github.com/gogf/gf/util/gconv" ) // DriverOracle is the driver for oracle database. @@ -30,11 +33,6 @@ type DriverOracle struct { *Core } -const ( - tableAlias1 = "GFORM1" - tableAlias2 = "GFORM2" -) - // New creates and returns a database object for oracle. // It implements the interface of gdb.Driver for extra database driver installation. func (d *DriverOracle) New(core *Core, node *ConfigNode) (DB, error) { @@ -46,12 +44,15 @@ func (d *DriverOracle) New(core *Core, node *ConfigNode) (DB, error) { // Open creates and returns a underlying sql.DB object for oracle. func (d *DriverOracle) Open(config *ConfigNode) (*sql.DB, error) { var source string - if config.LinkInfo != "" { - source = config.LinkInfo + if config.Link != "" { + source = config.Link } else { - source = fmt.Sprintf("%s/%s@%s", config.User, config.Pass, config.Name) + source = fmt.Sprintf( + "%s/%s@%s:%s/%s", + config.User, config.Pass, config.Host, config.Port, config.Name, + ) } - intlog.Printf("Open: %s", source) + intlog.Printf(d.GetCtx(), "Open: %s", source) if db, err := sql.Open("oci8", source); err == nil { return db, nil } else { @@ -59,30 +60,52 @@ func (d *DriverOracle) Open(config *ConfigNode) (*sql.DB, error) { } } +// FilteredLink retrieves and returns filtered `linkInfo` that can be using for +// logging or tracing purpose. +func (d *DriverOracle) FilteredLink() string { + linkInfo := d.GetConfig().Link + if linkInfo == "" { + return "" + } + s, _ := gregex.ReplaceString( + `(.+?)\s*/\s*(.+)\s*@\s*(.+)\s*:\s*(\d+)\s*/\s*(.+)`, + `$1/xxx@$3:$4/$5`, + linkInfo, + ) + return s +} + // GetChars returns the security char for this type of database. func (d *DriverOracle) GetChars() (charLeft string, charRight string) { return "\"", "\"" } -// HandleSqlBeforeCommit deals with the sql string before commits it to underlying sql driver. -func (d *DriverOracle) HandleSqlBeforeCommit(link Link, sql string, args []interface{}) (string, []interface{}) { +// DoCommit deals with the sql string before commits it to underlying sql driver. +func (d *DriverOracle) DoCommit(ctx context.Context, link Link, sql string, args []interface{}) (newSql string, newArgs []interface{}, err error) { + defer func() { + newSql, newArgs, err = d.Core.DoCommit(ctx, link, newSql, newArgs) + }() + var index int // Convert place holder char '?' to string ":vx". - str, _ := gregex.ReplaceStringFunc("\\?", sql, func(s string) string { + newSql, _ = gregex.ReplaceStringFunc("\\?", sql, func(s string) string { index++ return fmt.Sprintf(":v%d", index) }) - str, _ = gregex.ReplaceString("\"", "", str) - // Change time string argument wrapping with TO_DATE function. + newSql, _ = gregex.ReplaceString("\"", "", newSql) + // Handle string datetime argument. for i, v := range args { if reflect.TypeOf(v).Kind() == reflect.String { valueStr := gconv.String(v) if gregex.IsMatchString(`^\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}$`, valueStr) { - args[i] = fmt.Sprintf(`TO_DATE('%s','yyyy-MM-dd HH:MI:SS')`, valueStr) + //args[i] = fmt.Sprintf(`TO_DATE('%s','yyyy-MM-dd HH:MI:SS')`, valueStr) + args[i], _ = time.ParseInLocation("2006-01-02 15:04:05", valueStr, time.Local) } } } - return d.parseSql(str), args + newSql = d.parseSql(newSql) + newArgs = args + return } // parseSql does some replacement of the sql before commits it to underlying driver, @@ -142,10 +165,10 @@ func (d *DriverOracle) parseSql(sql string) string { // Tables retrieves and returns the tables of current schema. // It's mainly used in cli tool chain for automatically generating the models. -// Note that it ignores the parameter <schema> in oracle database, as it is not necessary. -func (d *DriverOracle) Tables(schema ...string) (tables []string, err error) { +// Note that it ignores the parameter `schema` in oracle database, as it is not necessary. +func (d *DriverOracle) Tables(ctx context.Context, schema ...string) (tables []string, err error) { var result Result - result, err = d.DB.DoGetAll(nil, "SELECT TABLE_NAME FROM USER_TABLES ORDER BY TABLE_NAME") + result, err = d.DoGetAll(ctx, nil, "SELECT TABLE_NAME FROM USER_TABLES ORDER BY TABLE_NAME") if err != nil { return } @@ -158,21 +181,27 @@ func (d *DriverOracle) Tables(schema ...string) (tables []string, err error) { } // TableFields retrieves and returns the fields information of specified table of current schema. -func (d *DriverOracle) TableFields(table string, schema ...string) (fields map[string]*TableField, err error) { +// +// Also see DriverMysql.TableFields. +func (d *DriverOracle) TableFields(ctx context.Context, table string, schema ...string) (fields map[string]*TableField, err error) { charL, charR := d.GetChars() table = gstr.Trim(table, charL+charR) if gstr.Contains(table, " ") { - return nil, gerror.New("function TableFields supports only single table operations") + return nil, gerror.NewCode(gcode.CodeInvalidParameter, "function TableFields supports only single table operations") } - checkSchema := d.DB.GetSchema() + useSchema := d.db.GetSchema() if len(schema) > 0 && schema[0] != "" { - checkSchema = schema[0] + useSchema = schema[0] } - v, _ := internalCache.GetOrSetFunc( - fmt.Sprintf(`oracle_table_fields_%s_%s@group:%s`, table, checkSchema, d.GetGroup()), - func() (interface{}, error) { - result := (Result)(nil) - structureSql := fmt.Sprintf(` + tableFieldsCacheKey := fmt.Sprintf( + `oracle_table_fields_%s_%s@group:%s`, + table, useSchema, d.GetGroup(), + ) + v := tableFieldsMap.GetOrSetFuncLock(tableFieldsCacheKey, func() interface{} { + var ( + result Result + link, err = d.SlaveLink(useSchema) + structureSql = fmt.Sprintf(` SELECT COLUMN_NAME AS FIELD, CASE DATA_TYPE @@ -182,257 +211,85 @@ SELECT 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, err + ) + if err != nil { + return nil + } + structureSql, _ = gregex.ReplaceString(`[\n\r\s]+`, " ", gstr.Trim(structureSql)) + result, err = d.DoGetAll(ctx, link, structureSql) + if err != nil { + return nil + } + fields = make(map[string]*TableField) + for i, m := range result { + fields[strings.ToLower(m["FIELD"].String())] = &TableField{ + Index: i, + Name: strings.ToLower(m["FIELD"].String()), + Type: strings.ToLower(m["TYPE"].String()), } - fields = make(map[string]*TableField) - for i, m := range result { - fields[strings.ToLower(m["FIELD"].String())] = &TableField{ - Index: i, - Name: strings.ToLower(m["FIELD"].String()), - Type: strings.ToLower(m["TYPE"].String()), - } - } - return fields, nil - }, 0) - if err == nil { + } + return fields + }) + if v != nil { fields = v.(map[string]*TableField) } return } -func (d *DriverOracle) getTableUniqueIndex(table string) (fields map[string]map[string]string, err error) { - table = strings.ToUpper(table) - v, _ := internalCache.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, 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) - } - return -} +// 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"}) +// Data(g.Slice{g.Map{"uid": 10000, "name":"john"}, g.Map{"uid": 20000, "name":"smith"}) +// +// The parameter `option` values are as follows: +// 0: insert: just insert, if there's unique/primary key in the data, it returns error; +// 1: replace: if there's unique/primary key in the data, it deletes it from table and inserts a new one; +// 2: save: if there's unique/primary key in the data, it updates it or else inserts a new one; +// 3: ignore: if there's unique/primary key in the data, it ignores the inserting; +func (d *DriverOracle) DoInsert(ctx context.Context, link Link, table string, list List, option DoInsertOption) (result sql.Result, err error) { + switch option.InsertOption { + case insertOptionSave: + return nil, gerror.NewCode(gcode.CodeNotSupported, `Save operation is not supported by mssql driver`) -func (d *DriverOracle) DoInsert(link Link, table string, data interface{}, option int, batch ...int) (result sql.Result, err error) { - var fields []string - var values []string - var params []interface{} - var dataMap Map - rv := reflect.ValueOf(data) - kind := rv.Kind() - if kind == reflect.Ptr { - rv = rv.Elem() - kind = rv.Kind() - } - switch kind { - case reflect.Slice: - fallthrough - case reflect.Array: - return d.DB.DoBatchInsert(link, table, data, option, batch...) - case reflect.Map: - fallthrough - case reflect.Struct: - dataMap = ConvertDataForTableRecord(data) - default: - return result, gerror.New(fmt.Sprint("unsupported data type:", kind)) + case insertOptionReplace: + return nil, gerror.NewCode(gcode.CodeNotSupported, `Replace operation is not supported by mssql driver`) } - indexs := make([]string, 0) - indexMap := make(map[string]string) - indexExists := false - if option != insertOptionDefault { - index, err := d.getTableUniqueIndex(table) - if err != nil { - return nil, err - } - - if len(index) > 0 { - for _, v := range index { - for k, _ := range v { - indexs = append(indexs, k) - } - indexMap = v - indexExists = true - break - } - } - - } - - subSqlStr := make([]string, 0) - onStr := make([]string, 0) - updateStr := make([]string, 0) - - charL, charR := d.DB.GetChars() - for k, v := range dataMap { - k = strings.ToUpper(k) - - // 操作类型为REPLACE/SAVE时且存在唯一索引才使用merge,否则使用insert - if (option == insertOptionReplace || option == insertOptionSave) && indexExists { - fields = append(fields, tableAlias1+"."+charL+k+charR) - values = append(values, tableAlias2+"."+charL+k+charR) - params = append(params, v) - - subSqlStr = append(subSqlStr, fmt.Sprintf("%s?%s %s", charL, charR, k)) - - //merge中的on子句中由唯一索引组成,update子句中不含唯一索引 - if _, ok := indexMap[k]; ok { - onStr = append(onStr, fmt.Sprintf("%s.%s = %s.%s ", tableAlias1, k, tableAlias2, k)) - } else { - updateStr = append(updateStr, fmt.Sprintf("%s.%s = %s.%s ", tableAlias1, k, tableAlias2, k)) - } - } else { - fields = append(fields, charL+k+charR) - values = append(values, "?") - params = append(params, v) - } - } - - if link == nil { - if link, err = d.DB.Master(); err != nil { - return nil, err - } - } - - if indexExists && option != insertOptionDefault { - switch option { - case insertOptionReplace: - fallthrough - case insertOptionSave: - tmp := fmt.Sprintf( - "MERGE INTO %s %s USING(SELECT %s FROM DUAL) %s ON(%s) WHEN MATCHED THEN UPDATE SET %s WHEN NOT MATCHED THEN INSERT (%s) VALUES(%s)", - table, tableAlias1, strings.Join(subSqlStr, ","), tableAlias2, - strings.Join(onStr, "AND"), strings.Join(updateStr, ","), strings.Join(fields, ","), strings.Join(values, ","), - ) - return d.DB.DoExec(link, tmp, params...) - case insertOptionIgnore: - return d.DB.DoExec(link, - fmt.Sprintf( - "INSERT /*+ IGNORE_ROW_ON_DUPKEY_INDEX(%s(%s)) */ INTO %s(%s) VALUES(%s)", - table, strings.Join(indexs, ","), table, strings.Join(fields, ","), strings.Join(values, ","), - ), - params...) - } - } - - return d.DB.DoExec( - link, - fmt.Sprintf( - "INSERT INTO %s(%s) VALUES(%s)", - table, strings.Join(fields, ","), strings.Join(values, ","), - ), - params...) -} - -func (d *DriverOracle) DoBatchInsert(link Link, table string, list interface{}, option int, batch ...int) (result sql.Result, err error) { - var keys []string - var values []string - var params []interface{} - listMap := (List)(nil) - switch v := list.(type) { - case Result: - listMap = v.List() - case Record: - listMap = List{v.Map()} - case List: - listMap = v - case Map: - listMap = List{v} - default: - rv := reflect.ValueOf(list) - kind := rv.Kind() - if kind == reflect.Ptr { - rv = rv.Elem() - kind = rv.Kind() - } - switch kind { - // 如果是slice,那么转换为List类型 - case reflect.Slice: - fallthrough - case reflect.Array: - listMap = make(List, rv.Len()) - for i := 0; i < rv.Len(); i++ { - listMap[i] = ConvertDataForTableRecord(rv.Index(i).Interface()) - } - case reflect.Map: - fallthrough - case reflect.Struct: - listMap = List{Map(ConvertDataForTableRecord(list))} - default: - return result, gerror.New(fmt.Sprint("unsupported list type:", kind)) - } - } - // 判断长度 - if len(listMap) < 1 { - return result, gerror.New("empty data list") - } - if link == nil { - if link, err = d.DB.Master(); err != nil { - return - } - } - // 首先获取字段名称及记录长度 - holders := []string(nil) - for k, _ := range listMap[0] { + var ( + keys []string + values []string + params []interface{} + ) + // Retrieve the table fields and length. + var ( + listLength = len(list) + valueHolder = make([]string, 0) + ) + for k, _ := range list[0] { keys = append(keys, k) - holders = append(holders, "?") + valueHolder = append(valueHolder, "?") } - batchResult := new(SqlResult) - charL, charR := d.DB.GetChars() - keyStr := charL + strings.Join(keys, charL+","+charR) + charR - valueHolderStr := strings.Join(holders, ",") - - // 当操作类型非insert时调用单笔的insert功能 - if option != insertOptionDefault { - for _, v := range listMap { - r, err := d.DB.DoInsert(link, table, v, option, 1) - if err != nil { - return r, err - } - - if n, err := r.RowsAffected(); err != nil { - return r, err - } else { - batchResult.result = r - batchResult.affected += n - } - } - return batchResult, nil - } - - // 构造批量写入数据格式(注意map的遍历是无序的) - batchNum := defaultBatchNumber - if len(batch) > 0 { - batchNum = batch[0] - } - - intoStr := make([]string, 0) //组装into语句 - for i := 0; i < len(listMap); i++ { + var ( + batchResult = new(SqlResult) + charL, charR = d.db.GetChars() + keyStr = charL + strings.Join(keys, charL+","+charR) + charR + valueHolderStr = strings.Join(valueHolder, ",") + ) + // Format "INSERT...INTO..." statement. + intoStr := make([]string, 0) + for i := 0; i < len(list); i++ { for _, k := range keys { - params = append(params, listMap[i][k]) + params = append(params, list[i][k]) } values = append(values, valueHolderStr) - intoStr = append(intoStr, fmt.Sprintf(" INTO %s(%s) VALUES(%s) ", table, keyStr, valueHolderStr)) - if len(intoStr) == batchNum { - r, err := d.DB.DoExec(link, fmt.Sprintf("INSERT ALL %s SELECT * FROM DUAL", strings.Join(intoStr, " ")), params...) + intoStr = append(intoStr, fmt.Sprintf("INTO %s(%s) VALUES(%s)", table, keyStr, valueHolderStr)) + if len(intoStr) == option.BatchCount || (i == listLength-1 && len(valueHolder) > 0) { + r, err := d.DoExec(ctx, link, fmt.Sprintf( + "INSERT ALL %s SELECT * FROM DUAL", + strings.Join(intoStr, " "), + ), params...) if err != nil { return r, err } @@ -446,18 +303,5 @@ func (d *DriverOracle) DoBatchInsert(link Link, table string, list interface{}, intoStr = intoStr[:0] } } - // 处理最后不构成指定批量的数据 - if len(intoStr) > 0 { - r, err := d.DB.DoExec(link, fmt.Sprintf("INSERT ALL %s SELECT * FROM DUAL", strings.Join(intoStr, " ")), params...) - if err != nil { - return r, err - } - if n, err := r.RowsAffected(); err != nil { - return r, err - } else { - batchResult.result = r - batchResult.affected += n - } - } return batchResult, nil } diff --git a/database/gdb/gdb_driver_pgsql.go b/database/gdb/gdb_driver_pgsql.go index 828439e4b..c0dbab9c9 100644 --- a/database/gdb/gdb_driver_pgsql.go +++ b/database/gdb/gdb_driver_pgsql.go @@ -1,4 +1,4 @@ -// Copyright GoFrame Author(https://github.com/gogf/gf). All Rights Reserved. +// Copyright GoFrame Author(https://goframe.org). All Rights Reserved. // // This Source Code Form is subject to the terms of the MIT License. // If a copy of the MIT was not distributed with this file, @@ -12,12 +12,15 @@ package gdb import ( + "context" "database/sql" "fmt" + "github.com/gogf/gf/errors/gcode" + "strings" + "github.com/gogf/gf/errors/gerror" "github.com/gogf/gf/internal/intlog" "github.com/gogf/gf/text/gstr" - "strings" "github.com/gogf/gf/text/gregex" ) @@ -38,15 +41,18 @@ func (d *DriverPgsql) New(core *Core, node *ConfigNode) (DB, error) { // Open creates and returns a underlying sql.DB object for pgsql. func (d *DriverPgsql) Open(config *ConfigNode) (*sql.DB, error) { var source string - if config.LinkInfo != "" { - source = config.LinkInfo + if config.Link != "" { + source = config.Link } else { source = fmt.Sprintf( "user=%s password=%s host=%s port=%s dbname=%s sslmode=disable", config.User, config.Pass, config.Host, config.Port, config.Name, ) + if config.Timezone != "" { + source = fmt.Sprintf("%s timezone=%s", source, config.Timezone) + } } - intlog.Printf("Open: %s", source) + intlog.Printf(d.GetCtx(), "Open: %s", source) if db, err := sql.Open("postgres", source); err == nil { return db, nil } else { @@ -54,28 +60,47 @@ func (d *DriverPgsql) Open(config *ConfigNode) (*sql.DB, error) { } } +// FilteredLink retrieves and returns filtered `linkInfo` that can be using for +// logging or tracing purpose. +func (d *DriverPgsql) FilteredLink() string { + linkInfo := d.GetConfig().Link + if linkInfo == "" { + return "" + } + s, _ := gregex.ReplaceString( + `(.+?)\s*password=(.+)\s*host=(.+)`, + `$1 password=xxx host=$3`, + linkInfo, + ) + return s +} + // GetChars returns the security char for this type of database. func (d *DriverPgsql) GetChars() (charLeft string, charRight string) { return "\"", "\"" } -// HandleSqlBeforeCommit deals with the sql string before commits it to underlying sql driver. -func (d *DriverPgsql) HandleSqlBeforeCommit(link Link, sql string, args []interface{}) (string, []interface{}) { +// DoCommit deals with the sql string before commits it to underlying sql driver. +func (d *DriverPgsql) DoCommit(ctx context.Context, link Link, sql string, args []interface{}) (newSql string, newArgs []interface{}, err error) { + defer func() { + newSql, newArgs, err = d.Core.DoCommit(ctx, link, newSql, newArgs) + }() + var index int // Convert place holder char '?' to string "$x". sql, _ = gregex.ReplaceStringFunc("\\?", sql, func(s string) string { index++ return fmt.Sprintf("$%d", index) }) - sql, _ = gregex.ReplaceString(` LIMIT (\d+),\s*(\d+)`, ` LIMIT $2 OFFSET $1`, sql) - return sql, args + newSql, _ = gregex.ReplaceString(` LIMIT (\d+),\s*(\d+)`, ` LIMIT $2 OFFSET $1`, sql) + return newSql, args, nil } // Tables retrieves and returns the tables of current schema. // It's mainly used in cli tool chain for automatically generating the models. -func (d *DriverPgsql) Tables(schema ...string) (tables []string, err error) { +func (d *DriverPgsql) Tables(ctx context.Context, schema ...string) (tables []string, err error) { var result Result - link, err := d.DB.GetSlave(schema...) + link, err := d.SlaveLink(schema...) if err != nil { return nil, err } @@ -83,7 +108,7 @@ func (d *DriverPgsql) Tables(schema ...string) (tables []string, err error) { if len(schema) > 0 && schema[0] != "" { query = fmt.Sprintf("SELECT TABLENAME FROM PG_TABLES WHERE SCHEMANAME = '%s' ORDER BY TABLENAME", schema[0]) } - result, err = d.DB.DoGetAll(link, query) + result, err = d.DoGetAll(ctx, link, query) if err != nil { return } @@ -96,53 +121,82 @@ func (d *DriverPgsql) Tables(schema ...string) (tables []string, err error) { } // TableFields retrieves and returns the fields information of specified table of current schema. -func (d *DriverPgsql) TableFields(table string, schema ...string) (fields map[string]*TableField, err error) { +// +// Also see DriverMysql.TableFields. +func (d *DriverPgsql) TableFields(ctx context.Context, table string, schema ...string) (fields map[string]*TableField, err error) { charL, charR := d.GetChars() table = gstr.Trim(table, charL+charR) if gstr.Contains(table, " ") { - return nil, gerror.New("function TableFields supports only single table operations") + return nil, gerror.NewCode(gcode.CodeInvalidParameter, "function TableFields supports only single table operations") } table, _ = gregex.ReplaceString("\"", "", table) - checkSchema := d.DB.GetSchema() + useSchema := d.db.GetSchema() if len(schema) > 0 && schema[0] != "" { - checkSchema = schema[0] + useSchema = schema[0] } - v, _ := internalCache.GetOrSetFunc( - fmt.Sprintf(`pgsql_table_fields_%s_%s@group:%s`, table, checkSchema, d.GetGroup()), - func() (interface{}, error) { - var ( - result Result - link *sql.DB - ) - link, err = d.DB.GetSlave(checkSchema) - if err != nil { - return nil, err - } - 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 + tableFieldsCacheKey := fmt.Sprintf( + `pgsql_table_fields_%s_%s@group:%s`, + table, useSchema, d.GetGroup(), + ) + v := tableFieldsMap.GetOrSetFuncLock(tableFieldsCacheKey, func() interface{} { + var ( + result Result + link, err = d.SlaveLink(useSchema) + structureSql = fmt.Sprintf(` +SELECT a.attname AS field, t.typname AS type,a.attnotnull as null, + (case when d.contype is not null then 'pri' else '' end) as key + ,ic.column_default as default_value,b.description as comment + ,coalesce(character_maximum_length, numeric_precision, -1) as length + ,numeric_scale as scale +FROM pg_attribute a + left join pg_class c on a.attrelid = c.oid + left join pg_constraint d on d.conrelid = c.oid and a.attnum = d.conkey[1] + left join pg_description b ON a.attrelid=b.objoid AND a.attnum = b.objsubid + left join pg_type t ON a.atttypid = t.oid + left join information_schema.columns ic on ic.column_name = a.attname and ic.table_name = c.relname +WHERE c.relname = '%s' and a.attnum > 0 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, err + ) + if err != nil { + return nil + } + structureSql, _ = gregex.ReplaceString(`[\n\r\s]+`, " ", gstr.Trim(structureSql)) + result, err = d.DoGetAll(ctx, link, structureSql) + if err != nil { + return nil + } + fields = make(map[string]*TableField) + for i, m := range result { + fields[m["field"].String()] = &TableField{ + Index: i, + Name: m["field"].String(), + Type: m["type"].String(), + Null: m["null"].Bool(), + Key: m["key"].String(), + Default: m["default_value"].Val(), + Comment: m["comment"].String(), } - - fields = make(map[string]*TableField) - for i, m := range result { - fields[m["field"].String()] = &TableField{ - Index: i, - Name: m["field"].String(), - Type: m["type"].String(), - } - } - return fields, nil - }, 0) - if err == nil { + } + return fields + }) + if v != nil { fields = v.(map[string]*TableField) } return } + +// DoInsert is not supported in pgsql. +func (d *DriverPgsql) DoInsert(ctx context.Context, link Link, table string, list List, option DoInsertOption) (result sql.Result, err error) { + switch option.InsertOption { + case insertOptionSave: + return nil, gerror.NewCode(gcode.CodeNotSupported, `Save operation is not supported by pgsql driver`) + + case insertOptionReplace: + return nil, gerror.NewCode(gcode.CodeNotSupported, `Replace operation is not supported by pgsql driver`) + + default: + return d.Core.DoInsert(ctx, link, table, list, option) + } +} diff --git a/database/gdb/gdb_driver_sqlite.go b/database/gdb/gdb_driver_sqlite.go index b70808b32..405a391a8 100644 --- a/database/gdb/gdb_driver_sqlite.go +++ b/database/gdb/gdb_driver_sqlite.go @@ -1,4 +1,4 @@ -// Copyright GoFrame Author(https://github.com/gogf/gf). All Rights Reserved. +// Copyright GoFrame Author(https://goframe.org). All Rights Reserved. // // This Source Code Form is subject to the terms of the MIT License. // If a copy of the MIT was not distributed with this file, @@ -11,13 +11,16 @@ package gdb import ( + "context" "database/sql" "fmt" + "github.com/gogf/gf/errors/gcode" + "strings" + "github.com/gogf/gf/errors/gerror" "github.com/gogf/gf/internal/intlog" "github.com/gogf/gf/os/gfile" "github.com/gogf/gf/text/gstr" - "strings" ) // DriverSqlite is the driver for sqlite database. @@ -36,8 +39,8 @@ func (d *DriverSqlite) New(core *Core, node *ConfigNode) (DB, error) { // Open creates and returns a underlying sql.DB object for sqlite. func (d *DriverSqlite) Open(config *ConfigNode) (*sql.DB, error) { var source string - if config.LinkInfo != "" { - source = config.LinkInfo + if config.Link != "" { + source = config.Link } else { source = config.Name } @@ -45,7 +48,7 @@ func (d *DriverSqlite) Open(config *ConfigNode) (*sql.DB, error) { if absolutePath, _ := gfile.Search(source); absolutePath != "" { source = absolutePath } - intlog.Printf("Open: %s", source) + intlog.Printf(d.GetCtx(), "Open: %s", source) if db, err := sql.Open("sqlite3", source); err == nil { return db, nil } else { @@ -53,28 +56,32 @@ func (d *DriverSqlite) Open(config *ConfigNode) (*sql.DB, error) { } } +// FilteredLink retrieves and returns filtered `linkInfo` that can be using for +// logging or tracing purpose. +func (d *DriverSqlite) FilteredLink() string { + return d.GetConfig().Link +} + // GetChars returns the security char for this type of database. func (d *DriverSqlite) GetChars() (charLeft string, charRight string) { return "`", "`" } -// HandleSqlBeforeCommit deals with the sql string before commits it to underlying sql driver. -// TODO 需要增加对Save方法的支持,可使用正则来实现替换, -// TODO 将ON DUPLICATE KEY UPDATE触发器修改为两条SQL语句(INSERT OR IGNORE & UPDATE) -func (d *DriverSqlite) HandleSqlBeforeCommit(link Link, sql string, args []interface{}) (string, []interface{}) { - return sql, args +// DoCommit deals with the sql string before commits it to underlying sql driver. +func (d *DriverSqlite) DoCommit(ctx context.Context, link Link, sql string, args []interface{}) (newSql string, newArgs []interface{}, err error) { + return d.Core.DoCommit(ctx, link, sql, args) } // Tables retrieves and returns the tables of current schema. // It's mainly used in cli tool chain for automatically generating the models. -func (d *DriverSqlite) Tables(schema ...string) (tables []string, err error) { +func (d *DriverSqlite) Tables(ctx context.Context, schema ...string) (tables []string, err error) { var result Result - link, err := d.DB.GetSlave(schema...) + link, err := d.SlaveLink(schema...) if err != nil { return nil, err } - result, err = d.DB.DoGetAll(link, `SELECT NAME FROM SQLITE_MASTER WHERE TYPE='table' ORDER BY NAME`) + result, err = d.DoGetAll(ctx, link, `SELECT NAME FROM SQLITE_MASTER WHERE TYPE='table' ORDER BY NAME`) if err != nil { return } @@ -87,43 +94,60 @@ func (d *DriverSqlite) Tables(schema ...string) (tables []string, err error) { } // TableFields retrieves and returns the fields information of specified table of current schema. -func (d *DriverSqlite) TableFields(table string, schema ...string) (fields map[string]*TableField, err error) { +// +// Also see DriverMysql.TableFields. +func (d *DriverSqlite) TableFields(ctx context.Context, table string, schema ...string) (fields map[string]*TableField, err error) { charL, charR := d.GetChars() table = gstr.Trim(table, charL+charR) if gstr.Contains(table, " ") { - return nil, gerror.New("function TableFields supports only single table operations") + return nil, gerror.NewCode(gcode.CodeInvalidParameter, "function TableFields supports only single table operations") } - checkSchema := d.DB.GetSchema() + useSchema := d.db.GetSchema() if len(schema) > 0 && schema[0] != "" { - checkSchema = schema[0] + useSchema = schema[0] } - v, _ := internalCache.GetOrSetFunc( - fmt.Sprintf(`sqlite_table_fields_%s_%s@group:%s`, table, checkSchema, d.GetGroup()), - func() (interface{}, error) { - var ( - result Result - link *sql.DB - ) - link, err = d.DB.GetSlave(checkSchema) - if err != nil { - return nil, err + tableFieldsCacheKey := fmt.Sprintf( + `sqlite_table_fields_%s_%s@group:%s`, + table, useSchema, d.GetGroup(), + ) + v := tableFieldsMap.GetOrSetFuncLock(tableFieldsCacheKey, func() interface{} { + var ( + result Result + link, err = d.SlaveLink(useSchema) + ) + if err != nil { + return nil + } + result, err = d.DoGetAll(ctx, link, fmt.Sprintf(`PRAGMA TABLE_INFO(%s)`, table)) + if err != nil { + return nil + } + fields = make(map[string]*TableField) + for i, m := range result { + fields[strings.ToLower(m["name"].String())] = &TableField{ + Index: i, + Name: strings.ToLower(m["name"].String()), + Type: strings.ToLower(m["type"].String()), } - result, err = d.DB.DoGetAll(link, fmt.Sprintf(`PRAGMA TABLE_INFO(%s)`, table)) - if err != nil { - return nil, err - } - fields = make(map[string]*TableField) - for i, m := range result { - fields[strings.ToLower(m["name"].String())] = &TableField{ - Index: i, - Name: strings.ToLower(m["name"].String()), - Type: strings.ToLower(m["type"].String()), - } - } - return fields, nil - }, 0) - if err == nil { + } + return fields + }) + if v != nil { fields = v.(map[string]*TableField) } return } + +// DoInsert is not supported in sqlite. +func (d *DriverSqlite) DoInsert(ctx context.Context, link Link, table string, list List, option DoInsertOption) (result sql.Result, err error) { + switch option.InsertOption { + case insertOptionSave: + return nil, gerror.NewCode(gcode.CodeNotSupported, `Save operation is not supported by sqlite driver`) + + case insertOptionReplace: + return nil, gerror.NewCode(gcode.CodeNotSupported, `Replace operation is not supported by sqlite driver`) + + default: + return d.Core.DoInsert(ctx, link, table, list, option) + } +} diff --git a/database/gdb/gdb_func.go b/database/gdb/gdb_func.go index 78c9ac47b..87c6e8763 100644 --- a/database/gdb/gdb_func.go +++ b/database/gdb/gdb_func.go @@ -1,4 +1,4 @@ -// Copyright GoFrame Author(https://github.com/gogf/gf). All Rights Reserved. +// Copyright GoFrame Author(https://goframe.org). All Rights Reserved. // // This Source Code Form is subject to the terms of the MIT License. // If a copy of the MIT was not distributed with this file, @@ -8,17 +8,21 @@ package gdb import ( "bytes" + "database/sql" "fmt" + "github.com/gogf/gf/errors/gcode" + "reflect" + "regexp" + "strings" + "time" + "github.com/gogf/gf/errors/gerror" "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/gmeta" "github.com/gogf/gf/util/gutil" - "reflect" - "regexp" - "strings" - "time" "github.com/gogf/gf/internal/structs" @@ -47,10 +51,19 @@ type apiMapStrAny interface { MapStrAny() map[string]interface{} } +// apiTableName is the interface for retrieving table name fro struct. +type apiTableName interface { + TableName() string +} + const ( - ORM_TAG_FOR_STRUCT = "orm" - ORM_TAG_FOR_UNIQUE = "unique" - ORM_TAG_FOR_PRIMARY = "primary" + OrmTagForStruct = "orm" + OrmTagForUnique = "unique" + OrmTagForPrimary = "primary" + OrmTagForTable = "table" + OrmTagForWith = "with" + OrmTagForWithWhere = "where" + OrmTagForWithOrder = "order" ) var ( @@ -58,33 +71,91 @@ var ( quoteWordReg = regexp.MustCompile(`^[a-zA-Z0-9\-_]+$`) // Priority tags for struct converting for orm field mapping. - structTagPriority = append([]string{ORM_TAG_FOR_STRUCT}, gconv.StructTagPriority...) + structTagPriority = append([]string{OrmTagForStruct}, gconv.StructTagPriority...) ) -// ListItemValues retrieves and returns the elements of all item struct/map with key <key>. -// Note that the parameter <list> should be type of slice which contains elements of map or struct, +// guessPrimaryTableName parses and returns the primary table name. +func (m *Model) guessPrimaryTableName(tableStr string) string { + if tableStr == "" { + return "" + } + var ( + guessedTableName = "" + array1 = gstr.SplitAndTrim(tableStr, ",") + array2 = gstr.SplitAndTrim(array1[0], " ") + array3 = gstr.SplitAndTrim(array2[0], ".") + ) + if len(array3) >= 2 { + guessedTableName = array3[1] + } else { + guessedTableName = array3[0] + } + charL, charR := m.db.GetChars() + if charL != "" || charR != "" { + guessedTableName = gstr.Trim(guessedTableName, charL+charR) + } + if !gregex.IsMatchString(regularFieldNameRegPattern, guessedTableName) { + return "" + } + return guessedTableName +} + +// getTableNameFromOrmTag retrieves and returns the table name from struct object. +func getTableNameFromOrmTag(object interface{}) string { + var tableName string + // Use the interface value. + if r, ok := object.(apiTableName); ok { + tableName = r.TableName() + } + // User meta data tag "orm". + if tableName == "" { + if ormTag := gmeta.Get(object, OrmTagForStruct); !ormTag.IsEmpty() { + match, _ := gregex.MatchString( + fmt.Sprintf(`%s\s*:\s*([^,]+)`, OrmTagForTable), + ormTag.String(), + ) + if len(match) > 1 { + tableName = match[1] + } + } + } + // Use the struct name of snake case. + if tableName == "" { + if t, err := structs.StructType(object); err != nil { + panic(err) + } else { + tableName = gstr.CaseSnakeFirstUpper( + gstr.StrEx(t.String(), "."), + ) + } + } + return tableName +} + +// ListItemValues retrieves and returns the elements of all item struct/map with key `key`. +// Note that the parameter `list` should be type of slice which contains elements of map or struct, // or else it returns an empty slice. // -// The parameter <list> supports types like: +// The parameter `list` supports types like: // []map[string]interface{} // []map[string]sub-map // []struct // []struct:sub-struct -// Note that the sub-map/sub-struct makes sense only if the optional parameter <subKey> is given. +// Note that the sub-map/sub-struct makes sense only if the optional parameter `subKey` is given. // See gutil.ListItemValues. func ListItemValues(list interface{}, key interface{}, subKey ...interface{}) (values []interface{}) { return gutil.ListItemValues(list, key, subKey...) } -// ListItemValuesUnique retrieves and returns the unique elements of all struct/map with key <key>. -// Note that the parameter <list> should be type of slice which contains elements of map or struct, +// ListItemValuesUnique retrieves and returns the unique elements of all struct/map with key `key`. +// Note that the parameter `list` should be type of slice which contains elements of map or struct, // or else it returns an empty slice. // See gutil.ListItemValuesUnique. func ListItemValuesUnique(list interface{}, key string, subKey ...interface{}) []interface{} { return gutil.ListItemValuesUnique(list, key, subKey...) } -// GetInsertOperationByOption returns proper insert option with given parameter <option>. +// GetInsertOperationByOption returns proper insert option with given parameter `option`. func GetInsertOperationByOption(option int) string { var operator string switch option { @@ -101,8 +172,8 @@ func GetInsertOperationByOption(option int) string { // 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. +// The parameter `value` should be type of *map/map/*struct/struct. +// It supports embedded struct definition for struct. func ConvertDataForTableRecord(value interface{}) map[string]interface{} { var ( rvValue reflect.Value @@ -123,12 +194,32 @@ func ConvertDataForTableRecord(value interface{}) map[string]interface{} { // 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: + switch r := v.(type) { + // If the time is zero, it then updates it to nil, + // which will insert/update the value to database as "null". + case time.Time: + if r.IsZero() { + data[k] = nil + } + + case gtime.Time: + if r.IsZero() { + data[k] = nil + } + + case *gtime.Time: + if r.IsZero() { + data[k] = nil + } + + case *time.Time: continue + case Counter, *Counter: continue + default: // Use string conversion in default. if s, ok := v.(apiString); ok { @@ -143,102 +234,26 @@ func ConvertDataForTableRecord(value interface{}) map[string]interface{} { 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. +// DataToMapDeep converts `value` to map type recursively(if attribute struct is embedded). +// The parameter `value` should be type of *map/map/*struct/struct. +// It supports embedded 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 - rvKind reflect.Kind - rtField reflect.StructField - ) - if v, ok := value.(reflect.Value); ok { - rvValue = v - } else { - rvValue = reflect.ValueOf(value) - } - rvKind = rvValue.Kind() - if rvKind == reflect.Ptr { - rvValue = rvValue.Elem() - rvKind = rvValue.Kind() - } - // If given <value> is not a struct, it uses gconv.Map for converting. - if rvKind != reflect.Struct { - return gconv.Map(value, structTagPriority...) - } - // Struct handling. - var ( - fieldTag reflect.StructTag - rvType = rvValue.Type() - name = "" - data = make(map[string]interface{}) - ) - for i := 0; i < rvValue.NumField(); i++ { - rtField = rvType.Field(i) - rvField = rvValue.Field(i) - fieldName := rtField.Name - if !utils.IsLetterUpper(fieldName[0]) { - continue - } - // Struct attribute inherit - if rtField.Anonymous { - for k, v := range DataToMapDeep(rvField) { - data[k] = v - } - continue - } - // Other attributes. - name = "" - fieldTag = rtField.Tag - for _, tag := range structTagPriority { - if s := fieldTag.Get(tag); s != "" { - name = s - break - } - } - if name == "" { - name = fieldName - } else { - // The "orm" tag supports json tag feature: -, omitempty - // The "orm" tag would be like: "id,priority", so it should use splitting handling. - name = gstr.Trim(name) - if name == "-" { - continue - } - array := gstr.SplitAndTrim(name, ",") - if len(array) > 1 { - switch array[1] { - case "omitempty": - if empty.IsEmpty(rvField.Interface()) { - continue - } else { - name = array[0] - } - default: - name = array[0] - } - } - } - - // The underlying driver supports time.Time/*time.Time types. - fieldValue := rvField.Interface() - switch fieldValue.(type) { + m := gconv.Map(value, structTagPriority...) + for k, v := range m { + switch v.(type) { case time.Time, *time.Time, gtime.Time, *gtime.Time: - data[name] = fieldValue + m[k] = v + default: // Use string conversion in default. - if s, ok := fieldValue.(apiString); ok { - data[name] = s.String() + if s, ok := v.(apiString); ok { + m[k] = s.String() } else { - data[name] = fieldValue + m[k] = v } } } - return data + return m } // doHandleTableName adds prefix string and quote chars for the table. It handles table string like: @@ -275,8 +290,8 @@ func doHandleTableName(table, prefix, charLeft, charRight string) string { return gstr.Join(array1, ",") } -// doQuoteWord checks given string <s> a word, if true quotes it with <charLeft> and <charRight> -// and returns the quoted string; or else returns <s> without any change. +// doQuoteWord checks given string `s` a word, if true quotes it with `charLeft` and `charRight` +// and returns the quoted string; or else returns `s` without any change. func doQuoteWord(s, charLeft, charRight string) string { if quoteWordReg.MatchString(s) && !gstr.ContainsAny(s, charLeft+charRight) { return charLeft + s + charRight @@ -284,14 +299,15 @@ func doQuoteWord(s, charLeft, charRight string) string { return s } -// doQuoteString quotes string with quote chars. It handles strings like: -// "user", -// "user u", -// "user,user_detail", -// "user u, user_detail ut", -// "user.user u, user.user_detail ut", -// "u.id, u.name, u.age", -// "u.id asc". +// doQuoteString quotes string with quote chars. +// For example, if quote char is '`': +// "user" => "`user`" +// "user u" => "`user` u" +// "user,user_detail" => "`user`,`user_detail`" +// "user u, user_detail ut" => "`user` u,`user_detail` ut" +// "user.user u, user.user_detail ut" => "`user`.`user` u,`user`.`user_detail` ut" +// "u.id, u.name, u.age" => "`u`.`id`,`u`.`name`,`u`.`age`" +// "u.id asc" => "`u`.`id` asc" func doQuoteString(s, charLeft, charRight string) string { array1 := gstr.SplitAndTrim(s, ",") for k1, v1 := range array1 { @@ -315,35 +331,35 @@ func doQuoteString(s, charLeft, charRight string) string { // GetWhereConditionOfStruct returns the where condition sql and arguments by given struct pointer. // This function automatically retrieves primary or unique field and its attribute value as condition. func GetWhereConditionOfStruct(pointer interface{}) (where string, args []interface{}, err error) { - tagField, err := structs.TagFields(pointer, []string{ORM_TAG_FOR_STRUCT}) + tagField, err := structs.TagFields(pointer, []string{OrmTagForStruct}) if err != nil { return "", nil, err } array := ([]string)(nil) for _, field := range tagField { array = strings.Split(field.TagValue, ",") - if len(array) > 1 && gstr.InArray([]string{ORM_TAG_FOR_UNIQUE, ORM_TAG_FOR_PRIMARY}, array[1]) { - return array[0], []interface{}{field.Value()}, nil + if len(array) > 1 && gstr.InArray([]string{OrmTagForUnique, OrmTagForPrimary}, array[1]) { + return array[0], []interface{}{field.Value.Interface()}, nil } if len(where) > 0 { where += " AND " } where += field.TagValue + "=?" - args = append(args, field.Value()) + args = append(args, field.Value.Interface()) } return } // GetPrimaryKey retrieves and returns primary key field name from given struct. func GetPrimaryKey(pointer interface{}) (string, error) { - tagField, err := structs.TagFields(pointer, []string{ORM_TAG_FOR_STRUCT}) + tagField, err := structs.TagFields(pointer, []string{OrmTagForStruct}) if err != nil { return "", err } array := ([]string)(nil) for _, field := range tagField { array = strings.Split(field.TagValue, ",") - if len(array) > 1 && array[1] == ORM_TAG_FOR_PRIMARY { + if len(array) > 1 && array[1] == OrmTagForPrimary { return array[0], nil } } @@ -351,7 +367,7 @@ func GetPrimaryKey(pointer interface{}) (string, error) { } // GetPrimaryKeyCondition returns a new where condition by primary field name. -// The optional parameter <where> is like follows: +// The optional parameter `where` is like follows: // 123 => primary=123 // []int{1, 2, 3} => primary IN(1,2,3) // "john" => primary='john' @@ -360,8 +376,8 @@ func GetPrimaryKey(pointer interface{}) (string, error) { // g.Map{"id": 1, "name": "john"} => id=1 AND name='john' // etc. // -// Note that it returns the given <where> parameter directly if the <primary> is empty -// or length of <where> > 1. +// Note that it returns the given `where` parameter directly if the `primary` is empty +// or length of `where` > 1. func GetPrimaryKeyCondition(primary string, where ...interface{}) (newWhereCondition []interface{}) { if len(where) == 0 { return nil @@ -380,7 +396,7 @@ func GetPrimaryKeyCondition(primary string, where ...interface{}) (newWhereCondi } switch kind { case reflect.Map, reflect.Struct: - // Ignore the parameter <primary>. + // Ignore the parameter `primary`. break default: @@ -403,60 +419,125 @@ func formatSql(sql string, args []interface{}) (newSql string, newArgs []interfa return handleArguments(sql, args) } -// formatWhere formats where statement and its arguments. -func formatWhere(db DB, where interface{}, args []interface{}, omitEmpty bool) (newWhere string, newArgs []interface{}) { +type formatWhereInput struct { + Where interface{} + Args []interface{} + OmitNil bool + OmitEmpty bool + Schema string + Table string +} + +// formatWhere formats where statement and its arguments for `Where` and `Having` statements. +func formatWhere(db DB, in formatWhereInput) (newWhere string, newArgs []interface{}) { var ( - buffer = bytes.NewBuffer(nil) - rv = reflect.ValueOf(where) - kind = rv.Kind() + buffer = bytes.NewBuffer(nil) + reflectValue = reflect.ValueOf(in.Where) + reflectKind = reflectValue.Kind() ) - if kind == reflect.Ptr { - rv = rv.Elem() - kind = rv.Kind() + for reflectKind == reflect.Ptr { + reflectValue = reflectValue.Elem() + reflectKind = reflectValue.Kind() } - switch kind { + switch reflectKind { case reflect.Array, reflect.Slice: - newArgs = formatWhereInterfaces(db, gconv.Interfaces(where), buffer, newArgs) + newArgs = formatWhereInterfaces(db, gconv.Interfaces(in.Where), buffer, newArgs) case reflect.Map: - for key, value := range DataToMapDeep(where) { - if gregex.IsMatchString(regularFieldNameRegPattern, key) && omitEmpty && empty.IsEmpty(value) { - continue + for key, value := range DataToMapDeep(in.Where) { + if gregex.IsMatchString(regularFieldNameRegPattern, key) { + if in.OmitNil && empty.IsNil(value) { + continue + } + if in.OmitEmpty && empty.IsEmpty(value) { + continue + } } newArgs = formatWhereKeyValue(db, buffer, newArgs, key, value) } case reflect.Struct: - // If <where> struct implements apiIterator interface, - // it then uses its Iterate function to iterates its key-value pairs. + // If `where` struct implements apiIterator interface, + // it then uses its Iterate function to iterate its key-value pairs. // For example, ListMap and TreeMap are ordered map, // which implement apiIterator interface and are index-friendly for where conditions. - if iterator, ok := where.(apiIterator); ok { + if iterator, ok := in.Where.(apiIterator); ok { iterator.Iterator(func(key, value interface{}) bool { ketStr := gconv.String(key) - if gregex.IsMatchString(regularFieldNameRegPattern, ketStr) && omitEmpty && empty.IsEmpty(value) { - return true + if gregex.IsMatchString(regularFieldNameRegPattern, ketStr) { + if in.OmitNil && empty.IsNil(value) { + return true + } + if in.OmitEmpty && empty.IsEmpty(value) { + return true + } } newArgs = formatWhereKeyValue(db, buffer, newArgs, ketStr, value) return true }) break } - for key, value := range DataToMapDeep(where) { - if omitEmpty && empty.IsEmpty(value) { - continue + // Automatically mapping and filtering the struct attribute. + var ( + reflectType = reflectValue.Type() + structField reflect.StructField + ) + data := DataToMapDeep(in.Where) + if in.Table != "" { + data, _ = db.GetCore().mappingAndFilterData(in.Schema, in.Table, data, true) + } + // Put the struct attributes in sequence in Where statement. + for i := 0; i < reflectType.NumField(); i++ { + structField = reflectType.Field(i) + foundKey, foundValue := gutil.MapPossibleItemByKey(data, structField.Name) + if foundKey != "" { + if in.OmitNil && empty.IsNil(foundValue) { + continue + } + if in.OmitEmpty && empty.IsEmpty(foundValue) { + continue + } + newArgs = formatWhereKeyValue(db, buffer, newArgs, foundKey, foundValue) } - newArgs = formatWhereKeyValue(db, buffer, newArgs, key, value) } default: - buffer.WriteString(gconv.String(where)) + // Usually a string. + var ( + i = 0 + whereStr = gconv.String(in.Where) + ) + for { + if i >= len(in.Args) { + break + } + // Sub query, which is always used along with a string condition. + if model, ok := in.Args[i].(*Model); ok { + var ( + index = -1 + ) + whereStr, _ = gregex.ReplaceStringFunc(`(\?)`, whereStr, func(s string) string { + index++ + if i+len(newArgs) == index { + sqlWithHolder, holderArgs := model.getFormattedSqlAndArgs(queryTypeNormal, false) + newArgs = append(newArgs, holderArgs...) + // Automatically adding the brackets. + return "(" + sqlWithHolder + ")" + } + return s + }) + in.Args = gutil.SliceDelete(in.Args, i) + continue + } + i++ + } + buffer.WriteString(whereStr) } if buffer.Len() == 0 { - return "", args + return "", in.Args } - newArgs = append(newArgs, args...) + newArgs = append(newArgs, in.Args...) newWhere = buffer.String() if len(newArgs) > 0 { if gstr.Pos(newWhere, "?") == -1 { @@ -464,17 +545,23 @@ func formatWhere(db DB, where interface{}, args []interface{}, omitEmpty bool) ( // Eg: Where/And/Or("uid>=", 1) newWhere += "?" } else if gregex.IsMatchString(regularFieldNameRegPattern, newWhere) { - newWhere = db.QuoteString(newWhere) + newWhere = db.GetCore().QuoteString(newWhere) if len(newArgs) > 0 { if utils.IsArray(newArgs[0]) { - // Eg: Where("id", []int{1,2,3}) + // Eg: + // Where("id", []int{1,2,3}) + // Where("user.id", []int{1,2,3}) newWhere += " IN (?)" } else if empty.IsNil(newArgs[0]) { - // Eg: Where("id", nil) + // Eg: + // Where("id", nil) + // Where("user.id", nil) newWhere += " IS NULL" newArgs = nil } else { - // Eg: Where/And/Or("uid", 1) + // Eg: + // Where/And/Or("uid", 1) + // Where/And/Or("user.uid", 1) newWhere += "=?" } } @@ -484,7 +571,7 @@ func formatWhere(db DB, where interface{}, args []interface{}, omitEmpty bool) ( return handleArguments(newWhere, newArgs) } -// formatWhereInterfaces formats <where> as []interface{}. +// formatWhereInterfaces formats `where` as []interface{}. func formatWhereInterfaces(db DB, where []interface{}, buffer *bytes.Buffer, newArgs []interface{}) []interface{} { if len(where) == 0 { return newArgs @@ -497,9 +584,9 @@ func formatWhereInterfaces(db DB, where []interface{}, buffer *bytes.Buffer, new for i := 0; i < len(where); i += 2 { str = gconv.String(where[i]) if buffer.Len() > 0 { - buffer.WriteString(" AND " + db.QuoteWord(str) + "=?") + buffer.WriteString(" AND " + db.GetCore().QuoteWord(str) + "=?") } else { - buffer.WriteString(db.QuoteWord(str) + "=?") + buffer.WriteString(db.GetCore().QuoteWord(str) + "=?") } if s, ok := where[i+1].(Raw); ok { buffer.WriteString(gconv.String(s)) @@ -512,7 +599,7 @@ func formatWhereInterfaces(db DB, where []interface{}, buffer *bytes.Buffer, new // formatWhereKeyValue handles each key-value pair of the parameter map. func formatWhereKeyValue(db DB, buffer *bytes.Buffer, newArgs []interface{}, key string, value interface{}) []interface{} { - quotedKey := db.QuoteWord(key) + quotedKey := db.GetCore().QuoteWord(key) if buffer.Len() > 0 { buffer.WriteString(" AND ") } @@ -693,9 +780,9 @@ func handleArguments(sql string, args []interface{}) (newSql string, newArgs []i } // formatError customizes and returns the SQL error. -func formatError(err error, sql string, args ...interface{}) error { - if err != nil && err != ErrNoRows { - return gerror.New(fmt.Sprintf("%s, %s\n", err.Error(), FormatSqlWithArgs(sql, args))) +func formatError(err error, s string, args ...interface{}) error { + if err != nil && err != sql.ErrNoRows { + return gerror.NewCodef(gcode.CodeDbOperationError, "%s, %s\n", err.Error(), FormatSqlWithArgs(s, args)) } return err } @@ -705,7 +792,9 @@ func formatError(err error, sql string, args ...interface{}) error { func FormatSqlWithArgs(sql string, args []interface{}) string { index := -1 newQuery, _ := gregex.ReplaceStringFunc( - `(\?|:v\d+|\$\d+|@p\d+)`, sql, func(s string) string { + `(\?|:v\d+|\$\d+|@p\d+)`, + sql, + func(s string) string { index++ if len(args) > index { if args[index] == nil { @@ -725,6 +814,7 @@ func FormatSqlWithArgs(sql string, args []interface{}) string { switch kind { case reflect.String, reflect.Map, reflect.Slice, reflect.Array: return `'` + gstr.QuoteMeta(gconv.String(args[index]), `'`) + `'` + case reflect.Struct: if t, ok := args[index].(time.Time); ok { return `'` + t.Format(`2006-01-02 15:04:05`) + `'` @@ -737,18 +827,3 @@ func FormatSqlWithArgs(sql string, args []interface{}) string { }) return newQuery } - -// convertMapToStruct maps the <data> to given struct. -// Note that the given parameter <pointer> should be a pointer to s struct. -func convertMapToStruct(data map[string]interface{}, pointer interface{}) error { - tagNameMap, err := structs.TagMapName(pointer, []string{ORM_TAG_FOR_STRUCT}) - if err != nil { - return err - } - // It retrieves and returns the mapping between orm tag and the struct attribute name. - mapping := make(map[string]string) - for tag, attr := range tagNameMap { - mapping[strings.Split(tag, ",")[0]] = attr - } - return gconv.Struct(data, pointer, mapping) -} diff --git a/database/gdb/gdb_model.go b/database/gdb/gdb_model.go index 34bdfc3e2..e7d4f823f 100644 --- a/database/gdb/gdb_model.go +++ b/database/gdb/gdb_model.go @@ -1,4 +1,4 @@ -// Copyright GoFrame Author(https://github.com/gogf/gf). All Rights Reserved. +// Copyright GoFrame Author(https://goframe.org). All Rights Reserved. // // This Source Code Form is subject to the terms of the MIT License. // If a copy of the MIT was not distributed with this file, @@ -9,109 +9,184 @@ package gdb import ( "context" "fmt" - "github.com/gogf/gf/text/gregex" + "github.com/gogf/gf/util/gconv" "time" + "github.com/gogf/gf/text/gregex" + "github.com/gogf/gf/text/gstr" ) -// Model is the DAO for ORM. +// Model is core struct implementing the DAO for ORM. type Model struct { - db DB // Underlying DB interface. - tx *TX // Underlying TX interface. - schema string // Custom database schema. - linkType int // Mark for operation on master or slave. - tablesInit string // Table names when model initialization. - tables string // Operation table names, which can be more than one table names and aliases, like: "user", "user u", "user u, user_detail ud". - fields string // Operation fields, multiple fields joined using char ','. - fieldsEx string // Excluded operation fields, multiple fields joined using char ','. - extraArgs []interface{} // Extra custom arguments for sql. - whereHolder []*whereHolder // Condition strings for where operation. - groupBy string // Used for "group by" statement. - orderBy string // Used for "order by" statement. - having []interface{} // Used for "having..." statement. - start int // Used for "select ... start, limit ..." statement. - limit int // Used for "select ... start, limit ..." statement. - option int // Option for extra operation features. - offset int // Offset statement for some databases grammar. - data interface{} // Data for operation, which can be type of map/[]map/struct/*struct/string, etc. - batch int // Batch number for batch Insert/Replace/Save operations. - filter bool // Filter data and where key-value pairs according to the fields of the table. - lockInfo string // Lock for update or in shared lock. - cacheEnabled bool // Enable sql result cache feature. - cacheDuration time.Duration // Cache TTL duration. - cacheName string // Cache name for custom operation. - unscoped bool // Disables soft deleting features when select/delete operations. - safe bool // If true, it clones and returns a new model object whenever operation done; or else it changes the attribute of current model. + db DB // Underlying DB interface. + tx *TX // Underlying TX interface. + rawSql string // rawSql is the raw SQL string which marks a raw SQL based Model not a table based Model. + schema string // Custom database schema. + linkType int // Mark for operation on master or slave. + tablesInit string // Table names when model initialization. + tables string // Operation table names, which can be more than one table names and aliases, like: "user", "user u", "user u, user_detail ud". + fields string // Operation fields, multiple fields joined using char ','. + fieldsEx string // Excluded operation fields, multiple fields joined using char ','. + withArray []interface{} // Arguments for With feature. + withAll bool // Enable model association operations on all objects that have "with" tag in the struct. + extraArgs []interface{} // Extra custom arguments for sql, which are prepended to the arguments before sql committed to underlying driver. + whereHolder []ModelWhereHolder // Condition strings for where operation. + groupBy string // Used for "group by" statement. + orderBy string // Used for "order by" statement. + having []interface{} // Used for "having..." statement. + start int // Used for "select ... start, limit ..." statement. + limit int // Used for "select ... start, limit ..." statement. + option int // Option for extra operation features. + offset int // Offset statement for some databases grammar. + data interface{} // Data for operation, which can be type of map/[]map/struct/*struct/string, etc. + batch int // Batch number for batch Insert/Replace/Save operations. + filter bool // Filter data and where key-value pairs according to the fields of the table. + distinct string // Force the query to only return distinct results. + lockInfo string // Lock for update or in shared lock. + cacheEnabled bool // Enable sql result cache feature. + cacheDuration time.Duration // Cache TTL duration (< 1 for removing cache, >= 0 for saving cache). + cacheName string // Cache name for custom operation. + unscoped bool // Disables soft deleting features when select/delete operations. + safe bool // If true, it clones and returns a new model object whenever operation done; or else it changes the attribute of current model. + onDuplicate interface{} // onDuplicate is used for ON "DUPLICATE KEY UPDATE" statement. + onDuplicateEx interface{} // onDuplicateEx is used for excluding some columns ON "DUPLICATE KEY UPDATE" statement. } -// whereHolder is the holder for where condition preparing. -type whereHolder struct { - operator int // Operator for this holder. - where interface{} // Where parameter. - args []interface{} // Arguments for where parameter. +// ModelHandler is a function that handles given Model and returns a new Model that is custom modified. +type ModelHandler func(m *Model) *Model + +// ChunkHandler is a function that is used in function Chunk, which handles given Result and error. +// It returns true if it wants continue chunking, or else it returns false to stop chunking. +type ChunkHandler func(result Result, err error) bool + +// ModelWhereHolder is the holder for where condition preparing. +type ModelWhereHolder struct { + Operator int // Operator for this holder. + Where interface{} // Where parameter, which can commonly be type of string/map/struct. + Args []interface{} // Arguments for where parameter. } const ( - OPTION_OMITEMPTY = 1 - OPTION_ALLOWEMPTY = 2 - - linkTypeMaster = 1 - linkTypeSlave = 2 - whereHolderWhere = 1 - whereHolderAnd = 2 - whereHolderOr = 3 + linkTypeMaster = 1 + linkTypeSlave = 2 + whereHolderOperatorWhere = 1 + whereHolderOperatorAnd = 2 + whereHolderOperatorOr = 3 + defaultFields = "*" ) -// Table creates and returns a new ORM model from given schema. -// The parameter <table> can be more than one table names, and also alias name, like: -// 1. Table names: -// Table("user") -// Table("user u") -// Table("user, user_detail") -// Table("user u, user_detail ud") -// 2. Table name with alias: Table("user", "u") -func (c *Core) Table(table ...string) *Model { - tables := "" - if len(table) > 1 { - tables = fmt.Sprintf( - `%s AS %s`, c.DB.QuotePrefixTableName(table[0]), c.DB.QuoteWord(table[1]), - ) - } else if len(table) == 1 { - tables = c.DB.QuotePrefixTableName(table[0]) - } else { - panic("table cannot be empty") +// Table is alias of Core.Model. +// See Core.Model. +// Deprecated, use Model instead. +func (c *Core) Table(tableNameQueryOrStruct ...interface{}) *Model { + return c.db.Model(tableNameQueryOrStruct...) +} + +// Model creates and returns a new ORM model from given schema. +// The parameter `tableNameQueryOrStruct` can be more than one table names, and also alias name, like: +// 1. Model names: +// db.Model("user") +// db.Model("user u") +// db.Model("user, user_detail") +// db.Model("user u, user_detail ud") +// 2. Model name with alias: +// db.Model("user", "u") +// 3. Model name with sub-query: +// db.Model("? AS a, ? AS b", subQuery1, subQuery2) +func (c *Core) Model(tableNameQueryOrStruct ...interface{}) *Model { + var ( + tableStr string + tableName string + extraArgs []interface{} + ) + // Model creation with sub-query. + if len(tableNameQueryOrStruct) > 1 { + conditionStr := gconv.String(tableNameQueryOrStruct[0]) + if gstr.Contains(conditionStr, "?") { + tableStr, extraArgs = formatWhere(c.db, formatWhereInput{ + Where: conditionStr, + Args: tableNameQueryOrStruct[1:], + OmitNil: false, + OmitEmpty: false, + Schema: "", + Table: "", + }) + } + } + // Normal model creation. + if tableStr == "" { + tableNames := make([]string, len(tableNameQueryOrStruct)) + for k, v := range tableNameQueryOrStruct { + if s, ok := v.(string); ok { + tableNames[k] = s + } else if tableName = getTableNameFromOrmTag(v); tableName != "" { + tableNames[k] = tableName + } + } + if len(tableNames) > 1 { + tableStr = fmt.Sprintf( + `%s AS %s`, c.QuotePrefixTableName(tableNames[0]), c.QuoteWord(tableNames[1]), + ) + } else if len(tableNames) == 1 { + tableStr = c.QuotePrefixTableName(tableNames[0]) + } } return &Model{ - db: c.DB, - tablesInit: tables, - tables: tables, - fields: "*", + db: c.db, + tablesInit: tableStr, + tables: tableStr, + fields: defaultFields, start: -1, offset: -1, - option: OPTION_ALLOWEMPTY, + filter: true, + extraArgs: extraArgs, } } -// Model is alias of Core.Table. -// See Core.Table. -func (c *Core) Model(table ...string) *Model { - return c.DB.Table(table...) +// Raw creates and returns a model based on a raw sql not a table. +// Example: +// db.Raw("SELECT * FROM `user` WHERE `name` = ?", "john").Scan(&result) +func (c *Core) Raw(rawSql string, args ...interface{}) *Model { + model := c.Model() + model.rawSql = rawSql + model.extraArgs = args + return model } -// Table acts like Core.Table except it operates on transaction. -// See Core.Table. -func (tx *TX) Table(table ...string) *Model { - model := tx.db.Table(table...) +// Raw creates and returns a model based on a raw sql not a table. +// Example: +// db.Raw("SELECT * FROM `user` WHERE `name` = ?", "john").Scan(&result) +// See Core.Raw. +func (m *Model) Raw(rawSql string, args ...interface{}) *Model { + model := m.db.Raw(rawSql, args...) + model.db = m.db + model.tx = m.tx + return model +} + +func (tx *TX) Raw(rawSql string, args ...interface{}) *Model { + return tx.Model().Raw(rawSql, args...) +} + +// With creates and returns an ORM model based on meta data of given object. +func (c *Core) With(objects ...interface{}) *Model { + return c.db.Model().With(objects...) +} + +// Model acts like Core.Model except it operates on transaction. +// See Core.Model. +func (tx *TX) Model(tableNameQueryOrStruct ...interface{}) *Model { + model := tx.db.Model(tableNameQueryOrStruct...) model.db = tx.db model.tx = tx return model } -// Model is alias of tx.Table. -// See tx.Table. -func (tx *TX) Model(table ...string) *Model { - return tx.Table(table...) +// With acts like Core.With except it operates on transaction. +// See Core.With. +func (tx *TX) With(object interface{}) *Model { + return tx.Model().With(object) } // Ctx sets the context for current operation. @@ -121,15 +196,27 @@ func (m *Model) Ctx(ctx context.Context) *Model { } model := m.getModel() model.db = model.db.Ctx(ctx) + if m.tx != nil { + model.tx = model.tx.Ctx(ctx) + } return model } +// GetCtx returns the context for current Model. +// It returns `context.Background()` is there's no context previously set. +func (m *Model) GetCtx() context.Context { + if m.tx != nil && m.tx.ctx != nil { + return m.tx.ctx + } + return m.db.GetCtx() +} + // As sets an alias name for current table. func (m *Model) As(as string) *Model { if m.tables != "" { model := m.getModel() split := " JOIN " - if gstr.Contains(model.tables, split) { + if gstr.ContainsI(model.tables, split) { // For join table. array := gstr.Split(model.tables, split) array[len(array)-1], _ = gregex.ReplaceString(`(.+) ON`, fmt.Sprintf(`$1 AS %s ON`, as), array[len(array)-1]) @@ -170,20 +257,24 @@ func (m *Model) Schema(schema string) *Model { func (m *Model) Clone() *Model { newModel := (*Model)(nil) if m.tx != nil { - newModel = m.tx.Table(m.tablesInit) + newModel = m.tx.Model(m.tablesInit) } else { - newModel = m.db.Table(m.tablesInit) + newModel = m.db.Model(m.tablesInit) } *newModel = *m - // Deep copy slice attributes. + // Shallow copy slice attributes. if n := len(m.extraArgs); n > 0 { newModel.extraArgs = make([]interface{}, n) copy(newModel.extraArgs, m.extraArgs) } if n := len(m.whereHolder); n > 0 { - newModel.whereHolder = make([]*whereHolder, n) + newModel.whereHolder = make([]ModelWhereHolder, n) copy(newModel.whereHolder, m.whereHolder) } + if n := len(m.withArray); n > 0 { + newModel.withArray = make([]interface{}, n) + copy(newModel.withArray, m.withArray) + } return newModel } @@ -219,3 +310,13 @@ func (m *Model) Args(args ...interface{}) *Model { model.extraArgs = append(model.extraArgs, args) return model } + +// Handler calls each of `handlers` on current Model and returns a new Model. +// ModelHandler is a function that handles given Model and returns a new Model that is custom modified. +func (m *Model) Handler(handlers ...ModelHandler) *Model { + model := m.getModel() + for _, handler := range handlers { + model = handler(model) + } + return model +} diff --git a/database/gdb/gdb_model_cache.go b/database/gdb/gdb_model_cache.go index 16263c904..b9ba72873 100644 --- a/database/gdb/gdb_model_cache.go +++ b/database/gdb/gdb_model_cache.go @@ -1,4 +1,4 @@ -// Copyright GoFrame Author(https://github.com/gogf/gf). All Rights Reserved. +// Copyright GoFrame Author(https://goframe.org). All Rights Reserved. // // This Source Code Form is subject to the terms of the MIT License. // If a copy of the MIT was not distributed with this file, @@ -14,13 +14,13 @@ import ( // if there's another same sql request, it just reads and returns the result from cache, it // but not committed and executed into the database. // -// If the parameter <duration> < 0, which means it clear the cache with given <name>. -// If the parameter <duration> = 0, which means it never expires. -// If the parameter <duration> > 0, which means it expires after <duration>. +// If the parameter `duration` < 0, which means it clear the cache with given `name`. +// If the parameter `duration` = 0, which means it never expires. +// If the parameter `duration` > 0, which means it expires after `duration`. // -// The optional parameter <name> is used to bind a name to the cache, which means you can -// later control the cache like changing the <duration> or clearing the cache with specified -// <name>. +// The optional parameter `name` is used to bind a name to the cache, which means you can +// later control the cache like changing the `duration` or clearing the cache with specified +// `name`. // // Note that, the cache feature is disabled if the model is performing select statement // on a transaction. @@ -38,6 +38,6 @@ func (m *Model) Cache(duration time.Duration, name ...string) *Model { // cache feature is enabled. func (m *Model) checkAndRemoveCache() { if m.cacheEnabled && m.cacheDuration < 0 && len(m.cacheName) > 0 { - m.db.GetCache().Remove(m.cacheName) + m.db.GetCache().Ctx(m.GetCtx()).Remove(m.cacheName) } } diff --git a/database/gdb/gdb_model_condition.go b/database/gdb/gdb_model_condition.go index 30f440f84..2ff16eb4c 100644 --- a/database/gdb/gdb_model_condition.go +++ b/database/gdb/gdb_model_condition.go @@ -1,4 +1,4 @@ -// Copyright GoFrame Author(https://github.com/gogf/gf). All Rights Reserved. +// Copyright GoFrame Author(https://goframe.org). All Rights Reserved. // // This Source Code Form is subject to the terms of the MIT License. // If a copy of the MIT was not distributed with this file, @@ -7,10 +7,13 @@ package gdb import ( + "fmt" + "github.com/gogf/gf/text/gstr" + "github.com/gogf/gf/util/gconv" "strings" ) -// Where sets the condition statement for the model. The parameter <where> can be type of +// Where sets the condition statement for the model. The parameter `where` can be type of // string/map/gmap/slice/struct/*struct, etc. Note that, if it's called more than one times, // multiple conditions will be joined into where statement using "AND". // Eg: @@ -24,12 +27,12 @@ import ( func (m *Model) Where(where interface{}, args ...interface{}) *Model { model := m.getModel() if model.whereHolder == nil { - model.whereHolder = make([]*whereHolder, 0) + model.whereHolder = make([]ModelWhereHolder, 0) } - model.whereHolder = append(model.whereHolder, &whereHolder{ - operator: whereHolderWhere, - where: where, - args: args, + model.whereHolder = append(model.whereHolder, ModelWhereHolder{ + Operator: whereHolderOperatorWhere, + Where: where, + Args: args, }) return model } @@ -45,9 +48,9 @@ func (m *Model) Having(having interface{}, args ...interface{}) *Model { return model } -// WherePri does the same logic as Model.Where except that if the parameter <where> +// WherePri does the same logic as Model.Where except that if the parameter `where` // is a single condition like int/string/float/slice, it treats the condition as the primary -// key value. That is, if primary key is "id" and given <where> parameter as "123", the +// key value. That is, if primary key is "id" and given `where` parameter as "123", the // WherePri function treats the condition as "id=123", but Model.Where treats the condition // as string "123". func (m *Model) WherePri(where interface{}, args ...interface{}) *Model { @@ -58,64 +61,274 @@ func (m *Model) WherePri(where interface{}, args ...interface{}) *Model { return m.Where(newWhere[0], newWhere[1:]...) } +// Wheref builds condition string using fmt.Sprintf and arguments. +// Note that if the number of `args` is more than the place holder in `format`, +// the extra `args` will be used as the where condition arguments of the Model. +func (m *Model) Wheref(format string, args ...interface{}) *Model { + var ( + placeHolderCount = gstr.Count(format, "?") + conditionStr = fmt.Sprintf(format, args[:len(args)-placeHolderCount]...) + ) + return m.Where(conditionStr, args[len(args)-placeHolderCount:]...) +} + +// WhereLT builds `column < value` statement. +func (m *Model) WhereLT(column string, value interface{}) *Model { + return m.Wheref(`%s < ?`, column, value) +} + +// WhereLTE builds `column <= value` statement. +func (m *Model) WhereLTE(column string, value interface{}) *Model { + return m.Wheref(`%s <= ?`, column, value) +} + +// WhereGT builds `column > value` statement. +func (m *Model) WhereGT(column string, value interface{}) *Model { + return m.Wheref(`%s > ?`, column, value) +} + +// WhereGTE builds `column >= value` statement. +func (m *Model) WhereGTE(column string, value interface{}) *Model { + return m.Wheref(`%s >= ?`, column, value) +} + +// WhereBetween builds `column BETWEEN min AND max` statement. +func (m *Model) WhereBetween(column string, min, max interface{}) *Model { + return m.Wheref(`%s BETWEEN ? AND ?`, m.db.GetCore().QuoteWord(column), min, max) +} + +// WhereLike builds `column LIKE like` statement. +func (m *Model) WhereLike(column string, like interface{}) *Model { + return m.Wheref(`%s LIKE ?`, m.db.GetCore().QuoteWord(column), like) +} + +// WhereIn builds `column IN (in)` statement. +func (m *Model) WhereIn(column string, in interface{}) *Model { + return m.Wheref(`%s IN (?)`, m.db.GetCore().QuoteWord(column), in) +} + +// WhereNull builds `columns[0] IS NULL AND columns[1] IS NULL ...` statement. +func (m *Model) WhereNull(columns ...string) *Model { + model := m + for _, column := range columns { + model = m.Wheref(`%s IS NULL`, m.db.GetCore().QuoteWord(column)) + } + return model +} + +// WhereNotBetween builds `column NOT BETWEEN min AND max` statement. +func (m *Model) WhereNotBetween(column string, min, max interface{}) *Model { + return m.Wheref(`%s NOT BETWEEN ? AND ?`, m.db.GetCore().QuoteWord(column), min, max) +} + +// WhereNotLike builds `column NOT LIKE like` statement. +func (m *Model) WhereNotLike(column string, like interface{}) *Model { + return m.Wheref(`%s NOT LIKE ?`, m.db.GetCore().QuoteWord(column), like) +} + +// WhereNot builds `column != value` statement. +func (m *Model) WhereNot(column string, value interface{}) *Model { + return m.Wheref(`%s != ?`, m.db.GetCore().QuoteWord(column), value) +} + +// WhereNotIn builds `column NOT IN (in)` statement. +func (m *Model) WhereNotIn(column string, in interface{}) *Model { + return m.Wheref(`%s NOT IN (?)`, m.db.GetCore().QuoteWord(column), in) +} + +// WhereNotNull builds `columns[0] IS NOT NULL AND columns[1] IS NOT NULL ...` statement. +func (m *Model) WhereNotNull(columns ...string) *Model { + model := m + for _, column := range columns { + model = m.Wheref(`%s IS NOT NULL`, m.db.GetCore().QuoteWord(column)) + } + return model +} + +// WhereOr adds "OR" condition to the where statement. +func (m *Model) WhereOr(where interface{}, args ...interface{}) *Model { + model := m.getModel() + if model.whereHolder == nil { + model.whereHolder = make([]ModelWhereHolder, 0) + } + model.whereHolder = append(model.whereHolder, ModelWhereHolder{ + Operator: whereHolderOperatorOr, + Where: where, + Args: args, + }) + return model +} + +// WhereOrf builds `OR` condition string using fmt.Sprintf and arguments. +func (m *Model) WhereOrf(format string, args ...interface{}) *Model { + var ( + placeHolderCount = gstr.Count(format, "?") + conditionStr = fmt.Sprintf(format, args[:len(args)-placeHolderCount]...) + ) + return m.WhereOr(conditionStr, args[len(args)-placeHolderCount:]...) +} + +// WhereOrLT builds `column < value` statement in `OR` conditions.. +func (m *Model) WhereOrLT(column string, value interface{}) *Model { + return m.WhereOrf(`%s < ?`, column, value) +} + +// WhereOrLTE builds `column <= value` statement in `OR` conditions.. +func (m *Model) WhereOrLTE(column string, value interface{}) *Model { + return m.WhereOrf(`%s <= ?`, column, value) +} + +// WhereOrGT builds `column > value` statement in `OR` conditions.. +func (m *Model) WhereOrGT(column string, value interface{}) *Model { + return m.WhereOrf(`%s > ?`, column, value) +} + +// WhereOrGTE builds `column >= value` statement in `OR` conditions.. +func (m *Model) WhereOrGTE(column string, value interface{}) *Model { + return m.WhereOrf(`%s >= ?`, column, value) +} + +// WhereOrBetween builds `column BETWEEN min AND max` statement in `OR` conditions. +func (m *Model) WhereOrBetween(column string, min, max interface{}) *Model { + return m.WhereOrf(`%s BETWEEN ? AND ?`, m.db.GetCore().QuoteWord(column), min, max) +} + +// WhereOrLike builds `column LIKE like` statement in `OR` conditions. +func (m *Model) WhereOrLike(column string, like interface{}) *Model { + return m.WhereOrf(`%s LIKE ?`, m.db.GetCore().QuoteWord(column), like) +} + +// WhereOrIn builds `column IN (in)` statement in `OR` conditions. +func (m *Model) WhereOrIn(column string, in interface{}) *Model { + return m.WhereOrf(`%s IN (?)`, m.db.GetCore().QuoteWord(column), in) +} + +// WhereOrNull builds `columns[0] IS NULL OR columns[1] IS NULL ...` statement in `OR` conditions. +func (m *Model) WhereOrNull(columns ...string) *Model { + model := m + for _, column := range columns { + model = m.WhereOrf(`%s IS NULL`, m.db.GetCore().QuoteWord(column)) + } + return model +} + +// WhereOrNotBetween builds `column NOT BETWEEN min AND max` statement in `OR` conditions. +func (m *Model) WhereOrNotBetween(column string, min, max interface{}) *Model { + return m.WhereOrf(`%s NOT BETWEEN ? AND ?`, m.db.GetCore().QuoteWord(column), min, max) +} + +// WhereOrNotLike builds `column NOT LIKE like` statement in `OR` conditions. +func (m *Model) WhereOrNotLike(column string, like interface{}) *Model { + return m.WhereOrf(`%s NOT LIKE ?`, m.db.GetCore().QuoteWord(column), like) +} + +// WhereOrNotIn builds `column NOT IN (in)` statement. +func (m *Model) WhereOrNotIn(column string, in interface{}) *Model { + return m.WhereOrf(`%s NOT IN (?)`, m.db.GetCore().QuoteWord(column), in) +} + +// WhereOrNotNull builds `columns[0] IS NOT NULL OR columns[1] IS NOT NULL ...` statement in `OR` conditions. +func (m *Model) WhereOrNotNull(columns ...string) *Model { + model := m + for _, column := range columns { + model = m.WhereOrf(`%s IS NOT NULL`, m.db.GetCore().QuoteWord(column)) + } + return model +} + +// Group sets the "GROUP BY" statement for the model. +func (m *Model) Group(groupBy ...string) *Model { + if len(groupBy) > 0 { + model := m.getModel() + model.groupBy = m.db.GetCore().QuoteString(gstr.Join(groupBy, ",")) + return model + } + return m +} + // And adds "AND" condition to the where statement. +// Deprecated, use Where instead. func (m *Model) And(where interface{}, args ...interface{}) *Model { model := m.getModel() if model.whereHolder == nil { - model.whereHolder = make([]*whereHolder, 0) + model.whereHolder = make([]ModelWhereHolder, 0) } - model.whereHolder = append(model.whereHolder, &whereHolder{ - operator: whereHolderAnd, - where: where, - args: args, + model.whereHolder = append(model.whereHolder, ModelWhereHolder{ + Operator: whereHolderOperatorAnd, + Where: where, + Args: args, }) return model } // Or adds "OR" condition to the where statement. +// Deprecated, use WhereOr instead. func (m *Model) Or(where interface{}, args ...interface{}) *Model { - model := m.getModel() - if model.whereHolder == nil { - model.whereHolder = make([]*whereHolder, 0) - } - model.whereHolder = append(model.whereHolder, &whereHolder{ - operator: whereHolderOr, - where: where, - args: args, - }) - return model -} - -// Group sets the "GROUP BY" statement for the model. -func (m *Model) Group(groupBy string) *Model { - model := m.getModel() - model.groupBy = m.db.QuoteString(groupBy) - return model + return m.WhereOr(where, args...) } // GroupBy is alias of Model.Group. // See Model.Group. -// Deprecated. +// Deprecated, use Group instead. func (m *Model) GroupBy(groupBy string) *Model { return m.Group(groupBy) } // Order sets the "ORDER BY" statement for the model. func (m *Model) Order(orderBy ...string) *Model { + if len(orderBy) == 0 { + return m + } model := m.getModel() - model.orderBy = m.db.QuoteString(strings.Join(orderBy, " ")) + if model.orderBy != "" { + model.orderBy += "," + } + model.orderBy = m.db.GetCore().QuoteString(strings.Join(orderBy, " ")) + return model +} + +// OrderAsc sets the "ORDER BY xxx ASC" statement for the model. +func (m *Model) OrderAsc(column string) *Model { + if len(column) == 0 { + return m + } + model := m.getModel() + if model.orderBy != "" { + model.orderBy += "," + } + model.orderBy = m.db.GetCore().QuoteWord(column) + " ASC" + return model +} + +// OrderDesc sets the "ORDER BY xxx DESC" statement for the model. +func (m *Model) OrderDesc(column string) *Model { + if len(column) == 0 { + return m + } + model := m.getModel() + if model.orderBy != "" { + model.orderBy += "," + } + model.orderBy = m.db.GetCore().QuoteWord(column) + " DESC" + return model +} + +// OrderRandom sets the "ORDER BY RANDOM()" statement for the model. +func (m *Model) OrderRandom() *Model { + model := m.getModel() + model.orderBy = "RAND()" return model } // OrderBy is alias of Model.Order. // See Model.Order. -// Deprecated. +// Deprecated, use Order instead. func (m *Model) OrderBy(orderBy string) *Model { return m.Order(orderBy) } // Limit sets the "LIMIT" statement for the model. -// The parameter <limit> can be either one or two number, if passed two number is passed, +// The parameter `limit` can be either one or two number, if passed two number is passed, // it then sets "LIMIT limit[0],limit[1]" statement for the model, or else it sets "LIMIT limit[0]" // statement. func (m *Model) Limit(limit ...int) *Model { @@ -138,8 +351,15 @@ func (m *Model) Offset(offset int) *Model { return model } +// Distinct forces the query to only return distinct results. +func (m *Model) Distinct() *Model { + model := m.getModel() + model.distinct = "DISTINCT " + return model +} + // Page sets the paging number for the model. -// The parameter <page> is started from 1 for paging. +// The parameter `page` is started from 1 for paging. // Note that, it differs that the Limit function starts from 0 for "LIMIT" statement. func (m *Model) Page(page, limit int) *Model { model := m.getModel() @@ -153,7 +373,141 @@ func (m *Model) Page(page, limit int) *Model { // ForPage is alias of Model.Page. // See Model.Page. -// Deprecated. +// Deprecated, use Page instead. func (m *Model) ForPage(page, limit int) *Model { return m.Page(page, limit) } + +// formatCondition formats where arguments of the model and returns a new condition sql and its arguments. +// Note that this function does not change any attribute value of the `m`. +// +// The parameter `limit1` specifies whether limits querying only one record if m.limit is not set. +func (m *Model) formatCondition(limit1 bool, isCountStatement bool) (conditionWhere string, conditionExtra string, conditionArgs []interface{}) { + if len(m.whereHolder) > 0 { + for _, v := range m.whereHolder { + switch v.Operator { + case whereHolderOperatorWhere: + if conditionWhere == "" { + newWhere, newArgs := formatWhere(m.db, formatWhereInput{ + Where: v.Where, + Args: v.Args, + OmitNil: m.option&optionOmitNilWhere > 0, + OmitEmpty: m.option&optionOmitEmptyWhere > 0, + Schema: m.schema, + Table: m.tables, + }) + if len(newWhere) > 0 { + conditionWhere = newWhere + conditionArgs = newArgs + } + continue + } + fallthrough + + case whereHolderOperatorAnd: + newWhere, newArgs := formatWhere(m.db, formatWhereInput{ + Where: v.Where, + Args: v.Args, + OmitNil: m.option&optionOmitNilWhere > 0, + OmitEmpty: m.option&optionOmitEmptyWhere > 0, + Schema: m.schema, + Table: m.tables, + }) + if len(newWhere) > 0 { + if len(conditionWhere) == 0 { + conditionWhere = newWhere + } else if conditionWhere[0] == '(' { + conditionWhere = fmt.Sprintf(`%s AND (%s)`, conditionWhere, newWhere) + } else { + conditionWhere = fmt.Sprintf(`(%s) AND (%s)`, conditionWhere, newWhere) + } + conditionArgs = append(conditionArgs, newArgs...) + } + + case whereHolderOperatorOr: + newWhere, newArgs := formatWhere(m.db, formatWhereInput{ + Where: v.Where, + Args: v.Args, + OmitNil: m.option&optionOmitNilWhere > 0, + OmitEmpty: m.option&optionOmitEmptyWhere > 0, + Schema: m.schema, + Table: m.tables, + }) + if len(newWhere) > 0 { + if len(conditionWhere) == 0 { + conditionWhere = newWhere + } else if conditionWhere[0] == '(' { + conditionWhere = fmt.Sprintf(`%s OR (%s)`, conditionWhere, newWhere) + } else { + conditionWhere = fmt.Sprintf(`(%s) OR (%s)`, conditionWhere, newWhere) + } + conditionArgs = append(conditionArgs, newArgs...) + } + } + } + } + // Soft deletion. + softDeletingCondition := m.getConditionForSoftDeleting() + if m.rawSql != "" && conditionWhere != "" { + if gstr.ContainsI(m.rawSql, " WHERE ") { + conditionWhere = " AND " + conditionWhere + } else { + conditionWhere = " WHERE " + conditionWhere + } + } else if !m.unscoped && softDeletingCondition != "" { + if conditionWhere == "" { + conditionWhere = fmt.Sprintf(` WHERE %s`, softDeletingCondition) + } else { + conditionWhere = fmt.Sprintf(` WHERE (%s) AND %s`, conditionWhere, softDeletingCondition) + } + } else { + if conditionWhere != "" { + conditionWhere = " WHERE " + conditionWhere + } + } + + // GROUP BY. + if m.groupBy != "" { + conditionExtra += " GROUP BY " + m.groupBy + } + // HAVING. + if len(m.having) > 0 { + havingStr, havingArgs := formatWhere(m.db, formatWhereInput{ + Where: m.having[0], + Args: gconv.Interfaces(m.having[1]), + OmitNil: m.option&optionOmitNilWhere > 0, + OmitEmpty: m.option&optionOmitEmptyWhere > 0, + Schema: m.schema, + Table: m.tables, + }) + if len(havingStr) > 0 { + conditionExtra += " HAVING " + havingStr + conditionArgs = append(conditionArgs, havingArgs...) + } + } + // ORDER BY. + if m.orderBy != "" { + conditionExtra += " ORDER BY " + m.orderBy + } + // LIMIT. + if !isCountStatement { + if m.limit != 0 { + if m.start >= 0 { + conditionExtra += fmt.Sprintf(" LIMIT %d,%d", m.start, m.limit) + } else { + conditionExtra += fmt.Sprintf(" LIMIT %d", m.limit) + } + } else if limit1 { + conditionExtra += " LIMIT 1" + } + + if m.offset >= 0 { + conditionExtra += fmt.Sprintf(" OFFSET %d", m.offset) + } + } + + if m.lockInfo != "" { + conditionExtra += " " + m.lockInfo + } + return +} diff --git a/database/gdb/gdb_model_delete.go b/database/gdb/gdb_model_delete.go index 16cdb84e6..b3f264f7d 100644 --- a/database/gdb/gdb_model_delete.go +++ b/database/gdb/gdb_model_delete.go @@ -1,4 +1,4 @@ -// Copyright GoFrame Author(https://github.com/gogf/gf). All Rights Reserved. +// Copyright GoFrame Author(https://goframe.org). All Rights Reserved. // // This Source Code Form is subject to the terms of the MIT License. // If a copy of the MIT was not distributed with this file, @@ -9,13 +9,15 @@ package gdb import ( "database/sql" "fmt" + "github.com/gogf/gf/errors/gcode" + "github.com/gogf/gf/errors/gerror" "github.com/gogf/gf/os/gtime" "github.com/gogf/gf/text/gstr" ) // Delete does "DELETE FROM ... " statement for the model. -// The optional parameter <where> is the same as the parameter of Model.Where function, +// The optional parameter `where` is the same as the parameter of Model.Where function, // see Model.Where. func (m *Model) Delete(where ...interface{}) (result sql.Result, err error) { if len(where) > 0 { @@ -33,16 +35,17 @@ func (m *Model) Delete(where ...interface{}) (result sql.Result, err error) { // Soft deleting. if !m.unscoped && fieldNameDelete != "" { return m.db.DoUpdate( + m.GetCtx(), m.getLink(true), m.tables, - fmt.Sprintf(`%s=?`, m.db.QuoteString(fieldNameDelete)), + fmt.Sprintf(`%s=?`, m.db.GetCore().QuoteString(fieldNameDelete)), conditionWhere+conditionExtra, append([]interface{}{gtime.Now().String()}, conditionArgs...), ) } conditionStr := conditionWhere + conditionExtra if !gstr.ContainsI(conditionStr, " WHERE ") { - return nil, gerror.New("there should be WHERE condition statement for DELETE operation") + return nil, gerror.NewCode(gcode.CodeMissingParameter, "there should be WHERE condition statement for DELETE operation") } - return m.db.DoDelete(m.getLink(true), m.tables, conditionStr, conditionArgs...) + return m.db.DoDelete(m.GetCtx(), m.getLink(true), m.tables, conditionStr, conditionArgs...) } diff --git a/database/gdb/gdb_model_fields.go b/database/gdb/gdb_model_fields.go index de331dcef..a4115039e 100644 --- a/database/gdb/gdb_model_fields.go +++ b/database/gdb/gdb_model_fields.go @@ -1,4 +1,4 @@ -// Copyright GoFrame Author(https://github.com/gogf/gf). All Rights Reserved. +// Copyright GoFrame Author(https://goframe.org). All Rights Reserved. // // This Source Code Form is subject to the terms of the MIT License. // If a copy of the MIT was not distributed with this file, @@ -14,8 +14,143 @@ import ( "github.com/gogf/gf/util/gutil" ) +// Fields appends `fieldNamesOrMapStruct` to the operation fields of the model, multiple fields joined using char ','. +// The parameter `fieldNamesOrMapStruct` can be type of string/map/*map/struct/*struct. +func (m *Model) Fields(fieldNamesOrMapStruct ...interface{}) *Model { + length := len(fieldNamesOrMapStruct) + if length == 0 { + return m + } + switch { + // String slice. + case length >= 2: + return m.appendFieldsByStr(gstr.Join( + m.mappingAndFilterToTableFields(gconv.Strings(fieldNamesOrMapStruct), true), + ",", + )) + // It needs type asserting. + case length == 1: + switch r := fieldNamesOrMapStruct[0].(type) { + case string: + return m.appendFieldsByStr(gstr.Join( + m.mappingAndFilterToTableFields([]string{r}, false), ",", + )) + case []string: + return m.appendFieldsByStr(gstr.Join( + m.mappingAndFilterToTableFields(r, true), ",", + )) + default: + return m.appendFieldsByStr(gstr.Join( + m.mappingAndFilterToTableFields(gutil.Keys(r), true), ",", + )) + } + } + return m +} + +// FieldsEx appends `fieldNamesOrMapStruct` to the excluded operation fields of the model, +// multiple fields joined using char ','. +// Note that this function supports only single table operations. +// The parameter `fieldNamesOrMapStruct` can be type of string/map/*map/struct/*struct. +func (m *Model) FieldsEx(fieldNamesOrMapStruct ...interface{}) *Model { + length := len(fieldNamesOrMapStruct) + if length == 0 { + return m + } + model := m.getModel() + switch { + case length >= 2: + model.fieldsEx = gstr.Join(m.mappingAndFilterToTableFields(gconv.Strings(fieldNamesOrMapStruct), true), ",") + return model + case length == 1: + switch r := fieldNamesOrMapStruct[0].(type) { + case string: + model.fieldsEx = gstr.Join(m.mappingAndFilterToTableFields([]string{r}, false), ",") + case []string: + model.fieldsEx = gstr.Join(m.mappingAndFilterToTableFields(r, true), ",") + default: + model.fieldsEx = gstr.Join(m.mappingAndFilterToTableFields(gutil.Keys(r), true), ",") + } + return model + } + return m +} + +// FieldCount formats and appends commonly used field `COUNT(column)` to the select fields of model. +func (m *Model) FieldCount(column string, as ...string) *Model { + asStr := "" + if len(as) > 0 && as[0] != "" { + asStr = fmt.Sprintf(` AS %s`, m.db.GetCore().QuoteWord(as[0])) + } + return m.appendFieldsByStr(fmt.Sprintf(`COUNT(%s)%s`, m.db.GetCore().QuoteWord(column), asStr)) +} + +// FieldSum formats and appends commonly used field `SUM(column)` to the select fields of model. +func (m *Model) FieldSum(column string, as ...string) *Model { + asStr := "" + if len(as) > 0 && as[0] != "" { + asStr = fmt.Sprintf(` AS %s`, m.db.GetCore().QuoteWord(as[0])) + } + return m.appendFieldsByStr(fmt.Sprintf(`SUM(%s)%s`, m.db.GetCore().QuoteWord(column), asStr)) +} + +// FieldMin formats and appends commonly used field `MIN(column)` to the select fields of model. +func (m *Model) FieldMin(column string, as ...string) *Model { + asStr := "" + if len(as) > 0 && as[0] != "" { + asStr = fmt.Sprintf(` AS %s`, m.db.GetCore().QuoteWord(as[0])) + } + return m.appendFieldsByStr(fmt.Sprintf(`MIN(%s)%s`, m.db.GetCore().QuoteWord(column), asStr)) +} + +// FieldMax formats and appends commonly used field `MAX(column)` to the select fields of model. +func (m *Model) FieldMax(column string, as ...string) *Model { + asStr := "" + if len(as) > 0 && as[0] != "" { + asStr = fmt.Sprintf(` AS %s`, m.db.GetCore().QuoteWord(as[0])) + } + return m.appendFieldsByStr(fmt.Sprintf(`MAX(%s)%s`, m.db.GetCore().QuoteWord(column), asStr)) +} + +// FieldAvg formats and appends commonly used field `AVG(column)` to the select fields of model. +func (m *Model) FieldAvg(column string, as ...string) *Model { + asStr := "" + if len(as) > 0 && as[0] != "" { + asStr = fmt.Sprintf(` AS %s`, m.db.GetCore().QuoteWord(as[0])) + } + return m.appendFieldsByStr(fmt.Sprintf(`AVG(%s)%s`, m.db.GetCore().QuoteWord(column), asStr)) +} + +func (m *Model) appendFieldsByStr(fields string) *Model { + if fields != "" { + model := m.getModel() + if model.fields == defaultFields { + model.fields = "" + } + if model.fields != "" { + model.fields += "," + } + model.fields += fields + return model + } + return m +} + +func (m *Model) appendFieldsExByStr(fieldsEx string) *Model { + if fieldsEx != "" { + model := m.getModel() + if model.fieldsEx != "" { + model.fieldsEx += "," + } + model.fieldsEx += fieldsEx + return model + } + return m +} + // Filter marks filtering the fields which does not exist in the fields of the operated table. // Note that this function supports only single table operations. +// Deprecated, filter feature is automatically enabled from GoFrame v1.16.0, it is so no longer used. func (m *Model) Filter() *Model { if gstr.Contains(m.tables, " ") { panic("function Filter supports only single table operations") @@ -25,76 +160,21 @@ func (m *Model) Filter() *Model { return model } -// Fields sets the operation fields of the model, multiple fields joined using char ','. -// The parameter <fieldNamesOrMapStruct> can be type of string/map/*map/struct/*struct. -func (m *Model) Fields(fieldNamesOrMapStruct ...interface{}) *Model { - length := len(fieldNamesOrMapStruct) - if length == 0 { - return m - } - switch { - // String slice. - case length >= 2: - model := m.getModel() - model.fields = gstr.Join(m.mappingAndFilterToTableFields(gconv.Strings(fieldNamesOrMapStruct)), ",") - return model - // It need type asserting. - case length == 1: - model := m.getModel() - switch r := fieldNamesOrMapStruct[0].(type) { - case string: - model.fields = gstr.Join(m.mappingAndFilterToTableFields([]string{r}), ",") - case []string: - model.fields = gstr.Join(m.mappingAndFilterToTableFields(r), ",") - default: - model.fields = gstr.Join(m.mappingAndFilterToTableFields(gutil.Keys(r)), ",") - } - 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. -// The parameter <fieldNamesOrMapStruct> can be type of string/map/*map/struct/*struct. -func (m *Model) FieldsEx(fieldNamesOrMapStruct ...interface{}) *Model { - length := len(fieldNamesOrMapStruct) - if length == 0 { - return m - } - model := m.getModel() - switch { - case length >= 2: - model.fieldsEx = gstr.Join(m.mappingAndFilterToTableFields(gconv.Strings(fieldNamesOrMapStruct)), ",") - return model - case length == 1: - switch r := fieldNamesOrMapStruct[0].(type) { - case string: - model.fieldsEx = gstr.Join(m.mappingAndFilterToTableFields([]string{r}), ",") - case []string: - model.fieldsEx = gstr.Join(m.mappingAndFilterToTableFields(r), ",") - default: - model.fieldsEx = gstr.Join(m.mappingAndFilterToTableFields(gutil.Keys(r)), ",") - } - return model - } - return m -} - +// FieldsStr retrieves and returns all fields from the table, joined with char ','. +// The optional parameter `prefix` specifies the prefix for each field, eg: FieldsStr("u."). // Deprecated, use GetFieldsStr instead. -// This function name confuses the user that it was a chaining function. func (m *Model) FieldsStr(prefix ...string) string { return m.GetFieldsStr(prefix...) } -// FieldsStr retrieves and returns all fields from the table, joined with char ','. -// The optional parameter <prefix> specifies the prefix for each field, eg: FieldsStr("u."). +// GetFieldsStr retrieves and returns all fields from the table, joined with char ','. +// The optional parameter `prefix` specifies the prefix for each field, eg: GetFieldsStr("u."). func (m *Model) GetFieldsStr(prefix ...string) string { prefixStr := "" if len(prefix) > 0 { prefixStr = prefix[0] } - tableFields, err := m.db.TableFields(m.tables) + tableFields, err := m.TableFields(m.tablesInit) if err != nil { panic(err) } @@ -112,26 +192,29 @@ func (m *Model) GetFieldsStr(prefix ...string) string { } newFields += prefixStr + k } - newFields = m.db.QuoteString(newFields) + newFields = m.db.GetCore().QuoteString(newFields) return newFields } +// FieldsExStr retrieves and returns fields which are not in parameter `fields` from the table, +// joined with char ','. +// The parameter `fields` specifies the fields that are excluded. +// The optional parameter `prefix` specifies the prefix for each field, eg: FieldsExStr("id", "u."). // Deprecated, use GetFieldsExStr instead. -// This function name confuses the user that it was a chaining function. func (m *Model) FieldsExStr(fields string, prefix ...string) string { return m.GetFieldsExStr(fields, prefix...) } -// FieldsExStr retrieves and returns fields which are not in parameter <fields> from the table, +// GetFieldsExStr retrieves and returns fields which are not in parameter `fields` from the table, // joined with char ','. -// The parameter <fields> specifies the fields that are excluded. -// The optional parameter <prefix> specifies the prefix for each field, eg: FieldsExStr("id", "u."). +// 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) GetFieldsExStr(fields string, prefix ...string) string { prefixStr := "" if len(prefix) > 0 { prefixStr = prefix[0] } - tableFields, err := m.db.TableFields(m.tables) + tableFields, err := m.TableFields(m.tablesInit) if err != nil { panic(err) } @@ -153,13 +236,13 @@ func (m *Model) GetFieldsExStr(fields string, prefix ...string) string { } newFields += prefixStr + k } - newFields = m.db.QuoteString(newFields) + newFields = m.db.GetCore().QuoteString(newFields) return newFields } // HasField determine whether the field exists in the table. func (m *Model) HasField(field string) (bool, error) { - tableFields, err := m.db.TableFields(m.tables) + tableFields, err := m.TableFields(m.tablesInit) if err != nil { return false, err } diff --git a/database/gdb/gdb_model_insert.go b/database/gdb/gdb_model_insert.go index 41562e665..78099b4e6 100644 --- a/database/gdb/gdb_model_insert.go +++ b/database/gdb/gdb_model_insert.go @@ -1,4 +1,4 @@ -// Copyright GoFrame Author(https://github.com/gogf/gf). All Rights Reserved. +// Copyright GoFrame Author(https://goframe.org). All Rights Reserved. // // This Source Code Form is subject to the terms of the MIT License. // If a copy of the MIT was not distributed with this file, @@ -8,12 +8,15 @@ package gdb import ( "database/sql" + "github.com/gogf/gf/container/gset" + "github.com/gogf/gf/errors/gcode" + "reflect" + "github.com/gogf/gf/errors/gerror" "github.com/gogf/gf/os/gtime" "github.com/gogf/gf/text/gstr" "github.com/gogf/gf/util/gconv" "github.com/gogf/gf/util/gutil" - "reflect" ) // Batch sets the batch operation number for the model. @@ -24,7 +27,7 @@ func (m *Model) Batch(batch int) *Model { } // Data sets the operation data for the model. -// The parameter <data> can be type of string/map/gmap/slice/struct/*struct, etc. +// The parameter `data` can be type of string/map/gmap/slice/struct/*struct, etc. // Note that, it uses shallow value copying for `data` if `data` is type of map/slice // to avoid changing it inside function. // Eg: @@ -50,16 +53,20 @@ func (m *Model) Data(data ...interface{}) *Model { switch params := data[0].(type) { case Result: model.data = params.List() + case Record: model.data = params.Map() + case List: list := make(List, len(params)) for k, v := range params { list[k] = gutil.MapCopy(v) } model.data = list + case Map: model.data = gutil.MapCopy(params) + default: var ( rv = reflect.ValueOf(params) @@ -76,8 +83,10 @@ func (m *Model) Data(data ...interface{}) *Model { list[i] = ConvertDataForTableRecord(rv.Index(i).Interface()) } model.data = list + case reflect.Map: model.data = ConvertDataForTableRecord(data[0]) + case reflect.Struct: if v, ok := data[0].(apiInterfaces); ok { var ( @@ -91,6 +100,7 @@ func (m *Model) Data(data ...interface{}) *Model { } else { model.data = ConvertDataForTableRecord(data[0]) } + default: model.data = data[0] } @@ -99,38 +109,92 @@ func (m *Model) Data(data ...interface{}) *Model { return model } +// OnDuplicate sets the operations when columns conflicts occurs. +// In MySQL, this is used for "ON DUPLICATE KEY UPDATE" statement. +// The parameter `onDuplicate` can be type of string/Raw/*Raw/map/slice. +// Example: +// OnDuplicate("nickname, age") +// OnDuplicate("nickname", "age") +// OnDuplicate(g.Map{ +// "nickname": gdb.Raw("CONCAT('name_', VALUES(`nickname`))"), +// }) +// OnDuplicate(g.Map{ +// "nickname": "passport", +// }) +func (m *Model) OnDuplicate(onDuplicate ...interface{}) *Model { + model := m.getModel() + if len(onDuplicate) > 1 { + model.onDuplicate = onDuplicate + } else { + model.onDuplicate = onDuplicate[0] + } + return model +} + +// OnDuplicateEx sets the excluding columns for operations when columns conflicts occurs. +// In MySQL, this is used for "ON DUPLICATE KEY UPDATE" statement. +// The parameter `onDuplicateEx` can be type of string/map/slice. +// Example: +// OnDuplicateEx("passport, password") +// OnDuplicateEx("passport", "password") +// OnDuplicateEx(g.Map{ +// "passport": "", +// "password": "", +// }) +func (m *Model) OnDuplicateEx(onDuplicateEx ...interface{}) *Model { + model := m.getModel() + if len(onDuplicateEx) > 1 { + model.onDuplicateEx = onDuplicateEx + } else { + model.onDuplicateEx = onDuplicateEx[0] + } + return model +} + // Insert does "INSERT INTO ..." statement for the model. -// The optional parameter <data> is the same as the parameter of Model.Data function, +// The optional parameter `data` is the same as the parameter of Model.Data function, // see Model.Data. func (m *Model) Insert(data ...interface{}) (result sql.Result, err error) { if len(data) > 0 { return m.Data(data...).Insert() } - return m.doInsertWithOption(insertOptionDefault, data...) + return m.doInsertWithOption(insertOptionDefault) +} + +// InsertAndGetId performs action Insert and returns the last insert id that automatically generated. +func (m *Model) InsertAndGetId(data ...interface{}) (lastInsertId int64, err error) { + if len(data) > 0 { + return m.Data(data...).InsertAndGetId() + } + result, err := m.doInsertWithOption(insertOptionDefault) + if err != nil { + return 0, err + } + return result.LastInsertId() } // InsertIgnore does "INSERT IGNORE INTO ..." statement for the model. -// The optional parameter <data> is the same as the parameter of Model.Data function, +// The optional parameter `data` is the same as the parameter of Model.Data function, // see Model.Data. func (m *Model) InsertIgnore(data ...interface{}) (result sql.Result, err error) { if len(data) > 0 { return m.Data(data...).InsertIgnore() } - return m.doInsertWithOption(insertOptionIgnore, data...) + return m.doInsertWithOption(insertOptionIgnore) } // Replace does "REPLACE INTO ..." statement for the model. -// The optional parameter <data> is the same as the parameter of Model.Data function, +// The optional parameter `data` is the same as the parameter of Model.Data function, // see Model.Data. func (m *Model) Replace(data ...interface{}) (result sql.Result, err error) { if len(data) > 0 { return m.Data(data...).Replace() } - return m.doInsertWithOption(insertOptionReplace, data...) + return m.doInsertWithOption(insertOptionReplace) } // Save does "INSERT INTO ... ON DUPLICATE KEY UPDATE..." statement for the model. -// The optional parameter <data> is the same as the parameter of Model.Data function, +// The optional parameter `data` is the same as the parameter of Model.Data function, // see Model.Data. // // It updates the record if there's primary or unique index in the saving data, @@ -139,78 +203,226 @@ func (m *Model) Save(data ...interface{}) (result sql.Result, err error) { if len(data) > 0 { return m.Data(data...).Save() } - return m.doInsertWithOption(insertOptionSave, data...) + return m.doInsertWithOption(insertOptionSave) } // doInsertWithOption inserts data with option parameter. -func (m *Model) doInsertWithOption(option int, data ...interface{}) (result sql.Result, err error) { +func (m *Model) doInsertWithOption(insertOption int) (result sql.Result, err error) { defer func() { if err == nil { m.checkAndRemoveCache() } }() if m.data == nil { - return nil, gerror.New("inserting into table with empty data") + return nil, gerror.NewCode(gcode.CodeMissingParameter, "inserting into table with empty data") } var ( + list List nowString = gtime.Now().String() fieldNameCreate = m.getSoftFieldNameCreated() fieldNameUpdate = m.getSoftFieldNameUpdated() - fieldNameDelete = m.getSoftFieldNameDeleted() ) - // Batch operation. - if list, ok := m.data.(List); ok { - batch := defaultBatchNumber - if m.batch > 0 { - batch = m.batch - } - // Automatic handling for creating/updating time. - if !m.unscoped && (fieldNameCreate != "" || fieldNameUpdate != "") { - for k, v := range list { - gutil.MapDelete(v, fieldNameCreate, fieldNameUpdate, fieldNameDelete) - if fieldNameCreate != "" { - v[fieldNameCreate] = nowString - } - if fieldNameUpdate != "" { - v[fieldNameUpdate] = nowString - } - list[k] = v - } - } - newData, err := m.filterDataForInsertOrUpdate(list) - if err != nil { - return nil, err - } - return m.db.DoBatchInsert( - m.getLink(true), - m.tables, - newData, - option, - batch, - ) + newData, err := m.filterDataForInsertOrUpdate(m.data) + if err != nil { + return nil, err } - // Single operation. - if data, ok := m.data.(Map); ok { - // Automatic handling for creating/updating time. - if !m.unscoped && (fieldNameCreate != "" || fieldNameUpdate != "") { - gutil.MapDelete(data, fieldNameCreate, fieldNameUpdate, fieldNameDelete) + + // It converts any data to List type for inserting. + switch value := newData.(type) { + case Result: + list = value.List() + + case Record: + list = List{value.Map()} + + case List: + list = value + for i, v := range list { + list[i] = ConvertDataForTableRecord(v) + } + + case Map: + list = List{ConvertDataForTableRecord(value)} + + default: + var ( + rv = reflect.ValueOf(newData) + kind = rv.Kind() + ) + if kind == reflect.Ptr { + rv = rv.Elem() + kind = rv.Kind() + } + switch kind { + // If it's slice type, it then converts it to List type. + case reflect.Slice, reflect.Array: + list = make(List, rv.Len()) + for i := 0; i < rv.Len(); i++ { + list[i] = ConvertDataForTableRecord(rv.Index(i).Interface()) + } + + case reflect.Map: + list = List{ConvertDataForTableRecord(value)} + + case reflect.Struct: + if v, ok := value.(apiInterfaces); ok { + var ( + array = v.Interfaces() + ) + list = make(List, len(array)) + for i := 0; i < len(array); i++ { + list[i] = ConvertDataForTableRecord(array[i]) + } + } else { + list = List{ConvertDataForTableRecord(value)} + } + + default: + return result, gerror.NewCodef(gcode.CodeInvalidParameter, "unsupported list type:%v", kind) + } + } + + if len(list) < 1 { + return result, gerror.NewCode(gcode.CodeMissingParameter, "data list cannot be empty") + } + + // Automatic handling for creating/updating time. + if !m.unscoped && (fieldNameCreate != "" || fieldNameUpdate != "") { + for k, v := range list { if fieldNameCreate != "" { - data[fieldNameCreate] = nowString + v[fieldNameCreate] = nowString } if fieldNameUpdate != "" { - data[fieldNameUpdate] = nowString + v[fieldNameUpdate] = nowString + } + list[k] = v + } + } + // Format DoInsertOption, especially for "ON DUPLICATE KEY UPDATE" statement. + columnNames := make([]string, 0, len(list[0])) + for k, _ := range list[0] { + columnNames = append(columnNames, k) + } + doInsertOption, err := m.formatDoInsertOption(insertOption, columnNames) + if err != nil { + return result, err + } + + return m.db.DoInsert(m.GetCtx(), m.getLink(true), m.tables, list, doInsertOption) +} + +func (m *Model) formatDoInsertOption(insertOption int, columnNames []string) (option DoInsertOption, err error) { + option = DoInsertOption{ + InsertOption: insertOption, + BatchCount: m.getBatch(), + } + if insertOption == insertOptionSave { + onDuplicateExKeys, err := m.formatOnDuplicateExKeys(m.onDuplicateEx) + if err != nil { + return option, err + } + var ( + onDuplicateExKeySet = gset.NewStrSetFrom(onDuplicateExKeys) + ) + if m.onDuplicate != nil { + switch m.onDuplicate.(type) { + case Raw, *Raw: + option.OnDuplicateStr = gconv.String(m.onDuplicate) + + default: + var ( + reflectValue = reflect.ValueOf(m.onDuplicate) + reflectKind = reflectValue.Kind() + ) + for reflectKind == reflect.Ptr { + reflectValue = reflectValue.Elem() + reflectKind = reflectValue.Kind() + } + switch reflectKind { + case reflect.String: + option.OnDuplicateMap = make(map[string]interface{}) + for _, v := range gstr.SplitAndTrim(reflectValue.String(), ",") { + if onDuplicateExKeySet.Contains(v) { + continue + } + option.OnDuplicateMap[v] = v + } + + case reflect.Map: + option.OnDuplicateMap = make(map[string]interface{}) + for k, v := range gconv.Map(m.onDuplicate) { + if onDuplicateExKeySet.Contains(k) { + continue + } + option.OnDuplicateMap[k] = v + } + + case reflect.Slice, reflect.Array: + option.OnDuplicateMap = make(map[string]interface{}) + for _, v := range gconv.Strings(m.onDuplicate) { + if onDuplicateExKeySet.Contains(v) { + continue + } + option.OnDuplicateMap[v] = v + } + + default: + return option, gerror.NewCodef( + gcode.CodeInvalidParameter, + `unsupported OnDuplicate parameter type "%s"`, + reflect.TypeOf(m.onDuplicate), + ) + } + } + } else if onDuplicateExKeySet.Size() > 0 { + option.OnDuplicateMap = make(map[string]interface{}) + for _, v := range columnNames { + if onDuplicateExKeySet.Contains(v) { + continue + } + option.OnDuplicateMap[v] = v } } - newData, err := m.filterDataForInsertOrUpdate(data) - if err != nil { - return nil, err - } - return m.db.DoInsert( - m.getLink(true), - m.tables, - newData, - option, + } + return +} + +func (m *Model) formatOnDuplicateExKeys(onDuplicateEx interface{}) ([]string, error) { + if onDuplicateEx == nil { + return nil, nil + } + + var ( + reflectValue = reflect.ValueOf(onDuplicateEx) + reflectKind = reflectValue.Kind() + ) + for reflectKind == reflect.Ptr { + reflectValue = reflectValue.Elem() + reflectKind = reflectValue.Kind() + } + switch reflectKind { + case reflect.String: + return gstr.SplitAndTrim(reflectValue.String(), ","), nil + + case reflect.Map: + return gutil.Keys(onDuplicateEx), nil + + case reflect.Slice, reflect.Array: + return gconv.Strings(onDuplicateEx), nil + + default: + return nil, gerror.NewCodef( + gcode.CodeInvalidParameter, + `unsupported OnDuplicateEx parameter type "%s"`, + reflect.TypeOf(onDuplicateEx), ) } - return nil, gerror.New("inserting into table with invalid data type") +} + +func (m *Model) getBatch() int { + batch := defaultBatchNumber + if m.batch > 0 { + batch = m.batch + } + return batch } diff --git a/database/gdb/gdb_model_join.go b/database/gdb/gdb_model_join.go index aa7e12a52..faf993d0e 100644 --- a/database/gdb/gdb_model_join.go +++ b/database/gdb/gdb_model_join.go @@ -1,4 +1,4 @@ -// Copyright GoFrame Author(https://github.com/gogf/gf). All Rights Reserved. +// Copyright GoFrame Author(https://goframe.org). All Rights Reserved. // // This Source Code Form is subject to the terms of the MIT License. // If a copy of the MIT was not distributed with this file, @@ -8,6 +8,7 @@ package gdb import ( "fmt" + "github.com/gogf/gf/text/gstr" ) @@ -23,7 +24,7 @@ func isSubQuery(s string) bool { } // LeftJoin does "LEFT JOIN ... ON ..." statement on the model. -// The parameter <table> can be joined table and its joined condition, +// The parameter `table` can be joined table and its joined condition, // and also with its alias name, like: // Table("user").LeftJoin("user_detail", "user_detail.uid=user.uid") // Table("user", "u").LeftJoin("user_detail", "ud", "ud.uid=u.uid") @@ -33,7 +34,7 @@ func (m *Model) LeftJoin(table ...string) *Model { } // RightJoin does "RIGHT JOIN ... ON ..." statement on the model. -// The parameter <table> can be joined table and its joined condition, +// The parameter `table` can be joined table and its joined condition, // and also with its alias name, like: // Table("user").RightJoin("user_detail", "user_detail.uid=user.uid") // Table("user", "u").RightJoin("user_detail", "ud", "ud.uid=u.uid") @@ -43,7 +44,7 @@ func (m *Model) RightJoin(table ...string) *Model { } // InnerJoin does "INNER JOIN ... ON ..." statement on the model. -// The parameter <table> can be joined table and its joined condition, +// The parameter `table` can be joined table and its joined condition, // and also with its alias name, like: // Table("user").InnerJoin("user_detail", "user_detail.uid=user.uid") // Table("user", "u").InnerJoin("user_detail", "ud", "ud.uid=u.uid") @@ -53,11 +54,11 @@ func (m *Model) InnerJoin(table ...string) *Model { } // doJoin does "LEFT/RIGHT/INNER JOIN ... ON ..." statement on the model. -// The parameter <table> can be joined table and its joined condition, +// The parameter `table` can be joined table and its joined condition, // and also with its alias name, like: -// Table("user").InnerJoin("user_detail", "user_detail.uid=user.uid") -// Table("user", "u").InnerJoin("user_detail", "ud", "ud.uid=u.uid") -// Table("user", "u").InnerJoin("SELECT xxx FROM xxx AS a", "a.uid=u.uid") +// Model("user").InnerJoin("user_detail", "user_detail.uid=user.uid") +// Model("user", "u").InnerJoin("user_detail", "ud", "ud.uid=u.uid") +// Model("user", "u").InnerJoin("SELECT xxx FROM xxx AS a", "a.uid=u.uid") // Related issues: // https://github.com/gogf/gf/issues/1024 func (m *Model) doJoin(operator string, table ...string) *Model { @@ -72,13 +73,13 @@ func (m *Model) doJoin(operator string, table ...string) *Model { joinStr = "(" + joinStr + ")" } } else { - joinStr = m.db.QuotePrefixTableName(table[0]) + joinStr = m.db.GetCore().QuotePrefixTableName(table[0]) } } if len(table) > 2 { model.tables += fmt.Sprintf( " %s JOIN %s AS %s ON (%s)", - operator, joinStr, m.db.QuoteWord(table[1]), table[2], + operator, joinStr, m.db.GetCore().QuoteWord(table[1]), table[2], ) } else if len(table) == 2 { model.tables += fmt.Sprintf( diff --git a/database/gdb/gdb_model_lock.go b/database/gdb/gdb_model_lock.go index 79fdf975b..a207a126e 100644 --- a/database/gdb/gdb_model_lock.go +++ b/database/gdb/gdb_model_lock.go @@ -1,4 +1,4 @@ -// Copyright GoFrame Author(https://github.com/gogf/gf). All Rights Reserved. +// Copyright GoFrame Author(https://goframe.org). All Rights Reserved. // // This Source Code Form is subject to the terms of the MIT License. // If a copy of the MIT was not distributed with this file, diff --git a/database/gdb/gdb_model_option.go b/database/gdb/gdb_model_option.go index 4634a75a1..68991fce4 100644 --- a/database/gdb/gdb_model_option.go +++ b/database/gdb/gdb_model_option.go @@ -1,4 +1,4 @@ -// Copyright GoFrame Author(https://github.com/gogf/gf). All Rights Reserved. +// Copyright GoFrame Author(https://goframe.org). All Rights Reserved. // // This Source Code Form is subject to the terms of the MIT License. // If a copy of the MIT was not distributed with this file, @@ -6,22 +6,67 @@ package gdb +const ( + optionOmitNil = optionOmitNilWhere | optionOmitNilData + optionOmitEmpty = optionOmitEmptyWhere | optionOmitEmptyData + optionOmitEmptyWhere = 1 << iota // 8 + optionOmitEmptyData // 16 + optionOmitNilWhere // 32 + optionOmitNilData // 64 +) + // Option adds extra operation option for the model. +// Deprecated, use separate operations instead. func (m *Model) Option(option int) *Model { model := m.getModel() model.option = model.option | option return model } -// OptionOmitEmpty sets OPTION_OMITEMPTY option for the model, which automatically filers -// the data and where attributes for empty values. -// Deprecated, use OmitEmpty instead. -func (m *Model) OptionOmitEmpty() *Model { - return m.Option(OPTION_OMITEMPTY) +// OmitEmpty sets optionOmitEmpty option for the model, which automatically filers +// the data and where parameters for `empty` values. +func (m *Model) OmitEmpty() *Model { + model := m.getModel() + model.option = model.option | optionOmitEmpty + return model } -// OmitEmpty sets OPTION_OMITEMPTY option for the model, which automatically filers -// the data and where attributes for empty values. -func (m *Model) OmitEmpty() *Model { - return m.Option(OPTION_OMITEMPTY) +// OmitEmptyWhere sets optionOmitEmptyWhere option for the model, which automatically filers +// the Where/Having parameters for `empty` values. +func (m *Model) OmitEmptyWhere() *Model { + model := m.getModel() + model.option = model.option | optionOmitEmptyWhere + return model +} + +// OmitEmptyData sets optionOmitEmptyData option for the model, which automatically filers +// the Data parameters for `empty` values. +func (m *Model) OmitEmptyData() *Model { + model := m.getModel() + model.option = model.option | optionOmitEmptyData + return model +} + +// OmitNil sets optionOmitNil option for the model, which automatically filers +// the data and where parameters for `nil` values. +func (m *Model) OmitNil() *Model { + model := m.getModel() + model.option = model.option | optionOmitNil + return model +} + +// OmitNilWhere sets optionOmitNilWhere option for the model, which automatically filers +// the Where/Having parameters for `nil` values. +func (m *Model) OmitNilWhere() *Model { + model := m.getModel() + model.option = model.option | optionOmitNilWhere + return model +} + +// OmitNilData sets optionOmitNilData option for the model, which automatically filers +// the Data parameters for `nil` values. +func (m *Model) OmitNilData() *Model { + model := m.getModel() + model.option = model.option | optionOmitNilData + return model } diff --git a/database/gdb/gdb_model_select.go b/database/gdb/gdb_model_select.go index 48ec07c05..eefbec99b 100644 --- a/database/gdb/gdb_model_select.go +++ b/database/gdb/gdb_model_select.go @@ -1,4 +1,4 @@ -// Copyright GoFrame Author(https://github.com/gogf/gf). All Rights Reserved. +// Copyright GoFrame Author(https://goframe.org). All Rights Reserved. // // This Source Code Form is subject to the terms of the MIT License. // If a copy of the MIT was not distributed with this file, @@ -8,17 +8,21 @@ package gdb import ( "fmt" + "github.com/gogf/gf/errors/gcode" + "github.com/gogf/gf/errors/gerror" + "reflect" + "github.com/gogf/gf/container/gset" "github.com/gogf/gf/container/gvar" + "github.com/gogf/gf/internal/intlog" "github.com/gogf/gf/internal/json" "github.com/gogf/gf/text/gstr" "github.com/gogf/gf/util/gconv" - "reflect" ) // Select is alias of Model.All. // See Model.All. -// Deprecated. +// Deprecated, use All instead. func (m *Model) Select(where ...interface{}) (Result, error) { return m.All(where...) } @@ -27,7 +31,7 @@ func (m *Model) Select(where ...interface{}) (Result, error) { // It retrieves the records from table and returns the result as slice type. // It returns nil if there's no record retrieved with the given conditions from table. // -// The optional parameter <where> is the same as the parameter of Model.Where function, +// The optional parameter `where` is the same as the parameter of Model.Where function, // see Model.Where. func (m *Model) All(where ...interface{}) (Result, error) { return m.doGetAll(false, where...) @@ -37,37 +41,15 @@ func (m *Model) All(where ...interface{}) (Result, error) { // It retrieves the records from table and returns the result as slice type. // It returns nil if there's no record retrieved with the given conditions from table. // -// The parameter <limit1> specifies whether limits querying only one record if m.limit is not set. -// The optional parameter <where> is the same as the parameter of Model.Where function, +// The parameter `limit1` specifies whether limits querying only one record if m.limit is not set. +// The optional parameter `where` is the same as the parameter of Model.Where function, // see Model.Where. func (m *Model) doGetAll(limit1 bool, where ...interface{}) (Result, error) { if len(where) > 0 { return m.Where(where[0], where[1:]...).All() } - var ( - softDeletingCondition = m.getConditionForSoftDeleting() - conditionWhere, conditionExtra, conditionArgs = m.formatCondition(limit1, false) - ) - if !m.unscoped && softDeletingCondition != "" { - if conditionWhere == "" { - conditionWhere = " WHERE " - } else { - conditionWhere += " AND " - } - 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.getFieldsFiltered(), - m.tables, - conditionWhere+conditionExtra, - ), - conditionArgs..., - ) + sqlWithHolder, holderArgs := m.getFormattedSqlAndArgs(queryTypeNormal, limit1) + return m.doGetAllBySql(sqlWithHolder, holderArgs...) } // getFieldsFiltered checks the fields and fieldsEx attributes, filters and returns the fields that will @@ -76,7 +58,7 @@ func (m *Model) getFieldsFiltered() string { if m.fieldsEx == "" { // No filtering. if !gstr.Contains(m.fields, ".") && !gstr.Contains(m.fields, " ") { - return m.db.QuoteString(m.fields) + return m.db.GetCore().QuoteString(m.fields) } return m.fields } @@ -95,7 +77,7 @@ func (m *Model) getFieldsFiltered() string { panic("function FieldsEx supports only single table operations") } // Filter table fields with fieldEx. - tableFields, err := m.db.TableFields(m.tables) + tableFields, err := m.TableFields(m.tablesInit) if err != nil { panic(err) } @@ -115,32 +97,32 @@ func (m *Model) getFieldsFiltered() string { if len(newFields) > 0 { newFields += "," } - newFields += m.db.QuoteWord(k) + newFields += m.db.GetCore().QuoteWord(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) { +// Chunk iterates the query result with given `size` and `handler` function. +func (m *Model) Chunk(size int, handler ChunkHandler) { page := m.start if page <= 0 { page = 1 } model := m for { - model = model.Page(page, limit) + model = model.Page(page, size) data, err := model.All() if err != nil { - callback(nil, err) + handler(nil, err) break } if len(data) == 0 { break } - if callback(data, err) == false { + if handler(data, err) == false { break } - if len(data) < limit { + if len(data) < size { break } page++ @@ -150,7 +132,7 @@ func (m *Model) Chunk(limit int, callback func(result Result, err error) bool) { // One retrieves one record from table and returns the result as map type. // It returns nil if there's no record retrieved with the given conditions from table. // -// The optional parameter <where> is the same as the parameter of Model.Where function, +// The optional parameter `where` is the same as the parameter of Model.Where function, // see Model.Where. func (m *Model) One(where ...interface{}) (Record, error) { if len(where) > 0 { @@ -169,7 +151,7 @@ func (m *Model) One(where ...interface{}) (Record, error) { // Value retrieves a specified record value from table and returns the result as interface type. // It returns nil if there's no record found with the given conditions from table. // -// If the optional parameter <fieldsAndWhere> is given, the fieldsAndWhere[0] is the selected fields +// If the optional parameter `fieldsAndWhere` is given, the fieldsAndWhere[0] is the selected fields // and fieldsAndWhere[1:] is treated as where condition fields. // Also see Model.Fields and Model.Where functions. func (m *Model) Value(fieldsAndWhere ...interface{}) (Value, error) { @@ -193,9 +175,9 @@ func (m *Model) Value(fieldsAndWhere ...interface{}) (Value, error) { } // Array queries and returns data values as slice from database. -// Note that if there're multiple columns in the result, it returns just one column values randomly. +// Note that if there are multiple columns in the result, it returns just one column values randomly. // -// If the optional parameter <fieldsAndWhere> is given, the fieldsAndWhere[0] is the selected fields +// If the optional parameter `fieldsAndWhere` is given, the fieldsAndWhere[0] is the selected fields // and fieldsAndWhere[1:] is treated as where condition fields. // Also see Model.Fields and Model.Where functions. func (m *Model) Array(fieldsAndWhere ...interface{}) ([]Value, error) { @@ -216,91 +198,149 @@ func (m *Model) Array(fieldsAndWhere ...interface{}) ([]Value, error) { } // Struct retrieves one record from table and converts it into given struct. -// The parameter <pointer> should be type of *struct/**struct. If type **struct is given, +// The parameter `pointer` should be type of *struct/**struct. If type **struct is given, // it can create the struct internally during converting. // -// The optional parameter <where> is the same as the parameter of Model.Where function, +// Deprecated, use Scan instead. +func (m *Model) Struct(pointer interface{}, where ...interface{}) error { + return m.doStruct(pointer, where...) +} + +// Struct retrieves one record from table and converts it into given struct. +// The parameter `pointer` should be type of *struct/**struct. If type **struct is given, +// it can create the struct internally during converting. +// +// The optional parameter `where` is the same as the parameter of Model.Where function, // see Model.Where. // -// Note that it returns sql.ErrNoRows if there's no record retrieved with the given conditions -// from table and <pointer> is not nil. +// Note that it returns sql.ErrNoRows if the given parameter `pointer` pointed to a variable that has +// default value and there's no record retrieved with the given conditions from table. // -// Eg: +// Example: // user := new(User) -// err := db.Table("user").Where("id", 1).Struct(user) +// err := db.Model("user").Where("id", 1).Scan(user) // // user := (*User)(nil) -// err := db.Table("user").Where("id", 1).Struct(&user) -func (m *Model) Struct(pointer interface{}, where ...interface{}) error { - one, err := m.One(where...) +// err := db.Model("user").Where("id", 1).Scan(&user) +func (m *Model) doStruct(pointer interface{}, where ...interface{}) error { + model := m + // Auto selecting fields by struct attributes. + if model.fieldsEx == "" && (model.fields == "" || model.fields == "*") { + model = m.Fields(pointer) + } + one, err := model.One(where...) if err != nil { return err } - return one.Struct(pointer) + if err = one.Struct(pointer); err != nil { + return err + } + return model.doWithScanStruct(pointer) } // Structs retrieves records from table and converts them into given struct slice. -// The parameter <pointer> should be type of *[]struct/*[]*struct. It can create and fill the struct +// The parameter `pointer` should be type of *[]struct/*[]*struct. It can create and fill the struct // slice internally during converting. // -// The optional parameter <where> is the same as the parameter of Model.Where function, +// Deprecated, use Scan instead. +func (m *Model) Structs(pointer interface{}, where ...interface{}) error { + return m.doStructs(pointer, where...) +} + +// Structs retrieves records from table and converts them into given struct slice. +// The parameter `pointer` should be type of *[]struct/*[]*struct. It can create and fill the struct +// slice internally during converting. +// +// The optional parameter `where` is the same as the parameter of Model.Where function, // see Model.Where. // -// Note that it returns sql.ErrNoRows if there's no record retrieved with the given conditions -// from table and <pointer> is not empty. +// Note that it returns sql.ErrNoRows if the given parameter `pointer` pointed to a variable that has +// default value and there's no record retrieved with the given conditions from table. // -// Eg: +// Example: // users := ([]User)(nil) -// err := db.Table("user").Structs(&users) +// err := db.Model("user").Scan(&users) // // users := ([]*User)(nil) -// err := db.Table("user").Structs(&users) -func (m *Model) Structs(pointer interface{}, where ...interface{}) error { - all, err := m.All(where...) +// err := db.Model("user").Scan(&users) +func (m *Model) doStructs(pointer interface{}, where ...interface{}) error { + model := m + // Auto selecting fields by struct attributes. + if model.fieldsEx == "" && (model.fields == "" || model.fields == "*") { + model = m.Fields( + reflect.New( + reflect.ValueOf(pointer).Elem().Type().Elem(), + ).Interface(), + ) + } + all, err := model.All(where...) if err != nil { return err } - return all.Structs(pointer) + if err = all.Structs(pointer); err != nil { + return err + } + return model.doWithScanStructs(pointer) } -// Scan automatically calls Struct or Structs function according to the type of parameter <pointer>. -// It calls function Struct if <pointer> is type of *struct/**struct. -// It calls function Structs if <pointer> is type of *[]struct/*[]*struct. +// Scan automatically calls Struct or Structs function according to the type of parameter `pointer`. +// It calls function doStruct if `pointer` is type of *struct/**struct. +// It calls function doStructs if `pointer` is type of *[]struct/*[]*struct. // -// The optional parameter <where> is the same as the parameter of Model.Where function, -// see Model.Where. +// The optional parameter `where` is the same as the parameter of Model.Where function, see Model.Where. // -// Note that it returns sql.ErrNoRows if there's no record retrieved with the given conditions -// from table. +// Note that it returns sql.ErrNoRows if the given parameter `pointer` pointed to a variable that has +// default value and there's no record retrieved with the given conditions from table. // -// Eg: +// Example: // user := new(User) -// err := db.Table("user").Where("id", 1).Struct(user) +// err := db.Model("user").Where("id", 1).Scan(user) // // user := (*User)(nil) -// err := db.Table("user").Where("id", 1).Struct(&user) +// err := db.Model("user").Where("id", 1).Scan(&user) // // users := ([]User)(nil) -// err := db.Table("user").Structs(&users) +// err := db.Model("user").Scan(&users) // // users := ([]*User)(nil) -// err := db.Table("user").Structs(&users) +// err := db.Model("user").Scan(&users) func (m *Model) Scan(pointer interface{}, where ...interface{}) error { - t := reflect.TypeOf(pointer) - k := t.Kind() - if k != reflect.Ptr { - return fmt.Errorf("params should be type of pointer, but got: %v", k) + var ( + reflectValue reflect.Value + reflectKind reflect.Kind + ) + if v, ok := pointer.(reflect.Value); ok { + reflectValue = v + } else { + reflectValue = reflect.ValueOf(pointer) } - switch t.Elem().Kind() { - case reflect.Array, reflect.Slice: - return m.Structs(pointer, where...) + + reflectKind = reflectValue.Kind() + if reflectKind != reflect.Ptr { + return gerror.NewCode(gcode.CodeInvalidParameter, `the parameter "pointer" for function Scan should type of pointer`) + } + for reflectKind == reflect.Ptr { + reflectValue = reflectValue.Elem() + reflectKind = reflectValue.Kind() + } + + switch reflectKind { + case reflect.Slice, reflect.Array: + return m.doStructs(pointer, where...) + + case reflect.Struct, reflect.Invalid: + return m.doStruct(pointer, where...) + default: - return m.Struct(pointer, where...) + return gerror.NewCode( + gcode.CodeInvalidParameter, + `element of parameter "pointer" for function Scan should type of struct/*struct/[]struct/[]*struct`, + ) } } -// ScanList converts <r> to struct slice which contains other complex struct attributes. -// Note that the parameter <listPointer> should be type of *[]struct/*[]*struct. +// ScanList converts `r` to struct slice which contains other complex struct attributes. +// Note that the parameter `listPointer` should be type of *[]struct/*[]*struct. // Usage example: // // type Entity struct { @@ -318,48 +358,28 @@ func (m *Model) Scan(pointer interface{}, where ...interface{}) error { // The parameters "User"/"UserDetail"/"UserScores" in the example codes specify the target attribute struct // that current result will be bound to. // The "uid" in the example codes is the table field name of the result, and the "Uid" is the relational -// struct attribute name. It automatically calculates the HasOne/HasMany relationship with given <relation> +// struct attribute name. It automatically calculates the HasOne/HasMany relationship with given `relation` // parameter. // See the example or unit testing cases for clear understanding for this function. func (m *Model) ScanList(listPointer interface{}, attributeName string, relation ...string) (err error) { - all, err := m.All() + result, err := m.All() if err != nil { return err } - return all.ScanList(listPointer, attributeName, relation...) + return doScanList(m, result, listPointer, attributeName, relation...) } // Count does "SELECT COUNT(x) FROM ..." statement for the model. -// The optional parameter <where> is the same as the parameter of Model.Where function, +// The optional parameter `where` is the same as the parameter of Model.Where function, // see Model.Where. func (m *Model) Count(where ...interface{}) (int, error) { if len(where) > 0 { return m.Where(where[0], where[1:]...).Count() } - countFields := "COUNT(1)" - if m.fields != "" && m.fields != "*" { - // DO NOT quote the m.fields here, in case of fields like: - // DISTINCT t.user_id uid - countFields = fmt.Sprintf(`COUNT(%s)`, m.fields) - } var ( - softDeletingCondition = m.getConditionForSoftDeleting() - conditionWhere, conditionExtra, conditionArgs = m.formatCondition(false, true) + sqlWithHolder, holderArgs = m.getFormattedSqlAndArgs(queryTypeCount, false) + list, err = m.doGetAllBySql(sqlWithHolder, holderArgs...) ) - if !m.unscoped && softDeletingCondition != "" { - if conditionWhere == "" { - conditionWhere = " WHERE " - } else { - conditionWhere += " AND " - } - conditionWhere += softDeletingCondition - } - - s := fmt.Sprintf("SELECT %s FROM %s%s", countFields, m.tables, conditionWhere+conditionExtra) - if len(m.groupBy) > 0 { - s = fmt.Sprintf("SELECT COUNT(1) FROM (%s) count_alias", s) - } - list, err := m.doGetAllBySql(s, conditionArgs...) if err != nil { return 0, err } @@ -371,6 +391,62 @@ func (m *Model) Count(where ...interface{}) (int, error) { return 0, nil } +// CountColumn does "SELECT COUNT(x) FROM ..." statement for the model. +func (m *Model) CountColumn(column string) (int, error) { + if len(column) == 0 { + return 0, nil + } + return m.Fields(column).Count() +} + +// Min does "SELECT MIN(x) FROM ..." statement for the model. +func (m *Model) Min(column string) (float64, error) { + if len(column) == 0 { + return 0, nil + } + value, err := m.Fields(fmt.Sprintf(`MIN(%s)`, m.db.GetCore().QuoteWord(column))).Value() + if err != nil { + return 0, err + } + return value.Float64(), err +} + +// Max does "SELECT MAX(x) FROM ..." statement for the model. +func (m *Model) Max(column string) (float64, error) { + if len(column) == 0 { + return 0, nil + } + value, err := m.Fields(fmt.Sprintf(`MAX(%s)`, m.db.GetCore().QuoteWord(column))).Value() + if err != nil { + return 0, err + } + return value.Float64(), err +} + +// Avg does "SELECT AVG(x) FROM ..." statement for the model. +func (m *Model) Avg(column string) (float64, error) { + if len(column) == 0 { + return 0, nil + } + value, err := m.Fields(fmt.Sprintf(`AVG(%s)`, m.db.GetCore().QuoteWord(column))).Value() + if err != nil { + return 0, err + } + return value.Float64(), err +} + +// Sum does "SELECT SUM(x) FROM ..." statement for the model. +func (m *Model) Sum(column string) (float64, error) { + if len(column) == 0 { + return 0, nil + } + value, err := m.Fields(fmt.Sprintf(`SUM(%s)`, m.db.GetCore().QuoteWord(column))).Value() + if err != nil { + return 0, err + } + return value.Float64(), err +} + // FindOne retrieves and returns a single Record by Model.WherePri and Model.One. // Also see Model.WherePri and Model.One. func (m *Model) FindOne(where ...interface{}) (Record, error) { @@ -402,7 +478,7 @@ func (m *Model) FindValue(fieldsAndWhere ...interface{}) (Value, error) { } // FindArray queries and returns data values as slice from database. -// Note that if there're multiple columns in the result, it returns just one column values randomly. +// Note that if there are multiple columns in the result, it returns just one column values randomly. // Also see Model.WherePri and Model.Value. func (m *Model) FindArray(fieldsAndWhere ...interface{}) ([]Value, error) { if len(fieldsAndWhere) >= 2 { @@ -432,10 +508,20 @@ func (m *Model) FindScan(pointer interface{}, where ...interface{}) error { return m.Scan(pointer) } +// Union does "(SELECT xxx FROM xxx) UNION (SELECT xxx FROM xxx) ..." statement for the model. +func (m *Model) Union(unions ...*Model) *Model { + return m.db.Union(unions...) +} + +// UnionAll does "(SELECT xxx FROM xxx) UNION ALL (SELECT xxx FROM xxx) ..." statement for the model. +func (m *Model) UnionAll(unions ...*Model) *Model { + return m.db.UnionAll(unions...) +} + // 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() + cacheObj := m.db.GetCache().Ctx(m.GetCtx()) // Retrieve from cache. if m.cacheEnabled && m.tx == nil { cacheKey = m.cacheName @@ -449,7 +535,7 @@ func (m *Model) doGetAllBySql(sql string, args ...interface{}) (result Result, e } else { // Other cache, it needs conversion. var result Result - if err = json.Unmarshal(v.Bytes(), &result); err != nil { + if err = json.UnmarshalUseNumber(v.Bytes(), &result); err != nil { return nil, err } else { return result, nil @@ -457,14 +543,69 @@ func (m *Model) doGetAllBySql(sql string, args ...interface{}) (result Result, e } } } - result, err = m.db.DoGetAll(m.getLink(false), sql, m.mergeArguments(args)...) + result, err = m.db.DoGetAll( + m.GetCtx(), m.getLink(false), sql, m.mergeArguments(args)..., + ) // Cache the result. if cacheKey != "" && err == nil { if m.cacheDuration < 0 { - cacheObj.Remove(cacheKey) + if _, err := cacheObj.Remove(cacheKey); err != nil { + intlog.Error(m.GetCtx(), err) + } } else { - cacheObj.Set(cacheKey, result, m.cacheDuration) + // In case of Cache Penetration. + if result == nil { + result = Result{} + } + if err := cacheObj.Set(cacheKey, result, m.cacheDuration); err != nil { + intlog.Error(m.GetCtx(), err) + } } } return result, err } + +func (m *Model) getFormattedSqlAndArgs(queryType int, limit1 bool) (sqlWithHolder string, holderArgs []interface{}) { + switch queryType { + case queryTypeCount: + countFields := "COUNT(1)" + if m.fields != "" && m.fields != "*" { + // DO NOT quote the m.fields here, in case of fields like: + // DISTINCT t.user_id uid + countFields = fmt.Sprintf(`COUNT(%s%s)`, m.distinct, m.fields) + } + // Raw SQL Model. + if m.rawSql != "" { + sqlWithHolder = fmt.Sprintf("SELECT %s FROM (%s) AS T", countFields, m.rawSql) + return sqlWithHolder, nil + } + conditionWhere, conditionExtra, conditionArgs := m.formatCondition(false, true) + sqlWithHolder = fmt.Sprintf("SELECT %s FROM %s%s", countFields, m.tables, conditionWhere+conditionExtra) + if len(m.groupBy) > 0 { + sqlWithHolder = fmt.Sprintf("SELECT COUNT(1) FROM (%s) count_alias", sqlWithHolder) + } + return sqlWithHolder, conditionArgs + + default: + conditionWhere, conditionExtra, conditionArgs := m.formatCondition(limit1, false) + // Raw SQL Model, especially for UNION/UNION ALL featured SQL. + if m.rawSql != "" { + sqlWithHolder = fmt.Sprintf( + "%s%s", + m.rawSql, + conditionWhere+conditionExtra, + ) + return sqlWithHolder, conditionArgs + } + // DO NOT quote the m.fields where, in case of fields like: + // DISTINCT t.user_id uid + sqlWithHolder = fmt.Sprintf( + "SELECT %s%s FROM %s%s", + m.distinct, + m.getFieldsFiltered(), + m.tables, + conditionWhere+conditionExtra, + ) + return sqlWithHolder, conditionArgs + } +} diff --git a/database/gdb/gdb_model_time.go b/database/gdb/gdb_model_time.go index ad594c230..0ca2a6877 100644 --- a/database/gdb/gdb_model_time.go +++ b/database/gdb/gdb_model_time.go @@ -1,4 +1,4 @@ -// Copyright GoFrame Author(https://github.com/gogf/gf). All Rights Reserved. +// Copyright GoFrame Author(https://goframe.org). All Rights Reserved. // // This Source Code Form is subject to the terms of the MIT License. // If a copy of the MIT was not distributed with this file, @@ -40,7 +40,7 @@ func (m *Model) getSoftFieldNameCreated(table ...string) string { if len(table) > 0 { tableName = table[0] } else { - tableName = m.getPrimaryTableName() + tableName = m.tablesInit } config := m.db.GetConfig() if config.CreatedAt != "" { @@ -61,7 +61,7 @@ func (m *Model) getSoftFieldNameUpdated(table ...string) (field string) { if len(table) > 0 { tableName = table[0] } else { - tableName = m.getPrimaryTableName() + tableName = m.tablesInit } config := m.db.GetConfig() if config.UpdatedAt != "" { @@ -82,7 +82,7 @@ func (m *Model) getSoftFieldNameDeleted(table ...string) (field string) { if len(table) > 0 { tableName = table[0] } else { - tableName = m.getPrimaryTableName() + tableName = m.tablesInit } config := m.db.GetConfig() if config.UpdatedAt != "" { @@ -93,7 +93,7 @@ func (m *Model) getSoftFieldNameDeleted(table ...string) (field string) { // getSoftFieldName retrieves and returns the field name of the table for possible key. func (m *Model) getSoftFieldName(table string, keys []string) (field string) { - fieldsMap, _ := m.db.TableFields(table) + fieldsMap, _ := m.TableFields(table) if len(fieldsMap) > 0 { for _, key := range keys { field, _ = gutil.MapPossibleItemByKey( @@ -140,7 +140,7 @@ func (m *Model) getConditionForSoftDeleting() string { } // Only one table. if fieldName := m.getSoftFieldNameDeleted(); fieldName != "" { - return fmt.Sprintf(`%s IS NULL`, m.db.QuoteWord(fieldName)) + return fmt.Sprintf(`%s IS NULL`, m.db.GetCore().QuoteWord(fieldName)) } return "" } @@ -163,21 +163,10 @@ func (m *Model) getConditionOfTableStringForSoftDeleting(s string) string { return "" } if len(array1) >= 3 { - return fmt.Sprintf(`%s.%s IS NULL`, m.db.QuoteWord(array1[2]), m.db.QuoteWord(field)) + return fmt.Sprintf(`%s.%s IS NULL`, m.db.GetCore().QuoteWord(array1[2]), m.db.GetCore().QuoteWord(field)) } if len(array1) >= 2 { - return fmt.Sprintf(`%s.%s IS NULL`, m.db.QuoteWord(array1[1]), m.db.QuoteWord(field)) + return fmt.Sprintf(`%s.%s IS NULL`, m.db.GetCore().QuoteWord(array1[1]), m.db.GetCore().QuoteWord(field)) } - return fmt.Sprintf(`%s.%s IS NULL`, m.db.QuoteWord(table), m.db.QuoteWord(field)) -} - -// getPrimaryTableName parses and returns the primary table name. -func (m *Model) getPrimaryTableName() string { - array1 := gstr.SplitAndTrim(m.tables, ",") - array2 := gstr.SplitAndTrim(array1[0], " ") - array3 := gstr.SplitAndTrim(array2[0], ".") - if len(array3) >= 2 { - return array3[1] - } - return array3[0] + return fmt.Sprintf(`%s.%s IS NULL`, m.db.GetCore().QuoteWord(table), m.db.GetCore().QuoteWord(field)) } diff --git a/database/gdb/gdb_model_transaction.go b/database/gdb/gdb_model_transaction.go new file mode 100644 index 000000000..6aaf933bf --- /dev/null +++ b/database/gdb/gdb_model_transaction.go @@ -0,0 +1,28 @@ +// Copyright GoFrame Author(https://goframe.org). All Rights Reserved. +// +// This Source Code Form is subject to the terms of the MIT License. +// If a copy of the MIT was not distributed with this file, +// You can obtain one at https://github.com/gogf/gf. + +package gdb + +import ( + "context" +) + +// Transaction wraps the transaction logic using function `f`. +// It rollbacks the transaction and returns the error from function `f` if +// it returns non-nil error. It commits the transaction and returns nil if +// function `f` returns nil. +// +// Note that, you should not Commit or Rollback the transaction in function `f` +// as it is automatically handled by this function. +func (m *Model) Transaction(ctx context.Context, f func(ctx context.Context, tx *TX) error) (err error) { + if ctx == nil { + ctx = m.GetCtx() + } + if m.tx != nil { + return m.tx.Transaction(ctx, f) + } + return m.db.Transaction(ctx, f) +} diff --git a/database/gdb/gdb_model_update.go b/database/gdb/gdb_model_update.go index e76b5d386..4826b4d43 100644 --- a/database/gdb/gdb_model_update.go +++ b/database/gdb/gdb_model_update.go @@ -1,4 +1,4 @@ -// Copyright GoFrame Author(https://github.com/gogf/gf). All Rights Reserved. +// Copyright GoFrame Author(https://goframe.org). All Rights Reserved. // // This Source Code Form is subject to the terms of the MIT License. // If a copy of the MIT was not distributed with this file, @@ -9,17 +9,18 @@ package gdb import ( "database/sql" "fmt" + "github.com/gogf/gf/errors/gcode" + "reflect" + "github.com/gogf/gf/errors/gerror" "github.com/gogf/gf/os/gtime" "github.com/gogf/gf/text/gstr" "github.com/gogf/gf/util/gconv" - "github.com/gogf/gf/util/gutil" - "reflect" ) // Update does "UPDATE ... " statement for the model. // -// If the optional parameter <dataAndWhere> is given, the dataAndWhere[0] is the updated data field, +// If the optional parameter `dataAndWhere` is given, the dataAndWhere[0] is the updated data field, // and dataAndWhere[1:] is treated as where condition fields. // Also see Model.Data and Model.Where functions. func (m *Model) Update(dataAndWhere ...interface{}) (result sql.Result, err error) { @@ -38,13 +39,11 @@ func (m *Model) Update(dataAndWhere ...interface{}) (result sql.Result, err erro } }() if m.data == nil { - return nil, gerror.New("updating table with empty data") + return nil, gerror.NewCode(gcode.CodeMissingParameter, "updating table with empty data") } var ( updateData = m.data - fieldNameCreate = m.getSoftFieldNameCreated() fieldNameUpdate = m.getSoftFieldNameUpdated() - fieldNameDelete = m.getSoftFieldNameDeleted() conditionWhere, conditionExtra, conditionArgs = m.formatCondition(false, false) ) // Automatically update the record updating time. @@ -60,7 +59,6 @@ func (m *Model) Update(dataAndWhere ...interface{}) (result sql.Result, err erro switch refKind { case reflect.Map, reflect.Struct: dataMap := ConvertDataForTableRecord(m.data) - gutil.MapDelete(dataMap, fieldNameCreate, fieldNameUpdate, fieldNameDelete) if fieldNameUpdate != "" { dataMap[fieldNameUpdate] = gtime.Now().String() } @@ -79,9 +77,10 @@ func (m *Model) Update(dataAndWhere ...interface{}) (result sql.Result, err erro } conditionStr := conditionWhere + conditionExtra if !gstr.ContainsI(conditionStr, " WHERE ") { - return nil, gerror.New("there should be WHERE condition statement for UPDATE operation") + return nil, gerror.NewCode(gcode.CodeMissingParameter, "there should be WHERE condition statement for UPDATE operation") } return m.db.DoUpdate( + m.GetCtx(), m.getLink(true), m.tables, newData, @@ -89,3 +88,21 @@ func (m *Model) Update(dataAndWhere ...interface{}) (result sql.Result, err erro m.mergeArguments(conditionArgs)..., ) } + +// Increment increments a column's value by a given amount. +// The parameter `amount` can be type of float or integer. +func (m *Model) Increment(column string, amount interface{}) (sql.Result, error) { + return m.getModel().Data(column, &Counter{ + Field: column, + Value: gconv.Float64(amount), + }).Update() +} + +// Decrement decrements a column's value by a given amount. +// The parameter `amount` can be type of float or integer. +func (m *Model) Decrement(column string, amount interface{}) (sql.Result, error) { + return m.getModel().Data(column, &Counter{ + Field: column, + Value: -gconv.Float64(amount), + }).Update() +} diff --git a/database/gdb/gdb_model_utility.go b/database/gdb/gdb_model_utility.go index c2949d64e..be9acfd41 100644 --- a/database/gdb/gdb_model_utility.go +++ b/database/gdb/gdb_model_utility.go @@ -1,4 +1,4 @@ -// Copyright GoFrame Author(https://github.com/gogf/gf). All Rights Reserved. +// Copyright GoFrame Author(https://goframe.org). All Rights Reserved. // // This Source Code Form is subject to the terms of the MIT License. // If a copy of the MIT was not distributed with this file, @@ -7,18 +7,29 @@ package gdb import ( - "fmt" + "time" + "github.com/gogf/gf/container/gset" "github.com/gogf/gf/internal/empty" "github.com/gogf/gf/os/gtime" "github.com/gogf/gf/text/gregex" "github.com/gogf/gf/text/gstr" - "github.com/gogf/gf/util/gconv" "github.com/gogf/gf/util/gutil" - "time" ) -// getModel creates and returns a cloned model of current model if <safe> is true, or else it returns +// TableFields retrieves and returns the fields information of specified table of current +// schema. +// +// Also see DriverMysql.TableFields. +func (m *Model) TableFields(tableStr string, schema ...string) (fields map[string]*TableField, err error) { + useSchema := m.schema + if len(schema) > 0 && schema[0] != "" { + useSchema = schema[0] + } + return m.db.TableFields(m.GetCtx(), m.guessPrimaryTableName(tableStr), useSchema) +} + +// getModel creates and returns a cloned model of current model if `safe` is true, or else it returns // the current model. func (m *Model) getModel() *Model { if !m.safe { @@ -29,8 +40,11 @@ func (m *Model) getModel() *Model { } // mappingAndFilterToTableFields mappings and changes given field name to really table field name. -func (m *Model) mappingAndFilterToTableFields(fields []string) []string { - fieldsMap, err := m.db.TableFields(m.tables) +// Eg: +// ID -> id +// NICK_Name -> nickname +func (m *Model) mappingAndFilterToTableFields(fields []string, filter bool) []string { + fieldsMap, err := m.TableFields(m.tablesInit) if err != nil || len(fieldsMap) == 0 { return fields } @@ -44,12 +58,16 @@ func (m *Model) mappingAndFilterToTableFields(fields []string) []string { } for _, field := range inputFieldsArray { if _, ok := fieldsKeyMap[field]; !ok { - if !gregex.IsMatchString(regularFieldNameRegPattern, field) { + if !gregex.IsMatchString(regularFieldNameWithoutDotRegPattern, field) { + // Eg: user.id, user.name outputFieldsArray = append(outputFieldsArray, field) continue } else { + // Eg: id, name if foundKey, _ := gutil.MapPossibleItemByKey(fieldsKeyMap, field); foundKey != "" { outputFieldsArray = append(outputFieldsArray, foundKey) + } else if !filter { + outputFieldsArray = append(outputFieldsArray, field) } } } else { @@ -85,12 +103,26 @@ func (m *Model) filterDataForInsertOrUpdate(data interface{}) (interface{}, erro // Note that, it does not filter list item, which is also type of map, for "omit empty" feature. 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) + data, err = m.db.GetCore().mappingAndFilterData( + m.schema, m.guessPrimaryTableName(m.tablesInit), data, m.filter, + ) if err != nil { return nil, err } + // Remove key-value pairs of which the value is nil. + if allowOmitEmpty && m.option&optionOmitNilData > 0 { + tempMap := make(Map, len(data)) + for k, v := range data { + if empty.IsNil(v) { + continue + } + tempMap[k] = v + } + data = tempMap + } + // Remove key-value pairs of which the value is empty. - if allowOmitEmpty && m.option&OPTION_OMITEMPTY > 0 { + if allowOmitEmpty && m.option&optionOmitEmptyData > 0 { tempMap := make(Map, len(data)) for k, v := range data { if empty.IsEmpty(v) { @@ -145,11 +177,11 @@ func (m *Model) doMappingAndFilterForInsertOrUpdateDataMap(data Map, allowOmitEm return data, nil } -// getLink returns the underlying database link object with configured <linkType> attribute. -// The parameter <master> specifies whether using the master node if master-slave configured. +// getLink returns the underlying database link object with configured `linkType` attribute. +// The parameter `master` specifies whether using the master node if master-slave configured. func (m *Model) getLink(master bool) Link { if m.tx != nil { - return m.tx.tx + return &txLink{m.tx.tx} } linkType := m.linkType if linkType == 0 { @@ -161,13 +193,13 @@ func (m *Model) getLink(master bool) Link { } switch linkType { case linkTypeMaster: - link, err := m.db.GetMaster(m.schema) + link, err := m.db.GetCore().MasterLink(m.schema) if err != nil { panic(err) } return link case linkTypeSlave: - link, err := m.db.GetSlave(m.schema) + link, err := m.db.GetCore().SlaveLink(m.schema) if err != nil { panic(err) } @@ -180,8 +212,8 @@ func (m *Model) getLink(master bool) Link { // It parses m.tables to retrieve the primary table name, supporting m.tables like: // "user", "user u", "user as u, user_detail as ud". func (m *Model) getPrimaryKey() string { - table := gstr.SplitAndTrim(m.tables, " ")[0] - tableFields, err := m.db.TableFields(table) + table := gstr.SplitAndTrim(m.tablesInit, " ")[0] + tableFields, err := m.TableFields(table) if err != nil { return "" } @@ -193,100 +225,7 @@ func (m *Model) getPrimaryKey() string { return "" } -// formatCondition formats where arguments of the model and returns a new condition sql and its arguments. -// Note that this function does not change any attribute value of the <m>. -// -// The parameter <limit1> specifies whether limits querying only one record if m.limit is not set. -func (m *Model) formatCondition(limit1 bool, isCountStatement bool) (conditionWhere string, conditionExtra string, conditionArgs []interface{}) { - if len(m.whereHolder) > 0 { - for _, v := range m.whereHolder { - switch v.operator { - case whereHolderWhere: - if conditionWhere == "" { - newWhere, newArgs := formatWhere( - m.db, v.where, v.args, m.option&OPTION_OMITEMPTY > 0, - ) - if len(newWhere) > 0 { - conditionWhere = newWhere - conditionArgs = newArgs - } - continue - } - fallthrough - - case whereHolderAnd: - newWhere, newArgs := formatWhere( - m.db, v.where, v.args, m.option&OPTION_OMITEMPTY > 0, - ) - if len(newWhere) > 0 { - if len(conditionWhere) == 0 { - conditionWhere = newWhere - } else if conditionWhere[0] == '(' { - conditionWhere = fmt.Sprintf(`%s AND (%s)`, conditionWhere, newWhere) - } else { - conditionWhere = fmt.Sprintf(`(%s) AND (%s)`, conditionWhere, newWhere) - } - conditionArgs = append(conditionArgs, newArgs...) - } - - case whereHolderOr: - newWhere, newArgs := formatWhere( - m.db, v.where, v.args, m.option&OPTION_OMITEMPTY > 0, - ) - if len(newWhere) > 0 { - if len(conditionWhere) == 0 { - conditionWhere = newWhere - } else if conditionWhere[0] == '(' { - conditionWhere = fmt.Sprintf(`%s OR (%s)`, conditionWhere, newWhere) - } else { - conditionWhere = fmt.Sprintf(`(%s) OR (%s)`, conditionWhere, newWhere) - } - conditionArgs = append(conditionArgs, newArgs...) - } - } - } - } - if conditionWhere != "" { - conditionWhere = " WHERE " + conditionWhere - } - if m.groupBy != "" { - conditionExtra += " GROUP BY " + m.groupBy - } - if len(m.having) > 0 { - havingStr, havingArgs := formatWhere( - m.db, m.having[0], gconv.Interfaces(m.having[1]), m.option&OPTION_OMITEMPTY > 0, - ) - if len(havingStr) > 0 { - conditionExtra += " HAVING " + havingStr - conditionArgs = append(conditionArgs, havingArgs...) - } - } - if m.orderBy != "" { - conditionExtra += " ORDER BY " + m.orderBy - } - if !isCountStatement { - if m.limit != 0 { - if m.start >= 0 { - conditionExtra += fmt.Sprintf(" LIMIT %d,%d", m.start, m.limit) - } else { - conditionExtra += fmt.Sprintf(" LIMIT %d", m.limit) - } - } else if limit1 { - conditionExtra += " LIMIT 1" - } - - if m.offset >= 0 { - conditionExtra += fmt.Sprintf(" OFFSET %d", m.offset) - } - } - - if m.lockInfo != "" { - conditionExtra += " " + m.lockInfo - } - return -} - -// mergeArguments creates and returns new arguments by merging <m.extraArgs> and given <args>. +// mergeArguments creates and returns new arguments by merging <m.extraArgs> and given `args`. func (m *Model) mergeArguments(args []interface{}) []interface{} { if len(m.extraArgs) > 0 { newArgs := make([]interface{}, len(m.extraArgs)+len(args)) diff --git a/database/gdb/gdb_model_with.go b/database/gdb/gdb_model_with.go new file mode 100644 index 000000000..36782fffc --- /dev/null +++ b/database/gdb/gdb_model_with.go @@ -0,0 +1,316 @@ +// Copyright GoFrame Author(https://goframe.org). All Rights Reserved. +// +// This Source Code Form is subject to the terms of the MIT License. +// If a copy of the MIT was not distributed with this file, +// You can obtain one at https://github.com/gogf/gf. + +package gdb + +import ( + "fmt" + "github.com/gogf/gf/errors/gcode" + "reflect" + + "github.com/gogf/gf/errors/gerror" + "github.com/gogf/gf/internal/structs" + "github.com/gogf/gf/internal/utils" + "github.com/gogf/gf/text/gregex" + "github.com/gogf/gf/text/gstr" +) + +// With creates and returns an ORM model based on metadata of given object. +// It also enables model association operations feature on given `object`. +// It can be called multiple times to add one or more objects to model and enable +// their mode association operations feature. +// For example, if given struct definition: +// type User struct { +// gmeta.Meta `orm:"table:user"` +// Id int `json:"id"` +// Name string `json:"name"` +// UserDetail *UserDetail `orm:"with:uid=id"` +// UserScores []*UserScores `orm:"with:uid=id"` +// } +// We can enable model association operations on attribute `UserDetail` and `UserScores` by: +// db.With(User{}.UserDetail).With(User{}.UserDetail).Scan(xxx) +// Or: +// db.With(UserDetail{}).With(UserDetail{}).Scan(xxx) +// Or: +// db.With(UserDetail{}, UserDetail{}).Scan(xxx) +func (m *Model) With(objects ...interface{}) *Model { + model := m.getModel() + for _, object := range objects { + if m.tables == "" { + m.tablesInit = m.db.GetCore().QuotePrefixTableName( + getTableNameFromOrmTag(object), + ) + m.tables = m.tablesInit + return model + } + model.withArray = append(model.withArray, object) + } + return model +} + +// WithAll enables model association operations on all objects that have "with" tag in the struct. +func (m *Model) WithAll() *Model { + model := m.getModel() + model.withAll = true + return model +} + +// doWithScanStruct handles model association operations feature for single struct. +func (m *Model) doWithScanStruct(pointer interface{}) error { + var ( + err error + allowedTypeStrArray = make([]string, 0) + ) + currentStructFieldMap, err := structs.FieldMap(structs.FieldMapInput{ + Pointer: pointer, + PriorityTagArray: nil, + RecursiveOption: structs.RecursiveOptionEmbeddedNoTag, + }) + if err != nil { + return err + } + // It checks the with array and automatically calls the ScanList to complete association querying. + if !m.withAll { + for _, field := range currentStructFieldMap { + for _, withItem := range m.withArray { + withItemReflectValueType, err := structs.StructType(withItem) + if err != nil { + return err + } + var ( + fieldTypeStr = gstr.TrimAll(field.Type().String(), "*[]") + withItemReflectValueTypeStr = gstr.TrimAll(withItemReflectValueType.String(), "*[]") + ) + // It does select operation if the field type is in the specified "with" type array. + if gstr.Compare(fieldTypeStr, withItemReflectValueTypeStr) == 0 { + allowedTypeStrArray = append(allowedTypeStrArray, fieldTypeStr) + } + } + } + } + for _, field := range currentStructFieldMap { + var ( + fieldTypeStr = gstr.TrimAll(field.Type().String(), "*[]") + parsedTagOutput = m.parseWithTagInFieldStruct(field) + ) + if parsedTagOutput.With == "" { + continue + } + // It just handlers "with" type attribute struct, so it ignores other struct types. + if !m.withAll && !gstr.InArray(allowedTypeStrArray, fieldTypeStr) { + continue + } + array := gstr.SplitAndTrim(parsedTagOutput.With, "=") + if len(array) == 1 { + // It also supports using only one column name + // if both tables associates using the same column name. + array = append(array, parsedTagOutput.With) + } + var ( + model *Model + fieldKeys []string + relatedSourceName = array[0] + relatedTargetName = array[1] + relatedTargetValue interface{} + ) + // Find the value of related attribute from `pointer`. + for attributeName, attributeValue := range currentStructFieldMap { + if utils.EqualFoldWithoutChars(attributeName, relatedTargetName) { + relatedTargetValue = attributeValue.Value.Interface() + break + } + } + if relatedTargetValue == nil { + return gerror.NewCodef( + gcode.CodeInvalidParameter, + `cannot find the target related value of name "%s" in with tag "%s" for attribute "%s.%s"`, + relatedTargetName, parsedTagOutput.With, reflect.TypeOf(pointer).Elem(), field.Name(), + ) + } + bindToReflectValue := field.Value + if bindToReflectValue.Kind() != reflect.Ptr && bindToReflectValue.CanAddr() { + bindToReflectValue = bindToReflectValue.Addr() + } + + // It automatically retrieves struct field names from current attribute struct/slice. + if structType, err := structs.StructType(field.Value); err != nil { + return err + } else { + fieldKeys = structType.FieldKeys() + } + + // Recursively with feature checks. + model = m.db.With(field.Value) + if m.withAll { + model = model.WithAll() + } else { + model = model.With(m.withArray...) + } + if parsedTagOutput.Where != "" { + model = model.Where(parsedTagOutput.Where) + } + if parsedTagOutput.Order != "" { + model = model.Order(parsedTagOutput.Order) + } + + err = model.Fields(fieldKeys).Where(relatedSourceName, relatedTargetValue).Scan(bindToReflectValue) + if err != nil { + return err + } + + } + return nil +} + +// doWithScanStructs handles model association operations feature for struct slice. +// Also see doWithScanStruct. +func (m *Model) doWithScanStructs(pointer interface{}) error { + if v, ok := pointer.(reflect.Value); ok { + pointer = v.Interface() + } + + var ( + err error + allowedTypeStrArray = make([]string, 0) + ) + currentStructFieldMap, err := structs.FieldMap(structs.FieldMapInput{ + Pointer: pointer, + PriorityTagArray: nil, + RecursiveOption: structs.RecursiveOptionEmbeddedNoTag, + }) + if err != nil { + return err + } + // It checks the with array and automatically calls the ScanList to complete association querying. + if !m.withAll { + for _, field := range currentStructFieldMap { + for _, withItem := range m.withArray { + withItemReflectValueType, err := structs.StructType(withItem) + if err != nil { + return err + } + var ( + fieldTypeStr = gstr.TrimAll(field.Type().String(), "*[]") + withItemReflectValueTypeStr = gstr.TrimAll(withItemReflectValueType.String(), "*[]") + ) + // It does select operation if the field type is in the specified with type array. + if gstr.Compare(fieldTypeStr, withItemReflectValueTypeStr) == 0 { + allowedTypeStrArray = append(allowedTypeStrArray, fieldTypeStr) + } + } + } + } + + for fieldName, field := range currentStructFieldMap { + var ( + fieldTypeStr = gstr.TrimAll(field.Type().String(), "*[]") + parsedTagOutput = m.parseWithTagInFieldStruct(field) + ) + if parsedTagOutput.With == "" { + continue + } + if !m.withAll && !gstr.InArray(allowedTypeStrArray, fieldTypeStr) { + continue + } + array := gstr.SplitAndTrim(parsedTagOutput.With, "=") + if len(array) == 1 { + // It supports using only one column name + // if both tables associates using the same column name. + array = append(array, parsedTagOutput.With) + } + var ( + model *Model + fieldKeys []string + relatedSourceName = array[0] + relatedTargetName = array[1] + relatedTargetValue interface{} + ) + // Find the value slice of related attribute from `pointer`. + for attributeName, _ := range currentStructFieldMap { + if utils.EqualFoldWithoutChars(attributeName, relatedTargetName) { + relatedTargetValue = ListItemValuesUnique(pointer, attributeName) + break + } + } + if relatedTargetValue == nil { + return gerror.NewCodef( + gcode.CodeInvalidParameter, + `cannot find the related value for attribute name "%s" of with tag "%s"`, + relatedTargetName, parsedTagOutput.With, + ) + } + + // It automatically retrieves struct field names from current attribute struct/slice. + if structType, err := structs.StructType(field.Value); err != nil { + return err + } else { + fieldKeys = structType.FieldKeys() + } + + // Recursively with feature checks. + model = m.db.With(field.Value) + if m.withAll { + model = model.WithAll() + } else { + model = model.With(m.withArray...) + } + if parsedTagOutput.Where != "" { + model = model.Where(parsedTagOutput.Where) + } + if parsedTagOutput.Order != "" { + model = model.Order(parsedTagOutput.Order) + } + + err = model.Fields(fieldKeys). + Where(relatedSourceName, relatedTargetValue). + ScanList(pointer, fieldName, parsedTagOutput.With) + if err != nil { + return err + } + } + return nil +} + +type parseWithTagInFieldStructOutput struct { + With string + Where string + Order string +} + +func (m *Model) parseWithTagInFieldStruct(field *structs.Field) (output parseWithTagInFieldStructOutput) { + var ( + match []string + ormTag = field.Tag(OrmTagForStruct) + ) + // with tag. + match, _ = gregex.MatchString( + fmt.Sprintf(`%s\s*:\s*([^,]+),{0,1}`, OrmTagForWith), + ormTag, + ) + if len(match) > 1 { + output.With = match[1] + } + if len(match) > 2 { + output.Where = gstr.Trim(match[2]) + } + // where string. + match, _ = gregex.MatchString( + fmt.Sprintf(`%s\s*:.+,\s*%s:\s*([^,]+),{0,1}`, OrmTagForWith, OrmTagForWithWhere), + ormTag, + ) + if len(match) > 1 { + output.Where = gstr.Trim(match[1]) + } + // order string. + match, _ = gregex.MatchString( + fmt.Sprintf(`%s\s*:.+,\s*%s:\s*([^,]+),{0,1}`, OrmTagForWith, OrmTagForWithOrder), + ormTag, + ) + if len(match) > 1 { + output.Order = gstr.Trim(match[1]) + } + return +} diff --git a/database/gdb/gdb_result.go b/database/gdb/gdb_result.go index f2d8fb444..63134bb09 100644 --- a/database/gdb/gdb_result.go +++ b/database/gdb/gdb_result.go @@ -1,4 +1,4 @@ -// Copyright GoFrame Author(https://github.com/gogf/gf). All Rights Reserved. +// Copyright GoFrame Author(https://goframe.org). All Rights Reserved. // // This Source Code Form is subject to the terms of the MIT License. // If a copy of the MIT was not distributed with this file, @@ -33,7 +33,10 @@ func (r *SqlResult) MustGetInsertId() int64 { return id } -// see sql.Result.RowsAffected +// RowsAffected returns the number of rows affected by an +// update, insert, or delete. Not every database or database +// driver may support this. +// Also See sql.Result. func (r *SqlResult) RowsAffected() (int64, error) { if r.affected > 0 { return r.affected, nil @@ -44,7 +47,12 @@ func (r *SqlResult) RowsAffected() (int64, error) { return r.result.RowsAffected() } -// see sql.Result.LastInsertId +// LastInsertId returns the integer generated by the database +// in response to a command. Typically this will be from an +// "auto increment" column when inserting a new row. Not all +// databases support this feature, and the syntax of such +// statements varies. +// Also See sql.Result. func (r *SqlResult) LastInsertId() (int64, error) { if r.result == nil { return 0, nil diff --git a/database/gdb/gdb_schema.go b/database/gdb/gdb_schema.go index b1d59ad04..7794eada4 100644 --- a/database/gdb/gdb_schema.go +++ b/database/gdb/gdb_schema.go @@ -1,4 +1,4 @@ -// Copyright GoFrame Author(https://github.com/gogf/gf). All Rights Reserved. +// Copyright GoFrame Author(https://goframe.org). All Rights Reserved. // // This Source Code Form is subject to the terms of the MIT License. // If a copy of the MIT was not distributed with this file, @@ -16,7 +16,7 @@ type Schema struct { // Schema creates and returns a schema. func (c *Core) Schema(schema string) *Schema { return &Schema{ - db: c.DB, + db: c.db, schema: schema, } } @@ -31,14 +31,14 @@ func (tx *TX) Schema(schema string) *Schema { } // Table creates and returns a new ORM model. -// The parameter <tables> can be more than one table names, like : +// The parameter `tables` can be more than one table names, like : // "user", "user u", "user, user_detail", "user u, user_detail ud" func (s *Schema) Table(table string) *Model { var m *Model if s.tx != nil { - m = s.tx.Table(table) + m = s.tx.Model(table) } else { - m = s.db.Table(table) + m = s.db.Model(table) } // Do not change the schema of the original db, // it here creates a new db and changes its schema. diff --git a/database/gdb/gdb_statement.go b/database/gdb/gdb_statement.go new file mode 100644 index 000000000..24f755eb7 --- /dev/null +++ b/database/gdb/gdb_statement.go @@ -0,0 +1,151 @@ +// Copyright GoFrame Author(https://goframe.org). All Rights Reserved. +// +// This Source Code Form is subject to the terms of the MIT License. +// If a copy of the MIT was not distributed with this file, +// You can obtain one at https://github.com/gogf/gf. + +package gdb + +import ( + "context" + "database/sql" + "github.com/gogf/gf/errors/gcode" + + "github.com/gogf/gf/errors/gerror" + "github.com/gogf/gf/os/gtime" +) + +// Stmt is a prepared statement. +// A Stmt is safe for concurrent use by multiple goroutines. +// +// If a Stmt is prepared on a Tx or Conn, it will be bound to a single +// underlying connection forever. If the Tx or Conn closes, the Stmt will +// become unusable and all operations will return an error. +// If a Stmt is prepared on a DB, it will remain usable for the lifetime of the +// DB. When the Stmt needs to execute on a new underlying connection, it will +// prepare itself on the new connection automatically. +type Stmt struct { + *sql.Stmt + core *Core + link Link + sql string +} + +const ( + stmtTypeExecContext = "Statement.ExecContext" + stmtTypeQueryContext = "Statement.QueryContext" + stmtTypeQueryRowContext = "Statement.QueryRowContext" +) + +// doStmtCommit commits statement according to given `stmtType`. +func (s *Stmt) doStmtCommit(ctx context.Context, stmtType string, args ...interface{}) (result interface{}, err error) { + var ( + cancelFuncForTimeout context.CancelFunc + timestampMilli1 = gtime.TimestampMilli() + ) + switch stmtType { + case stmtTypeExecContext: + ctx, cancelFuncForTimeout = s.core.GetCtxTimeout(ctxTimeoutTypeExec, ctx) + defer cancelFuncForTimeout() + result, err = s.Stmt.ExecContext(ctx, args...) + + case stmtTypeQueryContext: + ctx, cancelFuncForTimeout = s.core.GetCtxTimeout(ctxTimeoutTypeQuery, ctx) + defer cancelFuncForTimeout() + result, err = s.Stmt.QueryContext(ctx, args...) + + case stmtTypeQueryRowContext: + ctx, cancelFuncForTimeout = s.core.GetCtxTimeout(ctxTimeoutTypeQuery, ctx) + defer cancelFuncForTimeout() + result = s.Stmt.QueryRowContext(ctx, args...) + + default: + panic(gerror.NewCodef(gcode.CodeInvalidParameter, `invalid stmtType: %s`, stmtType)) + } + var ( + timestampMilli2 = gtime.TimestampMilli() + sqlObj = &Sql{ + Sql: s.sql, + Type: stmtType, + Args: args, + Format: FormatSqlWithArgs(s.sql, args), + Error: err, + Start: timestampMilli1, + End: timestampMilli2, + Group: s.core.db.GetGroup(), + IsTransaction: s.link.IsTransaction(), + } + ) + // Tracing and logging. + s.core.addSqlToTracing(ctx, sqlObj) + if s.core.db.GetDebug() { + s.core.writeSqlToLogger(ctx, sqlObj) + } + return result, err +} + +// ExecContext executes a prepared statement with the given arguments and +// returns a Result summarizing the effect of the statement. +func (s *Stmt) ExecContext(ctx context.Context, args ...interface{}) (sql.Result, error) { + result, err := s.doStmtCommit(ctx, stmtTypeExecContext, args...) + if result != nil { + return result.(sql.Result), err + } + return nil, err +} + +// QueryContext executes a prepared query statement with the given arguments +// and returns the query results as a *Rows. +func (s *Stmt) QueryContext(ctx context.Context, args ...interface{}) (*sql.Rows, error) { + result, err := s.doStmtCommit(ctx, stmtTypeQueryContext, args...) + if result != nil { + return result.(*sql.Rows), err + } + return nil, err +} + +// QueryRowContext executes a prepared query statement with the given arguments. +// If an error occurs during the execution of the statement, that error will +// be returned by a call to Scan on the returned *Row, which is always non-nil. +// If the query selects no rows, the *Row's Scan will return ErrNoRows. +// Otherwise, the *Row's Scan scans the first selected row and discards +// the rest. +func (s *Stmt) QueryRowContext(ctx context.Context, args ...interface{}) *sql.Row { + result, _ := s.doStmtCommit(ctx, stmtTypeQueryRowContext, args...) + if result != nil { + return result.(*sql.Row) + } + return nil +} + +// Exec executes a prepared statement with the given arguments and +// returns a Result summarizing the effect of the statement. +func (s *Stmt) Exec(args ...interface{}) (sql.Result, error) { + return s.ExecContext(context.Background(), args...) +} + +// Query executes a prepared query statement with the given arguments +// and returns the query results as a *Rows. +func (s *Stmt) Query(args ...interface{}) (*sql.Rows, error) { + return s.QueryContext(context.Background(), args...) +} + +// QueryRow executes a prepared query statement with the given arguments. +// If an error occurs during the execution of the statement, that error will +// be returned by a call to Scan on the returned *Row, which is always non-nil. +// If the query selects no rows, the *Row's Scan will return ErrNoRows. +// Otherwise, the *Row's Scan scans the first selected row and discards +// the rest. +// +// Example usage: +// +// var name string +// err := nameByUseridStmt.QueryRow(id).Scan(&name) +func (s *Stmt) QueryRow(args ...interface{}) *sql.Row { + return s.QueryRowContext(context.Background(), args...) +} + +// Close closes the statement. +func (s *Stmt) Close() error { + return s.Stmt.Close() +} diff --git a/database/gdb/gdb_transaction.go b/database/gdb/gdb_transaction.go deleted file mode 100644 index 263ff476f..000000000 --- a/database/gdb/gdb_transaction.go +++ /dev/null @@ -1,282 +0,0 @@ -// Copyright GoFrame 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 ( - "database/sql" - "fmt" - "reflect" - - "github.com/gogf/gf/text/gregex" -) - -// TX is the struct for transaction management. -type TX struct { - db DB - tx *sql.Tx - master *sql.DB -} - -// Commit commits the transaction. -func (tx *TX) Commit() error { - return tx.tx.Commit() -} - -// Rollback aborts the transaction. -func (tx *TX) Rollback() error { - return tx.tx.Rollback() -} - -// Query does query operation on transaction. -// See Core.Query. -func (tx *TX) Query(sql string, args ...interface{}) (rows *sql.Rows, err error) { - return tx.db.DoQuery(tx.tx, sql, args...) -} - -// Exec does none query operation on transaction. -// See Core.Exec. -func (tx *TX) Exec(sql string, args ...interface{}) (sql.Result, error) { - return tx.db.DoExec(tx.tx, sql, args...) -} - -// Prepare creates a prepared statement for later queries or executions. -// Multiple queries or executions may be run concurrently from the -// returned statement. -// The caller must call the statement's Close method -// when the statement is no longer needed. -func (tx *TX) Prepare(sql string) (*sql.Stmt, error) { - return tx.db.DoPrepare(tx.tx, sql) -} - -// GetAll queries and returns data records from database. -func (tx *TX) GetAll(sql string, args ...interface{}) (Result, error) { - rows, err := tx.Query(sql, args...) - if err != nil || rows == nil { - return nil, err - } - defer rows.Close() - return tx.db.convertRowsToResult(rows) -} - -// GetOne queries and returns one record from database. -func (tx *TX) GetOne(sql string, args ...interface{}) (Record, error) { - list, err := tx.GetAll(sql, args...) - if err != nil { - return nil, err - } - if len(list) > 0 { - return list[0], nil - } - return nil, nil -} - -// GetStruct queries one record from database and converts it to given struct. -// The parameter <pointer> should be a pointer to struct. -func (tx *TX) GetStruct(obj interface{}, sql string, args ...interface{}) error { - one, err := tx.GetOne(sql, args...) - if err != nil { - return err - } - return one.Struct(obj) -} - -// GetStructs queries records from database and converts them to given struct. -// The parameter <pointer> should be type of struct slice: []struct/[]*struct. -func (tx *TX) GetStructs(objPointerSlice interface{}, sql string, args ...interface{}) error { - all, err := tx.GetAll(sql, args...) - if err != nil { - return err - } - return all.Structs(objPointerSlice) -} - -// GetScan queries one or more records from database and converts them to given struct or -// struct array. -// -// If parameter <pointer> is type of struct pointer, it calls GetStruct internally for -// the conversion. If parameter <pointer> is type of slice, it calls GetStructs internally -// for conversion. -func (tx *TX) GetScan(objPointer interface{}, sql string, args ...interface{}) error { - t := reflect.TypeOf(objPointer) - k := t.Kind() - if k != reflect.Ptr { - return fmt.Errorf("params should be type of pointer, but got: %v", k) - } - k = t.Elem().Kind() - switch k { - case reflect.Array, reflect.Slice: - return tx.db.GetStructs(objPointer, sql, args...) - case reflect.Struct: - return tx.db.GetStruct(objPointer, sql, args...) - default: - return fmt.Errorf("element type should be type of struct/slice, unsupported: %v", k) - } -} - -// GetValue queries and returns the field value from database. -// The sql should queries only one field from database, or else it returns only one -// field of the result. -func (tx *TX) GetValue(sql string, args ...interface{}) (Value, error) { - one, err := tx.GetOne(sql, args...) - if err != nil { - return nil, err - } - for _, v := range one { - return v, nil - } - return nil, nil -} - -// GetCount queries and returns the count from database. -func (tx *TX) GetCount(sql string, args ...interface{}) (int, error) { - if !gregex.IsMatchString(`(?i)SELECT\s+COUNT\(.+\)\s+FROM`, sql) { - sql, _ = gregex.ReplaceString(`(?i)(SELECT)\s+(.+)\s+(FROM)`, `$1 COUNT($2) $3`, sql) - } - value, err := tx.GetValue(sql, args...) - if err != nil { - return 0, err - } - return value.Int(), nil -} - -// Insert does "INSERT INTO ..." statement for the table. -// If there's already one unique record of the data in the table, it returns error. -// -// The parameter <data> can be type of map/gmap/struct/*struct/[]map/[]struct, etc. -// Eg: -// Data(g.Map{"uid": 10000, "name":"john"}) -// Data(g.Slice{g.Map{"uid": 10000, "name":"john"}, g.Map{"uid": 20000, "name":"smith"}) -// -// 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) { - 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. -// If there's already one unique record of the data in the table, it ignores the inserting. -// -// The parameter <data> can be type of map/gmap/struct/*struct/[]map/[]struct, etc. -// Eg: -// Data(g.Map{"uid": 10000, "name":"john"}) -// Data(g.Slice{g.Map{"uid": 10000, "name":"john"}, g.Map{"uid": 20000, "name":"smith"}) -// -// 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) { - 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. -// If there's already one unique record of the data in the table, it deletes the record -// and inserts a new one. -// -// The parameter <data> can be type of map/gmap/struct/*struct/[]map/[]struct, etc. -// Eg: -// Data(g.Map{"uid": 10000, "name":"john"}) -// Data(g.Slice{g.Map{"uid": 10000, "name":"john"}, g.Map{"uid": 20000, "name":"smith"}) -// -// The parameter <data> can be type of map/gmap/struct/*struct/[]map/[]struct, etc. -// 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) { - 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. -// It updates the record if there's primary or unique index in the saving data, -// or else it inserts a new record into the table. -// -// The parameter <data> can be type of map/gmap/struct/*struct/[]map/[]struct, etc. -// Eg: -// Data(g.Map{"uid": 10000, "name":"john"}) -// Data(g.Slice{g.Map{"uid": 10000, "name":"john"}, g.Map{"uid": 20000, "name":"smith"}) -// -// 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) { - 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) { - 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) { - 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) { - 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) { - 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. -// -// The parameter <data> can be type of string/map/gmap/struct/*struct, etc. -// Eg: "uid=10000", "uid", 10000, g.Map{"uid": 10000, "name":"john"} -// -// The parameter <condition> can be type of string/map/gmap/slice/struct/*struct, etc. -// It is commonly used with parameter <args>. -// Eg: -// "uid=10000", -// "uid", 10000 -// "money>? AND name like ?", 99999, "vip_%" -// "status IN (?)", g.Slice{1,2,3} -// "age IN(?,?)", 18, 50 -// User{ Id : 1, UserName : "john"} -func (tx *TX) Update(table string, data interface{}, condition interface{}, args ...interface{}) (sql.Result, error) { - return tx.Model(table).Data(data).Where(condition, args...).Update() -} - -// Delete does "DELETE FROM ... " statement for the table. -// -// The parameter <condition> can be type of string/map/gmap/slice/struct/*struct, etc. -// It is commonly used with parameter <args>. -// Eg: -// "uid=10000", -// "uid", 10000 -// "money>? AND name like ?", 99999, "vip_%" -// "status IN (?)", g.Slice{1,2,3} -// "age IN(?,?)", 18, 50 -// User{ Id : 1, UserName : "john"} -func (tx *TX) Delete(table string, condition interface{}, args ...interface{}) (sql.Result, error) { - return tx.Model(table).Where(condition, args...).Delete() -} diff --git a/database/gdb/gdb_type_record.go b/database/gdb/gdb_type_record.go index 7801e18fc..eff35a1f4 100644 --- a/database/gdb/gdb_type_record.go +++ b/database/gdb/gdb_type_record.go @@ -1,4 +1,4 @@ -// Copyright GoFrame Author(https://github.com/gogf/gf). All Rights Reserved. +// Copyright GoFrame Author(https://goframe.org). All Rights Reserved. // // This Source Code Form is subject to the terms of the MIT License. // If a copy of the MIT was not distributed with this file, @@ -10,24 +10,28 @@ import ( "database/sql" "github.com/gogf/gf/container/gmap" "github.com/gogf/gf/encoding/gparser" - "github.com/gogf/gf/errors/gerror" + "github.com/gogf/gf/internal/empty" "github.com/gogf/gf/util/gconv" - "reflect" ) -// Json converts <r> to JSON format content. +// Interface converts and returns `r` as type of interface{}. +func (r Record) Interface() interface{} { + return r +} + +// Json converts `r` to JSON format content. func (r Record) Json() string { content, _ := gparser.VarToJson(r.Map()) return gconv.UnsafeBytesToStr(content) } -// Xml converts <r> to XML format content. +// Xml converts `r` to XML format content. func (r Record) Xml(rootTag ...string) string { content, _ := gparser.VarToXml(r.Map(), rootTag...) return gconv.UnsafeBytesToStr(content) } -// Map converts <r> to map[string]interface{}. +// Map converts `r` to map[string]interface{}. func (r Record) Map() Map { m := make(map[string]interface{}) for k, v := range r { @@ -36,43 +40,27 @@ func (r Record) Map() Map { return m } -// GMap converts <r> to a gmap. +// GMap converts `r` to a gmap. func (r Record) GMap() *gmap.StrAnyMap { return gmap.NewStrAnyMapFrom(r.Map()) } -// Struct converts <r> to a struct. -// Note that the parameter <pointer> should be type of *struct/**struct. +// Struct converts `r` to a struct. +// Note that the parameter `pointer` should be type of *struct/**struct. // -// Note that it returns sql.ErrNoRows if <r> is empty. +// Note that it returns sql.ErrNoRows if `r` is empty. func (r Record) Struct(pointer interface{}) error { // If the record is empty, it returns error. if r.IsEmpty() { - return sql.ErrNoRows + if !empty.IsNil(pointer, true) { + return sql.ErrNoRows + } + return nil } - // Special handling for parameter type: reflect.Value - if _, ok := pointer.(reflect.Value); ok { - return convertMapToStruct(r.Map(), pointer) - } - var ( - reflectValue = reflect.ValueOf(pointer) - reflectKind = reflectValue.Kind() - ) - if reflectKind != reflect.Ptr { - return gerror.New("parameter should be type of *struct/**struct") - } - reflectValue = reflectValue.Elem() - reflectKind = reflectValue.Kind() - if reflectKind == reflect.Invalid { - return gerror.New("parameter is an invalid pointer, maybe nil") - } - if reflectKind != reflect.Ptr && reflectKind != reflect.Struct { - return gerror.New("parameter should be type of *struct/**struct") - } - return convertMapToStruct(r.Map(), pointer) + return gconv.StructTag(r, pointer, OrmTagForStruct) } -// IsEmpty checks and returns whether <r> is empty. +// IsEmpty checks and returns whether `r` is empty. func (r Record) IsEmpty() bool { return len(r) == 0 } diff --git a/database/gdb/gdb_type_record_deprecated.go b/database/gdb/gdb_type_record_deprecated.go deleted file mode 100644 index 0a241de40..000000000 --- a/database/gdb/gdb_type_record_deprecated.go +++ /dev/null @@ -1,27 +0,0 @@ -// Copyright GoFrame 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 - -// Deprecated. -func (r Record) ToJson() string { - return r.Json() -} - -// Deprecated. -func (r Record) ToXml(rootTag ...string) string { - return r.Xml(rootTag...) -} - -// Deprecated. -func (r Record) ToMap() Map { - return r.Map() -} - -// Deprecated. -func (r Record) ToStruct(pointer interface{}) error { - return r.Struct(pointer) -} diff --git a/database/gdb/gdb_type_result.go b/database/gdb/gdb_type_result.go index e45ceffdb..cfe1ba92a 100644 --- a/database/gdb/gdb_type_result.go +++ b/database/gdb/gdb_type_result.go @@ -1,4 +1,4 @@ -// Copyright GoFrame Author(https://github.com/gogf/gf). All Rights Reserved. +// Copyright GoFrame Author(https://goframe.org). All Rights Reserved. // // This Source Code Form is subject to the terms of the MIT License. // If a copy of the MIT was not distributed with this file, @@ -7,16 +7,18 @@ package gdb import ( - "database/sql" - "fmt" "github.com/gogf/gf/container/gvar" - "math" - "reflect" - "github.com/gogf/gf/encoding/gparser" + "github.com/gogf/gf/util/gconv" + "math" ) -// IsEmpty checks and returns whether <r> is empty. +// Interface converts and returns `r` as type of interface{}. +func (r Result) Interface() interface{} { + return r +} + +// IsEmpty checks and returns whether `r` is empty. func (r Result) IsEmpty() bool { return r.Len() == 0 } @@ -32,7 +34,7 @@ func (r Result) Size() int { } // Chunk splits an Result into multiple Results, -// the size of each array is determined by <size>. +// the size of each array is determined by `size`. // The last chunk may contain less than size elements. func (r Result) Chunk(size int) []Result { if size < 1 { @@ -52,19 +54,19 @@ func (r Result) Chunk(size int) []Result { return n } -// Json converts <r> to JSON format content. +// Json converts `r` to JSON format content. func (r Result) Json() string { content, _ := gparser.VarToJson(r.List()) return string(content) } -// Xml converts <r> to XML format content. +// Xml converts `r` to XML format content. func (r Result) Xml(rootTag ...string) string { content, _ := gparser.VarToXml(r.List(), rootTag...) return string(content) } -// List converts <r> to a List. +// List converts `r` to a List. func (r Result) List() List { list := make(List, len(r)) for k, v := range r { @@ -74,7 +76,8 @@ func (r Result) List() List { } // Array retrieves and returns specified column values as slice. -// The parameter <field> is optional is the column field is only one. +// The parameter `field` is optional is the column field is only one. +// The default `field` is the first field name of the first item in `Result` if parameter `field` is not given. func (r Result) Array(field ...string) []Value { array := make([]Value, len(r)) if len(r) == 0 { @@ -95,7 +98,7 @@ func (r Result) Array(field ...string) []Value { return array } -// MapKeyValue converts <r> to a map[string]Value of which key is specified by <key>. +// MapKeyValue converts `r` to a map[string]Value of which key is specified by `key`. // Note that the item value may be type of slice. func (r Result) MapKeyValue(key string) map[string]Value { var ( @@ -123,7 +126,7 @@ func (r Result) MapKeyValue(key string) map[string]Value { return m } -// MapKeyStr converts <r> to a map[string]Map of which key is specified by <key>. +// MapKeyStr converts `r` to a map[string]Map of which key is specified by `key`. func (r Result) MapKeyStr(key string) map[string]Map { m := make(map[string]Map) for _, item := range r { @@ -134,7 +137,7 @@ func (r Result) MapKeyStr(key string) map[string]Map { return m } -// MapKeyInt converts <r> to a map[int]Map of which key is specified by <key>. +// MapKeyInt converts `r` to a map[int]Map of which key is specified by `key`. func (r Result) MapKeyInt(key string) map[int]Map { m := make(map[int]Map) for _, item := range r { @@ -145,7 +148,7 @@ func (r Result) MapKeyInt(key string) map[int]Map { return m } -// MapKeyUint converts <r> to a map[uint]Map of which key is specified by <key>. +// MapKeyUint converts `r` to a map[uint]Map of which key is specified by `key`. func (r Result) MapKeyUint(key string) map[uint]Map { m := make(map[uint]Map) for _, item := range r { @@ -156,7 +159,7 @@ func (r Result) MapKeyUint(key string) map[uint]Map { return m } -// RecordKeyInt converts <r> to a map[int]Record of which key is specified by <key>. +// RecordKeyStr converts `r` to a map[string]Record of which key is specified by `key`. func (r Result) RecordKeyStr(key string) map[string]Record { m := make(map[string]Record) for _, item := range r { @@ -167,7 +170,7 @@ func (r Result) RecordKeyStr(key string) map[string]Record { return m } -// RecordKeyInt converts <r> to a map[int]Record of which key is specified by <key>. +// RecordKeyInt converts `r` to a map[int]Record of which key is specified by `key`. func (r Result) RecordKeyInt(key string) map[int]Record { m := make(map[int]Record) for _, item := range r { @@ -178,7 +181,7 @@ func (r Result) RecordKeyInt(key string) map[int]Record { return m } -// RecordKeyUint converts <r> to a map[uint]Record of which key is specified by <key>. +// RecordKeyUint converts `r` to a map[uint]Record of which key is specified by `key`. func (r Result) RecordKeyUint(key string) map[uint]Record { m := make(map[uint]Record) for _, item := range r { @@ -189,52 +192,8 @@ func (r Result) RecordKeyUint(key string) map[uint]Record { return m } -// Structs converts <r> to struct slice. -// Note that the parameter <pointer> should be type of *[]struct/*[]*struct. +// Structs converts `r` to struct slice. +// Note that the parameter `pointer` should be type of *[]struct/*[]*struct. func (r Result) Structs(pointer interface{}) (err error) { - var ( - reflectValue = reflect.ValueOf(pointer) - reflectKind = reflectValue.Kind() - ) - if reflectKind != reflect.Ptr { - return fmt.Errorf("parameter should be type of *[]struct/*[]*struct, but got: %v", reflectKind) - } - reflectValue = reflectValue.Elem() - reflectKind = reflectValue.Kind() - if reflectKind != reflect.Slice && reflectKind != reflect.Array { - return fmt.Errorf("parameter should be type of *[]struct/*[]*struct, but got: %v", reflectKind) - } - length := len(r) - if length == 0 { - // The pointed slice is not empty. - if reflectValue.Len() > 0 { - // It here checks if it has struct item, which is already initialized. - // It then returns error to warn the developer its empty and no conversion. - if v := reflectValue.Index(0); v.Kind() != reflect.Ptr { - return sql.ErrNoRows - } - } - // Do nothing for empty struct slice. - return nil - } - var ( - reflectType = reflect.TypeOf(pointer) - array = reflect.MakeSlice(reflectType.Elem(), length, length) - itemType = array.Index(0).Type() - itemKind = itemType.Kind() - ) - for i := 0; i < length; i++ { - var elem reflect.Value - if itemKind == reflect.Ptr { - elem = reflect.New(itemType.Elem()) - } else { - elem = reflect.New(itemType).Elem() - } - if err = r[i].Struct(elem); err != nil { - return fmt.Errorf(`slice element conversion failed: %s`, err.Error()) - } - array.Index(i).Set(elem) - } - reflect.ValueOf(pointer).Elem().Set(array) - return nil + return gconv.StructsTag(r, pointer, OrmTagForStruct) } diff --git a/database/gdb/gdb_type_result_deprecated.go b/database/gdb/gdb_type_result_deprecated.go deleted file mode 100644 index d9a991bab..000000000 --- a/database/gdb/gdb_type_result_deprecated.go +++ /dev/null @@ -1,57 +0,0 @@ -// Copyright GoFrame 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 - -// Deprecated. -func (r Result) ToJson() string { - return r.Json() -} - -// Deprecated. -func (r Result) ToXml(rootTag ...string) string { - return r.Xml(rootTag...) -} - -// Deprecated. -func (r Result) ToList() List { - return r.List() -} - -// Deprecated. -func (r Result) ToStringMap(key string) map[string]Map { - return r.MapKeyStr(key) -} - -// Deprecated. -func (r Result) ToIntMap(key string) map[int]Map { - return r.MapKeyInt(key) -} - -// Deprecated. -func (r Result) ToUintMap(key string) map[uint]Map { - return r.MapKeyUint(key) -} - -// Deprecated. -func (r Result) ToStringRecord(key string) map[string]Record { - return r.RecordKeyStr(key) -} - -// Deprecated. -func (r Result) ToIntRecord(key string) map[int]Record { - return r.RecordKeyInt(key) -} - -// Deprecated. -func (r Result) ToUintRecord(key string) map[uint]Record { - return r.RecordKeyUint(key) -} - -// Deprecated. -func (r Result) ToStructs(pointer interface{}) (err error) { - return r.Structs(pointer) -} diff --git a/database/gdb/gdb_type_result_scanlist.go b/database/gdb/gdb_type_result_scanlist.go index cf3a74f2d..4468c3bad 100644 --- a/database/gdb/gdb_type_result_scanlist.go +++ b/database/gdb/gdb_type_result_scanlist.go @@ -1,4 +1,4 @@ -// Copyright GoFrame Author(https://github.com/gogf/gf). All Rights Reserved. +// Copyright GoFrame Author(https://goframe.org). All Rights Reserved. // // This Source Code Form is subject to the terms of the MIT License. // If a copy of the MIT was not distributed with this file, @@ -8,7 +8,7 @@ package gdb import ( "database/sql" - "fmt" + "github.com/gogf/gf/errors/gcode" "github.com/gogf/gf/errors/gerror" "github.com/gogf/gf/text/gstr" "github.com/gogf/gf/util/gconv" @@ -16,8 +16,8 @@ import ( "reflect" ) -// ScanList converts <r> to struct slice which contains other complex struct attributes. -// Note that the parameter <listPointer> should be type of *[]struct/*[]*struct. +// ScanList converts `r` to struct slice which contains other complex struct attributes. +// Note that the parameter `listPointer` should be type of *[]struct/*[]*struct. // Usage example: // // type Entity struct { @@ -33,27 +33,30 @@ import ( // ScanList(&users, "UserDetail", "User", "uid:Uid") // ScanList(&users, "UserScores", "User", "uid:Uid") // -// The parameters "User"/"UserDetail"/"UserScores" in the example codes specify the target attribute struct +// The parameters "User/UserDetail/UserScores" in the example codes specify the target attribute struct // that current result will be bound to. // // The "uid" in the example codes is the table field name of the result, and the "Uid" is the relational // struct attribute name - not the attribute name of the bound to target. In the example codes, it's attribute // name "Uid" of "User" of entity "Entity". It automatically calculates the HasOne/HasMany relationship with -// given <relation> parameter. +// given `relation` parameter. // // See the example or unit testing cases for clear understanding for this function. -func (r Result) ScanList(listPointer interface{}, attributeName string, relation ...string) (err error) { - // Necessary checks for parameters. - if attributeName == "" { - return gerror.New(`attributeName should not be empty`) +func (r Result) ScanList(listPointer interface{}, bindToAttrName string, relationKV ...string) (err error) { + return doScanList(nil, r, listPointer, bindToAttrName, relationKV...) +} + +// doScanList converts `result` to struct slice which contains other complex struct attributes recursively. +// The parameter `model` is used for recursively scanning purpose, which means, it can scans the attribute struct/structs recursively but +// it needs the Model for database accessing. +// Note that the parameter `listPointer` should be type of *[]struct/*[]*struct. +func doScanList(model *Model, result Result, listPointer interface{}, bindToAttrName string, relationKV ...string) (err error) { + if result.IsEmpty() { + return nil } - if len(relation) > 0 { - if len(relation) < 2 { - return gerror.New(`relation name and key should are both necessary`) - } - if relation[0] == "" || relation[1] == "" { - return gerror.New(`relation name and key should not be empty`) - } + // Necessary checks for parameters. + if bindToAttrName == "" { + return gerror.NewCode(gcode.CodeInvalidParameter, `bindToAttrName should not be empty`) } var ( @@ -65,14 +68,14 @@ func (r Result) ScanList(listPointer interface{}, attributeName string, relation reflectKind = reflectValue.Kind() } if reflectKind != reflect.Ptr { - return fmt.Errorf("parameter should be type of *[]struct/*[]*struct, but got: %v", reflectKind) + return gerror.NewCodef(gcode.CodeInvalidParameter, "listPointer should be type of *[]struct/*[]*struct, but got: %v", reflectKind) } reflectValue = reflectValue.Elem() reflectKind = reflectValue.Kind() if reflectKind != reflect.Slice && reflectKind != reflect.Array { - return fmt.Errorf("parameter should be type of *[]struct/*[]*struct, but got: %v", reflectKind) + return gerror.NewCodef(gcode.CodeInvalidParameter, "listPointer should be type of *[]struct/*[]*struct, but got: %v", reflectKind) } - length := len(r) + length := len(result) if length == 0 { // The pointed slice is not empty. if reflectValue.Len() > 0 { @@ -101,59 +104,91 @@ func (r Result) ScanList(listPointer interface{}, attributeName string, relation // Relation variables. var ( - relationDataMap map[string]Value - relationFieldName string - relationAttrName string + relationKVStr string + relationDataMap map[string]Value + relationFromAttrName string // Eg: relationKV: User, uid:Uid -> User + relationResultFieldName string // Eg: relationKV: uid:Uid -> uid + relationBindToSubAttrName string // Eg: relationKV: uid:Uid -> Uid ) - if len(relation) > 0 { - array := gstr.Split(relation[1], ":") - if len(array) > 1 { + if len(relationKV) > 0 { + if len(relationKV) == 1 { + relationKVStr = relationKV[0] + } else { + relationFromAttrName = relationKV[0] + relationKVStr = relationKV[1] + } + // The relation key string of table filed name and attribute name + // can be joined with char '=' or ':'. + array := gstr.SplitAndTrim(relationKVStr, "=") + if len(array) == 1 { + // Compatible with old splitting char ':'. + array = gstr.SplitAndTrim(relationKVStr, ":") + } + if len(array) == 1 { + // The relation names are the same. + array = []string{relationKVStr, relationKVStr} + } + if len(array) == 2 { // Defined table field to relation attribute name. // Like: // uid:Uid // uid:UserId - relationFieldName = array[0] - relationAttrName = array[1] - } else { - relationAttrName = relation[1] - // Find the possible map key by given only struct attribute name. - // Like: - // Uid - if k, _ := gutil.MapPossibleItemByKey(r[0].Map(), relation[1]); k != "" { - relationFieldName = k + relationResultFieldName = array[0] + relationBindToSubAttrName = array[1] + if key, _ := gutil.MapPossibleItemByKey(result[0].Map(), relationResultFieldName); key == "" { + return gerror.NewCodef( + gcode.CodeInvalidParameter, + `cannot find possible related table field name "%s" from given relation key "%s"`, + relationResultFieldName, + relationKVStr, + ) + } else { + relationResultFieldName = key } + } else { + return gerror.NewCode(gcode.CodeInvalidParameter, `parameter relationKV should be format of "ResultFieldName:BindToAttrName"`) } - if relationFieldName != "" { - relationDataMap = r.MapKeyValue(relationFieldName) + if relationResultFieldName != "" { + // Note that the value might be type of slice. + relationDataMap = result.MapKeyValue(relationResultFieldName) } if len(relationDataMap) == 0 { - return fmt.Errorf(`cannot find the relation data map, maybe invalid relation key given: %s`, relation[1]) + return gerror.NewCodef(gcode.CodeInvalidParameter, `cannot find the relation data map, maybe invalid relation given "%v"`, relationKV) } } // Bind to target attribute. var ( - ok bool - attrValue reflect.Value - attrKind reflect.Kind - attrType reflect.Type - attrField reflect.StructField + ok bool + bindToAttrValue reflect.Value + bindToAttrKind reflect.Kind + bindToAttrType reflect.Type + bindToAttrField reflect.StructField ) if arrayItemType.Kind() == reflect.Ptr { - if attrField, ok = arrayItemType.Elem().FieldByName(attributeName); !ok { - return fmt.Errorf(`invalid field name: %s`, attributeName) + if bindToAttrField, ok = arrayItemType.Elem().FieldByName(bindToAttrName); !ok { + return gerror.NewCodef( + gcode.CodeInvalidParameter, + `invalid parameter bindToAttrName: cannot find attribute with name "%s" from slice element`, + bindToAttrName, + ) } } else { - if attrField, ok = arrayItemType.FieldByName(attributeName); !ok { - return fmt.Errorf(`invalid field name: %s`, attributeName) + if bindToAttrField, ok = arrayItemType.FieldByName(bindToAttrName); !ok { + return gerror.NewCodef( + gcode.CodeInvalidParameter, + `invalid parameter bindToAttrName: cannot find attribute with name "%s" from slice element`, + bindToAttrName, + ) } } - attrType = attrField.Type - attrKind = attrType.Kind() + bindToAttrType = bindToAttrField.Type + bindToAttrKind = bindToAttrType.Kind() // Bind to relation conditions. var ( - relationValue reflect.Value - relationField reflect.Value + relationFromAttrValue reflect.Value + relationFromAttrField reflect.Value + relationBindToSubAttrNameChecked bool ) for i := 0; i < arrayValue.Len(); i++ { arrayElemValue := arrayValue.Index(i) @@ -173,95 +208,169 @@ func (r Result) ScanList(listPointer interface{}, attributeName string, relation } else { // Like: []Entity } - attrValue = arrayElemValue.FieldByName(attributeName) - if len(relation) > 0 { - relationValue = arrayElemValue.FieldByName(relation[0]) - if relationValue.Kind() == reflect.Ptr { - relationValue = relationValue.Elem() + bindToAttrValue = arrayElemValue.FieldByName(bindToAttrName) + if relationFromAttrName != "" { + // Attribute value of current slice element. + relationFromAttrValue = arrayElemValue.FieldByName(relationFromAttrName) + if relationFromAttrValue.Kind() == reflect.Ptr { + relationFromAttrValue = relationFromAttrValue.Elem() } + } else { + // Current slice element. + relationFromAttrValue = arrayElemValue } - if len(relationDataMap) > 0 && !relationValue.IsValid() { - return fmt.Errorf(`invalid relation: "%s:%s"`, relation[0], relation[1]) + if len(relationDataMap) > 0 && !relationFromAttrValue.IsValid() { + return gerror.NewCodef(gcode.CodeInvalidParameter, `invalid relation specified: "%v"`, relationKV) } - switch attrKind { + // Check and find possible bind to attribute name. + if relationKVStr != "" && !relationBindToSubAttrNameChecked { + relationFromAttrField = relationFromAttrValue.FieldByName(relationBindToSubAttrName) + if !relationFromAttrField.IsValid() { + var ( + relationFromAttrType = relationFromAttrValue.Type() + filedMap = make(map[string]interface{}) + ) + for i := 0; i < relationFromAttrType.NumField(); i++ { + filedMap[relationFromAttrType.Field(i).Name] = struct{}{} + } + if key, _ := gutil.MapPossibleItemByKey(filedMap, relationBindToSubAttrName); key == "" { + return gerror.NewCodef( + gcode.CodeInvalidParameter, + `cannot find possible related attribute name "%s" from given relation key "%s"`, + relationBindToSubAttrName, + relationKVStr, + ) + } else { + relationBindToSubAttrName = key + } + } + relationBindToSubAttrNameChecked = true + } + switch bindToAttrKind { case reflect.Array, reflect.Slice: if len(relationDataMap) > 0 { - relationField = relationValue.FieldByName(relationAttrName) - if relationField.IsValid() { - if err = gconv.Structs( - relationDataMap[gconv.String(relationField.Interface())], - attrValue.Addr(), - ); err != nil { + relationFromAttrField = relationFromAttrValue.FieldByName(relationBindToSubAttrName) + if relationFromAttrField.IsValid() { + results := make(Result, 0) + for _, v := range relationDataMap[gconv.String(relationFromAttrField.Interface())].Slice() { + results = append(results, v.(Record)) + } + if err = results.Structs(bindToAttrValue.Addr()); err != nil { return err } + // Recursively Scan. + if model != nil { + if err = model.doWithScanStructs(bindToAttrValue.Addr()); err != nil { + return nil + } + } } else { // May be the attribute does not exist yet. - return fmt.Errorf(`invalid relation: "%s:%s"`, relation[0], relation[1]) + return gerror.NewCodef(gcode.CodeInvalidParameter, `invalid relation specified: "%v"`, relationKV) } } else { - return fmt.Errorf(`relationKey should not be empty as field "%s" is slice`, attributeName) + return gerror.NewCodef( + gcode.CodeInvalidParameter, + `relationKey should not be empty as field "%s" is slice`, + bindToAttrName, + ) } case reflect.Ptr: - e := reflect.New(attrType.Elem()).Elem() + var element reflect.Value + if bindToAttrValue.IsNil() { + element = reflect.New(bindToAttrType.Elem()).Elem() + } else { + element = bindToAttrValue.Elem() + } if len(relationDataMap) > 0 { - relationField = relationValue.FieldByName(relationAttrName) - if relationField.IsValid() { - v := relationDataMap[gconv.String(relationField.Interface())] + relationFromAttrField = relationFromAttrValue.FieldByName(relationBindToSubAttrName) + if relationFromAttrField.IsValid() { + v := relationDataMap[gconv.String(relationFromAttrField.Interface())] if v == nil { // There's no relational data. continue } - if err = gconv.Struct(v, e); err != nil { - return err + if v.IsSlice() { + if err = v.Slice()[0].(Record).Struct(element); err != nil { + return err + } + } else { + if err = v.Val().(Record).Struct(element); 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 gerror.NewCodef(gcode.CodeInvalidParameter, `invalid relation specified: "%v"`, relationKV) } } else { - v := r[i] + if i >= len(result) { + // There's no relational data. + continue + } + v := result[i] if v == nil { // There's no relational data. continue } - if err = gconv.Struct(v, e); err != nil { + if err = v.Struct(element); err != nil { return err } } - attrValue.Set(e.Addr()) + // Recursively Scan. + if model != nil { + if err = model.doWithScanStruct(element); err != nil { + return err + } + } + bindToAttrValue.Set(element.Addr()) case reflect.Struct: - e := reflect.New(attrType).Elem() if len(relationDataMap) > 0 { - relationField = relationValue.FieldByName(relationAttrName) - if relationField.IsValid() { - v := relationDataMap[gconv.String(relationField.Interface())] - if v == nil { + relationFromAttrField = relationFromAttrValue.FieldByName(relationBindToSubAttrName) + if relationFromAttrField.IsValid() { + relationDataItem := relationDataMap[gconv.String(relationFromAttrField.Interface())] + if relationDataItem == nil { // There's no relational data. continue } - if err = gconv.Struct(v, e); err != nil { - return err + if relationDataItem.IsSlice() { + if err = relationDataItem.Slice()[0].(Record).Struct(bindToAttrValue); err != nil { + return err + } + } else { + if err = relationDataItem.Val().(Record).Struct(bindToAttrValue); 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 gerror.NewCodef(gcode.CodeInvalidParameter, `invalid relation specified: "%v"`, relationKV) } } else { - v := r[i] - if v == nil { + if i >= len(result) { // There's no relational data. continue } - if err = gconv.Struct(v, e); err != nil { + relationDataItem := result[i] + if relationDataItem == nil { + // There's no relational data. + continue + } + if err = relationDataItem.Struct(bindToAttrValue); err != nil { + return err + } + } + // Recursively Scan. + if model != nil { + if err = model.doWithScanStruct(bindToAttrValue); err != nil { return err } } - attrValue.Set(e) default: - return fmt.Errorf(`unsupport attribute type: %s`, attrKind.String()) + return gerror.NewCodef(gcode.CodeInvalidParameter, `unsupported attribute type: %s`, bindToAttrKind.String()) } } reflect.ValueOf(listPointer).Elem().Set(arrayValue) diff --git a/database/gdb/gdb_z_driver_test.go b/database/gdb/gdb_z_driver_test.go index 7daef73bb..848a96a04 100644 --- a/database/gdb/gdb_z_driver_test.go +++ b/database/gdb/gdb_z_driver_test.go @@ -1,4 +1,4 @@ -// Copyright GoFrame Author(https://github.com/gogf/gf). All Rights Reserved. +// Copyright GoFrame Author(https://goframe.org). All Rights Reserved. // // This Source Code Form is subject to the terms of the MIT License. // If a copy of the MIT was not distributed with this file, @@ -7,18 +7,20 @@ package gdb_test import ( + "context" + "testing" + "github.com/gogf/gf/container/gtype" "github.com/gogf/gf/database/gdb" "github.com/gogf/gf/frame/g" "github.com/gogf/gf/test/gtest" - "testing" ) // MyDriver is a custom database driver, which is used for testing only. // For simplifying the unit testing case purpose, MyDriver struct inherits the mysql driver -// gdb.DriverMysql and overwrites its function HandleSqlBeforeCommit. -// So if there's any sql execution, it goes through MyDriver.HandleSqlBeforeCommit firstly and -// then gdb.DriverMysql.HandleSqlBeforeCommit. +// gdb.DriverMysql and overwrites its function DoCommit. +// So if there's any sql execution, it goes through MyDriver.DoCommit firstly and +// then gdb.DriverMysql.DoCommit. // You can call it sql "HOOK" or "HiJack" as your will. type MyDriver struct { *gdb.DriverMysql @@ -39,11 +41,11 @@ func (d *MyDriver) New(core *gdb.Core, node *gdb.ConfigNode) (gdb.DB, error) { }, nil } -// HandleSqlBeforeCommit handles the sql before posts it to database. +// DoCommit handles the sql before posts it to database. // It here overwrites the same method of gdb.DriverMysql and makes some custom changes. -func (d *MyDriver) HandleSqlBeforeCommit(link gdb.Link, sql string, args []interface{}) (string, []interface{}) { +func (d *MyDriver) DoCommit(ctx context.Context, link gdb.Link, sql string, args []interface{}) (newSql string, newArgs []interface{}, err error) { latestSqlString.Set(sql) - return d.DriverMysql.HandleSqlBeforeCommit(link, sql, args) + return d.DriverMysql.DoCommit(ctx, link, sql, args) } func init() { @@ -56,8 +58,8 @@ func Test_Custom_Driver(t *testing.T) { gdb.AddConfigNode("driver-test", gdb.ConfigNode{ Host: "127.0.0.1", Port: "3306", - User: USER, - Pass: PASS, + User: TestDbUser, + Pass: TestDbPass, Name: "test", Type: customDriverName, Role: "master", @@ -67,7 +69,7 @@ func Test_Custom_Driver(t *testing.T) { t.Assert(latestSqlString.Val(), "") sqlString := "select 10000" value, err := g.DB("driver-test").GetValue(sqlString) - t.Assert(err, nil) + t.AssertNil(err) t.Assert(value, 10000) t.Assert(latestSqlString.Val(), sqlString) }) diff --git a/database/gdb/gdb_z_example_test.go b/database/gdb/gdb_z_example_test.go index b9f4edfa9..8b692a788 100644 --- a/database/gdb/gdb_z_example_test.go +++ b/database/gdb/gdb_z_example_test.go @@ -1,4 +1,4 @@ -// Copyright GoFrame Author(https://github.com/gogf/gf). All Rights Reserved. +// Copyright GoFrame Author(https://goframe.org). All Rights Reserved. // // This Source Code Form is subject to the terms of the MIT License. // If a copy of the MIT was not distributed with this file, @@ -7,12 +7,14 @@ package gdb_test import ( + "context" + "github.com/gogf/gf/database/gdb" "github.com/gogf/gf/frame/g" ) func Example_transaction() { - db.Transaction(func(tx *gdb.TX) error { + db.Transaction(context.TODO(), func(ctx context.Context, tx *gdb.TX) error { // user result, err := tx.Insert("user", g.Map{ "passport": "john", diff --git a/database/gdb/gdb_z_init_test.go b/database/gdb/gdb_z_init_test.go index b95081a8b..9e73bf49c 100644 --- a/database/gdb/gdb_z_init_test.go +++ b/database/gdb/gdb_z_init_test.go @@ -1,4 +1,4 @@ -// Copyright GoFrame Author(https://github.com/gogf/gf). All Rights Reserved. +// Copyright GoFrame Author(https://goframe.org). All Rights Reserved. // // This Source Code Form is subject to the terms of the MIT License. // If a copy of the MIT was not distributed with this file, @@ -7,6 +7,7 @@ package gdb_test import ( + "context" "fmt" "github.com/gogf/gf/container/garray" "github.com/gogf/gf/frame/g" @@ -18,19 +19,21 @@ import ( ) const ( - SIZE = 10 - TABLE = "user" - SCHEMA1 = "test1" - SCHEMA2 = "test2" - PREFIX1 = "gf_" - USER = "root" - PASS = "12345678" + TableSize = 10 + TableName = "user" + TestSchema1 = "test1" + TestSchema2 = "test2" + TableNamePrefix1 = "gf_" + TestDbUser = "root" + TestDbPass = "12345678" + CreateTime = "2018-10-24 10:00:00" ) var ( - db gdb.DB - dbPrefix gdb.DB - configNode gdb.ConfigNode + db gdb.DB + dbPrefix gdb.DB + dbCtxStrict gdb.DB + configNode gdb.ConfigNode ) func init() { @@ -38,12 +41,12 @@ func init() { "name": true, "type": true, }, false) - gtest.Assert(err, nil) + gtest.AssertNil(err) configNode = gdb.ConfigNode{ Host: "127.0.0.1", Port: "3306", - User: USER, - Pass: PASS, + User: TestDbUser, + Pass: TestDbPass, Name: parser.GetOpt("name", ""), Type: parser.GetOpt("type", "mysql"), Role: "master", @@ -51,13 +54,19 @@ func init() { Weight: 1, MaxIdleConnCount: 10, MaxOpenConnCount: 10, - MaxConnLifetime: 600, + MaxConnLifeTime: 600, } nodePrefix := configNode - nodePrefix.Prefix = PREFIX1 + nodePrefix.Prefix = TableNamePrefix1 + + nodeCtxStrict := configNode + nodeCtxStrict.CtxStrict = true + gdb.AddConfigNode("test", configNode) gdb.AddConfigNode("prefix", nodePrefix) + gdb.AddConfigNode("ctxstrict", nodeCtxStrict) gdb.AddConfigNode(gdb.DefaultGroupName, configNode) + // Default db. if r, err := gdb.New(); err != nil { gtest.Error(err) @@ -65,14 +74,13 @@ func init() { db = r } schemaTemplate := "CREATE DATABASE IF NOT EXISTS `%s` CHARACTER SET UTF8" - if _, err := db.Exec(fmt.Sprintf(schemaTemplate, SCHEMA1)); err != nil { + if _, err := db.Exec(fmt.Sprintf(schemaTemplate, TestSchema1)); err != nil { gtest.Error(err) } - if _, err := db.Exec(fmt.Sprintf(schemaTemplate, SCHEMA2)); err != nil { + if _, err := db.Exec(fmt.Sprintf(schemaTemplate, TestSchema2)); err != nil { gtest.Error(err) } - db.SetSchema(SCHEMA1) - createTable(TABLE) + db.SetSchema(TestSchema1) // Prefix db. if r, err := gdb.New("prefix"); err != nil { @@ -80,14 +88,27 @@ func init() { } else { dbPrefix = r } - if _, err := dbPrefix.Exec(fmt.Sprintf(schemaTemplate, SCHEMA1)); err != nil { + if _, err := dbPrefix.Exec(fmt.Sprintf(schemaTemplate, TestSchema1)); err != nil { gtest.Error(err) } - if _, err := dbPrefix.Exec(fmt.Sprintf(schemaTemplate, SCHEMA2)); err != nil { + if _, err := dbPrefix.Exec(fmt.Sprintf(schemaTemplate, TestSchema2)); err != nil { gtest.Error(err) } - dbPrefix.SetSchema(SCHEMA1) - createTable(TABLE) + dbPrefix.SetSchema(TestSchema1) + + // CtxStrict db. + if r, err := gdb.New("ctxstrict"); err != nil { + gtest.Error(err) + } else { + dbCtxStrict = r + } + if _, err := dbCtxStrict.Ctx(context.TODO()).Exec(fmt.Sprintf(schemaTemplate, TestSchema1)); err != nil { + gtest.Error(err) + } + if _, err := dbCtxStrict.Ctx(context.TODO()).Exec(fmt.Sprintf(schemaTemplate, TestSchema2)); err != nil { + gtest.Error(err) + } + dbCtxStrict.SetSchema(TestSchema1) } func createTable(table ...string) string { @@ -106,13 +127,13 @@ func createTableWithDb(db gdb.DB, table ...string) (name string) { if len(table) > 0 { name = table[0] } else { - name = fmt.Sprintf(`%s_%d`, TABLE, gtime.TimestampNano()) + name = fmt.Sprintf(`%s_%d`, TableName, gtime.TimestampNano()) } dropTableWithDb(db, name) switch configNode.Type { case "sqlite": - if _, err := db.Exec(fmt.Sprintf(` + if _, err := db.Ctx(context.TODO()).Exec(fmt.Sprintf(` CREATE TABLE %s ( id bigint unsigned NOT NULL AUTO_INCREMENT, passport varchar(45), @@ -125,7 +146,7 @@ func createTableWithDb(db gdb.DB, table ...string) (name string) { gtest.Fatal(err) } case "pgsql": - if _, err := db.Exec(fmt.Sprintf(` + if _, err := db.Ctx(context.TODO()).Exec(fmt.Sprintf(` CREATE TABLE %s ( id bigint NOT NULL, passport varchar(45), @@ -138,7 +159,7 @@ func createTableWithDb(db gdb.DB, table ...string) (name string) { gtest.Fatal(err) } case "mssql": - if _, err := db.Exec(fmt.Sprintf(` + if _, err := db.Ctx(context.TODO()).Exec(fmt.Sprintf(` IF NOT EXISTS (SELECT * FROM sysobjects WHERE name='%s' and xtype='U') CREATE TABLE %s ( ID numeric(10,0) NOT NULL, @@ -152,7 +173,7 @@ func createTableWithDb(db gdb.DB, table ...string) (name string) { gtest.Fatal(err) } case "oracle": - if _, err := db.Exec(fmt.Sprintf(` + if _, err := db.Ctx(context.TODO()).Exec(fmt.Sprintf(` CREATE TABLE %s ( ID NUMBER(10) NOT NULL, PASSPORT VARCHAR(45) NOT NULL, @@ -165,7 +186,7 @@ func createTableWithDb(db gdb.DB, table ...string) (name string) { gtest.Fatal(err) } case "mysql": - if _, err := db.Exec(fmt.Sprintf(` + if _, err := db.Ctx(context.TODO()).Exec(fmt.Sprintf(` CREATE TABLE %s ( id int(10) unsigned NOT NULL AUTO_INCREMENT, passport varchar(45) NULL, @@ -186,27 +207,27 @@ func createTableWithDb(db gdb.DB, table ...string) (name string) { func createInitTableWithDb(db gdb.DB, table ...string) (name string) { name = createTableWithDb(db, table...) array := garray.New(true) - for i := 1; i <= SIZE; i++ { + for i := 1; i <= TableSize; i++ { array.Append(g.Map{ "id": i, "passport": fmt.Sprintf(`user_%d`, i), "password": fmt.Sprintf(`pass_%d`, i), "nickname": fmt.Sprintf(`name_%d`, i), - "create_time": gtime.NewFromStr("2018-10-24 10:00:00").String(), + "create_time": gtime.NewFromStr(CreateTime).String(), }) } - result, err := db.BatchInsert(name, array.Slice()) - gtest.Assert(err, nil) + result, err := db.Ctx(context.TODO()).Insert(name, array.Slice()) + gtest.AssertNil(err) n, e := result.RowsAffected() gtest.Assert(e, nil) - gtest.Assert(n, SIZE) + gtest.Assert(n, TableSize) return } func dropTableWithDb(db gdb.DB, table string) { - if _, err := db.Exec(fmt.Sprintf("DROP TABLE IF EXISTS `%s`", table)); err != nil { + if _, err := db.Ctx(context.TODO()).Exec(fmt.Sprintf("DROP TABLE IF EXISTS `%s`", table)); err != nil { gtest.Error(err) } } diff --git a/database/gdb/gdb_z_mysql_association_scanlist_test.go b/database/gdb/gdb_z_mysql_association_scanlist_test.go new file mode 100644 index 000000000..54bf0ad9e --- /dev/null +++ b/database/gdb/gdb_z_mysql_association_scanlist_test.go @@ -0,0 +1,1672 @@ +// Copyright GoFrame Author(https://goframe.org). All Rights Reserved. +// +// This Source Code Form is subject to the terms of the MIT License. +// If a copy of the MIT was not distributed with this file, +// You can obtain one at https://github.com/gogf/gf. + +package gdb_test + +import ( + "context" + "fmt" + "testing" + + "github.com/gogf/gf/database/gdb" + "github.com/gogf/gf/frame/g" + "github.com/gogf/gf/util/gconv" + + "github.com/gogf/gf/os/gtime" + "github.com/gogf/gf/test/gtest" +) + +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(context.TODO(), func(ctx context.Context, tx *gdb.TX) error { + r, err := tx.Model(tableUser).Save(EntityUser{ + Name: "john", + }) + if err != nil { + return err + } + uid, err := r.LastInsertId() + if err != nil { + return err + } + _, err = tx.Model(tableUserDetail).Save(EntityUserDetail{ + Uid: int(uid), + Address: "Beijing DongZhiMen #66", + }) + if err != nil { + return err + } + _, err = tx.Model(tableUserScores).Save(g.Slice{ + EntityUserScores{Uid: int(uid), Score: 100, Course: "math"}, + EntityUserScores{Uid: int(uid), Score: 99, Course: "physics"}, + }) + return err + }) + t.AssertNil(err) + }) + // Data check. + gtest.C(t, func(t *gtest.T) { + r, err := db.Model(tableUser).All() + t.AssertNil(err) + t.Assert(r.Len(), 1) + t.Assert(r[0]["uid"].Int(), 1) + t.Assert(r[0]["name"].String(), "john") + + r, err = db.Model(tableUserDetail).Where("uid", r[0]["uid"].Int()).All() + t.AssertNil(err) + t.Assert(r.Len(), 1) + t.Assert(r[0]["uid"].Int(), 1) + t.Assert(r[0]["address"].String(), `Beijing DongZhiMen #66`) + + r, err = db.Model(tableUserScores).Where("uid", r[0]["uid"].Int()).All() + t.AssertNil(err) + 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.Model(tableUser).Scan(&user.User, "name", "john") + t.AssertNil(err) + + // SELECT * FROM `user_detail` WHERE `uid`=1 + err = db.Model(tableUserDetail).Scan(&user.UserDetail, "uid", user.User.Uid) + t.AssertNil(err) + + // SELECT * FROM `user_scores` WHERE `uid`=1 + err = db.Model(tableUserScores).Scan(&user.UserScores, "uid", user.User.Uid) + t.AssertNil(err) + + 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() + tableUserScores = "user_scores_" + gtime.TimestampMicroStr() + ) + if _, err := db.Exec(fmt.Sprintf(` +CREATE TABLE %s ( + uid int(10) unsigned NOT NULL AUTO_INCREMENT, + name varchar(45) NOT NULL, + PRIMARY KEY (uid) +) ENGINE=InnoDB DEFAULT CHARSET=utf8; + `, tableUser)); err != nil { + gtest.Error(err) + } + defer dropTable(tableUser) + + if _, err := db.Exec(fmt.Sprintf(` +CREATE TABLE %s ( + uid int(10) unsigned NOT NULL AUTO_INCREMENT, + address varchar(45) NOT NULL, + PRIMARY KEY (uid) +) ENGINE=InnoDB DEFAULT CHARSET=utf8; + `, tableUserDetail)); err != nil { + gtest.Error(err) + } + defer dropTable(tableUserDetail) + + if _, err := db.Exec(fmt.Sprintf(` +CREATE TABLE %s ( + id int(10) unsigned NOT NULL AUTO_INCREMENT, + uid int(10) unsigned NOT NULL, + score int(10) unsigned NOT NULL, + PRIMARY KEY (id) +) ENGINE=InnoDB DEFAULT CHARSET=utf8; + `, tableUserScores)); err != nil { + gtest.Error(err) + } + defer dropTable(tableUserScores) + + type EntityUser struct { + Uid int `json:"uid"` + Name string `json:"name"` + } + type EntityUserDetail struct { + Uid int `json:"uid"` + Address string `json:"address"` + } + type EntityUserScores struct { + Id int `json:"id"` + Uid int `json:"uid"` + Score int `json:"score"` + } + type Entity struct { + User *EntityUser + UserDetail *EntityUserDetail + UserScores []*EntityUserScores + } + + // Initialize the data. + gtest.C(t, func(t *gtest.T) { + var err error + for i := 1; i <= 5; i++ { + // User. + _, err = db.Insert(tableUser, g.Map{ + "uid": i, + "name": fmt.Sprintf(`name_%d`, i), + }) + t.AssertNil(err) + // Detail. + _, err = db.Insert(tableUserDetail, g.Map{ + "uid": i, + "address": fmt.Sprintf(`address_%d`, i), + }) + t.AssertNil(err) + // Scores. + for j := 1; j <= 5; j++ { + _, err = db.Insert(tableUserScores, g.Map{ + "uid": i, + "score": j, + }) + t.AssertNil(err) + } + } + }) + + // MapKeyValue. + gtest.C(t, func(t *gtest.T) { + all, err := db.Model(tableUser).Where("uid", g.Slice{3, 4}).Order("uid asc").All() + t.AssertNil(err) + t.Assert(all.Len(), 2) + t.Assert(len(all.MapKeyValue("uid")), 2) + t.Assert(all.MapKeyValue("uid")["3"].Map()["uid"], 3) + t.Assert(all.MapKeyValue("uid")["4"].Map()["uid"], 4) + all, err = db.Model(tableUserScores).Where("uid", g.Slice{3, 4}).Order("id asc").All() + t.AssertNil(err) + t.Assert(all.Len(), 10) + t.Assert(len(all.MapKeyValue("uid")), 2) + t.Assert(len(all.MapKeyValue("uid")["3"].Slice()), 5) + t.Assert(len(all.MapKeyValue("uid")["4"].Slice()), 5) + t.Assert(gconv.Map(all.MapKeyValue("uid")["3"].Slice()[0])["uid"], 3) + t.Assert(gconv.Map(all.MapKeyValue("uid")["3"].Slice()[0])["score"], 1) + t.Assert(gconv.Map(all.MapKeyValue("uid")["3"].Slice()[4])["uid"], 3) + t.Assert(gconv.Map(all.MapKeyValue("uid")["3"].Slice()[4])["score"], 5) + }) + // Result ScanList with struct elements and pointer attributes. + gtest.C(t, func(t *gtest.T) { + var users []Entity + // User + all, err := db.Model(tableUser).Where("uid", g.Slice{3, 4}).Order("uid asc").All() + t.AssertNil(err) + err = all.ScanList(&users, "User") + t.AssertNil(err) + t.Assert(len(users), 2) + t.Assert(users[0].User, &EntityUser{3, "name_3"}) + t.Assert(users[1].User, &EntityUser{4, "name_4"}) + // Detail + all, err = db.Model(tableUserDetail).Where("uid", gdb.ListItemValues(users, "User", "Uid")).Order("uid asc").All() + t.AssertNil(err) + err = all.ScanList(&users, "UserDetail", "User", "uid:Uid") + t.AssertNil(err) + t.Assert(users[0].UserDetail, &EntityUserDetail{3, "address_3"}) + t.Assert(users[1].UserDetail, &EntityUserDetail{4, "address_4"}) + // Scores + all, err = db.Model(tableUserScores).Where("uid", gdb.ListItemValues(users, "User", "Uid")).Order("id asc").All() + t.AssertNil(err) + err = all.ScanList(&users, "UserScores", "User", "uid:Uid") + t.AssertNil(err) + t.Assert(len(users[0].UserScores), 5) + t.Assert(len(users[1].UserScores), 5) + t.Assert(users[0].UserScores[0].Uid, 3) + t.Assert(users[0].UserScores[0].Score, 1) + t.Assert(users[0].UserScores[4].Score, 5) + t.Assert(users[1].UserScores[0].Uid, 4) + t.Assert(users[1].UserScores[0].Score, 1) + t.Assert(users[1].UserScores[4].Score, 5) + }) + + // Result ScanList with pointer elements and pointer attributes. + gtest.C(t, func(t *gtest.T) { + var users []*Entity + // User + all, err := db.Model(tableUser).Where("uid", g.Slice{3, 4}).Order("uid asc").All() + t.AssertNil(err) + err = all.ScanList(&users, "User") + t.AssertNil(err) + t.Assert(len(users), 2) + t.Assert(users[0].User, &EntityUser{3, "name_3"}) + t.Assert(users[1].User, &EntityUser{4, "name_4"}) + // Detail + all, err = db.Model(tableUserDetail).Where("uid", gdb.ListItemValues(users, "User", "Uid")).Order("uid asc").All() + t.AssertNil(err) + err = all.ScanList(&users, "UserDetail", "User", "uid:Uid") + t.AssertNil(err) + t.Assert(users[0].UserDetail, &EntityUserDetail{3, "address_3"}) + t.Assert(users[1].UserDetail, &EntityUserDetail{4, "address_4"}) + // Scores + all, err = db.Model(tableUserScores).Where("uid", gdb.ListItemValues(users, "User", "Uid")).Order("id asc").All() + t.AssertNil(err) + err = all.ScanList(&users, "UserScores", "User", "uid:Uid") + t.AssertNil(err) + t.Assert(len(users[0].UserScores), 5) + t.Assert(len(users[1].UserScores), 5) + t.Assert(users[0].UserScores[0].Uid, 3) + t.Assert(users[0].UserScores[0].Score, 1) + t.Assert(users[0].UserScores[4].Score, 5) + t.Assert(users[1].UserScores[0].Uid, 4) + t.Assert(users[1].UserScores[0].Score, 1) + t.Assert(users[1].UserScores[4].Score, 5) + }) + + // Result ScanList with struct elements and struct attributes. + gtest.C(t, func(t *gtest.T) { + type EntityUser struct { + Uid int `json:"uid"` + Name string `json:"name"` + } + type EntityUserDetail struct { + Uid int `json:"uid"` + Address string `json:"address"` + } + type EntityUserScores struct { + Id int `json:"id"` + Uid int `json:"uid"` + Score int `json:"score"` + } + type Entity struct { + User EntityUser + UserDetail EntityUserDetail + UserScores []EntityUserScores + } + var users []Entity + // User + all, err := db.Model(tableUser).Where("uid", g.Slice{3, 4}).Order("uid asc").All() + t.AssertNil(err) + err = all.ScanList(&users, "User") + t.AssertNil(err) + t.Assert(len(users), 2) + t.Assert(users[0].User, &EntityUser{3, "name_3"}) + t.Assert(users[1].User, &EntityUser{4, "name_4"}) + // Detail + all, err = db.Model(tableUserDetail).Where("uid", gdb.ListItemValues(users, "User", "Uid")).Order("uid asc").All() + t.AssertNil(err) + err = all.ScanList(&users, "UserDetail", "User", "uid:Uid") + t.AssertNil(err) + t.Assert(users[0].UserDetail, &EntityUserDetail{3, "address_3"}) + t.Assert(users[1].UserDetail, &EntityUserDetail{4, "address_4"}) + // Scores + all, err = db.Model(tableUserScores).Where("uid", gdb.ListItemValues(users, "User", "Uid")).Order("id asc").All() + t.AssertNil(err) + err = all.ScanList(&users, "UserScores", "User", "uid:Uid") + t.AssertNil(err) + t.Assert(len(users[0].UserScores), 5) + t.Assert(len(users[1].UserScores), 5) + t.Assert(users[0].UserScores[0].Uid, 3) + t.Assert(users[0].UserScores[0].Score, 1) + t.Assert(users[0].UserScores[4].Score, 5) + t.Assert(users[1].UserScores[0].Uid, 4) + t.Assert(users[1].UserScores[0].Score, 1) + t.Assert(users[1].UserScores[4].Score, 5) + }) + + // Result ScanList with pointer elements and struct attributes. + gtest.C(t, func(t *gtest.T) { + type EntityUser struct { + Uid int `json:"uid"` + Name string `json:"name"` + } + type EntityUserDetail struct { + Uid int `json:"uid"` + Address string `json:"address"` + } + type EntityUserScores struct { + Id int `json:"id"` + Uid int `json:"uid"` + Score int `json:"score"` + } + type Entity struct { + User EntityUser + UserDetail EntityUserDetail + UserScores []EntityUserScores + } + var users []*Entity + + // User + all, err := db.Model(tableUser).Where("uid", g.Slice{3, 4}).Order("uid asc").All() + t.AssertNil(err) + err = all.ScanList(&users, "User") + t.AssertNil(err) + t.Assert(len(users), 2) + t.Assert(users[0].User, &EntityUser{3, "name_3"}) + t.Assert(users[1].User, &EntityUser{4, "name_4"}) + // Detail + all, err = db.Model(tableUserDetail).Where("uid", gdb.ListItemValues(users, "User", "Uid")).Order("uid asc").All() + t.AssertNil(err) + err = all.ScanList(&users, "UserDetail", "User", "uid:Uid") + t.AssertNil(err) + t.Assert(users[0].UserDetail, &EntityUserDetail{3, "address_3"}) + t.Assert(users[1].UserDetail, &EntityUserDetail{4, "address_4"}) + // Scores + all, err = db.Model(tableUserScores).Where("uid", gdb.ListItemValues(users, "User", "Uid")).Order("id asc").All() + t.AssertNil(err) + err = all.ScanList(&users, "UserScores", "User", "uid:Uid") + t.AssertNil(err) + t.Assert(len(users[0].UserScores), 5) + t.Assert(len(users[1].UserScores), 5) + t.Assert(users[0].UserScores[0].Uid, 3) + t.Assert(users[0].UserScores[0].Score, 1) + t.Assert(users[0].UserScores[4].Score, 5) + t.Assert(users[1].UserScores[0].Uid, 4) + t.Assert(users[1].UserScores[0].Score, 1) + t.Assert(users[1].UserScores[4].Score, 5) + }) + + // Model ScanList with pointer elements and pointer attributes. + gtest.C(t, func(t *gtest.T) { + var users []*Entity + // User + err := db.Model(tableUser). + Where("uid", g.Slice{3, 4}). + Order("uid asc"). + ScanList(&users, "User") + t.AssertNil(err) + // Detail + err = db.Model(tableUserDetail). + Where("uid", gdb.ListItemValues(users, "User", "Uid")). + Order("uid asc"). + ScanList(&users, "UserDetail", "User", "uid:Uid") + t.AssertNil(err) + // Scores + err = db.Model(tableUserScores). + Where("uid", gdb.ListItemValues(users, "User", "Uid")). + Order("id asc"). + ScanList(&users, "UserScores", "User", "uid:Uid") + t.AssertNil(err) + + t.Assert(len(users), 2) + t.Assert(users[0].User, &EntityUser{3, "name_3"}) + t.Assert(users[1].User, &EntityUser{4, "name_4"}) + + t.Assert(users[0].UserDetail, &EntityUserDetail{3, "address_3"}) + t.Assert(users[1].UserDetail, &EntityUserDetail{4, "address_4"}) + + t.Assert(len(users[0].UserScores), 5) + t.Assert(len(users[1].UserScores), 5) + t.Assert(users[0].UserScores[0].Uid, 3) + t.Assert(users[0].UserScores[0].Score, 1) + t.Assert(users[0].UserScores[4].Score, 5) + t.Assert(users[1].UserScores[0].Uid, 4) + t.Assert(users[1].UserScores[0].Score, 1) + t.Assert(users[1].UserScores[4].Score, 5) + }) +} + +func Test_Table_Relation_Many_RelationKeyCaseInsensitive(t *testing.T) { + var ( + tableUser = "user_" + gtime.TimestampMicroStr() + tableUserDetail = "user_detail_" + gtime.TimestampMicroStr() + tableUserScores = "user_scores_" + gtime.TimestampMicroStr() + ) + if _, err := db.Exec(fmt.Sprintf(` +CREATE TABLE %s ( + uid int(10) unsigned NOT NULL AUTO_INCREMENT, + name varchar(45) NOT NULL, + PRIMARY KEY (uid) +) ENGINE=InnoDB DEFAULT CHARSET=utf8; + `, tableUser)); err != nil { + gtest.Error(err) + } + defer dropTable(tableUser) + + if _, err := db.Exec(fmt.Sprintf(` +CREATE TABLE %s ( + uid int(10) unsigned NOT NULL AUTO_INCREMENT, + address varchar(45) NOT NULL, + PRIMARY KEY (uid) +) ENGINE=InnoDB DEFAULT CHARSET=utf8; + `, tableUserDetail)); err != nil { + gtest.Error(err) + } + defer dropTable(tableUserDetail) + + if _, err := db.Exec(fmt.Sprintf(` +CREATE TABLE %s ( + id int(10) unsigned NOT NULL AUTO_INCREMENT, + uid int(10) unsigned NOT NULL, + score int(10) unsigned NOT NULL, + PRIMARY KEY (id) +) ENGINE=InnoDB DEFAULT CHARSET=utf8; + `, tableUserScores)); err != nil { + gtest.Error(err) + } + defer dropTable(tableUserScores) + + type EntityUser struct { + Uid int `json:"uid"` + Name string `json:"name"` + } + type EntityUserDetail struct { + Uid int `json:"uid"` + Address string `json:"address"` + } + type EntityUserScores struct { + Id int `json:"id"` + Uid int `json:"uid"` + Score int `json:"score"` + } + type Entity struct { + User *EntityUser + UserDetail *EntityUserDetail + UserScores []*EntityUserScores + } + + // Initialize the data. + gtest.C(t, func(t *gtest.T) { + var err error + for i := 1; i <= 5; i++ { + // User. + _, err = db.Insert(tableUser, g.Map{ + "uid": i, + "name": fmt.Sprintf(`name_%d`, i), + }) + t.AssertNil(err) + // Detail. + _, err = db.Insert(tableUserDetail, g.Map{ + "uid": i, + "address": fmt.Sprintf(`address_%d`, i), + }) + t.AssertNil(err) + // Scores. + for j := 1; j <= 5; j++ { + _, err = db.Insert(tableUserScores, g.Map{ + "uid": i, + "score": j, + }) + t.AssertNil(err) + } + } + }) + + // MapKeyValue. + gtest.C(t, func(t *gtest.T) { + all, err := db.Model(tableUser).Where("uid", g.Slice{3, 4}).Order("uid asc").All() + t.AssertNil(err) + t.Assert(all.Len(), 2) + t.Assert(len(all.MapKeyValue("uid")), 2) + t.Assert(all.MapKeyValue("uid")["3"].Map()["uid"], 3) + t.Assert(all.MapKeyValue("uid")["4"].Map()["uid"], 4) + all, err = db.Model(tableUserScores).Where("uid", g.Slice{3, 4}).Order("id asc").All() + t.AssertNil(err) + t.Assert(all.Len(), 10) + t.Assert(len(all.MapKeyValue("uid")), 2) + t.Assert(len(all.MapKeyValue("uid")["3"].Slice()), 5) + t.Assert(len(all.MapKeyValue("uid")["4"].Slice()), 5) + t.Assert(gconv.Map(all.MapKeyValue("uid")["3"].Slice()[0])["uid"], 3) + t.Assert(gconv.Map(all.MapKeyValue("uid")["3"].Slice()[0])["score"], 1) + t.Assert(gconv.Map(all.MapKeyValue("uid")["3"].Slice()[4])["uid"], 3) + t.Assert(gconv.Map(all.MapKeyValue("uid")["3"].Slice()[4])["score"], 5) + }) + // Result ScanList with struct elements and pointer attributes. + gtest.C(t, func(t *gtest.T) { + var users []Entity + // User + all, err := db.Model(tableUser).Where("uid", g.Slice{3, 4}).Order("uid asc").All() + t.AssertNil(err) + err = all.ScanList(&users, "User") + t.AssertNil(err) + t.Assert(len(users), 2) + t.Assert(users[0].User, &EntityUser{3, "name_3"}) + t.Assert(users[1].User, &EntityUser{4, "name_4"}) + // Detail + all, err = db.Model(tableUserDetail).Where("uid", gdb.ListItemValues(users, "User", "Uid")).Order("uid asc").All() + t.AssertNil(err) + err = all.ScanList(&users, "UserDetail", "User", "uid:uid") + t.AssertNil(err) + t.Assert(users[0].UserDetail, &EntityUserDetail{3, "address_3"}) + t.Assert(users[1].UserDetail, &EntityUserDetail{4, "address_4"}) + // Scores + all, err = db.Model(tableUserScores).Where("uid", gdb.ListItemValues(users, "User", "Uid")).Order("id asc").All() + t.AssertNil(err) + err = all.ScanList(&users, "UserScores", "User", "uid:uid") + t.AssertNil(err) + t.Assert(len(users[0].UserScores), 5) + t.Assert(len(users[1].UserScores), 5) + t.Assert(users[0].UserScores[0].Uid, 3) + t.Assert(users[0].UserScores[0].Score, 1) + t.Assert(users[0].UserScores[4].Score, 5) + t.Assert(users[1].UserScores[0].Uid, 4) + t.Assert(users[1].UserScores[0].Score, 1) + t.Assert(users[1].UserScores[4].Score, 5) + }) + + // Result ScanList with pointer elements and pointer attributes. + gtest.C(t, func(t *gtest.T) { + var users []*Entity + // User + all, err := db.Model(tableUser).Where("uid", g.Slice{3, 4}).Order("uid asc").All() + t.AssertNil(err) + err = all.ScanList(&users, "User") + t.AssertNil(err) + t.Assert(len(users), 2) + t.Assert(users[0].User, &EntityUser{3, "name_3"}) + t.Assert(users[1].User, &EntityUser{4, "name_4"}) + // Detail + all, err = db.Model(tableUserDetail).Where("uid", gdb.ListItemValues(users, "User", "Uid")).Order("uid asc").All() + t.AssertNil(err) + err = all.ScanList(&users, "UserDetail", "User", "Uid:UID") + t.AssertNil(err) + t.Assert(users[0].UserDetail, &EntityUserDetail{3, "address_3"}) + t.Assert(users[1].UserDetail, &EntityUserDetail{4, "address_4"}) + // Scores + all, err = db.Model(tableUserScores).Where("uid", gdb.ListItemValues(users, "User", "Uid")).Order("id asc").All() + t.AssertNil(err) + err = all.ScanList(&users, "UserScores", "User", "Uid:UID") + t.AssertNil(err) + t.Assert(len(users[0].UserScores), 5) + t.Assert(len(users[1].UserScores), 5) + t.Assert(users[0].UserScores[0].Uid, 3) + t.Assert(users[0].UserScores[0].Score, 1) + t.Assert(users[0].UserScores[4].Score, 5) + t.Assert(users[1].UserScores[0].Uid, 4) + t.Assert(users[1].UserScores[0].Score, 1) + t.Assert(users[1].UserScores[4].Score, 5) + }) + + // Result ScanList with struct elements and struct attributes. + gtest.C(t, func(t *gtest.T) { + type EntityUser struct { + Uid int `json:"uid"` + Name string `json:"name"` + } + type EntityUserDetail struct { + Uid int `json:"uid"` + Address string `json:"address"` + } + type EntityUserScores struct { + Id int `json:"id"` + Uid int `json:"uid"` + Score int `json:"score"` + } + type Entity struct { + User EntityUser + UserDetail EntityUserDetail + UserScores []EntityUserScores + } + var users []Entity + // User + all, err := db.Model(tableUser).Where("uid", g.Slice{3, 4}).Order("uid asc").All() + t.AssertNil(err) + err = all.ScanList(&users, "User") + t.AssertNil(err) + t.Assert(len(users), 2) + t.Assert(users[0].User, &EntityUser{3, "name_3"}) + t.Assert(users[1].User, &EntityUser{4, "name_4"}) + // Detail + all, err = db.Model(tableUserDetail).Where("uid", gdb.ListItemValues(users, "User", "Uid")).Order("uid asc").All() + t.AssertNil(err) + err = all.ScanList(&users, "UserDetail", "User", "uid:UId") + t.AssertNil(err) + t.Assert(users[0].UserDetail, &EntityUserDetail{3, "address_3"}) + t.Assert(users[1].UserDetail, &EntityUserDetail{4, "address_4"}) + // Scores + all, err = db.Model(tableUserScores).Where("uid", gdb.ListItemValues(users, "User", "Uid")).Order("id asc").All() + t.AssertNil(err) + err = all.ScanList(&users, "UserScores", "User", "UId:Uid") + t.AssertNil(err) + t.Assert(len(users[0].UserScores), 5) + t.Assert(len(users[1].UserScores), 5) + t.Assert(users[0].UserScores[0].Uid, 3) + t.Assert(users[0].UserScores[0].Score, 1) + t.Assert(users[0].UserScores[4].Score, 5) + t.Assert(users[1].UserScores[0].Uid, 4) + t.Assert(users[1].UserScores[0].Score, 1) + t.Assert(users[1].UserScores[4].Score, 5) + }) + + // Result ScanList with pointer elements and struct attributes. + gtest.C(t, func(t *gtest.T) { + type EntityUser struct { + Uid int `json:"uid"` + Name string `json:"name"` + } + type EntityUserDetail struct { + Uid int `json:"uid"` + Address string `json:"address"` + } + type EntityUserScores struct { + Id int `json:"id"` + Uid int `json:"uid"` + Score int `json:"score"` + } + type Entity struct { + User EntityUser + UserDetail EntityUserDetail + UserScores []EntityUserScores + } + var users []*Entity + + // User + all, err := db.Model(tableUser).Where("uid", g.Slice{3, 4}).Order("uid asc").All() + t.AssertNil(err) + err = all.ScanList(&users, "User") + t.AssertNil(err) + t.Assert(len(users), 2) + t.Assert(users[0].User, &EntityUser{3, "name_3"}) + t.Assert(users[1].User, &EntityUser{4, "name_4"}) + // Detail + all, err = db.Model(tableUserDetail).Where("uid", gdb.ListItemValues(users, "User", "Uid")).Order("uid asc").All() + t.AssertNil(err) + err = all.ScanList(&users, "UserDetail", "User", "uid:Uid") + t.AssertNil(err) + t.Assert(users[0].UserDetail, &EntityUserDetail{3, "address_3"}) + t.Assert(users[1].UserDetail, &EntityUserDetail{4, "address_4"}) + // Scores + all, err = db.Model(tableUserScores).Where("uid", gdb.ListItemValues(users, "User", "Uid")).Order("id asc").All() + t.AssertNil(err) + err = all.ScanList(&users, "UserScores", "User", "UID:Uid") + t.AssertNil(err) + t.Assert(len(users[0].UserScores), 5) + t.Assert(len(users[1].UserScores), 5) + t.Assert(users[0].UserScores[0].Uid, 3) + t.Assert(users[0].UserScores[0].Score, 1) + t.Assert(users[0].UserScores[4].Score, 5) + t.Assert(users[1].UserScores[0].Uid, 4) + t.Assert(users[1].UserScores[0].Score, 1) + t.Assert(users[1].UserScores[4].Score, 5) + }) + + // Model ScanList with pointer elements and pointer attributes. + gtest.C(t, func(t *gtest.T) { + var users []*Entity + // User + err := db.Model(tableUser). + Where("uid", g.Slice{3, 4}). + Order("uid asc"). + ScanList(&users, "User") + t.AssertNil(err) + // Detail + err = db.Model(tableUserDetail). + Where("uid", gdb.ListItemValues(users, "User", "Uid")). + Order("uid asc"). + ScanList(&users, "UserDetail", "User", "uid:Uid") + t.AssertNil(err) + // Scores + err = db.Model(tableUserScores). + Where("uid", gdb.ListItemValues(users, "User", "Uid")). + Order("id asc"). + ScanList(&users, "UserScores", "User", "uid:Uid") + t.AssertNil(err) + + t.Assert(len(users), 2) + t.Assert(users[0].User, &EntityUser{3, "name_3"}) + t.Assert(users[1].User, &EntityUser{4, "name_4"}) + + t.Assert(users[0].UserDetail, &EntityUserDetail{3, "address_3"}) + t.Assert(users[1].UserDetail, &EntityUserDetail{4, "address_4"}) + + t.Assert(len(users[0].UserScores), 5) + t.Assert(len(users[1].UserScores), 5) + t.Assert(users[0].UserScores[0].Uid, 3) + t.Assert(users[0].UserScores[0].Score, 1) + t.Assert(users[0].UserScores[4].Score, 5) + t.Assert(users[1].UserScores[0].Uid, 4) + t.Assert(users[1].UserScores[0].Score, 1) + t.Assert(users[1].UserScores[4].Score, 5) + }) +} + +func Test_Table_Relation_Many_TheSameRelationNames(t *testing.T) { + var ( + tableUser = "user_" + gtime.TimestampMicroStr() + tableUserDetail = "user_detail_" + gtime.TimestampMicroStr() + tableUserScores = "user_scores_" + gtime.TimestampMicroStr() + ) + if _, err := db.Exec(fmt.Sprintf(` +CREATE TABLE %s ( + uid int(10) unsigned NOT NULL AUTO_INCREMENT, + name varchar(45) NOT NULL, + PRIMARY KEY (uid) +) ENGINE=InnoDB DEFAULT CHARSET=utf8; + `, tableUser)); err != nil { + gtest.Error(err) + } + defer dropTable(tableUser) + + if _, err := db.Exec(fmt.Sprintf(` +CREATE TABLE %s ( + uid int(10) unsigned NOT NULL AUTO_INCREMENT, + address varchar(45) NOT NULL, + PRIMARY KEY (uid) +) ENGINE=InnoDB DEFAULT CHARSET=utf8; + `, tableUserDetail)); err != nil { + gtest.Error(err) + } + defer dropTable(tableUserDetail) + + if _, err := db.Exec(fmt.Sprintf(` +CREATE TABLE %s ( + id int(10) unsigned NOT NULL AUTO_INCREMENT, + uid int(10) unsigned NOT NULL, + score int(10) unsigned NOT NULL, + PRIMARY KEY (id) +) ENGINE=InnoDB DEFAULT CHARSET=utf8; + `, tableUserScores)); err != nil { + gtest.Error(err) + } + defer dropTable(tableUserScores) + + type EntityUser struct { + Uid int `json:"uid"` + Name string `json:"name"` + } + type EntityUserDetail struct { + Uid int `json:"uid"` + Address string `json:"address"` + } + type EntityUserScores struct { + Id int `json:"id"` + Uid int `json:"uid"` + Score int `json:"score"` + } + type Entity struct { + User *EntityUser + UserDetail *EntityUserDetail + UserScores []*EntityUserScores + } + + // Initialize the data. + gtest.C(t, func(t *gtest.T) { + var err error + for i := 1; i <= 5; i++ { + // User. + _, err = db.Insert(tableUser, g.Map{ + "uid": i, + "name": fmt.Sprintf(`name_%d`, i), + }) + t.AssertNil(err) + // Detail. + _, err = db.Insert(tableUserDetail, g.Map{ + "uid": i, + "address": fmt.Sprintf(`address_%d`, i), + }) + t.AssertNil(err) + // Scores. + for j := 1; j <= 5; j++ { + _, err = db.Insert(tableUserScores, g.Map{ + "uid": i, + "score": j, + }) + t.AssertNil(err) + } + } + }) + + // Result ScanList with struct elements and pointer attributes. + gtest.C(t, func(t *gtest.T) { + var users []Entity + // User + all, err := db.Model(tableUser).Where("uid", g.Slice{3, 4}).Order("uid asc").All() + t.AssertNil(err) + err = all.ScanList(&users, "User") + t.AssertNil(err) + t.Assert(len(users), 2) + t.Assert(users[0].User, &EntityUser{3, "name_3"}) + t.Assert(users[1].User, &EntityUser{4, "name_4"}) + // Detail + all, err = db.Model(tableUserDetail).Where("uid", gdb.ListItemValues(users, "User", "Uid")).Order("uid asc").All() + t.AssertNil(err) + err = all.ScanList(&users, "UserDetail", "User", "uid") + t.AssertNil(err) + t.Assert(users[0].UserDetail, &EntityUserDetail{3, "address_3"}) + t.Assert(users[1].UserDetail, &EntityUserDetail{4, "address_4"}) + // Scores + all, err = db.Model(tableUserScores).Where("uid", gdb.ListItemValues(users, "User", "Uid")).Order("id asc").All() + t.AssertNil(err) + err = all.ScanList(&users, "UserScores", "User", "uid") + t.AssertNil(err) + t.Assert(len(users[0].UserScores), 5) + t.Assert(len(users[1].UserScores), 5) + t.Assert(users[0].UserScores[0].Uid, 3) + t.Assert(users[0].UserScores[0].Score, 1) + t.Assert(users[0].UserScores[4].Score, 5) + t.Assert(users[1].UserScores[0].Uid, 4) + t.Assert(users[1].UserScores[0].Score, 1) + t.Assert(users[1].UserScores[4].Score, 5) + }) + + // Result ScanList with pointer elements and pointer attributes. + gtest.C(t, func(t *gtest.T) { + var users []*Entity + // User + all, err := db.Model(tableUser).Where("uid", g.Slice{3, 4}).Order("uid asc").All() + t.AssertNil(err) + err = all.ScanList(&users, "User") + t.AssertNil(err) + t.Assert(len(users), 2) + t.Assert(users[0].User, &EntityUser{3, "name_3"}) + t.Assert(users[1].User, &EntityUser{4, "name_4"}) + // Detail + all, err = db.Model(tableUserDetail).Where("uid", gdb.ListItemValues(users, "User", "Uid")).Order("uid asc").All() + t.AssertNil(err) + err = all.ScanList(&users, "UserDetail", "User", "Uid") + t.AssertNil(err) + t.Assert(users[0].UserDetail, &EntityUserDetail{3, "address_3"}) + t.Assert(users[1].UserDetail, &EntityUserDetail{4, "address_4"}) + // Scores + all, err = db.Model(tableUserScores).Where("uid", gdb.ListItemValues(users, "User", "Uid")).Order("id asc").All() + t.AssertNil(err) + err = all.ScanList(&users, "UserScores", "User", "UID") + t.AssertNil(err) + t.Assert(len(users[0].UserScores), 5) + t.Assert(len(users[1].UserScores), 5) + t.Assert(users[0].UserScores[0].Uid, 3) + t.Assert(users[0].UserScores[0].Score, 1) + t.Assert(users[0].UserScores[4].Score, 5) + t.Assert(users[1].UserScores[0].Uid, 4) + t.Assert(users[1].UserScores[0].Score, 1) + t.Assert(users[1].UserScores[4].Score, 5) + }) + + // Result ScanList with struct elements and struct attributes. + gtest.C(t, func(t *gtest.T) { + type EntityUser struct { + Uid int `json:"uid"` + Name string `json:"name"` + } + type EntityUserDetail struct { + Uid int `json:"uid"` + Address string `json:"address"` + } + type EntityUserScores struct { + Id int `json:"id"` + Uid int `json:"uid"` + Score int `json:"score"` + } + type Entity struct { + User EntityUser + UserDetail EntityUserDetail + UserScores []EntityUserScores + } + var users []Entity + // User + all, err := db.Model(tableUser).Where("uid", g.Slice{3, 4}).Order("uid asc").All() + t.AssertNil(err) + err = all.ScanList(&users, "User") + t.AssertNil(err) + t.Assert(len(users), 2) + t.Assert(users[0].User, &EntityUser{3, "name_3"}) + t.Assert(users[1].User, &EntityUser{4, "name_4"}) + // Detail + all, err = db.Model(tableUserDetail).Where("uid", gdb.ListItemValues(users, "User", "Uid")).Order("uid asc").All() + t.AssertNil(err) + err = all.ScanList(&users, "UserDetail", "User", "UId") + t.AssertNil(err) + t.Assert(users[0].UserDetail, &EntityUserDetail{3, "address_3"}) + t.Assert(users[1].UserDetail, &EntityUserDetail{4, "address_4"}) + // Scores + all, err = db.Model(tableUserScores).Where("uid", gdb.ListItemValues(users, "User", "Uid")).Order("id asc").All() + t.AssertNil(err) + err = all.ScanList(&users, "UserScores", "User", "Uid") + t.AssertNil(err) + t.Assert(len(users[0].UserScores), 5) + t.Assert(len(users[1].UserScores), 5) + t.Assert(users[0].UserScores[0].Uid, 3) + t.Assert(users[0].UserScores[0].Score, 1) + t.Assert(users[0].UserScores[4].Score, 5) + t.Assert(users[1].UserScores[0].Uid, 4) + t.Assert(users[1].UserScores[0].Score, 1) + t.Assert(users[1].UserScores[4].Score, 5) + }) + + // Result ScanList with pointer elements and struct attributes. + gtest.C(t, func(t *gtest.T) { + type EntityUser struct { + Uid int `json:"uid"` + Name string `json:"name"` + } + type EntityUserDetail struct { + Uid int `json:"uid"` + Address string `json:"address"` + } + type EntityUserScores struct { + Id int `json:"id"` + Uid int `json:"uid"` + Score int `json:"score"` + } + type Entity struct { + User EntityUser + UserDetail EntityUserDetail + UserScores []EntityUserScores + } + var users []*Entity + + // User + all, err := db.Model(tableUser).Where("uid", g.Slice{3, 4}).Order("uid asc").All() + t.AssertNil(err) + err = all.ScanList(&users, "User") + t.AssertNil(err) + t.Assert(len(users), 2) + t.Assert(users[0].User, &EntityUser{3, "name_3"}) + t.Assert(users[1].User, &EntityUser{4, "name_4"}) + // Detail + all, err = db.Model(tableUserDetail).Where("uid", gdb.ListItemValues(users, "User", "Uid")).Order("uid asc").All() + t.AssertNil(err) + err = all.ScanList(&users, "UserDetail", "User", "uid") + t.AssertNil(err) + t.Assert(users[0].UserDetail, &EntityUserDetail{3, "address_3"}) + t.Assert(users[1].UserDetail, &EntityUserDetail{4, "address_4"}) + // Scores + all, err = db.Model(tableUserScores).Where("uid", gdb.ListItemValues(users, "User", "Uid")).Order("id asc").All() + t.AssertNil(err) + err = all.ScanList(&users, "UserScores", "User", "UID") + t.AssertNil(err) + t.Assert(len(users[0].UserScores), 5) + t.Assert(len(users[1].UserScores), 5) + t.Assert(users[0].UserScores[0].Uid, 3) + t.Assert(users[0].UserScores[0].Score, 1) + t.Assert(users[0].UserScores[4].Score, 5) + t.Assert(users[1].UserScores[0].Uid, 4) + t.Assert(users[1].UserScores[0].Score, 1) + t.Assert(users[1].UserScores[4].Score, 5) + }) + + // Model ScanList with pointer elements and pointer attributes. + gtest.C(t, func(t *gtest.T) { + var users []*Entity + // User + err := db.Model(tableUser). + Where("uid", g.Slice{3, 4}). + Order("uid asc"). + ScanList(&users, "User") + t.AssertNil(err) + // Detail + err = db.Model(tableUserDetail). + Where("uid", gdb.ListItemValues(users, "User", "Uid")). + Order("uid asc"). + ScanList(&users, "UserDetail", "User", "uid") + t.AssertNil(err) + // Scores + err = db.Model(tableUserScores). + Where("uid", gdb.ListItemValues(users, "User", "Uid")). + Order("id asc"). + ScanList(&users, "UserScores", "User", "uid") + t.AssertNil(err) + + t.Assert(len(users), 2) + t.Assert(users[0].User, &EntityUser{3, "name_3"}) + t.Assert(users[1].User, &EntityUser{4, "name_4"}) + + t.Assert(users[0].UserDetail, &EntityUserDetail{3, "address_3"}) + t.Assert(users[1].UserDetail, &EntityUserDetail{4, "address_4"}) + + t.Assert(len(users[0].UserScores), 5) + t.Assert(len(users[1].UserScores), 5) + t.Assert(users[0].UserScores[0].Uid, 3) + t.Assert(users[0].UserScores[0].Score, 1) + t.Assert(users[0].UserScores[4].Score, 5) + t.Assert(users[1].UserScores[0].Uid, 4) + t.Assert(users[1].UserScores[0].Score, 1) + t.Assert(users[1].UserScores[4].Score, 5) + }) +} + +func Test_Table_Relation_EmptyData(t *testing.T) { + var ( + tableUser = "user_" + gtime.TimestampMicroStr() + tableUserDetail = "user_detail_" + gtime.TimestampMicroStr() + tableUserScores = "user_scores_" + gtime.TimestampMicroStr() + ) + if _, err := db.Exec(fmt.Sprintf(` +CREATE TABLE %s ( + uid int(10) unsigned NOT NULL AUTO_INCREMENT, + name varchar(45) NOT NULL, + PRIMARY KEY (uid) +) ENGINE=InnoDB DEFAULT CHARSET=utf8; + `, tableUser)); err != nil { + gtest.Error(err) + } + defer dropTable(tableUser) + + if _, err := db.Exec(fmt.Sprintf(` +CREATE TABLE %s ( + uid int(10) unsigned NOT NULL AUTO_INCREMENT, + address varchar(45) NOT NULL, + PRIMARY KEY (uid) +) ENGINE=InnoDB DEFAULT CHARSET=utf8; + `, tableUserDetail)); err != nil { + gtest.Error(err) + } + defer dropTable(tableUserDetail) + + if _, err := db.Exec(fmt.Sprintf(` +CREATE TABLE %s ( + id int(10) unsigned NOT NULL AUTO_INCREMENT, + uid int(10) unsigned NOT NULL, + score int(10) unsigned NOT NULL, + PRIMARY KEY (id) +) ENGINE=InnoDB DEFAULT CHARSET=utf8; + `, tableUserScores)); err != nil { + gtest.Error(err) + } + defer dropTable(tableUserScores) + + type EntityUser struct { + Uid int `json:"uid"` + Name string `json:"name"` + } + type EntityUserDetail struct { + Uid int `json:"uid"` + Address string `json:"address"` + } + type EntityUserScores struct { + Id int `json:"id"` + Uid int `json:"uid"` + Score int `json:"score"` + } + type Entity struct { + User *EntityUser + UserDetail *EntityUserDetail + UserScores []*EntityUserScores + } + + // Result ScanList with struct elements and pointer attributes. + gtest.C(t, func(t *gtest.T) { + var users []Entity + // User + all, err := db.Model(tableUser).Where("uid", g.Slice{3, 4}).Order("uid asc").All() + t.AssertNil(err) + err = all.ScanList(&users, "User") + t.AssertNil(err) + t.Assert(len(users), 0) + // Detail + all, err = db.Model(tableUserDetail).Where("uid", gdb.ListItemValues(users, "User", "Uid")).Order("uid asc").All() + t.AssertNil(err) + err = all.ScanList(&users, "UserDetail", "User", "uid:uid") + t.AssertNil(err) + + // Scores + all, err = db.Model(tableUserScores).Where("uid", gdb.ListItemValues(users, "User", "Uid")).Order("id asc").All() + t.AssertNil(err) + err = all.ScanList(&users, "UserScores", "User", "uid:uid") + t.AssertNil(err) + }) + return + // Result ScanList with pointer elements and pointer attributes. + gtest.C(t, func(t *gtest.T) { + var users []*Entity + // User + all, err := db.Model(tableUser).Where("uid", g.Slice{3, 4}).Order("uid asc").All() + t.AssertNil(err) + err = all.ScanList(&users, "User") + t.AssertNil(err) + t.Assert(len(users), 0) + + // Detail + all, err = db.Model(tableUserDetail).Where("uid", gdb.ListItemValues(users, "User", "Uid")).Order("uid asc").All() + t.AssertNil(err) + err = all.ScanList(&users, "UserDetail", "User", "Uid:UID") + t.AssertNil(err) + + // Scores + all, err = db.Model(tableUserScores).Where("uid", gdb.ListItemValues(users, "User", "Uid")).Order("id asc").All() + t.AssertNil(err) + err = all.ScanList(&users, "UserScores", "User", "Uid:UID") + t.AssertNil(err) + }) + + // Result ScanList with struct elements and struct attributes. + gtest.C(t, func(t *gtest.T) { + type EntityUser struct { + Uid int `json:"uid"` + Name string `json:"name"` + } + type EntityUserDetail struct { + Uid int `json:"uid"` + Address string `json:"address"` + } + type EntityUserScores struct { + Id int `json:"id"` + Uid int `json:"uid"` + Score int `json:"score"` + } + type Entity struct { + User EntityUser + UserDetail EntityUserDetail + UserScores []EntityUserScores + } + var users []Entity + // User + all, err := db.Model(tableUser).Where("uid", g.Slice{3, 4}).Order("uid asc").All() + t.AssertNil(err) + err = all.ScanList(&users, "User") + t.AssertNil(err) + + // Detail + all, err = db.Model(tableUserDetail).Where("uid", gdb.ListItemValues(users, "User", "Uid")).Order("uid asc").All() + t.AssertNil(err) + err = all.ScanList(&users, "UserDetail", "User", "uid:UId") + t.AssertNil(err) + + // Scores + all, err = db.Model(tableUserScores).Where("uid", gdb.ListItemValues(users, "User", "Uid")).Order("id asc").All() + t.AssertNil(err) + err = all.ScanList(&users, "UserScores", "User", "UId:Uid") + t.AssertNil(err) + }) + + // Result ScanList with pointer elements and struct attributes. + gtest.C(t, func(t *gtest.T) { + type EntityUser struct { + Uid int `json:"uid"` + Name string `json:"name"` + } + type EntityUserDetail struct { + Uid int `json:"uid"` + Address string `json:"address"` + } + type EntityUserScores struct { + Id int `json:"id"` + Uid int `json:"uid"` + Score int `json:"score"` + } + type Entity struct { + User EntityUser + UserDetail EntityUserDetail + UserScores []EntityUserScores + } + var users []*Entity + + // User + all, err := db.Model(tableUser).Where("uid", g.Slice{3, 4}).Order("uid asc").All() + t.AssertNil(err) + err = all.ScanList(&users, "User") + t.AssertNil(err) + t.Assert(len(users), 0) + // Detail + all, err = db.Model(tableUserDetail).Where("uid", gdb.ListItemValues(users, "User", "Uid")).Order("uid asc").All() + t.AssertNil(err) + err = all.ScanList(&users, "UserDetail", "User", "uid:Uid") + t.AssertNil(err) + + // Scores + all, err = db.Model(tableUserScores).Where("uid", gdb.ListItemValues(users, "User", "Uid")).Order("id asc").All() + t.AssertNil(err) + err = all.ScanList(&users, "UserScores", "User", "UID:Uid") + t.AssertNil(err) + }) + + // Model ScanList with pointer elements and pointer attributes. + gtest.C(t, func(t *gtest.T) { + var users []*Entity + // User + err := db.Model(tableUser). + Where("uid", g.Slice{3, 4}). + Order("uid asc"). + ScanList(&users, "User") + t.AssertNil(err) + // Detail + err = db.Model(tableUserDetail). + Where("uid", gdb.ListItemValues(users, "User", "Uid")). + Order("uid asc"). + ScanList(&users, "UserDetail", "User", "uid:Uid") + t.AssertNil(err) + // Scores + err = db.Model(tableUserScores). + Where("uid", gdb.ListItemValues(users, "User", "Uid")). + Order("id asc"). + ScanList(&users, "UserScores", "User", "uid:Uid") + t.AssertNil(err) + + t.Assert(len(users), 0) + }) +} + +func Test_Table_Relation_NoneEqualDataSize(t *testing.T) { + var ( + tableUser = "user_" + gtime.TimestampMicroStr() + tableUserDetail = "user_detail_" + gtime.TimestampMicroStr() + tableUserScores = "user_scores_" + gtime.TimestampMicroStr() + ) + if _, err := db.Exec(fmt.Sprintf(` +CREATE TABLE %s ( + uid int(10) unsigned NOT NULL AUTO_INCREMENT, + name varchar(45) NOT NULL, + PRIMARY KEY (uid) +) ENGINE=InnoDB DEFAULT CHARSET=utf8; + `, tableUser)); err != nil { + gtest.Error(err) + } + defer dropTable(tableUser) + + if _, err := db.Exec(fmt.Sprintf(` +CREATE TABLE %s ( + uid int(10) unsigned NOT NULL AUTO_INCREMENT, + address varchar(45) NOT NULL, + PRIMARY KEY (uid) +) ENGINE=InnoDB DEFAULT CHARSET=utf8; + `, tableUserDetail)); err != nil { + gtest.Error(err) + } + defer dropTable(tableUserDetail) + + if _, err := db.Exec(fmt.Sprintf(` +CREATE TABLE %s ( + id int(10) unsigned NOT NULL AUTO_INCREMENT, + uid int(10) unsigned NOT NULL, + score int(10) unsigned NOT NULL, + PRIMARY KEY (id) +) ENGINE=InnoDB DEFAULT CHARSET=utf8; + `, tableUserScores)); err != nil { + gtest.Error(err) + } + defer dropTable(tableUserScores) + + type EntityUser struct { + Uid int `json:"uid"` + Name string `json:"name"` + } + type EntityUserDetail struct { + Uid int `json:"uid"` + Address string `json:"address"` + } + type EntityUserScores struct { + Id int `json:"id"` + Uid int `json:"uid"` + Score int `json:"score"` + } + type Entity struct { + User *EntityUser + UserDetail *EntityUserDetail + UserScores []*EntityUserScores + } + + // Initialize the data. + gtest.C(t, func(t *gtest.T) { + var err error + for i := 1; i <= 5; i++ { + // User. + _, err = db.Insert(tableUser, g.Map{ + "uid": i, + "name": fmt.Sprintf(`name_%d`, i), + }) + t.AssertNil(err) + // Detail. + //_, err = db.Insert(tableUserDetail, g.Map{ + // "uid": i, + // "address": fmt.Sprintf(`address_%d`, i), + //}) + //t.AssertNil(err) + // Scores. + //for j := 1; j <= 5; j++ { + // _, err = db.Insert(tableUserScores, g.Map{ + // "uid": i, + // "score": j, + // }) + // t.AssertNil(err) + //} + } + }) + + // Result ScanList with struct elements and pointer attributes. + gtest.C(t, func(t *gtest.T) { + var users []Entity + // User + all, err := db.Model(tableUser).Where("uid", g.Slice{3, 4}).Order("uid asc").All() + t.AssertNil(err) + err = all.ScanList(&users, "User") + t.AssertNil(err) + t.Assert(len(users), 2) + t.Assert(users[0].User, &EntityUser{3, "name_3"}) + t.Assert(users[1].User, &EntityUser{4, "name_4"}) + // Detail + all, err = db.Model(tableUserDetail).Where("uid", gdb.ListItemValues(users, "User", "Uid")).Order("uid asc").All() + t.AssertNil(err) + err = all.ScanList(&users, "UserDetail", "User", "uid") + t.AssertNil(err) + t.Assert(users[0].UserDetail, nil) + // Scores + all, err = db.Model(tableUserScores).Where("uid", gdb.ListItemValues(users, "User", "Uid")).Order("id asc").All() + t.AssertNil(err) + err = all.ScanList(&users, "UserScores", "User", "uid") + t.AssertNil(err) + t.Assert(len(users[0].UserScores), 0) + }) + + // Result ScanList with pointer elements and pointer attributes. + gtest.C(t, func(t *gtest.T) { + var users []*Entity + // User + all, err := db.Model(tableUser).Where("uid", g.Slice{3, 4}).Order("uid asc").All() + t.AssertNil(err) + err = all.ScanList(&users, "User") + t.AssertNil(err) + t.Assert(len(users), 2) + t.Assert(users[0].User, &EntityUser{3, "name_3"}) + t.Assert(users[1].User, &EntityUser{4, "name_4"}) + // Detail + all, err = db.Model(tableUserDetail).Where("uid", gdb.ListItemValues(users, "User", "Uid")).Order("uid asc").All() + t.AssertNil(err) + err = all.ScanList(&users, "UserDetail", "User", "Uid") + t.AssertNil(err) + t.Assert(users[0].UserDetail, nil) + // Scores + all, err = db.Model(tableUserScores).Where("uid", gdb.ListItemValues(users, "User", "Uid")).Order("id asc").All() + t.AssertNil(err) + err = all.ScanList(&users, "UserScores", "User", "UID") + t.AssertNil(err) + t.Assert(len(users[0].UserScores), 0) + }) + + // Result ScanList with struct elements and struct attributes. + gtest.C(t, func(t *gtest.T) { + type EntityUser struct { + Uid int `json:"uid"` + Name string `json:"name"` + } + type EntityUserDetail struct { + Uid int `json:"uid"` + Address string `json:"address"` + } + type EntityUserScores struct { + Id int `json:"id"` + Uid int `json:"uid"` + Score int `json:"score"` + } + type Entity struct { + User EntityUser + UserDetail EntityUserDetail + UserScores []EntityUserScores + } + var users []Entity + // User + all, err := db.Model(tableUser).Where("uid", g.Slice{3, 4}).Order("uid asc").All() + t.AssertNil(err) + err = all.ScanList(&users, "User") + t.AssertNil(err) + t.Assert(len(users), 2) + t.Assert(users[0].User, &EntityUser{3, "name_3"}) + t.Assert(users[1].User, &EntityUser{4, "name_4"}) + // Detail + all, err = db.Model(tableUserDetail).Where("uid", gdb.ListItemValues(users, "User", "Uid")).Order("uid asc").All() + t.AssertNil(err) + err = all.ScanList(&users, "UserDetail", "User", "UId") + t.AssertNil(err) + t.Assert(users[0].UserDetail, EntityUserDetail{}) + // Scores + all, err = db.Model(tableUserScores).Where("uid", gdb.ListItemValues(users, "User", "Uid")).Order("id asc").All() + t.AssertNil(err) + err = all.ScanList(&users, "UserScores", "User", "Uid") + t.AssertNil(err) + t.Assert(len(users[0].UserScores), 0) + }) + + // Result ScanList with pointer elements and struct attributes. + gtest.C(t, func(t *gtest.T) { + type EntityUser struct { + Uid int `json:"uid"` + Name string `json:"name"` + } + type EntityUserDetail struct { + Uid int `json:"uid"` + Address string `json:"address"` + } + type EntityUserScores struct { + Id int `json:"id"` + Uid int `json:"uid"` + Score int `json:"score"` + } + type Entity struct { + User EntityUser + UserDetail EntityUserDetail + UserScores []EntityUserScores + } + var users []*Entity + + // User + all, err := db.Model(tableUser).Where("uid", g.Slice{3, 4}).Order("uid asc").All() + t.AssertNil(err) + err = all.ScanList(&users, "User") + t.AssertNil(err) + t.Assert(len(users), 2) + t.Assert(users[0].User, &EntityUser{3, "name_3"}) + t.Assert(users[1].User, &EntityUser{4, "name_4"}) + // Detail + all, err = db.Model(tableUserDetail).Where("uid", gdb.ListItemValues(users, "User", "Uid")).Order("uid asc").All() + t.AssertNil(err) + err = all.ScanList(&users, "UserDetail", "User", "uid") + t.AssertNil(err) + t.Assert(users[0].UserDetail, EntityUserDetail{}) + // Scores + all, err = db.Model(tableUserScores).Where("uid", gdb.ListItemValues(users, "User", "Uid")).Order("id asc").All() + t.AssertNil(err) + err = all.ScanList(&users, "UserScores", "User", "UID") + t.AssertNil(err) + t.Assert(len(users[0].UserScores), 0) + }) + + // Model ScanList with pointer elements and pointer attributes. + gtest.C(t, func(t *gtest.T) { + var users []*Entity + // User + err := db.Model(tableUser). + Where("uid", g.Slice{3, 4}). + Order("uid asc"). + ScanList(&users, "User") + t.AssertNil(err) + // Detail + err = db.Model(tableUserDetail). + Where("uid", gdb.ListItemValues(users, "User", "Uid")). + Order("uid asc"). + ScanList(&users, "UserDetail", "User", "uid") + t.AssertNil(err) + // Scores + err = db.Model(tableUserScores). + Where("uid", gdb.ListItemValues(users, "User", "Uid")). + Order("id asc"). + ScanList(&users, "UserScores", "User", "uid") + t.AssertNil(err) + + t.Assert(len(users), 2) + t.Assert(users[0].User, &EntityUser{3, "name_3"}) + t.Assert(users[1].User, &EntityUser{4, "name_4"}) + + t.Assert(users[0].UserDetail, nil) + + t.Assert(len(users[0].UserScores), 0) + }) +} + +func Test_Table_Relation_EmbeddedStruct(t *testing.T) { + var ( + tableUser = "user_" + gtime.TimestampMicroStr() + tableUserDetail = "user_detail_" + gtime.TimestampMicroStr() + tableUserScores = "user_scores_" + gtime.TimestampMicroStr() + ) + if _, err := db.Exec(fmt.Sprintf(` +CREATE TABLE %s ( + uid int(10) unsigned NOT NULL AUTO_INCREMENT, + name varchar(45) NOT NULL, + PRIMARY KEY (uid) +) ENGINE=InnoDB DEFAULT CHARSET=utf8; + `, tableUser)); err != nil { + gtest.Error(err) + } + defer dropTable(tableUser) + + if _, err := db.Exec(fmt.Sprintf(` +CREATE TABLE %s ( + uid int(10) unsigned NOT NULL AUTO_INCREMENT, + address varchar(45) NOT NULL, + PRIMARY KEY (uid) +) ENGINE=InnoDB DEFAULT CHARSET=utf8; + `, tableUserDetail)); err != nil { + gtest.Error(err) + } + defer dropTable(tableUserDetail) + + if _, err := db.Exec(fmt.Sprintf(` +CREATE TABLE %s ( + id int(10) unsigned NOT NULL AUTO_INCREMENT, + uid int(10) unsigned NOT NULL, + score int(10) unsigned NOT NULL, + PRIMARY KEY (id) +) ENGINE=InnoDB DEFAULT CHARSET=utf8; + `, tableUserScores)); err != nil { + gtest.Error(err) + } + defer dropTable(tableUserScores) + + type EntityUser struct { + Uid int `json:"uid"` + Name string `json:"name"` + } + type EntityUserDetail struct { + *EntityUser + Uid int `json:"uid"` + Address string `json:"address"` + } + type EntityUserScores struct { + *EntityUser + *EntityUserDetail + Id int `json:"id"` + Uid int `json:"uid"` + Score int `json:"score"` + } + + // Initialize the data. + gtest.C(t, func(t *gtest.T) { + var err error + for i := 1; i <= 5; i++ { + // User. + _, err = db.Insert(tableUser, g.Map{ + "uid": i, + "name": fmt.Sprintf(`name_%d`, i), + }) + t.AssertNil(err) + // Detail. + _, err = db.Insert(tableUserDetail, g.Map{ + "uid": i, + "address": fmt.Sprintf(`address_%d`, i), + }) + t.AssertNil(err) + // Scores. + for j := 1; j <= 5; j++ { + _, err = db.Insert(tableUserScores, g.Map{ + "uid": i, + "score": j, + }) + t.AssertNil(err) + } + } + }) + + gtest.C(t, func(t *gtest.T) { + var ( + err error + scores []*EntityUserScores + ) + // SELECT * FROM `user_scores` + err = db.Model(tableUserScores).Scan(&scores) + t.AssertNil(err) + + // SELECT * FROM `user_scores` WHERE `uid` IN(1,2,3,4,5) + err = db.Model(tableUser). + Where("uid", gdb.ListItemValuesUnique(&scores, "Uid")). + ScanList(&scores, "EntityUser", "uid:Uid") + t.AssertNil(err) + + // SELECT * FROM `user_detail` WHERE `uid` IN(1,2,3,4,5) + err = db.Model(tableUserDetail). + Where("uid", gdb.ListItemValuesUnique(&scores, "Uid")). + ScanList(&scores, "EntityUserDetail", "uid:Uid") + t.AssertNil(err) + + // Assertions. + t.Assert(len(scores), 25) + t.Assert(scores[0].Id, 1) + t.Assert(scores[0].Uid, 1) + t.Assert(scores[0].Name, "name_1") + t.Assert(scores[0].Address, "address_1") + t.Assert(scores[24].Id, 25) + t.Assert(scores[24].Uid, 5) + t.Assert(scores[24].Name, "name_5") + t.Assert(scores[24].Address, "address_5") + }) +} diff --git a/database/gdb/gdb_z_mysql_association_test.go b/database/gdb/gdb_z_mysql_association_test.go deleted file mode 100644 index 4b8df7c2d..000000000 --- a/database/gdb/gdb_z_mysql_association_test.go +++ /dev/null @@ -1,474 +0,0 @@ -// Copyright GoFrame 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_test - -import ( - "fmt" - "github.com/gogf/gf/database/gdb" - "github.com/gogf/gf/frame/g" - "github.com/gogf/gf/util/gconv" - "testing" - - "github.com/gogf/gf/os/gtime" - "github.com/gogf/gf/test/gtest" -) - -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() - tableUserScores = "user_scores_" + gtime.TimestampMicroStr() - ) - if _, err := db.Exec(fmt.Sprintf(` -CREATE TABLE %s ( - uid int(10) unsigned NOT NULL AUTO_INCREMENT, - name varchar(45) NOT NULL, - PRIMARY KEY (uid) -) ENGINE=InnoDB DEFAULT CHARSET=utf8; - `, tableUser)); err != nil { - gtest.Error(err) - } - defer dropTable(tableUser) - - if _, err := db.Exec(fmt.Sprintf(` -CREATE TABLE %s ( - uid int(10) unsigned NOT NULL AUTO_INCREMENT, - address varchar(45) NOT NULL, - PRIMARY KEY (uid) -) ENGINE=InnoDB DEFAULT CHARSET=utf8; - `, tableUserDetail)); err != nil { - gtest.Error(err) - } - defer dropTable(tableUserDetail) - - if _, err := db.Exec(fmt.Sprintf(` -CREATE TABLE %s ( - id int(10) unsigned NOT NULL AUTO_INCREMENT, - uid int(10) unsigned NOT NULL, - score int(10) unsigned NOT NULL, - PRIMARY KEY (id) -) ENGINE=InnoDB DEFAULT CHARSET=utf8; - `, tableUserScores)); err != nil { - gtest.Error(err) - } - defer dropTable(tableUserScores) - - type EntityUser struct { - Uid int `json:"uid"` - Name string `json:"name"` - } - type EntityUserDetail struct { - Uid int `json:"uid"` - Address string `json:"address"` - } - type EntityUserScores struct { - Id int `json:"id"` - Uid int `json:"uid"` - Score int `json:"score"` - } - type Entity struct { - User *EntityUser - UserDetail *EntityUserDetail - UserScores []*EntityUserScores - } - - // Initialize the data. - var err error - for i := 1; i <= 5; i++ { - // User. - _, err = db.Insert(tableUser, g.Map{ - "uid": i, - "name": fmt.Sprintf(`name_%d`, i), - }) - gtest.Assert(err, nil) - // Detail. - _, err = db.Insert(tableUserDetail, g.Map{ - "uid": i, - "address": fmt.Sprintf(`address_%d`, i), - }) - gtest.Assert(err, nil) - // Scores. - for j := 1; j <= 5; j++ { - _, err = db.Insert(tableUserScores, g.Map{ - "uid": i, - "score": j, - }) - gtest.Assert(err, nil) - } - } - // MapKeyValue. - gtest.C(t, func(t *gtest.T) { - all, err := db.Table(tableUser).Where("uid", g.Slice{3, 4}).Order("uid asc").All() - t.Assert(err, nil) - t.Assert(all.Len(), 2) - t.Assert(len(all.MapKeyValue("uid")), 2) - t.Assert(all.MapKeyValue("uid")["3"].Map()["uid"], 3) - t.Assert(all.MapKeyValue("uid")["4"].Map()["uid"], 4) - all, err = db.Table(tableUserScores).Where("uid", g.Slice{3, 4}).Order("id asc").All() - t.Assert(err, nil) - t.Assert(all.Len(), 10) - t.Assert(len(all.MapKeyValue("uid")), 2) - t.Assert(len(all.MapKeyValue("uid")["3"].Slice()), 5) - t.Assert(len(all.MapKeyValue("uid")["4"].Slice()), 5) - t.Assert(gconv.Map(all.MapKeyValue("uid")["3"].Slice()[0])["uid"], 3) - t.Assert(gconv.Map(all.MapKeyValue("uid")["3"].Slice()[0])["score"], 1) - t.Assert(gconv.Map(all.MapKeyValue("uid")["3"].Slice()[4])["uid"], 3) - t.Assert(gconv.Map(all.MapKeyValue("uid")["3"].Slice()[4])["score"], 5) - }) - // Result ScanList with struct elements and pointer attributes. - gtest.C(t, func(t *gtest.T) { - var users []Entity - // User - all, err := db.Table(tableUser).Where("uid", g.Slice{3, 4}).Order("uid asc").All() - t.Assert(err, nil) - err = all.ScanList(&users, "User") - t.Assert(err, nil) - t.Assert(len(users), 2) - t.Assert(users[0].User, &EntityUser{3, "name_3"}) - t.Assert(users[1].User, &EntityUser{4, "name_4"}) - // Detail - all, err = db.Table(tableUserDetail).Where("uid", gdb.ListItemValues(users, "User", "Uid")).Order("uid asc").All() - gtest.Assert(err, nil) - err = all.ScanList(&users, "UserDetail", "User", "uid:Uid") - t.Assert(err, nil) - t.Assert(users[0].UserDetail, &EntityUserDetail{3, "address_3"}) - t.Assert(users[1].UserDetail, &EntityUserDetail{4, "address_4"}) - // Scores - all, err = db.Table(tableUserScores).Where("uid", gdb.ListItemValues(users, "User", "Uid")).Order("id asc").All() - gtest.Assert(err, nil) - err = all.ScanList(&users, "UserScores", "User", "uid:Uid") - t.Assert(err, nil) - t.Assert(len(users[0].UserScores), 5) - t.Assert(len(users[1].UserScores), 5) - t.Assert(users[0].UserScores[0].Uid, 3) - t.Assert(users[0].UserScores[0].Score, 1) - t.Assert(users[0].UserScores[4].Score, 5) - t.Assert(users[1].UserScores[0].Uid, 4) - t.Assert(users[1].UserScores[0].Score, 1) - t.Assert(users[1].UserScores[4].Score, 5) - }) - - // Result ScanList with pointer elements and pointer attributes. - gtest.C(t, func(t *gtest.T) { - var users []*Entity - // User - all, err := db.Table(tableUser).Where("uid", g.Slice{3, 4}).Order("uid asc").All() - t.Assert(err, nil) - err = all.ScanList(&users, "User") - t.Assert(err, nil) - t.Assert(len(users), 2) - t.Assert(users[0].User, &EntityUser{3, "name_3"}) - t.Assert(users[1].User, &EntityUser{4, "name_4"}) - // Detail - all, err = db.Table(tableUserDetail).Where("uid", gdb.ListItemValues(users, "User", "Uid")).Order("uid asc").All() - gtest.Assert(err, nil) - err = all.ScanList(&users, "UserDetail", "User", "uid:Uid") - t.Assert(err, nil) - t.Assert(users[0].UserDetail, &EntityUserDetail{3, "address_3"}) - t.Assert(users[1].UserDetail, &EntityUserDetail{4, "address_4"}) - // Scores - all, err = db.Table(tableUserScores).Where("uid", gdb.ListItemValues(users, "User", "Uid")).Order("id asc").All() - gtest.Assert(err, nil) - err = all.ScanList(&users, "UserScores", "User", "uid:Uid") - t.Assert(err, nil) - t.Assert(len(users[0].UserScores), 5) - t.Assert(len(users[1].UserScores), 5) - t.Assert(users[0].UserScores[0].Uid, 3) - t.Assert(users[0].UserScores[0].Score, 1) - t.Assert(users[0].UserScores[4].Score, 5) - t.Assert(users[1].UserScores[0].Uid, 4) - t.Assert(users[1].UserScores[0].Score, 1) - t.Assert(users[1].UserScores[4].Score, 5) - }) - - // Result ScanList with struct elements and struct attributes. - gtest.C(t, func(t *gtest.T) { - type EntityUser struct { - Uid int `json:"uid"` - Name string `json:"name"` - } - type EntityUserDetail struct { - Uid int `json:"uid"` - Address string `json:"address"` - } - type EntityUserScores struct { - Id int `json:"id"` - Uid int `json:"uid"` - Score int `json:"score"` - } - type Entity struct { - User EntityUser - UserDetail EntityUserDetail - UserScores []EntityUserScores - } - var users []Entity - // User - all, err := db.Table(tableUser).Where("uid", g.Slice{3, 4}).Order("uid asc").All() - t.Assert(err, nil) - err = all.ScanList(&users, "User") - t.Assert(err, nil) - t.Assert(len(users), 2) - t.Assert(users[0].User, &EntityUser{3, "name_3"}) - t.Assert(users[1].User, &EntityUser{4, "name_4"}) - // Detail - all, err = db.Table(tableUserDetail).Where("uid", gdb.ListItemValues(users, "User", "Uid")).Order("uid asc").All() - gtest.Assert(err, nil) - err = all.ScanList(&users, "UserDetail", "User", "uid:Uid") - t.Assert(err, nil) - t.Assert(users[0].UserDetail, &EntityUserDetail{3, "address_3"}) - t.Assert(users[1].UserDetail, &EntityUserDetail{4, "address_4"}) - // Scores - all, err = db.Table(tableUserScores).Where("uid", gdb.ListItemValues(users, "User", "Uid")).Order("id asc").All() - gtest.Assert(err, nil) - err = all.ScanList(&users, "UserScores", "User", "uid:Uid") - t.Assert(err, nil) - t.Assert(len(users[0].UserScores), 5) - t.Assert(len(users[1].UserScores), 5) - t.Assert(users[0].UserScores[0].Uid, 3) - t.Assert(users[0].UserScores[0].Score, 1) - t.Assert(users[0].UserScores[4].Score, 5) - t.Assert(users[1].UserScores[0].Uid, 4) - t.Assert(users[1].UserScores[0].Score, 1) - t.Assert(users[1].UserScores[4].Score, 5) - }) - - // Result ScanList with pointer elements and struct attributes. - gtest.C(t, func(t *gtest.T) { - type EntityUser struct { - Uid int `json:"uid"` - Name string `json:"name"` - } - type EntityUserDetail struct { - Uid int `json:"uid"` - Address string `json:"address"` - } - type EntityUserScores struct { - Id int `json:"id"` - Uid int `json:"uid"` - Score int `json:"score"` - } - type Entity struct { - User EntityUser - UserDetail EntityUserDetail - UserScores []EntityUserScores - } - var users []*Entity - - // User - all, err := db.Table(tableUser).Where("uid", g.Slice{3, 4}).Order("uid asc").All() - t.Assert(err, nil) - err = all.ScanList(&users, "User") - t.Assert(err, nil) - t.Assert(len(users), 2) - t.Assert(users[0].User, &EntityUser{3, "name_3"}) - t.Assert(users[1].User, &EntityUser{4, "name_4"}) - // Detail - all, err = db.Table(tableUserDetail).Where("uid", gdb.ListItemValues(users, "User", "Uid")).Order("uid asc").All() - gtest.Assert(err, nil) - err = all.ScanList(&users, "UserDetail", "User", "uid:Uid") - t.Assert(err, nil) - t.Assert(users[0].UserDetail, &EntityUserDetail{3, "address_3"}) - t.Assert(users[1].UserDetail, &EntityUserDetail{4, "address_4"}) - // Scores - all, err = db.Table(tableUserScores).Where("uid", gdb.ListItemValues(users, "User", "Uid")).Order("id asc").All() - gtest.Assert(err, nil) - err = all.ScanList(&users, "UserScores", "User", "uid:Uid") - t.Assert(err, nil) - t.Assert(len(users[0].UserScores), 5) - t.Assert(len(users[1].UserScores), 5) - t.Assert(users[0].UserScores[0].Uid, 3) - t.Assert(users[0].UserScores[0].Score, 1) - t.Assert(users[0].UserScores[4].Score, 5) - t.Assert(users[1].UserScores[0].Uid, 4) - t.Assert(users[1].UserScores[0].Score, 1) - t.Assert(users[1].UserScores[4].Score, 5) - }) - - // Model ScanList with pointer elements and pointer attributes. - gtest.C(t, func(t *gtest.T) { - var users []*Entity - // User - err := db.Table(tableUser). - Where("uid", g.Slice{3, 4}). - Order("uid asc"). - ScanList(&users, "User") - t.Assert(err, nil) - // Detail - err = db.Table(tableUserDetail). - Where("uid", gdb.ListItemValues(users, "User", "Uid")). - Order("uid asc"). - ScanList(&users, "UserDetail", "User", "uid:Uid") - gtest.Assert(err, nil) - // Scores - err = db.Table(tableUserScores). - Where("uid", gdb.ListItemValues(users, "User", "Uid")). - Order("id asc"). - ScanList(&users, "UserScores", "User", "uid:Uid") - t.Assert(err, nil) - - t.Assert(len(users), 2) - t.Assert(users[0].User, &EntityUser{3, "name_3"}) - t.Assert(users[1].User, &EntityUser{4, "name_4"}) - - t.Assert(users[0].UserDetail, &EntityUserDetail{3, "address_3"}) - t.Assert(users[1].UserDetail, &EntityUserDetail{4, "address_4"}) - - t.Assert(len(users[0].UserScores), 5) - t.Assert(len(users[1].UserScores), 5) - t.Assert(users[0].UserScores[0].Uid, 3) - t.Assert(users[0].UserScores[0].Score, 1) - t.Assert(users[0].UserScores[4].Score, 5) - t.Assert(users[1].UserScores[0].Uid, 4) - t.Assert(users[1].UserScores[0].Score, 1) - t.Assert(users[1].UserScores[4].Score, 5) - }) -} diff --git a/database/gdb/gdb_z_mysql_association_with_test.go b/database/gdb/gdb_z_mysql_association_with_test.go new file mode 100644 index 000000000..7345221d1 --- /dev/null +++ b/database/gdb/gdb_z_mysql_association_with_test.go @@ -0,0 +1,1993 @@ +// Copyright GoFrame Author(https://goframe.org). All Rights Reserved. +// +// This Source Code Form is subject to the terms of the MIT License. +// If a copy of the MIT was not distributed with this file, +// You can obtain one at https://github.com/gogf/gf. + +package gdb_test + +import ( + "fmt" + "github.com/gogf/gf/debug/gdebug" + "github.com/gogf/gf/frame/g" + "github.com/gogf/gf/os/gfile" + "github.com/gogf/gf/test/gtest" + "github.com/gogf/gf/text/gstr" + "github.com/gogf/gf/util/gmeta" + "testing" +) + +/* +mysql> show tables; ++----------------+ +| Tables_in_test | ++----------------+ +| user | +| user_detail | +| user_score | ++----------------+ +3 rows in set (0.01 sec) + +mysql> select * from `user`; ++----+--------+ +| id | name | ++----+--------+ +| 1 | name_1 | +| 2 | name_2 | +| 3 | name_3 | +| 4 | name_4 | +| 5 | name_5 | ++----+--------+ +5 rows in set (0.01 sec) + +mysql> select * from `user_detail`; ++-----+-----------+ +| uid | address | ++-----+-----------+ +| 1 | address_1 | +| 2 | address_2 | +| 3 | address_3 | +| 4 | address_4 | +| 5 | address_5 | ++-----+-----------+ +5 rows in set (0.00 sec) + +mysql> select * from `user_score`; ++----+-----+-------+ +| id | uid | score | ++----+-----+-------+ +| 1 | 1 | 1 | +| 2 | 1 | 2 | +| 3 | 1 | 3 | +| 4 | 1 | 4 | +| 5 | 1 | 5 | +| 6 | 2 | 1 | +| 7 | 2 | 2 | +| 8 | 2 | 3 | +| 9 | 2 | 4 | +| 10 | 2 | 5 | +| 11 | 3 | 1 | +| 12 | 3 | 2 | +| 13 | 3 | 3 | +| 14 | 3 | 4 | +| 15 | 3 | 5 | +| 16 | 4 | 1 | +| 17 | 4 | 2 | +| 18 | 4 | 3 | +| 19 | 4 | 4 | +| 20 | 4 | 5 | +| 21 | 5 | 1 | +| 22 | 5 | 2 | +| 23 | 5 | 3 | +| 24 | 5 | 4 | +| 25 | 5 | 5 | ++----+-----+-------+ +25 rows in set (0.00 sec) +*/ + +func Test_Table_Relation_With_Scan(t *testing.T) { + var ( + tableUser = "user" + tableUserDetail = "user_detail" + tableUserScores = "user_score" + ) + if _, err := db.Exec(fmt.Sprintf(` +CREATE TABLE IF NOT EXISTS %s ( +id int(10) unsigned NOT NULL AUTO_INCREMENT, +name varchar(45) NOT NULL, +PRIMARY KEY (id) +) ENGINE=InnoDB DEFAULT CHARSET=utf8; + `, tableUser)); err != nil { + gtest.Error(err) + } + defer dropTable(tableUser) + + if _, err := db.Exec(fmt.Sprintf(` +CREATE TABLE IF NOT EXISTS %s ( +uid int(10) unsigned NOT NULL AUTO_INCREMENT, +address varchar(45) NOT NULL, +PRIMARY KEY (uid) +) ENGINE=InnoDB DEFAULT CHARSET=utf8; + `, tableUserDetail)); err != nil { + gtest.Error(err) + } + defer dropTable(tableUserDetail) + + if _, err := db.Exec(fmt.Sprintf(` +CREATE TABLE IF NOT EXISTS %s ( +id int(10) unsigned NOT NULL AUTO_INCREMENT, +uid int(10) unsigned NOT NULL, +score int(10) unsigned NOT NULL, +PRIMARY KEY (id) +) ENGINE=InnoDB DEFAULT CHARSET=utf8; + `, tableUserScores)); err != nil { + gtest.Error(err) + } + defer dropTable(tableUserScores) + + type UserDetail struct { + gmeta.Meta `orm:"table:user_detail"` + Uid int `json:"uid"` + Address string `json:"address"` + } + + type UserScore struct { + gmeta.Meta `orm:"table:user_score"` + Id int `json:"id"` + Uid int `json:"uid"` + Score int `json:"score"` + } + + type User struct { + gmeta.Meta `orm:"table:user"` + Id int `json:"id"` + Name string `json:"name"` + UserDetail *UserDetail `orm:"with:uid=id"` + UserScores []*UserScore `orm:"with:uid=id"` + } + + // Initialize the data. + gtest.C(t, func(t *gtest.T) { + for i := 1; i <= 5; i++ { + // User. + user := User{ + Name: fmt.Sprintf(`name_%d`, i), + } + lastInsertId, err := db.Model(user).Data(user).OmitEmpty().InsertAndGetId() + t.AssertNil(err) + // Detail. + userDetail := UserDetail{ + Uid: int(lastInsertId), + Address: fmt.Sprintf(`address_%d`, lastInsertId), + } + _, err = db.Model(userDetail).Data(userDetail).OmitEmpty().Insert() + t.AssertNil(err) + // Scores. + for j := 1; j <= 5; j++ { + userScore := UserScore{ + Uid: int(lastInsertId), + Score: j, + } + _, err = db.Model(userScore).Data(userScore).OmitEmpty().Insert() + t.AssertNil(err) + } + } + }) + for i := 1; i <= 5; i++ { + // User. + user := User{ + Name: fmt.Sprintf(`name_%d`, i), + } + lastInsertId, err := db.Model(user).Data(user).OmitEmpty().InsertAndGetId() + gtest.AssertNil(err) + // Detail. + userDetail := UserDetail{ + Uid: int(lastInsertId), + Address: fmt.Sprintf(`address_%d`, lastInsertId), + } + _, err = db.Model(userDetail).Data(userDetail).Insert() + gtest.AssertNil(err) + // Scores. + for j := 1; j <= 5; j++ { + userScore := UserScore{ + Uid: int(lastInsertId), + Score: j, + } + _, err = db.Model(userScore).Data(userScore).Insert() + gtest.AssertNil(err) + } + } + gtest.C(t, func(t *gtest.T) { + var user *User + err := db.With(User{}). + With(User{}.UserDetail). + With(User{}.UserScores). + Where("id", 3). + Scan(&user) + t.AssertNil(err) + t.Assert(user.Id, 3) + t.AssertNE(user.UserDetail, nil) + t.Assert(user.UserDetail.Uid, 3) + t.Assert(user.UserDetail.Address, `address_3`) + t.Assert(len(user.UserScores), 5) + t.Assert(user.UserScores[0].Uid, 3) + t.Assert(user.UserScores[0].Score, 1) + t.Assert(user.UserScores[4].Uid, 3) + t.Assert(user.UserScores[4].Score, 5) + }) + gtest.C(t, func(t *gtest.T) { + var user User + err := db.With(user). + With(user.UserDetail). + With(user.UserScores). + Where("id", 4). + Scan(&user) + t.AssertNil(err) + t.Assert(user.Id, 4) + t.AssertNE(user.UserDetail, nil) + t.Assert(user.UserDetail.Uid, 4) + t.Assert(user.UserDetail.Address, `address_4`) + t.Assert(len(user.UserScores), 5) + t.Assert(user.UserScores[0].Uid, 4) + t.Assert(user.UserScores[0].Score, 1) + t.Assert(user.UserScores[4].Uid, 4) + t.Assert(user.UserScores[4].Score, 5) + }) + gtest.C(t, func(t *gtest.T) { + var user *User + err := db.With(User{}). + With(UserDetail{}). + With(UserScore{}). + Where("id", 4). + Scan(&user) + t.AssertNil(err) + t.Assert(user.Id, 4) + t.AssertNE(user.UserDetail, nil) + t.Assert(user.UserDetail.Uid, 4) + t.Assert(user.UserDetail.Address, `address_4`) + t.Assert(len(user.UserScores), 5) + t.Assert(user.UserScores[0].Uid, 4) + t.Assert(user.UserScores[0].Score, 1) + t.Assert(user.UserScores[4].Uid, 4) + t.Assert(user.UserScores[4].Score, 5) + }) + // With part attribute: UserDetail. + gtest.C(t, func(t *gtest.T) { + var user User + err := db.With(user). + With(user.UserDetail). + Where("id", 4). + Scan(&user) + t.AssertNil(err) + t.Assert(user.Id, 4) + t.AssertNE(user.UserDetail, nil) + t.Assert(user.UserDetail.Uid, 4) + t.Assert(user.UserDetail.Address, `address_4`) + t.Assert(len(user.UserScores), 0) + }) + // With part attribute: UserScores. + gtest.C(t, func(t *gtest.T) { + var user User + err := db.With(user). + With(user.UserScores). + Where("id", 4). + Scan(&user) + t.AssertNil(err) + t.Assert(user.Id, 4) + t.Assert(user.UserDetail, nil) + t.Assert(len(user.UserScores), 5) + t.Assert(user.UserScores[0].Uid, 4) + t.Assert(user.UserScores[0].Score, 1) + t.Assert(user.UserScores[4].Uid, 4) + t.Assert(user.UserScores[4].Score, 5) + }) +} + +func Test_Table_Relation_With(t *testing.T) { + var ( + tableUser = "user" + tableUserDetail = "user_detail" + tableUserScores = "user_scores" + ) + if _, err := db.Exec(fmt.Sprintf(` +CREATE TABLE IF NOT EXISTS %s ( +id int(10) unsigned NOT NULL AUTO_INCREMENT, +name varchar(45) NOT NULL, +PRIMARY KEY (id) +) ENGINE=InnoDB DEFAULT CHARSET=utf8; + `, tableUser)); err != nil { + gtest.Error(err) + } + defer dropTable(tableUser) + + if _, err := db.Exec(fmt.Sprintf(` +CREATE TABLE IF NOT EXISTS %s ( +uid int(10) unsigned NOT NULL AUTO_INCREMENT, +address varchar(45) NOT NULL, +PRIMARY KEY (uid) +) ENGINE=InnoDB DEFAULT CHARSET=utf8; + `, tableUserDetail)); err != nil { + gtest.Error(err) + } + defer dropTable(tableUserDetail) + + if _, err := db.Exec(fmt.Sprintf(` +CREATE TABLE IF NOT EXISTS %s ( +id int(10) unsigned NOT NULL AUTO_INCREMENT, +uid int(10) unsigned NOT NULL, +score int(10) unsigned NOT NULL, +PRIMARY KEY (id) +) ENGINE=InnoDB DEFAULT CHARSET=utf8; + `, tableUserScores)); err != nil { + gtest.Error(err) + } + defer dropTable(tableUserScores) + + type UserDetail struct { + gmeta.Meta `orm:"table:user_detail"` + Uid int `json:"uid"` + Address string `json:"address"` + } + + type UserScores struct { + gmeta.Meta `orm:"table:user_scores"` + Id int `json:"id"` + Uid int `json:"uid"` + Score int `json:"score"` + } + + type User struct { + gmeta.Meta `orm:"table:user"` + Id int `json:"id"` + Name string `json:"name"` + UserDetail *UserDetail `orm:"with:uid=id"` + UserScores []*UserScores `orm:"with:uid=id"` + } + + // Initialize the data. + var err error + for i := 1; i <= 5; i++ { + // User. + _, err = db.Insert(tableUser, g.Map{ + "id": i, + "name": fmt.Sprintf(`name_%d`, i), + }) + gtest.Assert(err, nil) + // Detail. + _, err = db.Insert(tableUserDetail, g.Map{ + "uid": i, + "address": fmt.Sprintf(`address_%d`, i), + }) + gtest.Assert(err, nil) + // Scores. + for j := 1; j <= 5; j++ { + _, err = db.Insert(tableUserScores, g.Map{ + "uid": i, + "score": j, + }) + gtest.Assert(err, nil) + } + } + + gtest.C(t, func(t *gtest.T) { + var users []*User + err := db.With(User{}). + With(User{}.UserDetail). + With(User{}.UserScores). + Where("id", []int{3, 4}). + Scan(&users) + t.AssertNil(err) + t.Assert(len(users), 2) + t.Assert(users[0].Id, 3) + t.Assert(users[0].Name, "name_3") + t.AssertNE(users[0].UserDetail, nil) + t.Assert(users[0].UserDetail.Uid, 3) + t.Assert(users[0].UserDetail.Address, "address_3") + t.Assert(len(users[0].UserScores), 5) + t.Assert(users[0].UserScores[0].Uid, 3) + t.Assert(users[0].UserScores[0].Score, 1) + t.Assert(users[0].UserScores[4].Uid, 3) + t.Assert(users[0].UserScores[4].Score, 5) + + t.Assert(users[1].Id, 4) + t.Assert(users[1].Name, "name_4") + t.AssertNE(users[1].UserDetail, nil) + t.Assert(users[1].UserDetail.Uid, 4) + t.Assert(users[1].UserDetail.Address, "address_4") + t.Assert(len(users[1].UserScores), 5) + t.Assert(users[1].UserScores[0].Uid, 4) + t.Assert(users[1].UserScores[0].Score, 1) + t.Assert(users[1].UserScores[4].Uid, 4) + t.Assert(users[1].UserScores[4].Score, 5) + }) + gtest.C(t, func(t *gtest.T) { + var users []User + err := db.With(User{}). + With(User{}.UserDetail). + With(User{}.UserScores). + Where("id", []int{3, 4}). + Scan(&users) + t.AssertNil(err) + t.Assert(len(users), 2) + t.Assert(users[0].Id, 3) + t.Assert(users[0].Name, "name_3") + t.AssertNE(users[0].UserDetail, nil) + t.Assert(users[0].UserDetail.Uid, 3) + t.Assert(users[0].UserDetail.Address, "address_3") + t.Assert(len(users[0].UserScores), 5) + t.Assert(users[0].UserScores[0].Uid, 3) + t.Assert(users[0].UserScores[0].Score, 1) + t.Assert(users[0].UserScores[4].Uid, 3) + t.Assert(users[0].UserScores[4].Score, 5) + + t.Assert(users[1].Id, 4) + t.Assert(users[1].Name, "name_4") + t.AssertNE(users[1].UserDetail, nil) + t.Assert(users[1].UserDetail.Uid, 4) + t.Assert(users[1].UserDetail.Address, "address_4") + t.Assert(len(users[1].UserScores), 5) + t.Assert(users[1].UserScores[0].Uid, 4) + t.Assert(users[1].UserScores[0].Score, 1) + t.Assert(users[1].UserScores[4].Uid, 4) + t.Assert(users[1].UserScores[4].Score, 5) + }) + // With part attribute: UserDetail. + gtest.C(t, func(t *gtest.T) { + var users []*User + err := db.With(User{}). + With(User{}.UserDetail). + Where("id", []int{3, 4}). + Scan(&users) + t.AssertNil(err) + t.Assert(len(users), 2) + t.Assert(users[0].Id, 3) + t.Assert(users[0].Name, "name_3") + t.AssertNE(users[0].UserDetail, nil) + t.Assert(users[0].UserDetail.Uid, 3) + t.Assert(users[0].UserDetail.Address, "address_3") + t.Assert(len(users[0].UserScores), 0) + + t.Assert(users[1].Id, 4) + t.Assert(users[1].Name, "name_4") + t.AssertNE(users[1].UserDetail, nil) + t.Assert(users[1].UserDetail.Uid, 4) + t.Assert(users[1].UserDetail.Address, "address_4") + t.Assert(len(users[1].UserScores), 0) + }) + // With part attribute: UserScores. + gtest.C(t, func(t *gtest.T) { + var users []*User + err := db.With(User{}). + With(User{}.UserScores). + Where("id", []int{3, 4}). + Scan(&users) + t.AssertNil(err) + t.Assert(len(users), 2) + t.Assert(users[0].Id, 3) + t.Assert(users[0].Name, "name_3") + t.Assert(users[0].UserDetail, nil) + t.Assert(len(users[0].UserScores), 5) + t.Assert(users[0].UserScores[0].Uid, 3) + t.Assert(users[0].UserScores[0].Score, 1) + t.Assert(users[0].UserScores[4].Uid, 3) + t.Assert(users[0].UserScores[4].Score, 5) + + t.Assert(users[1].Id, 4) + t.Assert(users[1].Name, "name_4") + t.Assert(users[1].UserDetail, nil) + t.Assert(len(users[1].UserScores), 5) + t.Assert(users[1].UserScores[0].Uid, 4) + t.Assert(users[1].UserScores[0].Score, 1) + t.Assert(users[1].UserScores[4].Uid, 4) + t.Assert(users[1].UserScores[4].Score, 5) + }) +} + +func Test_Table_Relation_WithAll(t *testing.T) { + var ( + tableUser = "user" + tableUserDetail = "user_detail" + tableUserScores = "user_scores" + ) + if _, err := db.Exec(fmt.Sprintf(` +CREATE TABLE IF NOT EXISTS %s ( +id int(10) unsigned NOT NULL AUTO_INCREMENT, +name varchar(45) NOT NULL, +PRIMARY KEY (id) +) ENGINE=InnoDB DEFAULT CHARSET=utf8; + `, tableUser)); err != nil { + gtest.Error(err) + } + defer dropTable(tableUser) + + if _, err := db.Exec(fmt.Sprintf(` +CREATE TABLE IF NOT EXISTS %s ( +uid int(10) unsigned NOT NULL AUTO_INCREMENT, +address varchar(45) NOT NULL, +PRIMARY KEY (uid) +) ENGINE=InnoDB DEFAULT CHARSET=utf8; + `, tableUserDetail)); err != nil { + gtest.Error(err) + } + defer dropTable(tableUserDetail) + + if _, err := db.Exec(fmt.Sprintf(` +CREATE TABLE IF NOT EXISTS %s ( +id int(10) unsigned NOT NULL AUTO_INCREMENT, +uid int(10) unsigned NOT NULL, +score int(10) unsigned NOT NULL, +PRIMARY KEY (id) +) ENGINE=InnoDB DEFAULT CHARSET=utf8; + `, tableUserScores)); err != nil { + gtest.Error(err) + } + defer dropTable(tableUserScores) + + type UserDetail struct { + gmeta.Meta `orm:"table:user_detail"` + Uid int `json:"uid"` + Address string `json:"address"` + } + + type UserScores struct { + gmeta.Meta `orm:"table:user_scores"` + Id int `json:"id"` + Uid int `json:"uid"` + Score int `json:"score"` + } + + type User struct { + gmeta.Meta `orm:"table:user"` + Id int `json:"id"` + Name string `json:"name"` + UserDetail *UserDetail `orm:"with:uid=id"` + UserScores []*UserScores `orm:"with:uid=id"` + } + + // Initialize the data. + var err error + for i := 1; i <= 5; i++ { + // User. + _, err = db.Insert(tableUser, g.Map{ + "id": i, + "name": fmt.Sprintf(`name_%d`, i), + }) + gtest.Assert(err, nil) + // Detail. + _, err = db.Insert(tableUserDetail, g.Map{ + "uid": i, + "address": fmt.Sprintf(`address_%d`, i), + }) + gtest.Assert(err, nil) + // Scores. + for j := 1; j <= 5; j++ { + _, err = db.Insert(tableUserScores, g.Map{ + "uid": i, + "score": j, + }) + gtest.Assert(err, nil) + } + } + gtest.C(t, func(t *gtest.T) { + var user *User + err := db.Model(tableUser).WithAll().Where("id", 3).Scan(&user) + t.AssertNil(err) + t.Assert(user.Id, 3) + t.AssertNE(user.UserDetail, nil) + t.Assert(user.UserDetail.Uid, 3) + t.Assert(user.UserDetail.Address, `address_3`) + t.Assert(len(user.UserScores), 5) + t.Assert(user.UserScores[0].Uid, 3) + t.Assert(user.UserScores[0].Score, 1) + t.Assert(user.UserScores[4].Uid, 3) + t.Assert(user.UserScores[4].Score, 5) + }) + gtest.C(t, func(t *gtest.T) { + var user User + err := db.Model(tableUser).WithAll().Where("id", 4).Scan(&user) + t.AssertNil(err) + t.Assert(user.Id, 4) + t.AssertNE(user.UserDetail, nil) + t.Assert(user.UserDetail.Uid, 4) + t.Assert(user.UserDetail.Address, `address_4`) + t.Assert(len(user.UserScores), 5) + t.Assert(user.UserScores[0].Uid, 4) + t.Assert(user.UserScores[0].Score, 1) + t.Assert(user.UserScores[4].Uid, 4) + t.Assert(user.UserScores[4].Score, 5) + }) +} + +func Test_Table_Relation_WithAll_List(t *testing.T) { + var ( + tableUser = "user" + tableUserDetail = "user_detail" + tableUserScores = "user_scores" + ) + if _, err := db.Exec(fmt.Sprintf(` +CREATE TABLE IF NOT EXISTS %s ( +id int(10) unsigned NOT NULL AUTO_INCREMENT, +name varchar(45) NOT NULL, +PRIMARY KEY (id) +) ENGINE=InnoDB DEFAULT CHARSET=utf8; + `, tableUser)); err != nil { + gtest.Error(err) + } + defer dropTable(tableUser) + + if _, err := db.Exec(fmt.Sprintf(` +CREATE TABLE IF NOT EXISTS %s ( +uid int(10) unsigned NOT NULL AUTO_INCREMENT, +address varchar(45) NOT NULL, +PRIMARY KEY (uid) +) ENGINE=InnoDB DEFAULT CHARSET=utf8; + `, tableUserDetail)); err != nil { + gtest.Error(err) + } + defer dropTable(tableUserDetail) + + if _, err := db.Exec(fmt.Sprintf(` +CREATE TABLE IF NOT EXISTS %s ( +id int(10) unsigned NOT NULL AUTO_INCREMENT, +uid int(10) unsigned NOT NULL, +score int(10) unsigned NOT NULL, +PRIMARY KEY (id) +) ENGINE=InnoDB DEFAULT CHARSET=utf8; + `, tableUserScores)); err != nil { + gtest.Error(err) + } + defer dropTable(tableUserScores) + + type UserDetail struct { + gmeta.Meta `orm:"table:user_detail"` + Uid int `json:"uid"` + Address string `json:"address"` + } + + type UserScores struct { + gmeta.Meta `orm:"table:user_scores"` + Id int `json:"id"` + Uid int `json:"uid"` + Score int `json:"score"` + } + + type User struct { + gmeta.Meta `orm:"table:user"` + Id int `json:"id"` + Name string `json:"name"` + UserDetail *UserDetail `orm:"with:uid=id"` + UserScores []*UserScores `orm:"with:uid=id"` + } + + // Initialize the data. + var err error + for i := 1; i <= 5; i++ { + // User. + _, err = db.Insert(tableUser, g.Map{ + "id": i, + "name": fmt.Sprintf(`name_%d`, i), + }) + gtest.Assert(err, nil) + // Detail. + _, err = db.Insert(tableUserDetail, g.Map{ + "uid": i, + "address": fmt.Sprintf(`address_%d`, i), + }) + gtest.Assert(err, nil) + // Scores. + for j := 1; j <= 5; j++ { + _, err = db.Insert(tableUserScores, g.Map{ + "uid": i, + "score": j, + }) + gtest.Assert(err, nil) + } + } + gtest.C(t, func(t *gtest.T) { + var users []*User + err := db.Model(tableUser).WithAll().Where("id", []int{3, 4}).Scan(&users) + t.AssertNil(err) + t.Assert(len(users), 2) + t.Assert(users[0].Id, 3) + t.Assert(users[0].Name, "name_3") + t.AssertNE(users[0].UserDetail, nil) + t.Assert(users[0].UserDetail.Uid, 3) + t.Assert(users[0].UserDetail.Address, "address_3") + t.Assert(len(users[0].UserScores), 5) + t.Assert(users[0].UserScores[0].Uid, 3) + t.Assert(users[0].UserScores[0].Score, 1) + t.Assert(users[0].UserScores[4].Uid, 3) + t.Assert(users[0].UserScores[4].Score, 5) + + t.Assert(users[1].Id, 4) + t.Assert(users[1].Name, "name_4") + t.AssertNE(users[1].UserDetail, nil) + t.Assert(users[1].UserDetail.Uid, 4) + t.Assert(users[1].UserDetail.Address, "address_4") + t.Assert(len(users[1].UserScores), 5) + t.Assert(users[1].UserScores[0].Uid, 4) + t.Assert(users[1].UserScores[0].Score, 1) + t.Assert(users[1].UserScores[4].Uid, 4) + t.Assert(users[1].UserScores[4].Score, 5) + }) + gtest.C(t, func(t *gtest.T) { + var users []User + err := db.Model(tableUser).WithAll().Where("id", []int{3, 4}).Scan(&users) + t.AssertNil(err) + t.Assert(len(users), 2) + t.Assert(users[0].Id, 3) + t.Assert(users[0].Name, "name_3") + t.AssertNE(users[0].UserDetail, nil) + t.Assert(users[0].UserDetail.Uid, 3) + t.Assert(users[0].UserDetail.Address, "address_3") + t.Assert(len(users[0].UserScores), 5) + t.Assert(users[0].UserScores[0].Uid, 3) + t.Assert(users[0].UserScores[0].Score, 1) + t.Assert(users[0].UserScores[4].Uid, 3) + t.Assert(users[0].UserScores[4].Score, 5) + + t.Assert(users[1].Id, 4) + t.Assert(users[1].Name, "name_4") + t.AssertNE(users[1].UserDetail, nil) + t.Assert(users[1].UserDetail.Uid, 4) + t.Assert(users[1].UserDetail.Address, "address_4") + t.Assert(len(users[1].UserScores), 5) + t.Assert(users[1].UserScores[0].Uid, 4) + t.Assert(users[1].UserScores[0].Score, 1) + t.Assert(users[1].UserScores[4].Uid, 4) + t.Assert(users[1].UserScores[4].Score, 5) + }) +} + +func Test_Table_Relation_WithAllCondition_List(t *testing.T) { + var ( + tableUser = "user" + tableUserDetail = "user_detail" + tableUserScores = "user_scores" + ) + if _, err := db.Exec(fmt.Sprintf(` +CREATE TABLE IF NOT EXISTS %s ( +id int(10) unsigned NOT NULL AUTO_INCREMENT, +name varchar(45) NOT NULL, +PRIMARY KEY (id) +) ENGINE=InnoDB DEFAULT CHARSET=utf8; +`, tableUser)); err != nil { + gtest.Error(err) + } + defer dropTable(tableUser) + + if _, err := db.Exec(fmt.Sprintf(` +CREATE TABLE IF NOT EXISTS %s ( +uid int(10) unsigned NOT NULL AUTO_INCREMENT, +address varchar(45) NOT NULL, +PRIMARY KEY (uid) +) ENGINE=InnoDB DEFAULT CHARSET=utf8; +`, tableUserDetail)); err != nil { + gtest.Error(err) + } + defer dropTable(tableUserDetail) + + if _, err := db.Exec(fmt.Sprintf(` +CREATE TABLE IF NOT EXISTS %s ( +id int(10) unsigned NOT NULL AUTO_INCREMENT, +uid int(10) unsigned NOT NULL, +score int(10) unsigned NOT NULL, +PRIMARY KEY (id) +) ENGINE=InnoDB DEFAULT CHARSET=utf8; +`, tableUserScores)); err != nil { + gtest.Error(err) + } + defer dropTable(tableUserScores) + + type UserDetail struct { + gmeta.Meta `orm:"table:user_detail"` + Uid int `json:"uid"` + Address string `json:"address"` + } + + type UserScores struct { + gmeta.Meta `orm:"table:user_scores"` + Id int `json:"id"` + Uid int `json:"uid"` + Score int `json:"score"` + } + + type User struct { + gmeta.Meta `orm:"table:user"` + Id int `json:"id"` + Name string `json:"name"` + UserDetail *UserDetail `orm:"with:uid=id, where:uid > 3"` + UserScores []*UserScores `orm:"with:uid=id, where:score>1 and score<5, order:score desc"` + } + + // Initialize the data. + var err error + for i := 1; i <= 5; i++ { + // User. + _, err = db.Insert(tableUser, g.Map{ + "id": i, + "name": fmt.Sprintf(`name_%d`, i), + }) + gtest.Assert(err, nil) + // Detail. + _, err = db.Insert(tableUserDetail, g.Map{ + "uid": i, + "address": fmt.Sprintf(`address_%d`, i), + }) + gtest.Assert(err, nil) + // Scores. + for j := 1; j <= 5; j++ { + _, err = db.Insert(tableUserScores, g.Map{ + "uid": i, + "score": j, + }) + gtest.Assert(err, nil) + } + } + + db.SetDebug(true) + defer db.SetDebug(false) + + gtest.C(t, func(t *gtest.T) { + var users []*User + err := db.Model(tableUser).WithAll().Where("id", []int{3, 4}).Scan(&users) + t.AssertNil(err) + t.Assert(len(users), 2) + t.Assert(users[0].Id, 3) + t.Assert(users[0].Name, "name_3") + t.Assert(users[0].UserDetail, nil) + t.Assert(users[1].Id, 4) + t.Assert(users[1].Name, "name_4") + t.AssertNE(users[1].UserDetail, nil) + t.Assert(users[1].UserDetail.Uid, 4) + t.Assert(users[1].UserDetail.Address, "address_4") + t.Assert(len(users[1].UserScores), 3) + t.Assert(users[1].UserScores[0].Uid, 4) + t.Assert(users[1].UserScores[0].Score, 4) + t.Assert(users[1].UserScores[2].Uid, 4) + t.Assert(users[1].UserScores[2].Score, 2) + }) + gtest.C(t, func(t *gtest.T) { + var users []User + err := db.Model(tableUser).WithAll().Where("id", []int{3, 4}).Scan(&users) + t.AssertNil(err) + t.Assert(len(users), 2) + t.Assert(users[0].Id, 3) + t.Assert(users[0].Name, "name_3") + t.Assert(users[0].UserDetail, nil) + + t.Assert(len(users[0].UserScores), 3) + t.Assert(users[0].UserScores[0].Uid, 3) + t.Assert(users[0].UserScores[0].Score, 4) + t.Assert(users[0].UserScores[2].Uid, 3) + t.Assert(users[0].UserScores[2].Score, 2) + + t.Assert(users[1].Id, 4) + t.Assert(users[1].Name, "name_4") + t.AssertNE(users[1].UserDetail, nil) + t.Assert(users[1].UserDetail.Uid, 4) + t.Assert(users[1].UserDetail.Address, "address_4") + t.Assert(len(users[1].UserScores), 3) + t.Assert(users[1].UserScores[0].Uid, 4) + t.Assert(users[1].UserScores[0].Score, 4) + t.Assert(users[1].UserScores[2].Uid, 4) + t.Assert(users[1].UserScores[2].Score, 2) + }) +} + +func Test_Table_Relation_WithAll_Embedded_With_SelfMaintained_Attributes(t *testing.T) { + var ( + tableUser = "user" + tableUserDetail = "user_detail" + tableUserScores = "user_scores" + ) + if _, err := db.Exec(fmt.Sprintf(` +CREATE TABLE IF NOT EXISTS %s ( +id int(10) unsigned NOT NULL AUTO_INCREMENT, +name varchar(45) NOT NULL, +PRIMARY KEY (id) +) ENGINE=InnoDB DEFAULT CHARSET=utf8; + `, tableUser)); err != nil { + gtest.Error(err) + } + defer dropTable(tableUser) + + if _, err := db.Exec(fmt.Sprintf(` +CREATE TABLE IF NOT EXISTS %s ( +uid int(10) unsigned NOT NULL AUTO_INCREMENT, +address varchar(45) NOT NULL, +PRIMARY KEY (uid) +) ENGINE=InnoDB DEFAULT CHARSET=utf8; + `, tableUserDetail)); err != nil { + gtest.Error(err) + } + defer dropTable(tableUserDetail) + + if _, err := db.Exec(fmt.Sprintf(` +CREATE TABLE IF NOT EXISTS %s ( +id int(10) unsigned NOT NULL AUTO_INCREMENT, +uid int(10) unsigned NOT NULL, +score int(10) unsigned NOT NULL, +PRIMARY KEY (id) +) ENGINE=InnoDB DEFAULT CHARSET=utf8; + `, tableUserScores)); err != nil { + gtest.Error(err) + } + defer dropTable(tableUserScores) + + type UserDetail struct { + gmeta.Meta `orm:"table:user_detail"` + Uid int `json:"uid"` + Address string `json:"address"` + } + + type UserScores struct { + gmeta.Meta `orm:"table:user_scores"` + Id int `json:"id"` + Uid int `json:"uid"` + Score int `json:"score"` + } + + type User struct { + gmeta.Meta `orm:"table:user"` + *UserDetail `orm:"with:uid=id"` + Id int `json:"id"` + Name string `json:"name"` + UserScores []*UserScores `orm:"with:uid=id"` + } + + // Initialize the data. + var err error + for i := 1; i <= 5; i++ { + // User. + _, err = db.Insert(tableUser, g.Map{ + "id": i, + "name": fmt.Sprintf(`name_%d`, i), + }) + gtest.Assert(err, nil) + // Detail. + _, err = db.Insert(tableUserDetail, g.Map{ + "uid": i, + "address": fmt.Sprintf(`address_%d`, i), + }) + gtest.Assert(err, nil) + // Scores. + for j := 1; j <= 5; j++ { + _, err = db.Insert(tableUserScores, g.Map{ + "uid": i, + "score": j, + }) + gtest.Assert(err, nil) + } + } + gtest.C(t, func(t *gtest.T) { + var user *User + err := db.Model(tableUser).WithAll().Where("id", 3).Scan(&user) + t.AssertNil(err) + t.Assert(user.Id, 3) + t.AssertNE(user.UserDetail, nil) + t.Assert(user.UserDetail.Uid, 3) + t.Assert(user.UserDetail.Address, `address_3`) + t.Assert(len(user.UserScores), 5) + t.Assert(user.UserScores[0].Uid, 3) + t.Assert(user.UserScores[0].Score, 1) + t.Assert(user.UserScores[4].Uid, 3) + t.Assert(user.UserScores[4].Score, 5) + }) + gtest.C(t, func(t *gtest.T) { + var user User + err := db.Model(tableUser).WithAll().Where("id", 4).Scan(&user) + t.AssertNil(err) + t.Assert(user.Id, 4) + t.AssertNE(user.UserDetail, nil) + t.Assert(user.UserDetail.Uid, 4) + t.Assert(user.UserDetail.Address, `address_4`) + t.Assert(len(user.UserScores), 5) + t.Assert(user.UserScores[0].Uid, 4) + t.Assert(user.UserScores[0].Score, 1) + t.Assert(user.UserScores[4].Uid, 4) + t.Assert(user.UserScores[4].Score, 5) + }) +} + +func Test_Table_Relation_WithAll_Embedded_Without_SelfMaintained_Attributes(t *testing.T) { + var ( + tableUser = "user" + tableUserDetail = "user_detail" + tableUserScores = "user_scores" + ) + if _, err := db.Exec(fmt.Sprintf(` +CREATE TABLE IF NOT EXISTS %s ( +id int(10) unsigned NOT NULL AUTO_INCREMENT, +name varchar(45) NOT NULL, +PRIMARY KEY (id) +) ENGINE=InnoDB DEFAULT CHARSET=utf8; + `, tableUser)); err != nil { + gtest.Error(err) + } + defer dropTable(tableUser) + + if _, err := db.Exec(fmt.Sprintf(` +CREATE TABLE IF NOT EXISTS %s ( +uid int(10) unsigned NOT NULL AUTO_INCREMENT, +address varchar(45) NOT NULL, +PRIMARY KEY (uid) +) ENGINE=InnoDB DEFAULT CHARSET=utf8; + `, tableUserDetail)); err != nil { + gtest.Error(err) + } + defer dropTable(tableUserDetail) + + if _, err := db.Exec(fmt.Sprintf(` +CREATE TABLE IF NOT EXISTS %s ( +id int(10) unsigned NOT NULL AUTO_INCREMENT, +uid int(10) unsigned NOT NULL, +score int(10) unsigned NOT NULL, +PRIMARY KEY (id) +) ENGINE=InnoDB DEFAULT CHARSET=utf8; + `, tableUserScores)); err != nil { + gtest.Error(err) + } + defer dropTable(tableUserScores) + + type UserDetail struct { + gmeta.Meta `orm:"table:user_detail"` + Uid int `json:"uid"` + Address string `json:"address"` + } + + type UserScores struct { + gmeta.Meta `orm:"table:user_scores"` + Id int `json:"id"` + Uid int `json:"uid"` + Score int `json:"score"` + } + + // For Test Only + type UserEmbedded struct { + Id int `json:"id"` + Name string `json:"name"` + } + + type User struct { + gmeta.Meta `orm:"table:user"` + *UserDetail `orm:"with:uid=id"` + UserEmbedded + UserScores []*UserScores `orm:"with:uid=id"` + } + + // Initialize the data. + var err error + for i := 1; i <= 5; i++ { + // User. + _, err = db.Insert(tableUser, g.Map{ + "id": i, + "name": fmt.Sprintf(`name_%d`, i), + }) + gtest.Assert(err, nil) + // Detail. + _, err = db.Insert(tableUserDetail, g.Map{ + "uid": i, + "address": fmt.Sprintf(`address_%d`, i), + }) + gtest.Assert(err, nil) + // Scores. + for j := 1; j <= 5; j++ { + _, err = db.Insert(tableUserScores, g.Map{ + "uid": i, + "score": j, + }) + gtest.Assert(err, nil) + } + } + db.SetDebug(true) + defer db.SetDebug(false) + + gtest.C(t, func(t *gtest.T) { + var user *User + err := db.Model(tableUser).WithAll().Where("id", 3).Scan(&user) + t.AssertNil(err) + t.Assert(user.Id, 3) + t.AssertNE(user.UserDetail, nil) + t.Assert(user.UserDetail.Uid, 3) + t.Assert(user.UserDetail.Address, `address_3`) + t.Assert(len(user.UserScores), 5) + t.Assert(user.UserScores[0].Uid, 3) + t.Assert(user.UserScores[0].Score, 1) + t.Assert(user.UserScores[4].Uid, 3) + t.Assert(user.UserScores[4].Score, 5) + }) + gtest.C(t, func(t *gtest.T) { + var user User + err := db.Model(tableUser).WithAll().Where("id", 4).Scan(&user) + t.AssertNil(err) + t.Assert(user.Id, 4) + t.AssertNE(user.UserDetail, nil) + t.Assert(user.UserDetail.Uid, 4) + t.Assert(user.UserDetail.Address, `address_4`) + t.Assert(len(user.UserScores), 5) + t.Assert(user.UserScores[0].Uid, 4) + t.Assert(user.UserScores[0].Score, 1) + t.Assert(user.UserScores[4].Uid, 4) + t.Assert(user.UserScores[4].Score, 5) + }) +} + +func Test_Table_Relation_WithAll_Embedded_WithoutMeta(t *testing.T) { + var ( + tableUser = "user" + tableUserDetail = "user_detail" + tableUserScores = "user_scores" + ) + if _, err := db.Exec(fmt.Sprintf(` +CREATE TABLE IF NOT EXISTS %s ( +id int(10) unsigned NOT NULL AUTO_INCREMENT, +name varchar(45) NOT NULL, +PRIMARY KEY (id) +) ENGINE=InnoDB DEFAULT CHARSET=utf8; + `, tableUser)); err != nil { + gtest.Error(err) + } + defer dropTable(tableUser) + + if _, err := db.Exec(fmt.Sprintf(` +CREATE TABLE IF NOT EXISTS %s ( +uid int(10) unsigned NOT NULL AUTO_INCREMENT, +address varchar(45) NOT NULL, +PRIMARY KEY (uid) +) ENGINE=InnoDB DEFAULT CHARSET=utf8; + `, tableUserDetail)); err != nil { + gtest.Error(err) + } + defer dropTable(tableUserDetail) + + if _, err := db.Exec(fmt.Sprintf(` +CREATE TABLE IF NOT EXISTS %s ( +id int(10) unsigned NOT NULL AUTO_INCREMENT, +uid int(10) unsigned NOT NULL, +score int(10) unsigned NOT NULL, +PRIMARY KEY (id) +) ENGINE=InnoDB DEFAULT CHARSET=utf8; + `, tableUserScores)); err != nil { + gtest.Error(err) + } + defer dropTable(tableUserScores) + + type UserDetailBase struct { + Uid int `json:"uid"` + Address string `json:"address"` + } + + type UserDetail struct { + UserDetailBase + } + + type UserScores struct { + Id int `json:"id"` + Uid int `json:"uid"` + Score int `json:"score"` + } + + type User struct { + *UserDetail `orm:"with:uid=id"` + Id int `json:"id"` + Name string `json:"name"` + UserScores []*UserScores `orm:"with:uid=id"` + } + + // Initialize the data. + var err error + for i := 1; i <= 5; i++ { + // User. + _, err = db.Insert(tableUser, g.Map{ + "id": i, + "name": fmt.Sprintf(`name_%d`, i), + }) + gtest.Assert(err, nil) + // Detail. + _, err = db.Insert(tableUserDetail, g.Map{ + "uid": i, + "address": fmt.Sprintf(`address_%d`, i), + }) + gtest.Assert(err, nil) + // Scores. + for j := 1; j <= 5; j++ { + _, err = db.Insert(tableUserScores, g.Map{ + "uid": i, + "score": j, + }) + gtest.Assert(err, nil) + } + } + gtest.C(t, func(t *gtest.T) { + var user *User + err := db.Model(tableUser).WithAll().Where("id", 3).Scan(&user) + t.AssertNil(err) + t.Assert(user.Id, 3) + t.AssertNE(user.UserDetail, nil) + t.Assert(user.UserDetail.Uid, 3) + t.Assert(user.UserDetail.Address, `address_3`) + t.Assert(len(user.UserScores), 5) + t.Assert(user.UserScores[0].Uid, 3) + t.Assert(user.UserScores[0].Score, 1) + t.Assert(user.UserScores[4].Uid, 3) + t.Assert(user.UserScores[4].Score, 5) + }) + gtest.C(t, func(t *gtest.T) { + var user User + err := db.Model(tableUser).WithAll().Where("id", 4).Scan(&user) + t.AssertNil(err) + t.Assert(user.Id, 4) + t.AssertNE(user.UserDetail, nil) + t.Assert(user.UserDetail.Uid, 4) + t.Assert(user.UserDetail.Address, `address_4`) + t.Assert(len(user.UserScores), 5) + t.Assert(user.UserScores[0].Uid, 4) + t.Assert(user.UserScores[0].Score, 1) + t.Assert(user.UserScores[4].Uid, 4) + t.Assert(user.UserScores[4].Score, 5) + }) +} + +func Test_Table_Relation_WithAll_AttributeStructAlsoHasWithTag(t *testing.T) { + var ( + tableUser = "user" + tableUserDetail = "user_detail" + tableUserScores = "user_scores" + ) + if _, err := db.Exec(fmt.Sprintf(` +CREATE TABLE IF NOT EXISTS %s ( +id int(10) unsigned NOT NULL AUTO_INCREMENT, +name varchar(45) NOT NULL, +PRIMARY KEY (id) +) ENGINE=InnoDB DEFAULT CHARSET=utf8; +`, tableUser)); err != nil { + gtest.Error(err) + } + defer dropTable(tableUser) + + if _, err := db.Exec(fmt.Sprintf(` +CREATE TABLE IF NOT EXISTS %s ( +uid int(10) unsigned NOT NULL AUTO_INCREMENT, +address varchar(45) NOT NULL, +PRIMARY KEY (uid) +) ENGINE=InnoDB DEFAULT CHARSET=utf8; +`, tableUserDetail)); err != nil { + gtest.Error(err) + } + defer dropTable(tableUserDetail) + + if _, err := db.Exec(fmt.Sprintf(` +CREATE TABLE IF NOT EXISTS %s ( +id int(10) unsigned NOT NULL AUTO_INCREMENT, +uid int(10) unsigned NOT NULL, +score int(10) unsigned NOT NULL, +PRIMARY KEY (id) +) ENGINE=InnoDB DEFAULT CHARSET=utf8; +`, tableUserScores)); err != nil { + gtest.Error(err) + } + defer dropTable(tableUserScores) + + type UserScores struct { + gmeta.Meta `orm:"table:user_scores"` + Id int `json:"id"` + Uid int `json:"uid"` + Score int `json:"score"` + } + + type UserDetail struct { + gmeta.Meta `orm:"table:user_detail"` + Uid int `json:"uid"` + Address string `json:"address"` + UserScores []*UserScores `orm:"with:uid"` + } + + type User struct { + gmeta.Meta `orm:"table:user"` + *UserDetail `orm:"with:uid=id"` + Id int `json:"id"` + Name string `json:"name"` + } + + // Initialize the data. + var err error + for i := 1; i <= 5; i++ { + // User. + _, err = db.Insert(tableUser, g.Map{ + "id": i, + "name": fmt.Sprintf(`name_%d`, i), + }) + gtest.Assert(err, nil) + // Detail. + _, err = db.Insert(tableUserDetail, g.Map{ + "uid": i, + "address": fmt.Sprintf(`address_%d`, i), + }) + gtest.Assert(err, nil) + // Scores. + for j := 1; j <= 5; j++ { + _, err = db.Insert(tableUserScores, g.Map{ + "uid": i, + "score": j, + }) + gtest.Assert(err, nil) + } + } + + gtest.C(t, func(t *gtest.T) { + var user *User + err := db.Model(tableUser).WithAll().Where("id", 3).Scan(&user) + t.AssertNil(err) + t.Assert(user.Id, 3) + t.AssertNE(user.UserDetail, nil) + t.Assert(user.UserDetail.Uid, 3) + t.Assert(user.UserDetail.Address, `address_3`) + t.Assert(len(user.UserDetail.UserScores), 5) + t.Assert(user.UserDetail.UserScores[0].Uid, 3) + t.Assert(user.UserDetail.UserScores[0].Score, 1) + t.Assert(user.UserDetail.UserScores[4].Uid, 3) + t.Assert(user.UserDetail.UserScores[4].Score, 5) + }) + gtest.C(t, func(t *gtest.T) { + var user User + err := db.Model(tableUser).WithAll().Where("id", 4).Scan(&user) + t.AssertNil(err) + t.Assert(user.Id, 4) + t.AssertNE(user.UserDetail, nil) + t.Assert(user.UserDetail.Uid, 4) + t.Assert(user.UserDetail.Address, `address_4`) + t.Assert(len(user.UserDetail.UserScores), 5) + t.Assert(user.UserDetail.UserScores[0].Uid, 4) + t.Assert(user.UserDetail.UserScores[0].Score, 1) + t.Assert(user.UserDetail.UserScores[4].Uid, 4) + t.Assert(user.UserDetail.UserScores[4].Score, 5) + }) +} + +func Test_Table_Relation_WithAll_AttributeStructAlsoHasWithTag_MoreDeep(t *testing.T) { + var ( + tableUser = "user" + tableUserDetail = "user_detail" + tableUserScores = "user_scores" + ) + if _, err := db.Exec(fmt.Sprintf(` +CREATE TABLE IF NOT EXISTS %s ( +id int(10) unsigned NOT NULL AUTO_INCREMENT, +name varchar(45) NOT NULL, +PRIMARY KEY (id) +) ENGINE=InnoDB DEFAULT CHARSET=utf8; +`, tableUser)); err != nil { + gtest.Error(err) + } + defer dropTable(tableUser) + + if _, err := db.Exec(fmt.Sprintf(` +CREATE TABLE IF NOT EXISTS %s ( +uid int(10) unsigned NOT NULL AUTO_INCREMENT, +address varchar(45) NOT NULL, +PRIMARY KEY (uid) +) ENGINE=InnoDB DEFAULT CHARSET=utf8; +`, tableUserDetail)); err != nil { + gtest.Error(err) + } + defer dropTable(tableUserDetail) + + if _, err := db.Exec(fmt.Sprintf(` +CREATE TABLE IF NOT EXISTS %s ( +id int(10) unsigned NOT NULL AUTO_INCREMENT, +uid int(10) unsigned NOT NULL, +score int(10) unsigned NOT NULL, +PRIMARY KEY (id) +) ENGINE=InnoDB DEFAULT CHARSET=utf8; +`, tableUserScores)); err != nil { + gtest.Error(err) + } + defer dropTable(tableUserScores) + + type UserScores struct { + gmeta.Meta `orm:"table:user_scores"` + Id int `json:"id"` + Uid int `json:"uid"` + Score int `json:"score"` + } + + type UserDetail1 struct { + gmeta.Meta `orm:"table:user_detail"` + Uid int `json:"uid"` + Address string `json:"address"` + UserScores []*UserScores `orm:"with:uid"` + } + + type UserDetail2 struct { + gmeta.Meta `orm:"table:user_detail"` + Uid int `json:"uid"` + Address string `json:"address"` + UserDetail1 *UserDetail1 `orm:"with:uid"` + UserScores []*UserScores `orm:"with:uid"` + } + + type UserDetail3 struct { + gmeta.Meta `orm:"table:user_detail"` + Uid int `json:"uid"` + Address string `json:"address"` + UserDetail2 *UserDetail2 `orm:"with:uid"` + UserScores []*UserScores `orm:"with:uid"` + } + + type UserDetail struct { + gmeta.Meta `orm:"table:user_detail"` + Uid int `json:"uid"` + Address string `json:"address"` + UserDetail3 *UserDetail3 `orm:"with:uid"` + UserScores []*UserScores `orm:"with:uid"` + } + + type User struct { + gmeta.Meta `orm:"table:user"` + *UserDetail `orm:"with:uid=id"` + Id int `json:"id"` + Name string `json:"name"` + } + + // Initialize the data. + var err error + for i := 1; i <= 5; i++ { + // User. + _, err = db.Insert(tableUser, g.Map{ + "id": i, + "name": fmt.Sprintf(`name_%d`, i), + }) + gtest.Assert(err, nil) + // Detail. + _, err = db.Insert(tableUserDetail, g.Map{ + "uid": i, + "address": fmt.Sprintf(`address_%d`, i), + }) + gtest.Assert(err, nil) + // Scores. + for j := 1; j <= 5; j++ { + _, err = db.Insert(tableUserScores, g.Map{ + "uid": i, + "score": j, + }) + gtest.Assert(err, nil) + } + } + + gtest.C(t, func(t *gtest.T) { + var user *User + err := db.Model(tableUser).WithAll().Where("id", 3).Scan(&user) + t.AssertNil(err) + t.Assert(user.Id, 3) + t.AssertNE(user.UserDetail, nil) + t.Assert(user.UserDetail.Uid, 3) + t.Assert(user.UserDetail.UserDetail3.Uid, 3) + t.Assert(user.UserDetail.UserDetail3.UserDetail2.Uid, 3) + t.Assert(user.UserDetail.UserDetail3.UserDetail2.UserDetail1.Uid, 3) + t.Assert(user.UserDetail.Address, `address_3`) + t.Assert(len(user.UserDetail.UserScores), 5) + t.Assert(user.UserDetail.UserScores[0].Uid, 3) + t.Assert(user.UserDetail.UserScores[0].Score, 1) + t.Assert(user.UserDetail.UserScores[4].Uid, 3) + t.Assert(user.UserDetail.UserScores[4].Score, 5) + }) + gtest.C(t, func(t *gtest.T) { + var user User + err := db.Model(tableUser).WithAll().Where("id", 4).Scan(&user) + t.AssertNil(err) + t.Assert(user.Id, 4) + t.AssertNE(user.UserDetail, nil) + t.Assert(user.UserDetail.Uid, 4) + t.Assert(user.UserDetail.UserDetail3.Uid, 4) + t.Assert(user.UserDetail.UserDetail3.UserDetail2.Uid, 4) + t.Assert(user.UserDetail.UserDetail3.UserDetail2.UserDetail1.Uid, 4) + t.Assert(user.UserDetail.Address, `address_4`) + t.Assert(len(user.UserDetail.UserScores), 5) + t.Assert(user.UserDetail.UserScores[0].Uid, 4) + t.Assert(user.UserDetail.UserScores[0].Score, 1) + t.Assert(user.UserDetail.UserScores[4].Uid, 4) + t.Assert(user.UserDetail.UserScores[4].Score, 5) + }) +} + +func Test_Table_Relation_With_AttributeStructAlsoHasWithTag_MoreDeep(t *testing.T) { + var ( + tableUser = "user" + tableUserDetail = "user_detail" + tableUserScores = "user_scores" + ) + if _, err := db.Exec(fmt.Sprintf(` +CREATE TABLE IF NOT EXISTS %s ( +id int(10) unsigned NOT NULL AUTO_INCREMENT, +name varchar(45) NOT NULL, +PRIMARY KEY (id) +) ENGINE=InnoDB DEFAULT CHARSET=utf8; +`, tableUser)); err != nil { + gtest.Error(err) + } + defer dropTable(tableUser) + + if _, err := db.Exec(fmt.Sprintf(` +CREATE TABLE IF NOT EXISTS %s ( +uid int(10) unsigned NOT NULL AUTO_INCREMENT, +address varchar(45) NOT NULL, +PRIMARY KEY (uid) +) ENGINE=InnoDB DEFAULT CHARSET=utf8; +`, tableUserDetail)); err != nil { + gtest.Error(err) + } + defer dropTable(tableUserDetail) + + if _, err := db.Exec(fmt.Sprintf(` +CREATE TABLE IF NOT EXISTS %s ( +id int(10) unsigned NOT NULL AUTO_INCREMENT, +uid int(10) unsigned NOT NULL, +score int(10) unsigned NOT NULL, +PRIMARY KEY (id) +) ENGINE=InnoDB DEFAULT CHARSET=utf8; +`, tableUserScores)); err != nil { + gtest.Error(err) + } + defer dropTable(tableUserScores) + + type UserScores struct { + gmeta.Meta `orm:"table:user_scores"` + Id int `json:"id"` + Uid int `json:"uid"` + Score int `json:"score"` + } + + type UserDetail1 struct { + gmeta.Meta `orm:"table:user_detail"` + Uid int `json:"uid"` + Address string `json:"address"` + UserScores []*UserScores `orm:"with:uid"` + } + + type UserDetail2 struct { + gmeta.Meta `orm:"table:user_detail"` + Uid int `json:"uid"` + Address string `json:"address"` + UserDetail1 *UserDetail1 `orm:"with:uid"` + UserScores []*UserScores `orm:"with:uid"` + } + + type UserDetail3 struct { + gmeta.Meta `orm:"table:user_detail"` + Uid int `json:"uid"` + Address string `json:"address"` + UserDetail2 *UserDetail2 `orm:"with:uid"` + UserScores []*UserScores `orm:"with:uid"` + } + + type UserDetail struct { + gmeta.Meta `orm:"table:user_detail"` + Uid int `json:"uid"` + Address string `json:"address"` + UserDetail3 *UserDetail3 `orm:"with:uid"` + UserScores []*UserScores `orm:"with:uid"` + } + + type User struct { + gmeta.Meta `orm:"table:user"` + *UserDetail `orm:"with:uid=id"` + Id int `json:"id"` + Name string `json:"name"` + } + + // Initialize the data. + var err error + for i := 1; i <= 5; i++ { + // User. + _, err = db.Insert(tableUser, g.Map{ + "id": i, + "name": fmt.Sprintf(`name_%d`, i), + }) + gtest.Assert(err, nil) + // Detail. + _, err = db.Insert(tableUserDetail, g.Map{ + "uid": i, + "address": fmt.Sprintf(`address_%d`, i), + }) + gtest.Assert(err, nil) + // Scores. + for j := 1; j <= 5; j++ { + _, err = db.Insert(tableUserScores, g.Map{ + "uid": i, + "score": j, + }) + gtest.Assert(err, nil) + } + } + + gtest.C(t, func(t *gtest.T) { + var user *User + err := db.Model(tableUser).With(UserDetail{}, UserDetail2{}, UserDetail3{}, UserScores{}).Where("id", 3).Scan(&user) + t.AssertNil(err) + t.Assert(user.Id, 3) + t.AssertNE(user.UserDetail, nil) + t.Assert(user.UserDetail.Uid, 3) + t.Assert(user.UserDetail.UserDetail3.Uid, 3) + t.Assert(user.UserDetail.UserDetail3.UserDetail2.Uid, 3) + t.Assert(user.UserDetail.UserDetail3.UserDetail2.UserDetail1, nil) + t.Assert(user.UserDetail.Address, `address_3`) + t.Assert(len(user.UserDetail.UserScores), 5) + t.Assert(user.UserDetail.UserScores[0].Uid, 3) + t.Assert(user.UserDetail.UserScores[0].Score, 1) + t.Assert(user.UserDetail.UserScores[4].Uid, 3) + t.Assert(user.UserDetail.UserScores[4].Score, 5) + }) + gtest.C(t, func(t *gtest.T) { + var user User + err := db.Model(tableUser).With(UserDetail{}, UserDetail2{}, UserDetail3{}, UserScores{}).Where("id", 4).Scan(&user) + t.AssertNil(err) + t.Assert(user.Id, 4) + t.AssertNE(user.UserDetail, nil) + t.Assert(user.UserDetail.Uid, 4) + t.Assert(user.UserDetail.UserDetail3.Uid, 4) + t.Assert(user.UserDetail.UserDetail3.UserDetail2.Uid, 4) + t.Assert(user.UserDetail.UserDetail3.UserDetail2.UserDetail1, nil) + t.Assert(user.UserDetail.Address, `address_4`) + t.Assert(len(user.UserDetail.UserScores), 5) + t.Assert(user.UserDetail.UserScores[0].Uid, 4) + t.Assert(user.UserDetail.UserScores[0].Score, 1) + t.Assert(user.UserDetail.UserScores[4].Uid, 4) + t.Assert(user.UserDetail.UserScores[4].Score, 5) + }) +} + +func Test_Table_Relation_With_MultipleDepends1(t *testing.T) { + defer func() { + dropTable("table_a") + dropTable("table_b") + dropTable("table_c") + }() + for _, v := range gstr.SplitAndTrim(gfile.GetContents(gdebug.TestDataPath("with_multiple_depends.sql")), ";") { + if _, err := db.Exec(v); err != nil { + gtest.Error(err) + } + } + + type TableC struct { + gmeta.Meta `orm:"table_c"` + Id int `orm:"id,primary" json:"id"` + TableBId int `orm:"table_b_id" json:"table_b_id"` + } + + type TableB struct { + gmeta.Meta `orm:"table_b"` + Id int `orm:"id,primary" json:"id"` + TableAId int `orm:"table_a_id" json:"table_a_id"` + TableC *TableC `orm:"with:table_b_id=id" json:"table_c"` + } + + type TableA struct { + gmeta.Meta `orm:"table_a"` + Id int `orm:"id,primary" json:"id"` + TableB *TableB `orm:"with:table_a_id=id" json:"table_b"` + } + + db.SetDebug(true) + defer db.SetDebug(false) + + // Struct. + gtest.C(t, func(t *gtest.T) { + var tableA *TableA + err := db.Model("table_a").WithAll().Scan(&tableA) + //g.Dump(tableA) + t.AssertNil(err) + t.AssertNE(tableA, nil) + t.Assert(tableA.Id, 1) + + t.AssertNE(tableA.TableB, nil) + t.AssertNE(tableA.TableB.TableC, nil) + t.Assert(tableA.TableB.TableAId, 1) + t.Assert(tableA.TableB.TableC.Id, 100) + t.Assert(tableA.TableB.TableC.TableBId, 10) + }) + + // Structs + gtest.C(t, func(t *gtest.T) { + var tableA []*TableA + err := db.Model("table_a").WithAll().OrderAsc("id").Scan(&tableA) + //g.Dump(tableA) + t.AssertNil(err) + t.Assert(len(tableA), 2) + t.AssertNE(tableA[0].TableB, nil) + t.AssertNE(tableA[1].TableB, nil) + t.AssertNE(tableA[0].TableB.TableC, nil) + t.AssertNE(tableA[1].TableB.TableC, nil) + + t.Assert(tableA[0].Id, 1) + t.Assert(tableA[0].TableB.Id, 10) + t.Assert(tableA[0].TableB.TableC.Id, 100) + + t.Assert(tableA[1].Id, 2) + t.Assert(tableA[1].TableB.Id, 20) + t.Assert(tableA[1].TableB.TableC.Id, 300) + }) +} + +func Test_Table_Relation_With_MultipleDepends2(t *testing.T) { + defer func() { + dropTable("table_a") + dropTable("table_b") + dropTable("table_c") + }() + for _, v := range gstr.SplitAndTrim(gfile.GetContents(gdebug.TestDataPath("with_multiple_depends.sql")), ";") { + if _, err := db.Exec(v); err != nil { + gtest.Error(err) + } + } + + type TableC struct { + gmeta.Meta `orm:"table_c"` + Id int `orm:"id,primary" json:"id"` + TableBId int `orm:"table_b_id" json:"table_b_id"` + } + + type TableB struct { + gmeta.Meta `orm:"table_b"` + Id int `orm:"id,primary" json:"id"` + TableAId int `orm:"table_a_id" json:"table_a_id"` + TableC []*TableC `orm:"with:table_b_id=id" json:"table_c"` + } + + type TableA struct { + gmeta.Meta `orm:"table_a"` + Id int `orm:"id,primary" json:"id"` + TableB []*TableB `orm:"with:table_a_id=id" json:"table_b"` + } + + db.SetDebug(true) + defer db.SetDebug(false) + + // Struct. + gtest.C(t, func(t *gtest.T) { + var tableA *TableA + err := db.Model("table_a").WithAll().Scan(&tableA) + //g.Dump(tableA) + t.AssertNil(err) + t.AssertNE(tableA, nil) + t.Assert(tableA.Id, 1) + + t.Assert(len(tableA.TableB), 2) + t.Assert(tableA.TableB[0].Id, 10) + t.Assert(tableA.TableB[1].Id, 30) + + t.Assert(len(tableA.TableB[0].TableC), 2) + t.Assert(len(tableA.TableB[1].TableC), 1) + t.Assert(tableA.TableB[0].TableC[0].Id, 100) + t.Assert(tableA.TableB[0].TableC[0].TableBId, 10) + t.Assert(tableA.TableB[0].TableC[1].Id, 200) + t.Assert(tableA.TableB[0].TableC[1].TableBId, 10) + t.Assert(tableA.TableB[1].TableC[0].Id, 400) + t.Assert(tableA.TableB[1].TableC[0].TableBId, 30) + }) + + // Structs + gtest.C(t, func(t *gtest.T) { + var tableA []*TableA + err := db.Model("table_a").WithAll().OrderAsc("id").Scan(&tableA) + //g.Dump(tableA) + t.AssertNil(err) + t.Assert(len(tableA), 2) + + t.Assert(len(tableA[0].TableB), 2) + t.Assert(tableA[0].TableB[0].Id, 10) + t.Assert(tableA[0].TableB[1].Id, 30) + + t.Assert(len(tableA[0].TableB[0].TableC), 2) + t.Assert(len(tableA[0].TableB[1].TableC), 1) + t.Assert(tableA[0].TableB[0].TableC[0].Id, 100) + t.Assert(tableA[0].TableB[0].TableC[0].TableBId, 10) + t.Assert(tableA[0].TableB[0].TableC[1].Id, 200) + t.Assert(tableA[0].TableB[0].TableC[1].TableBId, 10) + t.Assert(tableA[0].TableB[1].TableC[0].Id, 400) + t.Assert(tableA[0].TableB[1].TableC[0].TableBId, 30) + + t.Assert(tableA[1].TableB[0].TableC[0].Id, 300) + t.Assert(tableA[1].TableB[0].TableC[0].TableBId, 20) + + t.Assert(tableA[1].TableB[1].Id, 40) + t.Assert(tableA[1].TableB[1].TableAId, 2) + t.Assert(tableA[1].TableB[1].TableC, nil) + }) +} + +func Test_Table_Relation_With_MultipleDepends_Embedded(t *testing.T) { + defer func() { + dropTable("table_a") + dropTable("table_b") + dropTable("table_c") + }() + for _, v := range gstr.SplitAndTrim(gfile.GetContents(gdebug.TestDataPath("with_multiple_depends.sql")), ";") { + if _, err := db.Exec(v); err != nil { + gtest.Error(err) + } + } + + type TableC struct { + gmeta.Meta `orm:"table_c"` + Id int `orm:"id,primary" json:"id"` + TableBId int `orm:"table_b_id" json:"table_b_id"` + } + + type TableB struct { + gmeta.Meta `orm:"table_b"` + Id int `orm:"id,primary" json:"id"` + TableAId int `orm:"table_a_id" json:"table_a_id"` + *TableC `orm:"with:table_b_id=id" json:"table_c"` + } + + type TableA struct { + gmeta.Meta `orm:"table_a"` + Id int `orm:"id,primary" json:"id"` + *TableB `orm:"with:table_a_id=id" json:"table_b"` + } + + db.SetDebug(true) + defer db.SetDebug(false) + + // Struct. + gtest.C(t, func(t *gtest.T) { + var tableA *TableA + err := db.Model("table_a").WithAll().Scan(&tableA) + //g.Dump(tableA) + t.AssertNil(err) + t.AssertNE(tableA, nil) + t.Assert(tableA.Id, 1) + + t.AssertNE(tableA.TableB, nil) + t.AssertNE(tableA.TableB.TableC, nil) + t.Assert(tableA.TableB.TableAId, 1) + t.Assert(tableA.TableB.TableC.Id, 100) + t.Assert(tableA.TableB.TableC.TableBId, 10) + }) + + // Structs + gtest.C(t, func(t *gtest.T) { + var tableA []*TableA + err := db.Model("table_a").WithAll().OrderAsc("id").Scan(&tableA) + //g.Dump(tableA) + t.AssertNil(err) + t.Assert(len(tableA), 2) + t.AssertNE(tableA[0].TableB, nil) + t.AssertNE(tableA[1].TableB, nil) + t.AssertNE(tableA[0].TableB.TableC, nil) + t.AssertNE(tableA[1].TableB.TableC, nil) + + t.Assert(tableA[0].Id, 1) + t.Assert(tableA[0].TableB.Id, 10) + t.Assert(tableA[0].TableB.TableC.Id, 100) + + t.Assert(tableA[1].Id, 2) + t.Assert(tableA[1].TableB.Id, 20) + t.Assert(tableA[1].TableB.TableC.Id, 300) + }) +} + +func Test_Table_Relation_WithAll_Embedded_Meta_NameMatchingRule(t *testing.T) { + var ( + tableUser = "user1" + tableUserDetail = "user_detail1" + tableUserScores = "user_scores1" + ) + if _, err := db.Exec(fmt.Sprintf(` +CREATE TABLE IF NOT EXISTS %s ( +id int(10) unsigned NOT NULL AUTO_INCREMENT, +name varchar(45) NOT NULL, +PRIMARY KEY (id) +) ENGINE=InnoDB DEFAULT CHARSET=utf8; + `, tableUser)); err != nil { + gtest.Error(err) + } + defer dropTable(tableUser) + + if _, err := db.Exec(fmt.Sprintf(` +CREATE TABLE IF NOT EXISTS %s ( +user_id int(10) unsigned NOT NULL, +address varchar(45) NOT NULL, +PRIMARY KEY (user_id) +) ENGINE=InnoDB DEFAULT CHARSET=utf8; + `, tableUserDetail)); err != nil { + gtest.Error(err) + } + defer dropTable(tableUserDetail) + + if _, err := db.Exec(fmt.Sprintf(` +CREATE TABLE IF NOT EXISTS %s ( +id int(10) unsigned NOT NULL AUTO_INCREMENT, +user_id int(10) unsigned NOT NULL, +score int(10) unsigned NOT NULL, +PRIMARY KEY (id) +) ENGINE=InnoDB DEFAULT CHARSET=utf8; + `, tableUserScores)); err != nil { + gtest.Error(err) + } + defer dropTable(tableUserScores) + + type UserDetail struct { + gmeta.Meta `orm:"table:user_detail1"` + UserID int `json:"user_id"` + Address string `json:"address"` + } + + type UserScores struct { + gmeta.Meta `orm:"table:user_scores1"` + ID int `json:"id"` + UserID int `json:"user_id"` + Score int `json:"score"` + } + + // For Test Only + type UserEmbedded struct { + ID int `json:"id"` + Name string `json:"name"` + } + + type User struct { + gmeta.Meta `orm:"table:user1"` + UserEmbedded + UserDetail UserDetail `orm:"with:user_id=id"` + UserScores []*UserScores `orm:"with:user_id=id"` + } + + // Initialize the data. + var err error + for i := 1; i <= 5; i++ { + // User. + _, err = db.Insert(tableUser, g.Map{ + "id": i, + "name": fmt.Sprintf(`name_%d`, i), + }) + gtest.AssertNil(err) + // Detail. + _, err = db.Insert(tableUserDetail, g.Map{ + "user_id": i, + "address": fmt.Sprintf(`address_%d`, i), + }) + gtest.AssertNil(err) + // Scores. + for j := 1; j <= 5; j++ { + _, err = db.Insert(tableUserScores, g.Map{ + "user_id": i, + "score": j, + }) + gtest.AssertNil(err) + } + } + + db.SetDebug(true) + defer db.SetDebug(false) + + gtest.C(t, func(t *gtest.T) { + var user *User + err := db.Model(tableUser).WithAll().Where("id", 3).Scan(&user) + t.AssertNil(err) + t.Assert(user.ID, 3) + t.AssertNE(user.UserDetail, nil) + t.Assert(user.UserDetail.UserID, 3) + t.Assert(user.UserDetail.Address, `address_3`) + t.Assert(len(user.UserScores), 5) + t.Assert(user.UserScores[0].UserID, 3) + t.Assert(user.UserScores[0].Score, 1) + t.Assert(user.UserScores[4].UserID, 3) + t.Assert(user.UserScores[4].Score, 5) + }) + gtest.C(t, func(t *gtest.T) { + var user User + err := db.Model(tableUser).WithAll().Where("id", 4).Scan(&user) + t.AssertNil(err) + t.Assert(user.ID, 4) + t.AssertNE(user.UserDetail, nil) + t.Assert(user.UserDetail.UserID, 4) + t.Assert(user.UserDetail.Address, `address_4`) + t.Assert(len(user.UserScores), 5) + t.Assert(user.UserScores[0].UserID, 4) + t.Assert(user.UserScores[0].Score, 1) + t.Assert(user.UserScores[4].UserID, 4) + t.Assert(user.UserScores[4].Score, 5) + }) +} diff --git a/database/gdb/gdb_z_mysql_basic_test.go b/database/gdb/gdb_z_mysql_basic_test.go index e4fee4e42..610592ac3 100644 --- a/database/gdb/gdb_z_mysql_basic_test.go +++ b/database/gdb/gdb_z_mysql_basic_test.go @@ -1,4 +1,4 @@ -// Copyright GoFrame Author(https://github.com/gogf/gf). All Rights Reserved. +// Copyright GoFrame Author(https://goframe.org). All Rights Reserved. // // This Source Code Form is subject to the terms of the MIT License. // If a copy of the MIT was not distributed with this file, @@ -7,10 +7,10 @@ package gdb_test import ( - "testing" - + "github.com/go-sql-driver/mysql" "github.com/gogf/gf/database/gdb" "github.com/gogf/gf/test/gtest" + "testing" ) func Test_Instance(t *testing.T) { @@ -19,7 +19,7 @@ func Test_Instance(t *testing.T) { t.AssertNE(err, nil) db, err := gdb.Instance() - t.Assert(err, nil) + t.AssertNil(err) err1 := db.PingMaster() err2 := db.PingSlave() @@ -27,3 +27,16 @@ func Test_Instance(t *testing.T) { t.Assert(err2, nil) }) } + +// Fix issue: https://github.com/gogf/gf/issues/819 +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 := gdb.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)) + }) +} diff --git a/database/gdb/gdb_z_mysql_ctx_test.go b/database/gdb/gdb_z_mysql_ctx_test.go index b99347942..0d3cf753a 100644 --- a/database/gdb/gdb_z_mysql_ctx_test.go +++ b/database/gdb/gdb_z_mysql_ctx_test.go @@ -1,4 +1,4 @@ -// Copyright GoFrame Author(https://github.com/gogf/gf). All Rights Reserved. +// Copyright GoFrame Author(https://goframe.org). All Rights Reserved. // // This Source Code Form is subject to the terms of the MIT License. // If a copy of the MIT was not distributed with this file, @@ -17,7 +17,7 @@ import ( func Test_Ctx(t *testing.T) { gtest.C(t, func(t *gtest.T) { db, err := gdb.Instance() - t.Assert(err, nil) + t.AssertNil(err) err1 := db.PingMaster() err2 := db.PingSlave() @@ -62,3 +62,23 @@ func Test_Ctx_Model(t *testing.T) { db.Model(table).All() }) } + +func Test_Ctx_Strict(t *testing.T) { + table := createInitTableWithDb(dbCtxStrict) + defer dropTable(table) + + gtest.C(t, func(t *gtest.T) { + _, err := dbCtxStrict.Query("select 1") + t.AssertNE(err, nil) + }) + gtest.C(t, func(t *gtest.T) { + r, err := dbCtxStrict.Model(table).All() + t.AssertNE(err, nil) + t.Assert(len(r), 0) + }) + gtest.C(t, func(t *gtest.T) { + r, err := dbCtxStrict.Model(table).Ctx(context.TODO()).All() + t.AssertNil(err) + t.Assert(len(r), TableSize) + }) +} diff --git a/database/gdb/gdb_z_mysql_filter_test.go b/database/gdb/gdb_z_mysql_filter_test.go new file mode 100644 index 000000000..cd9c30107 --- /dev/null +++ b/database/gdb/gdb_z_mysql_filter_test.go @@ -0,0 +1,218 @@ +// Copyright GoFrame Author(https://goframe.org). All Rights Reserved. +// +// This Source Code Form is subject to the terms of the MIT License. +// If a copy of the MIT was not distributed with this file, +// You can obtain one at https://github.com/gogf/gf. + +package gdb_test + +import ( + "fmt" + "github.com/gogf/gf/frame/g" + "github.com/gogf/gf/os/gtime" + "github.com/gogf/gf/test/gtest" + "testing" +) + +// Using filter dose not affect the outside value inside function. +func Test_Model_Insert_Filter(t *testing.T) { + // map + gtest.C(t, func(t *gtest.T) { + table := createTable() + defer dropTable(table) + data := g.Map{ + "id": 1, + "uid": 1, + "passport": "t1", + "password": "25d55ad283aa400af464c76d713c07ad", + "nickname": "name_1", + "create_time": gtime.Now().String(), + } + result, err := db.Model(table).Data(data).Insert() + t.AssertNil(err) + n, _ := result.LastInsertId() + t.Assert(n, 1) + + t.Assert(data["uid"], 1) + }) + // slice + gtest.C(t, func(t *gtest.T) { + table := createTable() + defer dropTable(table) + data := g.List{ + g.Map{ + "id": 1, + "uid": 1, + "passport": "t1", + "password": "25d55ad283aa400af464c76d713c07ad", + "nickname": "name_1", + "create_time": gtime.Now().String(), + }, + g.Map{ + "id": 2, + "uid": 2, + "passport": "t1", + "password": "25d55ad283aa400af464c76d713c07ad", + "nickname": "name_1", + "create_time": gtime.Now().String(), + }, + } + + result, err := db.Model(table).Data(data).Insert() + t.AssertNil(err) + n, _ := result.LastInsertId() + t.Assert(n, 2) + + t.Assert(data[0]["uid"], 1) + t.Assert(data[1]["uid"], 2) + }) +} + +func Test_Model_Embedded_Filter(t *testing.T) { + table := createTable() + defer dropTable(table) + gtest.C(t, func(t *gtest.T) { + type Base struct { + Id int + Uid int + CreateTime string + NoneExist string + } + type User struct { + Base + Passport string + Password string + Nickname string + } + result, err := db.Model(table).Data(User{ + Passport: "john-test", + Password: "123456", + Nickname: "John", + Base: Base{ + Id: 100, + Uid: 100, + CreateTime: gtime.Now().String(), + }, + }).Insert() + t.AssertNil(err) + n, _ := result.RowsAffected() + t.Assert(n, 1) + + var user *User + err = db.Model(table).Fields(user).Where("id=100").Scan(&user) + t.AssertNil(err) + t.Assert(user.Passport, "john-test") + t.Assert(user.Id, 100) + }) +} + +// This is no longer used as the filter feature is automatically enabled from GoFrame v1.16.0. +//func Test_Model_Insert_KeyFieldNameMapping_Error(t *testing.T) { +// table := createTable() +// defer dropTable(table) +// +// gtest.C(t, func(t *gtest.T) { +// type User struct { +// Id int +// Passport string +// Password string +// Nickname string +// CreateTime string +// NoneExistFiled string +// } +// data := User{ +// Id: 1, +// Passport: "user_1", +// Password: "pass_1", +// Nickname: "name_1", +// CreateTime: "2020-10-10 12:00:01", +// } +// _, err := db.Model(table).Data(data).Insert() +// t.AssertNE(err, nil) +// }) +//} + +func Test_Model_Fields_AutoFilterInJoinStatement(t *testing.T) { + gtest.C(t, func(t *gtest.T) { + var err error + table1 := "user" + table2 := "score" + table3 := "info" + if _, err := db.Exec(fmt.Sprintf(` + CREATE TABLE IF NOT EXISTS %s ( + id int(11) NOT NULL AUTO_INCREMENT, + name varchar(500) NOT NULL DEFAULT '', + PRIMARY KEY (id) + ) ENGINE=InnoDB DEFAULT CHARSET=utf8 AUTO_INCREMENT=1; + `, table1, + )); err != nil { + t.AssertNil(err) + } + defer dropTable(table1) + _, err = db.Model(table1).Insert(g.Map{ + "id": 1, + "name": "john", + }) + t.AssertNil(err) + + if _, err := db.Exec(fmt.Sprintf(` + CREATE TABLE IF NOT EXISTS %s ( + id int(11) NOT NULL AUTO_INCREMENT, + user_id int(11) NOT NULL DEFAULT 0, + number varchar(500) NOT NULL DEFAULT '', + PRIMARY KEY (id) + ) ENGINE=InnoDB DEFAULT CHARSET=utf8 AUTO_INCREMENT=1; + `, table2, + )); err != nil { + t.AssertNil(err) + } + defer dropTable(table2) + _, err = db.Model(table2).Insert(g.Map{ + "id": 1, + "user_id": 1, + "number": "n", + }) + t.AssertNil(err) + + if _, err := db.Exec(fmt.Sprintf(` + CREATE TABLE IF NOT EXISTS %s ( + id int(11) NOT NULL AUTO_INCREMENT, + user_id int(11) NOT NULL DEFAULT 0, + description varchar(500) NOT NULL DEFAULT '', + PRIMARY KEY (id) + ) ENGINE=InnoDB DEFAULT CHARSET=utf8 AUTO_INCREMENT=1; + `, table3, + )); err != nil { + t.AssertNil(err) + } + defer dropTable(table3) + _, err = db.Model(table3).Insert(g.Map{ + "id": 1, + "user_id": 1, + "description": "brief", + }) + t.AssertNil(err) + + one, err := db.Model("user"). + Where("user.id", 1). + Fields("score.number,user.name"). + LeftJoin("score", "user.id=score.user_id"). + LeftJoin("info", "info.id=info.user_id"). + Order("user.id asc"). + One() + t.AssertNil(err) + t.Assert(len(one), 2) + t.Assert(one["name"].String(), "john") + t.Assert(one["number"].String(), "n") + + one, err = db.Model("user"). + LeftJoin("score", "user.id=score.user_id"). + LeftJoin("info", "info.id=info.user_id"). + Fields("score.number,user.name"). + One() + t.AssertNil(err) + t.Assert(len(one), 2) + t.Assert(one["name"].String(), "john") + t.Assert(one["number"].String(), "n") + }) +} diff --git a/database/gdb/gdb_z_mysql_internal_test.go b/database/gdb/gdb_z_mysql_internal_test.go index af07624ae..d823b1253 100644 --- a/database/gdb/gdb_z_mysql_internal_test.go +++ b/database/gdb/gdb_z_mysql_internal_test.go @@ -1,4 +1,4 @@ -// Copyright GoFrame Author(https://github.com/gogf/gf). All Rights Reserved. +// Copyright GoFrame Author(https://goframe.org). All Rights Reserved. // // This Source Code Form is subject to the terms of the MIT License. // If a copy of the MIT was not distributed with this file, @@ -8,7 +8,6 @@ package gdb import ( "fmt" - "github.com/go-sql-driver/mysql" "github.com/gogf/gf/container/gvar" "github.com/gogf/gf/os/gcmd" "github.com/gogf/gf/os/gtime" @@ -17,9 +16,9 @@ import ( ) const ( - SCHEMA = "test_internal" - USER = "root" - PASS = "12345678" + SCHEMA = "test_internal" + TestDbUser = "root" + TestDbPass = "12345678" ) var ( @@ -32,12 +31,12 @@ func init() { "name": true, "type": true, }, false) - gtest.Assert(err, nil) + gtest.AssertNil(err) configNode = ConfigNode{ Host: "127.0.0.1", Port: "3306", - User: USER, - Pass: PASS, + User: TestDbUser, + Pass: TestDbPass, Name: parser.GetOpt("name", ""), Type: parser.GetOpt("type", "mysql"), Role: "master", @@ -45,7 +44,7 @@ func init() { Weight: 1, MaxIdleConnCount: 10, MaxOpenConnCount: 10, - MaxConnLifetime: 600, + MaxConnLifeTime: 600, } AddConfigNode(DefaultGroupName, configNode) // Default db. @@ -204,7 +203,7 @@ CREATE TABLE %s ( defer dropTable(table2) gtest.C(t, func(t *gtest.T) { - model := db.Table(table1) + model := db.Model(table1) gtest.Assert(model.getSoftFieldNameCreated(table2), "createat") gtest.Assert(model.getSoftFieldNameUpdated(table2), "updateat") gtest.Assert(model.getSoftFieldNameDeleted(table2), "deleteat") @@ -243,48 +242,48 @@ CREATE TABLE %s ( defer dropTable(table2) gtest.C(t, func(t *gtest.T) { - model := db.Table(table1) + model := db.Model(table1) t.Assert(model.getConditionForSoftDeleting(), "`delete_at` IS NULL") }) gtest.C(t, func(t *gtest.T) { - model := db.Table(fmt.Sprintf(`%s as t`, table1)) + model := db.Model(fmt.Sprintf(`%s as t`, table1)) t.Assert(model.getConditionForSoftDeleting(), "`delete_at` IS NULL") }) gtest.C(t, func(t *gtest.T) { - model := db.Table(fmt.Sprintf(`%s, %s`, table1, table2)) + model := db.Model(fmt.Sprintf(`%s, %s`, table1, table2)) t.Assert(model.getConditionForSoftDeleting(), fmt.Sprintf( "`%s`.`delete_at` IS NULL AND `%s`.`deleteat` IS NULL", table1, table2, )) }) gtest.C(t, func(t *gtest.T) { - model := db.Table(fmt.Sprintf(`%s t1, %s as t2`, table1, table2)) + model := db.Model(fmt.Sprintf(`%s t1, %s as t2`, table1, table2)) t.Assert(model.getConditionForSoftDeleting(), "`t1`.`delete_at` IS NULL AND `t2`.`deleteat` IS NULL") }) gtest.C(t, func(t *gtest.T) { - model := db.Table(fmt.Sprintf(`%s as t1, %s as t2`, table1, table2)) + model := db.Model(fmt.Sprintf(`%s as t1, %s as t2`, table1, table2)) t.Assert(model.getConditionForSoftDeleting(), "`t1`.`delete_at` IS NULL AND `t2`.`deleteat` IS NULL") }) gtest.C(t, func(t *gtest.T) { - model := db.Table(fmt.Sprintf(`%s as t1`, table1)).LeftJoin(table2+" t2", "t2.id2=t1.id1") + model := db.Model(fmt.Sprintf(`%s as t1`, table1)).LeftJoin(table2+" t2", "t2.id2=t1.id1") t.Assert(model.getConditionForSoftDeleting(), "`t1`.`delete_at` IS NULL AND `t2`.`deleteat` IS NULL") }) gtest.C(t, func(t *gtest.T) { - model := db.Table(fmt.Sprintf(`%s`, table1)).LeftJoin(table2, "t2.id2=t1.id1") + model := db.Model(fmt.Sprintf(`%s`, table1)).LeftJoin(table2, "t2.id2=t1.id1") t.Assert(model.getConditionForSoftDeleting(), fmt.Sprintf( "`%s`.`delete_at` IS NULL AND `%s`.`deleteat` IS NULL", table1, table2, )) }) gtest.C(t, func(t *gtest.T) { - model := db.Table(fmt.Sprintf(`%s`, table1)).LeftJoin(table2, "t2.id2=t1.id1").RightJoin(table2, "t2.id2=t1.id1") + model := db.Model(fmt.Sprintf(`%s`, table1)).LeftJoin(table2, "t2.id2=t1.id1").RightJoin(table2, "t2.id2=t1.id1") t.Assert(model.getConditionForSoftDeleting(), fmt.Sprintf( "`%s`.`delete_at` IS NULL AND `%s`.`deleteat` IS NULL AND `%s`.`deleteat` IS NULL", table1, table2, table2, )) }) gtest.C(t, func(t *gtest.T) { - model := db.Table(table1+" as t1").LeftJoin(table2+" as t2", "t2.id2=t1.id1").RightJoin(table2+" as t3 ", "t2.id2=t1.id1") + model := db.Model(table1+" as t1").LeftJoin(table2+" as t2", "t2.id2=t1.id1").RightJoin(table2+" as t3 ", "t2.id2=t1.id1") t.Assert( model.getConditionForSoftDeleting(), "`t1`.`delete_at` IS NULL AND `t2`.`deleteat` IS NULL AND `t3`.`deleteat` IS NULL", @@ -292,19 +291,6 @@ CREATE TABLE %s ( }) } -// Fix issue: https://github.com/gogf/gf/issues/819 -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 := 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) @@ -329,10 +315,330 @@ func TestResult_Structs1(t *testing.T) { } array := make([]*B, 2) err := r.Structs(&array) - t.Assert(err, nil) + t.AssertNil(err) t.Assert(array[0].Id, 0) t.Assert(array[1].Id, 0) t.Assert(array[0].Name, "john") t.Assert(array[1].Name, "smith") }) } + +// https://github.com/gogf/gf/issues/1159 +func Test_ScanList_NoRecreate_PtrAttribute(t *testing.T) { + gtest.C(t, func(t *gtest.T) { + type S1 struct { + Id int + Name string + Age int + Score int + } + type S3 struct { + One *S1 + } + var ( + s []*S3 + err error + ) + r1 := Result{ + Record{ + "id": gvar.New(1), + "name": gvar.New("john"), + "age": gvar.New(16), + }, + Record{ + "id": gvar.New(2), + "name": gvar.New("smith"), + "age": gvar.New(18), + }, + } + err = r1.ScanList(&s, "One") + t.AssertNil(err) + t.Assert(len(s), 2) + t.Assert(s[0].One.Name, "john") + t.Assert(s[0].One.Age, 16) + t.Assert(s[1].One.Name, "smith") + t.Assert(s[1].One.Age, 18) + + r2 := Result{ + Record{ + "id": gvar.New(1), + "age": gvar.New(20), + }, + Record{ + "id": gvar.New(2), + "age": gvar.New(21), + }, + } + err = r2.ScanList(&s, "One", "One", "id:Id") + t.AssertNil(err) + t.Assert(len(s), 2) + t.Assert(s[0].One.Name, "john") + t.Assert(s[0].One.Age, 20) + t.Assert(s[1].One.Name, "smith") + t.Assert(s[1].One.Age, 21) + }) +} + +// https://github.com/gogf/gf/issues/1159 +func Test_ScanList_NoRecreate_StructAttribute(t *testing.T) { + gtest.C(t, func(t *gtest.T) { + type S1 struct { + Id int + Name string + Age int + Score int + } + type S3 struct { + One S1 + } + var ( + s []*S3 + err error + ) + r1 := Result{ + Record{ + "id": gvar.New(1), + "name": gvar.New("john"), + "age": gvar.New(16), + }, + Record{ + "id": gvar.New(2), + "name": gvar.New("smith"), + "age": gvar.New(18), + }, + } + err = r1.ScanList(&s, "One") + t.AssertNil(err) + t.Assert(len(s), 2) + t.Assert(s[0].One.Name, "john") + t.Assert(s[0].One.Age, 16) + t.Assert(s[1].One.Name, "smith") + t.Assert(s[1].One.Age, 18) + + r2 := Result{ + Record{ + "id": gvar.New(1), + "age": gvar.New(20), + }, + Record{ + "id": gvar.New(2), + "age": gvar.New(21), + }, + } + err = r2.ScanList(&s, "One", "One", "id:Id") + t.AssertNil(err) + t.Assert(len(s), 2) + t.Assert(s[0].One.Name, "john") + t.Assert(s[0].One.Age, 20) + t.Assert(s[1].One.Name, "smith") + t.Assert(s[1].One.Age, 21) + }) +} + +// https://github.com/gogf/gf/issues/1159 +func Test_ScanList_NoRecreate_SliceAttribute_Ptr(t *testing.T) { + gtest.C(t, func(t *gtest.T) { + type S1 struct { + Id int + Name string + Age int + Score int + } + type S2 struct { + Id int + Pid int + Name string + Age int + Score int + } + type S3 struct { + One *S1 + Many []*S2 + } + var ( + s []*S3 + err error + ) + r1 := Result{ + Record{ + "id": gvar.New(1), + "name": gvar.New("john"), + "age": gvar.New(16), + }, + Record{ + "id": gvar.New(2), + "name": gvar.New("smith"), + "age": gvar.New(18), + }, + } + err = r1.ScanList(&s, "One") + t.AssertNil(err) + t.Assert(len(s), 2) + t.Assert(s[0].One.Name, "john") + t.Assert(s[0].One.Age, 16) + t.Assert(s[1].One.Name, "smith") + t.Assert(s[1].One.Age, 18) + + r2 := Result{ + Record{ + "id": gvar.New(100), + "pid": gvar.New(1), + "age": gvar.New(30), + "name": gvar.New("john"), + }, + Record{ + "id": gvar.New(200), + "pid": gvar.New(1), + "age": gvar.New(31), + "name": gvar.New("smith"), + }, + } + err = r2.ScanList(&s, "Many", "One", "pid:Id") + //fmt.Printf("%+v", err) + t.AssertNil(err) + t.Assert(len(s), 2) + t.Assert(s[0].One.Name, "john") + t.Assert(s[0].One.Age, 16) + t.Assert(len(s[0].Many), 2) + t.Assert(s[0].Many[0].Name, "john") + t.Assert(s[0].Many[0].Age, 30) + t.Assert(s[0].Many[1].Name, "smith") + t.Assert(s[0].Many[1].Age, 31) + + t.Assert(s[1].One.Name, "smith") + t.Assert(s[1].One.Age, 18) + t.Assert(len(s[1].Many), 0) + + r3 := Result{ + Record{ + "id": gvar.New(100), + "pid": gvar.New(1), + "age": gvar.New(40), + }, + Record{ + "id": gvar.New(200), + "pid": gvar.New(1), + "age": gvar.New(41), + }, + } + err = r3.ScanList(&s, "Many", "One", "pid:Id") + //fmt.Printf("%+v", err) + t.AssertNil(err) + t.Assert(len(s), 2) + t.Assert(s[0].One.Name, "john") + t.Assert(s[0].One.Age, 16) + t.Assert(len(s[0].Many), 2) + t.Assert(s[0].Many[0].Name, "john") + t.Assert(s[0].Many[0].Age, 40) + t.Assert(s[0].Many[1].Name, "smith") + t.Assert(s[0].Many[1].Age, 41) + + t.Assert(s[1].One.Name, "smith") + t.Assert(s[1].One.Age, 18) + t.Assert(len(s[1].Many), 0) + }) +} + +// https://github.com/gogf/gf/issues/1159 +func Test_ScanList_NoRecreate_SliceAttribute_Struct(t *testing.T) { + gtest.C(t, func(t *gtest.T) { + type S1 struct { + Id int + Name string + Age int + Score int + } + type S2 struct { + Id int + Pid int + Name string + Age int + Score int + } + type S3 struct { + One S1 + Many []S2 + } + var ( + s []S3 + err error + ) + r1 := Result{ + Record{ + "id": gvar.New(1), + "name": gvar.New("john"), + "age": gvar.New(16), + }, + Record{ + "id": gvar.New(2), + "name": gvar.New("smith"), + "age": gvar.New(18), + }, + } + err = r1.ScanList(&s, "One") + t.AssertNil(err) + t.Assert(len(s), 2) + t.Assert(s[0].One.Name, "john") + t.Assert(s[0].One.Age, 16) + t.Assert(s[1].One.Name, "smith") + t.Assert(s[1].One.Age, 18) + + r2 := Result{ + Record{ + "id": gvar.New(100), + "pid": gvar.New(1), + "age": gvar.New(30), + "name": gvar.New("john"), + }, + Record{ + "id": gvar.New(200), + "pid": gvar.New(1), + "age": gvar.New(31), + "name": gvar.New("smith"), + }, + } + err = r2.ScanList(&s, "Many", "One", "pid:Id") + //fmt.Printf("%+v", err) + t.AssertNil(err) + t.Assert(len(s), 2) + t.Assert(s[0].One.Name, "john") + t.Assert(s[0].One.Age, 16) + t.Assert(len(s[0].Many), 2) + t.Assert(s[0].Many[0].Name, "john") + t.Assert(s[0].Many[0].Age, 30) + t.Assert(s[0].Many[1].Name, "smith") + t.Assert(s[0].Many[1].Age, 31) + + t.Assert(s[1].One.Name, "smith") + t.Assert(s[1].One.Age, 18) + t.Assert(len(s[1].Many), 0) + + r3 := Result{ + Record{ + "id": gvar.New(100), + "pid": gvar.New(1), + "age": gvar.New(40), + }, + Record{ + "id": gvar.New(200), + "pid": gvar.New(1), + "age": gvar.New(41), + }, + } + err = r3.ScanList(&s, "Many", "One", "pid:Id") + //fmt.Printf("%+v", err) + t.AssertNil(err) + t.Assert(len(s), 2) + t.Assert(s[0].One.Name, "john") + t.Assert(s[0].One.Age, 16) + t.Assert(len(s[0].Many), 2) + t.Assert(s[0].Many[0].Name, "john") + t.Assert(s[0].Many[0].Age, 40) + t.Assert(s[0].Many[1].Name, "smith") + t.Assert(s[0].Many[1].Age, 41) + + t.Assert(s[1].One.Name, "smith") + t.Assert(s[1].One.Age, 18) + t.Assert(len(s[1].Many), 0) + }) +} diff --git a/database/gdb/gdb_z_mysql_method_test.go b/database/gdb/gdb_z_mysql_method_test.go index 2b26baa40..b571a1da3 100644 --- a/database/gdb/gdb_z_mysql_method_test.go +++ b/database/gdb/gdb_z_mysql_method_test.go @@ -1,4 +1,4 @@ -// Copyright GoFrame Author(https://github.com/gogf/gf). All Rights Reserved. +// Copyright GoFrame Author(https://goframe.org). All Rights Reserved. // // This Source Code Form is subject to the terms of the MIT License. // If a copy of the MIT was not distributed with this file, @@ -7,9 +7,11 @@ package gdb_test import ( + "context" "fmt" "github.com/gogf/gf/container/garray" "github.com/gogf/gf/encoding/gparser" + "github.com/gogf/gf/text/gstr" "testing" "time" @@ -34,13 +36,13 @@ func Test_DB_Ping(t *testing.T) { func Test_DB_Query(t *testing.T) { gtest.C(t, func(t *gtest.T) { _, err := db.Query("SELECT ?", 1) - t.Assert(err, nil) + t.AssertNil(err) _, err = db.Query("SELECT ?+?", 1, 2) - t.Assert(err, nil) + t.AssertNil(err) _, err = db.Query("SELECT ?+?", g.Slice{1, 2}) - t.Assert(err, nil) + t.AssertNil(err) _, err = db.Query("ERROR") t.AssertNE(err, nil) @@ -51,7 +53,7 @@ func Test_DB_Query(t *testing.T) { func Test_DB_Exec(t *testing.T) { gtest.C(t, func(t *gtest.T) { _, err := db.Exec("SELECT ?", 1) - t.Assert(err, nil) + t.AssertNil(err) _, err = db.Exec("ERROR") t.AssertNE(err, nil) @@ -62,17 +64,17 @@ func Test_DB_Exec(t *testing.T) { func Test_DB_Prepare(t *testing.T) { gtest.C(t, func(t *gtest.T) { st, err := db.Prepare("SELECT 100") - t.Assert(err, nil) + t.AssertNil(err) rows, err := st.Query() - t.Assert(err, nil) + t.AssertNil(err) array, err := rows.Columns() - t.Assert(err, nil) + t.AssertNil(err) t.Assert(array[0], "100") err = rows.Close() - t.Assert(err, nil) + t.AssertNil(err) }) } @@ -88,7 +90,7 @@ func Test_DB_Insert(t *testing.T) { "nickname": "T1", "create_time": gtime.Now().String(), }) - t.Assert(err, nil) + t.AssertNil(err) // normal map result, err := db.Insert(table, g.Map{ @@ -98,7 +100,7 @@ func Test_DB_Insert(t *testing.T) { "nickname": "name_2", "create_time": gtime.Now().String(), }) - t.Assert(err, nil) + t.AssertNil(err) n, _ := result.RowsAffected() t.Assert(n, 1) @@ -118,12 +120,12 @@ func Test_DB_Insert(t *testing.T) { Nickname: "name_3", CreateTime: timeStr, }) - t.Assert(err, nil) + t.AssertNil(err) n, _ = result.RowsAffected() t.Assert(n, 1) - one, err := db.Table(table).Where("id", 3).One() - t.Assert(err, nil) + one, err := db.Model(table).Where("id", 3).One() + t.AssertNil(err) t.Assert(one["id"].Int(), 3) t.Assert(one["passport"].String(), "user_3") @@ -140,12 +142,12 @@ func Test_DB_Insert(t *testing.T) { Nickname: "name_4", CreateTime: timeStr, }) - t.Assert(err, nil) + t.AssertNil(err) n, _ = result.RowsAffected() t.Assert(n, 1) - one, err = db.Table(table).Where("id", 4).One() - t.Assert(err, nil) + one, err = db.Model(table).Where("id", 4).One() + t.AssertNil(err) t.Assert(one["id"].Int(), 4) t.Assert(one["passport"].String(), "t4") t.Assert(one["password"].String(), "25d55ad283aa400af464c76d713c07ad") @@ -170,12 +172,12 @@ func Test_DB_Insert(t *testing.T) { "create_time": timeStr, }, }) - t.Assert(err, nil) + t.AssertNil(err) n, _ = r.RowsAffected() t.Assert(n, 2) - one, err = db.Table(table).Where("id", 200).One() - t.Assert(err, nil) + one, err = db.Model(table).Where("id", 200).One() + t.AssertNil(err) t.Assert(one["id"].Int(), 200) t.Assert(one["passport"].String(), "t200") t.Assert(one["password"].String(), "25d55ad283aa400af464c76d71qw07ad") @@ -202,10 +204,10 @@ func Test_DB_Insert_WithStructAndSliceAttribute(t *testing.T) { "create_time": gtime.Now().String(), } _, err := db.Insert(table, data) - t.Assert(err, nil) + t.AssertNil(err) one, err := db.GetOne(fmt.Sprintf("SELECT * FROM %s WHERE id=?", table), 1) - t.Assert(err, nil) + t.AssertNil(err) t.Assert(one["passport"], data["passport"]) t.Assert(one["create_time"], data["create_time"]) t.Assert(one["nickname"], gparser.MustToJson(data["nickname"])) @@ -232,10 +234,10 @@ func Test_DB_Insert_KeyFieldNameMapping(t *testing.T) { CreateTime: "2020-10-10 12:00:01", } _, err := db.Insert(table, data) - t.Assert(err, nil) + t.AssertNil(err) one, err := db.GetOne(fmt.Sprintf("SELECT * FROM %s WHERE id=?", table), 1) - t.Assert(err, nil) + t.AssertNil(err) t.Assert(one["passport"], data.Passport) t.Assert(one["create_time"], data.CreateTime) t.Assert(one["nickname"], data.Nickname) @@ -262,40 +264,41 @@ func Test_DB_Upadte_KeyFieldNameMapping(t *testing.T) { CreateTime: "2020-10-10 12:00:01", } _, err := db.Update(table, data, "id=1") - t.Assert(err, nil) + t.AssertNil(err) one, err := db.GetOne(fmt.Sprintf("SELECT * FROM %s WHERE id=?", table), 1) - t.Assert(err, nil) + t.AssertNil(err) 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) - }) -} +// This is no longer used as the filter feature is automatically enabled from GoFrame v1.16.0. +//func Test_DB_Insert_KeyFieldNameMapping_Error(t *testing.T) { +// table := createTable() +// defer dropTable(table) +// +// gtest.C(t, func(t *gtest.T) { +// type User struct { +// Id int +// Passport string +// Password string +// Nickname string +// CreateTime string +// NoneExistField string +// } +// data := User{ +// Id: 1, +// Passport: "user_1", +// Password: "pass_1", +// Nickname: "name_1", +// CreateTime: "2020-10-10 12:00:01", +// } +// _, err := db.Insert(table, data) +// t.AssertNE(err, nil) +// }) +//} func Test_DB_InsertIgnore(t *testing.T) { table := createInitTable() @@ -318,7 +321,7 @@ func Test_DB_InsertIgnore(t *testing.T) { "nickname": "T1", "create_time": gtime.Now().String(), }) - t.Assert(err, nil) + t.AssertNil(err) }) } @@ -326,7 +329,7 @@ func Test_DB_BatchInsert(t *testing.T) { gtest.C(t, func(t *gtest.T) { table := createTable() defer dropTable(table) - r, err := db.BatchInsert(table, g.List{ + r, err := db.Insert(table, g.List{ { "id": 2, "passport": "t2", @@ -342,7 +345,7 @@ func Test_DB_BatchInsert(t *testing.T) { "create_time": gtime.Now().String(), }, }, 1) - t.Assert(err, nil) + t.AssertNil(err) n, _ := r.RowsAffected() t.Assert(n, 2) @@ -354,7 +357,7 @@ func Test_DB_BatchInsert(t *testing.T) { table := createTable() defer dropTable(table) // []interface{} - r, err := db.BatchInsert(table, g.Slice{ + r, err := db.Insert(table, g.Slice{ g.Map{ "id": 2, "passport": "t2", @@ -370,7 +373,7 @@ func Test_DB_BatchInsert(t *testing.T) { "create_time": gtime.Now().String(), }, }, 1) - t.Assert(err, nil) + t.AssertNil(err) n, _ := r.RowsAffected() t.Assert(n, 2) }) @@ -379,14 +382,14 @@ func Test_DB_BatchInsert(t *testing.T) { gtest.C(t, func(t *gtest.T) { table := createTable() defer dropTable(table) - result, err := db.BatchInsert(table, g.Map{ + result, err := db.Insert(table, g.Map{ "id": 1, "passport": "t1", "password": "p1", "nickname": "T1", "create_time": gtime.Now().String(), }) - t.Assert(err, nil) + t.AssertNil(err) n, _ := result.RowsAffected() t.Assert(n, 1) }) @@ -413,8 +416,8 @@ func Test_DB_BatchInsert_Struct(t *testing.T) { NickName: "T1", CreateTime: gtime.Now(), } - result, err := db.BatchInsert(table, user) - t.Assert(err, nil) + result, err := db.Insert(table, user) + t.AssertNil(err) n, _ := result.RowsAffected() t.Assert(n, 1) }) @@ -433,10 +436,10 @@ func Test_DB_Save(t *testing.T) { "nickname": "T11", "create_time": timeStr, }) - t.Assert(err, nil) + t.AssertNil(err) - one, err := db.Table(table).Where("id", 1).One() - t.Assert(err, nil) + one, err := db.Model(table).Where("id", 1).One() + t.AssertNil(err) t.Assert(one["id"].Int(), 1) t.Assert(one["passport"].String(), "t1") t.Assert(one["password"].String(), "25d55ad283aa400af464c76d713c07ad") @@ -458,10 +461,10 @@ func Test_DB_Replace(t *testing.T) { "nickname": "T11", "create_time": timeStr, }) - t.Assert(err, nil) + t.AssertNil(err) - one, err := db.Table(table).Where("id", 1).One() - t.Assert(err, nil) + one, err := db.Model(table).Where("id", 1).One() + t.AssertNil(err) t.Assert(one["id"].Int(), 1) t.Assert(one["passport"].String(), "t1") t.Assert(one["password"].String(), "25d55ad283aa400af464c76d713c07ad") @@ -476,12 +479,12 @@ func Test_DB_Update(t *testing.T) { gtest.C(t, func(t *gtest.T) { result, err := db.Update(table, "password='987654321'", "id=3") - t.Assert(err, nil) + t.AssertNil(err) n, _ := result.RowsAffected() t.Assert(n, 1) - one, err := db.Table(table).Where("id", 3).One() - t.Assert(err, nil) + one, err := db.Model(table).Where("id", 3).One() + t.AssertNil(err) t.Assert(one["id"].Int(), 3) t.Assert(one["passport"].String(), "user_3") t.Assert(one["password"].String(), "987654321") @@ -495,19 +498,19 @@ func Test_DB_GetAll(t *testing.T) { gtest.C(t, func(t *gtest.T) { result, err := db.GetAll(fmt.Sprintf("SELECT * FROM %s WHERE id=?", table), 1) - t.Assert(err, nil) + t.AssertNil(err) t.Assert(len(result), 1) t.Assert(result[0]["id"].Int(), 1) }) gtest.C(t, func(t *gtest.T) { result, err := db.GetAll(fmt.Sprintf("SELECT * FROM %s WHERE id=?", table), g.Slice{1}) - t.Assert(err, nil) + t.AssertNil(err) t.Assert(len(result), 1) t.Assert(result[0]["id"].Int(), 1) }) gtest.C(t, func(t *gtest.T) { result, err := db.GetAll(fmt.Sprintf("SELECT * FROM %s WHERE id in(?)", table), g.Slice{1, 2, 3}) - t.Assert(err, nil) + t.AssertNil(err) t.Assert(len(result), 3) t.Assert(result[0]["id"].Int(), 1) t.Assert(result[1]["id"].Int(), 2) @@ -515,7 +518,7 @@ func Test_DB_GetAll(t *testing.T) { }) gtest.C(t, func(t *gtest.T) { result, err := db.GetAll(fmt.Sprintf("SELECT * FROM %s WHERE id in(?,?,?)", table), g.Slice{1, 2, 3}) - t.Assert(err, nil) + t.AssertNil(err) t.Assert(len(result), 3) t.Assert(result[0]["id"].Int(), 1) t.Assert(result[1]["id"].Int(), 2) @@ -523,7 +526,7 @@ func Test_DB_GetAll(t *testing.T) { }) gtest.C(t, func(t *gtest.T) { result, err := db.GetAll(fmt.Sprintf("SELECT * FROM %s WHERE id in(?,?,?)", table), g.Slice{1, 2, 3}...) - t.Assert(err, nil) + t.AssertNil(err) t.Assert(len(result), 3) t.Assert(result[0]["id"].Int(), 1) t.Assert(result[1]["id"].Int(), 2) @@ -531,7 +534,7 @@ func Test_DB_GetAll(t *testing.T) { }) gtest.C(t, func(t *gtest.T) { result, err := db.GetAll(fmt.Sprintf("SELECT * FROM %s WHERE id>=? AND id <=?", table), g.Slice{1, 3}) - t.Assert(err, nil) + t.AssertNil(err) t.Assert(len(result), 3) t.Assert(result[0]["id"].Int(), 1) t.Assert(result[1]["id"].Int(), 2) @@ -544,7 +547,7 @@ func Test_DB_GetOne(t *testing.T) { defer dropTable(table) gtest.C(t, func(t *gtest.T) { record, err := db.GetOne(fmt.Sprintf("SELECT * FROM %s WHERE passport=?", table), "user_1") - t.Assert(err, nil) + t.AssertNil(err) t.Assert(record["nickname"].String(), "name_1") }) } @@ -554,7 +557,7 @@ func Test_DB_GetValue(t *testing.T) { defer dropTable(table) gtest.C(t, func(t *gtest.T) { value, err := db.GetValue(fmt.Sprintf("SELECT id FROM %s WHERE passport=?", table), "user_3") - t.Assert(err, nil) + t.AssertNil(err) t.Assert(value.Int(), 3) }) } @@ -564,8 +567,8 @@ func Test_DB_GetCount(t *testing.T) { defer dropTable(table) gtest.C(t, func(t *gtest.T) { count, err := db.GetCount(fmt.Sprintf("SELECT * FROM %s", table)) - t.Assert(err, nil) - t.Assert(count, SIZE) + t.AssertNil(err) + t.Assert(count, TableSize) }) } @@ -581,8 +584,8 @@ func Test_DB_GetStruct(t *testing.T) { CreateTime gtime.Time } user := new(User) - err := db.GetStruct(user, fmt.Sprintf("SELECT * FROM %s WHERE id=?", table), 3) - t.Assert(err, nil) + err := db.GetScan(user, fmt.Sprintf("SELECT * FROM %s WHERE id=?", table), 3) + t.AssertNil(err) t.Assert(user.NickName, "name_3") }) gtest.C(t, func(t *gtest.T) { @@ -594,8 +597,8 @@ func Test_DB_GetStruct(t *testing.T) { CreateTime *gtime.Time } user := new(User) - err := db.GetStruct(user, fmt.Sprintf("SELECT * FROM %s WHERE id=?", table), 3) - t.Assert(err, nil) + err := db.GetScan(user, fmt.Sprintf("SELECT * FROM %s WHERE id=?", table), 3) + t.AssertNil(err) t.Assert(user.NickName, "name_3") }) } @@ -612,9 +615,9 @@ func Test_DB_GetStructs(t *testing.T) { CreateTime gtime.Time } var users []User - err := db.GetStructs(&users, fmt.Sprintf("SELECT * FROM %s WHERE id>?", table), 1) - t.Assert(err, nil) - t.Assert(len(users), SIZE-1) + err := db.GetScan(&users, fmt.Sprintf("SELECT * FROM %s WHERE id>?", table), 1) + t.AssertNil(err) + t.Assert(len(users), TableSize-1) t.Assert(users[0].Id, 2) t.Assert(users[1].Id, 3) t.Assert(users[2].Id, 4) @@ -632,9 +635,9 @@ func Test_DB_GetStructs(t *testing.T) { CreateTime *gtime.Time } var users []User - err := db.GetStructs(&users, fmt.Sprintf("SELECT * FROM %s WHERE id>?", table), 1) - t.Assert(err, nil) - t.Assert(len(users), SIZE-1) + err := db.GetScan(&users, fmt.Sprintf("SELECT * FROM %s WHERE id>?", table), 1) + t.AssertNil(err) + t.Assert(len(users), TableSize-1) t.Assert(users[0].Id, 2) t.Assert(users[1].Id, 3) t.Assert(users[2].Id, 4) @@ -657,7 +660,7 @@ func Test_DB_GetScan(t *testing.T) { } user := new(User) err := db.GetScan(user, fmt.Sprintf("SELECT * FROM %s WHERE id=?", table), 3) - t.Assert(err, nil) + t.AssertNil(err) t.Assert(user.NickName, "name_3") }) gtest.C(t, func(t *gtest.T) { @@ -670,7 +673,7 @@ func Test_DB_GetScan(t *testing.T) { } user := new(User) err := db.GetScan(user, fmt.Sprintf("SELECT * FROM %s WHERE id=?", table), 3) - t.Assert(err, nil) + t.AssertNil(err) t.Assert(user.NickName, "name_3") }) @@ -684,8 +687,8 @@ func Test_DB_GetScan(t *testing.T) { } var users []User err := db.GetScan(&users, fmt.Sprintf("SELECT * FROM %s WHERE id>?", table), 1) - t.Assert(err, nil) - t.Assert(len(users), SIZE-1) + t.AssertNil(err) + t.Assert(len(users), TableSize-1) t.Assert(users[0].Id, 2) t.Assert(users[1].Id, 3) t.Assert(users[2].Id, 4) @@ -704,8 +707,8 @@ func Test_DB_GetScan(t *testing.T) { } var users []User err := db.GetScan(&users, fmt.Sprintf("SELECT * FROM %s WHERE id>?", table), 1) - t.Assert(err, nil) - t.Assert(len(users), SIZE-1) + t.AssertNil(err) + t.Assert(len(users), TableSize-1) t.Assert(users[0].Id, 2) t.Assert(users[1].Id, 3) t.Assert(users[2].Id, 4) @@ -720,9 +723,9 @@ func Test_DB_Delete(t *testing.T) { defer dropTable(table) gtest.C(t, func(t *gtest.T) { result, err := db.Delete(table, 1) - t.Assert(err, nil) + t.AssertNil(err) n, _ := result.RowsAffected() - t.Assert(n, SIZE) + t.Assert(n, TableSize) }) } @@ -744,7 +747,7 @@ func Test_DB_Time(t *testing.T) { n, _ := result.RowsAffected() t.Assert(n, 1) value, err := db.GetValue(fmt.Sprintf("select `passport` from `%s` where id=?", table), 200) - t.Assert(err, nil) + t.AssertNil(err) t.Assert(value.String(), "t200") }) @@ -763,13 +766,13 @@ func Test_DB_Time(t *testing.T) { n, _ := result.RowsAffected() t.Assert(n, 1) value, err := db.GetValue(fmt.Sprintf("select `passport` from `%s` where id=?", table), 300) - t.Assert(err, nil) + t.AssertNil(err) t.Assert(value.String(), "t300") }) gtest.C(t, func(t *gtest.T) { result, err := db.Delete(table, 1) - t.Assert(err, nil) + t.AssertNil(err) n, _ := result.RowsAffected() t.Assert(n, 2) }) @@ -779,10 +782,10 @@ func Test_DB_ToJson(t *testing.T) { table := createInitTable() defer dropTable(table) _, err := db.Update(table, "create_time='2010-10-10 00:00:01'", "id=?", 1) - gtest.Assert(err, nil) + gtest.AssertNil(err) gtest.C(t, func(t *gtest.T) { - result, err := db.Table(table).Fields("*").Where("id =? ", 1).Select() + result, err := db.Model(table).Fields("*").Where("id =? ", 1).Select() if err != nil { gtest.Fatal(err) } @@ -819,11 +822,11 @@ func Test_DB_ToJson(t *testing.T) { result = nil err = result.Structs(&users) - t.AssertNE(err, nil) + t.AssertNil(err) }) gtest.C(t, func(t *gtest.T) { - result, err := db.Table(table).Fields("*").Where("id =? ", 1).One() + result, err := db.Model(table).Fields("*").Where("id =? ", 1).One() if err != nil { gtest.Fatal(err) } @@ -853,10 +856,10 @@ func Test_DB_ToXml(t *testing.T) { table := createInitTable() defer dropTable(table) _, err := db.Update(table, "create_time='2010-10-10 00:00:01'", "id=?", 1) - gtest.Assert(err, nil) + gtest.AssertNil(err) gtest.C(t, func(t *gtest.T) { - record, err := db.Table(table).Fields("*").Where("id = ?", 1).One() + record, err := db.Model(table).Fields("*").Where("id = ?", 1).One() if err != nil { gtest.Fatal(err) } @@ -919,10 +922,10 @@ func Test_DB_ToStringMap(t *testing.T) { table := createInitTable() defer dropTable(table) _, err := db.Update(table, "create_time='2010-10-10 00:00:01'", "id=?", 1) - gtest.Assert(err, nil) + gtest.AssertNil(err) gtest.C(t, func(t *gtest.T) { id := "1" - result, err := db.Table(table).Fields("*").Where("id = ?", 1).Select() + result, err := db.Model(table).Fields("*").Where("id = ?", 1).Select() if err != nil { gtest.Fatal(err) } @@ -955,11 +958,11 @@ func Test_DB_ToIntMap(t *testing.T) { table := createInitTable() defer dropTable(table) _, err := db.Update(table, "create_time='2010-10-10 00:00:01'", "id=?", 1) - gtest.Assert(err, nil) + gtest.AssertNil(err) gtest.C(t, func(t *gtest.T) { id := 1 - result, err := db.Table(table).Fields("*").Where("id = ?", id).Select() + result, err := db.Model(table).Fields("*").Where("id = ?", id).Select() if err != nil { gtest.Fatal(err) } @@ -991,11 +994,11 @@ func Test_DB_ToUintMap(t *testing.T) { table := createInitTable() defer dropTable(table) _, err := db.Update(table, "create_time='2010-10-10 00:00:01'", "id=?", 1) - gtest.Assert(err, nil) + gtest.AssertNil(err) gtest.C(t, func(t *gtest.T) { id := 1 - result, err := db.Table(table).Fields("*").Where("id = ?", id).Select() + result, err := db.Model(table).Fields("*").Where("id = ?", id).Select() if err != nil { gtest.Fatal(err) } @@ -1028,12 +1031,12 @@ func Test_DB_ToStringRecord(t *testing.T) { table := createInitTable() defer dropTable(table) _, err := db.Update(table, "create_time='2010-10-10 00:00:01'", "id=?", 1) - gtest.Assert(err, nil) + gtest.AssertNil(err) gtest.C(t, func(t *gtest.T) { id := 1 ids := "1" - result, err := db.Table(table).Fields("*").Where("id = ?", id).Select() + result, err := db.Model(table).Fields("*").Where("id = ?", id).Select() if err != nil { gtest.Fatal(err) } @@ -1066,11 +1069,11 @@ func Test_DB_ToIntRecord(t *testing.T) { table := createInitTable() defer dropTable(table) _, err := db.Update(table, "create_time='2010-10-10 00:00:01'", "id=?", 1) - gtest.Assert(err, nil) + gtest.AssertNil(err) gtest.C(t, func(t *gtest.T) { id := 1 - result, err := db.Table(table).Fields("*").Where("id = ?", id).Select() + result, err := db.Model(table).Fields("*").Where("id = ?", id).Select() if err != nil { gtest.Fatal(err) } @@ -1103,11 +1106,11 @@ func Test_DB_ToUintRecord(t *testing.T) { table := createInitTable() defer dropTable(table) _, err := db.Update(table, "create_time='2010-10-10 00:00:01'", "id=?", 1) - gtest.Assert(err, nil) + gtest.AssertNil(err) gtest.C(t, func(t *gtest.T) { id := 1 - result, err := db.Table(table).Fields("*").Where("id = ?", id).Select() + result, err := db.Model(table).Fields("*").Where("id = ?", id).Select() if err != nil { gtest.Fatal(err) } @@ -1167,7 +1170,7 @@ func Test_DB_TableField(t *testing.T) { "field_varchar": "abc", "field_varbinary": "aaa", } - res, err := db.Table(name).Data(data).Insert() + res, err := db.Model(name).Data(data).Insert() if err != nil { gtest.Fatal(err) } @@ -1179,7 +1182,7 @@ func Test_DB_TableField(t *testing.T) { gtest.Assert(n, 1) } - result, err := db.Table(name).Fields("*").Where("field_int = ?", 2).Select() + result, err := db.Model(name).Fields("*").Where("field_int = ?", 2).Select() if err != nil { gtest.Fatal(err) } @@ -1189,8 +1192,8 @@ func Test_DB_TableField(t *testing.T) { func Test_DB_Prefix(t *testing.T) { db := dbPrefix - name := fmt.Sprintf(`%s_%d`, TABLE, gtime.TimestampNano()) - table := PREFIX1 + name + name := fmt.Sprintf(`%s_%d`, TableName, gtime.TimestampNano()) + table := TableNamePrefix1 + name createTableWithDb(db, table) defer dropTable(table) @@ -1203,7 +1206,7 @@ func Test_DB_Prefix(t *testing.T) { "nickname": fmt.Sprintf(`name_%d`, id), "create_time": gtime.NewFromStr("2018-10-24 10:00:00").String(), }) - t.Assert(err, nil) + t.AssertNil(err) n, e := result.RowsAffected() t.Assert(e, nil) @@ -1219,7 +1222,7 @@ func Test_DB_Prefix(t *testing.T) { "nickname": fmt.Sprintf(`name_%d`, id), "create_time": gtime.NewFromStr("2018-10-24 10:00:01").String(), }) - t.Assert(err, nil) + t.AssertNil(err) n, e := result.RowsAffected() t.Assert(e, nil) @@ -1235,7 +1238,7 @@ func Test_DB_Prefix(t *testing.T) { "nickname": fmt.Sprintf(`name_%d`, id), "create_time": gtime.NewFromStr("2018-10-24 10:00:02").String(), }) - t.Assert(err, nil) + t.AssertNil(err) n, e := result.RowsAffected() t.Assert(e, nil) @@ -1251,7 +1254,7 @@ func Test_DB_Prefix(t *testing.T) { "nickname": fmt.Sprintf(`name_%d`, id), "create_time": gtime.NewFromStr("2018-10-24 10:00:03").String(), }, "id=?", id) - t.Assert(err, nil) + t.AssertNil(err) n, e := result.RowsAffected() t.Assert(e, nil) @@ -1261,7 +1264,7 @@ func Test_DB_Prefix(t *testing.T) { gtest.C(t, func(t *gtest.T) { id := 10000 result, err := db.Delete(name, "id=?", id) - t.Assert(err, nil) + t.AssertNil(err) n, e := result.RowsAffected() t.Assert(e, nil) @@ -1270,7 +1273,7 @@ func Test_DB_Prefix(t *testing.T) { gtest.C(t, func(t *gtest.T) { array := garray.New(true) - for i := 1; i <= SIZE; i++ { + for i := 1; i <= TableSize; i++ { array.Append(g.Map{ "id": i, "passport": fmt.Sprintf(`user_%d`, i), @@ -1280,12 +1283,12 @@ func Test_DB_Prefix(t *testing.T) { }) } - result, err := db.BatchInsert(name, array.Slice()) - t.Assert(err, nil) + result, err := db.Insert(name, array.Slice()) + t.AssertNil(err) n, e := result.RowsAffected() t.Assert(e, nil) - t.Assert(n, SIZE) + t.Assert(n, TableSize) }) } @@ -1298,7 +1301,7 @@ func Test_Model_InnerJoin(t *testing.T) { defer dropTable(table1) defer dropTable(table2) - res, err := db.Table(table1).Where("id > ?", 5).Delete() + res, err := db.Model(table1).Where("id > ?", 5).Delete() if err != nil { t.Fatal(err) } @@ -1310,14 +1313,14 @@ func Test_Model_InnerJoin(t *testing.T) { t.Assert(n, 5) - result, err := db.Table(table1+" u1").InnerJoin(table2+" u2", "u1.id = u2.id").OrderBy("u1.id").Select() + result, err := db.Model(table1+" u1").InnerJoin(table2+" u2", "u1.id = u2.id").OrderBy("u1.id").Select() if err != nil { t.Fatal(err) } t.Assert(len(result), 5) - result, err = db.Table(table1+" u1").InnerJoin(table2+" u2", "u1.id = u2.id").Where("u1.id > ?", 1).OrderBy("u1.id").Select() + result, err = db.Model(table1+" u1").InnerJoin(table2+" u2", "u1.id = u2.id").Where("u1.id > ?", 1).OrderBy("u1.id").Select() if err != nil { t.Fatal(err) } @@ -1334,7 +1337,7 @@ func Test_Model_LeftJoin(t *testing.T) { defer dropTable(table1) defer dropTable(table2) - res, err := db.Table(table2).Where("id > ?", 3).Delete() + res, err := db.Model(table2).Where("id > ?", 3).Delete() if err != nil { t.Fatal(err) } @@ -1346,14 +1349,14 @@ func Test_Model_LeftJoin(t *testing.T) { t.Assert(n, 7) } - result, err := db.Table(table1+" u1").LeftJoin(table2+" u2", "u1.id = u2.id").Select() + result, err := db.Model(table1+" u1").LeftJoin(table2+" u2", "u1.id = u2.id").Select() if err != nil { t.Fatal(err) } t.Assert(len(result), 10) - result, err = db.Table(table1+" u1").LeftJoin(table2+" u2", "u1.id = u2.id").Where("u1.id > ? ", 2).Select() + result, err = db.Model(table1+" u1").LeftJoin(table2+" u2", "u1.id = u2.id").Where("u1.id > ? ", 2).Select() if err != nil { t.Fatal(err) } @@ -1370,7 +1373,7 @@ func Test_Model_RightJoin(t *testing.T) { defer dropTable(table1) defer dropTable(table2) - res, err := db.Table(table1).Where("id > ?", 3).Delete() + res, err := db.Model(table1).Where("id > ?", 3).Delete() if err != nil { t.Fatal(err) } @@ -1382,13 +1385,13 @@ func Test_Model_RightJoin(t *testing.T) { t.Assert(n, 7) - result, err := db.Table(table1+" u1").RightJoin(table2+" u2", "u1.id = u2.id").Select() + result, err := db.Model(table1+" u1").RightJoin(table2+" u2", "u1.id = u2.id").Select() if err != nil { t.Fatal(err) } t.Assert(len(result), 10) - result, err = db.Table(table1+" u1").RightJoin(table2+" u2", "u1.id = u2.id").Where("u1.id > 2").Select() + result, err = db.Model(table1+" u1").RightJoin(table2+" u2", "u1.id = u2.id").Where("u1.id > 2").Select() if err != nil { t.Fatal(err) } @@ -1401,7 +1404,7 @@ func Test_Empty_Slice_Argument(t *testing.T) { defer dropTable(table) gtest.C(t, func(t *gtest.T) { result, err := db.GetAll(fmt.Sprintf(`select * from %s where id in(?)`, table), g.Slice{}) - t.Assert(err, nil) + t.AssertNil(err) t.Assert(len(result), 0) }) } @@ -1428,26 +1431,25 @@ func Test_DB_UpdateCounter(t *testing.T) { "updated_time": 0, } _, err = db.Insert(tableName, insertData) - t.Assert(err, nil) + t.AssertNil(err) }) gtest.C(t, func(t *gtest.T) { gdbCounter := &gdb.Counter{ - Field: "views", + Field: "id", Value: 1, } updateData := g.Map{ - "views": gdbCounter, - "updated_time": gtime.Now().Unix(), + "views": gdbCounter, } result, err := db.Update(tableName, updateData, "id", 1) - t.Assert(err, nil) + t.AssertNil(err) n, _ := result.RowsAffected() t.Assert(n, 1) - one, err := db.Table(tableName).Where("id", 1).One() - t.Assert(err, nil) + one, err := db.Model(tableName).Where("id", 1).One() + t.AssertNil(err) t.Assert(one["id"].Int(), 1) - t.Assert(one["views"].Int(), 1) + t.Assert(one["views"].Int(), 2) }) gtest.C(t, func(t *gtest.T) { @@ -1460,12 +1462,31 @@ func Test_DB_UpdateCounter(t *testing.T) { "updated_time": gtime.Now().Unix(), } result, err := db.Update(tableName, updateData, "id", 1) - t.Assert(err, nil) + t.AssertNil(err) n, _ := result.RowsAffected() t.Assert(n, 1) - one, err := db.Table(tableName).Where("id", 1).One() - t.Assert(err, nil) + one, err := db.Model(tableName).Where("id", 1).One() + t.AssertNil(err) t.Assert(one["id"].Int(), 1) - t.Assert(one["views"].Int(), 0) + t.Assert(one["views"].Int(), 1) + }) +} + +func Test_DB_Ctx(t *testing.T) { + gtest.C(t, func(t *gtest.T) { + ctx, cancel := context.WithTimeout(context.Background(), time.Second) + defer cancel() + _, err := db.Ctx(ctx).Query("SELECT SLEEP(10)") + t.Assert(gstr.Contains(err.Error(), "deadline"), true) + }) +} + +func Test_DB_Ctx_Logger(t *testing.T) { + gtest.C(t, func(t *gtest.T) { + defer db.SetDebug(db.GetDebug()) + db.SetDebug(true) + ctx := context.WithValue(context.Background(), "Trace-Id", "123456789") + _, err := db.Ctx(ctx).Query("SELECT 1") + t.AssertNil(err) }) } diff --git a/database/gdb/gdb_z_mysql_model_test.go b/database/gdb/gdb_z_mysql_model_test.go index 6e24164bd..f9f95fcef 100644 --- a/database/gdb/gdb_z_mysql_model_test.go +++ b/database/gdb/gdb_z_mysql_model_test.go @@ -1,4 +1,4 @@ -// Copyright GoFrame Author(https://github.com/gogf/gf). All Rights Reserved. +// Copyright GoFrame Author(https://goframe.org). All Rights Reserved. // // This Source Code Form is subject to the terms of the MIT License. // If a copy of the MIT was not distributed with this file, @@ -7,16 +7,18 @@ package gdb_test import ( + "context" "database/sql" "fmt" + "testing" + "time" + "github.com/gogf/gf/container/garray" "github.com/gogf/gf/container/gmap" "github.com/gogf/gf/debug/gdebug" "github.com/gogf/gf/encoding/gparser" "github.com/gogf/gf/os/gfile" "github.com/gogf/gf/util/gutil" - "testing" - "time" "github.com/gogf/gf/database/gdb" @@ -29,8 +31,8 @@ func Test_Model_Insert(t *testing.T) { table := createTable() defer dropTable(table) gtest.C(t, func(t *gtest.T) { - user := db.Table(table) - result, err := user.Filter().Data(g.Map{ + user := db.Model(table) + result, err := user.Data(g.Map{ "id": 1, "uid": 1, "passport": "t1", @@ -38,11 +40,11 @@ func Test_Model_Insert(t *testing.T) { "nickname": "name_1", "create_time": gtime.Now().String(), }).Insert() - t.Assert(err, nil) + t.AssertNil(err) n, _ := result.LastInsertId() t.Assert(n, 1) - result, err = db.Table(table).Filter().Data(g.Map{ + result, err = db.Model(table).Data(g.Map{ "id": "2", "uid": "2", "passport": "t2", @@ -50,7 +52,7 @@ func Test_Model_Insert(t *testing.T) { "nickname": "name_2", "create_time": gtime.Now().String(), }).Insert() - t.Assert(err, nil) + t.AssertNil(err) n, _ = result.RowsAffected() t.Assert(n, 1) @@ -63,21 +65,21 @@ func Test_Model_Insert(t *testing.T) { CreateTime *gtime.Time `json:"create_time"` } // Model inserting. - result, err = db.Table(table).Filter().Data(User{ + result, err = db.Model(table).Data(User{ Id: 3, Uid: 3, Passport: "t3", Password: "25d55ad283aa400af464c76d713c07ad", Nickname: "name_3", }).Insert() - t.Assert(err, nil) + t.AssertNil(err) n, _ = result.RowsAffected() t.Assert(n, 1) - value, err := db.Table(table).Fields("passport").Where("id=3").Value() - t.Assert(err, nil) + value, err := db.Model(table).Fields("passport").Where("id=3").Value() + t.AssertNil(err) t.Assert(value.String(), "t3") - result, err = db.Table(table).Filter().Data(&User{ + result, err = db.Model(table).Data(&User{ Id: 4, Uid: 4, Passport: "t4", @@ -85,74 +87,20 @@ func Test_Model_Insert(t *testing.T) { Nickname: "T4", CreateTime: gtime.Now(), }).Insert() - t.Assert(err, nil) + t.AssertNil(err) n, _ = result.RowsAffected() t.Assert(n, 1) - value, err = db.Table(table).Fields("passport").Where("id=4").Value() - t.Assert(err, nil) + value, err = db.Model(table).Fields("passport").Where("id=4").Value() + t.AssertNil(err) t.Assert(value.String(), "t4") - result, err = db.Table(table).Where("id>?", 1).Delete() - t.Assert(err, nil) + result, err = db.Model(table).Where("id>?", 1).Delete() + t.AssertNil(err) n, _ = result.RowsAffected() t.Assert(n, 3) }) } -// Using filter dose not affect the outside value inside function. -func Test_Model_Insert_Filter(t *testing.T) { - // map - gtest.C(t, func(t *gtest.T) { - table := createTable() - defer dropTable(table) - data := g.Map{ - "id": 1, - "uid": 1, - "passport": "t1", - "password": "25d55ad283aa400af464c76d713c07ad", - "nickname": "name_1", - "create_time": gtime.Now().String(), - } - result, err := db.Table(table).Filter().Data(data).Insert() - t.Assert(err, nil) - n, _ := result.LastInsertId() - t.Assert(n, 1) - - t.Assert(data["uid"], 1) - }) - // slice - gtest.C(t, func(t *gtest.T) { - table := createTable() - defer dropTable(table) - data := g.List{ - g.Map{ - "id": 1, - "uid": 1, - "passport": "t1", - "password": "25d55ad283aa400af464c76d713c07ad", - "nickname": "name_1", - "create_time": gtime.Now().String(), - }, - g.Map{ - "id": 2, - "uid": 2, - "passport": "t1", - "password": "25d55ad283aa400af464c76d713c07ad", - "nickname": "name_1", - "create_time": gtime.Now().String(), - }, - } - - result, err := db.Table(table).Filter().Data(data).Insert() - t.Assert(err, nil) - n, _ := result.LastInsertId() - t.Assert(n, 2) - - t.Assert(data[0]["uid"], 1) - t.Assert(data[1]["uid"], 2) - }) -} - // Fix issue: https://github.com/gogf/gf/issues/819 func Test_Model_Insert_WithStructAndSliceAttribute(t *testing.T) { table := createTable() @@ -169,11 +117,11 @@ func Test_Model_Insert_WithStructAndSliceAttribute(t *testing.T) { "nickname": []string{"A", "B", "C"}, "create_time": gtime.Now().String(), } - _, err := db.Table(table).Data(data).Insert() - t.Assert(err, nil) + _, err := db.Model(table).Data(data).Insert() + t.AssertNil(err) - one, err := db.Table(table).One("id", 1) - t.Assert(err, nil) + one, err := db.Model(table).One("id", 1) + t.AssertNil(err) t.Assert(one["passport"], data["passport"]) t.Assert(one["create_time"], data["create_time"]) t.Assert(one["nickname"], gparser.MustToJson(data["nickname"])) @@ -200,10 +148,10 @@ func Test_Model_Insert_KeyFieldNameMapping(t *testing.T) { CreateTime: "2020-10-10 12:00:01", } _, err := db.Model(table).Data(data).Insert() - t.Assert(err, nil) + t.AssertNil(err) one, err := db.Model(table).FindOne(1) - t.Assert(err, nil) + t.AssertNil(err) t.Assert(one["passport"], data.Passport) t.Assert(one["create_time"], data.CreateTime) t.Assert(one["nickname"], data.Nickname) @@ -230,41 +178,16 @@ func Test_Model_Update_KeyFieldNameMapping(t *testing.T) { CreateTime: "2020-10-10 12:00:01", } _, err := db.Model(table).Data(data).WherePri(1).Update() - t.Assert(err, nil) + t.AssertNil(err) one, err := db.Model(table).FindOne(1) - t.Assert(err, nil) + t.AssertNil(err) 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) @@ -276,11 +199,11 @@ func Test_Model_Insert_Time(t *testing.T) { "nickname": "n1", "create_time": "2020-10-10 20:09:18.334", } - _, err := db.Table(table).Data(data).Insert() - t.Assert(err, nil) + _, err := db.Model(table).Data(data).Insert() + t.AssertNil(err) - one, err := db.Table(table).One("id", 1) - t.Assert(err, nil) + one, err := db.Model(table).One("id", 1) + t.AssertNil(err) t.Assert(one["passport"], data["passport"]) t.Assert(one["create_time"], "2020-10-10 20:09:18") t.Assert(one["nickname"], data["nickname"]) @@ -291,9 +214,9 @@ func Test_Model_BatchInsertWithArrayStruct(t *testing.T) { table := createTable() defer dropTable(table) gtest.C(t, func(t *gtest.T) { - user := db.Table(table) + user := db.Model(table) array := garray.New() - for i := 1; i <= SIZE; i++ { + for i := 1; i <= TableSize; i++ { array.Append(g.Map{ "id": i, "uid": i, @@ -304,10 +227,10 @@ func Test_Model_BatchInsertWithArrayStruct(t *testing.T) { }) } - result, err := user.Filter().Data(array).Insert() - t.Assert(err, nil) + result, err := user.Data(array).Insert() + t.AssertNil(err) n, _ := result.LastInsertId() - t.Assert(n, SIZE) + t.Assert(n, TableSize) }) } @@ -315,7 +238,7 @@ func Test_Model_InsertIgnore(t *testing.T) { table := createInitTable() defer dropTable(table) gtest.C(t, func(t *gtest.T) { - _, err := db.Table(table).Filter().Data(g.Map{ + _, err := db.Model(table).Data(g.Map{ "id": 1, "uid": 1, "passport": "t1", @@ -326,7 +249,7 @@ func Test_Model_InsertIgnore(t *testing.T) { t.AssertNE(err, nil) }) gtest.C(t, func(t *gtest.T) { - _, err := db.Table(table).Filter().Data(g.Map{ + _, err := db.Model(table).Data(g.Map{ "id": 1, "uid": 1, "passport": "t1", @@ -334,7 +257,7 @@ func Test_Model_InsertIgnore(t *testing.T) { "nickname": "name_1", "create_time": gtime.Now().String(), }).InsertIgnore() - t.Assert(err, nil) + t.AssertNil(err) }) } @@ -343,7 +266,7 @@ func Test_Model_Batch(t *testing.T) { gtest.C(t, func(t *gtest.T) { table := createTable() defer dropTable(table) - result, err := db.Table(table).Filter().Data(g.List{ + result, err := db.Model(table).Data(g.List{ { "id": 2, "uid": 2, @@ -372,7 +295,7 @@ func Test_Model_Batch(t *testing.T) { gtest.C(t, func(t *gtest.T) { table := createTable() defer dropTable(table) - result, err := db.Table(table).Data(g.List{ + result, err := db.Model(table).Data(g.List{ {"passport": "t1"}, {"passport": "t2"}, {"passport": "t3"}, @@ -390,34 +313,34 @@ func Test_Model_Batch(t *testing.T) { gtest.C(t, func(t *gtest.T) { table := createInitTable() defer dropTable(table) - result, err := db.Table(table).All() - t.Assert(err, nil) - t.Assert(len(result), SIZE) + result, err := db.Model(table).All() + t.AssertNil(err) + t.Assert(len(result), TableSize) for _, v := range result { v["nickname"].Set(v["nickname"].String() + v["id"].String()) } - r, e := db.Table(table).Data(result).Save() + r, e := db.Model(table).Data(result).Save() t.Assert(e, nil) n, e := r.RowsAffected() t.Assert(e, nil) - t.Assert(n, SIZE*2) + t.Assert(n, TableSize*2) }) // batch replace gtest.C(t, func(t *gtest.T) { table := createInitTable() defer dropTable(table) - result, err := db.Table(table).All() - t.Assert(err, nil) - t.Assert(len(result), SIZE) + result, err := db.Model(table).All() + t.AssertNil(err) + t.Assert(len(result), TableSize) for _, v := range result { v["nickname"].Set(v["nickname"].String() + v["id"].String()) } - r, e := db.Table(table).Data(result).Replace() + r, e := db.Model(table).Data(result).Replace() t.Assert(e, nil) n, e := r.RowsAffected() t.Assert(e, nil) - t.Assert(n, SIZE*2) + t.Assert(n, TableSize*2) }) } @@ -426,14 +349,14 @@ func Test_Model_Replace(t *testing.T) { defer dropTable(table) gtest.C(t, func(t *gtest.T) { - result, err := db.Table(table).Data(g.Map{ + result, err := db.Model(table).Data(g.Map{ "id": 1, "passport": "t11", "password": "25d55ad283aa400af464c76d713c07ad", "nickname": "T11", "create_time": "2018-10-24 10:00:00", }).Replace() - t.Assert(err, nil) + t.AssertNil(err) n, _ := result.RowsAffected() t.Assert(n, 1) }) @@ -443,14 +366,14 @@ func Test_Model_Save(t *testing.T) { table := createTable() defer dropTable(table) gtest.C(t, func(t *gtest.T) { - result, err := db.Table(table).Data(g.Map{ + result, err := db.Model(table).Data(g.Map{ "id": 1, "passport": "t111", "password": "25d55ad283aa400af464c76d713c07ad", "nickname": "T111", "create_time": "2018-10-24 10:00:00", }).Save() - t.Assert(err, nil) + t.AssertNil(err) n, _ := result.RowsAffected() t.Assert(n, 1) }) @@ -461,38 +384,48 @@ func Test_Model_Update(t *testing.T) { defer dropTable(table) // UPDATE...LIMIT gtest.C(t, func(t *gtest.T) { - result, err := db.Table(table).Data("nickname", "T100").Where(1).Order("id desc").Limit(2).Update() - t.Assert(err, nil) + result, err := db.Model(table).Data("nickname", "T100").Where(1).Order("id desc").Limit(2).Update() + t.AssertNil(err) n, _ := result.RowsAffected() t.Assert(n, 2) - v1, err := db.Table(table).Fields("nickname").Where("id", 10).Value() - t.Assert(err, nil) + v1, err := db.Model(table).Fields("nickname").Where("id", 10).Value() + t.AssertNil(err) t.Assert(v1.String(), "T100") - v2, err := db.Table(table).Fields("nickname").Where("id", 8).Value() - t.Assert(err, nil) + v2, err := db.Model(table).Fields("nickname").Where("id", 8).Value() + t.AssertNil(err) t.Assert(v2.String(), "name_8") }) gtest.C(t, func(t *gtest.T) { - result, err := db.Table(table).Data("passport", "user_22").Where("passport=?", "user_2").Update() - t.Assert(err, nil) + result, err := db.Model(table).Data("passport", "user_22").Where("passport=?", "user_2").Update() + t.AssertNil(err) n, _ := result.RowsAffected() t.Assert(n, 1) }) gtest.C(t, func(t *gtest.T) { - result, err := db.Table(table).Data("passport", "user_2").Where("passport='user_22'").Update() - t.Assert(err, nil) + result, err := db.Model(table).Data("passport", "user_2").Where("passport='user_22'").Update() + t.AssertNil(err) n, _ := result.RowsAffected() t.Assert(n, 1) }) // Update + Data(string) gtest.C(t, func(t *gtest.T) { - result, err := db.Table(table).Data("passport='user_33'").Where("passport='user_3'").Update() - t.Assert(err, nil) + result, err := db.Model(table).Data("passport='user_33'").Where("passport='user_3'").Update() + t.AssertNil(err) + n, _ := result.RowsAffected() + t.Assert(n, 1) + }) + // Update + Fields(string) + gtest.C(t, func(t *gtest.T) { + result, err := db.Model(table).Fields("passport").Data(g.Map{ + "passport": "user_44", + "none": "none", + }).Where("passport='user_4'").Update() + t.AssertNil(err) n, _ := result.RowsAffected() t.Assert(n, 1) }) @@ -503,15 +436,15 @@ func Test_Model_Clone(t *testing.T) { defer dropTable(table) gtest.C(t, func(t *gtest.T) { - md := db.Table(table).Where("id IN(?)", g.Slice{1, 3}) + md := db.Model(table).Where("id IN(?)", g.Slice{1, 3}) count, err := md.Count() - t.Assert(err, nil) + t.AssertNil(err) record, err := md.Order("id DESC").One() - t.Assert(err, nil) + t.AssertNil(err) result, err := md.Order("id ASC").All() - t.Assert(err, nil) + t.AssertNil(err) t.Assert(count, 2) t.Assert(record["id"].Int(), 3) @@ -526,52 +459,52 @@ func Test_Model_Safe(t *testing.T) { defer dropTable(table) gtest.C(t, func(t *gtest.T) { - md := db.Table(table).Safe(false).Where("id IN(?)", g.Slice{1, 3}) + md := db.Model(table).Safe(false).Where("id IN(?)", g.Slice{1, 3}) count, err := md.Count() - t.Assert(err, nil) + t.AssertNil(err) t.Assert(count, 2) md.And("id = ?", 1) count, err = md.Count() - t.Assert(err, nil) + t.AssertNil(err) t.Assert(count, 1) }) gtest.C(t, func(t *gtest.T) { - md := db.Table(table).Safe(true).Where("id IN(?)", g.Slice{1, 3}) + md := db.Model(table).Safe(true).Where("id IN(?)", g.Slice{1, 3}) count, err := md.Count() - t.Assert(err, nil) + t.AssertNil(err) t.Assert(count, 2) md.And("id = ?", 1) count, err = md.Count() - t.Assert(err, nil) + t.AssertNil(err) t.Assert(count, 2) }) gtest.C(t, func(t *gtest.T) { - md := db.Table(table).Safe().Where("id IN(?)", g.Slice{1, 3}) + md := db.Model(table).Safe().Where("id IN(?)", g.Slice{1, 3}) count, err := md.Count() - t.Assert(err, nil) + t.AssertNil(err) t.Assert(count, 2) md.And("id = ?", 1) count, err = md.Count() - t.Assert(err, nil) + t.AssertNil(err) t.Assert(count, 2) }) gtest.C(t, func(t *gtest.T) { - md1 := db.Table(table).Safe() + md1 := db.Model(table).Safe() md2 := md1.Where("id in (?)", g.Slice{1, 3}) count, err := md2.Count() - t.Assert(err, nil) + t.AssertNil(err) t.Assert(count, 2) all, err := md2.All() - t.Assert(err, nil) + t.AssertNil(err) t.Assert(len(all), 2) all, err = md2.Page(1, 10).All() - t.Assert(err, nil) + t.AssertNil(err) t.Assert(len(all), 2) }) @@ -579,39 +512,39 @@ func Test_Model_Safe(t *testing.T) { table := createInitTable() defer dropTable(table) - md1 := db.Table(table).Where("id>", 0).Safe() + md1 := db.Model(table).Where("id>", 0).Safe() md2 := md1.Where("id in (?)", g.Slice{1, 3}) md3 := md1.Where("id in (?)", g.Slice{4, 5, 6}) // 1,3 count, err := md2.Count() - t.Assert(err, nil) + t.AssertNil(err) t.Assert(count, 2) all, err := md2.Order("id asc").All() - t.Assert(err, nil) + t.AssertNil(err) t.Assert(len(all), 2) t.Assert(all[0]["id"].Int(), 1) t.Assert(all[1]["id"].Int(), 3) all, err = md2.Page(1, 10).All() - t.Assert(err, nil) + t.AssertNil(err) t.Assert(len(all), 2) // 4,5,6 count, err = md3.Count() - t.Assert(err, nil) + t.AssertNil(err) t.Assert(count, 3) all, err = md3.Order("id asc").All() - t.Assert(err, nil) + t.AssertNil(err) t.Assert(len(all), 3) t.Assert(all[0]["id"].Int(), 4) t.Assert(all[1]["id"].Int(), 5) t.Assert(all[2]["id"].Int(), 6) all, err = md3.Page(1, 10).All() - t.Assert(err, nil) + t.AssertNil(err) t.Assert(len(all), 3) }) } @@ -621,14 +554,73 @@ func Test_Model_All(t *testing.T) { defer dropTable(table) gtest.C(t, func(t *gtest.T) { - result, err := db.Table(table).All() - t.Assert(err, nil) - t.Assert(len(result), SIZE) + result, err := db.Model(table).All() + t.AssertNil(err) + t.Assert(len(result), TableSize) }) gtest.C(t, func(t *gtest.T) { - result, err := db.Table(table).Where("id<0").All() + result, err := db.Model(table).Where("id<0").All() t.Assert(result, nil) - t.Assert(err, nil) + t.AssertNil(err) + }) +} + +func Test_Model_Fields(t *testing.T) { + tableName1 := createInitTable() + defer dropTable(tableName1) + + tableName2 := "user_" + gtime.Now().TimestampNanoStr() + if _, err := db.Exec(fmt.Sprintf(` + CREATE TABLE %s ( + id int(10) unsigned NOT NULL AUTO_INCREMENT, + name varchar(45) NULL, + age int(10) unsigned, + PRIMARY KEY (id) + ) ENGINE=InnoDB DEFAULT CHARSET=utf8; + `, tableName2, + )); err != nil { + gtest.AssertNil(err) + } + defer dropTable(tableName2) + + r, err := db.Insert(tableName2, g.Map{ + "id": 1, + "name": "table2_1", + "age": 18, + }) + gtest.AssertNil(err) + n, _ := r.RowsAffected() + gtest.Assert(n, 1) + + gtest.C(t, func(t *gtest.T) { + all, err := db.Model(tableName1).As("u").Fields("u.passport,u.id").Where("u.id<2").All() + t.AssertNil(err) + t.Assert(len(all), 1) + t.Assert(len(all[0]), 2) + }) + gtest.C(t, func(t *gtest.T) { + all, err := db.Model(tableName1).As("u1"). + LeftJoin(tableName1, "u2", "u2.id=u1.id"). + Fields("u1.passport,u1.id,u2.id AS u2id"). + Where("u1.id<2"). + All() + t.AssertNil(err) + t.Assert(len(all), 1) + t.Assert(len(all[0]), 3) + }) + gtest.C(t, func(t *gtest.T) { + all, err := db.Model(tableName1).As("u1"). + LeftJoin(tableName2, "u2", "u2.id=u1.id"). + Fields("u1.passport,u1.id,u2.name,u2.age"). + Where("u1.id<2"). + All() + t.AssertNil(err) + t.Assert(len(all), 1) + t.Assert(len(all[0]), 4) + t.Assert(all[0]["id"], 1) + t.Assert(all[0]["age"], 18) + t.Assert(all[0]["name"], "table2_1") + t.Assert(all[0]["passport"], "user_1") }) } @@ -637,36 +629,36 @@ func Test_Model_FindAll(t *testing.T) { defer dropTable(table) gtest.C(t, func(t *gtest.T) { - result, err := db.Table(table).FindAll(5) - t.Assert(err, nil) + result, err := db.Model(table).FindAll(5) + t.AssertNil(err) t.Assert(len(result), 1) t.Assert(result[0]["id"].Int(), 5) }) gtest.C(t, func(t *gtest.T) { - result, err := db.Table(table).Order("id asc").FindAll("id", 8) - t.Assert(err, nil) + result, err := db.Model(table).Order("id asc").FindAll("id", 8) + t.AssertNil(err) t.Assert(len(result), 1) t.Assert(result[0]["id"].Int(), 8) }) gtest.C(t, func(t *gtest.T) { - result, err := db.Table(table).Order("id asc").FindAll(g.Slice{3, 9}) - t.Assert(err, nil) + result, err := db.Model(table).Order("id asc").FindAll(g.Slice{3, 9}) + t.AssertNil(err) t.Assert(len(result), 2) t.Assert(result[0]["id"].Int(), 3) t.Assert(result[1]["id"].Int(), 9) }) gtest.C(t, func(t *gtest.T) { - result, err := db.Table(table).FindAll() - t.Assert(err, nil) - t.Assert(len(result), SIZE) + result, err := db.Model(table).FindAll() + t.AssertNil(err) + t.Assert(len(result), TableSize) }) gtest.C(t, func(t *gtest.T) { - result, err := db.Table(table).Where("id<0").FindAll() + result, err := db.Model(table).Where("id<0").FindAll() t.Assert(result, nil) - t.Assert(err, nil) + t.AssertNil(err) }) } @@ -675,28 +667,28 @@ func Test_Model_FindAll_GTime(t *testing.T) { defer dropTable(table) gtest.C(t, func(t *gtest.T) { - result, err := db.Table(table).FindAll("create_time < ?", gtime.NewFromStr("2000-01-01 00:00:00")) - t.Assert(err, nil) + result, err := db.Model(table).FindAll("create_time < ?", gtime.NewFromStr("2000-01-01 00:00:00")) + t.AssertNil(err) t.Assert(len(result), 0) }) gtest.C(t, func(t *gtest.T) { - result, err := db.Table(table).FindAll("create_time > ?", gtime.NewFromStr("2000-01-01 00:00:00")) - t.Assert(err, nil) - t.Assert(len(result), SIZE) + result, err := db.Model(table).FindAll("create_time > ?", gtime.NewFromStr("2000-01-01 00:00:00")) + t.AssertNil(err) + t.Assert(len(result), TableSize) }) gtest.C(t, func(t *gtest.T) { v := g.NewVar("2000-01-01 00:00:00") - result, err := db.Table(table).FindAll("create_time < ?", v) - t.Assert(err, nil) + result, err := db.Model(table).FindAll("create_time < ?", v) + t.AssertNil(err) t.Assert(len(result), 0) }) gtest.C(t, func(t *gtest.T) { v := g.NewVar("2000-01-01 00:00:00") - result, err := db.Table(table).FindAll("create_time > ?", v) - t.Assert(err, nil) - t.Assert(len(result), SIZE) + result, err := db.Model(table).FindAll("create_time > ?", v) + t.AssertNil(err) + t.Assert(len(result), TableSize) }) } @@ -704,14 +696,14 @@ func Test_Model_One(t *testing.T) { table := createInitTable() defer dropTable(table) gtest.C(t, func(t *gtest.T) { - record, err := db.Table(table).Where("id", 1).One() - t.Assert(err, nil) + record, err := db.Model(table).Where("id", 1).One() + t.AssertNil(err) t.Assert(record["nickname"].String(), "name_1") }) gtest.C(t, func(t *gtest.T) { - record, err := db.Table(table).Where("id", 0).One() - t.Assert(err, nil) + record, err := db.Model(table).Where("id", 0).One() + t.AssertNil(err) t.Assert(record, nil) }) } @@ -721,32 +713,32 @@ func Test_Model_FindOne(t *testing.T) { defer dropTable(table) gtest.C(t, func(t *gtest.T) { - record, err := db.Table(table).FindOne(1) - t.Assert(err, nil) + record, err := db.Model(table).FindOne(1) + t.AssertNil(err) t.Assert(record["nickname"].String(), "name_1") }) gtest.C(t, func(t *gtest.T) { - record, err := db.Table(table).FindOne(3) - t.Assert(err, nil) + record, err := db.Model(table).FindOne(3) + t.AssertNil(err) t.Assert(record["nickname"].String(), "name_3") }) gtest.C(t, func(t *gtest.T) { - record, err := db.Table(table).Where("id", 1).FindOne() - t.Assert(err, nil) + record, err := db.Model(table).Where("id", 1).FindOne() + t.AssertNil(err) t.Assert(record["nickname"].String(), "name_1") }) gtest.C(t, func(t *gtest.T) { - record, err := db.Table(table).FindOne("id", 9) - t.Assert(err, nil) + record, err := db.Model(table).FindOne("id", 9) + t.AssertNil(err) t.Assert(record["nickname"].String(), "name_9") }) gtest.C(t, func(t *gtest.T) { - record, err := db.Table(table).Where("id", 0).FindOne() - t.Assert(err, nil) + record, err := db.Model(table).Where("id", 0).FindOne() + t.AssertNil(err) t.Assert(record, nil) }) } @@ -756,14 +748,14 @@ func Test_Model_Value(t *testing.T) { defer dropTable(table) gtest.C(t, func(t *gtest.T) { - value, err := db.Table(table).Fields("nickname").Where("id", 1).Value() - t.Assert(err, nil) + value, err := db.Model(table).Fields("nickname").Where("id", 1).Value() + t.AssertNil(err) t.Assert(value.String(), "name_1") }) gtest.C(t, func(t *gtest.T) { - value, err := db.Table(table).Fields("nickname").Where("id", 0).Value() - t.Assert(err, nil) + value, err := db.Model(table).Fields("nickname").Where("id", 0).Value() + t.AssertNil(err) t.Assert(value, nil) }) } @@ -773,29 +765,29 @@ func Test_Model_Array(t *testing.T) { defer dropTable(table) gtest.C(t, func(t *gtest.T) { - all, err := db.Table(table).Where("id", g.Slice{1, 2, 3}).All() - t.Assert(err, nil) + all, err := db.Model(table).Where("id", g.Slice{1, 2, 3}).All() + t.AssertNil(err) t.Assert(all.Array("id"), g.Slice{1, 2, 3}) t.Assert(all.Array("nickname"), g.Slice{"name_1", "name_2", "name_3"}) }) gtest.C(t, func(t *gtest.T) { - array, err := db.Table(table).Fields("nickname").Where("id", g.Slice{1, 2, 3}).Array() - t.Assert(err, nil) + array, err := db.Model(table).Fields("nickname").Where("id", g.Slice{1, 2, 3}).Array() + t.AssertNil(err) t.Assert(array, g.Slice{"name_1", "name_2", "name_3"}) }) gtest.C(t, func(t *gtest.T) { - array, err := db.Table(table).Array("nickname", "id", g.Slice{1, 2, 3}) - t.Assert(err, nil) + array, err := db.Model(table).Array("nickname", "id", g.Slice{1, 2, 3}) + t.AssertNil(err) t.Assert(array, g.Slice{"name_1", "name_2", "name_3"}) }) gtest.C(t, func(t *gtest.T) { - array, err := db.Table(table).FindArray("nickname", "id", g.Slice{1, 2, 3}) - t.Assert(err, nil) + array, err := db.Model(table).FindArray("nickname", "id", g.Slice{1, 2, 3}) + t.AssertNil(err) t.Assert(array, g.Slice{"name_1", "name_2", "name_3"}) }) gtest.C(t, func(t *gtest.T) { - array, err := db.Table(table).FindArray("nickname", g.Slice{1, 2, 3}) - t.Assert(err, nil) + array, err := db.Model(table).FindArray("nickname", g.Slice{1, 2, 3}) + t.AssertNil(err) t.Assert(array, g.Slice{"name_1", "name_2", "name_3"}) }) } @@ -805,26 +797,26 @@ func Test_Model_FindValue(t *testing.T) { defer dropTable(table) gtest.C(t, func(t *gtest.T) { - value, err := db.Table(table).FindValue("nickname", 1) - t.Assert(err, nil) + value, err := db.Model(table).FindValue("nickname", 1) + t.AssertNil(err) t.Assert(value.String(), "name_1") }) gtest.C(t, func(t *gtest.T) { - value, err := db.Table(table).Order("id desc").FindValue("nickname") - t.Assert(err, nil) + value, err := db.Model(table).Order("id desc").FindValue("nickname") + t.AssertNil(err) t.Assert(value.String(), "name_10") }) gtest.C(t, func(t *gtest.T) { - value, err := db.Table(table).Fields("nickname").Where("id", 1).FindValue() - t.Assert(err, nil) + value, err := db.Model(table).Fields("nickname").Where("id", 1).FindValue() + t.AssertNil(err) t.Assert(value.String(), "name_1") }) gtest.C(t, func(t *gtest.T) { - value, err := db.Table(table).Fields("nickname").Where("id", 0).FindValue() - t.Assert(err, nil) + value, err := db.Model(table).Fields("nickname").Where("id", 0).FindValue() + t.AssertNil(err) t.Assert(value, nil) }) } @@ -833,34 +825,34 @@ func Test_Model_Count(t *testing.T) { table := createInitTable() defer dropTable(table) gtest.C(t, func(t *gtest.T) { - count, err := db.Table(table).Count() - t.Assert(err, nil) - t.Assert(count, SIZE) + count, err := db.Model(table).Count() + t.AssertNil(err) + t.Assert(count, TableSize) }) gtest.C(t, func(t *gtest.T) { - count, err := db.Table(table).FieldsEx("id").Where("id>8").Count() - t.Assert(err, nil) + count, err := db.Model(table).FieldsEx("id").Where("id>8").Count() + t.AssertNil(err) t.Assert(count, 2) }) gtest.C(t, func(t *gtest.T) { - count, err := db.Table(table).Fields("distinct id,nickname").Where("id>8").Count() - t.Assert(err, nil) + count, err := db.Model(table).Fields("distinct id,nickname").Where("id>8").Count() + t.AssertNil(err) t.Assert(count, 2) }) // COUNT...LIMIT... gtest.C(t, func(t *gtest.T) { - count, err := db.Table(table).Page(1, 2).Count() - t.Assert(err, nil) - t.Assert(count, SIZE) + count, err := db.Model(table).Page(1, 2).Count() + t.AssertNil(err) + t.Assert(count, TableSize) }) //gtest.C(t, func(t *gtest.T) { - // count, err := db.Table(table).Fields("id myid").Where("id>8").Count() - // t.Assert(err, nil) + // count, err := db.Model(table).Fields("id myid").Where("id>8").Count() + // t.AssertNil(err) // t.Assert(count, 2) //}) //gtest.C(t, func(t *gtest.T) { - // count, err := db.Table(table).As("u1").LeftJoin(table, "u2", "u2.id=u1.id").Fields("u2.id u2id").Where("u1.id>8").Count() - // t.Assert(err, nil) + // count, err := db.Model(table).As("u1").LeftJoin(table, "u2", "u2.id=u1.id").Fields("u2.id u2id").Where("u1.id>8").Count() + // t.AssertNil(err) // t.Assert(count, 2) //}) } @@ -869,19 +861,19 @@ func Test_Model_FindCount(t *testing.T) { table := createInitTable() defer dropTable(table) gtest.C(t, func(t *gtest.T) { - count, err := db.Table(table).FindCount(g.Slice{1, 3}) - t.Assert(err, nil) + count, err := db.Model(table).FindCount(g.Slice{1, 3}) + t.AssertNil(err) t.Assert(count, 2) }) gtest.C(t, func(t *gtest.T) { - count, err := db.Table(table).FindCount(g.Slice{1, 300000}) - t.Assert(err, nil) + count, err := db.Model(table).FindCount(g.Slice{1, 300000}) + t.AssertNil(err) t.Assert(count, 1) }) gtest.C(t, func(t *gtest.T) { - count, err := db.Table(table).FindCount() - t.Assert(err, nil) - t.Assert(count, SIZE) + count, err := db.Model(table).FindCount() + t.AssertNil(err) + t.Assert(count, TableSize) }) } @@ -889,9 +881,9 @@ func Test_Model_Select(t *testing.T) { table := createInitTable() defer dropTable(table) gtest.C(t, func(t *gtest.T) { - result, err := db.Table(table).Select() - t.Assert(err, nil) - t.Assert(len(result), SIZE) + result, err := db.Model(table).Select() + t.AssertNil(err) + t.Assert(len(result), TableSize) }) } @@ -907,8 +899,8 @@ func Test_Model_Struct(t *testing.T) { CreateTime gtime.Time } user := new(User) - err := db.Table(table).Where("id=1").Struct(user) - t.Assert(err, nil) + err := db.Model(table).Where("id=1").Scan(user) + t.AssertNil(err) t.Assert(user.NickName, "name_1") t.Assert(user.CreateTime.String(), "2018-10-24 10:00:00") }) @@ -921,8 +913,8 @@ func Test_Model_Struct(t *testing.T) { CreateTime *gtime.Time } user := new(User) - err := db.Table(table).Where("id=1").Struct(user) - t.Assert(err, nil) + err := db.Model(table).Where("id=1").Scan(user) + t.AssertNil(err) t.Assert(user.NickName, "name_1") t.Assert(user.CreateTime.String(), "2018-10-24 10:00:00") }) @@ -936,8 +928,8 @@ func Test_Model_Struct(t *testing.T) { CreateTime *gtime.Time } user := (*User)(nil) - err := db.Table(table).Where("id=1").Struct(&user) - t.Assert(err, nil) + err := db.Model(table).Where("id=1").Scan(&user) + t.AssertNil(err) t.Assert(user.NickName, "name_1") t.Assert(user.CreateTime.String(), "2018-10-24 10:00:00") }) @@ -951,7 +943,7 @@ func Test_Model_Struct(t *testing.T) { CreateTime *gtime.Time } user := (*User)(nil) - err := db.Table(table).Where("id=1").Scan(&user) + err := db.Model(table).Where("id=1").Scan(&user) if err != nil { gtest.Error(err) } @@ -968,9 +960,21 @@ func Test_Model_Struct(t *testing.T) { CreateTime *gtime.Time } user := new(User) - err := db.Table(table).Where("id=-1").Struct(user) + err := db.Model(table).Where("id=-1").Scan(user) t.Assert(err, sql.ErrNoRows) }) + gtest.C(t, func(t *gtest.T) { + type User struct { + Id int + Passport string + Password string + NickName string + CreateTime *gtime.Time + } + var user *User + err := db.Model(table).Where("id=-1").Scan(&user) + t.AssertNil(err) + }) } func Test_Model_Struct_CustomType(t *testing.T) { @@ -988,8 +992,8 @@ func Test_Model_Struct_CustomType(t *testing.T) { CreateTime gtime.Time } user := new(User) - err := db.Table(table).Where("id=1").Struct(user) - t.Assert(err, nil) + err := db.Model(table).Where("id=1").Scan(user) + t.AssertNil(err) t.Assert(user.NickName, "name_1") t.Assert(user.CreateTime.String(), "2018-10-24 10:00:00") }) @@ -1008,11 +1012,11 @@ func Test_Model_Structs(t *testing.T) { CreateTime gtime.Time } var users []User - err := db.Table(table).Order("id asc").Structs(&users) + err := db.Model(table).Order("id asc").Scan(&users) if err != nil { gtest.Error(err) } - t.Assert(len(users), SIZE) + t.Assert(len(users), TableSize) t.Assert(users[0].Id, 1) t.Assert(users[1].Id, 2) t.Assert(users[2].Id, 3) @@ -1031,11 +1035,11 @@ func Test_Model_Structs(t *testing.T) { CreateTime *gtime.Time } var users []*User - err := db.Table(table).Order("id asc").Structs(&users) + err := db.Model(table).Order("id asc").Scan(&users) if err != nil { gtest.Error(err) } - t.Assert(len(users), SIZE) + t.Assert(len(users), TableSize) t.Assert(users[0].Id, 1) t.Assert(users[1].Id, 2) t.Assert(users[2].Id, 3) @@ -1054,11 +1058,11 @@ func Test_Model_Structs(t *testing.T) { CreateTime *gtime.Time } var users []*User - err := db.Table(table).Order("id asc").Scan(&users) + err := db.Model(table).Order("id asc").Scan(&users) if err != nil { gtest.Error(err) } - t.Assert(len(users), SIZE) + t.Assert(len(users), TableSize) t.Assert(users[0].Id, 1) t.Assert(users[1].Id, 2) t.Assert(users[2].Id, 3) @@ -1077,38 +1081,40 @@ func Test_Model_Structs(t *testing.T) { CreateTime *gtime.Time } var users []*User - err := db.Table(table).Where("id<0").Structs(&users) - t.Assert(err, nil) + err := db.Model(table).Where("id<0").Scan(&users) + t.AssertNil(err) }) } -func Test_Model_StructsWithJsonTag(t *testing.T) { - table := createInitTable() - defer dropTable(table) - - gtest.C(t, func(t *gtest.T) { - type User struct { - Uid int `json:"id"` - Passport string - Password string - Name string `json:"nick_name"` - Time gtime.Time `json:"create_time"` - } - var users []User - err := db.Table(table).Order("id asc").Structs(&users) - if err != nil { - gtest.Error(err) - } - t.Assert(len(users), SIZE) - t.Assert(users[0].Uid, 1) - t.Assert(users[1].Uid, 2) - t.Assert(users[2].Uid, 3) - t.Assert(users[0].Name, "name_1") - t.Assert(users[1].Name, "name_2") - t.Assert(users[2].Name, "name_3") - t.Assert(users[0].Time.String(), "2018-10-24 10:00:00") - }) -} +// JSON tag is only used for JSON Marshal/Unmarshal, DO NOT use it in multiple purposes! +//func Test_Model_StructsWithJsonTag(t *testing.T) { +// table := createInitTable() +// defer dropTable(table) +// +// db.SetDebug(true) +// gtest.C(t, func(t *gtest.T) { +// type User struct { +// Uid int `json:"id"` +// Passport string +// Password string +// Name string `json:"nick_name"` +// Time gtime.Time `json:"create_time"` +// } +// var users []User +// err := db.Model(table).Order("id asc").Scan(&users) +// if err != nil { +// gtest.Error(err) +// } +// t.Assert(len(users), TableSize) +// t.Assert(users[0].Uid, 1) +// t.Assert(users[1].Uid, 2) +// t.Assert(users[2].Uid, 3) +// t.Assert(users[0].Name, "name_1") +// t.Assert(users[1].Name, "name_2") +// t.Assert(users[2].Name, "name_3") +// t.Assert(users[0].Time.String(), "2018-10-24 10:00:00") +// }) +//} func Test_Model_Scan(t *testing.T) { table := createInitTable() @@ -1123,8 +1129,8 @@ func Test_Model_Scan(t *testing.T) { CreateTime gtime.Time } user := new(User) - err := db.Table(table).Where("id=1").Scan(user) - t.Assert(err, nil) + err := db.Model(table).Where("id=1").Scan(user) + t.AssertNil(err) t.Assert(user.NickName, "name_1") t.Assert(user.CreateTime.String(), "2018-10-24 10:00:00") }) @@ -1137,8 +1143,8 @@ func Test_Model_Scan(t *testing.T) { CreateTime *gtime.Time } user := new(User) - err := db.Table(table).Where("id=1").Scan(user) - t.Assert(err, nil) + err := db.Model(table).Where("id=1").Scan(user) + t.AssertNil(err) t.Assert(user.NickName, "name_1") t.Assert(user.CreateTime.String(), "2018-10-24 10:00:00") }) @@ -1151,9 +1157,9 @@ func Test_Model_Scan(t *testing.T) { CreateTime gtime.Time } var users []User - err := db.Table(table).Order("id asc").Scan(&users) - t.Assert(err, nil) - t.Assert(len(users), SIZE) + err := db.Model(table).Order("id asc").Scan(&users) + t.AssertNil(err) + t.Assert(len(users), TableSize) t.Assert(users[0].Id, 1) t.Assert(users[1].Id, 2) t.Assert(users[2].Id, 3) @@ -1171,9 +1177,9 @@ func Test_Model_Scan(t *testing.T) { CreateTime *gtime.Time } var users []*User - err := db.Table(table).Order("id asc").Scan(&users) - t.Assert(err, nil) - t.Assert(len(users), SIZE) + err := db.Model(table).Order("id asc").Scan(&users) + t.AssertNil(err) + t.Assert(len(users), TableSize) t.Assert(users[0].Id, 1) t.Assert(users[1].Id, 2) t.Assert(users[2].Id, 3) @@ -1195,8 +1201,8 @@ func Test_Model_Scan(t *testing.T) { user = new(User) users = new([]*User) ) - err1 := db.Table(table).Where("id < 0").Scan(user) - err2 := db.Table(table).Where("id < 0").Scan(users) + err1 := db.Model(table).Where("id < 0").Scan(user) + err2 := db.Model(table).Where("id < 0").Scan(users) t.Assert(err1, sql.ErrNoRows) t.Assert(err2, nil) }) @@ -1207,10 +1213,10 @@ func Test_Model_OrderBy(t *testing.T) { defer dropTable(table) gtest.C(t, func(t *gtest.T) { - result, err := db.Table(table).Order("id DESC").Select() - t.Assert(err, nil) - t.Assert(len(result), SIZE) - t.Assert(result[0]["nickname"].String(), fmt.Sprintf("name_%d", SIZE)) + result, err := db.Model(table).Order("id DESC").Select() + t.AssertNil(err) + t.Assert(len(result), TableSize) + t.Assert(result[0]["nickname"].String(), fmt.Sprintf("name_%d", TableSize)) }) } @@ -1219,9 +1225,9 @@ func Test_Model_GroupBy(t *testing.T) { defer dropTable(table) gtest.C(t, func(t *gtest.T) { - result, err := db.Table(table).GroupBy("id").Select() - t.Assert(err, nil) - t.Assert(len(result), SIZE) + result, err := db.Model(table).GroupBy("id").Select() + t.AssertNil(err) + t.Assert(len(result), TableSize) t.Assert(result[0]["nickname"].String(), "name_1") }) } @@ -1230,8 +1236,8 @@ func Test_Model_Data(t *testing.T) { gtest.C(t, func(t *gtest.T) { table := createInitTable() defer dropTable(table) - result, err := db.Table(table).Data("nickname=?", "test").Where("id=?", 3).Update() - t.Assert(err, nil) + result, err := db.Model(table).Data("nickname=?", "test").Where("id=?", 3).Update() + t.AssertNil(err) n, _ := result.RowsAffected() t.Assert(n, 1) }) @@ -1247,8 +1253,8 @@ func Test_Model_Data(t *testing.T) { "nickname": fmt.Sprintf(`nickname_%d`, i), }) } - result, err := db.Table(table).Data(users).Batch(2).Insert() - t.Assert(err, nil) + result, err := db.Model(table).Data(users).Batch(2).Insert() + t.AssertNil(err) n, _ := result.RowsAffected() t.Assert(n, 10) }) @@ -1264,8 +1270,8 @@ func Test_Model_Data(t *testing.T) { "nickname": fmt.Sprintf(`nickname_%d`, i), }) } - result, err := db.Table(table).Data(users).Batch(2).Insert() - t.Assert(err, nil) + result, err := db.Model(table).Data(users).Batch(2).Insert() + t.AssertNil(err) n, _ := result.RowsAffected() t.Assert(n, 10) }) @@ -1277,161 +1283,161 @@ func Test_Model_Where(t *testing.T) { // string gtest.C(t, func(t *gtest.T) { - result, err := db.Table(table).Where("id=? and nickname=?", 3, "name_3").One() - t.Assert(err, nil) + result, err := db.Model(table).Where("id=? and nickname=?", 3, "name_3").One() + t.AssertNil(err) t.AssertGT(len(result), 0) t.Assert(result["id"].Int(), 3) }) // slice gtest.C(t, func(t *gtest.T) { - result, err := db.Table(table).Where(g.Slice{"id", 3}).One() - t.Assert(err, nil) + result, err := db.Model(table).Where(g.Slice{"id", 3}).One() + t.AssertNil(err) t.AssertGT(len(result), 0) t.Assert(result["id"].Int(), 3) }) gtest.C(t, func(t *gtest.T) { - result, err := db.Table(table).Where(g.Slice{"id", 3, "nickname", "name_3"}).One() - t.Assert(err, nil) + result, err := db.Model(table).Where(g.Slice{"id", 3, "nickname", "name_3"}).One() + t.AssertNil(err) t.AssertGT(len(result), 0) t.Assert(result["id"].Int(), 3) }) // slice parameter gtest.C(t, func(t *gtest.T) { - result, err := db.Table(table).Where("id=? and nickname=?", g.Slice{3, "name_3"}).One() - t.Assert(err, nil) + result, err := db.Model(table).Where("id=? and nickname=?", g.Slice{3, "name_3"}).One() + t.AssertNil(err) t.AssertGT(len(result), 0) t.Assert(result["id"].Int(), 3) }) // map like gtest.C(t, func(t *gtest.T) { - result, err := db.Table(table).Where(g.Map{ + result, err := db.Model(table).Where(g.Map{ "passport like": "user_1%", }).Order("id asc").All() - t.Assert(err, nil) + t.AssertNil(err) t.Assert(len(result), 2) t.Assert(result[0].GMap().Get("id"), 1) t.Assert(result[1].GMap().Get("id"), 10) }) // map + slice parameter gtest.C(t, func(t *gtest.T) { - result, err := db.Table(table).Where(g.Map{ + result, err := db.Model(table).Where(g.Map{ "id": g.Slice{1, 2, 3}, "passport": g.Slice{"user_2", "user_3"}, }).And("id=? and nickname=?", g.Slice{3, "name_3"}).One() - t.Assert(err, nil) + t.AssertNil(err) t.AssertGT(len(result), 0) t.Assert(result["id"].Int(), 3) }) gtest.C(t, func(t *gtest.T) { - result, err := db.Table(table).Where("id=3", g.Slice{}).One() - t.Assert(err, nil) + result, err := db.Model(table).Where("id=3", g.Slice{}).One() + t.AssertNil(err) t.AssertGT(len(result), 0) t.Assert(result["id"].Int(), 3) }) gtest.C(t, func(t *gtest.T) { - result, err := db.Table(table).Where("id=?", g.Slice{3}).One() - t.Assert(err, nil) + result, err := db.Model(table).Where("id=?", g.Slice{3}).One() + t.AssertNil(err) t.AssertGT(len(result), 0) t.Assert(result["id"].Int(), 3) }) gtest.C(t, func(t *gtest.T) { - result, err := db.Table(table).Where("id", 3).One() - t.Assert(err, nil) + result, err := db.Model(table).Where("id", 3).One() + t.AssertNil(err) t.AssertGT(len(result), 0) t.Assert(result["id"].Int(), 3) }) gtest.C(t, func(t *gtest.T) { - result, err := db.Table(table).Where("id", 3).Where("nickname", "name_3").One() - t.Assert(err, nil) + result, err := db.Model(table).Where("id", 3).Where("nickname", "name_3").One() + t.AssertNil(err) t.Assert(result["id"].Int(), 3) }) gtest.C(t, func(t *gtest.T) { - result, err := db.Table(table).Where("id", 3).And("nickname", "name_3").One() - t.Assert(err, nil) + result, err := db.Model(table).Where("id", 3).And("nickname", "name_3").One() + t.AssertNil(err) t.Assert(result["id"].Int(), 3) }) gtest.C(t, func(t *gtest.T) { - result, err := db.Table(table).Where("id", 30).Or("nickname", "name_3").One() - t.Assert(err, nil) + result, err := db.Model(table).Where("id", 30).Or("nickname", "name_3").One() + t.AssertNil(err) t.Assert(result["id"].Int(), 3) }) gtest.C(t, func(t *gtest.T) { - result, err := db.Table(table).Where("id", 30).Or("nickname", "name_3").And("id>?", 1).One() - t.Assert(err, nil) + result, err := db.Model(table).Where("id", 30).Or("nickname", "name_3").And("id>?", 1).One() + t.AssertNil(err) t.Assert(result["id"].Int(), 3) }) gtest.C(t, func(t *gtest.T) { - result, err := db.Table(table).Where("id", 30).Or("nickname", "name_3").And("id>", 1).One() - t.Assert(err, nil) + result, err := db.Model(table).Where("id", 30).Or("nickname", "name_3").And("id>", 1).One() + t.AssertNil(err) t.Assert(result["id"].Int(), 3) }) // slice gtest.C(t, func(t *gtest.T) { - result, err := db.Table(table).Where("id=? AND nickname=?", g.Slice{3, "name_3"}...).One() - t.Assert(err, nil) + result, err := db.Model(table).Where("id=? AND nickname=?", g.Slice{3, "name_3"}...).One() + t.AssertNil(err) t.Assert(result["id"].Int(), 3) }) gtest.C(t, func(t *gtest.T) { - result, err := db.Table(table).Where("id=? AND nickname=?", g.Slice{3, "name_3"}).One() - t.Assert(err, nil) + result, err := db.Model(table).Where("id=? AND nickname=?", g.Slice{3, "name_3"}).One() + t.AssertNil(err) t.Assert(result["id"].Int(), 3) }) gtest.C(t, func(t *gtest.T) { - result, err := db.Table(table).Where("passport like ? and nickname like ?", g.Slice{"user_3", "name_3"}).One() - t.Assert(err, nil) + result, err := db.Model(table).Where("passport like ? and nickname like ?", g.Slice{"user_3", "name_3"}).One() + t.AssertNil(err) t.Assert(result["id"].Int(), 3) }) // map gtest.C(t, func(t *gtest.T) { - result, err := db.Table(table).Where(g.Map{"id": 3, "nickname": "name_3"}).One() - t.Assert(err, nil) + result, err := db.Model(table).Where(g.Map{"id": 3, "nickname": "name_3"}).One() + t.AssertNil(err) t.Assert(result["id"].Int(), 3) }) // map key operator gtest.C(t, func(t *gtest.T) { - result, err := db.Table(table).Where(g.Map{"id>": 1, "id<": 3}).One() - t.Assert(err, nil) + result, err := db.Model(table).Where(g.Map{"id>": 1, "id<": 3}).One() + t.AssertNil(err) t.Assert(result["id"].Int(), 2) }) // gmap.Map gtest.C(t, func(t *gtest.T) { - result, err := db.Table(table).Where(gmap.NewFrom(g.MapAnyAny{"id": 3, "nickname": "name_3"})).One() - t.Assert(err, nil) + result, err := db.Model(table).Where(gmap.NewFrom(g.MapAnyAny{"id": 3, "nickname": "name_3"})).One() + t.AssertNil(err) t.Assert(result["id"].Int(), 3) }) // gmap.Map key operator gtest.C(t, func(t *gtest.T) { - result, err := db.Table(table).Where(gmap.NewFrom(g.MapAnyAny{"id>": 1, "id<": 3})).One() - t.Assert(err, nil) + result, err := db.Model(table).Where(gmap.NewFrom(g.MapAnyAny{"id>": 1, "id<": 3})).One() + t.AssertNil(err) t.Assert(result["id"].Int(), 2) }) // list map gtest.C(t, func(t *gtest.T) { - result, err := db.Table(table).Where(gmap.NewListMapFrom(g.MapAnyAny{"id": 3, "nickname": "name_3"})).One() - t.Assert(err, nil) + result, err := db.Model(table).Where(gmap.NewListMapFrom(g.MapAnyAny{"id": 3, "nickname": "name_3"})).One() + t.AssertNil(err) t.Assert(result["id"].Int(), 3) }) // list map key operator gtest.C(t, func(t *gtest.T) { - result, err := db.Table(table).Where(gmap.NewListMapFrom(g.MapAnyAny{"id>": 1, "id<": 3})).One() - t.Assert(err, nil) + result, err := db.Model(table).Where(gmap.NewListMapFrom(g.MapAnyAny{"id>": 1, "id<": 3})).One() + t.AssertNil(err) t.Assert(result["id"].Int(), 2) }) // tree map gtest.C(t, func(t *gtest.T) { - result, err := db.Table(table).Where(gmap.NewTreeMapFrom(gutil.ComparatorString, g.MapAnyAny{"id": 3, "nickname": "name_3"})).One() - t.Assert(err, nil) + result, err := db.Model(table).Where(gmap.NewTreeMapFrom(gutil.ComparatorString, g.MapAnyAny{"id": 3, "nickname": "name_3"})).One() + t.AssertNil(err) t.Assert(result["id"].Int(), 3) }) // tree map key operator gtest.C(t, func(t *gtest.T) { - result, err := db.Table(table).Where(gmap.NewTreeMapFrom(gutil.ComparatorString, g.MapAnyAny{"id>": 1, "id<": 3})).One() - t.Assert(err, nil) + result, err := db.Model(table).Where(gmap.NewTreeMapFrom(gutil.ComparatorString, g.MapAnyAny{"id>": 1, "id<": 3})).One() + t.AssertNil(err) t.Assert(result["id"].Int(), 2) }) @@ -1445,8 +1451,8 @@ func Test_Model_Where(t *testing.T) { "create_time > 0": nil, "id": g.Slice{1, 2, 3}, } - result, err := db.Table(table).Where(conditions).Order("id asc").All() - t.Assert(err, nil) + result, err := db.Model(table).Where(conditions).Order("id asc").All() + t.AssertNil(err) t.Assert(len(result), 3) t.Assert(result[0]["id"].Int(), 1) }) @@ -1460,47 +1466,47 @@ func Test_Model_Where(t *testing.T) { "create_time > ?": 0, "id in(?)": g.Slice{1, 2, 3}, } - result, err := db.Table(table).Where(conditions).Order("id asc").All() - t.Assert(err, nil) + result, err := db.Model(table).Where(conditions).Order("id asc").All() + t.AssertNil(err) t.Assert(len(result), 3) t.Assert(result[0]["id"].Int(), 1) }) - // struct + // struct, automatic mapping and filtering. gtest.C(t, func(t *gtest.T) { type User struct { - Id int `json:"id"` - Nickname string `gconv:"nickname"` + Id int + Nickname string } - result, err := db.Table(table).Where(User{3, "name_3"}).One() - t.Assert(err, nil) + result, err := db.Model(table).Where(User{3, "name_3"}).One() + t.AssertNil(err) t.Assert(result["id"].Int(), 3) - result, err = db.Table(table).Where(&User{3, "name_3"}).One() - t.Assert(err, nil) + result, err = db.Model(table).Where(&User{3, "name_3"}).One() + t.AssertNil(err) t.Assert(result["id"].Int(), 3) }) // slice single gtest.C(t, func(t *gtest.T) { - result, err := db.Table(table).Where("id IN(?)", g.Slice{1, 3}).Order("id ASC").All() - t.Assert(err, nil) + result, err := db.Model(table).Where("id IN(?)", g.Slice{1, 3}).Order("id ASC").All() + t.AssertNil(err) t.Assert(len(result), 2) t.Assert(result[0]["id"].Int(), 1) t.Assert(result[1]["id"].Int(), 3) }) // slice + string gtest.C(t, func(t *gtest.T) { - result, err := db.Table(table).Where("nickname=? AND id IN(?)", "name_3", g.Slice{1, 3}).Order("id ASC").All() - t.Assert(err, nil) + result, err := db.Model(table).Where("nickname=? AND id IN(?)", "name_3", g.Slice{1, 3}).Order("id ASC").All() + t.AssertNil(err) t.Assert(len(result), 1) t.Assert(result[0]["id"].Int(), 3) }) // slice + map gtest.C(t, func(t *gtest.T) { - result, err := db.Table(table).Where(g.Map{ + result, err := db.Model(table).Where(g.Map{ "id": g.Slice{1, 3}, "nickname": "name_3", }).Order("id ASC").All() - t.Assert(err, nil) + t.AssertNil(err) t.Assert(len(result), 1) t.Assert(result[0]["id"].Int(), 3) }) @@ -1510,11 +1516,11 @@ func Test_Model_Where(t *testing.T) { Ids []int `json:"id"` Nickname string `gconv:"nickname"` } - result, err := db.Table(table).Where(User{ + result, err := db.Model(table).Where(User{ Ids: []int{1, 3}, Nickname: "name_3", }).Order("id ASC").All() - t.Assert(err, nil) + t.AssertNil(err) t.Assert(len(result), 1) t.Assert(result[0]["id"].Int(), 3) }) @@ -1526,13 +1532,13 @@ func Test_Model_Where_ISNULL_1(t *testing.T) { gtest.C(t, func(t *gtest.T) { //db.SetDebug(true) - result, err := db.Table(table).Data("nickname", nil).Where("id", 2).Update() - t.Assert(err, nil) + result, err := db.Model(table).Data("nickname", nil).Where("id", 2).Update() + t.AssertNil(err) n, _ := result.RowsAffected() t.Assert(n, 1) - one, err := db.Table(table).Where("nickname", nil).One() - t.Assert(err, nil) + one, err := db.Model(table).Where("nickname", nil).One() + t.AssertNil(err) t.Assert(one.IsEmpty(), false) t.Assert(one["id"], 2) }) @@ -1552,8 +1558,8 @@ func Test_Model_Where_ISNULL_2(t *testing.T) { "create_time > 0": nil, "id": g.Slice{1, 2, 3}, } - result, err := db.Table(table).WherePri(conditions).Order("id asc").All() - t.Assert(err, nil) + result, err := db.Model(table).WherePri(conditions).Order("id asc").All() + t.AssertNil(err) t.Assert(len(result), 3) t.Assert(result[0]["id"].Int(), 1) }) @@ -1566,8 +1572,8 @@ func Test_Model_Where_OmitEmpty(t *testing.T) { conditions := g.Map{ "id < 4": "", } - result, err := db.Table(table).WherePri(conditions).Order("id asc").All() - t.Assert(err, nil) + result, err := db.Model(table).WherePri(conditions).Order("id asc").All() + t.AssertNil(err) t.Assert(len(result), 3) t.Assert(result[0]["id"].Int(), 1) }) @@ -1575,8 +1581,8 @@ func Test_Model_Where_OmitEmpty(t *testing.T) { conditions := g.Map{ "id < 4": "", } - result, err := db.Table(table).WherePri(conditions).OmitEmpty().Order("id asc").All() - t.Assert(err, nil) + result, err := db.Model(table).WherePri(conditions).OmitEmpty().Order("id asc").All() + t.AssertNil(err) t.Assert(len(result), 3) t.Assert(result[0]["id"].Int(), 1) }) @@ -1587,13 +1593,13 @@ func Test_Model_Where_GTime(t *testing.T) { defer dropTable(table) gtest.C(t, func(t *gtest.T) { - result, err := db.Table(table).Where("create_time>?", gtime.NewFromStr("2010-09-01")).All() - t.Assert(err, nil) + result, err := db.Model(table).Where("create_time>?", gtime.NewFromStr("2010-09-01")).All() + t.AssertNil(err) t.Assert(len(result), 10) }) gtest.C(t, func(t *gtest.T) { - result, err := db.Table(table).Where("create_time>?", *gtime.NewFromStr("2010-09-01")).All() - t.Assert(err, nil) + result, err := db.Model(table).Where("create_time>?", *gtime.NewFromStr("2010-09-01")).All() + t.AssertNil(err) t.Assert(len(result), 10) }) } @@ -1604,14 +1610,14 @@ func Test_Model_WherePri(t *testing.T) { // primary key gtest.C(t, func(t *gtest.T) { - one, err := db.Table(table).WherePri(3).One() - t.Assert(err, nil) + one, err := db.Model(table).WherePri(3).One() + t.AssertNil(err) t.AssertNE(one, nil) t.Assert(one["id"].Int(), 3) }) gtest.C(t, func(t *gtest.T) { - all, err := db.Table(table).WherePri(g.Slice{3, 9}).Order("id asc").All() - t.Assert(err, nil) + all, err := db.Model(table).WherePri(g.Slice{3, 9}).Order("id asc").All() + t.AssertNil(err) t.Assert(len(all), 2) t.Assert(all[0]["id"].Int(), 3) t.Assert(all[1]["id"].Int(), 9) @@ -1619,155 +1625,155 @@ func Test_Model_WherePri(t *testing.T) { // string gtest.C(t, func(t *gtest.T) { - result, err := db.Table(table).WherePri("id=? and nickname=?", 3, "name_3").One() - t.Assert(err, nil) + result, err := db.Model(table).WherePri("id=? and nickname=?", 3, "name_3").One() + t.AssertNil(err) t.AssertGT(len(result), 0) t.Assert(result["id"].Int(), 3) }) // slice parameter gtest.C(t, func(t *gtest.T) { - result, err := db.Table(table).WherePri("id=? and nickname=?", g.Slice{3, "name_3"}).One() - t.Assert(err, nil) + result, err := db.Model(table).WherePri("id=? and nickname=?", g.Slice{3, "name_3"}).One() + t.AssertNil(err) t.AssertGT(len(result), 0) t.Assert(result["id"].Int(), 3) }) // map like gtest.C(t, func(t *gtest.T) { - result, err := db.Table(table).WherePri(g.Map{ + result, err := db.Model(table).WherePri(g.Map{ "passport like": "user_1%", }).Order("id asc").All() - t.Assert(err, nil) + t.AssertNil(err) t.Assert(len(result), 2) t.Assert(result[0].GMap().Get("id"), 1) t.Assert(result[1].GMap().Get("id"), 10) }) // map + slice parameter gtest.C(t, func(t *gtest.T) { - result, err := db.Table(table).WherePri(g.Map{ + result, err := db.Model(table).WherePri(g.Map{ "id": g.Slice{1, 2, 3}, "passport": g.Slice{"user_2", "user_3"}, }).And("id=? and nickname=?", g.Slice{3, "name_3"}).One() - t.Assert(err, nil) + t.AssertNil(err) t.AssertGT(len(result), 0) t.Assert(result["id"].Int(), 3) }) gtest.C(t, func(t *gtest.T) { - result, err := db.Table(table).WherePri(g.Map{ + result, err := db.Model(table).WherePri(g.Map{ "id": g.Slice{1, 2, 3}, "passport": g.Slice{"user_2", "user_3"}, }).Or("nickname=?", g.Slice{"name_4"}).And("id", 3).One() - t.Assert(err, nil) + t.AssertNil(err) t.AssertGT(len(result), 0) t.Assert(result["id"].Int(), 2) }) gtest.C(t, func(t *gtest.T) { - result, err := db.Table(table).WherePri("id=3", g.Slice{}).One() - t.Assert(err, nil) + result, err := db.Model(table).WherePri("id=3", g.Slice{}).One() + t.AssertNil(err) t.AssertGT(len(result), 0) t.Assert(result["id"].Int(), 3) }) gtest.C(t, func(t *gtest.T) { - result, err := db.Table(table).WherePri("id=?", g.Slice{3}).One() - t.Assert(err, nil) + result, err := db.Model(table).WherePri("id=?", g.Slice{3}).One() + t.AssertNil(err) t.AssertGT(len(result), 0) t.Assert(result["id"].Int(), 3) }) gtest.C(t, func(t *gtest.T) { - result, err := db.Table(table).WherePri("id", 3).One() - t.Assert(err, nil) + result, err := db.Model(table).WherePri("id", 3).One() + t.AssertNil(err) t.AssertGT(len(result), 0) t.Assert(result["id"].Int(), 3) }) gtest.C(t, func(t *gtest.T) { - result, err := db.Table(table).WherePri("id", 3).WherePri("nickname", "name_3").One() - t.Assert(err, nil) + result, err := db.Model(table).WherePri("id", 3).WherePri("nickname", "name_3").One() + t.AssertNil(err) t.Assert(result["id"].Int(), 3) }) gtest.C(t, func(t *gtest.T) { - result, err := db.Table(table).WherePri("id", 3).And("nickname", "name_3").One() - t.Assert(err, nil) + result, err := db.Model(table).WherePri("id", 3).And("nickname", "name_3").One() + t.AssertNil(err) t.Assert(result["id"].Int(), 3) }) gtest.C(t, func(t *gtest.T) { - result, err := db.Table(table).WherePri("id", 30).Or("nickname", "name_3").One() - t.Assert(err, nil) + result, err := db.Model(table).WherePri("id", 30).Or("nickname", "name_3").One() + t.AssertNil(err) t.Assert(result["id"].Int(), 3) }) gtest.C(t, func(t *gtest.T) { - result, err := db.Table(table).WherePri("id", 30).Or("nickname", "name_3").And("id>?", 1).One() - t.Assert(err, nil) + result, err := db.Model(table).WherePri("id", 30).Or("nickname", "name_3").And("id>?", 1).One() + t.AssertNil(err) t.Assert(result["id"].Int(), 3) }) gtest.C(t, func(t *gtest.T) { - result, err := db.Table(table).WherePri("id", 30).Or("nickname", "name_3").And("id>", 1).One() - t.Assert(err, nil) + result, err := db.Model(table).WherePri("id", 30).Or("nickname", "name_3").And("id>", 1).One() + t.AssertNil(err) t.Assert(result["id"].Int(), 3) }) // slice gtest.C(t, func(t *gtest.T) { - result, err := db.Table(table).WherePri("id=? AND nickname=?", g.Slice{3, "name_3"}...).One() - t.Assert(err, nil) + result, err := db.Model(table).WherePri("id=? AND nickname=?", g.Slice{3, "name_3"}...).One() + t.AssertNil(err) t.Assert(result["id"].Int(), 3) }) gtest.C(t, func(t *gtest.T) { - result, err := db.Table(table).WherePri("id=? AND nickname=?", g.Slice{3, "name_3"}).One() - t.Assert(err, nil) + result, err := db.Model(table).WherePri("id=? AND nickname=?", g.Slice{3, "name_3"}).One() + t.AssertNil(err) t.Assert(result["id"].Int(), 3) }) gtest.C(t, func(t *gtest.T) { - result, err := db.Table(table).WherePri("passport like ? and nickname like ?", g.Slice{"user_3", "name_3"}).One() - t.Assert(err, nil) + result, err := db.Model(table).WherePri("passport like ? and nickname like ?", g.Slice{"user_3", "name_3"}).One() + t.AssertNil(err) t.Assert(result["id"].Int(), 3) }) // map gtest.C(t, func(t *gtest.T) { - result, err := db.Table(table).WherePri(g.Map{"id": 3, "nickname": "name_3"}).One() - t.Assert(err, nil) + result, err := db.Model(table).WherePri(g.Map{"id": 3, "nickname": "name_3"}).One() + t.AssertNil(err) t.Assert(result["id"].Int(), 3) }) // map key operator gtest.C(t, func(t *gtest.T) { - result, err := db.Table(table).WherePri(g.Map{"id>": 1, "id<": 3}).One() - t.Assert(err, nil) + result, err := db.Model(table).WherePri(g.Map{"id>": 1, "id<": 3}).One() + t.AssertNil(err) t.Assert(result["id"].Int(), 2) }) // gmap.Map gtest.C(t, func(t *gtest.T) { - result, err := db.Table(table).WherePri(gmap.NewFrom(g.MapAnyAny{"id": 3, "nickname": "name_3"})).One() - t.Assert(err, nil) + result, err := db.Model(table).WherePri(gmap.NewFrom(g.MapAnyAny{"id": 3, "nickname": "name_3"})).One() + t.AssertNil(err) t.Assert(result["id"].Int(), 3) }) // gmap.Map key operator gtest.C(t, func(t *gtest.T) { - result, err := db.Table(table).WherePri(gmap.NewFrom(g.MapAnyAny{"id>": 1, "id<": 3})).One() - t.Assert(err, nil) + result, err := db.Model(table).WherePri(gmap.NewFrom(g.MapAnyAny{"id>": 1, "id<": 3})).One() + t.AssertNil(err) t.Assert(result["id"].Int(), 2) }) // list map gtest.C(t, func(t *gtest.T) { - result, err := db.Table(table).WherePri(gmap.NewListMapFrom(g.MapAnyAny{"id": 3, "nickname": "name_3"})).One() - t.Assert(err, nil) + result, err := db.Model(table).WherePri(gmap.NewListMapFrom(g.MapAnyAny{"id": 3, "nickname": "name_3"})).One() + t.AssertNil(err) t.Assert(result["id"].Int(), 3) }) // list map key operator gtest.C(t, func(t *gtest.T) { - result, err := db.Table(table).WherePri(gmap.NewListMapFrom(g.MapAnyAny{"id>": 1, "id<": 3})).One() - t.Assert(err, nil) + result, err := db.Model(table).WherePri(gmap.NewListMapFrom(g.MapAnyAny{"id>": 1, "id<": 3})).One() + t.AssertNil(err) t.Assert(result["id"].Int(), 2) }) // tree map gtest.C(t, func(t *gtest.T) { - result, err := db.Table(table).WherePri(gmap.NewTreeMapFrom(gutil.ComparatorString, g.MapAnyAny{"id": 3, "nickname": "name_3"})).One() - t.Assert(err, nil) + result, err := db.Model(table).WherePri(gmap.NewTreeMapFrom(gutil.ComparatorString, g.MapAnyAny{"id": 3, "nickname": "name_3"})).One() + t.AssertNil(err) t.Assert(result["id"].Int(), 3) }) // tree map key operator gtest.C(t, func(t *gtest.T) { - result, err := db.Table(table).WherePri(gmap.NewTreeMapFrom(gutil.ComparatorString, g.MapAnyAny{"id>": 1, "id<": 3})).One() - t.Assert(err, nil) + result, err := db.Model(table).WherePri(gmap.NewTreeMapFrom(gutil.ComparatorString, g.MapAnyAny{"id>": 1, "id<": 3})).One() + t.AssertNil(err) t.Assert(result["id"].Int(), 2) }) @@ -1781,8 +1787,8 @@ func Test_Model_WherePri(t *testing.T) { "create_time > 0": nil, "id": g.Slice{1, 2, 3}, } - result, err := db.Table(table).WherePri(conditions).Order("id asc").All() - t.Assert(err, nil) + result, err := db.Model(table).WherePri(conditions).Order("id asc").All() + t.AssertNil(err) t.Assert(len(result), 3) t.Assert(result[0]["id"].Int(), 1) }) @@ -1796,8 +1802,8 @@ func Test_Model_WherePri(t *testing.T) { "create_time > ?": 0, "id in(?)": g.Slice{1, 2, 3}, } - result, err := db.Table(table).WherePri(conditions).Order("id asc").All() - t.Assert(err, nil) + result, err := db.Model(table).WherePri(conditions).Order("id asc").All() + t.AssertNil(err) t.Assert(len(result), 3) t.Assert(result[0]["id"].Int(), 1) }) @@ -1807,36 +1813,36 @@ func Test_Model_WherePri(t *testing.T) { Id int `json:"id"` Nickname string `gconv:"nickname"` } - result, err := db.Table(table).WherePri(User{3, "name_3"}).One() - t.Assert(err, nil) + result, err := db.Model(table).WherePri(User{3, "name_3"}).One() + t.AssertNil(err) t.Assert(result["id"].Int(), 3) - result, err = db.Table(table).WherePri(&User{3, "name_3"}).One() - t.Assert(err, nil) + result, err = db.Model(table).WherePri(&User{3, "name_3"}).One() + t.AssertNil(err) t.Assert(result["id"].Int(), 3) }) // slice single gtest.C(t, func(t *gtest.T) { - result, err := db.Table(table).WherePri("id IN(?)", g.Slice{1, 3}).Order("id ASC").All() - t.Assert(err, nil) + result, err := db.Model(table).WherePri("id IN(?)", g.Slice{1, 3}).Order("id ASC").All() + t.AssertNil(err) t.Assert(len(result), 2) t.Assert(result[0]["id"].Int(), 1) t.Assert(result[1]["id"].Int(), 3) }) // slice + string gtest.C(t, func(t *gtest.T) { - result, err := db.Table(table).WherePri("nickname=? AND id IN(?)", "name_3", g.Slice{1, 3}).Order("id ASC").All() - t.Assert(err, nil) + result, err := db.Model(table).WherePri("nickname=? AND id IN(?)", "name_3", g.Slice{1, 3}).Order("id ASC").All() + t.AssertNil(err) t.Assert(len(result), 1) t.Assert(result[0]["id"].Int(), 3) }) // slice + map gtest.C(t, func(t *gtest.T) { - result, err := db.Table(table).WherePri(g.Map{ + result, err := db.Model(table).WherePri(g.Map{ "id": g.Slice{1, 3}, "nickname": "name_3", }).Order("id ASC").All() - t.Assert(err, nil) + t.AssertNil(err) t.Assert(len(result), 1) t.Assert(result[0]["id"].Int(), 3) }) @@ -1846,11 +1852,11 @@ func Test_Model_WherePri(t *testing.T) { Ids []int `json:"id"` Nickname string `gconv:"nickname"` } - result, err := db.Table(table).WherePri(User{ + result, err := db.Model(table).WherePri(User{ Ids: []int{1, 3}, Nickname: "name_3", }).Order("id ASC").All() - t.Assert(err, nil) + t.AssertNil(err) t.Assert(len(result), 1) t.Assert(result[0]["id"].Int(), 3) }) @@ -1862,17 +1868,17 @@ func Test_Model_Delete(t *testing.T) { // DELETE...LIMIT gtest.C(t, func(t *gtest.T) { - result, err := db.Table(table).Where(1).Limit(2).Delete() - t.Assert(err, nil) + result, err := db.Model(table).Where(1).Limit(2).Delete() + t.AssertNil(err) n, _ := result.RowsAffected() t.Assert(n, 2) }) gtest.C(t, func(t *gtest.T) { - result, err := db.Table(table).Where(1).Delete() - t.Assert(err, nil) + result, err := db.Model(table).Where(1).Delete() + t.AssertNil(err) n, _ := result.RowsAffected() - t.Assert(n, SIZE-2) + t.Assert(n, TableSize-2) }) } @@ -1880,8 +1886,8 @@ func Test_Model_Offset(t *testing.T) { table := createInitTable() defer dropTable(table) gtest.C(t, func(t *gtest.T) { - result, err := db.Table(table).Limit(2).Offset(5).Order("id").Select() - t.Assert(err, nil) + result, err := db.Model(table).Limit(2).Offset(5).Order("id").Select() + t.AssertNil(err) t.Assert(len(result), 2) t.Assert(result[0]["id"], 6) t.Assert(result[1]["id"], 7) @@ -1892,20 +1898,20 @@ func Test_Model_Page(t *testing.T) { table := createInitTable() defer dropTable(table) gtest.C(t, func(t *gtest.T) { - result, err := db.Table(table).Page(3, 3).Order("id").All() - t.Assert(err, nil) + result, err := db.Model(table).Page(3, 3).Order("id").All() + t.AssertNil(err) t.Assert(len(result), 3) t.Assert(result[0]["id"], 7) t.Assert(result[1]["id"], 8) }) gtest.C(t, func(t *gtest.T) { - model := db.Table(table).Safe().Order("id") + model := db.Model(table).Safe().Order("id") all, err := model.Page(3, 3).All() count, err := model.Count() - t.Assert(err, nil) + t.AssertNil(err) t.Assert(len(all), 3) t.Assert(all[0]["id"], "7") - t.Assert(count, SIZE) + t.Assert(count, TableSize) }) } @@ -1914,17 +1920,17 @@ func Test_Model_Option_Map(t *testing.T) { gtest.C(t, func(t *gtest.T) { table := createTable() defer dropTable(table) - r, err := db.Table(table).Fields("id, passport").Data(g.Map{ + r, err := db.Model(table).Fields("id, passport").Data(g.Map{ "id": 1, "passport": "1", "password": "1", "nickname": "1", }).Insert() - t.Assert(err, nil) + t.AssertNil(err) n, _ := r.RowsAffected() t.Assert(n, 1) - one, err := db.Table(table).Where("id", 1).One() - t.Assert(err, nil) + one, err := db.Model(table).Where("id", 1).One() + t.AssertNil(err) t.AssertNE(one["password"].String(), "1") t.AssertNE(one["nickname"].String(), "1") t.Assert(one["passport"].String(), "1") @@ -1932,17 +1938,17 @@ func Test_Model_Option_Map(t *testing.T) { gtest.C(t, func(t *gtest.T) { table := createTable() defer dropTable(table) - r, err := db.Table(table).Option(gdb.OPTION_OMITEMPTY).Data(g.Map{ + r, err := db.Model(table).OmitEmptyData().Data(g.Map{ "id": 1, "passport": 0, "password": 0, "nickname": "1", }).Insert() - t.Assert(err, nil) + t.AssertNil(err) n, _ := r.RowsAffected() t.Assert(n, 1) - one, err := db.Table(table).Where("id", 1).One() - t.Assert(err, nil) + one, err := db.Model(table).Where("id", 1).One() + t.AssertNil(err) t.AssertNE(one["passport"].String(), "0") t.AssertNE(one["password"].String(), "0") t.Assert(one["nickname"].String(), "1") @@ -1952,15 +1958,15 @@ func Test_Model_Option_Map(t *testing.T) { gtest.C(t, func(t *gtest.T) { table := createInitTable() defer dropTable(table) - _, err := db.Table(table).Option(gdb.OPTION_OMITEMPTY).Data(g.Map{ + _, err := db.Model(table).OmitEmptyData().Data(g.Map{ "id": 1, "passport": 0, "password": 0, "nickname": "1", }).Replace() - t.Assert(err, nil) - one, err := db.Table(table).Where("id", 1).One() - t.Assert(err, nil) + t.AssertNil(err) + one, err := db.Model(table).Where("id", 1).One() + t.AssertNil(err) t.AssertNE(one["passport"].String(), "0") t.AssertNE(one["password"].String(), "0") t.Assert(one["nickname"].String(), "1") @@ -1970,17 +1976,17 @@ func Test_Model_Option_Map(t *testing.T) { gtest.C(t, func(t *gtest.T) { table := createTable() defer dropTable(table) - r, err := db.Table(table).Fields("id, passport").Data(g.Map{ + r, err := db.Model(table).Fields("id, passport").Data(g.Map{ "id": 1, "passport": "1", "password": "1", "nickname": "1", }).Save() - t.Assert(err, nil) + t.AssertNil(err) n, _ := r.RowsAffected() t.Assert(n, 1) - one, err := db.Table(table).Where("id", 1).One() - t.Assert(err, nil) + one, err := db.Model(table).Where("id", 1).One() + t.AssertNil(err) t.AssertNE(one["password"].String(), "1") t.AssertNE(one["nickname"].String(), "1") t.Assert(one["passport"].String(), "1") @@ -1988,28 +1994,28 @@ func Test_Model_Option_Map(t *testing.T) { gtest.C(t, func(t *gtest.T) { table := createTable() defer dropTable(table) - _, err := db.Table(table).Option(gdb.OPTION_OMITEMPTY).Data(g.Map{ + _, err := db.Model(table).OmitEmptyData().Data(g.Map{ "id": 1, "passport": 0, "password": 0, "nickname": "1", }).Save() - t.Assert(err, nil) - one, err := db.Table(table).Where("id", 1).One() - t.Assert(err, nil) + t.AssertNil(err) + one, err := db.Model(table).Where("id", 1).One() + t.AssertNil(err) t.AssertNE(one["passport"].String(), "0") t.AssertNE(one["password"].String(), "0") t.Assert(one["nickname"].String(), "1") - _, err = db.Table(table).Data(g.Map{ + _, err = db.Model(table).Data(g.Map{ "id": 1, "passport": 0, "password": 0, "nickname": "1", }).Save() - t.Assert(err, nil) - one, err = db.Table(table).Where("id", 1).One() - t.Assert(err, nil) + t.AssertNil(err) + one, err = db.Model(table).Where("id", 1).One() + t.AssertNil(err) t.Assert(one["passport"].String(), "0") t.Assert(one["password"].String(), "0") t.Assert(one["nickname"].String(), "1") @@ -2020,34 +2026,34 @@ func Test_Model_Option_Map(t *testing.T) { table := createInitTable() defer dropTable(table) - r, err := db.Table(table).Data(g.Map{"nickname": ""}).Where("id", 1).Update() - t.Assert(err, nil) + r, err := db.Model(table).Data(g.Map{"nickname": ""}).Where("id", 1).Update() + t.AssertNil(err) n, _ := r.RowsAffected() t.Assert(n, 1) - _, err = db.Table(table).Option(gdb.OPTION_OMITEMPTY).Data(g.Map{"nickname": ""}).Where("id", 2).Update() + _, err = db.Model(table).OmitEmptyData().Data(g.Map{"nickname": ""}).Where("id", 2).Update() t.AssertNE(err, nil) - r, err = db.Table(table).OmitEmpty().Data(g.Map{"nickname": "", "password": "123"}).Where("id", 3).Update() - t.Assert(err, nil) + r, err = db.Model(table).OmitEmpty().Data(g.Map{"nickname": "", "password": "123"}).Where("id", 3).Update() + t.AssertNil(err) n, _ = r.RowsAffected() t.Assert(n, 1) - _, err = db.Table(table).OmitEmpty().Fields("nickname").Data(g.Map{"nickname": "", "password": "123"}).Where("id", 4).Update() + _, err = db.Model(table).OmitEmpty().Fields("nickname").Data(g.Map{"nickname": "", "password": "123"}).Where("id", 4).Update() t.AssertNE(err, nil) - r, err = db.Table(table).OmitEmpty(). + r, err = db.Model(table).OmitEmpty(). Fields("password").Data(g.Map{ "nickname": "", "passport": "123", "password": "456", }).Where("id", 5).Update() - t.Assert(err, nil) + t.AssertNil(err) n, _ = r.RowsAffected() t.Assert(n, 1) - one, err := db.Table(table).Where("id", 5).One() - t.Assert(err, nil) + one, err := db.Model(table).Where("id", 5).One() + t.AssertNil(err) t.Assert(one["password"], "456") t.AssertNE(one["passport"].String(), "") t.AssertNE(one["passport"].String(), "123") @@ -2058,7 +2064,7 @@ func Test_Model_Option_List(t *testing.T) { gtest.C(t, func(t *gtest.T) { table := createTable() defer dropTable(table) - r, err := db.Table(table).Fields("id, password").Data(g.List{ + r, err := db.Model(table).Fields("id, password").Data(g.List{ g.Map{ "id": 1, "passport": "1", @@ -2072,11 +2078,11 @@ func Test_Model_Option_List(t *testing.T) { "nickname": "2", }, }).Save() - t.Assert(err, nil) + t.AssertNil(err) n, _ := r.RowsAffected() t.Assert(n, 2) - list, err := db.Table(table).Order("id asc").All() - t.Assert(err, nil) + list, err := db.Model(table).Order("id asc").All() + t.AssertNil(err) t.Assert(len(list), 2) t.Assert(list[0]["id"].String(), "1") t.Assert(list[0]["nickname"].String(), "") @@ -2092,7 +2098,7 @@ func Test_Model_Option_List(t *testing.T) { gtest.C(t, func(t *gtest.T) { table := createTable() defer dropTable(table) - r, err := db.Table(table).OmitEmpty().Fields("id, password").Data(g.List{ + r, err := db.Model(table).OmitEmpty().Fields("id, password").Data(g.List{ g.Map{ "id": 1, "passport": "1", @@ -2106,11 +2112,11 @@ func Test_Model_Option_List(t *testing.T) { "nickname": "2", }, }).Save() - t.Assert(err, nil) + t.AssertNil(err) n, _ := r.RowsAffected() t.Assert(n, 2) - list, err := db.Table(table).Order("id asc").All() - t.Assert(err, nil) + list, err := db.Model(table).Order("id asc").All() + t.AssertNil(err) t.Assert(len(list), 2) t.Assert(list[0]["id"].String(), "1") t.Assert(list[0]["nickname"].String(), "") @@ -2125,25 +2131,97 @@ func Test_Model_Option_List(t *testing.T) { }) } +func Test_Model_OmitEmpty(t *testing.T) { + table := fmt.Sprintf(`table_%s`, gtime.TimestampNanoStr()) + if _, err := db.Exec(fmt.Sprintf(` + CREATE TABLE IF NOT EXISTS %s ( + id int(10) unsigned NOT NULL AUTO_INCREMENT, + name varchar(45) NOT NULL, + PRIMARY KEY (id) + ) ENGINE=InnoDB DEFAULT CHARSET=utf8; + `, table)); err != nil { + gtest.Error(err) + } + defer dropTable(table) + + gtest.C(t, func(t *gtest.T) { + _, err := db.Model(table).OmitEmpty().Data(g.Map{ + "id": 1, + "name": "", + }).Save() + t.AssertNE(err, nil) + }) + gtest.C(t, func(t *gtest.T) { + _, err := db.Model(table).OmitEmptyData().Data(g.Map{ + "id": 1, + "name": "", + }).Save() + t.AssertNE(err, nil) + }) + gtest.C(t, func(t *gtest.T) { + _, err := db.Model(table).OmitEmptyWhere().Data(g.Map{ + "id": 1, + "name": "", + }).Save() + t.Assert(err, nil) + }) +} + +func Test_Model_OmitNil(t *testing.T) { + table := fmt.Sprintf(`table_%s`, gtime.TimestampNanoStr()) + if _, err := db.Exec(fmt.Sprintf(` + CREATE TABLE IF NOT EXISTS %s ( + id int(10) unsigned NOT NULL AUTO_INCREMENT, + name varchar(45) NOT NULL, + PRIMARY KEY (id) + ) ENGINE=InnoDB DEFAULT CHARSET=utf8; + `, table)); err != nil { + gtest.Error(err) + } + defer dropTable(table) + + gtest.C(t, func(t *gtest.T) { + _, err := db.Model(table).OmitNil().Data(g.Map{ + "id": 1, + "name": nil, + }).Save() + t.AssertNE(err, nil) + }) + gtest.C(t, func(t *gtest.T) { + _, err := db.Model(table).OmitNil().Data(g.Map{ + "id": 1, + "name": "", + }).Save() + t.Assert(err, nil) + }) + gtest.C(t, func(t *gtest.T) { + _, err := db.Model(table).OmitNilWhere().Data(g.Map{ + "id": 1, + "name": "", + }).Save() + t.Assert(err, nil) + }) +} + func Test_Model_Option_Where(t *testing.T) { gtest.C(t, func(t *gtest.T) { table := createInitTable() defer dropTable(table) - r, err := db.Table(table).OmitEmpty().Data("nickname", 1).Where(g.Map{"id": 0, "passport": ""}).And(1).Update() - t.Assert(err, nil) + r, err := db.Model(table).OmitEmpty().Data("nickname", 1).Where(g.Map{"id": 0, "passport": ""}).And(1).Update() + t.AssertNil(err) n, _ := r.RowsAffected() - t.Assert(n, SIZE) + t.Assert(n, TableSize) }) gtest.C(t, func(t *gtest.T) { table := createInitTable() defer dropTable(table) - r, err := db.Table(table).OmitEmpty().Data("nickname", 1).Where(g.Map{"id": 1, "passport": ""}).Update() - t.Assert(err, nil) + r, err := db.Model(table).OmitEmpty().Data("nickname", 1).Where(g.Map{"id": 1, "passport": ""}).Update() + t.AssertNil(err) n, _ := r.RowsAffected() t.Assert(n, 1) - v, err := db.Table(table).Where("id", 1).Fields("nickname").Value() - t.Assert(err, nil) + v, err := db.Model(table).Where("id", 1).Fields("nickname").Value() + t.AssertNil(err) t.Assert(v.String(), "1") }) } @@ -2152,23 +2230,23 @@ func Test_Model_Where_MultiSliceArguments(t *testing.T) { table := createInitTable() defer dropTable(table) gtest.C(t, func(t *gtest.T) { - r, err := db.Table(table).Where(g.Map{ + r, err := db.Model(table).Where(g.Map{ "id": g.Slice{1, 2, 3, 4}, "passport": g.Slice{"user_2", "user_3", "user_4"}, "nickname": g.Slice{"name_2", "name_4"}, "id >= 4": nil, }).All() - t.Assert(err, nil) + t.AssertNil(err) t.Assert(len(r), 1) t.Assert(r[0]["id"], 4) }) gtest.C(t, func(t *gtest.T) { - result, err := db.Table(table).Where(g.Map{ + result, err := db.Model(table).Where(g.Map{ "id": g.Slice{1, 2, 3}, "passport": g.Slice{"user_2", "user_3"}, }).Or("nickname=?", g.Slice{"name_4"}).And("id", 3).One() - t.Assert(err, nil) + t.AssertNil(err) t.AssertGT(len(result), 0) t.Assert(result["id"].Int(), 2) }) @@ -2179,8 +2257,8 @@ func Test_Model_FieldsEx(t *testing.T) { defer dropTable(table) // Select. gtest.C(t, func(t *gtest.T) { - r, err := db.Table(table).FieldsEx("create_time, id").Where("id in (?)", g.Slice{1, 2}).Order("id asc").All() - t.Assert(err, nil) + r, err := db.Model(table).FieldsEx("create_time, id").Where("id in (?)", g.Slice{1, 2}).Order("id asc").All() + t.AssertNil(err) t.Assert(len(r), 2) t.Assert(len(r[0]), 3) t.Assert(r[0]["id"], "") @@ -2196,13 +2274,13 @@ func Test_Model_FieldsEx(t *testing.T) { }) // Update. gtest.C(t, func(t *gtest.T) { - r, err := db.Table(table).FieldsEx("password").Data(g.Map{"nickname": "123", "password": "456"}).Where("id", 3).Update() - t.Assert(err, nil) + r, err := db.Model(table).FieldsEx("password").Data(g.Map{"nickname": "123", "password": "456"}).Where("id", 3).Update() + t.AssertNil(err) n, _ := r.RowsAffected() t.Assert(n, 1) - one, err := db.Table(table).Where("id", 3).One() - t.Assert(err, nil) + one, err := db.Model(table).Where("id", 3).One() + t.AssertNil(err) t.Assert(one["nickname"], "123") t.AssertNE(one["password"], "456") }) @@ -2216,8 +2294,8 @@ func Test_Model_FieldsEx_WithReservedWords(t *testing.T) { } defer dropTable(table) gtest.C(t, func(t *gtest.T) { - _, err := db.Table(table).FieldsEx("content").One() - t.Assert(err, nil) + _, err := db.Model(table).FieldsEx("content").One() + t.AssertNil(err) }) } @@ -2226,8 +2304,8 @@ func Test_Model_FieldsStr(t *testing.T) { defer dropTable(table) gtest.C(t, func(t *gtest.T) { - t.Assert(db.Table(table).FieldsStr(), "`id`,`passport`,`password`,`nickname`,`create_time`") - t.Assert(db.Table(table).FieldsStr("a."), "`a`.`id`,`a`.`passport`,`a`.`password`,`a`.`nickname`,`a`.`create_time`") + t.Assert(db.Model(table).FieldsStr(), "`id`,`passport`,`password`,`nickname`,`create_time`") + t.Assert(db.Model(table).FieldsStr("a."), "`a`.`id`,`a`.`passport`,`a`.`password`,`a`.`nickname`,`a`.`create_time`") }) } @@ -2236,43 +2314,58 @@ func Test_Model_FieldsExStr(t *testing.T) { defer dropTable(table) gtest.C(t, func(t *gtest.T) { - t.Assert(db.Table(table).FieldsExStr("create_time,nickname"), "`id`,`passport`,`password`") - t.Assert(db.Table(table).FieldsExStr("create_time,nickname", "a."), "`a`.`id`,`a`.`passport`,`a`.`password`") + t.Assert(db.Model(table).FieldsExStr("create_time,nickname"), "`id`,`passport`,`password`") + t.Assert(db.Model(table).FieldsExStr("create_time,nickname", "a."), "`a`.`id`,`a`.`passport`,`a`.`password`") }) } func Test_Model_Prefix(t *testing.T) { db := dbPrefix - table := fmt.Sprintf(`%s_%d`, TABLE, gtime.TimestampNano()) - createInitTableWithDb(db, PREFIX1+table) - defer dropTable(PREFIX1 + table) + table := fmt.Sprintf(`%s_%d`, TableName, gtime.TimestampNano()) + createInitTableWithDb(db, TableNamePrefix1+table) + defer dropTable(TableNamePrefix1 + table) // Select. gtest.C(t, func(t *gtest.T) { - r, err := db.Table(table).Where("id in (?)", g.Slice{1, 2}).Order("id asc").All() - t.Assert(err, nil) + r, err := db.Model(table).Where("id in (?)", g.Slice{1, 2}).Order("id asc").All() + t.AssertNil(err) t.Assert(len(r), 2) t.Assert(r[0]["id"], "1") t.Assert(r[1]["id"], "2") }) // Select with alias. gtest.C(t, func(t *gtest.T) { - r, err := db.Table(table+" as u").Where("u.id in (?)", g.Slice{1, 2}).Order("u.id asc").All() - t.Assert(err, nil) + r, err := db.Model(table+" as u").Where("u.id in (?)", g.Slice{1, 2}).Order("u.id asc").All() + t.AssertNil(err) t.Assert(len(r), 2) t.Assert(r[0]["id"], "1") t.Assert(r[1]["id"], "2") }) + // Select with alias to struct. + gtest.C(t, func(t *gtest.T) { + type User struct { + Id int + Passport string + Password string + NickName string + } + var users []User + err := db.Model(table+" u").Where("u.id in (?)", g.Slice{1, 5}).Order("u.id asc").Scan(&users) + t.AssertNil(err) + t.Assert(len(users), 2) + t.Assert(users[0].Id, 1) + t.Assert(users[1].Id, 5) + }) // Select with alias and join statement. gtest.C(t, func(t *gtest.T) { - r, err := db.Table(table+" as u1").LeftJoin(table+" as u2", "u2.id=u1.id").Where("u1.id in (?)", g.Slice{1, 2}).Order("u1.id asc").All() - t.Assert(err, nil) + r, err := db.Model(table+" as u1").LeftJoin(table+" as u2", "u2.id=u1.id").Where("u1.id in (?)", g.Slice{1, 2}).Order("u1.id asc").All() + t.AssertNil(err) t.Assert(len(r), 2) t.Assert(r[0]["id"], "1") t.Assert(r[1]["id"], "2") }) gtest.C(t, func(t *gtest.T) { - r, err := db.Table(table).As("u1").LeftJoin(table+" as u2", "u2.id=u1.id").Where("u1.id in (?)", g.Slice{1, 2}).Order("u1.id asc").All() - t.Assert(err, nil) + r, err := db.Model(table).As("u1").LeftJoin(table+" as u2", "u2.id=u1.id").Where("u1.id in (?)", g.Slice{1, 2}).Order("u1.id asc").All() + t.AssertNil(err) t.Assert(len(r), 2) t.Assert(r[0]["id"], "1") t.Assert(r[1]["id"], "2") @@ -2282,63 +2375,63 @@ func Test_Model_Prefix(t *testing.T) { func Test_Model_Schema1(t *testing.T) { //db.SetDebug(true) - db.SetSchema(SCHEMA1) - table := fmt.Sprintf(`%s_%s`, TABLE, gtime.TimestampNanoStr()) + db.SetSchema(TestSchema1) + table := fmt.Sprintf(`%s_%s`, TableName, gtime.TimestampNanoStr()) createInitTableWithDb(db, table) - db.SetSchema(SCHEMA2) + db.SetSchema(TestSchema2) createInitTableWithDb(db, table) defer func() { - db.SetSchema(SCHEMA1) + db.SetSchema(TestSchema1) dropTableWithDb(db, table) - db.SetSchema(SCHEMA2) + db.SetSchema(TestSchema2) dropTableWithDb(db, table) - db.SetSchema(SCHEMA1) + db.SetSchema(TestSchema1) }() // Method. gtest.C(t, func(t *gtest.T) { - db.SetSchema(SCHEMA1) - r, err := db.Table(table).Update(g.Map{"nickname": "name_100"}, "id=1") - t.Assert(err, nil) + db.SetSchema(TestSchema1) + r, err := db.Model(table).Update(g.Map{"nickname": "name_100"}, "id=1") + t.AssertNil(err) n, _ := r.RowsAffected() t.Assert(n, 1) - v, err := db.Table(table).Value("nickname", "id=1") - t.Assert(err, nil) + v, err := db.Model(table).Value("nickname", "id=1") + t.AssertNil(err) t.Assert(v.String(), "name_100") - db.SetSchema(SCHEMA2) - v, err = db.Table(table).Value("nickname", "id=1") - t.Assert(err, nil) + db.SetSchema(TestSchema2) + v, err = db.Model(table).Value("nickname", "id=1") + t.AssertNil(err) t.Assert(v.String(), "name_1") }) // Model. gtest.C(t, func(t *gtest.T) { - v, err := db.Table(table).Schema(SCHEMA1).Value("nickname", "id=2") - t.Assert(err, nil) + v, err := db.Model(table).Schema(TestSchema1).Value("nickname", "id=2") + t.AssertNil(err) t.Assert(v.String(), "name_2") - r, err := db.Table(table).Schema(SCHEMA1).Update(g.Map{"nickname": "name_200"}, "id=2") - t.Assert(err, nil) + r, err := db.Model(table).Schema(TestSchema1).Update(g.Map{"nickname": "name_200"}, "id=2") + t.AssertNil(err) n, _ := r.RowsAffected() t.Assert(n, 1) - v, err = db.Table(table).Schema(SCHEMA1).Value("nickname", "id=2") - t.Assert(err, nil) + v, err = db.Model(table).Schema(TestSchema1).Value("nickname", "id=2") + t.AssertNil(err) t.Assert(v.String(), "name_200") - v, err = db.Table(table).Schema(SCHEMA2).Value("nickname", "id=2") - t.Assert(err, nil) + v, err = db.Model(table).Schema(TestSchema2).Value("nickname", "id=2") + t.AssertNil(err) t.Assert(v.String(), "name_2") - v, err = db.Table(table).Schema(SCHEMA1).Value("nickname", "id=2") - t.Assert(err, nil) + v, err = db.Model(table).Schema(TestSchema1).Value("nickname", "id=2") + t.AssertNil(err) t.Assert(v.String(), "name_200") }) // Model. gtest.C(t, func(t *gtest.T) { i := 1000 - _, err := db.Table(table).Schema(SCHEMA1).Filter().Insert(g.Map{ + _, err := db.Model(table).Schema(TestSchema1).Insert(g.Map{ "id": i, "passport": fmt.Sprintf(`user_%d`, i), "password": fmt.Sprintf(`pass_%d`, i), @@ -2346,14 +2439,14 @@ func Test_Model_Schema1(t *testing.T) { "create_time": gtime.NewFromStr("2018-10-24 10:00:00").String(), "none-exist-field": 1, }) - t.Assert(err, nil) + t.AssertNil(err) - v, err := db.Table(table).Schema(SCHEMA1).Value("nickname", "id=?", i) - t.Assert(err, nil) + v, err := db.Model(table).Schema(TestSchema1).Value("nickname", "id=?", i) + t.AssertNil(err) t.Assert(v.String(), "name_1000") - v, err = db.Table(table).Schema(SCHEMA2).Value("nickname", "id=?", i) - t.Assert(err, nil) + v, err = db.Model(table).Schema(TestSchema2).Value("nickname", "id=?", i) + t.AssertNil(err) t.Assert(v.String(), "") }) } @@ -2361,46 +2454,46 @@ func Test_Model_Schema1(t *testing.T) { func Test_Model_Schema2(t *testing.T) { //db.SetDebug(true) - db.SetSchema(SCHEMA1) - table := fmt.Sprintf(`%s_%s`, TABLE, gtime.TimestampNanoStr()) + db.SetSchema(TestSchema1) + table := fmt.Sprintf(`%s_%s`, TableName, gtime.TimestampNanoStr()) createInitTableWithDb(db, table) - db.SetSchema(SCHEMA2) + db.SetSchema(TestSchema2) createInitTableWithDb(db, table) defer func() { - db.SetSchema(SCHEMA1) + db.SetSchema(TestSchema1) dropTableWithDb(db, table) - db.SetSchema(SCHEMA2) + db.SetSchema(TestSchema2) dropTableWithDb(db, table) - db.SetSchema(SCHEMA1) + db.SetSchema(TestSchema1) }() // Schema. gtest.C(t, func(t *gtest.T) { - v, err := db.Schema(SCHEMA1).Table(table).Value("nickname", "id=2") - t.Assert(err, nil) + v, err := db.Schema(TestSchema1).Table(table).Value("nickname", "id=2") + t.AssertNil(err) t.Assert(v.String(), "name_2") - r, err := db.Schema(SCHEMA1).Table(table).Update(g.Map{"nickname": "name_200"}, "id=2") - t.Assert(err, nil) + r, err := db.Schema(TestSchema1).Table(table).Update(g.Map{"nickname": "name_200"}, "id=2") + t.AssertNil(err) n, _ := r.RowsAffected() t.Assert(n, 1) - v, err = db.Schema(SCHEMA1).Table(table).Value("nickname", "id=2") - t.Assert(err, nil) + v, err = db.Schema(TestSchema1).Table(table).Value("nickname", "id=2") + t.AssertNil(err) t.Assert(v.String(), "name_200") - v, err = db.Schema(SCHEMA2).Table(table).Value("nickname", "id=2") - t.Assert(err, nil) + v, err = db.Schema(TestSchema2).Table(table).Value("nickname", "id=2") + t.AssertNil(err) t.Assert(v.String(), "name_2") - v, err = db.Schema(SCHEMA1).Table(table).Value("nickname", "id=2") - t.Assert(err, nil) + v, err = db.Schema(TestSchema1).Table(table).Value("nickname", "id=2") + t.AssertNil(err) t.Assert(v.String(), "name_200") }) // Schema. gtest.C(t, func(t *gtest.T) { i := 1000 - _, err := db.Schema(SCHEMA1).Table(table).Filter().Insert(g.Map{ + _, err := db.Schema(TestSchema1).Table(table).Insert(g.Map{ "id": i, "passport": fmt.Sprintf(`user_%d`, i), "password": fmt.Sprintf(`pass_%d`, i), @@ -2408,14 +2501,14 @@ func Test_Model_Schema2(t *testing.T) { "create_time": gtime.NewFromStr("2018-10-24 10:00:00").String(), "none-exist-field": 1, }) - t.Assert(err, nil) + t.AssertNil(err) - v, err := db.Schema(SCHEMA1).Table(table).Value("nickname", "id=?", i) - t.Assert(err, nil) + v, err := db.Schema(TestSchema1).Table(table).Value("nickname", "id=?", i) + t.AssertNil(err) t.Assert(v.String(), "name_1000") - v, err = db.Schema(SCHEMA2).Table(table).Value("nickname", "id=?", i) - t.Assert(err, nil) + v, err = db.Schema(TestSchema2).Table(table).Value("nickname", "id=?", i) + t.AssertNil(err) t.Assert(v.String(), "") }) } @@ -2436,10 +2529,10 @@ func Test_Model_FieldsExStruct(t *testing.T) { Password: "222", NickName: "333", } - r, err := db.Table(table).FieldsEx("create_time, password").OmitEmpty().Data(user).Insert() - t.Assert(err, nil) + r, err := db.Model(table).FieldsEx("create_time, password").OmitEmpty().Data(user).Insert() + t.AssertNil(err) n, err := r.RowsAffected() - t.Assert(err, nil) + t.AssertNil(err) t.Assert(n, 1) }) gtest.C(t, func(t *gtest.T) { @@ -2458,14 +2551,14 @@ func Test_Model_FieldsExStruct(t *testing.T) { NickName: fmt.Sprintf(`nickname_%d`, i), }) } - r, err := db.Table(table).FieldsEx("create_time, password"). + r, err := db.Model(table).FieldsEx("create_time, password"). OmitEmpty(). Batch(2). Data(users). Insert() - t.Assert(err, nil) + t.AssertNil(err) n, err := r.RowsAffected() - t.Assert(err, nil) + t.AssertNil(err) t.Assert(n, 10) }) } @@ -2486,10 +2579,10 @@ func Test_Model_OmitEmpty_Time(t *testing.T) { Password: "222", Time: time.Time{}, } - r, err := db.Table(table).OmitEmpty().Data(user).WherePri(1).Update() - t.Assert(err, nil) + r, err := db.Model(table).OmitEmpty().Data(user).WherePri(1).Update() + t.AssertNil(err) n, err := r.RowsAffected() - t.Assert(err, nil) + t.AssertNil(err) t.Assert(n, 1) }) } @@ -2498,8 +2591,8 @@ func Test_Result_Chunk(t *testing.T) { table := createInitTable() defer dropTable(table) gtest.C(t, func(t *gtest.T) { - r, err := db.Table(table).Order("id asc").All() - t.Assert(err, nil) + r, err := db.Model(table).Order("id asc").All() + t.AssertNil(err) chunks := r.Chunk(3) t.Assert(len(chunks), 4) t.Assert(chunks[0][0]["id"].Int(), 1) @@ -2516,15 +2609,15 @@ func Test_Model_DryRun(t *testing.T) { defer db.SetDryRun(false) gtest.C(t, func(t *gtest.T) { - one, err := db.Table(table).FindOne(1) - t.Assert(err, nil) + one, err := db.Model(table).FindOne(1) + t.AssertNil(err) t.Assert(one["id"], 1) }) gtest.C(t, func(t *gtest.T) { - r, err := db.Table(table).Data("passport", "port_1").WherePri(1).Update() - t.Assert(err, nil) + r, err := db.Model(table).Data("passport", "port_1").WherePri(1).Update() + t.AssertNil(err) n, err := r.RowsAffected() - t.Assert(err, nil) + t.AssertNil(err) t.Assert(n, 0) }) } @@ -2534,11 +2627,11 @@ func Test_Model_Join_SubQuery(t *testing.T) { 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) + r, err := db.Model(table, "t1").Fields("t2.id").LeftJoin(subQuery, "t2", "t2.id=t1.id").Array() + t.AssertNil(err) + t.Assert(len(r), TableSize) t.Assert(r[0], "1") - t.Assert(r[SIZE-1], SIZE) + t.Assert(r[TableSize-1], TableSize) }) } @@ -2547,96 +2640,96 @@ func Test_Model_Cache(t *testing.T) { defer dropTable(table) gtest.C(t, func(t *gtest.T) { - one, err := db.Table(table).Cache(time.Second, "test1").FindOne(1) - t.Assert(err, nil) + one, err := db.Model(table).Cache(time.Second, "test1").FindOne(1) + t.AssertNil(err) t.Assert(one["passport"], "user_1") - r, err := db.Table(table).Data("passport", "user_100").WherePri(1).Update() - t.Assert(err, nil) + r, err := db.Model(table).Data("passport", "user_100").WherePri(1).Update() + t.AssertNil(err) n, err := r.RowsAffected() - t.Assert(err, nil) + t.AssertNil(err) t.Assert(n, 1) - one, err = db.Table(table).Cache(time.Second, "test1").FindOne(1) - t.Assert(err, nil) + one, err = db.Model(table).Cache(time.Second, "test1").FindOne(1) + t.AssertNil(err) t.Assert(one["passport"], "user_1") time.Sleep(time.Second * 2) - one, err = db.Table(table).Cache(time.Second, "test1").FindOne(1) - t.Assert(err, nil) + one, err = db.Model(table).Cache(time.Second, "test1").FindOne(1) + t.AssertNil(err) t.Assert(one["passport"], "user_100") }) gtest.C(t, func(t *gtest.T) { - one, err := db.Table(table).Cache(time.Second, "test2").FindOne(2) - t.Assert(err, nil) + one, err := db.Model(table).Cache(time.Second, "test2").FindOne(2) + t.AssertNil(err) t.Assert(one["passport"], "user_2") - r, err := db.Table(table).Data("passport", "user_200").Cache(-1, "test2").WherePri(2).Update() - t.Assert(err, nil) + r, err := db.Model(table).Data("passport", "user_200").Cache(-1, "test2").WherePri(2).Update() + t.AssertNil(err) n, err := r.RowsAffected() - t.Assert(err, nil) + t.AssertNil(err) t.Assert(n, 1) - one, err = db.Table(table).Cache(time.Second, "test2").FindOne(2) - t.Assert(err, nil) + one, err = db.Model(table).Cache(time.Second, "test2").FindOne(2) + t.AssertNil(err) t.Assert(one["passport"], "user_200") }) // transaction. gtest.C(t, func(t *gtest.T) { // make cache for id 3 - one, err := db.Table(table).Cache(time.Second, "test3").FindOne(3) - t.Assert(err, nil) + one, err := db.Model(table).Cache(time.Second, "test3").FindOne(3) + t.AssertNil(err) t.Assert(one["passport"], "user_3") - r, err := db.Table(table).Data("passport", "user_300").Cache(time.Second, "test3").WherePri(3).Update() - t.Assert(err, nil) + r, err := db.Model(table).Data("passport", "user_300").Cache(time.Second, "test3").WherePri(3).Update() + t.AssertNil(err) n, err := r.RowsAffected() - t.Assert(err, nil) + t.AssertNil(err) t.Assert(n, 1) - err = db.Transaction(func(tx *gdb.TX) error { - one, err := tx.Table(table).Cache(time.Second, "test3").FindOne(3) - t.Assert(err, nil) + err = db.Transaction(context.TODO(), func(ctx context.Context, tx *gdb.TX) error { + one, err := tx.Model(table).Cache(time.Second, "test3").FindOne(3) + t.AssertNil(err) t.Assert(one["passport"], "user_300") return nil }) - t.Assert(err, nil) + t.AssertNil(err) - one, err = db.Table(table).Cache(time.Second, "test3").FindOne(3) - t.Assert(err, nil) + one, err = db.Model(table).Cache(time.Second, "test3").FindOne(3) + t.AssertNil(err) t.Assert(one["passport"], "user_3") }) gtest.C(t, func(t *gtest.T) { // make cache for id 4 - one, err := db.Table(table).Cache(time.Second, "test4").FindOne(4) - t.Assert(err, nil) + one, err := db.Model(table).Cache(time.Second, "test4").FindOne(4) + t.AssertNil(err) t.Assert(one["passport"], "user_4") - r, err := db.Table(table).Data("passport", "user_400").Cache(time.Second, "test3").WherePri(4).Update() - t.Assert(err, nil) + r, err := db.Model(table).Data("passport", "user_400").Cache(time.Second, "test3").WherePri(4).Update() + t.AssertNil(err) n, err := r.RowsAffected() - t.Assert(err, nil) + t.AssertNil(err) t.Assert(n, 1) - err = db.Transaction(func(tx *gdb.TX) error { + err = db.Transaction(context.TODO(), func(ctx context.Context, tx *gdb.TX) error { // Cache feature disabled. - one, err := tx.Table(table).Cache(time.Second, "test4").FindOne(4) - t.Assert(err, nil) + one, err := tx.Model(table).Cache(time.Second, "test4").FindOne(4) + t.AssertNil(err) t.Assert(one["passport"], "user_400") // Update the cache. - r, err := tx.Table(table).Data("passport", "user_4000"). + r, err := tx.Model(table).Data("passport", "user_4000"). Cache(-1, "test4").WherePri(4).Update() - t.Assert(err, nil) + t.AssertNil(err) n, err := r.RowsAffected() - t.Assert(err, nil) + t.AssertNil(err) t.Assert(n, 1) return nil }) - t.Assert(err, nil) + t.AssertNil(err) // Read from db. - one, err = db.Table(table).Cache(time.Second, "test4").FindOne(4) - t.Assert(err, nil) + one, err = db.Model(table).Cache(time.Second, "test4").FindOne(4) + t.AssertNil(err) t.Assert(one["passport"], "user_4000") }) } @@ -2646,23 +2739,23 @@ func Test_Model_Having(t *testing.T) { defer dropTable(table) gtest.C(t, func(t *gtest.T) { - all, err := db.Table(table).Where("id > 1").Having("id > 8").All() - t.Assert(err, nil) + all, err := db.Model(table).Where("id > 1").Having("id > 8").All() + t.AssertNil(err) t.Assert(len(all), 2) }) gtest.C(t, func(t *gtest.T) { - all, err := db.Table(table).Where("id > 1").Having("id > ?", 8).All() - t.Assert(err, nil) + all, err := db.Model(table).Where("id > 1").Having("id > ?", 8).All() + t.AssertNil(err) t.Assert(len(all), 2) }) gtest.C(t, func(t *gtest.T) { - all, err := db.Table(table).Where("id > ?", 1).Having("id > ?", 8).All() - t.Assert(err, nil) + all, err := db.Model(table).Where("id > ?", 1).Having("id > ?", 8).All() + t.AssertNil(err) t.Assert(len(all), 2) }) gtest.C(t, func(t *gtest.T) { - all, err := db.Table(table).Where("id > ?", 1).Having("id", 8).All() - t.Assert(err, nil) + all, err := db.Model(table).Where("id > ?", 1).Having("id", 8).All() + t.AssertNil(err) t.Assert(len(all), 1) }) } @@ -2672,10 +2765,15 @@ func Test_Model_Distinct(t *testing.T) { defer dropTable(table) gtest.C(t, func(t *gtest.T) { - all, err := db.Table(table, "t").Fields("distinct t.id").Where("id > 1").Having("id > 8").All() - t.Assert(err, nil) + all, err := db.Model(table, "t").Fields("distinct t.id").Where("id > 1").Having("id > 8").All() + t.AssertNil(err) t.Assert(len(all), 2) }) + gtest.C(t, func(t *gtest.T) { + count, err := db.Model(table).Where("id > 1").Distinct().Count() + t.AssertNil(err) + t.Assert(count, 9) + }) } func Test_Model_Min_Max(t *testing.T) { @@ -2683,13 +2781,13 @@ func Test_Model_Min_Max(t *testing.T) { defer dropTable(table) gtest.C(t, func(t *gtest.T) { - value, err := db.Table(table, "t").Fields("min(t.id)").Where("id > 1").Value() - t.Assert(err, nil) + value, err := db.Model(table, "t").Fields("min(t.id)").Where("id > 1").Value() + t.AssertNil(err) t.Assert(value.Int(), 2) }) gtest.C(t, func(t *gtest.T) { - value, err := db.Table(table, "t").Fields("max(t.id)").Where("id > 1").Value() - t.Assert(err, nil) + value, err := db.Model(table, "t").Fields("max(t.id)").Where("id > 1").Value() + t.AssertNil(err) t.Assert(value.Int(), 10) }) } @@ -2699,23 +2797,23 @@ func Test_Model_Fields_AutoMapping(t *testing.T) { defer dropTable(table) gtest.C(t, func(t *gtest.T) { - value, err := db.Table(table).Fields("ID").Where("id", 2).Value() - t.Assert(err, nil) + value, err := db.Model(table).Fields("ID").Where("id", 2).Value() + t.AssertNil(err) t.Assert(value.Int(), 2) }) gtest.C(t, func(t *gtest.T) { - value, err := db.Table(table).Fields("NICK_NAME").Where("id", 2).Value() - t.Assert(err, nil) + value, err := db.Model(table).Fields("NICK_NAME").Where("id", 2).Value() + t.AssertNil(err) t.Assert(value.String(), "name_2") }) // Map gtest.C(t, func(t *gtest.T) { - one, err := db.Table(table).Fields(g.Map{ + one, err := db.Model(table).Fields(g.Map{ "ID": 1, "NICK_NAME": 1, }).Where("id", 2).One() - t.Assert(err, nil) + t.AssertNil(err) t.Assert(len(one), 2) t.Assert(one["id"], 2) t.Assert(one["nickname"], "name_2") @@ -2726,11 +2824,11 @@ func Test_Model_Fields_AutoMapping(t *testing.T) { ID int NICKNAME int } - one, err := db.Table(table).Fields(&T{ + one, err := db.Model(table).Fields(&T{ ID: 0, NICKNAME: 0, }).Where("id", 2).One() - t.Assert(err, nil) + t.AssertNil(err) t.Assert(len(one), 2) t.Assert(one["id"], 2) t.Assert(one["nickname"], "name_2") @@ -2748,24 +2846,24 @@ func Test_Model_FieldsEx_AutoMapping(t *testing.T) { // "create_time": gtime.NewFromStr("2018-10-24 10:00:00").String(), gtest.C(t, func(t *gtest.T) { - value, err := db.Table(table).FieldsEx("Passport, Password, NickName, CreateTime").Where("id", 2).Value() - t.Assert(err, nil) + value, err := db.Model(table).FieldsEx("Passport, Password, NickName, CreateTime").Where("id", 2).Value() + t.AssertNil(err) t.Assert(value.Int(), 2) }) gtest.C(t, func(t *gtest.T) { - value, err := db.Table(table).FieldsEx("ID, Passport, Password, CreateTime").Where("id", 2).Value() - t.Assert(err, nil) + value, err := db.Model(table).FieldsEx("ID, Passport, Password, CreateTime").Where("id", 2).Value() + t.AssertNil(err) t.Assert(value.String(), "name_2") }) // Map gtest.C(t, func(t *gtest.T) { - one, err := db.Table(table).FieldsEx(g.Map{ + one, err := db.Model(table).FieldsEx(g.Map{ "Passport": 1, "Password": 1, "CreateTime": 1, }).Where("id", 2).One() - t.Assert(err, nil) + t.AssertNil(err) t.Assert(len(one), 2) t.Assert(one["id"], 2) t.Assert(one["nickname"], "name_2") @@ -2777,12 +2875,12 @@ func Test_Model_FieldsEx_AutoMapping(t *testing.T) { Password int CreateTime int } - one, err := db.Table(table).FieldsEx(&T{ + one, err := db.Model(table).FieldsEx(&T{ Passport: 0, Password: 0, CreateTime: 0, }).Where("id", 2).One() - t.Assert(err, nil) + t.AssertNil(err) t.Assert(len(one), 2) t.Assert(one["id"], 2) t.Assert(one["nickname"], "name_2") @@ -2802,30 +2900,30 @@ func Test_Model_Fields_Struct(t *testing.T) { NickName string } gtest.C(t, func(t *gtest.T) { - one, err := db.Table(table).Fields(A{}).Where("id", 2).One() - t.Assert(err, nil) + one, err := db.Model(table).Fields(A{}).Where("id", 2).One() + t.AssertNil(err) t.Assert(len(one), 2) t.Assert(one["passport"], "user_2") t.Assert(one["password"], "pass_2") }) gtest.C(t, func(t *gtest.T) { - one, err := db.Table(table).Fields(&A{}).Where("id", 2).One() - t.Assert(err, nil) + one, err := db.Model(table).Fields(&A{}).Where("id", 2).One() + t.AssertNil(err) t.Assert(len(one), 2) t.Assert(one["passport"], "user_2") t.Assert(one["password"], "pass_2") }) gtest.C(t, func(t *gtest.T) { - one, err := db.Table(table).Fields(B{}).Where("id", 2).One() - t.Assert(err, nil) + one, err := db.Model(table).Fields(B{}).Where("id", 2).One() + t.AssertNil(err) t.Assert(len(one), 3) t.Assert(one["passport"], "user_2") t.Assert(one["password"], "pass_2") t.Assert(one["nickname"], "name_2") }) gtest.C(t, func(t *gtest.T) { - one, err := db.Table(table).Fields(&B{}).Where("id", 2).One() - t.Assert(err, nil) + one, err := db.Model(table).Fields(&B{}).Where("id", 2).One() + t.AssertNil(err) t.Assert(len(one), 3) t.Assert(one["passport"], "user_2") t.Assert(one["password"], "pass_2") @@ -2845,16 +2943,16 @@ func Test_Model_NullField(t *testing.T) { "id": 1, "passport": nil, } - result, err := db.Table(table).Data(data).Insert() - t.Assert(err, nil) + result, err := db.Model(table).Data(data).Insert() + t.AssertNil(err) n, _ := result.RowsAffected() t.Assert(n, 1) - one, err := db.Table(table).FindOne(1) - t.Assert(err, nil) + one, err := db.Model(table).FindOne(1) + t.AssertNil(err) var user *User err = one.Struct(&user) - t.Assert(err, nil) + t.AssertNil(err) t.Assert(user.Id, data["id"]) t.Assert(user.Passport, data["passport"]) }) @@ -2865,12 +2963,12 @@ func Test_Model_Empty_Slice_Argument(t *testing.T) { defer dropTable(table) gtest.C(t, func(t *gtest.T) { result, err := db.Model(table).Where(`id`, g.Slice{}).All() - t.Assert(err, nil) + t.AssertNil(err) t.Assert(len(result), 0) }) gtest.C(t, func(t *gtest.T) { result, err := db.Model(table).Where(`id in(?)`, g.Slice{}).All() - t.Assert(err, nil) + t.AssertNil(err) t.Assert(len(result), 0) }) } @@ -2880,15 +2978,15 @@ func Test_Model_HasTable(t *testing.T) { defer dropTable(table) gtest.C(t, func(t *gtest.T) { - result, err := db.HasTable(table) + result, err := db.GetCore().HasTable(table) t.Assert(result, true) - t.Assert(err, nil) + t.AssertNil(err) }) gtest.C(t, func(t *gtest.T) { - result, err := db.HasTable("table12321") + result, err := db.GetCore().HasTable("table12321") t.Assert(result, false) - t.Assert(err, nil) + t.AssertNil(err) }) } @@ -2897,15 +2995,15 @@ func Test_Model_HasField(t *testing.T) { defer dropTable(table) gtest.C(t, func(t *gtest.T) { - result, err := db.Table(table).HasField("id") + result, err := db.Model(table).HasField("id") t.Assert(result, true) - t.Assert(err, nil) + t.AssertNil(err) }) gtest.C(t, func(t *gtest.T) { - result, err := db.Table(table).HasField("id123") + result, err := db.Model(table).HasField("id123") t.Assert(result, false) - t.Assert(err, nil) + t.AssertNil(err) }) } @@ -2914,83 +3012,108 @@ func Test_Model_Issue1002(t *testing.T) { table := createTable() defer dropTable(table) - result, err := db.Table(table).Data(g.Map{ + result, err := db.Model(table).Data(g.Map{ "id": 1, "passport": "port_1", "password": "pass_1", "nickname": "name_2", "create_time": "2020-10-27 19:03:33", }).Insert() - gtest.Assert(err, nil) + gtest.AssertNil(err) n, _ := result.RowsAffected() gtest.Assert(n, 1) // where + string. gtest.C(t, func(t *gtest.T) { - v, err := db.Table(table).Fields("id").Where("create_time>'2020-10-27 19:03:32' and create_time<'2020-10-27 19:03:34'").Value() - t.Assert(err, nil) + v, err := db.Model(table).Fields("id").Where("create_time>'2020-10-27 19:03:32' and create_time<'2020-10-27 19:03:34'").Value() + t.AssertNil(err) t.Assert(v.Int(), 1) }) gtest.C(t, func(t *gtest.T) { - v, err := db.Table(table).Fields("id").Where("create_time>'2020-10-27 19:03:32' and create_time<'2020-10-27 19:03:34'").FindValue() - t.Assert(err, nil) + v, err := db.Model(table).Fields("id").Where("create_time>'2020-10-27 19:03:32' and create_time<'2020-10-27 19:03:34'").FindValue() + t.AssertNil(err) t.Assert(v.Int(), 1) }) gtest.C(t, func(t *gtest.T) { - v, err := db.Table(table).Where("create_time>'2020-10-27 19:03:32' and create_time<'2020-10-27 19:03:34'").FindValue("id") - t.Assert(err, nil) + v, err := db.Model(table).Where("create_time>'2020-10-27 19:03:32' and create_time<'2020-10-27 19:03:34'").FindValue("id") + t.AssertNil(err) t.Assert(v.Int(), 1) }) // where + string arguments. gtest.C(t, func(t *gtest.T) { - v, err := db.Table(table).Fields("id").Where("create_time>? and create_time<?", "2020-10-27 19:03:32", "2020-10-27 19:03:34").Value() - t.Assert(err, nil) + v, err := db.Model(table).Fields("id").Where("create_time>? and create_time<?", "2020-10-27 19:03:32", "2020-10-27 19:03:34").Value() + t.AssertNil(err) t.Assert(v.Int(), 1) }) gtest.C(t, func(t *gtest.T) { - v, err := db.Table(table).Fields("id").Where("create_time>? and create_time<?", "2020-10-27 19:03:32", "2020-10-27 19:03:34").FindValue() - t.Assert(err, nil) + v, err := db.Model(table).Fields("id").Where("create_time>? and create_time<?", "2020-10-27 19:03:32", "2020-10-27 19:03:34").FindValue() + t.AssertNil(err) t.Assert(v.Int(), 1) }) gtest.C(t, func(t *gtest.T) { - v, err := db.Table(table).Where("create_time>? and create_time<?", "2020-10-27 19:03:32", "2020-10-27 19:03:34").FindValue("id") - t.Assert(err, nil) + v, err := db.Model(table).Where("create_time>? and create_time<?", "2020-10-27 19:03:32", "2020-10-27 19:03:34").FindValue("id") + t.AssertNil(err) t.Assert(v.Int(), 1) }) // where + gtime.Time arguments. gtest.C(t, func(t *gtest.T) { - v, err := db.Table(table).Fields("id").Where("create_time>? and create_time<?", gtime.New("2020-10-27 19:03:32"), gtime.New("2020-10-27 19:03:34")).Value() - t.Assert(err, nil) + v, err := db.Model(table).Fields("id").Where("create_time>? and create_time<?", gtime.New("2020-10-27 19:03:32"), gtime.New("2020-10-27 19:03:34")).Value() + t.AssertNil(err) t.Assert(v.Int(), 1) }) gtest.C(t, func(t *gtest.T) { - v, err := db.Table(table).Fields("id").Where("create_time>? and create_time<?", gtime.New("2020-10-27 19:03:32"), gtime.New("2020-10-27 19:03:34")).FindValue() - t.Assert(err, nil) + v, err := db.Model(table).Fields("id").Where("create_time>? and create_time<?", gtime.New("2020-10-27 19:03:32"), gtime.New("2020-10-27 19:03:34")).FindValue() + t.AssertNil(err) t.Assert(v.Int(), 1) }) gtest.C(t, func(t *gtest.T) { - v, err := db.Table(table).Where("create_time>? and create_time<?", gtime.New("2020-10-27 19:03:32"), gtime.New("2020-10-27 19:03:34")).FindValue("id") - t.Assert(err, nil) + v, err := db.Model(table).Where("create_time>? and create_time<?", gtime.New("2020-10-27 19:03:32"), gtime.New("2020-10-27 19:03:34")).FindValue("id") + t.AssertNil(err) t.Assert(v.Int(), 1) }) // where + time.Time arguments, UTC. - t1, _ := time.Parse("2006-01-02 15:04:05", "2020-10-27 19:03:32") - t2, _ := time.Parse("2006-01-02 15:04:05", "2020-10-27 19:03:34") gtest.C(t, func(t *gtest.T) { - v, err := db.Table(table).Fields("id").Where("create_time>? and create_time<?", t1, t2).Value() - t.Assert(err, nil) - t.Assert(v.Int(), 1) - }) - gtest.C(t, func(t *gtest.T) { - v, err := db.Table(table).Fields("id").Where("create_time>? and create_time<?", t1, t2).FindValue() - t.Assert(err, nil) - t.Assert(v.Int(), 1) - }) - gtest.C(t, func(t *gtest.T) { - v, err := db.Table(table).Where("create_time>? and create_time<?", t1, t2).FindValue("id") - t.Assert(err, nil) - t.Assert(v.Int(), 1) + t1, _ := time.Parse("2006-01-02 15:04:05", "2020-10-27 19:03:32") + t2, _ := time.Parse("2006-01-02 15:04:05", "2020-10-27 19:03:34") + { + v, err := db.Model(table).Fields("id").Where("create_time>? and create_time<?", t1, t2).Value() + t.AssertNil(err) + t.Assert(v.Int(), 1) + } + { + v, err := db.Model(table).Fields("id").Where("create_time>? and create_time<?", t1, t2).FindValue() + t.AssertNil(err) + t.Assert(v.Int(), 1) + } + { + v, err := db.Model(table).Where("create_time>? and create_time<?", t1, t2).FindValue("id") + t.AssertNil(err) + t.Assert(v.Int(), 1) + } }) + // where + time.Time arguments, +8. + //gtest.C(t, func(t *gtest.T) { + // // Change current timezone to +8 zone. + // location, err := time.LoadLocation("Asia/Shanghai") + // t.AssertNil(err) + // t1, _ := time.ParseInLocation("2006-01-02 15:04:05", "2020-10-27 19:03:32", location) + // t2, _ := time.ParseInLocation("2006-01-02 15:04:05", "2020-10-27 19:03:34", location) + // { + // v, err := db.Model(table).Fields("id").Where("create_time>? and create_time<?", t1, t2).Value() + // t.AssertNil(err) + // t.Assert(v.Int(), 1) + // } + // { + // v, err := db.Model(table).Fields("id").Where("create_time>? and create_time<?", t1, t2).FindValue() + // t.AssertNil(err) + // t.Assert(v.Int(), 1) + // } + // { + // v, err := db.Model(table).Where("create_time>? and create_time<?", t1, t2).FindValue("id") + // t.AssertNil(err) + // t.Assert(v.Int(), 1) + // } + //}) } func createTableForTimeZoneTest() string { @@ -3019,7 +3142,7 @@ func Test_TimeZoneInsert(t *testing.T) { defer dropTable(tableName) asiaLocal, err := time.LoadLocation("Asia/Shanghai") - gtest.Assert(err, nil) + gtest.AssertNil(err) CreateTime := "2020-11-22 12:23:45" UpdateTime := "2020-11-22 13:23:45" @@ -3041,10 +3164,10 @@ func Test_TimeZoneInsert(t *testing.T) { } gtest.C(t, func(t *gtest.T) { - _, _ = db.Table(tableName).Unscoped().Insert(u) + _, _ = db.Model(tableName).Unscoped().Insert(u) userEntity := &User{} - err := db.Table(tableName).Where("id", 1).Unscoped().Struct(&userEntity) - t.Assert(err, nil) + err := db.Model(tableName).Where("id", 1).Unscoped().Scan(&userEntity) + t.AssertNil(err) t.Assert(userEntity.CreatedAt.String(), "2020-11-22 04:23:45") t.Assert(userEntity.UpdatedAt.String(), "2020-11-22 05:23:45") t.Assert(gtime.NewFromTime(userEntity.DeletedAt).String(), "2020-11-22 06:23:45") @@ -3056,12 +3179,12 @@ func Test_Model_Fields_Map_Struct(t *testing.T) { defer dropTable(table) // map gtest.C(t, func(t *gtest.T) { - result, err := db.Table(table).Fields(g.Map{ + result, err := db.Model(table).Fields(g.Map{ "ID": 1, "PASSPORT": 1, "NONE_EXIST": 1, }).Where("id", 1).One() - t.Assert(err, nil) + t.AssertNil(err) t.Assert(len(result), 2) t.Assert(result["id"], 1) t.Assert(result["passport"], "user_1") @@ -3074,8 +3197,8 @@ func Test_Model_Fields_Map_Struct(t *testing.T) { XXX_TYPE int } var a = A{} - err := db.Table(table).Fields(a).Where("id", 1).Struct(&a) - t.Assert(err, nil) + err := db.Model(table).Fields(a).Where("id", 1).Scan(&a) + t.AssertNil(err) t.Assert(a.ID, 1) t.Assert(a.PASSPORT, "user_1") t.Assert(a.XXX_TYPE, 0) @@ -3088,8 +3211,8 @@ func Test_Model_Fields_Map_Struct(t *testing.T) { XXX_TYPE int } var a *A - err := db.Table(table).Fields(a).Where("id", 1).Struct(&a) - t.Assert(err, nil) + err := db.Model(table).Fields(a).Where("id", 1).Scan(&a) + t.AssertNil(err) t.Assert(a.ID, 1) t.Assert(a.PASSPORT, "user_1") t.Assert(a.XXX_TYPE, 0) @@ -3102,10 +3225,690 @@ func Test_Model_Fields_Map_Struct(t *testing.T) { XXX_TYPE int } var a *A - err := db.Table(table).Fields(&a).Where("id", 1).Struct(&a) - t.Assert(err, nil) + err := db.Model(table).Fields(&a).Where("id", 1).Scan(&a) + t.AssertNil(err) t.Assert(a.ID, 1) t.Assert(a.PASSPORT, "user_1") t.Assert(a.XXX_TYPE, 0) }) } + +func Test_Model_WhereIn(t *testing.T) { + table := createInitTable() + defer dropTable(table) + + gtest.C(t, func(t *gtest.T) { + result, err := db.Model(table).WhereIn("id", g.Slice{1, 2, 3, 4}).WhereIn("id", g.Slice{3, 4, 5}).OrderAsc("id").All() + t.AssertNil(err) + t.Assert(len(result), 2) + t.Assert(result[0]["id"], 3) + t.Assert(result[1]["id"], 4) + }) +} + +func Test_Model_WhereNotIn(t *testing.T) { + table := createInitTable() + defer dropTable(table) + + gtest.C(t, func(t *gtest.T) { + result, err := db.Model(table).WhereNotIn("id", g.Slice{1, 2, 3, 4}).WhereNotIn("id", g.Slice{3, 4, 5}).OrderAsc("id").All() + t.AssertNil(err) + t.Assert(len(result), 5) + t.Assert(result[0]["id"], 6) + t.Assert(result[1]["id"], 7) + }) +} + +func Test_Model_WhereOrIn(t *testing.T) { + table := createInitTable() + defer dropTable(table) + + gtest.C(t, func(t *gtest.T) { + result, err := db.Model(table).WhereOrIn("id", g.Slice{1, 2, 3, 4}).WhereOrIn("id", g.Slice{3, 4, 5}).OrderAsc("id").All() + t.AssertNil(err) + t.Assert(len(result), 5) + t.Assert(result[0]["id"], 1) + t.Assert(result[4]["id"], 5) + }) +} + +func Test_Model_WhereOrNotIn(t *testing.T) { + table := createInitTable() + defer dropTable(table) + + gtest.C(t, func(t *gtest.T) { + result, err := db.Model(table).WhereOrNotIn("id", g.Slice{1, 2, 3, 4}).WhereOrNotIn("id", g.Slice{3, 4, 5}).OrderAsc("id").All() + t.AssertNil(err) + t.Assert(len(result), 8) + t.Assert(result[0]["id"], 1) + t.Assert(result[4]["id"], 7) + }) +} + +func Test_Model_WhereBetween(t *testing.T) { + table := createInitTable() + defer dropTable(table) + + gtest.C(t, func(t *gtest.T) { + result, err := db.Model(table).WhereBetween("id", 1, 4).WhereBetween("id", 3, 5).OrderAsc("id").All() + t.AssertNil(err) + t.Assert(len(result), 2) + t.Assert(result[0]["id"], 3) + t.Assert(result[1]["id"], 4) + }) +} + +func Test_Model_WhereNotBetween(t *testing.T) { + table := createInitTable() + defer dropTable(table) + + gtest.C(t, func(t *gtest.T) { + result, err := db.Model(table).WhereNotBetween("id", 2, 8).WhereNotBetween("id", 3, 100).OrderAsc("id").All() + t.AssertNil(err) + t.Assert(len(result), 1) + t.Assert(result[0]["id"], 1) + }) +} + +func Test_Model_WhereOrBetween(t *testing.T) { + table := createInitTable() + defer dropTable(table) + + gtest.C(t, func(t *gtest.T) { + result, err := db.Model(table).WhereOrBetween("id", 1, 4).WhereOrBetween("id", 3, 5).OrderDesc("id").All() + t.AssertNil(err) + t.Assert(len(result), 5) + t.Assert(result[0]["id"], 5) + t.Assert(result[4]["id"], 1) + }) +} + +func Test_Model_WhereOrNotBetween(t *testing.T) { + table := createInitTable() + defer dropTable(table) + //db.SetDebug(true) + gtest.C(t, func(t *gtest.T) { + result, err := db.Model(table).WhereOrNotBetween("id", 1, 4).WhereOrNotBetween("id", 3, 5).OrderDesc("id").All() + t.AssertNil(err) + t.Assert(len(result), 8) + t.Assert(result[0]["id"], 10) + t.Assert(result[4]["id"], 6) + }) +} + +func Test_Model_WhereLike(t *testing.T) { + table := createInitTable() + defer dropTable(table) + + gtest.C(t, func(t *gtest.T) { + result, err := db.Model(table).WhereLike("nickname", "name%").OrderAsc("id").All() + t.AssertNil(err) + t.Assert(len(result), TableSize) + t.Assert(result[0]["id"], 1) + t.Assert(result[TableSize-1]["id"], TableSize) + }) +} + +func Test_Model_WhereNotLike(t *testing.T) { + table := createInitTable() + defer dropTable(table) + + gtest.C(t, func(t *gtest.T) { + result, err := db.Model(table).WhereNotLike("nickname", "name%").OrderAsc("id").All() + t.AssertNil(err) + t.Assert(len(result), 0) + }) +} + +func Test_Model_WhereOrLike(t *testing.T) { + table := createInitTable() + defer dropTable(table) + + gtest.C(t, func(t *gtest.T) { + result, err := db.Model(table).WhereOrLike("nickname", "namexxx%").WhereOrLike("nickname", "name%").OrderAsc("id").All() + t.AssertNil(err) + t.Assert(len(result), TableSize) + t.Assert(result[0]["id"], 1) + t.Assert(result[TableSize-1]["id"], TableSize) + }) +} + +func Test_Model_WhereOrNotLike(t *testing.T) { + table := createInitTable() + defer dropTable(table) + + gtest.C(t, func(t *gtest.T) { + result, err := db.Model(table).WhereOrNotLike("nickname", "namexxx%").WhereOrNotLike("nickname", "name%").OrderAsc("id").All() + t.AssertNil(err) + t.Assert(len(result), TableSize) + t.Assert(result[0]["id"], 1) + t.Assert(result[TableSize-1]["id"], TableSize) + }) +} + +func Test_Model_WhereNull(t *testing.T) { + table := createInitTable() + defer dropTable(table) + + gtest.C(t, func(t *gtest.T) { + result, err := db.Model(table).WhereNull("nickname").WhereNull("passport").OrderAsc("id").All() + t.AssertNil(err) + t.Assert(len(result), 0) + }) +} + +func Test_Model_WhereNotNull(t *testing.T) { + table := createInitTable() + defer dropTable(table) + + gtest.C(t, func(t *gtest.T) { + result, err := db.Model(table).WhereNotNull("nickname").WhereNotNull("passport").OrderAsc("id").All() + t.AssertNil(err) + t.Assert(len(result), TableSize) + t.Assert(result[0]["id"], 1) + t.Assert(result[TableSize-1]["id"], TableSize) + }) +} + +func Test_Model_WhereOrNull(t *testing.T) { + table := createInitTable() + defer dropTable(table) + + gtest.C(t, func(t *gtest.T) { + result, err := db.Model(table).WhereOrNull("nickname").WhereOrNull("passport").OrderAsc("id").OrderRandom().All() + t.AssertNil(err) + t.Assert(len(result), 0) + }) +} + +func Test_Model_WhereOrNotNull(t *testing.T) { + table := createInitTable() + defer dropTable(table) + + gtest.C(t, func(t *gtest.T) { + result, err := db.Model(table).WhereOrNotNull("nickname").WhereOrNotNull("passport").OrderAsc("id").All() + t.AssertNil(err) + t.Assert(len(result), TableSize) + t.Assert(result[0]["id"], 1) + t.Assert(result[TableSize-1]["id"], TableSize) + }) +} + +func Test_Model_WhereLT(t *testing.T) { + table := createInitTable() + defer dropTable(table) + + gtest.C(t, func(t *gtest.T) { + result, err := db.Model(table).WhereLT("id", 3).OrderAsc("id").All() + t.AssertNil(err) + t.Assert(len(result), 2) + t.Assert(result[0]["id"], 1) + }) +} + +func Test_Model_WhereLTE(t *testing.T) { + table := createInitTable() + defer dropTable(table) + + gtest.C(t, func(t *gtest.T) { + result, err := db.Model(table).WhereLTE("id", 3).OrderAsc("id").All() + t.AssertNil(err) + t.Assert(len(result), 3) + t.Assert(result[0]["id"], 1) + }) +} + +func Test_Model_WhereGT(t *testing.T) { + table := createInitTable() + defer dropTable(table) + + gtest.C(t, func(t *gtest.T) { + result, err := db.Model(table).WhereGT("id", 8).OrderAsc("id").All() + t.AssertNil(err) + t.Assert(len(result), 2) + t.Assert(result[0]["id"], 9) + }) +} + +func Test_Model_WhereGTE(t *testing.T) { + table := createInitTable() + defer dropTable(table) + + gtest.C(t, func(t *gtest.T) { + result, err := db.Model(table).WhereGTE("id", 8).OrderAsc("id").All() + t.AssertNil(err) + t.Assert(len(result), 3) + t.Assert(result[0]["id"], 8) + }) +} + +func Test_Model_WhereOrLT(t *testing.T) { + table := createInitTable() + defer dropTable(table) + + gtest.C(t, func(t *gtest.T) { + result, err := db.Model(table).WhereLT("id", 3).WhereOrLT("id", 4).OrderAsc("id").All() + t.AssertNil(err) + t.Assert(len(result), 3) + t.Assert(result[0]["id"], 1) + t.Assert(result[2]["id"], 3) + }) +} + +func Test_Model_WhereOrLTE(t *testing.T) { + table := createInitTable() + defer dropTable(table) + + gtest.C(t, func(t *gtest.T) { + result, err := db.Model(table).WhereLTE("id", 3).WhereOrLTE("id", 4).OrderAsc("id").All() + t.AssertNil(err) + t.Assert(len(result), 4) + t.Assert(result[0]["id"], 1) + t.Assert(result[3]["id"], 4) + }) +} + +func Test_Model_WhereOrGT(t *testing.T) { + table := createInitTable() + defer dropTable(table) + + gtest.C(t, func(t *gtest.T) { + result, err := db.Model(table).WhereGT("id", 8).WhereOrGT("id", 7).OrderAsc("id").All() + t.AssertNil(err) + t.Assert(len(result), 3) + t.Assert(result[0]["id"], 8) + }) +} + +func Test_Model_WhereOrGTE(t *testing.T) { + table := createInitTable() + defer dropTable(table) + + gtest.C(t, func(t *gtest.T) { + result, err := db.Model(table).WhereGTE("id", 8).WhereOrGTE("id", 7).OrderAsc("id").All() + t.AssertNil(err) + t.Assert(len(result), 4) + t.Assert(result[0]["id"], 7) + }) +} + +func Test_Model_Min_Max_Avg_Sum(t *testing.T) { + table := createInitTable() + defer dropTable(table) + + gtest.C(t, func(t *gtest.T) { + result, err := db.Model(table).Min("id") + t.AssertNil(err) + t.Assert(result, 1) + }) + gtest.C(t, func(t *gtest.T) { + result, err := db.Model(table).Max("id") + t.AssertNil(err) + t.Assert(result, TableSize) + }) + gtest.C(t, func(t *gtest.T) { + result, err := db.Model(table).Avg("id") + t.AssertNil(err) + t.Assert(result, 5.5) + }) + gtest.C(t, func(t *gtest.T) { + result, err := db.Model(table).Sum("id") + t.AssertNil(err) + t.Assert(result, 55) + }) +} + +func Test_Model_CountColumn(t *testing.T) { + table := createInitTable() + defer dropTable(table) + + gtest.C(t, func(t *gtest.T) { + result, err := db.Model(table).CountColumn("id") + t.AssertNil(err) + t.Assert(result, TableSize) + }) + gtest.C(t, func(t *gtest.T) { + result, err := db.Model(table).WhereIn("id", g.Slice{1, 2, 3}).CountColumn("id") + t.AssertNil(err) + t.Assert(result, 3) + }) +} + +func Test_Model_InsertAndGetId(t *testing.T) { + table := createTable() + defer dropTable(table) + gtest.C(t, func(t *gtest.T) { + id, err := db.Model(table).Data(g.Map{ + "id": 1, + "passport": "user_1", + "password": "pass_1", + "nickname": "name_1", + }).InsertAndGetId() + t.AssertNil(err) + t.Assert(id, 1) + }) + gtest.C(t, func(t *gtest.T) { + id, err := db.Model(table).Data(g.Map{ + "passport": "user_2", + "password": "pass_2", + "nickname": "name_2", + }).InsertAndGetId() + t.AssertNil(err) + t.Assert(id, 2) + }) +} + +func Test_Model_Increment_Decrement(t *testing.T) { + table := createInitTable() + defer dropTable(table) + gtest.C(t, func(t *gtest.T) { + result, err := db.Model(table).Where("id", 1).Increment("id", 100) + t.AssertNil(err) + rows, _ := result.RowsAffected() + t.Assert(rows, 1) + }) + gtest.C(t, func(t *gtest.T) { + result, err := db.Model(table).Where("id", 101).Decrement("id", 10) + t.AssertNil(err) + rows, _ := result.RowsAffected() + t.Assert(rows, 1) + }) + gtest.C(t, func(t *gtest.T) { + count, err := db.Model(table).Where("id", 91).Count() + t.AssertNil(err) + t.Assert(count, 1) + }) +} + +func Test_Model_OnDuplicate(t *testing.T) { + table := createInitTable() + defer dropTable(table) + + // string. + gtest.C(t, func(t *gtest.T) { + data := g.Map{ + "id": 1, + "passport": "pp1", + "password": "pw1", + "nickname": "n1", + "create_time": "2016-06-06", + } + _, err := db.Model(table).OnDuplicate("passport,password").Data(data).Save() + t.AssertNil(err) + one, err := db.Model(table).FindOne(1) + t.AssertNil(err) + t.Assert(one["passport"], data["passport"]) + t.Assert(one["password"], data["password"]) + t.Assert(one["nickname"], "name_1") + }) + + // slice. + gtest.C(t, func(t *gtest.T) { + data := g.Map{ + "id": 1, + "passport": "pp1", + "password": "pw1", + "nickname": "n1", + "create_time": "2016-06-06", + } + _, err := db.Model(table).OnDuplicate(g.Slice{"passport", "password"}).Data(data).Save() + t.AssertNil(err) + one, err := db.Model(table).FindOne(1) + t.AssertNil(err) + t.Assert(one["passport"], data["passport"]) + t.Assert(one["password"], data["password"]) + t.Assert(one["nickname"], "name_1") + }) + + // map. + gtest.C(t, func(t *gtest.T) { + data := g.Map{ + "id": 1, + "passport": "pp1", + "password": "pw1", + "nickname": "n1", + "create_time": "2016-06-06", + } + _, err := db.Model(table).OnDuplicate(g.Map{ + "passport": "nickname", + "password": "nickname", + }).Data(data).Save() + t.AssertNil(err) + one, err := db.Model(table).FindOne(1) + t.AssertNil(err) + t.Assert(one["passport"], data["nickname"]) + t.Assert(one["password"], data["nickname"]) + t.Assert(one["nickname"], "name_1") + }) + + // map+raw. + gtest.C(t, func(t *gtest.T) { + data := g.MapStrStr{ + "id": "1", + "passport": "pp1", + "password": "pw1", + "nickname": "n1", + "create_time": "2016-06-06", + } + _, err := db.Model(table).OnDuplicate(g.Map{ + "passport": gdb.Raw("CONCAT(VALUES(`passport`), '1')"), + "password": gdb.Raw("CONCAT(VALUES(`password`), '2')"), + }).Data(data).Save() + t.AssertNil(err) + one, err := db.Model(table).FindOne(1) + t.AssertNil(err) + t.Assert(one["passport"], data["passport"]+"1") + t.Assert(one["password"], data["password"]+"2") + t.Assert(one["nickname"], "name_1") + }) +} + +func Test_Model_OnDuplicateEx(t *testing.T) { + table := createInitTable() + defer dropTable(table) + + // string. + gtest.C(t, func(t *gtest.T) { + data := g.Map{ + "id": 1, + "passport": "pp1", + "password": "pw1", + "nickname": "n1", + "create_time": "2016-06-06", + } + _, err := db.Model(table).OnDuplicateEx("nickname,create_time").Data(data).Save() + t.AssertNil(err) + one, err := db.Model(table).FindOne(1) + t.AssertNil(err) + t.Assert(one["passport"], data["passport"]) + t.Assert(one["password"], data["password"]) + t.Assert(one["nickname"], "name_1") + }) + + // slice. + gtest.C(t, func(t *gtest.T) { + data := g.Map{ + "id": 1, + "passport": "pp1", + "password": "pw1", + "nickname": "n1", + "create_time": "2016-06-06", + } + _, err := db.Model(table).OnDuplicateEx(g.Slice{"nickname", "create_time"}).Data(data).Save() + t.AssertNil(err) + one, err := db.Model(table).FindOne(1) + t.AssertNil(err) + t.Assert(one["passport"], data["passport"]) + t.Assert(one["password"], data["password"]) + t.Assert(one["nickname"], "name_1") + }) + + // map. + gtest.C(t, func(t *gtest.T) { + data := g.Map{ + "id": 1, + "passport": "pp1", + "password": "pw1", + "nickname": "n1", + "create_time": "2016-06-06", + } + _, err := db.Model(table).OnDuplicateEx(g.Map{ + "nickname": "nickname", + "create_time": "nickname", + }).Data(data).Save() + t.AssertNil(err) + one, err := db.Model(table).FindOne(1) + t.AssertNil(err) + t.Assert(one["passport"], data["passport"]) + t.Assert(one["password"], data["password"]) + t.Assert(one["nickname"], "name_1") + }) +} + +func Test_Model_Raw(t *testing.T) { + table := createInitTable() + defer dropTable(table) + + gtest.C(t, func(t *gtest.T) { + all, err := db. + Raw(fmt.Sprintf("select * from %s where id in (?)", table), g.Slice{1, 5, 7, 8, 9, 10}). + WhereLT("id", 8). + WhereIn("id", g.Slice{1, 2, 3, 4, 5, 6, 7}). + OrderDesc("id"). + Limit(2). + All() + t.AssertNil(err) + t.Assert(len(all), 2) + t.Assert(all[0]["id"], 7) + t.Assert(all[1]["id"], 5) + }) + + gtest.C(t, func(t *gtest.T) { + count, err := db. + Raw(fmt.Sprintf("select * from %s where id in (?)", table), g.Slice{1, 5, 7, 8, 9, 10}). + WhereLT("id", 8). + WhereIn("id", g.Slice{1, 2, 3, 4, 5, 6, 7}). + OrderDesc("id"). + Limit(2). + Count() + t.AssertNil(err) + t.Assert(count, 6) + }) +} + +func Test_Model_Handler(t *testing.T) { + table := createInitTable() + defer dropTable(table) + + gtest.C(t, func(t *gtest.T) { + m := db.Model(table).Safe().Handler( + func(m *gdb.Model) *gdb.Model { + return m.Page(0, 3) + }, + func(m *gdb.Model) *gdb.Model { + return m.Where("id", g.Slice{1, 2, 3, 4, 5, 6}) + }, + func(m *gdb.Model) *gdb.Model { + return m.OrderDesc("id") + }, + ) + all, err := m.All() + t.AssertNil(err) + t.Assert(len(all), 3) + t.Assert(all[0]["id"], 6) + t.Assert(all[2]["id"], 4) + }) +} + +func Test_Model_FieldCount(t *testing.T) { + table := createInitTable() + defer dropTable(table) + + gtest.C(t, func(t *gtest.T) { + all, err := db.Model(table).Fields("id").FieldCount("id", "total").Group("id").OrderAsc("id").All() + t.AssertNil(err) + t.Assert(len(all), TableSize) + t.Assert(all[0]["id"], 1) + t.Assert(all[0]["total"], 1) + }) +} + +func Test_Model_FieldMax(t *testing.T) { + table := createInitTable() + defer dropTable(table) + + gtest.C(t, func(t *gtest.T) { + all, err := db.Model(table).Fields("id").FieldMax("id", "total").Group("id").OrderAsc("id").All() + t.AssertNil(err) + t.Assert(len(all), TableSize) + t.Assert(all[0]["id"], 1) + t.Assert(all[0]["total"], 1) + }) +} + +func Test_Model_FieldMin(t *testing.T) { + table := createInitTable() + defer dropTable(table) + + gtest.C(t, func(t *gtest.T) { + all, err := db.Model(table).Fields("id").FieldMin("id", "total").Group("id").OrderAsc("id").All() + t.AssertNil(err) + t.Assert(len(all), TableSize) + t.Assert(all[0]["id"], 1) + t.Assert(all[0]["total"], 1) + }) +} + +func Test_Model_FieldAvg(t *testing.T) { + table := createInitTable() + defer dropTable(table) + + gtest.C(t, func(t *gtest.T) { + all, err := db.Model(table).Fields("id").FieldAvg("id", "total").Group("id").OrderAsc("id").All() + t.AssertNil(err) + t.Assert(len(all), TableSize) + t.Assert(all[0]["id"], 1) + t.Assert(all[0]["total"], 1) + }) +} + +// https://github.com/gogf/gf/issues/1387 +func Test_Model_GTime_DefaultValue(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 *gtime.Time + } + data := User{ + Id: 1, + Passport: "user_1", + Password: "pass_1", + Nickname: "name_1", + } + // Insert + _, err := db.Model(table).Data(data).Insert() + t.AssertNil(err) + + // Select + var ( + user *User + ) + err = db.Model(table).Scan(&user) + t.AssertNil(err) + t.Assert(user.Passport, data.Passport) + t.Assert(user.Password, data.Password) + t.Assert(user.CreateTime, data.CreateTime) + t.Assert(user.Nickname, data.Nickname) + + // Insert + user.Id = 2 + _, err = db.Model(table).Data(user).Insert() + t.AssertNil(err) + }) +} diff --git a/database/gdb/gdb_z_mysql_raw_test.go b/database/gdb/gdb_z_mysql_raw_type_test.go similarity index 82% rename from database/gdb/gdb_z_mysql_raw_test.go rename to database/gdb/gdb_z_mysql_raw_type_test.go index ac6a29367..3b3f3dc05 100644 --- a/database/gdb/gdb_z_mysql_raw_test.go +++ b/database/gdb/gdb_z_mysql_raw_type_test.go @@ -1,4 +1,4 @@ -// Copyright GoFrame Author(https://github.com/gogf/gf). All Rights Reserved. +// Copyright GoFrame Author(https://goframe.org). All Rights Reserved. // // This Source Code Form is subject to the terms of the MIT License. // If a copy of the MIT was not distributed with this file, @@ -19,15 +19,15 @@ func Test_Insert_Raw(t *testing.T) { defer dropTable(table) gtest.C(t, func(t *gtest.T) { - user := db.Table(table) - result, err := user.Filter().Data(g.Map{ + user := db.Model(table) + result, err := user.Data(g.Map{ "id": gdb.Raw("id+2"), "passport": "port_1", "password": "pass_1", "nickname": "name_1", "create_time": gdb.Raw("now()"), }).Insert() - t.Assert(err, nil) + t.AssertNil(err) n, _ := result.LastInsertId() t.Assert(n, 2) }) @@ -38,8 +38,8 @@ func Test_BatchInsert_Raw(t *testing.T) { defer dropTable(table) gtest.C(t, func(t *gtest.T) { - user := db.Table(table) - result, err := user.Filter().Data( + user := db.Model(table) + result, err := user.Data( g.List{ g.Map{ "id": gdb.Raw("id+2"), @@ -57,7 +57,7 @@ func Test_BatchInsert_Raw(t *testing.T) { }, }, ).Insert() - t.Assert(err, nil) + t.AssertNil(err) n, _ := result.LastInsertId() t.Assert(n, 4) }) @@ -68,19 +68,19 @@ func Test_Update_Raw(t *testing.T) { defer dropTable(table) gtest.C(t, func(t *gtest.T) { - user := db.Table(table) + user := db.Model(table) result, err := user.Data(g.Map{ "id": gdb.Raw("id+100"), "create_time": gdb.Raw("now()"), }).Where("id", 1).Update() - t.Assert(err, nil) + t.AssertNil(err) n, _ := result.RowsAffected() t.Assert(n, 1) }) gtest.C(t, func(t *gtest.T) { - user := db.Table(table) + user := db.Model(table) n, err := user.Where("id", 101).Count() - t.Assert(err, nil) + t.AssertNil(err) t.Assert(n, 1) }) } diff --git a/database/gdb/gdb_z_mysql_struct_test.go b/database/gdb/gdb_z_mysql_struct_test.go index d11648aa3..5f12869a2 100644 --- a/database/gdb/gdb_z_mysql_struct_test.go +++ b/database/gdb/gdb_z_mysql_struct_test.go @@ -1,4 +1,4 @@ -// Copyright GoFrame Author(https://github.com/gogf/gf). All Rights Reserved. +// Copyright GoFrame Author(https://goframe.org). All Rights Reserved. // // This Source Code Form is subject to the terms of the MIT License. // If a copy of the MIT was not distributed with this file, @@ -7,14 +7,19 @@ package gdb_test import ( + "database/sql" + "github.com/gogf/gf/database/gdb" + "github.com/gogf/gf/errors/gcode" + "github.com/gogf/gf/errors/gerror" "github.com/gogf/gf/frame/g" "github.com/gogf/gf/os/gtime" "github.com/gogf/gf/test/gtest" "github.com/gogf/gf/util/gconv" + "reflect" "testing" ) -func Test_Model_Inherit_Insert(t *testing.T) { +func Test_Model_Embedded_Insert(t *testing.T) { table := createTable() defer dropTable(table) @@ -30,7 +35,7 @@ func Test_Model_Inherit_Insert(t *testing.T) { Password string `json:"password"` Nickname string `json:"nickname"` } - result, err := db.Table(table).Filter().Data(User{ + result, err := db.Model(table).Data(User{ Passport: "john-test", Password: "123456", Nickname: "John", @@ -40,16 +45,16 @@ func Test_Model_Inherit_Insert(t *testing.T) { CreateTime: gtime.Now().String(), }, }).Insert() - t.Assert(err, nil) + t.AssertNil(err) n, _ := result.RowsAffected() t.Assert(n, 1) - value, err := db.Table(table).Fields("passport").Where("id=100").Value() - t.Assert(err, nil) + value, err := db.Model(table).Fields("passport").Where("id=100").Value() + t.AssertNil(err) t.Assert(value.String(), "john-test") }) } -func Test_Model_Inherit_MapToStruct(t *testing.T) { +func Test_Model_Embedded_MapToStruct(t *testing.T) { table := createTable() defer dropTable(table) @@ -76,13 +81,13 @@ func Test_Model_Inherit_MapToStruct(t *testing.T) { "nickname": "T1", "create_time": gtime.Now().String(), } - result, err := db.Table(table).Filter().Data(data).Insert() - t.Assert(err, nil) + result, err := db.Model(table).Data(data).Insert() + t.AssertNil(err) n, _ := result.RowsAffected() t.Assert(n, 1) - one, err := db.Table(table).Where("id=100").One() - t.Assert(err, nil) + one, err := db.Model(table).Where("id=100").One() + t.AssertNil(err) user := new(User) @@ -108,11 +113,11 @@ func Test_Struct_Pointer_Attribute(t *testing.T) { } gtest.C(t, func(t *gtest.T) { - one, err := db.Table(table).FindOne(1) - t.Assert(err, nil) + one, err := db.Model(table).FindOne(1) + t.AssertNil(err) user := new(User) err = one.Struct(user) - t.Assert(err, nil) + t.AssertNil(err) t.Assert(*user.Id, 1) t.Assert(*user.Passport, "user_1") t.Assert(*user.Password, "pass_1") @@ -120,8 +125,8 @@ func Test_Struct_Pointer_Attribute(t *testing.T) { }) gtest.C(t, func(t *gtest.T) { user := new(User) - err := db.Table(table).Struct(user, "id=1") - t.Assert(err, nil) + err := db.Model(table).Scan(user, "id=1") + t.AssertNil(err) t.Assert(*user.Id, 1) t.Assert(*user.Passport, "user_1") t.Assert(*user.Password, "pass_1") @@ -129,8 +134,8 @@ func Test_Struct_Pointer_Attribute(t *testing.T) { }) gtest.C(t, func(t *gtest.T) { var user *User - err := db.Table(table).Struct(&user, "id=1") - t.Assert(err, nil) + err := db.Model(table).Scan(&user, "id=1") + t.AssertNil(err) t.Assert(*user.Id, 1) t.Assert(*user.Passport, "user_1") t.Assert(*user.Password, "pass_1") @@ -150,11 +155,11 @@ func Test_Structs_Pointer_Attribute(t *testing.T) { } // All gtest.C(t, func(t *gtest.T) { - one, err := db.Table(table).All("id < 3") - t.Assert(err, nil) + one, err := db.Model(table).All("id < 3") + t.AssertNil(err) users := make([]User, 0) err = one.Structs(&users) - t.Assert(err, nil) + t.AssertNil(err) t.Assert(len(users), 2) t.Assert(*users[0].Id, 1) t.Assert(*users[0].Passport, "user_1") @@ -162,11 +167,11 @@ func Test_Structs_Pointer_Attribute(t *testing.T) { t.Assert(users[0].Nickname, "name_1") }) gtest.C(t, func(t *gtest.T) { - one, err := db.Table(table).All("id < 3") - t.Assert(err, nil) + one, err := db.Model(table).All("id < 3") + t.AssertNil(err) users := make([]*User, 0) err = one.Structs(&users) - t.Assert(err, nil) + t.AssertNil(err) t.Assert(len(users), 2) t.Assert(*users[0].Id, 1) t.Assert(*users[0].Passport, "user_1") @@ -175,10 +180,10 @@ func Test_Structs_Pointer_Attribute(t *testing.T) { }) gtest.C(t, func(t *gtest.T) { var users []User - one, err := db.Table(table).All("id < 3") - t.Assert(err, nil) + one, err := db.Model(table).All("id < 3") + t.AssertNil(err) err = one.Structs(&users) - t.Assert(err, nil) + t.AssertNil(err) t.Assert(len(users), 2) t.Assert(*users[0].Id, 1) t.Assert(*users[0].Passport, "user_1") @@ -187,10 +192,10 @@ func Test_Structs_Pointer_Attribute(t *testing.T) { }) gtest.C(t, func(t *gtest.T) { var users []*User - one, err := db.Table(table).All("id < 3") - t.Assert(err, nil) + one, err := db.Model(table).All("id < 3") + t.AssertNil(err) err = one.Structs(&users) - t.Assert(err, nil) + t.AssertNil(err) t.Assert(len(users), 2) t.Assert(*users[0].Id, 1) t.Assert(*users[0].Passport, "user_1") @@ -200,8 +205,8 @@ func Test_Structs_Pointer_Attribute(t *testing.T) { // Structs gtest.C(t, func(t *gtest.T) { users := make([]User, 0) - err := db.Table(table).Structs(&users, "id < 3") - t.Assert(err, nil) + err := db.Model(table).Scan(&users, "id < 3") + t.AssertNil(err) t.Assert(len(users), 2) t.Assert(*users[0].Id, 1) t.Assert(*users[0].Passport, "user_1") @@ -210,8 +215,8 @@ func Test_Structs_Pointer_Attribute(t *testing.T) { }) gtest.C(t, func(t *gtest.T) { users := make([]*User, 0) - err := db.Table(table).Structs(&users, "id < 3") - t.Assert(err, nil) + err := db.Model(table).Scan(&users, "id < 3") + t.AssertNil(err) t.Assert(len(users), 2) t.Assert(*users[0].Id, 1) t.Assert(*users[0].Passport, "user_1") @@ -220,8 +225,8 @@ func Test_Structs_Pointer_Attribute(t *testing.T) { }) gtest.C(t, func(t *gtest.T) { var users []User - err := db.Table(table).Structs(&users, "id < 3") - t.Assert(err, nil) + err := db.Model(table).Scan(&users, "id < 3") + t.AssertNil(err) t.Assert(len(users), 2) t.Assert(*users[0].Id, 1) t.Assert(*users[0].Passport, "user_1") @@ -230,8 +235,8 @@ func Test_Structs_Pointer_Attribute(t *testing.T) { }) gtest.C(t, func(t *gtest.T) { var users []*User - err := db.Table(table).Structs(&users, "id < 3") - t.Assert(err, nil) + err := db.Model(table).Scan(&users, "id < 3") + t.AssertNil(err) t.Assert(len(users), 2) t.Assert(*users[0].Id, 1) t.Assert(*users[0].Passport, "user_1") @@ -252,24 +257,25 @@ func Test_Struct_Empty(t *testing.T) { } gtest.C(t, func(t *gtest.T) { - one, err := db.Table(table).Where("id=100").One() - t.Assert(err, nil) user := new(User) - t.AssertNE(one.Struct(user), nil) + err := db.Model(table).Where("id=100").Scan(user) + t.Assert(err, sql.ErrNoRows) + t.AssertNE(user, nil) }) gtest.C(t, func(t *gtest.T) { - one, err := db.Table(table).Where("id=100").One() - t.Assert(err, nil) + one, err := db.Model(table).Where("id=100").One() + t.AssertNil(err) var user *User - t.AssertNE(one.Struct(&user), nil) + t.Assert(one.Struct(&user), nil) + t.Assert(user, nil) }) gtest.C(t, func(t *gtest.T) { - one, err := db.Table(table).Where("id=100").One() - t.Assert(err, nil) var user *User - t.AssertNE(one.Struct(user), nil) + err := db.Model(table).Where("id=100").Scan(&user) + t.AssertNil(err) + t.Assert(user, nil) }) } @@ -285,39 +291,39 @@ func Test_Structs_Empty(t *testing.T) { } gtest.C(t, func(t *gtest.T) { - all, err := db.Table(table).Where("id>100").All() - t.Assert(err, nil) + all, err := db.Model(table).Where("id>100").All() + t.AssertNil(err) users := make([]User, 0) t.Assert(all.Structs(&users), nil) }) gtest.C(t, func(t *gtest.T) { - all, err := db.Table(table).Where("id>100").All() - t.Assert(err, nil) + all, err := db.Model(table).Where("id>100").All() + t.AssertNil(err) users := make([]User, 10) - t.AssertNE(all.Structs(&users), nil) + t.Assert(all.Structs(&users), nil) }) gtest.C(t, func(t *gtest.T) { - all, err := db.Table(table).Where("id>100").All() - t.Assert(err, nil) + all, err := db.Model(table).Where("id>100").All() + t.AssertNil(err) var users []User t.Assert(all.Structs(&users), nil) }) gtest.C(t, func(t *gtest.T) { - all, err := db.Table(table).Where("id>100").All() - t.Assert(err, nil) + all, err := db.Model(table).Where("id>100").All() + t.AssertNil(err) users := make([]*User, 0) t.Assert(all.Structs(&users), nil) }) gtest.C(t, func(t *gtest.T) { - all, err := db.Table(table).Where("id>100").All() - t.Assert(err, nil) + all, err := db.Model(table).Where("id>100").All() + t.AssertNil(err) users := make([]*User, 10) t.Assert(all.Structs(&users), nil) }) gtest.C(t, func(t *gtest.T) { - all, err := db.Table(table).Where("id>100").All() - t.Assert(err, nil) + all, err := db.Model(table).Where("id>100").All() + t.AssertNil(err) var users []*User t.Assert(all.Structs(&users), nil) }) @@ -341,21 +347,136 @@ func (st *MyTimeSt) UnmarshalValue(v interface{}) error { return nil } -func Test_Model_Scan_CustomType(t *testing.T) { +func Test_Model_Scan_CustomType_Time(t *testing.T) { table := createInitTable() defer dropTable(table) gtest.C(t, func(t *gtest.T) { st := new(MyTimeSt) - err := db.Table(table).Fields("create_time").Scan(st) - t.Assert(err, nil) + err := db.Model(table).Fields("create_time").Scan(st) + t.AssertNil(err) t.Assert(st.CreateTime.String(), "2018-10-24 10:00:00") }) gtest.C(t, func(t *gtest.T) { var stSlice []*MyTimeSt - err := db.Table(table).Fields("create_time").Scan(&stSlice) - t.Assert(err, nil) - t.Assert(len(stSlice), SIZE) + err := db.Model(table).Fields("create_time").Scan(&stSlice) + t.AssertNil(err) + t.Assert(len(stSlice), TableSize) t.Assert(stSlice[0].CreateTime.String(), "2018-10-24 10:00:00") t.Assert(stSlice[9].CreateTime.String(), "2018-10-24 10:00:00") }) } + +func Test_Model_Scan_CustomType_String(t *testing.T) { + type MyString string + + type MyStringSt struct { + Passport MyString + } + + table := createInitTable() + defer dropTable(table) + gtest.C(t, func(t *gtest.T) { + st := new(MyStringSt) + err := db.Model(table).Fields("Passport").WherePri(1).Scan(st) + t.AssertNil(err) + t.Assert(st.Passport, "user_1") + }) + gtest.C(t, func(t *gtest.T) { + var sts []MyStringSt + err := db.Model(table).Fields("Passport").Order("id asc").Scan(&sts) + t.AssertNil(err) + t.Assert(len(sts), TableSize) + t.Assert(sts[0].Passport, "user_1") + }) +} + +type User struct { + Id int + Passport string + Password string + Nickname string + CreateTime *gtime.Time +} + +func (user *User) UnmarshalValue(value interface{}) error { + if record, ok := value.(gdb.Record); ok { + *user = User{ + Id: record["id"].Int(), + Passport: record["passport"].String(), + Password: "", + Nickname: record["nickname"].String(), + CreateTime: record["create_time"].GTime(), + } + return nil + } + return gerror.NewCodef(gcode.CodeInvalidParameter, `unsupported value type for UnmarshalValue: %v`, reflect.TypeOf(value)) +} + +func Test_Model_Scan_UnmarshalValue(t *testing.T) { + table := createInitTable() + defer dropTable(table) + gtest.C(t, func(t *gtest.T) { + var users []*User + err := db.Model(table).Order("id asc").Scan(&users) + t.AssertNil(err) + t.Assert(len(users), TableSize) + t.Assert(users[0].Id, 1) + t.Assert(users[0].Passport, "user_1") + t.Assert(users[0].Password, "") + t.Assert(users[0].Nickname, "name_1") + t.Assert(users[0].CreateTime.String(), CreateTime) + + t.Assert(users[9].Id, 10) + t.Assert(users[9].Passport, "user_10") + t.Assert(users[9].Password, "") + t.Assert(users[9].Nickname, "name_10") + t.Assert(users[9].CreateTime.String(), CreateTime) + }) +} + +func Test_Model_Scan_Map(t *testing.T) { + table := createInitTable() + defer dropTable(table) + + gtest.C(t, func(t *gtest.T) { + var users []*User + err := db.Model(table).Order("id asc").Scan(&users) + t.AssertNil(err) + t.Assert(len(users), TableSize) + t.Assert(users[0].Id, 1) + t.Assert(users[0].Passport, "user_1") + t.Assert(users[0].Password, "") + t.Assert(users[0].Nickname, "name_1") + t.Assert(users[0].CreateTime.String(), CreateTime) + + t.Assert(users[9].Id, 10) + t.Assert(users[9].Passport, "user_10") + t.Assert(users[9].Password, "") + t.Assert(users[9].Nickname, "name_10") + t.Assert(users[9].CreateTime.String(), CreateTime) + }) +} + +func Test_Scan_AutoFilteringByStructAttributes(t *testing.T) { + table := createInitTable() + defer dropTable(table) + + type User struct { + Id int + Passport string + } + //db.SetDebug(true) + gtest.C(t, func(t *gtest.T) { + var user *User + err := db.Model(table).OrderAsc("id").Scan(&user) + t.AssertNil(err) + t.Assert(user.Id, 1) + }) + gtest.C(t, func(t *gtest.T) { + var users []User + err := db.Model(table).OrderAsc("id").Scan(&users) + t.AssertNil(err) + t.Assert(len(users), TableSize) + t.Assert(users[0].Id, 1) + }) +} diff --git a/database/gdb/gdb_z_mysql_subquery_test.go b/database/gdb/gdb_z_mysql_subquery_test.go new file mode 100644 index 000000000..6cb76abf2 --- /dev/null +++ b/database/gdb/gdb_z_mysql_subquery_test.go @@ -0,0 +1,66 @@ +// Copyright GoFrame Author(https://goframe.org). All Rights Reserved. +// +// This Source Code Form is subject to the terms of the MIT License. +// If a copy of the MIT was not distributed with this file, +// You can obtain one at https://github.com/gogf/gf. + +package gdb_test + +import ( + "github.com/gogf/gf/frame/g" + "testing" + + "github.com/gogf/gf/test/gtest" +) + +func Test_Model_SubQuery_Where(t *testing.T) { + table := createInitTable() + defer dropTable(table) + + gtest.C(t, func(t *gtest.T) { + r, err := db.Model(table).Where( + "id in ?", + db.Model(table).Fields("id").Where("id", g.Slice{1, 3, 5}), + ).OrderAsc("id").All() + t.AssertNil(err) + + t.Assert(len(r), 3) + t.Assert(r[0]["id"], 1) + t.Assert(r[1]["id"], 3) + t.Assert(r[2]["id"], 5) + }) +} + +func Test_Model_SubQuery_Having(t *testing.T) { + table := createInitTable() + defer dropTable(table) + + gtest.C(t, func(t *gtest.T) { + r, err := db.Model(table).Where( + "id in ?", + db.Model(table).Fields("id").Where("id", g.Slice{1, 3, 5}), + ).Having( + "id > ?", + db.Model(table).Fields("MAX(id)").Where("id", g.Slice{1, 3}), + ).OrderAsc("id").All() + t.AssertNil(err) + + t.Assert(len(r), 1) + t.Assert(r[0]["id"], 5) + }) +} + +func Test_Model_SubQuery_Model(t *testing.T) { + table := createInitTable() + defer dropTable(table) + + gtest.C(t, func(t *gtest.T) { + subQuery1 := db.Model(table).Where("id", g.Slice{1, 3, 5}) + subQuery2 := db.Model(table).Where("id", g.Slice{5, 7, 9}) + r, err := db.Model("? AS a, ? AS b", subQuery1, subQuery2).Fields("a.id").Where("a.id=b.id").OrderAsc("id").All() + t.AssertNil(err) + + t.Assert(len(r), 1) + t.Assert(r[0]["id"], 5) + }) +} diff --git a/database/gdb/gdb_z_mysql_time_maintain_test.go b/database/gdb/gdb_z_mysql_time_maintain_test.go index 4c4763acb..860518fc7 100644 --- a/database/gdb/gdb_z_mysql_time_maintain_test.go +++ b/database/gdb/gdb_z_mysql_time_maintain_test.go @@ -1,4 +1,4 @@ -// Copyright GoFrame Author(https://github.com/gogf/gf). All Rights Reserved. +// Copyright GoFrame Author(https://goframe.org). All Rights Reserved. // // This Source Code Form is subject to the terms of the MIT License. // If a copy of the MIT was not distributed with this file, @@ -17,8 +17,9 @@ import ( "github.com/gogf/gf/test/gtest" ) +// CreateAt/UpdateAt/DeleteAt func Test_SoftCreateUpdateDeleteTime(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, @@ -39,18 +40,18 @@ CREATE TABLE %s ( "id": 1, "name": "name_1", } - r, err := db.Table(table).Data(dataInsert).Insert() - t.Assert(err, nil) + r, err := db.Model(table).Data(dataInsert).Insert() + t.AssertNil(err) n, _ := r.RowsAffected() t.Assert(n, 1) - oneInsert, err := db.Table(table).FindOne(1) - t.Assert(err, nil) + oneInsert, err := db.Model(table).FindOne(1) + t.AssertNil(err) t.Assert(oneInsert["id"].Int(), 1) t.Assert(oneInsert["name"].String(), "name_1") t.Assert(oneInsert["delete_at"].String(), "") t.AssertGE(oneInsert["create_at"].GTime().Timestamp(), gtime.Timestamp()-2) - t.AssertGE(oneInsert["update_at"].GTime().Timestamp(), gtime.Timestamp()) + t.AssertGE(oneInsert["update_at"].GTime().Timestamp(), gtime.Timestamp()-2) // For time asserting purpose. time.Sleep(2 * time.Second) @@ -60,19 +61,19 @@ CREATE TABLE %s ( "id": 1, "name": "name_10", } - r, err = db.Table(table).Data(dataSave).Save() - t.Assert(err, nil) + r, err = db.Model(table).Data(dataSave).Save() + t.AssertNil(err) n, _ = r.RowsAffected() t.Assert(n, 2) - oneSave, err := db.Table(table).FindOne(1) - t.Assert(err, nil) + oneSave, err := db.Model(table).FindOne(1) + t.AssertNil(err) t.Assert(oneSave["id"].Int(), 1) t.Assert(oneSave["name"].String(), "name_10") t.Assert(oneSave["delete_at"].String(), "") t.Assert(oneSave["create_at"].GTime().Timestamp(), oneInsert["create_at"].GTime().Timestamp()) t.AssertNE(oneSave["update_at"].GTime().Timestamp(), oneInsert["update_at"].GTime().Timestamp()) - t.AssertGE(oneSave["update_at"].GTime().Timestamp(), gtime.Now().Timestamp()-2) + t.AssertGE(oneSave["update_at"].GTime().Timestamp(), gtime.Timestamp()-2) // For time asserting purpose. time.Sleep(2 * time.Second) @@ -81,31 +82,31 @@ CREATE TABLE %s ( dataUpdate := g.Map{ "name": "name_1000", } - r, err = db.Table(table).Data(dataUpdate).WherePri(1).Update() - t.Assert(err, nil) + r, err = db.Model(table).Data(dataUpdate).WherePri(1).Update() + t.AssertNil(err) n, _ = r.RowsAffected() t.Assert(n, 1) - oneUpdate, err := db.Table(table).FindOne(1) - t.Assert(err, nil) + oneUpdate, err := db.Model(table).FindOne(1) + t.AssertNil(err) t.Assert(oneUpdate["id"].Int(), 1) t.Assert(oneUpdate["name"].String(), "name_1000") t.Assert(oneUpdate["delete_at"].String(), "") t.Assert(oneUpdate["create_at"].GTime().Timestamp(), oneInsert["create_at"].GTime().Timestamp()) - t.AssertGE(oneUpdate["update_at"].GTime().Timestamp(), gtime.Now().Timestamp()-2) + t.AssertGE(oneUpdate["update_at"].GTime().Timestamp(), gtime.Timestamp()-2) // Replace dataReplace := g.Map{ "id": 1, "name": "name_100", } - r, err = db.Table(table).Data(dataReplace).Replace() - t.Assert(err, nil) + r, err = db.Model(table).Data(dataReplace).Replace() + t.AssertNil(err) n, _ = r.RowsAffected() t.Assert(n, 2) - oneReplace, err := db.Table(table).FindOne(1) - t.Assert(err, nil) + oneReplace, err := db.Model(table).FindOne(1) + t.AssertNil(err) t.Assert(oneReplace["id"].Int(), 1) t.Assert(oneReplace["name"].String(), "name_100") t.Assert(oneReplace["delete_at"].String(), "") @@ -116,36 +117,311 @@ CREATE TABLE %s ( time.Sleep(2 * time.Second) // Delete - r, err = db.Table(table).Delete("id", 1) - t.Assert(err, nil) + r, err = db.Model(table).Delete("id", 1) + t.AssertNil(err) n, _ = r.RowsAffected() t.Assert(n, 1) // Delete Select - one4, err := db.Table(table).FindOne(1) - t.Assert(err, nil) + one4, err := db.Model(table).FindOne(1) + t.AssertNil(err) t.Assert(len(one4), 0) - one5, err := db.Table(table).Unscoped().FindOne(1) - t.Assert(err, nil) + one5, err := db.Model(table).Unscoped().FindOne(1) + t.AssertNil(err) t.Assert(one5["id"].Int(), 1) - t.AssertGE(one5["delete_at"].GTime().Timestamp(), gtime.Now().Timestamp()-2) + t.AssertGE(one5["delete_at"].GTime().Timestamp(), gtime.Timestamp()-2) // Delete Count - i, err := db.Table(table).FindCount() - t.Assert(err, nil) + i, err := db.Model(table).FindCount() + t.AssertNil(err) t.Assert(i, 0) - i, err = db.Table(table).Unscoped().FindCount() - t.Assert(err, nil) + i, err = db.Model(table).Unscoped().FindCount() + t.AssertNil(err) t.Assert(i, 1) // Delete Unscoped - r, err = db.Table(table).Unscoped().Delete("id", 1) - t.Assert(err, nil) + r, err = db.Model(table).Unscoped().Delete("id", 1) + t.AssertNil(err) n, _ = r.RowsAffected() t.Assert(n, 1) - one6, err := db.Table(table).Unscoped().FindOne(1) - t.Assert(err, nil) + one6, err := db.Model(table).Unscoped().FindOne(1) + t.AssertNil(err) t.Assert(len(one6), 0) - i, err = db.Table(table).Unscoped().FindCount() - t.Assert(err, nil) + i, err = db.Model(table).Unscoped().FindCount() + t.AssertNil(err) + t.Assert(i, 0) + }) +} + +// CreatedAt/UpdatedAt/DeletedAt +func Test_SoftCreatedUpdatedDeletedTime_Map(t *testing.T) { + table := "time_test_table_" + gtime.TimestampNanoStr() + if _, err := db.Exec(fmt.Sprintf(` +CREATE TABLE %s ( + id int(11) NOT NULL, + name varchar(45) DEFAULT NULL, + created_at datetime DEFAULT NULL, + updated_at datetime DEFAULT NULL, + deleted_at datetime DEFAULT NULL, + PRIMARY KEY (id) +) ENGINE=InnoDB DEFAULT CHARSET=utf8; + `, table)); err != nil { + gtest.Error(err) + } + defer dropTable(table) + + gtest.C(t, func(t *gtest.T) { + // Insert + dataInsert := g.Map{ + "id": 1, + "name": "name_1", + } + r, err := db.Model(table).Data(dataInsert).Insert() + t.AssertNil(err) + n, _ := r.RowsAffected() + t.Assert(n, 1) + + oneInsert, err := db.Model(table).FindOne(1) + t.AssertNil(err) + t.Assert(oneInsert["id"].Int(), 1) + t.Assert(oneInsert["name"].String(), "name_1") + t.Assert(oneInsert["deleted_at"].String(), "") + t.AssertGE(oneInsert["created_at"].GTime().Timestamp(), gtime.Timestamp()-2) + t.AssertGE(oneInsert["updated_at"].GTime().Timestamp(), gtime.Timestamp()-2) + + // For time asserting purpose. + time.Sleep(2 * time.Second) + + // Save + dataSave := g.Map{ + "id": 1, + "name": "name_10", + } + r, err = db.Model(table).Data(dataSave).Save() + t.AssertNil(err) + n, _ = r.RowsAffected() + t.Assert(n, 2) + + oneSave, err := db.Model(table).FindOne(1) + t.AssertNil(err) + t.Assert(oneSave["id"].Int(), 1) + t.Assert(oneSave["name"].String(), "name_10") + t.Assert(oneSave["deleted_at"].String(), "") + t.Assert(oneSave["created_at"].GTime().Timestamp(), oneInsert["created_at"].GTime().Timestamp()) + t.AssertNE(oneSave["updated_at"].GTime().Timestamp(), oneInsert["updated_at"].GTime().Timestamp()) + t.AssertGE(oneSave["updated_at"].GTime().Timestamp(), gtime.Timestamp()-2) + + // For time asserting purpose. + time.Sleep(2 * time.Second) + + // Update + dataUpdate := g.Map{ + "name": "name_1000", + } + r, err = db.Model(table).Data(dataUpdate).WherePri(1).Update() + t.AssertNil(err) + n, _ = r.RowsAffected() + t.Assert(n, 1) + + oneUpdate, err := db.Model(table).FindOne(1) + t.AssertNil(err) + t.Assert(oneUpdate["id"].Int(), 1) + t.Assert(oneUpdate["name"].String(), "name_1000") + t.Assert(oneUpdate["deleted_at"].String(), "") + t.Assert(oneUpdate["created_at"].GTime().Timestamp(), oneInsert["created_at"].GTime().Timestamp()) + t.AssertGE(oneUpdate["updated_at"].GTime().Timestamp(), gtime.Timestamp()-2) + + // Replace + dataReplace := g.Map{ + "id": 1, + "name": "name_100", + } + r, err = db.Model(table).Data(dataReplace).Replace() + t.AssertNil(err) + n, _ = r.RowsAffected() + t.Assert(n, 2) + + oneReplace, err := db.Model(table).FindOne(1) + t.AssertNil(err) + t.Assert(oneReplace["id"].Int(), 1) + t.Assert(oneReplace["name"].String(), "name_100") + t.Assert(oneReplace["deleted_at"].String(), "") + t.AssertGE(oneReplace["created_at"].GTime().Timestamp(), oneInsert["created_at"].GTime().Timestamp()) + t.AssertGE(oneReplace["updated_at"].GTime().Timestamp(), oneInsert["updated_at"].GTime().Timestamp()) + + // For time asserting purpose. + time.Sleep(2 * time.Second) + + // Delete + r, err = db.Model(table).Delete("id", 1) + t.AssertNil(err) + n, _ = r.RowsAffected() + t.Assert(n, 1) + // Delete Select + one4, err := db.Model(table).FindOne(1) + t.AssertNil(err) + t.Assert(len(one4), 0) + one5, err := db.Model(table).Unscoped().FindOne(1) + t.AssertNil(err) + t.Assert(one5["id"].Int(), 1) + t.AssertGE(one5["deleted_at"].GTime().Timestamp(), gtime.Timestamp()-2) + // Delete Count + i, err := db.Model(table).FindCount() + t.AssertNil(err) + t.Assert(i, 0) + i, err = db.Model(table).Unscoped().FindCount() + t.AssertNil(err) + t.Assert(i, 1) + + // Delete Unscoped + r, err = db.Model(table).Unscoped().Delete("id", 1) + t.AssertNil(err) + n, _ = r.RowsAffected() + t.Assert(n, 1) + one6, err := db.Model(table).Unscoped().FindOne(1) + t.AssertNil(err) + t.Assert(len(one6), 0) + i, err = db.Model(table).Unscoped().FindCount() + t.AssertNil(err) + t.Assert(i, 0) + }) +} + +// CreatedAt/UpdatedAt/DeletedAt +func Test_SoftCreatedUpdatedDeletedTime_Struct(t *testing.T) { + table := "time_test_table_" + gtime.TimestampNanoStr() + if _, err := db.Exec(fmt.Sprintf(` +CREATE TABLE %s ( + id int(11) NOT NULL, + name varchar(45) DEFAULT NULL, + created_at datetime DEFAULT NULL, + updated_at datetime DEFAULT NULL, + deleted_at datetime DEFAULT NULL, + PRIMARY KEY (id) +) ENGINE=InnoDB DEFAULT CHARSET=utf8; + `, table)); err != nil { + gtest.Error(err) + } + defer dropTable(table) + + type User struct { + Id int + Name string + CreatedAT *gtime.Time + UpdatedAT *gtime.Time + DeletedAT *gtime.Time + } + gtest.C(t, func(t *gtest.T) { + // Insert + dataInsert := User{ + Id: 1, + Name: "name_1", + } + r, err := db.Model(table).Data(dataInsert).Insert() + t.AssertNil(err) + n, _ := r.RowsAffected() + t.Assert(n, 1) + + oneInsert, err := db.Model(table).FindOne(1) + t.AssertNil(err) + t.Assert(oneInsert["id"].Int(), 1) + t.Assert(oneInsert["name"].String(), "name_1") + t.Assert(oneInsert["deleted_at"].String(), "") + t.AssertGE(oneInsert["created_at"].GTime().Timestamp(), gtime.Timestamp()-2) + t.AssertGE(oneInsert["updated_at"].GTime().Timestamp(), gtime.Timestamp()-2) + + // For time asserting purpose. + time.Sleep(2 * time.Second) + + // Save + dataSave := User{ + Id: 1, + Name: "name_10", + } + r, err = db.Model(table).Data(dataSave).OmitEmpty().Save() + t.AssertNil(err) + n, _ = r.RowsAffected() + t.Assert(n, 2) + + oneSave, err := db.Model(table).FindOne(1) + t.AssertNil(err) + t.Assert(oneSave["id"].Int(), 1) + t.Assert(oneSave["name"].String(), "name_10") + t.Assert(oneSave["deleted_at"].String(), "") + t.Assert(oneSave["created_at"].GTime().Timestamp(), oneInsert["created_at"].GTime().Timestamp()) + t.AssertNE(oneSave["updated_at"].GTime().Timestamp(), oneInsert["updated_at"].GTime().Timestamp()) + t.AssertGE(oneSave["updated_at"].GTime().Timestamp(), gtime.Timestamp()-2) + + // For time asserting purpose. + time.Sleep(2 * time.Second) + + // Update + dataUpdate := User{ + Name: "name_1000", + } + r, err = db.Model(table).Data(dataUpdate).OmitEmpty().WherePri(1).Update() + t.AssertNil(err) + n, _ = r.RowsAffected() + t.Assert(n, 1) + + oneUpdate, err := db.Model(table).FindOne(1) + t.AssertNil(err) + t.Assert(oneUpdate["id"].Int(), 1) + t.Assert(oneUpdate["name"].String(), "name_1000") + t.Assert(oneUpdate["deleted_at"].String(), "") + t.Assert(oneUpdate["created_at"].GTime().Timestamp(), oneInsert["created_at"].GTime().Timestamp()) + t.AssertGE(oneUpdate["updated_at"].GTime().Timestamp(), gtime.Timestamp()-4) + + // Replace + dataReplace := User{ + Id: 1, + Name: "name_100", + } + r, err = db.Model(table).Data(dataReplace).OmitEmpty().Replace() + t.AssertNil(err) + n, _ = r.RowsAffected() + t.Assert(n, 2) + + oneReplace, err := db.Model(table).FindOne(1) + t.AssertNil(err) + t.Assert(oneReplace["id"].Int(), 1) + t.Assert(oneReplace["name"].String(), "name_100") + t.Assert(oneReplace["deleted_at"].String(), "") + t.AssertGE(oneReplace["created_at"].GTime().Timestamp(), oneInsert["created_at"].GTime().Timestamp()) + t.AssertGE(oneReplace["updated_at"].GTime().Timestamp(), oneInsert["updated_at"].GTime().Timestamp()) + + // For time asserting purpose. + time.Sleep(2 * time.Second) + + // Delete + r, err = db.Model(table).Delete("id", 1) + t.AssertNil(err) + n, _ = r.RowsAffected() + t.Assert(n, 1) + // Delete Select + one4, err := db.Model(table).FindOne(1) + t.AssertNil(err) + t.Assert(len(one4), 0) + one5, err := db.Model(table).Unscoped().FindOne(1) + t.AssertNil(err) + t.Assert(one5["id"].Int(), 1) + t.AssertGE(one5["deleted_at"].GTime().Timestamp(), gtime.Timestamp()-2) + // Delete Count + i, err := db.Model(table).FindCount() + t.AssertNil(err) + t.Assert(i, 0) + i, err = db.Model(table).Unscoped().FindCount() + t.AssertNil(err) + t.Assert(i, 1) + + // Delete Unscoped + r, err = db.Model(table).Unscoped().Delete("id", 1) + t.AssertNil(err) + n, _ = r.RowsAffected() + t.Assert(n, 1) + one6, err := db.Model(table).Unscoped().FindOne(1) + t.AssertNil(err) + t.Assert(len(one6), 0) + i, err = db.Model(table).Unscoped().FindCount() + t.AssertNil(err) t.Assert(i, 0) }) } @@ -172,26 +448,26 @@ CREATE TABLE %s ( "id": 1, "num": 10, } - r, err := db.Table(table).Data(dataInsert).Insert() - t.Assert(err, nil) + r, err := db.Model(table).Data(dataInsert).Insert() + t.AssertNil(err) n, _ := r.RowsAffected() t.Assert(n, 1) - oneInsert, err := db.Table(table).FindOne(1) - t.Assert(err, nil) + oneInsert, err := db.Model(table).FindOne(1) + t.AssertNil(err) t.Assert(oneInsert["id"].Int(), 1) t.Assert(oneInsert["num"].Int(), 10) // Update. - r, err = db.Table(table).Data("num=num+1").Where("id=?", 1).Update() - t.Assert(err, nil) + r, err = db.Model(table).Data("num=num+1").Where("id=?", 1).Update() + t.AssertNil(err) n, _ = r.RowsAffected() t.Assert(n, 1) }) } func Test_SoftDelete(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, @@ -212,39 +488,39 @@ CREATE TABLE %s ( "id": i, "name": fmt.Sprintf("name_%d", i), } - r, err := db.Table(table).Data(data).Insert() - t.Assert(err, nil) + r, err := db.Model(table).Data(data).Insert() + t.AssertNil(err) n, _ := r.RowsAffected() t.Assert(n, 1) } }) gtest.C(t, func(t *gtest.T) { - one, err := db.Table(table).FindOne(1) - t.Assert(err, nil) + one, err := db.Model(table).FindOne(1) + t.AssertNil(err) t.AssertNE(one["create_at"].String(), "") t.AssertNE(one["update_at"].String(), "") t.Assert(one["delete_at"].String(), "") }) gtest.C(t, func(t *gtest.T) { - one, err := db.Table(table).FindOne(10) - t.Assert(err, nil) + one, err := db.Model(table).FindOne(10) + t.AssertNil(err) t.AssertNE(one["create_at"].String(), "") t.AssertNE(one["update_at"].String(), "") t.Assert(one["delete_at"].String(), "") }) gtest.C(t, func(t *gtest.T) { ids := g.SliceInt{1, 3, 5} - r, err := db.Table(table).Where("id", ids).Delete() - t.Assert(err, nil) + r, err := db.Model(table).Where("id", ids).Delete() + t.AssertNil(err) n, _ := r.RowsAffected() t.Assert(n, 3) - count, err := db.Table(table).FindCount(ids) - t.Assert(err, nil) + count, err := db.Model(table).FindCount(ids) + t.AssertNil(err) t.Assert(count, 0) - all, err := db.Table(table).Unscoped().FindAll(ids) - t.Assert(err, nil) + all, err := db.Model(table).Unscoped().FindAll(ids) + t.AssertNil(err) t.Assert(len(all), 3) t.AssertNE(all[0]["create_at"].String(), "") t.AssertNE(all[0]["update_at"].String(), "") @@ -295,8 +571,8 @@ CREATE TABLE %s ( "id": 1, "name": "name_1", } - r, err := db.Table(table1).Data(dataInsert1).Insert() - t.Assert(err, nil) + r, err := db.Model(table1).Data(dataInsert1).Insert() + t.AssertNil(err) n, _ := r.RowsAffected() t.Assert(n, 1) @@ -304,33 +580,33 @@ CREATE TABLE %s ( "id": 1, "name": "name_2", } - r, err = db.Table(table2).Data(dataInsert2).Insert() - t.Assert(err, nil) + r, err = db.Model(table2).Data(dataInsert2).Insert() + t.AssertNil(err) n, _ = r.RowsAffected() t.Assert(n, 1) - one, err := db.Table(table1, "t1").LeftJoin(table2, "t2", "t2.id=t1.id").Fields("t1.name").FindOne() - t.Assert(err, nil) + one, err := db.Model(table1, "t1").LeftJoin(table2, "t2", "t2.id=t1.id").Fields("t1.name").FindOne() + t.AssertNil(err) t.Assert(one["name"], "name_1") // Soft deleting. - r, err = db.Table(table1).Delete() - t.Assert(err, nil) + r, err = db.Model(table1).Delete() + t.AssertNil(err) n, _ = r.RowsAffected() t.Assert(n, 1) - one, err = db.Table(table1, "t1").LeftJoin(table2, "t2", "t2.id=t1.id").Fields("t1.name").FindOne() - t.Assert(err, nil) + one, err = db.Model(table1, "t1").LeftJoin(table2, "t2", "t2.id=t1.id").Fields("t1.name").FindOne() + t.AssertNil(err) t.Assert(one.IsEmpty(), true) - one, err = db.Table(table2, "t2").LeftJoin(table1, "t1", "t2.id=t1.id").Fields("t2.name").FindOne() - t.Assert(err, nil) + one, err = db.Model(table2, "t2").LeftJoin(table1, "t1", "t2.id=t1.id").Fields("t2.name").FindOne() + t.AssertNil(err) t.Assert(one.IsEmpty(), true) }) } -func Test_CreateUpdateTime_Struct(t *testing.T) { - table := "time_test_table" +func Test_SoftDelete_WhereAndOr(t *testing.T) { + table := "time_test_table_" + gtime.TimestampNanoStr() if _, err := db.Exec(fmt.Sprintf(` CREATE TABLE %s ( id int(11) NOT NULL, @@ -344,6 +620,51 @@ CREATE TABLE %s ( gtest.Error(err) } defer dropTable(table) + //db.SetDebug(true) + // Add datas. + gtest.C(t, func(t *gtest.T) { + for i := 1; i <= 10; i++ { + data := g.Map{ + "id": i, + "name": fmt.Sprintf("name_%d", i), + } + r, err := db.Model(table).Data(data).Insert() + t.AssertNil(err) + n, _ := r.RowsAffected() + t.Assert(n, 1) + } + }) + gtest.C(t, func(t *gtest.T) { + ids := g.SliceInt{1, 3, 5} + r, err := db.Model(table).Where("id", ids).Delete() + t.AssertNil(err) + n, _ := r.RowsAffected() + t.Assert(n, 3) + + count, err := db.Model(table).Where("id", 1).Or("id", 3).Count() + t.AssertNil(err) + t.Assert(count, 0) + }) +} + +func Test_CreateUpdateTime_Struct(t *testing.T) { + table := "time_test_table_" + gtime.TimestampNanoStr() + if _, err := db.Exec(fmt.Sprintf(` +CREATE TABLE %s ( + id int(11) NOT NULL, + name varchar(45) DEFAULT NULL, + create_at datetime DEFAULT NULL, + update_at datetime DEFAULT NULL, + delete_at datetime DEFAULT NULL, + PRIMARY KEY (id) +) ENGINE=InnoDB DEFAULT CHARSET=utf8; + `, table)); err != nil { + gtest.Error(err) + } + defer dropTable(table) + + //db.SetDebug(true) + //defer db.SetDebug(false) type Entity struct { Id uint64 `orm:"id,primary" json:"id"` @@ -361,18 +682,18 @@ CREATE TABLE %s ( UpdateAt: nil, DeleteAt: nil, } - r, err := db.Table(table).Data(dataInsert).Insert() - t.Assert(err, nil) + r, err := db.Model(table).Data(dataInsert).OmitEmpty().Insert() + t.AssertNil(err) n, _ := r.RowsAffected() t.Assert(n, 1) - oneInsert, err := db.Table(table).FindOne(1) - t.Assert(err, nil) + oneInsert, err := db.Model(table).FindOne(1) + t.AssertNil(err) t.Assert(oneInsert["id"].Int(), 1) t.Assert(oneInsert["name"].String(), "name_1") t.Assert(oneInsert["delete_at"].String(), "") t.AssertGE(oneInsert["create_at"].GTime().Timestamp(), gtime.Timestamp()-2) - t.AssertGE(oneInsert["update_at"].GTime().Timestamp(), gtime.Timestamp()) + t.AssertGE(oneInsert["update_at"].GTime().Timestamp(), gtime.Timestamp()-2) time.Sleep(2 * time.Second) @@ -384,19 +705,19 @@ CREATE TABLE %s ( UpdateAt: nil, DeleteAt: nil, } - r, err = db.Table(table).Data(dataSave).Save() - t.Assert(err, nil) + r, err = db.Model(table).Data(dataSave).OmitEmpty().Save() + t.AssertNil(err) n, _ = r.RowsAffected() t.Assert(n, 2) - oneSave, err := db.Table(table).FindOne(1) - t.Assert(err, nil) + oneSave, err := db.Model(table).FindOne(1) + t.AssertNil(err) t.Assert(oneSave["id"].Int(), 1) t.Assert(oneSave["name"].String(), "name_10") t.Assert(oneSave["delete_at"].String(), "") t.Assert(oneSave["create_at"].GTime().Timestamp(), oneInsert["create_at"].GTime().Timestamp()) t.AssertNE(oneSave["update_at"].GTime().Timestamp(), oneInsert["update_at"].GTime().Timestamp()) - t.AssertGE(oneSave["update_at"].GTime().Timestamp(), gtime.Now().Timestamp()-2) + t.AssertGE(oneSave["update_at"].GTime().Timestamp(), gtime.Timestamp()-2) time.Sleep(2 * time.Second) @@ -408,18 +729,18 @@ CREATE TABLE %s ( UpdateAt: nil, DeleteAt: nil, } - r, err = db.Table(table).Data(dataUpdate).WherePri(1).Update() - t.Assert(err, nil) + r, err = db.Model(table).Data(dataUpdate).WherePri(1).OmitEmpty().Update() + t.AssertNil(err) n, _ = r.RowsAffected() t.Assert(n, 1) - oneUpdate, err := db.Table(table).FindOne(1) - t.Assert(err, nil) + oneUpdate, err := db.Model(table).FindOne(1) + t.AssertNil(err) t.Assert(oneUpdate["id"].Int(), 1) t.Assert(oneUpdate["name"].String(), "name_1000") t.Assert(oneUpdate["delete_at"].String(), "") t.Assert(oneUpdate["create_at"].GTime().Timestamp(), oneInsert["create_at"].GTime().Timestamp()) - t.AssertGE(oneUpdate["update_at"].GTime().Timestamp(), gtime.Now().Timestamp()-2) + t.AssertGE(oneUpdate["update_at"].GTime().Timestamp(), gtime.Timestamp()-2) // Replace dataReplace := &Entity{ @@ -429,13 +750,13 @@ CREATE TABLE %s ( UpdateAt: nil, DeleteAt: nil, } - r, err = db.Table(table).Data(dataReplace).Replace() - t.Assert(err, nil) + r, err = db.Model(table).Data(dataReplace).OmitEmpty().Replace() + t.AssertNil(err) n, _ = r.RowsAffected() t.Assert(n, 2) - oneReplace, err := db.Table(table).FindOne(1) - t.Assert(err, nil) + oneReplace, err := db.Model(table).FindOne(1) + t.AssertNil(err) t.Assert(oneReplace["id"].Int(), 1) t.Assert(oneReplace["name"].String(), "name_100") t.Assert(oneReplace["delete_at"].String(), "") @@ -445,36 +766,36 @@ CREATE TABLE %s ( time.Sleep(2 * time.Second) // Delete - r, err = db.Table(table).Delete("id", 1) - t.Assert(err, nil) + r, err = db.Model(table).Delete("id", 1) + t.AssertNil(err) n, _ = r.RowsAffected() t.Assert(n, 1) // Delete Select - one4, err := db.Table(table).FindOne(1) - t.Assert(err, nil) + one4, err := db.Model(table).FindOne(1) + t.AssertNil(err) t.Assert(len(one4), 0) - one5, err := db.Table(table).Unscoped().FindOne(1) - t.Assert(err, nil) + one5, err := db.Model(table).Unscoped().FindOne(1) + t.AssertNil(err) t.Assert(one5["id"].Int(), 1) - t.AssertGE(one5["delete_at"].GTime().Timestamp(), gtime.Now().Timestamp()-2) + t.AssertGE(one5["delete_at"].GTime().Timestamp(), gtime.Timestamp()-2) // Delete Count - i, err := db.Table(table).FindCount() - t.Assert(err, nil) + i, err := db.Model(table).FindCount() + t.AssertNil(err) t.Assert(i, 0) - i, err = db.Table(table).Unscoped().FindCount() - t.Assert(err, nil) + i, err = db.Model(table).Unscoped().FindCount() + t.AssertNil(err) t.Assert(i, 1) // Delete Unscoped - r, err = db.Table(table).Unscoped().Delete("id", 1) - t.Assert(err, nil) + r, err = db.Model(table).Unscoped().Delete("id", 1) + t.AssertNil(err) n, _ = r.RowsAffected() t.Assert(n, 1) - one6, err := db.Table(table).Unscoped().FindOne(1) - t.Assert(err, nil) + one6, err := db.Model(table).Unscoped().FindOne(1) + t.AssertNil(err) t.Assert(len(one6), 0) - i, err = db.Table(table).Unscoped().FindCount() - t.Assert(err, nil) + i, err = db.Model(table).Unscoped().FindCount() + t.AssertNil(err) t.Assert(i, 0) }) } diff --git a/database/gdb/gdb_z_mysql_transaction_test.go b/database/gdb/gdb_z_mysql_transaction_test.go index bbfaaddd4..d12ff51e4 100644 --- a/database/gdb/gdb_z_mysql_transaction_test.go +++ b/database/gdb/gdb_z_mysql_transaction_test.go @@ -1,4 +1,4 @@ -// Copyright GoFrame Author(https://github.com/gogf/gf). All Rights Reserved. +// Copyright GoFrame Author(https://goframe.org). All Rights Reserved. // // This Source Code Form is subject to the terms of the MIT License. // If a copy of the MIT was not distributed with this file, @@ -7,10 +7,13 @@ package gdb_test import ( + "context" "fmt" + "github.com/gogf/gf/os/gctx" + "testing" + "github.com/gogf/gf/database/gdb" "github.com/gogf/gf/errors/gerror" - "testing" "github.com/gogf/gf/frame/g" "github.com/gogf/gf/os/gtime" @@ -88,29 +91,26 @@ func Test_TX_Rollback(t *testing.T) { } func Test_TX_Prepare(t *testing.T) { - tx, err := db.Begin() - if err != nil { - gtest.Error(err) - } - st, err := tx.Prepare("SELECT 100") - if err != nil { - gtest.Error(err) - } - rows, err := st.Query() - if err != nil { - gtest.Error(err) - } - array, err := rows.Columns() - if err != nil { - gtest.Error(err) - } - gtest.Assert(array[0], "100") - if err := rows.Close(); err != nil { - gtest.Error(err) - } - if err := tx.Commit(); err != nil { - gtest.Error(err) - } + gtest.C(t, func(t *gtest.T) { + tx, err := db.Begin() + t.AssertNil(err) + + st, err := tx.Prepare("SELECT 100") + t.AssertNil(err) + + rows, err := st.Query() + t.AssertNil(err) + + array, err := rows.Columns() + t.AssertNil(err) + t.Assert(array[0], "100") + + rows.Close() + t.AssertNil(err) + + tx.Commit() + t.AssertNil(err) + }) } func Test_TX_Insert(t *testing.T) { @@ -122,7 +122,7 @@ func Test_TX_Insert(t *testing.T) { if err != nil { gtest.Error(err) } - user := tx.Table(table) + user := tx.Model(table) if _, err := user.Data(g.Map{ "id": 1, "passport": "t1", @@ -132,7 +132,6 @@ func Test_TX_Insert(t *testing.T) { }).Insert(); err != nil { gtest.Error(err) } - if _, err := tx.Insert(table, g.Map{ "id": 2, "passport": "t1", @@ -143,7 +142,7 @@ func Test_TX_Insert(t *testing.T) { gtest.Error(err) } - if n, err := tx.Table(table).Count(); err != nil { + if n, err := tx.Model(table).Count(); err != nil { gtest.Error(err) } else { t.Assert(n, 2) @@ -165,7 +164,7 @@ func Test_TX_BatchInsert(t *testing.T) { if err != nil { gtest.Error(err) } - if _, err := tx.BatchInsert(table, g.List{ + if _, err := tx.Insert(table, g.List{ { "id": 2, "passport": "t", @@ -186,7 +185,7 @@ func Test_TX_BatchInsert(t *testing.T) { if err := tx.Commit(); err != nil { gtest.Error(err) } - if n, err := db.Table(table).Count(); err != nil { + if n, err := db.Model(table).Count(); err != nil { gtest.Error(err) } else { t.Assert(n, 2) @@ -203,7 +202,7 @@ func Test_TX_BatchReplace(t *testing.T) { if err != nil { gtest.Error(err) } - if _, err := tx.BatchReplace(table, g.List{ + if _, err := tx.Replace(table, g.List{ { "id": 2, "passport": "USER_2", @@ -224,12 +223,12 @@ func Test_TX_BatchReplace(t *testing.T) { if err := tx.Commit(); err != nil { gtest.Error(err) } - if n, err := db.Table(table).Count(); err != nil { + if n, err := db.Model(table).Count(); err != nil { gtest.Error(err) } else { - t.Assert(n, SIZE) + t.Assert(n, TableSize) } - if value, err := db.Table(table).Fields("password").Where("id", 2).Value(); err != nil { + if value, err := db.Model(table).Fields("password").Where("id", 2).Value(); err != nil { gtest.Error(err) } else { t.Assert(value.String(), "PASS_2") @@ -246,7 +245,7 @@ func Test_TX_BatchSave(t *testing.T) { if err != nil { gtest.Error(err) } - if _, err := tx.BatchSave(table, g.List{ + if _, err := tx.Save(table, g.List{ { "id": 4, "passport": "USER_4", @@ -261,13 +260,13 @@ func Test_TX_BatchSave(t *testing.T) { gtest.Error(err) } - if n, err := db.Table(table).Count(); err != nil { + if n, err := db.Model(table).Count(); err != nil { gtest.Error(err) } else { - t.Assert(n, SIZE) + t.Assert(n, TableSize) } - if value, err := db.Table(table).Fields("password").Where("id", 4).Value(); err != nil { + if value, err := db.Model(table).Fields("password").Where("id", 4).Value(); err != nil { gtest.Error(err) } else { t.Assert(value.String(), "PASS_4") @@ -296,7 +295,7 @@ func Test_TX_Replace(t *testing.T) { if err := tx.Rollback(); err != nil { gtest.Error(err) } - if value, err := db.Table(table).Fields("nickname").Where("id", 1).Value(); err != nil { + if value, err := db.Model(table).Fields("nickname").Where("id", 1).Value(); err != nil { gtest.Error(err) } else { t.Assert(value.String(), "name_1") @@ -325,7 +324,7 @@ func Test_TX_Save(t *testing.T) { if err := tx.Commit(); err != nil { gtest.Error(err) } - if value, err := db.Table(table).Fields("nickname").Where("id", 1).Value(); err != nil { + if value, err := db.Model(table).Fields("nickname").Where("id", 1).Value(); err != nil { gtest.Error(err) } else { t.Assert(value.String(), "NAME_1") @@ -351,10 +350,10 @@ func Test_TX_Update(t *testing.T) { if err := tx.Commit(); err != nil { gtest.Error(err) } - _, err = tx.Table(table).Fields("create_time").Where("id", 3).Value() + _, err = tx.Model(table).Fields("create_time").Where("id", 3).Value() t.AssertNE(err, nil) - if value, err := db.Table(table).Fields("create_time").Where("id", 3).Value(); err != nil { + if value, err := db.Model(table).Fields("create_time").Where("id", 3).Value(); err != nil { gtest.Error(err) } else { t.Assert(value.String(), "2019-10-24 10:00:00") @@ -438,7 +437,7 @@ func Test_TX_GetCount(t *testing.T) { if count, err := tx.GetCount("SELECT * FROM " + table); err != nil { gtest.Error(err) } else { - t.Assert(count, SIZE) + t.Assert(count, TableSize) } if err := tx.Commit(); err != nil { gtest.Error(err) @@ -516,7 +515,7 @@ func Test_TX_GetStructs(t *testing.T) { if err := tx.GetStructs(&users, fmt.Sprintf("SELECT * FROM %s WHERE id>=?", table), 1); err != nil { gtest.Error(err) } - t.Assert(len(users), SIZE) + t.Assert(len(users), TableSize) t.Assert(users[0].Id, 1) t.Assert(users[1].Id, 2) t.Assert(users[2].Id, 3) @@ -545,7 +544,7 @@ func Test_TX_GetStructs(t *testing.T) { if err := tx.GetStructs(&users, fmt.Sprintf("SELECT * FROM %s WHERE id>=?", table), 1); err != nil { gtest.Error(err) } - t.Assert(len(users), SIZE) + t.Assert(len(users), TableSize) t.Assert(users[0].Id, 1) t.Assert(users[1].Id, 2) t.Assert(users[2].Id, 3) @@ -624,7 +623,7 @@ func Test_TX_GetScan(t *testing.T) { if err := tx.GetScan(&users, fmt.Sprintf("SELECT * FROM %s WHERE id>=?", table), 1); err != nil { gtest.Error(err) } - t.Assert(len(users), SIZE) + t.Assert(len(users), TableSize) t.Assert(users[0].Id, 1) t.Assert(users[1].Id, 2) t.Assert(users[2].Id, 3) @@ -653,7 +652,7 @@ func Test_TX_GetScan(t *testing.T) { if err := tx.GetScan(&users, fmt.Sprintf("SELECT * FROM %s WHERE id>=?", table), 1); err != nil { gtest.Error(err) } - t.Assert(len(users), SIZE) + t.Assert(len(users), TableSize) t.Assert(users[0].Id, 1) t.Assert(users[1].Id, 2) t.Assert(users[2].Id, 3) @@ -668,7 +667,6 @@ func Test_TX_GetScan(t *testing.T) { } func Test_TX_Delete(t *testing.T) { - gtest.C(t, func(t *gtest.T) { table := createInitTable() defer dropTable(table) @@ -682,11 +680,13 @@ func Test_TX_Delete(t *testing.T) { if err := tx.Commit(); err != nil { gtest.Error(err) } - if n, err := db.Table(table).Count(); err != nil { + if n, err := db.Model(table).Count(); err != nil { gtest.Error(err) } else { t.Assert(n, 0) } + + t.Assert(tx.IsClosed(), true) }) gtest.C(t, func(t *gtest.T) { @@ -699,7 +699,7 @@ func Test_TX_Delete(t *testing.T) { if _, err := tx.Delete(table, 1); err != nil { gtest.Error(err) } - if n, err := tx.Table(table).Count(); err != nil { + if n, err := tx.Model(table).Count(); err != nil { gtest.Error(err) } else { t.Assert(n, 0) @@ -707,12 +707,14 @@ func Test_TX_Delete(t *testing.T) { if err := tx.Rollback(); err != nil { gtest.Error(err) } - if n, err := db.Table(table).Count(); err != nil { + if n, err := db.Model(table).Count(); err != nil { gtest.Error(err) } else { - t.Assert(n, SIZE) + t.Assert(n, TableSize) t.AssertNE(n, 0) } + + t.Assert(tx.IsClosed(), true) }) } @@ -721,8 +723,9 @@ func Test_Transaction(t *testing.T) { 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{ + ctx := context.TODO() + err := db.Transaction(ctx, func(ctx context.Context, tx *gdb.TX) error { + if _, err := tx.Ctx(ctx).Replace(table, g.Map{ "id": 1, "passport": "USER_1", "password": "PASS_1", @@ -731,11 +734,12 @@ func Test_Transaction(t *testing.T) { }); err != nil { t.Error(err) } + t.Assert(tx.IsClosed(), false) return gerror.New("error") }) t.AssertNE(err, nil) - if value, err := db.Table(table).Fields("nickname").Where("id", 1).Value(); err != nil { + if value, err := db.Model(table).Ctx(ctx).Fields("nickname").Where("id", 1).Value(); err != nil { gtest.Error(err) } else { t.Assert(value.String(), "name_1") @@ -743,7 +747,8 @@ func Test_Transaction(t *testing.T) { }) gtest.C(t, func(t *gtest.T) { - err := db.Transaction(func(tx *gdb.TX) error { + ctx := context.TODO() + err := db.Transaction(ctx, func(ctx context.Context, tx *gdb.TX) error { if _, err := tx.Replace(table, g.Map{ "id": 1, "passport": "USER_1", @@ -755,9 +760,9 @@ func Test_Transaction(t *testing.T) { } return nil }) - t.Assert(err, nil) + t.AssertNil(err) - if value, err := db.Table(table).Fields("nickname").Where("id", 1).Value(); err != nil { + if value, err := db.Model(table).Fields("nickname").Where("id", 1).Value(); err != nil { gtest.Error(err) } else { t.Assert(value.String(), "NAME_1") @@ -770,7 +775,8 @@ func Test_Transaction_Panic(t *testing.T) { defer dropTable(table) gtest.C(t, func(t *gtest.T) { - err := db.Transaction(func(tx *gdb.TX) error { + ctx := context.TODO() + err := db.Transaction(ctx, func(ctx context.Context, tx *gdb.TX) error { if _, err := tx.Replace(table, g.Map{ "id": 1, "passport": "USER_1", @@ -785,10 +791,360 @@ func Test_Transaction_Panic(t *testing.T) { }) t.AssertNE(err, nil) - if value, err := db.Table(table).Fields("nickname").Where("id", 1).Value(); err != nil { + if value, err := db.Model(table).Fields("nickname").Where("id", 1).Value(); err != nil { gtest.Error(err) } else { t.Assert(value.String(), "name_1") } }) } + +func Test_Transaction_Nested_Begin_Rollback_Commit(t *testing.T) { + table := createTable() + defer dropTable(table) + + gtest.C(t, func(t *gtest.T) { + tx, err := db.Begin() + t.AssertNil(err) + // tx begin. + err = tx.Begin() + t.AssertNil(err) + // tx rollback. + _, err = tx.Model(table).Data(g.Map{ + "id": 1, + "passport": "user_1", + "password": "pass_1", + "nickname": "name_1", + }).Insert() + err = tx.Rollback() + t.AssertNil(err) + // tx commit. + _, err = tx.Model(table).Data(g.Map{ + "id": 2, + "passport": "user_2", + "password": "pass_2", + "nickname": "name_2", + }).Insert() + err = tx.Commit() + t.AssertNil(err) + // check data. + all, err := db.Model(table).All() + t.AssertNil(err) + t.Assert(len(all), 1) + t.Assert(all[0]["id"], 2) + }) +} + +func Test_Transaction_Nested_TX_Transaction_UseTX(t *testing.T) { + table := createTable() + defer dropTable(table) + + db.SetDebug(true) + defer db.SetDebug(false) + + gtest.C(t, func(t *gtest.T) { + var ( + err error + ctx = context.TODO() + ) + err = db.Transaction(ctx, func(ctx context.Context, tx *gdb.TX) error { + // commit + err = tx.Transaction(ctx, func(ctx context.Context, tx *gdb.TX) error { + err = tx.Transaction(ctx, func(ctx context.Context, tx *gdb.TX) error { + err = tx.Transaction(ctx, func(ctx context.Context, tx *gdb.TX) error { + err = tx.Transaction(ctx, func(ctx context.Context, tx *gdb.TX) error { + err = tx.Transaction(ctx, func(ctx context.Context, tx *gdb.TX) error { + _, err = tx.Model(table).Data(g.Map{ + "id": 1, + "passport": "USER_1", + "password": "PASS_1", + "nickname": "NAME_1", + "create_time": gtime.Now().String(), + }).Insert() + t.AssertNil(err) + return err + }) + t.AssertNil(err) + return err + }) + t.AssertNil(err) + return err + }) + t.AssertNil(err) + return err + }) + t.AssertNil(err) + return err + }) + t.AssertNil(err) + // rollback + err = tx.Transaction(ctx, func(ctx context.Context, tx *gdb.TX) error { + _, err = tx.Model(table).Data(g.Map{ + "id": 2, + "passport": "USER_2", + "password": "PASS_2", + "nickname": "NAME_2", + "create_time": gtime.Now().String(), + }).Insert() + t.AssertNil(err) + panic("error") + return err + }) + t.AssertNE(err, nil) + return nil + }) + t.AssertNil(err) + + all, err := db.Ctx(ctx).Model(table).All() + t.AssertNil(err) + t.Assert(len(all), 1) + t.Assert(all[0]["id"], 1) + + // another record. + err = db.Transaction(ctx, func(ctx context.Context, tx *gdb.TX) error { + // commit + err = tx.Transaction(ctx, func(ctx context.Context, tx *gdb.TX) error { + err = tx.Transaction(ctx, func(ctx context.Context, tx *gdb.TX) error { + err = tx.Transaction(ctx, func(ctx context.Context, tx *gdb.TX) error { + err = tx.Transaction(ctx, func(ctx context.Context, tx *gdb.TX) error { + err = tx.Transaction(ctx, func(ctx context.Context, tx *gdb.TX) error { + _, err = tx.Model(table).Data(g.Map{ + "id": 3, + "passport": "USER_1", + "password": "PASS_1", + "nickname": "NAME_1", + "create_time": gtime.Now().String(), + }).Insert() + t.AssertNil(err) + return err + }) + t.AssertNil(err) + return err + }) + t.AssertNil(err) + return err + }) + t.AssertNil(err) + return err + }) + t.AssertNil(err) + return err + }) + t.AssertNil(err) + // rollback + err = tx.Transaction(ctx, func(ctx context.Context, tx *gdb.TX) error { + _, err = tx.Model(table).Data(g.Map{ + "id": 4, + "passport": "USER_2", + "password": "PASS_2", + "nickname": "NAME_2", + "create_time": gtime.Now().String(), + }).Insert() + t.AssertNil(err) + panic("error") + return err + }) + t.AssertNE(err, nil) + return nil + }) + t.AssertNil(err) + + all, err = db.Ctx(ctx).Model(table).All() + t.AssertNil(err) + t.Assert(len(all), 2) + t.Assert(all[0]["id"], 1) + t.Assert(all[1]["id"], 3) + }) +} + +func Test_Transaction_Nested_TX_Transaction_UseDB(t *testing.T) { + table := createTable() + defer dropTable(table) + + //db.SetDebug(true) + //defer db.SetDebug(false) + + gtest.C(t, func(t *gtest.T) { + var ( + err error + ctx = context.TODO() + ) + err = db.Transaction(ctx, func(ctx context.Context, tx *gdb.TX) error { + // commit + err = db.Transaction(ctx, func(ctx context.Context, tx *gdb.TX) error { + err = db.Transaction(ctx, func(ctx context.Context, tx *gdb.TX) error { + err = db.Transaction(ctx, func(ctx context.Context, tx *gdb.TX) error { + err = db.Transaction(ctx, func(ctx context.Context, tx *gdb.TX) error { + err = db.Transaction(ctx, func(ctx context.Context, tx *gdb.TX) error { + _, err = db.Model(table).Ctx(ctx).Data(g.Map{ + "id": 1, + "passport": "USER_1", + "password": "PASS_1", + "nickname": "NAME_1", + "create_time": gtime.Now().String(), + }).Insert() + t.AssertNil(err) + return err + }) + t.AssertNil(err) + return err + }) + t.AssertNil(err) + return err + }) + t.AssertNil(err) + return err + }) + t.AssertNil(err) + return err + }) + t.AssertNil(err) + + // rollback + err = db.Transaction(ctx, func(ctx context.Context, tx *gdb.TX) error { + _, err = tx.Model(table).Ctx(ctx).Data(g.Map{ + "id": 2, + "passport": "USER_2", + "password": "PASS_2", + "nickname": "NAME_2", + "create_time": gtime.Now().String(), + }).Insert() + t.AssertNil(err) + // panic makes this transaction rollback. + panic("error") + return err + }) + t.AssertNE(err, nil) + return nil + }) + t.AssertNil(err) + all, err := db.Model(table).All() + t.AssertNil(err) + t.Assert(len(all), 1) + t.Assert(all[0]["id"], 1) + + err = db.Transaction(ctx, func(ctx context.Context, tx *gdb.TX) error { + // commit + err = db.Transaction(ctx, func(ctx context.Context, tx *gdb.TX) error { + err = db.Transaction(ctx, func(ctx context.Context, tx *gdb.TX) error { + err = db.Transaction(ctx, func(ctx context.Context, tx *gdb.TX) error { + err = db.Transaction(ctx, func(ctx context.Context, tx *gdb.TX) error { + err = db.Transaction(ctx, func(ctx context.Context, tx *gdb.TX) error { + _, err = db.Model(table).Ctx(ctx).Data(g.Map{ + "id": 3, + "passport": "USER_1", + "password": "PASS_1", + "nickname": "NAME_1", + "create_time": gtime.Now().String(), + }).Insert() + t.AssertNil(err) + return err + }) + t.AssertNil(err) + return err + }) + t.AssertNil(err) + return err + }) + t.AssertNil(err) + return err + }) + t.AssertNil(err) + return err + }) + t.AssertNil(err) + + // rollback + err = db.Transaction(ctx, func(ctx context.Context, tx *gdb.TX) error { + _, err = tx.Model(table).Ctx(ctx).Data(g.Map{ + "id": 4, + "passport": "USER_2", + "password": "PASS_2", + "nickname": "NAME_2", + "create_time": gtime.Now().String(), + }).Insert() + t.AssertNil(err) + // panic makes this transaction rollback. + panic("error") + return err + }) + t.AssertNE(err, nil) + return nil + }) + t.AssertNil(err) + + all, err = db.Model(table).All() + t.AssertNil(err) + t.Assert(len(all), 2) + t.Assert(all[0]["id"], 1) + t.Assert(all[1]["id"], 3) + }) +} + +func Test_Transaction_Nested_SavePoint_RollbackTo(t *testing.T) { + table := createTable() + defer dropTable(table) + + gtest.C(t, func(t *gtest.T) { + tx, err := db.Begin() + t.AssertNil(err) + // tx save point. + _, err = tx.Model(table).Data(g.Map{ + "id": 1, + "passport": "user_1", + "password": "pass_1", + "nickname": "name_1", + }).Insert() + err = tx.SavePoint("MyPoint") + t.AssertNil(err) + _, err = tx.Model(table).Data(g.Map{ + "id": 2, + "passport": "user_2", + "password": "pass_2", + "nickname": "name_2", + }).Insert() + // tx rollback to. + err = tx.RollbackTo("MyPoint") + t.AssertNil(err) + // tx commit. + err = tx.Commit() + t.AssertNil(err) + + // check data. + all, err := db.Model(table).All() + t.AssertNil(err) + t.Assert(len(all), 1) + t.Assert(all[0]["id"], 1) + }) +} + +func Test_Transaction_Method(t *testing.T) { + table := createTable() + defer dropTable(table) + + gtest.C(t, func(t *gtest.T) { + var err error + err = db.Transaction(gctx.New(), func(ctx context.Context, tx *gdb.TX) error { + _, err = db.Model(table).Ctx(ctx).Data(g.Map{ + "id": 1, + "passport": "t1", + "password": "25d55ad283aa400af464c76d713c07ad", + "nickname": "T1", + "create_time": gtime.Now().String(), + }).Insert() + t.AssertNil(err) + + _, err = db.Ctx(ctx).Exec(fmt.Sprintf( + "insert into %s(`passport`,`password`,`nickname`,`create_time`,`id`) "+ + "VALUES('t2','25d55ad283aa400af464c76d713c07ad','T2','2021-08-25 21:53:00',2) ", + table)) + t.AssertNil(err) + return gerror.New("rollback") + }) + t.AssertNE(err, nil) + + count, err := db.Model(table).Count() + t.AssertNil(err) + t.Assert(count, 0) + }) +} diff --git a/database/gdb/gdb_z_mysql_types_test.go b/database/gdb/gdb_z_mysql_types_test.go index 9f34771f4..7c02df03e 100644 --- a/database/gdb/gdb_z_mysql_types_test.go +++ b/database/gdb/gdb_z_mysql_types_test.go @@ -1,4 +1,4 @@ -// Copyright GoFrame Author(https://github.com/gogf/gf). All Rights Reserved. +// Copyright GoFrame Author(https://goframe.org). All Rights Reserved. // // This Source Code Form is subject to the terms of the MIT License. // If a copy of the MIT was not distributed with this file, @@ -8,6 +8,7 @@ package gdb_test import ( "fmt" + "github.com/gogf/gf/os/gtime" "testing" "github.com/gogf/gf/frame/g" @@ -15,6 +16,7 @@ import ( "github.com/gogf/gf/test/gtest" ) +// All types testing. func Test_Types(t *testing.T) { gtest.C(t, func(t *gtest.T) { if _, err := db.Exec(fmt.Sprintf(` @@ -56,22 +58,45 @@ func Test_Types(t *testing.T) { "tinyint": true, "bool": false, } - r, err := db.Table("types").Data(data).Insert() - t.Assert(err, nil) + r, err := db.Model("types").Data(data).Insert() + t.AssertNil(err) n, _ := r.RowsAffected() t.Assert(n, 1) - one, err := db.Table("types").One() - t.Assert(err, nil) + one, err := db.Model("types").One() + t.AssertNil(err) t.Assert(one["id"].Int(), 1) t.Assert(one["blob"].String(), data["blob"]) t.Assert(one["binary"].String(), data["binary"]) t.Assert(one["date"].String(), data["date"]) - t.Assert(one["time"].String(), data["time"]) + t.Assert(one["time"].String(), `0000-01-01 10:00:01`) t.Assert(one["decimal"].String(), -123.46) t.Assert(one["double"].String(), data["double"]) t.Assert(one["bit"].Int(), data["bit"]) t.Assert(one["tinyint"].Bool(), data["tinyint"]) - t.Assert(one["tinyint"].Bool(), data["tinyint"]) + + type T struct { + Id int + Blob []byte + Binary []byte + Date *gtime.Time + Time *gtime.Time + Decimal float64 + Double float64 + Bit int8 + TinyInt bool + } + var obj *T + err = db.Model("types").Scan(&obj) + t.AssertNil(err) + t.Assert(obj.Id, 1) + t.Assert(obj.Blob, data["blob"]) + t.Assert(obj.Binary, data["binary"]) + t.Assert(obj.Date.Format("Y-m-d"), data["date"]) + t.Assert(obj.Time.String(), `0000-01-01 10:00:01`) + t.Assert(obj.Decimal, -123.46) + t.Assert(obj.Double, data["double"]) + t.Assert(obj.Bit, data["bit"]) + t.Assert(obj.TinyInt, data["tinyint"]) }) } diff --git a/database/gdb/gdb_z_mysql_union_test.go b/database/gdb/gdb_z_mysql_union_test.go new file mode 100644 index 000000000..34df871b4 --- /dev/null +++ b/database/gdb/gdb_z_mysql_union_test.go @@ -0,0 +1,146 @@ +// Copyright GoFrame Author(https://goframe.org). All Rights Reserved. +// +// This Source Code Form is subject to the terms of the MIT License. +// If a copy of the MIT was not distributed with this file, +// You can obtain one at https://github.com/gogf/gf. + +package gdb_test + +import ( + "github.com/gogf/gf/frame/g" + "testing" + + "github.com/gogf/gf/test/gtest" +) + +func Test_Union(t *testing.T) { + table := createInitTable() + defer dropTable(table) + + gtest.C(t, func(t *gtest.T) { + r, err := db.Union( + db.Model(table).Where("id", 1), + db.Model(table).Where("id", 2), + db.Model(table).WhereIn("id", g.Slice{1, 2, 3}).OrderDesc("id"), + ).OrderDesc("id").All() + + t.AssertNil(err) + + t.Assert(len(r), 3) + t.Assert(r[0]["id"], 3) + t.Assert(r[1]["id"], 2) + t.Assert(r[2]["id"], 1) + }) + + gtest.C(t, func(t *gtest.T) { + r, err := db.Union( + db.Model(table).Where("id", 1), + db.Model(table).Where("id", 2), + db.Model(table).WhereIn("id", g.Slice{1, 2, 3}).OrderDesc("id"), + ).OrderDesc("id").One() + + t.AssertNil(err) + + t.Assert(r["id"], 3) + }) +} + +func Test_UnionAll(t *testing.T) { + table := createInitTable() + defer dropTable(table) + + gtest.C(t, func(t *gtest.T) { + r, err := db.UnionAll( + db.Model(table).Where("id", 1), + db.Model(table).Where("id", 2), + db.Model(table).WhereIn("id", g.Slice{1, 2, 3}).OrderDesc("id"), + ).OrderDesc("id").All() + + t.AssertNil(err) + + t.Assert(len(r), 5) + t.Assert(r[0]["id"], 3) + t.Assert(r[1]["id"], 2) + t.Assert(r[2]["id"], 2) + t.Assert(r[3]["id"], 1) + t.Assert(r[4]["id"], 1) + }) + + gtest.C(t, func(t *gtest.T) { + r, err := db.UnionAll( + db.Model(table).Where("id", 1), + db.Model(table).Where("id", 2), + db.Model(table).WhereIn("id", g.Slice{1, 2, 3}).OrderDesc("id"), + ).OrderDesc("id").One() + + t.AssertNil(err) + + t.Assert(r["id"], 3) + }) +} + +func Test_Model_Union(t *testing.T) { + table := createInitTable() + defer dropTable(table) + + gtest.C(t, func(t *gtest.T) { + r, err := db.Model(table).Union( + db.Model(table).Where("id", 1), + db.Model(table).Where("id", 2), + db.Model(table).WhereIn("id", g.Slice{1, 2, 3}).OrderDesc("id"), + ).OrderDesc("id").All() + + t.AssertNil(err) + + t.Assert(len(r), 3) + t.Assert(r[0]["id"], 3) + t.Assert(r[1]["id"], 2) + t.Assert(r[2]["id"], 1) + }) + + gtest.C(t, func(t *gtest.T) { + r, err := db.Model(table).Union( + db.Model(table).Where("id", 1), + db.Model(table).Where("id", 2), + db.Model(table).WhereIn("id", g.Slice{1, 2, 3}).OrderDesc("id"), + ).OrderDesc("id").One() + + t.AssertNil(err) + + t.Assert(r["id"], 3) + }) +} + +func Test_Model_UnionAll(t *testing.T) { + table := createInitTable() + defer dropTable(table) + + gtest.C(t, func(t *gtest.T) { + r, err := db.Model(table).UnionAll( + db.Model(table).Where("id", 1), + db.Model(table).Where("id", 2), + db.Model(table).WhereIn("id", g.Slice{1, 2, 3}).OrderDesc("id"), + ).OrderDesc("id").All() + + t.AssertNil(err) + + t.Assert(len(r), 5) + t.Assert(r[0]["id"], 3) + t.Assert(r[1]["id"], 2) + t.Assert(r[2]["id"], 2) + t.Assert(r[3]["id"], 1) + t.Assert(r[4]["id"], 1) + }) + + gtest.C(t, func(t *gtest.T) { + r, err := db.Model(table).UnionAll( + db.Model(table).Where("id", 1), + db.Model(table).Where("id", 2), + db.Model(table).WhereIn("id", g.Slice{1, 2, 3}).OrderDesc("id"), + ).OrderDesc("id").One() + + t.AssertNil(err) + + t.Assert(r["id"], 3) + }) +} diff --git a/database/gdb/gdb_z_oracle_internal_test.go b/database/gdb/gdb_z_oracle_internal_test.go index 1afdee74a..f10895f1e 100644 --- a/database/gdb/gdb_z_oracle_internal_test.go +++ b/database/gdb/gdb_z_oracle_internal_test.go @@ -1,4 +1,4 @@ -// Copyright GoFrame Author(https://github.com/gogf/gf). All Rights Reserved. +// Copyright GoFrame Author(https://goframe.org). All Rights Reserved. // // This Source Code Form is subject to the terms of the MIT License. // If a copy of the MIT was not distributed with this file, diff --git a/database/gdb/testdata/with_multiple_depends.sql b/database/gdb/testdata/with_multiple_depends.sql new file mode 100644 index 000000000..f4327b947 --- /dev/null +++ b/database/gdb/testdata/with_multiple_depends.sql @@ -0,0 +1,33 @@ + +CREATE TABLE `table_a` ( + `id` int(11) NOT NULL AUTO_INCREMENT, + `alias` varchar(255) NULL DEFAULT '', + PRIMARY KEY (`id`) USING BTREE +) ENGINE = InnoDB; + +INSERT INTO `table_a` VALUES (1, 'table_a_test1'); +INSERT INTO `table_a` VALUES (2, 'table_a_test2'); + +CREATE TABLE `table_b` ( + `id` int(11) NOT NULL AUTO_INCREMENT, + `table_a_id` int(11) NOT NULL, + `alias` varchar(255) NULL DEFAULT '', + PRIMARY KEY (`id`) USING BTREE +) ENGINE = InnoDB; + +INSERT INTO `table_b` VALUES (10, 1, 'table_b_test1'); +INSERT INTO `table_b` VALUES (20, 2, 'table_b_test2'); +INSERT INTO `table_b` VALUES (30, 1, 'table_b_test3'); +INSERT INTO `table_b` VALUES (40, 2, 'table_b_test4'); + +CREATE TABLE `table_c` ( + `id` int(11) NOT NULL AUTO_INCREMENT, + `table_b_id` int(11) NOT NULL, + `alias` varchar(255) NULL DEFAULT '', + PRIMARY KEY (`id`) USING BTREE +) ENGINE = InnoDB; + +INSERT INTO `table_c` VALUES (100, 10, 'table_c_test1'); +INSERT INTO `table_c` VALUES (200, 10, 'table_c_test2'); +INSERT INTO `table_c` VALUES (300, 20, 'table_c_test3'); +INSERT INTO `table_c` VALUES (400, 30, 'table_c_test4'); \ No newline at end of file diff --git a/database/gredis/gredis.go b/database/gredis/gredis.go index a489c325f..8de774e15 100644 --- a/database/gredis/gredis.go +++ b/database/gredis/gredis.go @@ -1,4 +1,4 @@ -// Copyright 2017 gf Author(https://github.com/gogf/gf). All Rights Reserved. +// Copyright GoFrame Author(https://goframe.org). All Rights Reserved. // // This Source Code Form is subject to the terms of the MIT License. // If a copy of the MIT was not distributed with this file, @@ -14,7 +14,9 @@ package gredis import ( + "context" "fmt" + "github.com/gogf/gf/internal/intlog" "time" "github.com/gogf/gf/container/gmap" @@ -24,42 +26,45 @@ import ( // Redis client. type Redis struct { - pool *redis.Pool // Underlying connection pool. - group string // Configuration group. - config Config // Configuration. + pool *redis.Pool // Underlying connection pool. + group string // Configuration group. + config *Config // Configuration. + ctx context.Context // Context. } -// Redis connection. +// Conn is redis connection. type Conn struct { redis.Conn + ctx context.Context + redis *Redis } -// Redis configuration. +// Config is redis configuration. type Config struct { - Host string - Port int - Db int - Pass string // Password for AUTH. - MaxIdle int // Maximum number of connections allowed to be idle (default is 10) - MaxActive int // Maximum number of connections limit (default is 0 means no limit). - IdleTimeout time.Duration // Maximum idle time for connection (default is 10 seconds, not allowed to be set to 0) - MaxConnLifetime time.Duration // Maximum lifetime of the connection (default is 30 seconds, not allowed to be set to 0) - ConnectTimeout time.Duration // Dial connection timeout. - TLS bool // Specifies the config to use when a TLS connection is dialed. - TLSSkipVerify bool // Disables server name verification when connecting over TLS + Host string `json:"host"` + Port int `json:"port"` + Db int `json:"db"` + Pass string `json:"pass"` // Password for AUTH. + MaxIdle int `json:"maxIdle"` // Maximum number of connections allowed to be idle (default is 10) + MaxActive int `json:"maxActive"` // Maximum number of connections limit (default is 0 means no limit). + IdleTimeout time.Duration `json:"idleTimeout"` // Maximum idle time for connection (default is 10 seconds, not allowed to be set to 0) + MaxConnLifetime time.Duration `json:"maxConnLifetime"` // Maximum lifetime of the connection (default is 30 seconds, not allowed to be set to 0) + ConnectTimeout time.Duration `json:"connectTimeout"` // Dial connection timeout. + TLS bool `json:"tls"` // Specifies the config to use when a TLS connection is dialed. + TLSSkipVerify bool `json:"tlsSkipVerify"` // Disables server name verification when connecting over TLS. } -// Pool statistics. +// PoolStats is statistics of redis connection pool. type PoolStats struct { redis.PoolStats } const ( - gDEFAULT_POOL_IDLE_TIMEOUT = 10 * time.Second - gDEFAULT_POOL_CONN_TIMEOUT = 10 * time.Second - gDEFAULT_POOL_MAX_IDLE = 10 - gDEFAULT_POOL_MAX_ACTIVE = 100 - gDEFAULT_POOL_MAX_LIFE_TIME = 30 * time.Second + defaultPoolIdleTimeout = 10 * time.Second + defaultPoolConnTimeout = 10 * time.Second + defaultPoolMaxIdle = 10 + defaultPoolMaxActive = 100 + defaultPoolMaxLifeTime = 30 * time.Second ) var ( @@ -69,25 +74,25 @@ var ( // New creates a redis client object with given configuration. // Redis client maintains a connection pool automatically. -func New(config Config) *Redis { +func New(config *Config) *Redis { // The MaxIdle is the most important attribute of the connection pool. // Only if this attribute is set, the created connections from client // can not exceed the limit of the server. if config.MaxIdle == 0 { - config.MaxIdle = gDEFAULT_POOL_MAX_IDLE + config.MaxIdle = defaultPoolMaxIdle } // This value SHOULD NOT exceed the connection limit of redis server. if config.MaxActive == 0 { - config.MaxActive = gDEFAULT_POOL_MAX_ACTIVE + config.MaxActive = defaultPoolMaxActive } if config.IdleTimeout == 0 { - config.IdleTimeout = gDEFAULT_POOL_IDLE_TIMEOUT + config.IdleTimeout = defaultPoolIdleTimeout } if config.ConnectTimeout == 0 { - config.ConnectTimeout = gDEFAULT_POOL_CONN_TIMEOUT + config.ConnectTimeout = defaultPoolConnTimeout } if config.MaxConnLifetime == 0 { - config.MaxConnLifetime = gDEFAULT_POOL_MAX_LIFE_TIME + config.MaxConnLifetime = defaultPoolMaxLifeTime } return &Redis{ config: config, @@ -109,6 +114,7 @@ func New(config Config) *Redis { if err != nil { return nil, err } + intlog.Printf(context.TODO(), `open new connection, config:%+v`, config) // AUTH if len(config.Pass) > 0 { if _, err := c.Do("AUTH", config.Pass); err != nil { @@ -158,15 +164,33 @@ func (r *Redis) Close() error { return r.pool.Close() } +// Clone clones and returns a new Redis object, which is a shallow copy of current one. +func (r *Redis) Clone() *Redis { + newRedis := New(r.config) + *newRedis = *r + return newRedis +} + +// Ctx is a channing function which sets the context for next operation. +func (r *Redis) Ctx(ctx context.Context) *Redis { + newRedis := r.Clone() + newRedis.ctx = ctx + return newRedis +} + // Conn returns a raw underlying connection object, // which expose more methods to communicate with server. // **You should call Close function manually if you do not use this connection any further.** func (r *Redis) Conn() *Conn { - return &Conn{r.pool.Get()} + return &Conn{ + Conn: r.pool.Get(), + ctx: r.ctx, + redis: r, + } } -// Alias of Conn, see Conn. -// Deprecated. +// GetConn is alias of Conn, see Conn. +// Deprecated, use Conn instead. func (r *Redis) GetConn() *Conn { return r.Conn() } @@ -209,7 +233,11 @@ func (r *Redis) Stats() *PoolStats { // Do automatically get a connection from pool, and close it when the reply received. // It does not really "close" the connection, but drops it back to the connection pool. func (r *Redis) Do(commandName string, args ...interface{}) (interface{}, error) { - conn := &Conn{r.pool.Get()} + conn := &Conn{ + Conn: r.pool.Get(), + ctx: r.ctx, + redis: r, + } defer conn.Close() return conn.Do(commandName, args...) } @@ -217,7 +245,11 @@ func (r *Redis) Do(commandName string, args ...interface{}) (interface{}, error) // DoWithTimeout sends a command to the server and returns the received reply. // The timeout overrides the read timeout set when dialing the connection. func (r *Redis) DoWithTimeout(timeout time.Duration, commandName string, args ...interface{}) (interface{}, error) { - conn := &Conn{r.pool.Get()} + conn := &Conn{ + Conn: r.pool.Get(), + ctx: r.ctx, + redis: r, + } defer conn.Close() return conn.DoWithTimeout(timeout, commandName, args...) } diff --git a/database/gredis/gredis_config.go b/database/gredis/gredis_config.go index 0902c8362..e6fcae37e 100644 --- a/database/gredis/gredis_config.go +++ b/database/gredis/gredis_config.go @@ -1,4 +1,4 @@ -// Copyright 2019 gf Author(https://github.com/gogf/gf). All Rights Reserved. +// Copyright GoFrame Author(https://goframe.org). All Rights Reserved. // // This Source Code Form is subject to the terms of the MIT License. // If a copy of the MIT was not distributed with this file, @@ -7,10 +7,10 @@ package gredis import ( - "github.com/gogf/gf/internal/intlog" - "time" - + "context" + "github.com/gogf/gf/errors/gcode" "github.com/gogf/gf/errors/gerror" + "github.com/gogf/gf/internal/intlog" "github.com/gogf/gf/container/gmap" "github.com/gogf/gf/text/gregex" @@ -30,7 +30,7 @@ var ( // SetConfig sets the global configuration for specified group. // If <name> is not passed, it sets configuration for the default group name. -func SetConfig(config Config, name ...string) { +func SetConfig(config *Config, name ...string) { group := DefaultGroupName if len(name) > 0 { group = name[0] @@ -38,7 +38,7 @@ func SetConfig(config Config, name ...string) { configs.Set(group, config) instances.Remove(group) - intlog.Printf(`SetConfig for group "%s": %+v`, group, config) + intlog.Printf(context.TODO(), `SetConfig for group "%s": %+v`, group, config) } // SetConfigByStr sets the global configuration for specified group with string. @@ -59,15 +59,15 @@ func SetConfigByStr(str string, name ...string) error { // GetConfig returns the global configuration with specified group name. // If <name> is not passed, it returns configuration of the default group name. -func GetConfig(name ...string) (config Config, ok bool) { +func GetConfig(name ...string) (config *Config, ok bool) { group := DefaultGroupName if len(name) > 0 { group = name[0] } if v := configs.Get(group); v != nil { - return v.(Config), true + return v.(*Config), true } - return Config{}, false + return &Config{}, false } // RemoveConfig removes the global configuration with specified group. @@ -80,16 +80,16 @@ func RemoveConfig(name ...string) { configs.Remove(group) instances.Remove(group) - intlog.Printf(`RemoveConfig: %s`, group) + intlog.Printf(context.TODO(), `RemoveConfig: %s`, group) } // ConfigFromStr parses and returns config from given str. // Eg: host:port[,db,pass?maxIdle=x&maxActive=x&idleTimeout=x&maxConnLifetime=x] -func ConfigFromStr(str string) (config Config, err error) { - array, _ := gregex.MatchString(`([^:]+):*(\d*),{0,1}(\d*),{0,1}(.*)\?(.+)`, str) +func ConfigFromStr(str string) (config *Config, err error) { + array, _ := gregex.MatchString(`^([^:]+):*(\d*),{0,1}(\d*),{0,1}(.*)\?(.+)$`, str) if len(array) == 6 { parse, _ := gstr.Parse(array[5]) - config = Config{ + config = &Config{ Host: array[1], Port: gconv.Int(array[2]), Db: gconv.Int(array[3]), @@ -98,29 +98,14 @@ func ConfigFromStr(str string) (config Config, err error) { if config.Port == 0 { config.Port = DefaultRedisPort } - if v, ok := parse["maxIdle"]; ok { - config.MaxIdle = gconv.Int(v) - } - if v, ok := parse["maxActive"]; ok { - config.MaxActive = gconv.Int(v) - } - if v, ok := parse["idleTimeout"]; ok { - config.IdleTimeout = gconv.Duration(v) * time.Second - } - if v, ok := parse["maxConnLifetime"]; ok { - config.MaxConnLifetime = gconv.Duration(v) * time.Second - } - if v, ok := parse["tls"]; ok { - config.TLS = gconv.Bool(v) - } - if v, ok := parse["skipVerify"]; ok { - config.TLSSkipVerify = gconv.Bool(v) + if err = gconv.Struct(parse, config); err != nil { + return nil, err } return } array, _ = gregex.MatchString(`([^:]+):*(\d*),{0,1}(\d*),{0,1}(.*)`, str) if len(array) == 5 { - config = Config{ + config = &Config{ Host: array[1], Port: gconv.Int(array[2]), Db: gconv.Int(array[3]), @@ -130,7 +115,7 @@ func ConfigFromStr(str string) (config Config, err error) { config.Port = DefaultRedisPort } } else { - err = gerror.Newf(`invalid redis configuration: "%s"`, str) + err = gerror.NewCodef(gcode.CodeInvalidConfiguration, `invalid redis configuration: "%s"`, str) } return } diff --git a/database/gredis/gredis_conn.go b/database/gredis/gredis_conn.go index 32538f639..a56e9ddd7 100644 --- a/database/gredis/gredis_conn.go +++ b/database/gredis/gredis_conn.go @@ -1,4 +1,4 @@ -// Copyright 2019 gf Author(https://github.com/gogf/gf). All Rights Reserved. +// Copyright GoFrame Author(https://goframe.org). All Rights Reserved. // // This Source Code Form is subject to the terms of the MIT License. // If a copy of the MIT was not distributed with this file, @@ -7,9 +7,12 @@ package gredis import ( - "errors" + "context" "github.com/gogf/gf/container/gvar" + "github.com/gogf/gf/errors/gcode" + "github.com/gogf/gf/errors/gerror" "github.com/gogf/gf/internal/json" + "github.com/gogf/gf/os/gtime" "github.com/gogf/gf/util/gconv" "github.com/gomodule/redigo/redis" "reflect" @@ -48,11 +51,28 @@ func (c *Conn) do(timeout time.Duration, commandName string, args ...interface{} if timeout > 0 { conn, ok := c.Conn.(redis.ConnWithTimeout) if !ok { - return gvar.New(nil), errors.New(`current connection does not support "ConnWithTimeout"`) + return gvar.New(nil), gerror.NewCode(gcode.CodeNotSupported, `current connection does not support "ConnWithTimeout"`) } return conn.DoWithTimeout(timeout, commandName, args...) } - return c.Conn.Do(commandName, args...) + timestampMilli1 := gtime.TimestampMilli() + reply, err = c.Conn.Do(commandName, args...) + timestampMilli2 := gtime.TimestampMilli() + + // Tracing. + c.addTracingItem(&tracingItem{ + err: err, + commandName: commandName, + arguments: args, + costMilli: timestampMilli2 - timestampMilli1, + }) + return +} + +// Ctx is a channing function which sets the context for next operation. +func (c *Conn) Ctx(ctx context.Context) *Conn { + c.ctx = ctx + return c } // Do sends a command to the server and returns the received reply. @@ -88,7 +108,7 @@ func (c *Conn) ReceiveVar() (*gvar.Var, error) { func (c *Conn) ReceiveVarWithTimeout(timeout time.Duration) (*gvar.Var, error) { conn, ok := c.Conn.(redis.ConnWithTimeout) if !ok { - return gvar.New(nil), errors.New(`current connection does not support "ConnWithTimeout"`) + return gvar.New(nil), gerror.NewCode(gcode.CodeNotSupported, `current connection does not support "ConnWithTimeout"`) } return resultToVar(conn.ReceiveWithTimeout(timeout)) } diff --git a/database/gredis/gredis_conn_tracing.go b/database/gredis/gredis_conn_tracing.go new file mode 100644 index 000000000..4fc478423 --- /dev/null +++ b/database/gredis/gredis_conn_tracing.go @@ -0,0 +1,70 @@ +// Copyright GoFrame Author(https://goframe.org). All Rights Reserved. +// +// This Source Code Form is subject to the terms of the MIT License. +// If a copy of the MIT was not distributed with this file, +// You can obtain one at https://github.com/gogf/gf. + +package gredis + +import ( + "context" + "fmt" + "github.com/gogf/gf" + "github.com/gogf/gf/internal/json" + "github.com/gogf/gf/net/gtrace" + "go.opentelemetry.io/otel" + "go.opentelemetry.io/otel/attribute" + "go.opentelemetry.io/otel/codes" + "go.opentelemetry.io/otel/trace" +) + +// tracingItem holds the information for redis tracing. +type tracingItem struct { + err error + commandName string + arguments []interface{} + costMilli int64 +} + +const ( + tracingInstrumentName = "github.com/gogf/gf/database/gredis" + tracingAttrRedisHost = "redis.host" + tracingAttrRedisPort = "redis.port" + tracingAttrRedisDb = "redis.db" + tracingEventRedisExecution = "redis.execution" + tracingEventRedisExecutionCommand = "redis.execution.command" + tracingEventRedisExecutionCost = "redis.execution.cost" + tracingEventRedisExecutionArguments = "redis.execution.arguments" +) + +// addTracingItem checks and adds redis tracing information to OpenTelemetry. +func (c *Conn) addTracingItem(item *tracingItem) { + if !gtrace.IsTracingInternal() || !gtrace.IsActivated(c.ctx) { + return + } + tr := otel.GetTracerProvider().Tracer( + tracingInstrumentName, + trace.WithInstrumentationVersion(gf.VERSION), + ) + ctx := c.ctx + if ctx == nil { + ctx = context.Background() + } + _, span := tr.Start(ctx, "Redis."+item.commandName, trace.WithSpanKind(trace.SpanKindInternal)) + defer span.End() + if item.err != nil { + span.SetStatus(codes.Error, fmt.Sprintf(`%+v`, item.err)) + } + span.SetAttributes(gtrace.CommonLabels()...) + span.SetAttributes( + attribute.String(tracingAttrRedisHost, c.redis.config.Host), + attribute.Int(tracingAttrRedisPort, c.redis.config.Port), + attribute.Int(tracingAttrRedisDb, c.redis.config.Db), + ) + jsonBytes, _ := json.Marshal(item.arguments) + span.AddEvent(tracingEventRedisExecution, trace.WithAttributes( + attribute.String(tracingEventRedisExecutionCommand, item.commandName), + attribute.String(tracingEventRedisExecutionCost, fmt.Sprintf(`%d ms`, item.costMilli)), + attribute.String(tracingEventRedisExecutionArguments, string(jsonBytes)), + )) +} diff --git a/database/gredis/gredis_instance.go b/database/gredis/gredis_instance.go index 34124dd21..4a5afcd25 100644 --- a/database/gredis/gredis_instance.go +++ b/database/gredis/gredis_instance.go @@ -1,4 +1,4 @@ -// Copyright 2017 gf Author(https://github.com/gogf/gf). All Rights Reserved. +// Copyright GoFrame Author(https://goframe.org). All Rights Reserved. // // This Source Code Form is subject to the terms of the MIT License. // If a copy of the MIT was not distributed with this file, diff --git a/database/gredis/gredis_z_example_test.go b/database/gredis/gredis_z_example_test.go index 499d31b34..17e4b8f02 100644 --- a/database/gredis/gredis_z_example_test.go +++ b/database/gredis/gredis_z_example_test.go @@ -1,4 +1,4 @@ -// Copyright 2020 gf Author(https://github.com/gogf/gf). All Rights Reserved. +// Copyright GoFrame Author(https://goframe.org). All Rights Reserved. // // This Source Code Form is subject to the terms of the MIT License. // If a copy of the MIT was not distributed with this file, @@ -10,6 +10,7 @@ import ( "fmt" "github.com/gogf/gf/container/gvar" "github.com/gogf/gf/frame/g" + "github.com/gogf/gf/util/gutil" ) func Example_autoMarshalUnmarshalMap() { @@ -101,7 +102,7 @@ func Example_autoMarshalUnmarshalStructSlice() { fmt.Println(users2) } -func Example_hashSet() { +func Example_hSet() { var ( err error result *gvar.Var @@ -124,3 +125,54 @@ func Example_hashSet() { // May Output: // map[id:10000 name:john] } + +func Example_hMSet_Map() { + var ( + key = "user_100" + data = g.Map{ + "name": "gf", + "sex": 0, + "score": 100, + } + ) + _, err := g.Redis().Do("HMSET", append(g.Slice{key}, gutil.MapToSlice(data)...)...) + if err != nil { + g.Log().Fatal(err) + } + v, err := g.Redis().DoVar("HMGET", key, "name") + if err != nil { + g.Log().Fatal(err) + } + fmt.Println(v.Slice()) + + // May Output: + // [gf] +} + +func Example_hMSet_Struct() { + type User struct { + Name string `json:"name"` + Sex int `json:"sex"` + Score int `json:"score"` + } + var ( + key = "user_100" + data = &User{ + Name: "gf", + Sex: 0, + Score: 100, + } + ) + _, err := g.Redis().Do("HMSET", append(g.Slice{key}, gutil.StructToSlice(data)...)...) + if err != nil { + g.Log().Fatal(err) + } + v, err := g.Redis().DoVar("HMGET", key, "name") + if err != nil { + g.Log().Fatal(err) + } + fmt.Println(v.Slice()) + + // May Output: + // ["gf"] +} diff --git a/database/gredis/gredis_z_unit_conn_test.go b/database/gredis/gredis_z_unit_conn_test.go index ce5157a03..bd93429ec 100644 --- a/database/gredis/gredis_z_unit_conn_test.go +++ b/database/gredis/gredis_z_unit_conn_test.go @@ -1,4 +1,4 @@ -// Copyright 2019 gf Author(https://github.com/gogf/gf). All Rights Reserved. +// Copyright GoFrame Author(https://goframe.org). All Rights Reserved. // // This Source Code Form is subject to the terms of the MIT License. // If a copy of the MIT was not distributed with this file, diff --git a/database/gredis/gredis_z_unit_test.go b/database/gredis/gredis_z_unit_test.go index e13d1934c..bf24e991f 100644 --- a/database/gredis/gredis_z_unit_test.go +++ b/database/gredis/gredis_z_unit_test.go @@ -1,4 +1,4 @@ -// Copyright 2019 gf Author(https://github.com/gogf/gf). All Rights Reserved. +// Copyright GoFrame Author(https://goframe.org). All Rights Reserved. // // This Source Code Form is subject to the terms of the MIT License. // If a copy of the MIT was not distributed with this file, @@ -10,6 +10,7 @@ import ( "github.com/gogf/gf/container/gvar" "github.com/gogf/gf/frame/g" "github.com/gogf/gf/util/guid" + "github.com/gogf/gf/util/gutil" "testing" "time" @@ -21,7 +22,7 @@ import ( ) var ( - config = gredis.Config{ + config = &gredis.Config{ Host: "127.0.0.1", Port: 6379, Db: 1, @@ -138,8 +139,8 @@ func Test_Instance(t *testing.T) { func Test_Error(t *testing.T) { gtest.C(t, func(t *gtest.T) { - config1 := gredis.Config{ - Host: "127.0.0.2", + config1 := &gredis.Config{ + Host: "192.111.0.2", Port: 6379, Db: 1, ConnectTimeout: time.Second, @@ -148,17 +149,7 @@ func Test_Error(t *testing.T) { _, err := redis.Do("info") t.AssertNE(err, nil) - config1 = gredis.Config{ - Host: "127.0.0.1", - Port: 6379, - Db: 1, - Pass: "666666", - } - redis = gredis.New(config1) - _, err = redis.Do("info") - t.AssertNE(err, nil) - - config1 = gredis.Config{ + config1 = &gredis.Config{ Host: "127.0.0.1", Port: 6379, Db: 100, @@ -255,9 +246,11 @@ func Test_HSet(t *testing.T) { func Test_HGetAll1(t *testing.T) { gtest.C(t, func(t *gtest.T) { - var err error - redis := gredis.New(config) - key := guid.S() + var ( + err error + key = guid.S() + redis = gredis.New(config) + ) defer redis.Do("DEL", key) _, err = redis.Do("HSET", key, "id", 100) @@ -296,6 +289,54 @@ func Test_HGetAll2(t *testing.T) { }) } +func Test_HMSet(t *testing.T) { + // map + gtest.C(t, func(t *gtest.T) { + var ( + err error + key = guid.S() + redis = gredis.New(config) + data = g.Map{ + "name": "gf", + "sex": 0, + "score": 100, + } + ) + defer redis.Do("DEL", key) + + _, err = redis.Do("HMSET", append(g.Slice{key}, gutil.MapToSlice(data)...)...) + t.Assert(err, nil) + v, err := redis.DoVar("HMGET", key, "name") + t.Assert(err, nil) + t.Assert(v.Slice(), g.Slice{data["name"]}) + }) + // struct + gtest.C(t, func(t *gtest.T) { + type User struct { + Name string `json:"name"` + Sex int `json:"sex"` + Score int `json:"score"` + } + var ( + err error + key = guid.S() + redis = gredis.New(config) + data = &User{ + Name: "gf", + Sex: 0, + Score: 100, + } + ) + defer redis.Do("DEL", key) + + _, err = redis.Do("HMSET", append(g.Slice{key}, gutil.StructToSlice(data)...)...) + t.Assert(err, nil) + v, err := redis.DoVar("HMGET", key, "name") + t.Assert(err, nil) + t.Assert(v.Slice(), g.Slice{data.Name}) + }) +} + func Test_Auto_Marshal(t *testing.T) { var ( err error diff --git a/debug/gdebug/gdebug.go b/debug/gdebug/gdebug.go index 4771ee648..7ff8e3db9 100644 --- a/debug/gdebug/gdebug.go +++ b/debug/gdebug/gdebug.go @@ -1,4 +1,4 @@ -// Copyright 2019 gf Author(https://github.com/gogf/gf). All Rights Reserved. +// Copyright GoFrame Author(https://goframe.org). All Rights Reserved. // // This Source Code Form is subject to the terms of the MIT License. // If a copy of the MIT was not distributed with this file, diff --git a/debug/gdebug/gdebug_caller.go b/debug/gdebug/gdebug_caller.go index d5a55594e..b2e2577f3 100644 --- a/debug/gdebug/gdebug_caller.go +++ b/debug/gdebug/gdebug_caller.go @@ -1,4 +1,4 @@ -// Copyright 2019-2020 gf Author(https://github.com/gogf/gf). All Rights Reserved. +// Copyright GoFrame Author(https://goframe.org). All Rights Reserved. // // This Source Code Form is subject to the terms of the MIT License. // If a copy of the MIT was not distributed with this file, @@ -8,6 +8,7 @@ package gdebug import ( "fmt" + "github.com/gogf/gf/internal/utils" "os" "os/exec" "path/filepath" @@ -17,8 +18,8 @@ import ( ) const ( - gMAX_DEPTH = 1000 - gFILTER_KEY = "/debug/gdebug/gdebug" + maxCallerDepth = 1000 + stackFilterKey = "/debug/gdebug/gdebug" ) var ( @@ -42,35 +43,31 @@ func init() { } } -// CallerPath returns the function name and the absolute file path along with its line +// Caller returns the function name and the absolute file path along with its line // number of the caller. func Caller(skip ...int) (function string, path string, line int) { return CallerWithFilter("", skip...) } -// CallerPathWithFilter returns the function name and the absolute file path along with +// CallerWithFilter returns the function name and the absolute file path along with // its line number of the caller. // -// The parameter <filter> is used to filter the path of the caller. +// The parameter `filter` is used to filter the path of the caller. func CallerWithFilter(filter string, skip ...int) (function string, path string, line int) { - number := 0 + var ( + number = 0 + ok = true + ) if len(skip) > 0 { number = skip[0] } - ok := true pc, file, line, start := callerFromIndex([]string{filter}) if start != -1 { - for i := start + number; i < gMAX_DEPTH; i++ { + for i := start + number; i < maxCallerDepth; i++ { if i != start { pc, file, line, ok = runtime.Caller(i) } if ok { - if filter != "" && strings.Contains(file, filter) { - continue - } - if strings.Contains(file, gFILTER_KEY) { - continue - } function := "" if fn := runtime.FuncForPC(pc); fn == nil { function = "unknown" @@ -92,7 +89,7 @@ func CallerWithFilter(filter string, skip ...int) (function string, path string, // VERY NOTE THAT, the returned index value should be <index - 1> as the caller's start point. func callerFromIndex(filters []string) (pc uintptr, file string, line int, index int) { var filtered, ok bool - for index = 0; index < gMAX_DEPTH; index++ { + for index = 0; index < maxCallerDepth; index++ { if pc, file, line, ok = runtime.Caller(index); ok { filtered = false for _, filter := range filters { @@ -104,8 +101,14 @@ func callerFromIndex(filters []string) (pc uintptr, file string, line int, index if filtered { continue } - if strings.Contains(file, gFILTER_KEY) { - continue + if !utils.IsDebugEnabled() { + if strings.Contains(file, utils.StackFilterKeyForGoFrame) { + continue + } + } else { + if strings.Contains(file, stackFilterKey) { + continue + } } if index > 0 { index-- @@ -163,12 +166,12 @@ func CallerFileLineShort() string { return fmt.Sprintf(`%s:%d`, filepath.Base(path), line) } -// FuncPath returns the complete function path of given <f>. +// FuncPath returns the complete function path of given `f`. func FuncPath(f interface{}) string { return runtime.FuncForPC(reflect.ValueOf(f).Pointer()).Name() } -// FuncName returns the function name of given <f>. +// FuncName returns the function name of given `f`. func FuncName(f interface{}) string { path := FuncPath(f) if path == "" { diff --git a/debug/gdebug/gdebug_grid.go b/debug/gdebug/gdebug_grid.go index 4f01fb9cd..f43023d87 100644 --- a/debug/gdebug/gdebug_grid.go +++ b/debug/gdebug/gdebug_grid.go @@ -1,4 +1,4 @@ -// Copyright 2019-2020 gf Author(https://github.com/gogf/gf). All Rights Reserved. +// Copyright GoFrame Author(https://goframe.org). All Rights Reserved. // // This Source Code Form is subject to the terms of the MIT License. // If a copy of the MIT was not distributed with this file, diff --git a/debug/gdebug/gdebug_stack.go b/debug/gdebug/gdebug_stack.go index 67ec3e30e..1a2f0c37d 100644 --- a/debug/gdebug/gdebug_stack.go +++ b/debug/gdebug/gdebug_stack.go @@ -1,4 +1,4 @@ -// Copyright 2019-2020 gf Author(https://github.com/gogf/gf). All Rights Reserved. +// Copyright GoFrame Author(https://goframe.org). All Rights Reserved. // // This Source Code Form is subject to the terms of the MIT License. // If a copy of the MIT was not distributed with this file, @@ -9,6 +9,7 @@ package gdebug import ( "bytes" "fmt" + "github.com/gogf/gf/internal/utils" "runtime" "strings" ) @@ -27,7 +28,7 @@ func Stack(skip ...int) string { // StackWithFilter returns a formatted stack trace of the goroutine that calls it. // It calls runtime.Stack with a large enough buffer to capture the entire trace. // -// The parameter <filter> is used to filter the path of the caller. +// The parameter `filter` is used to filter the path of the caller. func StackWithFilter(filter string, skip ...int) string { return StackWithFilters([]string{filter}, skip...) } @@ -35,7 +36,7 @@ func StackWithFilter(filter string, skip ...int) string { // StackWithFilters returns a formatted stack trace of the goroutine that calls it. // It calls runtime.Stack with a large enough buffer to capture the entire trace. // -// The parameter <filters> is a slice of strings, which are used to filter the path of the +// The parameter `filters` is a slice of strings, which are used to filter the path of the // caller. // // TODO Improve the performance using debug.Stack. @@ -53,7 +54,7 @@ func StackWithFilters(filters []string, skip ...int) string { ok = true pc, file, line, start = callerFromIndex(filters) ) - for i := start + number; i < gMAX_DEPTH; i++ { + for i := start + number; i < maxCallerDepth; i++ { if i != start { pc, file, line, ok = runtime.Caller(i) } @@ -79,9 +80,17 @@ func StackWithFilters(filters []string, skip ...int) string { if filtered { continue } - if strings.Contains(file, gFILTER_KEY) { - continue + + if !utils.IsDebugEnabled() { + if strings.Contains(file, utils.StackFilterKeyForGoFrame) { + continue + } + } else { + if strings.Contains(file, stackFilterKey) { + continue + } } + if fn := runtime.FuncForPC(pc); fn == nil { name = "unknown" } else { diff --git a/debug/gdebug/gdebug_testdata.go b/debug/gdebug/gdebug_testdata.go index f0be40a72..c87f24b13 100644 --- a/debug/gdebug/gdebug_testdata.go +++ b/debug/gdebug/gdebug_testdata.go @@ -1,4 +1,4 @@ -// Copyright 2019-2020 gf Author(https://github.com/gogf/gf). All Rights Reserved. +// Copyright GoFrame Author(https://goframe.org). All Rights Reserved. // // This Source Code Form is subject to the terms of the MIT License. // If a copy of the MIT was not distributed with this file, @@ -7,12 +7,13 @@ package gdebug import ( + "io/ioutil" "path/filepath" ) // TestDataPath retrieves and returns the testdata path of current package, // which is used for unit testing cases only. -// The optional parameter <names> specifies the its sub-folders/sub-files, +// The optional parameter `names` specifies the sub-folders/sub-files, // which will be joined with current system separator and returned with the path. func TestDataPath(names ...string) string { path := CallerDirectory() + string(filepath.Separator) + "testdata" @@ -21,3 +22,15 @@ func TestDataPath(names ...string) string { } return path } + +// TestDataContent retrieves and returns the file content for specified testdata path of current package +func TestDataContent(names ...string) string { + path := TestDataPath(names...) + if path != "" { + data, err := ioutil.ReadFile(path) + if err == nil { + return string(data) + } + } + return "" +} diff --git a/debug/gdebug/gdebug_version.go b/debug/gdebug/gdebug_version.go index 69a5fc97e..dabfb0c06 100644 --- a/debug/gdebug/gdebug_version.go +++ b/debug/gdebug/gdebug_version.go @@ -1,4 +1,4 @@ -// Copyright 2019-2020 gf Author(https://github.com/gogf/gf). All Rights Reserved. +// Copyright GoFrame Author(https://goframe.org). All Rights Reserved. // // This Source Code Form is subject to the terms of the MIT License. // If a copy of the MIT was not distributed with this file, diff --git a/debug/gdebug/gdebug_z_bench_test.go b/debug/gdebug/gdebug_z_bench_test.go index 7e26cdd60..768d53ce2 100644 --- a/debug/gdebug/gdebug_z_bench_test.go +++ b/debug/gdebug/gdebug_z_bench_test.go @@ -1,4 +1,4 @@ -// Copyright 2018 gf Author(https://github.com/gogf/gf). All Rights Reserved. +// Copyright GoFrame Author(https://goframe.org). All Rights Reserved. // // This Source Code Form is subject to the terms of the MIT License. // If a copy of the MIT was not distributed with this file, diff --git a/encoding/gbase64/gbase64.go b/encoding/gbase64/gbase64.go index 6a2e7b76d..b88373677 100644 --- a/encoding/gbase64/gbase64.go +++ b/encoding/gbase64/gbase64.go @@ -1,4 +1,4 @@ -// Copyright 2017 gf Author(https://github.com/gogf/gf). All Rights Reserved. +// Copyright GoFrame Author(https://goframe.org). All Rights Reserved. // // This Source Code Form is subject to the terms of the MIT License. // If a copy of the MIT was not distributed with this file, diff --git a/encoding/gbase64/gbase64_test.go b/encoding/gbase64/gbase64_test.go index c382a239b..5193c7172 100644 --- a/encoding/gbase64/gbase64_test.go +++ b/encoding/gbase64/gbase64_test.go @@ -1,4 +1,4 @@ -// Copyright 2017 gf Author(https://github.com/gogf/gf). All Rights Reserved. +// Copyright GoFrame Author(https://goframe.org). All Rights Reserved. // // This Source Code Form is subject to the terms of the MIT License. // If a copy of the MIT was not distributed with this file, diff --git a/encoding/gbinary/gbinary.go b/encoding/gbinary/gbinary.go index 7081b88dc..5fbc48477 100644 --- a/encoding/gbinary/gbinary.go +++ b/encoding/gbinary/gbinary.go @@ -1,4 +1,4 @@ -// Copyright 2017 gf Author(https://github.com/gogf/gf). All Rights Reserved. +// Copyright GoFrame Author(https://goframe.org). All Rights Reserved. // // This Source Code Form is subject to the terms of the MIT License. // If a copy of the MIT was not distributed with this file, diff --git a/encoding/gbinary/gbinary_be.go b/encoding/gbinary/gbinary_be.go index 7d5fcbf40..2313aff1d 100644 --- a/encoding/gbinary/gbinary_be.go +++ b/encoding/gbinary/gbinary_be.go @@ -1,4 +1,4 @@ -// Copyright 2017 gf Author(https://github.com/gogf/gf). All Rights Reserved. +// Copyright GoFrame Author(https://goframe.org). All Rights Reserved. // // This Source Code Form is subject to the terms of the MIT License. // If a copy of the MIT was not distributed with this file, diff --git a/encoding/gbinary/gbinary_bit.go b/encoding/gbinary/gbinary_bit.go index 04cdd32bb..536fb4030 100644 --- a/encoding/gbinary/gbinary_bit.go +++ b/encoding/gbinary/gbinary_bit.go @@ -1,4 +1,4 @@ -// Copyright 2017 gf Author(https://github.com/gogf/gf). All Rights Reserved. +// Copyright GoFrame Author(https://goframe.org). All Rights Reserved. // // This Source Code Form is subject to the terms of the MIT License. // If a copy of the MIT was not distributed with this file, diff --git a/encoding/gbinary/gbinary_func.go b/encoding/gbinary/gbinary_func.go index 8fd332e98..6e1fba246 100644 --- a/encoding/gbinary/gbinary_func.go +++ b/encoding/gbinary/gbinary_func.go @@ -1,4 +1,4 @@ -// Copyright 2019 gf Author(https://github.com/gogf/gf). All Rights Reserved. +// Copyright GoFrame Author(https://goframe.org). All Rights Reserved. // // This Source Code Form is subject to the terms of the MIT License. // If a copy of the MIT was not distributed with this file, diff --git a/encoding/gbinary/gbinary_le.go b/encoding/gbinary/gbinary_le.go index db2cf0439..001208ddf 100644 --- a/encoding/gbinary/gbinary_le.go +++ b/encoding/gbinary/gbinary_le.go @@ -1,4 +1,4 @@ -// Copyright 2017 gf Author(https://github.com/gogf/gf). All Rights Reserved. +// Copyright GoFrame Author(https://goframe.org). All Rights Reserved. // // This Source Code Form is subject to the terms of the MIT License. // If a copy of the MIT was not distributed with this file, diff --git a/encoding/gbinary/gbinary_z_be_test.go b/encoding/gbinary/gbinary_z_be_test.go index 509424c62..41cd46772 100644 --- a/encoding/gbinary/gbinary_z_be_test.go +++ b/encoding/gbinary/gbinary_z_be_test.go @@ -1,4 +1,4 @@ -// Copyright 2017 gf Author(https://github.com/gogf/gf). All Rights Reserved. +// Copyright GoFrame Author(https://goframe.org). All Rights Reserved. // // This Source Code Form is subject to the terms of the MIT License. // If a copy of the MIT was not distributed with this file, diff --git a/encoding/gbinary/gbinary_z_le_test.go b/encoding/gbinary/gbinary_z_le_test.go index a25528b69..6609f40d7 100644 --- a/encoding/gbinary/gbinary_z_le_test.go +++ b/encoding/gbinary/gbinary_z_le_test.go @@ -1,4 +1,4 @@ -// Copyright 2017 gf Author(https://github.com/gogf/gf). All Rights Reserved. +// Copyright GoFrame Author(https://goframe.org). All Rights Reserved. // // This Source Code Form is subject to the terms of the MIT License. // If a copy of the MIT was not distributed with this file, diff --git a/encoding/gbinary/gbinary_z_test.go b/encoding/gbinary/gbinary_z_test.go index 6472375ba..349531f2e 100644 --- a/encoding/gbinary/gbinary_z_test.go +++ b/encoding/gbinary/gbinary_z_test.go @@ -1,4 +1,4 @@ -// Copyright 2017 gf Author(https://github.com/gogf/gf). All Rights Reserved. +// Copyright GoFrame Author(https://goframe.org). All Rights Reserved. // // This Source Code Form is subject to the terms of the MIT License. // If a copy of the MIT was not distributed with this file, diff --git a/encoding/gcharset/gcharset.go b/encoding/gcharset/gcharset.go index 3fcae958f..5d8137087 100644 --- a/encoding/gcharset/gcharset.go +++ b/encoding/gcharset/gcharset.go @@ -1,4 +1,4 @@ -// Copyright 2018-2019 gf Author(https://github.com/gogf/gf). All Rights Reserved. +// Copyright GoFrame Author(https://goframe.org). All Rights Reserved. // // This Source Code Form is subject to the terms of the MIT License. // If a copy of the MIT was not distributed with this file, @@ -21,8 +21,8 @@ package gcharset import ( "bytes" - "errors" - "fmt" + "github.com/gogf/gf/errors/gcode" + "github.com/gogf/gf/errors/gerror" "io/ioutil" "golang.org/x/text/encoding" @@ -60,11 +60,11 @@ func Convert(dstCharset string, srcCharset string, src string) (dst string, err transform.NewReader(bytes.NewReader([]byte(src)), e.NewDecoder()), ) if err != nil { - return "", fmt.Errorf("%s to utf8 failed. %v", srcCharset, err) + return "", gerror.WrapCodef(gcode.CodeInternalError, err, "%s to utf8 failed", srcCharset) } src = string(tmp) } else { - return dst, errors.New(fmt.Sprintf("unsupport srcCharset: %s", srcCharset)) + return dst, gerror.NewCodef(gcode.CodeInvalidParameter, "unsupported srcCharset: %s", srcCharset) } } // Do the converting from UTF-8 to <dstCharset>. @@ -74,11 +74,11 @@ func Convert(dstCharset string, srcCharset string, src string) (dst string, err transform.NewReader(bytes.NewReader([]byte(src)), e.NewEncoder()), ) if err != nil { - return "", fmt.Errorf("utf to %s failed. %v", dstCharset, err) + return "", gerror.WrapCodef(gcode.CodeInternalError, err, "utf to %s failed", dstCharset) } dst = string(tmp) } else { - return dst, errors.New(fmt.Sprintf("unsupport dstCharset: %s", dstCharset)) + return dst, gerror.NewCodef(gcode.CodeInvalidParameter, "unsupported dstCharset: %s", dstCharset) } } else { dst = src diff --git a/encoding/gcharset/gcharset_test.go b/encoding/gcharset/gcharset_test.go index bee6e7b83..2f09f9e84 100644 --- a/encoding/gcharset/gcharset_test.go +++ b/encoding/gcharset/gcharset_test.go @@ -1,4 +1,4 @@ -// Copyright 2018 gf Author(https://github.com/gogf/gf). All Rights Reserved. +// Copyright GoFrame Author(https://goframe.org). All Rights Reserved. // // This Source Code Form is subject to the terms of the MIT License. // If a copy of the MIT was not distributed with this file, diff --git a/encoding/gcompress/gcompress.go b/encoding/gcompress/gcompress.go index 6b78b780c..1b4ca9423 100644 --- a/encoding/gcompress/gcompress.go +++ b/encoding/gcompress/gcompress.go @@ -1,4 +1,4 @@ -// Copyright 2017 gf Author(https://github.com/gogf/gf). All Rights Reserved. +// Copyright GoFrame Author(https://goframe.org). All Rights Reserved. // // This Source Code Form is subject to the terms of the MIT License. // If a copy of the MIT was not distributed with this file, diff --git a/encoding/gcompress/gcompress_gzip.go b/encoding/gcompress/gcompress_gzip.go index e6bd5f155..884bb4a1d 100644 --- a/encoding/gcompress/gcompress_gzip.go +++ b/encoding/gcompress/gcompress_gzip.go @@ -1,4 +1,4 @@ -// Copyright 2017 gf Author(https://github.com/gogf/gf). All Rights Reserved. +// Copyright GoFrame Author(https://goframe.org). All Rights Reserved. // // This Source Code Form is subject to the terms of the MIT License. // If a copy of the MIT was not distributed with this file, diff --git a/encoding/gcompress/gcompress_z_unit_gzip_test.go b/encoding/gcompress/gcompress_z_unit_gzip_test.go index 77bb4fc95..cb0674b11 100644 --- a/encoding/gcompress/gcompress_z_unit_gzip_test.go +++ b/encoding/gcompress/gcompress_z_unit_gzip_test.go @@ -1,4 +1,4 @@ -// Copyright 2017 gf Author(https://github.com/gogf/gf). All Rights Reserved. +// Copyright GoFrame Author(https://goframe.org). All Rights Reserved. // // This Source Code Form is subject to the terms of the MIT License. // If a copy of the MIT was not distributed with this file, diff --git a/encoding/gcompress/gcompress_z_unit_zip_test.go b/encoding/gcompress/gcompress_z_unit_zip_test.go index 66ec5a515..5a289202b 100644 --- a/encoding/gcompress/gcompress_z_unit_zip_test.go +++ b/encoding/gcompress/gcompress_z_unit_zip_test.go @@ -1,4 +1,4 @@ -// Copyright 2017 gf Author(https://github.com/gogf/gf). All Rights Reserved. +// Copyright GoFrame Author(https://goframe.org). All Rights Reserved. // // This Source Code Form is subject to the terms of the MIT License. // If a copy of the MIT was not distributed with this file, diff --git a/encoding/gcompress/gcompress_z_unit_zlib_test.go b/encoding/gcompress/gcompress_z_unit_zlib_test.go index 5683fd297..02d6330e7 100644 --- a/encoding/gcompress/gcompress_z_unit_zlib_test.go +++ b/encoding/gcompress/gcompress_z_unit_zlib_test.go @@ -1,4 +1,4 @@ -// Copyright 2017 gf Author(https://github.com/gogf/gf). All Rights Reserved. +// Copyright GoFrame Author(https://goframe.org). All Rights Reserved. // // This Source Code Form is subject to the terms of the MIT License. // If a copy of the MIT was not distributed with this file, diff --git a/encoding/gcompress/gcompress_zip.go b/encoding/gcompress/gcompress_zip.go index 34076e413..dad27820b 100644 --- a/encoding/gcompress/gcompress_zip.go +++ b/encoding/gcompress/gcompress_zip.go @@ -1,4 +1,4 @@ -// Copyright 2017 gf Author(https://github.com/gogf/gf). All Rights Reserved. +// Copyright GoFrame Author(https://goframe.org). All Rights Reserved. // // This Source Code Form is subject to the terms of the MIT License. // If a copy of the MIT was not distributed with this file, @@ -9,6 +9,7 @@ package gcompress import ( "archive/zip" "bytes" + "context" "github.com/gogf/gf/internal/intlog" "github.com/gogf/gf/os/gfile" "github.com/gogf/gf/text/gstr" @@ -92,7 +93,7 @@ func doZipPathWriter(path string, exclude string, zipWriter *zip.Writer, prefix headerPrefix = strings.Replace(headerPrefix, "//", "/", -1) for _, file := range files { if exclude == file { - intlog.Printf(`exclude file path: %s`, file) + intlog.Printf(context.TODO(), `exclude file path: %s`, file) continue } dir := gfile.Dir(file[len(path):]) diff --git a/encoding/gcompress/gcompress_zlib.go b/encoding/gcompress/gcompress_zlib.go index 8ffcdaf6b..8d3f74605 100644 --- a/encoding/gcompress/gcompress_zlib.go +++ b/encoding/gcompress/gcompress_zlib.go @@ -1,4 +1,4 @@ -// Copyright 2017 gf Author(https://github.com/gogf/gf). All Rights Reserved. +// Copyright GoFrame Author(https://goframe.org). All Rights Reserved. // // This Source Code Form is subject to the terms of the MIT License. // If a copy of the MIT was not distributed with this file, diff --git a/encoding/ghash/ghash.go b/encoding/ghash/ghash.go index ede93117f..7d69d4fc6 100644 --- a/encoding/ghash/ghash.go +++ b/encoding/ghash/ghash.go @@ -1,4 +1,4 @@ -// Copyright 2017 gf Author(https://github.com/gogf/gf). All Rights Reserved. +// Copyright GoFrame Author(https://goframe.org). All Rights Reserved. // // This Source Code Form is subject to the terms of the MIT License. // If a copy of the MIT was not distributed with this file, diff --git a/encoding/ghash/ghash_bench_test.go b/encoding/ghash/ghash_bench_test.go index bb91711e8..1504a9b34 100644 --- a/encoding/ghash/ghash_bench_test.go +++ b/encoding/ghash/ghash_bench_test.go @@ -1,4 +1,4 @@ -// Copyright 2017 gf Author(https://github.com/gogf/gf). All Rights Reserved. +// Copyright GoFrame Author(https://goframe.org). All Rights Reserved. // // This Source Code Form is subject to the terms of the MIT License. // If a copy of the MIT was not distributed with this file, diff --git a/encoding/ghtml/ghtml.go b/encoding/ghtml/ghtml.go index 1064ffd2b..da0b798cd 100644 --- a/encoding/ghtml/ghtml.go +++ b/encoding/ghtml/ghtml.go @@ -1,4 +1,4 @@ -// Copyright 2017 gf Author(https://github.com/gogf/gf). All Rights Reserved. +// Copyright GoFrame Author(https://goframe.org). All Rights Reserved. // // This Source Code Form is subject to the terms of the MIT License. // If a copy of the MIT was not distributed with this file, diff --git a/encoding/ghtml/ghtml_test.go b/encoding/ghtml/ghtml_test.go index 9752db19b..fbf62aee9 100644 --- a/encoding/ghtml/ghtml_test.go +++ b/encoding/ghtml/ghtml_test.go @@ -1,4 +1,4 @@ -// Copyright 2017 gf Author(https://github.com/gogf/gf). All Rights Reserved. +// Copyright GoFrame Author(https://goframe.org). All Rights Reserved. // // This Source Code Form is subject to the terms of the MIT License. // If a copy of the MIT was not distributed with this file, diff --git a/encoding/gini/gini.go b/encoding/gini/gini.go index e9c7db20e..0ff476b46 100644 --- a/encoding/gini/gini.go +++ b/encoding/gini/gini.go @@ -1,4 +1,4 @@ -// Copyright 2017 gf Author(https://github.com/gogf/gf). All Rights Reserved. +// Copyright GoFrame Author(https://goframe.org). All Rights Reserved. // // This Source Code Form is subject to the terms of the MIT License. // If a copy of the MIT was not distributed with this file, @@ -10,8 +10,9 @@ package gini import ( "bufio" "bytes" - "errors" "fmt" + "github.com/gogf/gf/errors/gcode" + "github.com/gogf/gf/errors/gerror" "github.com/gogf/gf/internal/json" "io" "strings" @@ -70,7 +71,7 @@ func Decode(data []byte) (res map[string]interface{}, err error) { } if haveSection == false { - return nil, errors.New("failed to parse INI file, section not found") + return nil, gerror.NewCode(gcode.CodeInvalidParameter, "failed to parse INI file, section not found") } return res, nil } diff --git a/encoding/gini/gini_test.go b/encoding/gini/gini_test.go index f5339cca2..e373410b5 100644 --- a/encoding/gini/gini_test.go +++ b/encoding/gini/gini_test.go @@ -1,4 +1,4 @@ -// Copyright 2017 gf Author(https://github.com/gogf/gf). All Rights Reserved. +// Copyright GoFrame Author(https://goframe.org). All Rights Reserved. // // This Source Code Form is subject to the terms of the MIT License. // If a copy of the MIT was not distributed with this file, diff --git a/encoding/gjson/gjson.go b/encoding/gjson/gjson.go index 1087422a6..887b0f232 100644 --- a/encoding/gjson/gjson.go +++ b/encoding/gjson/gjson.go @@ -1,4 +1,4 @@ -// Copyright 2017 gf Author(https://github.com/gogf/gf). All Rights Reserved. +// Copyright GoFrame Author(https://goframe.org). All Rights Reserved. // // This Source Code Form is subject to the terms of the MIT License. // If a copy of the MIT was not distributed with this file, @@ -8,6 +8,7 @@ package gjson import ( + "github.com/gogf/gf/internal/utils" "reflect" "strconv" "strings" @@ -19,10 +20,10 @@ import ( const ( // Separator char for hierarchical data access. - gDEFAULT_SPLIT_CHAR = '.' + defaultSplitChar = '.' ) -// The customized JSON struct. +// Json is the customized JSON struct. type Json struct { mu *rwmutex.RWMutex p *interface{} // Pointer for hierarchical data access, it's the root of data in default. @@ -30,11 +31,30 @@ type Json struct { vc bool // Violence Check(false in default), which is used to access data when the hierarchical data key contains separator char. } +// Options for Json object creating. +type Options struct { + Safe bool // Mark this object is for in concurrent-safe usage. + Tags string // Custom priority tags for decoding. + StrNumber bool // StrNumber causes the Decoder to unmarshal a number into an interface{} as a string instead of as a float64. +} + +// apiInterface is used for type assert api for Interface(). +type apiInterface interface { + Interface() interface{} +} + // setValue sets <value> to <j> by <pattern>. // Note: // 1. If value is nil and removed is true, means deleting this value; // 2. It's quite complicated in hierarchical data search, node creating and data assignment; func (j *Json) setValue(pattern string, value interface{}, removed bool) error { + if value != nil { + if utils.IsStruct(value) { + if v, ok := value.(apiInterface); ok { + value = v.Interface() + } + } + } array := strings.Split(pattern, string(j.c)) length := len(array) value = j.convertValue(value) diff --git a/encoding/gjson/gjson_api.go b/encoding/gjson/gjson_api.go index 9a76d5992..642990201 100644 --- a/encoding/gjson/gjson_api.go +++ b/encoding/gjson/gjson_api.go @@ -1,4 +1,4 @@ -// Copyright GoFrame Author(https://github.com/gogf/gf). All Rights Reserved. +// Copyright GoFrame Author(https://goframe.org). All Rights Reserved. // // This Source Code Form is subject to the terms of the MIT License. // If a copy of the MIT was not distributed with this file, @@ -18,12 +18,23 @@ import ( ) // Value returns the json value. +// Deprecated, use Interface instead. func (j *Json) Value() interface{} { + return j.Interface() +} + +// Interface returns the json value. +func (j *Json) Interface() interface{} { j.mu.RLock() defer j.mu.RUnlock() return *(j.p) } +// Var returns the json value as *gvar.Var. +func (j *Json) Var() *gvar.Var { + return gvar.New(j.Value()) +} + // IsNil checks whether the value pointed by <j> is nil. func (j *Json) IsNil() bool { j.mu.RLock() @@ -314,23 +325,11 @@ func (j *Json) GetStruct(pattern string, pointer interface{}, mapping ...map[str return gconv.Struct(j.Get(pattern), pointer, mapping...) } -// GetStructDeep does GetStruct recursively. -// Deprecated, use GetStruct instead. -func (j *Json) GetStructDeep(pattern string, pointer interface{}, mapping ...map[string]string) error { - return gconv.StructDeep(j.Get(pattern), pointer, mapping...) -} - // GetStructs converts any slice to given struct slice. func (j *Json) GetStructs(pattern string, pointer interface{}, mapping ...map[string]string) error { return gconv.Structs(j.Get(pattern), pointer, mapping...) } -// GetStructsDeep converts any slice to given struct slice recursively. -// Deprecated, use GetStructs instead. -func (j *Json) GetStructsDeep(pattern string, pointer interface{}, mapping ...map[string]string) error { - return gconv.StructsDeep(j.Get(pattern), pointer, mapping...) -} - // GetScan automatically calls Struct or Structs function according to the type of parameter // <pointer> to implement the converting.. func (j *Json) GetScan(pattern string, pointer interface{}, mapping ...map[string]string) error { @@ -349,13 +348,6 @@ func (j *Json) GetMapToMap(pattern string, pointer interface{}, mapping ...map[s return gconv.MapToMap(j.Get(pattern), pointer, mapping...) } -// GetMapToMapDeep retrieves the value by specified <pattern> and converts it to specified map -// variable recursively. -// See gconv.MapToMapDeep. -func (j *Json) GetMapToMapDeep(pattern string, pointer interface{}, mapping ...map[string]string) error { - return gconv.MapToMapDeep(j.Get(pattern), pointer, mapping...) -} - // GetMapToMaps retrieves the value by specified <pattern> and converts it to specified map slice // variable. // See gconv.MapToMaps. diff --git a/encoding/gjson/gjson_api_config.go b/encoding/gjson/gjson_api_config.go index ff83ed540..1fe53a9ac 100644 --- a/encoding/gjson/gjson_api_config.go +++ b/encoding/gjson/gjson_api_config.go @@ -1,4 +1,4 @@ -// Copyright 2017 gf Author(https://github.com/gogf/gf). All Rights Reserved. +// Copyright GoFrame Author(https://goframe.org). All Rights Reserved. // // This Source Code Form is subject to the terms of the MIT License. // If a copy of the MIT was not distributed with this file, diff --git a/encoding/gjson/gjson_api_encoding.go b/encoding/gjson/gjson_api_encoding.go index dd8bb5d87..cea7a8a1b 100644 --- a/encoding/gjson/gjson_api_encoding.go +++ b/encoding/gjson/gjson_api_encoding.go @@ -1,4 +1,4 @@ -// Copyright 2017 gf Author(https://github.com/gogf/gf). All Rights Reserved. +// Copyright GoFrame Author(https://goframe.org). All Rights Reserved. // // This Source Code Form is subject to the terms of the MIT License. // If a copy of the MIT was not distributed with this file, @@ -70,7 +70,7 @@ func (j *Json) MustToJsonIndentString() string { // ======================================================================== func (j *Json) ToXml(rootTag ...string) ([]byte, error) { - return gxml.Encode(j.ToMap(), rootTag...) + return gxml.Encode(j.Map(), rootTag...) } func (j *Json) ToXmlString(rootTag ...string) (string, error) { @@ -79,7 +79,7 @@ func (j *Json) ToXmlString(rootTag ...string) (string, error) { } func (j *Json) ToXmlIndent(rootTag ...string) ([]byte, error) { - return gxml.EncodeWithIndent(j.ToMap(), rootTag...) + return gxml.EncodeWithIndent(j.Map(), rootTag...) } func (j *Json) ToXmlIndentString(rootTag ...string) (string, error) { @@ -169,14 +169,16 @@ func (j *Json) MustToTomlString() string { // INI // ======================================================================== +// ToIni json to ini func (j *Json) ToIni() ([]byte, error) { j.mu.RLock() defer j.mu.RUnlock() return gini.Encode((*(j.p)).(map[string]interface{})) } +// ToIniString ini to string func (j *Json) ToIniString() (string, error) { - b, e := j.ToToml() + b, e := j.ToIni() return string(b), e } @@ -188,6 +190,7 @@ func (j *Json) MustToIni() []byte { return result } +// MustToIniString . func (j *Json) MustToIniString() string { return gconv.UnsafeBytesToStr(j.MustToIni()) } diff --git a/encoding/gjson/gjson_api_new_load.go b/encoding/gjson/gjson_api_new_load.go index e0b2d52a1..f9b5c673b 100644 --- a/encoding/gjson/gjson_api_new_load.go +++ b/encoding/gjson/gjson_api_new_load.go @@ -1,4 +1,4 @@ -// Copyright 2017 gf Author(https://github.com/gogf/gf). All Rights Reserved. +// Copyright GoFrame Author(https://goframe.org). All Rights Reserved. // // This Source Code Form is subject to the terms of the MIT License. // If a copy of the MIT was not distributed with this file, @@ -8,8 +8,9 @@ package gjson import ( "bytes" - "errors" "fmt" + "github.com/gogf/gf/errors/gcode" + "github.com/gogf/gf/errors/gerror" "reflect" "github.com/gogf/gf/internal/json" @@ -42,15 +43,27 @@ func New(data interface{}, safe ...bool) *Json { // The parameter <safe> specifies whether using this Json object in concurrent-safe context, which // is false in default. func NewWithTag(data interface{}, tags string, safe ...bool) *Json { - j := (*Json)(nil) + option := Options{ + Tags: tags, + } + if len(safe) > 0 && safe[0] { + option.Safe = true + } + return NewWithOptions(data, option) +} + +// NewWithOptions creates a Json object with any variable type of <data>, but <data> should be a map +// or slice for data access reason, or it will make no sense. +func NewWithOptions(data interface{}, options Options) *Json { + var j *Json switch data.(type) { case string, []byte: - if r, err := LoadContent(gconv.Bytes(data)); err == nil { + if r, err := loadContentWithOptions(data, options); err == nil { j = r } else { j = &Json{ p: &data, - c: byte(gDEFAULT_SPLIT_CHAR), + c: byte(defaultSplitChar), vc: false, } } @@ -69,26 +82,26 @@ func NewWithTag(data interface{}, tags string, safe ...bool) *Json { i = gconv.Interfaces(data) j = &Json{ p: &i, - c: byte(gDEFAULT_SPLIT_CHAR), + c: byte(defaultSplitChar), vc: false, } case reflect.Map, reflect.Struct: i := interface{}(nil) - i = gconv.MapDeep(data, tags) + i = gconv.MapDeep(data, options.Tags) j = &Json{ p: &i, - c: byte(gDEFAULT_SPLIT_CHAR), + c: byte(defaultSplitChar), vc: false, } default: j = &Json{ p: &data, - c: byte(gDEFAULT_SPLIT_CHAR), + c: byte(defaultSplitChar), vc: false, } } } - j.mu = rwmutex.New(safe...) + j.mu = rwmutex.New(options.Safe) return j } @@ -99,42 +112,133 @@ func Load(path string, safe ...bool) (*Json, error) { } else { path = p } - return doLoadContent(gfile.Ext(path), gfile.GetBytesWithCache(path), safe...) + option := Options{} + if len(safe) > 0 && safe[0] { + option.Safe = true + } + return doLoadContentWithOptions(gfile.Ext(path), gfile.GetBytesWithCache(path), option) } // LoadJson creates a Json object from given JSON format content. func LoadJson(data interface{}, safe ...bool) (*Json, error) { - return doLoadContent("json", gconv.Bytes(data), safe...) + option := Options{} + if len(safe) > 0 && safe[0] { + option.Safe = true + } + return doLoadContentWithOptions("json", gconv.Bytes(data), option) } // LoadXml creates a Json object from given XML format content. func LoadXml(data interface{}, safe ...bool) (*Json, error) { - return doLoadContent("xml", gconv.Bytes(data), safe...) + option := Options{} + if len(safe) > 0 && safe[0] { + option.Safe = true + } + return doLoadContentWithOptions("xml", gconv.Bytes(data), option) } // LoadIni creates a Json object from given INI format content. func LoadIni(data interface{}, safe ...bool) (*Json, error) { - return doLoadContent("ini", gconv.Bytes(data), safe...) + option := Options{} + if len(safe) > 0 && safe[0] { + option.Safe = true + } + return doLoadContentWithOptions("ini", gconv.Bytes(data), option) } // LoadYaml creates a Json object from given YAML format content. func LoadYaml(data interface{}, safe ...bool) (*Json, error) { - return doLoadContent("yaml", gconv.Bytes(data), safe...) + option := Options{} + if len(safe) > 0 && safe[0] { + option.Safe = true + } + return doLoadContentWithOptions("yaml", gconv.Bytes(data), option) } // LoadToml creates a Json object from given TOML format content. func LoadToml(data interface{}, safe ...bool) (*Json, error) { - return doLoadContent("toml", gconv.Bytes(data), safe...) + option := Options{} + if len(safe) > 0 && safe[0] { + option.Safe = true + } + return doLoadContentWithOptions("toml", gconv.Bytes(data), option) +} + +// LoadContent creates a Json object from given content, it checks the data type of <content> +// automatically, supporting data content type as follows: +// JSON, XML, INI, YAML and TOML. +func LoadContent(data interface{}, safe ...bool) (*Json, error) { + content := gconv.Bytes(data) + if len(content) == 0 { + return New(nil, safe...), nil + } + return LoadContentType(checkDataType(content), content, safe...) +} + +// LoadContentType creates a Json object from given type and content, +// supporting data content type as follows: +// JSON, XML, INI, YAML and TOML. +func LoadContentType(dataType string, data interface{}, safe ...bool) (*Json, error) { + content := gconv.Bytes(data) + if len(content) == 0 { + return New(nil, safe...), nil + } + //ignore UTF8-BOM + if content[0] == 0xEF && content[1] == 0xBB && content[2] == 0xBF { + content = content[3:] + } + option := Options{} + if len(safe) > 0 && safe[0] { + option.Safe = true + } + return doLoadContentWithOptions(dataType, content, option) +} + +// 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 +} + +func loadContentWithOptions(data interface{}, options Options) (*Json, error) { + content := gconv.Bytes(data) + if len(content) == 0 { + return NewWithOptions(nil, options), nil + } + return loadContentTypeWithOptions(checkDataType(content), content, options) +} + +func loadContentTypeWithOptions(dataType string, data interface{}, options Options) (*Json, error) { + content := gconv.Bytes(data) + if len(content) == 0 { + return NewWithOptions(nil, options), nil + } + //ignore UTF8-BOM + if content[0] == 0xEF && content[1] == 0xBB && content[2] == 0xBF { + content = content[3:] + } + return doLoadContentWithOptions(dataType, content, options) } // doLoadContent creates a Json object from given content. // It supports data content type as follows: // JSON, XML, INI, YAML and TOML. -func doLoadContent(dataType string, data []byte, safe ...bool) (*Json, error) { - var err error - var result interface{} +func doLoadContentWithOptions(dataType string, data []byte, options Options) (*Json, error) { + var ( + err error + result interface{} + ) if len(data) == 0 { - return New(nil, safe...), nil + return NewWithOptions(nil, options), nil } if dataType == "" { dataType = checkDataType(data) @@ -161,16 +265,15 @@ func doLoadContent(dataType string, data []byte, safe ...bool) (*Json, error) { return nil, err } default: - err = errors.New("unsupported type for loading") + err = gerror.NewCode(gcode.CodeInvalidParameter, "unsupported type for loading") } if err != nil { return nil, err } decoder := json.NewDecoder(bytes.NewReader(data)) - // Do not use number, it converts float64 to json.Number type, - // which actually a string type. It causes converting issue for other data formats, - // for example: yaml. - //decoder.UseNumber() + if options.StrNumber { + decoder.UseNumber() + } if err := decoder.Decode(&result); err != nil { return nil, err } @@ -178,48 +281,7 @@ func doLoadContent(dataType string, data []byte, safe ...bool) (*Json, error) { case string, []byte: return nil, fmt.Errorf(`json decoding failed for content: %s`, string(data)) } - return New(result, safe...), nil -} - -// LoadContent creates a Json object from given content, it checks the data type of <content> -// automatically, supporting data content type as follows: -// JSON, XML, INI, YAML and TOML. -func LoadContent(data interface{}, safe ...bool) (*Json, error) { - content := gconv.Bytes(data) - if len(content) == 0 { - return New(nil, safe...), nil - } - return LoadContentType(checkDataType(content), content, safe...) -} - -// LoadContentType creates a Json object from given type and content, -// supporting data content type as follows: -// JSON, XML, INI, YAML and TOML. -func LoadContentType(dataType string, data interface{}, safe ...bool) (*Json, error) { - content := gconv.Bytes(data) - if len(content) == 0 { - return New(nil, safe...), nil - } - //ignore UTF8-BOM - if content[0] == 0xEF && content[1] == 0xBB && content[2] == 0xBF { - content = content[3:] - } - 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 + return NewWithOptions(result, options), nil } // checkDataType automatically checks and returns the data type for <content>. @@ -230,7 +292,8 @@ func checkDataType(content []byte) string { return "json" } else if gregex.IsMatch(`^<.+>[\S\s]+<.+>\s*$`, content) { return "xml" - } 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) && + } 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" diff --git a/encoding/gjson/gjson_deprecated.go b/encoding/gjson/gjson_deprecated.go deleted file mode 100644 index 581f0e653..000000000 --- a/encoding/gjson/gjson_deprecated.go +++ /dev/null @@ -1,113 +0,0 @@ -// Copyright GoFrame 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 gjson - -import "github.com/gogf/gf/util/gconv" - -// ToMap converts current Json object to map[string]interface{}. -// It returns nil if fails. -// Deprecated, use Map instead. -func (j *Json) ToMap() map[string]interface{} { - j.mu.RLock() - defer j.mu.RUnlock() - return gconv.Map(*(j.p)) -} - -// ToArray converts current Json object to []interface{}. -// It returns nil if fails. -// Deprecated, use Array instead. -func (j *Json) ToArray() []interface{} { - j.mu.RLock() - defer j.mu.RUnlock() - return gconv.Interfaces(*(j.p)) -} - -// ToStruct converts current Json object to specified object. -// The <pointer> should be a pointer type of *struct. -// Deprecated, use Struct instead. -func (j *Json) ToStruct(pointer interface{}, mapping ...map[string]string) error { - j.mu.RLock() - defer j.mu.RUnlock() - return gconv.Struct(*(j.p), pointer, mapping...) -} - -// ToStructDeep converts current Json object to specified object recursively. -// The <pointer> should be a pointer type of *struct. -// Deprecated, use Struct instead. -func (j *Json) ToStructDeep(pointer interface{}, mapping ...map[string]string) error { - j.mu.RLock() - defer j.mu.RUnlock() - return gconv.StructDeep(*(j.p), pointer, mapping...) -} - -// ToStructs converts current Json object to specified object slice. -// The <pointer> should be a pointer type of []struct/*struct. -// Deprecated, use Structs instead. -func (j *Json) ToStructs(pointer interface{}, mapping ...map[string]string) error { - j.mu.RLock() - defer j.mu.RUnlock() - return gconv.Structs(*(j.p), pointer, mapping...) -} - -// ToStructsDeep converts current Json object to specified object slice recursively. -// The <pointer> should be a pointer type of []struct/*struct. -// Deprecated, use Structs instead. -func (j *Json) ToStructsDeep(pointer interface{}, mapping ...map[string]string) error { - j.mu.RLock() - defer j.mu.RUnlock() - return gconv.StructsDeep(*(j.p), pointer, mapping...) -} - -// ToScan automatically calls Struct or Structs function according to the type of parameter -// <pointer> to implement the converting.. -// Deprecated, use Scan instead. -func (j *Json) ToScan(pointer interface{}, mapping ...map[string]string) error { - return gconv.Scan(*(j.p), pointer, mapping...) -} - -// ToScanDeep automatically calls StructDeep or StructsDeep function according to the type of -// parameter <pointer> to implement the converting.. -// Deprecated, use Scan instead. -func (j *Json) ToScanDeep(pointer interface{}, mapping ...map[string]string) error { - return gconv.ScanDeep(*(j.p), pointer, mapping...) -} - -// ToMapToMap converts current Json object to specified map variable. -// The parameter of <pointer> should be type of *map. -// Deprecated, use MapToMap instead. -func (j *Json) ToMapToMap(pointer interface{}, mapping ...map[string]string) error { - j.mu.RLock() - defer j.mu.RUnlock() - return gconv.MapToMap(*(j.p), pointer, mapping...) -} - -// ToMapToMapDeep converts current Json object to specified map variable recursively. -// The parameter of <pointer> should be type of *map. -// Deprecated, use MapToMap instead. -func (j *Json) ToMapToMapDeep(pointer interface{}, mapping ...map[string]string) error { - j.mu.RLock() - defer j.mu.RUnlock() - return gconv.MapToMapDeep(*(j.p), pointer, mapping...) -} - -// ToMapToMaps converts current Json object to specified map variable slice. -// The parameter of <pointer> should be type of []map/*map. -// Deprecated, use MapToMaps instead. -func (j *Json) ToMapToMaps(pointer interface{}, mapping ...map[string]string) error { - j.mu.RLock() - defer j.mu.RUnlock() - return gconv.MapToMaps(*(j.p), pointer, mapping...) -} - -// ToMapToMapsDeep converts current Json object to specified map variable slice recursively. -// The parameter of <pointer> should be type of []map/*map. -// Deprecated, use MapToMaps instead. -func (j *Json) ToMapToMapsDeep(pointer interface{}, mapping ...map[string]string) error { - j.mu.RLock() - defer j.mu.RUnlock() - return gconv.MapToMapsDeep(*(j.p), pointer, mapping...) -} diff --git a/encoding/gjson/gjson_implements.go b/encoding/gjson/gjson_implements.go index 8a75e9ae7..c621482e9 100644 --- a/encoding/gjson/gjson_implements.go +++ b/encoding/gjson/gjson_implements.go @@ -1,4 +1,4 @@ -// Copyright 2019 gf Author(https://github.com/gogf/gf). All Rights Reserved. +// Copyright GoFrame Author(https://goframe.org). All Rights Reserved. // // This Source Code Form is subject to the terms of the MIT License. // If a copy of the MIT was not distributed with this file, diff --git a/encoding/gjson/gjson_stdlib_json_util.go b/encoding/gjson/gjson_stdlib_json_util.go index c7ebc0bd1..be1c57668 100644 --- a/encoding/gjson/gjson_stdlib_json_util.go +++ b/encoding/gjson/gjson_stdlib_json_util.go @@ -1,4 +1,4 @@ -// Copyright 2019 gf Author(https://github.com/gogf/gf). All Rights Reserved. +// Copyright GoFrame Author(https://goframe.org). All Rights Reserved. // // This Source Code Form is subject to the terms of the MIT License. // If a copy of the MIT was not distributed with this file, diff --git a/encoding/gjson/gjson_z_bench_test.go b/encoding/gjson/gjson_z_bench_test.go index 2d30159b4..2b94f89f7 100644 --- a/encoding/gjson/gjson_z_bench_test.go +++ b/encoding/gjson/gjson_z_bench_test.go @@ -1,4 +1,4 @@ -// Copyright 2017 gf Author(https://github.com/gogf/gf). All Rights Reserved. +// Copyright GoFrame Author(https://goframe.org). All Rights Reserved. // // This Source Code Form is subject to the terms of the MIT License. // If a copy of the MIT was not distributed with this file, diff --git a/encoding/gjson/gjson_z_example_conversion_test.go b/encoding/gjson/gjson_z_example_conversion_test.go index 1d067e217..d2f4bdd9f 100644 --- a/encoding/gjson/gjson_z_example_conversion_test.go +++ b/encoding/gjson/gjson_z_example_conversion_test.go @@ -1,4 +1,4 @@ -// Copyright 2017 gf Author(https://github.com/gogf/gf). All Rights Reserved. +// Copyright GoFrame Author(https://goframe.org). All Rights Reserved. // // This Source Code Form is subject to the terms of the MIT License. // If a copy of the MIT was not distributed with this file, @@ -19,6 +19,7 @@ func Example_conversionNormalFormats() { "array" : ["John", "Ming"] } }` + if j, err := gjson.DecodeToJson(data); err != nil { panic(err) } else { @@ -48,8 +49,8 @@ func Example_conversionNormalFormats() { // YAML: // users: // array: - // - John - // - Ming + // - John + // - Ming // count: 1 // // ====================== @@ -100,7 +101,7 @@ func Example_conversionToStruct() { Array []string } users := new(Users) - if err := j.ToStruct(users); err != nil { + if err := j.Struct(users); err != nil { panic(err) } fmt.Printf(`%+v`, users) diff --git a/encoding/gjson/gjson_z_example_dataset_test.go b/encoding/gjson/gjson_z_example_dataset_test.go index e335d1369..868052a3f 100644 --- a/encoding/gjson/gjson_z_example_dataset_test.go +++ b/encoding/gjson/gjson_z_example_dataset_test.go @@ -1,4 +1,4 @@ -// Copyright 2017 gf Author(https://github.com/gogf/gf). All Rights Reserved. +// Copyright GoFrame Author(https://goframe.org). All Rights Reserved. // // This Source Code Form is subject to the terms of the MIT License. // If a copy of the MIT was not distributed with this file, diff --git a/encoding/gjson/gjson_z_example_load_test.go b/encoding/gjson/gjson_z_example_load_test.go index c67c2368a..733cf4db9 100644 --- a/encoding/gjson/gjson_z_example_load_test.go +++ b/encoding/gjson/gjson_z_example_load_test.go @@ -1,4 +1,4 @@ -// Copyright 2017 gf Author(https://github.com/gogf/gf). All Rights Reserved. +// Copyright GoFrame Author(https://goframe.org). All Rights Reserved. // // This Source Code Form is subject to the terms of the MIT License. // If a copy of the MIT was not distributed with this file, diff --git a/encoding/gjson/gjson_z_example_new_test.go b/encoding/gjson/gjson_z_example_new_test.go index e735284c9..6b0f2b918 100644 --- a/encoding/gjson/gjson_z_example_new_test.go +++ b/encoding/gjson/gjson_z_example_new_test.go @@ -1,4 +1,4 @@ -// Copyright 2017 gf Author(https://github.com/gogf/gf). All Rights Reserved. +// Copyright GoFrame Author(https://goframe.org). All Rights Reserved. // // This Source Code Form is subject to the terms of the MIT License. // If a copy of the MIT was not distributed with this file, diff --git a/encoding/gjson/gjson_z_example_pattern_test.go b/encoding/gjson/gjson_z_example_pattern_test.go index 9af658333..faa6e09a5 100644 --- a/encoding/gjson/gjson_z_example_pattern_test.go +++ b/encoding/gjson/gjson_z_example_pattern_test.go @@ -1,4 +1,4 @@ -// Copyright 2017 gf Author(https://github.com/gogf/gf). All Rights Reserved. +// Copyright GoFrame Author(https://goframe.org). All Rights Reserved. // // This Source Code Form is subject to the terms of the MIT License. // If a copy of the MIT was not distributed with this file, diff --git a/encoding/gjson/gjson_z_unit_basic_test.go b/encoding/gjson/gjson_z_unit_basic_test.go index 6269c94ff..7013f2fbf 100644 --- a/encoding/gjson/gjson_z_unit_basic_test.go +++ b/encoding/gjson/gjson_z_unit_basic_test.go @@ -1,4 +1,4 @@ -// Copyright 2017 gf Author(https://github.com/gogf/gf). All Rights Reserved. +// Copyright GoFrame Author(https://goframe.org). All Rights Reserved. // // This Source Code Form is subject to the terms of the MIT License. // If a copy of the MIT was not distributed with this file, @@ -353,7 +353,7 @@ func Test_Convert2(t *testing.T) { t.Assert(j.GetGTime("time").Format("Y-m-d"), "2019-06-12") t.Assert(j.GetDuration("time").String(), "0s") - err := j.ToStruct(&name) + err := j.Struct(&name) t.Assert(err, nil) t.Assert(name.Name, "gf") //j.Dump() @@ -369,7 +369,7 @@ func Test_Convert2(t *testing.T) { t.Assert(err, nil) j = gjson.New(`[1,2,3]`) - t.Assert(len(j.ToArray()), 3) + t.Assert(len(j.Array()), 3) }) } @@ -400,7 +400,7 @@ func Test_Basic(t *testing.T) { err = j.Remove("1") t.Assert(err, nil) t.Assert(j.Get("0"), 1) - t.Assert(len(j.ToArray()), 2) + t.Assert(len(j.Array()), 2) j = gjson.New(`[1,2,3]`) // If index 0 is delete, its next item will be at index 0. @@ -408,13 +408,13 @@ func Test_Basic(t *testing.T) { t.Assert(j.Remove("0"), nil) t.Assert(j.Remove("0"), nil) t.Assert(j.Get("0"), nil) - t.Assert(len(j.ToArray()), 0) + t.Assert(len(j.Array()), 0) j = gjson.New(`[1,2,3]`) err = j.Remove("3") t.Assert(err, nil) t.Assert(j.Get("0"), 1) - t.Assert(len(j.ToArray()), 3) + t.Assert(len(j.Array()), 3) j = gjson.New(`[1,2,3]`) err = j.Remove("0.3") @@ -466,9 +466,36 @@ func Test_Basic(t *testing.T) { }) } -func Test_IsNil(t *testing.T) { +func TestJson_Var(t *testing.T) { + gtest.C(t, func(t *gtest.T) { + data := []byte("[9223372036854775807, 9223372036854775806]") + array := gjson.New(data).Var().Array() + t.Assert(array, []uint64{9223372036854776000, 9223372036854776000}) + }) + gtest.C(t, func(t *gtest.T) { + data := []byte("[9223372036854775807, 9223372036854775806]") + array := gjson.NewWithOptions(data, gjson.Options{StrNumber: true}).Var().Array() + t.Assert(array, []uint64{9223372036854775807, 9223372036854775806}) + }) +} + +func TestJson_IsNil(t *testing.T) { gtest.C(t, func(t *gtest.T) { j := gjson.New(nil) t.Assert(j.IsNil(), true) }) } + +func TestJson_Set_With_Struct(t *testing.T) { + gtest.C(t, func(t *gtest.T) { + v := gjson.New(g.Map{ + "user1": g.Map{"name": "user1"}, + "user2": g.Map{"name": "user2"}, + "user3": g.Map{"name": "user3"}, + }) + user1 := v.GetJson("user1") + user1.Set("id", 111) + v.Set("user1", user1) + t.Assert(v.Get("user1.id"), 111) + }) +} diff --git a/encoding/gjson/gjson_z_unit_implements_test.go b/encoding/gjson/gjson_z_unit_implements_test.go index 7f1601098..8350f3a29 100644 --- a/encoding/gjson/gjson_z_unit_implements_test.go +++ b/encoding/gjson/gjson_z_unit_implements_test.go @@ -1,4 +1,4 @@ -// Copyright 2017 gf Author(https://github.com/gogf/gf). All Rights Reserved. +// Copyright GoFrame Author(https://goframe.org). All Rights Reserved. // // This Source Code Form is subject to the terms of the MIT License. // If a copy of the MIT was not distributed with this file, @@ -20,7 +20,7 @@ func TestJson_UnmarshalJSON(t *testing.T) { data := []byte(`{"n":123456789, "m":{"k":"v"}, "a":[1,2,3]}`) gtest.C(t, func(t *gtest.T) { j := gjson.New(nil) - err := json.Unmarshal(data, j) + err := json.UnmarshalUseNumber(data, j) t.Assert(err, nil) t.Assert(j.Get("n"), "123456789") t.Assert(j.Get("m"), g.Map{"k": "v"}) diff --git a/encoding/gjson/gjson_z_unit_internal_test.go b/encoding/gjson/gjson_z_unit_internal_test.go index 1fb508956..99b07ad62 100644 --- a/encoding/gjson/gjson_z_unit_internal_test.go +++ b/encoding/gjson/gjson_z_unit_internal_test.go @@ -1,4 +1,4 @@ -// Copyright 2017 gf Author(https://github.com/gogf/gf). All Rights Reserved. +// Copyright GoFrame Author(https://goframe.org). All Rights Reserved. // // This Source Code Form is subject to the terms of the MIT License. // If a copy of the MIT was not distributed with this file, diff --git a/encoding/gjson/gjson_z_unit_json_test.go b/encoding/gjson/gjson_z_unit_json_test.go index e2d113cff..27f5bd27c 100644 --- a/encoding/gjson/gjson_z_unit_json_test.go +++ b/encoding/gjson/gjson_z_unit_json_test.go @@ -1,4 +1,4 @@ -// Copyright 2017 gf Author(https://github.com/gogf/gf). All Rights Reserved. +// Copyright GoFrame Author(https://goframe.org). All Rights Reserved. // // This Source Code Form is subject to the terms of the MIT License. // If a copy of the MIT was not distributed with this file, @@ -61,7 +61,7 @@ func Test_MapAttributeConvert(t *testing.T) { Title map[string]interface{} }{} - err = j.ToStruct(&tx) + err = j.Struct(&tx) gtest.Assert(err, nil) t.Assert(tx.Title, g.Map{ "l1": "标签1", "l2": "标签2", @@ -76,7 +76,7 @@ func Test_MapAttributeConvert(t *testing.T) { Title map[string]string }{} - err = j.ToStruct(&tx) + err = j.Struct(&tx) gtest.Assert(err, nil) t.Assert(tx.Title, g.Map{ "l1": "标签1", "l2": "标签2", diff --git a/encoding/gjson/gjson_z_unit_load_test.go b/encoding/gjson/gjson_z_unit_load_test.go index a59137e18..c26a4e258 100644 --- a/encoding/gjson/gjson_z_unit_load_test.go +++ b/encoding/gjson/gjson_z_unit_load_test.go @@ -1,4 +1,4 @@ -// Copyright 2017 gf Author(https://github.com/gogf/gf). All Rights Reserved. +// Copyright GoFrame Author(https://goframe.org). All Rights Reserved. // // This Source Code Form is subject to the terms of the MIT License. // If a copy of the MIT was not distributed with this file, diff --git a/encoding/gjson/gjson_z_unit_new_test.go b/encoding/gjson/gjson_z_unit_new_test.go index a4b64fb1b..359cafd64 100644 --- a/encoding/gjson/gjson_z_unit_new_test.go +++ b/encoding/gjson/gjson_z_unit_new_test.go @@ -1,4 +1,4 @@ -// Copyright 2017 gf Author(https://github.com/gogf/gf). All Rights Reserved. +// Copyright GoFrame Author(https://goframe.org). All Rights Reserved. // // This Source Code Form is subject to the terms of the MIT License. // If a copy of the MIT was not distributed with this file, @@ -13,7 +13,7 @@ import ( "github.com/gogf/gf/test/gtest" ) -func Test_Load_NewWithTag(t *testing.T) { +func Test_NewWithTag(t *testing.T) { type User struct { Age int `xml:"age-xml" json:"age-json"` Name string `xml:"name-xml" json:"name-json"` @@ -48,7 +48,7 @@ func Test_Load_NewWithTag(t *testing.T) { }) } -func Test_Load_New_CustomStruct(t *testing.T) { +func Test_New_CustomStruct(t *testing.T) { type Base struct { Id int } @@ -70,7 +70,7 @@ func Test_Load_New_CustomStruct(t *testing.T) { }) } -func Test_Load_New_HierarchicalStruct(t *testing.T) { +func Test_New_HierarchicalStruct(t *testing.T) { gtest.C(t, func(t *gtest.T) { type Me struct { Name string `json:"name"` @@ -97,3 +97,16 @@ func Test_Load_New_HierarchicalStruct(t *testing.T) { t.Assert(j.MustToJsonString(), `{"children":[{"children":null,"name":"Bean"},{"children":null,"name":"Sam"}],"name":"john","score":100}`) }) } + +func Test_NewWithOptions(t *testing.T) { + gtest.C(t, func(t *gtest.T) { + data := []byte("[9223372036854775807, 9223372036854775806]") + array := gjson.New(data).Array() + t.Assert(array, []uint64{9223372036854776000, 9223372036854776000}) + }) + gtest.C(t, func(t *gtest.T) { + data := []byte("[9223372036854775807, 9223372036854775806]") + array := gjson.NewWithOptions(data, gjson.Options{StrNumber: true}).Array() + t.Assert(array, []uint64{9223372036854775807, 9223372036854775806}) + }) +} diff --git a/encoding/gjson/gjson_z_unit_set_test.go b/encoding/gjson/gjson_z_unit_set_test.go index 353a0e214..1a6dc557d 100644 --- a/encoding/gjson/gjson_z_unit_set_test.go +++ b/encoding/gjson/gjson_z_unit_set_test.go @@ -1,4 +1,4 @@ -// Copyright 2017 gf Author(https://github.com/gogf/gf). All Rights Reserved. +// Copyright GoFrame Author(https://goframe.org). All Rights Reserved. // // This Source Code Form is subject to the terms of the MIT License. // If a copy of the MIT was not distributed with this file, diff --git a/encoding/gjson/gjson_z_unit_struct_test.go b/encoding/gjson/gjson_z_unit_struct_test.go index 82054c894..bd41a366b 100644 --- a/encoding/gjson/gjson_z_unit_struct_test.go +++ b/encoding/gjson/gjson_z_unit_struct_test.go @@ -1,4 +1,4 @@ -// Copyright 2017 gf Author(https://github.com/gogf/gf). All Rights Reserved. +// Copyright GoFrame Author(https://goframe.org). All Rights Reserved. // // This Source Code Form is subject to the terms of the MIT License. // If a copy of the MIT was not distributed with this file, @@ -76,7 +76,7 @@ func Test_GetScanDeep(t *testing.T) { }) } -func Test_ToScan(t *testing.T) { +func Test_Scan1(t *testing.T) { type User struct { Name string Score float64 @@ -84,7 +84,7 @@ func Test_ToScan(t *testing.T) { j := gjson.New(`[{"name":"john", "score":"100"},{"name":"smith", "score":"60"}]`) gtest.C(t, func(t *gtest.T) { var users []User - err := j.ToScan(&users) + err := j.Scan(&users) t.Assert(err, nil) t.Assert(users, []User{ { @@ -99,7 +99,7 @@ func Test_ToScan(t *testing.T) { }) } -func Test_ToScanDeep(t *testing.T) { +func Test_Scan2(t *testing.T) { type User struct { Name string Score float64 @@ -107,7 +107,7 @@ func Test_ToScanDeep(t *testing.T) { j := gjson.New(`[{"name":"john", "score":"100"},{"name":"smith", "score":"60"}]`) gtest.C(t, func(t *gtest.T) { var users []User - err := j.ToScanDeep(&users) + err := j.Scan(&users) t.Assert(err, nil) t.Assert(users, []User{ { @@ -122,7 +122,7 @@ func Test_ToScanDeep(t *testing.T) { }) } -func Test_ToStruct1(t *testing.T) { +func Test_Struct1(t *testing.T) { gtest.C(t, func(t *gtest.T) { type BaseInfoItem struct { IdCardNumber string `db:"id_card_number" json:"idCardNumber" field:"id_card_number"` @@ -198,12 +198,12 @@ func Test_ToStruct1(t *testing.T) { data := new(UserCollectionAddReq) j, err := gjson.LoadJson(jsonContent) t.Assert(err, nil) - err = j.ToStruct(data) + err = j.Struct(data) t.Assert(err, nil) }) } -func Test_ToStruct(t *testing.T) { +func Test_Struct(t *testing.T) { gtest.C(t, func(t *gtest.T) { type Item struct { Title string `json:"title"` @@ -231,7 +231,7 @@ func Test_ToStruct(t *testing.T) { t.Assert(j.GetBool("items"), false) t.Assert(j.GetArray("items"), nil) m := new(M) - err = j.ToStruct(m) + err = j.Struct(m) t.Assert(err, nil) t.AssertNE(m.Me, nil) t.Assert(m.Me["day"], "20009") @@ -239,7 +239,7 @@ func Test_ToStruct(t *testing.T) { }) } -func Test_ToStruct_Complicated(t *testing.T) { +func Test_Struct_Complicated(t *testing.T) { type CertInfo struct { UserRealName string `json:"userRealname,omitempty"` IdentType string `json:"identType,omitempty"` @@ -290,7 +290,7 @@ func Test_ToStruct_Complicated(t *testing.T) { j, err := gjson.LoadContent(jsonContent) t.Assert(err, nil) var response = new(Response) - err = j.ToStruct(response) + err = j.Struct(response) t.Assert(err, nil) t.Assert(len(response.CertList), 3) t.Assert(response.CertList[0].CertID, 2023313) diff --git a/encoding/gparser/gparser.go b/encoding/gparser/gparser.go index ff7f2738b..89ea36e29 100644 --- a/encoding/gparser/gparser.go +++ b/encoding/gparser/gparser.go @@ -1,4 +1,4 @@ -// Copyright 2017 gf Author(https://github.com/gogf/gf). All Rights Reserved. +// Copyright GoFrame Author(https://goframe.org). All Rights Reserved. // // This Source Code Form is subject to the terms of the MIT License. // If a copy of the MIT was not distributed with this file, diff --git a/encoding/gparser/gparser_api_encoding.go b/encoding/gparser/gparser_api_encoding.go index a848161a7..a9b2901a6 100644 --- a/encoding/gparser/gparser_api_encoding.go +++ b/encoding/gparser/gparser_api_encoding.go @@ -1,4 +1,4 @@ -// Copyright 2017 gf Author(https://github.com/gogf/gf). All Rights Reserved. +// Copyright GoFrame Author(https://goframe.org). All Rights Reserved. // // This Source Code Form is subject to the terms of the MIT License. // If a copy of the MIT was not distributed with this file, diff --git a/encoding/gparser/gparser_api_new_load.go b/encoding/gparser/gparser_api_new_load.go index d731d1140..5b368858b 100644 --- a/encoding/gparser/gparser_api_new_load.go +++ b/encoding/gparser/gparser_api_new_load.go @@ -1,4 +1,4 @@ -// Copyright 2017 gf Author(https://github.com/gogf/gf). All Rights Reserved. +// Copyright GoFrame Author(https://goframe.org). All Rights Reserved. // // This Source Code Form is subject to the terms of the MIT License. // If a copy of the MIT was not distributed with this file, diff --git a/encoding/gparser/gparser_unit_basic_test.go b/encoding/gparser/gparser_unit_basic_test.go index 1eeca7bae..bcdae3700 100644 --- a/encoding/gparser/gparser_unit_basic_test.go +++ b/encoding/gparser/gparser_unit_basic_test.go @@ -1,4 +1,4 @@ -// Copyright 2017 gf Author(https://github.com/gogf/gf). All Rights Reserved. +// Copyright GoFrame Author(https://goframe.org). All Rights Reserved. // // This Source Code Form is subject to the terms of the MIT License. // If a copy of the MIT was not distributed with this file, @@ -230,14 +230,14 @@ func Test_Convert(t *testing.T) { err := p.GetStruct("person", &name) t.Assert(err, nil) t.Assert(name.Name, "gf") - t.Assert(p.ToMap()["name"], "gf") - err = p.ToStruct(&name) + t.Assert(p.Map()["name"], "gf") + err = p.Struct(&name) t.Assert(err, nil) t.Assert(name.Name, "gf") //p.Dump() p = gparser.New(`[0,1,2]`) - t.Assert(p.ToArray()[0], 0) + t.Assert(p.Array()[0], 0) }) } diff --git a/encoding/gparser/gparser_unit_load_test.go b/encoding/gparser/gparser_unit_load_test.go index a92f50c9f..b2cc024d4 100644 --- a/encoding/gparser/gparser_unit_load_test.go +++ b/encoding/gparser/gparser_unit_load_test.go @@ -1,4 +1,4 @@ -// Copyright 2017 gf Author(https://github.com/gogf/gf). All Rights Reserved. +// Copyright GoFrame Author(https://goframe.org). All Rights Reserved. // // This Source Code Form is subject to the terms of the MIT License. // If a copy of the MIT was not distributed with this file, diff --git a/encoding/gparser/gparser_unit_new_test.go b/encoding/gparser/gparser_unit_new_test.go index 0f8984825..1e9614444 100644 --- a/encoding/gparser/gparser_unit_new_test.go +++ b/encoding/gparser/gparser_unit_new_test.go @@ -1,4 +1,4 @@ -// Copyright 2017 gf Author(https://github.com/gogf/gf). All Rights Reserved. +// Copyright GoFrame Author(https://goframe.org). All Rights Reserved. // // This Source Code Form is subject to the terms of the MIT License. // If a copy of the MIT was not distributed with this file, diff --git a/encoding/gparser/gparser_unit_set_test.go b/encoding/gparser/gparser_unit_set_test.go index 777b496c1..7b07f542d 100644 --- a/encoding/gparser/gparser_unit_set_test.go +++ b/encoding/gparser/gparser_unit_set_test.go @@ -1,4 +1,4 @@ -// Copyright 2017 gf Author(https://github.com/gogf/gf). All Rights Reserved. +// Copyright GoFrame Author(https://goframe.org). All Rights Reserved. // // This Source Code Form is subject to the terms of the MIT License. // If a copy of the MIT was not distributed with this file, diff --git a/encoding/gtoml/gtoml.go b/encoding/gtoml/gtoml.go index 3d4a7887d..80610373d 100644 --- a/encoding/gtoml/gtoml.go +++ b/encoding/gtoml/gtoml.go @@ -1,4 +1,4 @@ -// Copyright 2017 gf Author(https://github.com/gogf/gf). All Rights Reserved. +// Copyright GoFrame Author(https://goframe.org). All Rights Reserved. // // This Source Code Form is subject to the terms of the MIT License. // If a copy of the MIT was not distributed with this file, diff --git a/encoding/gtoml/gtoml_test.go b/encoding/gtoml/gtoml_test.go index b9c4ccfd3..1074600b5 100644 --- a/encoding/gtoml/gtoml_test.go +++ b/encoding/gtoml/gtoml_test.go @@ -1,4 +1,4 @@ -// Copyright 2017 gf Author(https://github.com/gogf/gf). All Rights Reserved. +// Copyright GoFrame Author(https://goframe.org). All Rights Reserved. // // This Source Code Form is subject to the terms of the MIT License. // If a copy of the MIT was not distributed with this file, diff --git a/encoding/gurl/url.go b/encoding/gurl/url.go index c1a480519..2e3d2d508 100644 --- a/encoding/gurl/url.go +++ b/encoding/gurl/url.go @@ -1,4 +1,4 @@ -// Copyright 2017 gf Author(https://github.com/gogf/gf). All Rights Reserved. +// Copyright GoFrame Author(https://goframe.org). All Rights Reserved. // // This Source Code Form is subject to the terms of the MIT License. // If a copy of the MIT was not distributed with this file, diff --git a/encoding/gurl/url_test.go b/encoding/gurl/url_test.go index 59cba45b3..c992caea3 100644 --- a/encoding/gurl/url_test.go +++ b/encoding/gurl/url_test.go @@ -1,4 +1,4 @@ -// Copyright 2017 gf Author(https://github.com/gogf/gf). All Rights Reserved. +// Copyright GoFrame Author(https://goframe.org). All Rights Reserved. // // This Source Code Form is subject to the terms of the MIT License. // If a copy of the MIT was not distributed with this file, diff --git a/encoding/gxml/gxml.go b/encoding/gxml/gxml.go index df096f31b..cf831bd28 100644 --- a/encoding/gxml/gxml.go +++ b/encoding/gxml/gxml.go @@ -1,4 +1,4 @@ -// Copyright 2017 gf Author(https://github.com/gogf/gf). All Rights Reserved. +// Copyright GoFrame Author(https://goframe.org). All Rights Reserved. // // This Source Code Form is subject to the terms of the MIT License. // If a copy of the MIT was not distributed with this file, diff --git a/encoding/gxml/gxml_test.go b/encoding/gxml/gxml_test.go index d9ea1970d..8d9989a6d 100644 --- a/encoding/gxml/gxml_test.go +++ b/encoding/gxml/gxml_test.go @@ -1,4 +1,4 @@ -// Copyright 2017 gf Author(https://github.com/gogf/gf). All Rights Reserved. +// Copyright GoFrame Author(https://goframe.org). All Rights Reserved. // // This Source Code Form is subject to the terms of the MIT License. // If a copy of the MIT was not distributed with this file, diff --git a/encoding/gyaml/gyaml.go b/encoding/gyaml/gyaml.go index 587eba319..95c531b57 100644 --- a/encoding/gyaml/gyaml.go +++ b/encoding/gyaml/gyaml.go @@ -1,4 +1,4 @@ -// Copyright 2017 gf Author(https://github.com/gogf/gf). All Rights Reserved. +// Copyright GoFrame Author(https://goframe.org). All Rights Reserved. // // This Source Code Form is subject to the terms of the MIT License. // If a copy of the MIT was not distributed with this file, diff --git a/encoding/gyaml/gyaml_test.go b/encoding/gyaml/gyaml_test.go index e63267c73..072349c37 100644 --- a/encoding/gyaml/gyaml_test.go +++ b/encoding/gyaml/gyaml_test.go @@ -1,4 +1,4 @@ -// Copyright 2017 gf Author(https://github.com/gogf/gf). All Rights Reserved. +// Copyright GoFrame Author(https://goframe.org). All Rights Reserved. // // This Source Code Form is subject to the terms of the MIT License. // If a copy of the MIT was not distributed with this file, diff --git a/errors/gcode/gcode.go b/errors/gcode/gcode.go new file mode 100644 index 000000000..8665b8a27 --- /dev/null +++ b/errors/gcode/gcode.go @@ -0,0 +1,59 @@ +// Copyright GoFrame gf Author(https://goframe.org). All Rights Reserved. +// +// This Source Code Form is subject to the terms of the MIT License. +// If a copy of the MIT was not distributed with this file, +// You can obtain one at https://github.com/gogf/gf. + +// Package gcode provides universal error code definition and common error codes implements. +package gcode + +// Code is universal error code interface definition. +type Code interface { + // Code returns the integer number of current error code. + Code() int + + // Message returns the brief message for current error code. + Message() string + + // Detail returns the detailed information of current error code, + // which is mainly designed as an extension field for error code. + Detail() interface{} +} + +// ================================================================================================================ +// Common error code definition. +// There are reserved internal error code by framework: code < 1000. +// ================================================================================================================ + +var ( + CodeNil = localCode{-1, "", nil} // No error code specified. + CodeOK = localCode{0, "OK", nil} // It is OK. + CodeInternalError = localCode{50, "Internal Error", nil} // An error occurred internally. + CodeValidationFailed = localCode{51, "Validation Failed", nil} // Data validation failed. + CodeDbOperationError = localCode{52, "Database Operation Error", nil} // Database operation error. + CodeInvalidParameter = localCode{53, "Invalid Parameter", nil} // The given parameter for current operation is invalid. + CodeMissingParameter = localCode{54, "Missing Parameter", nil} // Parameter for current operation is missing. + CodeInvalidOperation = localCode{55, "Invalid Operation", nil} // The function cannot be used like this. + CodeInvalidConfiguration = localCode{56, "Invalid Configuration", nil} // The configuration is invalid for current operation. + CodeMissingConfiguration = localCode{57, "Missing Configuration", nil} // The configuration is missing for current operation. + CodeNotImplemented = localCode{58, "Not Implemented", nil} // The operation is not implemented yet. + CodeNotSupported = localCode{59, "Not Supported", nil} // The operation is not supported yet. + CodeOperationFailed = localCode{60, "Operation Failed", nil} // I tried, but I cannot give you what you want. + CodeNotAuthorized = localCode{61, "Not Authorized", nil} // Not Authorized. + CodeSecurityReason = localCode{62, "Security Reason", nil} // Security Reason. + CodeServerBusy = localCode{63, "Server Is Busy", nil} // Server is busy, please try again later. + CodeUnknown = localCode{64, "Unknown Error", nil} // Unknown error. + CodeNotFound = localCode{65, "Not Found", nil} // Resource does not exist. + CodeInvalidRequest = localCode{66, "Invalid Request", nil} // Invalid request. + CodeBusinessValidationFailed = localCode{300, "Business Validation Failed", nil} // Business validation failed. +) + +// New creates and returns an error code. +// Note that it returns an interface object of Code. +func New(code int, message string, detail interface{}) Code { + return localCode{ + code: code, + message: message, + detail: detail, + } +} diff --git a/errors/gcode/gcode_local.go b/errors/gcode/gcode_local.go new file mode 100644 index 000000000..1ec1d1ea9 --- /dev/null +++ b/errors/gcode/gcode_local.go @@ -0,0 +1,43 @@ +// Copyright GoFrame gf Author(https://goframe.org). All Rights Reserved. +// +// This Source Code Form is subject to the terms of the MIT License. +// If a copy of the MIT was not distributed with this file, +// You can obtain one at https://github.com/gogf/gf. + +package gcode + +import "fmt" + +// localCode is an implementer for interface Code for internal usage only. +type localCode struct { + code int // Error code, usually an integer. + message string // Brief message for this error code. + detail interface{} // As type of interface, it is mainly designed as an extension field for error code. +} + +// Code returns the integer number of current error code. +func (c localCode) Code() int { + return c.code +} + +// Message returns the brief message for current error code. +func (c localCode) Message() string { + return c.message +} + +// Detail returns the detailed information of current error code, +// which is mainly designed as an extension field for error code. +func (c localCode) Detail() interface{} { + return c.detail +} + +// String returns current error code as a string. +func (c localCode) String() string { + if c.detail != nil { + return fmt.Sprintf(`%d:%s %v`, c.code, c.message, c.detail) + } + if c.message != "" { + return fmt.Sprintf(`%d:%s`, c.code, c.message) + } + return fmt.Sprintf(`%d`, c.code) +} diff --git a/errors/gcode/gcode_test.go b/errors/gcode/gcode_test.go new file mode 100644 index 000000000..68a4e7da4 --- /dev/null +++ b/errors/gcode/gcode_test.go @@ -0,0 +1,23 @@ +// Copyright GoFrame Author(https://goframe.org). All Rights Reserved. +// +// This Source Code Form is subject to the terms of the MIT License. +// If a copy of the MIT was not distributed with this file, +// You can obtain one at https://github.com/gogf/gf. + +package gcode_test + +import ( + "github.com/gogf/gf/errors/gcode" + "testing" + + "github.com/gogf/gf/test/gtest" +) + +func Test_Nil(t *testing.T) { + gtest.C(t, func(t *gtest.T) { + c := gcode.New(1, "custom error", "detailed description") + t.Assert(c.Code(), 1) + t.Assert(c.Message(), "custom error") + t.Assert(c.Detail(), "detailed description") + }) +} diff --git a/errors/gerror/gerror.go b/errors/gerror/gerror.go index c989d7f32..268af2fab 100644 --- a/errors/gerror/gerror.go +++ b/errors/gerror/gerror.go @@ -1,10 +1,10 @@ -// Copyright GoFrame gf Author(https://github.com/gogf/gf). All Rights Reserved. +// Copyright GoFrame gf Author(https://goframe.org). All Rights Reserved. // // This Source Code Form is subject to the terms of the MIT License. // If a copy of the MIT was not distributed with this file, // You can obtain one at https://github.com/gogf/gf. -// Package errors provides simple functions to manipulate errors. +// Package gerror provides simple functions to manipulate errors. // // Very note that, this package is quite a base package, which should not import extra // packages except standard packages, to avoid cycle imports. @@ -12,35 +12,36 @@ package gerror import ( "fmt" + "github.com/gogf/gf/errors/gcode" ) // apiCode is the interface for Code feature. type apiCode interface { - Error() string // It should be an error. - Code() int + Error() string + Code() gcode.Code } // apiStack is the interface for Stack feature. type apiStack interface { - Error() string // It should be an error. + Error() string Stack() string } // apiCause is the interface for Cause feature. type apiCause interface { - Error() string // It should be an error. + Error() string Cause() error } // apiCurrent is the interface for Current feature. type apiCurrent interface { - Error() string // It should be an error. + Error() string Current() error } // apiNext is the interface for Next feature. type apiNext interface { - Error() string // It should be an error. + Error() string Next() error } @@ -49,7 +50,7 @@ func New(text string) error { return &Error{ stack: callers(), text: text, - code: -1, + code: gcode.CodeNil, } } @@ -58,7 +59,7 @@ func Newf(format string, args ...interface{}) error { return &Error{ stack: callers(), text: fmt.Sprintf(format, args...), - code: -1, + code: gcode.CodeNil, } } @@ -68,7 +69,7 @@ func NewSkip(skip int, text string) error { return &Error{ stack: callers(skip), text: text, - code: -1, + code: gcode.CodeNil, } } @@ -78,7 +79,7 @@ func NewSkipf(skip int, format string, args ...interface{}) error { return &Error{ stack: callers(skip), text: fmt.Sprintf(format, args...), - code: -1, + code: gcode.CodeNil, } } @@ -92,7 +93,7 @@ func Wrap(err error, text string) error { error: err, stack: callers(), text: text, - code: -1, + code: Code(err), } } @@ -107,21 +108,55 @@ func Wrapf(err error, format string, args ...interface{}) error { error: err, stack: callers(), text: fmt.Sprintf(format, args...), - code: -1, + code: Code(err), + } +} + +// WrapSkip wraps error with text. +// It returns nil if given err is nil. +// The parameter <skip> specifies the stack callers skipped amount. +func WrapSkip(skip int, err error, text string) error { + if err == nil { + return nil + } + return &Error{ + error: err, + stack: callers(skip), + text: text, + code: Code(err), + } +} + +// WrapSkipf wraps error with text that is formatted with given format and args. +// It returns nil if given err is nil. +// The parameter <skip> specifies the stack callers skipped amount. +func WrapSkipf(skip int, err error, format string, args ...interface{}) error { + if err == nil { + return nil + } + return &Error{ + error: err, + stack: callers(skip), + text: fmt.Sprintf(format, args...), + code: Code(err), } } // NewCode creates and returns an error that has error code and given text. -func NewCode(code int, text string) error { +func NewCode(code gcode.Code, text ...string) error { + errText := "" + if len(text) > 0 { + errText = text[0] + } return &Error{ stack: callers(), - text: text, + text: errText, code: code, } } // NewCodef returns an error that has error code and formats as the given format and args. -func NewCodef(code int, format string, args ...interface{}) error { +func NewCodef(code gcode.Code, format string, args ...interface{}) error { return &Error{ stack: callers(), text: fmt.Sprintf(format, args...), @@ -131,17 +166,21 @@ func NewCodef(code int, format string, args ...interface{}) error { // NewCodeSkip creates and returns an error which has error code and is formatted from given text. // The parameter <skip> specifies the stack callers skipped amount. -func NewCodeSkip(code, skip int, text string) error { +func NewCodeSkip(code gcode.Code, skip int, text ...string) error { + errText := "" + if len(text) > 0 { + errText = text[0] + } return &Error{ stack: callers(skip), - text: text, + text: errText, code: code, } } // NewCodeSkipf returns an error that has error code and formats as the given format and args. // The parameter <skip> specifies the stack callers skipped amount. -func NewCodeSkipf(code, skip int, format string, args ...interface{}) error { +func NewCodeSkipf(code gcode.Code, skip int, format string, args ...interface{}) error { return &Error{ stack: callers(skip), text: fmt.Sprintf(format, args...), @@ -151,21 +190,25 @@ func NewCodeSkipf(code, skip int, format string, args ...interface{}) error { // WrapCode wraps error with code and text. // It returns nil if given err is nil. -func WrapCode(code int, err error, text string) error { +func WrapCode(code gcode.Code, err error, text ...string) error { if err == nil { return nil } + errText := "" + if len(text) > 0 { + errText = text[0] + } return &Error{ error: err, stack: callers(), - text: text, + text: errText, code: code, } } // WrapCodef wraps error with code and format specifier. // It returns nil if given <err> is nil. -func WrapCodef(code int, err error, format string, args ...interface{}) error { +func WrapCodef(code gcode.Code, err error, format string, args ...interface{}) error { if err == nil { return nil } @@ -177,15 +220,49 @@ func WrapCodef(code int, err error, format string, args ...interface{}) error { } } -// Cause returns the error code of current error. -// It returns -1 if it has no error code or it does not implements interface Code. -func Code(err error) int { +// WrapCodeSkip wraps error with code and text. +// It returns nil if given err is nil. +// The parameter <skip> specifies the stack callers skipped amount. +func WrapCodeSkip(code gcode.Code, skip int, err error, text ...string) error { + if err == nil { + return nil + } + errText := "" + if len(text) > 0 { + errText = text[0] + } + return &Error{ + error: err, + stack: callers(skip), + text: errText, + code: code, + } +} + +// WrapCodeSkipf wraps error with code and text that is formatted with given format and args. +// It returns nil if given err is nil. +// The parameter <skip> specifies the stack callers skipped amount. +func WrapCodeSkipf(code gcode.Code, skip int, err error, format string, args ...interface{}) error { + if err == nil { + return nil + } + return &Error{ + error: err, + stack: callers(skip), + text: fmt.Sprintf(format, args...), + code: code, + } +} + +// Code returns the error code of current error. +// It returns CodeNil if it has no error code or it does not implements interface Code. +func Code(err error) gcode.Code { if err != nil { if e, ok := err.(apiCode); ok { return e.Code() } } - return -1 + return gcode.CodeNil } // Cause returns the root cause error of <err>. @@ -199,7 +276,7 @@ func Cause(err error) error { } // Stack returns the stack callers as string. -// It returns an empty string if the <err> does not support stacks. +// It returns the error string directly if the <err> does not support stacks. func Stack(err error) string { if err == nil { return "" @@ -207,7 +284,7 @@ func Stack(err error) string { if e, ok := err.(apiStack); ok { return e.Stack() } - return "" + return err.Error() } // Current creates and returns the current level error. diff --git a/errors/gerror/gerror_error.go b/errors/gerror/gerror_error.go index dfbd5183f..bbc00bdf1 100644 --- a/errors/gerror/gerror_error.go +++ b/errors/gerror/gerror_error.go @@ -1,4 +1,4 @@ -// Copyright GoFrame Author(https://github.com/gogf/gf). All Rights Reserved. +// Copyright GoFrame Author(https://goframe.org). All Rights Reserved. // // This Source Code Form is subject to the terms of the MIT License. // If a copy of the MIT was not distributed with this file, @@ -10,6 +10,8 @@ import ( "bytes" "errors" "fmt" + "github.com/gogf/gf/errors/gcode" + "github.com/gogf/gf/internal/utils" "io" "runtime" "strings" @@ -17,14 +19,15 @@ import ( // Error is custom error for additional features. type Error struct { - error error // Wrapped error. - stack stack // Stack array, which records the stack information when this error is created or wrapped. - text string // Error text, which is created by New* functions. - code int // Error code if necessary. + error error // Wrapped error. + stack stack // Stack array, which records the stack information when this error is created or wrapped. + text string // Error text, which is created by New* functions. + code gcode.Code // Error code if necessary. } const ( - stackFilterKey = "/errors/gerror/gerror" + // Filtering key for current error module paths. + stackFilterKeyLocal = "/errors/gerror/gerror" ) var ( @@ -45,8 +48,11 @@ func (err *Error) Error() string { return "" } errStr := err.text + if errStr == "" && err.code != nil { + errStr = err.code.Message() + } if err.error != nil { - if err.text != "" { + if errStr != "" { errStr += ": " } errStr += err.error.Error() @@ -55,10 +61,10 @@ func (err *Error) Error() string { } // Code returns the error code. -// It returns -1 if it has no error code. -func (err *Error) Code() int { +// It returns CodeNil if it has no error code. +func (err *Error) Code() gcode.Code { if err == nil { - return -1 + return gcode.CodeNil } return err.code } @@ -157,6 +163,7 @@ func (err *Error) Current() error { error: nil, stack: err.stack, text: err.text, + code: err.code, } } @@ -169,27 +176,50 @@ func (err *Error) Next() error { return err.error } +// MarshalJSON implements the interface MarshalJSON for json.Marshal. +// Note that do not use pointer as its receiver here. +func (err *Error) MarshalJSON() ([]byte, error) { + return []byte(`"` + err.Error() + `"`), nil +} + // formatSubStack formats the stack for error. func formatSubStack(st stack, buffer *bytes.Buffer) { + if st == nil { + return + } index := 1 space := " " for _, p := range st { if fn := runtime.FuncForPC(p - 1); fn != nil { file, line := fn.FileLine(p - 1) - if strings.Contains(file, stackFilterKey) { - continue + // Custom filtering. + if !utils.IsDebugEnabled() { + if strings.Contains(file, utils.StackFilterKeyForGoFrame) { + continue + } + } else { + if strings.Contains(file, stackFilterKeyLocal) { + continue + } } // Avoid stack string like "<autogenerated>" if strings.Contains(file, "<") { continue } - if goRootForFilter != "" && len(file) >= len(goRootForFilter) && file[0:len(goRootForFilter)] == goRootForFilter { + // Ignore GO ROOT paths. + if goRootForFilter != "" && + len(file) >= len(goRootForFilter) && + file[0:len(goRootForFilter)] == goRootForFilter { continue } + // Graceful indent. if index > 9 { space = " " } - buffer.WriteString(fmt.Sprintf(" %d).%s%s\n \t%s:%d\n", index, space, fn.Name(), file, line)) + buffer.WriteString(fmt.Sprintf( + " %d).%s%s\n \t%s:%d\n", + index, space, fn.Name(), file, line, + )) index++ } } diff --git a/errors/gerror/gerror_option.go b/errors/gerror/gerror_option.go new file mode 100644 index 000000000..30c2c38e5 --- /dev/null +++ b/errors/gerror/gerror_option.go @@ -0,0 +1,31 @@ +// Copyright GoFrame Author(https://goframe.org). All Rights Reserved. +// +// This Source Code Form is subject to the terms of the MIT License. +// If a copy of the MIT was not distributed with this file, +// You can obtain one at https://github.com/gogf/gf. + +package gerror + +import "github.com/gogf/gf/errors/gcode" + +// Option is option for creating error. +type Option struct { + Error error // Wrapped error if any. + Stack bool // Whether recording stack information into error. + Text string // Error text, which is created by New* functions. + Code gcode.Code // Error code if necessary. +} + +// NewOption creates and returns an error with Option. +// It is the senior usage for creating error, which is often used internally in framework. +func NewOption(option Option) error { + err := &Error{ + error: option.Error, + text: option.Text, + code: option.Code, + } + if option.Stack { + err.stack = callers() + } + return err +} diff --git a/errors/gerror/gerror_stack.go b/errors/gerror/gerror_stack.go index d5fbb3ffb..b65cce769 100644 --- a/errors/gerror/gerror_stack.go +++ b/errors/gerror/gerror_stack.go @@ -1,4 +1,4 @@ -// Copyright GoFrame Author(https://github.com/gogf/gf). All Rights Reserved. +// Copyright GoFrame Author(https://goframe.org). All Rights Reserved. // // This Source Code Form is subject to the terms of the MIT License. // If a copy of the MIT was not distributed with this file, diff --git a/errors/gerror/gerror_z_bench_test.go b/errors/gerror/gerror_z_bench_test.go index 6877780d8..7e510abdb 100644 --- a/errors/gerror/gerror_z_bench_test.go +++ b/errors/gerror/gerror_z_bench_test.go @@ -1,17 +1,91 @@ -// Copyright GoFrame Author(https://github.com/gogf/gf). All Rights Reserved. +// Copyright GoFrame Author(https://goframe.org). All Rights Reserved. // // This Source Code Form is subject to the terms of the MIT License. // If a copy of the MIT was not distributed with this file, // You can obtain one at https://github.com/gogf/gf. -package gerror +package gerror_test import ( + "errors" + "github.com/gogf/gf/errors/gcode" + "github.com/gogf/gf/errors/gerror" "testing" ) -func Benchmark_Stack(b *testing.B) { +var ( + // base error for benchmark testing of Wrap* functions. + baseError = errors.New("test") +) + +func Benchmark_New(b *testing.B) { for i := 0; i < b.N; i++ { - callers() + gerror.New("test") + } +} + +func Benchmark_Newf(b *testing.B) { + for i := 0; i < b.N; i++ { + gerror.Newf("%s", "test") + } +} + +func Benchmark_Wrap(b *testing.B) { + for i := 0; i < b.N; i++ { + gerror.Wrap(baseError, "test") + } +} + +func Benchmark_Wrapf(b *testing.B) { + for i := 0; i < b.N; i++ { + gerror.Wrapf(baseError, "%s", "test") + } +} + +func Benchmark_NewSkip(b *testing.B) { + for i := 0; i < b.N; i++ { + gerror.NewSkip(1, "test") + } +} + +func Benchmark_NewSkipf(b *testing.B) { + for i := 0; i < b.N; i++ { + gerror.NewSkipf(1, "%s", "test") + } +} + +func Benchmark_NewCode(b *testing.B) { + for i := 0; i < b.N; i++ { + gerror.NewCode(gcode.New(500, "", nil), "test") + } +} + +func Benchmark_NewCodef(b *testing.B) { + for i := 0; i < b.N; i++ { + gerror.NewCodef(gcode.New(500, "", nil), "%s", "test") + } +} + +func Benchmark_NewCodeSkip(b *testing.B) { + for i := 0; i < b.N; i++ { + gerror.NewCodeSkip(gcode.New(1, "", nil), 500, "test") + } +} + +func Benchmark_NewCodeSkipf(b *testing.B) { + for i := 0; i < b.N; i++ { + gerror.NewCodeSkipf(gcode.New(1, "", nil), 500, "%s", "test") + } +} + +func Benchmark_WrapCode(b *testing.B) { + for i := 0; i < b.N; i++ { + gerror.WrapCode(gcode.New(500, "", nil), baseError, "test") + } +} + +func Benchmark_WrapCodef(b *testing.B) { + for i := 0; i < b.N; i++ { + gerror.WrapCodef(gcode.New(500, "", nil), baseError, "test") } } diff --git a/errors/gerror/gerror_z_example_test.go b/errors/gerror/gerror_z_example_test.go new file mode 100644 index 000000000..f52528854 --- /dev/null +++ b/errors/gerror/gerror_z_example_test.go @@ -0,0 +1,56 @@ +// Copyright GoFrame Author(https://goframe.org). All Rights Reserved. +// +// This Source Code Form is subject to the terms of the MIT License. +// If a copy of the MIT was not distributed with this file, +// You can obtain one at https://github.com/gogf/gf. + +package gerror_test + +import ( + "errors" + "fmt" + "github.com/gogf/gf/errors/gcode" + "github.com/gogf/gf/errors/gerror" +) + +func ExampleNewCode() { + err := gerror.NewCode(gcode.New(10000, "", nil), "My Error") + fmt.Println(err.Error()) + fmt.Println(gerror.Code(err)) + + // Output: + // My Error + // 10000 +} + +func ExampleNewCodef() { + err := gerror.NewCodef(gcode.New(10000, "", nil), "It's %s", "My Error") + fmt.Println(err.Error()) + fmt.Println(gerror.Code(err).Code()) + + // Output: + // It's My Error + // 10000 +} + +func ExampleWrapCode() { + err1 := errors.New("permission denied") + err2 := gerror.WrapCode(gcode.New(10000, "", nil), err1, "Custom Error") + fmt.Println(err2.Error()) + fmt.Println(gerror.Code(err2).Code()) + + // Output: + // Custom Error: permission denied + // 10000 +} + +func ExampleWrapCodef() { + err1 := errors.New("permission denied") + err2 := gerror.WrapCodef(gcode.New(10000, "", nil), err1, "It's %s", "Custom Error") + fmt.Println(err2.Error()) + fmt.Println(gerror.Code(err2).Code()) + + // Output: + // It's Custom Error: permission denied + // 10000 +} diff --git a/errors/gerror/gerror_z_unit_test.go b/errors/gerror/gerror_z_unit_test.go index c7c6e2541..05521833c 100644 --- a/errors/gerror/gerror_z_unit_test.go +++ b/errors/gerror/gerror_z_unit_test.go @@ -1,4 +1,4 @@ -// Copyright GoFrame Author(https://github.com/gogf/gf). All Rights Reserved. +// Copyright GoFrame Author(https://goframe.org). All Rights Reserved. // // This Source Code Form is subject to the terms of the MIT License. // If a copy of the MIT was not distributed with this file, @@ -9,6 +9,8 @@ package gerror_test import ( "errors" "fmt" + "github.com/gogf/gf/errors/gcode" + "github.com/gogf/gf/internal/json" "testing" "github.com/gogf/gf/errors/gerror" @@ -26,6 +28,29 @@ func Test_Nil(t *testing.T) { }) } +func Test_New(t *testing.T) { + gtest.C(t, func(t *gtest.T) { + err := gerror.New("1") + t.AssertNE(err, nil) + t.Assert(err.Error(), "1") + }) + gtest.C(t, func(t *gtest.T) { + err := gerror.Newf("%d", 1) + t.AssertNE(err, nil) + t.Assert(err.Error(), "1") + }) + gtest.C(t, func(t *gtest.T) { + err := gerror.NewSkip(1, "1") + t.AssertNE(err, nil) + t.Assert(err.Error(), "1") + }) + gtest.C(t, func(t *gtest.T) { + err := gerror.NewSkipf(1, "%d", 1) + t.AssertNE(err, nil) + t.Assert(err.Error(), "1") + }) +} + func Test_Wrap(t *testing.T) { gtest.C(t, func(t *gtest.T) { err := errors.New("1") @@ -49,6 +74,75 @@ func Test_Wrap(t *testing.T) { }) } +func Test_Wrapf(t *testing.T) { + gtest.C(t, func(t *gtest.T) { + err := errors.New("1") + err = gerror.Wrapf(err, "%d", 2) + err = gerror.Wrapf(err, "%d", 3) + t.AssertNE(err, nil) + t.Assert(err.Error(), "3: 2: 1") + }) + gtest.C(t, func(t *gtest.T) { + err := gerror.New("1") + err = gerror.Wrapf(err, "%d", 2) + err = gerror.Wrapf(err, "%d", 3) + t.AssertNE(err, nil) + t.Assert(err.Error(), "3: 2: 1") + }) + gtest.C(t, func(t *gtest.T) { + err := gerror.New("1") + err = gerror.Wrapf(err, "") + t.AssertNE(err, nil) + t.Assert(err.Error(), "1") + }) +} + +func Test_WrapSkip(t *testing.T) { + gtest.C(t, func(t *gtest.T) { + err := errors.New("1") + err = gerror.WrapSkip(1, err, "2") + err = gerror.WrapSkip(1, err, "3") + t.AssertNE(err, nil) + t.Assert(err.Error(), "3: 2: 1") + }) + gtest.C(t, func(t *gtest.T) { + err := gerror.New("1") + err = gerror.WrapSkip(1, err, "2") + err = gerror.WrapSkip(1, err, "3") + t.AssertNE(err, nil) + t.Assert(err.Error(), "3: 2: 1") + }) + gtest.C(t, func(t *gtest.T) { + err := gerror.New("1") + err = gerror.WrapSkip(1, err, "") + t.AssertNE(err, nil) + t.Assert(err.Error(), "1") + }) +} + +func Test_WrapSkipf(t *testing.T) { + gtest.C(t, func(t *gtest.T) { + err := errors.New("1") + err = gerror.WrapSkipf(1, err, "2") + err = gerror.WrapSkipf(1, err, "3") + t.AssertNE(err, nil) + t.Assert(err.Error(), "3: 2: 1") + }) + gtest.C(t, func(t *gtest.T) { + err := gerror.New("1") + err = gerror.WrapSkipf(1, err, "2") + err = gerror.WrapSkipf(1, err, "3") + t.AssertNE(err, nil) + t.Assert(err.Error(), "3: 2: 1") + }) + gtest.C(t, func(t *gtest.T) { + err := gerror.New("1") + err = gerror.WrapSkipf(1, err, "") + t.AssertNE(err, nil) + t.Assert(err.Error(), "1") + }) +} + func Test_Cause(t *testing.T) { gtest.C(t, func(t *gtest.T) { err := errors.New("1") @@ -168,37 +262,60 @@ func Test_Code(t *testing.T) { t.Assert(err.Error(), "123") }) gtest.C(t, func(t *gtest.T) { - err := gerror.NewCode(1, "123") - t.Assert(gerror.Code(err), 1) + err := gerror.NewCode(gcode.CodeUnknown, "123") + t.Assert(gerror.Code(err), gcode.CodeUnknown) t.Assert(err.Error(), "123") }) gtest.C(t, func(t *gtest.T) { - err := gerror.NewCodef(1, "%s", "123") - t.Assert(gerror.Code(err), 1) + err := gerror.NewCodef(gcode.New(1, "", nil), "%s", "123") + t.Assert(gerror.Code(err).Code(), 1) t.Assert(err.Error(), "123") }) gtest.C(t, func(t *gtest.T) { - err := gerror.NewCodeSkip(1, 0, "123") - t.Assert(gerror.Code(err), 1) + err := gerror.NewCodeSkip(gcode.New(1, "", nil), 0, "123") + t.Assert(gerror.Code(err).Code(), 1) t.Assert(err.Error(), "123") }) gtest.C(t, func(t *gtest.T) { - err := gerror.NewCodeSkipf(1, 0, "%s", "123") - t.Assert(gerror.Code(err), 1) + err := gerror.NewCodeSkipf(gcode.New(1, "", nil), 0, "%s", "123") + t.Assert(gerror.Code(err).Code(), 1) t.Assert(err.Error(), "123") }) gtest.C(t, func(t *gtest.T) { err := errors.New("1") err = gerror.Wrap(err, "2") - err = gerror.WrapCode(1, err, "3") - t.Assert(gerror.Code(err), 1) + err = gerror.WrapCode(gcode.New(1, "", nil), err, "3") + t.Assert(gerror.Code(err).Code(), 1) t.Assert(err.Error(), "3: 2: 1") }) gtest.C(t, func(t *gtest.T) { err := errors.New("1") err = gerror.Wrap(err, "2") - err = gerror.WrapCodef(1, err, "%s", "3") - t.Assert(gerror.Code(err), 1) + err = gerror.WrapCodef(gcode.New(1, "", nil), err, "%s", "3") + t.Assert(gerror.Code(err).Code(), 1) + t.Assert(err.Error(), "3: 2: 1") + }) + gtest.C(t, func(t *gtest.T) { + err := errors.New("1") + err = gerror.Wrap(err, "2") + err = gerror.WrapCodeSkip(gcode.New(1, "", nil), 100, err, "3") + t.Assert(gerror.Code(err).Code(), 1) + t.Assert(err.Error(), "3: 2: 1") + }) + gtest.C(t, func(t *gtest.T) { + err := errors.New("1") + err = gerror.Wrap(err, "2") + err = gerror.WrapCodeSkipf(gcode.New(1, "", nil), 100, err, "%s", "3") + t.Assert(gerror.Code(err).Code(), 1) t.Assert(err.Error(), "3: 2: 1") }) } + +func Test_Json(t *testing.T) { + gtest.C(t, func(t *gtest.T) { + err := gerror.Wrap(gerror.New("1"), "2") + b, e := json.Marshal(err) + t.Assert(e, nil) + t.Assert(string(b), `"2: 1"`) + }) +} diff --git a/frame/g/g.go b/frame/g/g.go index 72bdd723b..c8d4163e7 100644 --- a/frame/g/g.go +++ b/frame/g/g.go @@ -1,4 +1,4 @@ -// Copyright 2017 gf Author(https://github.com/gogf/gf). All Rights Reserved. +// Copyright GoFrame Author(https://goframe.org). All Rights Reserved. // // This Source Code Form is subject to the terms of the MIT License. // If a copy of the MIT was not distributed with this file, @@ -7,50 +7,57 @@ package g import ( + "context" "github.com/gogf/gf/container/gvar" ) -// Var is a universal variable interface, like generics. -type Var = gvar.Var +type ( + Var = gvar.Var // Var is a universal variable interface, like generics. + Ctx = context.Context // Ctx is alias of frequently-used context.Context. +) -// Frequently-used map type alias. -type Map = map[string]interface{} -type MapAnyAny = map[interface{}]interface{} -type MapAnyStr = map[interface{}]string -type MapAnyInt = map[interface{}]int -type MapStrAny = map[string]interface{} -type MapStrStr = map[string]string -type MapStrInt = map[string]int -type MapIntAny = map[int]interface{} -type MapIntStr = map[int]string -type MapIntInt = map[int]int -type MapAnyBool = map[interface{}]bool -type MapStrBool = map[string]bool -type MapIntBool = map[int]bool +type ( + Map = map[string]interface{} // Map is alias of frequently-used map type map[string]interface{}. + MapAnyAny = map[interface{}]interface{} // MapAnyAny is alias of frequently-used map type map[interface{}]interface{}. + MapAnyStr = map[interface{}]string // MapAnyStr is alias of frequently-used map type map[interface{}]string. + MapAnyInt = map[interface{}]int // MapAnyInt is alias of frequently-used map type map[interface{}]int. + MapStrAny = map[string]interface{} // MapStrAny is alias of frequently-used map type map[string]interface{}. + MapStrStr = map[string]string // MapStrStr is alias of frequently-used map type map[string]string. + MapStrInt = map[string]int // MapStrInt is alias of frequently-used map type map[string]int. + MapIntAny = map[int]interface{} // MapIntAny is alias of frequently-used map type map[int]interface{}. + MapIntStr = map[int]string // MapIntStr is alias of frequently-used map type map[int]string. + MapIntInt = map[int]int // MapIntInt is alias of frequently-used map type map[int]int. + MapAnyBool = map[interface{}]bool // MapAnyBool is alias of frequently-used map type map[interface{}]bool. + MapStrBool = map[string]bool // MapStrBool is alias of frequently-used map type map[string]bool. + MapIntBool = map[int]bool // MapIntBool is alias of frequently-used map type map[int]bool. +) -// Frequently-used slice type alias. -type List = []Map -type ListAnyAny = []Map -type ListAnyStr = []MapAnyStr -type ListAnyInt = []MapAnyInt -type ListStrAny = []MapStrAny -type ListStrStr = []MapStrStr -type ListStrInt = []MapStrInt -type ListIntAny = []MapIntAny -type ListIntStr = []MapIntStr -type ListIntInt = []MapIntInt -type ListAnyBool = []MapAnyBool -type ListStrBool = []MapStrBool -type ListIntBool = []MapIntBool +type ( + List = []Map // List is alias of frequently-used slice type []Map. + ListAnyAny = []MapAnyAny // ListAnyAny is alias of frequently-used slice type []MapAnyAny. + ListAnyStr = []MapAnyStr // ListAnyStr is alias of frequently-used slice type []MapAnyStr. + ListAnyInt = []MapAnyInt // ListAnyInt is alias of frequently-used slice type []MapAnyInt. + ListStrAny = []MapStrAny // ListStrAny is alias of frequently-used slice type []MapStrAny. + ListStrStr = []MapStrStr // ListStrStr is alias of frequently-used slice type []MapStrStr. + ListStrInt = []MapStrInt // ListStrInt is alias of frequently-used slice type []MapStrInt. + ListIntAny = []MapIntAny // ListIntAny is alias of frequently-used slice type []MapIntAny. + ListIntStr = []MapIntStr // ListIntStr is alias of frequently-used slice type []MapIntStr. + ListIntInt = []MapIntInt // ListIntInt is alias of frequently-used slice type []MapIntInt. + ListAnyBool = []MapAnyBool // ListAnyBool is alias of frequently-used slice type []MapAnyBool. + ListStrBool = []MapStrBool // ListStrBool is alias of frequently-used slice type []MapStrBool. + ListIntBool = []MapIntBool // ListIntBool is alias of frequently-used slice type []MapIntBool. +) -// Frequently-used slice type alias. -type Slice = []interface{} -type SliceAny = []interface{} -type SliceStr = []string -type SliceInt = []int +type ( + Slice = []interface{} // Slice is alias of frequently-used slice type []interface{}. + SliceAny = []interface{} // SliceAny is alias of frequently-used slice type []interface{}. + SliceStr = []string // SliceStr is alias of frequently-used slice type []string. + SliceInt = []int // SliceInt is alias of frequently-used slice type []int. +) -// Array is alias of Slice. -type Array = []interface{} -type ArrayAny = []interface{} -type ArrayStr = []string -type ArrayInt = []int +type ( + Array = []interface{} // Array is alias of frequently-used slice type []interface{}. + ArrayAny = []interface{} // ArrayAny is alias of frequently-used slice type []interface{}. + ArrayStr = []string // ArrayStr is alias of frequently-used slice type []string. + ArrayInt = []int // ArrayInt is alias of frequently-used slice type []int. +) diff --git a/frame/g/g_func.go b/frame/g/g_func.go index d99a77ff3..5034a8f08 100644 --- a/frame/g/g_func.go +++ b/frame/g/g_func.go @@ -1,4 +1,4 @@ -// Copyright 2017 gf Author(https://github.com/gogf/gf). All Rights Reserved. +// Copyright GoFrame Author(https://goframe.org). All Rights Reserved. // // This Source Code Form is subject to the terms of the MIT License. // If a copy of the MIT was not distributed with this file, @@ -7,9 +7,11 @@ package g import ( + "context" "github.com/gogf/gf/container/gvar" "github.com/gogf/gf/internal/empty" "github.com/gogf/gf/net/ghttp" + "github.com/gogf/gf/os/gproc" "github.com/gogf/gf/util/gutil" ) @@ -18,11 +20,19 @@ func NewVar(i interface{}, safe ...bool) *Var { return gvar.New(i, safe...) } -// Wait blocks until all the web servers shutdown. +// Wait is an alias of ghttp.Wait, which blocks until all the web servers shutdown. +// It's commonly used in multiple servers situation. func Wait() { ghttp.Wait() } +// Listen is an alias of gproc.Listen, which handles the signals received and automatically +// calls registered signal handler functions. +// It blocks until shutdown signals received and all registered shutdown handlers done. +func Listen() { + gproc.Listen() +} + // Dump dumps a variable to stdout with more manually readable. func Dump(i ...interface{}) { gutil.Dump(i...) @@ -52,9 +62,12 @@ func TryCatch(try func(), catch ...func(exception error)) { } // IsNil checks whether given <value> is nil. +// Parameter <traceSource> is used for tracing to the source variable if given <value> is type +// of a pinter that also points to a pointer. It returns nil if the source is nil when <traceSource> +// is true. // Note that it might use reflect feature which affects performance a little bit. -func IsNil(value interface{}) bool { - return empty.IsNil(value) +func IsNil(value interface{}, traceSource ...bool) bool { + return empty.IsNil(value, traceSource...) } // IsEmpty checks whether given <value> empty. @@ -63,3 +76,8 @@ func IsNil(value interface{}) bool { func IsEmpty(value interface{}) bool { return empty.IsEmpty(value) } + +// RequestFromCtx retrieves and returns the Request object from context. +func RequestFromCtx(ctx context.Context) *ghttp.Request { + return ghttp.RequestFromCtx(ctx) +} diff --git a/frame/g/g_logger.go b/frame/g/g_logger.go index 280162e97..7d0a6c2d1 100644 --- a/frame/g/g_logger.go +++ b/frame/g/g_logger.go @@ -1,4 +1,4 @@ -// Copyright 2018 gf Author(https://github.com/gogf/gf). All Rights Reserved. +// Copyright GoFrame Author(https://goframe.org). All Rights Reserved. // // This Source Code Form is subject to the terms of the MIT License. // If a copy of the MIT was not distributed with this file, diff --git a/frame/g/g_object.go b/frame/g/g_object.go index 551719ee6..571d47247 100644 --- a/frame/g/g_object.go +++ b/frame/g/g_object.go @@ -1,4 +1,4 @@ -// Copyright 2018 gf Author(https://github.com/gogf/gf). All Rights Reserved. +// Copyright GoFrame Author(https://goframe.org). All Rights Reserved. // // This Source Code Form is subject to the terms of the MIT License. // If a copy of the MIT was not distributed with this file, @@ -18,9 +18,10 @@ import ( "github.com/gogf/gf/os/glog" "github.com/gogf/gf/os/gres" "github.com/gogf/gf/os/gview" + "github.com/gogf/gf/util/gvalid" ) -// Client is a convenience function, that creates and returns a new HTTP client. +// Client is a convenience function, which creates and returns a new HTTP client. func Client() *ghttp.Client { return ghttp.NewClient() } @@ -80,30 +81,31 @@ func Log(name ...string) *glog.Logger { return gins.Log(name...) } -// Database returns an instance of database ORM object with specified configuration group name. -func Database(name ...string) gdb.DB { - return gins.Database(name...) -} - -// DB is alias of Database. -// See Database. +// DB returns an instance of database ORM object with specified configuration group name. func DB(name ...string) gdb.DB { return gins.Database(name...) } // Table is alias of Model. -func Table(tables string, db ...string) *gdb.Model { - return DB(db...).Table(tables) +// The database component is designed not only for +// relational databases but also for NoSQL databases in the future. The name +// "Table" is not proper for that purpose any more. +// Deprecated, use Model instead. +func Table(tableNameOrStruct ...interface{}) *gdb.Model { + return DB().Model(tableNameOrStruct...) } -// Model creates and returns a model from specified database or default database configuration. -// The optional parameter <db> specifies the configuration group name of the database, -// which is "default" in default. -func Model(tables string, db ...string) *gdb.Model { - return DB(db...).Model(tables) +// Model creates and returns a model based on configuration of default database group. +func Model(tableNameOrStruct ...interface{}) *gdb.Model { + return DB().Model(tableNameOrStruct...) } // Redis returns an instance of redis client with specified configuration group name. func Redis(name ...string) *gredis.Redis { return gins.Redis(name...) } + +// Validator is a convenience function, which creates and returns a new validation manager object. +func Validator() *gvalid.Validator { + return gvalid.New() +} diff --git a/frame/g/g_setting.go b/frame/g/g_setting.go index d989adaf1..ddb9e966a 100644 --- a/frame/g/g_setting.go +++ b/frame/g/g_setting.go @@ -1,4 +1,4 @@ -// Copyright 2019 gf Author(https://github.com/gogf/gf). All Rights Reserved. +// Copyright GoFrame Author(https://goframe.org). All Rights Reserved. // // This Source Code Form is subject to the terms of the MIT License. // If a copy of the MIT was not distributed with this file, @@ -11,7 +11,7 @@ import ( "github.com/gogf/gf/net/ghttp" ) -// SetEnabled enables/disables the GoFrame internal logging manually. +// SetDebug 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) { diff --git a/frame/g/g_z_example_test.go b/frame/g/g_z_example_test.go index 414b41d88..262ebb775 100644 --- a/frame/g/g_z_example_test.go +++ b/frame/g/g_z_example_test.go @@ -1,4 +1,4 @@ -// Copyright 2020 gf Author(https://github.com/gogf/gf). All Rights Reserved. +// Copyright GoFrame Author(https://goframe.org). All Rights Reserved. // // This Source Code Form is subject to the terms of the MIT License. // If a copy of the MIT was not distributed with this file, diff --git a/frame/gins/gins.go b/frame/gins/gins.go index 5e8ff2e51..af822b0f6 100644 --- a/frame/gins/gins.go +++ b/frame/gins/gins.go @@ -1,4 +1,4 @@ -// Copyright 2017 gf Author(https://github.com/gogf/gf). All Rights Reserved. +// Copyright GoFrame Author(https://goframe.org). All Rights Reserved. // // This Source Code Form is subject to the terms of the MIT License. // If a copy of the MIT was not distributed with this file, diff --git a/frame/gins/gins_config.go b/frame/gins/gins_config.go index b68d74d0a..38bca58b4 100644 --- a/frame/gins/gins_config.go +++ b/frame/gins/gins_config.go @@ -1,4 +1,4 @@ -// Copyright 2019 gf Author(https://github.com/gogf/gf). All Rights Reserved. +// Copyright GoFrame Author(https://goframe.org). All Rights Reserved. // // This Source Code Form is subject to the terms of the MIT License. // If a copy of the MIT was not distributed with this file, diff --git a/frame/gins/gins_database.go b/frame/gins/gins_database.go index bcedabf7f..dfe83eae7 100644 --- a/frame/gins/gins_database.go +++ b/frame/gins/gins_database.go @@ -1,4 +1,4 @@ -// Copyright GoFrame Author(https://github.com/gogf/gf). All Rights Reserved. +// Copyright GoFrame Author(https://goframe.org). All Rights Reserved. // // This Source Code Form is subject to the terms of the MIT License. // If a copy of the MIT was not distributed with this file, @@ -7,7 +7,10 @@ package gins import ( + "context" "fmt" + "github.com/gogf/gf/errors/gcode" + "github.com/gogf/gf/errors/gerror" "github.com/gogf/gf/internal/intlog" "github.com/gogf/gf/text/gstr" "github.com/gogf/gf/util/gutil" @@ -47,7 +50,29 @@ func Database(name ...string) gdb.DB { configMap = Config().GetMap(configNodeKey) } if len(configMap) == 0 && !gdb.IsConfigured() { - panic(fmt.Sprintf(`database init failed: "%s" node not found, is config file or configuration missing?`, configNodeNameDatabase)) + configFilePath, _ := Config().GetFilePath() + if configFilePath == "" { + exampleFileName := "config.example.toml" + if exampleConfigFilePath, _ := Config().GetFilePath(exampleFileName); exampleConfigFilePath != "" { + panic(gerror.NewCodef( + gcode.CodeMissingConfiguration, + `configuration file "%s" not found, but found "%s", did you miss renaming the example configuration file?`, + Config().GetFileName(), + exampleFileName, + )) + } else { + panic(gerror.NewCodef( + gcode.CodeMissingConfiguration, + `configuration file "%s" not found, did you miss the configuration file or the misspell the configuration file name?`, + Config().GetFileName(), + )) + } + } + panic(gerror.NewCodef( + gcode.CodeMissingConfiguration, + `database initialization failed: "%s" node not found, is configuration file or configuration node missing?`, + configNodeNameDatabase, + )) } if len(configMap) == 0 { configMap = make(map[string]interface{}) @@ -69,11 +94,11 @@ func Database(name ...string) gdb.DB { } if len(cg) > 0 { if gdb.GetConfig(group) == nil { - intlog.Printf("add configuration for group: %s, %#v", g, cg) + intlog.Printf(context.TODO(), "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) + intlog.Printf(context.TODO(), "ignore configuration as it already exists for group: %s, %#v", g, cg) + intlog.Printf(context.TODO(), "%s, %#v", g, cg) } } } @@ -81,17 +106,17 @@ func Database(name ...string) gdb.DB { // which is the default group configuration. if node := parseDBConfigNode(configMap); node != nil { cg := gdb.ConfigGroup{} - if node.LinkInfo != "" || node.Host != "" { + if node.Link != "" || node.Host != "" { cg = append(cg, *node) } if len(cg) > 0 { if gdb.GetConfig(group) == nil { - intlog.Printf("add configuration for group: %s, %#v", gdb.DefaultGroupName, cg) + intlog.Printf(context.TODO(), "add configuration for group: %s, %#v", gdb.DefaultGroupName, cg) gdb.SetConfigGroup(gdb.DefaultGroupName, cg) } else { - intlog.Printf("ignore configuration as it already exists for group: %s, %#v", gdb.DefaultGroupName, cg) - intlog.Printf("%s, %#v", gdb.DefaultGroupName, cg) + intlog.Printf(context.TODO(), "ignore configuration as it already exists for group: %s, %#v", gdb.DefaultGroupName, cg) + intlog.Printf(context.TODO(), "%s, %#v", gdb.DefaultGroupName, cg) } } } @@ -112,7 +137,7 @@ func Database(name ...string) gdb.DB { } return db } else { - // It panics often because it dose not find its configuration for given group. + // If panics, often because it does not find its configuration for given group. panic(err) } return nil @@ -133,15 +158,19 @@ func parseDBConfigNode(value interface{}) *gdb.ConfigNode { if err != nil { panic(err) } - if _, v := gutil.MapPossibleItemByKey(nodeMap, "link"); v != nil { - node.LinkInfo = gconv.String(v) + // To be compatible with old version. + if _, v := gutil.MapPossibleItemByKey(nodeMap, "LinkInfo"); v != nil { + node.Link = gconv.String(v) + } + if _, v := gutil.MapPossibleItemByKey(nodeMap, "Link"); v != nil { + node.Link = gconv.String(v) } // Parse link syntax. - if node.LinkInfo != "" && node.Type == "" { - match, _ := gregex.MatchString(`([a-z]+):(.+)`, node.LinkInfo) + if node.Link != "" && node.Type == "" { + match, _ := gregex.MatchString(`([a-z]+):(.+)`, node.Link) if len(match) == 3 { node.Type = gstr.Trim(match[1]) - node.LinkInfo = gstr.Trim(match[2]) + node.Link = gstr.Trim(match[2]) } } return node diff --git a/frame/gins/gins_i18n.go b/frame/gins/gins_i18n.go index cc4cb8c40..71b57b63a 100644 --- a/frame/gins/gins_i18n.go +++ b/frame/gins/gins_i18n.go @@ -1,4 +1,4 @@ -// Copyright 2019 gf Author(https://github.com/gogf/gf). All Rights Reserved. +// Copyright GoFrame Author(https://goframe.org). All Rights Reserved. // // This Source Code Form is subject to the terms of the MIT License. // If a copy of the MIT was not distributed with this file, diff --git a/frame/gins/gins_log.go b/frame/gins/gins_log.go index 4fb4bef51..3ec6c691c 100644 --- a/frame/gins/gins_log.go +++ b/frame/gins/gins_log.go @@ -1,4 +1,4 @@ -// Copyright GoFrame Author(https://github.com/gogf/gf). All Rights Reserved. +// Copyright GoFrame Author(https://goframe.org). All Rights Reserved. // // This Source Code Form is subject to the terms of the MIT License. // If a copy of the MIT was not distributed with this file, diff --git a/frame/gins/gins_redis.go b/frame/gins/gins_redis.go index 3c1ef55a8..ef0ad47f4 100644 --- a/frame/gins/gins_redis.go +++ b/frame/gins/gins_redis.go @@ -1,4 +1,4 @@ -// Copyright GoFrame Author(https://github.com/gogf/gf). All Rights Reserved. +// Copyright GoFrame Author(https://goframe.org). All Rights Reserved. // // This Source Code Form is subject to the terms of the MIT License. // If a copy of the MIT was not distributed with this file, @@ -47,7 +47,14 @@ func Redis(name ...string) *gredis.Redis { panic(fmt.Sprintf(`configuration for redis not found for group "%s"`, group)) } } else { - panic(fmt.Sprintf(`incomplete configuration for redis: "redis" node not found in config file "%s"`, config.FilePath())) + filepath, err := config.GetFilePath() + if err != nil { + panic(err) + } + panic(fmt.Sprintf( + `incomplete configuration for redis: "redis" node not found in config file "%s"`, + filepath, + )) } return nil }) diff --git a/frame/gins/gins_resource.go b/frame/gins/gins_resource.go index 20f235a63..fa97179fb 100644 --- a/frame/gins/gins_resource.go +++ b/frame/gins/gins_resource.go @@ -1,4 +1,4 @@ -// Copyright 2019 gf Author(https://github.com/gogf/gf). All Rights Reserved. +// Copyright GoFrame Author(https://goframe.org). All Rights Reserved. // // This Source Code Form is subject to the terms of the MIT License. // If a copy of the MIT was not distributed with this file, diff --git a/frame/gins/gins_server.go b/frame/gins/gins_server.go index 9a84c84b9..e73eadaa8 100644 --- a/frame/gins/gins_server.go +++ b/frame/gins/gins_server.go @@ -1,4 +1,4 @@ -// Copyright GoFrame Author(https://github.com/gogf/gf). All Rights Reserved. +// Copyright GoFrame Author(https://goframe.org). All Rights Reserved. // // This Source Code Form is subject to the terms of the MIT License. // If a copy of the MIT was not distributed with this file, @@ -24,17 +24,30 @@ func Server(name ...interface{}) *ghttp.Server { s := ghttp.GetServer(name...) // To avoid file no found error while it's not necessary. if Config().Available() { - var m map[string]interface{} + var ( + serverConfigMap map[string]interface{} + serverLoggerConfigMap map[string]interface{} + ) nodeKey, _ := gutil.MapPossibleItemByKey(Config().GetMap("."), configNodeNameServer) if nodeKey == "" { nodeKey = configNodeNameServer } - m = Config().GetMap(fmt.Sprintf(`%s.%s`, nodeKey, s.GetName())) - if len(m) == 0 { - m = Config().GetMap(nodeKey) + // Server configuration. + serverConfigMap = Config().GetMap(fmt.Sprintf(`%s.%s`, nodeKey, s.GetName())) + if len(serverConfigMap) == 0 { + serverConfigMap = Config().GetMap(nodeKey) } - if len(m) > 0 { - if err := s.SetConfigWithMap(m); err != nil { + if len(serverConfigMap) > 0 { + if err := s.SetConfigWithMap(serverConfigMap); err != nil { + panic(err) + } + } + // Server logger configuration. + serverLoggerConfigMap = Config().GetMap( + fmt.Sprintf(`%s.%s.%s`, nodeKey, s.GetName(), configNodeNameLogger), + ) + if len(serverLoggerConfigMap) > 0 { + if err := s.Logger().SetConfigWithMap(serverLoggerConfigMap); err != nil { panic(err) } } diff --git a/frame/gins/gins_view.go b/frame/gins/gins_view.go index b76b58761..ab49298fe 100644 --- a/frame/gins/gins_view.go +++ b/frame/gins/gins_view.go @@ -1,4 +1,4 @@ -// Copyright GoFrame Author(https://github.com/gogf/gf). All Rights Reserved. +// Copyright GoFrame Author(https://goframe.org). All Rights Reserved. // // This Source Code Form is subject to the terms of the MIT License. // If a copy of the MIT was not distributed with this file, diff --git a/frame/gins/gins_z_unit_basic_test.go b/frame/gins/gins_z_unit_basic_test.go index 9ed95fabd..ba9253cc9 100644 --- a/frame/gins/gins_z_unit_basic_test.go +++ b/frame/gins/gins_z_unit_basic_test.go @@ -1,4 +1,4 @@ -// Copyright 2017 gf Author(https://github.com/gogf/gf). All Rights Reserved. +// Copyright GoFrame Author(https://goframe.org). All Rights Reserved. // // This Source Code Form is subject to the terms of the MIT License. // If a copy of the MIT was not distributed with this file, diff --git a/frame/gins/gins_z_unit_config_test.go b/frame/gins/gins_z_unit_config_test.go index d65041407..0057b347b 100644 --- a/frame/gins/gins_z_unit_config_test.go +++ b/frame/gins/gins_z_unit_config_test.go @@ -1,4 +1,4 @@ -// Copyright 2017 gf Author(https://github.com/gogf/gf). All Rights Reserved. +// Copyright GoFrame Author(https://goframe.org). All Rights Reserved. // // This Source Code Form is subject to the terms of the MIT License. // If a copy of the MIT was not distributed with this file, diff --git a/frame/gins/gins_z_unit_database_test.go b/frame/gins/gins_z_unit_database_test.go index ce85196f9..2693a27e7 100644 --- a/frame/gins/gins_z_unit_database_test.go +++ b/frame/gins/gins_z_unit_database_test.go @@ -1,4 +1,4 @@ -// Copyright 2017 gf Author(https://github.com/gogf/gf). All Rights Reserved. +// Copyright GoFrame Author(https://goframe.org). All Rights Reserved. // // This Source Code Form is subject to the terms of the MIT License. // If a copy of the MIT was not distributed with this file, diff --git a/frame/gins/gins_z_unit_redis_test.go b/frame/gins/gins_z_unit_redis_test.go index 3619b1dd5..6ac44f85f 100644 --- a/frame/gins/gins_z_unit_redis_test.go +++ b/frame/gins/gins_z_unit_redis_test.go @@ -1,4 +1,4 @@ -// Copyright 2017 gf Author(https://github.com/gogf/gf). All Rights Reserved. +// Copyright GoFrame Author(https://goframe.org). All Rights Reserved. // // This Source Code Form is subject to the terms of the MIT License. // If a copy of the MIT was not distributed with this file, diff --git a/frame/gins/gins_z_unit_view_test.go b/frame/gins/gins_z_unit_view_test.go index e03f398dd..084c09705 100644 --- a/frame/gins/gins_z_unit_view_test.go +++ b/frame/gins/gins_z_unit_view_test.go @@ -1,4 +1,4 @@ -// Copyright 2017 gf Author(https://github.com/gogf/gf). All Rights Reserved. +// Copyright GoFrame Author(https://goframe.org). All Rights Reserved. // // This Source Code Form is subject to the terms of the MIT License. // If a copy of the MIT was not distributed with this file, @@ -7,6 +7,7 @@ package gins import ( + "context" "fmt" "github.com/gogf/gf/debug/gdebug" "github.com/gogf/gf/os/gcfg" @@ -20,7 +21,7 @@ import ( func Test_View(t *testing.T) { gtest.C(t, func(t *gtest.T) { t.AssertNE(View(), nil) - b, e := View().ParseContent(`{{"我是中国人" | substr 2 -1}}`, nil) + b, e := View().ParseContent(context.TODO(), `{{"我是中国人" | substr 2 -1}}`, nil) t.Assert(e, nil) t.Assert(b, "中国人") }) @@ -30,7 +31,7 @@ func Test_View(t *testing.T) { t.Assert(err, nil) defer gfile.Remove(tpl) - b, e := View().Parse("t.tpl", nil) + b, e := View().Parse(context.TODO(), "t.tpl", nil) t.Assert(e, nil) t.Assert(b, "中国人") }) @@ -43,7 +44,7 @@ func Test_View(t *testing.T) { err = View().AddPath(path) t.Assert(err, nil) - b, e := View().Parse("t.tpl", nil) + b, e := View().Parse(context.TODO(), "t.tpl", nil) t.Assert(e, nil) t.Assert(b, "中国人") }) @@ -64,11 +65,11 @@ func Test_View_Config(t *testing.T) { str := `hello ${.name},version:${.version}` view.Assigns(map[string]interface{}{"version": "1.9.0"}) - result, err := view.ParseContent(str, nil) + result, err := view.ParseContent(context.TODO(), str, nil) t.Assert(err, nil) t.Assert(result, "hello test1,version:1.9.0") - result, err = view.ParseDefault() + result, err = view.ParseDefault(context.TODO()) t.Assert(err, nil) t.Assert(result, "test1:test1") }) @@ -86,11 +87,11 @@ func Test_View_Config(t *testing.T) { str := `hello #{.name},version:#{.version}` view.Assigns(map[string]interface{}{"version": "1.9.0"}) - result, err := view.ParseContent(str, nil) + result, err := view.ParseContent(context.TODO(), str, nil) t.Assert(err, nil) t.Assert(result, "hello test2,version:1.9.0") - result, err = view.ParseDefault() + result, err = view.ParseDefault(context.TODO()) t.Assert(err, nil) t.Assert(result, "test2:test2") }) @@ -108,11 +109,11 @@ func Test_View_Config(t *testing.T) { str := `hello {.name},version:{.version}` view.Assigns(map[string]interface{}{"version": "1.9.0"}) - result, err := view.ParseContent(str, nil) + result, err := view.ParseContent(context.TODO(), str, nil) t.Assert(err, nil) t.Assert(result, "hello test,version:1.9.0") - result, err = view.ParseDefault() + result, err = view.ParseDefault(context.TODO()) t.Assert(err, nil) t.Assert(result, "test:test") }) @@ -130,11 +131,11 @@ func Test_View_Config(t *testing.T) { str := `hello {.name},version:{.version}` view.Assigns(map[string]interface{}{"version": "1.9.0"}) - result, err := view.ParseContent(str, nil) + result, err := view.ParseContent(context.TODO(), str, nil) t.Assert(err, nil) t.Assert(result, "hello test,version:1.9.0") - result, err = view.ParseDefault() + result, err = view.ParseDefault(context.TODO()) t.Assert(err, nil) t.Assert(result, "test:test") }) diff --git a/frame/gmvc/controller.go b/frame/gmvc/controller.go index 678edb3be..573d27ff7 100644 --- a/frame/gmvc/controller.go +++ b/frame/gmvc/controller.go @@ -1,10 +1,11 @@ -// Copyright 2017 gf Author(https://github.com/gogf/gf). All Rights Reserved. +// Copyright GoFrame Author(https://goframe.org). All Rights Reserved. // // This Source Code Form is subject to the terms of the MIT License. // If a copy of the MIT was not distributed with this file, // You can obtain one at https://github.com/gogf/gf. // Package gmvc provides basic object classes for MVC. +// Deprecated, no longer suggested. package gmvc import ( @@ -12,6 +13,7 @@ import ( ) // Controller is used for controller register of ghttp.Server. +// Deprecated, no longer suggested. type Controller struct { Request *ghttp.Request Response *ghttp.Response diff --git a/frame/gmvc/model.go b/frame/gmvc/model.go index 5eb656d0d..cc6a67c2b 100644 --- a/frame/gmvc/model.go +++ b/frame/gmvc/model.go @@ -1,4 +1,4 @@ -// Copyright 2017 gf Author(https://github.com/gogf/gf). All Rights Reserved. +// Copyright GoFrame Author(https://goframe.org). All Rights Reserved. // // This Source Code Form is subject to the terms of the MIT License. // If a copy of the MIT was not distributed with this file, @@ -9,6 +9,11 @@ package gmvc import "github.com/gogf/gf/database/gdb" type ( - M = Model // M is alias for Model, just for short write purpose. - Model = *gdb.Model // Model is alias for *gdb.Model. + // M is alias for Model, just for short write purpose. + // Deprecated, no longer suggested. + M = *gdb.Model + + // Model is alias for *gdb.Model. + // Deprecated, no longer suggested. + Model = *gdb.Model ) diff --git a/frame/gmvc/view.go b/frame/gmvc/view.go index 73caebdea..3f77dce2b 100644 --- a/frame/gmvc/view.go +++ b/frame/gmvc/view.go @@ -1,4 +1,4 @@ -// Copyright 2017 gf Author(https://github.com/gogf/gf). All Rights Reserved. +// Copyright GoFrame Author(https://goframe.org). All Rights Reserved. // // This Source Code Form is subject to the terms of the MIT License. // If a copy of the MIT was not distributed with this file, @@ -19,6 +19,7 @@ import ( // View is the view object for controller. // It's initialized when controller request initializes and destroyed // when the controller request closes. +// Deprecated, no longer suggested. type View struct { mu sync.RWMutex view *gview.View @@ -27,6 +28,7 @@ type View struct { } // NewView creates and returns a controller view object. +// Deprecated, no longer suggested. func NewView(w *ghttp.Response) *View { return &View{ view: gins.View(), @@ -76,7 +78,7 @@ func (view *View) LockFunc(f func(data gview.Params)) { f(view.data) } -// LockFunc locks reading for template variables by callback function <f>. +// RLockFunc locks reading for template variables by callback function <f>. func (view *View) RLockFunc(f func(data gview.Params)) { view.mu.RLock() defer view.mu.RUnlock() diff --git a/go.mod b/go.mod index 77c4f3f19..bbdb48b3a 100644 --- a/go.mod +++ b/go.mod @@ -1,18 +1,21 @@ module github.com/gogf/gf -go 1.11 +go 1.14 require ( github.com/BurntSushi/toml v0.3.1 github.com/clbanning/mxj v1.8.5-0.20200714211355-ff02cfb8ea28 + github.com/fatih/color v1.12.0 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 + github.com/go-sql-driver/mysql v1.6.0 + github.com/gomodule/redigo v1.8.5 + github.com/gorilla/websocket v1.4.2 github.com/grokify/html-strip-tags-go v0.0.0-20190921062105-daaa06bf1aaf - 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/text v0.3.2 - gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c + github.com/olekukonko/tablewriter v0.0.5 + go.opentelemetry.io/otel v1.0.0-RC2 + go.opentelemetry.io/otel/oteltest v1.0.0-RC2 + go.opentelemetry.io/otel/trace v1.0.0-RC2 + golang.org/x/net v0.0.0-20210520170846-37e1c6afe023 + golang.org/x/text v0.3.6 + gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b ) diff --git a/go.sum b/go.sum new file mode 100644 index 000000000..72133957f --- /dev/null +++ b/go.sum @@ -0,0 +1,60 @@ +github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ= +github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= +github.com/clbanning/mxj v1.8.5-0.20200714211355-ff02cfb8ea28 h1:LdXxtjzvZYhhUaonAaAKArG3pyC67kGL3YY+6hGG8G4= +github.com/clbanning/mxj v1.8.5-0.20200714211355-ff02cfb8ea28/go.mod h1:BVjHeAH+rl9rs6f+QIpeRl0tfu10SXn1pUSa5PVGJng= +github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/fatih/color v1.12.0 h1:mRhaKNwANqRgUBGKmnI5ZxEk7QXmjQeCcuYFMX2bfcc= +github.com/fatih/color v1.12.0/go.mod h1:ELkj/draVOlAH/xkhN6mQ50Qd0MPOk5AAr3maGEBuJM= +github.com/fsnotify/fsnotify v1.4.9 h1:hsms1Qyu0jgnwNXIxa+/V/PDsU6CfLf6CNO8H7IWoS4= +github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ= +github.com/go-sql-driver/mysql v1.6.0 h1:BCTh4TKNUYmOmMUcQ3IipzF5prigylS7XXjEkfCHuOE= +github.com/go-sql-driver/mysql v1.6.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg= +github.com/gomodule/redigo v1.8.5 h1:nRAxCa+SVsyjSBrtZmG/cqb6VbTmuRzpg/PoTFlpumc= +github.com/gomodule/redigo v1.8.5/go.mod h1:P9dn9mFrCBvWhGE1wpxx6fgq7BAeLBk+UUUzlpkBYO0= +github.com/google/go-cmp v0.5.6 h1:BKbKCqvP6I+rmFHt06ZmyQtvB8xAkWdhFyr0ZUNZcxQ= +github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/gorilla/websocket v1.4.2 h1:+/TMaTYc4QFitKJxsQ7Yye35DkWvkdLcvGKqM+x0Ufc= +github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= +github.com/grokify/html-strip-tags-go v0.0.0-20190921062105-daaa06bf1aaf h1:wIOAyJMMen0ELGiFzlmqxdcV1yGbkyHBAB6PolcNbLA= +github.com/grokify/html-strip-tags-go v0.0.0-20190921062105-daaa06bf1aaf/go.mod h1:2Su6romC5/1VXOQMaWL2yb618ARB8iVo6/DR99A6d78= +github.com/mattn/go-colorable v0.1.8 h1:c1ghPdyEDarC70ftn0y+A/Ee++9zz8ljHG1b13eJ0s8= +github.com/mattn/go-colorable v0.1.8/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= +github.com/mattn/go-isatty v0.0.12 h1:wuysRhFDzyxgEmMf5xjvJ2M9dZoWAXNNr5LSBS7uHXY= +github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= +github.com/mattn/go-runewidth v0.0.9 h1:Lm995f3rfxdpd6TSmuVCHVb/QhupuXlYr8sCI/QdE+0= +github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI= +github.com/olekukonko/tablewriter v0.0.5 h1:P2Ga83D34wi1o9J6Wh1mRuqd4mF/x/lgBS7N7AbDhec= +github.com/olekukonko/tablewriter v0.0.5/go.mod h1:hPp6KlRPjbx+hW8ykQs1w3UBbZlj6HuIJcUGPhkA7kY= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= +github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY= +github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +go.opentelemetry.io/otel v1.0.0-RC2 h1:SHhxSjB+omnGZPgGlKe+QMp3MyazcOHdQ8qwo89oKbg= +go.opentelemetry.io/otel v1.0.0-RC2/go.mod h1:w1thVQ7qbAy8MHb0IFj8a5Q2QU0l2ksf8u/CN8m3NOM= +go.opentelemetry.io/otel/oteltest v1.0.0-RC2 h1:xNKqMhlZYkASSyvF4JwObZFMq0jhFN3c3SP+2rCzVPk= +go.opentelemetry.io/otel/oteltest v1.0.0-RC2/go.mod h1:kiQ4tw5tAL4JLTbcOYwK1CWI1HkT5aiLzHovgOVnz/A= +go.opentelemetry.io/otel/trace v1.0.0-RC2 h1:dunAP0qDULMIT82atj34m5RgvsIK6LcsXf1c/MsYg1w= +go.opentelemetry.io/otel/trace v1.0.0-RC2/go.mod h1:JPQ+z6nNw9mqEGT8o3eoPTdnNI+Aj5JcxEsVGREIAy4= +golang.org/x/net v0.0.0-20210520170846-37e1c6afe023 h1:ADo5wSpq2gqaCGQWzk7S5vd//0iyyLeAratkEoG5dLE= +golang.org/x/net v0.0.0-20210520170846-37e1c6afe023/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210423082822-04245dca01da h1:b3NXsE2LusjYGGjL5bxEVZZORm/YEFFrWFjR8eFrw/c= +golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= +golang.org/x/text v0.3.6 h1:aRYxNxv6iGQlyVaZmk6ZgYEDa+Jg18DxebPSrd6bg1M= +golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4= +golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b h1:h8qDotaEPuJATrMmW04NCwg7v22aHH28wwpauUhK9Oo= +gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/i18n/gi18n/gi18n.go b/i18n/gi18n/gi18n.go index 5cb8aea0b..460239807 100644 --- a/i18n/gi18n/gi18n.go +++ b/i18n/gi18n/gi18n.go @@ -1,4 +1,4 @@ -// Copyright 2019 gf Author(https://github.com/gogf/gf). All Rights Reserved. +// Copyright GoFrame Author(https://goframe.org). All Rights Reserved. // // This Source Code Form is subject to the terms of the MIT License. // If a copy of the MIT was not distributed with this file, @@ -7,63 +7,46 @@ // Package gi18n implements internationalization and localization. package gi18n -var ( - // defaultManager is the default i18n instance for package functions. - defaultManager = Instance() -) +import "context" // SetPath sets the directory path storing i18n files. func SetPath(path string) error { - return defaultManager.SetPath(path) + return Instance().SetPath(path) } // SetLanguage sets the language for translator. func SetLanguage(language string) { - defaultManager.SetLanguage(language) + Instance().SetLanguage(language) } // SetDelimiters sets the delimiters for translator. func SetDelimiters(left, right string) { - defaultManager.SetDelimiters(left, right) + Instance().SetDelimiters(left, right) } // T is alias of Translate for convenience. -func T(content string, language ...string) string { - return defaultManager.T(content, language...) +func T(ctx context.Context, content string) string { + return Instance().T(ctx, content) } // Tf is alias of TranslateFormat for convenience. -func Tf(format string, values ...interface{}) string { - return defaultManager.TranslateFormat(format, values...) -} - -// Tfl is alias of TranslateFormatLang for convenience. -func Tfl(language string, format string, values ...interface{}) string { - return defaultManager.TranslateFormatLang(language, format, values...) +func Tf(ctx context.Context, format string, values ...interface{}) string { + return Instance().TranslateFormat(ctx, format, values...) } // TranslateFormat translates, formats and returns the <format> with configured language // and given <values>. -func TranslateFormat(format string, values ...interface{}) string { - return defaultManager.TranslateFormat(format, values...) -} - -// TranslateFormatLang translates, formats and returns the <format> with configured language -// and given <values>. The parameter <language> specifies custom translation language ignoring -// configured language. If <language> is given empty string, it uses the default configured -// language for the translation. -func TranslateFormatLang(language string, format string, values ...interface{}) string { - return defaultManager.TranslateFormatLang(format, language, values...) +func TranslateFormat(ctx context.Context, format string, values ...interface{}) string { + return Instance().TranslateFormat(ctx, format, values...) } // Translate translates <content> with configured language and returns the translated content. -// The parameter <language> specifies custom translation language ignoring configured language. -func Translate(content string, language ...string) string { - return defaultManager.Translate(content, language...) +func Translate(ctx context.Context, content string) string { + return Instance().Translate(ctx, content) } -// GetValue retrieves and returns the configured content for given key and specified language. +// GetContent retrieves and returns the configured content for given key and specified language. // It returns an empty string if not found. -func GetContent(key string, language ...string) string { - return defaultManager.GetContent(key, language...) +func GetContent(ctx context.Context, key string) string { + return Instance().GetContent(ctx, key) } diff --git a/i18n/gi18n/gi18n_ctx.go b/i18n/gi18n/gi18n_ctx.go new file mode 100644 index 000000000..a2c7293a7 --- /dev/null +++ b/i18n/gi18n/gi18n_ctx.go @@ -0,0 +1,35 @@ +// Copyright GoFrame Author(https://goframe.org). All Rights Reserved. +// +// This Source Code Form is subject to the terms of the MIT License. +// If a copy of the MIT was not distributed with this file, +// You can obtain one at https://github.com/gogf/gf. + +// Package gi18n implements internationalization and localization. +package gi18n + +import "context" + +const ( + ctxLanguage = "I18nLanguage" +) + +// WithLanguage append language setting to the context and returns a new context. +func WithLanguage(ctx context.Context, language string) context.Context { + if ctx == nil { + ctx = context.TODO() + } + return context.WithValue(ctx, ctxLanguage, language) +} + +// LanguageFromCtx retrieves and returns language name from context. +// It returns an empty string if it is not set previously. +func LanguageFromCtx(ctx context.Context) string { + if ctx == nil { + return "" + } + v := ctx.Value(ctxLanguage) + if v != nil { + return v.(string) + } + return "" +} diff --git a/i18n/gi18n/gi18n_instance.go b/i18n/gi18n/gi18n_instance.go index 4007fc1aa..43af97cc1 100644 --- a/i18n/gi18n/gi18n_instance.go +++ b/i18n/gi18n/gi18n_instance.go @@ -1,4 +1,4 @@ -// Copyright 2019 gf Author(https://github.com/gogf/gf). All Rights Reserved. +// Copyright GoFrame Author(https://goframe.org). All Rights Reserved. // // This Source Code Form is subject to the terms of the MIT License. // If a copy of the MIT was not distributed with this file, @@ -9,7 +9,7 @@ package gi18n import "github.com/gogf/gf/container/gmap" const ( - // Default group name for instance usage. + // DefaultName is the default group name for instance usage. DefaultName = "default" ) diff --git a/i18n/gi18n/gi18n_manager.go b/i18n/gi18n/gi18n_manager.go index e7105c5be..e5d18f7bc 100644 --- a/i18n/gi18n/gi18n_manager.go +++ b/i18n/gi18n/gi18n_manager.go @@ -1,4 +1,4 @@ -// Copyright 2019 gf Author(https://github.com/gogf/gf). All Rights Reserved. +// Copyright GoFrame Author(https://goframe.org). All Rights Reserved. // // This Source Code Form is subject to the terms of the MIT License. // If a copy of the MIT was not distributed with this file, @@ -7,14 +7,14 @@ package gi18n import ( - "errors" + "context" "fmt" + "github.com/gogf/gf/errors/gcode" + "github.com/gogf/gf/errors/gerror" "github.com/gogf/gf/internal/intlog" "strings" "sync" - "github.com/gogf/gf/os/glog" - "github.com/gogf/gf/os/gfsnotify" "github.com/gogf/gf/text/gregex" @@ -27,7 +27,7 @@ import ( "github.com/gogf/gf/os/gres" ) -// Manager, it is concurrent safe, supporting hot reload. +// Manager for i18n contents, it is concurrent safe, supporting hot reload. type Manager struct { mu sync.RWMutex data map[string]map[string]string // Translating map. @@ -38,13 +38,13 @@ type Manager struct { // Options is used for i18n object configuration. type Options struct { Path string // I18n files storage path. - Language string // Local language. + Language string // Default local language. Delimiters []string // Delimiters for variable parsing. } var ( - // defaultDelimiters defines the default key variable delimiters. - defaultDelimiters = []string{"{#", "}"} + defaultLanguage = "en" // defaultDelimiters defines the default language if user does not specified in options. + defaultDelimiters = []string{"{#", "}"} // defaultDelimiters defines the default key variable delimiters. ) // New creates and returns a new i18n manager. @@ -57,6 +57,9 @@ func New(options ...Options) *Manager { } else { opts = DefaultOptions() } + if len(opts.Language) == 0 { + opts.Language = defaultLanguage + } if len(opts.Delimiters) == 0 { opts.Delimiters = defaultDelimiters } @@ -68,7 +71,7 @@ func New(options ...Options) *Manager { gregex.Quote(opts.Delimiters[1]), ), } - intlog.Printf(`New: %#v`, m) + intlog.Printf(context.TODO(), `New: %#v`, m) return m } @@ -99,66 +102,50 @@ func (m *Manager) SetPath(path string) error { } else { realPath, _ := gfile.Search(path) if realPath == "" { - return errors.New(fmt.Sprintf(`%s does not exist`, path)) + return gerror.NewCodef(gcode.CodeInvalidParameter, `%s does not exist`, path) } m.options.Path = realPath } - intlog.Printf(`SetPath: %s`, m.options.Path) + intlog.Printf(context.TODO(), `SetPath: %s`, m.options.Path) return nil } // SetLanguage sets the language for translator. func (m *Manager) SetLanguage(language string) { m.options.Language = language - intlog.Printf(`SetLanguage: %s`, m.options.Language) + intlog.Printf(context.TODO(), `SetLanguage: %s`, m.options.Language) } // SetDelimiters sets the delimiters for translator. func (m *Manager) SetDelimiters(left, right string) { m.pattern = fmt.Sprintf(`%s(\w+)%s`, gregex.Quote(left), gregex.Quote(right)) - intlog.Printf(`SetDelimiters: %v`, m.pattern) + intlog.Printf(context.TODO(), `SetDelimiters: %v`, m.pattern) } // T is alias of Translate for convenience. -func (m *Manager) T(content string, language ...string) string { - return m.Translate(content, language...) +func (m *Manager) T(ctx context.Context, content string) string { + return m.Translate(ctx, content) } // Tf is alias of TranslateFormat for convenience. -func (m *Manager) Tf(format string, values ...interface{}) string { - return m.TranslateFormat(format, values...) -} - -// Tfl is alias of TranslateFormatLang for convenience. -func (m *Manager) Tfl(language string, format string, values ...interface{}) string { - return m.TranslateFormatLang(language, format, values...) +func (m *Manager) Tf(ctx context.Context, format string, values ...interface{}) string { + return m.TranslateFormat(ctx, format, values...) } // TranslateFormat translates, formats and returns the <format> with configured language // and given <values>. -func (m *Manager) TranslateFormat(format string, values ...interface{}) string { - return fmt.Sprintf(m.Translate(format), values...) -} - -// TranslateFormatLang translates, formats and returns the <format> with configured language -// and given <values>. The parameter <language> specifies custom translation language ignoring -// configured language. If <language> is given empty string, it uses the default configured -// language for the translation. -func (m *Manager) TranslateFormatLang(language string, format string, values ...interface{}) string { - return fmt.Sprintf(m.Translate(format, language), values...) +func (m *Manager) TranslateFormat(ctx context.Context, format string, values ...interface{}) string { + return fmt.Sprintf(m.Translate(ctx, format), values...) } // Translate translates <content> with configured language. -// The parameter <language> specifies custom translation language ignoring configured language. -func (m *Manager) Translate(content string, language ...string) string { - m.init() +func (m *Manager) Translate(ctx context.Context, content string) string { + m.init(ctx) m.mu.RLock() defer m.mu.RUnlock() transLang := m.options.Language - if len(language) > 0 && language[0] != "" { - transLang = language[0] - } else { - transLang = m.options.Language + if lang := LanguageFromCtx(ctx); lang != "" { + transLang = lang } data := m.data[transLang] if data == nil { @@ -177,21 +164,19 @@ func (m *Manager) Translate(content string, language ...string) string { } return match[0] }) - intlog.Printf(`Translate for language: %s`, transLang) + intlog.Printf(ctx, `Translate for language: %s`, transLang) return result } -// GetValue retrieves and returns the configured content for given key and specified language. +// GetContent retrieves and returns the configured content for given key and specified language. // It returns an empty string if not found. -func (m *Manager) GetContent(key string, language ...string) string { - m.init() +func (m *Manager) GetContent(ctx context.Context, key string) string { + m.init(ctx) m.mu.RLock() defer m.mu.RUnlock() transLang := m.options.Language - if len(language) > 0 && language[0] != "" { - transLang = language[0] - } else { - transLang = m.options.Language + if lang := LanguageFromCtx(ctx); lang != "" { + transLang = lang } if data, ok := m.data[transLang]; ok { return data[key] @@ -201,7 +186,7 @@ func (m *Manager) GetContent(key string, language ...string) string { // init initializes the manager for lazy initialization design. // The i18n manager is only initialized once. -func (m *Manager) init() { +func (m *Manager) init(ctx context.Context) { m.mu.RLock() // If the data is not nil, means it's already initialized. if m.data != nil { @@ -235,21 +220,17 @@ func (m *Manager) init() { m.data[lang] = make(map[string]string) } if j, err := gjson.LoadContent(file.Content()); err == nil { - for k, v := range j.ToMap() { + for k, v := range j.Map() { m.data[lang][k] = gconv.String(v) } } else { - glog.Errorf("load i18n file '%s' failed: %v", name, err) + intlog.Errorf(ctx, "load i18n file '%s' failed: %v", name, err) } } } } else if m.options.Path != "" { files, _ := gfile.ScanDirFile(m.options.Path, "*.*", true) if len(files) == 0 { - //intlog.Printf( - // "no i18n files found in configured directory: %s", - // m.options.Path, - //) return } var ( @@ -270,11 +251,11 @@ func (m *Manager) init() { m.data[lang] = make(map[string]string) } if j, err := gjson.LoadContent(gfile.GetBytes(file)); err == nil { - for k, v := range j.ToMap() { + for k, v := range j.Map() { m.data[lang][k] = gconv.String(v) } } else { - glog.Errorf("load i18n file '%s' failed: %v", file, err) + intlog.Errorf(ctx, "load i18n file '%s' failed: %v", file, err) } } // Monitor changes of i18n files for hot reload feature. diff --git a/i18n/gi18n/gi18n_unit_test.go b/i18n/gi18n/gi18n_unit_test.go index 00757686f..0579bce5c 100644 --- a/i18n/gi18n/gi18n_unit_test.go +++ b/i18n/gi18n/gi18n_unit_test.go @@ -1,4 +1,4 @@ -// Copyright 2019 gf Author(https://github.com/gogf/gf). All Rights Reserved. +// Copyright GoFrame Author(https://goframe.org). All Rights Reserved. // // This Source Code Form is subject to the terms of the MIT License. // If a copy of the MIT was not distributed with this file, @@ -7,6 +7,7 @@ package gi18n_test import ( + "context" "testing" "github.com/gogf/gf/os/gres" @@ -32,16 +33,16 @@ func Test_Basic(t *testing.T) { Path: gdebug.TestDataPath("i18n"), }) i18n.SetLanguage("none") - t.Assert(i18n.T("{#hello}{#world}"), "{#hello}{#world}") + t.Assert(i18n.T(context.Background(), "{#hello}{#world}"), "{#hello}{#world}") i18n.SetLanguage("ja") - t.Assert(i18n.T("{#hello}{#world}"), "こんにちは世界") + t.Assert(i18n.T(context.Background(), "{#hello}{#world}"), "こんにちは世界") i18n.SetLanguage("zh-CN") - t.Assert(i18n.T("{#hello}{#world}"), "你好世界") + t.Assert(i18n.T(context.Background(), "{#hello}{#world}"), "你好世界") i18n.SetDelimiters("{$", "}") - t.Assert(i18n.T("{#hello}{#world}"), "{#hello}{#world}") - t.Assert(i18n.T("{$hello}{$world}"), "你好世界") + t.Assert(i18n.T(context.Background(), "{#hello}{#world}"), "{#hello}{#world}") + t.Assert(i18n.T(context.Background(), "{$hello}{$world}"), "你好世界") }) gtest.C(t, func(t *gtest.T) { @@ -49,13 +50,13 @@ func Test_Basic(t *testing.T) { Path: gdebug.TestDataPath("i18n-file"), }) i18n.SetLanguage("none") - t.Assert(i18n.T("{#hello}{#world}"), "{#hello}{#world}") + t.Assert(i18n.T(context.Background(), "{#hello}{#world}"), "{#hello}{#world}") i18n.SetLanguage("ja") - t.Assert(i18n.T("{#hello}{#world}"), "こんにちは世界") + t.Assert(i18n.T(context.Background(), "{#hello}{#world}"), "こんにちは世界") i18n.SetLanguage("zh-CN") - t.Assert(i18n.T("{#hello}{#world}"), "你好世界") + t.Assert(i18n.T(context.Background(), "{#hello}{#world}"), "你好世界") }) gtest.C(t, func(t *gtest.T) { @@ -63,13 +64,13 @@ func Test_Basic(t *testing.T) { Path: gdebug.CallerDirectory() + gfile.Separator + "testdata" + gfile.Separator + "i18n-dir", }) i18n.SetLanguage("none") - t.Assert(i18n.T("{#hello}{#world}"), "{#hello}{#world}") + t.Assert(i18n.T(context.Background(), "{#hello}{#world}"), "{#hello}{#world}") i18n.SetLanguage("ja") - t.Assert(i18n.T("{#hello}{#world}"), "こんにちは世界") + t.Assert(i18n.T(context.Background(), "{#hello}{#world}"), "こんにちは世界") i18n.SetLanguage("zh-CN") - t.Assert(i18n.T("{#hello}{#world}"), "你好世界") + t.Assert(i18n.T(context.Background(), "{#hello}{#world}"), "你好世界") }) } @@ -80,18 +81,10 @@ func Test_TranslateFormat(t *testing.T) { Path: gdebug.TestDataPath("i18n"), }) i18n.SetLanguage("none") - t.Assert(i18n.Tf("{#hello}{#world} %d", 2020), "{#hello}{#world} 2020") + t.Assert(i18n.Tf(context.Background(), "{#hello}{#world} %d", 2020), "{#hello}{#world} 2020") i18n.SetLanguage("ja") - t.Assert(i18n.Tf("{#hello}{#world} %d", 2020), "こんにちは世界 2020") - }) - // Tfl - gtest.C(t, func(t *gtest.T) { - i18n := gi18n.New(gi18n.Options{ - Path: gdebug.TestDataPath("i18n"), - }) - t.Assert(i18n.Tfl("ja", "{#hello}{#world} %d", 2020), "こんにちは世界 2020") - t.Assert(i18n.Tfl("zh-CN", "{#hello}{#world} %d", 2020), "你好世界 2020") + t.Assert(i18n.Tf(context.Background(), "{#hello}{#world} %d", 2020), "こんにちは世界 2020") }) } @@ -101,13 +94,13 @@ func Test_DefaultManager(t *testing.T) { t.Assert(err, nil) gi18n.SetLanguage("none") - t.Assert(gi18n.T("{#hello}{#world}"), "{#hello}{#world}") + t.Assert(gi18n.T(context.Background(), "{#hello}{#world}"), "{#hello}{#world}") gi18n.SetLanguage("ja") - t.Assert(gi18n.T("{#hello}{#world}"), "こんにちは世界") + t.Assert(gi18n.T(context.Background(), "{#hello}{#world}"), "こんにちは世界") gi18n.SetLanguage("zh-CN") - t.Assert(gi18n.T("{#hello}{#world}"), "你好世界") + t.Assert(gi18n.T(context.Background(), "{#hello}{#world}"), "你好世界") }) gtest.C(t, func(t *gtest.T) { @@ -115,13 +108,13 @@ func Test_DefaultManager(t *testing.T) { t.Assert(err, nil) gi18n.SetLanguage("none") - t.Assert(gi18n.Translate("{#hello}{#world}"), "{#hello}{#world}") + t.Assert(gi18n.Translate(context.Background(), "{#hello}{#world}"), "{#hello}{#world}") gi18n.SetLanguage("ja") - t.Assert(gi18n.Translate("{#hello}{#world}"), "こんにちは世界") + t.Assert(gi18n.Translate(context.Background(), "{#hello}{#world}"), "こんにちは世界") gi18n.SetLanguage("zh-CN") - t.Assert(gi18n.Translate("{#hello}{#world}"), "你好世界") + t.Assert(gi18n.Translate(context.Background(), "{#hello}{#world}"), "你好世界") }) } @@ -132,21 +125,21 @@ func Test_Instance(t *testing.T) { err := m.SetPath("i18n-dir") t.Assert(err, nil) m.SetLanguage("zh-CN") - t.Assert(m.T("{#hello}{#world}"), "你好世界") + t.Assert(m.T(context.Background(), "{#hello}{#world}"), "你好世界") }) gtest.C(t, func(t *gtest.T) { m := gi18n.Instance() - t.Assert(m.T("{#hello}{#world}"), "你好世界") + t.Assert(m.T(context.Background(), "{#hello}{#world}"), "你好世界") }) gtest.C(t, func(t *gtest.T) { - t.Assert(g.I18n().T("{#hello}{#world}"), "你好世界") + t.Assert(g.I18n().T(context.Background(), "{#hello}{#world}"), "你好世界") }) // Default language is: en gtest.C(t, func(t *gtest.T) { m := gi18n.Instance(gconv.String(gtime.TimestampNano())) - t.Assert(m.T("{#hello}{#world}"), "HelloWorld") + t.Assert(m.T(context.Background(), "{#hello}{#world}"), "HelloWorld") }) } @@ -157,12 +150,12 @@ func Test_Resource(t *testing.T) { t.Assert(err, nil) m.SetLanguage("none") - t.Assert(m.T("{#hello}{#world}"), "{#hello}{#world}") + t.Assert(m.T(context.Background(), "{#hello}{#world}"), "{#hello}{#world}") m.SetLanguage("ja") - t.Assert(m.T("{#hello}{#world}"), "こんにちは世界") + t.Assert(m.T(context.Background(), "{#hello}{#world}"), "こんにちは世界") m.SetLanguage("zh-CN") - t.Assert(m.T("{#hello}{#world}"), "你好世界") + t.Assert(m.T(context.Background(), "{#hello}{#world}"), "你好世界") }) } diff --git a/internal/command/command.go b/internal/command/command.go new file mode 100644 index 000000000..87fce610c --- /dev/null +++ b/internal/command/command.go @@ -0,0 +1,127 @@ +// Copyright GoFrame Author(https://goframe.org). All Rights Reserved. +// +// This Source Code Form is subject to the terms of the MIT License. +// If a copy of the MIT was not distributed with this file, +// You can obtain one at https://github.com/gogf/gf. +// + +// Package command provides console operations, like options/arguments reading. +package command + +import ( + "os" + "regexp" + "strings" +) + +var ( + defaultParsedArgs = make([]string, 0) + defaultParsedOptions = make(map[string]string) + argumentRegex = regexp.MustCompile(`^\-{1,2}([\w\?\.\-]+)(=){0,1}(.*)$`) +) + +// Init does custom initialization. +func Init(args ...string) { + if len(args) == 0 { + if len(defaultParsedArgs) == 0 && len(defaultParsedOptions) == 0 { + args = os.Args + } else { + return + } + } else { + defaultParsedArgs = make([]string, 0) + defaultParsedOptions = make(map[string]string) + } + // Parsing os.Args with default algorithm. + for i := 0; i < len(args); { + array := argumentRegex.FindStringSubmatch(args[i]) + if len(array) > 2 { + if array[2] == "=" { + defaultParsedOptions[array[1]] = array[3] + } else if i < len(args)-1 { + if len(args[i+1]) > 0 && args[i+1][0] == '-' { + // Eg: gf gen -d -n 1 + defaultParsedOptions[array[1]] = array[3] + } else { + // Eg: gf gen -n 2 + defaultParsedOptions[array[1]] = args[i+1] + i += 2 + continue + } + } else { + // Eg: gf gen -h + defaultParsedOptions[array[1]] = array[3] + } + } else { + defaultParsedArgs = append(defaultParsedArgs, args[i]) + } + i++ + } +} + +// GetOpt returns the option value named `name`. +func GetOpt(name string, def ...string) string { + Init() + if v, ok := defaultParsedOptions[name]; ok { + return v + } + if len(def) > 0 { + return def[0] + } + return "" +} + +// GetOptAll returns all parsed options. +func GetOptAll() map[string]string { + Init() + return defaultParsedOptions +} + +// ContainsOpt checks whether option named `name` exist in the arguments. +func ContainsOpt(name string) bool { + Init() + _, ok := defaultParsedOptions[name] + return ok +} + +// GetArg returns the argument at `index`. +func GetArg(index int, def ...string) string { + Init() + if index < len(defaultParsedArgs) { + return defaultParsedArgs[index] + } + if len(def) > 0 { + return def[0] + } + return "" +} + +// GetArgAll returns all parsed arguments. +func GetArgAll() []string { + Init() + return defaultParsedArgs +} + +// GetOptWithEnv returns the command line argument of the specified `key`. +// If the argument does not exist, then it returns the environment variable with specified `key`. +// It returns the default value `def` if none of them exists. +// +// Fetching Rules: +// 1. Command line arguments are in lowercase format, eg: gf.<package name>.<variable name>; +// 2. Environment arguments are in uppercase format, eg: GF_<package name>_<variable name>; +func GetOptWithEnv(key string, def ...string) string { + cmdKey := strings.ToLower(strings.Replace(key, "_", ".", -1)) + if ContainsOpt(cmdKey) { + return GetOpt(cmdKey) + } else { + envKey := strings.ToUpper(strings.Replace(key, ".", "_", -1)) + if r, ok := os.LookupEnv(envKey); ok { + return r + } else { + if len(def) > 0 { + return def[0] + } + } + } + return "" +} diff --git a/internal/empty/empty.go b/internal/empty/empty.go index ae0beb57a..af1b7fe75 100644 --- a/internal/empty/empty.go +++ b/internal/empty/empty.go @@ -1,14 +1,15 @@ -// Copyright 2019 gf Author(https://github.com/gogf/gf). All Rights Reserved. +// Copyright GoFrame gf Author(https://goframe.org). All Rights Reserved. // // This Source Code Form is subject to the terms of the MIT License. // If a copy of the MIT was not distributed with this file, // You can obtain one at https://github.com/gogf/gf. -// Package empty provides functions for checking empty variables. +// Package empty provides functions for checking empty/nil variables. package empty import ( "reflect" + "time" ) // apiString is used for type assert api for String(). @@ -26,8 +27,13 @@ type apiMapStrAny interface { MapStrAny() map[string]interface{} } -// IsEmpty checks whether given <value> empty. -// It returns true if <value> is in: 0, nil, false, "", len(slice/map/chan) == 0, +type apiTime interface { + Date() (year int, month time.Month, day int) + IsZero() bool +} + +// IsEmpty checks whether given `value` empty. +// It returns true if `value` is in: 0, nil, false, "", len(slice/map/chan) == 0, // or else it returns false. func IsEmpty(value interface{}) bool { if value == nil { @@ -79,7 +85,15 @@ func IsEmpty(value interface{}) bool { case map[string]interface{}: return len(value) == 0 default: + // ========================= // Common interfaces checks. + // ========================= + if f, ok := value.(apiTime); ok { + if f == nil { + return true + } + return f.IsZero() + } if f, ok := value.(apiString); ok { if f == nil { return true @@ -151,9 +165,131 @@ func IsEmpty(value interface{}) bool { return false } -// IsNil checks whether given <value> is nil. -// Note that it might use reflect feature which affects performance a little bit. -func IsNil(value interface{}) bool { +// IsEmptyLength checks whether given `value` is empty length. +// It returns true if `value` is in: nil, "", len(slice/map/chan) == 0, +// or else it returns false. +//func IsEmptyLength(value interface{}) bool { +// if value == nil { +// return true +// } +// // It firstly checks the variable as common types using assertion to enhance the performance, +// // and then using reflection. +// switch value := value.(type) { +// case +// int, +// int8, +// int16, +// int32, +// int64, +// uint, +// uint8, +// uint16, +// uint32, +// uint64, +// float32, +// float64, +// bool: +// return false +// case string: +// return value == "" +// case []byte: +// return len(value) == 0 +// case []rune: +// return len(value) == 0 +// case []int: +// return len(value) == 0 +// case []string: +// return len(value) == 0 +// case []float32: +// return len(value) == 0 +// case []float64: +// return len(value) == 0 +// case map[string]interface{}: +// return len(value) == 0 +// default: +// // ========================= +// // Common interfaces checks. +// // ========================= +// if f, ok := value.(apiTime); ok { +// if f == nil { +// return true +// } +// return f.IsZero() +// } +// if f, ok := value.(apiString); ok { +// if f == nil { +// return true +// } +// return f.String() == "" +// } +// if f, ok := value.(apiInterfaces); ok { +// if f == nil { +// return true +// } +// return len(f.Interfaces()) == 0 +// } +// if f, ok := value.(apiMapStrAny); ok { +// if f == nil { +// return true +// } +// return len(f.MapStrAny()) == 0 +// } +// // Finally using reflect. +// var rv reflect.Value +// if v, ok := value.(reflect.Value); ok { +// rv = v +// } else { +// rv = reflect.ValueOf(value) +// } +// +// switch rv.Kind() { +// case +// reflect.Int, +// reflect.Int8, +// reflect.Int16, +// reflect.Int32, +// reflect.Int64, +// reflect.Uint, +// reflect.Uint8, +// reflect.Uint16, +// reflect.Uint32, +// reflect.Uint64, +// reflect.Uintptr, +// reflect.Float32, +// reflect.Float64, +// reflect.Bool: +// return false +// case reflect.String: +// return rv.Len() == 0 +// case reflect.Struct: +// for i := 0; i < rv.NumField(); i++ { +// if !IsEmpty(rv) { +// return false +// } +// } +// return true +// case reflect.Chan, +// reflect.Map, +// reflect.Slice, +// reflect.Array: +// return rv.Len() == 0 +// case reflect.Func, +// reflect.Ptr, +// reflect.Interface, +// reflect.UnsafePointer: +// if rv.IsNil() { +// return true +// } +// } +// } +// return false +//} + +// IsNil checks whether given `value` is nil. +// Parameter `traceSource` is used for tracing to the source variable if given `value` is type of pinter +// that also points to a pointer. It returns nil if the source is nil when `traceSource` is true. +// Note that it might use reflect feature which affects performance a little. +func IsNil(value interface{}, traceSource ...bool) bool { if value == nil { return true } @@ -168,10 +304,24 @@ func IsNil(value interface{}) bool { reflect.Map, reflect.Slice, reflect.Func, - reflect.Ptr, reflect.Interface, reflect.UnsafePointer: - return rv.IsNil() + return !rv.IsValid() || rv.IsNil() + + case reflect.Ptr: + if len(traceSource) > 0 && traceSource[0] { + for rv.Kind() == reflect.Ptr { + rv = rv.Elem() + } + if !rv.IsValid() { + return true + } + if rv.Kind() == reflect.Ptr { + return rv.IsNil() + } + } else { + return !rv.IsValid() || rv.IsNil() + } } return false } diff --git a/internal/empty/empty_test.go b/internal/empty/empty_test.go index 520aa64ee..2a4e6ecbd 100644 --- a/internal/empty/empty_test.go +++ b/internal/empty/empty_test.go @@ -1,4 +1,4 @@ -// Copyright 2019 gf Author(https://github.com/gogf/gf). All Rights Reserved. +// Copyright GoFrame Author(https://goframe.org). All Rights Reserved. // // This Source Code Form is subject to the terms of the MIT License. // If a copy of the MIT was not distributed with this file, @@ -108,3 +108,22 @@ func TestIsEmpty(t *testing.T) { t.Assert(empty.IsEmpty(tmpF6), false) }) } + +func TestIsNil(t *testing.T) { + gtest.C(t, func(t *gtest.T) { + t.Assert(empty.IsNil(nil), true) + }) + gtest.C(t, func(t *gtest.T) { + var i int + t.Assert(empty.IsNil(i), false) + }) + gtest.C(t, func(t *gtest.T) { + var i *int + t.Assert(empty.IsNil(i), true) + }) + gtest.C(t, func(t *gtest.T) { + var i *int + t.Assert(empty.IsNil(&i), false) + t.Assert(empty.IsNil(&i, true), true) + }) +} diff --git a/internal/fileinfo/fileinfo.go b/internal/fileinfo/fileinfo.go index 05747b126..d469e3ca7 100644 --- a/internal/fileinfo/fileinfo.go +++ b/internal/fileinfo/fileinfo.go @@ -1,4 +1,4 @@ -// Copyright 2019 gf Author(https://github.com/gogf/gf). All Rights Reserved. +// Copyright GoFrame Author(https://goframe.org). All Rights Reserved. // // This Source Code Form is subject to the terms of the MIT License. // If a copy of the MIT was not distributed with this file, diff --git a/internal/intlog/intlog.go b/internal/intlog/intlog.go index a001ab54e..f5f3559c9 100644 --- a/internal/intlog/intlog.go +++ b/internal/intlog/intlog.go @@ -1,4 +1,4 @@ -// Copyright 2019 gf Author(https://github.com/gogf/gf). All Rights Reserved. +// Copyright GoFrame Author(https://goframe.org). All Rights Reserved. // // This Source Code Form is subject to the terms of the MIT License. // If a copy of the MIT was not distributed with this file, @@ -8,15 +8,18 @@ package intlog import ( + "bytes" + "context" "fmt" "github.com/gogf/gf/debug/gdebug" - "github.com/gogf/gf/os/gcmd" + "github.com/gogf/gf/internal/utils" + "go.opentelemetry.io/otel/trace" "path/filepath" "time" ) const ( - gFILTER_KEY = "/internal/intlog" + stackFilterKey = "/internal/intlog" ) var ( @@ -25,65 +28,97 @@ var ( ) func init() { - // Debugging configured. - if !gcmd.GetWithEnv("GF_DEBUG").IsEmpty() { - isGFDebug = true - return - } + isGFDebug = utils.IsDebugEnabled() } // SetEnabled enables/disables the internal logging manually. // Note that this function is not concurrent safe, be aware of the DATA RACE. func SetEnabled(enabled bool) { - // If they're the same, it does not write the <isGFDebug> but only reading operation. + // 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. -func IsEnabled() bool { - return isGFDebug +// Print prints `v` with newline using fmt.Println. +// The parameter `v` can be multiple variables. +func Print(ctx context.Context, v ...interface{}) { + doPrint(ctx, fmt.Sprint(v...), false) } -// Print prints <v> with newline using fmt.Println. -// The parameter <v> can be multiple variables. -func Print(v ...interface{}) { +// Printf prints `v` with format `format` using fmt.Printf. +// The parameter `v` can be multiple variables. +func Printf(ctx context.Context, format string, v ...interface{}) { + doPrint(ctx, fmt.Sprintf(format, v...), false) +} + +// Error prints `v` with newline using fmt.Println. +// The parameter `v` can be multiple variables. +func Error(ctx context.Context, v ...interface{}) { + doPrint(ctx, fmt.Sprint(v...), true) +} + +// Errorf prints `v` with format `format` using fmt.Printf. +func Errorf(ctx context.Context, format string, v ...interface{}) { + doPrint(ctx, fmt.Sprintf(format, v...), true) +} + +// PrintFunc prints the output from function `f`. +// It only calls function `f` if debug mode is enabled. +func PrintFunc(ctx context.Context, f func() string) { if !isGFDebug { return } - fmt.Println(append([]interface{}{now(), "[INTE]", file()}, v...)...) + s := f() + if s == "" { + return + } + doPrint(ctx, s, false) } -// Printf prints <v> with format <format> using fmt.Printf. -// The parameter <v> can be multiple variables. -func Printf(format string, v ...interface{}) { +// ErrorFunc prints the output from function `f`. +// It only calls function `f` if debug mode is enabled. +func ErrorFunc(ctx context.Context, f func() string) { if !isGFDebug { return } - fmt.Printf(now()+" [INTE] "+file()+" "+format+"\n", v...) + s := f() + if s == "" { + return + } + doPrint(ctx, s, true) } -// Error prints <v> with newline using fmt.Println. -// The parameter <v> can be multiple variables. -func Error(v ...interface{}) { +func doPrint(ctx context.Context, content string, stack bool) { if !isGFDebug { return } - array := append([]interface{}{now(), "[INTE]", file()}, v...) - array = append(array, "\n"+gdebug.StackWithFilter(gFILTER_KEY)) - fmt.Println(array...) + buffer := bytes.NewBuffer(nil) + buffer.WriteString(now()) + buffer.WriteString(" [INTE] ") + buffer.WriteString(file()) + buffer.WriteString(" ") + if s := traceIdStr(ctx); s != "" { + buffer.WriteString(s + " ") + } + buffer.WriteString(content) + buffer.WriteString("\n") + if stack { + buffer.WriteString(gdebug.StackWithFilter(stackFilterKey)) + } + fmt.Print(buffer.String()) } -// Errorf prints <v> with format <format> using fmt.Printf. -func Errorf(format string, v ...interface{}) { - if !isGFDebug { - return +// traceIdStr retrieves and returns the trace id string for logging output. +func traceIdStr(ctx context.Context) string { + if ctx == nil { + return "" } - fmt.Printf( - now()+" [INTE] "+file()+" "+format+"\n%s\n", - append(v, gdebug.StackWithFilter(gFILTER_KEY))..., - ) + spanCtx := trace.SpanContextFromContext(ctx) + if traceId := spanCtx.TraceID(); traceId.IsValid() { + return "{" + traceId.String() + "}" + } + return "" } // now returns current time string. @@ -93,6 +128,6 @@ func now() string { // file returns caller file name along with its line number. func file() string { - _, p, l := gdebug.CallerWithFilter(gFILTER_KEY) + _, p, l := gdebug.CallerWithFilter(stackFilterKey) return fmt.Sprintf(`%s:%d`, filepath.Base(p), l) } diff --git a/internal/json/json.go b/internal/json/json.go index 589f7a2f3..ae6c38f13 100644 --- a/internal/json/json.go +++ b/internal/json/json.go @@ -1,4 +1,4 @@ -// Copyright 2020 gf Author(https://github.com/gogf/gf). All Rights Reserved. +// Copyright GoFrame Author(https://goframe.org). All Rights Reserved. // // This Source Code Form is subject to the terms of the MIT License. // If a copy of the MIT was not distributed with this file, @@ -8,6 +8,7 @@ package json import ( + "bytes" "encoding/json" "io" ) @@ -33,6 +34,13 @@ func Unmarshal(data []byte, v interface{}) error { return json.Unmarshal(data, v) } +// UnmarshalUseNumber decodes the json data bytes to target interface using number option. +func UnmarshalUseNumber(data []byte, v interface{}) error { + decoder := NewDecoder(bytes.NewReader(data)) + decoder.UseNumber() + return decoder.Decode(v) +} + // NewEncoder same as json.NewEncoder func NewEncoder(writer io.Writer) *json.Encoder { return json.NewEncoder(writer) diff --git a/internal/mutex/mutex.go b/internal/mutex/mutex.go index 50eb5b210..ccb32d037 100644 --- a/internal/mutex/mutex.go +++ b/internal/mutex/mutex.go @@ -1,4 +1,4 @@ -// Copyright 2019 gf Author(https://github.com/gogf/gf). All Rights Reserved. +// Copyright GoFrame Author(https://goframe.org). All Rights Reserved. // // This Source Code Form is subject to the terms of the MIT License. // If a copy of the MIT was not distributed with this file, @@ -16,7 +16,7 @@ type Mutex struct { } // New creates and returns a new *Mutex. -// The parameter <safe> is used to specify whether using this mutex in concurrent-safety, +// The parameter `safe` is used to specify whether using this mutex in concurrent-safety, // which is false in default. func New(safe ...bool) *Mutex { mu := new(Mutex) diff --git a/internal/mutex/mutex_z_bench_test.go b/internal/mutex/mutex_z_bench_test.go index f67f1d3d9..90e81199a 100644 --- a/internal/mutex/mutex_z_bench_test.go +++ b/internal/mutex/mutex_z_bench_test.go @@ -1,4 +1,4 @@ -// Copyright 2018 gf Author(https://github.com/gogf/gf). All Rights Reserved. +// Copyright GoFrame Author(https://goframe.org). All Rights Reserved. // // This Source Code Form is subject to the terms of the MIT License. // If a copy of the MIT was not distributed with this file, diff --git a/internal/mutex/mutex_z_unit_test.go b/internal/mutex/mutex_z_unit_test.go index 69c273804..3713d2b5a 100644 --- a/internal/mutex/mutex_z_unit_test.go +++ b/internal/mutex/mutex_z_unit_test.go @@ -1,4 +1,4 @@ -// Copyright 2018 gf Author(https://github.com/gogf/gf). All Rights Reserved. +// Copyright GoFrame Author(https://goframe.org). All Rights Reserved. // // This Source Code Form is subject to the terms of the MIT License. // If a copy of the MIT was not distributed with this file, diff --git a/internal/rwmutex/rwmutex.go b/internal/rwmutex/rwmutex.go index 6bd21e49e..0d16fb7d3 100644 --- a/internal/rwmutex/rwmutex.go +++ b/internal/rwmutex/rwmutex.go @@ -1,4 +1,4 @@ -// Copyright 2018 gf Author(https://github.com/gogf/gf). All Rights Reserved. +// Copyright GoFrame Author(https://goframe.org). All Rights Reserved. // // This Source Code Form is subject to the terms of the MIT License. // If a copy of the MIT was not distributed with this file, @@ -17,7 +17,7 @@ type RWMutex struct { } // New creates and returns a new *RWMutex. -// The parameter <safe> is used to specify whether using this mutex in concurrent safety, +// The parameter `safe` is used to specify whether using this mutex in concurrent safety, // which is false in default. func New(safe ...bool) *RWMutex { mu := Create(safe...) @@ -25,7 +25,7 @@ func New(safe ...bool) *RWMutex { } // Create creates and returns a new RWMutex object. -// The parameter <safe> is used to specify whether using this mutex in concurrent safety, +// The parameter `safe` is used to specify whether using this mutex in concurrent safety, // which is false in default. func Create(safe ...bool) RWMutex { mu := RWMutex{} diff --git a/internal/rwmutex/rwmutex_z_bench_test.go b/internal/rwmutex/rwmutex_z_bench_test.go index e743c2547..ec745f71e 100644 --- a/internal/rwmutex/rwmutex_z_bench_test.go +++ b/internal/rwmutex/rwmutex_z_bench_test.go @@ -1,4 +1,4 @@ -// Copyright 2018 gf Author(https://github.com/gogf/gf). All Rights Reserved. +// Copyright GoFrame Author(https://goframe.org). All Rights Reserved. // // This Source Code Form is subject to the terms of the MIT License. // If a copy of the MIT was not distributed with this file, diff --git a/internal/rwmutex/rwmutex_z_unit_test.go b/internal/rwmutex/rwmutex_z_unit_test.go index 7f44d4590..336855ccf 100644 --- a/internal/rwmutex/rwmutex_z_unit_test.go +++ b/internal/rwmutex/rwmutex_z_unit_test.go @@ -1,4 +1,4 @@ -// Copyright 2018 gf Author(https://github.com/gogf/gf). All Rights Reserved. +// Copyright GoFrame Author(https://goframe.org). All Rights Reserved. // // This Source Code Form is subject to the terms of the MIT License. // If a copy of the MIT was not distributed with this file, diff --git a/internal/structs/structs.go b/internal/structs/structs.go index ea7455b4a..d9dbfa83c 100644 --- a/internal/structs/structs.go +++ b/internal/structs/structs.go @@ -1,10 +1,10 @@ -// Copyright 2019 gf Author(https://github.com/gogf/gf). All Rights Reserved. +// Copyright GoFrame Author(https://goframe.org). All Rights Reserved. // // This Source Code Form is subject to the terms of the MIT License. // If a copy of the MIT was not distributed with this file, // You can obtain one at https://github.com/gogf/gf. -// Package structs provides functions for struct conversion. +// Package structs provides functions for struct information retrieving and struct conversion. // // Inspired and improved from: https://github.com/fatih/structs package structs @@ -13,38 +13,14 @@ import ( "reflect" ) +// Type wraps reflect.Type for additional features. +type Type struct { + reflect.Type +} + // Field contains information of a struct field . type Field struct { - value reflect.Value - field reflect.StructField - // Retrieved tag value. There might be more than one tags in the field, - // but only one can be retrieved according to calling function rules. - TagValue string -} - -// Tag returns the value associated with key in the tag string. If there is no -// such key in the tag, Tag returns the empty string. -func (f *Field) Tag(key string) string { - return f.field.Tag.Get(key) -} - -// Value returns the underlying value of the field. It panics if the field -// is not exported. -func (f *Field) Value() interface{} { - return f.value.Interface() -} - -// IsEmbedded returns true if the given field is an anonymous field (embedded) -func (f *Field) IsEmbedded() bool { - return f.field.Anonymous -} - -// IsExported returns true if the given field is exported. -func (f *Field) IsExported() bool { - return f.field.PkgPath == "" -} - -// Name returns the name of the given field -func (f *Field) Name() string { - return f.field.Name + Value reflect.Value // The underlying value of the field. + Field reflect.StructField // The underlying field of the field. + TagValue string // Retrieved tag value. There might be more than one tags in the field, but only one can be retrieved according to calling function rules. } diff --git a/internal/structs/structs_field.go b/internal/structs/structs_field.go new file mode 100644 index 000000000..f72bc83e6 --- /dev/null +++ b/internal/structs/structs_field.go @@ -0,0 +1,158 @@ +// Copyright GoFrame Author(https://goframe.org). All Rights Reserved. +// +// This Source Code Form is subject to the terms of the MIT License. +// If a copy of the MIT was not distributed with this file, +// You can obtain one at https://github.com/gogf/gf. + +package structs + +import "reflect" + +// Tag returns the value associated with key in the tag string. If there is no +// such key in the tag, Tag returns the empty string. +func (f *Field) Tag(key string) string { + return f.Field.Tag.Get(key) +} + +// TagLookup returns the value associated with key in the tag string. +// If the key is present in the tag the value (which may be empty) +// is returned. Otherwise, the returned value will be the empty string. +// The ok return value reports whether the value was explicitly set in +// the tag string. If the tag does not have the conventional format, +// the value returned by Lookup is unspecified. +func (f *Field) TagLookup(key string) (value string, ok bool) { + return f.Field.Tag.Lookup(key) +} + +// IsEmbedded returns true if the given field is an anonymous field (embedded) +func (f *Field) IsEmbedded() bool { + return f.Field.Anonymous +} + +// TagStr returns the tag string of the field. +func (f *Field) TagStr() string { + return string(f.Field.Tag) +} + +// IsExported returns true if the given field is exported. +func (f *Field) IsExported() bool { + return f.Field.PkgPath == "" +} + +// Name returns the name of the given field +func (f *Field) Name() string { + return f.Field.Name +} + +// Type returns the type of the given field +func (f *Field) Type() Type { + return Type{ + Type: f.Field.Type, + } +} + +// Kind returns the reflect.Kind for Value of Field `f`. +func (f *Field) Kind() reflect.Kind { + return f.Value.Kind() +} + +// OriginalKind retrieves and returns the original reflect.Kind for Value of Field `f`. +func (f *Field) OriginalKind() reflect.Kind { + var ( + kind = f.Value.Kind() + value = f.Value + ) + for kind == reflect.Ptr { + value = value.Elem() + kind = value.Kind() + } + return kind +} + +const ( + RecursiveOptionNone = 0 // No recursively retrieving fields as map if the field is an embedded struct. + RecursiveOptionEmbedded = 1 // Recursively retrieving fields as map if the field is an embedded struct. + RecursiveOptionEmbeddedNoTag = 2 // Recursively retrieving fields as map if the field is an embedded struct and the field has no tag. +) + +type FieldMapInput struct { + // Pointer should be type of struct/*struct. + Pointer interface{} + + // PriorityTagArray specifies the priority tag array for retrieving from high to low. + // If it's given `nil`, it returns map[name]*Field, of which the `name` is attribute name. + PriorityTagArray []string + + // RecursiveOption specifies the way retrieving the fields recursively if the attribute + // is an embedded struct. It is RecursiveOptionNone in default. + RecursiveOption int +} + +// FieldMap retrieves and returns struct field as map[name/tag]*Field from `pointer`. +// +// The parameter `pointer` should be type of struct/*struct. +// +// The parameter `priority` specifies the priority tag array for retrieving from high to low. +// If it's given `nil`, it returns map[name]*Field, of which the `name` is attribute name. +// +// The parameter `recursive` specifies the whether retrieving the fields recursively if the attribute +// is an embedded struct. +// +// Note that it only retrieves the exported attributes with first letter up-case from struct. +func FieldMap(input FieldMapInput) (map[string]*Field, error) { + fields, err := getFieldValues(input.Pointer) + if err != nil { + return nil, err + } + var ( + tagValue = "" + mapField = make(map[string]*Field) + ) + for _, field := range fields { + // Only retrieve exported attributes. + if !field.IsExported() { + continue + } + tagValue = "" + for _, p := range input.PriorityTagArray { + tagValue = field.Tag(p) + if tagValue != "" && tagValue != "-" { + break + } + } + tempField := field + tempField.TagValue = tagValue + if tagValue != "" { + mapField[tagValue] = tempField + } else { + if input.RecursiveOption != RecursiveOptionNone && field.IsEmbedded() { + switch input.RecursiveOption { + case RecursiveOptionEmbeddedNoTag: + if field.TagStr() != "" { + mapField[field.Name()] = tempField + break + } + fallthrough + case RecursiveOptionEmbedded: + m, err := FieldMap(FieldMapInput{ + Pointer: field.Value, + PriorityTagArray: input.PriorityTagArray, + RecursiveOption: input.RecursiveOption, + }) + if err != nil { + return nil, err + } + for k, v := range m { + if _, ok := mapField[k]; !ok { + tempV := v + mapField[k] = tempV + } + } + } + } else { + mapField[field.Name()] = tempField + } + } + } + return mapField, nil +} diff --git a/internal/structs/structs_map.go b/internal/structs/structs_map.go deleted file mode 100644 index 0585a04d9..000000000 --- a/internal/structs/structs_map.go +++ /dev/null @@ -1,59 +0,0 @@ -// 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 structs - -// MapField retrieves struct field as map[name/tag]*Field from <pointer>, and returns the map. -// -// The parameter <pointer> should be type of struct/*struct. -// -// The parameter <priority> specifies the priority tag array for retrieving from high to low. -// -// Note that it only retrieves the exported attributes with first letter up-case from struct. -func MapField(pointer interface{}, priority []string) (map[string]*Field, error) { - fields, err := getFieldValues(pointer) - if err != nil { - return nil, err - } - var ( - tagValue = "" - mapField = make(map[string]*Field) - ) - for _, field := range fields { - // Only retrieve exported attributes. - if !field.IsExported() { - continue - } - tagValue = "" - for _, p := range priority { - tagValue = field.Tag(p) - if tagValue != "" && tagValue != "-" { - break - } - } - tempField := field - tempField.TagValue = tagValue - if tagValue != "" { - mapField[tagValue] = tempField - } else { - if field.IsEmbedded() { - m, err := MapField(field.value, priority) - if err != nil { - return nil, err - } - for k, v := range m { - if _, ok := mapField[k]; !ok { - tempV := v - mapField[k] = tempV - } - } - } else { - mapField[field.Name()] = tempField - } - } - } - return mapField, nil -} diff --git a/internal/structs/structs_tag.go b/internal/structs/structs_tag.go index 6603c12b5..f7d8c85c7 100644 --- a/internal/structs/structs_tag.go +++ b/internal/structs/structs_tag.go @@ -1,4 +1,4 @@ -// Copyright 2019 gf Author(https://github.com/gogf/gf). All Rights Reserved. +// Copyright GoFrame Author(https://goframe.org). All Rights Reserved. // // This Source Code Form is subject to the terms of the MIT License. // If a copy of the MIT was not distributed with this file, @@ -9,20 +9,73 @@ package structs import ( "errors" "reflect" + "strconv" ) -// TagFields retrieves struct tags as []*Field from <pointer>, and returns it. +// ParseTag parses tag string into map. +func ParseTag(tag string) map[string]string { + var ( + key string + data = make(map[string]string) + ) + for tag != "" { + // Skip leading space. + i := 0 + for i < len(tag) && tag[i] == ' ' { + i++ + } + tag = tag[i:] + if tag == "" { + break + } + // Scan to colon. A space, a quote or a control character is a syntax error. + // Strictly speaking, control chars include the range [0x7f, 0x9f], not just + // [0x00, 0x1f], but in practice, we ignore the multi-byte control characters + // as it is simpler to inspect the tag's bytes than the tag's runes. + i = 0 + for i < len(tag) && tag[i] > ' ' && tag[i] != ':' && tag[i] != '"' && tag[i] != 0x7f { + i++ + } + if i == 0 || i+1 >= len(tag) || tag[i] != ':' || tag[i+1] != '"' { + break + } + key = tag[:i] + tag = tag[i+1:] + + // Scan quoted string to find value. + i = 1 + for i < len(tag) && tag[i] != '"' { + if tag[i] == '\\' { + i++ + } + i++ + } + if i >= len(tag) { + break + } + quotedValue := tag[:i+1] + tag = tag[i+1:] + value, err := strconv.Unquote(quotedValue) + if err != nil { + panic(err) + } + data[key] = value + } + return data +} + +// TagFields retrieves and returns struct tags as []*Field from `pointer`. // -// The parameter <pointer> should be type of struct/*struct. +// The parameter `pointer` should be type of struct/*struct. // // Note that it only retrieves the exported attributes with first letter up-case from struct. func TagFields(pointer interface{}, priority []string) ([]*Field, error) { return getFieldValuesByTagPriority(pointer, priority, map[string]struct{}{}) } -// TagMapName retrieves struct tags as map[tag]attribute from <pointer>, and returns it. +// TagMapName retrieves and returns struct tags as map[tag]attribute from `pointer`. // -// The parameter <pointer> should be type of struct/*struct. +// The parameter `pointer` should be type of struct/*struct. // // Note that it only retrieves the exported attributes with first letter up-case from struct. func TagMapName(pointer interface{}, priority []string) (map[string]string, error) { @@ -37,13 +90,12 @@ func TagMapName(pointer interface{}, priority []string) (map[string]string, erro return tagMap, nil } -// TagMapField retrieves struct tags as map[tag]*Field from <pointer>, and returns it. -// -// The parameter <pointer> should be type of struct/*struct. +// TagMapField retrieves struct tags as map[tag]*Field from `pointer`, and returns it. +// The parameter `object` should be either type of struct/*struct/[]struct/[]*struct. // // Note that it only retrieves the exported attributes with first letter up-case from struct. -func TagMapField(pointer interface{}, priority []string) (map[string]*Field, error) { - fields, err := TagFields(pointer, priority) +func TagMapField(object interface{}, priority []string) (map[string]*Field, error) { + fields, err := TagFields(object, priority) if err != nil { return nil, err } @@ -67,19 +119,32 @@ func getFieldValues(value interface{}) ([]*Field, error) { reflectValue = reflect.ValueOf(value) reflectKind = reflectValue.Kind() } - - for reflectKind == reflect.Ptr { - if !reflectValue.IsValid() || reflectValue.IsNil() { - // If pointer is type of *struct and nil, then automatically create a temporary struct. + for { + switch reflectKind { + case reflect.Ptr: + if !reflectValue.IsValid() || reflectValue.IsNil() { + // If pointer is type of *struct and nil, then automatically create a temporary struct. + reflectValue = reflect.New(reflectValue.Type().Elem()).Elem() + reflectKind = reflectValue.Kind() + } else { + reflectValue = reflectValue.Elem() + reflectKind = reflectValue.Kind() + } + case reflect.Array, reflect.Slice: reflectValue = reflect.New(reflectValue.Type().Elem()).Elem() reflectKind = reflectValue.Kind() - } else { - reflectValue = reflectValue.Elem() - reflectKind = reflectValue.Kind() + default: + goto exitLoop } } + +exitLoop: + for reflectKind == reflect.Ptr { + reflectValue = reflectValue.Elem() + reflectKind = reflectValue.Kind() + } if reflectKind != reflect.Struct { - return nil, errors.New("given value should be type of struct/*struct") + return nil, errors.New("given value should be either type of struct/*struct/[]struct/[]*struct") } var ( structType = reflectValue.Type() @@ -88,8 +153,8 @@ func getFieldValues(value interface{}) ([]*Field, error) { ) for i := 0; i < length; i++ { fields[i] = &Field{ - value: reflectValue.Field(i), - field: structType.Field(i), + Value: reflectValue.Field(i), + Field: structType.Field(i), } } return fields, nil @@ -127,7 +192,7 @@ func getFieldValuesByTagPriority(pointer interface{}, priority []string, tagMap } // If this is an embedded attribute, it retrieves the tags recursively. if field.IsEmbedded() { - if subTagFields, err := getFieldValuesByTagPriority(field.value, priority, tagMap); err != nil { + if subTagFields, err := getFieldValuesByTagPriority(field.Value, priority, tagMap); err != nil { return nil, err } else { tagFields = append(tagFields, subTagFields...) diff --git a/internal/structs/structs_type.go b/internal/structs/structs_type.go new file mode 100644 index 000000000..40cb02050 --- /dev/null +++ b/internal/structs/structs_type.go @@ -0,0 +1,81 @@ +// Copyright GoFrame Author(https://goframe.org). All Rights Reserved. +// +// This Source Code Form is subject to the terms of the MIT License. +// If a copy of the MIT was not distributed with this file, +// You can obtain one at https://github.com/gogf/gf. + +package structs + +import ( + "errors" + "fmt" + "reflect" +) + +// StructType retrieves and returns the struct Type of specified struct/*struct. +// The parameter `object` should be either type of struct/*struct/[]struct/[]*struct. +func StructType(object interface{}) (*Type, error) { + var ( + reflectValue reflect.Value + reflectKind reflect.Kind + reflectType reflect.Type + ) + if rv, ok := object.(reflect.Value); ok { + reflectValue = rv + } else { + reflectValue = reflect.ValueOf(object) + } + reflectKind = reflectValue.Kind() + for { + switch reflectKind { + case reflect.Ptr: + if !reflectValue.IsValid() || reflectValue.IsNil() { + // If pointer is type of *struct and nil, then automatically create a temporary struct. + reflectValue = reflect.New(reflectValue.Type().Elem()).Elem() + reflectKind = reflectValue.Kind() + } else { + reflectValue = reflectValue.Elem() + reflectKind = reflectValue.Kind() + } + + case reflect.Array, reflect.Slice: + reflectValue = reflect.New(reflectValue.Type().Elem()).Elem() + reflectKind = reflectValue.Kind() + + default: + goto exitLoop + } + } + +exitLoop: + for reflectKind == reflect.Ptr { + reflectValue = reflectValue.Elem() + reflectKind = reflectValue.Kind() + } + if reflectKind != reflect.Struct { + return nil, errors.New( + fmt.Sprintf( + `invalid object kind "%s", kind of "struct" is required`, + reflectKind, + ), + ) + } + reflectType = reflectValue.Type() + return &Type{ + Type: reflectType, + }, nil +} + +// Signature returns a unique string as this type. +func (t Type) Signature() string { + return t.PkgPath() + "/" + t.String() +} + +// FieldKeys returns the keys of current struct/map. +func (t Type) FieldKeys() []string { + keys := make([]string, t.NumField()) + for i := 0; i < t.NumField(); i++ { + keys[i] = t.Field(i).Name + } + return keys +} diff --git a/internal/structs/structs_z_bench_test.go b/internal/structs/structs_z_bench_test.go index 6cb7fc909..afdf7ad4b 100644 --- a/internal/structs/structs_z_bench_test.go +++ b/internal/structs/structs_z_bench_test.go @@ -1,4 +1,4 @@ -// Copyright 2020 gf Author(https://github.com/gogf/gf). All Rights Reserved. +// Copyright GoFrame Author(https://goframe.org). All Rights Reserved. // // This Source Code Form is subject to the terms of the MIT License. // If a copy of the MIT was not distributed with this file, diff --git a/internal/structs/structs_z_unit_test.go b/internal/structs/structs_z_unit_test.go index f3bdba4de..825d095bb 100644 --- a/internal/structs/structs_z_unit_test.go +++ b/internal/structs/structs_z_unit_test.go @@ -1,4 +1,4 @@ -// Copyright 2019 gf Author(https://github.com/gogf/gf). All Rights Reserved. +// Copyright GoFrame Author(https://goframe.org). All Rights Reserved. // // This Source Code Form is subject to the terms of the MIT License. // If a copy of the MIT was not distributed with this file, @@ -102,7 +102,7 @@ func Test_StructOfNilPointer(t *testing.T) { }) } -func Test_MapField(t *testing.T) { +func Test_FieldMap(t *testing.T) { gtest.C(t, func(t *gtest.T) { type User struct { Id int @@ -110,7 +110,11 @@ func Test_MapField(t *testing.T) { Pass string `my-tag1:"pass1" my-tag2:"pass2" params:"pass"` } var user *User - m, _ := structs.MapField(user, []string{"params"}) + m, _ := structs.FieldMap(structs.FieldMapInput{ + Pointer: user, + PriorityTagArray: []string{"params"}, + RecursiveOption: structs.RecursiveOptionEmbedded, + }) t.Assert(len(m), 3) _, ok := m["Id"] t.Assert(ok, true) @@ -123,4 +127,127 @@ func Test_MapField(t *testing.T) { _, ok = m["pass"] t.Assert(ok, true) }) + gtest.C(t, func(t *gtest.T) { + type User struct { + Id int + Name string `params:"name"` + Pass string `my-tag1:"pass1" my-tag2:"pass2" params:"pass"` + } + var user *User + m, _ := structs.FieldMap(structs.FieldMapInput{ + Pointer: user, + PriorityTagArray: nil, + RecursiveOption: structs.RecursiveOptionEmbedded, + }) + t.Assert(len(m), 3) + _, ok := m["Id"] + t.Assert(ok, true) + _, ok = m["Name"] + t.Assert(ok, true) + _, ok = m["name"] + t.Assert(ok, false) + _, ok = m["Pass"] + t.Assert(ok, true) + _, ok = m["pass"] + t.Assert(ok, false) + }) +} + +func Test_StructType(t *testing.T) { + gtest.C(t, func(t *gtest.T) { + type B struct { + Name string + } + type A struct { + B + } + r, err := structs.StructType(new(A)) + t.AssertNil(err) + t.Assert(r.Signature(), `github.com/gogf/gf/internal/structs_test/structs_test.A`) + }) + gtest.C(t, func(t *gtest.T) { + type B struct { + Name string + } + type A struct { + B + } + r, err := structs.StructType(new(A).B) + t.AssertNil(err) + t.Assert(r.Signature(), `github.com/gogf/gf/internal/structs_test/structs_test.B`) + }) + gtest.C(t, func(t *gtest.T) { + type B struct { + Name string + } + type A struct { + *B + } + r, err := structs.StructType(new(A).B) + t.AssertNil(err) + t.Assert(r.String(), `structs_test.B`) + }) + // Error. + gtest.C(t, func(t *gtest.T) { + type B struct { + Name string + } + type A struct { + *B + Id int + } + _, err := structs.StructType(new(A).Id) + t.AssertNE(err, nil) + }) +} + +func Test_StructTypeBySlice(t *testing.T) { + gtest.C(t, func(t *gtest.T) { + type B struct { + Name string + } + type A struct { + Array []*B + } + r, err := structs.StructType(new(A).Array) + t.AssertNil(err) + t.Assert(r.Signature(), `github.com/gogf/gf/internal/structs_test/structs_test.B`) + }) + gtest.C(t, func(t *gtest.T) { + type B struct { + Name string + } + type A struct { + Array []B + } + r, err := structs.StructType(new(A).Array) + t.AssertNil(err) + t.Assert(r.Signature(), `github.com/gogf/gf/internal/structs_test/structs_test.B`) + }) + gtest.C(t, func(t *gtest.T) { + type B struct { + Name string + } + type A struct { + Array *[]B + } + r, err := structs.StructType(new(A).Array) + t.AssertNil(err) + t.Assert(r.Signature(), `github.com/gogf/gf/internal/structs_test/structs_test.B`) + }) +} + +func TestType_FieldKeys(t *testing.T) { + gtest.C(t, func(t *gtest.T) { + type B struct { + Id int + Name string + } + type A struct { + Array []*B + } + r, err := structs.StructType(new(A).Array) + t.AssertNil(err) + t.Assert(r.FieldKeys(), g.Slice{"Id", "Name"}) + }) } diff --git a/internal/utils/utils.go b/internal/utils/utils.go index 05d3fa6af..414a90ca0 100644 --- a/internal/utils/utils.go +++ b/internal/utils/utils.go @@ -1,4 +1,4 @@ -// Copyright 2019 gf Author(https://github.com/gogf/gf). All Rights Reserved. +// Copyright GoFrame Author(https://goframe.org). All Rights Reserved. // // This Source Code Form is subject to the terms of the MIT License. // If a copy of the MIT was not distributed with this file, diff --git a/internal/utils/utils_array.go b/internal/utils/utils_array.go index eae9b96b6..b96e039e6 100644 --- a/internal/utils/utils_array.go +++ b/internal/utils/utils_array.go @@ -1,4 +1,4 @@ -// Copyright 2019 gf Author(https://github.com/gogf/gf). All Rights Reserved. +// Copyright GoFrame Author(https://goframe.org). All Rights Reserved. // // This Source Code Form is subject to the terms of the MIT License. // If a copy of the MIT was not distributed with this file, diff --git a/internal/utils/utils_debug.go b/internal/utils/utils_debug.go new file mode 100644 index 000000000..7817aecf9 --- /dev/null +++ b/internal/utils/utils_debug.go @@ -0,0 +1,37 @@ +// Copyright GoFrame Author(https://goframe.org). All Rights Reserved. +// +// This Source Code Form is subject to the terms of the MIT License. +// If a copy of the MIT was not distributed with this file, +// You can obtain one at https://github.com/gogf/gf. + +package utils + +import ( + "github.com/gogf/gf/internal/command" +) + +const ( + commandEnvKeyForDebugKey = "gf.debug" // Debug key for checking if in debug mode. + StackFilterKeyForGoFrame = "github.com/gogf/gf@" // Stack filtering key for all GoFrame module paths. +) + +var ( + // isDebugEnabled marks whether GoFrame debug mode is enabled. + isDebugEnabled = false +) + +func init() { + // Debugging configured. + value := command.GetOptWithEnv(commandEnvKeyForDebugKey) + if value == "" || value == "0" || value == "false" { + isDebugEnabled = false + } else { + isDebugEnabled = true + } +} + +// IsDebugEnabled checks and returns whether debug mode is enabled. +// The debug mode is enabled when command argument "gf.debug" or environment "GF_DEBUG" is passed. +func IsDebugEnabled() bool { + return isDebugEnabled +} diff --git a/internal/utils/utils_io.go b/internal/utils/utils_io.go index 45dc34470..9da382d4e 100644 --- a/internal/utils/utils_io.go +++ b/internal/utils/utils_io.go @@ -1,4 +1,4 @@ -// Copyright 2020 gf Author(https://github.com/gogf/gf). All Rights Reserved. +// Copyright GoFrame Author(https://goframe.org). All Rights Reserved. // // This Source Code Form is subject to the terms of the MIT License. // If a copy of the MIT was not distributed with this file, @@ -21,7 +21,7 @@ type ReadCloser struct { repeatable bool } -// NewRepeatReadCloser creates and returns a RepeatReadCloser object. +// NewReadCloser creates and returns a RepeatReadCloser object. func NewReadCloser(content []byte, repeatable bool) io.ReadCloser { return &ReadCloser{ content: content, @@ -29,7 +29,7 @@ func NewReadCloser(content []byte, repeatable bool) io.ReadCloser { } } -// NewRepeatReadCloserWithReadCloser creates and returns a RepeatReadCloser object +// NewReadCloserWithReadCloser creates and returns a RepeatReadCloser object // with given io.ReadCloser. func NewReadCloserWithReadCloser(r io.ReadCloser, repeatable bool) (io.ReadCloser, error) { content, err := ioutil.ReadAll(r) diff --git a/internal/utils/utils_is.go b/internal/utils/utils_is.go new file mode 100644 index 000000000..7e5757a8d --- /dev/null +++ b/internal/utils/utils_is.go @@ -0,0 +1,98 @@ +// Copyright GoFrame Author(https://goframe.org). All Rights Reserved. +// +// This Source Code Form is subject to the terms of the MIT License. +// If a copy of the MIT was not distributed with this file, +// You can obtain one at https://github.com/gogf/gf. + +package utils + +import ( + "github.com/gogf/gf/internal/empty" + "reflect" +) + +// IsNil checks whether `value` is nil. +func IsNil(value interface{}) bool { + return value == nil +} + +// IsEmpty checks whether `value` is empty. +func IsEmpty(value interface{}) bool { + return empty.IsEmpty(value) +} + +// IsInt checks whether `value` is type of int. +func IsInt(value interface{}) bool { + switch value.(type) { + case int, *int, int8, *int8, int16, *int16, int32, *int32, int64, *int64: + return true + } + return false +} + +// IsUint checks whether `value` is type of uint. +func IsUint(value interface{}) bool { + switch value.(type) { + case uint, *uint, uint8, *uint8, uint16, *uint16, uint32, *uint32, uint64, *uint64: + return true + } + return false +} + +// IsFloat checks whether `value` is type of float. +func IsFloat(value interface{}) bool { + switch value.(type) { + case float32, *float32, float64, *float64: + return true + } + return false +} + +// IsSlice checks whether `value` is type of slice. +func IsSlice(value interface{}) bool { + var ( + reflectValue = reflect.ValueOf(value) + reflectKind = reflectValue.Kind() + ) + for reflectKind == reflect.Ptr { + reflectValue = reflectValue.Elem() + } + switch reflectKind { + case reflect.Slice, reflect.Array: + return true + } + return false +} + +// IsMap checks whether `value` is type of map. +func IsMap(value interface{}) bool { + var ( + reflectValue = reflect.ValueOf(value) + reflectKind = reflectValue.Kind() + ) + for reflectKind == reflect.Ptr { + reflectValue = reflectValue.Elem() + } + switch reflectKind { + case reflect.Map: + return true + } + return false +} + +// IsStruct checks whether `value` is type of struct. +func IsStruct(value interface{}) bool { + var ( + reflectValue = reflect.ValueOf(value) + reflectKind = reflectValue.Kind() + ) + for reflectKind == reflect.Ptr { + reflectValue = reflectValue.Elem() + reflectKind = reflectValue.Kind() + } + switch reflectKind { + case reflect.Struct: + return true + } + return false +} diff --git a/internal/utils/utils_str.go b/internal/utils/utils_str.go index 1f2ca6ce1..01688e9da 100644 --- a/internal/utils/utils_str.go +++ b/internal/utils/utils_str.go @@ -1,4 +1,4 @@ -// Copyright 2019 gf Author(https://github.com/gogf/gf). All Rights Reserved. +// Copyright GoFrame Author(https://goframe.org). All Rights Reserved. // // This Source Code Form is subject to the terms of the MIT License. // If a copy of the MIT was not distributed with this file, @@ -10,6 +10,21 @@ import ( "strings" ) +var ( + // DefaultTrimChars are the characters which are stripped by Trim* functions in default. + DefaultTrimChars = string([]byte{ + '\t', // Tab. + '\v', // Vertical tab. + '\n', // New line (line feed). + '\r', // Carriage return. + '\f', // New page. + ' ', // Ordinary space. + 0x00, // NUL-byte. + 0x85, // Delete. + 0xA0, // Non-breaking space. + }) +) + // IsLetterUpper checks whether the given byte b is in upper case. func IsLetterUpper(b byte) bool { if b >= byte('A') && b <= byte('Z') { @@ -67,7 +82,7 @@ func UcFirst(s string) string { return s } -// ReplaceByMap returns a copy of <origin>, +// ReplaceByMap returns a copy of `origin`, // which is replaced by a map in unordered way, case-sensitively. func ReplaceByMap(origin string, replaces map[string]string) string { for k, v := range replaces { @@ -87,8 +102,32 @@ func RemoveSymbols(s string) string { return string(b) } -// EqualFoldWithoutChars checks string <s1> and <s2> equal case-insensitively, +// EqualFoldWithoutChars checks string `s1` and `s2` equal case-insensitively, // with/without chars '-'/'_'/'.'/' '. func EqualFoldWithoutChars(s1, s2 string) bool { return strings.EqualFold(RemoveSymbols(s1), RemoveSymbols(s2)) } + +// SplitAndTrim splits string <str> by a string <delimiter> to an array, +// and calls Trim to every element of this array. It ignores the elements +// which are empty after Trim. +func SplitAndTrim(str, delimiter string, characterMask ...string) []string { + array := make([]string, 0) + for _, v := range strings.Split(str, delimiter) { + v = Trim(v, characterMask...) + if v != "" { + array = append(array, v) + } + } + return array +} + +// Trim strips whitespace (or other characters) from the beginning and end of a string. +// The optional parameter <characterMask> specifies the additional stripped characters. +func Trim(str string, characterMask ...string) string { + trimChars := DefaultTrimChars + if len(characterMask) > 0 { + trimChars += characterMask[0] + } + return strings.Trim(str, trimChars) +} diff --git a/internal/utils/utils_z_bench_test.go b/internal/utils/utils_z_bench_test.go index 13320b73a..586a642fb 100644 --- a/internal/utils/utils_z_bench_test.go +++ b/internal/utils/utils_z_bench_test.go @@ -1,4 +1,4 @@ -// Copyright 2020 gf Author(https://github.com/gogf/gf). All Rights Reserved. +// Copyright GoFrame Author(https://goframe.org). All Rights Reserved. // // This Source Code Form is subject to the terms of the MIT License. // If a copy of the MIT was not distributed with this file, diff --git a/internal/utils/utils_z_is_test.go b/internal/utils/utils_z_is_test.go new file mode 100644 index 000000000..77df365c4 --- /dev/null +++ b/internal/utils/utils_z_is_test.go @@ -0,0 +1,171 @@ +// Copyright GoFrame Author(https://goframe.org). All Rights Reserved. +// +// This Source Code Form is subject to the terms of the MIT License. +// If a copy of the MIT was not distributed with this file, +// You can obtain one at https://github.com/gogf/gf. + +package utils_test + +import ( + "github.com/gogf/gf/frame/g" + "github.com/gogf/gf/internal/utils" + "github.com/gogf/gf/test/gtest" + "testing" +) + +func TestVar_IsNil(t *testing.T) { + gtest.C(t, func(t *gtest.T) { + t.Assert(utils.IsNil(0), false) + t.Assert(utils.IsNil(nil), true) + t.Assert(utils.IsNil(g.Map{}), false) + t.Assert(utils.IsNil(g.Slice{}), false) + }) + gtest.C(t, func(t *gtest.T) { + t.Assert(utils.IsNil(1), false) + t.Assert(utils.IsNil(0.1), false) + t.Assert(utils.IsNil(g.Map{"k": "v"}), false) + t.Assert(utils.IsNil(g.Slice{0}), false) + }) +} + +func TestVar_IsEmpty(t *testing.T) { + gtest.C(t, func(t *gtest.T) { + t.Assert(utils.IsEmpty(0), true) + t.Assert(utils.IsEmpty(nil), true) + t.Assert(utils.IsEmpty(g.Map{}), true) + t.Assert(utils.IsEmpty(g.Slice{}), true) + }) + gtest.C(t, func(t *gtest.T) { + t.Assert(utils.IsEmpty(1), false) + t.Assert(utils.IsEmpty(0.1), false) + t.Assert(utils.IsEmpty(g.Map{"k": "v"}), false) + t.Assert(utils.IsEmpty(g.Slice{0}), false) + }) +} + +func TestVar_IsInt(t *testing.T) { + gtest.C(t, func(t *gtest.T) { + t.Assert(utils.IsInt(0), true) + t.Assert(utils.IsInt(nil), false) + t.Assert(utils.IsInt(g.Map{}), false) + t.Assert(utils.IsInt(g.Slice{}), false) + }) + gtest.C(t, func(t *gtest.T) { + t.Assert(utils.IsInt(1), true) + t.Assert(utils.IsInt(-1), true) + t.Assert(utils.IsInt(0.1), false) + t.Assert(utils.IsInt(g.Map{"k": "v"}), false) + t.Assert(utils.IsInt(g.Slice{0}), false) + }) + gtest.C(t, func(t *gtest.T) { + t.Assert(utils.IsInt(int8(1)), true) + t.Assert(utils.IsInt(uint8(1)), false) + }) +} + +func TestVar_IsUint(t *testing.T) { + gtest.C(t, func(t *gtest.T) { + t.Assert(utils.IsUint(0), false) + t.Assert(utils.IsUint(nil), false) + t.Assert(utils.IsUint(g.Map{}), false) + t.Assert(utils.IsUint(g.Slice{}), false) + }) + gtest.C(t, func(t *gtest.T) { + t.Assert(utils.IsUint(1), false) + t.Assert(utils.IsUint(-1), false) + t.Assert(utils.IsUint(0.1), false) + t.Assert(utils.IsUint(g.Map{"k": "v"}), false) + t.Assert(utils.IsUint(g.Slice{0}), false) + }) + gtest.C(t, func(t *gtest.T) { + t.Assert(utils.IsUint(int8(1)), false) + t.Assert(utils.IsUint(uint8(1)), true) + }) +} + +func TestVar_IsFloat(t *testing.T) { + gtest.C(t, func(t *gtest.T) { + t.Assert(utils.IsFloat(0), false) + t.Assert(utils.IsFloat(nil), false) + t.Assert(utils.IsFloat(g.Map{}), false) + t.Assert(utils.IsFloat(g.Slice{}), false) + }) + gtest.C(t, func(t *gtest.T) { + t.Assert(utils.IsFloat(1), false) + t.Assert(utils.IsFloat(-1), false) + t.Assert(utils.IsFloat(0.1), true) + t.Assert(utils.IsFloat(float64(1)), true) + t.Assert(utils.IsFloat(g.Map{"k": "v"}), false) + t.Assert(utils.IsFloat(g.Slice{0}), false) + }) + gtest.C(t, func(t *gtest.T) { + t.Assert(utils.IsFloat(int8(1)), false) + t.Assert(utils.IsFloat(uint8(1)), false) + }) +} + +func TestVar_IsSlice(t *testing.T) { + gtest.C(t, func(t *gtest.T) { + t.Assert(utils.IsSlice(0), false) + t.Assert(utils.IsSlice(nil), false) + t.Assert(utils.IsSlice(g.Map{}), false) + t.Assert(utils.IsSlice(g.Slice{}), true) + }) + gtest.C(t, func(t *gtest.T) { + t.Assert(utils.IsSlice(1), false) + t.Assert(utils.IsSlice(-1), false) + t.Assert(utils.IsSlice(0.1), false) + t.Assert(utils.IsSlice(float64(1)), false) + t.Assert(utils.IsSlice(g.Map{"k": "v"}), false) + t.Assert(utils.IsSlice(g.Slice{0}), true) + }) + gtest.C(t, func(t *gtest.T) { + t.Assert(utils.IsSlice(int8(1)), false) + t.Assert(utils.IsSlice(uint8(1)), false) + }) +} + +func TestVar_IsMap(t *testing.T) { + gtest.C(t, func(t *gtest.T) { + t.Assert(utils.IsMap(0), false) + t.Assert(utils.IsMap(nil), false) + t.Assert(utils.IsMap(g.Map{}), true) + t.Assert(utils.IsMap(g.Slice{}), false) + }) + gtest.C(t, func(t *gtest.T) { + t.Assert(utils.IsMap(1), false) + t.Assert(utils.IsMap(-1), false) + t.Assert(utils.IsMap(0.1), false) + t.Assert(utils.IsMap(float64(1)), false) + t.Assert(utils.IsMap(g.Map{"k": "v"}), true) + t.Assert(utils.IsMap(g.Slice{0}), false) + }) + gtest.C(t, func(t *gtest.T) { + t.Assert(utils.IsMap(int8(1)), false) + t.Assert(utils.IsMap(uint8(1)), false) + }) +} + +func TestVar_IsStruct(t *testing.T) { + gtest.C(t, func(t *gtest.T) { + t.Assert(utils.IsStruct(0), false) + t.Assert(utils.IsStruct(nil), false) + t.Assert(utils.IsStruct(g.Map{}), false) + t.Assert(utils.IsStruct(g.Slice{}), false) + }) + gtest.C(t, func(t *gtest.T) { + t.Assert(utils.IsStruct(1), false) + t.Assert(utils.IsStruct(-1), false) + t.Assert(utils.IsStruct(0.1), false) + t.Assert(utils.IsStruct(float64(1)), false) + t.Assert(utils.IsStruct(g.Map{"k": "v"}), false) + t.Assert(utils.IsStruct(g.Slice{0}), false) + }) + gtest.C(t, func(t *gtest.T) { + a := &struct { + }{} + t.Assert(utils.IsStruct(a), true) + t.Assert(utils.IsStruct(*a), true) + t.Assert(utils.IsStruct(&a), true) + }) +} diff --git a/internal/utils/utils_z_test.go b/internal/utils/utils_z_test.go index a26e86e7a..12a946f80 100644 --- a/internal/utils/utils_z_test.go +++ b/internal/utils/utils_z_test.go @@ -1,4 +1,4 @@ -// Copyright 2020 gf Author(https://github.com/gogf/gf). All Rights Reserved. +// Copyright GoFrame Author(https://goframe.org). All Rights Reserved. // // This Source Code Form is subject to the terms of the MIT License. // If a copy of the MIT was not distributed with this file, diff --git a/net/ghttp/ghttp.go b/net/ghttp/ghttp.go index 2fdfe53c4..f4f55c34b 100644 --- a/net/ghttp/ghttp.go +++ b/net/ghttp/ghttp.go @@ -1,4 +1,4 @@ -// Copyright GoFrame Author(https://github.com/gogf/gf). All Rights Reserved. +// Copyright GoFrame Author(https://goframe.org). All Rights Reserved. // // This Source Code Form is subject to the terms of the MIT License. // If a copy of the MIT was not distributed with this file, @@ -19,16 +19,16 @@ import ( ) type ( - // Server wraps the http.Server and provides more feature. + // Server wraps the http.Server and provides more rich features. Server struct { name string // Unique name for instance management. config ServerConfig // Configuration. - plugins []Plugin // Plugin array. + plugins []Plugin // Plugin array to extend server functionality. servers []*gracefulServer // Underlying http.Server array. serverCount *gtype.Int // Underlying http.Server count. closeChan chan struct{} // Used for underlying server closing event notification. serveTree map[string]interface{} // The route map tree. - serveCache *gcache.Cache // Server cache for internal usage. + serveCache *gcache.Cache // Server caches for internal usage. routesMap map[string][]registeredRouteItem // Route map mainly for route dumps and repeated route checks. statusHandlerMap map[string][]HandlerFunc // Custom status handler map. sessionManager *gsession.Manager // Session manager. @@ -44,7 +44,7 @@ type ( Priority int // Just for reference. } - // Router item just for route dumps. + // RouterItem is just for route dumps. RouterItem struct { Server string // Server name. Address string // Listening address. @@ -58,38 +58,41 @@ type ( handler *handlerItem // The handler. } + // HandlerFunc is request handler function. + HandlerFunc = func(r *Request) + + // handlerFuncInfo contains the HandlerFunc address and its reflect type. + handlerFuncInfo struct { + Func HandlerFunc // Handler function address. + Type reflect.Type // Reflect type information for current handler, which is used for extension of handler feature. + Value reflect.Value // Reflect value information for current handler, which is used for extension of handler feature. + } + // handlerItem is the registered handler for route handling, // including middleware and hook functions. handlerItem struct { - itemId int // Unique handler item id mark. - itemName string // Handler name, which is automatically retrieved from runtime stack when registered. - itemType int // Handler type: object/handler/controller/middleware/hook. - itemFunc HandlerFunc // Handler address. - initFunc HandlerFunc // Initialization function when request enters the object(only available for object register type). - shutFunc HandlerFunc // Shutdown function when request leaves out the object(only available for object register type). - middleware []HandlerFunc // Bound middleware array. - ctrlInfo *handlerController // Controller information for reflect usage. - hookName string // Hook type name. - router *Router // Router object. - source string // Source file path:line when registering. + Id int // Unique handler item id mark. + Name string // Handler name, which is automatically retrieved from runtime stack when registered. + Type int // Handler type: object/handler/controller/middleware/hook. + Info handlerFuncInfo // Handler function information. + InitFunc HandlerFunc // Initialization function when request enters the object (only available for object register type). + ShutFunc HandlerFunc // Shutdown function when request leaves out the object (only available for object register type). + Middleware []HandlerFunc // Bound middleware array. + HookName string // Hook type name, only available for hook type. + Router *Router // Router object. + Source string // Registering source file `path:line`. } // handlerParsedItem is the item parsed from URL.Path. handlerParsedItem struct { - handler *handlerItem // Handler information. - values map[string]string // Router values parsed from URL.Path. - } - - // handlerController is the controller information used for reflect. - handlerController struct { - name string // Handler method name. - reflect reflect.Type // Reflect type of the controller. + Handler *handlerItem // Handler information. + Values map[string]string // Router values parsed from URL.Path. } // registeredRouteItem stores the information of the router and is used for route map. registeredRouteItem struct { - source string // Source file path and its line number. - handler *handlerItem // Handler object. + Source string // Source file path and its line number. + Handler *handlerItem // Handler object. } // errorStack is the interface for Stack feature. @@ -98,26 +101,19 @@ type ( Stack() string } - // Request handler function. - HandlerFunc = func(r *Request) - // Listening file descriptor mapping. // The key is either "http" or "https" and the value is its FD. listenerFdMap = map[string]string ) const ( - HOOK_BEFORE_SERVE = "HOOK_BEFORE_SERVE" // Deprecated, use HookBeforeServe instead. - HOOK_AFTER_SERVE = "HOOK_AFTER_SERVE" // Deprecated, use HookAfterServe instead. - HOOK_BEFORE_OUTPUT = "HOOK_BEFORE_OUTPUT" // Deprecated, use HookBeforeOutput instead. - HOOK_AFTER_OUTPUT = "HOOK_AFTER_OUTPUT" // Deprecated, use HookAfterOutput instead. HookBeforeServe = "HOOK_BEFORE_SERVE" HookAfterServe = "HOOK_AFTER_SERVE" HookBeforeOutput = "HOOK_BEFORE_OUTPUT" HookAfterOutput = "HOOK_AFTER_OUTPUT" ServerStatusStopped = 0 ServerStatusRunning = 1 - SupportedHttpMethods = "GET,PUT,POST,DELETE,PATCH,HEAD,CONNECT,OPTIONS,TRACE" + supportedHttpMethods = "GET,PUT,POST,DELETE,PATCH,HEAD,CONNECT,OPTIONS,TRACE" defaultServerName = "default" defaultDomainName = "default" defaultMethod = "ALL" @@ -130,6 +126,10 @@ const ( exceptionExitAll = "exit_all" exceptionExitHook = "exit_hook" routeCacheDuration = time.Hour + methodNameInit = "Init" + methodNameShut = "Shut" + methodNameExit = "Exit" + ctxKeyForRequest = "gHttpRequestObject" ) var ( @@ -142,7 +142,7 @@ var ( serverMapping = gmap.NewStrAnyMap(true) // serverRunning marks the running server count. - // If there no successful server running or all servers shutdown, this value is 0. + // If there is no successful server running or all servers' shutdown, this value is 0. serverRunning = gtype.NewInt() // wsUpGrader is the default up-grader configuration for websocket. diff --git a/net/ghttp/ghttp_client_api.go b/net/ghttp/ghttp_client.go similarity index 74% rename from net/ghttp/ghttp_client_api.go rename to net/ghttp/ghttp_client.go index 591d47064..949754bf6 100644 --- a/net/ghttp/ghttp_client_api.go +++ b/net/ghttp/ghttp_client.go @@ -1,4 +1,4 @@ -// Copyright 2017 gf Author(https://github.com/gogf/gf). All Rights Reserved. +// Copyright GoFrame Author(https://goframe.org). All Rights Reserved. // // This Source Code Form is subject to the terms of the MIT License. // If a copy of the MIT was not distributed with this file, @@ -6,238 +6,291 @@ package ghttp -import "github.com/gogf/gf/container/gvar" +import ( + "github.com/gogf/gf/container/gvar" + "github.com/gogf/gf/net/ghttp/internal/client" +) + +type ( + Client = client.Client + ClientResponse = client.Response + ClientHandlerFunc = client.HandlerFunc +) + +// NewClient creates and returns a new HTTP client object. +func NewClient() *Client { + return client.New() +} // Get is a convenience method for sending GET request. // NOTE that remembers CLOSING the response object when it'll never be used. +// Deprecated, please use g.Client().Get or NewClient().Get instead. func Get(url string, data ...interface{}) (*ClientResponse, error) { return DoRequest("GET", url, data...) } // Put is a convenience method for sending PUT request. // NOTE that remembers CLOSING the response object when it'll never be used. +// Deprecated, please use g.Client().Put or NewClient().Put instead. func Put(url string, data ...interface{}) (*ClientResponse, error) { return DoRequest("PUT", url, data...) } // Post is a convenience method for sending POST request. // NOTE that remembers CLOSING the response object when it'll never be used. +// Deprecated, please use g.Client().Post or NewClient().Post instead. func Post(url string, data ...interface{}) (*ClientResponse, error) { return DoRequest("POST", url, data...) } // Delete is a convenience method for sending DELETE request. // NOTE that remembers CLOSING the response object when it'll never be used. +// Deprecated, please use g.Client().Delete or NewClient().Delete instead. func Delete(url string, data ...interface{}) (*ClientResponse, error) { return DoRequest("DELETE", url, data...) } // Head is a convenience method for sending HEAD request. // NOTE that remembers CLOSING the response object when it'll never be used. +// Deprecated, please use g.Client().Head or NewClient().Head instead. func Head(url string, data ...interface{}) (*ClientResponse, error) { return DoRequest("HEAD", url, data...) } // Patch is a convenience method for sending PATCH request. // NOTE that remembers CLOSING the response object when it'll never be used. +// Deprecated, please use g.Client().Patch or NewClient().Patch instead. func Patch(url string, data ...interface{}) (*ClientResponse, error) { return DoRequest("PATCH", url, data...) } // Connect is a convenience method for sending CONNECT request. // NOTE that remembers CLOSING the response object when it'll never be used. +// Deprecated, please use g.Client().Connect or NewClient().Connect instead. func Connect(url string, data ...interface{}) (*ClientResponse, error) { return DoRequest("CONNECT", url, data...) } // Options is a convenience method for sending OPTIONS request. // NOTE that remembers CLOSING the response object when it'll never be used. +// Deprecated, please use g.Client().Options or NewClient().Options instead. func Options(url string, data ...interface{}) (*ClientResponse, error) { return DoRequest("OPTIONS", url, data...) } // Trace is a convenience method for sending TRACE request. // NOTE that remembers CLOSING the response object when it'll never be used. +// Deprecated, please use g.Client().Trace or NewClient().Trace instead. func Trace(url string, data ...interface{}) (*ClientResponse, error) { return DoRequest("TRACE", url, data...) } // DoRequest is a convenience method for sending custom http method request. // NOTE that remembers CLOSING the response object when it'll never be used. +// Deprecated, please use g.Client().DoRequest or NewClient().DoRequest instead. func DoRequest(method, url string, data ...interface{}) (*ClientResponse, error) { - return NewClient().DoRequest(method, url, data...) + return client.New().DoRequest(method, url, data...) } // GetContent is a convenience method for sending GET request, which retrieves and returns // the result content and automatically closes response object. +// Deprecated, please use g.Client().GetContent or NewClient().GetContent instead. func GetContent(url string, data ...interface{}) string { return RequestContent("GET", url, data...) } // PutContent is a convenience method for sending PUT request, which retrieves and returns // the result content and automatically closes response object. +// Deprecated, please use g.Client().PutContent or NewClient().PutContent instead. func PutContent(url string, data ...interface{}) string { return RequestContent("PUT", url, data...) } // PostContent is a convenience method for sending POST request, which retrieves and returns // the result content and automatically closes response object. +// Deprecated, please use g.Client().PostContent or NewClient().PostContent instead. func PostContent(url string, data ...interface{}) string { return RequestContent("POST", url, data...) } // DeleteContent is a convenience method for sending DELETE request, which retrieves and returns // the result content and automatically closes response object. +// Deprecated, please use g.Client().DeleteContent or NewClient().DeleteContent instead. func DeleteContent(url string, data ...interface{}) string { return RequestContent("DELETE", url, data...) } // HeadContent is a convenience method for sending HEAD request, which retrieves and returns // the result content and automatically closes response object. +// Deprecated, please use g.Client().HeadContent or NewClient().HeadContent instead. func HeadContent(url string, data ...interface{}) string { return RequestContent("HEAD", url, data...) } // PatchContent is a convenience method for sending PATCH request, which retrieves and returns // the result content and automatically closes response object. +// Deprecated, please use g.Client().PatchContent or NewClient().PatchContent instead. func PatchContent(url string, data ...interface{}) string { return RequestContent("PATCH", url, data...) } // ConnectContent is a convenience method for sending CONNECT request, which retrieves and returns // the result content and automatically closes response object. +// Deprecated, please use g.Client().ConnectContent or NewClient().ConnectContent instead. func ConnectContent(url string, data ...interface{}) string { return RequestContent("CONNECT", url, data...) } // OptionsContent is a convenience method for sending OPTIONS request, which retrieves and returns // the result content and automatically closes response object. +// Deprecated, please use g.Client().OptionsContent or NewClient().OptionsContent instead. func OptionsContent(url string, data ...interface{}) string { return RequestContent("OPTIONS", url, data...) } // TraceContent is a convenience method for sending TRACE request, which retrieves and returns // the result content and automatically closes response object. +// Deprecated, please use g.Client().TraceContent or NewClient().TraceContent instead. func TraceContent(url string, data ...interface{}) string { return RequestContent("TRACE", url, data...) } // RequestContent is a convenience method for sending custom http method request, which // retrieves and returns the result content and automatically closes response object. +// Deprecated, please use g.Client().RequestContent or NewClient().RequestContent instead. func RequestContent(method string, url string, data ...interface{}) string { - return NewClient().RequestContent(method, url, data...) + return client.New().RequestContent(method, url, data...) } // GetBytes is a convenience method for sending GET request, which retrieves and returns // the result content as bytes and automatically closes response object. +// Deprecated, please use g.Client().GetBytes or NewClient().GetBytes instead. func GetBytes(url string, data ...interface{}) []byte { return RequestBytes("GET", url, data...) } // PutBytes is a convenience method for sending PUT request, which retrieves and returns // the result content as bytes and automatically closes response object. +// Deprecated, please use g.Client().PutBytes or NewClient().PutBytes instead. func PutBytes(url string, data ...interface{}) []byte { return RequestBytes("PUT", url, data...) } // PostBytes is a convenience method for sending POST request, which retrieves and returns // the result content as bytes and automatically closes response object. +// Deprecated, please use g.Client().PostBytes or NewClient().PostBytes instead. func PostBytes(url string, data ...interface{}) []byte { return RequestBytes("POST", url, data...) } // DeleteBytes is a convenience method for sending DELETE request, which retrieves and returns // the result content as bytes and automatically closes response object. +// Deprecated, please use g.Client().DeleteBytes or NewClient().DeleteBytes instead. func DeleteBytes(url string, data ...interface{}) []byte { return RequestBytes("DELETE", url, data...) } // HeadBytes is a convenience method for sending HEAD request, which retrieves and returns // the result content as bytes and automatically closes response object. +// Deprecated, please use g.Client().HeadBytes or NewClient().HeadBytes instead. func HeadBytes(url string, data ...interface{}) []byte { return RequestBytes("HEAD", url, data...) } // PatchBytes is a convenience method for sending PATCH request, which retrieves and returns // the result content as bytes and automatically closes response object. +// Deprecated, please use g.Client().PatchBytes or NewClient().PatchBytes instead. func PatchBytes(url string, data ...interface{}) []byte { return RequestBytes("PATCH", url, data...) } // ConnectBytes is a convenience method for sending CONNECT request, which retrieves and returns // the result content as bytes and automatically closes response object. +// Deprecated, please use g.Client().ConnectBytes or NewClient().ConnectBytes instead. func ConnectBytes(url string, data ...interface{}) []byte { return RequestBytes("CONNECT", url, data...) } // OptionsBytes is a convenience method for sending OPTIONS request, which retrieves and returns // the result content as bytes and automatically closes response object. +// Deprecated, please use g.Client().OptionsBytes or NewClient().OptionsBytes instead. func OptionsBytes(url string, data ...interface{}) []byte { return RequestBytes("OPTIONS", url, data...) } // TraceBytes is a convenience method for sending TRACE request, which retrieves and returns // the result content as bytes and automatically closes response object. +// Deprecated, please use g.Client().TraceBytes or NewClient().TraceBytes instead. func TraceBytes(url string, data ...interface{}) []byte { return RequestBytes("TRACE", url, data...) } // RequestBytes is a convenience method for sending custom http method request, which // retrieves and returns the result content as bytes and automatically closes response object. +// Deprecated, please use g.Client().RequestBytes or NewClient().RequestBytes instead. func RequestBytes(method string, url string, data ...interface{}) []byte { - return NewClient().RequestBytes(method, url, data...) + return client.New().RequestBytes(method, url, data...) } // GetVar sends a GET request, retrieves and converts the result content to specified pointer. // The parameter <pointer> can be type of: struct/*struct/**struct/[]struct/[]*struct/*[]struct, et +// Deprecated, please use g.Client().GetVar or NewClient().GetVar instead. func GetVar(url string, data ...interface{}) *gvar.Var { return RequestVar("GET", url, data...) } // PutVar sends a PUT request, retrieves and converts the result content to specified pointer. // The parameter <pointer> can be type of: struct/*struct/**struct/[]struct/[]*struct/*[]struct, et +// Deprecated, please use g.Client().PutVar or NewClient().PutVar instead. func PutVar(url string, data ...interface{}) *gvar.Var { return RequestVar("PUT", url, data...) } // PostVar sends a POST request, retrieves and converts the result content to specified pointer. // The parameter <pointer> can be type of: struct/*struct/**struct/[]struct/[]*struct/*[]struct, et +// Deprecated, please use g.Client().PostVar or NewClient().PostVar instead. func PostVar(url string, data ...interface{}) *gvar.Var { return RequestVar("POST", url, data...) } // DeleteVar sends a DELETE request, retrieves and converts the result content to specified pointer. // The parameter <pointer> can be type of: struct/*struct/**struct/[]struct/[]*struct/*[]struct, et +// Deprecated, please use g.Client().DeleteVar or NewClient().DeleteVar instead. func DeleteVar(url string, data ...interface{}) *gvar.Var { return RequestVar("DELETE", url, data...) } // HeadVar sends a HEAD request, retrieves and converts the result content to specified pointer. // The parameter <pointer> can be type of: struct/*struct/**struct/[]struct/[]*struct/*[]struct, et +// Deprecated, please use g.Client().HeadVar or NewClient().HeadVar instead. func HeadVar(url string, data ...interface{}) *gvar.Var { return RequestVar("HEAD", url, data...) } // PatchVar sends a PATCH request, retrieves and converts the result content to specified pointer. // The parameter <pointer> can be type of: struct/*struct/**struct/[]struct/[]*struct/*[]struct, et +// Deprecated, please use g.Client().PatchVar or NewClient().PatchVar instead. func PatchVar(url string, data ...interface{}) *gvar.Var { return RequestVar("PATCH", url, data...) } // ConnectVar sends a CONNECT request, retrieves and converts the result content to specified pointer. // The parameter <pointer> can be type of: struct/*struct/**struct/[]struct/[]*struct/*[]struct, et +// Deprecated, please use g.Client().ConnectVar or NewClient().ConnectVar instead. func ConnectVar(url string, data ...interface{}) *gvar.Var { return RequestVar("CONNECT", url, data...) } // OptionsVar sends a OPTIONS request, retrieves and converts the result content to specified pointer. // The parameter <pointer> can be type of: struct/*struct/**struct/[]struct/[]*struct/*[]struct, et +// Deprecated, please use g.Client().OptionsVar or NewClient().OptionsVar instead. func OptionsVar(url string, data ...interface{}) *gvar.Var { return RequestVar("OPTIONS", url, data...) } // TraceVar sends a TRACE request, retrieves and converts the result content to specified pointer. // The parameter <pointer> can be type of: struct/*struct/**struct/[]struct/[]*struct/*[]struct, et +// Deprecated, please use g.Client().TraceVar or NewClient().TraceVar instead. func TraceVar(url string, data ...interface{}) *gvar.Var { return RequestVar("TRACE", url, data...) } @@ -245,6 +298,7 @@ func TraceVar(url string, data ...interface{}) *gvar.Var { // RequestVar sends request using given HTTP method and data, retrieves converts the result // to specified pointer. It reads and closes the response object internally automatically. // The parameter <pointer> can be type of: struct/*struct/**struct/[]struct/[]*struct/*[]struct, et +// Deprecated, please use g.Client().RequestVar or NewClient().RequestVar instead. func RequestVar(method string, url string, data ...interface{}) *gvar.Var { response, err := DoRequest(method, url, data...) if err != nil { diff --git a/net/ghttp/ghttp_client_websocket.go b/net/ghttp/ghttp_client_websocket.go new file mode 100644 index 000000000..1f1f228f7 --- /dev/null +++ b/net/ghttp/ghttp_client_websocket.go @@ -0,0 +1,29 @@ +// Copyright GoFrame Author(https://goframe.org). All Rights Reserved. +// +// This Source Code Form is subject to the terms of the MIT License. +// If a copy of the MIT was not distributed with this file, +// You can obtain one at https://github.com/gogf/gf. + +package ghttp + +import ( + "github.com/gorilla/websocket" + "net/http" + "time" +) + +// WebSocketClient wraps the underlying websocket client connection +// and provides convenient functions. +type WebSocketClient struct { + *websocket.Dialer +} + +// NewWebSocketClient New creates and returns a new WebSocketClient object. +func NewWebSocketClient() *WebSocketClient { + return &WebSocketClient{ + &websocket.Dialer{ + Proxy: http.ProxyFromEnvironment, + HandshakeTimeout: 45 * time.Second, + }, + } +} diff --git a/net/ghttp/ghttp_controller.go b/net/ghttp/ghttp_controller.go deleted file mode 100644 index 376c1427b..000000000 --- a/net/ghttp/ghttp_controller.go +++ /dev/null @@ -1,13 +0,0 @@ -// Copyright 2017 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 ghttp - -// Controller is the base struct for controller. -type Controller interface { - Init(*Request) - Shut() -} diff --git a/net/ghttp/ghttp_func.go b/net/ghttp/ghttp_func.go index 8e6b06acd..a4d96037e 100644 --- a/net/ghttp/ghttp_func.go +++ b/net/ghttp/ghttp_func.go @@ -1,4 +1,4 @@ -// Copyright GoFrame Author(https://github.com/gogf/gf). All Rights Reserved. +// Copyright GoFrame Author(https://goframe.org). All Rights Reserved. // // This Source Code Form is subject to the terms of the MIT License. // If a copy of the MIT was not distributed with this file, @@ -7,16 +7,9 @@ package ghttp import ( + "github.com/gogf/gf/errors/gcode" "github.com/gogf/gf/errors/gerror" - "github.com/gogf/gf/text/gstr" - "strings" - - "github.com/gogf/gf/encoding/gurl" - "github.com/gogf/gf/util/gconv" -) - -const ( - fileUploadingKey = "@file:" + "github.com/gogf/gf/net/ghttp/internal/httputil" ) // BuildParams builds the request string for the http client. The <params> can be type of: @@ -24,64 +17,34 @@ const ( // // The optional parameter <noUrlEncode> specifies whether ignore the url encoding for the data. func BuildParams(params interface{}, noUrlEncode ...bool) (encodedParamStr string) { - // If given string/[]byte, converts and returns it directly as string. - switch v := params.(type) { - case string, []byte: - return gconv.String(params) - case []interface{}: - if len(v) > 0 { - params = v[0] - } else { - params = nil - } - } - // Else converts it to map and does the url encoding. - m, urlEncode := gconv.Map(params), true - if len(m) == 0 { - return gconv.String(params) - } - if len(noUrlEncode) == 1 { - urlEncode = !noUrlEncode[0] - } - // If there's file uploading, it ignores the url encoding. - if urlEncode { - for k, v := range m { - if gstr.Contains(k, fileUploadingKey) || gstr.Contains(gconv.String(v), fileUploadingKey) { - urlEncode = false - break - } - } - } - s := "" - for k, v := range m { - if len(encodedParamStr) > 0 { - encodedParamStr += "&" - } - s = gconv.String(v) - if urlEncode && len(s) > 6 && strings.Compare(s[0:6], fileUploadingKey) != 0 { - s = gurl.Encode(s) - } - encodedParamStr += k + "=" + s - } - return + return httputil.BuildParams(params, noUrlEncode...) } // niceCallFunc calls function <f> with exception capture logic. func niceCallFunc(f func()) { defer func() { - if e := recover(); e != nil { - switch e { + if exception := recover(); exception != nil { + switch exception { case exceptionExit, exceptionExitAll: return + default: - if _, ok := e.(errorStack); ok { + if _, ok := exception.(errorStack); ok { // It's already an error that has stack info. - panic(e) + panic(exception) } else { // Create a new error with stack info. // Note that there's a skip pointing the start stacktrace // of the real error point. - panic(gerror.NewSkipf(1, "%v", e)) + if err, ok := exception.(error); ok { + if gerror.Code(err) != gcode.CodeNil { + panic(err) + } else { + panic(gerror.WrapCodeSkip(gcode.CodeInternalError, 1, err, "")) + } + } else { + panic(gerror.NewCodeSkipf(gcode.CodeInternalError, 1, "%+v", exception)) + } } } } diff --git a/net/ghttp/ghttp_middleware_handler_response.go b/net/ghttp/ghttp_middleware_handler_response.go new file mode 100644 index 000000000..e37e92374 --- /dev/null +++ b/net/ghttp/ghttp_middleware_handler_response.go @@ -0,0 +1,53 @@ +// Copyright GoFrame Author(https://goframe.org). All Rights Reserved. +// +// This Source Code Form is subject to the terms of the MIT License. +// If a copy of the MIT was not distributed with this file, +// You can obtain one at https://github.com/gogf/gf. + +package ghttp + +import ( + "github.com/gogf/gf/errors/gcode" + "github.com/gogf/gf/errors/gerror" + "github.com/gogf/gf/internal/intlog" +) + +type DefaultHandlerResponse struct { + Code int `json:"code"` + Message string `json:"message"` + Data interface{} `json:"data"` +} + +// MiddlewareHandlerResponse is the default middleware handling handler response object and its error. +func MiddlewareHandlerResponse(r *Request) { + r.Middleware.Next() + var ( + err error + res interface{} + internalErr error + ) + res, err = r.GetHandlerResponse() + if err != nil { + code := gerror.Code(err) + if code == gcode.CodeNil { + code = gcode.CodeInternalError + } + internalErr = r.Response.WriteJson(DefaultHandlerResponse{ + Code: code.Code(), + Message: err.Error(), + Data: nil, + }) + if internalErr != nil { + intlog.Error(r.Context(), internalErr) + } + return + } + internalErr = r.Response.WriteJson(DefaultHandlerResponse{ + Code: gcode.CodeOK.Code(), + Message: "", + Data: res, + }) + if internalErr != nil { + intlog.Error(r.Context(), internalErr) + } +} diff --git a/net/ghttp/ghttp_middleware_tracing.go b/net/ghttp/ghttp_middleware_tracing.go new file mode 100644 index 000000000..052b4ee7e --- /dev/null +++ b/net/ghttp/ghttp_middleware_tracing.go @@ -0,0 +1,89 @@ +// Copyright GoFrame Author(https://goframe.org). All Rights Reserved. +// +// This Source Code Form is subject to the terms of the MIT License. +// If a copy of the MIT was not distributed with this file, +// You can obtain one at https://github.com/gogf/gf. + +package ghttp + +import ( + "fmt" + "github.com/gogf/gf" + "github.com/gogf/gf/internal/utils" + "github.com/gogf/gf/net/ghttp/internal/client" + "github.com/gogf/gf/net/ghttp/internal/httputil" + "github.com/gogf/gf/net/gtrace" + "github.com/gogf/gf/text/gstr" + "go.opentelemetry.io/otel" + "go.opentelemetry.io/otel/attribute" + "go.opentelemetry.io/otel/codes" + "go.opentelemetry.io/otel/propagation" + "go.opentelemetry.io/otel/trace" + "io/ioutil" + "net/http" +) + +const ( + tracingInstrumentName = "github.com/gogf/gf/net/ghttp.Server" + tracingEventHttpRequest = "http.request" + tracingEventHttpRequestHeaders = "http.request.headers" + tracingEventHttpRequestBaggage = "http.request.baggage" + tracingEventHttpRequestBody = "http.request.body" + tracingEventHttpResponse = "http.response" + tracingEventHttpResponseHeaders = "http.response.headers" + tracingEventHttpResponseBody = "http.response.body" +) + +// MiddlewareClientTracing is a client middleware that enables tracing feature using standards of OpenTelemetry. +func MiddlewareClientTracing(c *Client, r *http.Request) (*ClientResponse, error) { + return client.MiddlewareTracing(c, r) +} + +// MiddlewareServerTracing is a serer middleware that enables tracing feature using standards of OpenTelemetry. +func MiddlewareServerTracing(r *Request) { + tr := otel.GetTracerProvider().Tracer(tracingInstrumentName, trace.WithInstrumentationVersion(gf.VERSION)) + ctx := otel.GetTextMapPropagator().Extract(r.Context(), propagation.HeaderCarrier(r.Header)) + ctx, span := tr.Start(ctx, r.URL.String(), trace.WithSpanKind(trace.SpanKindServer)) + defer span.End() + + span.SetAttributes(gtrace.CommonLabels()...) + + // Inject tracing context. + r.SetCtx(ctx) + + // Request content logging. + reqBodyContentBytes, _ := ioutil.ReadAll(r.Body) + r.Body = utils.NewReadCloser(reqBodyContentBytes, false) + + span.AddEvent(tracingEventHttpRequest, trace.WithAttributes( + attribute.Any(tracingEventHttpRequestHeaders, httputil.HeaderToMap(r.Header)), + attribute.Any(tracingEventHttpRequestBaggage, gtrace.GetBaggageMap(ctx)), + attribute.String(tracingEventHttpRequestBody, gstr.StrLimit( + string(reqBodyContentBytes), + gtrace.MaxContentLogSize(), + "...", + )), + )) + + // Continue executing. + r.Middleware.Next() + + // Error logging. + if err := r.GetError(); err != nil { + span.SetStatus(codes.Error, fmt.Sprintf(`%+v`, err)) + } + // Response content logging. + var resBodyContent string + resBodyContent = r.Response.BufferString() + resBodyContent = gstr.StrLimit( + r.Response.BufferString(), + gtrace.MaxContentLogSize(), + "...", + ) + + span.AddEvent(tracingEventHttpResponse, trace.WithAttributes( + attribute.Any(tracingEventHttpResponseHeaders, httputil.HeaderToMap(r.Response.Header())), + attribute.String(tracingEventHttpResponseBody, resBodyContent), + )) + return +} diff --git a/net/ghttp/ghttp_request.go b/net/ghttp/ghttp_request.go index 9256455fa..97d33fe25 100644 --- a/net/ghttp/ghttp_request.go +++ b/net/ghttp/ghttp_request.go @@ -1,4 +1,4 @@ -// Copyright GoFrame Author(https://github.com/gogf/gf). All Rights Reserved. +// Copyright GoFrame Author(https://goframe.org). All Rights Reserved. // // This Source Code Form is subject to the terms of the MIT License. // If a copy of the MIT was not distributed with this file, @@ -33,10 +33,11 @@ type Request struct { Router *Router // Matched Router for this request. Note that it's not available in HOOK handler. EnterTime int64 // Request starting time in microseconds. LeaveTime int64 // Request ending time in microseconds. - Middleware *Middleware // Middleware manager. - StaticFile *StaticFile // Static file object for static file serving. + Middleware *middleware // Middleware manager. + StaticFile *staticFile // Static file object for static file serving. context context.Context // Custom context for internal usage purpose. handlers []*handlerParsedItem // All matched handlers containing handler, hook and middleware for this request. + handlerResponse handlerResponse // Handler response object and its error value. hasHookHandler bool // A bool marking whether there's hook handler in the handlers for performance purpose. hasServeHandler bool // A bool marking whether there's serving handler in the handlers for performance purpose. parsedQuery bool // A bool marking whether the GET parameters parsed. @@ -57,8 +58,13 @@ type Request struct { viewParams gview.Params // Custom template view variables for this response. } -// StaticFile is the file struct for static file service. -type StaticFile struct { +type handlerResponse struct { + Object interface{} + Error error +} + +// staticFile is the file struct for static file service. +type staticFile struct { File *gres.File // Resource file object. Path string // File path. IsDir bool // Is directory. @@ -73,9 +79,12 @@ func newRequest(s *Server, r *http.Request, w http.ResponseWriter) *Request { EnterTime: gtime.TimestampMilli(), } request.Cookie = GetCookie(request) - request.Session = s.sessionManager.New(request.GetSessionId()) + request.Session = s.sessionManager.New( + r.Context(), + request.GetSessionId(), + ) request.Response.Request = request - request.Middleware = &Middleware{ + request.Middleware = &middleware{ request: request, } // Custom session id creating function. @@ -84,7 +93,7 @@ func newRequest(s *Server, r *http.Request, w http.ResponseWriter) *Request { address = request.RemoteAddr header = fmt.Sprintf("%v", request.Header) ) - intlog.Print(address, header) + intlog.Print(r.Context(), address, header) return guid.S([]byte(address), []byte(header)) }) if err != nil { @@ -233,3 +242,8 @@ func (r *Request) ReloadParam() { r.parsedQuery = false r.bodyContent = nil } + +// GetHandlerResponse retrieves and returns the handler response object and its error. +func (r *Request) GetHandlerResponse() (res interface{}, err error) { + return r.handlerResponse.Object, r.handlerResponse.Error +} diff --git a/net/ghttp/ghttp_request_auth.go b/net/ghttp/ghttp_request_auth.go index 82ab1c666..a021692eb 100644 --- a/net/ghttp/ghttp_request_auth.go +++ b/net/ghttp/ghttp_request_auth.go @@ -1,4 +1,4 @@ -// Copyright 2018 gf Author(https://github.com/gogf/gf). All Rights Reserved. +// Copyright GoFrame Author(https://goframe.org). All Rights Reserved. // // This Source Code Form is subject to the terms of the MIT License. // If a copy of the MIT was not distributed with this file, diff --git a/net/ghttp/ghttp_request_middleware.go b/net/ghttp/ghttp_request_middleware.go index 13da4129d..7f7956a37 100644 --- a/net/ghttp/ghttp_request_middleware.go +++ b/net/ghttp/ghttp_request_middleware.go @@ -1,4 +1,4 @@ -// Copyright GoFrame Author(https://github.com/gogf/gf). All Rights Reserved. +// Copyright GoFrame Author(https://goframe.org). All Rights Reserved. // // This Source Code Form is subject to the terms of the MIT License. // If a copy of the MIT was not distributed with this file, @@ -7,6 +7,7 @@ package ghttp import ( + "github.com/gogf/gf/errors/gcode" "github.com/gogf/gf/errors/gerror" "net/http" "reflect" @@ -14,8 +15,8 @@ import ( "github.com/gogf/gf/util/gutil" ) -// Middleware is the plugin for request workflow management. -type Middleware struct { +// middleware is the plugin for request workflow management. +type middleware struct { served bool // Is the request served, which is used for checking response status 404. request *Request // The request object pointer. handlerIndex int // Index number for executing sequence purpose for handler items. @@ -24,7 +25,7 @@ type Middleware struct { // Next calls the next workflow handler. // It's an important function controlling the workflow of the server request execution. -func (m *Middleware) Next() { +func (m *middleware) Next() { var item *handlerParsedItem var loop = true for loop { @@ -34,20 +35,20 @@ func (m *Middleware) Next() { } item = m.request.handlers[m.handlerIndex] // Filter the HOOK handlers, which are designed to be called in another standalone procedure. - if item.handler.itemType == handlerTypeHook { + if item.Handler.Type == handlerTypeHook { m.handlerIndex++ continue } // Current router switching. - m.request.Router = item.handler.router + m.request.Router = item.Handler.Router // Router values switching. - m.request.routerMap = item.values + m.request.routerMap = item.Values gutil.TryCatch(func() { // Execute bound middleware array of the item if it's not empty. - if m.handlerMDIndex < len(item.handler.middleware) { - md := item.handler.middleware[m.handlerMDIndex] + if m.handlerMDIndex < len(item.Handler.Middleware) { + md := item.Handler.Middleware[m.handlerMDIndex] m.handlerMDIndex++ niceCallFunc(func() { md(m.request) @@ -57,47 +58,24 @@ func (m *Middleware) Next() { } m.handlerIndex++ - switch item.handler.itemType { - // Service controller. - case handlerTypeController: - m.served = true - if m.request.IsExited() { - break - } - c := reflect.New(item.handler.ctrlInfo.reflect) - niceCallFunc(func() { - c.MethodByName("Init").Call([]reflect.Value{reflect.ValueOf(m.request)}) - }) - if !m.request.IsExited() { - niceCallFunc(func() { - c.MethodByName(item.handler.ctrlInfo.name).Call(nil) - }) - } - if !m.request.IsExited() { - niceCallFunc(func() { - c.MethodByName("Shut").Call(nil) - }) - } - + switch item.Handler.Type { // Service object. case handlerTypeObject: m.served = true if m.request.IsExited() { break } - if item.handler.initFunc != nil { + if item.Handler.InitFunc != nil { niceCallFunc(func() { - item.handler.initFunc(m.request) + item.Handler.InitFunc(m.request) }) } if !m.request.IsExited() { - niceCallFunc(func() { - item.handler.itemFunc(m.request) - }) + m.callHandlerFunc(item.Handler.Info) } - if !m.request.IsExited() && item.handler.shutFunc != nil { + if !m.request.IsExited() && item.Handler.ShutFunc != nil { niceCallFunc(func() { - item.handler.shutFunc(m.request) + item.Handler.ShutFunc(m.request) }) } @@ -108,13 +86,13 @@ func (m *Middleware) Next() { break } niceCallFunc(func() { - item.handler.itemFunc(m.request) + m.callHandlerFunc(item.Handler.Info) }) // Global middleware array. case handlerTypeMiddleware: niceCallFunc(func() { - item.handler.itemFunc(m.request) + item.Handler.Info.Func(m.request) }) // It does not continue calling next middleware after another middleware done. // There should be a "Next" function to be called in the middleware in order to manage the workflow. @@ -128,7 +106,7 @@ func (m *Middleware) Next() { // Create a new error with stack info. // Note that there's a skip pointing the start stacktrace // of the real error point. - m.request.error = gerror.NewSkip(1, exception.Error()) + m.request.error = gerror.WrapCodeSkip(gcode.CodeInternalError, 1, exception, "") } m.request.Response.WriteStatus(http.StatusInternalServerError, exception) loop = false @@ -145,3 +123,50 @@ func (m *Middleware) Next() { } } } + +func (m *middleware) callHandlerFunc(funcInfo handlerFuncInfo) { + niceCallFunc(func() { + if funcInfo.Func != nil { + funcInfo.Func(m.request) + } else { + var inputValues = []reflect.Value{ + reflect.ValueOf(m.request.Context()), + } + if funcInfo.Type.NumIn() == 2 { + var ( + inputObject reflect.Value + ) + if funcInfo.Type.In(1).Kind() == reflect.Ptr { + inputObject = reflect.New(funcInfo.Type.In(1).Elem()) + m.request.handlerResponse.Error = m.request.Parse(inputObject.Interface()) + } else { + inputObject = reflect.New(funcInfo.Type.In(1).Elem()).Elem() + m.request.handlerResponse.Error = m.request.Parse(inputObject.Addr().Interface()) + } + if m.request.handlerResponse.Error != nil { + return + } + inputValues = append(inputValues, inputObject) + } + + // Call handler with dynamic created parameter values. + results := funcInfo.Value.Call(inputValues) + switch len(results) { + case 1: + if !results[0].IsNil() { + if err, ok := results[0].Interface().(error); ok { + m.request.handlerResponse.Error = err + } + } + + case 2: + m.request.handlerResponse.Object = results[0].Interface() + if !results[1].IsNil() { + if err, ok := results[1].Interface().(error); ok { + m.request.handlerResponse.Error = err + } + } + } + } + }) +} diff --git a/net/ghttp/ghttp_request_param.go b/net/ghttp/ghttp_request_param.go index 102de9a1d..8c9689b2e 100644 --- a/net/ghttp/ghttp_request_param.go +++ b/net/ghttp/ghttp_request_param.go @@ -1,4 +1,4 @@ -// Copyright 2019 gf Author(https://github.com/gogf/gf). All Rights Reserved. +// Copyright GoFrame Author(https://goframe.org). All Rights Reserved. // // This Source Code Form is subject to the terms of the MIT License. // If a copy of the MIT was not distributed with this file, @@ -13,6 +13,8 @@ import ( "github.com/gogf/gf/encoding/gjson" "github.com/gogf/gf/encoding/gurl" "github.com/gogf/gf/encoding/gxml" + "github.com/gogf/gf/errors/gcode" + "github.com/gogf/gf/errors/gerror" "github.com/gogf/gf/internal/json" "github.com/gogf/gf/internal/utils" "github.com/gogf/gf/text/gregex" @@ -82,24 +84,27 @@ func (r *Request) doParse(pointer interface{}, requestType int) error { // 1. {"id":1, "name":"john"} // 2. ?id=1&name=john case reflect.Ptr, reflect.Struct: + var ( + err error + data map[string]interface{} + ) // Converting. switch requestType { case parseTypeQuery: - if err := r.GetQueryStruct(pointer); err != nil { + if data, err = r.doGetQueryStruct(pointer); err != nil { return err } case parseTypeForm: - if err := r.GetFormStruct(pointer); err != nil { + if data, err = r.doGetFormStruct(pointer); err != nil { return err } default: - if err := r.GetStruct(pointer); err != nil { + if data, err = r.doGetRequestStruct(pointer); err != nil { return err } } - // Validation. - if err := gvalid.CheckStruct(pointer, nil); err != nil { + if err := gvalid.CheckStructWithData(r.Context(), pointer, data, nil); err != nil { return err } @@ -107,7 +112,7 @@ func (r *Request) doParse(pointer interface{}, requestType int) error { // [{"id":1, "name":"john"}, {"id":, "name":"smith"}] case reflect.Array, reflect.Slice: // If struct slice conversion, it might post JSON/XML content, - // so it uses gjson for the conversion. + // so it uses `gjson` for the conversion. j, err := gjson.LoadContent(r.GetBody()) if err != nil { return err @@ -116,7 +121,12 @@ func (r *Request) doParse(pointer interface{}, requestType int) error { return err } for i := 0; i < reflectVal2.Len(); i++ { - if err := gvalid.CheckStruct(reflectVal2.Index(i), nil); err != nil { + if err := gvalid.CheckStructWithData( + r.Context(), + reflectVal2.Index(i), + j.GetMap(gconv.String(i)), + nil, + ); err != nil { return err } } @@ -139,14 +149,14 @@ func (r *Request) GetVar(key string, def ...interface{}) *gvar.Var { // GetRaw is alias of GetBody. // See GetBody. -// Deprecated. +// Deprecated, use GetBody instead. func (r *Request) GetRaw() []byte { return r.GetBody() } // GetRawString is alias of GetBodyString. // See GetBodyString. -// Deprecated. +// Deprecated, use GetBodyString instead. func (r *Request) GetRawString() string { return r.GetBodyString() } @@ -291,7 +301,7 @@ func (r *Request) parseQuery() { var err error r.queryMap, err = gstr.Parse(r.URL.RawQuery) if err != nil { - panic(err) + panic(gerror.WrapCode(gcode.CodeInvalidParameter, err, "")) } } } @@ -312,7 +322,7 @@ func (r *Request) parseBody() { body = bytes.TrimSpace(body) // JSON format checks. if body[0] == '{' && body[len(body)-1] == '}' { - _ = json.Unmarshal(body, &r.bodyMap) + _ = json.UnmarshalUseNumber(body, &r.bodyMap) } // XML format checks. if len(body) > 5 && bytes.EqualFold(body[:5], xmlHeaderBytes) { @@ -346,12 +356,12 @@ func (r *Request) parseForm() { if gstr.Contains(contentType, "multipart/") { // multipart/form-data, multipart/mixed if err = r.ParseMultipartForm(r.Server.config.FormParsingMemory); err != nil { - panic(err) + panic(gerror.WrapCode(gcode.CodeInvalidRequest, err, "")) } } else if gstr.Contains(contentType, "form") { // application/x-www-form-urlencoded if err = r.Request.ParseForm(); err != nil { - panic(err) + panic(gerror.WrapCode(gcode.CodeInvalidRequest, err, "")) } } if len(r.PostForm) > 0 { @@ -394,7 +404,7 @@ func (r *Request) parseForm() { } if params != "" { if r.formMap, err = gstr.Parse(params); err != nil { - panic(err) + panic(gerror.WrapCode(gcode.CodeInvalidParameter, err, "")) } } } diff --git a/net/ghttp/ghttp_request_param_ctx.go b/net/ghttp/ghttp_request_param_ctx.go index 0fe2fcda7..efcafba2e 100644 --- a/net/ghttp/ghttp_request_param_ctx.go +++ b/net/ghttp/ghttp_request_param_ctx.go @@ -1,4 +1,4 @@ -// Copyright 2017 gf Author(https://github.com/gogf/gf). All Rights Reserved. +// Copyright GoFrame Author(https://goframe.org). All Rights Reserved. // // This Source Code Form is subject to the terms of the MIT License. // If a copy of the MIT was not distributed with this file, @@ -11,6 +11,14 @@ import ( "github.com/gogf/gf/container/gvar" ) +// RequestFromCtx retrieves and returns the Request object from context. +func RequestFromCtx(ctx context.Context) *Request { + if v := ctx.Value(ctxKeyForRequest); v != nil { + return v.(*Request) + } + return nil +} + // Context is alias for function GetCtx. // This function overwrites the http.Request.Context function. // See GetCtx. @@ -18,6 +26,10 @@ func (r *Request) Context() context.Context { if r.context == nil { r.context = r.Request.Context() } + // Inject Request object into context. + if RequestFromCtx(r.context) == nil { + r.context = context.WithValue(r.context, ctxKeyForRequest, r) + } return r.context } diff --git a/net/ghttp/ghttp_request_param_file.go b/net/ghttp/ghttp_request_param_file.go index 0086b9e3e..7219117e2 100644 --- a/net/ghttp/ghttp_request_param_file.go +++ b/net/ghttp/ghttp_request_param_file.go @@ -1,4 +1,4 @@ -// Copyright 2019 gf Author(https://github.com/gogf/gf). All Rights Reserved. +// Copyright GoFrame Author(https://goframe.org). All Rights Reserved. // // This Source Code Form is subject to the terms of the MIT License. // If a copy of the MIT was not distributed with this file, @@ -7,7 +7,9 @@ package ghttp import ( - "errors" + "context" + "github.com/gogf/gf/errors/gcode" + "github.com/gogf/gf/errors/gerror" "github.com/gogf/gf/internal/intlog" "github.com/gogf/gf/os/gfile" "github.com/gogf/gf/os/gtime" @@ -21,6 +23,7 @@ import ( // UploadFile wraps the multipart uploading file with more and convenient features. type UploadFile struct { *multipart.FileHeader + ctx context.Context } // UploadFiles is array type for *UploadFile. @@ -33,14 +36,17 @@ type UploadFiles []*UploadFile // Note that it will OVERWRITE the target file if there's already a same name file exist. func (f *UploadFile) Save(dirPath string, randomlyRename ...bool) (filename string, err error) { if f == nil { - return "", errors.New("file is empty, maybe you retrieve it from invalid field name or form enctype") + return "", gerror.NewCode( + gcode.CodeMissingParameter, + "file is empty, maybe you retrieve it from invalid field name or form enctype", + ) } if !gfile.Exists(dirPath) { if err = gfile.Mkdir(dirPath); err != nil { return } } else if !gfile.IsDir(dirPath) { - return "", errors.New(`parameter "dirPath" should be a directory path`) + return "", gerror.NewCode(gcode.CodeInvalidParameter, `parameter "dirPath" should be a directory path`) } file, err := f.Open() @@ -60,7 +66,7 @@ func (f *UploadFile) Save(dirPath string, randomlyRename ...bool) (filename stri return "", err } defer newFile.Close() - intlog.Printf(`save upload file: %s`, filePath) + intlog.Printf(f.ctx, `save upload file: %s`, filePath) if _, err := io.Copy(newFile, file); err != nil { return "", err } @@ -74,7 +80,10 @@ func (f *UploadFile) Save(dirPath string, randomlyRename ...bool) (filename stri // The parameter <randomlyRename> specifies whether randomly renames all the file names. func (fs UploadFiles) Save(dirPath string, randomlyRename ...bool) (filenames []string, err error) { if len(fs) == 0 { - return nil, errors.New("file array is empty, maybe you retrieve it from invalid field name or form enctype") + return nil, gerror.NewCode( + gcode.CodeMissingParameter, + "file array is empty, maybe you retrieve it from invalid field name or form enctype", + ) } for _, f := range fs { if filename, err := f.Save(dirPath, randomlyRename...); err != nil { @@ -114,6 +123,7 @@ func (r *Request) GetUploadFiles(name string) UploadFiles { uploadFiles := make(UploadFiles, len(multipartFiles)) for k, v := range multipartFiles { uploadFiles[k] = &UploadFile{ + ctx: r.Context(), FileHeader: v, } } diff --git a/net/ghttp/ghttp_request_param_form.go b/net/ghttp/ghttp_request_param_form.go index ef9f6bf6e..2e09b8b00 100644 --- a/net/ghttp/ghttp_request_param_form.go +++ b/net/ghttp/ghttp_request_param_form.go @@ -1,4 +1,4 @@ -// Copyright 2017 gf Author(https://github.com/gogf/gf). All Rights Reserved. +// Copyright GoFrame Author(https://goframe.org). All Rights Reserved. // // This Source Code Form is subject to the terms of the MIT License. // If a copy of the MIT was not distributed with this file, @@ -188,13 +188,18 @@ func (r *Request) GetFormMapStrVar(kvMap ...map[string]interface{}) map[string]* // given struct object. Note that the parameter <pointer> is a pointer to the struct object. // The optional parameter <mapping> is used to specify the key to attribute mapping. func (r *Request) GetFormStruct(pointer interface{}, mapping ...map[string]string) error { + _, err := r.doGetFormStruct(pointer, mapping...) + return err +} + +func (r *Request) doGetFormStruct(pointer interface{}, mapping ...map[string]string) (data map[string]interface{}, err error) { r.parseForm() - data := r.formMap + data = r.formMap if data == nil { data = map[string]interface{}{} } if err := r.mergeDefaultStructValue(data, pointer); err != nil { - return nil + return data, nil } - return gconv.Struct(data, pointer, mapping...) + return data, gconv.Struct(data, pointer, mapping...) } diff --git a/net/ghttp/ghttp_request_param_page.go b/net/ghttp/ghttp_request_param_page.go index c9fbe7d03..b63a290a0 100644 --- a/net/ghttp/ghttp_request_param_page.go +++ b/net/ghttp/ghttp_request_param_page.go @@ -1,4 +1,4 @@ -// Copyright 2017 gf Author(https://github.com/gogf/gf). All Rights Reserved. +// Copyright GoFrame Author(https://goframe.org). All Rights Reserved. // // This Source Code Form is subject to the terms of the MIT License. // If a copy of the MIT was not distributed with this file, @@ -14,20 +14,22 @@ import ( ) // GetPage creates and returns the pagination object for given <totalSize> and <pageSize>. -// NOTE THAT the page parameter name from client is constantly defined as gpage.PAGE_NAME +// NOTE THAT the page parameter name from client is constantly defined as gpage.DefaultPageName // for simplification and convenience. func (r *Request) GetPage(totalSize, pageSize int) *gpage.Page { - // It must has Router object attribute. + // It must have Router object attribute. if r.Router == nil { panic("Router object not found") } - url := *r.URL - urlTemplate := url.Path - uriHasPageName := false + var ( + url = *r.URL + urlTemplate = url.Path + uriHasPageName = false + ) // Check the page variable in the URI. if len(r.Router.RegNames) > 0 { for _, name := range r.Router.RegNames { - if name == gpage.PAGE_NAME { + if name == gpage.DefaultPageName { uriHasPageName = true break } @@ -38,20 +40,25 @@ func (r *Request) GetPage(totalSize, pageSize int) *gpage.Page { urlTemplate = r.Router.Uri for i, name := range r.Router.RegNames { rule := fmt.Sprintf(`[:\*]%s|\{%s\}`, name, name) - if name == gpage.PAGE_NAME { - urlTemplate, _ = gregex.ReplaceString(rule, gpage.PAGE_PLACE_HOLDER, urlTemplate) + if name == gpage.DefaultPageName { + urlTemplate, err = gregex.ReplaceString(rule, gpage.DefaultPagePlaceHolder, urlTemplate) } else { - urlTemplate, _ = gregex.ReplaceString(rule, match[i+1], urlTemplate) + urlTemplate, err = gregex.ReplaceString(rule, match[i+1], urlTemplate) + } + if err != nil { + panic(err) } } } + } else { + panic(err) } } } // Check the page variable in the query string. if !uriHasPageName { values := url.Query() - values.Set(gpage.PAGE_NAME, gpage.PAGE_PLACE_HOLDER) + values.Set(gpage.DefaultPageName, gpage.DefaultPagePlaceHolder) url.RawQuery = values.Encode() // Replace the encoded "{.page}" to original "{.page}". url.RawQuery = gstr.Replace(url.RawQuery, "%7B.page%7D", "{.page}") @@ -60,5 +67,5 @@ func (r *Request) GetPage(totalSize, pageSize int) *gpage.Page { urlTemplate += "?" + url.RawQuery } - return gpage.New(totalSize, pageSize, r.GetInt(gpage.PAGE_NAME), urlTemplate) + return gpage.New(totalSize, pageSize, r.GetInt(gpage.DefaultPageName), urlTemplate) } diff --git a/net/ghttp/ghttp_request_param_param.go b/net/ghttp/ghttp_request_param_param.go index c318ae64b..a965b6d46 100644 --- a/net/ghttp/ghttp_request_param_param.go +++ b/net/ghttp/ghttp_request_param_param.go @@ -1,4 +1,4 @@ -// Copyright 2017 gf Author(https://github.com/gogf/gf). All Rights Reserved. +// Copyright GoFrame Author(https://goframe.org). All Rights Reserved. // // This Source Code Form is subject to the terms of the MIT License. // If a copy of the MIT was not distributed with this file, diff --git a/net/ghttp/ghttp_request_param_post.go b/net/ghttp/ghttp_request_param_post.go index f49dd4c4f..8ddfa17ab 100644 --- a/net/ghttp/ghttp_request_param_post.go +++ b/net/ghttp/ghttp_request_param_post.go @@ -1,4 +1,4 @@ -// Copyright 2017 gf Author(https://github.com/gogf/gf). All Rights Reserved. +// Copyright GoFrame Author(https://goframe.org). All Rights Reserved. // // This Source Code Form is subject to the terms of the MIT License. // If a copy of the MIT was not distributed with this file, @@ -18,7 +18,7 @@ import ( // Note that if there're multiple parameters with the same name, the parameters are retrieved // and overwrote in order of priority: form > body. // -// Deprecated. +// Deprecated, use GetForm instead. func (r *Request) GetPost(key string, def ...interface{}) interface{} { r.parseForm() if len(r.formMap) > 0 { @@ -38,82 +38,82 @@ func (r *Request) GetPost(key string, def ...interface{}) interface{} { return nil } -// Deprecated. +// Deprecated, use GetFormVar instead. func (r *Request) GetPostVar(key string, def ...interface{}) *gvar.Var { return gvar.New(r.GetPost(key, def...)) } -// Deprecated. +// Deprecated, use GetFormString instead. func (r *Request) GetPostString(key string, def ...interface{}) string { return r.GetPostVar(key, def...).String() } -// Deprecated. +// Deprecated, use GetFormBool instead. func (r *Request) GetPostBool(key string, def ...interface{}) bool { return r.GetPostVar(key, def...).Bool() } -// Deprecated. +// Deprecated, use GetFormInt instead. func (r *Request) GetPostInt(key string, def ...interface{}) int { return r.GetPostVar(key, def...).Int() } -// Deprecated. +// Deprecated, use GetFormInt32 instead. func (r *Request) GetPostInt32(key string, def ...interface{}) int32 { return r.GetPostVar(key, def...).Int32() } -// Deprecated. +// Deprecated, use GetFormInt64 instead. func (r *Request) GetPostInt64(key string, def ...interface{}) int64 { return r.GetPostVar(key, def...).Int64() } -// Deprecated. +// Deprecated, use GetFormInts instead. func (r *Request) GetPostInts(key string, def ...interface{}) []int { return r.GetPostVar(key, def...).Ints() } -// Deprecated. +// Deprecated, use GetFormUint instead. func (r *Request) GetPostUint(key string, def ...interface{}) uint { return r.GetPostVar(key, def...).Uint() } -// Deprecated. +// Deprecated, use GetFormUint32 instead. func (r *Request) GetPostUint32(key string, def ...interface{}) uint32 { return r.GetPostVar(key, def...).Uint32() } -// Deprecated. +// Deprecated, use GetFormUint64 instead. func (r *Request) GetPostUint64(key string, def ...interface{}) uint64 { return r.GetPostVar(key, def...).Uint64() } -// Deprecated. +// Deprecated, use GetFormFloat32 instead. func (r *Request) GetPostFloat32(key string, def ...interface{}) float32 { return r.GetPostVar(key, def...).Float32() } -// Deprecated. +// Deprecated, use GetFormFloat64 instead. func (r *Request) GetPostFloat64(key string, def ...interface{}) float64 { return r.GetPostVar(key, def...).Float64() } -// Deprecated. +// Deprecated, use GetFormFloats instead. func (r *Request) GetPostFloats(key string, def ...interface{}) []float64 { return r.GetPostVar(key, def...).Floats() } -// Deprecated. +// Deprecated, use GetFormArray instead. func (r *Request) GetPostArray(key string, def ...interface{}) []string { return r.GetPostVar(key, def...).Strings() } -// Deprecated. +// Deprecated, use GetFormStrings instead. func (r *Request) GetPostStrings(key string, def ...interface{}) []string { return r.GetPostVar(key, def...).Strings() } -// Deprecated. +// Deprecated, use GetFormInterfaces instead. func (r *Request) GetPostInterfaces(key string, def ...interface{}) []interface{} { return r.GetPostVar(key, def...).Interfaces() } diff --git a/net/ghttp/ghttp_request_param_query.go b/net/ghttp/ghttp_request_param_query.go index 1b84ce5aa..b108b4b3c 100644 --- a/net/ghttp/ghttp_request_param_query.go +++ b/net/ghttp/ghttp_request_param_query.go @@ -1,4 +1,4 @@ -// Copyright 2017 gf Author(https://github.com/gogf/gf). All Rights Reserved. +// Copyright GoFrame Author(https://goframe.org). All Rights Reserved. // // This Source Code Form is subject to the terms of the MIT License. // If a copy of the MIT was not distributed with this file, @@ -196,13 +196,18 @@ func (r *Request) GetQueryMapStrVar(kvMap ...map[string]interface{}) map[string] // to the struct object. The optional parameter <mapping> is used to specify the key to // attribute mapping. func (r *Request) GetQueryStruct(pointer interface{}, mapping ...map[string]string) error { + _, err := r.doGetQueryStruct(pointer, mapping...) + return err +} + +func (r *Request) doGetQueryStruct(pointer interface{}, mapping ...map[string]string) (data map[string]interface{}, err error) { r.parseQuery() - data := r.GetQueryMap() + data = r.GetQueryMap() if data == nil { data = map[string]interface{}{} } if err := r.mergeDefaultStructValue(data, pointer); err != nil { - return nil + return data, nil } - return gconv.Struct(data, pointer, mapping...) + return data, gconv.Struct(data, pointer, mapping...) } diff --git a/net/ghttp/ghttp_request_param_request.go b/net/ghttp/ghttp_request_param_request.go index ff5396a10..2e62a1992 100644 --- a/net/ghttp/ghttp_request_param_request.go +++ b/net/ghttp/ghttp_request_param_request.go @@ -1,4 +1,4 @@ -// Copyright 2017 gf Author(https://github.com/gogf/gf). All Rights Reserved. +// Copyright GoFrame Author(https://goframe.org). All Rights Reserved. // // This Source Code Form is subject to the terms of the MIT License. // If a copy of the MIT was not distributed with this file, @@ -270,14 +270,19 @@ func (r *Request) GetRequestMapStrVar(kvMap ...map[string]interface{}) map[strin // the parameter <pointer> is a pointer to the struct object. // The optional parameter <mapping> is used to specify the key to attribute mapping. func (r *Request) GetRequestStruct(pointer interface{}, mapping ...map[string]string) error { - data := r.GetRequestMap() + _, err := r.doGetRequestStruct(pointer, mapping...) + return err +} + +func (r *Request) doGetRequestStruct(pointer interface{}, mapping ...map[string]string) (data map[string]interface{}, err error) { + data = r.GetRequestMap() if data == nil { data = map[string]interface{}{} } if err := r.mergeDefaultStructValue(data, pointer); err != nil { - return nil + return data, nil } - return gconv.Struct(data, pointer, mapping...) + return data, gconv.Struct(data, pointer, mapping...) } // mergeDefaultStructValue merges the request parameters with default values from struct tag definition. diff --git a/net/ghttp/ghttp_request_param_router.go b/net/ghttp/ghttp_request_param_router.go index 0d387a8ca..1641688b8 100644 --- a/net/ghttp/ghttp_request_param_router.go +++ b/net/ghttp/ghttp_request_param_router.go @@ -1,4 +1,4 @@ -// Copyright 2017 gf Author(https://github.com/gogf/gf). All Rights Reserved. +// Copyright GoFrame Author(https://goframe.org). All Rights Reserved. // // This Source Code Form is subject to the terms of the MIT License. // If a copy of the MIT was not distributed with this file, diff --git a/net/ghttp/ghttp_request_view.go b/net/ghttp/ghttp_request_view.go index acccea816..74244d4cb 100644 --- a/net/ghttp/ghttp_request_view.go +++ b/net/ghttp/ghttp_request_view.go @@ -1,4 +1,4 @@ -// Copyright 2019 gf Author(https://github.com/gogf/gf). All Rights Reserved. +// Copyright GoFrame Author(https://goframe.org). All Rights Reserved. // // This Source Code Form is subject to the terms of the MIT License. // If a copy of the MIT was not distributed with this file, diff --git a/net/ghttp/ghttp_response.go b/net/ghttp/ghttp_response.go index 507767421..819a3590d 100644 --- a/net/ghttp/ghttp_response.go +++ b/net/ghttp/ghttp_response.go @@ -1,4 +1,4 @@ -// Copyright 2017 gf Author(https://github.com/gogf/gf). All Rights Reserved. +// Copyright GoFrame Author(https://goframe.org). All Rights Reserved. // // This Source Code Form is subject to the terms of the MIT License. // If a copy of the MIT was not distributed with this file, @@ -41,32 +41,36 @@ func newResponse(s *Server, w http.ResponseWriter) *Response { // ServeFile serves the file to the response. func (r *Response) ServeFile(path string, allowIndex ...bool) { - serveFile := (*StaticFile)(nil) + var ( + serveFile *staticFile + ) if file := gres.Get(path); file != nil { - serveFile = &StaticFile{ + serveFile = &staticFile{ File: file, IsDir: file.FileInfo().IsDir(), } } else { - path = gfile.RealPath(path) + path, _ = gfile.Search(path) if path == "" { r.WriteStatus(http.StatusNotFound) return } - serveFile = &StaticFile{Path: path} + serveFile = &staticFile{Path: path} } r.Server.serveFile(r.Request, serveFile, allowIndex...) } // ServeFileDownload serves file downloading to the response. func (r *Response) ServeFileDownload(path string, name ...string) { - serveFile := (*StaticFile)(nil) + var ( + serveFile *staticFile + ) downloadName := "" if len(name) > 0 { downloadName = name[0] } if file := gres.Get(path); file != nil { - serveFile = &StaticFile{ + serveFile = &staticFile{ File: file, IsDir: file.FileInfo().IsDir(), } @@ -74,12 +78,12 @@ func (r *Response) ServeFileDownload(path string, name ...string) { downloadName = gfile.Basename(file.Name()) } } else { - path = gfile.RealPath(path) + path, _ = gfile.Search(path) if path == "" { r.WriteStatus(http.StatusNotFound) return } - serveFile = &StaticFile{Path: path} + serveFile = &staticFile{Path: path} if downloadName == "" { downloadName = gfile.Basename(path) } @@ -110,7 +114,7 @@ func (r *Response) RedirectBack(code ...int) { r.RedirectTo(r.Request.GetReferer(), code...) } -// BufferString returns the buffered content as []byte. +// Buffer returns the buffered content as []byte. func (r *Response) Buffer() []byte { return r.buffer.Bytes() } @@ -136,7 +140,7 @@ func (r *Response) ClearBuffer() { r.buffer.Reset() } -// Output outputs the buffer content to the client and clears the buffer. +// Flush outputs the buffer content to the client and clears the buffer. func (r *Response) Flush() { if r.Server.config.ServerAgent != "" { r.Header().Set("Server", r.Server.config.ServerAgent) diff --git a/net/ghttp/ghttp_response_cors.go b/net/ghttp/ghttp_response_cors.go index 8bf139a06..32206ed7b 100644 --- a/net/ghttp/ghttp_response_cors.go +++ b/net/ghttp/ghttp_response_cors.go @@ -1,4 +1,4 @@ -// Copyright GoFrame Author(https://github.com/gogf/gf). All Rights Reserved. +// Copyright GoFrame Author(https://goframe.org). All Rights Reserved. // // This Source Code Form is subject to the terms of the MIT License. // If a copy of the MIT was not distributed with this file, @@ -45,7 +45,7 @@ func init() { func (r *Response) DefaultCORSOptions() CORSOptions { options := CORSOptions{ AllowOrigin: "*", - AllowMethods: SupportedHttpMethods, + AllowMethods: supportedHttpMethods, AllowCredentials: "true", AllowHeaders: defaultAllowHeaders, MaxAge: 3628800, diff --git a/net/ghttp/ghttp_response_view.go b/net/ghttp/ghttp_response_view.go index bb7abf1af..47da241dc 100644 --- a/net/ghttp/ghttp_response_view.go +++ b/net/ghttp/ghttp_response_view.go @@ -1,4 +1,4 @@ -// Copyright 2017 gf Author(https://github.com/gogf/gf). All Rights Reserved. +// Copyright GoFrame Author(https://goframe.org). All Rights Reserved. // // This Source Code Form is subject to the terms of the MIT License. // If a copy of the MIT was not distributed with this file, @@ -10,6 +10,7 @@ package ghttp import ( "github.com/gogf/gf/os/gcfg" "github.com/gogf/gf/os/gview" + "github.com/gogf/gf/util/gconv" "github.com/gogf/gf/util/gmode" "github.com/gogf/gf/util/gutil" ) @@ -59,18 +60,18 @@ func (r *Response) WriteTplContent(content string, params ...gview.Params) error // ParseTpl parses given template file <tpl> with given template variables <params> // and returns the parsed template content. func (r *Response) ParseTpl(tpl string, params ...gview.Params) (string, error) { - return r.Request.GetView().Parse(tpl, r.buildInVars(params...)) + return r.Request.GetView().Parse(r.Request.Context(), tpl, r.buildInVars(params...)) } -// ParseDefault parses the default template file with params. +// ParseTplDefault parses the default template file with params. func (r *Response) ParseTplDefault(params ...gview.Params) (string, error) { - return r.Request.GetView().ParseDefault(r.buildInVars(params...)) + return r.Request.GetView().ParseDefault(r.Request.Context(), r.buildInVars(params...)) } // ParseTplContent parses given template file <file> with given template parameters <params> // and returns the parsed template content. func (r *Response) ParseTplContent(content string, params ...gview.Params) (string, error) { - return r.Request.GetView().ParseContent(content, r.buildInVars(params...)) + return r.Request.GetView().ParseContent(r.Request.Context(), content, r.buildInVars(params...)) } // buildInVars merges build-in variables into <params> and returns the new template variables. @@ -81,16 +82,18 @@ func (r *Response) buildInVars(params ...map[string]interface{}) map[string]inte gutil.MapMerge(m, params[0]) } // Retrieve custom template variables from request object. + sessionMap := gconv.MapDeep(r.Request.Session.Map()) gutil.MapMerge(m, map[string]interface{}{ "Form": r.Request.GetFormMap(), "Query": r.Request.GetQueryMap(), + "Request": r.Request.GetMap(), "Cookie": r.Request.Cookie.Map(), - "Session": r.Request.Session.Map(), + "Session": sessionMap, }) // Note that it should assign no Config variable to template // if there's no configuration file. if c := gcfg.Instance(); c.Available() { - m["Config"] = c.GetMap(".") + m["Config"] = c.Map() } return m } diff --git a/net/ghttp/ghttp_response_write.go b/net/ghttp/ghttp_response_write.go index ab6e5aedf..c7dbd10f8 100644 --- a/net/ghttp/ghttp_response_write.go +++ b/net/ghttp/ghttp_response_write.go @@ -1,4 +1,4 @@ -// Copyright 2017 gf Author(https://github.com/gogf/gf). All Rights Reserved. +// Copyright GoFrame Author(https://goframe.org). All Rights Reserved. // // This Source Code Form is subject to the terms of the MIT License. // If a copy of the MIT was not distributed with this file, @@ -71,7 +71,7 @@ func (r *Response) WritefExit(format string, params ...interface{}) { r.Request.Exit() } -// Writef writes the response with <content> and new line. +// Writeln writes the response with <content> and new line. func (r *Response) Writeln(content ...interface{}) { if len(content) == 0 { r.Write("\n") diff --git a/net/ghttp/ghttp_response_writer.go b/net/ghttp/ghttp_response_writer.go index c8cc4d6ab..7d87981ff 100644 --- a/net/ghttp/ghttp_response_writer.go +++ b/net/ghttp/ghttp_response_writer.go @@ -1,4 +1,4 @@ -// Copyright 2017 gf Author(https://github.com/gogf/gf). All Rights Reserved. +// Copyright GoFrame Author(https://goframe.org). All Rights Reserved. // // This Source Code Form is subject to the terms of the MIT License. // If a copy of the MIT was not distributed with this file, diff --git a/net/ghttp/ghttp_server.go b/net/ghttp/ghttp_server.go index b1ea3228e..1b2eef058 100644 --- a/net/ghttp/ghttp_server.go +++ b/net/ghttp/ghttp_server.go @@ -1,4 +1,4 @@ -// Copyright GoFrame Author(https://github.com/gogf/gf). All Rights Reserved. +// Copyright GoFrame Author(https://goframe.org). All Rights Reserved. // // This Source Code Form is subject to the terms of the MIT License. // If a copy of the MIT was not distributed with this file, @@ -8,9 +8,10 @@ package ghttp import ( "bytes" - "errors" - "fmt" + "context" "github.com/gogf/gf/debug/gdebug" + "github.com/gogf/gf/errors/gcode" + "github.com/gogf/gf/errors/gerror" "github.com/gogf/gf/internal/intlog" "net/http" "os" @@ -35,7 +36,7 @@ import ( func init() { // Initialize the methods map. - for _, v := range strings.Split(SupportedHttpMethods, ",") { + for _, v := range strings.Split(supportedHttpMethods, ",") { methodsMap[v] = struct{}{} } } @@ -71,15 +72,15 @@ func serverProcessInit() { // Process message handler. // It's enabled only graceful feature is enabled. if gracefulEnabled { - intlog.Printf("%d: graceful reload feature is enabled", gproc.Pid()) + intlog.Printf(context.TODO(), "%d: graceful reload feature is enabled", gproc.Pid()) go handleProcessMessage() } else { - intlog.Printf("%d: graceful reload feature is disabled", gproc.Pid()) + intlog.Printf(context.TODO(), "%d: graceful reload feature is disabled", gproc.Pid()) } // It's an ugly calling for better initializing the main package path // in source development environment. It is useful only be used in main goroutine. - // It fails retrieving the main package path in asynchronized goroutines. + // It fails retrieving the main package path in asynchronous goroutines. gfile.MainPkgPath() } @@ -107,7 +108,7 @@ func GetServer(name ...interface{}) *Server { } // Initialize the server using default configurations. if err := s.SetConfig(NewConfig()); err != nil { - panic(err) + panic(gerror.WrapCode(gcode.CodeInvalidConfiguration, err, "")) } // Record the server to internal server mapping by name. serverMapping.Set(serverName, s) @@ -125,13 +126,13 @@ func (s *Server) Start() error { // Server can only be run once. if s.Status() == ServerStatusRunning { - return errors.New("[ghttp] server is already running") + return gerror.NewCode(gcode.CodeInvalidOperation, "server is already running") } // Logging path setting check. - if s.config.LogPath != "" { + if s.config.LogPath != "" && s.config.LogPath != s.config.Logger.GetPath() { if err := s.config.Logger.SetPath(s.config.LogPath); err != nil { - return errors.New(fmt.Sprintf("[ghttp] set log path '%s' error: %v", s.config.LogPath, err)) + return err } } // Default session storage. @@ -141,7 +142,7 @@ func (s *Server) Start() error { path = gfile.Join(s.config.SessionPath, s.name) if !gfile.Exists(path) { if err := gfile.Mkdir(path); err != nil { - return errors.New(fmt.Sprintf("[ghttp] mkdir failed for '%s': %v", path, err)) + return gerror.WrapCodef(gcode.CodeInternalError, err, `mkdir failed for "%s"`, path) } } } @@ -175,7 +176,10 @@ func (s *Server) Start() error { // 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?`) + return gerror.NewCode( + gcode.CodeInvalidOperation, + `there's no route set or static feature enabled, did you forget import the router?`, + ) } // Start the HTTP server. @@ -194,9 +198,9 @@ func (s *Server) Start() error { // If this is a child process, it then notifies its parent exit. if gproc.IsChild() { - gtimer.SetTimeout(2*time.Second, func() { + gtimer.SetTimeout(time.Duration(s.config.GracefulTimeout)*time.Second, func() { if err := gproc.Send(gproc.PPid(), []byte("exit"), adminGProcCommGroup); err != nil { - //glog.Error("[ghttp] server error in process communication:", err) + intlog.Error(context.TODO(), "server error in process communication:", err) } }) } @@ -221,7 +225,7 @@ func (s *Server) dumpRouterMap() { data[2] = item.Address data[3] = item.Method data[4] = item.Route - data[5] = item.handler.itemName + data[5] = item.handler.Name data[6] = item.Middleware table.Append(data) } @@ -248,28 +252,28 @@ func (s *Server) GetRouterArray() []RouterItem { Server: s.name, Address: address, Domain: array[4], - Type: registeredItem.handler.itemType, + Type: registeredItem.Handler.Type, Middleware: array[1], Method: array[2], Route: array[3], Priority: len(registeredItems) - index - 1, - handler: registeredItem.handler, + handler: registeredItem.Handler, } - switch item.handler.itemType { + switch item.handler.Type { case handlerTypeController, handlerTypeObject, handlerTypeHandler: item.IsServiceHandler = true case handlerTypeMiddleware: item.Middleware = "GLOBAL MIDDLEWARE" } - if len(item.handler.middleware) > 0 { - for _, v := range item.handler.middleware { + if len(item.handler.Middleware) > 0 { + for _, v := range item.handler.Middleware { if item.Middleware != "" { item.Middleware += "," } item.Middleware += gdebug.FuncName(v) } } - // If the domain does not exist in the dump map, it create the map. + // If the domain does not exist in the dump map, it creates the map. // The value of the map is a custom sorted array. if _, ok := m[item.Domain]; !ok { // Sort in ASC order. @@ -280,9 +284,9 @@ func (s *Server) GetRouterArray() []RouterItem { if r = strings.Compare(item1.Domain, item2.Domain); r == 0 { if r = strings.Compare(item1.Route, item2.Route); r == 0 { if r = strings.Compare(item1.Method, item2.Method); r == 0 { - if item1.handler.itemType == handlerTypeMiddleware && item2.handler.itemType != handlerTypeMiddleware { + if item1.handler.Type == handlerTypeMiddleware && item2.handler.Type != handlerTypeMiddleware { return -1 - } else if item1.handler.itemType == handlerTypeMiddleware && item2.handler.itemType == handlerTypeMiddleware { + } else if item1.handler.Type == handlerTypeMiddleware && item2.handler.Type == handlerTypeMiddleware { return 1 } else if r = strings.Compare(item1.Middleware, item2.Middleware); r == 0 { r = item2.Priority - item1.Priority @@ -316,11 +320,13 @@ func (s *Server) Run() { // Remove plugins. if len(s.plugins) > 0 { for _, p := range s.plugins { - intlog.Printf(`remove plugin: %s`, p.Name()) - p.Remove() + intlog.Printf(context.TODO(), `remove plugin: %s`, p.Name()) + if err := p.Remove(); err != nil { + intlog.Errorf(context.TODO(), "%+v", err) + } } } - s.Logger().Printf("[ghttp] %d: all servers shutdown", gproc.Pid()) + s.Logger().Printf("%d: all servers shutdown", gproc.Pid()) } // Wait blocks to wait for all servers done. @@ -332,13 +338,13 @@ func Wait() { s := v.(*Server) if len(s.plugins) > 0 { for _, p := range s.plugins { - intlog.Printf(`remove plugin: %s`, p.Name()) + intlog.Printf(context.TODO(), `remove plugin: %s`, p.Name()) p.Remove() } } return true }) - glog.Printf("[ghttp] %d: all servers shutdown", gproc.Pid()) + glog.Printf("%d: all servers shutdown", gproc.Pid()) } // startServer starts the underlying server listening. @@ -351,7 +357,7 @@ func (s *Server) startServer(fdMap listenerFdMap) { s.config.HTTPSAddr = s.config.Address s.config.Address = "" } else { - s.config.HTTPSAddr = gDEFAULT_HTTPS_ADDR + s.config.HTTPSAddr = defaultHttpsAddr } } httpsEnabled = len(s.config.HTTPSAddr) > 0 @@ -386,7 +392,7 @@ func (s *Server) startServer(fdMap listenerFdMap) { } // HTTP if !httpsEnabled && len(s.config.Address) == 0 { - s.config.Address = gDEFAULT_HTTP_ADDR + s.config.Address = defaultHttpAddr } var array []string if v, ok := fdMap["http"]; ok && len(v) > 0 { @@ -415,7 +421,7 @@ func (s *Server) startServer(fdMap listenerFdMap) { s.servers = append(s.servers, s.newGracefulServer(itemFunc)) } } - // Start listening asynchronizedly. + // Start listening asynchronously. serverRunning.Add(1) for _, v := range s.servers { go func(server *gracefulServer) { diff --git a/net/ghttp/ghttp_server_admin.go b/net/ghttp/ghttp_server_admin.go index 12ffe619f..dc4b107ea 100644 --- a/net/ghttp/ghttp_server_admin.go +++ b/net/ghttp/ghttp_server_admin.go @@ -1,4 +1,4 @@ -// Copyright GoFrame Author(https://github.com/gogf/gf). All Rights Reserved. +// Copyright GoFrame Author(https://goframe.org). All Rights Reserved. // // This Source Code Form is subject to the terms of the MIT License. // If a copy of the MIT was not distributed with this file, @@ -26,7 +26,7 @@ func (p *utilAdmin) Index(r *Request) { "path": gfile.SelfPath(), "uri": strings.TrimRight(r.URL.Path, "/"), } - buffer, _ := gview.ParseContent(` + buffer, _ := gview.ParseContent(r.Context(), ` <html> <head> <title>GoFrame Web Server Admin</title> diff --git a/net/ghttp/ghttp_server_admin_process.go b/net/ghttp/ghttp_server_admin_process.go index 18299e8b6..3f854dcbb 100644 --- a/net/ghttp/ghttp_server_admin_process.go +++ b/net/ghttp/ghttp_server_admin_process.go @@ -1,4 +1,4 @@ -// Copyright GoFrame Author(https://github.com/gogf/gf). All Rights Reserved. +// Copyright GoFrame Author(https://goframe.org). All Rights Reserved. // // This Source Code Form is subject to the terms of the MIT License. // If a copy of the MIT was not distributed with this file, @@ -8,8 +8,10 @@ package ghttp import ( "bytes" - "errors" + "context" "fmt" + "github.com/gogf/gf/errors/gcode" + "github.com/gogf/gf/errors/gerror" "github.com/gogf/gf/internal/intlog" "github.com/gogf/gf/text/gstr" "os" @@ -51,7 +53,7 @@ var serverProcessStatus = gtype.NewInt() // The optional parameter <newExeFilePath> specifies the new binary file for creating process. func RestartAllServer(newExeFilePath ...string) error { if !gracefulEnabled { - return errors.New("graceful reload feature is disabled") + return gerror.NewCode(gcode.CodeInvalidOperation, "graceful reload feature is disabled") } serverActionLocker.Lock() defer serverActionLocker.Unlock() @@ -84,9 +86,10 @@ func checkProcessStatus() error { if status > 0 { switch status { case adminActionRestarting: - return errors.New("server is restarting") + return gerror.NewCode(gcode.CodeInvalidOperation, "server is restarting") + case adminActionShuttingDown: - return errors.New("server is shutting down") + return gerror.NewCode(gcode.CodeInvalidOperation, "server is shutting down") } } return nil @@ -97,7 +100,11 @@ func checkProcessStatus() error { func checkActionFrequency() error { interval := gtime.TimestampMilli() - serverActionLastTime.Val() if interval < adminActionIntervalLimit { - return errors.New(fmt.Sprintf("too frequent action, please retry in %d ms", adminActionIntervalLimit-interval)) + return gerror.NewCodef( + gcode.CodeInvalidOperation, + "too frequent action, please retry in %d ms", + adminActionIntervalLimit-interval, + ) } serverActionLastTime.Set(gtime.TimestampMilli()) return nil @@ -173,7 +180,7 @@ func bufferToServerFdMap(buffer []byte) map[string]listenerFdMap { sfm := make(map[string]listenerFdMap) if len(buffer) > 0 { j, _ := gjson.LoadContent(buffer) - for k, _ := range j.ToMap() { + for k, _ := range j.Map() { m := make(map[string]string) for k, v := range j.GetMap(k) { m[k] = gconv.String(v) @@ -233,8 +240,13 @@ func shutdownWebServers(signal ...string) { } } -// gracefulShutdownWebServers gracefully shuts down all servers. -func gracefulShutdownWebServers() { +// shutdownWebServersGracefully gracefully shuts down all servers. +func shutdownWebServersGracefully(signal ...string) { + if len(signal) > 0 { + glog.Printf("%d: server gracefully shutting down by signal: %s", gproc.Pid(), signal[0]) + } else { + glog.Printf("%d: server gracefully shutting down by api", gproc.Pid()) + } serverMapping.RLockFunc(func(m map[string]interface{}) { for _, v := range m { for _, s := range v.(*Server).servers { @@ -261,10 +273,10 @@ func handleProcessMessage() { for { if msg := gproc.Receive(adminGProcCommGroup); msg != nil { if bytes.EqualFold(msg.Data, []byte("exit")) { - intlog.Printf("%d: process message: exit", gproc.Pid()) - gracefulShutdownWebServers() + intlog.Printf(context.TODO(), "%d: process message: exit", gproc.Pid()) + shutdownWebServersGracefully() allDoneChan <- struct{}{} - intlog.Printf("%d: process message: exit done", gproc.Pid()) + intlog.Printf(context.TODO(), "%d: process message: exit done", gproc.Pid()) return } } diff --git a/net/ghttp/ghttp_server_admin_unix.go b/net/ghttp/ghttp_server_admin_unix.go index 19fff62af..3e5591473 100644 --- a/net/ghttp/ghttp_server_admin_unix.go +++ b/net/ghttp/ghttp_server_admin_unix.go @@ -1,4 +1,4 @@ -// Copyright 2017 gf Author(https://github.com/gogf/gf). All Rights Reserved. +// Copyright GoFrame Author(https://goframe.org). All Rights Reserved. // // This Source Code Form is subject to the terms of the MIT License. // If a copy of the MIT was not distributed with this file, @@ -9,6 +9,7 @@ package ghttp import ( + "context" "github.com/gogf/gf/internal/intlog" "os" "os/signal" @@ -33,16 +34,24 @@ func handleProcessSignal() { ) for { sig = <-procSignalChan - intlog.Printf(`signal received: %s`, sig.String()) + intlog.Printf(context.TODO(), `signal received: %s`, sig.String()) switch sig { - // Stop the servers. - case syscall.SIGINT, syscall.SIGQUIT, syscall.SIGKILL, syscall.SIGTERM, syscall.SIGABRT: + // Shutdown the servers. + case syscall.SIGINT, syscall.SIGQUIT, syscall.SIGKILL, syscall.SIGABRT: shutdownWebServers(sig.String()) return + // Shutdown the servers gracefully. + // Especially from K8S when running server in POD. + case syscall.SIGTERM: + shutdownWebServersGracefully(sig.String()) + return + // Restart the servers. case syscall.SIGUSR1: - restartWebServers(sig.String()) + if err := restartWebServers(sig.String()); err != nil { + intlog.Error(context.TODO(), err) + } return default: diff --git a/net/ghttp/ghttp_server_admin_windows.go b/net/ghttp/ghttp_server_admin_windows.go index 7a830d579..bf914ad14 100644 --- a/net/ghttp/ghttp_server_admin_windows.go +++ b/net/ghttp/ghttp_server_admin_windows.go @@ -1,4 +1,4 @@ -// Copyright 2018 gf Author(https://github.com/gogf/gf). All Rights Reserved. +// Copyright GoFrame Author(https://goframe.org). All Rights Reserved. // // This Source Code Form is subject to the terms of the MIT License. // If a copy of the MIT was not distributed with this file, @@ -8,7 +8,7 @@ package ghttp -// handleProcessSignal does nothing on windows platform. +// registerSignalHandler does nothing on windows platform. func handleProcessSignal() { } diff --git a/net/ghttp/ghttp_server_config.go b/net/ghttp/ghttp_server_config.go index 3cc4a1b95..9f2e8be33 100644 --- a/net/ghttp/ghttp_server_config.go +++ b/net/ghttp/ghttp_server_config.go @@ -1,4 +1,4 @@ -// Copyright 2017 gf Author(https://github.com/gogf/gf). All Rights Reserved. +// Copyright GoFrame Author(https://goframe.org). All Rights Reserved. // // This Source Code Form is subject to the terms of the MIT License. // If a copy of the MIT was not distributed with this file, @@ -7,6 +7,7 @@ package ghttp import ( + "context" "crypto/tls" "fmt" "github.com/gogf/gf/internal/intlog" @@ -27,12 +28,16 @@ import ( ) const ( - gDEFAULT_HTTP_ADDR = ":80" // Default listening port for HTTP. - gDEFAULT_HTTPS_ADDR = ":443" // Default listening port for HTTPS. - URI_TYPE_DEFAULT = 0 // Method name to URI converting type, which converts name to its lower case and joins the words using char '-'. - URI_TYPE_FULLNAME = 1 // Method name to URI converting type, which does no converting to the method name. - URI_TYPE_ALLLOWER = 2 // Method name to URI converting type, which converts name to its lower case. - URI_TYPE_CAMEL = 3 // Method name to URI converting type, which converts name to its camel case. + defaultHttpAddr = ":80" // Default listening port for HTTP. + defaultHttpsAddr = ":443" // Default listening port for HTTPS. + URI_TYPE_DEFAULT = 0 // Deprecated, please use UriTypeDefault instead. + URI_TYPE_FULLNAME = 1 // Deprecated, please use UriTypeFullName instead. + URI_TYPE_ALLLOWER = 2 // Deprecated, please use UriTypeAllLower instead. + URI_TYPE_CAMEL = 3 // Deprecated, please use UriTypeCamel instead. + UriTypeDefault = 0 // Method name to URI converting type, which converts name to its lower case and joins the words using char '-'. + UriTypeFullName = 1 // Method name to URI converting type, which does no converting to the method name. + UriTypeAllLower = 2 // Method name to URI converting type, which converts name to its lower case. + UriTypeCamel = 3 // Method name to URI converting type, which converts name to its camel case. ) // ServerConfig is the HTTP Server configuration manager. @@ -43,16 +48,16 @@ type ServerConfig struct { // Address specifies the server listening address like "port" or ":port", // multiple addresses joined using ','. - Address string + Address string `json:"address"` // HTTPSAddr specifies the HTTPS addresses, multiple addresses joined using char ','. - HTTPSAddr string + HTTPSAddr string `json:"httpsAddr"` // HTTPSCertPath specifies certification file path for HTTPS service. - HTTPSCertPath string + HTTPSCertPath string `json:"httpsCertPath"` // HTTPSKeyPath specifies the key file path for HTTPS service. - HTTPSKeyPath string + HTTPSKeyPath string `json:"httpsKeyPath"` // TLSConfig optionally provides a TLS configuration for use // by ServeTLS and ListenAndServeTLS. Note that this value is @@ -61,10 +66,10 @@ type ServerConfig struct { // tls.Config.SetSessionTicketKeys. To use // SetSessionTicketKeys, use Server.Serve with a TLS Listener // instead. - TLSConfig *tls.Config + TLSConfig *tls.Config `json:"tlsConfig"` // Handler the handler for HTTP request. - Handler http.Handler + Handler http.Handler `json:"-"` // ReadTimeout is the maximum duration for reading the entire // request, including the body. @@ -73,19 +78,19 @@ type ServerConfig struct { // decisions on each request body's acceptable deadline or // upload rate, most users will prefer to use // ReadHeaderTimeout. It is valid to use them both. - ReadTimeout time.Duration + ReadTimeout time.Duration `json:"readTimeout"` // WriteTimeout is the maximum duration before timing out // writes of the response. It is reset whenever a new // request's header is read. Like ReadTimeout, it does not // let Handlers make decisions on a per-request basis. - WriteTimeout time.Duration + WriteTimeout time.Duration `json:"writeTimeout"` // IdleTimeout is the maximum amount of time to wait for the // next request when keep-alives are enabled. If IdleTimeout // is zero, the value of ReadTimeout is used. If both are // zero, there is no timeout. - IdleTimeout time.Duration + IdleTimeout time.Duration `json:"idleTimeout"` // MaxHeaderBytes controls the maximum number of bytes the // server will read parsing the request header's keys and @@ -94,117 +99,102 @@ type ServerConfig struct { // // It can be configured in configuration file using string like: 1m, 10m, 500kb etc. // It's 10240 bytes in default. - MaxHeaderBytes int + MaxHeaderBytes int `json:"maxHeaderBytes"` // KeepAlive enables HTTP keep-alive. - KeepAlive bool + KeepAlive bool `json:"keepAlive"` // ServerAgent specifies the server agent information, which is wrote to // HTTP response header as "Server". - ServerAgent string + ServerAgent string `json:"serverAgent"` // View specifies the default template view object for the server. - View *gview.View + View *gview.View `json:"view"` // ================================== // Static. // ================================== // Rewrites specifies the URI rewrite rules map. - Rewrites map[string]string + Rewrites map[string]string `json:"rewrites"` // IndexFiles specifies the index files for static folder. - IndexFiles []string + IndexFiles []string `json:"indexFiles"` // IndexFolder specifies if listing sub-files when requesting folder. // The server responses HTTP status code 403 if it is false. - IndexFolder bool + IndexFolder bool `json:"indexFolder"` // ServerRoot specifies the root directory for static service. - ServerRoot string + ServerRoot string `json:"serverRoot"` // SearchPaths specifies additional searching directories for static service. - SearchPaths []string + SearchPaths []string `json:"searchPaths"` // StaticPaths specifies URI to directory mapping array. - StaticPaths []staticPathItem + StaticPaths []staticPathItem `json:"staticPaths"` // FileServerEnabled is the global switch for static service. // It is automatically set enabled if any static path is set. - FileServerEnabled bool + FileServerEnabled bool `json:"fileServerEnabled"` // ================================== // Cookie. // ================================== // CookieMaxAge specifies the max TTL for cookie items. - CookieMaxAge time.Duration + CookieMaxAge time.Duration `json:"cookieMaxAge"` // CookiePath specifies cookie path. // It also affects the default storage for session id. - CookiePath string + CookiePath string `json:"cookiePath"` // CookieDomain specifies cookie domain. // It also affects the default storage for session id. - CookieDomain string + CookieDomain string `json:"cookieDomain"` // ================================== // Session. // ================================== - // SessionMaxAge specifies max TTL for session items. - SessionMaxAge time.Duration - // SessionIdName specifies the session id name. - SessionIdName string + SessionIdName string `json:"sessionIdName"` - // SessionCookieOutput specifies whether automatic outputting session id to cookie. - SessionCookieOutput bool + // SessionMaxAge specifies max TTL for session items. + SessionMaxAge time.Duration `json:"sessionMaxAge"` // 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 + SessionPath string `json:"sessionPath"` // SessionStorage specifies the session storage. - SessionStorage gsession.Storage + SessionStorage gsession.Storage `json:"sessionStorage"` + + // SessionCookieMaxAge specifies the cookie ttl for session id. + // It it is set 0, it means it expires along with browser session. + SessionCookieMaxAge time.Duration `json:"sessionCookieMaxAge"` + + // SessionCookieOutput specifies whether automatic outputting session id to cookie. + SessionCookieOutput bool `json:"sessionCookieOutput"` // ================================== // Logging. // ================================== - - // Logger specifies the logger for server. - Logger *glog.Logger - - // LogPath specifies the directory for storing logging files. - LogPath string - - // LogStdout specifies whether printing logging content to stdout. - LogStdout bool - - // ErrorStack specifies whether logging stack information when error. - ErrorStack bool - - // ErrorLogEnabled enables error logging content to files. - ErrorLogEnabled bool - - // ErrorLogPattern specifies the error log file pattern like: error-{Ymd}.log - ErrorLogPattern string - - // AccessLogEnabled enables access logging content to files. - AccessLogEnabled bool - - // AccessLogPattern specifies the error log file pattern like: access-{Ymd}.log - AccessLogPattern string + Logger *glog.Logger `json:"logger"` // Logger specifies the logger for server. + LogPath string `json:"logPath"` // LogPath specifies the directory for storing logging files. + LogLevel string `json:"logLevel"` // LogLevel specifies the logging level for logger. + LogStdout bool `json:"logStdout"` // LogStdout specifies whether printing logging content to stdout. + ErrorStack bool `json:"errorStack"` // ErrorStack specifies whether logging stack information when error. + ErrorLogEnabled bool `json:"errorLogEnabled"` // ErrorLogEnabled enables error logging content to files. + ErrorLogPattern string `json:"errorLogPattern"` // ErrorLogPattern specifies the error log file pattern like: error-{Ymd}.log + AccessLogEnabled bool `json:"accessLogEnabled"` // AccessLogEnabled enables access logging content to files. + AccessLogPattern string `json:"accessLogPattern"` // AccessLogPattern specifies the error log file pattern like: access-{Ymd}.log // ================================== // PProf. // ================================== - - // PProfEnabled enables PProf feature. - PProfEnabled bool - - // PProfPattern specifies the PProf service pattern for router. - PProfPattern string + PProfEnabled bool `json:"pprofEnabled"` // PProfEnabled enables PProf feature. + PProfPattern string `json:"pprofPattern"` // PProfPattern specifies the PProf service pattern for router. // ================================== // Other. @@ -213,35 +203,39 @@ type ServerConfig struct { // ClientMaxBodySize specifies the max body size limit in bytes for client request. // It can be configured in configuration file using string like: 1m, 10m, 500kb etc. // It's 8MB in default. - ClientMaxBodySize int64 + ClientMaxBodySize int64 `json:"clientMaxBodySize"` // FormParsingMemory specifies max memory buffer size in bytes which can be used for // parsing multimedia form. // It can be configured in configuration file using string like: 1m, 10m, 500kb etc. // It's 1MB in default. - FormParsingMemory int64 + FormParsingMemory int64 `json:"formParsingMemory"` // NameToUriType specifies the type for converting struct method name to URI when // registering routes. - NameToUriType int + NameToUriType int `json:"nameToUriType"` // RouteOverWrite allows overwrite the route if duplicated. - RouteOverWrite bool + RouteOverWrite bool `json:"routeOverWrite"` // DumpRouterMap specifies whether automatically dumps router map when server starts. - DumpRouterMap bool + DumpRouterMap bool `json:"dumpRouterMap"` // Graceful enables graceful reload feature for all servers of the process. - Graceful bool + Graceful bool `json:"graceful"` + + // GracefulTimeout set the maximum survival time (seconds) of the parent process. + GracefulTimeout uint8 `json:"gracefulTimeout"` } +// 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 +// Note that, do not define this default configuration to local package variable, as there are // some pointer attributes that may be shared in different servers. func NewConfig() ServerConfig { return ServerConfig{ @@ -262,11 +256,13 @@ func NewConfig() ServerConfig { CookieMaxAge: time.Hour * 24 * 365, CookiePath: "/", CookieDomain: "", - SessionMaxAge: time.Hour * 24, SessionIdName: "gfsessionid", SessionPath: gsession.DefaultStorageFilePath, + SessionMaxAge: time.Hour * 24, SessionCookieOutput: true, + SessionCookieMaxAge: time.Hour * 24, Logger: glog.New(), + LogLevel: "all", LogStdout: true, ErrorStack: true, ErrorLogEnabled: true, @@ -278,6 +274,7 @@ func NewConfig() ServerConfig { FormParsingMemory: 1024 * 1024, // 1MB Rewrites: make(map[string]string), Graceful: false, + GracefulTimeout: 2, // seconds } } @@ -334,8 +331,18 @@ func (s *Server) SetConfig(c ServerConfig) error { if c.TLSConfig == nil && c.HTTPSCertPath != "" { s.EnableHTTPS(c.HTTPSCertPath, c.HTTPSKeyPath) } + // Logging. + if s.config.LogPath != "" && s.config.LogPath != s.config.Logger.GetPath() { + if err := s.config.Logger.SetPath(s.config.LogPath); err != nil { + return err + } + } + if err := s.config.Logger.SetLevelStr(s.config.LogLevel); err != nil { + intlog.Error(context.TODO(), err) + } + SetGraceful(c.Graceful) - intlog.Printf("SetConfig: %+v", s.config) + intlog.Printf(context.TODO(), "SetConfig: %+v", s.config) return nil } @@ -393,7 +400,7 @@ func (s *Server) EnableHTTPS(certFile, keyFile string, tlsConfig ...*tls.Config) certFileRealPath = certFile } if certFileRealPath == "" { - s.Logger().Fatal(fmt.Sprintf(`[ghttp] EnableHTTPS failed: certFile "%s" does not exist`, certFile)) + s.Logger().Fatal(fmt.Sprintf(`EnableHTTPS failed: certFile "%s" does not exist`, certFile)) } keyFileRealPath := gfile.RealPath(keyFile) if keyFileRealPath == "" { @@ -407,7 +414,7 @@ func (s *Server) EnableHTTPS(certFile, keyFile string, tlsConfig ...*tls.Config) keyFileRealPath = keyFile } if keyFileRealPath == "" { - s.Logger().Fatal(fmt.Sprintf(`[ghttp] EnableHTTPS failed: keyFile "%s" does not exist`, keyFile)) + s.Logger().Fatal(fmt.Sprintf(`EnableHTTPS failed: keyFile "%s" does not exist`, keyFile)) } s.config.HTTPSCertPath = certFileRealPath s.config.HTTPSKeyPath = keyFileRealPath diff --git a/net/ghttp/ghttp_server_config_cookie.go b/net/ghttp/ghttp_server_config_cookie.go index 21dc2a289..266462028 100644 --- a/net/ghttp/ghttp_server_config_cookie.go +++ b/net/ghttp/ghttp_server_config_cookie.go @@ -1,4 +1,4 @@ -// Copyright 2017 gf Author(https://github.com/gogf/gf). All Rights Reserved. +// Copyright GoFrame Author(https://goframe.org). All Rights Reserved. // // This Source Code Form is subject to the terms of the MIT License. // If a copy of the MIT was not distributed with this file, diff --git a/net/ghttp/ghttp_server_config_logging.go b/net/ghttp/ghttp_server_config_logging.go index 8643b56d4..471ea876c 100644 --- a/net/ghttp/ghttp_server_config_logging.go +++ b/net/ghttp/ghttp_server_config_logging.go @@ -1,4 +1,4 @@ -// Copyright 2017 gf Author(https://github.com/gogf/gf). All Rights Reserved. +// Copyright GoFrame Author(https://goframe.org). All Rights Reserved. // // This Source Code Form is subject to the terms of the MIT License. // If a copy of the MIT was not distributed with this file, @@ -6,18 +6,39 @@ package ghttp -import "github.com/gogf/gf/internal/intlog" +import "github.com/gogf/gf/os/glog" // SetLogPath sets the log path for server. // It logs content to file only if the log path is set. -func (s *Server) SetLogPath(path string) { +func (s *Server) SetLogPath(path string) error { if len(path) == 0 { - return + return nil } - intlog.Print("SetLogPath:", path) s.config.LogPath = path s.config.ErrorLogEnabled = true s.config.AccessLogEnabled = true + if s.config.LogPath != "" && s.config.LogPath != s.config.Logger.GetPath() { + if err := s.config.Logger.SetPath(s.config.LogPath); err != nil { + return err + } + } + return nil +} + +// SetLogger sets the logger for logging responsibility. +// Note that it cannot be set in runtime as there may be concurrent safety issue. +func (s *Server) SetLogger(logger *glog.Logger) { + s.config.Logger = logger +} + +// Logger is alias of GetLogger. +func (s *Server) Logger() *glog.Logger { + return s.config.Logger +} + +// SetLogLevel sets logging level by level string. +func (s *Server) SetLogLevel(level string) { + s.config.LogLevel = level } // SetLogStdout sets whether output the logging content to stdout. diff --git a/net/ghttp/ghttp_server_config_mess.go b/net/ghttp/ghttp_server_config_mess.go index ffcbc998c..ebf2de3e2 100644 --- a/net/ghttp/ghttp_server_config_mess.go +++ b/net/ghttp/ghttp_server_config_mess.go @@ -1,4 +1,4 @@ -// Copyright 2019 gf Author(https://github.com/gogf/gf). All Rights Reserved. +// Copyright GoFrame Author(https://goframe.org). All Rights Reserved. // // This Source Code Form is subject to the terms of the MIT License. // If a copy of the MIT was not distributed with this file, diff --git a/net/ghttp/ghttp_server_config_route.go b/net/ghttp/ghttp_server_config_route.go index a931f5ecf..1fcde0eb5 100644 --- a/net/ghttp/ghttp_server_config_route.go +++ b/net/ghttp/ghttp_server_config_route.go @@ -1,4 +1,4 @@ -// Copyright 2018 gf Author(https://github.com/gogf/gf). All Rights Reserved. +// Copyright GoFrame Author(https://goframe.org). All Rights Reserved. // // This Source Code Form is subject to the terms of the MIT License. // If a copy of the MIT was not distributed with this file, diff --git a/net/ghttp/ghttp_server_config_session.go b/net/ghttp/ghttp_server_config_session.go index e9f712629..205eae613 100644 --- a/net/ghttp/ghttp_server_config_session.go +++ b/net/ghttp/ghttp_server_config_session.go @@ -1,4 +1,4 @@ -// Copyright 2017 gf Author(https://github.com/gogf/gf). All Rights Reserved. +// Copyright GoFrame Author(https://goframe.org). All Rights Reserved. // // This Source Code Form is subject to the terms of the MIT License. // If a copy of the MIT was not distributed with this file, @@ -32,6 +32,11 @@ func (s *Server) SetSessionCookieOutput(enabled bool) { s.config.SessionCookieOutput = enabled } +// SetSessionCookieMaxAge sets the SessionCookieMaxAge for server. +func (s *Server) SetSessionCookieMaxAge(maxAge time.Duration) { + s.config.SessionCookieMaxAge = maxAge +} + // GetSessionMaxAge returns the SessionMaxAge of server. func (s *Server) GetSessionMaxAge() time.Duration { return s.config.SessionMaxAge @@ -41,3 +46,8 @@ func (s *Server) GetSessionMaxAge() time.Duration { func (s *Server) GetSessionIdName() string { return s.config.SessionIdName } + +// GetSessionCookieMaxAge returns the SessionCookieMaxAge of server. +func (s *Server) GetSessionCookieMaxAge() time.Duration { + return s.config.SessionCookieMaxAge +} diff --git a/net/ghttp/ghttp_server_config_static.go b/net/ghttp/ghttp_server_config_static.go index 8056acf3a..c71d6faf2 100644 --- a/net/ghttp/ghttp_server_config_static.go +++ b/net/ghttp/ghttp_server_config_static.go @@ -1,10 +1,10 @@ -// Copyright 2017 gf Author(https://github.com/gogf/gf). All Rights Reserved. +// Copyright GoFrame Author(https://goframe.org). All Rights Reserved. // // This Source Code Form is subject to the terms of the MIT License. // If a copy of the MIT was not distributed with this file, // You can obtain one at https://github.com/gogf/gf. -// 静态文件搜索优先级: Resource > ServerPaths > ServerRoot > SearchPath +// Static Searching Priority: Resource > ServerPaths > ServerRoot > SearchPath package ghttp @@ -53,12 +53,12 @@ func (s *Server) SetServerRoot(root string) { realPath := root if !gres.Contains(realPath) { if p, err := gfile.Search(root); err != nil { - s.Logger().Fatal(fmt.Sprintf(`[ghttp] SetServerRoot failed: %v`, err)) + s.Logger().Fatal(fmt.Sprintf(`SetServerRoot failed: %v`, err)) } else { realPath = p } } - s.Logger().Debug("[ghttp] SetServerRoot path:", realPath) + s.Logger().Debug("SetServerRoot path:", realPath) s.config.SearchPaths = []string{strings.TrimRight(realPath, gfile.Separator)} s.config.FileServerEnabled = true } @@ -68,7 +68,7 @@ func (s *Server) AddSearchPath(path string) { realPath := path if !gres.Contains(realPath) { if p, err := gfile.Search(path); err != nil { - s.Logger().Fatal(fmt.Sprintf(`[ghttp] AddSearchPath failed: %v`, err)) + s.Logger().Fatal(fmt.Sprintf(`AddSearchPath failed: %v`, err)) } else { realPath = p } @@ -82,7 +82,7 @@ func (s *Server) AddStaticPath(prefix string, path string) { realPath := path if !gres.Contains(realPath) { if p, err := gfile.Search(path); err != nil { - s.Logger().Fatal(fmt.Sprintf(`[ghttp] AddStaticPath failed: %v`, err)) + s.Logger().Fatal(fmt.Sprintf(`AddStaticPath failed: %v`, err)) } else { realPath = p } diff --git a/net/ghttp/ghttp_server_cookie.go b/net/ghttp/ghttp_server_cookie.go index af7f7473c..6647e9d83 100644 --- a/net/ghttp/ghttp_server_cookie.go +++ b/net/ghttp/ghttp_server_cookie.go @@ -1,4 +1,4 @@ -// Copyright 2017 gf Author(https://github.com/gogf/gf). All Rights Reserved. +// Copyright GoFrame Author(https://goframe.org). All Rights Reserved. // // This Source Code Form is subject to the terms of the MIT License. // If a copy of the MIT was not distributed with this file, @@ -14,9 +14,6 @@ import ( // Cookie for HTTP COOKIE management. type Cookie struct { data map[string]*cookieItem // Underlying cookie items. - path string // The default cookie path. - domain string // The default cookie domain - maxAge time.Duration // The default cookie max age. server *Server // Belonged HTTP server request *Request // Belonged HTTP request. response *Response // Belonged HTTP response. @@ -47,13 +44,10 @@ func (c *Cookie) init() { return } c.data = make(map[string]*cookieItem) - c.path = c.request.Server.GetCookiePath() - c.domain = c.request.Server.GetCookieDomain() - c.maxAge = c.request.Server.GetCookieMaxAge() c.response = c.request.Response // DO NOT ADD ANY DEFAULT COOKIE DOMAIN! - //if c.domain == "" { - // c.domain = c.request.GetHost() + //if c.request.Server.GetCookieDomain() == "" { + // c.request.Server.GetCookieDomain() = c.request.GetHost() //} for _, v := range c.request.Cookies() { c.data[v.Name] = &cookieItem{ @@ -86,7 +80,13 @@ func (c *Cookie) Contains(key string) bool { // Set sets cookie item with default domain, path and expiration age. func (c *Cookie) Set(key, value string) { - c.SetCookie(key, value, c.domain, c.path, c.maxAge) + c.SetCookie( + key, + value, + c.request.Server.GetCookieDomain(), + c.request.Server.GetCookiePath(), + c.request.Server.GetCookieMaxAge(), + ) } // SetCookie sets cookie item given given domain, path and expiration age. @@ -128,7 +128,13 @@ func (c *Cookie) GetSessionId() string { // SetSessionId sets session id in the cookie. func (c *Cookie) SetSessionId(id string) { - c.Set(c.server.GetSessionIdName(), id) + c.SetCookie( + c.server.GetSessionIdName(), + id, + c.request.Server.GetCookieDomain(), + c.request.Server.GetCookiePath(), + c.server.GetSessionCookieMaxAge(), + ) } // Get retrieves and returns the value with specified key. @@ -149,13 +155,19 @@ func (c *Cookie) Get(key string, def ...string) string { // Remove deletes specified key and its value from cookie using default domain and path. // It actually tells the http client that the cookie is expired, do not send it to server next time. func (c *Cookie) Remove(key string) { - c.SetCookie(key, "", c.domain, c.path, -86400) + c.SetCookie( + key, + "", + c.request.Server.GetCookieDomain(), + c.request.Server.GetCookiePath(), + -24*time.Hour, + ) } // RemoveCookie deletes specified key and its value from cookie using given domain and path. // It actually tells the http client that the cookie is expired, do not send it to server next time. func (c *Cookie) RemoveCookie(key, domain, path string) { - c.SetCookie(key, "", domain, path, -86400) + c.SetCookie(key, "", domain, path, -24*time.Hour) } // Flush outputs the cookie items to client. diff --git a/net/ghttp/ghttp_server_domain.go b/net/ghttp/ghttp_server_domain.go index cfe2332a1..624ebe12e 100644 --- a/net/ghttp/ghttp_server_domain.go +++ b/net/ghttp/ghttp_server_domain.go @@ -1,4 +1,4 @@ -// Copyright 2017 gf Author(https://github.com/gogf/gf). All Rights Reserved. +// Copyright GoFrame Author(https://goframe.org). All Rights Reserved. // // This Source Code Form is subject to the terms of the MIT License. // If a copy of the MIT was not distributed with this file, @@ -28,18 +28,15 @@ func (s *Server) Domain(domains string) *Domain { return d } -func (d *Domain) BindHandler(pattern string, handler HandlerFunc) { +func (d *Domain) BindHandler(pattern string, handler interface{}) { for domain, _ := range d.domains { d.server.BindHandler(pattern+"@"+domain, handler) } } -func (d *Domain) doBindHandler( - pattern string, handler HandlerFunc, - middleware []HandlerFunc, source string, -) { +func (d *Domain) doBindHandler(pattern string, funcInfo handlerFuncInfo, middleware []HandlerFunc, source string) { for domain, _ := range d.domains { - d.server.doBindHandler(pattern+"@"+domain, handler, middleware, source) + d.server.doBindHandler(pattern+"@"+domain, funcInfo, middleware, source) } } @@ -49,10 +46,7 @@ func (d *Domain) BindObject(pattern string, obj interface{}, methods ...string) } } -func (d *Domain) doBindObject( - pattern string, obj interface{}, methods string, - middleware []HandlerFunc, source string, -) { +func (d *Domain) doBindObject(pattern string, obj interface{}, methods string, middleware []HandlerFunc, source string) { for domain, _ := range d.domains { d.server.doBindObject(pattern+"@"+domain, obj, methods, middleware, source) } @@ -79,60 +73,12 @@ func (d *Domain) BindObjectRest(pattern string, obj interface{}) { } } -func (d *Domain) doBindObjectRest( - pattern string, obj interface{}, - middleware []HandlerFunc, source string, -) { +func (d *Domain) doBindObjectRest(pattern string, obj interface{}, middleware []HandlerFunc, source string) { for domain, _ := range d.domains { d.server.doBindObjectRest(pattern+"@"+domain, obj, middleware, source) } } -func (d *Domain) BindController(pattern string, c Controller, methods ...string) { - for domain, _ := range d.domains { - d.server.BindController(pattern+"@"+domain, c, methods...) - } -} - -func (d *Domain) doBindController( - pattern string, c Controller, methods string, - middleware []HandlerFunc, source string, -) { - for domain, _ := range d.domains { - d.server.doBindController(pattern+"@"+domain, c, methods, middleware, source) - } -} - -func (d *Domain) BindControllerMethod(pattern string, c Controller, method string) { - for domain, _ := range d.domains { - d.server.BindControllerMethod(pattern+"@"+domain, c, method) - } -} - -func (d *Domain) doBindControllerMethod( - pattern string, c Controller, method string, - middleware []HandlerFunc, source string, -) { - for domain, _ := range d.domains { - d.server.doBindControllerMethod(pattern+"@"+domain, c, method, middleware, source) - } -} - -func (d *Domain) BindControllerRest(pattern string, c Controller) { - for domain, _ := range d.domains { - d.server.BindControllerRest(pattern+"@"+domain, c) - } -} - -func (d *Domain) doBindControllerRest( - pattern string, c Controller, - middleware []HandlerFunc, source string, -) { - for domain, _ := range d.domains { - d.server.doBindControllerRest(pattern+"@"+domain, c, middleware, source) - } -} - func (d *Domain) BindHookHandler(pattern string, hook string, handler HandlerFunc) { for domain, _ := range d.domains { d.server.BindHookHandler(pattern+"@"+domain, hook, handler) @@ -171,7 +117,7 @@ func (d *Domain) BindMiddleware(pattern string, handlers ...HandlerFunc) { func (d *Domain) BindMiddlewareDefault(handlers ...HandlerFunc) { for domain, _ := range d.domains { - d.server.BindMiddleware(gDEFAULT_MIDDLEWARE_PATTERN+"@"+domain, handlers...) + d.server.BindMiddleware(defaultMiddlewarePattern+"@"+domain, handlers...) } } diff --git a/net/ghttp/ghttp_server_error_logger.go b/net/ghttp/ghttp_server_error_logger.go index 13f5d21e0..36870e26e 100644 --- a/net/ghttp/ghttp_server_error_logger.go +++ b/net/ghttp/ghttp_server_error_logger.go @@ -1,4 +1,4 @@ -// Copyright 2018 gf Author(https://github.com/gogf/gf). All Rights Reserved. +// Copyright GoFrame Author(https://goframe.org). All Rights Reserved. // // This Source Code Form is subject to the terms of the MIT License. // If a copy of the MIT was not distributed with this file, diff --git a/net/ghttp/ghttp_server_graceful.go b/net/ghttp/ghttp_server_graceful.go index 9c53363e5..8f6d60b53 100644 --- a/net/ghttp/ghttp_server_graceful.go +++ b/net/ghttp/ghttp_server_graceful.go @@ -1,4 +1,4 @@ -// Copyright GoFrame Author(https://github.com/gogf/gf). All Rights Reserved. +// Copyright GoFrame Author(https://goframe.org). All Rights Reserved. // // This Source Code Form is subject to the terms of the MIT License. // If a copy of the MIT was not distributed with this file, @@ -9,8 +9,9 @@ package ghttp import ( "context" "crypto/tls" - "errors" "fmt" + "github.com/gogf/gf/errors/gcode" + "github.com/gogf/gf/errors/gerror" "github.com/gogf/gf/os/gproc" "github.com/gogf/gf/os/gres" "github.com/gogf/gf/text/gstr" @@ -122,7 +123,7 @@ func (s *gracefulServer) ListenAndServeTLS(certFile, keyFile string, tlsConfig . } if err != nil { - return errors.New(fmt.Sprintf(`open cert file "%s","%s" failed: %s`, certFile, keyFile, err.Error())) + return gerror.WrapCodef(gcode.CodeInternalError, err, `open cert file "%s","%s" failed`, certFile, keyFile) } ln, err := s.getNetListener() if err != nil { diff --git a/net/ghttp/ghttp_server_handler.go b/net/ghttp/ghttp_server_handler.go index fbe416a8b..30d747c80 100644 --- a/net/ghttp/ghttp_server_handler.go +++ b/net/ghttp/ghttp_server_handler.go @@ -1,4 +1,4 @@ -// Copyright GoFrame Author(https://github.com/gogf/gf). All Rights Reserved. +// Copyright GoFrame Author(https://goframe.org). All Rights Reserved. // // This Source Code Form is subject to the terms of the MIT License. // If a copy of the MIT was not distributed with this file, @@ -7,6 +7,8 @@ package ghttp import ( + "github.com/gogf/gf/errors/gcode" + "github.com/gogf/gf/internal/intlog" "net/http" "os" "sort" @@ -65,7 +67,15 @@ func (s *Server) ServeHTTP(w http.ResponseWriter, r *http.Request) { } else { if exception := recover(); exception != nil { request.Response.WriteStatus(http.StatusInternalServerError) - s.handleErrorLog(gerror.Newf("%v", exception), request) + if err, ok := exception.(error); ok { + if code := gerror.Code(err); code != gcode.CodeNil { + s.handleErrorLog(err, request) + } else { + s.handleErrorLog(gerror.WrapCodeSkip(gcode.CodeInternalError, 1, err, ""), request) + } + } else { + s.handleErrorLog(gerror.NewCodeSkipf(gcode.CodeInternalError, 1, "%+v", exception), request) + } } } // access log handling. @@ -76,9 +86,15 @@ func (s *Server) ServeHTTP(w http.ResponseWriter, r *http.Request) { // Close the request and response body // to release the file descriptor in time. - request.Request.Body.Close() + err := request.Request.Body.Close() + if err != nil { + intlog.Error(request.Context(), err) + } if request.Request.Response != nil { - request.Request.Response.Body.Close() + err = request.Request.Response.Body.Close() + if err != nil { + intlog.Error(request.Context(), err) + } } }() @@ -164,7 +180,8 @@ func (s *Server) ServeHTTP(w http.ResponseWriter, r *http.Request) { } // Automatically set the session id to cookie - // if it creates a new session id in this request. + // if it creates a new session id in this request + // and SessionCookieOutput is enabled. if s.config.SessionCookieOutput && request.Session.IsDirty() && request.Session.Id() != request.GetSessionId() { @@ -182,10 +199,12 @@ func (s *Server) ServeHTTP(w http.ResponseWriter, r *http.Request) { // searchStaticFile searches the file with given URI. // It returns a file struct specifying the file information. -func (s *Server) searchStaticFile(uri string) *StaticFile { - var file *gres.File - var path string - var dir bool +func (s *Server) searchStaticFile(uri string) *staticFile { + var ( + file *gres.File + path string + dir bool + ) // Firstly search the StaticPaths mapping. if len(s.config.StaticPaths) > 0 { for _, item := range s.config.StaticPaths { @@ -196,14 +215,14 @@ func (s *Server) searchStaticFile(uri string) *StaticFile { } file = gres.GetWithIndex(item.path+uri[len(item.prefix):], s.config.IndexFiles) if file != nil { - return &StaticFile{ + return &staticFile{ File: file, IsDir: file.FileInfo().IsDir(), } } path, dir = gspath.Search(item.path, uri[len(item.prefix):], s.config.IndexFiles...) if path != "" { - return &StaticFile{ + return &staticFile{ Path: path, IsDir: dir, } @@ -217,13 +236,13 @@ func (s *Server) searchStaticFile(uri string) *StaticFile { for _, p := range s.config.SearchPaths { file = gres.GetWithIndex(p+uri, s.config.IndexFiles) if file != nil { - return &StaticFile{ + return &staticFile{ File: file, IsDir: file.FileInfo().IsDir(), } } if path, dir = gspath.Search(p, uri, s.config.IndexFiles...); path != "" { - return &StaticFile{ + return &staticFile{ Path: path, IsDir: dir, } @@ -233,7 +252,7 @@ func (s *Server) searchStaticFile(uri string) *StaticFile { // Lastly search the resource manager. if len(s.config.StaticPaths) == 0 && len(s.config.SearchPaths) == 0 { if file = gres.GetWithIndex(uri, s.config.IndexFiles); file != nil { - return &StaticFile{ + return &staticFile{ File: file, IsDir: file.FileInfo().IsDir(), } @@ -244,7 +263,7 @@ func (s *Server) searchStaticFile(uri string) *StaticFile { // serveFile serves the static file for client. // The optional parameter <allowIndex> specifies if allowing directory listing if <f> is directory. -func (s *Server) serveFile(r *Request, f *StaticFile, allowIndex ...bool) { +func (s *Server) serveFile(r *Request, f *staticFile, allowIndex ...bool) { // Use resource file from memory. if f.File != nil { if f.IsDir { diff --git a/net/ghttp/ghttp_server_log.go b/net/ghttp/ghttp_server_log.go index 93e66dcb5..24ce77bcc 100644 --- a/net/ghttp/ghttp_server_log.go +++ b/net/ghttp/ghttp_server_log.go @@ -1,4 +1,4 @@ -// Copyright 2018 gf Author(https://github.com/gogf/gf). All Rights Reserved. +// Copyright GoFrame Author(https://goframe.org). All Rights Reserved. // // This Source Code Form is subject to the terms of the MIT License. // If a copy of the MIT was not distributed with this file, @@ -9,14 +9,8 @@ package ghttp import ( "fmt" "github.com/gogf/gf/errors/gerror" - "github.com/gogf/gf/os/glog" ) -// Logger returns the logger of the server. -func (s *Server) Logger() *glog.Logger { - return s.config.Logger -} - // handleAccessLog handles the access logging for server. func (s *Server) handleAccessLog(r *Request) { if !s.IsAccessLogEnabled() { @@ -26,7 +20,7 @@ func (s *Server) handleAccessLog(r *Request) { if r.TLS != nil { scheme = "https" } - s.Logger().File(s.config.AccessLogPattern). + s.Logger().Ctx(r.Context()).File(s.config.AccessLogPattern). Stdout(s.config.LogStdout). Printf( `%d "%s %s %s %s %s" %.3f, %s, "%s", "%s"`, @@ -63,7 +57,7 @@ func (s *Server) handleErrorLog(err error, r *Request) { } else { content += ", " + err.Error() } - s.config.Logger. + s.Logger().Ctx(r.Context()). File(s.config.ErrorLogPattern). Stdout(s.config.LogStdout). Print(content) diff --git a/net/ghttp/ghttp_server_plugin.go b/net/ghttp/ghttp_server_plugin.go index d8d47aaf3..ce97e7f9b 100644 --- a/net/ghttp/ghttp_server_plugin.go +++ b/net/ghttp/ghttp_server_plugin.go @@ -1,4 +1,4 @@ -// Copyright 2019 gf Author(https://github.com/gogf/gf). All Rights Reserved. +// Copyright GoFrame Author(https://goframe.org). All Rights Reserved. // // This Source Code Form is subject to the terms of the MIT License. // If a copy of the MIT was not distributed with this file, diff --git a/net/ghttp/ghttp_server_pprof.go b/net/ghttp/ghttp_server_pprof.go index 27179030b..cef4f2d35 100644 --- a/net/ghttp/ghttp_server_pprof.go +++ b/net/ghttp/ghttp_server_pprof.go @@ -1,4 +1,4 @@ -// Copyright GoFrame Author(https://github.com/gogf/gf). All Rights Reserved. +// Copyright GoFrame Author(https://goframe.org). All Rights Reserved. // // This Source Code Form is subject to the terms of the MIT License. // If a copy of the MIT was not distributed with this file, @@ -18,9 +18,18 @@ import ( type utilPProf struct{} const ( - gDEFAULT_PPROF_PATTERN = "/debug/pprof" + defaultPProfServerName = "pprof-server" + defaultPProfPattern = "/debug/pprof" ) +// StartPProfServer starts and runs a new server for pprof. +func StartPProfServer(port int, pattern ...string) { + s := GetServer(defaultPProfServerName) + s.EnablePProf() + s.SetPort(port) + s.Run() +} + // EnablePProf enables PProf feature for server. func (s *Server) EnablePProf(pattern ...string) { s.Domain(defaultDomainName).EnablePProf(pattern...) @@ -28,7 +37,7 @@ func (s *Server) EnablePProf(pattern ...string) { // EnablePProf enables PProf feature for server of specified domain. func (d *Domain) EnablePProf(pattern ...string) { - p := gDEFAULT_PPROF_PATTERN + p := defaultPProfPattern if len(pattern) > 0 && pattern[0] != "" { p = pattern[0] } @@ -53,16 +62,21 @@ func (p *utilPProf) Index(r *Request) { "profiles": profiles, } if len(action) == 0 { - buffer, _ := gview.ParseContent(` + buffer, _ := gview.ParseContent(r.Context(), ` <html> <head> - <title>gf ghttp pprof</title> + <title>GoFrame PProf</title> </head> {{$uri := .uri}} <body> profiles:<br> <table> - {{range .profiles}}<tr><td align=right>{{.Count}}<td><a href="{{$uri}}{{.Name}}?debug=1">{{.Name}}</a>{{end}} + {{range .profiles}} + <tr> + <td align=right>{{.Count}}</td> + <td><a href="{{$uri}}{{.Name}}?debug=1">{{.Name}}</a></td> + <tr> + {{end}} </table> <br><a href="{{$uri}}goroutine?debug=2">full goroutine stack dump</a><br> </body> diff --git a/net/ghttp/ghttp_server_router.go b/net/ghttp/ghttp_server_router.go index aee073026..83d061b5e 100644 --- a/net/ghttp/ghttp_server_router.go +++ b/net/ghttp/ghttp_server_router.go @@ -1,4 +1,4 @@ -// Copyright GoFrame Author(https://github.com/gogf/gf). All Rights Reserved. +// Copyright GoFrame Author(https://goframe.org). All Rights Reserved. // // This Source Code Form is subject to the terms of the MIT License. // If a copy of the MIT was not distributed with this file, @@ -7,9 +7,10 @@ package ghttp import ( - "errors" "fmt" "github.com/gogf/gf/container/gtype" + "github.com/gogf/gf/errors/gcode" + "github.com/gogf/gf/errors/gerror" "strings" "github.com/gogf/gf/debug/gdebug" @@ -20,7 +21,7 @@ import ( ) const ( - gFILTER_KEY = "/net/ghttp/ghttp" + stackFilterKey = "/net/ghttp/ghttp" ) var ( @@ -53,7 +54,7 @@ func (s *Server) parsePattern(pattern string) (domain, method, path string, err } } if path == "" { - err = errors.New("invalid pattern: URI should not be empty") + err = gerror.NewCode(gcode.CodeInvalidParameter, "invalid pattern: URI should not be empty") } if path != "/" { path = strings.TrimRight(path, "/") @@ -66,10 +67,10 @@ func (s *Server) parsePattern(pattern string) (domain, method, path string, err // This function is called during server starts up, which cares little about the performance. What really cares // is the well designed router storage structure for router searching when the request is under serving. func (s *Server) setHandler(pattern string, handler *handlerItem) { - handler.itemId = handlerIdGenerator.Add(1) - if handler.source == "" { - _, file, line := gdebug.CallerWithFilter(gFILTER_KEY) - handler.source = fmt.Sprintf(`%s:%d`, file, line) + handler.Id = handlerIdGenerator.Add(1) + if handler.Source == "" { + _, file, line := gdebug.CallerWithFilter(stackFilterKey) + handler.Source = fmt.Sprintf(`%s:%d`, file, line) } domain, method, uri, err := s.parsePattern(pattern) if err != nil { @@ -82,27 +83,27 @@ func (s *Server) setHandler(pattern string, handler *handlerItem) { } // Repeated router checks, this feature can be disabled by server configuration. - routerKey := s.routerMapKey(handler.hookName, method, uri, domain) + routerKey := s.routerMapKey(handler.HookName, method, uri, domain) if !s.config.RouteOverWrite { - switch handler.itemType { + switch handler.Type { case handlerTypeHandler, handlerTypeObject, handlerTypeController: if item, ok := s.routesMap[routerKey]; ok { s.Logger().Fatalf( `duplicated route registry "%s" at %s , already registered at %s`, - pattern, handler.source, item[0].source, + pattern, handler.Source, item[0].Source, ) return } } } // Create a new router by given parameter. - handler.router = &Router{ + handler.Router = &Router{ Uri: uri, Domain: domain, Method: strings.ToUpper(method), Priority: strings.Count(uri[1:], "/"), } - handler.router.RegRule, handler.router.RegNames = s.patternToRegular(uri) + handler.Router.RegRule, handler.Router.RegNames = s.patternToRegular(uri) if _, ok := s.serveTree[domain]; !ok { s.serveTree[domain] = make(map[string]interface{}) @@ -193,10 +194,10 @@ func (s *Server) setHandler(pattern string, handler *handlerItem) { } routeItem := registeredRouteItem{ - source: handler.source, - handler: handler, + Source: handler.Source, + Handler: handler, } - switch handler.itemType { + switch handler.Type { case handlerTypeHandler, handlerTypeObject, handlerTypeController: // Overwrite the route. s.routesMap[routerKey] = []registeredRouteItem{routeItem} @@ -216,30 +217,30 @@ func (s *Server) setHandler(pattern string, handler *handlerItem) { // 3. Route type: {xxx} > :xxx > *xxx. func (s *Server) compareRouterPriority(newItem *handlerItem, oldItem *handlerItem) bool { // If they're all type of middleware, the priority is according their registered sequence. - if newItem.itemType == handlerTypeMiddleware && oldItem.itemType == handlerTypeMiddleware { + if newItem.Type == handlerTypeMiddleware && oldItem.Type == handlerTypeMiddleware { return false } // The middleware has the most high priority. - if newItem.itemType == handlerTypeMiddleware && oldItem.itemType != handlerTypeMiddleware { + if newItem.Type == handlerTypeMiddleware && oldItem.Type != handlerTypeMiddleware { return true } // URI: The deeper the higher (simply check the count of char '/' in the URI). - if newItem.router.Priority > oldItem.router.Priority { + if newItem.Router.Priority > oldItem.Router.Priority { return true } - if newItem.router.Priority < oldItem.router.Priority { + if newItem.Router.Priority < oldItem.Router.Priority { return false } // Compare the length of their URI, // but the fuzzy and named parts of the URI are not calculated to the result. - // Eg: + // Example: // /admin-goods-{page} > /admin-{page} // /{hash}.{type} > /{hash} var uriNew, uriOld string - uriNew, _ = gregex.ReplaceString(`\{[^/]+?\}`, "", newItem.router.Uri) - uriOld, _ = gregex.ReplaceString(`\{[^/]+?\}`, "", oldItem.router.Uri) + uriNew, _ = gregex.ReplaceString(`\{[^/]+?\}`, "", newItem.Router.Uri) + uriOld, _ = gregex.ReplaceString(`\{[^/]+?\}`, "", oldItem.Router.Uri) uriNew, _ = gregex.ReplaceString(`:[^/]+?`, "", uriNew) uriOld, _ = gregex.ReplaceString(`:[^/]+?`, "", uriOld) uriNew, _ = gregex.ReplaceString(`\*[^/]*`, "", uriNew) // Replace "/*" and "/*any". @@ -252,7 +253,7 @@ func (s *Server) compareRouterPriority(newItem *handlerItem, oldItem *handlerIte } // Route type checks: {xxx} > :xxx > *xxx. - // Eg: + // Example: // /name/act > /{name}/:act var ( fuzzyCountFieldNew int @@ -264,7 +265,7 @@ func (s *Server) compareRouterPriority(newItem *handlerItem, oldItem *handlerIte fuzzyCountTotalNew int fuzzyCountTotalOld int ) - for _, v := range newItem.router.Uri { + for _, v := range newItem.Router.Uri { switch v { case '{': fuzzyCountFieldNew++ @@ -274,7 +275,7 @@ func (s *Server) compareRouterPriority(newItem *handlerItem, oldItem *handlerIte fuzzyCountAnyNew++ } } - for _, v := range oldItem.router.Uri { + for _, v := range oldItem.Router.Uri { switch v { case '{': fuzzyCountFieldOld++ @@ -312,18 +313,16 @@ func (s *Server) compareRouterPriority(newItem *handlerItem, oldItem *handlerIte // It then compares the accuracy of their http method, // the more accurate the more priority. - if newItem.router.Method != defaultMethod { + if newItem.Router.Method != defaultMethod { return true } - if oldItem.router.Method != defaultMethod { + if oldItem.Router.Method != defaultMethod { return true } // If they have different router type, // the new router item has more priority than the other one. - if newItem.itemType == handlerTypeHandler || - newItem.itemType == handlerTypeObject || - newItem.itemType == handlerTypeController { + if newItem.Type == handlerTypeHandler || newItem.Type == handlerTypeObject || newItem.Type == handlerTypeController { return true } diff --git a/net/ghttp/ghttp_server_router_group.go b/net/ghttp/ghttp_server_router_group.go index 5c02f3d86..1eaf85b3a 100644 --- a/net/ghttp/ghttp_server_router_group.go +++ b/net/ghttp/ghttp_server_router_group.go @@ -1,4 +1,4 @@ -// Copyright GoFrame Author(https://github.com/gogf/gf). All Rights Reserved. +// Copyright GoFrame Author(https://goframe.org). All Rights Reserved. // // This Source Code Form is subject to the terms of the MIT License. // If a copy of the MIT was not distributed with this file, @@ -43,6 +43,13 @@ type ( } ) +const ( + groupBindTypeHandler = "HANDLER" + groupBindTypeRest = "REST" + groupBindTypeHook = "HOOK" + groupBindTypeMiddleware = "MIDDLEWARE" +) + var ( preBindItems = make([]*preBindItem, 0, 64) ) @@ -153,10 +160,12 @@ func (g *RouterGroup) Bind(items []GroupItem) *RouterGroup { } bindType := gstr.ToUpper(gconv.String(item[0])) switch bindType { - case "REST": - group.preBindToLocalArray("REST", gconv.String(item[0])+":"+gconv.String(item[1]), item[2]) - case "MIDDLEWARE": - group.preBindToLocalArray("MIDDLEWARE", gconv.String(item[0])+":"+gconv.String(item[1]), item[2]) + case groupBindTypeRest: + group.preBindToLocalArray(groupBindTypeRest, gconv.String(item[0])+":"+gconv.String(item[1]), item[2]) + + case groupBindTypeMiddleware: + group.preBindToLocalArray(groupBindTypeMiddleware, gconv.String(item[0])+":"+gconv.String(item[1]), item[2]) + default: if strings.EqualFold(bindType, "ALL") { bindType = "" @@ -164,9 +173,9 @@ func (g *RouterGroup) Bind(items []GroupItem) *RouterGroup { bindType += ":" } if len(item) > 3 { - group.preBindToLocalArray("HANDLER", bindType+gconv.String(item[1]), item[2], item[3]) + group.preBindToLocalArray(groupBindTypeHandler, bindType+gconv.String(item[1]), item[2], item[3]) } else { - group.preBindToLocalArray("HANDLER", bindType+gconv.String(item[1]), item[2]) + group.preBindToLocalArray(groupBindTypeHandler, bindType+gconv.String(item[1]), item[2]) } } } @@ -175,71 +184,76 @@ func (g *RouterGroup) Bind(items []GroupItem) *RouterGroup { // ALL registers a http handler to given route pattern and all http methods. func (g *RouterGroup) ALL(pattern string, object interface{}, params ...interface{}) *RouterGroup { - return g.Clone().preBindToLocalArray("HANDLER", defaultMethod+":"+pattern, object, params...) + return g.Clone().preBindToLocalArray(groupBindTypeHandler, defaultMethod+":"+pattern, object, params...) } // ALLMap registers http handlers for http methods using map. -func (g *RouterGroup) ALLMap(m map[string]interface{}) *RouterGroup { - var group *RouterGroup +func (g *RouterGroup) ALLMap(m map[string]interface{}) { for pattern, object := range m { - group = g.ALL(pattern, object) + g.ALL(pattern, object) + } +} + +// Map registers http handlers for http methods using map. +func (g *RouterGroup) Map(m map[string]interface{}) { + for pattern, object := range m { + g.preBindToLocalArray(groupBindTypeHandler, pattern, object) } - return group } // GET registers a http handler to given route pattern and http method: GET. func (g *RouterGroup) GET(pattern string, object interface{}, params ...interface{}) *RouterGroup { - return g.Clone().preBindToLocalArray("HANDLER", "GET:"+pattern, object, params...) + return g.Clone().preBindToLocalArray(groupBindTypeHandler, "GET:"+pattern, object, params...) } // PUT registers a http handler to given route pattern and http method: PUT. func (g *RouterGroup) PUT(pattern string, object interface{}, params ...interface{}) *RouterGroup { - return g.Clone().preBindToLocalArray("HANDLER", "PUT:"+pattern, object, params...) + return g.Clone().preBindToLocalArray(groupBindTypeHandler, "PUT:"+pattern, object, params...) } // POST registers a http handler to given route pattern and http method: POST. func (g *RouterGroup) POST(pattern string, object interface{}, params ...interface{}) *RouterGroup { - return g.Clone().preBindToLocalArray("HANDLER", "POST:"+pattern, object, params...) + return g.Clone().preBindToLocalArray(groupBindTypeHandler, "POST:"+pattern, object, params...) } // DELETE registers a http handler to given route pattern and http method: DELETE. func (g *RouterGroup) DELETE(pattern string, object interface{}, params ...interface{}) *RouterGroup { - return g.Clone().preBindToLocalArray("HANDLER", "DELETE:"+pattern, object, params...) + return g.Clone().preBindToLocalArray(groupBindTypeHandler, "DELETE:"+pattern, object, params...) } // PATCH registers a http handler to given route pattern and http method: PATCH. func (g *RouterGroup) PATCH(pattern string, object interface{}, params ...interface{}) *RouterGroup { - return g.Clone().preBindToLocalArray("HANDLER", "PATCH:"+pattern, object, params...) + return g.Clone().preBindToLocalArray(groupBindTypeHandler, "PATCH:"+pattern, object, params...) } // HEAD registers a http handler to given route pattern and http method: HEAD. func (g *RouterGroup) HEAD(pattern string, object interface{}, params ...interface{}) *RouterGroup { - return g.Clone().preBindToLocalArray("HANDLER", "HEAD:"+pattern, object, params...) + return g.Clone().preBindToLocalArray(groupBindTypeHandler, "HEAD:"+pattern, object, params...) } // CONNECT registers a http handler to given route pattern and http method: CONNECT. func (g *RouterGroup) CONNECT(pattern string, object interface{}, params ...interface{}) *RouterGroup { - return g.Clone().preBindToLocalArray("HANDLER", "CONNECT:"+pattern, object, params...) + return g.Clone().preBindToLocalArray(groupBindTypeHandler, "CONNECT:"+pattern, object, params...) } // OPTIONS registers a http handler to given route pattern and http method: OPTIONS. func (g *RouterGroup) OPTIONS(pattern string, object interface{}, params ...interface{}) *RouterGroup { - return g.Clone().preBindToLocalArray("HANDLER", "OPTIONS:"+pattern, object, params...) + return g.Clone().preBindToLocalArray(groupBindTypeHandler, "OPTIONS:"+pattern, object, params...) } // TRACE registers a http handler to given route pattern and http method: TRACE. func (g *RouterGroup) TRACE(pattern string, object interface{}, params ...interface{}) *RouterGroup { - return g.Clone().preBindToLocalArray("HANDLER", "TRACE:"+pattern, object, params...) + return g.Clone().preBindToLocalArray(groupBindTypeHandler, "TRACE:"+pattern, object, params...) } // REST registers a http handler to given route pattern according to REST rule. func (g *RouterGroup) REST(pattern string, object interface{}) *RouterGroup { - return g.Clone().preBindToLocalArray("REST", pattern, object) + return g.Clone().preBindToLocalArray(groupBindTypeRest, pattern, object) } // Hook registers a hook to given route pattern. func (g *RouterGroup) Hook(pattern string, hook string, handler HandlerFunc) *RouterGroup { - return g.Clone().preBindToLocalArray("HANDLER", pattern, handler, hook) + return g.Clone().preBindToLocalArray(groupBindTypeHandler, pattern, handler, hook) } // Middleware binds one or more middleware to the router group. @@ -250,7 +264,7 @@ func (g *RouterGroup) Middleware(handlers ...HandlerFunc) *RouterGroup { // preBindToLocalArray adds the route registering parameters to internal variable array for lazily registering feature. func (g *RouterGroup) preBindToLocalArray(bindType string, pattern string, object interface{}, params ...interface{}) *RouterGroup { - _, file, line := gdebug.CallerWithFilter(gFILTER_KEY) + _, file, line := gdebug.CallerWithFilter(stackFilterKey) preBindItems = append(preBindItems, &preBindItem{ group: g, bindType: bindType, @@ -273,7 +287,7 @@ func (g *RouterGroup) getPrefix() string { return prefix } -// doBindRoutersToServer does really registering for the group. +// doBindRoutersToServer does really register for the group. func (g *RouterGroup) doBindRoutersToServer(item *preBindItem) *RouterGroup { var ( bindType = item.bindType @@ -289,11 +303,11 @@ func (g *RouterGroup) doBindRoutersToServer(item *preBindItem) *RouterGroup { if err != nil { g.server.Logger().Fatalf("invalid pattern: %s", pattern) } - // If there'a already a domain, unset the domain field in the pattern. + // If there is already a domain, unset the domain field in the pattern. if g.domain != nil { domain = "" } - if bindType == "REST" { + if bindType == groupBindTypeRest { pattern = prefix + "/" + strings.TrimLeft(path, "/") } else { pattern = g.server.serveHandlerKey( @@ -303,53 +317,26 @@ func (g *RouterGroup) doBindRoutersToServer(item *preBindItem) *RouterGroup { } // Filter repeated char '/'. pattern = gstr.Replace(pattern, "//", "/") + // Convert params to string array. extras := gconv.Strings(params) + // Check whether it's a hook handler. if _, ok := object.(HandlerFunc); ok && len(extras) > 0 { - bindType = "HOOK" + bindType = groupBindTypeHook } switch bindType { - case "HANDLER": - if h, ok := object.(HandlerFunc); ok { - if g.server != nil { - g.server.doBindHandler(pattern, h, g.middleware, source) - } else { - g.domain.doBindHandler(pattern, h, g.middleware, source) + case groupBindTypeHandler: + if reflect.ValueOf(object).Kind() == reflect.Func { + funcInfo, err := g.server.checkAndCreateFuncInfo(object, "", "", "") + if err != nil { + g.server.Logger().Error(err.Error()) + return g } - } else if g.isController(object) { - if len(extras) > 0 { - if g.server != nil { - if gstr.Contains(extras[0], ",") { - g.server.doBindController( - pattern, object.(Controller), extras[0], g.middleware, source, - ) - } else { - g.server.doBindControllerMethod( - pattern, object.(Controller), extras[0], g.middleware, source, - ) - } - } else { - if gstr.Contains(extras[0], ",") { - g.domain.doBindController( - pattern, object.(Controller), extras[0], g.middleware, source, - ) - } else { - g.domain.doBindControllerMethod( - pattern, object.(Controller), extras[0], g.middleware, source, - ) - } - } + if g.server != nil { + g.server.doBindHandler(pattern, funcInfo, g.middleware, source) } else { - if g.server != nil { - g.server.doBindController( - pattern, object.(Controller), "", g.middleware, source, - ) - } else { - g.domain.doBindController( - pattern, object.(Controller), "", g.middleware, source, - ) - } + g.domain.doBindHandler(pattern, funcInfo, g.middleware, source) } } else { if len(extras) > 0 { @@ -375,6 +362,7 @@ func (g *RouterGroup) doBindRoutersToServer(item *preBindItem) *RouterGroup { } } } else { + // At last, it treats the `object` as Object registering type. if g.server != nil { g.server.doBindObject(pattern, object, "", g.middleware, source) } else { @@ -382,25 +370,15 @@ func (g *RouterGroup) doBindRoutersToServer(item *preBindItem) *RouterGroup { } } } - case "REST": - if g.isController(object) { - if g.server != nil { - g.server.doBindControllerRest( - pattern, object.(Controller), g.middleware, source, - ) - } else { - g.domain.doBindControllerRest( - pattern, object.(Controller), g.middleware, source, - ) - } + + case groupBindTypeRest: + if g.server != nil { + g.server.doBindObjectRest(pattern, object, g.middleware, source) } else { - if g.server != nil { - g.server.doBindObjectRest(pattern, object, g.middleware, source) - } else { - g.domain.doBindObjectRest(pattern, object, g.middleware, source) - } + g.domain.doBindObjectRest(pattern, object, g.middleware, source) } - case "HOOK": + + case groupBindTypeHook: if h, ok := object.(HandlerFunc); ok { if g.server != nil { g.server.doBindHookHandler(pattern, extras[0], h, source) @@ -413,26 +391,3 @@ func (g *RouterGroup) doBindRoutersToServer(item *preBindItem) *RouterGroup { } return g } - -// isController checks and returns whether given <value> is a controller. -// A controller should contains attributes: Request/Response/Server/Cookie/Session/View. -func (g *RouterGroup) isController(value interface{}) bool { - // Whether implements interface Controller. - if _, ok := value.(Controller); !ok { - return false - } - // Check the necessary attributes. - v := reflect.ValueOf(value) - if v.Kind() == reflect.Ptr { - v = v.Elem() - } - if v.FieldByName("Request").IsValid() && - v.FieldByName("Response").IsValid() && - v.FieldByName("Server").IsValid() && - v.FieldByName("Cookie").IsValid() && - v.FieldByName("Session").IsValid() && - v.FieldByName("View").IsValid() { - return true - } - return false -} diff --git a/net/ghttp/ghttp_server_router_hook.go b/net/ghttp/ghttp_server_router_hook.go index 46fd63693..b8486c082 100644 --- a/net/ghttp/ghttp_server_router_hook.go +++ b/net/ghttp/ghttp_server_router_hook.go @@ -1,4 +1,4 @@ -// Copyright GoFrame Author(https://github.com/gogf/gf). All Rights Reserved. +// Copyright GoFrame Author(https://goframe.org). All Rights Reserved. // // This Source Code Form is subject to the terms of the MIT License. // If a copy of the MIT was not distributed with this file, @@ -9,6 +9,7 @@ package ghttp import ( "github.com/gogf/gf/debug/gdebug" "net/http" + "reflect" ) // BindHookHandler registers handler for specified hook. @@ -18,11 +19,14 @@ func (s *Server) BindHookHandler(pattern string, hook string, handler HandlerFun func (s *Server) doBindHookHandler(pattern string, hook string, handler HandlerFunc, source string) { s.setHandler(pattern, &handlerItem{ - itemType: handlerTypeHook, - itemName: gdebug.FuncPath(handler), - itemFunc: handler, - hookName: hook, - source: source, + Type: handlerTypeHook, + Name: gdebug.FuncPath(handler), + Info: handlerFuncInfo{ + Func: handler, + Type: reflect.TypeOf(handler), + }, + HookName: hook, + Source: source, }) } @@ -39,11 +43,11 @@ func (s *Server) callHookHandler(hook string, r *Request) { // Backup the old router variable map. oldRouterMap := r.routerMap for _, item := range hookItems { - r.routerMap = item.values + r.routerMap = item.Values // DO NOT USE the router of the hook handler, // which can overwrite the router of serving handler. // r.Router = item.handler.router - if err := s.niceCallHookHandler(item.handler.itemFunc, r); err != nil { + if err := s.niceCallHookHandler(item.Handler.Info.Func, r); err != nil { switch err { case exceptionExit: break @@ -69,7 +73,7 @@ func (r *Request) getHookHandlers(hook string) []*handlerParsedItem { } parsedItems := make([]*handlerParsedItem, 0, 4) for _, v := range r.handlers { - if v.handler.hookName != hook { + if v.Handler.HookName != hook { continue } item := v diff --git a/net/ghttp/ghttp_server_router_middleware.go b/net/ghttp/ghttp_server_router_middleware.go index 10f207e4a..4f90d8288 100644 --- a/net/ghttp/ghttp_server_router_middleware.go +++ b/net/ghttp/ghttp_server_router_middleware.go @@ -1,4 +1,4 @@ -// Copyright GoFrame Author(https://github.com/gogf/gf). All Rights Reserved. +// Copyright GoFrame Author(https://goframe.org). All Rights Reserved. // // This Source Code Form is subject to the terms of the MIT License. // If a copy of the MIT was not distributed with this file, @@ -8,11 +8,12 @@ package ghttp import ( "github.com/gogf/gf/debug/gdebug" + "reflect" ) const ( // The default route pattern for global middleware. - gDEFAULT_MIDDLEWARE_PATTERN = "/*" + defaultMiddlewarePattern = "/*" ) // BindMiddleware registers one or more global middleware to the server. @@ -22,9 +23,12 @@ const ( func (s *Server) BindMiddleware(pattern string, handlers ...HandlerFunc) { for _, handler := range handlers { s.setHandler(pattern, &handlerItem{ - itemType: handlerTypeMiddleware, - itemName: gdebug.FuncPath(handler), - itemFunc: handler, + Type: handlerTypeMiddleware, + Name: gdebug.FuncPath(handler), + Info: handlerFuncInfo{ + Func: handler, + Type: reflect.TypeOf(handler), + }, }) } } @@ -34,10 +38,13 @@ func (s *Server) BindMiddleware(pattern string, handlers ...HandlerFunc) { // before or after service handler. func (s *Server) BindMiddlewareDefault(handlers ...HandlerFunc) { for _, handler := range handlers { - s.setHandler(gDEFAULT_MIDDLEWARE_PATTERN, &handlerItem{ - itemType: handlerTypeMiddleware, - itemName: gdebug.FuncPath(handler), - itemFunc: handler, + s.setHandler(defaultMiddlewarePattern, &handlerItem{ + Type: handlerTypeMiddleware, + Name: gdebug.FuncPath(handler), + Info: handlerFuncInfo{ + Func: handler, + Type: reflect.TypeOf(handler), + }, }) } } diff --git a/net/ghttp/ghttp_server_router_serve.go b/net/ghttp/ghttp_server_router_serve.go index bee6e9896..00987de67 100644 --- a/net/ghttp/ghttp_server_router_serve.go +++ b/net/ghttp/ghttp_server_router_serve.go @@ -1,4 +1,4 @@ -// Copyright GoFrame Author(https://github.com/gogf/gf). All Rights Reserved. +// Copyright GoFrame Author(https://goframe.org). All Rights Reserved. // // This Source Code Form is subject to the terms of the MIT License. // If a copy of the MIT was not distributed with this file, @@ -8,6 +8,8 @@ package ghttp import ( "fmt" + "github.com/gogf/gf/errors/gcode" + "github.com/gogf/gf/errors/gerror" "github.com/gogf/gf/internal/json" "strings" @@ -141,42 +143,42 @@ func (s *Server) searchHandlers(method, path, domain string) (parsedItems []*han item := e.Value.(*handlerItem) // Filter repeated handler item, especially the middleware and hook handlers. // It is necessary, do not remove this checks logic unless you really know how it is necessary. - if _, ok := repeatHandlerCheckMap[item.itemId]; ok { + if _, ok := repeatHandlerCheckMap[item.Id]; ok { continue } else { - repeatHandlerCheckMap[item.itemId] = struct{}{} + repeatHandlerCheckMap[item.Id] = struct{}{} } // Serving handler can only be added to the handler array just once. if hasServe { - switch item.itemType { + switch item.Type { case handlerTypeHandler, handlerTypeObject, handlerTypeController: continue } } - if item.router.Method == defaultMethod || item.router.Method == method { + if item.Router.Method == defaultMethod || item.Router.Method == method { // Note the rule having no fuzzy rules: len(match) == 1 - if match, err := gregex.MatchString(item.router.RegRule, path); err == nil && len(match) > 0 { + if match, err := gregex.MatchString(item.Router.RegRule, path); err == nil && len(match) > 0 { parsedItem := &handlerParsedItem{item, nil} // If the rule contains fuzzy names, // it needs paring the URL to retrieve the values for the names. - if len(item.router.RegNames) > 0 { - if len(match) > len(item.router.RegNames) { - parsedItem.values = make(map[string]string) + if len(item.Router.RegNames) > 0 { + if len(match) > len(item.Router.RegNames) { + parsedItem.Values = make(map[string]string) // It there repeated names, it just overwrites the same one. - for i, name := range item.router.RegNames { - parsedItem.values[name] = match[i+1] + for i, name := range item.Router.RegNames { + parsedItem.Values[name] = match[i+1] } } } - switch item.itemType { + switch item.Type { // The serving handler can be only added just once. case handlerTypeHandler, handlerTypeObject, handlerTypeController: hasServe = true parsedItemList.PushBack(parsedItem) // The middleware is inserted before the serving handler. - // If there're multiple middleware, they're inserted into the result list by their registering order. - // The middleware are also executed by their registered order. + // If there are multiple middleware, they're inserted into the result list by their registering order. + // The middleware is also executed by their registered order. case handlerTypeMiddleware: if lastMiddlewareElem == nil { lastMiddlewareElem = parsedItemList.PushFront(parsedItem) @@ -190,7 +192,7 @@ func (s *Server) searchHandlers(method, path, domain string) (parsedItems []*han parsedItemList.PushBack(parsedItem) default: - panic(fmt.Sprintf(`invalid handler type %d`, item.itemType)) + panic(gerror.NewCodef(gcode.CodeInternalError, `invalid handler type %d`, item.Type)) } } } @@ -210,33 +212,33 @@ func (s *Server) searchHandlers(method, path, domain string) (parsedItems []*han // MarshalJSON implements the interface MarshalJSON for json.Marshal. func (item *handlerItem) MarshalJSON() ([]byte, error) { - switch item.itemType { + switch item.Type { case handlerTypeHook: return json.Marshal( fmt.Sprintf( `%s %s:%s (%s)`, - item.router.Uri, - item.router.Domain, - item.router.Method, - item.hookName, + item.Router.Uri, + item.Router.Domain, + item.Router.Method, + item.HookName, ), ) case handlerTypeMiddleware: return json.Marshal( fmt.Sprintf( `%s %s:%s (MIDDLEWARE)`, - item.router.Uri, - item.router.Domain, - item.router.Method, + item.Router.Uri, + item.Router.Domain, + item.Router.Method, ), ) default: return json.Marshal( fmt.Sprintf( `%s %s:%s`, - item.router.Uri, - item.router.Domain, - item.router.Method, + item.Router.Uri, + item.Router.Domain, + item.Router.Method, ), ) } @@ -244,5 +246,5 @@ func (item *handlerItem) MarshalJSON() ([]byte, error) { // MarshalJSON implements the interface MarshalJSON for json.Marshal. func (item *handlerParsedItem) MarshalJSON() ([]byte, error) { - return json.Marshal(item.handler) + return json.Marshal(item.Handler) } diff --git a/net/ghttp/ghttp_server_service_controller.go b/net/ghttp/ghttp_server_service_controller.go deleted file mode 100644 index 380399c54..000000000 --- a/net/ghttp/ghttp_server_service_controller.go +++ /dev/null @@ -1,232 +0,0 @@ -// Copyright GoFrame 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 ghttp - -import ( - "fmt" - "reflect" - "strings" - - "github.com/gogf/gf/os/gfile" - "github.com/gogf/gf/text/gregex" - "github.com/gogf/gf/text/gstr" -) - -// BindController registers controller to server routes with specified pattern. The controller -// needs to implement the gmvc.Controller interface. Each request of the controller bound in -// this way will initialize a new controller object for processing, corresponding to different -// request sessions. -// -// The optional parameter <method> is used to specify the method to be registered, which -// supports multiple method names, multiple methods are separated by char ',', case sensitive. -func (s *Server) BindController(pattern string, controller Controller, method ...string) { - bindMethod := "" - if len(method) > 0 { - bindMethod = method[0] - } - s.doBindController(pattern, controller, bindMethod, nil, "") -} - -// BindControllerMethod registers specified method to server routes with specified pattern. -// -// The optional parameter <method> is used to specify the method to be registered, which -// does not supports multiple method names but only one, case sensitive. -func (s *Server) BindControllerMethod(pattern string, controller Controller, method string) { - s.doBindControllerMethod(pattern, controller, method, nil, "") -} - -// BindControllerRest registers controller in REST API style to server with specified pattern. -// The controller needs to implement the gmvc.Controller interface. Each request of the controller -// bound in this way will initialize a new controller object for processing, corresponding to -// different request sessions. -// The method will recognize the HTTP method and do REST binding, for example: -// The method "Post" of controller will be bound to the HTTP POST method request processing, -// and the method "Delete" will be bound to the HTTP DELETE method request processing. -// Therefore, only the method corresponding to the HTTP Method will be bound, other methods will -// not automatically register the binding. -func (s *Server) BindControllerRest(pattern string, controller Controller) { - s.doBindControllerRest(pattern, controller, nil, "") -} - -func (s *Server) doBindController( - pattern string, controller Controller, method string, - middleware []HandlerFunc, source string, -) { - // Convert input method to map for convenience and high performance searching. - var methodMap map[string]bool - if len(method) > 0 { - methodMap = make(map[string]bool) - for _, v := range strings.Split(method, ",") { - methodMap[strings.TrimSpace(v)] = true - } - } - domain, method, path, err := s.parsePattern(pattern) - if err != nil { - s.Logger().Fatal(err) - return - } - if strings.EqualFold(method, defaultMethod) { - pattern = s.serveHandlerKey("", path, domain) - } - // Retrieve a list of methods, create construct corresponding URI. - m := make(map[string]*handlerItem) - v := reflect.ValueOf(controller) - t := v.Type() - pkgPath := t.Elem().PkgPath() - pkgName := gfile.Basename(pkgPath) - structName := t.Elem().Name() - for i := 0; i < v.NumMethod(); i++ { - methodName := t.Method(i).Name - if methodMap != nil && !methodMap[methodName] { - continue - } - if methodName == "Init" || methodName == "Shut" || methodName == "Exit" { - continue - } - ctlName := gstr.Replace(t.String(), fmt.Sprintf(`%s.`, pkgName), "") - if ctlName[0] == '*' { - ctlName = fmt.Sprintf(`(%s)`, ctlName) - } - if _, ok := v.Method(i).Interface().(func()); !ok { - if len(methodMap) > 0 { - // If registering with specified method, print error. - s.Logger().Errorf( - `invalid route method: %s.%s.%s defined as "%s", but "func()" is required for controller registry`, - pkgPath, ctlName, methodName, v.Method(i).Type().String(), - ) - } else { - // Else, just print debug information. - s.Logger().Debugf( - `ignore route method: %s.%s.%s defined as "%s", no match "func()" for controller registry`, - pkgPath, ctlName, methodName, v.Method(i).Type().String(), - ) - } - continue - } - key := s.mergeBuildInNameToPattern(pattern, structName, methodName, true) - m[key] = &handlerItem{ - itemName: fmt.Sprintf(`%s.%s.%s`, pkgPath, ctlName, methodName), - itemType: handlerTypeController, - ctrlInfo: &handlerController{ - name: methodName, - reflect: v.Elem().Type(), - }, - middleware: middleware, - source: source, - } - // If there's "Index" method, then an additional route is automatically added - // to match the main URI, for example: - // If pattern is "/user", then "/user" and "/user/index" are both automatically - // registered. - // - // Note that if there's built-in variables in pattern, this route will not be added - // automatically. - if strings.EqualFold(methodName, "Index") && !gregex.IsMatchString(`\{\.\w+\}`, pattern) { - p := gstr.PosRI(key, "/index") - k := key[0:p] + key[p+6:] - if len(k) == 0 || k[0] == '@' { - k = "/" + k - } - m[k] = &handlerItem{ - itemName: fmt.Sprintf(`%s.%s.%s`, pkgPath, ctlName, methodName), - itemType: handlerTypeController, - ctrlInfo: &handlerController{ - name: methodName, - reflect: v.Elem().Type(), - }, - middleware: middleware, - source: source, - } - } - } - s.bindHandlerByMap(m) -} - -func (s *Server) doBindControllerMethod( - pattern string, - controller Controller, - method string, - middleware []HandlerFunc, - source string, -) { - m := make(map[string]*handlerItem) - v := reflect.ValueOf(controller) - t := v.Type() - structName := t.Elem().Name() - methodName := strings.TrimSpace(method) - methodValue := v.MethodByName(methodName) - if !methodValue.IsValid() { - s.Logger().Fatal("invalid method name: " + methodName) - return - } - pkgPath := t.Elem().PkgPath() - pkgName := gfile.Basename(pkgPath) - ctlName := gstr.Replace(t.String(), fmt.Sprintf(`%s.`, pkgName), "") - if ctlName[0] == '*' { - ctlName = fmt.Sprintf(`(%s)`, ctlName) - } - if _, ok := methodValue.Interface().(func()); !ok { - s.Logger().Errorf( - `invalid route method: %s.%s.%s defined as "%s", but "func()" is required for controller registry`, - pkgPath, ctlName, methodName, methodValue.Type().String(), - ) - return - } - key := s.mergeBuildInNameToPattern(pattern, structName, methodName, false) - m[key] = &handlerItem{ - itemName: fmt.Sprintf(`%s.%s.%s`, pkgPath, ctlName, methodName), - itemType: handlerTypeController, - ctrlInfo: &handlerController{ - name: methodName, - reflect: v.Elem().Type(), - }, - middleware: middleware, - source: source, - } - s.bindHandlerByMap(m) -} - -func (s *Server) doBindControllerRest( - pattern string, controller Controller, - middleware []HandlerFunc, source string, -) { - m := make(map[string]*handlerItem) - v := reflect.ValueOf(controller) - t := v.Type() - pkgPath := t.Elem().PkgPath() - structName := t.Elem().Name() - for i := 0; i < v.NumMethod(); i++ { - methodName := t.Method(i).Name - if _, ok := methodsMap[strings.ToUpper(methodName)]; !ok { - continue - } - pkgName := gfile.Basename(pkgPath) - ctlName := gstr.Replace(t.String(), fmt.Sprintf(`%s.`, pkgName), "") - if ctlName[0] == '*' { - ctlName = fmt.Sprintf(`(%s)`, ctlName) - } - if _, ok := v.Method(i).Interface().(func()); !ok { - s.Logger().Errorf( - `invalid route method: %s.%s.%s defined as "%s", but "func()" is required for controller registry`, - pkgPath, ctlName, methodName, v.Method(i).Type().String(), - ) - return - } - key := s.mergeBuildInNameToPattern(methodName+":"+pattern, structName, methodName, false) - m[key] = &handlerItem{ - itemName: fmt.Sprintf(`%s.%s.%s`, pkgPath, ctlName, methodName), - itemType: handlerTypeController, - ctrlInfo: &handlerController{ - name: methodName, - reflect: v.Elem().Type(), - }, - middleware: middleware, - source: source, - } - } - s.bindHandlerByMap(m) -} diff --git a/net/ghttp/ghttp_server_service_handler.go b/net/ghttp/ghttp_server_service_handler.go index 0bae88483..5fc5ca6ab 100644 --- a/net/ghttp/ghttp_server_service_handler.go +++ b/net/ghttp/ghttp_server_service_handler.go @@ -1,4 +1,4 @@ -// Copyright GoFrame Author(https://github.com/gogf/gf). All Rights Reserved. +// Copyright GoFrame Author(https://goframe.org). All Rights Reserved. // // This Source Code Form is subject to the terms of the MIT License. // If a copy of the MIT was not distributed with this file, @@ -9,29 +9,40 @@ package ghttp import ( "bytes" "github.com/gogf/gf/debug/gdebug" + "github.com/gogf/gf/errors/gcode" + "github.com/gogf/gf/errors/gerror" + "reflect" "strings" "github.com/gogf/gf/text/gstr" ) // BindHandler registers a handler function to server with given pattern. -func (s *Server) BindHandler(pattern string, handler HandlerFunc) { - s.doBindHandler(pattern, handler, nil, "") +// The parameter `handler` can be type of: +// func(*ghttp.Request) +// func(context.Context) +// func(context.Context,TypeRequest) +// func(context.Context,TypeRequest) error +// func(context.Context,TypeRequest)(TypeResponse,error) +func (s *Server) BindHandler(pattern string, handler interface{}) { + funcInfo, err := s.checkAndCreateFuncInfo(handler, "", "", "") + if err != nil { + s.Logger().Error(err.Error()) + return + } + s.doBindHandler(pattern, funcInfo, nil, "") } // doBindHandler registers a handler function to server with given pattern. // The parameter <pattern> is like: // /user/list, put:/user, delete:/user, post:/user@goframe.org -func (s *Server) doBindHandler( - pattern string, handler HandlerFunc, - middleware []HandlerFunc, source string, -) { +func (s *Server) doBindHandler(pattern string, funcInfo handlerFuncInfo, middleware []HandlerFunc, source string) { s.setHandler(pattern, &handlerItem{ - itemName: gdebug.FuncPath(handler), - itemType: handlerTypeHandler, - itemFunc: handler, - middleware: middleware, - source: source, + Name: gdebug.FuncPath(funcInfo.Func), + Type: handlerTypeHandler, + Info: funcInfo, + Middleware: middleware, + Source: source, }) } @@ -77,13 +88,13 @@ func (s *Server) mergeBuildInNameToPattern(pattern string, structName, methodNam // Rule 3: Use camel case naming. func (s *Server) nameToUri(name string) string { switch s.config.NameToUriType { - case URI_TYPE_FULLNAME: + case UriTypeFullName: return name - case URI_TYPE_ALLLOWER: + case UriTypeAllLower: return strings.ToLower(name) - case URI_TYPE_CAMEL: + case UriTypeCamel: part := bytes.NewBuffer(nil) if gstr.IsLetterUpper(name[0]) { part.WriteByte(name[0] + 32) @@ -93,8 +104,9 @@ func (s *Server) nameToUri(name string) string { part.WriteString(name[1:]) return part.String() - case URI_TYPE_DEFAULT: + case UriTypeDefault: fallthrough + default: part := bytes.NewBuffer(nil) for i := 0; i < len(name); i++ { @@ -110,3 +122,48 @@ func (s *Server) nameToUri(name string) string { return part.String() } } + +func (s *Server) checkAndCreateFuncInfo(f interface{}, pkgPath, objName, methodName string) (info handlerFuncInfo, err error) { + handlerFunc, ok := f.(HandlerFunc) + if !ok { + reflectType := reflect.TypeOf(f) + if reflectType.NumIn() == 0 || reflectType.NumIn() > 2 || reflectType.NumOut() > 2 { + if pkgPath != "" { + err = gerror.NewCodef( + gcode.CodeInvalidParameter, + `invalid handler: %s.%s.%s defined as "%s", but "func(*ghttp.Request)" or "func(context.Context)/func(context.Context,Request)/func(context.Context,Request) error/func(context.Context,Request)(Response,error)" is required`, + pkgPath, objName, methodName, reflect.TypeOf(f).String(), + ) + } else { + err = gerror.NewCodef( + gcode.CodeInvalidParameter, + `invalid handler: defined as "%s", but "func(*ghttp.Request)" or "func(context.Context)/func(context.Context,Request)/func(context.Context,Request) error/func(context.Context,Request)(Response,error)" is required`, + reflect.TypeOf(f).String(), + ) + } + return + } + + if reflectType.In(0).String() != "context.Context" { + err = gerror.NewCodef( + gcode.CodeInvalidParameter, + `invalid handler: defined as "%s", but the first input parameter should be type of "context.Context"`, + reflect.TypeOf(f).String(), + ) + return + } + + if reflectType.NumOut() > 0 && reflectType.Out(reflectType.NumOut()-1).String() != "error" { + err = gerror.NewCodef( + gcode.CodeInvalidParameter, + `invalid handler: defined as "%s", but the last output parameter should be type of "error"`, + reflect.TypeOf(f).String(), + ) + return + } + } + info.Func = handlerFunc + info.Type = reflect.TypeOf(f) + info.Value = reflect.ValueOf(f) + return +} diff --git a/net/ghttp/ghttp_server_service_object.go b/net/ghttp/ghttp_server_service_object.go index 656f32c4f..e0130bc3b 100644 --- a/net/ghttp/ghttp_server_service_object.go +++ b/net/ghttp/ghttp_server_service_object.go @@ -1,4 +1,4 @@ -// Copyright GoFrame Author(https://github.com/gogf/gf). All Rights Reserved. +// Copyright GoFrame Author(https://goframe.org). All Rights Reserved. // // This Source Code Form is subject to the terms of the MIT License. // If a copy of the MIT was not distributed with this file, @@ -46,10 +46,7 @@ func (s *Server) BindObjectRest(pattern string, object interface{}) { s.doBindObjectRest(pattern, object, nil, "") } -func (s *Server) doBindObject( - pattern string, object interface{}, method string, - middleware []HandlerFunc, source string, -) { +func (s *Server) doBindObject(pattern string, object interface{}, method string, middleware []HandlerFunc, source string) { // Convert input method to map for convenience and high performance searching purpose. var methodMap map[string]bool if len(method) > 0 { @@ -58,7 +55,8 @@ func (s *Server) doBindObject( methodMap[strings.TrimSpace(v)] = true } } - // 当pattern中的method为all时,去掉该method,以便于后续方法判断 + // If the `method` in `pattern` is `defaultMethod`, + // it removes for convenience for next statement control. domain, method, path, err := s.parsePattern(pattern) if err != nil { s.Logger().Fatal(err) @@ -67,11 +65,21 @@ func (s *Server) doBindObject( if strings.EqualFold(method, defaultMethod) { pattern = s.serveHandlerKey("", path, domain) } - m := make(map[string]*handlerItem) - v := reflect.ValueOf(object) - t := v.Type() - initFunc := (func(*Request))(nil) - shutFunc := (func(*Request))(nil) + var ( + m = make(map[string]*handlerItem) + v = reflect.ValueOf(object) + t = v.Type() + initFunc func(*Request) + shutFunc func(*Request) + ) + // If given `object` is not pointer, it then creates a temporary one, + // of which the value is `v`. + if v.Kind() == reflect.Struct { + newValue := reflect.New(t) + newValue.Elem().Set(v) + v = newValue + t = v.Type() + } structName := t.Elem().Name() if v.MethodByName("Init").IsValid() { initFunc = v.MethodByName("Init").Interface().(func(*Request)) @@ -93,30 +101,22 @@ func (s *Server) doBindObject( if objName[0] == '*' { objName = fmt.Sprintf(`(%s)`, objName) } - itemFunc, ok := v.Method(i).Interface().(func(*Request)) - if !ok { - if len(methodMap) > 0 { - s.Logger().Errorf( - `invalid route method: %s.%s.%s defined as "%s", but "func(*ghttp.Request)" is required for object registry`, - pkgPath, objName, methodName, v.Method(i).Type().String(), - ) - } else { - s.Logger().Debugf( - `ignore route method: %s.%s.%s defined as "%s", no match "func(*ghttp.Request)" for object registry`, - pkgPath, objName, methodName, v.Method(i).Type().String(), - ) - } - continue + + funcInfo, err := s.checkAndCreateFuncInfo(v.Method(i).Interface(), pkgPath, objName, methodName) + if err != nil { + s.Logger().Error(err.Error()) + return } + key := s.mergeBuildInNameToPattern(pattern, structName, methodName, true) m[key] = &handlerItem{ - itemName: fmt.Sprintf(`%s.%s.%s`, pkgPath, objName, methodName), - itemType: handlerTypeObject, - itemFunc: itemFunc, - initFunc: initFunc, - shutFunc: shutFunc, - middleware: middleware, - source: source, + Name: fmt.Sprintf(`%s.%s.%s`, pkgPath, objName, methodName), + Type: handlerTypeObject, + Info: funcInfo, + InitFunc: initFunc, + ShutFunc: shutFunc, + Middleware: middleware, + Source: source, } // If there's "Index" method, then an additional route is automatically added // to match the main URI, for example: @@ -132,26 +132,35 @@ func (s *Server) doBindObject( k = "/" + k } m[k] = &handlerItem{ - itemName: fmt.Sprintf(`%s.%s.%s`, pkgPath, objName, methodName), - itemType: handlerTypeObject, - itemFunc: itemFunc, - initFunc: initFunc, - shutFunc: shutFunc, - middleware: middleware, - source: source, + Name: fmt.Sprintf(`%s.%s.%s`, pkgPath, objName, methodName), + Type: handlerTypeObject, + Info: funcInfo, + InitFunc: initFunc, + ShutFunc: shutFunc, + Middleware: middleware, + Source: source, } } } s.bindHandlerByMap(m) } -func (s *Server) doBindObjectMethod( - pattern string, object interface{}, method string, - middleware []HandlerFunc, source string, -) { - m := make(map[string]*handlerItem) - v := reflect.ValueOf(object) - t := v.Type() +func (s *Server) doBindObjectMethod(pattern string, object interface{}, method string, middleware []HandlerFunc, source string) { + var ( + m = make(map[string]*handlerItem) + v = reflect.ValueOf(object) + t = v.Type() + initFunc func(*Request) + shutFunc func(*Request) + ) + // If given `object` is not pointer, it then creates a temporary one, + // of which the value is `v`. + if v.Kind() == reflect.Struct { + newValue := reflect.New(t) + newValue.Elem().Set(v) + v = newValue + t = v.Type() + } structName := t.Elem().Name() methodName := strings.TrimSpace(method) methodValue := v.MethodByName(methodName) @@ -159,8 +168,6 @@ func (s *Server) doBindObjectMethod( s.Logger().Fatal("invalid method name: " + methodName) return } - initFunc := (func(*Request))(nil) - shutFunc := (func(*Request))(nil) if v.MethodByName("Init").IsValid() { initFunc = v.MethodByName("Init").Interface().(func(*Request)) } @@ -173,43 +180,49 @@ func (s *Server) doBindObjectMethod( if objName[0] == '*' { objName = fmt.Sprintf(`(%s)`, objName) } - itemFunc, ok := methodValue.Interface().(func(*Request)) - if !ok { - s.Logger().Errorf( - `invalid route method: %s.%s.%s defined as "%s", but "func(*ghttp.Request)" is required for object registry`, - pkgPath, objName, methodName, methodValue.Type().String(), - ) + + funcInfo, err := s.checkAndCreateFuncInfo(methodValue.Interface(), pkgPath, objName, methodName) + if err != nil { + s.Logger().Error(err.Error()) return } + key := s.mergeBuildInNameToPattern(pattern, structName, methodName, false) m[key] = &handlerItem{ - itemName: fmt.Sprintf(`%s.%s.%s`, pkgPath, objName, methodName), - itemType: handlerTypeObject, - itemFunc: itemFunc, - initFunc: initFunc, - shutFunc: shutFunc, - middleware: middleware, - source: source, + Name: fmt.Sprintf(`%s.%s.%s`, pkgPath, objName, methodName), + Type: handlerTypeObject, + Info: funcInfo, + InitFunc: initFunc, + ShutFunc: shutFunc, + Middleware: middleware, + Source: source, } s.bindHandlerByMap(m) } -func (s *Server) doBindObjectRest( - pattern string, object interface{}, - middleware []HandlerFunc, source string, -) { - m := make(map[string]*handlerItem) - v := reflect.ValueOf(object) - t := v.Type() - initFunc := (func(*Request))(nil) - shutFunc := (func(*Request))(nil) - structName := t.Elem().Name() - if v.MethodByName("Init").IsValid() { - initFunc = v.MethodByName("Init").Interface().(func(*Request)) +func (s *Server) doBindObjectRest(pattern string, object interface{}, middleware []HandlerFunc, source string) { + var ( + m = make(map[string]*handlerItem) + v = reflect.ValueOf(object) + t = v.Type() + initFunc func(*Request) + shutFunc func(*Request) + ) + // If given `object` is not pointer, it then creates a temporary one, + // of which the value is `v`. + if v.Kind() == reflect.Struct { + newValue := reflect.New(t) + newValue.Elem().Set(v) + v = newValue + t = v.Type() } - if v.MethodByName("Shut").IsValid() { - shutFunc = v.MethodByName("Shut").Interface().(func(*Request)) + structName := t.Elem().Name() + if v.MethodByName(methodNameInit).IsValid() { + initFunc = v.MethodByName(methodNameInit).Interface().(func(*Request)) + } + if v.MethodByName(methodNameShut).IsValid() { + shutFunc = v.MethodByName(methodNameShut).Interface().(func(*Request)) } pkgPath := t.Elem().PkgPath() for i := 0; i < v.NumMethod(); i++ { @@ -222,23 +235,22 @@ func (s *Server) doBindObjectRest( if objName[0] == '*' { objName = fmt.Sprintf(`(%s)`, objName) } - itemFunc, ok := v.Method(i).Interface().(func(*Request)) - if !ok { - s.Logger().Errorf( - `invalid route method: %s.%s.%s defined as "%s", but "func(*ghttp.Request)" is required for object registry`, - pkgPath, objName, methodName, v.Method(i).Type().String(), - ) - continue + + funcInfo, err := s.checkAndCreateFuncInfo(v.Method(i).Interface(), pkgPath, objName, methodName) + if err != nil { + s.Logger().Error(err.Error()) + return } + key := s.mergeBuildInNameToPattern(methodName+":"+pattern, structName, methodName, false) m[key] = &handlerItem{ - itemName: fmt.Sprintf(`%s.%s.%s`, pkgPath, objName, methodName), - itemType: handlerTypeObject, - itemFunc: itemFunc, - initFunc: initFunc, - shutFunc: shutFunc, - middleware: middleware, - source: source, + Name: fmt.Sprintf(`%s.%s.%s`, pkgPath, objName, methodName), + Type: handlerTypeObject, + Info: funcInfo, + InitFunc: initFunc, + ShutFunc: shutFunc, + Middleware: middleware, + Source: source, } } s.bindHandlerByMap(m) diff --git a/net/ghttp/ghttp_server_session.go b/net/ghttp/ghttp_server_session.go index debc0389d..a295628b3 100644 --- a/net/ghttp/ghttp_server_session.go +++ b/net/ghttp/ghttp_server_session.go @@ -1,4 +1,4 @@ -// Copyright 2017 gf Author(https://github.com/gogf/gf). All Rights Reserved. +// Copyright GoFrame Author(https://goframe.org). All Rights Reserved. // // This Source Code Form is subject to the terms of the MIT License. // If a copy of the MIT was not distributed with this file, diff --git a/net/ghttp/ghttp_server_status.go b/net/ghttp/ghttp_server_status.go index 8b20769e8..7cc627abd 100644 --- a/net/ghttp/ghttp_server_status.go +++ b/net/ghttp/ghttp_server_status.go @@ -1,4 +1,4 @@ -// Copyright GoFrame Author(https://github.com/gogf/gf). All Rights Reserved. +// Copyright GoFrame Author(https://goframe.org). All Rights Reserved. // // This Source Code Form is subject to the terms of the MIT License. // If a copy of the MIT was not distributed with this file, diff --git a/net/ghttp/ghttp_server_util.go b/net/ghttp/ghttp_server_util.go new file mode 100644 index 000000000..f9a3ea8e1 --- /dev/null +++ b/net/ghttp/ghttp_server_util.go @@ -0,0 +1,23 @@ +// Copyright GoFrame Author(https://goframe.org). All Rights Reserved. +// +// This Source Code Form is subject to the terms of the MIT License. +// If a copy of the MIT was not distributed with this file, +// You can obtain one at https://github.com/gogf/gf. + +package ghttp + +import "net/http" + +// WrapF is a helper function for wrapping http.HandlerFunc and returns a ghttp.HandlerFunc. +func WrapF(f http.HandlerFunc) HandlerFunc { + return func(r *Request) { + f(r.Response.Writer, r.Request) + } +} + +// WrapH is a helper function for wrapping http.Handler and returns a ghttp.HandlerFunc. +func WrapH(h http.Handler) HandlerFunc { + return func(r *Request) { + h.ServeHTTP(r.Response.Writer, r.Request) + } +} diff --git a/net/ghttp/ghttp_server_websocket.go b/net/ghttp/ghttp_server_websocket.go index 07d3a8bca..65c8740ec 100644 --- a/net/ghttp/ghttp_server_websocket.go +++ b/net/ghttp/ghttp_server_websocket.go @@ -1,4 +1,4 @@ -// Copyright 2018 gf Author(https://github.com/gogf/gf). All Rights Reserved. +// Copyright GoFrame Author(https://goframe.org). All Rights Reserved. // // This Source Code Form is subject to the terms of the MIT License. // If a copy of the MIT was not distributed with this file, diff --git a/net/ghttp/ghttp_unit_client_dump_test.go b/net/ghttp/ghttp_unit_client_dump_test.go index 0bb505759..f04f89eb0 100644 --- a/net/ghttp/ghttp_unit_client_dump_test.go +++ b/net/ghttp/ghttp_unit_client_dump_test.go @@ -1,4 +1,4 @@ -// Copyright 2020 gf Author(https://github.com/gogf/gf). All Rights Reserved. +// Copyright GoFrame Author(https://goframe.org). All Rights Reserved. // // This Source Code Form is subject to the terms of the MIT License. // If a copy of the MIT was not distributed with this file, @@ -36,7 +36,7 @@ func Test_Client_Request_13_Dump(t *testing.T) { time.Sleep(100 * time.Millisecond) gtest.C(t, func(t *gtest.T) { url := fmt.Sprintf("http://127.0.0.1:%d", p) - client := g.Client().SetPrefix(url).ContentJson() + client := g.Client().SetPrefix(url).ContentJson().SetDump(true) r, err := client.Post("/hello", g.Map{"field": "test_for_request_body"}) t.Assert(err, nil) dumpedText := r.RawRequest() @@ -46,13 +46,11 @@ func Test_Client_Request_13_Dump(t *testing.T) { t.Assert(gstr.Contains(dumpedText2, "test_for_response_body"), true) client2 := g.Client().SetPrefix(url).ContentType("text/html") - r2, err := client2.Post("/hello2", g.Map{"field": "test_for_request_body"}) + r2, err := client2.Dump().Post("/hello2", g.Map{"field": "test_for_request_body"}) t.Assert(err, nil) dumpedText3 := r2.RawRequest() t.Assert(gstr.Contains(dumpedText3, "test_for_request_body"), true) dumpedText4 := r2.RawResponse() t.Assert(gstr.Contains(dumpedText4, "test_for_request_body"), false) - }) - } diff --git a/net/ghttp/ghttp_unit_client_test.go b/net/ghttp/ghttp_unit_client_test.go index e48340209..0845b8be7 100644 --- a/net/ghttp/ghttp_unit_client_test.go +++ b/net/ghttp/ghttp_unit_client_test.go @@ -1,4 +1,4 @@ -// Copyright 2018 gf Author(https://github.com/gogf/gf). All Rights Reserved. +// Copyright GoFrame Author(https://goframe.org). All Rights Reserved. // // This Source Code Form is subject to the terms of the MIT License. // If a copy of the MIT was not distributed with this file, @@ -7,14 +7,19 @@ package ghttp_test import ( + "bytes" "context" "fmt" - "github.com/gogf/gf/debug/gdebug" - "github.com/gogf/gf/os/gfile" - "github.com/gogf/gf/util/guid" + "io/ioutil" + "net/http" "testing" "time" + "github.com/gogf/gf/debug/gdebug" + "github.com/gogf/gf/errors/gerror" + "github.com/gogf/gf/os/gfile" + "github.com/gogf/gf/util/guid" + "github.com/gogf/gf/frame/g" "github.com/gogf/gf/net/ghttp" "github.com/gogf/gf/test/gtest" @@ -332,3 +337,107 @@ func Test_Client_File_And_Param(t *testing.T) { t.Assert(c.PostContent("/", data), data["json"].(string)+gfile.GetContents(path)) }) } + +func Test_Client_Middleware(t *testing.T) { + p, _ := ports.PopRand() + s := g.Server(p) + isServerHandler := false + s.BindHandler("/", func(r *ghttp.Request) { + isServerHandler = true + }) + s.SetPort(p) + s.SetDumpRouterMap(false) + s.Start() + defer s.Shutdown() + + time.Sleep(100 * time.Millisecond) + + gtest.C(t, func(t *gtest.T) { + var ( + str1 = "" + str2 = "resp body" + ) + c := g.Client().SetPrefix(fmt.Sprintf("http://127.0.0.1:%d", p)) + c.Use(func(c *ghttp.Client, r *http.Request) (resp *ghttp.ClientResponse, err error) { + str1 += "a" + resp, err = c.Next(r) + if err != nil { + return nil, err + } + str1 += "b" + return + }) + c.Use(func(c *ghttp.Client, r *http.Request) (resp *ghttp.ClientResponse, err error) { + str1 += "c" + resp, err = c.Next(r) + if err != nil { + return nil, err + } + str1 += "d" + return + }) + c.Use(func(c *ghttp.Client, r *http.Request) (resp *ghttp.ClientResponse, err error) { + str1 += "e" + resp, err = c.Next(r) + if err != nil { + return nil, err + } + resp.Response.Body = ioutil.NopCloser(bytes.NewBufferString(str2)) + str1 += "f" + return + }) + resp, err := c.Get("/") + t.Assert(str1, "acefdb") + t.Assert(err, nil) + t.Assert(resp.ReadAllString(), str2) + t.Assert(isServerHandler, true) + + // test abort, abort will not send + var ( + str3 = "" + abortStr = "abort request" + ) + + c = g.Client().SetPrefix(fmt.Sprintf("http://127.0.0.1:%d", p)) + c.Use(func(c *ghttp.Client, r *http.Request) (resp *ghttp.ClientResponse, err error) { + str3 += "a" + resp, err = c.Next(r) + str3 += "b" + return + }) + c.Use(func(c *ghttp.Client, r *http.Request) (*ghttp.ClientResponse, error) { + str3 += "c" + return nil, gerror.New(abortStr) + }) + c.Use(func(c *ghttp.Client, r *http.Request) (resp *ghttp.ClientResponse, err error) { + str3 += "f" + resp, err = c.Next(r) + str3 += "g" + return + }) + resp, err = c.Get("/") + t.Assert(err, abortStr) + t.Assert(str3, "acb") + t.Assert(resp, nil) + }) +} + +func Test_Client_Agent(t *testing.T) { + p, _ := ports.PopRand() + s := g.Server(p) + s.BindHandler("/", func(r *ghttp.Request) { + r.Response.Write(r.UserAgent()) + }) + s.SetPort(p) + s.SetDumpRouterMap(false) + s.Start() + defer s.Shutdown() + + time.Sleep(100 * time.Millisecond) + + gtest.C(t, func(t *gtest.T) { + c := g.Client().SetPrefix(fmt.Sprintf("http://127.0.0.1:%d", p)) + c.SetAgent("test") + t.Assert(c.GetContent("/"), "test") + }) +} diff --git a/net/ghttp/ghttp_unit_config_test.go b/net/ghttp/ghttp_unit_config_test.go index 45a049cfd..cd5949b97 100644 --- a/net/ghttp/ghttp_unit_config_test.go +++ b/net/ghttp/ghttp_unit_config_test.go @@ -1,4 +1,4 @@ -// Copyright 2018 gf Author(https://github.com/gogf/gf). All Rights Reserved. +// Copyright GoFrame Author(https://goframe.org). All Rights Reserved. // // This Source Code Form is subject to the terms of the MIT License. // If a copy of the MIT was not distributed with this file, @@ -52,7 +52,7 @@ func Test_SetConfigWithMap(t *testing.T) { "AccessLogEnabled": true, "ErrorLogEnabled": true, "PProfEnabled": true, - "LogPath": "/var/log/MyServerLog", + "LogPath": "/tmp/log/MyServerLog", "SessionIdName": "MySessionId", "SessionPath": "/tmp/MySessionStoragePath", "SessionMaxAge": 24 * time.Hour, @@ -152,7 +152,7 @@ func Test_ClientMaxBodySize_File(t *testing.T) { defer gfile.Remove(path) t.Assert( gstr.Trim(c.PostContent("/", "name=john&file=@file:"+path)), - "http: request body too large", + "Invalid Request: http: request body too large", ) }) } diff --git a/net/ghttp/ghttp_unit_context_test.go b/net/ghttp/ghttp_unit_context_test.go index 86be300aa..74897cfcf 100644 --- a/net/ghttp/ghttp_unit_context_test.go +++ b/net/ghttp/ghttp_unit_context_test.go @@ -1,4 +1,4 @@ -// Copyright 2018 gf Author(https://github.com/gogf/gf). All Rights Reserved. +// Copyright GoFrame Author(https://goframe.org). All Rights Reserved. // // This Source Code Form is subject to the terms of the MIT License. // If a copy of the MIT was not distributed with this file, diff --git a/net/ghttp/ghttp_unit_cookie_test.go b/net/ghttp/ghttp_unit_cookie_test.go index 83262b739..b9f6c0e92 100644 --- a/net/ghttp/ghttp_unit_cookie_test.go +++ b/net/ghttp/ghttp_unit_cookie_test.go @@ -1,4 +1,4 @@ -// Copyright 2018 gf Author(https://github.com/gogf/gf). All Rights Reserved. +// Copyright GoFrame Author(https://goframe.org). All Rights Reserved. // // This Source Code Form is subject to the terms of the MIT License. // If a copy of the MIT was not distributed with this file, diff --git a/net/ghttp/ghttp_unit_error_code_test.go b/net/ghttp/ghttp_unit_error_code_test.go new file mode 100644 index 000000000..ca77f4254 --- /dev/null +++ b/net/ghttp/ghttp_unit_error_code_test.go @@ -0,0 +1,46 @@ +// Copyright GoFrame Author(https://goframe.org). All Rights Reserved. +// +// This Source Code Form is subject to the terms of the MIT License. +// If a copy of the MIT was not distributed with this file, +// You can obtain one at https://github.com/gogf/gf. + +// static service testing. + +package ghttp_test + +import ( + "fmt" + "github.com/gogf/gf/errors/gcode" + "github.com/gogf/gf/errors/gerror" + "github.com/gogf/gf/net/ghttp" + "testing" + "time" + + "github.com/gogf/gf/frame/g" + "github.com/gogf/gf/test/gtest" +) + +func Test_Error_Code(t *testing.T) { + gtest.C(t, func(t *gtest.T) { + p, _ := ports.PopRand() + s := g.Server(p) + s.Group("/", func(group *ghttp.RouterGroup) { + group.Middleware(func(r *ghttp.Request) { + r.Middleware.Next() + r.Response.ClearBuffer() + r.Response.Write(gerror.Code(r.GetError())) + }) + group.ALL("/", func(r *ghttp.Request) { + panic(gerror.NewCode(gcode.New(10000, "", nil), "test error")) + }) + }) + s.SetPort(p) + s.Start() + defer s.Shutdown() + time.Sleep(100 * time.Millisecond) + c := g.Client() + c.SetPrefix(fmt.Sprintf("http://127.0.0.1:%d", p)) + + t.Assert(c.GetContent("/"), "10000") + }) +} diff --git a/net/ghttp/ghttp_unit_https_test.go b/net/ghttp/ghttp_unit_https_test.go index 25b87995b..3d8c3e417 100644 --- a/net/ghttp/ghttp_unit_https_test.go +++ b/net/ghttp/ghttp_unit_https_test.go @@ -1,4 +1,4 @@ -// Copyright 2018 gf Author(https://github.com/gogf/gf). All Rights Reserved. +// Copyright GoFrame Author(https://goframe.org). All Rights Reserved. // // This Source Code Form is subject to the terms of the MIT License. // If a copy of the MIT was not distributed with this file, diff --git a/net/ghttp/ghttp_unit_init_test.go b/net/ghttp/ghttp_unit_init_test.go index d5d02dc33..df18b679a 100644 --- a/net/ghttp/ghttp_unit_init_test.go +++ b/net/ghttp/ghttp_unit_init_test.go @@ -1,4 +1,4 @@ -// Copyright 2018 gf Author(https://github.com/gogf/gf). All Rights Reserved. +// Copyright GoFrame Author(https://goframe.org). All Rights Reserved. // // This Source Code Form is subject to the terms of the MIT License. // If a copy of the MIT was not distributed with this file, diff --git a/net/ghttp/ghttp_unit_ip_test.go b/net/ghttp/ghttp_unit_ip_test.go index 6d8f851ee..e1d9ffb34 100644 --- a/net/ghttp/ghttp_unit_ip_test.go +++ b/net/ghttp/ghttp_unit_ip_test.go @@ -1,4 +1,4 @@ -// Copyright 2020 gf Author(https://github.com/gogf/gf). All Rights Reserved. +// Copyright GoFrame Author(https://goframe.org). All Rights Reserved. // // This Source Code Form is subject to the terms of the MIT License. // If a copy of the MIT was not distributed with this file, diff --git a/net/ghttp/ghttp_unit_log_test.go b/net/ghttp/ghttp_unit_log_test.go index 28d1ed428..4d9e876f7 100644 --- a/net/ghttp/ghttp_unit_log_test.go +++ b/net/ghttp/ghttp_unit_log_test.go @@ -1,4 +1,4 @@ -// Copyright 2018 gf Author(https://github.com/gogf/gf). All Rights Reserved. +// Copyright GoFrame Author(https://goframe.org). All Rights Reserved. // // This Source Code Form is subject to the terms of the MIT License. // If a copy of the MIT was not distributed with this file, diff --git a/net/ghttp/ghttp_unit_mess_test.go b/net/ghttp/ghttp_unit_mess_test.go index 563d61dcf..401620d08 100644 --- a/net/ghttp/ghttp_unit_mess_test.go +++ b/net/ghttp/ghttp_unit_mess_test.go @@ -1,4 +1,4 @@ -// Copyright 2018 gf Author(https://github.com/gogf/gf). All Rights Reserved. +// Copyright GoFrame Author(https://goframe.org). All Rights Reserved. // // This Source Code Form is subject to the terms of the MIT License. // If a copy of the MIT was not distributed with this file, diff --git a/net/ghttp/ghttp_unit_middleware_basic_test.go b/net/ghttp/ghttp_unit_middleware_basic_test.go index 8ef8d52e5..c950891cd 100644 --- a/net/ghttp/ghttp_unit_middleware_basic_test.go +++ b/net/ghttp/ghttp_unit_middleware_basic_test.go @@ -1,4 +1,4 @@ -// Copyright GoFrame Author(https://github.com/gogf/gf). All Rights Reserved. +// Copyright GoFrame Author(https://goframe.org). All Rights Reserved. // // This Source Code Form is subject to the terms of the MIT License. // If a copy of the MIT was not distributed with this file, diff --git a/net/ghttp/ghttp_unit_middleware_cors_test.go b/net/ghttp/ghttp_unit_middleware_cors_test.go index 117d51623..71fb4f4ff 100644 --- a/net/ghttp/ghttp_unit_middleware_cors_test.go +++ b/net/ghttp/ghttp_unit_middleware_cors_test.go @@ -1,4 +1,4 @@ -// Copyright 2018 gf Author(https://github.com/gogf/gf). All Rights Reserved. +// Copyright GoFrame Author(https://goframe.org). All Rights Reserved. // // This Source Code Form is subject to the terms of the MIT License. // If a copy of the MIT was not distributed with this file, diff --git a/net/ghttp/ghttp_unit_pprof_test.go b/net/ghttp/ghttp_unit_pprof_test.go index 2b3c96b66..be2413216 100644 --- a/net/ghttp/ghttp_unit_pprof_test.go +++ b/net/ghttp/ghttp_unit_pprof_test.go @@ -1,4 +1,4 @@ -// Copyright 2018 gf Author(https://github.com/gogf/gf). All Rights Reserved. +// Copyright GoFrame Author(https://goframe.org). All Rights Reserved. // // This Source Code Form is subject to the terms of the MIT License. // If a copy of the MIT was not distributed with this file, diff --git a/net/ghttp/ghttp_unit_request_ctx_test.go b/net/ghttp/ghttp_unit_request_ctx_test.go index f4b31a6c1..92b30aa97 100644 --- a/net/ghttp/ghttp_unit_request_ctx_test.go +++ b/net/ghttp/ghttp_unit_request_ctx_test.go @@ -1,4 +1,4 @@ -// Copyright GoFrame Author(https://github.com/gogf/gf). All Rights Reserved. +// Copyright GoFrame Author(https://goframe.org). All Rights Reserved. // // This Source Code Form is subject to the terms of the MIT License. // If a copy of the MIT was not distributed with this file, diff --git a/net/ghttp/ghttp_unit_request_file_test.go b/net/ghttp/ghttp_unit_request_file_test.go index 03a4b4245..b6b9dd57d 100644 --- a/net/ghttp/ghttp_unit_request_file_test.go +++ b/net/ghttp/ghttp_unit_request_file_test.go @@ -1,4 +1,4 @@ -// Copyright GoFrame Author(https://github.com/gogf/gf). All Rights Reserved. +// Copyright GoFrame Author(https://goframe.org). All Rights Reserved. // // This Source Code Form is subject to the terms of the MIT License. // If a copy of the MIT was not distributed with this file, diff --git a/net/ghttp/ghttp_unit_request_json_test.go b/net/ghttp/ghttp_unit_request_json_test.go index 8965be3ad..7feb32e52 100644 --- a/net/ghttp/ghttp_unit_request_json_test.go +++ b/net/ghttp/ghttp_unit_request_json_test.go @@ -1,4 +1,4 @@ -// Copyright GoFrame Author(https://github.com/gogf/gf). All Rights Reserved. +// Copyright GoFrame Author(https://goframe.org). All Rights Reserved. // // This Source Code Form is subject to the terms of the MIT License. // If a copy of the MIT was not distributed with this file, @@ -23,7 +23,7 @@ func Test_Params_Json_Request(t *testing.T) { Name string Time *time.Time Pass1 string `p:"password1"` - Pass2 string `p:"password2" v:"required|length:2,20|password3|same:password1#||密码强度不足|两次密码不一致"` + Pass2 string `p:"password2" v:"password2@required|length:2,20|password3|same:password1#||密码强度不足|两次密码不一致"` } p, _ := ports.PopRand() s := g.Server(p) @@ -144,7 +144,7 @@ func Test_Params_Json_Response(t *testing.T) { client.SetPrefix(fmt.Sprintf("http://127.0.0.1:%d", p)) map1 := make(map[string]interface{}) - err1 := json.Unmarshal([]byte(client.GetContent("/json1")), &map1) + err1 := json.UnmarshalUseNumber([]byte(client.GetContent("/json1")), &map1) t.Assert(err1, nil) t.Assert(len(map1), 4) t.Assert(map1["Name"], "john") @@ -153,7 +153,7 @@ func Test_Params_Json_Response(t *testing.T) { t.Assert(map1["password2"], "456") map2 := make(map[string]interface{}) - err2 := json.Unmarshal([]byte(client.GetContent("/json2")), &map2) + err2 := json.UnmarshalUseNumber([]byte(client.GetContent("/json2")), &map2) t.Assert(err2, nil) t.Assert(len(map2), 4) t.Assert(map2["Name"], "john") @@ -162,14 +162,14 @@ func Test_Params_Json_Response(t *testing.T) { t.Assert(map2["password2"], "456") map3 := make(map[string]interface{}) - err3 := json.Unmarshal([]byte(client.GetContent("/json3")), &map3) + err3 := json.UnmarshalUseNumber([]byte(client.GetContent("/json3")), &map3) t.Assert(err3, nil) t.Assert(len(map3), 2) t.Assert(map3["success"], "true") t.Assert(map3["message"], g.Map{"body": "测试", "code": 3, "error": "error"}) map4 := make(map[string]interface{}) - err4 := json.Unmarshal([]byte(client.GetContent("/json4")), &map4) + err4 := json.UnmarshalUseNumber([]byte(client.GetContent("/json4")), &map4) t.Assert(err4, nil) t.Assert(len(map4), 2) t.Assert(map4["success"], "true") diff --git a/net/ghttp/ghttp_unit_request_page_test.go b/net/ghttp/ghttp_unit_request_page_test.go index 7fdb83562..42c54d44e 100644 --- a/net/ghttp/ghttp_unit_request_page_test.go +++ b/net/ghttp/ghttp_unit_request_page_test.go @@ -1,4 +1,4 @@ -// Copyright GoFrame Author(https://github.com/gogf/gf). All Rights Reserved. +// Copyright GoFrame Author(https://goframe.org). All Rights Reserved. // // This Source Code Form is subject to the terms of the MIT License. // If a copy of the MIT was not distributed with this file, diff --git a/net/ghttp/ghttp_unit_request_struct_test.go b/net/ghttp/ghttp_unit_request_struct_test.go index 009c45672..5f4601744 100644 --- a/net/ghttp/ghttp_unit_request_struct_test.go +++ b/net/ghttp/ghttp_unit_request_struct_test.go @@ -1,4 +1,4 @@ -// Copyright GoFrame Author(https://github.com/gogf/gf). All Rights Reserved. +// Copyright GoFrame Author(https://goframe.org). All Rights Reserved. // // This Source Code Form is subject to the terms of the MIT License. // If a copy of the MIT was not distributed with this file, @@ -395,7 +395,7 @@ func Test_Params_Struct(t *testing.T) { Name string Time *time.Time Pass1 string `p:"password1"` - Pass2 string `p:"password2" v:"passwd1 @required|length:2,20|password3#||密码强度不足"` + Pass2 string `p:"password2" v:"password2 @required|length:2,20|password3#||密码强度不足"` } p, _ := ports.PopRand() s := g.Server(p) @@ -425,7 +425,7 @@ func Test_Params_Struct(t *testing.T) { if err := r.GetStruct(user); err != nil { r.Response.WriteExit(err) } - if err := gvalid.CheckStruct(user, nil); err != nil { + if err := gvalid.CheckStruct(r.Context(), user, nil); err != nil { r.Response.WriteExit(err) } } @@ -452,8 +452,8 @@ func Test_Params_Struct(t *testing.T) { t.Assert(client.PostContent("/struct1", `id=1&name=john&password1=123&password2=456`), `1john123456`) t.Assert(client.PostContent("/struct2", `id=1&name=john&password1=123&password2=456`), `1john123456`) t.Assert(client.PostContent("/struct2", ``), ``) - t.Assert(client.PostContent("/struct-valid", `id=1&name=john&password1=123&password2=0`), `The passwd1 value length must be between 2 and 20; 密码强度不足`) - t.Assert(client.PostContent("/parse", `id=1&name=john&password1=123&password2=0`), `The passwd1 value length must be between 2 and 20; 密码强度不足`) + t.Assert(client.PostContent("/struct-valid", `id=1&name=john&password1=123&password2=0`), `The password2 value length must be between 2 and 20; 密码强度不足`) + t.Assert(client.PostContent("/parse", `id=1&name=john&password1=123&password2=0`), `The password2 value length must be between 2 and 20; 密码强度不足`) t.Assert(client.PostContent("/parse", `{"id":1,"name":"john","password1":"123Abc!@#","password2":"123Abc!@#"}`), `1john123Abc!@#123Abc!@#`) }) } @@ -464,7 +464,7 @@ func Test_Params_Structs(t *testing.T) { Name string Time *time.Time Pass1 string `p:"password1"` - Pass2 string `p:"password2" v:"passwd1 @required|length:2,20|password3#||密码强度不足"` + Pass2 string `p:"password2" v:"password2 @required|length:2,20|password3#||密码强度不足"` } p, _ := ports.PopRand() s := g.Server(p) @@ -491,3 +491,39 @@ func Test_Params_Structs(t *testing.T) { ) }) } + +func Test_Params_Struct_Validation(t *testing.T) { + type User struct { + Id int `v:"required"` + Name string `v:"name@required-with:id"` + } + p, _ := ports.PopRand() + s := g.Server(p) + s.Group("/", func(group *ghttp.RouterGroup) { + group.ALL("/", func(r *ghttp.Request) { + var ( + err error + user *User + ) + err = r.Parse(&user) + if err != nil { + r.Response.WriteExit(err) + } + r.Response.WriteExit(user.Id, user.Name) + }) + }) + s.SetPort(p) + s.SetDumpRouterMap(false) + s.Start() + defer s.Shutdown() + + time.Sleep(100 * time.Millisecond) + gtest.C(t, func(t *gtest.T) { + c := g.Client() + c.SetPrefix(fmt.Sprintf("http://127.0.0.1:%d", p)) + t.Assert(c.GetContent("/", ``), `The Id field is required`) + t.Assert(c.GetContent("/", `id=1&name=john`), `1john`) + t.Assert(c.PostContent("/", `id=1&name=john&password1=123&password2=456`), `1john`) + t.Assert(c.PostContent("/", `id=1`), `The name field is required`) + }) +} diff --git a/net/ghttp/ghttp_unit_request_test.go b/net/ghttp/ghttp_unit_request_test.go index 47e2798d8..96ba9a375 100644 --- a/net/ghttp/ghttp_unit_request_test.go +++ b/net/ghttp/ghttp_unit_request_test.go @@ -1,4 +1,4 @@ -// Copyright GoFrame Author(https://github.com/gogf/gf). All Rights Reserved. +// Copyright GoFrame Author(https://goframe.org). All Rights Reserved. // // This Source Code Form is subject to the terms of the MIT License. // If a copy of the MIT was not distributed with this file, @@ -586,3 +586,124 @@ func Test_Params_Parse_DefaultValueTag(t *testing.T) { t.Assert(client.PostContent("/parse", `{"name":"smith", "score":100}`), `{"Name":"smith","Score":100}`) }) } + +func Test_Params_Parse_Validation(t *testing.T) { + type RegisterReq struct { + Name string `p:"username" v:"required|length:6,30#请输入账号|账号长度为:min到:max位"` + Pass string `p:"password1" v:"required|length:6,30#请输入密码|密码长度不够"` + Pass2 string `p:"password2" v:"required|length:6,30|same:password1#请确认密码|密码长度不够|两次密码不一致"` + } + + p, _ := ports.PopRand() + s := g.Server(p) + s.BindHandler("/parse", func(r *ghttp.Request) { + var req *RegisterReq + if err := r.Parse(&req); err != nil { + r.Response.Write(err) + } else { + r.Response.Write("ok") + } + }) + s.SetPort(p) + s.SetDumpRouterMap(false) + s.Start() + defer s.Shutdown() + + time.Sleep(100 * time.Millisecond) + gtest.C(t, func(t *gtest.T) { + prefix := fmt.Sprintf("http://127.0.0.1:%d", p) + client := g.Client() + client.SetPrefix(prefix) + + t.Assert(client.GetContent("/parse"), `请输入账号; 账号长度为6到30位; 请输入密码; 密码长度不够; 请确认密码; 密码长度不够; 两次密码不一致`) + t.Assert(client.GetContent("/parse?name=john11&password1=123456&password2=123"), `密码长度不够; 两次密码不一致`) + t.Assert(client.GetContent("/parse?name=john&password1=123456&password2=123456"), `账号长度为6到30位`) + t.Assert(client.GetContent("/parse?name=john11&password1=123456&password2=123456"), `ok`) + }) +} + +func Test_Params_Parse_EmbeddedWithAliasName1(t *testing.T) { + // 获取内容列表 + type ContentGetListInput struct { + Type string + CategoryId uint + Page int + Size int + Sort int + UserId uint + } + // 获取内容列表 + type ContentGetListReq struct { + ContentGetListInput + CategoryId uint `p:"cate"` + Page int `d:"1" v:"min:0#分页号码错误"` + Size int `d:"10" v:"max:50#分页数量最大50条"` + } + + p, _ := ports.PopRand() + s := g.Server(p) + s.BindHandler("/parse", func(r *ghttp.Request) { + var req *ContentGetListReq + if err := r.Parse(&req); err != nil { + r.Response.Write(err) + } else { + r.Response.Write(req.ContentGetListInput) + } + }) + s.SetPort(p) + s.SetDumpRouterMap(false) + s.Start() + defer s.Shutdown() + + time.Sleep(100 * time.Millisecond) + gtest.C(t, func(t *gtest.T) { + prefix := fmt.Sprintf("http://127.0.0.1:%d", p) + client := g.Client() + client.SetPrefix(prefix) + + t.Assert(client.GetContent("/parse?cate=1&page=2&size=10"), `{"Type":"","CategoryId":0,"Page":2,"Size":10,"Sort":0,"UserId":0}`) + }) +} + +func Test_Params_Parse_EmbeddedWithAliasName2(t *testing.T) { + // 获取内容列表 + type ContentGetListInput struct { + Type string + CategoryId uint `p:"cate"` + Page int + Size int + Sort int + UserId uint + } + // 获取内容列表 + type ContentGetListReq struct { + ContentGetListInput + CategoryId uint `p:"cate"` + Page int `d:"1" v:"min:0#分页号码错误"` + Size int `d:"10" v:"max:50#分页数量最大50条"` + } + + p, _ := ports.PopRand() + s := g.Server(p) + s.BindHandler("/parse", func(r *ghttp.Request) { + var req *ContentGetListReq + if err := r.Parse(&req); err != nil { + r.Response.Write(err) + } else { + r.Response.Write(req.ContentGetListInput) + } + }) + s.SetPort(p) + s.SetDumpRouterMap(false) + s.Start() + defer s.Shutdown() + + time.Sleep(100 * time.Millisecond) + gtest.C(t, func(t *gtest.T) { + prefix := fmt.Sprintf("http://127.0.0.1:%d", p) + client := g.Client() + client.SetPrefix(prefix) + + t.Assert(client.GetContent("/parse?cate=1&page=2&size=10"), `{"Type":"","CategoryId":1,"Page":2,"Size":10,"Sort":0,"UserId":0}`) + }) +} diff --git a/net/ghttp/ghttp_unit_request_xml_test.go b/net/ghttp/ghttp_unit_request_xml_test.go index 1000be366..6dd253d11 100644 --- a/net/ghttp/ghttp_unit_request_xml_test.go +++ b/net/ghttp/ghttp_unit_request_xml_test.go @@ -1,4 +1,4 @@ -// Copyright GoFrame Author(https://github.com/gogf/gf). All Rights Reserved. +// Copyright GoFrame Author(https://goframe.org). All Rights Reserved. // // This Source Code Form is subject to the terms of the MIT License. // If a copy of the MIT was not distributed with this file, @@ -22,7 +22,7 @@ func Test_Params_Xml_Request(t *testing.T) { Name string Time *time.Time Pass1 string `p:"password1"` - Pass2 string `p:"password2" v:"required|length:2,20|password3|same:password1#||密码强度不足|两次密码不一致"` + Pass2 string `p:"password2" v:"password2@required|length:2,20|password3|same:password1#||密码强度不足|两次密码不一致"` } p, _ := ports.PopRand() s := g.Server(p) diff --git a/net/ghttp/ghttp_unit_router_basic_test.go b/net/ghttp/ghttp_unit_router_basic_test.go index 89f033e7e..f15391909 100644 --- a/net/ghttp/ghttp_unit_router_basic_test.go +++ b/net/ghttp/ghttp_unit_router_basic_test.go @@ -1,4 +1,4 @@ -// Copyright 2018 gf Author(https://github.com/gogf/gf). All Rights Reserved. +// Copyright GoFrame Author(https://goframe.org). All Rights Reserved. // // This Source Code Form is subject to the terms of the MIT License. // If a copy of the MIT was not distributed with this file, diff --git a/net/ghttp/ghttp_unit_router_controller_rest_test.go b/net/ghttp/ghttp_unit_router_controller_rest_test.go deleted file mode 100644 index fae4aff4c..000000000 --- a/net/ghttp/ghttp_unit_router_controller_rest_test.go +++ /dev/null @@ -1,93 +0,0 @@ -// Copyright 2018 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 ghttp_test - -import ( - "fmt" - "testing" - "time" - - "github.com/gogf/gf/frame/g" - "github.com/gogf/gf/frame/gmvc" - "github.com/gogf/gf/net/ghttp" - "github.com/gogf/gf/test/gtest" -) - -type ControllerRest struct { - gmvc.Controller -} - -func (c *ControllerRest) Init(r *ghttp.Request) { - c.Controller.Init(r) - c.Response.Write("1") -} - -func (c *ControllerRest) Shut() { - c.Response.Write("2") -} - -func (c *ControllerRest) Get() { - c.Response.Write("Controller Get") -} - -func (c *ControllerRest) Put() { - c.Response.Write("Controller Put") -} - -func (c *ControllerRest) Post() { - c.Response.Write("Controller Post") -} - -func (c *ControllerRest) Delete() { - c.Response.Write("Controller Delete") -} - -func (c *ControllerRest) Head() { - c.Response.Header().Set("head-ok", "1") -} - -// 控制器注册测试 -func Test_Router_ControllerRest(t *testing.T) { - p, _ := ports.PopRand() - s := g.Server(p) - s.BindControllerRest("/", new(ControllerRest)) - s.BindControllerRest("/{.struct}/{.method}", new(ControllerRest)) - 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)) - - t.Assert(client.GetContent("/"), "1Controller Get2") - t.Assert(client.PutContent("/"), "1Controller Put2") - t.Assert(client.PostContent("/"), "1Controller Post2") - t.Assert(client.DeleteContent("/"), "1Controller Delete2") - resp1, err := client.Head("/") - if err == nil { - defer resp1.Close() - } - t.Assert(err, nil) - t.Assert(resp1.Header.Get("head-ok"), "1") - - t.Assert(client.GetContent("/controller-rest/get"), "1Controller Get2") - t.Assert(client.PutContent("/controller-rest/put"), "1Controller Put2") - t.Assert(client.PostContent("/controller-rest/post"), "1Controller Post2") - t.Assert(client.DeleteContent("/controller-rest/delete"), "1Controller Delete2") - resp2, err := client.Head("/controller-rest/head") - if err == nil { - defer resp2.Close() - } - t.Assert(err, nil) - t.Assert(resp2.Header.Get("head-ok"), "1") - - t.Assert(client.GetContent("/none-exist"), "Not Found") - }) -} diff --git a/net/ghttp/ghttp_unit_router_controller_test.go b/net/ghttp/ghttp_unit_router_controller_test.go deleted file mode 100644 index 2c01a8876..000000000 --- a/net/ghttp/ghttp_unit_router_controller_test.go +++ /dev/null @@ -1,128 +0,0 @@ -// Copyright 2018 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 ghttp_test - -import ( - "fmt" - "testing" - "time" - - "github.com/gogf/gf/frame/g" - "github.com/gogf/gf/frame/gmvc" - "github.com/gogf/gf/net/ghttp" - "github.com/gogf/gf/test/gtest" -) - -// 控制器 -type Controller struct { - gmvc.Controller -} - -func (c *Controller) Init(r *ghttp.Request) { - c.Controller.Init(r) - c.Response.Write("1") -} - -func (c *Controller) Shut() { - c.Response.Write("2") -} - -func (c *Controller) Index() { - c.Response.Write("Controller Index") -} - -func (c *Controller) Show() { - c.Response.Write("Controller Show") -} - -func (c *Controller) Info() { - c.Response.Write("Controller Info") -} - -func Test_Router_Controller1(t *testing.T) { - p, _ := ports.PopRand() - s := g.Server(p) - s.BindController("/", new(Controller)) - s.BindController("/{.struct}/{.method}", new(Controller)) - 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)) - - t.Assert(client.GetContent("/"), "1Controller Index2") - t.Assert(client.GetContent("/init"), "Not Found") - t.Assert(client.GetContent("/shut"), "Not Found") - t.Assert(client.GetContent("/index"), "1Controller Index2") - t.Assert(client.GetContent("/show"), "1Controller Show2") - - t.Assert(client.GetContent("/controller"), "Not Found") - t.Assert(client.GetContent("/controller/init"), "Not Found") - t.Assert(client.GetContent("/controller/shut"), "Not Found") - t.Assert(client.GetContent("/controller/index"), "1Controller Index2") - t.Assert(client.GetContent("/controller/show"), "1Controller Show2") - - t.Assert(client.GetContent("/none-exist"), "Not Found") - }) -} - -func Test_Router_Controller2(t *testing.T) { - p, _ := ports.PopRand() - s := g.Server(p) - s.BindController("/controller", new(Controller), "Show, Info") - 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)) - - t.Assert(client.GetContent("/"), "Not Found") - t.Assert(client.GetContent("/controller"), "Not Found") - t.Assert(client.GetContent("/controller/init"), "Not Found") - t.Assert(client.GetContent("/controller/shut"), "Not Found") - t.Assert(client.GetContent("/controller/index"), "Not Found") - t.Assert(client.GetContent("/controller/show"), "1Controller Show2") - t.Assert(client.GetContent("/controller/info"), "1Controller Info2") - - t.Assert(client.GetContent("/none-exist"), "Not Found") - }) -} - -func Test_Router_ControllerMethod(t *testing.T) { - p, _ := ports.PopRand() - s := g.Server(p) - s.BindControllerMethod("/controller-info", new(Controller), "Info") - 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)) - - t.Assert(client.GetContent("/"), "Not Found") - t.Assert(client.GetContent("/controller"), "Not Found") - t.Assert(client.GetContent("/controller/init"), "Not Found") - t.Assert(client.GetContent("/controller/shut"), "Not Found") - t.Assert(client.GetContent("/controller/index"), "Not Found") - t.Assert(client.GetContent("/controller/show"), "Not Found") - t.Assert(client.GetContent("/controller/info"), "Not Found") - t.Assert(client.GetContent("/controller-info"), "1Controller Info2") - - t.Assert(client.GetContent("/none-exist"), "Not Found") - }) -} diff --git a/net/ghttp/ghttp_unit_router_domain_basic_test.go b/net/ghttp/ghttp_unit_router_domain_basic_test.go index eef7df82a..3db0f35cc 100644 --- a/net/ghttp/ghttp_unit_router_domain_basic_test.go +++ b/net/ghttp/ghttp_unit_router_domain_basic_test.go @@ -1,4 +1,4 @@ -// Copyright 2018 gf Author(https://github.com/gogf/gf). All Rights Reserved. +// Copyright GoFrame Author(https://goframe.org). All Rights Reserved. // // This Source Code Form is subject to the terms of the MIT License. // If a copy of the MIT was not distributed with this file, @@ -335,15 +335,15 @@ func Test_Router_DomainGroup(t *testing.T) { d.Group("/", func(group *ghttp.RouterGroup) { group.Group("/app", func(gApp *ghttp.RouterGroup) { gApp.GET("/{table}/list/{page}.html", func(r *ghttp.Request) { - intlog.Print("/{table}/list/{page}.html") + intlog.Print(r.Context(), "/{table}/list/{page}.html") r.Response.Write(r.Get("table"), "&", r.Get("page")) }) gApp.GET("/order/info/{order_id}", func(r *ghttp.Request) { - intlog.Print("/order/info/{order_id}") + intlog.Print(r.Context(), "/order/info/{order_id}") r.Response.Write(r.Get("order_id")) }) gApp.DELETE("/comment/{id}", func(r *ghttp.Request) { - intlog.Print("/comment/{id}") + intlog.Print(r.Context(), "/comment/{id}") r.Response.Write(r.Get("id")) }) }) diff --git a/net/ghttp/ghttp_unit_router_domain_controller_rest_test.go b/net/ghttp/ghttp_unit_router_domain_controller_rest_test.go deleted file mode 100644 index f01c7abe0..000000000 --- a/net/ghttp/ghttp_unit_router_domain_controller_rest_test.go +++ /dev/null @@ -1,127 +0,0 @@ -// Copyright 2018 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 ghttp_test - -import ( - "fmt" - "testing" - "time" - - "github.com/gogf/gf/frame/g" - "github.com/gogf/gf/frame/gmvc" - "github.com/gogf/gf/net/ghttp" - "github.com/gogf/gf/test/gtest" -) - -type DomainControllerRest struct { - gmvc.Controller -} - -func (c *DomainControllerRest) Init(r *ghttp.Request) { - c.Controller.Init(r) - c.Response.Write("1") -} - -func (c *DomainControllerRest) Shut() { - c.Response.Write("2") -} - -func (c *DomainControllerRest) Get() { - c.Response.Write("Controller Get") -} - -func (c *DomainControllerRest) Put() { - c.Response.Write("Controller Put") -} - -func (c *DomainControllerRest) Post() { - c.Response.Write("Controller Post") -} - -func (c *DomainControllerRest) Delete() { - c.Response.Write("Controller Delete") -} - -func (c *DomainControllerRest) Patch() { - c.Response.Write("Controller Patch") -} - -func (c *DomainControllerRest) Options() { - c.Response.Write("Controller Options") -} - -func (c *DomainControllerRest) Head() { - c.Response.Header().Set("head-ok", "1") -} - -// 控制器注册测试 -func Test_Router_DomainControllerRest(t *testing.T) { - p, _ := ports.PopRand() - s := g.Server(p) - d := s.Domain("localhost, local") - d.BindControllerRest("/", new(DomainControllerRest)) - 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)) - - t.Assert(client.GetContent("/"), "Not Found") - t.Assert(client.PutContent("/"), "Not Found") - t.Assert(client.PostContent("/"), "Not Found") - t.Assert(client.DeleteContent("/"), "Not Found") - t.Assert(client.PatchContent("/"), "Not Found") - t.Assert(client.OptionsContent("/"), "Not Found") - resp1, err := client.Head("/") - if err == nil { - defer resp1.Close() - } - t.Assert(err, nil) - t.Assert(resp1.Header.Get("head-ok"), "") - t.Assert(client.GetContent("/none-exist"), "Not Found") - }) - gtest.C(t, func(t *gtest.T) { - client := g.Client() - client.SetPrefix(fmt.Sprintf("http://localhost:%d", p)) - - t.Assert(client.GetContent("/"), "1Controller Get2") - t.Assert(client.PutContent("/"), "1Controller Put2") - t.Assert(client.PostContent("/"), "1Controller Post2") - t.Assert(client.DeleteContent("/"), "1Controller Delete2") - t.Assert(client.PatchContent("/"), "1Controller Patch2") - t.Assert(client.OptionsContent("/"), "1Controller Options2") - resp1, err := client.Head("/") - if err == nil { - defer resp1.Close() - } - t.Assert(err, nil) - t.Assert(resp1.Header.Get("head-ok"), "1") - t.Assert(client.GetContent("/none-exist"), "Not Found") - }) - gtest.C(t, func(t *gtest.T) { - client := g.Client() - client.SetPrefix(fmt.Sprintf("http://local:%d", p)) - - t.Assert(client.GetContent("/"), "1Controller Get2") - t.Assert(client.PutContent("/"), "1Controller Put2") - t.Assert(client.PostContent("/"), "1Controller Post2") - t.Assert(client.DeleteContent("/"), "1Controller Delete2") - t.Assert(client.PatchContent("/"), "1Controller Patch2") - t.Assert(client.OptionsContent("/"), "1Controller Options2") - resp1, err := client.Head("/") - if err == nil { - defer resp1.Close() - } - t.Assert(err, nil) - t.Assert(resp1.Header.Get("head-ok"), "1") - t.Assert(client.GetContent("/none-exist"), "Not Found") - }) -} diff --git a/net/ghttp/ghttp_unit_router_domain_controller_test.go b/net/ghttp/ghttp_unit_router_domain_controller_test.go deleted file mode 100644 index e9d3f0191..000000000 --- a/net/ghttp/ghttp_unit_router_domain_controller_test.go +++ /dev/null @@ -1,200 +0,0 @@ -// Copyright 2018 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 ghttp_test - -import ( - "fmt" - "testing" - "time" - - "github.com/gogf/gf/frame/g" - "github.com/gogf/gf/frame/gmvc" - "github.com/gogf/gf/net/ghttp" - "github.com/gogf/gf/test/gtest" -) - -type DomainController struct { - gmvc.Controller -} - -func (c *DomainController) Init(r *ghttp.Request) { - c.Controller.Init(r) - c.Response.Write("1") -} - -func (c *DomainController) Shut() { - c.Response.Write("2") -} - -func (c *DomainController) Index() { - c.Response.Write("Controller Index") -} - -func (c *DomainController) Show() { - c.Response.Write("Controller Show") -} - -func (c *DomainController) Info() { - c.Response.Write("Controller Info") -} - -func Test_Router_DomainController1(t *testing.T) { - p, _ := ports.PopRand() - s := g.Server(p) - s.Domain("localhost, local").BindController("/", new(DomainController)) - 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)) - - t.Assert(client.GetContent("/"), "Not Found") - t.Assert(client.GetContent("/init"), "Not Found") - t.Assert(client.GetContent("/shut"), "Not Found") - t.Assert(client.GetContent("/index"), "Not Found") - t.Assert(client.GetContent("/show"), "Not Found") - t.Assert(client.GetContent("/info"), "Not Found") - t.Assert(client.GetContent("/none-exist"), "Not Found") - }) - - gtest.C(t, func(t *gtest.T) { - client := g.Client() - client.SetPrefix(fmt.Sprintf("http://localhost:%d", p)) - - t.Assert(client.GetContent("/"), "1Controller Index2") - t.Assert(client.GetContent("/init"), "Not Found") - t.Assert(client.GetContent("/shut"), "Not Found") - t.Assert(client.GetContent("/index"), "1Controller Index2") - t.Assert(client.GetContent("/show"), "1Controller Show2") - t.Assert(client.GetContent("/info"), "1Controller Info2") - t.Assert(client.GetContent("/none-exist"), "Not Found") - }) - - gtest.C(t, func(t *gtest.T) { - client := g.Client() - client.SetPrefix(fmt.Sprintf("http://local:%d", p)) - - t.Assert(client.GetContent("/"), "1Controller Index2") - t.Assert(client.GetContent("/init"), "Not Found") - t.Assert(client.GetContent("/shut"), "Not Found") - t.Assert(client.GetContent("/index"), "1Controller Index2") - t.Assert(client.GetContent("/show"), "1Controller Show2") - t.Assert(client.GetContent("/info"), "1Controller Info2") - t.Assert(client.GetContent("/none-exist"), "Not Found") - }) -} - -func Test_Router_DomainController2(t *testing.T) { - p, _ := ports.PopRand() - s := g.Server(p) - s.Domain("localhost, local").BindController("/controller", new(DomainController), "Show, Info") - 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)) - - t.Assert(client.GetContent("/"), "Not Found") - t.Assert(client.GetContent("/controller"), "Not Found") - t.Assert(client.GetContent("/controller/init"), "Not Found") - t.Assert(client.GetContent("/controller/shut"), "Not Found") - t.Assert(client.GetContent("/controller/index"), "Not Found") - t.Assert(client.GetContent("/controller/show"), "Not Found") - t.Assert(client.GetContent("/controller/info"), "Not Found") - t.Assert(client.GetContent("/none-exist"), "Not Found") - }) - - gtest.C(t, func(t *gtest.T) { - client := g.Client() - client.SetPrefix(fmt.Sprintf("http://localhost:%d", p)) - - t.Assert(client.GetContent("/"), "Not Found") - t.Assert(client.GetContent("/controller"), "Not Found") - t.Assert(client.GetContent("/controller/init"), "Not Found") - t.Assert(client.GetContent("/controller/shut"), "Not Found") - t.Assert(client.GetContent("/controller/index"), "Not Found") - t.Assert(client.GetContent("/controller/show"), "1Controller Show2") - t.Assert(client.GetContent("/controller/info"), "1Controller Info2") - t.Assert(client.GetContent("/none-exist"), "Not Found") - }) - - gtest.C(t, func(t *gtest.T) { - client := g.Client() - client.SetPrefix(fmt.Sprintf("http://local:%d", p)) - - t.Assert(client.GetContent("/"), "Not Found") - t.Assert(client.GetContent("/controller"), "Not Found") - t.Assert(client.GetContent("/controller/init"), "Not Found") - t.Assert(client.GetContent("/controller/shut"), "Not Found") - t.Assert(client.GetContent("/controller/index"), "Not Found") - t.Assert(client.GetContent("/controller/show"), "1Controller Show2") - t.Assert(client.GetContent("/controller/info"), "1Controller Info2") - t.Assert(client.GetContent("/none-exist"), "Not Found") - }) -} - -func Test_Router_DomainControllerMethod(t *testing.T) { - p, _ := ports.PopRand() - s := g.Server(p) - s.Domain("localhost, local").BindControllerMethod("/controller-info", new(DomainController), "Info") - 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)) - - t.Assert(client.GetContent("/"), "Not Found") - t.Assert(client.GetContent("/controller"), "Not Found") - t.Assert(client.GetContent("/controller/init"), "Not Found") - t.Assert(client.GetContent("/controller/shut"), "Not Found") - t.Assert(client.GetContent("/controller/index"), "Not Found") - t.Assert(client.GetContent("/controller/show"), "Not Found") - t.Assert(client.GetContent("/controller/info"), "Not Found") - t.Assert(client.GetContent("/controller-info"), "Not Found") - t.Assert(client.GetContent("/none-exist"), "Not Found") - }) - gtest.C(t, func(t *gtest.T) { - client := g.Client() - client.SetPrefix(fmt.Sprintf("http://localhost:%d", p)) - - t.Assert(client.GetContent("/"), "Not Found") - t.Assert(client.GetContent("/controller"), "Not Found") - t.Assert(client.GetContent("/controller/init"), "Not Found") - t.Assert(client.GetContent("/controller/shut"), "Not Found") - t.Assert(client.GetContent("/controller/index"), "Not Found") - t.Assert(client.GetContent("/controller/show"), "Not Found") - t.Assert(client.GetContent("/controller/info"), "Not Found") - t.Assert(client.GetContent("/controller-info"), "1Controller Info2") - t.Assert(client.GetContent("/none-exist"), "Not Found") - }) - gtest.C(t, func(t *gtest.T) { - client := g.Client() - client.SetPrefix(fmt.Sprintf("http://local:%d", p)) - - t.Assert(client.GetContent("/"), "Not Found") - t.Assert(client.GetContent("/controller"), "Not Found") - t.Assert(client.GetContent("/controller/init"), "Not Found") - t.Assert(client.GetContent("/controller/shut"), "Not Found") - t.Assert(client.GetContent("/controller/index"), "Not Found") - t.Assert(client.GetContent("/controller/show"), "Not Found") - t.Assert(client.GetContent("/controller/info"), "Not Found") - t.Assert(client.GetContent("/controller-info"), "1Controller Info2") - t.Assert(client.GetContent("/none-exist"), "Not Found") - }) -} diff --git a/net/ghttp/ghttp_unit_router_domain_object_rest_test.go b/net/ghttp/ghttp_unit_router_domain_object_rest_test.go index 5d4a5cb59..49bf230a6 100644 --- a/net/ghttp/ghttp_unit_router_domain_object_rest_test.go +++ b/net/ghttp/ghttp_unit_router_domain_object_rest_test.go @@ -1,4 +1,4 @@ -// Copyright 2018 gf Author(https://github.com/gogf/gf). All Rights Reserved. +// Copyright GoFrame Author(https://goframe.org). All Rights Reserved. // // This Source Code Form is subject to the terms of the MIT License. // If a copy of the MIT was not distributed with this file, diff --git a/net/ghttp/ghttp_unit_router_domain_object_test.go b/net/ghttp/ghttp_unit_router_domain_object_test.go index 78131fcc6..d390a3bf8 100644 --- a/net/ghttp/ghttp_unit_router_domain_object_test.go +++ b/net/ghttp/ghttp_unit_router_domain_object_test.go @@ -1,4 +1,4 @@ -// Copyright 2018 gf Author(https://github.com/gogf/gf). All Rights Reserved. +// Copyright GoFrame Author(https://goframe.org). All Rights Reserved. // // This Source Code Form is subject to the terms of the MIT License. // If a copy of the MIT was not distributed with this file, diff --git a/net/ghttp/ghttp_unit_router_exit_test.go b/net/ghttp/ghttp_unit_router_exit_test.go index c29f19160..3fb4f83a2 100644 --- a/net/ghttp/ghttp_unit_router_exit_test.go +++ b/net/ghttp/ghttp_unit_router_exit_test.go @@ -1,4 +1,4 @@ -// Copyright GoFrame Author(https://github.com/gogf/gf). All Rights Reserved. +// Copyright GoFrame Author(https://goframe.org). All Rights Reserved. // // This Source Code Form is subject to the terms of the MIT License. // If a copy of the MIT was not distributed with this file, diff --git a/net/ghttp/ghttp_unit_router_group_group_test.go b/net/ghttp/ghttp_unit_router_group_group_test.go index 886519d17..b743d16a0 100644 --- a/net/ghttp/ghttp_unit_router_group_group_test.go +++ b/net/ghttp/ghttp_unit_router_group_group_test.go @@ -1,4 +1,4 @@ -// Copyright GoFrame Author(https://github.com/gogf/gf). All Rights Reserved. +// Copyright GoFrame Author(https://goframe.org). All Rights Reserved. // // This Source Code Form is subject to the terms of the MIT License. // If a copy of the MIT was not distributed with this file, diff --git a/net/ghttp/ghttp_unit_router_group_hook_test.go b/net/ghttp/ghttp_unit_router_group_hook_test.go index 30fada8e8..555a37835 100644 --- a/net/ghttp/ghttp_unit_router_group_hook_test.go +++ b/net/ghttp/ghttp_unit_router_group_hook_test.go @@ -1,4 +1,4 @@ -// Copyright GoFrame Author(https://github.com/gogf/gf). All Rights Reserved. +// Copyright GoFrame Author(https://goframe.org). All Rights Reserved. // // This Source Code Form is subject to the terms of the MIT License. // If a copy of the MIT was not distributed with this file, diff --git a/net/ghttp/ghttp_unit_router_group_rest_test.go b/net/ghttp/ghttp_unit_router_group_rest_test.go index f4ca999eb..e31ce2b79 100644 --- a/net/ghttp/ghttp_unit_router_group_rest_test.go +++ b/net/ghttp/ghttp_unit_router_group_rest_test.go @@ -1,4 +1,4 @@ -// Copyright 2018 gf Author(https://github.com/gogf/gf). All Rights Reserved. +// Copyright GoFrame Author(https://goframe.org). All Rights Reserved. // // This Source Code Form is subject to the terms of the MIT License. // If a copy of the MIT was not distributed with this file, @@ -13,52 +13,10 @@ import ( "time" "github.com/gogf/gf/frame/g" - "github.com/gogf/gf/frame/gmvc" "github.com/gogf/gf/net/ghttp" "github.com/gogf/gf/test/gtest" ) -type GroupCtlRest struct { - gmvc.Controller -} - -func (c *GroupCtlRest) Init(r *ghttp.Request) { - c.Controller.Init(r) - c.Response.Write("1") -} - -func (c *GroupCtlRest) Shut() { - c.Response.Write("2") -} - -func (c *GroupCtlRest) Get() { - c.Response.Write("Controller Get") -} - -func (c *GroupCtlRest) Put() { - c.Response.Write("Controller Put") -} - -func (c *GroupCtlRest) Post() { - c.Response.Write("Controller Post") -} - -func (c *GroupCtlRest) Delete() { - c.Response.Write("Controller Delete") -} - -func (c *GroupCtlRest) Patch() { - c.Response.Write("Controller Patch") -} - -func (c *GroupCtlRest) Options() { - c.Response.Write("Controller Options") -} - -func (c *GroupCtlRest) Head() { - c.Response.Header().Set("head-ok", "1") -} - type GroupObjRest struct{} func (o *GroupObjRest) Init(r *ghttp.Request) { @@ -101,11 +59,8 @@ func Test_Router_GroupRest1(t *testing.T) { p, _ := ports.PopRand() s := g.Server(p) group := s.Group("/api") - ctl := new(GroupCtlRest) obj := new(GroupObjRest) - group.REST("/ctl", ctl) group.REST("/obj", obj) - group.REST("/{.struct}/{.method}", ctl) group.REST("/{.struct}/{.method}", obj) s.SetPort(p) s.SetDumpRouterMap(false) @@ -117,19 +72,6 @@ func Test_Router_GroupRest1(t *testing.T) { client := g.Client() client.SetPrefix(fmt.Sprintf("http://127.0.0.1:%d", p)) - t.Assert(client.GetContent("/api/ctl"), "1Controller Get2") - t.Assert(client.PutContent("/api/ctl"), "1Controller Put2") - t.Assert(client.PostContent("/api/ctl"), "1Controller Post2") - t.Assert(client.DeleteContent("/api/ctl"), "1Controller Delete2") - t.Assert(client.PatchContent("/api/ctl"), "1Controller Patch2") - t.Assert(client.OptionsContent("/api/ctl"), "1Controller Options2") - resp1, err := client.Head("/api/ctl") - if err == nil { - defer resp1.Close() - } - t.Assert(err, nil) - t.Assert(resp1.Header.Get("head-ok"), "1") - t.Assert(client.GetContent("/api/obj"), "1Object Get2") t.Assert(client.PutContent("/api/obj"), "1Object Put2") t.Assert(client.PostContent("/api/obj"), "1Object Post2") @@ -143,20 +85,6 @@ func Test_Router_GroupRest1(t *testing.T) { t.Assert(err, nil) t.Assert(resp2.Header.Get("head-ok"), "1") - t.Assert(client.GetContent("/api/group-ctl-rest"), "Not Found") - t.Assert(client.GetContent("/api/group-ctl-rest/get"), "1Controller Get2") - t.Assert(client.PutContent("/api/group-ctl-rest/put"), "1Controller Put2") - t.Assert(client.PostContent("/api/group-ctl-rest/post"), "1Controller Post2") - t.Assert(client.DeleteContent("/api/group-ctl-rest/delete"), "1Controller Delete2") - t.Assert(client.PatchContent("/api/group-ctl-rest/patch"), "1Controller Patch2") - t.Assert(client.OptionsContent("/api/group-ctl-rest/options"), "1Controller Options2") - resp3, err := client.Head("/api/group-ctl-rest/head") - if err == nil { - defer resp3.Close() - } - t.Assert(err, nil) - t.Assert(resp3.Header.Get("head-ok"), "1") - t.Assert(client.GetContent("/api/group-obj-rest"), "Not Found") t.Assert(client.GetContent("/api/group-obj-rest/get"), "1Object Get2") t.Assert(client.PutContent("/api/group-obj-rest/put"), "1Object Put2") @@ -177,11 +105,8 @@ func Test_Router_GroupRest2(t *testing.T) { p, _ := ports.PopRand() s := g.Server(p) s.Group("/api", func(group *ghttp.RouterGroup) { - ctl := new(GroupCtlRest) obj := new(GroupObjRest) - group.REST("/ctl", ctl) group.REST("/obj", obj) - group.REST("/{.struct}/{.method}", ctl) group.REST("/{.struct}/{.method}", obj) }) s.SetPort(p) @@ -194,19 +119,6 @@ func Test_Router_GroupRest2(t *testing.T) { client := g.Client() client.SetPrefix(fmt.Sprintf("http://127.0.0.1:%d", p)) - t.Assert(client.GetContent("/api/ctl"), "1Controller Get2") - t.Assert(client.PutContent("/api/ctl"), "1Controller Put2") - t.Assert(client.PostContent("/api/ctl"), "1Controller Post2") - t.Assert(client.DeleteContent("/api/ctl"), "1Controller Delete2") - t.Assert(client.PatchContent("/api/ctl"), "1Controller Patch2") - t.Assert(client.OptionsContent("/api/ctl"), "1Controller Options2") - resp1, err := client.Head("/api/ctl") - if err == nil { - defer resp1.Close() - } - t.Assert(err, nil) - t.Assert(resp1.Header.Get("head-ok"), "1") - t.Assert(client.GetContent("/api/obj"), "1Object Get2") t.Assert(client.PutContent("/api/obj"), "1Object Put2") t.Assert(client.PostContent("/api/obj"), "1Object Post2") @@ -220,20 +132,6 @@ func Test_Router_GroupRest2(t *testing.T) { t.Assert(err, nil) t.Assert(resp2.Header.Get("head-ok"), "1") - t.Assert(client.GetContent("/api/group-ctl-rest"), "Not Found") - t.Assert(client.GetContent("/api/group-ctl-rest/get"), "1Controller Get2") - t.Assert(client.PutContent("/api/group-ctl-rest/put"), "1Controller Put2") - t.Assert(client.PostContent("/api/group-ctl-rest/post"), "1Controller Post2") - t.Assert(client.DeleteContent("/api/group-ctl-rest/delete"), "1Controller Delete2") - t.Assert(client.PatchContent("/api/group-ctl-rest/patch"), "1Controller Patch2") - t.Assert(client.OptionsContent("/api/group-ctl-rest/options"), "1Controller Options2") - resp3, err := client.Head("/api/group-ctl-rest/head") - if err == nil { - defer resp3.Close() - } - t.Assert(err, nil) - t.Assert(resp3.Header.Get("head-ok"), "1") - t.Assert(client.GetContent("/api/group-obj-rest"), "Not Found") t.Assert(client.GetContent("/api/group-obj-rest/get"), "1Object Get2") t.Assert(client.PutContent("/api/group-obj-rest/put"), "1Object Put2") diff --git a/net/ghttp/ghttp_unit_router_group_test.go b/net/ghttp/ghttp_unit_router_group_test.go index 76763bbf1..3a2b35135 100644 --- a/net/ghttp/ghttp_unit_router_group_test.go +++ b/net/ghttp/ghttp_unit_router_group_test.go @@ -1,4 +1,4 @@ -// Copyright 2018 gf Author(https://github.com/gogf/gf). All Rights Reserved. +// Copyright GoFrame Author(https://goframe.org). All Rights Reserved. // // This Source Code Form is subject to the terms of the MIT License. // If a copy of the MIT was not distributed with this file, @@ -12,7 +12,6 @@ import ( "time" "github.com/gogf/gf/frame/g" - "github.com/gogf/gf/frame/gmvc" "github.com/gogf/gf/net/ghttp" "github.com/gogf/gf/test/gtest" ) @@ -40,32 +39,6 @@ func (o *GroupObject) Delete(r *ghttp.Request) { r.Response.Write("Object Delete") } -// 控制器 -type GroupController struct { - gmvc.Controller -} - -func (c *GroupController) Init(r *ghttp.Request) { - c.Controller.Init(r) - c.Response.Write("1") -} - -func (c *GroupController) Shut() { - c.Response.Write("2") -} - -func (c *GroupController) Index() { - c.Response.Write("Controller Index") -} - -func (c *GroupController) Show() { - c.Response.Write("Controller Show") -} - -func (c *GroupController) Post() { - c.Response.Write("Controller Post") -} - func Handler(r *ghttp.Request) { r.Response.Write("Handler") } @@ -74,13 +47,9 @@ func Test_Router_GroupBasic1(t *testing.T) { p, _ := ports.PopRand() s := g.Server(p) obj := new(GroupObject) - ctl := new(GroupController) // 分组路由方法注册 group := s.Group("/api") group.ALL("/handler", Handler) - group.ALL("/ctl", ctl) - group.GET("/ctl/my-show", ctl, "Show") - group.REST("/ctl/rest", ctl) group.ALL("/obj", obj) group.GET("/obj/my-show", obj, "Show") group.REST("/obj/rest", obj) @@ -96,14 +65,6 @@ func Test_Router_GroupBasic1(t *testing.T) { t.Assert(client.GetContent("/api/handler"), "Handler") - t.Assert(client.GetContent("/api/ctl"), "1Controller Index2") - t.Assert(client.GetContent("/api/ctl/"), "1Controller Index2") - t.Assert(client.GetContent("/api/ctl/index"), "1Controller Index2") - t.Assert(client.GetContent("/api/ctl/my-show"), "1Controller Show2") - t.Assert(client.GetContent("/api/ctl/post"), "1Controller Post2") - t.Assert(client.GetContent("/api/ctl/show"), "1Controller Show2") - t.Assert(client.PostContent("/api/ctl/rest"), "1Controller Post2") - t.Assert(client.GetContent("/api/obj"), "1Object Index2") t.Assert(client.GetContent("/api/obj/"), "1Object Index2") t.Assert(client.GetContent("/api/obj/index"), "1Object Index2") @@ -121,13 +82,9 @@ func Test_Router_GroupBasic2(t *testing.T) { p, _ := ports.PopRand() s := g.Server(p) obj := new(GroupObject) - ctl := new(GroupController) // 分组路由批量注册 s.Group("/api").Bind([]g.Slice{ {"ALL", "/handler", Handler}, - {"ALL", "/ctl", ctl}, - {"GET", "/ctl/my-show", ctl, "Show"}, - {"REST", "/ctl/rest", ctl}, {"ALL", "/obj", obj}, {"GET", "/obj/my-show", obj, "Show"}, {"REST", "/obj/rest", obj}, @@ -144,11 +101,6 @@ func Test_Router_GroupBasic2(t *testing.T) { t.Assert(client.GetContent("/api/handler"), "Handler") - t.Assert(client.GetContent("/api/ctl/my-show"), "1Controller Show2") - t.Assert(client.GetContent("/api/ctl/post"), "1Controller Post2") - t.Assert(client.GetContent("/api/ctl/show"), "1Controller Show2") - t.Assert(client.PostContent("/api/ctl/rest"), "1Controller Post2") - t.Assert(client.GetContent("/api/obj/delete"), "1Object Delete2") t.Assert(client.GetContent("/api/obj/my-show"), "1Object Show2") t.Assert(client.GetContent("/api/obj/show"), "1Object Show2") @@ -163,10 +115,8 @@ func Test_Router_GroupBuildInVar(t *testing.T) { p, _ := ports.PopRand() s := g.Server(p) obj := new(GroupObject) - ctl := new(GroupController) // 分组路由方法注册 group := s.Group("/api") - group.ALL("/{.struct}/{.method}", ctl) group.ALL("/{.struct}/{.method}", obj) s.SetPort(p) s.SetDumpRouterMap(false) @@ -178,10 +128,6 @@ func Test_Router_GroupBuildInVar(t *testing.T) { client := g.Client() client.SetPrefix(fmt.Sprintf("http://127.0.0.1:%d", p)) - t.Assert(client.GetContent("/api/group-controller/index"), "1Controller Index2") - t.Assert(client.GetContent("/api/group-controller/post"), "1Controller Post2") - t.Assert(client.GetContent("/api/group-controller/show"), "1Controller Show2") - t.Assert(client.GetContent("/api/group-object/index"), "1Object Index2") t.Assert(client.GetContent("/api/group-object/delete"), "1Object Delete2") t.Assert(client.GetContent("/api/group-object/show"), "1Object Show2") @@ -191,14 +137,12 @@ func Test_Router_GroupBuildInVar(t *testing.T) { }) } -func Test_Router_Group_Mthods(t *testing.T) { +func Test_Router_Group_Methods(t *testing.T) { p, _ := ports.PopRand() s := g.Server(p) obj := new(GroupObject) - ctl := new(GroupController) group := s.Group("/") group.ALL("/obj", obj, "Show, Delete") - group.ALL("/ctl", ctl, "Show, Post") s.SetPort(p) s.SetDumpRouterMap(false) s.Start() @@ -208,8 +152,6 @@ func Test_Router_Group_Mthods(t *testing.T) { gtest.C(t, func(t *gtest.T) { client := g.Client() client.SetPrefix(fmt.Sprintf("http://127.0.0.1:%d", p)) - t.Assert(client.GetContent("/ctl/show"), "1Controller Show2") - t.Assert(client.GetContent("/ctl/post"), "1Controller Post2") t.Assert(client.GetContent("/obj/show"), "1Object Show2") t.Assert(client.GetContent("/obj/delete"), "1Object Delete2") }) @@ -249,3 +191,33 @@ func Test_Router_Group_MultiServer(t *testing.T) { t.Assert(c2.PostContent("/post"), "post2") }) } + +func Test_Router_Group_Map(t *testing.T) { + testFuncGet := func(r *ghttp.Request) { + r.Response.Write("get") + } + testFuncPost := func(r *ghttp.Request) { + r.Response.Write("post") + } + p, _ := ports.PopRand() + s := g.Server(p) + s.Group("/", func(group *ghttp.RouterGroup) { + group.Map(map[string]interface{}{ + "Get: /test": testFuncGet, + "Post:/test": testFuncPost, + }) + }) + s.SetPort(p) + //s.SetDumpRouterMap(false) + gtest.Assert(s.Start(), nil) + defer s.Shutdown() + + time.Sleep(100 * time.Millisecond) + gtest.C(t, func(t *gtest.T) { + c := g.Client() + c.SetPrefix(fmt.Sprintf("http://127.0.0.1:%d", p)) + + t.Assert(c.GetContent("/test"), "get") + t.Assert(c.PostContent("/test"), "post") + }) +} diff --git a/net/ghttp/ghttp_unit_router_handler_extended_test.go b/net/ghttp/ghttp_unit_router_handler_extended_test.go new file mode 100644 index 000000000..77d50c8f2 --- /dev/null +++ b/net/ghttp/ghttp_unit_router_handler_extended_test.go @@ -0,0 +1,82 @@ +// Copyright GoFrame Author(https://goframe.org). All Rights Reserved. +// +// This Source Code Form is subject to the terms of the MIT License. +// If a copy of the MIT was not distributed with this file, +// You can obtain one at https://github.com/gogf/gf. + +package ghttp_test + +import ( + "context" + "fmt" + "github.com/gogf/gf/errors/gerror" + "testing" + "time" + + "github.com/gogf/gf/frame/g" + "github.com/gogf/gf/net/ghttp" + "github.com/gogf/gf/test/gtest" +) + +func Test_Router_Handler_Extended_Handler_Basic(t *testing.T) { + p, _ := ports.PopRand() + s := g.Server(p) + s.BindHandler("/test", func(ctx context.Context) { + r := g.RequestFromCtx(ctx) + r.Response.Write("test") + }) + 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)) + + t.Assert(client.GetContent("/test"), "test") + }) +} + +func Test_Router_Handler_Extended_Handler_WithObject(t *testing.T) { + type TestReq struct { + Age int + Name string + } + type TestRes struct { + Id int + Age int + Name string + } + p, _ := ports.PopRand() + s := g.Server(p) + s.Use(ghttp.MiddlewareHandlerResponse) + s.BindHandler("/test", func(ctx context.Context, req *TestReq) (res *TestRes, err error) { + return &TestRes{ + Id: 1, + Age: req.Age, + Name: req.Name, + }, nil + }) + s.BindHandler("/test/error", func(ctx context.Context, req *TestReq) (res *TestRes, err error) { + return &TestRes{ + Id: 1, + Age: req.Age, + Name: req.Name, + }, gerror.New("error") + }) + 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)) + + t.Assert(client.GetContent("/test?age=18&name=john"), `{"code":0,"message":"","data":{"Id":1,"Age":18,"Name":"john"}}`) + t.Assert(client.GetContent("/test/error"), `{"code":50,"message":"error","data":null}`) + }) +} diff --git a/net/ghttp/ghttp_unit_router_hook_test.go b/net/ghttp/ghttp_unit_router_hook_test.go index 0bc806a46..7381fe215 100644 --- a/net/ghttp/ghttp_unit_router_hook_test.go +++ b/net/ghttp/ghttp_unit_router_hook_test.go @@ -1,4 +1,4 @@ -// Copyright GoFrame Author(https://github.com/gogf/gf). All Rights Reserved. +// Copyright GoFrame Author(https://goframe.org). All Rights Reserved. // // This Source Code Form is subject to the terms of the MIT License. // If a copy of the MIT was not distributed with this file, diff --git a/net/ghttp/ghttp_unit_router_names_test.go b/net/ghttp/ghttp_unit_router_names_test.go index f51a2ee7a..5c79cd9f8 100644 --- a/net/ghttp/ghttp_unit_router_names_test.go +++ b/net/ghttp/ghttp_unit_router_names_test.go @@ -1,4 +1,4 @@ -// Copyright 2018 gf Author(https://github.com/gogf/gf). All Rights Reserved. +// Copyright GoFrame Author(https://goframe.org). All Rights Reserved. // // This Source Code Form is subject to the terms of the MIT License. // If a copy of the MIT was not distributed with this file, @@ -88,7 +88,7 @@ func Test_NameToUri_Camel(t *testing.T) { func Test_NameToUri_Default(t *testing.T) { p, _ := ports.PopRand() s := g.Server(p) - s.SetNameToUriType(ghttp.URI_TYPE_DEFAULT) + s.SetNameToUriType(ghttp.UriTypeDefault) s.BindObject("/{.struct}/{.method}", new(NamesObject)) s.SetPort(p) s.SetDumpRouterMap(false) diff --git a/net/ghttp/ghttp_unit_router_object_rest1_test.go b/net/ghttp/ghttp_unit_router_object_rest1_test.go index 7c8ac17a3..fd57ace76 100644 --- a/net/ghttp/ghttp_unit_router_object_rest1_test.go +++ b/net/ghttp/ghttp_unit_router_object_rest1_test.go @@ -1,4 +1,4 @@ -// Copyright 2018 gf Author(https://github.com/gogf/gf). All Rights Reserved. +// Copyright GoFrame Author(https://goframe.org). All Rights Reserved. // // This Source Code Form is subject to the terms of the MIT License. // If a copy of the MIT was not distributed with this file, diff --git a/net/ghttp/ghttp_unit_router_object_rest2_test.go b/net/ghttp/ghttp_unit_router_object_rest2_test.go index 343b44558..1cac4155b 100644 --- a/net/ghttp/ghttp_unit_router_object_rest2_test.go +++ b/net/ghttp/ghttp_unit_router_object_rest2_test.go @@ -1,4 +1,4 @@ -// Copyright 2018 gf Author(https://github.com/gogf/gf). All Rights Reserved. +// Copyright GoFrame Author(https://goframe.org). All Rights Reserved. // // This Source Code Form is subject to the terms of the MIT License. // If a copy of the MIT was not distributed with this file, diff --git a/net/ghttp/ghttp_unit_router_object_test.go b/net/ghttp/ghttp_unit_router_object_test.go index b339f81ce..5a30aefe3 100644 --- a/net/ghttp/ghttp_unit_router_object_test.go +++ b/net/ghttp/ghttp_unit_router_object_test.go @@ -1,4 +1,4 @@ -// Copyright 2018 gf Author(https://github.com/gogf/gf). All Rights Reserved. +// Copyright GoFrame Author(https://goframe.org). All Rights Reserved. // // This Source Code Form is subject to the terms of the MIT License. // If a copy of the MIT was not distributed with this file, diff --git a/net/ghttp/ghttp_unit_server_util_test.go b/net/ghttp/ghttp_unit_server_util_test.go new file mode 100644 index 000000000..66127e2ad --- /dev/null +++ b/net/ghttp/ghttp_unit_server_util_test.go @@ -0,0 +1,69 @@ +// Copyright GoFrame Author(https://goframe.org). All Rights Reserved. +// +// This Source Code Form is subject to the terms of the MIT License. +// If a copy of the MIT was not distributed with this file, +// You can obtain one at https://github.com/gogf/gf. + +package ghttp_test + +import ( + "fmt" + "net/http" + "testing" + "time" + + "github.com/gogf/gf/frame/g" + "github.com/gogf/gf/net/ghttp" + "github.com/gogf/gf/test/gtest" +) + +type testWrapStdHTTPStruct struct { + T *gtest.T + text string +} + +func (t *testWrapStdHTTPStruct) ServeHTTP(w http.ResponseWriter, req *http.Request) { + t.T.Assert(req.Method, "POST") + t.T.Assert(req.URL.Path, "/api/wraph") + w.WriteHeader(http.StatusInternalServerError) + fmt.Fprint(w, t.text) +} +func Test_Server_Wrap_Handler(t *testing.T) { + gtest.C(t, func(t *gtest.T) { + p, _ := ports.PopRand() + s := g.Server(p) + str1 := "hello" + str2 := "hello again" + s.Group("/api", func(group *ghttp.RouterGroup) { + group.GET("/wrapf", ghttp.WrapF(func(w http.ResponseWriter, req *http.Request) { + t.Assert(req.Method, "GET") + t.Assert(req.URL.Path, "/api/wrapf") + w.WriteHeader(http.StatusBadRequest) + fmt.Fprint(w, str1) + })) + + group.POST("/wraph", ghttp.WrapH(&testWrapStdHTTPStruct{t, str2})) + }) + + s.SetPort(p) + s.SetDumpRouterMap(false) + s.Start() + defer s.Shutdown() + + time.Sleep(100 * time.Millisecond) + client := g.Client() + client.SetPrefix(fmt.Sprintf("http://127.0.0.1:%d/api", p)) + + response, er1 := client.Get("/wrapf") + defer response.Close() + t.Assert(er1, nil) + t.Assert(response.StatusCode, http.StatusBadRequest) + t.Assert(response.ReadAllString(), str1) + + response2, er2 := client.Post("/wraph") + defer response2.Close() + t.Assert(er2, nil) + t.Assert(response2.StatusCode, http.StatusInternalServerError) + t.Assert(response2.ReadAllString(), str2) + }) +} diff --git a/net/ghttp/ghttp_unit_session_test.go b/net/ghttp/ghttp_unit_session_test.go index f4b8c3d78..67cdc9ecb 100644 --- a/net/ghttp/ghttp_unit_session_test.go +++ b/net/ghttp/ghttp_unit_session_test.go @@ -1,4 +1,4 @@ -// Copyright 2018 gf Author(https://github.com/gogf/gf). All Rights Reserved. +// Copyright GoFrame Author(https://goframe.org). All Rights Reserved. // // This Source Code Form is subject to the terms of the MIT License. // If a copy of the MIT was not distributed with this file, diff --git a/net/ghttp/ghttp_unit_static_test.go b/net/ghttp/ghttp_unit_static_test.go index 5f8ce1244..bdd24cc70 100644 --- a/net/ghttp/ghttp_unit_static_test.go +++ b/net/ghttp/ghttp_unit_static_test.go @@ -1,4 +1,4 @@ -// Copyright 2018 gf Author(https://github.com/gogf/gf). All Rights Reserved. +// Copyright GoFrame Author(https://goframe.org). All Rights Reserved. // // This Source Code Form is subject to the terms of the MIT License. // If a copy of the MIT was not distributed with this file, diff --git a/net/ghttp/ghttp_unit_status_test.go b/net/ghttp/ghttp_unit_status_test.go index f37a185b8..07cf33f87 100644 --- a/net/ghttp/ghttp_unit_status_test.go +++ b/net/ghttp/ghttp_unit_status_test.go @@ -1,4 +1,4 @@ -// Copyright 2018 gf Author(https://github.com/gogf/gf). All Rights Reserved. +// Copyright GoFrame Author(https://goframe.org). All Rights Reserved. // // This Source Code Form is subject to the terms of the MIT License. // If a copy of the MIT was not distributed with this file, diff --git a/net/ghttp/ghttp_unit_template_test.go b/net/ghttp/ghttp_unit_template_test.go index 781443af7..d4ce1ec1e 100644 --- a/net/ghttp/ghttp_unit_template_test.go +++ b/net/ghttp/ghttp_unit_template_test.go @@ -1,4 +1,4 @@ -// Copyright 2018 gf Author(https://github.com/gogf/gf). All Rights Reserved. +// Copyright GoFrame Author(https://goframe.org). All Rights Reserved. // // This Source Code Form is subject to the terms of the MIT License. // If a copy of the MIT was not distributed with this file, @@ -139,6 +139,27 @@ func Test_Template_Layout2(t *testing.T) { }) } +func Test_Template_BuildInVarRequest(t *testing.T) { + gtest.C(t, func(t *gtest.T) { + p, _ := ports.PopRand() + s := g.Server(p) + s.BindHandler("/:table/test", func(r *ghttp.Request) { + err := r.Response.WriteTplContent("{{.Request.table}}") + t.Assert(err, nil) + }) + s.SetDumpRouterMap(false) + s.SetPort(p) + s.Start() + defer s.Shutdown() + time.Sleep(100 * time.Millisecond) + client := g.Client() + client.SetPrefix(fmt.Sprintf("http://127.0.0.1:%d", p)) + + t.Assert(client.GetContent("/user/test"), "user") + t.Assert(client.GetContent("/order/test"), "order") + }) +} + func Test_Template_XSS(t *testing.T) { gtest.C(t, func(t *gtest.T) { v := gview.New() diff --git a/net/ghttp/ghttp_unit_websocket_client_test.go b/net/ghttp/ghttp_unit_websocket_client_test.go new file mode 100644 index 000000000..823130ee1 --- /dev/null +++ b/net/ghttp/ghttp_unit_websocket_client_test.go @@ -0,0 +1,64 @@ +// Copyright GoFrame Author(https://goframe.org). All Rights Reserved. +// +// This Source Code Form is subject to the terms of the MIT License. +// If a copy of the MIT was not distributed with this file, +// You can obtain one at https://github.com/gogf/gf. + +package ghttp_test + +import ( + "fmt" + "net/http" + "testing" + "time" + + "github.com/gorilla/websocket" + + "github.com/gogf/gf/frame/g" + "github.com/gogf/gf/net/ghttp" + "github.com/gogf/gf/test/gtest" +) + +func Test_WebSocketClient(t *testing.T) { + p, _ := ports.PopRand() + s := g.Server(p) + s.BindHandler("/ws", func(r *ghttp.Request) { + ws, err := r.WebSocket() + if err != nil { + r.Exit() + } + for { + msgType, msg, err := ws.ReadMessage() + if err != nil { + return + } + if err = ws.WriteMessage(msgType, msg); err != nil { + return + } + } + }) + s.SetPort(p) + s.SetDumpRouterMap(false) + s.Start() + defer s.Shutdown() + + time.Sleep(100 * time.Millisecond) + gtest.C(t, func(t *gtest.T) { + client := ghttp.NewWebSocketClient() + client.Proxy = http.ProxyFromEnvironment + client.HandshakeTimeout = time.Minute + + conn, _, err := client.Dial(fmt.Sprintf("ws://127.0.0.1:%d/ws", p), nil) + t.Assert(err, nil) + defer conn.Close() + + msg := []byte("hello") + err = conn.WriteMessage(websocket.TextMessage, msg) + t.Assert(err, nil) + + mt, data, err := conn.ReadMessage() + t.Assert(err, nil) + t.Assert(mt, websocket.TextMessage) + t.Assert(data, msg) + }) +} diff --git a/net/ghttp/ghttp_unit_websocket_test.go b/net/ghttp/ghttp_unit_websocket_test.go index 7fd1ecea1..5b06ff589 100644 --- a/net/ghttp/ghttp_unit_websocket_test.go +++ b/net/ghttp/ghttp_unit_websocket_test.go @@ -1,4 +1,4 @@ -// Copyright 2018 gf Author(https://github.com/gogf/gf). All Rights Reserved. +// Copyright GoFrame Author(https://goframe.org). All Rights Reserved. // // This Source Code Form is subject to the terms of the MIT License. // If a copy of the MIT was not distributed with this file, diff --git a/net/ghttp/ghttp_z_bench_test.go b/net/ghttp/ghttp_z_bench_test.go index 63deec5a7..e18f46c7c 100644 --- a/net/ghttp/ghttp_z_bench_test.go +++ b/net/ghttp/ghttp_z_bench_test.go @@ -1,4 +1,4 @@ -// Copyright 2020 gf Author(https://github.com/gogf/gf). All Rights Reserved. +// Copyright GoFrame Author(https://goframe.org). All Rights Reserved. // // This Source Code Form is subject to the terms of the MIT License. // If a copy of the MIT was not distributed with this file, diff --git a/net/ghttp/ghttp_z_example_client_test.go b/net/ghttp/ghttp_z_example_client_test.go index b1148d441..d33fc9415 100644 --- a/net/ghttp/ghttp_z_example_client_test.go +++ b/net/ghttp/ghttp_z_example_client_test.go @@ -1,4 +1,4 @@ -// Copyright 2020 gf Author(https://github.com/gogf/gf). All Rights Reserved. +// Copyright GoFrame Author(https://goframe.org). All Rights Reserved. // // This Source Code Form is subject to the terms of the MIT License. // If a copy of the MIT was not distributed with this file, diff --git a/net/ghttp/ghttp_z_example_get_test.go b/net/ghttp/ghttp_z_example_get_test.go index 1505b37f1..7448c651e 100644 --- a/net/ghttp/ghttp_z_example_get_test.go +++ b/net/ghttp/ghttp_z_example_get_test.go @@ -1,4 +1,4 @@ -// Copyright 2020 gf Author(https://github.com/gogf/gf). All Rights Reserved. +// Copyright GoFrame Author(https://goframe.org). All Rights Reserved. // // This Source Code Form is subject to the terms of the MIT License. // If a copy of the MIT was not distributed with this file, diff --git a/net/ghttp/ghttp_z_example_init_test.go b/net/ghttp/ghttp_z_example_init_test.go index db741774f..cc3c844b9 100644 --- a/net/ghttp/ghttp_z_example_init_test.go +++ b/net/ghttp/ghttp_z_example_init_test.go @@ -1,4 +1,4 @@ -// Copyright 2020 gf Author(https://github.com/gogf/gf). All Rights Reserved. +// Copyright GoFrame Author(https://goframe.org). All Rights Reserved. // // This Source Code Form is subject to the terms of the MIT License. // If a copy of the MIT was not distributed with this file, diff --git a/net/ghttp/ghttp_z_example_post_test.go b/net/ghttp/ghttp_z_example_post_test.go index 9f9a69f50..f63c6f259 100644 --- a/net/ghttp/ghttp_z_example_post_test.go +++ b/net/ghttp/ghttp_z_example_post_test.go @@ -1,4 +1,4 @@ -// Copyright 2020 gf Author(https://github.com/gogf/gf). All Rights Reserved. +// Copyright GoFrame Author(https://goframe.org). All Rights Reserved. // // This Source Code Form is subject to the terms of the MIT License. // If a copy of the MIT was not distributed with this file, diff --git a/net/ghttp/ghttp_z_example_test.go b/net/ghttp/ghttp_z_example_test.go index 6d651f8e7..d451723f5 100644 --- a/net/ghttp/ghttp_z_example_test.go +++ b/net/ghttp/ghttp_z_example_test.go @@ -1,4 +1,4 @@ -// Copyright 2020 gf Author(https://github.com/gogf/gf). All Rights Reserved. +// Copyright GoFrame Author(https://goframe.org). All Rights Reserved. // // This Source Code Form is subject to the terms of the MIT License. // If a copy of the MIT was not distributed with this file, diff --git a/net/ghttp/ghttp_client_config.go b/net/ghttp/internal/client/client.go similarity index 56% rename from net/ghttp/ghttp_client_config.go rename to net/ghttp/internal/client/client.go index 207cfe868..c4f649e2f 100644 --- a/net/ghttp/ghttp_client_config.go +++ b/net/ghttp/internal/client/client.go @@ -1,18 +1,25 @@ -// Copyright 2017 gf Author(https://github.com/gogf/gf). All Rights Reserved. +// Copyright GoFrame Author(https://goframe.org). All Rights Reserved. // // This Source Code Form is subject to the terms of the MIT License. // If a copy of the MIT was not distributed with this file, // You can obtain one at https://github.com/gogf/gf. -package ghttp +package client import ( "context" + "crypto/rand" "crypto/tls" + "fmt" + "github.com/gogf/gf" + "github.com/gogf/gf/errors/gcode" + "github.com/gogf/gf/errors/gerror" + "github.com/gogf/gf/os/gfile" "github.com/gogf/gf/text/gstr" "golang.org/x/net/proxy" "net" "net/http" + "net/http/cookiejar" "net/url" "strings" "time" @@ -22,22 +29,27 @@ import ( // Client is the HTTP client for HTTP request management. type Client struct { - http.Client // Underlying HTTP Client. - ctx context.Context // Context for each request. - parent *Client // Parent http client, this is used for chaining operations. - header map[string]string // Custom header map. - cookies map[string]string // Custom cookie map. - prefix string // Prefix for request. - authUser string // HTTP basic authentication: user. - authPass string // HTTP basic authentication: pass. - browserMode bool // Whether auto saving and sending cookie content. - retryCount int // Retry count when request fails. - retryInterval time.Duration // Retry interval when request fails. + http.Client // Underlying HTTP Client. + ctx context.Context // Context for each request. + dump bool // Mark this request will be dumped. + parent *Client // Parent http client, this is used for chaining operations. + header map[string]string // Custom header map. + cookies map[string]string // Custom cookie map. + prefix string // Prefix for request. + authUser string // HTTP basic authentication: user. + authPass string // HTTP basic authentication: pass. + retryCount int // Retry count when request fails. + retryInterval time.Duration // Retry interval when request fails. + middlewareHandler []HandlerFunc // Interceptor handlers } -// NewClient creates and returns a new HTTP client object. -func NewClient() *Client { - return &Client{ +var ( + defaultClientAgent = fmt.Sprintf(`GoFrameHTTPClient %s`, gf.VERSION) +) + +// New creates and returns a new HTTP client object. +func New() *Client { + client := &Client{ Client: http.Client{ Transport: &http.Transport{ // No validation for https certification of the server in default. @@ -50,11 +62,13 @@ func NewClient() *Client { header: make(map[string]string), cookies: make(map[string]string), } + client.header["User-Agent"] = defaultClientAgent + return client } -// Clone clones current client and returns a new one. +// Clone deeply clones current client and returns a new one. func (c *Client) Clone() *Client { - newClient := NewClient() + newClient := New() *newClient = *c newClient.header = make(map[string]string) newClient.cookies = make(map[string]string) @@ -71,7 +85,10 @@ func (c *Client) Clone() *Client { // When browser mode is enabled, it automatically saves and sends cookie content // from and to server. func (c *Client) SetBrowserMode(enabled bool) *Client { - c.browserMode = enabled + if enabled { + jar, _ := cookiejar.New(nil) + c.Jar = jar + } return c } @@ -89,6 +106,12 @@ func (c *Client) SetHeaderMap(m map[string]string) *Client { return c } +// SetAgent sets the User-Agent header for client. +func (c *Client) SetAgent(agent string) *Client { + c.header["User-Agent"] = agent + return c +} + // SetContentType sets HTTP content type for the client. func (c *Client) SetContentType(contentType string) *Client { c.header["Content-Type"] = contentType @@ -112,6 +135,12 @@ func (c *Client) SetCookie(key, value string) *Client { return c } +// SetDump enables/disables dump feature for this request. +func (c *Client) SetDump(dump bool) *Client { + c.dump = dump + return c +} + // SetCookieMap sets cookie items with map. func (c *Client) SetCookieMap(m map[string]string) *Client { for k, v := range m { @@ -126,7 +155,7 @@ func (c *Client) SetPrefix(prefix string) *Client { return c } -// SetTimeOut sets the request timeout for the client. +// SetTimeout sets the request timeout for the client. func (c *Client) SetTimeout(t time.Duration) *Client { c.Client.Timeout = t return c @@ -176,8 +205,8 @@ func (c *Client) SetProxy(proxyURL string) { return } if _proxy.Scheme == "http" { - if _, ok := c.Transport.(*http.Transport); ok { - c.Transport.(*http.Transport).Proxy = http.ProxyURL(_proxy) + if v, ok := c.Transport.(*http.Transport); ok { + v.Proxy = http.ProxyURL(_proxy) } } else { var auth = &proxy.Auth{} @@ -205,11 +234,55 @@ func (c *Client) SetProxy(proxyURL string) { if err != nil { return } - if _, ok := c.Transport.(*http.Transport); ok { - c.Transport.(*http.Transport).DialContext = func(ctx context.Context, network, addr string) (conn net.Conn, e error) { + if v, ok := c.Transport.(*http.Transport); ok { + v.DialContext = func(ctx context.Context, network, addr string) (conn net.Conn, e error) { return dialer.Dial(network, addr) } } //c.SetTimeout(10*time.Second) } } + +// SetTLSKeyCrt sets the certificate and key file for TLS configuration of client. +func (c *Client) SetTLSKeyCrt(crtFile, keyFile string) error { + tlsConfig, err := LoadKeyCrt(crtFile, keyFile) + if err != nil { + return gerror.WrapCode(gcode.CodeInternalError, err, "LoadKeyCrt failed") + } + if v, ok := c.Transport.(*http.Transport); ok { + tlsConfig.InsecureSkipVerify = true + v.TLSClientConfig = tlsConfig + return nil + } + return gerror.NewCode(gcode.CodeInternalError, `cannot set TLSClientConfig for custom Transport of the client`) +} + +// SetTLSConfig sets the TLS configuration of client. +func (c *Client) SetTLSConfig(tlsConfig *tls.Config) error { + if v, ok := c.Transport.(*http.Transport); ok { + v.TLSClientConfig = tlsConfig + return nil + } + return gerror.NewCode(gcode.CodeInternalError, `cannot set TLSClientConfig for custom Transport of the client`) +} + +// LoadKeyCrt creates and returns a TLS configuration object with given certificate and key files. +func LoadKeyCrt(crtFile, keyFile string) (*tls.Config, error) { + crtPath, err := gfile.Search(crtFile) + if err != nil { + return nil, err + } + keyPath, err := gfile.Search(keyFile) + if err != nil { + return nil, err + } + crt, err := tls.LoadX509KeyPair(crtPath, keyPath) + if err != nil { + return nil, err + } + tlsConfig := &tls.Config{} + tlsConfig.Certificates = []tls.Certificate{crt} + tlsConfig.Time = time.Now + tlsConfig.Rand = rand.Reader + return tlsConfig, nil +} diff --git a/net/ghttp/ghttp_client_bytes.go b/net/ghttp/internal/client/client_bytes.go similarity index 96% rename from net/ghttp/ghttp_client_bytes.go rename to net/ghttp/internal/client/client_bytes.go index cde71c21f..41794370c 100644 --- a/net/ghttp/ghttp_client_bytes.go +++ b/net/ghttp/internal/client/client_bytes.go @@ -1,10 +1,10 @@ -// Copyright 2017 gf Author(https://github.com/gogf/gf). All Rights Reserved. +// Copyright GoFrame Author(https://goframe.org). All Rights Reserved. // // This Source Code Form is subject to the terms of the MIT License. // If a copy of the MIT was not distributed with this file, // You can obtain one at https://github.com/gogf/gf. -package ghttp +package client // GetBytes sends a GET request, retrieves and returns the result content as bytes. func (c *Client) GetBytes(url string, data ...interface{}) []byte { diff --git a/net/ghttp/ghttp_client_chain.go b/net/ghttp/internal/client/client_chain.go similarity index 88% rename from net/ghttp/ghttp_client_chain.go rename to net/ghttp/internal/client/client_chain.go index 781ed0e81..81c1f3af3 100644 --- a/net/ghttp/ghttp_client_chain.go +++ b/net/ghttp/internal/client/client_chain.go @@ -1,10 +1,10 @@ -// Copyright 2020 gf Author(https://github.com/gogf/gf). All Rights Reserved. +// Copyright GoFrame Author(https://goframe.org). All Rights Reserved. // // This Source Code Form is subject to the terms of the MIT License. // If a copy of the MIT was not distributed with this file, // You can obtain one at https://github.com/gogf/gf. -package ghttp +package client import ( "context" @@ -33,7 +33,7 @@ func (c *Client) Header(m map[string]string) *Client { return newClient } -// Header is a chaining function, +// HeaderRaw is a chaining function, // which sets custom HTTP header using raw string for next request. func (c *Client) HeaderRaw(headers string) *Client { newClient := c @@ -92,7 +92,7 @@ func (c *Client) ContentXml() *Client { return newClient } -// TimeOut is a chaining function, +// Timeout is a chaining function, // which sets the timeout for next request. func (c *Client) Timeout(t time.Duration) *Client { newClient := c @@ -133,7 +133,22 @@ func (c *Client) Retry(retryCount int, retryInterval time.Duration) *Client { newClient = c.Clone() } newClient.SetRetry(retryCount, retryInterval) - return c + return newClient +} + +// Dump is a chaining function, +// which enables/disables dump feature for this request. +func (c *Client) Dump(dump ...bool) *Client { + newClient := c + if c.parent == nil { + newClient = c.Clone() + } + if len(dump) > 0 { + newClient.SetDump(dump[0]) + } else { + newClient.SetDump(true) + } + return newClient } // Proxy is a chaining function, @@ -147,7 +162,7 @@ func (c *Client) Proxy(proxyURL string) *Client { newClient = c.Clone() } newClient.SetProxy(proxyURL) - return c + return newClient } // RedirectLimit is a chaining function, @@ -158,5 +173,5 @@ func (c *Client) RedirectLimit(redirectLimit int) *Client { newClient = c.Clone() } newClient.SetRedirectLimit(redirectLimit) - return c + return newClient } diff --git a/net/ghttp/ghttp_client_content.go b/net/ghttp/internal/client/client_content.go similarity index 97% rename from net/ghttp/ghttp_client_content.go rename to net/ghttp/internal/client/client_content.go index d010e3470..a64ea73ff 100644 --- a/net/ghttp/ghttp_client_content.go +++ b/net/ghttp/internal/client/client_content.go @@ -1,10 +1,10 @@ -// Copyright 2017 gf Author(https://github.com/gogf/gf). All Rights Reserved. +// Copyright GoFrame Author(https://goframe.org). All Rights Reserved. // // This Source Code Form is subject to the terms of the MIT License. // If a copy of the MIT was not distributed with this file, // You can obtain one at https://github.com/gogf/gf. -package ghttp +package client // GetContent is a convenience method for sending GET request, which retrieves and returns // the result content and automatically closes response object. diff --git a/net/ghttp/ghttp_client_dump.go b/net/ghttp/internal/client/client_dump.go similarity index 84% rename from net/ghttp/ghttp_client_dump.go rename to net/ghttp/internal/client/client_dump.go index beba4f847..9ac718a65 100644 --- a/net/ghttp/ghttp_client_dump.go +++ b/net/ghttp/internal/client/client_dump.go @@ -1,10 +1,10 @@ -// Copyright 2020 gf Author(https://github.com/gogf/gf). All Rights Reserved. +// Copyright GoFrame Author(https://goframe.org). All Rights Reserved. // // This Source Code Form is subject to the terms of the MIT License. // If a copy of the MIT was not distributed with this file, // You can obtain one at https://github.com/gogf/gf. -package ghttp +package client import ( "fmt" @@ -35,8 +35,8 @@ func getResponseBody(res *http.Response) string { } // RawRequest returns the raw content of the request. -func (r *ClientResponse) RawRequest() string { - // ClientResponse can be nil. +func (r *Response) RawRequest() string { + // Response can be nil. if r == nil { return "" } @@ -57,8 +57,8 @@ func (r *ClientResponse) RawRequest() string { } // RawResponse returns the raw content of the response. -func (r *ClientResponse) RawResponse() string { - // ClientResponse can be nil. +func (r *Response) RawResponse() string { + // Response might be nil. if r == nil || r.Response == nil { return "" } @@ -76,11 +76,11 @@ func (r *ClientResponse) RawResponse() string { } // Raw returns the raw text of the request and the response. -func (r *ClientResponse) Raw() string { +func (r *Response) Raw() string { return fmt.Sprintf("%s\n%s", r.RawRequest(), r.RawResponse()) } // RawDump outputs the raw text of the request and the response to stdout. -func (r *ClientResponse) RawDump() { +func (r *Response) RawDump() { fmt.Println(r.Raw()) } diff --git a/net/ghttp/internal/client/client_middleware.go b/net/ghttp/internal/client/client_middleware.go new file mode 100644 index 000000000..d483e9df3 --- /dev/null +++ b/net/ghttp/internal/client/client_middleware.go @@ -0,0 +1,48 @@ +package client + +import ( + "net/http" +) + +// HandlerFunc middleware handler func +type HandlerFunc = func(c *Client, r *http.Request) (*Response, error) + +// clientMiddleware is the plugin for http client request workflow management. +type clientMiddleware struct { + client *Client // http client. + handlers []HandlerFunc // mdl handlers. + handlerIndex int // current handler index. + resp *Response // save resp. + err error // save err. +} + +const clientMiddlewareKey = "__clientMiddlewareKey" + +// Use adds one or more middleware handlers to client. +func (c *Client) Use(handlers ...HandlerFunc) *Client { + c.middlewareHandler = append(c.middlewareHandler, handlers...) + return c +} + +// Next calls next middleware. +// This is should only be call in HandlerFunc. +func (c *Client) Next(req *http.Request) (*Response, error) { + if v := req.Context().Value(clientMiddlewareKey); v != nil { + if m, ok := v.(*clientMiddleware); ok { + return m.Next(req) + } + } + return c.callRequest(req) +} + +// Next calls next middleware handler. +func (m *clientMiddleware) Next(req *http.Request) (resp *Response, err error) { + if m.err != nil { + return m.resp, m.err + } + if m.handlerIndex < len(m.handlers) { + m.handlerIndex++ + m.resp, m.err = m.handlers[m.handlerIndex](m.client, req) + } + return m.resp, m.err +} diff --git a/net/ghttp/ghttp_client_request.go b/net/ghttp/internal/client/client_request.go similarity index 60% rename from net/ghttp/ghttp_client_request.go rename to net/ghttp/internal/client/client_request.go index ceb6007ef..3f7b2b85d 100644 --- a/net/ghttp/ghttp_client_request.go +++ b/net/ghttp/internal/client/client_request.go @@ -1,17 +1,20 @@ -// Copyright 2017 gf Author(https://github.com/gogf/gf). All Rights Reserved. +// Copyright GoFrame Author(https://goframe.org). All Rights Reserved. // // This Source Code Form is subject to the terms of the MIT License. // If a copy of the MIT was not distributed with this file, // You can obtain one at https://github.com/gogf/gf. -package ghttp +package client import ( "bytes" - "errors" - "fmt" + "context" + "github.com/gogf/gf/errors/gcode" + "github.com/gogf/gf/errors/gerror" + "github.com/gogf/gf/internal/intlog" "github.com/gogf/gf/internal/json" "github.com/gogf/gf/internal/utils" + "github.com/gogf/gf/net/ghttp/internal/httputil" "io" "io/ioutil" "mime/multipart" @@ -30,55 +33,55 @@ import ( // Get send GET request and returns the response object. // Note that the response object MUST be closed if it'll be never used. -func (c *Client) Get(url string, data ...interface{}) (*ClientResponse, error) { +func (c *Client) Get(url string, data ...interface{}) (*Response, error) { return c.DoRequest("GET", url, data...) } // Put send PUT request and returns the response object. // Note that the response object MUST be closed if it'll be never used. -func (c *Client) Put(url string, data ...interface{}) (*ClientResponse, error) { +func (c *Client) Put(url string, data ...interface{}) (*Response, error) { return c.DoRequest("PUT", url, data...) } // Post sends request using HTTP method POST and returns the response object. // Note that the response object MUST be closed if it'll be never used. -func (c *Client) Post(url string, data ...interface{}) (*ClientResponse, error) { +func (c *Client) Post(url string, data ...interface{}) (*Response, error) { return c.DoRequest("POST", url, data...) } // Delete send DELETE request and returns the response object. // Note that the response object MUST be closed if it'll be never used. -func (c *Client) Delete(url string, data ...interface{}) (*ClientResponse, error) { +func (c *Client) Delete(url string, data ...interface{}) (*Response, error) { return c.DoRequest("DELETE", url, data...) } // Head send HEAD request and returns the response object. // Note that the response object MUST be closed if it'll be never used. -func (c *Client) Head(url string, data ...interface{}) (*ClientResponse, error) { +func (c *Client) Head(url string, data ...interface{}) (*Response, error) { return c.DoRequest("HEAD", url, data...) } // Patch send PATCH request and returns the response object. // Note that the response object MUST be closed if it'll be never used. -func (c *Client) Patch(url string, data ...interface{}) (*ClientResponse, error) { +func (c *Client) Patch(url string, data ...interface{}) (*Response, error) { return c.DoRequest("PATCH", url, data...) } // Connect send CONNECT request and returns the response object. // Note that the response object MUST be closed if it'll be never used. -func (c *Client) Connect(url string, data ...interface{}) (*ClientResponse, error) { +func (c *Client) Connect(url string, data ...interface{}) (*Response, error) { return c.DoRequest("CONNECT", url, data...) } // Options send OPTIONS request and returns the response object. // Note that the response object MUST be closed if it'll be never used. -func (c *Client) Options(url string, data ...interface{}) (*ClientResponse, error) { +func (c *Client) Options(url string, data ...interface{}) (*Response, error) { return c.DoRequest("OPTIONS", url, data...) } // Trace send TRACE request and returns the response object. // Note that the response object MUST be closed if it'll be never used. -func (c *Client) Trace(url string, data ...interface{}) (*ClientResponse, error) { +func (c *Client) Trace(url string, data ...interface{}) (*Response, error) { return c.DoRequest("TRACE", url, data...) } @@ -89,72 +92,116 @@ func (c *Client) Trace(url string, data ...interface{}) (*ClientResponse, error) // else it uses "application/x-www-form-urlencoded". It also automatically detects the post // content for JSON format, and for that it automatically sets the Content-Type as // "application/json". -func (c *Client) DoRequest(method, url string, data ...interface{}) (resp *ClientResponse, err error) { +func (c *Client) DoRequest(method, url string, data ...interface{}) (resp *Response, err error) { + req, err := c.prepareRequest(method, url, data...) + if err != nil { + return nil, err + } + + // Client middleware. + if len(c.middlewareHandler) > 0 { + mdlHandlers := make([]HandlerFunc, 0, len(c.middlewareHandler)+1) + mdlHandlers = append(mdlHandlers, c.middlewareHandler...) + mdlHandlers = append(mdlHandlers, func(cli *Client, r *http.Request) (*Response, error) { + return cli.callRequest(r) + }) + ctx := context.WithValue(req.Context(), clientMiddlewareKey, &clientMiddleware{ + client: c, + handlers: mdlHandlers, + handlerIndex: -1, + }) + req = req.WithContext(ctx) + resp, err = c.Next(req) + } else { + resp, err = c.callRequest(req) + } + return resp, err +} + +// prepareRequest verifies request parameters, builds and returns http request. +func (c *Client) prepareRequest(method, url string, data ...interface{}) (req *http.Request, err error) { method = strings.ToUpper(method) if len(c.prefix) > 0 { url = c.prefix + gstr.Trim(url) } - param := "" + var params string if len(data) > 0 { switch c.header["Content-Type"] { case "application/json": switch data[0].(type) { case string, []byte: - param = gconv.String(data[0]) + params = gconv.String(data[0]) default: if b, err := json.Marshal(data[0]); err != nil { return nil, err } else { - param = gconv.UnsafeBytesToStr(b) + params = string(b) } } case "application/xml": switch data[0].(type) { case string, []byte: - param = gconv.String(data[0]) + params = gconv.String(data[0]) default: if b, err := gparser.VarToXml(data[0]); err != nil { return nil, err } else { - param = gconv.UnsafeBytesToStr(b) + params = string(b) } } default: - param = BuildParams(data[0]) + params = httputil.BuildParams(data[0]) } } - var req *http.Request if method == "GET" { - // It appends the parameters to the url if http method is GET. - if param != "" { - if gstr.Contains(url, "?") { - url = url + "&" + param - } else { - url = url + "?" + param + var bodyBuffer *bytes.Buffer + if params != "" { + switch c.header["Content-Type"] { + case + "application/json", + "application/xml": + bodyBuffer = bytes.NewBuffer([]byte(params)) + default: + // It appends the parameters to the url + // if http method is GET and Content-Type is not specified. + if gstr.Contains(url, "?") { + url = url + "&" + params + } else { + url = url + "?" + params + } + bodyBuffer = bytes.NewBuffer(nil) } + } else { + bodyBuffer = bytes.NewBuffer(nil) } - if req, err = http.NewRequest(method, url, bytes.NewBuffer(nil)); err != nil { + if req, err = http.NewRequest(method, url, bodyBuffer); err != nil { return nil, err } } else { - if strings.Contains(param, "@file:") { + if strings.Contains(params, "@file:") { // File uploading request. - buffer := new(bytes.Buffer) - writer := multipart.NewWriter(buffer) - for _, item := range strings.Split(param, "&") { + var ( + buffer = bytes.NewBuffer(nil) + writer = multipart.NewWriter(buffer) + ) + for _, item := range strings.Split(params, "&") { array := strings.Split(item, "=") if len(array[1]) > 6 && strings.Compare(array[1][0:6], "@file:") == 0 { path := array[1][6:] if !gfile.Exists(path) { - return nil, errors.New(fmt.Sprintf(`"%s" does not exist`, path)) + return nil, gerror.NewCodef(gcode.CodeInvalidParameter, `"%s" does not exist`, path) } if file, err := writer.CreateFormFile(array[0], gfile.Basename(path)); err == nil { if f, err := os.Open(path); err == nil { if _, err = io.Copy(file, f); err != nil { - f.Close() + if err := f.Close(); err != nil { + intlog.Errorf(c.ctx, `%+v`, err) + } return nil, err } - f.Close() + if err := f.Close(); err != nil { + intlog.Errorf(c.ctx, `%+v`, err) + } } else { return nil, err } @@ -180,7 +227,7 @@ func (c *Client) DoRequest(method, url string, data ...interface{}) (resp *Clien } } else { // Normal request. - paramBytes := []byte(param) + paramBytes := []byte(params) if req, err = http.NewRequest(method, url, bytes.NewReader(paramBytes)); err != nil { return nil, err } else { @@ -191,7 +238,7 @@ func (c *Client) DoRequest(method, url string, data ...interface{}) (resp *Clien if (paramBytes[0] == '[' || paramBytes[0] == '{') && json.Valid(paramBytes) { // Auto detecting and setting the post content format: JSON. req.Header.Set("Content-Type", "application/json") - } else if gregex.IsMatchString(`^[\w\[\]]+=.+`, param) { + } else if gregex.IsMatchString(`^[\w\[\]]+=.+`, params) { // If the parameters passed like "name=value", it then uses form type. req.Header.Set("Content-Type", "application/x-www-form-urlencoded") } @@ -203,6 +250,8 @@ func (c *Client) DoRequest(method, url string, data ...interface{}) (resp *Clien // Context. if c.ctx != nil { req = req.WithContext(c.ctx) + } else { + req = req.WithContext(context.Background()) } // Custom header. if len(c.header) > 0 { @@ -211,8 +260,8 @@ func (c *Client) DoRequest(method, url string, data ...interface{}) (resp *Clien } } // It's necessary set the req.Host if you want to custom the host value of the request. - // It uses the "Host" value from header if it's not set in the request. - if host := req.Header.Get("Host"); host != "" && req.Host == "" { + // It uses the "Host" value from header if it's not empty. + if host := req.Header.Get("Host"); host != "" { req.Host = host } // Custom Cookie. @@ -232,41 +281,41 @@ func (c *Client) DoRequest(method, url string, data ...interface{}) (resp *Clien if len(c.authUser) > 0 { req.SetBasicAuth(c.authUser, c.authPass) } - resp = &ClientResponse{ + return req, nil +} + +// callRequest sends request with give http.Request, and returns the responses object. +// Note that the response object MUST be closed if it'll be never used. +func (c *Client) callRequest(req *http.Request) (resp *Response, err error) { + resp = &Response{ request: req, } + // Dump feature. // The request body can be reused for dumping // raw HTTP request-response procedure. - reqBodyContent, _ := ioutil.ReadAll(req.Body) - resp.requestBody = reqBodyContent - req.Body = utils.NewReadCloser(reqBodyContent, false) + if c.dump { + reqBodyContent, _ := ioutil.ReadAll(req.Body) + resp.requestBody = reqBodyContent + req.Body = utils.NewReadCloser(reqBodyContent, false) + } for { if resp.Response, err = c.Do(req); err != nil { // The response might not be nil when err != nil. if resp.Response != nil { - resp.Response.Body.Close() + if err := resp.Response.Body.Close(); err != nil { + intlog.Errorf(c.ctx, `%+v`, err) + } } if c.retryCount > 0 { c.retryCount-- time.Sleep(c.retryInterval) } else { - return resp, err + //return resp, err + break } } else { break } } - - // Auto saving cookie content. - if c.browserMode { - now := time.Now() - for _, v := range resp.Response.Cookies() { - if !v.Expires.IsZero() && v.Expires.UnixNano() < now.UnixNano() { - delete(c.cookies, v.Name) - } else { - c.cookies[v.Name] = v.Value - } - } - } - return resp, nil + return resp, err } diff --git a/net/ghttp/ghttp_client_response.go b/net/ghttp/internal/client/client_response.go similarity index 63% rename from net/ghttp/ghttp_client_response.go rename to net/ghttp/internal/client/client_response.go index 30995be5b..324115ebf 100644 --- a/net/ghttp/ghttp_client_response.go +++ b/net/ghttp/internal/client/client_response.go @@ -1,10 +1,10 @@ -// Copyright 2017 gf Author(https://github.com/gogf/gf). All Rights Reserved. +// Copyright GoFrame Author(https://goframe.org). All Rights Reserved. // // This Source Code Form is subject to the terms of the MIT License. // If a copy of the MIT was not distributed with this file, // You can obtain one at https://github.com/gogf/gf. -package ghttp +package client import ( "io/ioutil" @@ -13,32 +13,35 @@ import ( "github.com/gogf/gf/util/gconv" ) -// ClientResponse is the struct for client request response. -type ClientResponse struct { +// Response is the struct for client request response. +type Response struct { *http.Response request *http.Request requestBody []byte cookies map[string]string } -// initCookie initializes the cookie map attribute of ClientResponse. -func (r *ClientResponse) initCookie() { +// initCookie initializes the cookie map attribute of Response. +func (r *Response) initCookie() { if r.cookies == nil { r.cookies = make(map[string]string) - for _, v := range r.Cookies() { - r.cookies[v.Name] = v.Value + // Response might be nil. + if r != nil && r.Response != nil { + for _, v := range r.Cookies() { + r.cookies[v.Name] = v.Value + } } } } // GetCookie retrieves and returns the cookie value of specified <key>. -func (r *ClientResponse) GetCookie(key string) string { +func (r *Response) GetCookie(key string) string { r.initCookie() return r.cookies[key] } // GetCookieMap retrieves and returns a copy of current cookie values map. -func (r *ClientResponse) GetCookieMap() map[string]string { +func (r *Response) GetCookieMap() map[string]string { r.initCookie() m := make(map[string]string, len(r.cookies)) for k, v := range r.cookies { @@ -48,7 +51,11 @@ func (r *ClientResponse) GetCookieMap() map[string]string { } // ReadAll retrieves and returns the response content as []byte. -func (r *ClientResponse) ReadAll() []byte { +func (r *Response) ReadAll() []byte { + // Response might be nil. + if r == nil || r.Response == nil { + return []byte{} + } body, err := ioutil.ReadAll(r.Response.Body) if err != nil { return nil @@ -57,12 +64,12 @@ func (r *ClientResponse) ReadAll() []byte { } // ReadAllString retrieves and returns the response content as string. -func (r *ClientResponse) ReadAllString() string { +func (r *Response) ReadAllString() string { return gconv.UnsafeBytesToStr(r.ReadAll()) } // Close closes the response when it will never be used. -func (r *ClientResponse) Close() error { +func (r *Response) Close() error { if r == nil || r.Response == nil || r.Response.Close { return nil } diff --git a/net/ghttp/internal/client/client_tracing.go b/net/ghttp/internal/client/client_tracing.go new file mode 100644 index 000000000..567ea6c14 --- /dev/null +++ b/net/ghttp/internal/client/client_tracing.go @@ -0,0 +1,81 @@ +// Copyright GoFrame Author(https://goframe.org). All Rights Reserved. +// +// This Source Code Form is subject to the terms of the MIT License. +// If a copy of the MIT was not distributed with this file, +// You can obtain one at https://github.com/gogf/gf. + +package client + +import ( + "fmt" + "github.com/gogf/gf" + "github.com/gogf/gf/internal/utils" + "github.com/gogf/gf/net/ghttp/internal/httputil" + "github.com/gogf/gf/net/gtrace" + "github.com/gogf/gf/text/gstr" + "go.opentelemetry.io/otel" + "go.opentelemetry.io/otel/attribute" + "go.opentelemetry.io/otel/codes" + "go.opentelemetry.io/otel/propagation" + "go.opentelemetry.io/otel/trace" + "io/ioutil" + "net/http" + "net/http/httptrace" +) + +const ( + tracingInstrumentName = "github.com/gogf/gf/net/ghttp.Client" + tracingAttrHttpAddressRemote = "http.address.remote" + tracingAttrHttpAddressLocal = "http.address.local" + tracingAttrHttpDnsStart = "http.dns.start" + tracingAttrHttpDnsDone = "http.dns.done" + tracingAttrHttpConnectStart = "http.connect.start" + tracingAttrHttpConnectDone = "http.connect.done" + tracingEventHttpRequest = "http.request" + tracingEventHttpRequestHeaders = "http.request.headers" + tracingEventHttpRequestBaggage = "http.request.baggage" + tracingEventHttpRequestBody = "http.request.body" + tracingEventHttpResponse = "http.response" + tracingEventHttpResponseHeaders = "http.response.headers" + tracingEventHttpResponseBody = "http.response.body" +) + +// MiddlewareTracing is a client middleware that enables tracing feature using standards of OpenTelemetry. +func MiddlewareTracing(c *Client, r *http.Request) (response *Response, err error) { + tr := otel.GetTracerProvider().Tracer(tracingInstrumentName, trace.WithInstrumentationVersion(gf.VERSION)) + ctx, span := tr.Start(r.Context(), r.URL.String(), trace.WithSpanKind(trace.SpanKindClient)) + defer span.End() + + span.SetAttributes(gtrace.CommonLabels()...) + + // Inject tracing content into http header. + otel.GetTextMapPropagator().Inject(ctx, propagation.HeaderCarrier(r.Header)) + + // Continue client handler executing. + response, err = c.Next( + r.WithContext( + httptrace.WithClientTrace( + ctx, newClientTrace(ctx, span, r), + ), + ), + ) + if err != nil { + span.SetStatus(codes.Error, fmt.Sprintf(`%+v`, err)) + } + if response == nil || response.Response == nil { + return + } + + reqBodyContentBytes, _ := ioutil.ReadAll(response.Body) + response.Body = utils.NewReadCloser(reqBodyContentBytes, false) + + span.AddEvent(tracingEventHttpResponse, trace.WithAttributes( + attribute.Any(tracingEventHttpResponseHeaders, httputil.HeaderToMap(response.Header)), + attribute.String(tracingEventHttpResponseBody, gstr.StrLimit( + string(reqBodyContentBytes), + gtrace.MaxContentLogSize(), + "...", + )), + )) + return +} diff --git a/net/ghttp/internal/client/client_tracing_tracer.go b/net/ghttp/internal/client/client_tracing_tracer.go new file mode 100644 index 000000000..ef4733f45 --- /dev/null +++ b/net/ghttp/internal/client/client_tracing_tracer.go @@ -0,0 +1,174 @@ +// Copyright GoFrame Author(https://goframe.org). All Rights Reserved. +// +// This Source Code Form is subject to the terms of the MIT License. +// If a copy of the MIT was not distributed with this file, +// You can obtain one at https://github.com/gogf/gf. + +package client + +import ( + "context" + "crypto/tls" + "fmt" + "github.com/gogf/gf/internal/utils" + "github.com/gogf/gf/net/gtrace" + "github.com/gogf/gf/text/gstr" + "go.opentelemetry.io/otel/attribute" + "go.opentelemetry.io/otel/codes" + "go.opentelemetry.io/otel/trace" + "io/ioutil" + "net/http" + "net/http/httptrace" + "net/textproto" + "strings" + "sync" +) + +type clientTracer struct { + context.Context + span trace.Span + request *http.Request + requestBody []byte + headers map[string]interface{} + mtx sync.Mutex +} + +func newClientTrace(ctx context.Context, span trace.Span, request *http.Request) *httptrace.ClientTrace { + ct := &clientTracer{ + Context: ctx, + span: span, + request: request, + headers: make(map[string]interface{}), + } + + reqBodyContent, _ := ioutil.ReadAll(ct.request.Body) + ct.requestBody = reqBodyContent + ct.request.Body = utils.NewReadCloser(reqBodyContent, false) + + return &httptrace.ClientTrace{ + GetConn: ct.getConn, + GotConn: ct.gotConn, + PutIdleConn: ct.putIdleConn, + GotFirstResponseByte: ct.gotFirstResponseByte, + Got100Continue: ct.got100Continue, + Got1xxResponse: ct.got1xxResponse, + DNSStart: ct.dnsStart, + DNSDone: ct.dnsDone, + ConnectStart: ct.connectStart, + ConnectDone: ct.connectDone, + TLSHandshakeStart: ct.tlsHandshakeStart, + TLSHandshakeDone: ct.tlsHandshakeDone, + WroteHeaderField: ct.wroteHeaderField, + WroteHeaders: ct.wroteHeaders, + Wait100Continue: ct.wait100Continue, + WroteRequest: ct.wroteRequest, + } +} + +func (ct *clientTracer) getConn(host string) { + +} + +func (ct *clientTracer) gotConn(info httptrace.GotConnInfo) { + ct.span.SetAttributes( + attribute.String(tracingAttrHttpAddressRemote, info.Conn.RemoteAddr().String()), + attribute.String(tracingAttrHttpAddressLocal, info.Conn.LocalAddr().String()), + ) +} + +func (ct *clientTracer) putIdleConn(err error) { + if err != nil { + ct.span.SetStatus(codes.Error, fmt.Sprintf(`%+v`, err)) + } +} + +func (ct *clientTracer) dnsStart(info httptrace.DNSStartInfo) { + ct.span.SetAttributes( + attribute.String(tracingAttrHttpDnsStart, info.Host), + ) +} + +func (ct *clientTracer) dnsDone(info httptrace.DNSDoneInfo) { + var buffer strings.Builder + for _, v := range info.Addrs { + if buffer.Len() != 0 { + buffer.WriteString(",") + } + buffer.WriteString(v.String()) + } + if info.Err != nil { + ct.span.SetStatus(codes.Error, fmt.Sprintf(`%+v`, info.Err)) + } + ct.span.SetAttributes( + attribute.String(tracingAttrHttpDnsDone, buffer.String()), + ) +} + +func (ct *clientTracer) connectStart(network, addr string) { + ct.span.SetAttributes( + attribute.String(tracingAttrHttpConnectStart, network+"@"+addr), + ) +} + +func (ct *clientTracer) connectDone(network, addr string, err error) { + if err != nil { + ct.span.SetStatus(codes.Error, fmt.Sprintf(`%+v`, err)) + } + ct.span.SetAttributes( + attribute.String(tracingAttrHttpConnectDone, network+"@"+addr), + ) +} + +func (ct *clientTracer) tlsHandshakeStart() { + +} + +func (ct *clientTracer) tlsHandshakeDone(_ tls.ConnectionState, err error) { + if err != nil { + ct.span.SetStatus(codes.Error, fmt.Sprintf(`%+v`, err)) + } +} + +func (ct *clientTracer) wroteHeaderField(k string, v []string) { + if len(v) > 1 { + ct.headers[k] = v + } else { + ct.headers[k] = v[0] + } +} + +func (ct *clientTracer) wroteHeaders() { + +} + +func (ct *clientTracer) wroteRequest(info httptrace.WroteRequestInfo) { + if info.Err != nil { + ct.span.SetStatus(codes.Error, fmt.Sprintf(`%+v`, info.Err)) + } + + ct.span.AddEvent(tracingEventHttpRequest, trace.WithAttributes( + attribute.Any(tracingEventHttpRequestHeaders, ct.headers), + attribute.Any(tracingEventHttpRequestBaggage, gtrace.GetBaggageMap(ct.Context)), + attribute.String(tracingEventHttpRequestBody, gstr.StrLimit( + string(ct.requestBody), + gtrace.MaxContentLogSize(), + "...", + )), + )) +} + +func (ct *clientTracer) got100Continue() { + +} + +func (ct *clientTracer) wait100Continue() { + +} + +func (ct *clientTracer) gotFirstResponseByte() { + +} + +func (ct *clientTracer) got1xxResponse(code int, header textproto.MIMEHeader) error { + return nil +} diff --git a/net/ghttp/ghttp_client_var.go b/net/ghttp/internal/client/client_var.go similarity index 97% rename from net/ghttp/ghttp_client_var.go rename to net/ghttp/internal/client/client_var.go index 5102462e6..1fe9810e1 100644 --- a/net/ghttp/ghttp_client_var.go +++ b/net/ghttp/internal/client/client_var.go @@ -1,10 +1,10 @@ -// Copyright 2017 gf Author(https://github.com/gogf/gf). All Rights Reserved. +// Copyright GoFrame Author(https://goframe.org). All Rights Reserved. // // This Source Code Form is subject to the terms of the MIT License. // If a copy of the MIT was not distributed with this file, // You can obtain one at https://github.com/gogf/gf. -package ghttp +package client import ( "github.com/gogf/gf/container/gvar" diff --git a/net/ghttp/internal/httputil/httputils.go b/net/ghttp/internal/httputil/httputils.go new file mode 100644 index 000000000..f7ea4db35 --- /dev/null +++ b/net/ghttp/internal/httputil/httputils.go @@ -0,0 +1,80 @@ +// Copyright GoFrame Author(https://goframe.org). All Rights Reserved. +// +// This Source Code Form is subject to the terms of the MIT License. +// If a copy of the MIT was not distributed with this file, +// You can obtain one at https://github.com/gogf/gf. + +package httputil + +import ( + "github.com/gogf/gf/text/gstr" + "net/http" + "strings" + + "github.com/gogf/gf/encoding/gurl" + "github.com/gogf/gf/util/gconv" +) + +const ( + fileUploadingKey = "@file:" +) + +// BuildParams builds the request string for the http client. The <params> can be type of: +// string/[]byte/map/struct/*struct. +// +// The optional parameter <noUrlEncode> specifies whether ignore the url encoding for the data. +func BuildParams(params interface{}, noUrlEncode ...bool) (encodedParamStr string) { + // If given string/[]byte, converts and returns it directly as string. + switch v := params.(type) { + case string, []byte: + return gconv.String(params) + case []interface{}: + if len(v) > 0 { + params = v[0] + } else { + params = nil + } + } + // Else converts it to map and does the url encoding. + m, urlEncode := gconv.Map(params), true + if len(m) == 0 { + return gconv.String(params) + } + if len(noUrlEncode) == 1 { + urlEncode = !noUrlEncode[0] + } + // If there's file uploading, it ignores the url encoding. + if urlEncode { + for k, v := range m { + if gstr.Contains(k, fileUploadingKey) || gstr.Contains(gconv.String(v), fileUploadingKey) { + urlEncode = false + break + } + } + } + s := "" + for k, v := range m { + if len(encodedParamStr) > 0 { + encodedParamStr += "&" + } + s = gconv.String(v) + if urlEncode && len(s) > 6 && strings.Compare(s[0:6], fileUploadingKey) != 0 { + s = gurl.Encode(s) + } + encodedParamStr += k + "=" + s + } + return +} + +// HeaderToMap coverts request headers to map. +func HeaderToMap(header http.Header) map[string]interface{} { + m := make(map[string]interface{}) + for k, v := range header { + if len(v) > 1 { + m[k] = v + } else { + m[k] = v[0] + } + } + return m +} diff --git a/net/gipv4/gipv4.go b/net/gipv4/gipv4.go index 222be658f..fd49e1479 100644 --- a/net/gipv4/gipv4.go +++ b/net/gipv4/gipv4.go @@ -1,4 +1,4 @@ -// Copyright 2017 gf Author(https://github.com/gogf/gf). All Rights Reserved. +// Copyright GoFrame Author(https://goframe.org). All Rights Reserved. // // This Source Code Form is subject to the terms of the MIT License. // If a copy of the MIT was not distributed with this file, diff --git a/net/gipv4/gipv4_ip.go b/net/gipv4/gipv4_ip.go index c99bab283..e1819e078 100644 --- a/net/gipv4/gipv4_ip.go +++ b/net/gipv4/gipv4_ip.go @@ -1,4 +1,4 @@ -// Copyright 2017 gf Author(https://github.com/gogf/gf). All Rights Reserved. +// Copyright GoFrame Author(https://goframe.org). All Rights Reserved. // // This Source Code Form is subject to the terms of the MIT License. // If a copy of the MIT was not distributed with this file, @@ -8,7 +8,8 @@ package gipv4 import ( - "errors" + "github.com/gogf/gf/errors/gcode" + "github.com/gogf/gf/errors/gerror" "net" "strconv" "strings" @@ -38,7 +39,7 @@ func GetIntranetIp() (ip string, err error) { return "", err } if len(ips) == 0 { - return "", errors.New("no intranet ip found") + return "", gerror.NewCode(gcode.CodeOperationFailed, "no intranet ip found") } return ips[0], nil } diff --git a/net/gipv4/gipv4_lookup.go b/net/gipv4/gipv4_lookup.go index 423a611f1..f6aed4b0f 100644 --- a/net/gipv4/gipv4_lookup.go +++ b/net/gipv4/gipv4_lookup.go @@ -1,4 +1,4 @@ -// Copyright 2017 gf Author(https://github.com/gogf/gf). All Rights Reserved. +// Copyright GoFrame Author(https://goframe.org). All Rights Reserved. // // This Source Code Form is subject to the terms of the MIT License. // If a copy of the MIT was not distributed with this file, diff --git a/net/gipv4/gipv4_mac.go b/net/gipv4/gipv4_mac.go index a39128fc9..78c6e21cd 100644 --- a/net/gipv4/gipv4_mac.go +++ b/net/gipv4/gipv4_mac.go @@ -1,4 +1,4 @@ -// Copyright 2017 gf Author(https://github.com/gogf/gf). All Rights Reserved. +// Copyright GoFrame Author(https://goframe.org). All Rights Reserved. // // This Source Code Form is subject to the terms of the MIT License. // If a copy of the MIT was not distributed with this file, diff --git a/net/gipv6/gipv6.go b/net/gipv6/gipv6.go index 20cb3ad41..1905a9676 100644 --- a/net/gipv6/gipv6.go +++ b/net/gipv6/gipv6.go @@ -1,4 +1,4 @@ -// Copyright 2017 gf Author(https://github.com/gogf/gf). All Rights Reserved. +// Copyright GoFrame Author(https://goframe.org). All Rights Reserved. // // This Source Code Form is subject to the terms of the MIT License. // If a copy of the MIT was not distributed with this file, diff --git a/net/gsmtp/gsmtp.go b/net/gsmtp/gsmtp.go index 0492b465f..37c8dfd5d 100644 --- a/net/gsmtp/gsmtp.go +++ b/net/gsmtp/gsmtp.go @@ -1,4 +1,4 @@ -// Copyright 2017 gf Author(https://github.com/gogf/gf). All Rights Reserved. +// Copyright GoFrame Author(https://goframe.org). All Rights Reserved. // // This Source Code Form is subject to the terms of the MIT License. // If a copy of the MIT was not distributed with this file, diff --git a/net/gsmtp/gsmtp_test.go b/net/gsmtp/gsmtp_test.go index edae876ea..9688a9fae 100644 --- a/net/gsmtp/gsmtp_test.go +++ b/net/gsmtp/gsmtp_test.go @@ -1,4 +1,4 @@ -// Copyright 2017 gf Author(https://github.com/gogf/gf). All Rights Reserved. +// Copyright GoFrame Author(https://goframe.org). All Rights Reserved. // // This Source Code Form is subject to the terms of the MIT License. // If a copy of the MIT was not distributed with this file, diff --git a/net/gtcp/gtcp.go b/net/gtcp/gtcp.go index b69fdc792..75b58f6eb 100644 --- a/net/gtcp/gtcp.go +++ b/net/gtcp/gtcp.go @@ -1,4 +1,4 @@ -// Copyright 2017 gf Author(https://github.com/gogf/gf). All Rights Reserved. +// Copyright GoFrame Author(https://goframe.org). All Rights Reserved. // // This Source Code Form is subject to the terms of the MIT License. // If a copy of the MIT was not distributed with this file, diff --git a/net/gtcp/gtcp_conn.go b/net/gtcp/gtcp_conn.go index 84bbfac25..ff0154c13 100644 --- a/net/gtcp/gtcp_conn.go +++ b/net/gtcp/gtcp_conn.go @@ -1,4 +1,4 @@ -// Copyright 2018 gf Author(https://github.com/gogf/gf). All Rights Reserved. +// Copyright GoFrame Author(https://goframe.org). All Rights Reserved. // // This Source Code Form is subject to the terms of the MIT License. // If a copy of the MIT was not distributed with this file, @@ -15,18 +15,18 @@ import ( "time" ) -// TCP connection object. +// Conn is the TCP connection object. type Conn struct { - net.Conn // Underlying TCP connection object. - reader *bufio.Reader // Buffer reader for connection. - recvDeadline time.Time // Timeout point for reading. - sendDeadline time.Time // Timeout point for writing. - recvBufferWait time.Duration // Interval duration for reading buffer. + net.Conn // Underlying TCP connection object. + reader *bufio.Reader // Buffer reader for connection. + receiveDeadline time.Time // Timeout point for reading. + sendDeadline time.Time // Timeout point for writing. + receiveBufferWait time.Duration // Interval duration for reading buffer. } const ( // Default interval for reading buffer. - gRECV_ALL_WAIT_TIMEOUT = time.Millisecond + receiveAllWaitTimeout = time.Millisecond ) // NewConn creates and returns a new connection with given address. @@ -61,11 +61,11 @@ func NewConnKeyCrt(addr, crtFile, keyFile string) (*Conn, error) { // NewConnByNetConn creates and returns a TCP connection object with given net.Conn object. func NewConnByNetConn(conn net.Conn) *Conn { return &Conn{ - Conn: conn, - reader: bufio.NewReader(conn), - recvDeadline: time.Time{}, - sendDeadline: time.Time{}, - recvBufferWait: gRECV_ALL_WAIT_TIMEOUT, + Conn: conn, + reader: bufio.NewReader(conn), + receiveDeadline: time.Time{}, + sendDeadline: time.Time{}, + receiveBufferWait: receiveAllWaitTimeout, } } @@ -84,7 +84,7 @@ func (c *Conn) Send(data []byte, retry ...Retry) error { if len(retry) > 0 { retry[0].Count-- if retry[0].Interval == 0 { - retry[0].Interval = gDEFAULT_RETRY_INTERVAL + retry[0].Interval = defaultRetryInternal } time.Sleep(retry[0].Interval) } @@ -113,13 +113,13 @@ func (c *Conn) Recv(length int, retry ...Retry) ([]byte, error) { if length > 0 { buffer = make([]byte, length) } else { - buffer = make([]byte, gDEFAULT_READ_BUFFER_SIZE) + buffer = make([]byte, defaultReadBufferSize) } for { if length < 0 && index > 0 { bufferWait = true - if err = c.SetReadDeadline(time.Now().Add(c.recvBufferWait)); err != nil { + if err = c.SetReadDeadline(time.Now().Add(c.receiveBufferWait)); err != nil { return nil, err } } @@ -132,9 +132,9 @@ func (c *Conn) Recv(length int, retry ...Retry) ([]byte, error) { break } } else { - if index >= gDEFAULT_READ_BUFFER_SIZE { + if index >= defaultReadBufferSize { // If it exceeds the buffer size, it then automatically increases its buffer size. - buffer = append(buffer, make([]byte, gDEFAULT_READ_BUFFER_SIZE)...) + buffer = append(buffer, make([]byte, defaultReadBufferSize)...) } else { // It returns immediately if received size is lesser than buffer size. if !bufferWait { @@ -150,7 +150,7 @@ func (c *Conn) Recv(length int, retry ...Retry) ([]byte, error) { } // Re-set the timeout when reading data. if bufferWait && isTimeout(err) { - if err = c.SetReadDeadline(c.recvDeadline); err != nil { + if err = c.SetReadDeadline(c.receiveDeadline); err != nil { return nil, err } err = nil @@ -163,7 +163,7 @@ func (c *Conn) Recv(length int, retry ...Retry) ([]byte, error) { } retry[0].Count-- if retry[0].Interval == 0 { - retry[0].Interval = gDEFAULT_RETRY_INTERVAL + retry[0].Interval = defaultRetryInternal } time.Sleep(retry[0].Interval) continue @@ -230,10 +230,10 @@ func (c *Conn) RecvTil(til []byte, retry ...Retry) ([]byte, error) { // RecvWithTimeout reads data from the connection with timeout. func (c *Conn) RecvWithTimeout(length int, timeout time.Duration, retry ...Retry) (data []byte, err error) { - if err := c.SetRecvDeadline(time.Now().Add(timeout)); err != nil { + if err := c.SetreceiveDeadline(time.Now().Add(timeout)); err != nil { return nil, err } - defer c.SetRecvDeadline(time.Time{}) + defer c.SetreceiveDeadline(time.Time{}) data, err = c.Recv(length, retry...) return } @@ -269,16 +269,16 @@ func (c *Conn) SendRecvWithTimeout(data []byte, length int, timeout time.Duratio func (c *Conn) SetDeadline(t time.Time) error { err := c.Conn.SetDeadline(t) if err == nil { - c.recvDeadline = t + c.receiveDeadline = t c.sendDeadline = t } return err } -func (c *Conn) SetRecvDeadline(t time.Time) error { +func (c *Conn) SetreceiveDeadline(t time.Time) error { err := c.SetReadDeadline(t) if err == nil { - c.recvDeadline = t + c.receiveDeadline = t } return err } @@ -291,8 +291,8 @@ func (c *Conn) SetSendDeadline(t time.Time) error { return err } -// SetRecvBufferWait sets the buffer waiting timeout when reading all data from connection. +// SetreceiveBufferWait sets the buffer waiting timeout when reading all data from connection. // The waiting duration cannot be too long which might delay receiving data from remote address. -func (c *Conn) SetRecvBufferWait(bufferWaitDuration time.Duration) { - c.recvBufferWait = bufferWaitDuration +func (c *Conn) SetreceiveBufferWait(bufferWaitDuration time.Duration) { + c.receiveBufferWait = bufferWaitDuration } diff --git a/net/gtcp/gtcp_conn_pkg.go b/net/gtcp/gtcp_conn_pkg.go index ba75fd374..ef090adcf 100644 --- a/net/gtcp/gtcp_conn_pkg.go +++ b/net/gtcp/gtcp_conn_pkg.go @@ -1,4 +1,4 @@ -// Copyright 2019 gf Author(https://github.com/gogf/gf). All Rights Reserved. +// Copyright GoFrame Author(https://goframe.org). All Rights Reserved. // // This Source Code Form is subject to the terms of the MIT License. // If a copy of the MIT was not distributed with this file, @@ -13,11 +13,11 @@ import ( ) const ( - gPKG_HEADER_SIZE_DEFAULT = 2 // Header size for simple package protocol. - gPKG_HEADER_SIZE_MAX = 4 // Max header size for simple package protocol. + pkgHeaderSizeDefault = 2 // Header size for simple package protocol. + pkgHeaderSizeMax = 4 // Max header size for simple package protocol. ) -// Package option for simple protocol. +// PkgOption is package option for simple protocol. type PkgOption struct { // HeaderSize is used to mark the data length for next data receiving. // It's 2 bytes in default, 4 bytes max, which stands for the max data length @@ -51,10 +51,10 @@ func (c *Conn) SendPkg(data []byte, option ...PkgOption) error { length, pkgOption.MaxDataSize, ) } - offset := gPKG_HEADER_SIZE_MAX - pkgOption.HeaderSize - buffer := make([]byte, gPKG_HEADER_SIZE_MAX+len(data)) + offset := pkgHeaderSizeMax - pkgOption.HeaderSize + buffer := make([]byte, pkgHeaderSizeMax+len(data)) binary.BigEndian.PutUint32(buffer[0:], uint32(length)) - copy(buffer[gPKG_HEADER_SIZE_MAX:], data) + copy(buffer[pkgHeaderSizeMax:], data) if pkgOption.Retry.Count > 0 { return c.Send(buffer[offset:], pkgOption.Retry) } @@ -128,10 +128,10 @@ func (c *Conn) RecvPkg(option ...PkgOption) (result []byte, err error) { // RecvPkgWithTimeout reads data from connection with timeout using simple package protocol. func (c *Conn) RecvPkgWithTimeout(timeout time.Duration, option ...PkgOption) (data []byte, err error) { - if err := c.SetRecvDeadline(time.Now().Add(timeout)); err != nil { + if err := c.SetreceiveDeadline(time.Now().Add(timeout)); err != nil { return nil, err } - defer c.SetRecvDeadline(time.Time{}) + defer c.SetreceiveDeadline(time.Time{}) data, err = c.RecvPkg(option...) return } @@ -144,12 +144,12 @@ func getPkgOption(option ...PkgOption) (*PkgOption, error) { pkgOption = option[0] } if pkgOption.HeaderSize == 0 { - pkgOption.HeaderSize = gPKG_HEADER_SIZE_DEFAULT + pkgOption.HeaderSize = pkgHeaderSizeDefault } - if pkgOption.HeaderSize > gPKG_HEADER_SIZE_MAX { + if pkgOption.HeaderSize > pkgHeaderSizeMax { return nil, fmt.Errorf( `package header size %d definition exceeds max header size %d`, - pkgOption.HeaderSize, gPKG_HEADER_SIZE_MAX, + pkgOption.HeaderSize, pkgHeaderSizeMax, ) } if pkgOption.MaxDataSize == 0 { diff --git a/net/gtcp/gtcp_func.go b/net/gtcp/gtcp_func.go index e120f5ec8..c2d520f49 100644 --- a/net/gtcp/gtcp_func.go +++ b/net/gtcp/gtcp_func.go @@ -1,4 +1,4 @@ -// Copyright 2017 gf Author(https://github.com/gogf/gf). All Rights Reserved. +// Copyright GoFrame Author(https://goframe.org). All Rights Reserved. // // This Source Code Form is subject to the terms of the MIT License. // If a copy of the MIT was not distributed with this file, @@ -16,9 +16,9 @@ import ( ) const ( - gDEFAULT_CONN_TIMEOUT = 30 * time.Second // Default connection timeout. - gDEFAULT_RETRY_INTERVAL = 100 * time.Millisecond // Default retry interval. - gDEFAULT_READ_BUFFER_SIZE = 128 // (Byte) Buffer size for reading. + defaultConnTimeout = 30 * time.Second // Default connection timeout. + defaultRetryInternal = 100 * time.Millisecond // Default retry interval. + defaultReadBufferSize = 128 // (Byte) Buffer size for reading. ) type Retry struct { @@ -29,7 +29,7 @@ type Retry struct { // NewNetConn creates and returns a net.Conn with given address like "127.0.0.1:80". // The optional parameter <timeout> specifies the timeout for dialing connection. func NewNetConn(addr string, timeout ...time.Duration) (net.Conn, error) { - d := gDEFAULT_CONN_TIMEOUT + d := defaultConnTimeout if len(timeout) > 0 { d = timeout[0] } @@ -40,7 +40,7 @@ func NewNetConn(addr string, timeout ...time.Duration) (net.Conn, error) { // The optional parameter <timeout> specifies the timeout for dialing connection. func NewNetConnTLS(addr string, tlsConfig *tls.Config, timeout ...time.Duration) (net.Conn, error) { dialer := &net.Dialer{ - Timeout: gDEFAULT_CONN_TIMEOUT, + Timeout: defaultConnTimeout, } if len(timeout) > 0 { dialer.Timeout = timeout[0] diff --git a/net/gtcp/gtcp_func_pkg.go b/net/gtcp/gtcp_func_pkg.go index 8bcd0aa3d..09cf2fdb9 100644 --- a/net/gtcp/gtcp_func_pkg.go +++ b/net/gtcp/gtcp_func_pkg.go @@ -1,4 +1,4 @@ -// Copyright 2017 gf Author(https://github.com/gogf/gf). All Rights Reserved. +// Copyright GoFrame Author(https://goframe.org). All Rights Reserved. // // This Source Code Form is subject to the terms of the MIT License. // If a copy of the MIT was not distributed with this file, diff --git a/net/gtcp/gtcp_pool.go b/net/gtcp/gtcp_pool.go index 815c8a8bf..49a5cf33e 100644 --- a/net/gtcp/gtcp_pool.go +++ b/net/gtcp/gtcp_pool.go @@ -1,4 +1,4 @@ -// Copyright 2018 gf Author(https://github.com/gogf/gf). All Rights Reserved. +// Copyright GoFrame Author(https://goframe.org). All Rights Reserved. // // This Source Code Form is subject to the terms of the MIT License. // If a copy of the MIT was not distributed with this file, @@ -14,19 +14,18 @@ import ( ) // PoolConn is a connection with pool feature for TCP. -// Note that it is NOT a pool or connection manager, -// it is just a TCP connection object. +// Note that it is NOT a pool or connection manager, it is just a TCP connection object. type PoolConn struct { *Conn // Underlying connection object. - pool *gpool.Pool // Connection pool, which is not a really connection pool, but a connection reusable pool. + pool *gpool.Pool // Connection pool, which is not a real connection pool, but a connection reusable pool. status int // Status of current connection, which is used to mark this connection usable or not. } const ( - gDEFAULT_POOL_EXPIRE = 10 * time.Second // Default TTL for connection in the pool. - gCONN_STATUS_UNKNOWN = 0 // Means it is unknown it's connective or not. - gCONN_STATUS_ACTIVE = 1 // Means it is now connective. - gCONN_STATUS_ERROR = 2 // Means it should be closed and removed from pool. + defaultPoolExpire = 10 * time.Second // Default TTL for connection in the pool. + connStatusUnknown = 0 // Means it is unknown it's connective or not. + connStatusActive = 1 // Means it is now connective. + connStatusError = 2 // Means it should be closed and removed from pool. ) var ( @@ -38,9 +37,9 @@ var ( func NewPoolConn(addr string, timeout ...time.Duration) (*PoolConn, error) { v := addressPoolMap.GetOrSetFuncLock(addr, func() interface{} { var pool *gpool.Pool - pool = gpool.New(gDEFAULT_POOL_EXPIRE, func() (interface{}, error) { + pool = gpool.New(defaultPoolExpire, func() (interface{}, error) { if conn, err := NewConn(addr, timeout...); err == nil { - return &PoolConn{conn, pool, gCONN_STATUS_ACTIVE}, nil + return &PoolConn{conn, pool, connStatusActive}, nil } else { return nil, err } @@ -60,8 +59,8 @@ func NewPoolConn(addr string, timeout ...time.Duration) (*PoolConn, error) { // Note that, if <c> calls Close function closing itself, <c> can not // be used again. func (c *PoolConn) Close() error { - if c.pool != nil && c.status == gCONN_STATUS_ACTIVE { - c.status = gCONN_STATUS_UNKNOWN + if c.pool != nil && c.status == connStatusActive { + c.status = connStatusUnknown c.pool.Put(c) } else { return c.Conn.Close() @@ -73,7 +72,7 @@ func (c *PoolConn) Close() error { // writing data. func (c *PoolConn) Send(data []byte, retry ...Retry) error { err := c.Conn.Send(data, retry...) - if err != nil && c.status == gCONN_STATUS_UNKNOWN { + if err != nil && c.status == connStatusUnknown { if v, e := c.pool.Get(); e == nil { c.Conn = v.(*PoolConn).Conn err = c.Send(data, retry...) @@ -82,9 +81,9 @@ func (c *PoolConn) Send(data []byte, retry ...Retry) error { } } if err != nil { - c.status = gCONN_STATUS_ERROR + c.status = connStatusError } else { - c.status = gCONN_STATUS_ACTIVE + c.status = connStatusActive } return err } @@ -93,9 +92,9 @@ func (c *PoolConn) Send(data []byte, retry ...Retry) error { func (c *PoolConn) Recv(length int, retry ...Retry) ([]byte, error) { data, err := c.Conn.Recv(length, retry...) if err != nil { - c.status = gCONN_STATUS_ERROR + c.status = connStatusError } else { - c.status = gCONN_STATUS_ACTIVE + c.status = connStatusActive } return data, err } @@ -105,9 +104,9 @@ func (c *PoolConn) Recv(length int, retry ...Retry) ([]byte, error) { func (c *PoolConn) RecvLine(retry ...Retry) ([]byte, error) { data, err := c.Conn.RecvLine(retry...) if err != nil { - c.status = gCONN_STATUS_ERROR + c.status = connStatusError } else { - c.status = gCONN_STATUS_ACTIVE + c.status = connStatusActive } return data, err } @@ -117,19 +116,19 @@ func (c *PoolConn) RecvLine(retry ...Retry) ([]byte, error) { func (c *PoolConn) RecvTil(til []byte, retry ...Retry) ([]byte, error) { data, err := c.Conn.RecvTil(til, retry...) if err != nil { - c.status = gCONN_STATUS_ERROR + c.status = connStatusError } else { - c.status = gCONN_STATUS_ACTIVE + c.status = connStatusActive } return data, err } // RecvWithTimeout reads data from the connection with timeout. func (c *PoolConn) RecvWithTimeout(length int, timeout time.Duration, retry ...Retry) (data []byte, err error) { - if err := c.SetRecvDeadline(time.Now().Add(timeout)); err != nil { + if err := c.SetreceiveDeadline(time.Now().Add(timeout)); err != nil { return nil, err } - defer c.SetRecvDeadline(time.Time{}) + defer c.SetreceiveDeadline(time.Time{}) data, err = c.Recv(length, retry...) return } diff --git a/net/gtcp/gtcp_pool_pkg.go b/net/gtcp/gtcp_pool_pkg.go index 1f5cd29a7..ba64af00e 100644 --- a/net/gtcp/gtcp_pool_pkg.go +++ b/net/gtcp/gtcp_pool_pkg.go @@ -1,4 +1,4 @@ -// Copyright 2018 gf Author(https://github.com/gogf/gf). All Rights Reserved. +// Copyright GoFrame Author(https://goframe.org). All Rights Reserved. // // This Source Code Form is subject to the terms of the MIT License. // If a copy of the MIT was not distributed with this file, @@ -13,7 +13,7 @@ import ( // SendPkg sends a package containing <data> to the connection. // The optional parameter <option> specifies the package options for sending. func (c *PoolConn) SendPkg(data []byte, option ...PkgOption) (err error) { - if err = c.Conn.SendPkg(data, option...); err != nil && c.status == gCONN_STATUS_UNKNOWN { + if err = c.Conn.SendPkg(data, option...); err != nil && c.status == connStatusUnknown { if v, e := c.pool.NewFunc(); e == nil { c.Conn = v.(*PoolConn).Conn err = c.Conn.SendPkg(data, option...) @@ -22,9 +22,9 @@ func (c *PoolConn) SendPkg(data []byte, option ...PkgOption) (err error) { } } if err != nil { - c.status = gCONN_STATUS_ERROR + c.status = connStatusError } else { - c.status = gCONN_STATUS_ACTIVE + c.status = connStatusActive } return err } @@ -34,19 +34,19 @@ func (c *PoolConn) SendPkg(data []byte, option ...PkgOption) (err error) { func (c *PoolConn) RecvPkg(option ...PkgOption) ([]byte, error) { data, err := c.Conn.RecvPkg(option...) if err != nil { - c.status = gCONN_STATUS_ERROR + c.status = connStatusError } else { - c.status = gCONN_STATUS_ACTIVE + c.status = connStatusActive } return data, err } // RecvPkgWithTimeout reads data from connection with timeout using simple package protocol. func (c *PoolConn) RecvPkgWithTimeout(timeout time.Duration, option ...PkgOption) (data []byte, err error) { - if err := c.SetRecvDeadline(time.Now().Add(timeout)); err != nil { + if err := c.SetreceiveDeadline(time.Now().Add(timeout)); err != nil { return nil, err } - defer c.SetRecvDeadline(time.Time{}) + defer c.SetreceiveDeadline(time.Time{}) data, err = c.RecvPkg(option...) return } @@ -70,7 +70,7 @@ func (c *PoolConn) SendRecvPkg(data []byte, option ...PkgOption) ([]byte, error) } } -// RecvPkgWithTimeout reads data from connection with timeout using simple package protocol. +// SendRecvPkgWithTimeout reads data from connection with timeout using simple package protocol. func (c *PoolConn) SendRecvPkgWithTimeout(data []byte, timeout time.Duration, option ...PkgOption) ([]byte, error) { if err := c.SendPkg(data, option...); err == nil { return c.RecvPkgWithTimeout(timeout, option...) diff --git a/net/gtcp/gtcp_server.go b/net/gtcp/gtcp_server.go index e56538526..538dd6b2f 100644 --- a/net/gtcp/gtcp_server.go +++ b/net/gtcp/gtcp_server.go @@ -1,4 +1,4 @@ -// Copyright 2017 gf Author(https://github.com/gogf/gf). All Rights Reserved. +// Copyright GoFrame Author(https://goframe.org). All Rights Reserved. // // This Source Code Form is subject to the terms of the MIT License. // If a copy of the MIT was not distributed with this file, @@ -8,7 +8,8 @@ package gtcp import ( "crypto/tls" - "errors" + "github.com/gogf/gf/errors/gcode" + "github.com/gogf/gf/errors/gerror" "net" "sync" @@ -18,11 +19,11 @@ import ( ) const ( - // Default TCP server name. - gDEFAULT_SERVER = "default" + // defaultServer is the default TCP server name. + defaultServer = "default" ) -// TCP Server. +// Server is a TCP server. type Server struct { mu sync.Mutex // Used for Server.listen concurrent safety. listen net.Listener // Listener. @@ -38,7 +39,7 @@ var serverMapping = gmap.NewStrAnyMap(true) // or it returns a new normal TCP server named <name> if it does not exist. // The parameter <name> is used to specify the TCP server func GetServer(name ...interface{}) *Server { - serverName := gDEFAULT_SERVER + serverName := defaultServer if len(name) > 0 && name[0] != "" { serverName = gconv.String(name[0]) } @@ -88,7 +89,7 @@ func (s *Server) SetHandler(handler func(*Conn)) { s.handler = handler } -// SetTlsKeyCrt sets the certificate and key file for TLS configuration of server. +// SetTLSKeyCrt sets the certificate and key file for TLS configuration of server. func (s *Server) SetTLSKeyCrt(crtFile, keyFile string) error { tlsConfig, err := LoadKeyCrt(crtFile, keyFile) if err != nil { @@ -98,7 +99,7 @@ func (s *Server) SetTLSKeyCrt(crtFile, keyFile string) error { return nil } -// SetTlsConfig sets the TLS configuration of server. +// SetTLSConfig sets the TLS configuration of server. func (s *Server) SetTLSConfig(tlsConfig *tls.Config) { s.tlsConfig = tlsConfig } @@ -116,7 +117,7 @@ func (s *Server) Close() error { // Run starts running the TCP Server. func (s *Server) Run() (err error) { if s.handler == nil { - err = errors.New("start running failed: socket handler not defined") + err = gerror.NewCode(gcode.CodeMissingConfiguration, "start running failed: socket handler not defined") glog.Error(err) return } diff --git a/net/gtcp/gtcp_unit_init_test.go b/net/gtcp/gtcp_unit_init_test.go index 974fa04db..1a3c533da 100644 --- a/net/gtcp/gtcp_unit_init_test.go +++ b/net/gtcp/gtcp_unit_init_test.go @@ -1,4 +1,4 @@ -// Copyright 2017 gf Author(https://github.com/gogf/gf). All Rights Reserved. +// Copyright GoFrame Author(https://goframe.org). All Rights Reserved. // // This Source Code Form is subject to the terms of the MIT License. // If a copy of the MIT was not distributed with this file, diff --git a/net/gtcp/gtcp_unit_pkg_test.go b/net/gtcp/gtcp_unit_pkg_test.go index 89687b800..3f9e15fd8 100644 --- a/net/gtcp/gtcp_unit_pkg_test.go +++ b/net/gtcp/gtcp_unit_pkg_test.go @@ -1,4 +1,4 @@ -// Copyright 2017 gf Author(https://github.com/gogf/gf). All Rights Reserved. +// Copyright GoFrame Author(https://goframe.org). All Rights Reserved. // // This Source Code Form is subject to the terms of the MIT License. // If a copy of the MIT was not distributed with this file, diff --git a/net/gtcp/gtcp_unit_pool_pkg_test.go b/net/gtcp/gtcp_unit_pool_pkg_test.go index 49e7fa407..62ffe21b9 100644 --- a/net/gtcp/gtcp_unit_pool_pkg_test.go +++ b/net/gtcp/gtcp_unit_pool_pkg_test.go @@ -1,4 +1,4 @@ -// Copyright 2017 gf Author(https://github.com/gogf/gf). All Rights Reserved. +// Copyright GoFrame Author(https://goframe.org). All Rights Reserved. // // This Source Code Form is subject to the terms of the MIT License. // If a copy of the MIT was not distributed with this file, diff --git a/net/gtcp/gtcp_unit_pool_test.go b/net/gtcp/gtcp_unit_pool_test.go index dbd8cf051..f887494f8 100644 --- a/net/gtcp/gtcp_unit_pool_test.go +++ b/net/gtcp/gtcp_unit_pool_test.go @@ -1,4 +1,4 @@ -// Copyright 2017 gf Author(https://github.com/gogf/gf). All Rights Reserved. +// Copyright GoFrame Author(https://goframe.org). All Rights Reserved. // // This Source Code Form is subject to the terms of the MIT License. // If a copy of the MIT was not distributed with this file, diff --git a/net/gtrace/gtrace.go b/net/gtrace/gtrace.go new file mode 100644 index 000000000..6d847474b --- /dev/null +++ b/net/gtrace/gtrace.go @@ -0,0 +1,135 @@ +// Copyright GoFrame Author(https://goframe.org). All Rights Reserved. +// +// This Source Code Form is subject to the terms of the MIT License. +// If a copy of the MIT was not distributed with this file, +// You can obtain one at https://github.com/gogf/gf. + +// Package gtrace provides convenience wrapping functionality for tracing feature using OpenTelemetry. +package gtrace + +import ( + "context" + "github.com/gogf/gf/container/gmap" + "github.com/gogf/gf/container/gvar" + "github.com/gogf/gf/net/gipv4" + "github.com/gogf/gf/os/gcmd" + "go.opentelemetry.io/otel" + "go.opentelemetry.io/otel/attribute" + "go.opentelemetry.io/otel/propagation" + "go.opentelemetry.io/otel/trace" + "os" + "strings" +) + +const ( + tracingCommonKeyIpIntranet = `ip.intranet` + tracingCommonKeyIpHostname = `hostname` + commandEnvKeyForMaxContentLogSize = "gf.gtrace.maxcontentlogsize" + commandEnvKeyForTracingInternal = "gf.gtrace.tracinginternal" +) + +var ( + intranetIps, _ = gipv4.GetIntranetIpArray() + intranetIpStr = strings.Join(intranetIps, ",") + hostname, _ = os.Hostname() + tracingInternal = true // tracingInternal enables tracing for internal type spans. + tracingMaxContentLogSize = 256 * 1024 // Max log size for request and response body, especially for HTTP/RPC request. + // defaultTextMapPropagator is the default propagator for context propagation between peers. + defaultTextMapPropagator = propagation.NewCompositeTextMapPropagator( + propagation.TraceContext{}, + propagation.Baggage{}, + ) +) + +func init() { + tracingInternal = gcmd.GetOptWithEnv(commandEnvKeyForTracingInternal, true).Bool() + if maxContentLogSize := gcmd.GetOptWithEnv(commandEnvKeyForMaxContentLogSize).Int(); maxContentLogSize > 0 { + tracingMaxContentLogSize = maxContentLogSize + } + CheckSetDefaultTextMapPropagator() +} + +// IsTracingInternal returns whether tracing spans of internal components. +func IsTracingInternal() bool { + return tracingInternal +} + +// MaxContentLogSize returns the max log size for request and response body, especially for HTTP/RPC request. +func MaxContentLogSize() int { + return tracingMaxContentLogSize +} + +// CommonLabels returns common used attribute labels: +// ip.intranet, hostname. +func CommonLabels() []attribute.KeyValue { + return []attribute.KeyValue{ + attribute.String(tracingCommonKeyIpHostname, hostname), + attribute.String(tracingCommonKeyIpIntranet, intranetIpStr), + } +} + +// IsActivated checks and returns if tracing feature is activated. +func IsActivated(ctx context.Context) bool { + return GetTraceId(ctx) != "" +} + +// CheckSetDefaultTextMapPropagator sets the default TextMapPropagator if it is not set previously. +func CheckSetDefaultTextMapPropagator() { + p := otel.GetTextMapPropagator() + if len(p.Fields()) == 0 { + otel.SetTextMapPropagator(GetDefaultTextMapPropagator()) + } +} + +// GetDefaultTextMapPropagator returns the default propagator for context propagation between peers. +func GetDefaultTextMapPropagator() propagation.TextMapPropagator { + return defaultTextMapPropagator +} + +// GetTraceId retrieves and returns TraceId from context. +// It returns an empty string is tracing feature is not activated. +func GetTraceId(ctx context.Context) string { + if ctx == nil { + return "" + } + traceId := trace.SpanContextFromContext(ctx).TraceID() + if traceId.IsValid() { + return traceId.String() + } + return "" +} + +// GetSpanId retrieves and returns SpanId from context. +// It returns an empty string is tracing feature is not activated. +func GetSpanId(ctx context.Context) string { + if ctx == nil { + return "" + } + spanId := trace.SpanContextFromContext(ctx).SpanID() + if spanId.IsValid() { + return spanId.String() + } + return "" +} + +// SetBaggageValue is a convenient function for adding one key-value pair to baggage. +// Note that it uses attribute.Any to set the key-value pair. +func SetBaggageValue(ctx context.Context, key string, value interface{}) context.Context { + return NewBaggage(ctx).SetValue(key, value) +} + +// SetBaggageMap is a convenient function for adding map key-value pairs to baggage. +// Note that it uses attribute.Any to set the key-value pair. +func SetBaggageMap(ctx context.Context, data map[string]interface{}) context.Context { + return NewBaggage(ctx).SetMap(data) +} + +// GetBaggageMap retrieves and returns the baggage values as map. +func GetBaggageMap(ctx context.Context) *gmap.StrAnyMap { + return NewBaggage(ctx).GetMap() +} + +// GetBaggageVar retrieves value and returns a *gvar.Var for specified key from baggage. +func GetBaggageVar(ctx context.Context, key string) *gvar.Var { + return NewBaggage(ctx).GetVar(key) +} diff --git a/net/gtrace/gtrace_baggage.go b/net/gtrace/gtrace_baggage.go new file mode 100644 index 000000000..d04834645 --- /dev/null +++ b/net/gtrace/gtrace_baggage.go @@ -0,0 +1,73 @@ +// Copyright GoFrame Author(https://goframe.org). All Rights Reserved. +// +// This Source Code Form is subject to the terms of the MIT License. +// If a copy of the MIT was not distributed with this file, +// You can obtain one at https://github.com/gogf/gf. + +package gtrace + +import ( + "context" + "github.com/gogf/gf/container/gmap" + "github.com/gogf/gf/container/gvar" + "github.com/gogf/gf/util/gconv" + "go.opentelemetry.io/otel/baggage" +) + +// Baggage holds the data through all tracing spans. +type Baggage struct { + ctx context.Context +} + +// NewBaggage creates and returns a new Baggage object from given tracing context. +func NewBaggage(ctx context.Context) *Baggage { + if ctx == nil { + ctx = context.Background() + } + return &Baggage{ + ctx: ctx, + } +} + +// Ctx returns the context that Baggage holds. +func (b *Baggage) Ctx() context.Context { + return b.ctx +} + +// SetValue is a convenient function for adding one key-value pair to baggage. +// Note that it uses attribute.Any to set the key-value pair. +func (b *Baggage) SetValue(key string, value interface{}) context.Context { + member, _ := baggage.NewMember(key, gconv.String(value)) + bag, _ := baggage.New(member) + b.ctx = baggage.ContextWithBaggage(b.ctx, bag) + return b.ctx +} + +// SetMap is a convenient function for adding map key-value pairs to baggage. +// Note that it uses attribute.Any to set the key-value pair. +func (b *Baggage) SetMap(data map[string]interface{}) context.Context { + members := make([]baggage.Member, 0) + for k, v := range data { + member, _ := baggage.NewMember(k, gconv.String(v)) + members = append(members, member) + } + bag, _ := baggage.New(members...) + b.ctx = baggage.ContextWithBaggage(b.ctx, bag) + return b.ctx +} + +// GetMap retrieves and returns the baggage values as map. +func (b *Baggage) GetMap() *gmap.StrAnyMap { + m := gmap.NewStrAnyMap() + members := baggage.FromContext(b.ctx).Members() + for i := range members { + m.Set(members[i].Key(), members[i].Value()) + } + return m +} + +// GetVar retrieves value and returns a *gvar.Var for specified key from baggage. +func (b *Baggage) GetVar(key string) *gvar.Var { + value := baggage.FromContext(b.ctx).Member(key).Value() + return gvar.New(value) +} diff --git a/net/gtrace/gtrace_carrier.go b/net/gtrace/gtrace_carrier.go new file mode 100644 index 000000000..997f2ea91 --- /dev/null +++ b/net/gtrace/gtrace_carrier.go @@ -0,0 +1,59 @@ +// Copyright GoFrame Author(https://goframe.org). All Rights Reserved. +// +// This Source Code Form is subject to the terms of the MIT License. +// If a copy of the MIT was not distributed with this file, +// You can obtain one at https://github.com/gogf/gf. + +package gtrace + +import ( + "github.com/gogf/gf/internal/json" + "github.com/gogf/gf/util/gconv" +) + +// Carrier is the storage medium used by a TextMapPropagator. +type Carrier map[string]interface{} + +func NewCarrier(data ...map[string]interface{}) Carrier { + if len(data) > 0 && data[0] != nil { + return data[0] + } else { + return make(map[string]interface{}) + } +} + +// Get returns the value associated with the passed key. +func (c Carrier) Get(k string) string { + return gconv.String(c[k]) +} + +// Set stores the key-value pair. +func (c Carrier) Set(k, v string) { + c[k] = v +} + +// Keys lists the keys stored in this carrier. +func (c Carrier) Keys() []string { + keys := make([]string, 0, len(c)) + for k := range c { + keys = append(keys, k) + } + return keys +} + +func (c Carrier) MustMarshal() []byte { + b, err := json.Marshal(c) + if err != nil { + panic(err) + } + return b +} + +func (c Carrier) String() string { + return string(c.MustMarshal()) +} + +func (c Carrier) UnmarshalJSON(b []byte) error { + carrier := NewCarrier(nil) + return json.UnmarshalUseNumber(b, carrier) +} diff --git a/net/gtrace/gtrace_span.go b/net/gtrace/gtrace_span.go new file mode 100644 index 000000000..72f88c418 --- /dev/null +++ b/net/gtrace/gtrace_span.go @@ -0,0 +1,24 @@ +// Copyright GoFrame Author(https://goframe.org). All Rights Reserved. +// +// This Source Code Form is subject to the terms of the MIT License. +// If a copy of the MIT was not distributed with this file, +// You can obtain one at https://github.com/gogf/gf. + +package gtrace + +import ( + "context" + "go.opentelemetry.io/otel/trace" +) + +type Span struct { + trace.Span +} + +// NewSpan creates a span using default tracer. +func NewSpan(ctx context.Context, spanName string, opts ...trace.SpanStartOption) (context.Context, *Span) { + ctx, span := NewTracer().Start(ctx, spanName, opts...) + return ctx, &Span{ + Span: span, + } +} diff --git a/net/gtrace/gtrace_tracer.go b/net/gtrace/gtrace_tracer.go new file mode 100644 index 000000000..8394fcebf --- /dev/null +++ b/net/gtrace/gtrace_tracer.go @@ -0,0 +1,27 @@ +// Copyright GoFrame Author(https://goframe.org). All Rights Reserved. +// +// This Source Code Form is subject to the terms of the MIT License. +// If a copy of the MIT was not distributed with this file, +// You can obtain one at https://github.com/gogf/gf. + +package gtrace + +import ( + "go.opentelemetry.io/otel" + "go.opentelemetry.io/otel/trace" +) + +type Tracer struct { + trace.Tracer +} + +// Tracer is a short function for retrieving Tracer. +func NewTracer(name ...string) *Tracer { + tracerName := "" + if len(name) > 0 { + tracerName = name[0] + } + return &Tracer{ + Tracer: otel.Tracer(tracerName), + } +} diff --git a/net/gtrace/gtrace_unit_carrier_test.go b/net/gtrace/gtrace_unit_carrier_test.go new file mode 100644 index 000000000..095250c92 --- /dev/null +++ b/net/gtrace/gtrace_unit_carrier_test.go @@ -0,0 +1,64 @@ +// Copyright GoFrame Author(https://goframe.org). All Rights Reserved. +// +// This Source Code Form is subject to the terms of the MIT License. +// If a copy of the MIT was not distributed with this file, +// You can obtain one at https://github.com/gogf/gf. + +package gtrace_test + +import ( + "context" + "github.com/gogf/gf/net/gtrace" + "github.com/gogf/gf/test/gtest" + "go.opentelemetry.io/otel" + "go.opentelemetry.io/otel/oteltest" + "go.opentelemetry.io/otel/trace" + "testing" +) + +const ( + traceIDStr = "4bf92f3577b34da6a3ce929d0e0e4736" + spanIDStr = "00f067aa0ba902b7" +) + +var ( + traceID = mustTraceIDFromHex(traceIDStr) + spanID = mustSpanIDFromHex(spanIDStr) +) + +func mustTraceIDFromHex(s string) (t trace.TraceID) { + var err error + t, err = trace.TraceIDFromHex(s) + if err != nil { + panic(err) + } + return +} + +func mustSpanIDFromHex(s string) (t trace.SpanID) { + var err error + t, err = trace.SpanIDFromHex(s) + if err != nil { + panic(err) + } + return +} + +func TestNewCarrier(t *testing.T) { + gtest.C(t, func(t *gtest.T) { + ctx := trace.ContextWithRemoteSpanContext(context.Background(), trace.NewSpanContext(trace.SpanContextConfig{ + TraceID: traceID, + SpanID: spanID, + TraceFlags: trace.FlagsSampled, + })) + ctx, _ = oteltest.DefaultTracer().Start(ctx, "inject") + carrier1 := gtrace.NewCarrier() + otel.GetTextMapPropagator().Inject(ctx, carrier1) + t.Assert(carrier1.String(), `{"traceparent":"00-4bf92f3577b34da6a3ce929d0e0e4736-0000000000000002-01"}`) + + ctx = otel.GetTextMapPropagator().Extract(ctx, carrier1) + gotSc := trace.SpanContextFromContext(ctx) + t.Assert(gotSc.TraceID().String(), traceID.String()) + t.Assert(gotSc.SpanID().String(), "0000000000000002") + }) +} diff --git a/net/gudp/gudp.go b/net/gudp/gudp.go index 81a3cf941..1f0dc99d6 100644 --- a/net/gudp/gudp.go +++ b/net/gudp/gudp.go @@ -1,8 +1,8 @@ -// Copyright 2017 gf Author(https://github.com/gogf/gf). All Rights Reserved. +// Copyright GoFrame Author(https://goframe.org). All Rights Reserved. // // This Source Code Form is subject to the terms of the MIT License. // If a copy of the MIT was not distributed with this file, // You can obtain one at https://github.com/gogf/gf. -// Package gtcp provides UDP server and client implementations. +// Package gudp provides UDP server and client implementations. package gudp diff --git a/net/gudp/gudp_conn.go b/net/gudp/gudp_conn.go index d9fdf317f..ff3d5341a 100644 --- a/net/gudp/gudp_conn.go +++ b/net/gudp/gudp_conn.go @@ -1,4 +1,4 @@ -// Copyright 2018 gf Author(https://github.com/gogf/gf). All Rights Reserved. +// Copyright GoFrame Author(https://goframe.org). All Rights Reserved. // // This Source Code Form is subject to the terms of the MIT License. // If a copy of the MIT was not distributed with this file, @@ -14,17 +14,17 @@ import ( // Conn handles the UDP connection. type Conn struct { - *net.UDPConn // Underlying UDP connection. - remoteAddr *net.UDPAddr // Remote address. - recvDeadline time.Time // Timeout point for reading data. - sendDeadline time.Time // Timeout point for writing data. - recvBufferWait time.Duration // Interval duration for reading buffer. + *net.UDPConn // Underlying UDP connection. + remoteAddr *net.UDPAddr // Remote address. + receiveDeadline time.Time // Timeout point for reading data. + sendDeadline time.Time // Timeout point for writing data. + receiveBufferWait time.Duration // Interval duration for reading buffer. } const ( - gDEFAULT_RETRY_INTERVAL = 100 * time.Millisecond // Retry interval. - gDEFAULT_READ_BUFFER_SIZE = 1024 // (Byte)Buffer size. - gRECV_ALL_WAIT_TIMEOUT = time.Millisecond // Default interval for reading buffer. + defaultRetryInterval = 100 * time.Millisecond // Retry interval. + defaultReadBufferSize = 1024 // (Byte)Buffer size. + receiveAllWaitTimeout = time.Millisecond // Default interval for reading buffer. ) type Retry struct { @@ -45,10 +45,10 @@ func NewConn(remoteAddress string, localAddress ...string) (*Conn, error) { // NewConnByNetConn creates a UDP connection object with given *net.UDPConn object. func NewConnByNetConn(udp *net.UDPConn) *Conn { return &Conn{ - UDPConn: udp, - recvDeadline: time.Time{}, - sendDeadline: time.Time{}, - recvBufferWait: gRECV_ALL_WAIT_TIMEOUT, + UDPConn: udp, + receiveDeadline: time.Time{}, + sendDeadline: time.Time{}, + receiveBufferWait: receiveAllWaitTimeout, } } @@ -72,7 +72,7 @@ func (c *Conn) Send(data []byte, retry ...Retry) (err error) { if len(retry) > 0 { retry[0].Count-- if retry[0].Interval == 0 { - retry[0].Interval = gDEFAULT_RETRY_INTERVAL + retry[0].Interval = defaultRetryInterval } time.Sleep(retry[0].Interval) } @@ -97,7 +97,7 @@ func (c *Conn) Recv(buffer int, retry ...Retry) ([]byte, error) { if buffer > 0 { data = make([]byte, buffer) } else { - data = make([]byte, gDEFAULT_READ_BUFFER_SIZE) + data = make([]byte, defaultReadBufferSize) } for { size, remoteAddr, err = c.ReadFromUDP(data) @@ -116,7 +116,7 @@ func (c *Conn) Recv(buffer int, retry ...Retry) ([]byte, error) { } retry[0].Count-- if retry[0].Interval == 0 { - retry[0].Interval = gDEFAULT_RETRY_INTERVAL + retry[0].Interval = defaultRetryInterval } time.Sleep(retry[0].Interval) continue @@ -169,7 +169,7 @@ func (c *Conn) SendRecvWithTimeout(data []byte, receive int, timeout time.Durati func (c *Conn) SetDeadline(t time.Time) error { err := c.UDPConn.SetDeadline(t) if err == nil { - c.recvDeadline = t + c.receiveDeadline = t c.sendDeadline = t } return err @@ -178,7 +178,7 @@ func (c *Conn) SetDeadline(t time.Time) error { func (c *Conn) SetRecvDeadline(t time.Time) error { err := c.SetReadDeadline(t) if err == nil { - c.recvDeadline = t + c.receiveDeadline = t } return err } @@ -194,7 +194,7 @@ func (c *Conn) SetSendDeadline(t time.Time) error { // SetRecvBufferWait sets the buffer waiting timeout when reading all data from connection. // The waiting duration cannot be too long which might delay receiving data from remote address. func (c *Conn) SetRecvBufferWait(d time.Duration) { - c.recvBufferWait = d + c.receiveBufferWait = d } // RemoteAddr returns the remote address of current UDP connection. diff --git a/net/gudp/gudp_func.go b/net/gudp/gudp_func.go index a4d08fe6d..9616871fe 100644 --- a/net/gudp/gudp_func.go +++ b/net/gudp/gudp_func.go @@ -1,4 +1,4 @@ -// Copyright 2017-2018 gf Author(https://github.com/gogf/gf). All Rights Reserved. +// Copyright GoFrame Author(https://goframe.org). All Rights Reserved. // // This Source Code Form is subject to the terms of the MIT License. // If a copy of the MIT was not distributed with this file, diff --git a/net/gudp/gudp_server.go b/net/gudp/gudp_server.go index 2ecb28a76..8e9bb8846 100644 --- a/net/gudp/gudp_server.go +++ b/net/gudp/gudp_server.go @@ -1,4 +1,4 @@ -// Copyright 2017 gf Author(https://github.com/gogf/gf). All Rights Reserved. +// Copyright GoFrame Author(https://goframe.org). All Rights Reserved. // // This Source Code Form is subject to the terms of the MIT License. // If a copy of the MIT was not distributed with this file, @@ -7,7 +7,8 @@ package gudp import ( - "errors" + "github.com/gogf/gf/errors/gcode" + "github.com/gogf/gf/errors/gerror" "net" "github.com/gogf/gf/container/gmap" @@ -16,7 +17,7 @@ import ( ) const ( - gDEFAULT_SERVER = "default" + defaultServer = "default" ) // Server is the UDP server. @@ -33,7 +34,7 @@ var ( // GetServer creates and returns a UDP server instance with given name. func GetServer(name ...interface{}) *Server { - serverName := gDEFAULT_SERVER + serverName := defaultServer if len(name) > 0 && name[0] != "" { serverName = gconv.String(name[0]) } @@ -78,7 +79,7 @@ func (s *Server) Close() error { // Run starts listening UDP connection. func (s *Server) Run() error { if s.handler == nil { - err := errors.New("start running failed: socket handler not defined") + err := gerror.NewCode(gcode.CodeMissingConfiguration, "start running failed: socket handler not defined") glog.Error(err) return err } diff --git a/net/gudp/gudp_unit_basic_test.go b/net/gudp/gudp_unit_basic_test.go index fbe3c26bd..c7bff0215 100644 --- a/net/gudp/gudp_unit_basic_test.go +++ b/net/gudp/gudp_unit_basic_test.go @@ -1,4 +1,4 @@ -// Copyright 2019 gf Author(https://github.com/gogf/gf). All Rights Reserved. +// Copyright GoFrame Author(https://goframe.org). All Rights Reserved. // // This Source Code Form is subject to the terms of the MIT License. // If a copy of the MIT was not distributed with this file, diff --git a/net/gudp/gudp_unit_init_test.go b/net/gudp/gudp_unit_init_test.go index bc5815922..df1b0fbf7 100644 --- a/net/gudp/gudp_unit_init_test.go +++ b/net/gudp/gudp_unit_init_test.go @@ -1,4 +1,4 @@ -// Copyright 2017 gf Author(https://github.com/gogf/gf). All Rights Reserved. +// Copyright GoFrame Author(https://goframe.org). All Rights Reserved. // // This Source Code Form is subject to the terms of the MIT License. // If a copy of the MIT was not distributed with this file, diff --git a/os/gbuild/gbuild.go b/os/gbuild/gbuild.go index 19b9e1fe5..c44cd57fa 100644 --- a/os/gbuild/gbuild.go +++ b/os/gbuild/gbuild.go @@ -1,4 +1,4 @@ -// Copyright 2019 gf Author(https://github.com/gogf/gf). All Rights Reserved. +// Copyright GoFrame Author(https://goframe.org). All Rights Reserved. // // This Source Code Form is subject to the terms of the MIT License. // If a copy of the MIT was not distributed with this file, @@ -8,6 +8,7 @@ package gbuild import ( + "context" "github.com/gogf/gf" "github.com/gogf/gf/container/gvar" "github.com/gogf/gf/encoding/gbase64" @@ -24,15 +25,15 @@ var ( func init() { if builtInVarStr != "" { - err := json.Unmarshal(gbase64.MustDecodeString(builtInVarStr), &builtInVarMap) + err := json.UnmarshalUseNumber(gbase64.MustDecodeString(builtInVarStr), &builtInVarMap) if err != nil { - intlog.Error(err) + intlog.Error(context.TODO(), err) } builtInVarMap["gfVersion"] = gf.VERSION builtInVarMap["goVersion"] = runtime.Version() - intlog.Printf("build variables: %+v", builtInVarMap) + intlog.Printf(context.TODO(), "build variables: %+v", builtInVarMap) } else { - intlog.Print("no build variables") + intlog.Print(context.TODO(), "no build variables") } } @@ -59,7 +60,7 @@ func Get(name string, def ...interface{}) interface{} { return nil } -// Get retrieves and returns the build-in binary variable of given name as gvar.Var. +// GetVar retrieves and returns the build-in binary variable of given name as gvar.Var. func GetVar(name string, def ...interface{}) *gvar.Var { return gvar.New(Get(name, def...)) } diff --git a/os/gcache/gcache.go b/os/gcache/gcache.go index 80ef28aab..8a0b1c07d 100644 --- a/os/gcache/gcache.go +++ b/os/gcache/gcache.go @@ -1,14 +1,15 @@ -// Copyright 2017-2018 gf Author(https://github.com/gogf/gf). All Rights Reserved. +// Copyright GoFrame Author(https://goframe.org). All Rights Reserved. // // This Source Code Form is subject to the terms of the MIT License. // If a copy of the MIT was not distributed with this file, // You can obtain one at https://github.com/gogf/gf. // Package gcache provides kinds of cache management for process. -// It default provides a concurrent-safe in-memory cache adapter for process. +// It provides a concurrent-safe in-memory cache adapter for process in default. package gcache import ( + "context" "github.com/gogf/gf/container/gvar" "time" ) @@ -16,62 +17,68 @@ import ( // Default cache object. var defaultCache = New() -// Set sets cache with <key>-<value> pair, which is expired after <duration>. -// It does not expire if <duration> == 0. -func Set(key interface{}, value interface{}, duration time.Duration) { - defaultCache.Set(key, value, duration) +// Ctx is a chaining function, which shallowly clones current object and sets the context +// for next operation. +func Ctx(ctx context.Context) *Cache { + return defaultCache.Ctx(ctx) } -// 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. +// Set sets cache with `key`-`value` pair, which is expired after `duration`. +// It does not expire if `duration` == 0. +func Set(key interface{}, value interface{}, duration time.Duration) error { + return defaultCache.Set(key, value, duration) +} + +// 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, error) { return defaultCache.SetIfNotExist(key, value, duration) } -// Sets batch sets cache with key-value pairs by <data>, which is expired after <duration>. +// Sets batch sets cache with key-value pairs by `data`, which is expired after `duration`. // -// It does not expire if <duration> == 0. +// It does not expire if `duration` == 0. func Sets(data map[interface{}]interface{}, duration time.Duration) error { return defaultCache.Sets(data, duration) } -// Get returns the value of <key>. +// Get returns the value of `key`. // It returns nil if it does not exist or its value is nil. func Get(key interface{}) (interface{}, error) { return defaultCache.Get(key) } -// GetVar retrieves and returns the value of <key> as gvar.Var. +// GetVar retrieves and returns the value of `key` as gvar.Var. func GetVar(key interface{}) (*gvar.Var, error) { return defaultCache.GetVar(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>. +// 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. +// It does not expire if `duration` == 0. 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. +// 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{}, error), duration time.Duration) (interface{}, error) { return defaultCache.GetOrSetFunc(key, f, duration) } -// 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. +// 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. // -// Note that the function <f> is executed within writing mutex lock. +// Note that the function `f` is executed within writing mutex lock. 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. +// Contains returns true if `key` exists in the cache, or else returns false. func Contains(key interface{}) (bool, error) { return defaultCache.Contains(key) } @@ -82,10 +89,10 @@ func Remove(keys ...interface{}) (value interface{}, err error) { return defaultCache.Remove(keys...) } -// Removes deletes <keys> in the cache. +// Removes deletes `keys` in the cache. // Deprecated, use Remove instead. func Removes(keys []interface{}) { - defaultCache.Removes(keys) + defaultCache.Remove(keys...) } // Data returns a copy of all key-value pairs in the cache as map type. @@ -113,20 +120,20 @@ 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. +// 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, 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. +// 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. +// 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, err error) { return defaultCache.UpdateExpire(key, duration) } diff --git a/os/gcache/gcache_adapter.go b/os/gcache/gcache_adapter.go index 1dad55efe..33f179058 100644 --- a/os/gcache/gcache_adapter.go +++ b/os/gcache/gcache_adapter.go @@ -1,4 +1,4 @@ -// Copyright 2020 gf Author(https://github.com/gogf/gf). All Rights Reserved. +// Copyright GoFrame Author(https://goframe.org). All Rights Reserved. // // This Source Code Form is subject to the terms of the MIT License. // If a copy of the MIT was not distributed with this file, @@ -7,111 +7,112 @@ package gcache import ( + "context" "time" ) -// Adapter is the adapter for cache features implements. +// Adapter is the core adapter for cache features implements. type Adapter interface { - // Set sets cache with <key>-<value> pair, which is expired after <duration>. + // 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 + // It does not expire if `duration` == 0. + // It deletes the `key` if `duration` < 0. + Set(ctx context.Context, key interface{}, value interface{}, duration time.Duration) error - // Sets batch sets cache with key-value pairs by <data>, which is expired after <duration>. + // 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 + // It does not expire if `duration` == 0. + // It deletes the keys of `data` if `duration` < 0 or given `value` is nil. + Sets(ctx context.Context, 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. + // 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` does 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 + // The parameter `value` can be type of `func() interface{}`, but it does 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) + // It does not expire if `duration` == 0. + // It deletes the `key` if `duration` < 0 or given `value` is nil. + SetIfNotExist(ctx context.Context, 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) + // Get retrieves and returns the associated value of given `key`. + // It returns nil if it does not exist, its value is nil, or it's expired. + Get(ctx context.Context, 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>. + // 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) + // 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(ctx context.Context, 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>. + // 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) + // 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(ctx context.Context, 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>. + // 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. + // 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 + // 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) + GetOrSetFuncLock(ctx context.Context, 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) + // Contains returns true if `key` exists in the cache, or else returns false. + Contains(ctx context.Context, key interface{}) (bool, error) - // GetExpire retrieves and returns the expiration of <key> in the cache. + // 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) + // It returns 0 if the `key` does not expire. + // It returns -1 if the `key` does not exist in the cache. + GetExpire(ctx context.Context, 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) + Remove(ctx context.Context, 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. + // 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) + // It deletes the `key` if given `value` is nil. + // It does nothing if `key` does not exist in the cache. + Update(ctx context.Context, key interface{}, value interface{}) (oldValue interface{}, exist bool, err error) - // UpdateExpire updates the expiration of <key> and returns the old expiration duration value. + // 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) + // It returns -1 and does nothing if the `key` does not exist in the cache. + // It deletes the `key` if `duration` < 0. + UpdateExpire(ctx context.Context, key interface{}, duration time.Duration) (oldDuration time.Duration, err error) // Size returns the number of items in the cache. - Size() (size int, err error) + Size(ctx context.Context) (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 + // Note that this function may lead lots of memory usage, you can implement this function // if necessary. - Data() (map[interface{}]interface{}, error) + Data(ctx context.Context) (map[interface{}]interface{}, error) // Keys returns all keys in the cache as slice. - Keys() ([]interface{}, error) + Keys(ctx context.Context) ([]interface{}, error) // Values returns all values in the cache as slice. - Values() ([]interface{}, error) + Values(ctx context.Context) ([]interface{}, error) // Clear clears all data of the cache. // Note that this function is sensitive and should be carefully used. - Clear() error + Clear(ctx context.Context) error // Close closes the cache if necessary. - Close() error + Close(ctx context.Context) error } diff --git a/os/gcache/gcache_adapter_memory.go b/os/gcache/gcache_adapter_memory.go index 5dd532c61..c70b2b3e3 100644 --- a/os/gcache/gcache_adapter_memory.go +++ b/os/gcache/gcache_adapter_memory.go @@ -1,4 +1,4 @@ -// Copyright 2018 gf Author(https://github.com/gogf/gf). All Rights Reserved. +// Copyright GoFrame Author(https://goframe.org). All Rights Reserved. // // This Source Code Form is subject to the terms of the MIT License. // If a copy of the MIT was not distributed with this file, @@ -7,8 +7,8 @@ package gcache import ( + "context" "math" - "sync" "time" "github.com/gogf/gf/container/glist" @@ -20,43 +20,18 @@ import ( // Internal cache object. type adapterMemory struct { - // dataMu ensures the concurrent safety of underlying data map. - dataMu sync.RWMutex - - // expireTimeMu ensures the concurrent safety of expireTimes map. - expireTimeMu sync.RWMutex - - // expireSetMu ensures the concurrent safety of expireSets map. - expireSetMu sync.RWMutex - // cap limits the size of the cache pool. // If the size of the cache exceeds the cap, // the cache expiration process performs according to the LRU algorithm. // It is 0 in default which means no limits. - cap int - - // data is the underlying cache data which is stored in a hash table. - data map[interface{}]adapterMemoryItem - - // expireTimes is the expiring key to its timestamp mapping, - // which is used for quick indexing and deleting. - expireTimes map[interface{}]int64 - - // expireSets is the expiring timestamp to its key set mapping, - // which is used for quick indexing and deleting. - expireSets map[int64]*gset.Set - - // lru is the LRU manager, which is enabled when attribute cap > 0. - lru *adapterMemoryLru - - // lruGetList is the LRU history according with Get function. - lruGetList *glist.List - - // eventList is the asynchronous event list for internal data synchronization. - eventList *glist.List - - // closed controls the cache closed or not. - closed *gtype.Bool + cap int + data *adapterMemoryData // data is the underlying cache data which is stored in a hash table. + expireTimes *adapterMemoryExpireTimes // expireTimes is the expiring key to its timestamp mapping, which is used for quick indexing and deleting. + expireSets *adapterMemoryExpireSets // expireSets is the expiring timestamp to its key set mapping, which is used for quick indexing and deleting. + lru *adapterMemoryLru // lru is the LRU manager, which is enabled when attribute cap > 0. + lruGetList *glist.List // lruGetList is the LRU history according to Get function. + eventList *glist.List // eventList is the asynchronous event list for internal data synchronization. + closed *gtype.Bool // closed controls the cache closed or not. } // Internal cache item. @@ -72,18 +47,18 @@ type adapterMemoryEvent struct { } const ( - // gDEFAULT_MAX_EXPIRE is the default expire time for no expiring items. + // defaultMaxExpire is the default expire time for no expiring items. // It equals to math.MaxInt64/1000000. - gDEFAULT_MAX_EXPIRE = 9223372036854 + defaultMaxExpire = 9223372036854 ) // newAdapterMemory creates and returns a new memory cache object. func newAdapterMemory(lruCap ...int) *adapterMemory { c := &adapterMemory{ + data: newAdapterMemoryData(), lruGetList: glist.New(true), - data: make(map[interface{}]adapterMemoryItem), - expireTimes: make(map[interface{}]int64), - expireSets: make(map[int64]*gset.Set), + expireTimes: newAdapterMemoryExpireTimes(), + expireSets: newAdapterMemoryExpireSets(), eventList: glist.New(true), closed: gtype.NewBool(), } @@ -94,18 +69,16 @@ func newAdapterMemory(lruCap ...int) *adapterMemory { return c } -// Set sets cache with <key>-<value> pair, which is expired after <duration>. +// 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. -func (c *adapterMemory) Set(key interface{}, value interface{}, duration time.Duration) error { +// It does not expire if `duration` == 0. +// It deletes the `key` if `duration` < 0. +func (c *adapterMemory) Set(ctx context.Context, key interface{}, value interface{}, duration time.Duration) error { expireTime := c.getInternalExpire(duration) - c.dataMu.Lock() - c.data[key] = adapterMemoryItem{ + c.data.Set(key, adapterMemoryItem{ v: value, e: expireTime, - } - c.dataMu.Unlock() + }) c.eventList.PushBack(&adapterMemoryEvent{ k: key, e: expireTime, @@ -113,69 +86,55 @@ func (c *adapterMemory) Set(key interface{}, value interface{}, duration time.Du return nil } -// 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. +// 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. -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] = adapterMemoryItem{ - v: value, - e: item.e, - } - return item.v, true, nil - } - return nil, false, nil +// It deletes the `key` if given `value` is nil. +// It does nothing if `key` does not exist in the cache. +func (c *adapterMemory) Update(ctx context.Context, key interface{}, value interface{}) (oldValue interface{}, exist bool, err error) { + return c.data.Update(key, value) } -// UpdateExpire updates the expiration of <key> and returns the old expiration duration value. +// 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. -func (c *adapterMemory) UpdateExpire(key interface{}, duration time.Duration) (oldDuration time.Duration, err error) { +// 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(ctx context.Context, 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] = adapterMemoryItem{ - v: item.v, - e: newExpireTime, - } + oldDuration, err = c.data.UpdateExpire(key, newExpireTime) + if err != nil { + return + } + if oldDuration != -1 { c.eventList.PushBack(&adapterMemoryEvent{ k: key, e: newExpireTime, }) - return time.Duration(item.e-gtime.TimestampMilli()) * time.Millisecond, nil } - return -1, nil + return } -// GetExpire retrieves and returns the expiration of <key> in the cache. +// 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 *adapterMemory) GetExpire(key interface{}) (time.Duration, error) { - c.dataMu.RLock() - defer c.dataMu.RUnlock() - if item, ok := c.data[key]; ok { +// It returns 0 if the `key` does not expire. +// It returns -1 if the `key` does not exist in the cache. +func (c *adapterMemory) GetExpire(ctx context.Context, key interface{}) (time.Duration, error) { + if item, ok := c.data.Get(key); ok { return time.Duration(item.e-gtime.TimestampMilli()) * time.Millisecond, nil } 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 +// 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` does 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) +// It does not expire if `duration` == 0. +// It deletes the `key` if `duration` < 0 or given `value` is nil. +func (c *adapterMemory) SetIfNotExist(ctx context.Context, key interface{}, value interface{}, duration time.Duration) (bool, error) { + isContained, err := c.Contains(ctx, key) if err != nil { return false, err } @@ -189,19 +148,19 @@ func (c *adapterMemory) SetIfNotExist(key interface{}, value interface{}, durati return false, nil } -// Sets batch sets cache with key-value pairs by <data>, which is expired after <duration>. +// 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() +// 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(ctx context.Context, data map[interface{}]interface{}, duration time.Duration) error { + var ( + expireTime = c.getInternalExpire(duration) + err = c.data.Sets(data, expireTime) + ) + if err != nil { + return err + } + for k, _ := range data { c.eventList.PushBack(&adapterMemoryEvent{ k: k, e: expireTime, @@ -210,12 +169,10 @@ func (c *adapterMemory) Sets(data map[interface{}]interface{}, duration time.Dur return nil } -// Get retrieves and returns the associated value of given <key>. +// 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() +func (c *adapterMemory) Get(ctx context.Context, key interface{}) (interface{}, error) { + item, ok := c.data.Get(key) if ok && !item.IsExpired() { // Adding to LRU history if LRU feature is enabled. if c.cap > 0 { @@ -226,15 +183,15 @@ func (c *adapterMemory) Get(key interface{}) (interface{}, error) { 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>. +// 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) +// 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(ctx context.Context, key interface{}, value interface{}, duration time.Duration) (interface{}, error) { + v, err := c.Get(ctx, key) if err != nil { return nil, err } @@ -245,15 +202,15 @@ func (c *adapterMemory) GetOrSet(key interface{}, value interface{}, duration ti } } -// 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>. +// 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) +// 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(ctx context.Context, key interface{}, f func() (interface{}, error), duration time.Duration) (interface{}, error) { + v, err := c.Get(ctx, key) if err != nil { return nil, err } @@ -271,17 +228,17 @@ func (c *adapterMemory) GetOrSetFunc(key interface{}, f func() (interface{}, err } } -// 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>. +// 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. +// 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 +// 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) +func (c *adapterMemory) GetOrSetFuncLock(ctx context.Context, key interface{}, f func() (interface{}, error), duration time.Duration) (interface{}, error) { + v, err := c.Get(ctx, key) if err != nil { return nil, err } @@ -292,9 +249,9 @@ func (c *adapterMemory) GetOrSetFuncLock(key interface{}, f func() (interface{}, } } -// 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) +// Contains returns true if `key` exists in the cache, or else returns false. +func (c *adapterMemory) Contains(ctx context.Context, key interface{}) (bool, error) { + v, err := c.Get(ctx, key) if err != nil { return false, err } @@ -303,81 +260,49 @@ func (c *adapterMemory) Contains(key interface{}) (bool, error) { // 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, - }) - } +func (c *adapterMemory) Remove(ctx context.Context, keys ...interface{}) (value interface{}, err error) { + var removedKeys []interface{} + removedKeys, value, err = c.data.Remove(keys...) + if err != nil { + return } - return value, nil + for _, key := range removedKeys { + c.eventList.PushBack(&adapterMemoryEvent{ + k: key, + e: gtime.TimestampMilli() - 1000000, + }) + } + return } // 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 +func (c *adapterMemory) Data(ctx context.Context) (map[interface{}]interface{}, error) { + return c.data.Data() } // 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 +func (c *adapterMemory) Keys(ctx context.Context) ([]interface{}, error) { + return c.data.Keys() } // 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 +func (c *adapterMemory) Values(ctx context.Context) ([]interface{}, error) { + return c.data.Values() } // 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 +func (c *adapterMemory) Size(ctx context.Context) (size int, err error) { + return c.data.Size() } // 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 +func (c *adapterMemory) Clear(ctx context.Context) error { + return c.data.Clear() } // Close closes the cache. -func (c *adapterMemory) Close() error { +func (c *adapterMemory) Close(ctx context.Context) error { if c.cap > 0 { c.lru.Close() } @@ -385,79 +310,39 @@ func (c *adapterMemory) Close() error { return nil } -// doSetWithLockCheck sets cache with <key>-<value> pair if <key> does not exist in the -// cache, which is expired after <duration>. +// 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 the +// It does not expire if `duration` == 0. +// 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 +// It doubly checks the `key` whether exists in the cache using mutex writing lock // before setting it to the cache. -func (c *adapterMemory) doSetWithLockCheck(key interface{}, value interface{}, duration time.Duration) (interface{}, error) { +func (c *adapterMemory) doSetWithLockCheck(key interface{}, value interface{}, duration time.Duration) (result interface{}, err error) { expireTimestamp := c.getInternalExpire(duration) - c.dataMu.Lock() - defer c.dataMu.Unlock() - if v, ok := c.data[key]; ok && !v.IsExpired() { - return v.v, 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] = adapterMemoryItem{v: value, e: expireTimestamp} + result, err = c.data.SetWithLock(key, value, expireTimestamp) c.eventList.PushBack(&adapterMemoryEvent{k: key, e: expireTimestamp}) - return value, nil + return } // getInternalExpire converts and returns the expire time with given expired duration in milliseconds. func (c *adapterMemory) getInternalExpire(duration time.Duration) int64 { if duration == 0 { - return gDEFAULT_MAX_EXPIRE + return defaultMaxExpire } else { return gtime.TimestampMilli() + duration.Nanoseconds()/1000000 } } -// makeExpireKey groups the <expire> in milliseconds to its according seconds. +// makeExpireKey groups the `expire` in milliseconds to its according seconds. 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 *adapterMemory) getExpireSet(expire int64) (expireSet *gset.Set) { - c.expireSetMu.RLock() - expireSet, _ = c.expireSets[expire] - c.expireSetMu.RUnlock() - return -} - -// 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 *adapterMemory) getOrNewExpireSet(expire int64) (expireSet *gset.Set) { - if expireSet = c.getExpireSet(expire); expireSet == nil { - expireSet = gset.New(true) - c.expireSetMu.Lock() - if es, ok := c.expireSets[expire]; ok { - expireSet = es - } else { - c.expireSets[expire] = expireSet - } - c.expireSetMu.Unlock() - } - return -} - // 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. +// and synchronize the results to the `expireTimes` and `expireSets` properties. // 2. Clean up the expired key-value pair data. func (c *adapterMemory) syncEventAndClearExpired() { if c.closed.Val() { @@ -479,20 +364,16 @@ func (c *adapterMemory) syncEventAndClearExpired() { } event = v.(*adapterMemoryEvent) // Fetching the old expire set. - c.expireTimeMu.RLock() - oldExpireTime = c.expireTimes[event.k] - c.expireTimeMu.RUnlock() + oldExpireTime = c.expireTimes.Get(event.k) // Calculating the new expire set. newExpireTime = c.makeExpireKey(event.e) if newExpireTime != oldExpireTime { - c.getOrNewExpireSet(newExpireTime).Add(event.k) + c.expireSets.GetOrNew(newExpireTime).Add(event.k) if oldExpireTime != 0 { - c.getOrNewExpireSet(oldExpireTime).Remove(event.k) + c.expireSets.GetOrNew(oldExpireTime).Remove(event.k) } // Updating the expire time for <event.k>. - c.expireTimeMu.Lock() - c.expireTimes[event.k] = newExpireTime - c.expireTimeMu.Unlock() + c.expireTimes.Set(event.k, newExpireTime) } // Adding the key the LRU history by writing operations. if c.cap > 0 { @@ -518,34 +399,26 @@ func (c *adapterMemory) syncEventAndClearExpired() { eks = []int64{ek - 1000, ek - 2000, ek - 3000, ek - 4000, ek - 5000} ) for _, expireTime := range eks { - if expireSet = c.getExpireSet(expireTime); expireSet != nil { + if expireSet = c.expireSets.Get(expireTime); expireSet != nil { // Iterating the set to delete all keys in it. expireSet.Iterator(func(key interface{}) bool { c.clearByKey(key) return true }) // Deleting the set after all of its keys are deleted. - c.expireSetMu.Lock() - delete(c.expireSets, expireTime) - c.expireSetMu.Unlock() + c.expireSets.Delete(expireTime) } } } -// clearByKey deletes the key-value pair with given <key>. -// The parameter <force> specifies whether doing this deleting forcibly. +// clearByKey deletes the key-value pair with given `key`. +// The parameter `force` specifies whether doing this deleting forcibly. 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]) { - delete(c.data, key) - } - c.dataMu.Unlock() + c.data.DeleteWithDoubleCheck(key, force...) - // Deleting its expire time from <expireTimes>. - c.expireTimeMu.Lock() - delete(c.expireTimes, key) - c.expireTimeMu.Unlock() + // Deleting its expire time from `expireTimes`. + c.expireTimes.Delete(key) // Deleting it from LRU. if c.cap > 0 { diff --git a/os/gcache/gcache_adapter_memory_data.go b/os/gcache/gcache_adapter_memory_data.go new file mode 100644 index 000000000..86b20e0fc --- /dev/null +++ b/os/gcache/gcache_adapter_memory_data.go @@ -0,0 +1,199 @@ +// Copyright GoFrame Author(https://goframe.org). All Rights Reserved. +// +// This Source Code Form is subject to the terms of the MIT License. +// If a copy of the MIT was not distributed with this file, +// You can obtain one at https://github.com/gogf/gf. + +package gcache + +import ( + "github.com/gogf/gf/os/gtime" + "sync" + "time" +) + +type adapterMemoryData struct { + mu sync.RWMutex // dataMu ensures the concurrent safety of underlying data map. + data map[interface{}]adapterMemoryItem // data is the underlying cache data which is stored in a hash table. +} + +func newAdapterMemoryData() *adapterMemoryData { + return &adapterMemoryData{ + data: make(map[interface{}]adapterMemoryItem), + } +} + +// 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. +func (d *adapterMemoryData) Update(key interface{}, value interface{}) (oldValue interface{}, exist bool, err error) { + d.mu.Lock() + defer d.mu.Unlock() + if item, ok := d.data[key]; ok { + d.data[key] = adapterMemoryItem{ + v: value, + e: item.e, + } + return item.v, true, nil + } + return nil, false, nil +} + +// 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. +func (d *adapterMemoryData) UpdateExpire(key interface{}, expireTime int64) (oldDuration time.Duration, err error) { + d.mu.Lock() + defer d.mu.Unlock() + if item, ok := d.data[key]; ok { + d.data[key] = adapterMemoryItem{ + v: item.v, + e: expireTime, + } + return time.Duration(item.e-gtime.TimestampMilli()) * time.Millisecond, nil + } + return -1, 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 (d *adapterMemoryData) Remove(keys ...interface{}) (removedKeys []interface{}, value interface{}, err error) { + d.mu.Lock() + defer d.mu.Unlock() + removedKeys = make([]interface{}, 0) + for _, key := range keys { + item, ok := d.data[key] + if ok { + value = item.v + delete(d.data, key) + removedKeys = append(removedKeys, key) + } + } + return removedKeys, value, nil +} + +// Data returns a copy of all key-value pairs in the cache as map type. +func (d *adapterMemoryData) Data() (map[interface{}]interface{}, error) { + d.mu.RLock() + m := make(map[interface{}]interface{}, len(d.data)) + for k, v := range d.data { + if !v.IsExpired() { + m[k] = v.v + } + } + d.mu.RUnlock() + return m, nil +} + +// Keys returns all keys in the cache as slice. +func (d *adapterMemoryData) Keys() ([]interface{}, error) { + d.mu.RLock() + var ( + index = 0 + keys = make([]interface{}, len(d.data)) + ) + for k, v := range d.data { + if !v.IsExpired() { + keys[index] = k + index++ + } + } + d.mu.RUnlock() + return keys, nil +} + +// Values returns all values in the cache as slice. +func (d *adapterMemoryData) Values() ([]interface{}, error) { + d.mu.RLock() + var ( + index = 0 + values = make([]interface{}, len(d.data)) + ) + for _, v := range d.data { + if !v.IsExpired() { + values[index] = v.v + index++ + } + } + d.mu.RUnlock() + return values, nil +} + +// Size returns the size of the cache. +func (d *adapterMemoryData) Size() (size int, err error) { + d.mu.RLock() + size = len(d.data) + d.mu.RUnlock() + return size, nil +} + +// Clear clears all data of the cache. +// Note that this function is sensitive and should be carefully used. +func (d *adapterMemoryData) Clear() error { + d.mu.Lock() + defer d.mu.Unlock() + d.data = make(map[interface{}]adapterMemoryItem) + return nil +} + +func (d *adapterMemoryData) Get(key interface{}) (item adapterMemoryItem, ok bool) { + d.mu.RLock() + item, ok = d.data[key] + d.mu.RUnlock() + return +} + +func (d *adapterMemoryData) Set(key interface{}, value adapterMemoryItem) { + d.mu.Lock() + d.data[key] = value + d.mu.Unlock() +} + +// 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 (d *adapterMemoryData) Sets(data map[interface{}]interface{}, expireTime int64) error { + d.mu.Lock() + for k, v := range data { + d.data[k] = adapterMemoryItem{ + v: v, + e: expireTime, + } + } + d.mu.Unlock() + return nil +} + +func (d *adapterMemoryData) SetWithLock(key interface{}, value interface{}, expireTimestamp int64) (interface{}, error) { + d.mu.Lock() + defer d.mu.Unlock() + if v, ok := d.data[key]; ok && !v.IsExpired() { + return v.v, 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 + } + } + d.data[key] = adapterMemoryItem{v: value, e: expireTimestamp} + return value, nil +} + +func (d *adapterMemoryData) DeleteWithDoubleCheck(key interface{}, force ...bool) { + d.mu.Lock() + // Doubly check before really deleting it from cache. + if item, ok := d.data[key]; (ok && item.IsExpired()) || (len(force) > 0 && force[0]) { + delete(d.data, key) + } + d.mu.Unlock() +} diff --git a/os/gcache/gcache_adapter_memory_expire_sets.go b/os/gcache/gcache_adapter_memory_expire_sets.go new file mode 100644 index 000000000..f041f65ac --- /dev/null +++ b/os/gcache/gcache_adapter_memory_expire_sets.go @@ -0,0 +1,51 @@ +// Copyright GoFrame Author(https://goframe.org). All Rights Reserved. +// +// This Source Code Form is subject to the terms of the MIT License. +// If a copy of the MIT was not distributed with this file, +// You can obtain one at https://github.com/gogf/gf. + +package gcache + +import ( + "github.com/gogf/gf/container/gset" + "sync" +) + +type adapterMemoryExpireSets struct { + mu sync.RWMutex // expireSetMu ensures the concurrent safety of expireSets map. + expireSets map[int64]*gset.Set // expireSets is the expiring timestamp to its key set mapping, which is used for quick indexing and deleting. +} + +func newAdapterMemoryExpireSets() *adapterMemoryExpireSets { + return &adapterMemoryExpireSets{ + expireSets: make(map[int64]*gset.Set), + } +} + +func (d *adapterMemoryExpireSets) Get(key int64) (result *gset.Set) { + d.mu.RLock() + result = d.expireSets[key] + d.mu.RUnlock() + return +} + +func (d *adapterMemoryExpireSets) GetOrNew(key int64) (result *gset.Set) { + if result = d.Get(key); result != nil { + return + } + d.mu.Lock() + if es, ok := d.expireSets[key]; ok { + result = es + } else { + result = gset.New(true) + d.expireSets[key] = result + } + d.mu.Unlock() + return +} + +func (d *adapterMemoryExpireSets) Delete(key int64) { + d.mu.Lock() + delete(d.expireSets, key) + d.mu.Unlock() +} diff --git a/os/gcache/gcache_adapter_memory_expire_times.go b/os/gcache/gcache_adapter_memory_expire_times.go new file mode 100644 index 000000000..af3d4b419 --- /dev/null +++ b/os/gcache/gcache_adapter_memory_expire_times.go @@ -0,0 +1,41 @@ +// Copyright GoFrame Author(https://goframe.org). All Rights Reserved. +// +// This Source Code Form is subject to the terms of the MIT License. +// If a copy of the MIT was not distributed with this file, +// You can obtain one at https://github.com/gogf/gf. + +package gcache + +import ( + "sync" +) + +type adapterMemoryExpireTimes struct { + mu sync.RWMutex // expireTimeMu ensures the concurrent safety of expireTimes map. + expireTimes map[interface{}]int64 // expireTimes is the expiring key to its timestamp mapping, which is used for quick indexing and deleting. +} + +func newAdapterMemoryExpireTimes() *adapterMemoryExpireTimes { + return &adapterMemoryExpireTimes{ + expireTimes: make(map[interface{}]int64), + } +} + +func (d *adapterMemoryExpireTimes) Get(key interface{}) (value int64) { + d.mu.RLock() + value = d.expireTimes[key] + d.mu.RUnlock() + return +} + +func (d *adapterMemoryExpireTimes) Set(key interface{}, value int64) { + d.mu.Lock() + d.expireTimes[key] = value + d.mu.Unlock() +} + +func (d *adapterMemoryExpireTimes) Delete(key interface{}) { + d.mu.Lock() + delete(d.expireTimes, key) + d.mu.Unlock() +} diff --git a/os/gcache/gcache_adapter_memory_item.go b/os/gcache/gcache_adapter_memory_item.go index 9865a6d92..ce411be8f 100644 --- a/os/gcache/gcache_adapter_memory_item.go +++ b/os/gcache/gcache_adapter_memory_item.go @@ -1,4 +1,4 @@ -// Copyright 2018 gf Author(https://github.com/gogf/gf). All Rights Reserved. +// Copyright GoFrame Author(https://goframe.org). All Rights Reserved. // // This Source Code Form is subject to the terms of the MIT License. // If a copy of the MIT was not distributed with this file, @@ -10,7 +10,7 @@ import ( "github.com/gogf/gf/os/gtime" ) -// IsExpired checks whether <item> is expired. +// IsExpired checks whether `item` is expired. 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. diff --git a/os/gcache/gcache_adapter_memory_lru.go b/os/gcache/gcache_adapter_memory_lru.go index b6f6c1b86..c2f2a64d5 100644 --- a/os/gcache/gcache_adapter_memory_lru.go +++ b/os/gcache/gcache_adapter_memory_lru.go @@ -1,4 +1,4 @@ -// Copyright 2018 gf Author(https://github.com/gogf/gf). All Rights Reserved. +// Copyright GoFrame Author(https://goframe.org). All Rights Reserved. // // This Source Code Form is subject to the terms of the MIT License. // If a copy of the MIT was not distributed with this file, @@ -43,7 +43,7 @@ func (lru *adapterMemoryLru) Close() { lru.closed.Set(true) } -// Remove deletes the <key> FROM <lru>. +// Remove deletes the `key` FROM `lru`. func (lru *adapterMemoryLru) Remove(key interface{}) { if v := lru.data.Get(key); v != nil { lru.data.Remove(key) @@ -51,17 +51,17 @@ func (lru *adapterMemoryLru) Remove(key interface{}) { } } -// Size returns the size of <lru>. +// Size returns the size of `lru`. func (lru *adapterMemoryLru) Size() int { return lru.data.Size() } -// Push pushes <key> to the tail of <lru>. +// Push pushes `key` to the tail of `lru`. func (lru *adapterMemoryLru) Push(key interface{}) { lru.rawList.PushBack(key) } -// Pop deletes and returns the key from tail of <lru>. +// Pop deletes and returns the key from tail of `lru`. func (lru *adapterMemoryLru) Pop() interface{} { if v := lru.list.PopBack(); v != nil { lru.data.Remove(v) @@ -78,7 +78,7 @@ func (lru *adapterMemoryLru) Pop() interface{} { // fmt.Println() //} -// SyncAndClear synchronizes the keys from <rawList> to <list> and <data> +// SyncAndClear synchronizes the keys from `rawList` to `list` and `data` // using Least Recently Used algorithm. func (lru *adapterMemoryLru) SyncAndClear() { if lru.closed.Val() { diff --git a/os/gcache/gcache_cache.go b/os/gcache/gcache_cache.go index 5c80c9a2e..216549bd9 100644 --- a/os/gcache/gcache_cache.go +++ b/os/gcache/gcache_cache.go @@ -1,4 +1,4 @@ -// Copyright 2018 gf Author(https://github.com/gogf/gf). All Rights Reserved. +// Copyright GoFrame Author(https://goframe.org). All Rights Reserved. // // This Source Code Form is subject to the terms of the MIT License. // If a copy of the MIT was not distributed with this file, @@ -7,6 +7,7 @@ package gcache import ( + "context" "github.com/gogf/gf/container/gvar" "github.com/gogf/gf/os/gtimer" "github.com/gogf/gf/util/gconv" @@ -15,7 +16,8 @@ import ( // Cache struct. type Cache struct { - Adapter // Adapter for cache features. + adapter Adapter // Adapter for cache features. + ctx context.Context // Context for operations. } // New creates and returns a new cache object using default memory adapter. @@ -23,7 +25,7 @@ type Cache struct { func New(lruCap ...int) *Cache { memAdapter := newAdapterMemory(lruCap...) c := &Cache{ - Adapter: memAdapter, + adapter: memAdapter, } // 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. @@ -31,20 +33,36 @@ func New(lruCap ...int) *Cache { return c } +// Clone returns a shallow copy of current object. +func (c *Cache) Clone() *Cache { + return &Cache{ + adapter: c.adapter, + ctx: c.ctx, + } +} + +// Ctx is a chaining function, which shallowly clones current object and sets the context +// for next operation. +func (c *Cache) Ctx(ctx context.Context) *Cache { + newCache := c.Clone() + newCache.ctx = ctx + return newCache +} + // 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 + c.adapter = adapter } -// GetVar retrieves and returns the value of <key> as gvar.Var. +// 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. +// Removes deletes `keys` in the cache. // Deprecated, use Remove instead. func (c *Cache) Removes(keys []interface{}) error { _, err := c.Remove(keys...) @@ -59,3 +77,11 @@ func (c *Cache) KeyStrings() ([]string, error) { } return gconv.Strings(keys), nil } + +// KeyStrings returns all keys in the cache as string slice. +func (c *Cache) getCtx() context.Context { + if c.ctx == nil { + return context.Background() + } + return c.ctx +} diff --git a/os/gcache/gcache_cache_adapter.go b/os/gcache/gcache_cache_adapter.go new file mode 100644 index 000000000..dce6b19ef --- /dev/null +++ b/os/gcache/gcache_cache_adapter.go @@ -0,0 +1,150 @@ +// Copyright GoFrame Author(https://goframe.org). All Rights Reserved. +// +// This Source Code Form is subject to the terms of the MIT License. +// If a copy of the MIT was not distributed with this file, +// You can obtain one at https://github.com/gogf/gf. + +package gcache + +import ( + "time" +) + +// 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. +func (c *Cache) Set(key interface{}, value interface{}, duration time.Duration) error { + return c.adapter.Set(c.getCtx(), 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. +// It deletes the keys of `data` if `duration` < 0 or given `value` is nil. +func (c *Cache) Sets(data map[interface{}]interface{}, duration time.Duration) error { + return c.adapter.Sets(c.getCtx(), data, duration) +} + +// 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` does 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 does 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 *Cache) SetIfNotExist(key interface{}, value interface{}, duration time.Duration) (bool, error) { + return c.adapter.SetIfNotExist(c.getCtx(), key, value, duration) +} + +// Get retrieves and returns the associated value of given `key`. +// It returns nil if it does not exist, its value is nil or it's expired. +func (c *Cache) Get(key interface{}) (interface{}, error) { + return c.adapter.Get(c.getCtx(), key) +} + +// 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 *Cache) GetOrSet(key interface{}, value interface{}, duration time.Duration) (interface{}, error) { + return c.adapter.GetOrSet(c.getCtx(), key, value, duration) +} + +// 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 *Cache) GetOrSetFunc(key interface{}, f func() (interface{}, error), duration time.Duration) (interface{}, error) { + return c.adapter.GetOrSetFunc(c.getCtx(), key, f, duration) +} + +// 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 *Cache) GetOrSetFuncLock(key interface{}, f func() (interface{}, error), duration time.Duration) (interface{}, error) { + return c.adapter.GetOrSetFuncLock(c.getCtx(), key, f, duration) +} + +// Contains returns true if `key` exists in the cache, or else returns false. +func (c *Cache) Contains(key interface{}) (bool, error) { + return c.adapter.Contains(c.getCtx(), 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 *Cache) GetExpire(key interface{}) (time.Duration, error) { + return c.adapter.GetExpire(c.getCtx(), key) +} + +// 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. +func (c *Cache) Remove(keys ...interface{}) (value interface{}, err error) { + return c.adapter.Remove(c.getCtx(), keys...) +} + +// 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. +func (c *Cache) Update(key interface{}, value interface{}) (oldValue interface{}, exist bool, err error) { + return c.adapter.Update(c.getCtx(), key, value) +} + +// 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. +func (c *Cache) UpdateExpire(key interface{}, duration time.Duration) (oldDuration time.Duration, err error) { + return c.adapter.UpdateExpire(c.getCtx(), key, duration) +} + +// Size returns the number of items in the cache. +func (c *Cache) Size() (size int, err error) { + return c.adapter.Size(c.getCtx()) +} + +// Data returns a copy of all key-value pairs in the cache as map type. +// Note that this function may lead lots of memory usage, you can implement this function +// if necessary. +func (c *Cache) Data() (map[interface{}]interface{}, error) { + return c.adapter.Data(c.getCtx()) +} + +// Keys returns all keys in the cache as slice. +func (c *Cache) Keys() ([]interface{}, error) { + return c.adapter.Keys(c.getCtx()) +} + +// Values returns all values in the cache as slice. +func (c *Cache) Values() ([]interface{}, error) { + return c.adapter.Values(c.getCtx()) +} + +// Clear clears all data of the cache. +// Note that this function is sensitive and should be carefully used. +func (c *Cache) Clear() error { + return c.adapter.Clear(c.getCtx()) +} + +// Close closes the cache if necessary. +func (c *Cache) Close() error { + return c.adapter.Close(c.getCtx()) +} diff --git a/os/gcache/gcache_z_bench_test.go b/os/gcache/gcache_z_bench_test.go index a3df89941..028c07beb 100644 --- a/os/gcache/gcache_z_bench_test.go +++ b/os/gcache/gcache_z_bench_test.go @@ -1,4 +1,4 @@ -// Copyright 2017 gf Author(https://github.com/gogf/gf). All Rights Reserved. +// Copyright GoFrame Author(https://goframe.org). All Rights Reserved. // // This Source Code Form is subject to the terms of the MIT License. // If a copy of the MIT was not distributed with this file, diff --git a/os/gcache/gcache_z_unit_1_test.go b/os/gcache/gcache_z_unit_basic_test.go similarity index 96% rename from os/gcache/gcache_z_unit_1_test.go rename to os/gcache/gcache_z_unit_basic_test.go index 459800a91..9ea748cd3 100644 --- a/os/gcache/gcache_z_unit_1_test.go +++ b/os/gcache/gcache_z_unit_basic_test.go @@ -1,4 +1,4 @@ -// Copyright 2017 gf Author(https://github.com/gogf/gf). All Rights Reserved. +// Copyright GoFrame Author(https://goframe.org). All Rights Reserved. // // This Source Code Form is subject to the terms of the MIT License. // If a copy of the MIT was not distributed with this file, @@ -9,6 +9,7 @@ package gcache_test import ( + "context" "github.com/gogf/gf/util/guid" "math" "testing" @@ -413,3 +414,14 @@ func TestCache_Basic(t *testing.T) { } }) } + +func TestCache_Ctx(t *testing.T) { + gtest.C(t, func(t *gtest.T) { + cache := gcache.New() + cache.Ctx(context.Background()).Sets(g.MapAnyAny{1: 11, 2: 22}, 0) + b, _ := cache.Contains(1) + t.Assert(b, true) + v, _ := cache.Get(1) + t.Assert(v, 11) + }) +} diff --git a/os/gcfg/gcfg.go b/os/gcfg/gcfg.go index f0c5e0249..ec8bf5153 100644 --- a/os/gcfg/gcfg.go +++ b/os/gcfg/gcfg.go @@ -1,4 +1,4 @@ -// Copyright GoFrame Author(https://github.com/gogf/gf). All Rights Reserved. +// Copyright GoFrame Author(https://goframe.org). All Rights Reserved. // // This Source Code Form is subject to the terms of the MIT License. // If a copy of the MIT was not distributed with this file, @@ -8,384 +8,98 @@ package gcfg import ( - "bytes" - "errors" - "fmt" - "github.com/gogf/gf/os/gcmd" - "github.com/gogf/gf/text/gstr" - - "github.com/gogf/gf/os/gres" - + "context" "github.com/gogf/gf/container/garray" "github.com/gogf/gf/container/gmap" - "github.com/gogf/gf/encoding/gjson" - "github.com/gogf/gf/os/gfile" - "github.com/gogf/gf/os/gfsnotify" - "github.com/gogf/gf/os/glog" - "github.com/gogf/gf/os/gspath" + "github.com/gogf/gf/internal/intlog" + "github.com/gogf/gf/os/gcmd" ) -const ( - DefaultConfigFile = "config.toml" // The default configuration file name. - cmdEnvKey = "gf.gcfg" // Configuration key for command argument or environment. -) - -// Configuration struct. +// Config is the configuration manager. type Config struct { - name string // Default configuration file name. - paths *garray.StrArray // Searching path array. - jsons *gmap.StrAnyMap // The pared JSON objects for configuration files. - vc bool // Whether do violence check in value index searching. It affects the performance when set true(false in default). + defaultName string // Default configuration file name. + searchPaths *garray.StrArray // Searching path array. + jsonMap *gmap.StrAnyMap // The pared JSON objects for configuration files. + violenceCheck bool // Whether do violence check in value index searching. It affects the performance when set true(false in default). } -var ( - supportedFileTypes = []string{"toml", "yaml", "json", "ini", "xml"} - resourceTryFiles = []string{"", "/", "config/", "config", "/config", "/config/"} +const ( + DefaultName = "config" // DefaultName is the default group name for instance usage. + DefaultConfigFile = "config.toml" // DefaultConfigFile is the default configuration file name. + commandEnvKeyForFile = "gf.gcfg.file" // commandEnvKeyForFile is the configuration key for command argument or environment configuring file name. + commandEnvKeyForPath = "gf.gcfg.path" // commandEnvKeyForPath is the configuration key for command argument or environment configuring directory path. + commandEnvKeyForErrorPrint = "gf.gcfg.errorprint" // commandEnvKeyForErrorPrint is used to specify the key controlling error printing to stdout. ) -// New returns a new configuration management object. -// The parameter <file> specifies the default configuration file name for reading. -func New(file ...string) *Config { +var ( + supportedFileTypes = []string{"toml", "yaml", "yml", "json", "ini", "xml"} // All supported file types suffixes. + resourceTryFiles = []string{"", "/", "config/", "config", "/config", "/config/"} // Prefix array for trying searching in resource manager. + instances = gmap.NewStrAnyMap(true) // Instances map containing configuration instances. + customConfigContentMap = gmap.NewStrStrMap(true) // Customized configuration content. +) + +// SetContent sets customized configuration content for specified `file`. +// The `file` is unnecessary param, default is DefaultConfigFile. +func SetContent(content string, file ...string) { name := DefaultConfigFile if len(file) > 0 { name = file[0] } - c := &Config{ - name: name, - paths: garray.NewStrArray(true), - jsons: gmap.NewStrAnyMap(true), - } - // Customized dir path from env/cmd. - if envPath := gcmd.GetWithEnv(fmt.Sprintf("%s.path", cmdEnvKey)).String(); envPath != "" { - if gfile.Exists(envPath) { - _ = c.SetPath(envPath) - } else { - if errorPrint() { - glog.Errorf("Configuration directory path does not exist: %s", envPath) + // Clear file cache for instances which cached `name`. + instances.LockFunc(func(m map[string]interface{}) { + if customConfigContentMap.Contains(name) { + for _, v := range m { + v.(*Config).jsonMap.Remove(name) } } - } else { - // Dir path of working dir. - _ = c.SetPath(gfile.Pwd()) - // Dir path of binary. - if selfPath := gfile.SelfDir(); selfPath != "" && gfile.Exists(selfPath) { - _ = c.AddPath(selfPath) - } - // Dir path of main package. - if mainPath := gfile.MainPkgPath(); mainPath != "" && gfile.Exists(mainPath) { - _ = c.AddPath(mainPath) - } - } - return c + customConfigContentMap.Set(name, content) + }) } -// filePath returns the absolute configuration file path for the given filename by <file>. -func (c *Config) filePath(file ...string) (path string) { - name := c.name +// GetContent returns customized configuration content for specified `file`. +// The `file` is unnecessary param, default is DefaultConfigFile. +func GetContent(file ...string) string { + name := DefaultConfigFile if len(file) > 0 { name = file[0] } - path = c.FilePath(name) - if path == "" { - buffer := bytes.NewBuffer(nil) - if c.paths.Len() > 0 { - buffer.WriteString(fmt.Sprintf("[gcfg] cannot find config file \"%s\" in following paths:", name)) - c.paths.RLockFunc(func(array []string) { - index := 1 - for _, v := range array { - v = gstr.TrimRight(v, `\/`) - buffer.WriteString(fmt.Sprintf("\n%d. %s", index, v)) - index++ - buffer.WriteString(fmt.Sprintf("\n%d. %s", index, v+gfile.Separator+"config")) - index++ - } - }) - } else { - buffer.WriteString(fmt.Sprintf("[gcfg] cannot find config file \"%s\" with no path set/add", name)) - } - if errorPrint() { - glog.Error(buffer.String()) - } - } - return path + return customConfigContentMap.Get(name) } -// SetPath sets the configuration directory path for file search. -// The parameter <path> can be absolute or relative path, -// but absolute path is strongly recommended. -func (c *Config) SetPath(path string) error { - var ( - isDir = false - realPath = "" - ) - if file := gres.Get(path); file != nil { - realPath = path - isDir = file.FileInfo().IsDir() - } else { - // Absolute path. - realPath = gfile.RealPath(path) - if realPath == "" { - // Relative path. - c.paths.RLockFunc(func(array []string) { - for _, v := range array { - if path, _ := gspath.Search(v, path); path != "" { - realPath = path - break - } - } - }) - } - if realPath != "" { - isDir = gfile.IsDir(realPath) - } - } - // Path not exist. - if realPath == "" { - buffer := bytes.NewBuffer(nil) - if c.paths.Len() > 0 { - buffer.WriteString(fmt.Sprintf("[gcfg] SetPath failed: cannot find directory \"%s\" in following paths:", path)) - c.paths.RLockFunc(func(array []string) { - for k, v := range array { - buffer.WriteString(fmt.Sprintf("\n%d. %s", k+1, v)) - } - }) - } else { - buffer.WriteString(fmt.Sprintf(`[gcfg] SetPath failed: path "%s" does not exist`, path)) - } - err := errors.New(buffer.String()) - if errorPrint() { - glog.Error(err) - } - return err - } - // Should be a directory. - if !isDir { - err := fmt.Errorf(`[gcfg] SetPath failed: path "%s" should be directory type`, path) - if errorPrint() { - glog.Error(err) - } - return err - } - // Repeated path check. - if c.paths.Search(realPath) != -1 { - return nil - } - c.jsons.Clear() - c.paths.Clear() - c.paths.Append(realPath) - return nil -} - -// SetViolenceCheck sets whether to perform hierarchical conflict checking. -// This feature needs to be enabled when there is a level symbol in the key name. -// It is off in default. -// -// Note that, turning on this feature is quite expensive, and it is not recommended -// to allow separators in the key names. It is best to avoid this on the application side. -func (c *Config) SetViolenceCheck(check bool) { - c.vc = check - c.Clear() -} - -// AddPath adds a absolute or relative path to the search paths. -func (c *Config) AddPath(path string) error { - var ( - isDir = false - realPath = "" - ) - // It firstly checks the resource manager, - // and then checks the filesystem for the path. - if file := gres.Get(path); file != nil { - realPath = path - isDir = file.FileInfo().IsDir() - } else { - // Absolute path. - realPath = gfile.RealPath(path) - if realPath == "" { - // Relative path. - c.paths.RLockFunc(func(array []string) { - for _, v := range array { - if path, _ := gspath.Search(v, path); path != "" { - realPath = path - break - } - } - }) - } - if realPath != "" { - isDir = gfile.IsDir(realPath) - } - } - if realPath == "" { - buffer := bytes.NewBuffer(nil) - if c.paths.Len() > 0 { - buffer.WriteString(fmt.Sprintf("[gcfg] AddPath failed: cannot find directory \"%s\" in following paths:", path)) - c.paths.RLockFunc(func(array []string) { - for k, v := range array { - buffer.WriteString(fmt.Sprintf("\n%d. %s", k+1, v)) - } - }) - } else { - buffer.WriteString(fmt.Sprintf(`[gcfg] AddPath failed: path "%s" does not exist`, path)) - } - err := errors.New(buffer.String()) - if errorPrint() { - glog.Error(err) - } - return err - } - if !isDir { - err := fmt.Errorf(`[gcfg] AddPath failed: path "%s" should be directory type`, path) - if errorPrint() { - glog.Error(err) - } - return err - } - // Repeated path check. - if c.paths.Search(realPath) != -1 { - return nil - } - c.paths.Append(realPath) - //glog.Debug("[gcfg] AddPath:", realPath) - return nil -} - -// GetFilePath returns the absolute path of the specified configuration file. -// If <file> is not passed, it returns the configuration file path of the default name. -// If the specified configuration file does not exist, -// an empty string is returned. -func (c *Config) FilePath(file ...string) (path string) { - name := c.name +// RemoveContent removes the global configuration with specified `file`. +// If `name` is not passed, it removes configuration of the default group name. +func RemoveContent(file ...string) { + name := DefaultConfigFile if len(file) > 0 { name = file[0] } - // Searching resource manager. - if !gres.IsEmpty() { - for _, v := range resourceTryFiles { - if file := gres.Get(v + name); file != nil { - path = file.Name() - return - } - } - c.paths.RLockFunc(func(array []string) { - for _, prefix := range array { - for _, v := range resourceTryFiles { - if file := gres.Get(prefix + v + name); file != nil { - path = file.Name() - return - } - } - } - }) - } - // Searching the file system. - c.paths.RLockFunc(func(array []string) { - for _, prefix := range array { - prefix = gstr.TrimRight(prefix, `\/`) - if path, _ = gspath.Search(prefix, name); path != "" { - return - } - if path, _ = gspath.Search(prefix+gfile.Separator+"config", name); path != "" { - return + // Clear file cache for instances which cached `name`. + instances.LockFunc(func(m map[string]interface{}) { + if customConfigContentMap.Contains(name) { + for _, v := range m { + v.(*Config).jsonMap.Remove(name) } + customConfigContentMap.Remove(name) } }) - return + + intlog.Printf(context.TODO(), `RemoveContent: %s`, name) } -// SetFileName sets the default configuration file name. -func (c *Config) SetFileName(name string) *Config { - c.name = name - return c -} - -// GetFileName returns the default configuration file name. -func (c *Config) GetFileName() string { - return c.name -} - -// Available checks and returns whether configuration of given <file> is available. -func (c *Config) Available(file ...string) bool { - var name string - if len(file) > 0 && file[0] != "" { - name = file[0] - } else { - name = c.name - } - if c.FilePath(name) != "" { - return true - } - if GetContent(name) != "" { - return true - } - return false -} - -// getJson returns a *gjson.Json object for the specified <file> content. -// It would print error if file reading fails. It return nil if any error occurs. -func (c *Config) getJson(file ...string) *gjson.Json { - var name string - if len(file) > 0 && file[0] != "" { - name = file[0] - } else { - name = c.name - } - r := c.jsons.GetOrSetFuncLock(name, func() interface{} { - var ( - 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 - } - if file := gres.Get(filePath); file != nil { - content = string(file.Content()) - } else { - content = gfile.GetContents(filePath) - } +// ClearContent removes all global configuration contents. +func ClearContent() { + customConfigContentMap.Clear() + // Clear cache for all instances. + instances.LockFunc(func(m map[string]interface{}) { + for _, v := range m { + v.(*Config).jsonMap.Clear() } - // Note that the underlying configuration json object operations are concurrent safe. - 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. - if filePath != "" && !gres.Contains(filePath) { - _, err = gfsnotify.Add(filePath, func(event *gfsnotify.Event) { - c.jsons.Remove(name) - }) - if err != nil && errorPrint() { - glog.Error(err) - } - } - return j - } else { - if errorPrint() { - if filePath != "" { - glog.Criticalf(`[gcfg] Load config file "%s" failed: %s`, filePath, err.Error()) - } else { - glog.Criticalf(`[gcfg] Load configuration failed: %s`, err.Error()) - } - } - } - return nil }) - if r != nil { - return r.(*gjson.Json) - } - return nil + + intlog.Print(context.TODO(), `RemoveConfig`) +} + +// errorPrint checks whether printing error to stdout. +func errorPrint() bool { + return gcmd.GetOptWithEnv(commandEnvKeyForErrorPrint, true).Bool() } diff --git a/os/gcfg/gcfg_config.go b/os/gcfg/gcfg_config.go index e9e404834..f7db8f603 100644 --- a/os/gcfg/gcfg_config.go +++ b/os/gcfg/gcfg_config.go @@ -1,4 +1,4 @@ -// Copyright GoFrame Author(https://github.com/gogf/gf). All Rights Reserved. +// Copyright GoFrame Author(https://goframe.org). All Rights Reserved. // // This Source Code Form is subject to the terms of the MIT License. // If a copy of the MIT was not distributed with this file, @@ -7,72 +7,414 @@ package gcfg import ( + "bytes" + "context" + "fmt" + "github.com/gogf/gf/container/garray" "github.com/gogf/gf/container/gmap" + "github.com/gogf/gf/encoding/gjson" + "github.com/gogf/gf/errors/gcode" + "github.com/gogf/gf/errors/gerror" "github.com/gogf/gf/internal/intlog" + "github.com/gogf/gf/os/gcmd" + "github.com/gogf/gf/os/gfile" + "github.com/gogf/gf/os/gfsnotify" + "github.com/gogf/gf/os/glog" + "github.com/gogf/gf/os/gres" + "github.com/gogf/gf/os/gspath" + "github.com/gogf/gf/text/gstr" + "github.com/gogf/gf/util/gmode" ) -var ( - // Customized configuration content. - configs = gmap.NewStrStrMap(true) -) - -// SetContent sets customized configuration content for specified <file>. -// The <file> is unnecessary param, default is DefaultConfigFile. -func SetContent(content string, file ...string) { +// New returns a new configuration management object. +// The parameter `file` specifies the default configuration file name for reading. +func New(file ...string) *Config { name := DefaultConfigFile if len(file) > 0 { name = file[0] + } else { + // Custom default configuration file name from command line or environment. + if customFile := gcmd.GetOptWithEnv(commandEnvKeyForFile).String(); customFile != "" { + name = customFile + } } - // Clear file cache for instances which cached <name>. - instances.LockFunc(func(m map[string]interface{}) { - if configs.Contains(name) { - for _, v := range m { - v.(*Config).jsons.Remove(name) + c := &Config{ + defaultName: name, + searchPaths: garray.NewStrArray(true), + jsonMap: gmap.NewStrAnyMap(true), + } + // Customized dir path from env/cmd. + if customPath := gcmd.GetOptWithEnv(commandEnvKeyForPath).String(); customPath != "" { + if gfile.Exists(customPath) { + _ = c.SetPath(customPath) + } else { + if errorPrint() { + glog.Errorf("[gcfg] Configuration directory path does not exist: %s", customPath) } } - configs.Set(name, content) - }) -} + } else { + // Dir path of working dir. + if err := c.AddPath(gfile.Pwd()); err != nil { + intlog.Error(context.TODO(), err) + } -// GetContent returns customized configuration content for specified <file>. -// The <file> is unnecessary param, default is DefaultConfigFile. -func GetContent(file ...string) string { - name := DefaultConfigFile - if len(file) > 0 { - name = file[0] - } - return configs.Get(name) -} - -// RemoveContent removes the global configuration with specified <file>. -// If <name> is not passed, it removes configuration of the default group name. -func RemoveContent(file ...string) { - name := DefaultConfigFile - if len(file) > 0 { - name = file[0] - } - // Clear file cache for instances which cached <name>. - instances.LockFunc(func(m map[string]interface{}) { - if configs.Contains(name) { - for _, v := range m { - v.(*Config).jsons.Remove(name) + // Dir path of main package. + if mainPath := gfile.MainPkgPath(); mainPath != "" && gfile.Exists(mainPath) { + if err := c.AddPath(mainPath); err != nil { + intlog.Error(context.TODO(), err) } - configs.Remove(name) } - }) - intlog.Printf(`RemoveContent: %s`, name) + // Dir path of binary. + if selfPath := gfile.SelfDir(); selfPath != "" && gfile.Exists(selfPath) { + if err := c.AddPath(selfPath); err != nil { + intlog.Error(context.TODO(), err) + } + } + } + return c } -// ClearContent removes all global configuration contents. -func ClearContent() { - configs.Clear() - // Clear cache for all instances. - instances.LockFunc(func(m map[string]interface{}) { - for _, v := range m { - v.(*Config).jsons.Clear() +// Instance returns an instance of Config with default settings. +// The parameter `name` is the name for the instance. But very note that, if the file "name.toml" +// exists in the configuration directory, it then sets it as the default configuration file. The +// toml file type is the default configuration file type. +func Instance(name ...string) *Config { + key := DefaultName + if len(name) > 0 && name[0] != "" { + key = name[0] + } + return instances.GetOrSetFuncLock(key, func() interface{} { + c := New() + // If it's not using default configuration or its configuration file is not available, + // it searches the possible configuration file according to the name and all supported + // file types. + if key != DefaultName || !c.Available() { + for _, fileType := range supportedFileTypes { + if file := fmt.Sprintf(`%s.%s`, key, fileType); c.Available(file) { + c.SetFileName(file) + break + } + } + } + return c + }).(*Config) +} + +// SetPath sets the configuration directory path for file search. +// The parameter `path` can be absolute or relative path, +// but absolute path is strongly recommended. +func (c *Config) SetPath(path string) error { + var ( + isDir = false + realPath = "" + ) + if file := gres.Get(path); file != nil { + realPath = path + isDir = file.FileInfo().IsDir() + } else { + // Absolute path. + realPath = gfile.RealPath(path) + if realPath == "" { + // Relative path. + c.searchPaths.RLockFunc(func(array []string) { + for _, v := range array { + if path, _ := gspath.Search(v, path); path != "" { + realPath = path + break + } + } + }) + } + if realPath != "" { + isDir = gfile.IsDir(realPath) + } + } + // Path not exist. + if realPath == "" { + buffer := bytes.NewBuffer(nil) + if c.searchPaths.Len() > 0 { + buffer.WriteString(fmt.Sprintf("[gcfg] SetPath failed: cannot find directory \"%s\" in following paths:", path)) + c.searchPaths.RLockFunc(func(array []string) { + for k, v := range array { + buffer.WriteString(fmt.Sprintf("\n%d. %s", k+1, v)) + } + }) + } else { + buffer.WriteString(fmt.Sprintf(`[gcfg] SetPath failed: path "%s" does not exist`, path)) + } + err := gerror.NewCode(gcode.CodeOperationFailed, buffer.String()) + if errorPrint() { + glog.Error(err) + } + return err + } + // Should be a directory. + if !isDir { + err := fmt.Errorf(`[gcfg] SetPath failed: path "%s" should be directory type`, path) + if errorPrint() { + glog.Error(err) + } + return err + } + // Repeated path check. + if c.searchPaths.Search(realPath) != -1 { + return nil + } + c.jsonMap.Clear() + c.searchPaths.Clear() + c.searchPaths.Append(realPath) + intlog.Print(context.TODO(), "SetPath:", realPath) + return nil +} + +// SetViolenceCheck sets whether to perform hierarchical conflict checking. +// This feature needs to be enabled when there is a level symbol in the key name. +// It is off in default. +// +// Note that, turning on this feature is quite expensive, and it is not recommended +// to allow separators in the key names. It is best to avoid this on the application side. +func (c *Config) SetViolenceCheck(check bool) { + c.violenceCheck = check + c.Clear() +} + +// AddPath adds a absolute or relative path to the search paths. +func (c *Config) AddPath(path string) error { + var ( + isDir = false + realPath = "" + ) + // It firstly checks the resource manager, + // and then checks the filesystem for the path. + if file := gres.Get(path); file != nil { + realPath = path + isDir = file.FileInfo().IsDir() + } else { + // Absolute path. + realPath = gfile.RealPath(path) + if realPath == "" { + // Relative path. + c.searchPaths.RLockFunc(func(array []string) { + for _, v := range array { + if path, _ := gspath.Search(v, path); path != "" { + realPath = path + break + } + } + }) + } + if realPath != "" { + isDir = gfile.IsDir(realPath) + } + } + if realPath == "" { + buffer := bytes.NewBuffer(nil) + if c.searchPaths.Len() > 0 { + buffer.WriteString(fmt.Sprintf("[gcfg] AddPath failed: cannot find directory \"%s\" in following paths:", path)) + c.searchPaths.RLockFunc(func(array []string) { + for k, v := range array { + buffer.WriteString(fmt.Sprintf("\n%d. %s", k+1, v)) + } + }) + } else { + buffer.WriteString(fmt.Sprintf(`[gcfg] AddPath failed: path "%s" does not exist`, path)) + } + err := gerror.NewCode(gcode.CodeOperationFailed, buffer.String()) + if errorPrint() { + glog.Error(err) + } + return err + } + if !isDir { + err := gerror.NewCodef(gcode.CodeInvalidParameter, `[gcfg] AddPath failed: path "%s" should be directory type`, path) + if errorPrint() { + glog.Error(err) + } + return err + } + // Repeated path check. + if c.searchPaths.Search(realPath) != -1 { + return nil + } + c.searchPaths.Append(realPath) + intlog.Print(context.TODO(), "AddPath:", realPath) + return nil +} + +// SetFileName sets the default configuration file name. +func (c *Config) SetFileName(name string) *Config { + c.defaultName = name + return c +} + +// GetFileName returns the default configuration file name. +func (c *Config) GetFileName() string { + return c.defaultName +} + +// Available checks and returns whether configuration of given `file` is available. +func (c *Config) Available(file ...string) bool { + var name string + if len(file) > 0 && file[0] != "" { + name = file[0] + } else { + name = c.defaultName + } + if path, _ := c.GetFilePath(name); path != "" { + return true + } + if GetContent(name) != "" { + return true + } + return false +} + +// GetFilePath returns the absolute configuration file path for the given filename by `file`. +// If `file` is not passed, it returns the configuration file path of the default name. +// It returns an empty `path` string and an error if the given `file` does not exist. +func (c *Config) GetFilePath(file ...string) (path string, err error) { + name := c.defaultName + if len(file) > 0 { + name = file[0] + } + // Searching resource manager. + if !gres.IsEmpty() { + for _, v := range resourceTryFiles { + if file := gres.Get(v + name); file != nil { + path = file.Name() + return + } + } + c.searchPaths.RLockFunc(func(array []string) { + for _, prefix := range array { + for _, v := range resourceTryFiles { + if file := gres.Get(prefix + v + name); file != nil { + path = file.Name() + return + } + } + } + }) + } + c.autoCheckAndAddMainPkgPathToSearchPaths() + // Searching the file system. + c.searchPaths.RLockFunc(func(array []string) { + for _, prefix := range array { + prefix = gstr.TrimRight(prefix, `\/`) + if path, _ = gspath.Search(prefix, name); path != "" { + return + } + if path, _ = gspath.Search(prefix+gfile.Separator+"config", name); path != "" { + return + } } }) - - intlog.Print(`RemoveConfig`) + // If it cannot find the path of `file`, it formats and returns a detailed error. + if path == "" { + var ( + buffer = bytes.NewBuffer(nil) + ) + if c.searchPaths.Len() > 0 { + buffer.WriteString(fmt.Sprintf(`[gcfg] cannot find config file "%s" in resource manager or the following paths:`, name)) + c.searchPaths.RLockFunc(func(array []string) { + index := 1 + for _, v := range array { + v = gstr.TrimRight(v, `\/`) + buffer.WriteString(fmt.Sprintf("\n%d. %s", index, v)) + index++ + buffer.WriteString(fmt.Sprintf("\n%d. %s", index, v+gfile.Separator+"config")) + index++ + } + }) + } else { + buffer.WriteString(fmt.Sprintf("[gcfg] cannot find config file \"%s\" with no path configured", name)) + } + err = gerror.NewCode(gcode.CodeOperationFailed, buffer.String()) + } + return +} + +// autoCheckAndAddMainPkgPathToSearchPaths automatically checks and adds directory path of package main +// to the searching path list if it's currently in development environment. +func (c *Config) autoCheckAndAddMainPkgPathToSearchPaths() { + if gmode.IsDevelop() { + mainPkgPath := gfile.MainPkgPath() + if mainPkgPath != "" { + if !c.searchPaths.Contains(mainPkgPath) { + c.searchPaths.Append(mainPkgPath) + } + } + } +} + +// getJson returns a *gjson.Json object for the specified `file` content. +// It would print error if file reading fails. It return nil if any error occurs. +func (c *Config) getJson(file ...string) *gjson.Json { + var name string + if len(file) > 0 && file[0] != "" { + name = file[0] + } else { + name = c.defaultName + } + r := c.jsonMap.GetOrSetFuncLock(name, func() interface{} { + var ( + err error + content string + filePath string + ) + // 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, err = c.GetFilePath(name) + if err != nil && errorPrint() { + glog.Error(err) + } + if filePath == "" { + return nil + } + if file := gres.Get(filePath); file != nil { + content = string(file.Content()) + } else { + content = gfile.GetContents(filePath) + } + } + // Note that the underlying configuration json object operations are concurrent safe. + var ( + j *gjson.Json + ) + 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.violenceCheck) + // Add monitor for this configuration file, + // any changes of this file will refresh its cache in Config object. + if filePath != "" && !gres.Contains(filePath) { + _, err = gfsnotify.Add(filePath, func(event *gfsnotify.Event) { + c.jsonMap.Remove(name) + }) + if err != nil && errorPrint() { + glog.Error(err) + } + } + return j + } + if errorPrint() { + if filePath != "" { + glog.Criticalf(`[gcfg] load config file "%s" failed: %s`, filePath, err.Error()) + } else { + glog.Criticalf(`[gcfg] load configuration failed: %s`, err.Error()) + } + } + return nil + }) + if r != nil { + return r.(*gjson.Json) + } + return nil } diff --git a/os/gcfg/gcfg_api.go b/os/gcfg/gcfg_config_api.go similarity index 55% rename from os/gcfg/gcfg_api.go rename to os/gcfg/gcfg_config_api.go index 4675561e9..f3adb7c9c 100644 --- a/os/gcfg/gcfg_api.go +++ b/os/gcfg/gcfg_config_api.go @@ -1,4 +1,4 @@ -// Copyright 2017 gf Author(https://github.com/gogf/gf). All Rights Reserved. +// Copyright GoFrame Author(https://goframe.org). All Rights Reserved. // // This Source Code Form is subject to the terms of the MIT License. // If a copy of the MIT was not distributed with this file, @@ -7,7 +7,8 @@ package gcfg import ( - "errors" + "github.com/gogf/gf/errors/gcode" + "github.com/gogf/gf/errors/gerror" "time" "github.com/gogf/gf/encoding/gjson" @@ -16,7 +17,7 @@ import ( "github.com/gogf/gf/os/gtime" ) -// Set sets value with specified <pattern>. +// Set sets value with specified `pattern`. // It supports hierarchical data access by char separator, which is '.' in default. // It is commonly used for updates certain configuration value in runtime. func (c *Config) Set(pattern string, value interface{}) error { @@ -26,14 +27,14 @@ func (c *Config) Set(pattern string, value interface{}) error { return nil } -// Get retrieves and returns value by specified <pattern>. -// It returns all values of current Json object if <pattern> is given empty or string ".". -// It returns nil if no value found by <pattern>. +// Get retrieves and returns value by specified `pattern`. +// It returns all values of current Json object if `pattern` is given empty or string ".". +// It returns nil if no value found by `pattern`. // -// We can also access slice item by its index number in <pattern> like: +// We can also access slice item by its index number in `pattern` like: // "list.10", "array.0.name", "array.0.1.id". // -// It returns a default value specified by <def> if value for <pattern> is not found. +// It returns a default value specified by `def` if value for `pattern` is not found. func (c *Config) Get(pattern string, def ...interface{}) interface{} { if j := c.getJson(); j != nil { return j.Get(pattern, def...) @@ -41,7 +42,7 @@ func (c *Config) Get(pattern string, def ...interface{}) interface{} { return nil } -// GetVar returns a gvar.Var with value by given <pattern>. +// GetVar returns a gvar.Var with value by given `pattern`. func (c *Config) GetVar(pattern string, def ...interface{}) *gvar.Var { if j := c.getJson(); j != nil { return j.GetVar(pattern, def...) @@ -49,7 +50,7 @@ func (c *Config) GetVar(pattern string, def ...interface{}) *gvar.Var { return gvar.New(nil) } -// Contains checks whether the value by specified <pattern> exist. +// Contains checks whether the value by specified `pattern` exist. func (c *Config) Contains(pattern string) bool { if j := c.getJson(); j != nil { return j.Contains(pattern) @@ -57,7 +58,7 @@ func (c *Config) Contains(pattern string) bool { return false } -// GetMap retrieves and returns the value by specified <pattern> as map[string]interface{}. +// GetMap retrieves and returns the value by specified `pattern` as map[string]interface{}. func (c *Config) GetMap(pattern string, def ...interface{}) map[string]interface{} { if j := c.getJson(); j != nil { return j.GetMap(pattern, def...) @@ -65,7 +66,7 @@ func (c *Config) GetMap(pattern string, def ...interface{}) map[string]interface return nil } -// GetMapStrStr retrieves and returns the value by specified <pattern> as map[string]string. +// GetMapStrStr retrieves and returns the value by specified `pattern` as map[string]string. func (c *Config) GetMapStrStr(pattern string, def ...interface{}) map[string]string { if j := c.getJson(); j != nil { return j.GetMapStrStr(pattern, def...) @@ -73,7 +74,7 @@ func (c *Config) GetMapStrStr(pattern string, def ...interface{}) map[string]str return nil } -// GetArray retrieves the value by specified <pattern>, +// GetArray retrieves the value by specified `pattern`, // and converts it to a slice of []interface{}. func (c *Config) GetArray(pattern string, def ...interface{}) []interface{} { if j := c.getJson(); j != nil { @@ -82,7 +83,7 @@ func (c *Config) GetArray(pattern string, def ...interface{}) []interface{} { return nil } -// GetBytes retrieves the value by specified <pattern> and converts it to []byte. +// GetBytes retrieves the value by specified `pattern` and converts it to []byte. func (c *Config) GetBytes(pattern string, def ...interface{}) []byte { if j := c.getJson(); j != nil { return j.GetBytes(pattern, def...) @@ -90,7 +91,7 @@ func (c *Config) GetBytes(pattern string, def ...interface{}) []byte { return nil } -// GetString retrieves the value by specified <pattern> and converts it to string. +// GetString retrieves the value by specified `pattern` and converts it to string. func (c *Config) GetString(pattern string, def ...interface{}) string { if j := c.getJson(); j != nil { return j.GetString(pattern, def...) @@ -98,7 +99,7 @@ func (c *Config) GetString(pattern string, def ...interface{}) string { return "" } -// GetStrings retrieves the value by specified <pattern> and converts it to []string. +// GetStrings retrieves the value by specified `pattern` and converts it to []string. func (c *Config) GetStrings(pattern string, def ...interface{}) []string { if j := c.getJson(); j != nil { return j.GetStrings(pattern, def...) @@ -115,7 +116,7 @@ func (c *Config) GetInterfaces(pattern string, def ...interface{}) []interface{} return nil } -// GetBool retrieves the value by specified <pattern>, +// GetBool retrieves the value by specified `pattern`, // converts and returns it as bool. // It returns false when value is: "", 0, false, off, nil; // or returns true instead. @@ -126,7 +127,7 @@ func (c *Config) GetBool(pattern string, def ...interface{}) bool { return false } -// GetFloat32 retrieves the value by specified <pattern> and converts it to float32. +// GetFloat32 retrieves the value by specified `pattern` and converts it to float32. func (c *Config) GetFloat32(pattern string, def ...interface{}) float32 { if j := c.getJson(); j != nil { return j.GetFloat32(pattern, def...) @@ -134,7 +135,7 @@ func (c *Config) GetFloat32(pattern string, def ...interface{}) float32 { return 0 } -// GetFloat64 retrieves the value by specified <pattern> and converts it to float64. +// GetFloat64 retrieves the value by specified `pattern` and converts it to float64. func (c *Config) GetFloat64(pattern string, def ...interface{}) float64 { if j := c.getJson(); j != nil { return j.GetFloat64(pattern, def...) @@ -142,7 +143,7 @@ func (c *Config) GetFloat64(pattern string, def ...interface{}) float64 { return 0 } -// GetFloats retrieves the value by specified <pattern> and converts it to []float64. +// GetFloats retrieves the value by specified `pattern` and converts it to []float64. func (c *Config) GetFloats(pattern string, def ...interface{}) []float64 { if j := c.getJson(); j != nil { return j.GetFloats(pattern, def...) @@ -150,7 +151,7 @@ func (c *Config) GetFloats(pattern string, def ...interface{}) []float64 { return nil } -// GetInt retrieves the value by specified <pattern> and converts it to int. +// GetInt retrieves the value by specified `pattern` and converts it to int. func (c *Config) GetInt(pattern string, def ...interface{}) int { if j := c.getJson(); j != nil { return j.GetInt(pattern, def...) @@ -158,7 +159,7 @@ func (c *Config) GetInt(pattern string, def ...interface{}) int { return 0 } -// GetInt8 retrieves the value by specified <pattern> and converts it to int8. +// GetInt8 retrieves the value by specified `pattern` and converts it to int8. func (c *Config) GetInt8(pattern string, def ...interface{}) int8 { if j := c.getJson(); j != nil { return j.GetInt8(pattern, def...) @@ -166,7 +167,7 @@ func (c *Config) GetInt8(pattern string, def ...interface{}) int8 { return 0 } -// GetInt16 retrieves the value by specified <pattern> and converts it to int16. +// GetInt16 retrieves the value by specified `pattern` and converts it to int16. func (c *Config) GetInt16(pattern string, def ...interface{}) int16 { if j := c.getJson(); j != nil { return j.GetInt16(pattern, def...) @@ -174,7 +175,7 @@ func (c *Config) GetInt16(pattern string, def ...interface{}) int16 { return 0 } -// GetInt32 retrieves the value by specified <pattern> and converts it to int32. +// GetInt32 retrieves the value by specified `pattern` and converts it to int32. func (c *Config) GetInt32(pattern string, def ...interface{}) int32 { if j := c.getJson(); j != nil { return j.GetInt32(pattern, def...) @@ -182,7 +183,7 @@ func (c *Config) GetInt32(pattern string, def ...interface{}) int32 { return 0 } -// GetInt64 retrieves the value by specified <pattern> and converts it to int64. +// GetInt64 retrieves the value by specified `pattern` and converts it to int64. func (c *Config) GetInt64(pattern string, def ...interface{}) int64 { if j := c.getJson(); j != nil { return j.GetInt64(pattern, def...) @@ -190,7 +191,7 @@ func (c *Config) GetInt64(pattern string, def ...interface{}) int64 { return 0 } -// GetInts retrieves the value by specified <pattern> and converts it to []int. +// GetInts retrieves the value by specified `pattern` and converts it to []int. func (c *Config) GetInts(pattern string, def ...interface{}) []int { if j := c.getJson(); j != nil { return j.GetInts(pattern, def...) @@ -198,7 +199,7 @@ func (c *Config) GetInts(pattern string, def ...interface{}) []int { return nil } -// GetUint retrieves the value by specified <pattern> and converts it to uint. +// GetUint retrieves the value by specified `pattern` and converts it to uint. func (c *Config) GetUint(pattern string, def ...interface{}) uint { if j := c.getJson(); j != nil { return j.GetUint(pattern, def...) @@ -206,7 +207,7 @@ func (c *Config) GetUint(pattern string, def ...interface{}) uint { return 0 } -// GetUint8 retrieves the value by specified <pattern> and converts it to uint8. +// GetUint8 retrieves the value by specified `pattern` and converts it to uint8. func (c *Config) GetUint8(pattern string, def ...interface{}) uint8 { if j := c.getJson(); j != nil { return j.GetUint8(pattern, def...) @@ -214,7 +215,7 @@ func (c *Config) GetUint8(pattern string, def ...interface{}) uint8 { return 0 } -// GetUint16 retrieves the value by specified <pattern> and converts it to uint16. +// GetUint16 retrieves the value by specified `pattern` and converts it to uint16. func (c *Config) GetUint16(pattern string, def ...interface{}) uint16 { if j := c.getJson(); j != nil { return j.GetUint16(pattern, def...) @@ -222,7 +223,7 @@ func (c *Config) GetUint16(pattern string, def ...interface{}) uint16 { return 0 } -// GetUint32 retrieves the value by specified <pattern> and converts it to uint32. +// GetUint32 retrieves the value by specified `pattern` and converts it to uint32. func (c *Config) GetUint32(pattern string, def ...interface{}) uint32 { if j := c.getJson(); j != nil { return j.GetUint32(pattern, def...) @@ -230,7 +231,7 @@ func (c *Config) GetUint32(pattern string, def ...interface{}) uint32 { return 0 } -// GetUint64 retrieves the value by specified <pattern> and converts it to uint64. +// GetUint64 retrieves the value by specified `pattern` and converts it to uint64. func (c *Config) GetUint64(pattern string, def ...interface{}) uint64 { if j := c.getJson(); j != nil { return j.GetUint64(pattern, def...) @@ -238,7 +239,7 @@ func (c *Config) GetUint64(pattern string, def ...interface{}) uint64 { return 0 } -// GetTime retrieves the value by specified <pattern> and converts it to time.Time. +// GetTime retrieves the value by specified `pattern` and converts it to time.Time. func (c *Config) GetTime(pattern string, format ...string) time.Time { if j := c.getJson(); j != nil { return j.GetTime(pattern, format...) @@ -246,7 +247,7 @@ func (c *Config) GetTime(pattern string, format ...string) time.Time { return time.Time{} } -// GetDuration retrieves the value by specified <pattern> and converts it to time.Duration. +// GetDuration retrieves the value by specified `pattern` and converts it to time.Duration. func (c *Config) GetDuration(pattern string, def ...interface{}) time.Duration { if j := c.getJson(); j != nil { return j.GetDuration(pattern, def...) @@ -254,7 +255,7 @@ func (c *Config) GetDuration(pattern string, def ...interface{}) time.Duration { return 0 } -// GetGTime retrieves the value by specified <pattern> and converts it to *gtime.Time. +// GetGTime retrieves the value by specified `pattern` and converts it to *gtime.Time. func (c *Config) GetGTime(pattern string, format ...string) *gtime.Time { if j := c.getJson(); j != nil { return j.GetGTime(pattern, format...) @@ -262,7 +263,7 @@ func (c *Config) GetGTime(pattern string, format ...string) *gtime.Time { return nil } -// GetJson gets the value by specified <pattern>, +// GetJson gets the value by specified `pattern`, // and converts it to a un-concurrent-safe Json object. func (c *Config) GetJson(pattern string, def ...interface{}) *gjson.Json { if j := c.getJson(); j != nil { @@ -271,7 +272,7 @@ func (c *Config) GetJson(pattern string, def ...interface{}) *gjson.Json { return nil } -// GetJsons gets the value by specified <pattern>, +// GetJsons gets the value by specified `pattern`, // and converts it to a slice of un-concurrent-safe Json object. func (c *Config) GetJsons(pattern string, def ...interface{}) []*gjson.Json { if j := c.getJson(); j != nil { @@ -280,7 +281,7 @@ func (c *Config) GetJsons(pattern string, def ...interface{}) []*gjson.Json { return nil } -// GetJsonMap gets the value by specified <pattern>, +// GetJsonMap gets the value by specified `pattern`, // and converts it to a map of un-concurrent-safe Json object. func (c *Config) GetJsonMap(pattern string, def ...interface{}) map[string]*gjson.Json { if j := c.getJson(); j != nil { @@ -289,22 +290,13 @@ func (c *Config) GetJsonMap(pattern string, def ...interface{}) map[string]*gjso return nil } -// GetStruct retrieves the value by specified <pattern> and converts it to specified object -// <pointer>. The <pointer> should be the pointer to an object. +// GetStruct retrieves the value by specified `pattern` and converts it to specified object +// `pointer`. The `pointer` should be the pointer to an object. func (c *Config) GetStruct(pattern string, pointer interface{}, mapping ...map[string]string) error { if j := c.getJson(); j != nil { return j.GetStruct(pattern, pointer, mapping...) } - return errors.New("configuration not found") -} - -// GetStructDeep does GetStruct recursively. -// Deprecated, use GetStruct instead. -func (c *Config) GetStructDeep(pattern string, pointer interface{}, mapping ...map[string]string) error { - if j := c.getJson(); j != nil { - return j.GetStructDeep(pattern, pointer, mapping...) - } - return errors.New("configuration not found") + return gerror.NewCode(gcode.CodeMissingConfiguration, "configuration not found") } // GetStructs converts any slice to given struct slice. @@ -312,151 +304,95 @@ func (c *Config) GetStructs(pattern string, pointer interface{}, mapping ...map[ if j := c.getJson(); j != nil { return j.GetStructs(pattern, pointer, mapping...) } - return errors.New("configuration not found") + return gerror.NewCode(gcode.CodeMissingConfiguration, "configuration not found") } -// GetStructsDeep converts any slice to given struct slice recursively. -// Deprecated, use GetStructs instead. -func (c *Config) GetStructsDeep(pattern string, pointer interface{}, mapping ...map[string]string) error { - if j := c.getJson(); j != nil { - return j.GetStructsDeep(pattern, pointer, mapping...) - } - return errors.New("configuration not found") -} - -// GetMapToMap retrieves the value by specified <pattern> and converts it to specified map variable. +// GetMapToMap retrieves the value by specified `pattern` and converts it to specified map variable. // See gconv.MapToMap. func (c *Config) GetMapToMap(pattern string, pointer interface{}, mapping ...map[string]string) error { if j := c.getJson(); j != nil { return j.GetMapToMap(pattern, pointer, mapping...) } - return errors.New("configuration not found") + return gerror.NewCode(gcode.CodeMissingConfiguration, "configuration not found") } -// GetMapToMapDeep retrieves the value by specified <pattern> and converts it to specified map -// variable recursively. -// See gconv.MapToMapDeep. -func (c *Config) GetMapToMapDeep(pattern string, pointer interface{}, mapping ...map[string]string) error { - if j := c.getJson(); j != nil { - return j.GetMapToMapDeep(pattern, pointer, mapping...) - } - return errors.New("configuration not found") -} - -// GetMapToMaps retrieves the value by specified <pattern> and converts it to specified map slice +// GetMapToMaps retrieves the value by specified `pattern` and converts it to specified map slice // variable. // See gconv.MapToMaps. func (c *Config) GetMapToMaps(pattern string, pointer interface{}, mapping ...map[string]string) error { if j := c.getJson(); j != nil { return j.GetMapToMaps(pattern, pointer, mapping...) } - return errors.New("configuration not found") + return gerror.NewCode(gcode.CodeMissingConfiguration, "configuration not found") } -// GetMapToMapsDeep retrieves the value by specified <pattern> and converts it to specified map slice +// GetMapToMapsDeep retrieves the value by specified `pattern` and converts it to specified map slice // variable recursively. // See gconv.MapToMapsDeep. func (c *Config) GetMapToMapsDeep(pattern string, pointer interface{}, mapping ...map[string]string) error { if j := c.getJson(); j != nil { return j.GetMapToMapsDeep(pattern, pointer, mapping...) } - return errors.New("configuration not found") + return gerror.NewCode(gcode.CodeMissingConfiguration, "configuration not found") } -// ToMap converts current Json object to map[string]interface{}. -// It returns nil if fails. -func (c *Config) ToMap() map[string]interface{} { +// Map converts current Json object to map[string]interface{}. It returns nil if fails. +func (c *Config) Map() map[string]interface{} { if j := c.getJson(); j != nil { - return j.ToMap() + return j.Map() } return nil } -// ToArray converts current Json object to []interface{}. +// Array converts current Json object to []interface{}. // It returns nil if fails. -func (c *Config) ToArray() []interface{} { +func (c *Config) Array() []interface{} { if j := c.getJson(); j != nil { - return j.ToArray() + return j.Array() } return nil } -// ToStruct converts current Json object to specified object. -// The <pointer> should be a pointer type of *struct. -func (c *Config) ToStruct(pointer interface{}, mapping ...map[string]string) error { +// Struct converts current Json object to specified object. +// The `pointer` should be a pointer type of *struct. +func (c *Config) Struct(pointer interface{}, mapping ...map[string]string) error { if j := c.getJson(); j != nil { - return j.ToStruct(pointer, mapping...) + return j.Struct(pointer, mapping...) } - return errors.New("configuration not found") + return gerror.NewCode(gcode.CodeMissingConfiguration, "configuration not found") } -// ToStructDeep converts current Json object to specified object recursively. -// The <pointer> should be a pointer type of *struct. -func (c *Config) ToStructDeep(pointer interface{}, mapping ...map[string]string) error { +// Structs converts current Json object to specified object slice. +// The `pointer` should be a pointer type of []struct/*struct. +func (c *Config) Structs(pointer interface{}, mapping ...map[string]string) error { if j := c.getJson(); j != nil { - return j.ToStructDeep(pointer, mapping...) + return j.Structs(pointer, mapping...) } - return errors.New("configuration not found") + return gerror.NewCode(gcode.CodeMissingConfiguration, "configuration not found") } -// ToStructs converts current Json object to specified object slice. -// The <pointer> should be a pointer type of []struct/*struct. -func (c *Config) ToStructs(pointer interface{}, mapping ...map[string]string) error { +// MapToMap converts current Json object to specified map variable. +// The parameter of `pointer` should be type of *map. +func (c *Config) MapToMap(pointer interface{}, mapping ...map[string]string) error { if j := c.getJson(); j != nil { - return j.ToStructs(pointer, mapping...) + return j.MapToMap(pointer, mapping...) } - return errors.New("configuration not found") + return gerror.NewCode(gcode.CodeMissingConfiguration, "configuration not found") } -// ToStructsDeep converts current Json object to specified object slice recursively. -// The <pointer> should be a pointer type of []struct/*struct. -func (c *Config) ToStructsDeep(pointer interface{}, mapping ...map[string]string) error { +// MapToMaps converts current Json object to specified map variable slice. +// The parameter of `pointer` should be type of []map/*map. +func (c *Config) MapToMaps(pointer interface{}, mapping ...map[string]string) error { if j := c.getJson(); j != nil { - return j.ToStructsDeep(pointer, mapping...) + return j.MapToMaps(pointer, mapping...) } - return errors.New("configuration not found") -} - -// ToMapToMap converts current Json object to specified map variable. -// The parameter of <pointer> should be type of *map. -func (c *Config) ToMapToMap(pointer interface{}, mapping ...map[string]string) error { - if j := c.getJson(); j != nil { - return j.ToMapToMap(pointer, mapping...) - } - return errors.New("configuration not found") -} - -// ToMapToMapDeep converts current Json object to specified map variable recursively. -// The parameter of <pointer> should be type of *map. -func (c *Config) ToMapToMapDeep(pointer interface{}, mapping ...map[string]string) error { - if j := c.getJson(); j != nil { - return j.ToMapToMapDeep(pointer, mapping...) - } - return errors.New("configuration not found") -} - -// ToMapToMaps converts current Json object to specified map variable slice. -// The parameter of <pointer> should be type of []map/*map. -func (c *Config) ToMapToMaps(pointer interface{}, mapping ...map[string]string) error { - if j := c.getJson(); j != nil { - return j.ToMapToMaps(pointer, mapping...) - } - return errors.New("configuration not found") -} - -// ToMapToMapsDeep converts current Json object to specified map variable slice recursively. -// The parameter of <pointer> should be type of []map/*map. -func (c *Config) ToMapToMapsDeep(pointer interface{}, mapping ...map[string]string) error { - if j := c.getJson(); j != nil { - return j.ToMapToMapsDeep(pointer, mapping...) - } - return errors.New("configuration not found") + return gerror.NewCode(gcode.CodeMissingConfiguration, "configuration not found") } // Clear removes all parsed configuration files content cache, // which will force reload configuration content from file. func (c *Config) Clear() { - c.jsons.Clear() + c.jsonMap.Clear() } // Dump prints current Json object with more manually readable. diff --git a/os/gcfg/gcfg_error.go b/os/gcfg/gcfg_error.go deleted file mode 100644 index f3ad32921..000000000 --- a/os/gcfg/gcfg_error.go +++ /dev/null @@ -1,22 +0,0 @@ -// 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 gcfg - -import ( - "github.com/gogf/gf/os/gcmd" -) - -const ( - // gERROR_PRINT_KEY is used to specify the key controlling error printing to stdout. - // This error is designed not to be returned by functions. - gERROR_PRINT_KEY = "gf.gcfg.errorprint" -) - -// errorPrint checks whether printing error to stdout. -func errorPrint() bool { - return gcmd.GetWithEnv(gERROR_PRINT_KEY, true).Bool() -} diff --git a/os/gcfg/gcfg_instance.go b/os/gcfg/gcfg_instance.go deleted file mode 100644 index 4f3669374..000000000 --- a/os/gcfg/gcfg_instance.go +++ /dev/null @@ -1,43 +0,0 @@ -// Copyright GoFrame 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 gcfg - -import ( - "fmt" - "github.com/gogf/gf/container/gmap" -) - -const ( - // Default group name for instance usage. - DefaultName = "default" -) - -var ( - // Instances map containing configuration instances. - instances = gmap.NewStrAnyMap(true) -) - -// Instance returns an instance of Config with default settings. -// The parameter <name> is the name for the instance. But very note that, if the file "name.toml" -// exists in the configuration directory, it then sets it as the default configuration file. The -// toml file type is the default configuration file type. -func Instance(name ...string) *Config { - key := DefaultName - if len(name) > 0 && name[0] != "" { - key = name[0] - } - return instances.GetOrSetFuncLock(key, func() interface{} { - c := New() - for _, fileType := range supportedFileTypes { - if file := fmt.Sprintf(`%s.%s`, key, fileType); c.Available(file) { - c.SetFileName(file) - break - } - } - return c - }).(*Config) -} diff --git a/os/gcfg/gcfg_z_example_pattern_test.go b/os/gcfg/gcfg_z_example_pattern_test.go index 9616cd85c..a5ba919d5 100644 --- a/os/gcfg/gcfg_z_example_pattern_test.go +++ b/os/gcfg/gcfg_z_example_pattern_test.go @@ -1,4 +1,4 @@ -// Copyright 2020 gf Author(https://github.com/gogf/gf). All Rights Reserved. +// Copyright GoFrame Author(https://goframe.org). All Rights Reserved. // // This Source Code Form is subject to the terms of the MIT License. // If a copy of the MIT was not distributed with this file, diff --git a/os/gcfg/gcfg_z_unit_test.go b/os/gcfg/gcfg_z_unit_basic_test.go similarity index 79% rename from os/gcfg/gcfg_z_unit_test.go rename to os/gcfg/gcfg_z_unit_basic_test.go index 3fd04683a..51fd92638 100644 --- a/os/gcfg/gcfg_z_unit_test.go +++ b/os/gcfg/gcfg_z_unit_basic_test.go @@ -1,4 +1,4 @@ -// Copyright GoFrame Author(https://github.com/gogf/gf). All Rights Reserved. +// Copyright GoFrame Author(https://goframe.org). All Rights Reserved. // // This Source Code Form is subject to the terms of the MIT License. // If a copy of the MIT was not distributed with this file, @@ -12,7 +12,6 @@ import ( "os" "testing" - "github.com/gogf/gf/debug/gdebug" "github.com/gogf/gf/os/gtime" "github.com/gogf/gf/encoding/gjson" @@ -82,7 +81,8 @@ array = [1,2,3] "disk": "127.0.0.1:6379,0", "cache": "127.0.0.1:6379,1", }) - t.AssertEQ(c.FilePath(), gfile.Pwd()+gfile.Separator+path) + filepath, _ := c.GetFilePath() + t.AssertEQ(filepath, gfile.Pwd()+gfile.Separator+path) }) } @@ -224,73 +224,8 @@ func Test_SetFileName(t *testing.T) { "disk": "127.0.0.1:6379,0", "cache": "127.0.0.1:6379,1", }) - t.AssertEQ(c.FilePath(), gfile.Pwd()+gfile.Separator+path) - - }) -} - -func Test_Instance(t *testing.T) { - config := ` -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.DefaultConfigFile - err := gfile.PutContents(path, config) - t.Assert(err, nil) - defer func() { - t.Assert(gfile.Remove(path), nil) - }() - - c := gcfg.Instance() - t.Assert(c.Get("v1"), 1) - t.AssertEQ(c.GetInt("v1"), 1) - t.AssertEQ(c.GetInt8("v1"), int8(1)) - t.AssertEQ(c.GetInt16("v1"), int16(1)) - t.AssertEQ(c.GetInt32("v1"), int32(1)) - t.AssertEQ(c.GetInt64("v1"), int64(1)) - t.AssertEQ(c.GetUint("v1"), uint(1)) - t.AssertEQ(c.GetUint8("v1"), uint8(1)) - t.AssertEQ(c.GetUint16("v1"), uint16(1)) - t.AssertEQ(c.GetUint32("v1"), uint32(1)) - t.AssertEQ(c.GetUint64("v1"), uint64(1)) - - t.AssertEQ(c.GetVar("v1").String(), "1") - t.AssertEQ(c.GetVar("v1").Bool(), true) - t.AssertEQ(c.GetVar("v2").String(), "true") - t.AssertEQ(c.GetVar("v2").Bool(), true) - - t.AssertEQ(c.GetString("v1"), "1") - t.AssertEQ(c.GetFloat32("v4"), float32(1.234)) - t.AssertEQ(c.GetFloat64("v4"), float64(1.234)) - t.AssertEQ(c.GetString("v2"), "true") - t.AssertEQ(c.GetBool("v2"), true) - t.AssertEQ(c.GetBool("v3"), false) - - t.AssertEQ(c.Contains("v1"), true) - t.AssertEQ(c.Contains("v2"), true) - t.AssertEQ(c.Contains("v3"), true) - t.AssertEQ(c.Contains("v4"), true) - t.AssertEQ(c.Contains("v5"), false) - - t.AssertEQ(c.GetInts("array"), []int{1, 2, 3}) - t.AssertEQ(c.GetStrings("array"), []string{"1", "2", "3"}) - t.AssertEQ(c.GetArray("array"), []interface{}{1, 2, 3}) - t.AssertEQ(c.GetInterfaces("array"), []interface{}{1, 2, 3}) - t.AssertEQ(c.GetMap("redis"), map[string]interface{}{ - "disk": "127.0.0.1:6379,0", - "cache": "127.0.0.1:6379,1", - }) - t.AssertEQ(c.FilePath(), gfile.Pwd()+gfile.Separator+path) - + filepath, _ := c.GetFilePath() + t.AssertEQ(filepath, gfile.Pwd()+gfile.Separator+path) }) } @@ -347,9 +282,9 @@ func TestCfg_AddPath(t *testing.T) { func TestCfg_FilePath(t *testing.T) { gtest.C(t, func(t *gtest.T) { c := gcfg.New("config.yml") - path := c.FilePath("tmp") + path, _ := c.GetFilePath("tmp") t.Assert(path, "") - path = c.FilePath("tmp") + path, _ = c.GetFilePath("tmp") t.Assert(path, "") }) } @@ -446,26 +381,6 @@ func TestCfg_Get(t *testing.T) { }) } -func TestCfg_Instance(t *testing.T) { - gtest.C(t, func(t *gtest.T) { - t.Assert(gcfg.Instance("gf") != nil, true) - }) - gtest.C(t, func(t *gtest.T) { - pwd := gfile.Pwd() - gfile.Chdir(gdebug.TestDataPath()) - defer gfile.Chdir(pwd) - t.Assert(gcfg.Instance("c1") != nil, true) - t.Assert(gcfg.Instance("c1").Get("my-config"), "1") - t.Assert(gcfg.Instance("folder1/c1").Get("my-config"), "2") - }) - gtest.C(t, func(t *gtest.T) { - pwd := gfile.Pwd() - gfile.Chdir(gdebug.TestDataPath("folder1")) - defer gfile.Chdir(pwd) - t.Assert(gcfg.Instance("c2").Get("my-config"), 2) - }) -} - func TestCfg_Config(t *testing.T) { gtest.C(t, func(t *gtest.T) { gcfg.SetContent("gf", "config.yml") diff --git a/os/gcfg/gcfg_z_unit_instance_test.go b/os/gcfg/gcfg_z_unit_instance_test.go new file mode 100644 index 000000000..879a5461d --- /dev/null +++ b/os/gcfg/gcfg_z_unit_instance_test.go @@ -0,0 +1,140 @@ +// Copyright GoFrame Author(https://goframe.org). All Rights Reserved. +// +// This Source Code Form is subject to the terms of the MIT License. +// If a copy of the MIT was not distributed with this file, +// You can obtain one at https://github.com/gogf/gf. + +// go test *.go -bench=".*" -benchmem + +package gcfg + +import ( + "github.com/gogf/gf/container/gmap" + "github.com/gogf/gf/debug/gdebug" + "github.com/gogf/gf/os/genv" + "github.com/gogf/gf/os/gfile" + "github.com/gogf/gf/test/gtest" + "testing" +) + +func Test_Instance_Basic(t *testing.T) { + config := ` +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 := DefaultConfigFile + err := gfile.PutContents(path, config) + t.Assert(err, nil) + defer func() { + t.Assert(gfile.Remove(path), nil) + }() + + c := Instance() + t.Assert(c.Get("v1"), 1) + t.AssertEQ(c.GetInt("v1"), 1) + t.AssertEQ(c.GetInt8("v1"), int8(1)) + t.AssertEQ(c.GetInt16("v1"), int16(1)) + t.AssertEQ(c.GetInt32("v1"), int32(1)) + t.AssertEQ(c.GetInt64("v1"), int64(1)) + t.AssertEQ(c.GetUint("v1"), uint(1)) + t.AssertEQ(c.GetUint8("v1"), uint8(1)) + t.AssertEQ(c.GetUint16("v1"), uint16(1)) + t.AssertEQ(c.GetUint32("v1"), uint32(1)) + t.AssertEQ(c.GetUint64("v1"), uint64(1)) + + t.AssertEQ(c.GetVar("v1").String(), "1") + t.AssertEQ(c.GetVar("v1").Bool(), true) + t.AssertEQ(c.GetVar("v2").String(), "true") + t.AssertEQ(c.GetVar("v2").Bool(), true) + + t.AssertEQ(c.GetString("v1"), "1") + t.AssertEQ(c.GetFloat32("v4"), float32(1.234)) + t.AssertEQ(c.GetFloat64("v4"), float64(1.234)) + t.AssertEQ(c.GetString("v2"), "true") + t.AssertEQ(c.GetBool("v2"), true) + t.AssertEQ(c.GetBool("v3"), false) + + t.AssertEQ(c.Contains("v1"), true) + t.AssertEQ(c.Contains("v2"), true) + t.AssertEQ(c.Contains("v3"), true) + t.AssertEQ(c.Contains("v4"), true) + t.AssertEQ(c.Contains("v5"), false) + + t.AssertEQ(c.GetInts("array"), []int{1, 2, 3}) + t.AssertEQ(c.GetStrings("array"), []string{"1", "2", "3"}) + t.AssertEQ(c.GetArray("array"), []interface{}{1, 2, 3}) + t.AssertEQ(c.GetInterfaces("array"), []interface{}{1, 2, 3}) + t.AssertEQ(c.GetMap("redis"), map[string]interface{}{ + "disk": "127.0.0.1:6379,0", + "cache": "127.0.0.1:6379,1", + }) + filepath, _ := c.GetFilePath() + t.AssertEQ(filepath, gfile.Pwd()+gfile.Separator+path) + }) +} + +func Test_Instance_AutoLocateConfigFile(t *testing.T) { + gtest.C(t, func(t *gtest.T) { + t.Assert(Instance("gf") != nil, true) + }) + // Automatically locate the configuration file with supported file extensions. + gtest.C(t, func(t *gtest.T) { + pwd := gfile.Pwd() + t.AssertNil(gfile.Chdir(gdebug.TestDataPath())) + defer gfile.Chdir(pwd) + t.Assert(Instance("c1") != nil, true) + t.Assert(Instance("c1").Get("my-config"), "1") + t.Assert(Instance("folder1/c1").Get("my-config"), "2") + }) + // Automatically locate the configuration file with supported file extensions. + gtest.C(t, func(t *gtest.T) { + pwd := gfile.Pwd() + t.AssertNil(gfile.Chdir(gdebug.TestDataPath("folder1"))) + defer gfile.Chdir(pwd) + t.Assert(Instance("c2").Get("my-config"), 2) + }) + // Default configuration file. + gtest.C(t, func(t *gtest.T) { + instances.Clear() + pwd := gfile.Pwd() + t.AssertNil(gfile.Chdir(gdebug.TestDataPath("default"))) + defer gfile.Chdir(pwd) + t.Assert(Instance().Get("my-config"), 1) + + instances.Clear() + t.AssertNil(genv.Set("GF_GCFG_FILE", "config.json")) + defer genv.Set("GF_GCFG_FILE", "") + t.Assert(Instance().Get("my-config"), 2) + }) +} + +func Test_Instance_EnvPath(t *testing.T) { + gtest.C(t, func(t *gtest.T) { + genv.Set("GF_GCFG_PATH", gdebug.TestDataPath("envpath")) + defer genv.Set("GF_GCFG_PATH", "") + t.Assert(Instance("c3") != nil, true) + t.Assert(Instance("c3").Get("my-config"), "3") + t.Assert(Instance("c4").Get("my-config"), "4") + instances = gmap.NewStrAnyMap(true) + }) +} + +func Test_Instance_EnvFile(t *testing.T) { + gtest.C(t, func(t *gtest.T) { + genv.Set("GF_GCFG_PATH", gdebug.TestDataPath("envfile")) + defer genv.Set("GF_GCFG_PATH", "") + genv.Set("GF_GCFG_FILE", "c6.json") + defer genv.Set("GF_GCFG_FILE", "") + t.Assert(Instance().Get("my-config"), "6") + instances = gmap.NewStrAnyMap(true) + }) +} diff --git a/os/gcfg/testdata/default/config.json b/os/gcfg/testdata/default/config.json new file mode 100644 index 000000000..ea5047901 --- /dev/null +++ b/os/gcfg/testdata/default/config.json @@ -0,0 +1 @@ +{"my-config": 2} \ No newline at end of file diff --git a/os/gcfg/testdata/default/config.toml b/os/gcfg/testdata/default/config.toml new file mode 100644 index 000000000..ec4ea12a8 --- /dev/null +++ b/os/gcfg/testdata/default/config.toml @@ -0,0 +1 @@ +my-config = "1" \ No newline at end of file diff --git a/os/gcfg/testdata/envfile/c6.json b/os/gcfg/testdata/envfile/c6.json new file mode 100644 index 000000000..ac49cc62e --- /dev/null +++ b/os/gcfg/testdata/envfile/c6.json @@ -0,0 +1,2 @@ + +{"my-config": 6} \ No newline at end of file diff --git a/os/gcfg/testdata/envpath/c3.toml b/os/gcfg/testdata/envpath/c3.toml new file mode 100644 index 000000000..13fcebc0f --- /dev/null +++ b/os/gcfg/testdata/envpath/c3.toml @@ -0,0 +1,2 @@ + +my-config = "3" \ No newline at end of file diff --git a/os/gcfg/testdata/envpath/c4.json b/os/gcfg/testdata/envpath/c4.json new file mode 100644 index 000000000..9f8a930de --- /dev/null +++ b/os/gcfg/testdata/envpath/c4.json @@ -0,0 +1,2 @@ + +{"my-config": 4} \ No newline at end of file diff --git a/os/gcmd/gcmd.go b/os/gcmd/gcmd.go index a587b1aa7..d32ca03d6 100644 --- a/os/gcmd/gcmd.go +++ b/os/gcmd/gcmd.go @@ -1,4 +1,4 @@ -// Copyright 2019 gf Author(https://github.com/gogf/gf). All Rights Reserved. +// Copyright GoFrame Author(https://goframe.org). All Rights Reserved. // // This Source Code Form is subject to the terms of the MIT License. // If a copy of the MIT was not distributed with this file, @@ -9,69 +9,25 @@ package gcmd import ( + "github.com/gogf/gf/container/gvar" + "github.com/gogf/gf/internal/command" "os" "strings" - - "github.com/gogf/gf/container/gvar" - - "github.com/gogf/gf/text/gregex" ) var ( - defaultParsedArgs = make([]string, 0) - defaultParsedOptions = make(map[string]string) defaultCommandFuncMap = make(map[string]func()) ) // Custom initialization. func Init(args ...string) { - if len(args) == 0 { - if len(defaultParsedArgs) == 0 && len(defaultParsedOptions) == 0 { - args = os.Args - } else { - return - } - } else { - defaultParsedArgs = make([]string, 0) - defaultParsedOptions = make(map[string]string) - } - // Parsing os.Args with default algorithm. - for i := 0; i < len(args); { - array, _ := gregex.MatchString(`^\-{1,2}([\w\?\.\-]+)(=){0,1}(.*)$`, args[i]) - if len(array) > 2 { - if array[2] == "=" { - defaultParsedOptions[array[1]] = array[3] - } else if i < len(args)-1 { - if len(args[i+1]) > 0 && args[i+1][0] == '-' { - // Eg: gf gen -d -n 1 - defaultParsedOptions[array[1]] = array[3] - } else { - // Eg: gf gen -n 2 - defaultParsedOptions[array[1]] = args[i+1] - i += 2 - continue - } - } else { - // Eg: gf gen -h - defaultParsedOptions[array[1]] = array[3] - } - } else { - defaultParsedArgs = append(defaultParsedArgs, args[i]) - } - i++ - } + command.Init(args...) } // GetOpt returns the option value named <name>. func GetOpt(name string, def ...string) string { Init() - if v, ok := defaultParsedOptions[name]; ok { - return v - } - if len(def) > 0 { - return def[0] - } - return "" + return command.GetOpt(name, def...) } // GetOptVar returns the option value named <name> as gvar.Var. @@ -83,26 +39,19 @@ func GetOptVar(name string, def ...string) *gvar.Var { // GetOptAll returns all parsed options. func GetOptAll() map[string]string { Init() - return defaultParsedOptions + return command.GetOptAll() } // ContainsOpt checks whether option named <name> exist in the arguments. -func ContainsOpt(name string, def ...string) bool { +func ContainsOpt(name string) bool { Init() - _, ok := defaultParsedOptions[name] - return ok + return command.ContainsOpt(name) } // GetArg returns the argument at <index>. func GetArg(index int, def ...string) string { Init() - if index < len(defaultParsedArgs) { - return defaultParsedArgs[index] - } - if len(def) > 0 { - return def[0] - } - return "" + return command.GetArg(index, def...) } // GetArgVar returns the argument at <index> as gvar.Var. @@ -114,31 +63,37 @@ func GetArgVar(index int, def ...string) *gvar.Var { // GetArgAll returns all parsed arguments. func GetArgAll() []string { Init() - return defaultParsedArgs + return command.GetArgAll() } -// GetWithEnv returns the command line argument of the specified <key>. +// GetWithEnv is alias of GetOptWithEnv. +// Deprecated, use GetOptWithEnv instead. +func GetWithEnv(key string, def ...interface{}) *gvar.Var { + return GetOptWithEnv(key, def...) +} + +// GetOptWithEnv returns the command line argument of the specified <key>. // If the argument does not exist, then it returns the environment variable with specified <key>. // It returns the default value <def> if none of them exists. // // Fetching Rules: // 1. Command line arguments are in lowercase format, eg: gf.<package name>.<variable name>; // 2. Environment arguments are in uppercase format, eg: GF_<package name>_<variable name>; -func GetWithEnv(key string, def ...interface{}) *gvar.Var { - value := interface{}(nil) - if len(def) > 0 { - value = def[0] - } +func GetOptWithEnv(key string, def ...interface{}) *gvar.Var { cmdKey := strings.ToLower(strings.Replace(key, "_", ".", -1)) - if v := GetOpt(cmdKey); v != "" { - value = v + if ContainsOpt(cmdKey) { + return gvar.New(GetOpt(cmdKey)) } else { envKey := strings.ToUpper(strings.Replace(key, ".", "_", -1)) - if v := os.Getenv(envKey); v != "" { - value = v + if r, ok := os.LookupEnv(envKey); ok { + return gvar.New(r) + } else { + if len(def) > 0 { + return gvar.New(def[0]) + } } } - return gvar.New(value) + return gvar.New(nil) } // BuildOptions builds the options as string. diff --git a/os/gcmd/gcmd_handler.go b/os/gcmd/gcmd_handler.go index 1fa5096d2..9853a2019 100644 --- a/os/gcmd/gcmd_handler.go +++ b/os/gcmd/gcmd_handler.go @@ -1,4 +1,4 @@ -// Copyright 2019 gf Author(https://github.com/gogf/gf). All Rights Reserved. +// Copyright GoFrame Author(https://goframe.org). All Rights Reserved. // // This Source Code Form is subject to the terms of the MIT License. // If a copy of the MIT was not distributed with this file, @@ -8,20 +8,21 @@ package gcmd import ( - "errors" + "github.com/gogf/gf/errors/gcode" + "github.com/gogf/gf/errors/gerror" ) // BindHandle registers callback function <f> with <cmd>. func BindHandle(cmd string, f func()) error { if _, ok := defaultCommandFuncMap[cmd]; ok { - return errors.New("duplicated handle for command:" + cmd) + return gerror.NewCode(gcode.CodeInvalidOperation, "duplicated handle for command:"+cmd) } else { defaultCommandFuncMap[cmd] = f } return nil } -// BindHandle registers callback function with map <m>. +// BindHandleMap registers callback function with map <m>. func BindHandleMap(m map[string]func()) error { var err error for k, v := range m { @@ -37,7 +38,7 @@ func RunHandle(cmd string) error { if handle, ok := defaultCommandFuncMap[cmd]; ok { handle() } else { - return errors.New("no handle found for command:" + cmd) + return gerror.NewCode(gcode.CodeMissingConfiguration, "no handle found for command:"+cmd) } return nil } @@ -49,10 +50,10 @@ func AutoRun() error { if handle, ok := defaultCommandFuncMap[cmd]; ok { handle() } else { - return errors.New("no handle found for command:" + cmd) + return gerror.NewCode(gcode.CodeMissingConfiguration, "no handle found for command:"+cmd) } } else { - return errors.New("no command found") + return gerror.NewCode(gcode.CodeMissingParameter, "no command found") } return nil } diff --git a/os/gcmd/gcmd_parser.go b/os/gcmd/gcmd_parser.go index 8bc235d53..a53868746 100644 --- a/os/gcmd/gcmd_parser.go +++ b/os/gcmd/gcmd_parser.go @@ -1,4 +1,4 @@ -// Copyright 2019 gf Author(https://github.com/gogf/gf). All Rights Reserved. +// Copyright GoFrame Author(https://goframe.org). All Rights Reserved. // // This Source Code Form is subject to the terms of the MIT License. // If a copy of the MIT was not distributed with this file, @@ -8,15 +8,14 @@ package gcmd import ( - "fmt" + "github.com/gogf/gf/errors/gcode" + "github.com/gogf/gf/errors/gerror" "github.com/gogf/gf/internal/json" "os" "strings" "github.com/gogf/gf/text/gstr" - "errors" - "github.com/gogf/gf/container/gvar" "github.com/gogf/gf/text/gregex" @@ -96,7 +95,7 @@ func ParseWithArgs(args []string, supportedOptions map[string]bool, strict ...bo i++ continue } else if parser.strict { - return nil, errors.New(fmt.Sprintf(`invalid option '%s'`, args[i])) + return nil, gerror.NewCodef(gcode.CodeInvalidParameter, `invalid option '%s'`, args[i]) } } } diff --git a/os/gcmd/gcmd_parser_handler.go b/os/gcmd/gcmd_parser_handler.go index 21acc11af..c30ecbdad 100644 --- a/os/gcmd/gcmd_parser_handler.go +++ b/os/gcmd/gcmd_parser_handler.go @@ -1,4 +1,4 @@ -// Copyright 2019 gf Author(https://github.com/gogf/gf). All Rights Reserved. +// Copyright GoFrame Author(https://goframe.org). All Rights Reserved. // // This Source Code Form is subject to the terms of the MIT License. // If a copy of the MIT was not distributed with this file, @@ -8,20 +8,21 @@ package gcmd import ( - "errors" + "github.com/gogf/gf/errors/gcode" + "github.com/gogf/gf/errors/gerror" ) // BindHandle registers callback function <f> with <cmd>. func (p *Parser) BindHandle(cmd string, f func()) error { if _, ok := p.commandFuncMap[cmd]; ok { - return errors.New("duplicated handle for command:" + cmd) + return gerror.NewCode(gcode.CodeInvalidOperation, "duplicated handle for command:"+cmd) } else { p.commandFuncMap[cmd] = f } return nil } -// BindHandle registers callback function with map <m>. +// BindHandleMap registers callback function with map <m>. func (p *Parser) BindHandleMap(m map[string]func()) error { var err error for k, v := range m { @@ -37,7 +38,7 @@ func (p *Parser) RunHandle(cmd string) error { if handle, ok := p.commandFuncMap[cmd]; ok { handle() } else { - return errors.New("no handle found for command:" + cmd) + return gerror.NewCode(gcode.CodeMissingConfiguration, "no handle found for command:"+cmd) } return nil } @@ -49,10 +50,10 @@ func (p *Parser) AutoRun() error { if handle, ok := p.commandFuncMap[cmd]; ok { handle() } else { - return errors.New("no handle found for command:" + cmd) + return gerror.NewCode(gcode.CodeMissingConfiguration, "no handle found for command:"+cmd) } } else { - return errors.New("no command found") + return gerror.NewCode(gcode.CodeMissingParameter, "no command found") } return nil } diff --git a/os/gcmd/gcmd_scan.go b/os/gcmd/gcmd_scan.go index 492c668f0..8c59c84e2 100644 --- a/os/gcmd/gcmd_scan.go +++ b/os/gcmd/gcmd_scan.go @@ -1,4 +1,4 @@ -// Copyright 2019 gf Author(https://github.com/gogf/gf). All Rights Reserved. +// Copyright GoFrame Author(https://goframe.org). All Rights Reserved. // // This Source Code Form is subject to the terms of the MIT License. // If a copy of the MIT was not distributed with this file, diff --git a/os/gcmd/gcmd_z_unit_default_test.go b/os/gcmd/gcmd_z_unit_default_test.go index df9f4df16..173273c0d 100644 --- a/os/gcmd/gcmd_z_unit_default_test.go +++ b/os/gcmd/gcmd_z_unit_default_test.go @@ -1,4 +1,4 @@ -// Copyright 2019 gf Author(https://github.com/gogf/gf). All Rights Reserved. +// Copyright GoFrame Author(https://goframe.org). All Rights Reserved. // // This Source Code Form is subject to the terms of the MIT License. // If a copy of the MIT was not distributed with this file, @@ -59,12 +59,12 @@ func Test_GetWithEnv(t *testing.T) { gtest.C(t, func(t *gtest.T) { genv.Set("TEST", "1") defer genv.Remove("TEST") - t.Assert(gcmd.GetWithEnv("test"), 1) + t.Assert(gcmd.GetOptWithEnv("test"), 1) }) gtest.C(t, func(t *gtest.T) { genv.Set("TEST", "1") defer genv.Remove("TEST") gcmd.Init("-test", "2") - t.Assert(gcmd.GetWithEnv("test"), 2) + t.Assert(gcmd.GetOptWithEnv("test"), 2) }) } diff --git a/os/gcmd/gcmd_z_unit_parser_test.go b/os/gcmd/gcmd_z_unit_parser_test.go index 4988c64be..0d8a666bc 100644 --- a/os/gcmd/gcmd_z_unit_parser_test.go +++ b/os/gcmd/gcmd_z_unit_parser_test.go @@ -1,4 +1,4 @@ -// Copyright 2019 gf Author(https://github.com/gogf/gf). All Rights Reserved. +// Copyright GoFrame Author(https://goframe.org). All Rights Reserved. // // This Source Code Form is subject to the terms of the MIT License. // If a copy of the MIT was not distributed with this file, diff --git a/os/gcron/gcron.go b/os/gcron/gcron.go index 23ce47823..66c5692aa 100644 --- a/os/gcron/gcron.go +++ b/os/gcron/gcron.go @@ -1,4 +1,4 @@ -// Copyright 2018 gf Author(https://github.com/gogf/gf). All Rights Reserved. +// Copyright GoFrame Author(https://goframe.org). All Rights Reserved. // // This Source Code Form is subject to the terms of the MIT License. // If a copy of the MIT was not distributed with this file, @@ -8,18 +8,17 @@ package gcron import ( - "math" + "github.com/gogf/gf/os/glog" "time" "github.com/gogf/gf/os/gtimer" ) const ( - StatusReady = gtimer.StatusReady - StatusRunning = gtimer.StatusRunning - StatusStopped = gtimer.StatusStopped - StatusClosed = gtimer.StatusClosed - gDEFAULT_TIMES = math.MaxInt32 + StatusReady = gtimer.StatusReady + StatusRunning = gtimer.StatusRunning + StatusStopped = gtimer.StatusStopped + StatusClosed = gtimer.StatusClosed ) var ( @@ -27,84 +26,98 @@ var ( defaultCron = New() ) +// SetLogger sets the logger for cron. +func SetLogger(logger *glog.Logger) { + defaultCron.SetLogger(logger) +} + +// GetLogger returns the logger in the cron. +func GetLogger() *glog.Logger { + return defaultCron.GetLogger() +} + // SetLogPath sets the logging folder path for default cron object. +// Deprecated, use SetLogger instead. func SetLogPath(path string) { defaultCron.SetLogPath(path) } // GetLogPath returns the logging folder path of default cron object. +// Deprecated, use GetLogger instead. func GetLogPath() string { return defaultCron.GetLogPath() } // SetLogLevel sets the logging level for default cron object. +// Deprecated, use SetLogger instead. func SetLogLevel(level int) { defaultCron.SetLogLevel(level) } // GetLogLevel returns the logging level for default cron object. +// Deprecated, use GetLogger instead. func GetLogLevel() int { return defaultCron.GetLogLevel() } // Add adds a timed task to default cron object. -// A unique <name> can be bound with the timed task. -// It returns and error if the <name> is already used. +// A unique `name` can be bound with the timed task. +// It returns and error if the `name` is already used. func Add(pattern string, job func(), name ...string) (*Entry, error) { return defaultCron.Add(pattern, job, name...) } // AddSingleton adds a singleton timed task, to default cron object. // A singleton timed task is that can only be running one single instance at the same time. -// A unique <name> can be bound with the timed task. -// It returns and error if the <name> is already used. +// A unique `name` can be bound with the timed task. +// It returns and error if the `name` is already used. func AddSingleton(pattern string, job func(), name ...string) (*Entry, error) { return defaultCron.AddSingleton(pattern, job, name...) } // AddOnce adds a timed task which can be run only once, to default cron object. -// A unique <name> can be bound with the timed task. -// It returns and error if the <name> is already used. +// A unique `name` can be bound with the timed task. +// It returns and error if the `name` is already used. func AddOnce(pattern string, job func(), name ...string) (*Entry, error) { return defaultCron.AddOnce(pattern, job, name...) } // AddTimes adds a timed task which can be run specified times, to default cron object. -// A unique <name> can be bound with the timed task. -// It returns and error if the <name> is already used. +// A unique `name` can be bound with the timed task. +// It returns and error if the `name` is already used. func AddTimes(pattern string, times int, job func(), name ...string) (*Entry, error) { return defaultCron.AddTimes(pattern, times, job, name...) } -// DelayAdd adds a timed task to default cron object after <delay> time. +// DelayAdd adds a timed task to default cron object after `delay` time. func DelayAdd(delay time.Duration, pattern string, job func(), name ...string) { defaultCron.DelayAdd(delay, pattern, job, name...) } -// DelayAddSingleton adds a singleton timed task after <delay> time to default cron object. +// DelayAddSingleton adds a singleton timed task after `delay` time to default cron object. func DelayAddSingleton(delay time.Duration, pattern string, job func(), name ...string) { defaultCron.DelayAddSingleton(delay, pattern, job, name...) } -// DelayAddOnce adds a timed task after <delay> time to default cron object. +// DelayAddOnce adds a timed task after `delay` time to default cron object. // This timed task can be run only once. func DelayAddOnce(delay time.Duration, pattern string, job func(), name ...string) { defaultCron.DelayAddOnce(delay, pattern, job, name...) } -// DelayAddTimes adds a timed task after <delay> time to default cron object. +// DelayAddTimes adds a timed task after `delay` time to default cron object. // This timed task can be run specified times. func DelayAddTimes(delay time.Duration, pattern string, times int, job func(), name ...string) { defaultCron.DelayAddTimes(delay, pattern, times, job, name...) } -// Search returns a scheduled task with the specified <name>. +// Search returns a scheduled task with the specified `name`. // It returns nil if no found. func Search(name string) *Entry { return defaultCron.Search(name) } -// Remove deletes scheduled task which named <name>. +// Remove deletes scheduled task which named `name`. func Remove(name string) { defaultCron.Remove(name) } @@ -119,12 +132,14 @@ func Entries() []*Entry { return defaultCron.Entries() } -// Start starts running the specified timed task named <name>. -func Start(name string) { - defaultCron.Start(name) +// Start starts running the specified timed task named `name`. +// If no`name` specified, it starts the entire cron. +func Start(name ...string) { + defaultCron.Start(name...) } -// Stop stops running the specified timed task named <name>. -func Stop(name string) { - defaultCron.Stop(name) +// Stop stops running the specified timed task named `name`. +// If no`name` specified, it stops the entire cron. +func Stop(name ...string) { + defaultCron.Stop(name...) } diff --git a/os/gcron/gcron_cron.go b/os/gcron/gcron_cron.go index 0e7ce7337..4b954810d 100644 --- a/os/gcron/gcron_cron.go +++ b/os/gcron/gcron_cron.go @@ -1,4 +1,4 @@ -// Copyright 2018 gf Author(https://github.com/gogf/gf). All Rights Reserved. +// Copyright GoFrame Author(https://goframe.org). All Rights Reserved. // // This Source Code Form is subject to the terms of the MIT License. // If a copy of the MIT was not distributed with this file, @@ -7,8 +7,6 @@ package gcron import ( - "errors" - "fmt" "time" "github.com/gogf/gf/container/garray" @@ -19,11 +17,18 @@ import ( ) type Cron struct { - idGen *gtype.Int64 // Used for unique name generation. - status *gtype.Int // Timed task status(0: Not Start; 1: Running; 2: Stopped; -1: Closed) - entries *gmap.StrAnyMap // All timed task entries. - logPath *gtype.String // Logging path(folder). - logLevel *gtype.Int // Logging level. + idGen *gtype.Int64 // Used for unique name generation. + status *gtype.Int // Timed task status(0: Not Start; 1: Running; 2: Stopped; -1: Closed) + entries *gmap.StrAnyMap // All timed task entries. + logger *glog.Logger // Logger, it is nil in default. + + // Logging path(folder). + // Deprecated, use logger instead. + logPath *gtype.String + + // Logging level. + // Deprecated, use logger instead. + logLevel *gtype.Int } // New returns a new Cron object with default settings. @@ -37,76 +42,101 @@ func New() *Cron { } } +// SetLogger sets the logger for cron. +func (c *Cron) SetLogger(logger *glog.Logger) { + c.logger = logger +} + +// GetLogger returns the logger in the cron. +func (c *Cron) GetLogger() *glog.Logger { + return c.logger +} + // SetLogPath sets the logging folder path. +// Deprecated, use SetLogger instead. func (c *Cron) SetLogPath(path string) { c.logPath.Set(path) } // GetLogPath return the logging folder path. +// Deprecated, use GetLogger instead. func (c *Cron) GetLogPath() string { return c.logPath.Val() } // SetLogLevel sets the logging level. +// Deprecated, use SetLogger instead. func (c *Cron) SetLogLevel(level int) { c.logLevel.Set(level) } // GetLogLevel returns the logging level. +// Deprecated, use GetLogger instead. func (c *Cron) GetLogLevel() int { return c.logLevel.Val() } -// Add adds a timed task. -// A unique <name> can be bound with the timed task. -// It returns and error if the <name> is already used. -func (c *Cron) Add(pattern string, job func(), name ...string) (*Entry, error) { +// AddEntry creates and returns a new Entry object. +func (c *Cron) AddEntry(pattern string, job func(), times int, singleton bool, name ...string) (*Entry, error) { + var ( + entryName = "" + infinite = false + ) if len(name) > 0 { - if c.Search(name[0]) != nil { - return nil, errors.New(fmt.Sprintf(`cron job "%s" already exists`, name[0])) - } + entryName = name[0] } - return c.addEntry(pattern, job, false, name...) + if times <= 0 { + infinite = true + } + return c.doAddEntry(addEntryInput{ + Name: entryName, + Job: job, + Times: times, + Pattern: pattern, + Singleton: singleton, + Infinite: infinite, + }) +} + +// Add adds a timed task. +// A unique `name` can be bound with the timed task. +// It returns and error if the `name` is already used. +func (c *Cron) Add(pattern string, job func(), name ...string) (*Entry, error) { + return c.AddEntry(pattern, job, -1, false, name...) } // AddSingleton adds a singleton timed task. // A singleton timed task is that can only be running one single instance at the same time. -// A unique <name> can be bound with the timed task. -// It returns and error if the <name> is already used. +// A unique `name` can be bound with the timed task. +// It returns and error if the `name` is already used. func (c *Cron) AddSingleton(pattern string, job func(), name ...string) (*Entry, error) { - if entry, err := c.Add(pattern, job, name...); err != nil { - return nil, err - } else { - entry.SetSingleton(true) - return entry, nil - } -} - -// AddOnce adds a timed task which can be run only once. -// A unique <name> can be bound with the timed task. -// It returns and error if the <name> is already used. -func (c *Cron) AddOnce(pattern string, job func(), name ...string) (*Entry, error) { - if entry, err := c.Add(pattern, job, name...); err != nil { - return nil, err - } else { - entry.SetTimes(1) - return entry, nil - } + return c.AddEntry(pattern, job, -1, true, name...) } // AddTimes adds a timed task which can be run specified times. -// A unique <name> can be bound with the timed task. -// It returns and error if the <name> is already used. +// A unique `name` can be bound with the timed task. +// It returns and error if the `name` is already used. func (c *Cron) AddTimes(pattern string, times int, job func(), name ...string) (*Entry, error) { - if entry, err := c.Add(pattern, job, name...); err != nil { - return nil, err - } else { - entry.SetTimes(times) - return entry, nil - } + return c.AddEntry(pattern, job, times, false, name...) } -// DelayAdd adds a timed task after <delay> time. +// AddOnce adds a timed task which can be run only once. +// A unique `name` can be bound with the timed task. +// It returns and error if the `name` is already used. +func (c *Cron) AddOnce(pattern string, job func(), name ...string) (*Entry, error) { + return c.AddEntry(pattern, job, 1, false, name...) +} + +// DelayAddEntry adds a timed task after `delay` time. +func (c *Cron) DelayAddEntry(delay time.Duration, pattern string, job func(), times int, singleton bool, name ...string) { + gtimer.AddOnce(delay, func() { + if _, err := c.AddEntry(pattern, job, times, singleton, name...); err != nil { + panic(err) + } + }) +} + +// DelayAdd adds a timed task after `delay` time. func (c *Cron) DelayAdd(delay time.Duration, pattern string, job func(), name ...string) { gtimer.AddOnce(delay, func() { if _, err := c.Add(pattern, job, name...); err != nil { @@ -115,7 +145,7 @@ func (c *Cron) DelayAdd(delay time.Duration, pattern string, job func(), name .. }) } -// DelayAddSingleton adds a singleton timed task after <delay> time. +// DelayAddSingleton adds a singleton timed task after `delay` time. func (c *Cron) DelayAddSingleton(delay time.Duration, pattern string, job func(), name ...string) { gtimer.AddOnce(delay, func() { if _, err := c.AddSingleton(pattern, job, name...); err != nil { @@ -124,7 +154,7 @@ func (c *Cron) DelayAddSingleton(delay time.Duration, pattern string, job func() }) } -// DelayAddOnce adds a timed task after <delay> time. +// DelayAddOnce adds a timed task after `delay` time. // This timed task can be run only once. func (c *Cron) DelayAddOnce(delay time.Duration, pattern string, job func(), name ...string) { gtimer.AddOnce(delay, func() { @@ -134,7 +164,7 @@ func (c *Cron) DelayAddOnce(delay time.Duration, pattern string, job func(), nam }) } -// DelayAddTimes adds a timed task after <delay> time. +// DelayAddTimes adds a timed task after `delay` time. // This timed task can be run specified times. func (c *Cron) DelayAddTimes(delay time.Duration, pattern string, times int, job func(), name ...string) { gtimer.AddOnce(delay, func() { @@ -144,8 +174,8 @@ func (c *Cron) DelayAddTimes(delay time.Duration, pattern string, times int, job }) } -// Search returns a scheduled task with the specified <name>. -// It returns nil if no found. +// Search returns a scheduled task with the specified `name`. +// It returns nil if not found. func (c *Cron) Search(name string) *Entry { if v := c.entries.Get(name); v != nil { return v.(*Entry) @@ -153,7 +183,8 @@ func (c *Cron) Search(name string) *Entry { return nil } -// Start starts running the specified timed task named <name>. +// Start starts running the specified timed task named `name`. +// If no`name` specified, it starts the entire cron. func (c *Cron) Start(name ...string) { if len(name) > 0 { for _, v := range name { @@ -166,7 +197,8 @@ func (c *Cron) Start(name ...string) { } } -// Stop stops running the specified timed task named <name>. +// Stop stops running the specified timed task named `name`. +// If no`name` specified, it stops the entire cron. func (c *Cron) Stop(name ...string) { if len(name) > 0 { for _, v := range name { @@ -179,7 +211,7 @@ func (c *Cron) Stop(name ...string) { } } -// Remove deletes scheduled task which named <name>. +// Remove deletes scheduled task which named `name`. func (c *Cron) Remove(name string) { if v := c.entries.Get(name); v != nil { v.(*Entry).Close() diff --git a/os/gcron/gcron_entry.go b/os/gcron/gcron_entry.go index 5d3f571e7..3f3b90d8f 100644 --- a/os/gcron/gcron_entry.go +++ b/os/gcron/gcron_entry.go @@ -1,4 +1,4 @@ -// Copyright 2018 gf Author(https://github.com/gogf/gf). All Rights Reserved. +// Copyright GoFrame Author(https://goframe.org). All Rights Reserved. // // This Source Code Form is subject to the terms of the MIT License. // If a copy of the MIT was not distributed with this file, @@ -7,144 +7,169 @@ package gcron import ( + "github.com/gogf/gf/errors/gcode" + "github.com/gogf/gf/errors/gerror" "reflect" "runtime" "time" "github.com/gogf/gf/container/gtype" - "github.com/gogf/gf/os/glog" "github.com/gogf/gf/os/gtimer" "github.com/gogf/gf/util/gconv" ) -// Timed task entry. +// Entry is timing task entry. type Entry struct { - cron *Cron // Cron object belonged to. - entry *gtimer.Entry // Associated gtimer.Entry. - schedule *cronSchedule // Timed schedule object. - jobName string // Callback function name(address info). - times *gtype.Int // Running times limit. - Name string // Entry name. - Job func() `json:"-"` // Callback function. - Time time.Time // Registered time. + cron *Cron // Cron object belonged to. + timerEntry *gtimer.Entry // Associated timer Entry. + schedule *cronSchedule // Timed schedule object. + jobName string // Callback function name(address info). + times *gtype.Int // Running times limit. + infinite *gtype.Bool // No times limit. + Name string // Entry name. + Job func() `json:"-"` // Callback function. + Time time.Time // Registered time. } -// addEntry creates and returns a new Entry object. -// Param <job> is the callback function for timed task execution. -// Param <singleton> specifies whether timed task executing in singleton mode. -// Param <name> names this entry for manual control. -func (c *Cron) addEntry(pattern string, job func(), singleton bool, name ...string) (*Entry, error) { - schedule, err := newSchedule(pattern) +type addEntryInput struct { + Name string // Name names this entry for manual control. + Job func() // Job is the callback function for timed task execution. + Times int // Times specifies the running limit times for the entry. + Pattern string // Pattern is the crontab style string for scheduler. + Singleton bool // Singleton specifies whether timed task executing in singleton mode. + Infinite bool // Infinite specifies whether this entry is running with no times limit. +} + +// doAddEntry creates and returns a new Entry object. +func (c *Cron) doAddEntry(in addEntryInput) (*Entry, error) { + if in.Name != "" { + if c.Search(in.Name) != nil { + return nil, gerror.NewCodef(gcode.CodeInvalidOperation, `cron job "%s" already exists`, in.Name) + } + } + + schedule, err := newSchedule(in.Pattern) if err != nil { return nil, err } - // No limit for <times>, for gtimer checking scheduling every second. + // No limit for `times`, for timer checking scheduling every second. entry := &Entry{ cron: c, schedule: schedule, - jobName: runtime.FuncForPC(reflect.ValueOf(job).Pointer()).Name(), - times: gtype.NewInt(gDEFAULT_TIMES), - Job: job, + jobName: runtime.FuncForPC(reflect.ValueOf(in.Job).Pointer()).Name(), + times: gtype.NewInt(in.Times), + infinite: gtype.NewBool(in.Infinite), + Job: in.Job, Time: time.Now(), } - if len(name) > 0 { - entry.Name = name[0] + if in.Name != "" { + entry.Name = in.Name } else { - entry.Name = "gcron-" + gconv.String(c.idGen.Add(1)) + entry.Name = "cron-" + gconv.String(c.idGen.Add(1)) } // When you add a scheduled task, you cannot allow it to run. - // It cannot start running when added to gtimer. - // It should start running after the entry is added to the entries map, - // to avoid the task from running during adding where the entries - // does not have the entry information, which might cause panic. - entry.entry = gtimer.AddEntry(time.Second, entry.check, singleton, -1, gtimer.StatusStopped) + // It cannot start running when added to timer. + // It should start running after the entry is added to the Cron entries map, to avoid the task + // from running during adding where the entries do not have the entry information, which might cause panic. + entry.timerEntry = gtimer.AddEntry(time.Second, entry.check, in.Singleton, -1, gtimer.StatusStopped) c.entries.Set(entry.Name, entry) - entry.entry.Start() + entry.timerEntry.Start() return entry, nil } // IsSingleton return whether this entry is a singleton timed task. func (entry *Entry) IsSingleton() bool { - return entry.entry.IsSingleton() + return entry.timerEntry.IsSingleton() } // SetSingleton sets the entry running in singleton mode. func (entry *Entry) SetSingleton(enabled bool) { - entry.entry.SetSingleton(true) + entry.timerEntry.SetSingleton(enabled) } // SetTimes sets the times which the entry can run. func (entry *Entry) SetTimes(times int) { entry.times.Set(times) + entry.infinite.Set(false) } // Status returns the status of entry. func (entry *Entry) Status() int { - return entry.entry.Status() + return entry.timerEntry.Status() } // SetStatus sets the status of the entry. func (entry *Entry) SetStatus(status int) int { - return entry.entry.SetStatus(status) + return entry.timerEntry.SetStatus(status) } // Start starts running the entry. func (entry *Entry) Start() { - entry.entry.Start() + entry.timerEntry.Start() } // Stop stops running the entry. func (entry *Entry) Stop() { - entry.entry.Stop() + entry.timerEntry.Stop() } // Close stops and removes the entry from cron. func (entry *Entry) Close() { entry.cron.entries.Remove(entry.Name) - entry.entry.Close() + entry.timerEntry.Close() } -// Timed task check execution. +// check is the core timing task check logic. // The running times limits feature is implemented by gcron.Entry and cannot be implemented by gtimer.Entry. // gcron.Entry relies on gtimer to implement a scheduled task check for gcron.Entry per second. func (entry *Entry) check() { if entry.schedule.meet(time.Now()) { - path := entry.cron.GetLogPath() - level := entry.cron.GetLogLevel() switch entry.cron.status.Val() { case StatusStopped: return case StatusClosed: - glog.Path(path).Level(level).Debugf("[gcron] %s(%s) %s removed", entry.Name, entry.schedule.pattern, entry.jobName) + entry.logDebugf("[gcron] %s %s removed", entry.schedule.pattern, entry.jobName) entry.Close() case StatusReady: fallthrough case StatusRunning: - // Running times check. - times := entry.times.Add(-1) - if times <= 0 { - if entry.entry.SetStatus(StatusClosed) == StatusClosed || times < 0 { - return - } - } - if times < 2000000000 && times > 1000000000 { - entry.times.Set(gDEFAULT_TIMES) - } - glog.Path(path).Level(level).Debugf("[gcron] %s(%s) %s start", entry.Name, entry.schedule.pattern, entry.jobName) defer func() { if err := recover(); err != nil { - glog.Path(path).Level(level).Errorf("[gcron] %s(%s) %s end with error: %v", entry.Name, entry.schedule.pattern, entry.jobName, err) + entry.logErrorf("[gcron] %s %s end with error: %+v", entry.schedule.pattern, entry.jobName, err) } else { - glog.Path(path).Level(level).Debugf("[gcron] %s(%s) %s end", entry.Name, entry.schedule.pattern, entry.jobName) + entry.logDebugf("[gcron] %s %s end", entry.schedule.pattern, entry.jobName) } - if entry.entry.Status() == StatusClosed { + + if entry.timerEntry.Status() == StatusClosed { entry.Close() } }() - entry.Job() + // Running times check. + if !entry.infinite.Val() { + times := entry.times.Add(-1) + if times <= 0 { + if entry.timerEntry.SetStatus(StatusClosed) == StatusClosed || times < 0 { + return + } + } + } + entry.logDebugf("[gcron] %s %s start", entry.schedule.pattern, entry.jobName) + + entry.Job() } } } +func (entry *Entry) logDebugf(format string, v ...interface{}) { + if logger := entry.cron.GetLogger(); logger != nil { + logger.Debugf(format, v...) + } +} + +func (entry *Entry) logErrorf(format string, v ...interface{}) { + if logger := entry.cron.GetLogger(); logger != nil { + logger.Errorf(format, v...) + } +} diff --git a/os/gcron/gcron_schedule.go b/os/gcron/gcron_schedule.go index a413cf7c1..8bfb68a35 100644 --- a/os/gcron/gcron_schedule.go +++ b/os/gcron/gcron_schedule.go @@ -1,4 +1,4 @@ -// Copyright 2018 gf Author(https://github.com/gogf/gf). All Rights Reserved. +// Copyright GoFrame Author(https://goframe.org). All Rights Reserved. // // This Source Code Form is subject to the terms of the MIT License. // If a copy of the MIT was not distributed with this file, @@ -7,8 +7,8 @@ package gcron import ( - "errors" - "fmt" + "github.com/gogf/gf/errors/gcode" + "github.com/gogf/gf/errors/gerror" "github.com/gogf/gf/os/gtime" "strconv" "strings" @@ -32,7 +32,11 @@ type cronSchedule struct { const ( // regular expression for cron pattern, which contains 6 parts of time units. - gREGEX_FOR_CRON = `^([\-/\d\*\?,]+)\s+([\-/\d\*\?,]+)\s+([\-/\d\*\?,]+)\s+([\-/\d\*\?,]+)\s+([\-/\d\*\?,A-Za-z]+)\s+([\-/\d\*\?,A-Za-z]+)$` + regexForCron = `^([\-/\d\*\?,]+)\s+([\-/\d\*\?,]+)\s+([\-/\d\*\?,]+)\s+([\-/\d\*\?,]+)\s+([\-/\d\*\?,A-Za-z]+)\s+([\-/\d\*\?,A-Za-z]+)$` + + patternItemTypeUnknown = iota + patternItemTypeWeek + patternItemTypeMonth ) var ( @@ -91,105 +95,108 @@ func newSchedule(pattern string) (*cronSchedule, error) { }, nil } } else { - return nil, errors.New(fmt.Sprintf(`invalid pattern: "%s"`, pattern)) + return nil, gerror.NewCodef(gcode.CodeInvalidParameter, `invalid pattern: "%s"`, pattern) } } // Handle the common cron pattern, like: // 0 0 0 1 1 2 - if match, _ := gregex.MatchString(gREGEX_FOR_CRON, pattern); len(match) == 7 { + if match, _ := gregex.MatchString(regexForCron, pattern); len(match) == 7 { schedule := &cronSchedule{ create: time.Now().Unix(), every: 0, pattern: pattern, } // Second. - if m, err := parseItem(match[1], 0, 59, false); err != nil { + if m, err := parsePatternItem(match[1], 0, 59, false); err != nil { return nil, err } else { schedule.second = m } // Minute. - if m, err := parseItem(match[2], 0, 59, false); err != nil { + if m, err := parsePatternItem(match[2], 0, 59, false); err != nil { return nil, err } else { schedule.minute = m } // Hour. - if m, err := parseItem(match[3], 0, 23, false); err != nil { + if m, err := parsePatternItem(match[3], 0, 23, false); err != nil { return nil, err } else { schedule.hour = m } // Day. - if m, err := parseItem(match[4], 1, 31, true); err != nil { + if m, err := parsePatternItem(match[4], 1, 31, true); err != nil { return nil, err } else { schedule.day = m } // Month. - if m, err := parseItem(match[5], 1, 12, false); err != nil { + if m, err := parsePatternItem(match[5], 1, 12, false); err != nil { return nil, err } else { schedule.month = m } // Week. - if m, err := parseItem(match[6], 0, 6, true); err != nil { + if m, err := parsePatternItem(match[6], 0, 6, true); err != nil { return nil, err } else { schedule.week = m } return schedule, nil } else { - return nil, errors.New(fmt.Sprintf(`invalid pattern: "%s"`, pattern)) + return nil, gerror.NewCodef(gcode.CodeInvalidParameter, `invalid pattern: "%s"`, pattern) } } -// parseItem parses every item in the pattern and returns the result as map. -func parseItem(item string, min int, max int, allowQuestionMark bool) (map[int]struct{}, error) { +// parsePatternItem parses every item in the pattern and returns the result as map, which is used for indexing. +func parsePatternItem(item string, min int, max int, allowQuestionMark bool) (map[int]struct{}, error) { m := make(map[int]struct{}, max-min+1) if item == "*" || (allowQuestionMark && item == "?") { for i := min; i <= max; i++ { m[i] = struct{}{} } } else { + // Like: MON,FRI for _, item := range strings.Split(item, ",") { - interval := 1 - intervalArray := strings.Split(item, "/") + var ( + interval = 1 + intervalArray = strings.Split(item, "/") + ) if len(intervalArray) == 2 { - if i, err := strconv.Atoi(intervalArray[1]); err != nil { - return nil, errors.New(fmt.Sprintf(`invalid pattern item: "%s"`, item)) + if number, err := strconv.Atoi(intervalArray[1]); err != nil { + return nil, gerror.NewCodef(gcode.CodeInvalidParameter, `invalid pattern item: "%s"`, item) } else { - interval = i + interval = number } } var ( rangeMin = min rangeMax = max - fieldType = byte(0) + itemType = patternItemTypeUnknown rangeArray = strings.Split(intervalArray[0], "-") // Like: 1-30, JAN-DEC ) switch max { case 6: // It's checking week field. - fieldType = 'w' + itemType = patternItemTypeWeek case 12: // It's checking month field. - fieldType = 'm' + itemType = patternItemTypeMonth } // Eg: */5 if rangeArray[0] != "*" { - if i, err := parseItemValue(rangeArray[0], fieldType); err != nil { - return nil, errors.New(fmt.Sprintf(`invalid pattern item: "%s"`, item)) + if number, err := parsePatternItemValue(rangeArray[0], itemType); err != nil { + return nil, gerror.NewCodef(gcode.CodeInvalidParameter, `invalid pattern item: "%s"`, item) } else { - rangeMin = i - rangeMax = i + rangeMin = number + rangeMax = number } } if len(rangeArray) == 2 { - if i, err := parseItemValue(rangeArray[1], fieldType); err != nil { - return nil, errors.New(fmt.Sprintf(`invalid pattern item: "%s"`, item)) + if number, err := parsePatternItemValue(rangeArray[1], itemType); err != nil { + return nil, gerror.NewCodef(gcode.CodeInvalidParameter, `invalid pattern item: "%s"`, item) } else { - rangeMax = i + rangeMax = number } } for i := rangeMin; i <= rangeMax; i += interval { @@ -200,31 +207,31 @@ func parseItem(item string, min int, max int, allowQuestionMark bool) (map[int]s return m, nil } -// parseItemValue parses the field value to a number according to its field type. -func parseItemValue(value string, fieldType byte) (int, error) { +// parsePatternItemValue parses the field value to a number according to its field type. +func parsePatternItemValue(value string, itemType int) (int, error) { if gregex.IsMatchString(`^\d+$`, value) { - // Pure number. - if i, err := strconv.Atoi(value); err == nil { - return i, nil + // It is pure number. + if number, err := strconv.Atoi(value); err == nil { + return number, nil } } else { - // Check if contains letter, + // Check if it contains letter, // it converts the value to number according to predefined map. - switch fieldType { - case 'm': - if i, ok := monthMap[strings.ToLower(value)]; ok { - return i, nil + switch itemType { + case patternItemTypeWeek: + if number, ok := monthMap[strings.ToLower(value)]; ok { + return number, nil } - case 'w': - if i, ok := weekMap[strings.ToLower(value)]; ok { - return i, nil + case patternItemTypeMonth: + if number, ok := weekMap[strings.ToLower(value)]; ok { + return number, nil } } } - return 0, errors.New(fmt.Sprintf(`invalid pattern value: "%s"`, value)) + return 0, gerror.NewCodef(gcode.CodeInvalidParameter, `invalid pattern value: "%s"`, value) } -// meet checks if the given time <t> meets the runnable point for the job. +// meet checks if the given time `t` meets the runnable point for the job. func (s *cronSchedule) meet(t time.Time) bool { if s.every != 0 { // It checks using interval. diff --git a/os/gcron/gcron_unit_1_test.go b/os/gcron/gcron_unit_1_test.go index 01a31353e..c8754498b 100644 --- a/os/gcron/gcron_unit_1_test.go +++ b/os/gcron/gcron_unit_1_test.go @@ -1,4 +1,4 @@ -// Copyright 2018 gf Author(https://github.com/gogf/gf). All Rights Reserved. +// Copyright GoFrame Author(https://goframe.org). All Rights Reserved. // // This Source Code Form is subject to the terms of the MIT License. // If a copy of the MIT was not distributed with this file, @@ -7,6 +7,7 @@ package gcron_test import ( + "github.com/gogf/gf/frame/g" "testing" "time" @@ -20,33 +21,24 @@ func TestCron_Add_Close(t *testing.T) { cron := gcron.New() array := garray.New(true) _, err1 := cron.Add("* * * * * *", func() { - //glog.Println("cron1") + g.Log().Println("cron1") array.Append(1) }) _, err2 := cron.Add("* * * * * *", func() { - //glog.Println("cron2") + g.Log().Println("cron2") array.Append(1) }, "test") - _, err3 := cron.Add("* * * * * *", func() { - array.Append(1) - }, "test") - _, err4 := cron.Add("@every 2s", func() { - //glog.Println("cron3") - array.Append(1) - }) t.Assert(err1, nil) t.Assert(err2, nil) - t.AssertNE(err3, nil) - t.Assert(err4, nil) - t.Assert(cron.Size(), 3) - time.Sleep(1200 * time.Millisecond) + t.Assert(cron.Size(), 2) + time.Sleep(1300 * time.Millisecond) t.Assert(array.Len(), 2) - time.Sleep(1200 * time.Millisecond) - t.Assert(array.Len(), 5) + time.Sleep(1300 * time.Millisecond) + t.Assert(array.Len(), 4) cron.Close() - time.Sleep(1200 * time.Millisecond) + time.Sleep(1300 * time.Millisecond) fixedLength := array.Len() - time.Sleep(1200 * time.Millisecond) + time.Sleep(1300 * time.Millisecond) t.Assert(array.Len(), fixedLength) }) } diff --git a/os/gcron/gcron_unit_2_test.go b/os/gcron/gcron_unit_2_test.go index 8635bff94..5956dc8bd 100644 --- a/os/gcron/gcron_unit_2_test.go +++ b/os/gcron/gcron_unit_2_test.go @@ -1,4 +1,4 @@ -// Copyright 2018 gf Author(https://github.com/gogf/gf). All Rights Reserved. +// Copyright GoFrame Author(https://goframe.org). All Rights Reserved. // // This Source Code Form is subject to the terms of the MIT License. // If a copy of the MIT was not distributed with this file, @@ -7,21 +7,23 @@ package gcron_test import ( + "github.com/gogf/gf/frame/g" "testing" "time" "github.com/gogf/gf/container/garray" "github.com/gogf/gf/os/gcron" - "github.com/gogf/gf/os/glog" "github.com/gogf/gf/test/gtest" ) func TestCron_Entry_Operations(t *testing.T) { gtest.C(t, func(t *gtest.T) { - cron := gcron.New() - array := garray.New(true) + var ( + cron = gcron.New() + array = garray.New(true) + ) cron.DelayAddTimes(500*time.Millisecond, "* * * * * *", 2, func() { - glog.Println("add times") + g.Log().Println("add times") array.Append(1) }) t.Assert(cron.Size(), 0) @@ -34,25 +36,27 @@ func TestCron_Entry_Operations(t *testing.T) { }) gtest.C(t, func(t *gtest.T) { - cron := gcron.New() - array := garray.New(true) + var ( + cron = gcron.New() + array = garray.New(true) + ) entry, err1 := cron.Add("* * * * * *", func() { - glog.Println("add") + g.Log().Println("add") array.Append(1) }) t.Assert(err1, nil) t.Assert(array.Len(), 0) t.Assert(cron.Size(), 1) - time.Sleep(1200 * time.Millisecond) + time.Sleep(1300 * time.Millisecond) t.Assert(array.Len(), 1) t.Assert(cron.Size(), 1) entry.Stop() - time.Sleep(2000 * time.Millisecond) + time.Sleep(5000 * time.Millisecond) t.Assert(array.Len(), 1) t.Assert(cron.Size(), 1) entry.Start() - glog.Println("start") - time.Sleep(1200 * time.Millisecond) + g.Log().Println("start") + time.Sleep(1000 * time.Millisecond) t.Assert(array.Len(), 2) t.Assert(cron.Size(), 1) entry.Close() diff --git a/os/gcron/gcron_z_bench_test.go b/os/gcron/gcron_z_bench_test.go index fd9925691..037a9b058 100644 --- a/os/gcron/gcron_z_bench_test.go +++ b/os/gcron/gcron_z_bench_test.go @@ -1,4 +1,4 @@ -// Copyright 2018 gf Author(https://github.com/gogf/gf). All Rights Reserved. +// Copyright GoFrame Author(https://goframe.org). All Rights Reserved. // // This Source Code Form is subject to the terms of the MIT License. // If a copy of the MIT was not distributed with this file, diff --git a/os/gcron/gcron_z_example_1_test.go b/os/gcron/gcron_z_example_1_test.go index e1f636250..d49f2142f 100644 --- a/os/gcron/gcron_z_example_1_test.go +++ b/os/gcron/gcron_z_example_1_test.go @@ -1,4 +1,4 @@ -// Copyright 2019 gf Author(https://github.com/gogf/gf). All Rights Reserved. +// Copyright GoFrame Author(https://goframe.org). All Rights Reserved. // // This Source Code Form is subject to the terms of the MIT License. // If a copy of the MIT was not distributed with this file, diff --git a/os/gctx/gctx.go b/os/gctx/gctx.go new file mode 100644 index 000000000..9eb91f5e6 --- /dev/null +++ b/os/gctx/gctx.go @@ -0,0 +1,60 @@ +// Copyright GoFrame Author(https://goframe.org). All Rights Reserved. +// +// This Source Code Form is subject to the terms of the MIT License. +// If a copy of the MIT was not distributed with this file, +// You can obtain one at https://github.com/gogf/gf. + +// Package gctx wraps context.Context and provides extra context features. +package gctx + +import ( + "context" + "github.com/gogf/gf/util/guid" +) + +type ( + Ctx = context.Context // Ctx is short name alias for context.Context. + StrKey string // StrKey is a type for warps basic type string as context key. +) + +const ( + // CtxKey is custom tracing context key for context id. + // The context id a unique string for certain context. + CtxKey StrKey = "GoFrameCtxId" +) + +// New creates and returns a context which contains context id. +func New() context.Context { + return WithCtx(context.Background()) +} + +// WithCtx creates and returns a context containing context id upon given parent context `ctx`. +func WithCtx(ctx context.Context) context.Context { + return WithPrefix(ctx, "") +} + +// WithPrefix creates and returns a context containing context id upon given parent context `ctx`. +// The generated context id has custom prefix string specified by parameter `prefix`. +func WithPrefix(ctx context.Context, prefix string) context.Context { + return WithValue(ctx, prefix+getUniqueID()) +} + +// WithValue creates and returns a context containing context id upon given parent context `ctx`. +// The generated context id value is specified by parameter `value`. +func WithValue(ctx context.Context, value string) context.Context { + if value == "" { + return New() + } + return context.WithValue(ctx, CtxKey, value) +} + +// Value retrieves and returns the context id from context. +func Value(ctx context.Context) string { + s, _ := ctx.Value(CtxKey).(string) + return s +} + +// getUniqueID produces a global unique string. +func getUniqueID() string { + return guid.S() +} diff --git a/os/gctx/gctx_test.go b/os/gctx/gctx_test.go new file mode 100644 index 000000000..d190d5a7a --- /dev/null +++ b/os/gctx/gctx_test.go @@ -0,0 +1,51 @@ +// Copyright GoFrame Author(https://goframe.org). All Rights Reserved. +// +// This Source Code Form is subject to the terms of the MIT License. +// If a copy of the MIT was not distributed with this file, +// You can obtain one at https://github.com/gogf/gf. + +package gctx_test + +import ( + "context" + "github.com/gogf/gf/os/gctx" + "github.com/gogf/gf/text/gstr" + "testing" + + "github.com/gogf/gf/test/gtest" +) + +func Test_New(t *testing.T) { + gtest.C(t, func(t *gtest.T) { + ctx := gctx.New() + t.AssertNE(ctx, nil) + t.AssertNE(gctx.Value(ctx), "") + }) +} + +func Test_WithCtx(t *testing.T) { + gtest.C(t, func(t *gtest.T) { + ctx := context.WithValue(context.TODO(), "TEST", 1) + ctx = gctx.WithCtx(ctx) + t.AssertNE(gctx.Value(ctx), "") + t.Assert(ctx.Value("TEST"), 1) + }) +} + +func Test_WithPrefix(t *testing.T) { + gtest.C(t, func(t *gtest.T) { + ctx := context.WithValue(context.TODO(), "TEST", 1) + ctx = gctx.WithPrefix(ctx, "H-") + t.Assert(gstr.Contains(gctx.Value(ctx), "H-"), true) + t.Assert(ctx.Value("TEST"), 1) + }) +} + +func Test_WithValue(t *testing.T) { + gtest.C(t, func(t *gtest.T) { + ctx := context.WithValue(context.TODO(), "TEST", 1) + ctx = gctx.WithValue(ctx, "123") + t.Assert(gctx.Value(ctx), "123") + t.Assert(ctx.Value("TEST"), 1) + }) +} diff --git a/os/genv/genv.go b/os/genv/genv.go index 815552e36..0b0e6a8e4 100644 --- a/os/genv/genv.go +++ b/os/genv/genv.go @@ -1,4 +1,4 @@ -// Copyright GoFrame Author(https://github.com/gogf/gf). All Rights Reserved. +// Copyright GoFrame Author(https://goframe.org). All Rights Reserved. // // This Source Code Form is subject to the terms of the MIT License. // If a copy of the MIT was not distributed with this file, @@ -11,8 +11,8 @@ import ( "github.com/gogf/gf/container/gvar" "github.com/gogf/gf/os/gcmd" "os" + "strings" ) -import "strings" // All returns a copy of strings representing the environment, // in the form "key=value". diff --git a/os/genv/genv_test.go b/os/genv/genv_test.go index 39ca57fae..5ff118464 100644 --- a/os/genv/genv_test.go +++ b/os/genv/genv_test.go @@ -1,4 +1,4 @@ -// Copyright GoFrame Author(https://github.com/gogf/gf). All Rights Reserved. +// Copyright GoFrame Author(https://goframe.org). All Rights Reserved. // // This Source Code Form is subject to the terms of the MIT License. // If a copy of the MIT was not distributed with this file, diff --git a/os/gfile/gfile.go b/os/gfile/gfile.go index b1be677d8..84eede389 100644 --- a/os/gfile/gfile.go +++ b/os/gfile/gfile.go @@ -1,4 +1,4 @@ -// Copyright 2017 gf Author(https://github.com/gogf/gf). All Rights Reserved. +// Copyright GoFrame Author(https://goframe.org). All Rights Reserved. // // This Source Code Form is subject to the terms of the MIT License. // If a copy of the MIT was not distributed with this file, @@ -59,10 +59,9 @@ func init() { } // Mkdir creates directories recursively with given <path>. -// The parameter <path> is suggested to be absolute path. +// The parameter <path> is suggested to be an absolute path instead of relative one. func Mkdir(path string) error { - err := os.MkdirAll(path, os.ModePerm) - if err != nil { + if err := os.MkdirAll(path, os.ModePerm); err != nil { return err } return nil @@ -344,10 +343,14 @@ func Name(path string) string { // Dir returns all but the last element of path, typically the path's directory. // After dropping the final element, Dir calls Clean on the path and trailing // slashes are removed. -// If the path is empty, Dir returns ".". -// If the path consists entirely of separators, Dir returns a single separator. +// If the `path` is empty, Dir returns ".". +// If the `path` is ".", Dir treats the path as current working directory. +// If the `path` consists entirely of separators, Dir returns a single separator. // The returned path does not end in a separator unless it is the root directory. func Dir(path string) string { + if path == "." { + return filepath.Dir(RealPath(path)) + } return filepath.Dir(path) } diff --git a/os/gfile/gfile_cache.go b/os/gfile/gfile_cache.go index 6dee6324a..c4c28d490 100644 --- a/os/gfile/gfile_cache.go +++ b/os/gfile/gfile_cache.go @@ -1,4 +1,4 @@ -// Copyright 2017-2018 gf Author(https://github.com/gogf/gf). All Rights Reserved. +// Copyright GoFrame Author(https://goframe.org). All Rights Reserved. // // This Source Code Form is subject to the terms of the MIT License. // If a copy of the MIT was not distributed with this file, @@ -14,26 +14,26 @@ import ( ) const ( - // Default expire time for file content caching in seconds. - gDEFAULT_CACHE_EXPIRE = time.Minute + defaultCacheExpire = time.Minute // defaultCacheExpire is the expire time for file content caching in seconds. + commandEnvKeyForCache = "gf.gfile.cache" // commandEnvKeyForCache is the configuration key for command argument or environment configuring cache expire duration. ) var ( // Default expire time for file content caching. - cacheExpire = gcmd.GetWithEnv("gf.gfile.cache", gDEFAULT_CACHE_EXPIRE).Duration() + cacheExpire = gcmd.GetOptWithEnv(commandEnvKeyForCache, defaultCacheExpire).Duration() // internalCache is the memory cache for internal usage. internalCache = gcache.New() ) -// GetContents returns string content of given file by <path> from cache. +// GetContentsWithCache returns string content of given file by <path> from cache. // If there's no content in the cache, it will read it from disk file specified by <path>. // The parameter <expire> specifies the caching time for this file content in seconds. func GetContentsWithCache(path string, duration ...time.Duration) string { return string(GetBytesWithCache(path, duration...)) } -// GetBinContents returns []byte content of given file by <path> from cache. +// GetBytesWithCache returns []byte content of given file by <path> from cache. // If there's no content in the cache, it will read it from disk file specified by <path>. // The parameter <expire> specifies the caching time for this file content in seconds. func GetBytesWithCache(path string, duration ...time.Duration) []byte { @@ -62,5 +62,5 @@ func GetBytesWithCache(path string, duration ...time.Duration) []byte { // cacheKey produces the cache key for gcache. func cacheKey(path string) string { - return "gf.gfile.cache:" + path + return commandEnvKeyForCache + path } diff --git a/os/gfile/gfile_contents.go b/os/gfile/gfile_contents.go index e4a8e48e9..048e15d76 100644 --- a/os/gfile/gfile_contents.go +++ b/os/gfile/gfile_contents.go @@ -1,4 +1,4 @@ -// Copyright 2017-2018 gf Author(https://github.com/gogf/gf). All Rights Reserved. +// Copyright GoFrame Author(https://goframe.org). All Rights Reserved. // // This Source Code Form is subject to the terms of the MIT License. // If a copy of the MIT was not distributed with this file, @@ -16,7 +16,7 @@ import ( ) var ( - // Buffer size for reading file content. + // DefaultReadBuffer is the buffer size for reading file content. DefaultReadBuffer = 1024 ) diff --git a/os/gfile/gfile_copy.go b/os/gfile/gfile_copy.go index d3b6887a1..26ace7bcf 100644 --- a/os/gfile/gfile_copy.go +++ b/os/gfile/gfile_copy.go @@ -1,4 +1,4 @@ -// Copyright 2019 gf Author(https://github.com/gogf/gf). All Rights Reserved. +// Copyright GoFrame Author(https://goframe.org). All Rights Reserved. // // This Source Code Form is subject to the terms of the MIT License. // If a copy of the MIT was not distributed with this file, @@ -7,8 +7,9 @@ package gfile import ( - "errors" "fmt" + "github.com/gogf/gf/errors/gcode" + "github.com/gogf/gf/errors/gerror" "io" "io/ioutil" "os" @@ -21,10 +22,10 @@ import ( // or else it calls CopyDir. func Copy(src string, dst string) error { if src == "" { - return errors.New("source path cannot be empty") + return gerror.NewCode(gcode.CodeInvalidParameter, "source path cannot be empty") } if dst == "" { - return errors.New("destination path cannot be empty") + return gerror.NewCode(gcode.CodeInvalidParameter, "destination path cannot be empty") } if IsFile(src) { return CopyFile(src, dst) @@ -40,10 +41,10 @@ func Copy(src string, dst string) error { // Thanks: https://gist.github.com/r0l1/92462b38df26839a3ca324697c8cba04 func CopyFile(src, dst string) (err error) { if src == "" { - return errors.New("source file cannot be empty") + return gerror.NewCode(gcode.CodeInvalidParameter, "source file cannot be empty") } if dst == "" { - return errors.New("destination file cannot be empty") + return gerror.NewCode(gcode.CodeInvalidParameter, "destination file cannot be empty") } // If src and dst are the same path, it does nothing. if src == dst { @@ -87,10 +88,10 @@ func CopyFile(src, dst string) (err error) { // Note that, the Source directory must exist and symlinks are ignored and skipped. func CopyDir(src string, dst string) (err error) { if src == "" { - return errors.New("source directory cannot be empty") + return gerror.NewCode(gcode.CodeInvalidParameter, "source directory cannot be empty") } if dst == "" { - return errors.New("destination directory cannot be empty") + return gerror.NewCode(gcode.CodeInvalidParameter, "destination directory cannot be empty") } // If src and dst are the same path, it does nothing. if src == dst { diff --git a/os/gfile/gfile_home.go b/os/gfile/gfile_home.go index 2842864b4..44e0da4ad 100644 --- a/os/gfile/gfile_home.go +++ b/os/gfile/gfile_home.go @@ -1,4 +1,4 @@ -// Copyright 2020 gf Author(https://github.com/gogf/gf). All Rights Reserved. +// Copyright GoFrame Author(https://goframe.org). All Rights Reserved. // // This Source Code Form is subject to the terms of the MIT License. // If a copy of the MIT was not distributed with this file, @@ -8,7 +8,8 @@ package gfile import ( "bytes" - "errors" + "github.com/gogf/gf/errors/gcode" + "github.com/gogf/gf/errors/gerror" "os" "os/exec" "os/user" @@ -56,7 +57,7 @@ func homeUnix() (string, error) { result := strings.TrimSpace(stdout.String()) if result == "" { - return "", errors.New("blank output when reading home directory") + return "", gerror.NewCode(gcode.CodeInternalError, "blank output when reading home directory") } return result, nil @@ -73,7 +74,7 @@ func homeWindows() (string, error) { home = os.Getenv("USERPROFILE") } if home == "" { - return "", errors.New("HOMEDRIVE, HOMEPATH, and USERPROFILE are blank") + return "", gerror.NewCode(gcode.CodeOperationFailed, "HOMEDRIVE, HOMEPATH, and USERPROFILE are blank") } return home, nil diff --git a/os/gfile/gfile_replace.go b/os/gfile/gfile_replace.go index a0664bbdf..90a9fadd4 100644 --- a/os/gfile/gfile_replace.go +++ b/os/gfile/gfile_replace.go @@ -1,4 +1,4 @@ -// Copyright 2017-2018 gf Author(https://github.com/gogf/gf). All Rights Reserved. +// Copyright GoFrame Author(https://goframe.org). All Rights Reserved. // // This Source Code Form is subject to the terms of the MIT License. // If a copy of the MIT was not distributed with this file, diff --git a/os/gfile/gfile_scan.go b/os/gfile/gfile_scan.go index ac6aa1809..05f46aee8 100644 --- a/os/gfile/gfile_scan.go +++ b/os/gfile/gfile_scan.go @@ -1,4 +1,4 @@ -// Copyright 2017-2018 gf Author(https://github.com/gogf/gf). All Rights Reserved. +// Copyright GoFrame Author(https://goframe.org). All Rights Reserved. // // This Source Code Form is subject to the terms of the MIT License. // If a copy of the MIT was not distributed with this file, @@ -7,6 +7,7 @@ package gfile import ( + "github.com/gogf/gf/errors/gcode" "github.com/gogf/gf/errors/gerror" "github.com/gogf/gf/text/gstr" "os" @@ -16,7 +17,7 @@ import ( const ( // Max recursive depth for directory scanning. - gMAX_SCAN_DEPTH = 100000 + maxScanDepth = 100000 ) // ScanDir returns all sub-files with absolute paths of given <path>, @@ -136,8 +137,8 @@ func ScanDirFileFunc(path string, pattern string, recursive bool, handler func(p // 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(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) + if depth >= maxScanDepth { + return nil, gerror.NewCodef(gcode.CodeOperationFailed, "directory scanning exceeds max recursive depth: %d", maxScanDepth) } list := ([]string)(nil) file, err := os.Open(path) diff --git a/os/gfile/gfile_search.go b/os/gfile/gfile_search.go index 2936f280a..97ab2deac 100644 --- a/os/gfile/gfile_search.go +++ b/os/gfile/gfile_search.go @@ -1,4 +1,4 @@ -// Copyright 2017-2018 gf Author(https://github.com/gogf/gf). All Rights Reserved. +// Copyright GoFrame Author(https://goframe.org). All Rights Reserved. // // This Source Code Form is subject to the terms of the MIT License. // If a copy of the MIT was not distributed with this file, @@ -8,8 +8,9 @@ package gfile import ( "bytes" - "errors" "fmt" + "github.com/gogf/gf/errors/gcode" + "github.com/gogf/gf/errors/gerror" "github.com/gogf/gf/container/garray" ) @@ -52,7 +53,7 @@ func Search(name string, prioritySearchPaths ...string) (realPath string, err er buffer.WriteString(fmt.Sprintf("\n%d. %s", k+1, v)) } }) - err = errors.New(buffer.String()) + err = gerror.NewCode(gcode.CodeOperationFailed, buffer.String()) } return } diff --git a/os/gfile/gfile_size.go b/os/gfile/gfile_size.go index 37d08e947..9e3d8f0a6 100644 --- a/os/gfile/gfile_size.go +++ b/os/gfile/gfile_size.go @@ -1,4 +1,4 @@ -// Copyright 2017-2018 gf Author(https://github.com/gogf/gf). All Rights Reserved. +// Copyright GoFrame Author(https://goframe.org). All Rights Reserved. // // This Source Code Form is subject to the terms of the MIT License. // If a copy of the MIT was not distributed with this file, @@ -22,6 +22,11 @@ func Size(path string) int64 { return s.Size() } +// SizeFormat returns the size of file specified by <path> in format string. +func SizeFormat(path string) string { + return FormatSize(Size(path)) +} + // ReadableSize formats size of file given by <path>, for more human readable. func ReadableSize(path string) string { return FormatSize(Size(path)) diff --git a/os/gfile/gfile_sort.go b/os/gfile/gfile_sort.go index f1a6d4dc8..d4162de93 100644 --- a/os/gfile/gfile_sort.go +++ b/os/gfile/gfile_sort.go @@ -1,4 +1,4 @@ -// Copyright 2017-2018 gf Author(https://github.com/gogf/gf). All Rights Reserved. +// Copyright GoFrame Author(https://goframe.org). All Rights Reserved. // // This Source Code Form is subject to the terms of the MIT License. // If a copy of the MIT was not distributed with this file, diff --git a/os/gfile/gfile_source.go b/os/gfile/gfile_source.go index 8f5192470..37611ef17 100644 --- a/os/gfile/gfile_source.go +++ b/os/gfile/gfile_source.go @@ -1,4 +1,4 @@ -// Copyright 2017 gf Author(https://github.com/gogf/gf). All Rights Reserved. +// Copyright GoFrame Author(https://goframe.org). All Rights Reserved. // // This Source Code Form is subject to the terms of the MIT License. // If a copy of the MIT was not distributed with this file, @@ -7,6 +7,7 @@ package gfile import ( + "github.com/gogf/gf/text/gstr" "os" "runtime" "strings" @@ -36,7 +37,7 @@ func init() { // Note2: When the method is called for the first time, if it is in an asynchronous goroutine, // the method may not get the main package path. func MainPkgPath() string { - // Only for source development environments. + // It is only for source development environments. if goRootForFilter == "" { return "" } @@ -44,21 +45,26 @@ func MainPkgPath() string { if path != "" { return path } - lastFile := "" + var lastFile string for i := 1; i < 10000; i++ { - if _, file, _, ok := runtime.Caller(i); ok { + if pc, file, _, ok := runtime.Caller(i); ok { if goRootForFilter != "" && len(file) >= len(goRootForFilter) && file[0:len(goRootForFilter)] == goRootForFilter { continue } - if gregex.IsMatchString(`/github.com/[^/]+/gf/`, file) && - !gregex.IsMatchString(`/github.com/[^/]+/gf/\.example/`, file) { - continue - } if Ext(file) != ".go" { continue } lastFile = file - if gregex.IsMatchString(`package\s+main`, GetContents(file)) { + // Check if it is called in package initialization function, + // in which it here cannot retrieve main package path, + // it so just returns that can make next check. + if fn := runtime.FuncForPC(pc); fn != nil { + array := gstr.Split(fn.Name(), ".") + if array[0] != "main" { + continue + } + } + if gregex.IsMatchString(`package\s+main\s+`, GetContents(file)) { mainPkgPath.Set(Dir(file)) return Dir(file) } @@ -66,11 +72,14 @@ func MainPkgPath() string { break } } + // If it still cannot find the path of the package main, + // it recursively searches the directory and its parents directory of the last go file. + // It's usually necessary for uint testing cases of business project. if lastFile != "" { for path = Dir(lastFile); len(path) > 1 && Exists(path) && path[len(path)-1] != os.PathSeparator; { files, _ := ScanDir(path, "*.go") for _, v := range files { - if gregex.IsMatchString(`package\s+main`, GetContents(v)) { + if gregex.IsMatchString(`package\s+main\s+`, GetContents(v)) { mainPkgPath.Set(path) return path } diff --git a/os/gfile/gfile_time.go b/os/gfile/gfile_time.go index 80b4b3754..f91fee5b8 100644 --- a/os/gfile/gfile_time.go +++ b/os/gfile/gfile_time.go @@ -1,4 +1,4 @@ -// Copyright 2017-2018 gf Author(https://github.com/gogf/gf). All Rights Reserved. +// Copyright GoFrame Author(https://goframe.org). All Rights Reserved. // // This Source Code Form is subject to the terms of the MIT License. // If a copy of the MIT was not distributed with this file, diff --git a/os/gfile/gfile_z_cache_test.go b/os/gfile/gfile_z_cache_test.go index eb0e30005..6aec14346 100644 --- a/os/gfile/gfile_z_cache_test.go +++ b/os/gfile/gfile_z_cache_test.go @@ -1,4 +1,4 @@ -// Copyright 2020 gf Author(https://github.com/gogf/gf). All Rights Reserved. +// Copyright GoFrame Author(https://goframe.org). All Rights Reserved. // // This Source Code Form is subject to the terms of the MIT License. // If a copy of the MIT was not distributed with this file, diff --git a/os/gfile/gfile_z_contents_test.go b/os/gfile/gfile_z_contents_test.go index 1b28bbb8f..a141d6d4e 100644 --- a/os/gfile/gfile_z_contents_test.go +++ b/os/gfile/gfile_z_contents_test.go @@ -1,4 +1,4 @@ -// Copyright 2019 gf Author(https://github.com/gogf/gf). All Rights Reserved. +// Copyright GoFrame Author(https://goframe.org). All Rights Reserved. // // This Source Code Form is subject to the terms of the MIT License. // If a copy of the MIT was not distributed with this file, diff --git a/os/gfile/gfile_z_copy_test.go b/os/gfile/gfile_z_copy_test.go index 484318383..9aceb72df 100644 --- a/os/gfile/gfile_z_copy_test.go +++ b/os/gfile/gfile_z_copy_test.go @@ -1,4 +1,4 @@ -// Copyright 2019 gf Author(https://github.com/gogf/gf). All Rights Reserved. +// Copyright GoFrame Author(https://goframe.org). All Rights Reserved. // // This Source Code Form is subject to the terms of the MIT License. // If a copy of the MIT was not distributed with this file, diff --git a/os/gfile/gfile_z_readline_test.go b/os/gfile/gfile_z_readline_test.go index 7c92efe05..57899301b 100644 --- a/os/gfile/gfile_z_readline_test.go +++ b/os/gfile/gfile_z_readline_test.go @@ -1,4 +1,4 @@ -// Copyright GoFrame Author(https://github.com/gogf/gf). All Rights Reserved. +// Copyright GoFrame Author(https://goframe.org). All Rights Reserved. // // This Source Code Form is subject to the terms of the MIT License. // If a copy of the MIT was not distributed with this file, diff --git a/os/gfile/gfile_z_scan_test.go b/os/gfile/gfile_z_scan_test.go index f334517ef..786f5f5e4 100644 --- a/os/gfile/gfile_z_scan_test.go +++ b/os/gfile/gfile_z_scan_test.go @@ -1,4 +1,4 @@ -// Copyright 2017-2018 gf Author(https://github.com/gogf/gf). All Rights Reserved. +// Copyright GoFrame Author(https://goframe.org). All Rights Reserved. // // This Source Code Form is subject to the terms of the MIT License. // If a copy of the MIT was not distributed with this file, diff --git a/os/gfile/gfile_z_search_test.go b/os/gfile/gfile_z_search_test.go index cde8d5277..fc40d508d 100644 --- a/os/gfile/gfile_z_search_test.go +++ b/os/gfile/gfile_z_search_test.go @@ -1,4 +1,4 @@ -// Copyright 2019 gf Author(https://github.com/gogf/gf). All Rights Reserved. +// Copyright GoFrame Author(https://goframe.org). All Rights Reserved. // // This Source Code Form is subject to the terms of the MIT License. // If a copy of the MIT was not distributed with this file, diff --git a/os/gfile/gfile_z_size_test.go b/os/gfile/gfile_z_size_test.go index fe74cb392..9425fa866 100644 --- a/os/gfile/gfile_z_size_test.go +++ b/os/gfile/gfile_z_size_test.go @@ -1,4 +1,4 @@ -// Copyright 2019 gf Author(https://github.com/gogf/gf). All Rights Reserved. +// Copyright GoFrame Author(https://goframe.org). All Rights Reserved. // // This Source Code Form is subject to the terms of the MIT License. // If a copy of the MIT was not distributed with this file, @@ -33,6 +33,25 @@ func Test_Size(t *testing.T) { }) } +func Test_SizeFormat(t *testing.T) { + gtest.C(t, func(t *gtest.T) { + var ( + paths1 = "/testfile_t1.txt" + sizes string + ) + + createTestFile(paths1, "abcdefghijklmn") + defer delTestFiles(paths1) + + sizes = gfile.SizeFormat(testpath() + paths1) + t.Assert(sizes, "14.00B") + + sizes = gfile.SizeFormat("") + t.Assert(sizes, "0.00B") + + }) +} + func Test_StrToSize(t *testing.T) { gtest.C(t, func(t *gtest.T) { t.Assert(gfile.StrToSize("0.00B"), 0) diff --git a/os/gfile/gfile_z_test.go b/os/gfile/gfile_z_test.go index 4262601b7..35b10b292 100644 --- a/os/gfile/gfile_z_test.go +++ b/os/gfile/gfile_z_test.go @@ -1,4 +1,4 @@ -// Copyright 2019 gf Author(https://github.com/gogf/gf). All Rights Reserved. +// Copyright GoFrame Author(https://goframe.org). All Rights Reserved. // // This Source Code Form is subject to the terms of the MIT License. // If a copy of the MIT was not distributed with this file, diff --git a/os/gfile/gfile_z_time_test.go b/os/gfile/gfile_z_time_test.go index 37dfaf87e..b40510539 100644 --- a/os/gfile/gfile_z_time_test.go +++ b/os/gfile/gfile_z_time_test.go @@ -1,4 +1,4 @@ -// Copyright 2019 gf Author(https://github.com/gogf/gf). All Rights Reserved. +// Copyright GoFrame Author(https://goframe.org). All Rights Reserved. // // This Source Code Form is subject to the terms of the MIT License. // If a copy of the MIT was not distributed with this file, diff --git a/os/gfpool/gfpool.go b/os/gfpool/gfpool.go index db4e7ecd9..cf91b5565 100644 --- a/os/gfpool/gfpool.go +++ b/os/gfpool/gfpool.go @@ -1,4 +1,4 @@ -// Copyright 2017-2020 gf Author(https://github.com/gogf/gf). All Rights Reserved. +// Copyright GoFrame Author(https://goframe.org). All Rights Reserved. // // This Source Code Form is subject to the terms of the MIT License. // If a copy of the MIT was not distributed with this file, diff --git a/os/gfpool/gfpool_file.go b/os/gfpool/gfpool_file.go index b985722b1..9519322e2 100644 --- a/os/gfpool/gfpool_file.go +++ b/os/gfpool/gfpool_file.go @@ -1,4 +1,4 @@ -// Copyright 2017-2020 gf Author(https://github.com/gogf/gf). All Rights Reserved. +// Copyright GoFrame Author(https://goframe.org). All Rights Reserved. // // This Source Code Form is subject to the terms of the MIT License. // If a copy of the MIT was not distributed with this file, @@ -7,8 +7,9 @@ package gfpool import ( - "errors" "fmt" + "github.com/gogf/gf/errors/gcode" + "github.com/gogf/gf/errors/gerror" "os" "time" ) @@ -41,7 +42,7 @@ func Open(path string, flag int, perm os.FileMode, ttl ...time.Duration) (file * // Stat returns the FileInfo structure describing file. func (f *File) Stat() (os.FileInfo, error) { if f.stat == nil { - return nil, errors.New("file stat is empty") + return nil, gerror.NewCode(gcode.CodeInternalError, "file stat is empty") } return f.stat, nil } diff --git a/os/gfpool/gfpool_pool.go b/os/gfpool/gfpool_pool.go index e83abf6aa..288e18360 100644 --- a/os/gfpool/gfpool_pool.go +++ b/os/gfpool/gfpool_pool.go @@ -1,4 +1,4 @@ -// Copyright 2017-2020 gf Author(https://github.com/gogf/gf). All Rights Reserved. +// Copyright GoFrame Author(https://goframe.org). All Rights Reserved. // // This Source Code Form is subject to the terms of the MIT License. // If a copy of the MIT was not distributed with this file, diff --git a/os/gfsnotify/gfsnotify.go b/os/gfsnotify/gfsnotify.go index e4a605473..e66377d2c 100644 --- a/os/gfsnotify/gfsnotify.go +++ b/os/gfsnotify/gfsnotify.go @@ -1,4 +1,4 @@ -// Copyright 2018 gf Author(https://github.com/gogf/gf). All Rights Reserved. +// Copyright GoFrame Author(https://goframe.org). All Rights Reserved. // // This Source Code Form is subject to the terms of the MIT License. // If a copy of the MIT was not distributed with this file, @@ -8,9 +8,10 @@ package gfsnotify import ( - "errors" - "fmt" + "context" "github.com/gogf/gf/container/gset" + "github.com/gogf/gf/errors/gcode" + "github.com/gogf/gf/errors/gerror" "github.com/gogf/gf/internal/intlog" "sync" "time" @@ -88,16 +89,16 @@ func New() (*Watcher, error) { if watcher, err := fsnotify.NewWatcher(); err == nil { w.watcher = watcher } else { - intlog.Printf("New watcher failed: %v", err) + intlog.Printf(context.TODO(), "New watcher failed: %v", err) return nil, err } - w.startWatchLoop() - w.startEventLoop() + w.watchLoop() + w.eventLoop() return w, nil } -// 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. +// 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) { w, err := getDefaultWatcher() if err != nil { @@ -106,11 +107,11 @@ func Add(path string, callbackFunc func(event *Event), recursive ...bool) (callb return w.Add(path, callbackFunc, recursive...) } -// AddOnce monitors <path> using default watcher with callback function <callbackFunc> only once using unique name <name>. -// If AddOnce is called multiple times with the same <name> parameter, <path> is only added to monitor once. It returns error -// if it's called twice with the same <name>. +// AddOnce monitors `path` using default watcher with callback function `callbackFunc` only once using unique name `name`. +// If AddOnce is called multiple times with the same `name` parameter, `path` is only added to monitor once. It returns error +// if it's called twice with the same `name`. // -// The optional parameter <recursive> specifies whether monitoring the <path> recursively, which is true in default. +// 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) { w, err := getDefaultWatcher() if err != nil { @@ -119,7 +120,7 @@ func AddOnce(name, path string, callbackFunc func(event *Event), recursive ...bo return w.AddOnce(name, path, callbackFunc, recursive...) } -// Remove removes all monitoring callbacks of given <path> from watcher recursively. +// Remove removes all monitoring callbacks of given `path` from watcher recursively. func Remove(path string) error { w, err := getDefaultWatcher() if err != nil { @@ -139,7 +140,7 @@ func RemoveCallback(callbackId int) error { callback = r.(*Callback) } if callback == nil { - return errors.New(fmt.Sprintf(`callback for id %d not found`, callbackId)) + return gerror.NewCodef(gcode.CodeInvalidParameter, `callback for id %d not found`, callbackId) } w.RemoveCallback(callbackId) return nil diff --git a/os/gfsnotify/gfsnotify_event.go b/os/gfsnotify/gfsnotify_event.go index 4811b6235..f91638ca5 100644 --- a/os/gfsnotify/gfsnotify_event.go +++ b/os/gfsnotify/gfsnotify_event.go @@ -1,4 +1,4 @@ -// Copyright 2018 gf Author(https://github.com/gogf/gf). All Rights Reserved. +// Copyright GoFrame Author(https://goframe.org). All Rights Reserved. // // ThIs Source Code Form Is subject to the terms of the MIT License. // If a copy of the MIT was not dIstributed with thIs file, diff --git a/os/gfsnotify/gfsnotify_filefunc.go b/os/gfsnotify/gfsnotify_filefunc.go index 7518ca19f..3be6fbde8 100644 --- a/os/gfsnotify/gfsnotify_filefunc.go +++ b/os/gfsnotify/gfsnotify_filefunc.go @@ -1,4 +1,4 @@ -// Copyright 2018 gf Author(https://github.com/gogf/gf). All Rights Reserved. +// Copyright GoFrame Author(https://goframe.org). All Rights Reserved. // // ThIs Source Code Form Is subject to the terms of the MIT License. // If a copy of the MIT was not dIstributed with thIs file, @@ -24,7 +24,7 @@ func fileDir(path string) string { return filepath.Dir(path) } -// fileRealPath converts the given <path> to its absolute path +// fileRealPath converts the given `path` to its absolute path // and checks if the file path exists. // If the file does not exist, return an empty string. func fileRealPath(path string) string { @@ -38,7 +38,7 @@ func fileRealPath(path string) string { return p } -// fileExists checks whether given <path> exist. +// fileExists checks whether given `path` exist. func fileExists(path string) bool { if stat, err := os.Stat(path); stat != nil && !os.IsNotExist(err) { return true @@ -46,7 +46,7 @@ func fileExists(path string) bool { return false } -// fileIsDir checks whether given <path> a directory. +// fileIsDir checks whether given `path` a directory. func fileIsDir(path string) bool { s, err := os.Stat(path) if err != nil { @@ -55,7 +55,7 @@ func fileIsDir(path string) bool { return s.IsDir() } -// fileAllDirs returns all sub-folders including itself of given <path> recursively. +// fileAllDirs returns all sub-folders including itself of given `path` recursively. func fileAllDirs(path string) (list []string) { list = []string{path} file, err := os.Open(path) @@ -78,8 +78,8 @@ func fileAllDirs(path string) (list []string) { return } -// fileScanDir returns all sub-files with absolute paths of given <path>, -// It scans directory recursively if given parameter <recursive> is true. +// fileScanDir returns all sub-files with absolute paths of given `path`, +// It scans directory recursively if given parameter `recursive` is true. func fileScanDir(path string, pattern string, recursive ...bool) ([]string, error) { list, err := doFileScanDir(path, pattern, recursive...) if err != nil { @@ -94,10 +94,10 @@ func fileScanDir(path string, pattern string, recursive ...bool) ([]string, erro // doFileScanDir is an internal method which scans directory // and returns the absolute path list of files that are not sorted. // -// The pattern parameter <pattern> supports multiple file name patterns, +// The pattern parameter `pattern` supports multiple file name patterns, // using the ',' symbol to separate multiple patterns. // -// It scans directory recursively if given parameter <recursive> is true. +// It scans directory recursively if given parameter `recursive` is true. func doFileScanDir(path string, pattern string, recursive ...bool) ([]string, error) { list := ([]string)(nil) file, err := os.Open(path) diff --git a/os/gfsnotify/gfsnotify_watcher.go b/os/gfsnotify/gfsnotify_watcher.go index 8b3e07636..810e8c1aa 100644 --- a/os/gfsnotify/gfsnotify_watcher.go +++ b/os/gfsnotify/gfsnotify_watcher.go @@ -1,4 +1,4 @@ -// Copyright 2018 gf Author(https://github.com/gogf/gf). All Rights Reserved. +// Copyright GoFrame Author(https://goframe.org). All Rights Reserved. // // This Source Code Form is subject to the terms of the MIT License. // If a copy of the MIT was not distributed with this file, @@ -7,26 +7,28 @@ package gfsnotify import ( - "errors" - "fmt" + "context" + "github.com/gogf/gf/errors/gcode" + "github.com/gogf/gf/errors/gerror" "github.com/gogf/gf/internal/intlog" "github.com/gogf/gf/container/glist" ) -// Add monitors <path> with callback function <callbackFunc> to the watcher. -// The optional parameter <recursive> specifies whether monitoring the <path> recursively, +// Add monitors `path` with callback function `callbackFunc` to the watcher. +// The optional parameter `recursive` specifies whether monitoring the `path` recursively, // which is true in default. func (w *Watcher) Add(path string, callbackFunc func(event *Event), recursive ...bool) (callback *Callback, err error) { return w.AddOnce("", path, callbackFunc, recursive...) } -// AddOnce monitors <path> with callback function <callbackFunc> only once using unique name -// <name> to the watcher. If AddOnce is called multiple times with the same <name> parameter, -// <path> is only added to monitor once. -// It returns error if it's called twice with the same <name>. +// AddOnce monitors `path` with callback function `callbackFunc` only once using unique name +// `name` to the watcher. If AddOnce is called multiple times with the same `name` parameter, +// `path` is only added to monitor once. // -// The optional parameter <recursive> specifies whether monitoring the <path> recursively, +// It returns error if it's called twice with the same `name`. +// +// The optional parameter `recursive` specifies whether monitoring the `path` recursively, // which is true in default. func (w *Watcher) AddOnce(name, path string, callbackFunc func(event *Event), recursive ...bool) (callback *Callback, err error) { w.nameSet.AddIfNotExistFuncLock(name, func() bool { @@ -45,9 +47,9 @@ func (w *Watcher) AddOnce(name, path string, callbackFunc func(event *Event), re for _, subPath := range fileAllDirs(path) { if fileIsDir(subPath) { if err := w.watcher.Add(subPath); err != nil { - intlog.Error(err) + intlog.Error(context.TODO(), err) } else { - intlog.Printf("watcher adds monitor for: %s", subPath) + intlog.Printf(context.TODO(), "watcher adds monitor for: %s", subPath) } } } @@ -61,10 +63,11 @@ func (w *Watcher) AddOnce(name, path string, callbackFunc func(event *Event), re } // addWithCallbackFunc adds the path to underlying monitor, creates and returns a callback object. +// Very note that if it calls multiple times with the same `path`, the latest one will overwrite the previous one. func (w *Watcher) addWithCallbackFunc(name, path string, callbackFunc func(event *Event), recursive ...bool) (callback *Callback, err error) { // Check and convert the given path to absolute path. if t := fileRealPath(path); t == "" { - return nil, errors.New(fmt.Sprintf(`"%s" does not exist`, path)) + return nil, gerror.NewCodef(gcode.CodeInvalidParameter, `"%s" does not exist`, path) } else { path = t } @@ -92,14 +95,12 @@ func (w *Watcher) addWithCallbackFunc(name, path string, callbackFunc func(event }) // Add the path to underlying monitor. if err := w.watcher.Add(path); err != nil { - intlog.Error(err) + intlog.Error(context.TODO(), err) } else { - intlog.Printf("watcher adds monitor for: %s", path) + intlog.Printf(context.TODO(), "watcher adds monitor for: %s", path) } // Add the callback to global callback map. callbackIdMap.Set(callback.Id, callback) - - //intlog.Print("addWithCallbackFunc", name, path, callback.recursive) return } @@ -107,12 +108,12 @@ func (w *Watcher) addWithCallbackFunc(name, path string, callbackFunc func(event func (w *Watcher) Close() { w.events.Close() if err := w.watcher.Close(); err != nil { - intlog.Error(err) + intlog.Error(context.TODO(), err) } close(w.closeChan) } -// Remove removes monitor and all callbacks associated with the <path> recursively. +// Remove removes monitor and all callbacks associated with the `path` recursively. func (w *Watcher) Remove(path string) error { // Firstly remove the callbacks of the path. if r := w.callbacks.Remove(path); r != nil { @@ -130,7 +131,7 @@ func (w *Watcher) Remove(path string) error { for _, subPath := range subPaths { if w.checkPathCanBeRemoved(subPath) { if err := w.watcher.Remove(subPath); err != nil { - intlog.Error(err) + intlog.Error(context.TODO(), err) } } } diff --git a/os/gfsnotify/gfsnotify_watcher_loop.go b/os/gfsnotify/gfsnotify_watcher_loop.go index c9ada4006..3c47a7f0a 100644 --- a/os/gfsnotify/gfsnotify_watcher_loop.go +++ b/os/gfsnotify/gfsnotify_watcher_loop.go @@ -1,4 +1,4 @@ -// Copyright 2018 gf Author(https://github.com/gogf/gf). All Rights Reserved. +// Copyright GoFrame Author(https://goframe.org). All Rights Reserved. // // This Source Code Form is subject to the terms of the MIT License. // If a copy of the MIT was not distributed with this file, @@ -7,12 +7,13 @@ package gfsnotify import ( + "context" "github.com/gogf/gf/container/glist" "github.com/gogf/gf/internal/intlog" ) -// startWatchLoop starts the loop for event listening fro underlying inotify monitor. -func (w *Watcher) startWatchLoop() { +// watchLoop starts the loop for event listening from underlying inotify monitor. +func (w *Watcher) watchLoop() { go func() { for { select { @@ -34,53 +35,14 @@ func (w *Watcher) startWatchLoop() { }, repeatEventFilterDuration) case err := <-w.watcher.Errors: - intlog.Error(err) + intlog.Error(context.TODO(), err) } } }() } -// getCallbacks searches and returns all callbacks with given <path>. -// It also searches its parent for callbacks if they're recursive. -func (w *Watcher) getCallbacks(path string) (callbacks []*Callback) { - // Firstly add the callbacks of itself. - if v := w.callbacks.Get(path); v != nil { - for _, v := range v.(*glist.List).FrontAll() { - callback := v.(*Callback) - callbacks = append(callbacks, callback) - } - } - // Secondly searches its parent for callbacks. - dirPath := fileDir(path) - if v := w.callbacks.Get(dirPath); v != nil { - for _, v := range v.(*glist.List).FrontAll() { - callback := v.(*Callback) - if callback.recursive { - callbacks = append(callbacks, callback) - } - } - } - // Lastly searches the parent recursively for callbacks. - for { - parentDirPath := fileDir(dirPath) - if parentDirPath == dirPath { - break - } - if v := w.callbacks.Get(parentDirPath); v != nil { - for _, v := range v.(*glist.List).FrontAll() { - callback := v.(*Callback) - if callback.recursive { - callbacks = append(callbacks, callback) - } - } - } - dirPath = parentDirPath - } - return -} - -// startEventLoop is the core event handler. -func (w *Watcher) startEventLoop() { +// eventLoop is the core event handler. +func (w *Watcher) eventLoop() { go func() { for { if v := w.events.Pop(); v != nil { @@ -99,9 +61,9 @@ func (w *Watcher) startEventLoop() { // It adds the path back to monitor. // We need no worry about the repeat adding. if err := w.watcher.Add(event.Path); err != nil { - intlog.Error(err) + intlog.Error(context.TODO(), err) } else { - intlog.Printf("fake remove event, watcher re-adds monitor for: %s", event.Path) + intlog.Printf(context.TODO(), "fake remove event, watcher re-adds monitor for: %s", event.Path) } // Change the event to RENAME, which means it renames itself to its origin name. event.Op = RENAME @@ -115,9 +77,9 @@ func (w *Watcher) startEventLoop() { // It might lost the monitoring for the path, so we add the path back to monitor. // We need no worry about the repeat adding. if err := w.watcher.Add(event.Path); err != nil { - intlog.Error(err) + intlog.Error(context.TODO(), err) } else { - intlog.Printf("fake rename event, watcher re-adds monitor for: %s", event.Path) + intlog.Printf(context.TODO(), "fake rename event, watcher re-adds monitor for: %s", event.Path) } // Change the event to CHMOD. event.Op = CHMOD @@ -133,18 +95,18 @@ func (w *Watcher) startEventLoop() { for _, subPath := range fileAllDirs(event.Path) { if fileIsDir(subPath) { if err := w.watcher.Add(subPath); err != nil { - intlog.Error(err) + intlog.Error(context.TODO(), err) } else { - intlog.Printf("folder creation event, watcher adds monitor for: %s", subPath) + intlog.Printf(context.TODO(), "folder creation event, watcher adds monitor for: %s", subPath) } } } } else { // If it's a file, it directly adds it to monitor. if err := w.watcher.Add(event.Path); err != nil { - intlog.Error(err) + intlog.Error(context.TODO(), err) } else { - intlog.Printf("file creation event, watcher adds monitor for: %s", event.Path) + intlog.Printf(context.TODO(), "file creation event, watcher adds monitor for: %s", event.Path) } } @@ -171,3 +133,42 @@ func (w *Watcher) startEventLoop() { } }() } + +// getCallbacks searches and returns all callbacks with given `path`. +// It also searches its parents for callbacks if they're recursive. +func (w *Watcher) getCallbacks(path string) (callbacks []*Callback) { + // Firstly add the callbacks of itself. + if v := w.callbacks.Get(path); v != nil { + for _, v := range v.(*glist.List).FrontAll() { + callback := v.(*Callback) + callbacks = append(callbacks, callback) + } + } + // Secondly searches its direct parent for callbacks. + // It is special handling here, which is the different between `recursive` and `not recursive` logic + // for direct parent folder of `path` that events are from. + dirPath := fileDir(path) + if v := w.callbacks.Get(dirPath); v != nil { + for _, v := range v.(*glist.List).FrontAll() { + callback := v.(*Callback) + callbacks = append(callbacks, callback) + } + } + // Lastly searches all the parents of directory of `path` recursively for callbacks. + for { + parentDirPath := fileDir(dirPath) + if parentDirPath == dirPath { + break + } + if v := w.callbacks.Get(parentDirPath); v != nil { + for _, v := range v.(*glist.List).FrontAll() { + callback := v.(*Callback) + if callback.recursive { + callbacks = append(callbacks, callback) + } + } + } + dirPath = parentDirPath + } + return +} diff --git a/os/gfsnotify/gfsnotify_z_unit_test.go b/os/gfsnotify/gfsnotify_z_unit_test.go index 341b02050..e1e5b1f95 100644 --- a/os/gfsnotify/gfsnotify_z_unit_test.go +++ b/os/gfsnotify/gfsnotify_z_unit_test.go @@ -1,4 +1,4 @@ -// Copyright 2019 gf Author(https://github.com/gogf/gf). All Rights Reserved. +// Copyright GoFrame Author(https://goframe.org). All Rights Reserved. // // This Source Code Form is subject to the terms of the MIT License. // If a copy of the MIT was not distributed with this file, @@ -7,6 +7,7 @@ package gfsnotify_test import ( + "github.com/gogf/gf/container/garray" "testing" "time" @@ -191,3 +192,29 @@ func TestWatcher_Callback2(t *testing.T) { t.Assert(v2.Val(), 2) }) } + +func TestWatcher_WatchFolderWithoutRecursively(t *testing.T) { + gtest.C(t, func(t *gtest.T) { + var ( + err error + array = garray.New(true) + dirPath = gfile.TempDir(gtime.TimestampNanoStr()) + ) + err = gfile.Mkdir(dirPath) + t.AssertNil(err) + + _, err = gfsnotify.Add(dirPath, func(event *gfsnotify.Event) { + //fmt.Println(event.String()) + array.Append(1) + }, false) + t.AssertNil(err) + time.Sleep(time.Millisecond * 100) + t.Assert(array.Len(), 0) + + f, err := gfile.Create(gfile.Join(dirPath, "1")) + t.AssertNil(err) + t.AssertNil(f.Close()) + time.Sleep(time.Millisecond * 100) + t.Assert(array.Len(), 1) + }) +} diff --git a/os/glog/glog.go b/os/glog/glog.go index 8389fefe7..afa420db8 100644 --- a/os/glog/glog.go +++ b/os/glog/glog.go @@ -1,4 +1,4 @@ -// Copyright 2017 gf Author(https://github.com/gogf/gf). All Rights Reserved. +// Copyright GoFrame Author(https://goframe.org). All Rights Reserved. // // This Source Code Form is subject to the terms of the MIT License. // If a copy of the MIT was not distributed with this file, @@ -12,12 +12,16 @@ import ( "github.com/gogf/gf/os/grpool" ) +const ( + commandEnvKeyForDebug = "gf.glog.debug" +) + var ( // Default logger object, for package method usage. logger = New() // Goroutine pool for async logging output. - // It uses only one asynchronize worker to ensure log sequence. + // It uses only one asynchronous worker to ensure log sequence. asyncPool = grpool.New(1) // defaultDebug enables debug level or not in default, @@ -26,11 +30,11 @@ var ( ) func init() { - defaultDebug = gcmd.GetWithEnv("gf.glog.debug", true).Bool() + defaultDebug = gcmd.GetOptWithEnv(commandEnvKeyForDebug, true).Bool() SetDebug(defaultDebug) } -// Default returns the default logger. +// DefaultLogger returns the default logger. func DefaultLogger() *Logger { return logger } diff --git a/os/glog/glog_api.go b/os/glog/glog_api.go index 8cc66a442..4a26d84b7 100644 --- a/os/glog/glog_api.go +++ b/os/glog/glog_api.go @@ -1,4 +1,4 @@ -// Copyright 2017 gf Author(https://github.com/gogf/gf). All Rights Reserved. +// Copyright GoFrame Author(https://goframe.org). All Rights Reserved. // // This Source Code Form is subject to the terms of the MIT License. // If a copy of the MIT was not distributed with this file, @@ -6,18 +6,19 @@ package glog -// Print prints <v> with newline using fmt.Sprintln. -// The parameter <v> can be multiple variables. +// Print prints `v` with newline using fmt.Sprintln. +// The parameter `v` can be multiple variables. func Print(v ...interface{}) { logger.Print(v...) } -// Printf prints <v> with format <format> using fmt.Sprintf. -// The parameter <v> can be multiple variables. +// Printf prints `v` with format `format` using fmt.Sprintf. +// The parameter `v` can be multiple variables. func Printf(format string, v ...interface{}) { logger.Printf(format, v...) } +// Println is alias of Print. // See Print. func Println(v ...interface{}) { logger.Println(v...) diff --git a/os/glog/glog_chaining.go b/os/glog/glog_chaining.go index b62eb7c74..90d93c0af 100644 --- a/os/glog/glog_chaining.go +++ b/os/glog/glog_chaining.go @@ -1,4 +1,4 @@ -// Copyright 2017 gf Author(https://github.com/gogf/gf). All Rights Reserved. +// Copyright GoFrame Author(https://goframe.org). All Rights Reserved. // // This Source Code Form is subject to the terms of the MIT License. // If a copy of the MIT was not distributed with this file, @@ -11,38 +11,38 @@ import ( "io" ) -// Expose returns the default logger of glog. +// Expose returns the default logger of package glog. func Expose() *Logger { return logger } // Ctx is a chaining function, // which sets the context for current logging. -// The parameter <keys> specifies the context keys for retrieving values. +// The parameter `keys` specifies the context keys for retrieving values. func Ctx(ctx context.Context, keys ...interface{}) *Logger { return logger.Ctx(ctx, keys...) } // To is a chaining function, -// which redirects current logging content output to the sepecified <writer>. +// which redirects current logging content output to the sepecified `writer`. func To(writer io.Writer) *Logger { return logger.To(writer) } // Path is a chaining function, -// which sets the directory path to <path> for current logging content output. +// which sets the directory path to `path` for current logging content output. func Path(path string) *Logger { return logger.Path(path) } // Cat is a chaining function, -// which sets the category to <category> for current logging content output. +// which sets the category to `category` for current logging content output. func Cat(category string) *Logger { return logger.Cat(category) } // File is a chaining function, -// which sets file name <pattern> for the current logging content output. +// which sets file name `pattern` for the current logging content output. func File(pattern string) *Logger { return logger.File(pattern) } @@ -78,7 +78,7 @@ func StackWithFilter(filter string) *Logger { return logger.StackWithFilter(filter) } -// StdPrint is a chaining function, +// Stdout is a chaining function, // which enables/disables stdout for the current logging content output. // It's enabled in default. func Stdout(enabled ...bool) *Logger { @@ -94,7 +94,7 @@ func Header(enabled ...bool) *Logger { // Line is a chaining function, // which enables/disables printing its caller file along with its line number. -// The parameter <long> specified whether print the long absolute file path, eg: /a/b/c/d.go:23. +// The parameter `long` specified whether print the long absolute file path, eg: /a/b/c/d.go:23. func Line(long ...bool) *Logger { return logger.Line(long...) } diff --git a/os/glog/glog_config.go b/os/glog/glog_config.go index 9dcab7eba..101646e2e 100644 --- a/os/glog/glog_config.go +++ b/os/glog/glog_config.go @@ -1,4 +1,4 @@ -// Copyright 2017 gf Author(https://github.com/gogf/gf). All Rights Reserved. +// Copyright GoFrame Author(https://goframe.org). All Rights Reserved. // // This Source Code Form is subject to the terms of the MIT License. // If a copy of the MIT was not distributed with this file, @@ -31,8 +31,8 @@ func GetPath() string { return logger.GetPath() } -// SetFile sets the file name <pattern> for file logging. -// Datetime pattern can be used in <pattern>, eg: access-{Ymd}.log. +// SetFile sets the file name `pattern` for file logging. +// Datetime pattern can be used in `pattern`, eg: access-{Ymd}.log. // The default file name pattern is: Y-m-d.log, eg: 2018-01-01.log func SetFile(pattern string) { logger.SetFile(pattern) @@ -48,9 +48,9 @@ func GetLevel() int { return logger.GetLevel() } -// SetWriter sets the customized logging <writer> for logging. -// The <writer> object should implements the io.Writer interface. -// Developer can use customized logging <writer> to redirect logging output to another service, +// SetWriter sets the customized logging `writer` for logging. +// The `writer` object should implements the io.Writer interface. +// Developer can use customized logging `writer` to redirect logging output to another service, // eg: kafka, mysql, mongodb, etc. func SetWriter(writer io.Writer) { logger.SetWriter(writer) @@ -113,13 +113,13 @@ func GetCtxKeys() []interface{} { } // PrintStack prints the caller stack, -// the optional parameter <skip> specify the skipped stack offset from the end point. +// the optional parameter `skip` specify the skipped stack offset from the end point. func PrintStack(skip ...int) { logger.PrintStack(skip...) } // GetStack returns the caller stack content, -// the optional parameter <skip> specify the skipped stack offset from the end point. +// the optional parameter `skip` specify the skipped stack offset from the end point. func GetStack(skip ...int) string { return logger.GetStack(skip...) } @@ -148,3 +148,13 @@ func SetLevelPrefixes(prefixes map[int]string) { func GetLevelPrefix(level int) string { return logger.GetLevelPrefix(level) } + +// SetHandlers sets the logging handlers for default logger. +func SetHandlers(handlers ...Handler) { + logger.SetHandlers(handlers...) +} + +//SetWriterColorEnable sets the file logging with color +func SetWriterColorEnable(enabled bool) { + logger.SetWriterColorEnable(enabled) +} diff --git a/os/glog/glog_instance.go b/os/glog/glog_instance.go index 400cdde9b..49ca83264 100644 --- a/os/glog/glog_instance.go +++ b/os/glog/glog_instance.go @@ -1,4 +1,4 @@ -// Copyright 2017 gf Author(https://github.com/gogf/gf). All Rights Reserved. +// Copyright GoFrame Author(https://goframe.org). All Rights Reserved. // // This Source Code Form is subject to the terms of the MIT License. // If a copy of the MIT was not distributed with this file, @@ -9,7 +9,7 @@ package glog import "github.com/gogf/gf/container/gmap" const ( - // Default group name for instance usage. + // DefaultName is the default group name for instance usage. DefaultName = "default" ) @@ -19,7 +19,7 @@ var ( ) // Instance returns an instance of Logger with default settings. -// The parameter <name> is the name for the instance. +// The parameter `name` is the name for the instance. func Instance(name ...string) *Logger { key := DefaultName if len(name) > 0 && name[0] != "" { diff --git a/os/glog/glog_logger.go b/os/glog/glog_logger.go index 4af7bf1f9..b52b860b1 100644 --- a/os/glog/glog_logger.go +++ b/os/glog/glog_logger.go @@ -1,4 +1,4 @@ -// Copyright GoFrame Author(https://github.com/gogf/gf). All Rights Reserved. +// Copyright GoFrame Author(https://goframe.org). All Rights Reserved. // // This Source Code Form is subject to the terms of the MIT License. // If a copy of the MIT was not distributed with this file, @@ -10,11 +10,14 @@ import ( "bytes" "context" "fmt" + "github.com/fatih/color" "github.com/gogf/gf/container/gtype" "github.com/gogf/gf/internal/intlog" + "github.com/gogf/gf/os/gctx" "github.com/gogf/gf/os/gfpool" "github.com/gogf/gf/os/gmlock" "github.com/gogf/gf/os/gtimer" + "go.opentelemetry.io/otel/trace" "io" "os" "strings" @@ -37,11 +40,12 @@ type Logger struct { } const ( - defaultFileFormat = `{Y-m-d}.log` - defaultFileFlags = os.O_CREATE | os.O_WRONLY | os.O_APPEND - defaultFilePerm = os.FileMode(0666) - defaultFileExpire = time.Minute - pathFilterKey = "/os/glog/glog" + defaultFileFormat = `{Y-m-d}.log` + defaultFileFlags = os.O_CREATE | os.O_WRONLY | os.O_APPEND + defaultFilePerm = os.FileMode(0666) + defaultFileExpire = time.Minute + pathFilterKey = "/os/glog/glog" + memoryLockPrefixForPrintingToFile = "glog.printToFile:" ) const ( @@ -74,11 +78,11 @@ func NewWithWriter(writer io.Writer) *Logger { // Clone returns a new logger, which is the clone the current logger. // It's commonly used for chaining operations. func (l *Logger) Clone() *Logger { - logger := New() - logger.ctx = l.ctx - logger.config = l.config - logger.parent = l - return logger + newLogger := New() + newLogger.ctx = l.ctx + newLogger.config = l.config + newLogger.parent = l + return newLogger } // getFilePath returns the logging file path. @@ -92,8 +96,8 @@ func (l *Logger) getFilePath(now time.Time) string { return file } -// print prints <s> to defined writer, logging file or passed <std>. -func (l *Logger) print(std io.Writer, lead string, values ...interface{}) { +// print prints `s` to defined writer, logging file or passed `std`. +func (l *Logger) print(ctx context.Context, level int, values ...interface{}) { // Lazy initialize for rotation feature. // It uses atomic reading operation to enhance the performance checking. // It here uses CAP for performance and concurrent safety. @@ -101,137 +105,194 @@ func (l *Logger) print(std io.Writer, lead string, values ...interface{}) { if p.parent != nil { p = p.parent } - if !p.init.Val() && p.init.Cas(false, true) { - // It just initializes once for each logger. - if p.config.RotateSize > 0 || p.config.RotateExpire > 0 { + // It just initializes once for each logger. + if p.config.RotateSize > 0 || p.config.RotateExpire > 0 { + if !p.init.Val() && p.init.Cas(false, true) { gtimer.AddOnce(p.config.RotateCheckInterval, p.rotateChecksTimely) - intlog.Printf("logger rotation initialized: every %s", p.config.RotateCheckInterval.String()) + intlog.Printf(ctx, "logger rotation initialized: every %s", p.config.RotateCheckInterval.String()) } } var ( - now = time.Now() - buffer = bytes.NewBuffer(nil) + now = time.Now() + input = &HandlerInput{ + Logger: l, + Buffer: bytes.NewBuffer(nil), + Ctx: ctx, + Time: now, + Color: defaultLevelColor[level], + Level: level, + handlerIndex: -1, + } ) if l.config.HeaderPrint { // Time. timeFormat := "" if l.config.Flags&F_TIME_DATE > 0 { - timeFormat += "2006-01-02 " + timeFormat += "2006-01-02" } if l.config.Flags&F_TIME_TIME > 0 { - timeFormat += "15:04:05 " + if timeFormat != "" { + timeFormat += " " + } + timeFormat += "15:04:05" } if l.config.Flags&F_TIME_MILLI > 0 { - timeFormat += "15:04:05.000 " + if timeFormat != "" { + timeFormat += " " + } + timeFormat += "15:04:05.000" } if len(timeFormat) > 0 { - buffer.WriteString(now.Format(timeFormat)) - } - // Lead string. - if len(lead) > 0 { - buffer.WriteString(lead) - if len(values) > 0 { - buffer.WriteByte(' ') - } + input.TimeFormat = now.Format(timeFormat) } + + // Level string. + input.LevelFormat = l.getLevelPrefixWithBrackets(level) + // Caller path and Fn name. if l.config.Flags&(F_FILE_LONG|F_FILE_SHORT|F_CALLER_FN) > 0 { - callerPath := "" callerFnName, path, line := gdebug.CallerWithFilter(pathFilterKey, l.config.StSkip) if l.config.Flags&F_CALLER_FN > 0 { - buffer.WriteString(fmt.Sprintf(`[%s] `, callerFnName)) + if len(callerFnName) > 2 { + input.CallerFunc = fmt.Sprintf(`[%s]`, callerFnName) + } } - if l.config.Flags&F_FILE_LONG > 0 { - callerPath = fmt.Sprintf(`%s:%d: `, path, line) + if line >= 0 && len(path) > 1 { + if l.config.Flags&F_FILE_LONG > 0 { + input.CallerPath = fmt.Sprintf(`%s:%d:`, path, line) + } + if l.config.Flags&F_FILE_SHORT > 0 { + input.CallerPath = fmt.Sprintf(`%s:%d:`, gfile.Basename(path), line) + } } - if l.config.Flags&F_FILE_SHORT > 0 { - callerPath = fmt.Sprintf(`%s:%d: `, gfile.Basename(path), line) - } - buffer.WriteString(callerPath) - } // Prefix. if len(l.config.Prefix) > 0 { - buffer.WriteString(l.config.Prefix + " ") + input.Prefix = l.config.Prefix } } // Convert value to string. - var ( - tempStr = "" - valueStr = "" - ) - // Context values. - if l.ctx != nil && len(l.config.CtxKeys) > 0 { - ctxStr := "" - for _, key := range l.config.CtxKeys { - if v := l.ctx.Value(key); v != nil { - if ctxStr != "" { - ctxStr += ", " + if ctx != nil { + // Tracing values. + spanCtx := trace.SpanContextFromContext(ctx) + if traceId := spanCtx.TraceID(); traceId.IsValid() { + input.CtxStr = traceId.String() + } + // Context values. + if len(l.config.CtxKeys) > 0 { + for _, ctxKey := range l.config.CtxKeys { + var ctxValue interface{} + if ctxValue = ctx.Value(ctxKey); ctxValue == nil { + ctxValue = ctx.Value(gctx.StrKey(gconv.String(ctxKey))) + } + if ctxValue != nil { + if input.CtxStr != "" { + input.CtxStr += ", " + } + input.CtxStr += gconv.String(ctxValue) } - ctxStr += fmt.Sprintf("%s: %+v", key, v) } } - if ctxStr != "" { - buffer.WriteString(fmt.Sprintf("{%s} ", ctxStr)) + if input.CtxStr != "" { + input.CtxStr = "{" + input.CtxStr + "}" } } + var tempStr string for _, v := range values { tempStr = gconv.String(v) - if len(valueStr) > 0 { - if valueStr[len(valueStr)-1] == '\n' { + if len(input.Content) > 0 { + if input.Content[len(input.Content)-1] == '\n' { // Remove one blank line(\n\n). if tempStr[0] == '\n' { - valueStr += tempStr[1:] + input.Content += tempStr[1:] } else { - valueStr += tempStr + input.Content += tempStr } } else { - valueStr += " " + tempStr + input.Content += " " + tempStr } } else { - valueStr = tempStr + input.Content = tempStr } } - buffer.WriteString(valueStr + "\n") if l.config.Flags&F_ASYNC > 0 { + input.IsAsync = true err := asyncPool.Add(func() { - l.printToWriter(now, std, buffer) + input.Next() }) if err != nil { - intlog.Error(err) + intlog.Error(ctx, err) } } else { - l.printToWriter(now, std, buffer) + input.Next() } } +// doDefaultPrint outputs the logging content according configuration. +func (l *Logger) doDefaultPrint(ctx context.Context, input *HandlerInput) *bytes.Buffer { + var buffer *bytes.Buffer + if l.config.Writer == nil { + // Allow output to stdout? + if l.config.StdoutPrint { + if buf := l.printToStdout(ctx, input); buf != nil { + buffer = buf + } + } + + // Output content to disk file. + if l.config.Path != "" { + if buf := l.printToFile(ctx, input.Time, input); buf != nil { + buffer = buf + } + } + } else { + // Output to custom writer. + if buf := l.printToWriter(ctx, input); buf != nil { + buffer = buf + } + } + return buffer +} + // printToWriter writes buffer to writer. -func (l *Logger) printToWriter(now time.Time, std io.Writer, buffer *bytes.Buffer) { - if l.config.Writer == nil { - // Output content to disk file. - if l.config.Path != "" { - l.printToFile(now, buffer) - } - // Allow output to stdout? - if l.config.StdoutPrint { - if _, err := std.Write(buffer.Bytes()); err != nil { - intlog.Error(err) - } - } - } else { +func (l *Logger) printToWriter(ctx context.Context, input *HandlerInput) *bytes.Buffer { + if l.config.Writer != nil { + var ( + buffer = input.getRealBuffer(l.config.WriterColorEnable) + ) if _, err := l.config.Writer.Write(buffer.Bytes()); err != nil { - // panic(err) - intlog.Error(err) + intlog.Error(ctx, err) } + return buffer } + return nil +} + +// printToStdout outputs logging content to stdout. +func (l *Logger) printToStdout(ctx context.Context, input *HandlerInput) *bytes.Buffer { + if l.config.StdoutPrint { + var ( + buffer = input.getRealBuffer(true) + ) + // This will lose color in Windows os system. + // if _, err := os.Stdout.Write(input.getRealBuffer(true).Bytes()); err != nil { + + // This will print color in Windows os system. + if _, err := fmt.Fprint(color.Output, buffer.String()); err != nil { + intlog.Error(ctx, err) + } + return buffer + } + return nil } // printToFile outputs logging content to disk file. -func (l *Logger) printToFile(now time.Time, buffer *bytes.Buffer) { +func (l *Logger) printToFile(ctx context.Context, t time.Time, in *HandlerInput) *bytes.Buffer { var ( - logFilePath = l.getFilePath(now) - memoryLockKey = "glog.printToFile:" + logFilePath + buffer = in.getRealBuffer(l.config.WriterColorEnable) + logFilePath = l.getFilePath(t) + memoryLockKey = memoryLockPrefixForPrintingToFile + logFilePath ) gmlock.Lock(memoryLockKey) defer gmlock.Unlock(memoryLockKey) @@ -239,24 +300,25 @@ func (l *Logger) printToFile(now time.Time, buffer *bytes.Buffer) { // Rotation file size checks. if l.config.RotateSize > 0 { if gfile.Size(logFilePath) > l.config.RotateSize { - l.rotateFileBySize(now) + l.rotateFileBySize(t) } } // Logging content outputting to disk file. - if file := l.getFilePointer(logFilePath); file == nil { - intlog.Errorf(`got nil file pointer for: %s`, logFilePath) + if file := l.getFilePointer(ctx, logFilePath); file == nil { + intlog.Errorf(ctx, `got nil file pointer for: %s`, logFilePath) } else { if _, err := file.Write(buffer.Bytes()); err != nil { - intlog.Error(err) + intlog.Error(ctx, err) } if err := file.Close(); err != nil { - intlog.Error(err) + intlog.Error(ctx, err) } } + return buffer } // getFilePointer retrieves and returns a file pointer from file pool. -func (l *Logger) getFilePointer(path string) *gfpool.File { +func (l *Logger) getFilePointer(ctx context.Context, path string) *gfpool.File { file, err := gfpool.Open( path, defaultFileFlags, @@ -265,34 +327,43 @@ func (l *Logger) getFilePointer(path string) *gfpool.File { ) if err != nil { // panic(err) - intlog.Error(err) + intlog.Error(ctx, err) } return file } -// printStd prints content <s> without stack. -func (l *Logger) printStd(lead string, value ...interface{}) { - l.print(os.Stdout, lead, value...) +// getCtx returns the context which is set through chaining operations. +// It returns an empty context if no context set previously. +func (l *Logger) getCtx() context.Context { + if l.ctx != nil { + return l.ctx + } + return context.TODO() } -// printStd prints content <s> with stack check. -func (l *Logger) printErr(lead string, value ...interface{}) { +// printStd prints content `s` without stack. +func (l *Logger) printStd(level int, value ...interface{}) { + l.print(l.getCtx(), level, value...) +} + +// printStd prints content `s` with stack check. +func (l *Logger) printErr(level int, value ...interface{}) { if l.config.StStatus == 1 { if s := l.GetStack(); s != "" { value = append(value, "\nStack:\n"+s) } } // In matter of sequence, do not use stderr here, but use the same stdout. - l.print(os.Stdout, lead, value...) + l.print(l.getCtx(), level, value...) } -// format formats <values> using fmt.Sprintf. +// format formats `values` using fmt.Sprintf. func (l *Logger) format(format string, value ...interface{}) string { return fmt.Sprintf(format, value...) } // PrintStack prints the caller stack, -// the optional parameter <skip> specify the skipped stack offset from the end point. +// the optional parameter `skip` specify the skipped stack offset from the end point. func (l *Logger) PrintStack(skip ...int) { if s := l.GetStack(skip...); s != "" { l.Println("Stack:\n" + s) @@ -302,7 +373,7 @@ func (l *Logger) PrintStack(skip ...int) { } // GetStack returns the caller stack content, -// the optional parameter <skip> specify the skipped stack offset from the end point. +// the optional parameter `skip` specify the skipped stack offset from the end point. func (l *Logger) GetStack(skip ...int) string { stackSkip := l.config.StSkip if len(skip) > 0 { @@ -314,3 +385,8 @@ func (l *Logger) GetStack(skip ...int) string { } return gdebug.StackWithFilters(filters, stackSkip) } + +// GetConfig returns the configuration of current Logger. +func (l *Logger) GetConfig() Config { + return l.config +} diff --git a/os/glog/glog_logger_api.go b/os/glog/glog_logger_api.go index 93aa69cbe..8b211bc1c 100644 --- a/os/glog/glog_logger_api.go +++ b/os/glog/glog_logger_api.go @@ -1,4 +1,4 @@ -// Copyright 2017 gf Author(https://github.com/gogf/gf). All Rights Reserved. +// Copyright GoFrame Author(https://goframe.org). All Rights Reserved. // // This Source Code Form is subject to the terms of the MIT License. // If a copy of the MIT was not distributed with this file, @@ -11,16 +11,16 @@ import ( "os" ) -// Print prints <v> with newline using fmt.Sprintln. -// The parameter <v> can be multiple variables. +// Print prints `v` with newline using fmt.Sprintln. +// The parameter `v` can be multiple variables. func (l *Logger) Print(v ...interface{}) { - l.printStd("", v...) + l.printStd(LEVEL_NONE, v...) } -// Printf prints <v> with format <format> using fmt.Sprintf. -// The parameter <v> can be multiple variables. +// Printf prints `v` with format `format` using fmt.Sprintf. +// The parameter `v` can be multiple variables. func (l *Logger) Printf(format string, v ...interface{}) { - l.printStd("", l.format(format, v...)) + l.printStd(LEVEL_NONE, l.format(format, v...)) } // Println is alias of Print. @@ -31,53 +31,53 @@ func (l *Logger) Println(v ...interface{}) { // Fatal prints the logging content with [FATA] header and newline, then exit the current process. func (l *Logger) Fatal(v ...interface{}) { - l.printErr(l.getLevelPrefixWithBrackets(LEVEL_FATA), v...) + l.printErr(LEVEL_FATA, v...) os.Exit(1) } // Fatalf prints the logging content with [FATA] header, custom format and newline, then exit the current process. func (l *Logger) Fatalf(format string, v ...interface{}) { - l.printErr(l.getLevelPrefixWithBrackets(LEVEL_FATA), l.format(format, v...)) + l.printErr(LEVEL_FATA, l.format(format, v...)) os.Exit(1) } // Panic prints the logging content with [PANI] header and newline, then panics. func (l *Logger) Panic(v ...interface{}) { - l.printErr(l.getLevelPrefixWithBrackets(LEVEL_PANI), v...) + l.printErr(LEVEL_PANI, v...) panic(fmt.Sprint(v...)) } // Panicf prints the logging content with [PANI] header, custom format and newline, then panics. func (l *Logger) Panicf(format string, v ...interface{}) { - l.printErr(l.getLevelPrefixWithBrackets(LEVEL_PANI), l.format(format, v...)) + l.printErr(LEVEL_PANI, l.format(format, v...)) panic(l.format(format, v...)) } // Info prints the logging content with [INFO] header and newline. func (l *Logger) Info(v ...interface{}) { if l.checkLevel(LEVEL_INFO) { - l.printStd(l.getLevelPrefixWithBrackets(LEVEL_INFO), v...) + l.printStd(LEVEL_INFO, v...) } } // Infof prints the logging content with [INFO] header, custom format and newline. func (l *Logger) Infof(format string, v ...interface{}) { if l.checkLevel(LEVEL_INFO) { - l.printStd(l.getLevelPrefixWithBrackets(LEVEL_INFO), l.format(format, v...)) + l.printStd(LEVEL_INFO, l.format(format, v...)) } } // Debug prints the logging content with [DEBU] header and newline. func (l *Logger) Debug(v ...interface{}) { if l.checkLevel(LEVEL_DEBU) { - l.printStd(l.getLevelPrefixWithBrackets(LEVEL_DEBU), v...) + l.printStd(LEVEL_DEBU, v...) } } // Debugf prints the logging content with [DEBU] header, custom format and newline. func (l *Logger) Debugf(format string, v ...interface{}) { if l.checkLevel(LEVEL_DEBU) { - l.printStd(l.getLevelPrefixWithBrackets(LEVEL_DEBU), l.format(format, v...)) + l.printStd(LEVEL_DEBU, l.format(format, v...)) } } @@ -85,7 +85,7 @@ func (l *Logger) Debugf(format string, v ...interface{}) { // It also prints caller stack info if stack feature is enabled. func (l *Logger) Notice(v ...interface{}) { if l.checkLevel(LEVEL_NOTI) { - l.printStd(l.getLevelPrefixWithBrackets(LEVEL_NOTI), v...) + l.printStd(LEVEL_NOTI, v...) } } @@ -93,7 +93,7 @@ func (l *Logger) Notice(v ...interface{}) { // It also prints caller stack info if stack feature is enabled. func (l *Logger) Noticef(format string, v ...interface{}) { if l.checkLevel(LEVEL_NOTI) { - l.printStd(l.getLevelPrefixWithBrackets(LEVEL_NOTI), l.format(format, v...)) + l.printStd(LEVEL_NOTI, l.format(format, v...)) } } @@ -101,7 +101,7 @@ func (l *Logger) Noticef(format string, v ...interface{}) { // It also prints caller stack info if stack feature is enabled. func (l *Logger) Warning(v ...interface{}) { if l.checkLevel(LEVEL_WARN) { - l.printStd(l.getLevelPrefixWithBrackets(LEVEL_WARN), v...) + l.printStd(LEVEL_WARN, v...) } } @@ -109,7 +109,7 @@ func (l *Logger) Warning(v ...interface{}) { // It also prints caller stack info if stack feature is enabled. func (l *Logger) Warningf(format string, v ...interface{}) { if l.checkLevel(LEVEL_WARN) { - l.printStd(l.getLevelPrefixWithBrackets(LEVEL_WARN), l.format(format, v...)) + l.printStd(LEVEL_WARN, l.format(format, v...)) } } @@ -117,7 +117,7 @@ func (l *Logger) Warningf(format string, v ...interface{}) { // It also prints caller stack info if stack feature is enabled. func (l *Logger) Error(v ...interface{}) { if l.checkLevel(LEVEL_ERRO) { - l.printErr(l.getLevelPrefixWithBrackets(LEVEL_ERRO), v...) + l.printErr(LEVEL_ERRO, v...) } } @@ -125,7 +125,7 @@ func (l *Logger) Error(v ...interface{}) { // It also prints caller stack info if stack feature is enabled. func (l *Logger) Errorf(format string, v ...interface{}) { if l.checkLevel(LEVEL_ERRO) { - l.printErr(l.getLevelPrefixWithBrackets(LEVEL_ERRO), l.format(format, v...)) + l.printErr(LEVEL_ERRO, l.format(format, v...)) } } @@ -133,7 +133,7 @@ func (l *Logger) Errorf(format string, v ...interface{}) { // It also prints caller stack info if stack feature is enabled. func (l *Logger) Critical(v ...interface{}) { if l.checkLevel(LEVEL_CRIT) { - l.printErr(l.getLevelPrefixWithBrackets(LEVEL_CRIT), v...) + l.printErr(LEVEL_CRIT, v...) } } @@ -141,11 +141,11 @@ func (l *Logger) Critical(v ...interface{}) { // It also prints caller stack info if stack feature is enabled. func (l *Logger) Criticalf(format string, v ...interface{}) { if l.checkLevel(LEVEL_CRIT) { - l.printErr(l.getLevelPrefixWithBrackets(LEVEL_CRIT), l.format(format, v...)) + l.printErr(LEVEL_CRIT, l.format(format, v...)) } } -// checkLevel checks whether the given <level> could be output. +// checkLevel checks whether the given `level` could be output. func (l *Logger) checkLevel(level int) bool { return l.config.Level&level > 0 } diff --git a/os/glog/glog_logger_chaining.go b/os/glog/glog_logger_chaining.go index 3c0e88eb4..89351df3f 100644 --- a/os/glog/glog_logger_chaining.go +++ b/os/glog/glog_logger_chaining.go @@ -1,4 +1,4 @@ -// Copyright 2017 gf Author(https://github.com/gogf/gf). All Rights Reserved. +// Copyright GoFrame Author(https://goframe.org). All Rights Reserved. // // This Source Code Form is subject to the terms of the MIT License. // If a copy of the MIT was not distributed with this file, @@ -34,7 +34,7 @@ func (l *Logger) Ctx(ctx context.Context, keys ...interface{}) *Logger { } // To is a chaining function, -// which redirects current logging content output to the specified <writer>. +// which redirects current logging content output to the specified `writer`. func (l *Logger) To(writer io.Writer) *Logger { logger := (*Logger)(nil) if l.parent == nil { @@ -47,9 +47,9 @@ func (l *Logger) To(writer io.Writer) *Logger { } // Path is a chaining function, -// which sets the directory path to <path> for current logging content output. +// which sets the directory path to `path` for current logging content output. // -// Note that the parameter <path> is a directory path, not a file path. +// Note that the parameter `path` is a directory path, not a file path. func (l *Logger) Path(path string) *Logger { logger := (*Logger)(nil) if l.parent == nil { @@ -60,15 +60,15 @@ func (l *Logger) Path(path string) *Logger { if path != "" { if err := logger.SetPath(path); err != nil { // panic(err) - intlog.Error(err) + intlog.Error(l.getCtx(), err) } } return logger } // Cat is a chaining function, -// which sets the category to <category> for current logging content output. -// Param <category> can be hierarchical, eg: module/user. +// which sets the category to `category` for current logging content output. +// Param `category` can be hierarchical, eg: module/user. func (l *Logger) Cat(category string) *Logger { logger := (*Logger)(nil) if l.parent == nil { @@ -79,14 +79,14 @@ 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) - intlog.Error(err) + intlog.Error(l.getCtx(), err) } } return logger } // File is a chaining function, -// which sets file name <pattern> for the current logging content output. +// which sets file name `pattern` for the current logging content output. func (l *Logger) File(file string) *Logger { logger := (*Logger)(nil) if l.parent == nil { @@ -122,7 +122,7 @@ func (l *Logger) LevelStr(levelStr string) *Logger { } if err := logger.SetLevelStr(levelStr); err != nil { // panic(err) - intlog.Error(err) + intlog.Error(l.getCtx(), err) } return logger } @@ -181,7 +181,7 @@ func (l *Logger) Stdout(enabled ...bool) *Logger { } else { logger = l } - // stdout printing is enabled if <enabled> is not passed. + // stdout printing is enabled if `enabled` is not passed. if len(enabled) > 0 && !enabled[0] { logger.config.StdoutPrint = false } else { @@ -200,7 +200,7 @@ func (l *Logger) Header(enabled ...bool) *Logger { } else { logger = l } - // header is enabled if <enabled> is not passed. + // header is enabled if `enabled` is not passed. if len(enabled) > 0 && !enabled[0] { logger.SetHeaderPrint(false) } else { @@ -211,7 +211,7 @@ func (l *Logger) Header(enabled ...bool) *Logger { // Line is a chaining function, // which enables/disables printing its caller file path along with its line number. -// The parameter <long> specified whether print the long absolute file path, eg: /a/b/c/d.go:23, +// The parameter `long` specified whether print the long absolute file path, eg: /a/b/c/d.go:23, // or else short one: d.go:23. func (l *Logger) Line(long ...bool) *Logger { logger := (*Logger)(nil) @@ -237,7 +237,7 @@ func (l *Logger) Async(enabled ...bool) *Logger { } else { logger = l } - // async feature is enabled if <enabled> is not passed. + // async feature is enabled if `enabled` is not passed. if len(enabled) > 0 && !enabled[0] { logger.SetAsync(false) } else { diff --git a/os/glog/glog_logger_color.go b/os/glog/glog_logger_color.go new file mode 100644 index 000000000..98bf966c5 --- /dev/null +++ b/os/glog/glog_logger_color.go @@ -0,0 +1,53 @@ +// Copyright GoFrame Author(https://goframe.org). All Rights Reserved. +// +// This Source Code Form is subject to the terms of the MIT License. +// If a copy of the MIT was not distributed with this file, +// You can obtain one at https://github.com/gogf/gf. + +package glog + +import "github.com/fatih/color" + +const ( + COLOR_BLACK = 30 + iota + COLOR_RED + COLOR_GREEN + COLOR_YELLOW + COLOR_BLUE + COLOR_MAGENTA + COLOR_CYAN + COLOR_WHITE +) + +// Foreground Hi-Intensity text colors +const ( + COLOR_HI_BLACK = 90 + iota + COLOR_HI_RED + COLOR_HI_GREEN + COLOR_HI_YELLOW + COLOR_HI_BLUE + COLOR_HI_MAGENTA + COLOR_HI_CYAN + COLOR_HI_WHITE +) + +// defaultLevelColor defines the default level and its mapping prefix string. +var defaultLevelColor = map[int]int{ + LEVEL_DEBU: COLOR_YELLOW, + LEVEL_INFO: COLOR_GREEN, + LEVEL_NOTI: COLOR_CYAN, + LEVEL_WARN: COLOR_MAGENTA, + LEVEL_ERRO: COLOR_RED, + LEVEL_CRIT: COLOR_HI_RED, + LEVEL_PANI: COLOR_HI_RED, + LEVEL_FATA: COLOR_HI_RED, +} + +// getColoredStr returns a string that is colored by given color. +func (l *Logger) getColoredStr(c int, s string) string { + return color.New(color.Attribute(c)).Sprint(s) +} + +func (l *Logger) getColorByLevel(level int) int { + return defaultLevelColor[level] +} diff --git a/os/glog/glog_logger_config.go b/os/glog/glog_logger_config.go index f9cbea08c..a14181e67 100644 --- a/os/glog/glog_logger_config.go +++ b/os/glog/glog_logger_config.go @@ -1,4 +1,4 @@ -// Copyright 2017 gf Author(https://github.com/gogf/gf). All Rights Reserved. +// Copyright GoFrame Author(https://goframe.org). All Rights Reserved. // // This Source Code Form is subject to the terms of the MIT License. // If a copy of the MIT was not distributed with this file, @@ -7,38 +7,42 @@ package glog import ( - "errors" - "fmt" + "github.com/gogf/gf/errors/gcode" + "github.com/gogf/gf/os/gctx" + "io" + "strings" + "time" + + "github.com/gogf/gf/errors/gerror" "github.com/gogf/gf/internal/intlog" "github.com/gogf/gf/os/gfile" "github.com/gogf/gf/util/gconv" "github.com/gogf/gf/util/gutil" - "io" - "strings" - "time" ) // Config is the configuration object for logger. type Config struct { - Writer io.Writer // Customized io.Writer. - Flags int // Extra flags for logging output features. - Path string // Logging directory path. - File string // Format for logging file. - Level int // Output level. - Prefix string // Prefix string for every logging content. - StSkip int // Skip count for stack. - StStatus int // Stack status(1: enabled - default; 0: disabled) - StFilter string // Stack string filter. - CtxKeys []interface{} // Context keys for logging, which is used for value retrieving from context. - HeaderPrint bool `c:"header"` // Print header or not(true in default). - StdoutPrint bool `c:"stdout"` // Output to stdout or not(true in default). - LevelPrefixes map[int]string // Logging level to its prefix string mapping. - RotateSize int64 // Rotate the logging file if its size > 0 in bytes. - RotateExpire time.Duration // Rotate the logging file if its mtime exceeds this duration. - RotateBackupLimit int // Max backup for rotated files, default is 0, means no backups. - RotateBackupExpire time.Duration // Max expire for rotated files, which is 0 in default, means no expiration. - RotateBackupCompress int // Compress level for rotated files using gzip algorithm. It's 0 in default, means no compression. - RotateCheckInterval time.Duration // Asynchronizely checks the backups and expiration at intervals. It's 1 hour in default. + Handlers []Handler `json:"-"` // Logger handlers which implement feature similar as middleware. + Writer io.Writer `json:"-"` // Customized io.Writer. + Flags int `json:"flags"` // Extra flags for logging output features. + Path string `json:"path"` // Logging directory path. + File string `json:"file"` // Format pattern for logging file. + Level int `json:"level"` // Output level. + Prefix string `json:"prefix"` // Prefix string for every logging content. + StSkip int `json:"stSkip"` // Skipping count for stack. + StStatus int `json:"stStatus"` // Stack status(1: enabled - default; 0: disabled) + StFilter string `json:"stFilter"` // Stack string filter. + CtxKeys []interface{} `json:"ctxKeys"` // Context keys for logging, which is used for value retrieving from context. + HeaderPrint bool `json:"header"` // Print header or not(true in default). + StdoutPrint bool `json:"stdout"` // Output to stdout or not(true in default). + LevelPrefixes map[int]string `json:"levelPrefixes"` // Logging level to its prefix string mapping. + RotateSize int64 `json:"rotateSize"` // Rotate the logging file if its size > 0 in bytes. + RotateExpire time.Duration `json:"rotateExpire"` // Rotate the logging file if its mtime exceeds this duration. + RotateBackupLimit int `json:"rotateBackupLimit"` // Max backup for rotated files, default is 0, means no backups. + RotateBackupExpire time.Duration `json:"rotateBackupExpire"` // Max expire for rotated files, which is 0 in default, means no expiration. + RotateBackupCompress int `json:"rotateBackupCompress"` // Compress level for rotated files using gzip algorithm. It's 0 in default, means no compression. + RotateCheckInterval time.Duration `json:"rotateCheckInterval"` // Asynchronously checks the backups and expiration at intervals. It's 1 hour in default. + WriterColorEnable bool `json:"writerColorEnable"` // Logging level prefix with color to writer or not (false in default). } // DefaultConfig returns the default configuration for logger. @@ -47,6 +51,7 @@ func DefaultConfig() Config { File: defaultFileFormat, Flags: F_TIME_STD, Level: LEVEL_ALL, + CtxKeys: []interface{}{gctx.CtxKey}, StStatus: 1, HeaderPrint: true, StdoutPrint: true, @@ -68,18 +73,18 @@ func (l *Logger) SetConfig(config Config) error { // Necessary validation. if config.Path != "" { if err := l.SetPath(config.Path); err != nil { - intlog.Error(err) + intlog.Error(l.ctx, err) return err } } - intlog.Printf("SetConfig: %+v", l.config) + intlog.Printf(l.ctx, "SetConfig: %+v", l.config) return nil } // SetConfigWithMap set configurations with map for the logger. func (l *Logger) SetConfigWithMap(m map[string]interface{}) error { if m == nil || len(m) == 0 { - return errors.New("configuration cannot be empty") + return gerror.NewCode(gcode.CodeInvalidParameter, "configuration cannot be empty") } // The m now is a shallow copy of m. // A little tricky, isn't it? @@ -90,7 +95,7 @@ func (l *Logger) SetConfigWithMap(m map[string]interface{}) error { if level, ok := levelStringMap[strings.ToUpper(gconv.String(levelValue))]; ok { m[levelKey] = level } else { - return errors.New(fmt.Sprintf(`invalid level string: %v`, levelValue)) + return gerror.NewCodef(gcode.CodeInvalidParameter, `invalid level string: %v`, levelValue) } } // Change string configuration to int value for file rotation size. @@ -98,11 +103,10 @@ func (l *Logger) SetConfigWithMap(m map[string]interface{}) error { if rotateSizeValue != nil { m[rotateSizeKey] = gfile.StrToSize(gconv.String(rotateSizeValue)) if m[rotateSizeKey] == -1 { - return errors.New(fmt.Sprintf(`invalid rotate size: %v`, rotateSizeValue)) + return gerror.NewCodef(gcode.CodeInvalidConfiguration, `invalid rotate size: %v`, rotateSizeValue) } } - err := gconv.Struct(m, &l.config) - if err != nil { + if err := gconv.Struct(m, &l.config); err != nil { return err } return l.SetConfig(l.config) @@ -162,6 +166,25 @@ func (l *Logger) SetStackFilter(filter string) { // Note that multiple calls of this function will overwrite the previous set context keys. func (l *Logger) SetCtxKeys(keys ...interface{}) { l.config.CtxKeys = keys + l.config.CtxKeys = append(l.config.CtxKeys, gctx.CtxKey) +} + +// AppendCtxKeys appends extra keys to logger. +// It ignores the key if it is already appended to the logger previously. +func (l *Logger) AppendCtxKeys(keys ...interface{}) { + var isExist bool + for _, key := range keys { + isExist = false + for _, ctxKey := range l.config.CtxKeys { + if ctxKey == key { + isExist = true + break + } + } + if !isExist { + l.config.CtxKeys = append(l.config.CtxKeys, key) + } + } } // GetCtxKeys retrieves and returns the context keys for logging. @@ -169,9 +192,9 @@ func (l *Logger) GetCtxKeys() []interface{} { return l.config.CtxKeys } -// SetWriter sets the customized logging <writer> for logging. -// The <writer> object should implements the io.Writer interface. -// Developer can use customized logging <writer> to redirect logging output to another service, +// SetWriter sets the customized logging `writer` for logging. +// The `writer` object should implements the io.Writer interface. +// Developer can use customized logging `writer` to redirect logging output to another service, // eg: kafka, mysql, mongodb, etc. func (l *Logger) SetWriter(writer io.Writer) { l.config.Writer = writer @@ -186,12 +209,11 @@ func (l *Logger) GetWriter() io.Writer { // SetPath sets the directory path for file logging. func (l *Logger) SetPath(path string) error { if path == "" { - return errors.New("logging path is empty") + return gerror.NewCode(gcode.CodeInvalidParameter, "logging path is empty") } if !gfile.Exists(path) { if err := gfile.Mkdir(path); err != nil { - //fmt.Fprintln(os.Stderr, fmt.Sprintf(`[glog] mkdir "%s" failed: %s`, path, err.Error())) - return err + return gerror.WrapCodef(gcode.CodeOperationFailed, err, `Mkdir "%s" failed in PWD "%s"`, path, gfile.Pwd()) } } l.config.Path = strings.TrimRight(path, gfile.Separator) @@ -204,8 +226,8 @@ func (l *Logger) GetPath() string { return l.config.Path } -// SetFile sets the file name <pattern> for file logging. -// Datetime pattern can be used in <pattern>, eg: access-{Ymd}.log. +// SetFile sets the file name `pattern` for file logging. +// Datetime pattern can be used in `pattern`, eg: access-{Ymd}.log. // The default file name pattern is: Y-m-d.log, eg: 2018-01-01.log func (l *Logger) SetFile(pattern string) { l.config.File = pattern @@ -226,3 +248,13 @@ func (l *Logger) SetHeaderPrint(enabled bool) { func (l *Logger) SetPrefix(prefix string) { l.config.Prefix = prefix } + +// SetHandlers sets the logging handlers for current logger. +func (l *Logger) SetHandlers(handlers ...Handler) { + l.config.Handlers = handlers +} + +//SetWriterColorEnable sets the file logging with color +func (l *Logger) SetWriterColorEnable(enabled bool) { + l.config.WriterColorEnable = enabled +} diff --git a/os/glog/glog_logger_handler.go b/os/glog/glog_logger_handler.go new file mode 100644 index 000000000..bdeb4cdfc --- /dev/null +++ b/os/glog/glog_logger_handler.go @@ -0,0 +1,111 @@ +// Copyright GoFrame Author(https://goframe.org). All Rights Reserved. +// +// This Source Code Form is subject to the terms of the MIT License. +// If a copy of the MIT was not distributed with this file, +// You can obtain one at https://github.com/gogf/gf. + +package glog + +import ( + "bytes" + "context" + "time" +) + +// Handler is function handler for custom logging content outputs. +type Handler func(ctx context.Context, in *HandlerInput) + +// HandlerInput is the input parameter struct for logging Handler. +type HandlerInput struct { + Logger *Logger // Logger. + Ctx context.Context // Context. + Buffer *bytes.Buffer // Buffer for logging content outputs. + Time time.Time // Logging time, which is the time that logging triggers. + TimeFormat string // Formatted time string, like "2016-01-09 12:00:00". + Color int // Using color, like COLOR_RED, COLOR_BLUE, etc. + Level int // Using level, like LEVEL_INFO, LEVEL_ERRO, etc. + LevelFormat string // Formatted level string, like "DEBU", "ERRO", etc. + CallerFunc string // The source function name that calls logging. + CallerPath string // The source file path and its line number that calls logging. + CtxStr string // The retrieved context value string from context. + Prefix string // Custom prefix string for logging content. + Content string // Content is the main logging content that passed by you. + IsAsync bool // IsAsync marks it is in asynchronous logging. + handlerIndex int // Middleware handling index for internal usage. +} + +// Next calls the next logging handler in middleware way. +func (i *HandlerInput) Next() { + if len(i.Logger.config.Handlers)-1 > i.handlerIndex { + i.handlerIndex++ + i.Logger.config.Handlers[i.handlerIndex](i.Ctx, i) + } else { + defaultHandler(i.Ctx, i) + } +} + +// String returns the logging content formatted by default logging handler. +func (i *HandlerInput) String(withColor ...bool) string { + formatWithColor := false + if len(withColor) > 0 { + formatWithColor = withColor[0] + } + return i.getDefaultBuffer(formatWithColor).String() +} + +func (i *HandlerInput) getDefaultBuffer(withColor bool) *bytes.Buffer { + buffer := bytes.NewBuffer(nil) + if i.TimeFormat != "" { + buffer.WriteString(i.TimeFormat) + } + if i.LevelFormat != "" { + if withColor { + i.addStringToBuffer(buffer, i.Logger.getColoredStr( + i.Logger.getColorByLevel(i.Level), i.LevelFormat, + )) + } else { + i.addStringToBuffer(buffer, i.LevelFormat) + } + } + if i.Prefix != "" { + i.addStringToBuffer(buffer, i.Prefix) + } + if i.CtxStr != "" { + i.addStringToBuffer(buffer, i.CtxStr) + } + if i.CallerFunc != "" { + i.addStringToBuffer(buffer, i.CallerFunc) + } + if i.CallerPath != "" { + i.addStringToBuffer(buffer, i.CallerPath) + } + if i.Content != "" { + i.addStringToBuffer(buffer, i.Content) + } + i.addStringToBuffer(buffer, "\n") + return buffer +} + +func (i *HandlerInput) getRealBuffer(withColor bool) *bytes.Buffer { + if i.Buffer.Len() > 0 { + return i.Buffer + } + return i.getDefaultBuffer(withColor) +} + +// defaultHandler is the default handler for logger. +func defaultHandler(ctx context.Context, in *HandlerInput) { + buffer := in.Logger.doDefaultPrint(ctx, in) + if in.Buffer.Len() == 0 { + in.Buffer = buffer + } +} + +func (i *HandlerInput) addStringToBuffer(buffer *bytes.Buffer, strings ...string) { + for _, s := range strings { + if buffer.Len() > 0 { + buffer.WriteByte(' ') + } + buffer.WriteString(s) + } +} diff --git a/os/glog/glog_logger_level.go b/os/glog/glog_logger_level.go index 25e50ec0e..5d9b0ad5d 100644 --- a/os/glog/glog_logger_level.go +++ b/os/glog/glog_logger_level.go @@ -1,4 +1,4 @@ -// Copyright 2019 gf Author(https://github.com/gogf/gf). All Rights Reserved. +// Copyright GoFrame Author(https://goframe.org). All Rights Reserved. // // This Source Code Form is subject to the terms of the MIT License. // If a copy of the MIT was not distributed with this file, @@ -7,8 +7,8 @@ package glog import ( - "errors" - "fmt" + "github.com/gogf/gf/errors/gcode" + "github.com/gogf/gf/errors/gerror" "strings" ) @@ -18,6 +18,7 @@ const ( LEVEL_ALL = LEVEL_DEBU | LEVEL_INFO | LEVEL_NOTI | LEVEL_WARN | LEVEL_ERRO | LEVEL_CRIT LEVEL_DEV = LEVEL_ALL LEVEL_PROD = LEVEL_WARN | LEVEL_ERRO | LEVEL_CRIT + LEVEL_NONE = 0 LEVEL_DEBU = 1 << iota // 8 LEVEL_INFO // 16 LEVEL_NOTI // 32 @@ -61,8 +62,10 @@ var levelStringMap = map[string]int{ } // SetLevel sets the logging level. +// Note that levels ` LEVEL_CRIT | LEVEL_PANI | LEVEL_FATA ` cannot be removed for logging content, +// which are automatically added to levels. func (l *Logger) SetLevel(level int) { - l.config.Level = level + l.config.Level = level | LEVEL_CRIT | LEVEL_PANI | LEVEL_FATA } // GetLevel returns the logging level value. @@ -75,7 +78,7 @@ func (l *Logger) SetLevelStr(levelStr string) error { if level, ok := levelStringMap[strings.ToUpper(levelStr)]; ok { l.config.Level = level } else { - return errors.New(fmt.Sprintf(`invalid level string: %s`, levelStr)) + return gerror.NewCodef(gcode.CodeInvalidParameter, `invalid level string: %s`, levelStr) } return nil } @@ -99,8 +102,9 @@ func (l *Logger) GetLevelPrefix(level int) string { // getLevelPrefixWithBrackets returns the prefix string with brackets for specified level. func (l *Logger) getLevelPrefixWithBrackets(level int) string { + levelStr := "" if s, ok := l.config.LevelPrefixes[level]; ok { - return "[" + s + "]" + levelStr = "[" + s + "]" } - return "" + return levelStr } diff --git a/os/glog/glog_logger_rotate.go b/os/glog/glog_logger_rotate.go index a89ecec35..9262b989b 100644 --- a/os/glog/glog_logger_rotate.go +++ b/os/glog/glog_logger_rotate.go @@ -1,4 +1,4 @@ -// Copyright GoFrame Author(https://github.com/gogf/gf). All Rights Reserved. +// Copyright GoFrame Author(https://goframe.org). All Rights Reserved. // // This Source Code Form is subject to the terms of the MIT License. // If a copy of the MIT was not distributed with this file, @@ -19,6 +19,10 @@ import ( "time" ) +const ( + memoryLockPrefixForRotating = "glog.rotateChecksTimely:" +) + // rotateFileBySize rotates the current logging file according to the // configured rotation size. func (l *Logger) rotateFileBySize(now time.Time) { @@ -27,7 +31,7 @@ func (l *Logger) rotateFileBySize(now time.Time) { } if err := l.doRotateFile(l.getFilePath(now)); err != nil { // panic(err) - intlog.Error(err) + intlog.Error(l.ctx, err) } } @@ -39,12 +43,23 @@ func (l *Logger) doRotateFile(filePath string) error { } defer gmlock.Unlock(memoryLockKey) + intlog.PrintFunc(l.ctx, func() string { + return fmt.Sprintf(`start rotating file by size: %s, file: %s`, gfile.SizeFormat(filePath), filePath) + }) + defer intlog.PrintFunc(l.ctx, func() string { + return fmt.Sprintf(`done rotating file by size: %s, size: %s`, gfile.SizeFormat(filePath), filePath) + }) + // No backups, it then just removes the current logging file. if l.config.RotateBackupLimit == 0 { if err := gfile.Remove(filePath); err != nil { return err } - intlog.Printf(`%d size exceeds, no backups set, remove original logging file: %s`, l.config.RotateSize, filePath) + intlog.Printf( + l.ctx, + `%d size exceeds, no backups set, remove original logging file: %s`, + l.config.RotateSize, filePath, + ) return nil } // Else it creates new backup files. @@ -62,8 +77,12 @@ func (l *Logger) doRotateFile(filePath string) error { now = gtime.Now() micro = now.Microsecond() % 1000 ) - for micro < 100 { - micro *= 10 + if micro == 0 { + micro = 101 + } else { + for micro < 100 { + micro *= 10 + } } newFilePath = gfile.Join( dirPath, @@ -75,9 +94,10 @@ func (l *Logger) doRotateFile(filePath string) error { if !gfile.Exists(newFilePath) { break } else { - intlog.Printf(`rotation file exists, continue: %s`, newFilePath) + intlog.Printf(l.ctx, `rotation file exists, continue: %s`, newFilePath) } } + intlog.Printf(l.ctx, "rotating file by size from %s to %s", filePath, newFilePath) if err := gfile.Rename(filePath, newFilePath); err != nil { return err } @@ -87,9 +107,11 @@ func (l *Logger) doRotateFile(filePath string) error { // rotateChecksTimely timely checks the backups expiration and the compression. func (l *Logger) rotateChecksTimely() { defer gtimer.AddOnce(l.config.RotateCheckInterval, l.rotateChecksTimely) + // Checks whether file rotation not enabled. if l.config.RotateSize <= 0 && l.config.RotateExpire == 0 { intlog.Printf( + l.ctx, "logging rotation ignore checks: RotateSize: %d, RotateExpire: %s", l.config.RotateSize, l.config.RotateExpire.String(), ) @@ -97,18 +119,21 @@ func (l *Logger) rotateChecksTimely() { } // It here uses memory lock to guarantee the concurrent safety. - memoryLockKey := "glog.rotateChecksTimely:" + l.config.Path + memoryLockKey := memoryLockPrefixForRotating + l.config.Path if !gmlock.TryLock(memoryLockKey) { return } defer gmlock.Unlock(memoryLockKey) var ( - now = time.Now() - pattern = "*.log, *.gz" - files, _ = gfile.ScanDirFile(l.config.Path, pattern, true) + now = time.Now() + pattern = "*.log, *.gz" + files, err = gfile.ScanDirFile(l.config.Path, pattern, true) ) - intlog.Printf("logging rotation start checks: %+v", files) + if err != nil { + intlog.Error(l.ctx, err) + } + intlog.Printf(l.ctx, "logging rotation start checks: %+v", files) // ============================================================= // Rotation of expired file checks. // ============================================================= @@ -127,17 +152,21 @@ func (l *Logger) rotateChecksTimely() { if subDuration > l.config.RotateExpire { expireRotated = true intlog.Printf( + l.ctx, `%v - %v = %v > %v, rotation expire logging file: %s`, now, mtime, subDuration, l.config.RotateExpire, file, ) if err := l.doRotateFile(file); err != nil { - intlog.Error(err) + intlog.Error(l.ctx, err) } } } if expireRotated { // Update the files array. - files, _ = gfile.ScanDirFile(l.config.Path, pattern, true) + files, err = gfile.ScanDirFile(l.config.Path, pattern, true) + if err != nil { + intlog.Error(l.ctx, err) + } } } @@ -161,17 +190,20 @@ func (l *Logger) rotateChecksTimely() { needCompressFileArray.Iterator(func(_ int, path string) bool { err := gcompress.GzipFile(path, path+".gz") if err == nil { - intlog.Printf(`compressed done, remove original logging file: %s`, path) + intlog.Printf(l.ctx, `compressed done, remove original logging file: %s`, path) if err = gfile.Remove(path); err != nil { - intlog.Print(err) + intlog.Print(l.ctx, err) } } else { - intlog.Print(err) + intlog.Print(l.ctx, err) } return true }) // Update the files array. - files, _ = gfile.ScanDirFile(l.config.Path, pattern, true) + files, err = gfile.ScanDirFile(l.config.Path, pattern, true) + if err != nil { + intlog.Error(l.ctx, err) + } } } @@ -188,10 +220,12 @@ func (l *Logger) rotateChecksTimely() { if backupFilesMap[originalLoggingFilePath] == nil { backupFilesMap[originalLoggingFilePath] = garray.NewSortedArray(func(a, b interface{}) int { // Sorted by rotated/backup file mtime. - // The old rotated/backup file is put in the head of array. - file1 := a.(string) - file2 := b.(string) - result := gfile.MTimestampMilli(file1) - gfile.MTimestampMilli(file2) + // The older rotated/backup file is put in the head of array. + var ( + file1 = a.(string) + file2 = b.(string) + result = gfile.MTimestampMilli(file1) - gfile.MTimestampMilli(file2) + ) if result <= 0 { return -1 } @@ -203,18 +237,18 @@ func (l *Logger) rotateChecksTimely() { backupFilesMap[originalLoggingFilePath].Add(file) } } - intlog.Printf(`calculated backup files map: %+v`, backupFilesMap) + intlog.Printf(l.ctx, `calculated backup files map: %+v`, backupFilesMap) for _, array := range backupFilesMap { diff := array.Len() - l.config.RotateBackupLimit for i := 0; i < diff; i++ { path, _ := array.PopLeft() - intlog.Printf(`remove exceeded backup limit file: %s`, path) + intlog.Printf(l.ctx, `remove exceeded backup limit file: %s`, path) if err := gfile.Remove(path.(string)); err != nil { - intlog.Print(err) + intlog.Error(l.ctx, err) } } } - // Backup expiration checks. + // Backups expiration checking. if l.config.RotateBackupExpire > 0 { var ( mtime time.Time @@ -227,11 +261,12 @@ func (l *Logger) rotateChecksTimely() { subDuration = now.Sub(mtime) if subDuration > l.config.RotateBackupExpire { intlog.Printf( + l.ctx, `%v - %v = %v > %v, remove expired backup file: %s`, now, mtime, subDuration, l.config.RotateBackupExpire, path, ) if err := gfile.Remove(path); err != nil { - intlog.Print(err) + intlog.Error(l.ctx, err) } return true } else { diff --git a/os/glog/glog_logger_writer.go b/os/glog/glog_logger_writer.go index de4f3a853..765834df5 100644 --- a/os/glog/glog_logger_writer.go +++ b/os/glog/glog_logger_writer.go @@ -1,4 +1,4 @@ -// Copyright 2017 gf Author(https://github.com/gogf/gf). All Rights Reserved. +// Copyright GoFrame Author(https://goframe.org). All Rights Reserved. // // This Source Code Form is subject to the terms of the MIT License. // If a copy of the MIT was not distributed with this file, diff --git a/os/glog/glog_z_example_test.go b/os/glog/glog_z_example_test.go index 17de0a755..6ac5d5336 100644 --- a/os/glog/glog_z_example_test.go +++ b/os/glog/glog_z_example_test.go @@ -1,4 +1,4 @@ -// Copyright 2020 gf Author(https://github.com/gogf/gf). All Rights Reserved. +// Copyright GoFrame Author(https://goframe.org). All Rights Reserved. // // This Source Code Form is subject to the terms of the MIT License. // If a copy of the MIT was not distributed with this file, diff --git a/os/glog/glog_z_unit_basic_test.go b/os/glog/glog_z_unit_basic_test.go index 0f126ce69..f20d56b86 100644 --- a/os/glog/glog_z_unit_basic_test.go +++ b/os/glog/glog_z_unit_basic_test.go @@ -1,4 +1,4 @@ -// Copyright 2017 gf Author(https://github.com/gogf/gf). All Rights Reserved. +// Copyright GoFrame Author(https://goframe.org). All Rights Reserved. // // This Source Code Form is subject to the terms of the MIT License. // If a copy of the MIT was not distributed with this file, diff --git a/os/glog/glog_z_unit_chaining_test.go b/os/glog/glog_z_unit_chaining_test.go index d6360cce7..f72f88180 100644 --- a/os/glog/glog_z_unit_chaining_test.go +++ b/os/glog/glog_z_unit_chaining_test.go @@ -1,4 +1,4 @@ -// Copyright 2017 gf Author(https://github.com/gogf/gf). All Rights Reserved. +// Copyright GoFrame Author(https://goframe.org). All Rights Reserved. // // This Source Code Form is subject to the terms of the MIT License. // If a copy of the MIT was not distributed with this file, @@ -129,6 +129,8 @@ func Test_StackWithFilter(t *testing.T) { t.Assert(gstr.Count(content, defaultLevelPrefixes[LEVEL_ERRO]), 1) t.Assert(gstr.Count(content, "1 2 3"), 1) t.Assert(gstr.Count(content, "Stack"), 1) + fmt.Println("Content:") + fmt.Println(content) }) gtest.C(t, func(t *gtest.T) { path := gfile.TempDir(gtime.TimestampNanoStr()) @@ -138,11 +140,13 @@ func Test_StackWithFilter(t *testing.T) { t.Assert(err, nil) defer gfile.Remove(path) - Path(path).File(file).StackWithFilter("gogf").Stdout(false).Error(1, 2, 3) + Path(path).File(file).StackWithFilter("/gf/").Stdout(false).Error(1, 2, 3) content := gfile.GetContents(gfile.Join(path, file)) t.Assert(gstr.Count(content, defaultLevelPrefixes[LEVEL_ERRO]), 1) t.Assert(gstr.Count(content, "1 2 3"), 1) t.Assert(gstr.Count(content, "Stack"), 0) + fmt.Println("Content:") + fmt.Println(content) }) } diff --git a/os/glog/glog_z_unit_concurrent_test.go b/os/glog/glog_z_unit_concurrent_test.go index 1378e2278..1f5275b62 100644 --- a/os/glog/glog_z_unit_concurrent_test.go +++ b/os/glog/glog_z_unit_concurrent_test.go @@ -1,4 +1,4 @@ -// Copyright 2017 gf Author(https://github.com/gogf/gf). All Rights Reserved. +// Copyright GoFrame Author(https://goframe.org). All Rights Reserved. // // This Source Code Form is subject to the terms of the MIT License. // If a copy of the MIT was not distributed with this file, diff --git a/os/glog/glog_z_unit_config_test.go b/os/glog/glog_z_unit_config_test.go index 54b6a7346..6e50b01dd 100644 --- a/os/glog/glog_z_unit_config_test.go +++ b/os/glog/glog_z_unit_config_test.go @@ -1,4 +1,4 @@ -// Copyright 2017 gf Author(https://github.com/gogf/gf). All Rights Reserved. +// Copyright GoFrame Author(https://goframe.org). All Rights Reserved. // // This Source Code Form is subject to the terms of the MIT License. // If a copy of the MIT was not distributed with this file, diff --git a/os/glog/glog_z_unit_ctx_test.go b/os/glog/glog_z_unit_ctx_test.go index 9ac5e0001..e6811df72 100644 --- a/os/glog/glog_z_unit_ctx_test.go +++ b/os/glog/glog_z_unit_ctx_test.go @@ -1,4 +1,4 @@ -// Copyright 2017 gf Author(https://github.com/gogf/gf). All Rights Reserved. +// Copyright GoFrame Author(https://goframe.org). All Rights Reserved. // // This Source Code Form is subject to the terms of the MIT License. // If a copy of the MIT was not distributed with this file, @@ -10,6 +10,7 @@ import ( "bytes" "context" "github.com/gogf/gf/frame/g" + "github.com/gogf/gf/os/gctx" "github.com/gogf/gf/os/glog" "github.com/gogf/gf/test/gtest" "github.com/gogf/gf/text/gstr" @@ -25,9 +26,7 @@ func Test_Ctx(t *testing.T) { ctx = context.WithValue(ctx, "Span-Id", "abcdefg") l.Ctx(ctx).Print(1, 2, 3) - t.Assert(gstr.Count(w.String(), "Trace-Id"), 1) t.Assert(gstr.Count(w.String(), "1234567890"), 1) - t.Assert(gstr.Count(w.String(), "Span-Id"), 1) t.Assert(gstr.Count(w.String(), "abcdefg"), 1) t.Assert(gstr.Count(w.String(), "1 2 3"), 1) }) @@ -46,9 +45,17 @@ func Test_Ctx_Config(t *testing.T) { ctx = context.WithValue(ctx, "Span-Id", "abcdefg") l.Ctx(ctx).Print(1, 2, 3) - t.Assert(gstr.Count(w.String(), "Trace-Id"), 1) t.Assert(gstr.Count(w.String(), "1234567890"), 1) - t.Assert(gstr.Count(w.String(), "Span-Id"), 1) + t.Assert(gstr.Count(w.String(), "abcdefg"), 1) + t.Assert(gstr.Count(w.String(), "1 2 3"), 1) + }) +} + +func Test_Ctx_CtxKey(t *testing.T) { + gtest.C(t, func(t *gtest.T) { + w := bytes.NewBuffer(nil) + l := glog.NewWithWriter(w) + l.Ctx(gctx.WithValue(context.TODO(), "abcdefg")).Print(1, 2, 3) t.Assert(gstr.Count(w.String(), "abcdefg"), 1) t.Assert(gstr.Count(w.String(), "1 2 3"), 1) }) diff --git a/os/glog/glog_z_unit_handler_test.go b/os/glog/glog_z_unit_handler_test.go new file mode 100644 index 000000000..31f922f13 --- /dev/null +++ b/os/glog/glog_z_unit_handler_test.go @@ -0,0 +1,72 @@ +// Copyright GoFrame Author(https://goframe.org). All Rights Reserved. +// +// This Source Code Form is subject to the terms of the MIT License. +// If a copy of the MIT was not distributed with this file, +// You can obtain one at https://github.com/gogf/gf. + +package glog_test + +import ( + "bytes" + "context" + "github.com/gogf/gf/container/garray" + "github.com/gogf/gf/os/glog" + "github.com/gogf/gf/test/gtest" + "github.com/gogf/gf/text/gstr" + "testing" +) + +var arrayForHandlerTest1 = garray.NewStrArray() + +func customHandler1(ctx context.Context, input *glog.HandlerInput) { + arrayForHandlerTest1.Append(input.String(false)) + input.Next() +} + +func TestLogger_SetHandlers1(t *testing.T) { + gtest.C(t, func(t *gtest.T) { + w := bytes.NewBuffer(nil) + l := glog.NewWithWriter(w) + l.SetHandlers(customHandler1) + l.SetCtxKeys("Trace-Id", "Span-Id", "Test") + ctx := context.WithValue(context.Background(), "Trace-Id", "1234567890") + ctx = context.WithValue(ctx, "Span-Id", "abcdefg") + + l.Ctx(ctx).Print(1, 2, 3) + t.Assert(gstr.Count(w.String(), "1234567890"), 1) + t.Assert(gstr.Count(w.String(), "abcdefg"), 1) + t.Assert(gstr.Count(w.String(), "1 2 3"), 1) + + t.Assert(arrayForHandlerTest1.Len(), 1) + t.Assert(gstr.Count(arrayForHandlerTest1.At(0), "1234567890"), 1) + t.Assert(gstr.Count(arrayForHandlerTest1.At(0), "abcdefg"), 1) + t.Assert(gstr.Count(arrayForHandlerTest1.At(0), "1 2 3"), 1) + }) +} + +var arrayForHandlerTest2 = garray.NewStrArray() + +func customHandler2(ctx context.Context, input *glog.HandlerInput) { + arrayForHandlerTest2.Append(input.String(false)) +} + +func TestLogger_SetHandlers2(t *testing.T) { + gtest.C(t, func(t *gtest.T) { + w := bytes.NewBuffer(nil) + l := glog.NewWithWriter(w) + l.SetHandlers(customHandler2) + l.SetCtxKeys("Trace-Id", "Span-Id", "Test") + ctx := context.WithValue(context.Background(), "Trace-Id", "1234567890") + ctx = context.WithValue(ctx, "Span-Id", "abcdefg") + + l.Ctx(ctx).Print(1, 2, 3) + t.Assert(gstr.Count(w.String(), "1234567890"), 0) + t.Assert(gstr.Count(w.String(), "abcdefg"), 0) + t.Assert(gstr.Count(w.String(), "1 2 3"), 0) + + t.Assert(arrayForHandlerTest2.Len(), 1) + t.Assert(gstr.Count(arrayForHandlerTest2.At(0), "1234567890"), 1) + t.Assert(gstr.Count(arrayForHandlerTest2.At(0), "abcdefg"), 1) + t.Assert(gstr.Count(arrayForHandlerTest2.At(0), "1 2 3"), 1) + }) +} diff --git a/os/glog/glog_z_unit_level_test.go b/os/glog/glog_z_unit_level_test.go index 3ceefaaf2..d12535895 100644 --- a/os/glog/glog_z_unit_level_test.go +++ b/os/glog/glog_z_unit_level_test.go @@ -1,4 +1,4 @@ -// Copyright 2017 gf Author(https://github.com/gogf/gf). All Rights Reserved. +// Copyright GoFrame Author(https://goframe.org). All Rights Reserved. // // This Source Code Form is subject to the terms of the MIT License. // If a copy of the MIT was not distributed with this file, diff --git a/os/glog/glog_z_unit_rotate_test.go b/os/glog/glog_z_unit_rotate_test.go index fa0581e66..8b8096216 100644 --- a/os/glog/glog_z_unit_rotate_test.go +++ b/os/glog/glog_z_unit_rotate_test.go @@ -1,4 +1,4 @@ -// Copyright GoFrame Author(https://github.com/gogf/gf). All Rights Reserved. +// Copyright GoFrame Author(https://goframe.org). All Rights Reserved. // // This Source Code Form is subject to the terms of the MIT License. // If a copy of the MIT was not distributed with this file, diff --git a/os/gmlock/gmlock.go b/os/gmlock/gmlock.go index a6baa38ee..60a17a08a 100644 --- a/os/gmlock/gmlock.go +++ b/os/gmlock/gmlock.go @@ -1,4 +1,4 @@ -// Copyright 2018 gf Author(https://github.com/gogf/gf). All Rights Reserved. +// Copyright GoFrame Author(https://goframe.org). All Rights Reserved. // // This Source Code Form is subject to the terms of the MIT License. // If a copy of the MIT was not distributed with this file, diff --git a/os/gmlock/gmlock_locker.go b/os/gmlock/gmlock_locker.go index 5c0138da7..075d139e2 100644 --- a/os/gmlock/gmlock_locker.go +++ b/os/gmlock/gmlock_locker.go @@ -1,4 +1,4 @@ -// Copyright 2018 gf Author(https://github.com/gogf/gf). All Rights Reserved. +// Copyright GoFrame Author(https://goframe.org). All Rights Reserved. // // This Source Code Form is subject to the terms of the MIT License. // If a copy of the MIT was not distributed with this file, @@ -11,9 +11,9 @@ import ( "github.com/gogf/gf/os/gmutex" ) -// Memory locker. +// Locker is a memory based locker. // Note that there's no cache expire mechanism for mutex in locker. -// You need remove certain mutex manually when you do not want use it any more. +// You need remove certain mutex manually when you do not want use it anymore. type Locker struct { m *gmap.StrAnyMap } @@ -28,7 +28,7 @@ func New() *Locker { // Lock locks the <key> with writing lock. // If there's a write/reading lock the <key>, -// it will blocks until the lock is released. +// it will block until the lock is released. func (l *Locker) Lock(key string) { l.getOrNewMutex(key).Lock() } @@ -68,7 +68,7 @@ func (l *Locker) RUnlock(key string) { // LockFunc locks the <key> with writing lock and callback function <f>. // If there's a write/reading lock the <key>, -// it will blocks until the lock is released. +// it will block until the lock is released. // // It releases the lock after <f> is executed. func (l *Locker) LockFunc(key string, f func()) { @@ -79,7 +79,7 @@ func (l *Locker) LockFunc(key string, f func()) { // RLockFunc locks the <key> with reading lock and callback function <f>. // If there's a writing lock the <key>, -// it will blocks until the lock is released. +// it will block until the lock is released. // // It releases the lock after <f> is executed. func (l *Locker) RLockFunc(key string, f func()) { diff --git a/os/gmlock/gmlock_unit_lock_test.go b/os/gmlock/gmlock_unit_lock_test.go index eba02d936..16a4d4eab 100644 --- a/os/gmlock/gmlock_unit_lock_test.go +++ b/os/gmlock/gmlock_unit_lock_test.go @@ -1,4 +1,4 @@ -// Copyright 2019 gf Author(https://github.com/gogf/gf). All Rights Reserved. +// Copyright GoFrame Author(https://goframe.org). All Rights Reserved. // // This Source Code Form is subject to the terms of the MIT License. // If a copy of the MIT was not distributed with this file, diff --git a/os/gmlock/gmlock_unit_rlock_test.go b/os/gmlock/gmlock_unit_rlock_test.go index 4d01bc930..8a6d3a09b 100644 --- a/os/gmlock/gmlock_unit_rlock_test.go +++ b/os/gmlock/gmlock_unit_rlock_test.go @@ -1,4 +1,4 @@ -// Copyright 2019 gf Author(https://github.com/gogf/gf). All Rights Reserved. +// Copyright GoFrame Author(https://goframe.org). All Rights Reserved. // // This Source Code Form is subject to the terms of the MIT License. // If a copy of the MIT was not distributed with this file, diff --git a/os/gmlock/gmlock_z_bench_test.go b/os/gmlock/gmlock_z_bench_test.go index 2ca7d63ab..c87876d2b 100644 --- a/os/gmlock/gmlock_z_bench_test.go +++ b/os/gmlock/gmlock_z_bench_test.go @@ -1,4 +1,4 @@ -// Copyright 2020 gf Author(https://github.com/gogf/gf). All Rights Reserved. +// Copyright GoFrame Author(https://goframe.org). All Rights Reserved. // // This Source Code Form is subject to the terms of the MIT License. // If a copy of the MIT was not distributed with this file, diff --git a/os/gmutex/gmutex.go b/os/gmutex/gmutex.go index ad0254fdf..54acea5d2 100644 --- a/os/gmutex/gmutex.go +++ b/os/gmutex/gmutex.go @@ -1,4 +1,4 @@ -// Copyright GoFrame Author(https://github.com/gogf/gf). All Rights Reserved. +// Copyright GoFrame Author(https://goframe.org). All Rights Reserved. // // This Source Code Form is subject to the terms of the MIT License. // If a copy of the MIT was not distributed with this file, diff --git a/os/gmutex/gmutex_bench_test.go b/os/gmutex/gmutex_bench_test.go index b54a7b5cf..13c8af7bc 100644 --- a/os/gmutex/gmutex_bench_test.go +++ b/os/gmutex/gmutex_bench_test.go @@ -1,4 +1,4 @@ -// Copyright 2019 gf Author(https://github.com/gogf/gf). All Rights Reserved. +// Copyright GoFrame Author(https://goframe.org). All Rights Reserved. // // This Source Code Form is subject to the terms of the MIT License. // If a copy of the MIT was not distributed with this file, diff --git a/os/gmutex/gmutex_unit_test.go b/os/gmutex/gmutex_unit_test.go index 1f992a8a8..7da5e58dc 100644 --- a/os/gmutex/gmutex_unit_test.go +++ b/os/gmutex/gmutex_unit_test.go @@ -1,4 +1,4 @@ -// Copyright 2019 gf Author(https://github.com/gogf/gf). All Rights Reserved. +// Copyright GoFrame Author(https://goframe.org). All Rights Reserved. // // This Source Code Form is subject to the terms of the MIT License. // If a copy of the MIT was not distributed with this file, diff --git a/os/gproc/gproc.go b/os/gproc/gproc.go index bfbee3685..33fe32f00 100644 --- a/os/gproc/gproc.go +++ b/os/gproc/gproc.go @@ -1,4 +1,4 @@ -// Copyright 2018 gf Author(https://github.com/gogf/gf). All Rights Reserved. +// Copyright GoFrame Author(https://goframe.org). All Rights Reserved. // // This Source Code Form is subject to the terms of the MIT License. // If a copy of the MIT was not distributed with this file, @@ -21,14 +21,12 @@ import ( ) const ( - gPROC_ENV_KEY_PPID_KEY = "GPROC_PPID" + envKeyPPid = "GPROC_PPID" ) var ( - // processPid is the pid of current process. - processPid = os.Getpid() - // processStartTime is the start time of current process. - processStartTime = time.Now() + processPid = os.Getpid() // processPid is the pid of current process. + processStartTime = time.Now() // processStartTime is the start time of current process. ) // Pid returns the pid of current process. @@ -41,7 +39,7 @@ func PPid() int { if !IsChild() { return Pid() } - ppidValue := os.Getenv(gPROC_ENV_KEY_PPID_KEY) + ppidValue := os.Getenv(envKeyPPid) if ppidValue != "" && ppidValue != "0" { return gconv.Int(ppidValue) } @@ -59,16 +57,16 @@ func PPidOS() int { // IsChild checks and returns whether current process is a child process. // A child process is forked by another gproc process. func IsChild() bool { - ppidValue := os.Getenv(gPROC_ENV_KEY_PPID_KEY) + ppidValue := os.Getenv(envKeyPPid) return ppidValue != "" && ppidValue != "0" } // SetPPid sets custom parent pid for current process. func SetPPid(ppid int) error { if ppid > 0 { - return os.Setenv(gPROC_ENV_KEY_PPID_KEY, gconv.String(ppid)) + return os.Setenv(envKeyPPid, gconv.String(ppid)) } else { - return os.Unsetenv(gPROC_ENV_KEY_PPID_KEY) + return os.Unsetenv(envKeyPPid) } } diff --git a/os/gproc/gproc_comm.go b/os/gproc/gproc_comm.go index 779ff6432..4ab499ca6 100644 --- a/os/gproc/gproc_comm.go +++ b/os/gproc/gproc_comm.go @@ -1,4 +1,4 @@ -// Copyright 2018 gf Author(https://github.com/gogf/gf). All Rights Reserved. +// Copyright GoFrame Author(https://goframe.org). All Rights Reserved. // // This Source Code Form is subject to the terms of the MIT License. // If a copy of the MIT was not distributed with this file, @@ -7,9 +7,12 @@ package gproc import ( - "errors" + "context" "fmt" "github.com/gogf/gf/container/gmap" + "github.com/gogf/gf/errors/gcode" + "github.com/gogf/gf/errors/gerror" + "github.com/gogf/gf/internal/intlog" "github.com/gogf/gf/net/gtcp" "github.com/gogf/gf/os/gfile" "github.com/gogf/gf/util/gconv" @@ -31,9 +34,10 @@ type MsgResponse struct { } const ( - gPROC_COMM_DEFAULT_GRUOP_NAME = "" // Default group name. - gPROC_DEFAULT_TCP_PORT = 10000 // Starting port number for receiver listening. - gPROC_MSG_QUEUE_MAX_LENGTH = 10000 // Max size for each message queue of the group. + defaultFolderNameForProcComm = "gf_pid_port_mapping" // Default folder name for storing pid to port mapping files. + defaultGroupNameForProcComm = "" // Default group name. + defaultTcpPortForProcComm = 10000 // Starting port number for receiver listening. + maxLengthForProcMsgQueue = 10000 // Max size for each message queue of the group. ) var ( @@ -42,17 +46,36 @@ var ( commReceiveQueues = gmap.NewStrAnyMap(true) // commPidFolderPath specifies the folder path storing pid to port mapping files. - commPidFolderPath = gfile.TempDir("gproc") + commPidFolderPath string ) func init() { - // Automatically create the storage folder. - if !gfile.Exists(commPidFolderPath) { - err := gfile.Mkdir(commPidFolderPath) - if err != nil { - panic(fmt.Errorf(`create gproc folder failed: %v`, err)) + availablePaths := []string{ + "/var/tmp", + "/var/run", + } + if homePath, _ := gfile.Home(); homePath != "" { + availablePaths = append(availablePaths, gfile.Join(homePath, ".config")) + } + availablePaths = append(availablePaths, gfile.TempDir()) + for _, availablePath := range availablePaths { + checkPath := gfile.Join(availablePath, defaultFolderNameForProcComm) + if !gfile.Exists(checkPath) && gfile.Mkdir(checkPath) != nil { + continue + } + if gfile.IsWritable(checkPath) { + commPidFolderPath = checkPath + break } } + if commPidFolderPath == "" { + intlog.Errorf( + context.TODO(), + `cannot find available folder for storing pid to port mapping files in paths: %+v, process communication feature will fail`, + availablePaths, + ) + } + } // getConnByPid creates and returns a TCP connection for specified pid. @@ -65,15 +88,13 @@ func getConnByPid(pid int) (*gtcp.PoolConn, error) { return nil, err } } - return nil, errors.New(fmt.Sprintf("could not find port for pid: %d", pid)) + return nil, gerror.NewCodef(gcode.CodeOperationFailed, "could not find port for pid: %d", pid) } // getPortByPid returns the listening port for specified pid. // It returns 0 if no port found for the specified pid. func getPortByPid(pid int) int { - path := getCommFilePath(pid) - content := gfile.GetContentsWithCache(path) - return gconv.Int(content) + return gconv.Int(gfile.GetContentsWithCache(getCommFilePath(pid))) } // getCommFilePath returns the pid to port mapping file path for given pid. diff --git a/os/gproc/gproc_comm_receive.go b/os/gproc/gproc_comm_receive.go index c101fe0f0..f358db822 100644 --- a/os/gproc/gproc_comm_receive.go +++ b/os/gproc/gproc_comm_receive.go @@ -1,4 +1,4 @@ -// Copyright 2018 gf Author(https://github.com/gogf/gf). All Rights Reserved. +// Copyright GoFrame Author(https://goframe.org). All Rights Reserved. // // This Source Code Form is subject to the terms of the MIT License. // If a copy of the MIT was not distributed with this file, @@ -35,10 +35,10 @@ func Receive(group ...string) *MsgRequest { if len(group) > 0 { groupName = group[0] } else { - groupName = gPROC_COMM_DEFAULT_GRUOP_NAME + groupName = defaultGroupNameForProcComm } queue := commReceiveQueues.GetOrSetFuncLock(groupName, func() interface{} { - return gqueue.New(gPROC_MSG_QUEUE_MAX_LENGTH) + return gqueue.New(maxLengthForProcMsgQueue) }).(*gqueue.Queue) // Blocking receiving. @@ -52,7 +52,7 @@ func Receive(group ...string) *MsgRequest { func receiveTcpListening() { var listen *net.TCPListener // Scan the available port for listening. - for i := gPROC_DEFAULT_TCP_PORT; ; i++ { + for i := defaultTcpPortForProcComm; ; i++ { addr, err := net.ResolveTCPAddr("tcp", fmt.Sprintf("127.0.0.1:%d", i)) if err != nil { continue @@ -79,8 +79,10 @@ func receiveTcpListening() { // receiveTcpHandler is the connection handler for receiving data. func receiveTcpHandler(conn *gtcp.Conn) { - var result []byte - var response MsgResponse + var ( + result []byte + response MsgResponse + ) for { response.Code = 0 response.Message = "" @@ -89,7 +91,7 @@ func receiveTcpHandler(conn *gtcp.Conn) { if len(buffer) > 0 { // Package decoding. msg := new(MsgRequest) - if err := json.Unmarshal(buffer, msg); err != nil { + if err := json.UnmarshalUseNumber(buffer, msg); err != nil { //glog.Error(err) continue } diff --git a/os/gproc/gproc_comm_send.go b/os/gproc/gproc_comm_send.go index 74b947c5e..9e05d5437 100644 --- a/os/gproc/gproc_comm_send.go +++ b/os/gproc/gproc_comm_send.go @@ -1,4 +1,4 @@ -// Copyright 2018 gf Author(https://github.com/gogf/gf). All Rights Reserved. +// Copyright GoFrame Author(https://goframe.org). All Rights Reserved. // // This Source Code Form is subject to the terms of the MIT License. // If a copy of the MIT was not distributed with this file, @@ -7,7 +7,7 @@ package gproc import ( - "errors" + "github.com/gogf/gf/errors/gerror" "github.com/gogf/gf/internal/json" "github.com/gogf/gf/net/gtcp" "io" @@ -18,7 +18,7 @@ func Send(pid int, data []byte, group ...string) error { msg := MsgRequest{ SendPid: Pid(), RecvPid: pid, - Group: gPROC_COMM_DEFAULT_GRUOP_NAME, + Group: defaultGroupNameForProcComm, Data: data, } if len(group) > 0 { @@ -43,10 +43,9 @@ func Send(pid int, data []byte, group ...string) error { }) if len(result) > 0 { response := new(MsgResponse) - err = json.Unmarshal(result, response) - if err == nil { + if err = json.UnmarshalUseNumber(result, response); err == nil { if response.Code != 1 { - err = errors.New(response.Message) + err = gerror.New(response.Message) } } } diff --git a/os/gproc/gproc_manager.go b/os/gproc/gproc_manager.go index 49e11c055..cc448eb65 100644 --- a/os/gproc/gproc_manager.go +++ b/os/gproc/gproc_manager.go @@ -1,4 +1,4 @@ -// Copyright 2018 gf Author(https://github.com/gogf/gf). All Rights Reserved. +// Copyright GoFrame Author(https://goframe.org). All Rights Reserved. // // This Source Code Form is subject to the terms of the MIT License. // If a copy of the MIT was not distributed with this file, @@ -12,26 +12,27 @@ import ( "github.com/gogf/gf/container/gmap" ) -// 进程管理器 +// Manager is a process manager maintaining multiple processes. type Manager struct { - processes *gmap.IntAnyMap // 所管理的子进程map + processes *gmap.IntAnyMap // Process id to Process object mapping. } -// 创建一个进程管理器 +// NewManager creates and returns a new process manager. func NewManager() *Manager { return &Manager{ processes: gmap.NewIntAnyMap(true), } } -// 创建一个进程(不执行) +// NewProcess creates and returns a Process object. func (m *Manager) NewProcess(path string, args []string, environment []string) *Process { p := NewProcess(path, args, environment) p.Manager = m return p } -// 获取当前进程管理器中的一个进程 +// GetProcess retrieves and returns a Process object. +// It returns nil if it does not find the process with given `pid`. func (m *Manager) GetProcess(pid int) *Process { if v := m.processes.Get(pid); v != nil { return v.(*Process) @@ -39,7 +40,8 @@ func (m *Manager) GetProcess(pid int) *Process { return nil } -// 添加一个已存在进程到进程管理器中 +// AddProcess adds a process to current manager. +// It does nothing if the process with given `pid` does not exist. func (m *Manager) AddProcess(pid int) { if m.processes.Get(pid) == nil { if process, err := os.FindProcess(pid); err == nil { @@ -50,12 +52,12 @@ func (m *Manager) AddProcess(pid int) { } } -// 移除进程管理器中的指定进程 +// RemoveProcess removes a process from current manager. func (m *Manager) RemoveProcess(pid int) { m.processes.Remove(pid) } -// 获取所有的进程对象,构成列表返回 +// Processes retrieves and returns all processes in current manager. func (m *Manager) Processes() []*Process { processes := make([]*Process, 0) m.processes.RLockFunc(func(m map[int]interface{}) { @@ -66,12 +68,12 @@ func (m *Manager) Processes() []*Process { return processes } -// 获取所有的进程pid,构成列表返回 +// Pids retrieves and returns all process id array in current manager. func (m *Manager) Pids() []int { return m.processes.Keys() } -// 等待所有子进程结束 +// WaitAll waits until all process exit. func (m *Manager) WaitAll() { processes := m.Processes() if len(processes) > 0 { @@ -81,7 +83,7 @@ func (m *Manager) WaitAll() { } } -// 关闭所有的进程 +// KillAll kills all processes in current manager. func (m *Manager) KillAll() error { for _, p := range m.Processes() { if err := p.Kill(); err != nil { @@ -91,7 +93,7 @@ func (m *Manager) KillAll() error { return nil } -// 向所有进程发送信号量 +// SignalAll sends a signal `sig` to all processes in current manager. func (m *Manager) SignalAll(sig os.Signal) error { for _, p := range m.Processes() { if err := p.Signal(sig); err != nil { @@ -101,24 +103,24 @@ func (m *Manager) SignalAll(sig os.Signal) error { return nil } -// 向所有进程发送消息 +// Send sends data bytes to all processes in current manager. func (m *Manager) Send(data []byte) { for _, p := range m.Processes() { p.Send(data) } } -// 向指定进程发送消息 +// SendTo sneds data bytes to specified processe in current manager. func (m *Manager) SendTo(pid int, data []byte) error { return Send(pid, data) } -// 清空管理器 +// Clear removes all processes in current manager. func (m *Manager) Clear() { m.processes.Clear() } -// 当前进程总数 +// Size returns the size of processes in current manager. func (m *Manager) Size() int { return m.processes.Size() } diff --git a/os/gproc/gproc_process.go b/os/gproc/gproc_process.go index b6062c8e3..a560c0eb8 100644 --- a/os/gproc/gproc_process.go +++ b/os/gproc/gproc_process.go @@ -1,4 +1,4 @@ -// Copyright 2018 gf Author(https://github.com/gogf/gf). All Rights Reserved. +// Copyright GoFrame Author(https://goframe.org). All Rights Reserved. // // This Source Code Form is subject to the terms of the MIT License. // If a copy of the MIT was not distributed with this file, @@ -7,8 +7,10 @@ package gproc import ( - "errors" + "context" "fmt" + "github.com/gogf/gf/errors/gcode" + "github.com/gogf/gf/errors/gerror" "github.com/gogf/gf/internal/intlog" "os" "os/exec" @@ -67,7 +69,7 @@ func (p *Process) Start() (int, error) { if p.Process != nil { return p.Pid(), nil } - p.Env = append(p.Env, fmt.Sprintf("%s=%d", gPROC_ENV_KEY_PPID_KEY, p.PPid)) + p.Env = append(p.Env, fmt.Sprintf("%s=%d", envKeyPPid, p.PPid)) if err := p.Cmd.Start(); err == nil { if p.Manager != nil { p.Manager.processes.Set(p.Process.Pid, p) @@ -87,7 +89,7 @@ func (p *Process) Run() error { } } -// PID +// Pid retrieves and returns the PID for the process. func (p *Process) Pid() int { if p.Process != nil { return p.Process.Pid @@ -95,12 +97,12 @@ func (p *Process) Pid() int { return 0 } -// Send send custom data to the process. +// Send sends custom data to the process. func (p *Process) Send(data []byte) error { if p.Process != nil { return Send(p.Process.Pid, data) } - return errors.New("invalid process") + return gerror.NewCode(gcode.CodeInvalidParameter, "invalid process") } // Release releases any resources associated with the Process p, @@ -118,12 +120,11 @@ func (p *Process) Kill() error { } if runtime.GOOS != "windows" { if err = p.Process.Release(); err != nil { - intlog.Error(err) - //return err + intlog.Error(context.TODO(), err) } } _, err = p.Process.Wait() - intlog.Error(err) + intlog.Error(context.TODO(), err) //return err return nil } else { diff --git a/os/gproc/gproc_signal.go b/os/gproc/gproc_signal.go new file mode 100644 index 000000000..1f742289f --- /dev/null +++ b/os/gproc/gproc_signal.go @@ -0,0 +1,88 @@ +// Copyright GoFrame Author(https://goframe.org). All Rights Reserved. +// +// This Source Code Form is subject to the terms of the MIT License. +// If a copy of the MIT was not distributed with this file, +// You can obtain one at https://github.com/gogf/gf. + +package gproc + +import ( + "context" + "github.com/gogf/gf/internal/intlog" + "os" + "os/signal" + "sync" + "syscall" +) + +// SigHandler defines a function type for signal handling. +type SigHandler func(sig os.Signal) + +var ( + signalHandlerMap = make(map[os.Signal][]SigHandler) + shutdownSignalMap = map[os.Signal]struct{}{ + syscall.SIGINT: {}, + syscall.SIGQUIT: {}, + syscall.SIGKILL: {}, + syscall.SIGTERM: {}, + syscall.SIGABRT: {}, + } +) + +func init() { + for sig, _ := range shutdownSignalMap { + signalHandlerMap[sig] = make([]SigHandler, 0) + } +} + +// AddSigHandler adds custom signal handler for custom one or more signals. +func AddSigHandler(handler SigHandler, signals ...os.Signal) { + for _, sig := range signals { + signalHandlerMap[sig] = append(signalHandlerMap[sig], handler) + } +} + +// AddSigHandlerShutdown adds custom signal handler for shutdown signals: +// syscall.SIGINT, +// syscall.SIGQUIT, +// syscall.SIGKILL, +// syscall.SIGTERM, +// syscall.SIGABRT. +func AddSigHandlerShutdown(handler ...SigHandler) { + for _, h := range handler { + for sig, _ := range shutdownSignalMap { + signalHandlerMap[sig] = append(signalHandlerMap[sig], h) + } + } +} + +// Listen blocks and does signal listening and handling. +func Listen() { + signals := make([]os.Signal, 0) + for sig, _ := range signalHandlerMap { + signals = append(signals, sig) + } + sigChan := make(chan os.Signal, 1) + signal.Notify(sigChan, signals...) + var sig os.Signal + for { + wg := sync.WaitGroup{} + sig = <-sigChan + intlog.Printf(context.TODO(), `signal received: %s`, sig.String()) + if handlers, ok := signalHandlerMap[sig]; ok { + for _, handler := range handlers { + wg.Add(1) + go func(handler SigHandler, sig os.Signal) { + defer wg.Done() + handler(sig) + }(handler, sig) + } + } + // If it is shutdown signal, it exits this signal listening. + if _, ok := shutdownSignalMap[sig]; ok { + // Wait until signal handlers done. + wg.Wait() + return + } + } +} diff --git a/os/gres/gres.go b/os/gres/gres.go index 636cca352..0c4489a8a 100644 --- a/os/gres/gres.go +++ b/os/gres/gres.go @@ -1,4 +1,4 @@ -// Copyright 2019 gf Author(https://github.com/gogf/gf). All Rights Reserved. +// Copyright GoFrame Author(https://goframe.org). All Rights Reserved. // // This Source Code Form is subject to the terms of the MIT License. // If a copy of the MIT was not distributed with this file, diff --git a/os/gres/gres_file.go b/os/gres/gres_file.go index dd2f2ccb7..a6c48b6a7 100644 --- a/os/gres/gres_file.go +++ b/os/gres/gres_file.go @@ -1,4 +1,4 @@ -// Copyright 2019 gf Author(https://github.com/gogf/gf). All Rights Reserved. +// Copyright GoFrame Author(https://goframe.org). All Rights Reserved. // // This Source Code Form is subject to the terms of the MIT License. // If a copy of the MIT was not distributed with this file, diff --git a/os/gres/gres_func.go b/os/gres/gres_func.go index 52d449ab8..4995fbc29 100644 --- a/os/gres/gres_func.go +++ b/os/gres/gres_func.go @@ -1,4 +1,4 @@ -// Copyright 2019 gf Author(https://github.com/gogf/gf). All Rights Reserved. +// Copyright GoFrame Author(https://goframe.org). All Rights Reserved. // // This Source Code Form is subject to the terms of the MIT License. // If a copy of the MIT was not distributed with this file, @@ -20,7 +20,7 @@ import ( ) const ( - gPACKAGE_TEMPLATE = ` + packedGoSouceTemplate = ` package %s import "github.com/gogf/gf/os/gres" @@ -39,8 +39,10 @@ func init() { // // Note that parameter <srcPaths> supports multiple paths join with ','. func Pack(srcPaths string, keyPrefix ...string) ([]byte, error) { - buffer := bytes.NewBuffer(nil) - headerPrefix := "" + var ( + buffer = bytes.NewBuffer(nil) + headerPrefix = "" + ) if len(keyPrefix) > 0 && keyPrefix[0] != "" { headerPrefix = keyPrefix[0] } @@ -79,7 +81,7 @@ func PackToGoFile(srcPath, goFilePath, pkgName string, keyPrefix ...string) erro } return gfile.PutContents( goFilePath, - fmt.Sprintf(gstr.TrimLeft(gPACKAGE_TEMPLATE), pkgName, gbase64.EncodeToString(data)), + fmt.Sprintf(gstr.TrimLeft(packedGoSouceTemplate), pkgName, gbase64.EncodeToString(data)), ) } diff --git a/os/gres/gres_func_zip.go b/os/gres/gres_func_zip.go index 22af99258..268ac87f0 100644 --- a/os/gres/gres_func_zip.go +++ b/os/gres/gres_func_zip.go @@ -1,4 +1,4 @@ -// Copyright 2020 gf Author(https://github.com/gogf/gf). All Rights Reserved. +// Copyright GoFrame Author(https://goframe.org). All Rights Reserved. // // This Source Code Form is subject to the terms of the MIT License. // If a copy of the MIT was not distributed with this file, @@ -8,9 +8,11 @@ package gres import ( "archive/zip" + "context" "github.com/gogf/gf/internal/fileinfo" "github.com/gogf/gf/internal/intlog" "github.com/gogf/gf/os/gfile" + "github.com/gogf/gf/text/gregex" "io" "os" "strings" @@ -59,20 +61,20 @@ func doZipPathWriter(path string, exclude string, zipWriter *zip.Writer, prefix if len(prefix) > 0 && prefix[0] != "" { headerPrefix = prefix[0] } - headerPrefix = strings.TrimRight(headerPrefix, "\\/") + headerPrefix = strings.TrimRight(headerPrefix, `\/`) if len(headerPrefix) > 0 && gfile.IsDir(path) { headerPrefix += "/" } if headerPrefix == "" { headerPrefix = gfile.Basename(path) } - headerPrefix = strings.Replace(headerPrefix, "//", "/", -1) + headerPrefix = strings.Replace(headerPrefix, `//`, `/`, -1) for _, file := range files { if exclude == file { - intlog.Printf(`exclude file path: %s`, file) + intlog.Printf(context.TODO(), `exclude file path: %s`, file) continue } - err := zipFile(file, headerPrefix+gfile.Dir(file[len(path):]), zipWriter) + err = zipFile(file, headerPrefix+gfile.Dir(file[len(path):]), zipWriter) if err != nil { return err } @@ -82,14 +84,14 @@ func doZipPathWriter(path string, exclude string, zipWriter *zip.Writer, prefix var name string path = headerPrefix for { - name = gfile.Basename(path) - err := zipFileVirtual( + name = strings.Replace(gfile.Basename(path), `\`, `/`, -1) + err = zipFileVirtual( fileinfo.New(name, 0, os.ModeDir|os.ModePerm, time.Now()), path, zipWriter, ) if err != nil { return err } - if path == "/" || !strings.Contains(path, "/") { + if path == `/` || !strings.Contains(path, `/`) { break } path = gfile.Dir(path) @@ -101,7 +103,7 @@ func doZipPathWriter(path string, exclude string, zipWriter *zip.Writer, prefix // zipFile compresses the file of given <path> and writes the content to <zw>. // The parameter <prefix> indicates the path prefix for zip file. func zipFile(path string, prefix string, zw *zip.Writer) error { - prefix = strings.Replace(prefix, "//", "/", -1) + prefix = strings.Replace(prefix, `//`, `/`, -1) file, err := os.Open(path) if err != nil { return nil @@ -136,7 +138,7 @@ func zipFileVirtual(info os.FileInfo, path string, zw *zip.Writer) error { return err } header.Name = path - if _, err := zw.CreateHeader(header); err != nil { + if _, err = zw.CreateHeader(header); err != nil { return err } return nil @@ -148,9 +150,9 @@ func createFileHeader(info os.FileInfo, prefix string) (*zip.FileHeader, error) return nil, err } if len(prefix) > 0 { - prefix = strings.Replace(prefix, `\`, `/`, -1) - prefix = strings.TrimRight(prefix, `/`) header.Name = prefix + `/` + header.Name + header.Name = strings.Replace(header.Name, `\`, `/`, -1) + header.Name, _ = gregex.ReplaceString(`/{2,}`, `/`, header.Name) } return header, nil } diff --git a/os/gres/gres_http_file.go b/os/gres/gres_http_file.go index d56edeca1..62c5f56db 100644 --- a/os/gres/gres_http_file.go +++ b/os/gres/gres_http_file.go @@ -1,4 +1,4 @@ -// Copyright 2019 gf Author(https://github.com/gogf/gf). All Rights Reserved. +// Copyright GoFrame Author(https://goframe.org). All Rights Reserved. // // This Source Code Form is subject to the terms of the MIT License. // If a copy of the MIT was not distributed with this file, diff --git a/os/gres/gres_instance.go b/os/gres/gres_instance.go index afaefc22b..9971af7f4 100644 --- a/os/gres/gres_instance.go +++ b/os/gres/gres_instance.go @@ -1,4 +1,4 @@ -// Copyright 2017 gf Author(https://github.com/gogf/gf). All Rights Reserved. +// Copyright GoFrame Author(https://goframe.org). All Rights Reserved. // // This Source Code Form is subject to the terms of the MIT License. // If a copy of the MIT was not distributed with this file, diff --git a/os/gres/gres_resource.go b/os/gres/gres_resource.go index ce410108f..cb9213b00 100644 --- a/os/gres/gres_resource.go +++ b/os/gres/gres_resource.go @@ -1,4 +1,4 @@ -// Copyright 2019 gf Author(https://github.com/gogf/gf). All Rights Reserved. +// Copyright GoFrame Author(https://goframe.org). All Rights Reserved. // // This Source Code Form is subject to the terms of the MIT License. // If a copy of the MIT was not distributed with this file, @@ -7,6 +7,7 @@ package gres import ( + "context" "fmt" "github.com/gogf/gf/internal/intlog" "os" @@ -42,7 +43,7 @@ func New() *Resource { func (r *Resource) Add(content string, prefix ...string) error { files, err := UnpackContent(content) if err != nil { - intlog.Printf("Add resource files failed: %v", err) + intlog.Printf(context.TODO(), "Add resource files failed: %v", err) return err } namePrefix := "" @@ -53,7 +54,7 @@ func (r *Resource) Add(content string, prefix ...string) error { files[i].resource = r r.tree.Set(namePrefix+files[i].file.Name, files[i]) } - intlog.Printf("Add %d files to resource manager", r.tree.Size()) + intlog.Printf(context.TODO(), "Add %d files to resource manager", r.tree.Size()) return nil } @@ -74,6 +75,7 @@ func (r *Resource) Get(path string) *File { return nil } path = strings.Replace(path, "\\", "/", -1) + path = strings.Replace(path, "//", "/", -1) if path != "/" { for path[len(path)-1] == '/' { path = path[:len(path)-1] @@ -93,6 +95,7 @@ func (r *Resource) Get(path string) *File { func (r *Resource) GetWithIndex(path string, indexFiles []string) *File { // Necessary for double char '/' replacement in prefix. path = strings.Replace(path, "\\", "/", -1) + path = strings.Replace(path, "//", "/", -1) if path != "/" { for path[len(path)-1] == '/' { path = path[:len(path)-1] @@ -114,7 +117,6 @@ func (r *Resource) GetWithIndex(path string, indexFiles []string) *File { // GetContent directly returns the content of <path>. func (r *Resource) GetContent(path string) []byte { - path = strings.Replace(path, "\\", "/", -1) file := r.Get(path) if file != nil { return file.Content() @@ -169,6 +171,7 @@ func (r *Resource) ScanDirFile(path string, pattern string, recursive ...bool) [ // It scans directory recursively if given parameter <recursive> is true. func (r *Resource) doScanDir(path string, pattern string, recursive bool, onlyFile bool) []*File { path = strings.Replace(path, "\\", "/", -1) + path = strings.Replace(path, "//", "/", -1) if path != "/" { for path[len(path)-1] == '/' { path = path[:len(path)-1] diff --git a/os/gres/gres_z_unit_1_test.go b/os/gres/gres_z_unit_1_test.go index 3cc0d85c3..56ed0ef95 100644 --- a/os/gres/gres_z_unit_1_test.go +++ b/os/gres/gres_z_unit_1_test.go @@ -1,4 +1,4 @@ -// Copyright 2019 gf Author(https://github.com/gogf/gf). All Rights Reserved. +// Copyright GoFrame Author(https://goframe.org). All Rights Reserved. // // This Source Code Form is subject to the terms of the MIT License. // If a copy of the MIT was not distributed with this file, diff --git a/os/gres/gres_z_unit_2_test.go b/os/gres/gres_z_unit_2_test.go index f051f952b..8e7bc3496 100644 --- a/os/gres/gres_z_unit_2_test.go +++ b/os/gres/gres_z_unit_2_test.go @@ -1,4 +1,4 @@ -// Copyright 2019 gf Author(https://github.com/gogf/gf). All Rights Reserved. +// Copyright GoFrame Author(https://goframe.org). All Rights Reserved. // // This Source Code Form is subject to the terms of the MIT License. // If a copy of the MIT was not distributed with this file, diff --git a/os/gres/testdata/data/data.go b/os/gres/testdata/data/data.go index 4c5967568..53bc0f480 100644 --- a/os/gres/testdata/data/data.go +++ b/os/gres/testdata/data/data.go @@ -3,7 +3,7 @@ package data import "github.com/gogf/gf/os/gres" func init() { - if err := gres.Add("H4sIAAAAAAAC/7RaeThU7f9+sq8hSyohYpAxtkiyFLKMfYtSGgwRI4y9BaUsSSlU2l4U6bVNRJJS2bdsSYuiENlHROh3RXLOGEve37frin88931/7vNs53NuQzQlFQegA3RgEOtoCCD/1gB6YOuKs3d0QM38ksC7ujibmVKDVR3b7Q+IVegiUVriZYYZZoZ1NaU1eo0oFKoJpV2FLNUW10aat4VJ6OiViZ9rNzY0NNQqE682B3dlZaWbZF7KvJTJkpaRy2mQMqRAIxDCDok2F+RMKagA+PnTEE1Ld+3ZeTtzAIA9AGBhecxz8lx8JRxxjv+vyoznlJnPKfseqKZIqgwAP2s7I6gymj/KpiU9jI23/jUY+icLl7Xuz2CkracH3tXlf2a+0VyJZnMlmqrLvV3afI55Kv8Hz8BkTuCeOYGStO2nln4GzKQCl3wUAFxyuwErkg7QAztHdymUh6fN9PDmdykHlv8kuSDDf/1H4rEeeCkJvA9+ziUJvTqkNlJCylSrumaLdtWWVX/KLLh/cftaAADbohwMsxzT2HO45DFDKFWm2KfNWcw6qt+YK3dM+r85Jg1zTJq8Y8bzqvOP5KVctmPS045Jwx2bj6mnQihZtmPSK1juTIAeOEptw6GwOPgKbyvXqior25NhZjgEpC7llhPD3Y1VJ46wqtGzzoqz9Uaq8gEA+JfH4IRZiMHv3bYxt/rPJRH3+OQljr7LPRB6b8cTEQT1ZqJdSVanif8lsGqW08iB30sKALBleZzungtx5kn+tHkevHGt3P7XfpnavwnF8zufnQtn1gkAf2rclncYCQAQWZSPZZbP7xBSTR9OOVclwbuw/rZ/9IyXTykU7yjdfJNARTdL1dVSHiEEANi0bCrTPf+PVOSn1S+qFSxExt9DkXaO7igsboVrEYKAOoR1dnZdbIrOzRH3r062HAAA1r8i8HZ1d7YjIRCrQZVrG2WYGTLQQgmus3kiSAmW8MAJ8189cML89sAXM8+DHOP5a2hunebbi1nyTx/tf0M24weMbNqOTGNDP4LtG+JdJcgyubHT2plrenYu2xF3zxU4wglH+O0I9OBfYIHPueF8dJPIxul6/4Zoxg3YDWN2buRlkG4a2K+9j9YuOT9WQymmN44VGLJuHshvT5w8XHF/tKZ0/LrmGM54QzCe2x+cVv2Z1AWbGPOWniVk+GasIcc3YxFhbsJYZc+ZxNCLniLlW9ok0z0rMIl7Hshvk3wgk5uQmV5aZSSCRopVV9bcN5NukDE01qpCVxuUVaJzzMSQ2vXZhoScT4Qy2azS+59+1ZdRNr32/GfMdBnetENiw7EMBtU/JbL6cUUaAAAM/lbdjKX/UZ3o9Gr1n7FebpyVNzZC2YKaf1bcuLButD6JuPlnAB1E3AquF2yzw+0dnbH/uzsGnMYJs9w9cubYL9LZ2VV2zrEmaURkFcUsMUdxhcWvi4boosSsMGJ3z+VsRQveNZ4ZoybFAQCIJV995ihnLhzzF9+8pY5XDWof01AQPVi4sf6GQa9RWwr1n1umsiHqgQQAQHxRYnZSYtM9/+8LKETX97iRftj7i9MiM2vPDOQ6Dxf6ze1SZarl9RgAgOOis5YeKnWF7xHurq54lK2Hxwr2mzWQ4SgPvK8zVmIW6JdRdcbJ0/ujVkXNllpk9b862caScmLJGTSMMYnml74Id9jd6bQU3ZzUEZ3IZenRSPFnToq12HRvBQBILyqeYZbd0QXjgF2BfHYYAMrZ1cFV4gjO4Y9+51jfbQ2HN5QkSUeHCmyPeXlZ3dK+8hS3GkZyxKXUWNa+rFTcXti/ucTdkL35PkdZ1NZ9pgKnFDgIQj+ibMu9om5fG3XX7/et8WwhWjURS/qnjqmMjhak/5jqDM18IMtFpR0EQMAtehoXRgBYH41+PA4Av4dyHgUAPZg3679S6jy6/evsVtn46QUVWB3a06IUMKzrBg66iq77WuqeJanZ5sghck7VA7FrUgT/tG/n2KZJETzDhWDKH0b5ybqJN1d/ZqGOKlfvO41hHeBb82rrxpCE4K2vA4r5gxr/cTI0C5GgY6JJ4KZaU8flMkzpwKq1nZWFK/zcqcKjx479U+Dwj2yxGbcVGs14JyxgE6sYE4V2cJcBtnXCNLny4gnhMVde2QuhDue7xKUjGFomix/Wp93H85/nu6pSE7lddPjJ2eRKvv2tjhyX+c6tPqlmhvlJwzZwQmr4K1O6io7gR4w6SiBYCyuFOSIyYa3AdUV93DvnvcqJN11TkWpjRl+0KGnYSgTi3cTjJXZxpt2OT0mLTwkMFjzvwKaVmyfDMMJS+6RVliZmN7Gu8W025YYL69St139az+58MGlEfuLMF7586ghV4bGfNx9vN95P6WtOPDP5Qf3Ec3qfzaHFtz5a5Nkx9Imx7U46oFE4tXYizj3HP5HzpG4i4cgjtfrghIM/1/7oD8D8vDxw8yvlj0yVPH6OnvT9DdTIR8P9lEC1UPiQ0r+0AxcTGJkVRdzGxL6JZd85ppJjZrXz6N4vIoYfzhlI9YwI+j86IGjiH1Iwue7HFMJcXrkt3FMjM1jT+QHL2R3yd16c3ikQGYRQ6q+oEKb7bnH55L2vYwOBB5Tuv8ui7nRJizkbkC/U0lPpNLRdvvd0vU8vcdR2D7raISxW94vfZ9yZU50x1pctr30FLcYjgnaisYwK6Wynj8danCgI2tRQGttkqXLPUtKMc8M7f5uBUU+b8y/kL7+7RKeU94W4ToyW8VK27Hqcdl7WDT4eNcXaAJdgXh+62gEvV2EsY9rXFwdxcWUUTc14/nZk/J7X3cbBTYnPr4QwVZwUuJLTIyb4Kn+jeBpbkdvJC2HnpHUQzD5JnA14Pc1wZJEG3bVQU/PHr/149vbJ9ga1sXopa77frSKum2o6VVV7Llcdxx5ocBYjmNJeW89jf+9Qpa1tgLmvsVCtaA61cdsTdeWYD5rSzyftg0eq33CzieKth1kOGbbRPa6ofJTw+cJP9Hod98ATBmHGQ3douPdvxXNJeN3r7owM5tETrP8cjSNqD4hm/fhW+8oj6taDmMl1Glrfzoaw7Lrf5aZSMz7AyW20mZb1YTkC6Vij9vwxtUP3kSRdn3PaNM4KL3MMvAXqxowfpfd1mW7nsfhWth8zYOXa6WSVen/k+bBI4ZrwPGbaR8+FV6n1qiicspp6+jz2CgJl20sYWbv/tSn74Ko4nvJwi6IrrdiPpuLD+Na7+eiXg4HdUlKHPqcHq212QIS/9l6PsHqJOJQug+PfhSuKq3YaUC2kPa0jyNw4vvEdy5jAYfvdiOBWWwv5rOH3Zvwq5p1JN7EK3EeuKqjfGYltkNu56ZstY+ID1d7TjYdL8xQ35CoMb6QIfJZ5AJ3SXKGWsEbSHcNfGHSmibn/vNuG2+Zavtm2io1i6cFuKo2WZeZnNsbafDC6sX1z/kRHxMupScPmHt4JVJeyQco9az+HA6E1oT2HWyy6Ca1aV/Gnw7tlR9TqDY9pfStL5nqHn7hwwx/Jzn6w7P4acT4+rYoof+rdSYPvJ53k/D9ZBVs+MZs6EuZd/BZR4ecdf93SAXVc3JddkCkssSb/Ab2Nl8JAccrqj2dar1yd0nxbMpEQ9aNa925JyCX5KI3T9xjOIu/nfQgZuM9XQnB033a/9aBo1fk0jtcnCQRBpSf+1x49zH7zbTD4wz58cgGxrT0gjCr1tIWXn3kDb6vM7VU18ZEYVd7vWUdy5fydK7+91T2uY1UVPrhWszpyawh1HjU93zYBopi7bmyvbHfIpPN1B60WjsicUE0G3s6h7BytY1WHXjxu4q7HDuix+BOHFOS9t0fLeenEuq/1DVTLziLu3r171+DuQaLBv6oZE1QGipWKjXYZGgUmsXQsUa41IuKDLW06PAV6PrmdTvKHh952vNld93Tkx2HsQ2bm/ocTyGJKs1WRhVz7r8o3MXGY5AnzNfEW6w1faXRMOV2a63BiPLv541U5pWiUJP8tqRBb686YvL2anKktL9FPv//EXWWvCy8tPmSXnB5WeO3ZSO16Nfa3Jcx85ng7OU8GjwJThFlx3vUUy/ih8JZI5oh4XbodrnKcLC1d7vu60p5WSClW7sSJ2Zs//c7Itp4aFW+AotZzNfVbTTP8ul7f+5DgBSvNz0qCn1RdvAOPBglkVjUXFbUd0byflGqvWNpXa5X8QvdSu7N23OSn+KuJShYfD/bHOGwyJ7xuVuizYh2YDKFk1XROycmPFLgoLccS38eHpMvh9RnKJQiwb/2WZffudkf5x+YboaFMb473UmLTkJY2yt8dHxx8Oxn3IgezLrV9r+cHLlvBsKuftqUXxf7L+DWZv2lT0fWe3ljdJ6icoLgIAyq0nrj+q4LXpnybmtolT0kdVQ80YY4w6WR6iaHadqL/jMznolDlsQQd7yqh3dbiY6uVHh8vld4XO3R1qGB3RidRK/FqtazSgb5vMoZeTfZj6At1btkUX/M6WsZzx3LxOQxnCHG7enMS95W8D7r95MTPvtbBcR3/Z0n/hG1g1kcM3hG90ZWQW78t6Tpx8Gz6Ue/JS71x7SXlFm2ZNXVxEXbtFXvM85Noz/dkyan14BE7nt84Ss3249arClzkuLHAk+Sea+mvd+xJOvS4a6O+9lgD47O76lMBSacaeQ8UnE2VsnWm5fSzSj0nVXf8VKC+Zb0p3UD/RQHfHWEDurosF9TiON50ZcVUK/Ide7MRMdHrr9GtESfAb8GBV0U37Pfhl5ds3fvC9u2aN4ncyYMdvXnttg2l/NsszPdeLPV700OQaDCO5pHKYZH25u+o1bL1L35+V54BSUBUjVZHpY1+sd/K3U38YOcQWoMYY9mjX0GfuXZS9bRPxBpdqzgB5c2jX9GRlaaBwfGx5dJurCFr2QP4S7ZcK1C/Vfr5uuKpH3fwVo+ZGvbto+wRaVQ0cT9avZ5X6v2z/q1vGVyx6yniCoS5rX3aV+GLhZua1Q0GJN9tdyn7jlX+Mqy1mZgQM1p48WRe62glX2XHgyfNR9uJOrpFGXbCCNai917v/I9m3sJnD+9StqYRKxms5h/BO7blcK7tvyH8SCKNGGEkOKzMOtHiPiI1yUydn1OIbU3LqixPihrYhm1Na3lVffnfyyeNdaw84wnD9yUPbM5yu1eB/Ib2/Ejsl4mJqDDD5eZbbug59t3VvGyY9q4eoqwGlerTcVvE14OqQSVZ+taL9mZrg3Er+2rDtpx0pcwJPLdAc3pNpsKd97qIi7kvHeNdLCedQrZcd2nj3Z/+CrNfO21zgybizHiBBvOLPV2VlM/TUeUZ+0YsIxoUBzSESvN3uOszj7YGjrwtRD5c82/IUaeDj9QdrnrTTsb27jPBEfLG17RqHzE55R1wgecf/536uEyOVOFwbl+8TO1mEeUSj/63wWuKgvyMJviftG5oKOZ9cQvb59L0stnmc7mrR9DVBGv3M0knfRXfK09mCMkGUm8TyBup7d3SXXdN40IqDbK/2eT70fMvLJCItcEH35XWH2crWL/twwe+6/hBGzdPvLPUnZej8o5ZigaJuKjID4Xj5UENJj/VBEKn0o7R8F424fTllH6W9nxUtaivpY2mqDmVPbp4l9hUGjMzhTkLJ67ym9aX13LcXlyuJsf1Ht30ik32YUv4Nyd7pLF4qNEedZWGL9qNp1XGQwyXR3ftuOQrrKTUDqUmi/TvWtkB+owHbm7cx4RXPUHT0cC47mNX6u4vulP5yaf1ojKjMTZjHCyd40PuMQceE2zuhe7vp80zDTf2ehtr3nWysu6zcOY5/JB+phYuW4cQHqSo3Hn6WUPNye8Oo3vDO9TRvJ580fQ1p66bHeyXjMTGEsW1q3nCAzJ1WAq6wmSzK5sjwhlYlRTYimgaNNrRWOLNhqTCa33veIQGDepMLiZms+JCTa4dkN5SJxTZmTN0a4eg1See7ag3wnnHhxD6sSOFnPcyNB1orAIpjt0z1T9CLM8NlJ18/ann05m3VJeL2YYF9mDOBNugCziodzD0yPYNS0eu88gRYW18MOL0KJ3+4nt3NPp4942RmMZi6WMNtdk9IN6tR12FsXyrHZXfa6yMnrXGYxm0N7J8i8rZl80hlxkjVT1Y3MKUXVNHDgS0SR/UjGtSQdM6X0/auEslibcga2J7pU+abf8exsh+mzgEuubM7oGcfuVoXSZk7e3xPc+m2P90/fpvBWQwAXCJeanm+sybD84O6yNxCA95vRWvrnUp5mei+uggHVwEBilOi+aprufJYNj0zDza2frClEFMf6KBZtHeiPCEZ/vCwj1M2ycCC7f6ZCcRuOyJhjR8hf0p3+98TesuvBF+Ur6A+QMf4hRfaIG/X4fe0AbtvTQb/QfqHlRa+Pd99bTno3d4kpsuwBB/MHhn9Qt5ngT50pOe17uf7P7eN8k4WxGm97vpWQDAgyV7+L8qWkHv5tdrIB7rcsQZg8eSM2RI3AhlLlKmZVZaidZONRT5/W3VTKuiUruR8k9z4XGMCBAEAGxc9KWVFUrmjPF19fz9OfHvXl35yMCgbF1xeIwjDusO15+Spq+tV1GJNjPUqa7Zol1ZiUYap6S2E8okszxGRhndhoc9mAm1NZJZn1LS9KvQd1PnGiZCtG/75QEAUouK4SEnxt7VFb+IkooalPacDOIoi9tCGkyLFbWW/hJGVsMhLMZuYQ3lWnq6MxpmejSSWcQjnvgFdYz7n6/fCgBA/b2Omd9wHQQj/KD36lCmI2alImVVmZ8IgUZ3Q0W9rBgYGBjkhK4IKdz16hvDaYQ+vMugHBo9dltIbpzVKCOsuzuq4V3QtRdRptRC5wsaBG6/2Sux4VjIOjntMdFowwRj6QqOKI814eEu0VFEos4aGaJx+CVJ5p2lja92yXYn2cVgrl6IuWkd1AiSqVJwG46FoJ2HC49T/SmyvPrCpigAwPjfTmLpFUxiMl5Jk503JHNkbs3tdR6u4iT5HLNMHnJzgwCfA3M84krNO1bIQ+7Zp6RVV6KrRfW0ZydhXYVIWZWR8ct7qe27Srvahe07gjk+36Lfcru9M5b/8NxncqqxTtpfc1B/0cfDSU6HC8ZxJZ8n+RfCmv4hRcY/z+FhmSy4fZV6vdMfKlf/ByrpBahInpSNim87KRX5bv8s1SInxioKDso5rdC41BpA/+fvEgN//VwkwUUKBE03McOAbs4BkeR85jDI56BmF+bPnQqrwPxUFFwANHu0DibA68/gxVJRpHjQqBAHDI+HAiyeX1qsLmZYXY9JociUR77DPYvRu3MVJSCXNIJXA00EccGqUYUMXyBpRAoGjQIxwMASZ8HgkaLFDKGCGfL9N8AKfJChAuTyQ3Dp0JwP3Ad3yPAF8kOkYNCAD9yHV7Ng8KDQ8n0QowakqSA4N/TbGhOMG0sNyKeCSCGgcRw4RMksBGnsZ54KSMIGDqFIA8ineEghoHEWFhjEvVkIMsGc5aNw0YIFMzfLfx6Bv1GWNS8ZYfMy//fQ+XEaeAnQ5At8ak7BEcjFaUjBoCkXOJg1HVgqOrP82p5CwWAxGbgcaIoFLoeCHiwVkyEFg+ZV4GCuJGBkYjDLr60OCgYLvMDlQDMpnDA5HAxgicALKRY0fALHOkaCNT/Tslhlq2GVfYRikYZXSM4NSLAEfqoKMYJlhFdI8aDBEThe9Hw8MuGU5RdJzQQWDp/ARUGjHtwwUTrzQMiET0jhoOEMONyX+XDz0yKLbUp0sE0pmBmQy3csfFCwwdS8mB1ONt9BigPNVMBxtqwGiwQ4SHGgQQlW+OUBhkOSxyCFgcYe4NeztSxg8YwFKRQ0lsAOgzpPCkWSmljsUdHDHpUiKyAbalju5cafFZALNcALgUYM4Jf5PMhwMqGGxXQwwHRsYwPk8wkkeyKkYwe31BcGMD+fQIoE7ZTBD3YPXrBgv2/5B7sGHyDtscEFQFtg8FIC+MCiPbbFXGWFudoLRZrfQIMLgvav+GCCZPnBshto816fIB0pHhhqITlUch2NefsgpLkEh9TYBJbX2CKFhLZy4JBV5CDJ9SmW/1QIAmCxjhBcGbRrA1c2RgZmWf5BGzRwSIwgWF7zhxQS2muBQ5aRg/xb/zhh/hltBku2bODyoD0Vfpi86wthkWvZkMJC+ydwWDohsPz2zPIPZg8ILGRjoab59QfKQBn4swFA+PW+Av4vAAD//6Jqwe1DNwAA"); err != nil { + if err := gres.Add("H4sIAAAAAAAC/7RaeTiU6/9+sq8hSyohYpAxtkiyFLKMfYtSGgxxGGHsLShlSUqh0nZQpGObiCMplX3LlrQoCpF9RIR+V4T3HWPJ+X27rvjHc9/3536f7f28tyGakooD0AE6MIh1NASQf+sAPbB1xdk7OqBmfkngXV2czUypwZqOnfaHxCp0kSgt8TLDDDPDuprSGr1GFArVhNKuQpZqi2sjzdvCJHT0ysTPtxsbGhpqlYlXm4N7srLSTTIvZV7KZEnLyOU0SBlSoBEIYYdEm4typhRUAPz8aYimpbv+7IKdOQDAHgCwuDzmeXkuvhKOOMf/V2XG88rM55V9D1RTJFUGwFprChOoMpo5ZdOSXN7TYn4Nhv7J4mVtmBuMtPX0wLu6/M/MN5ov0Wy+RFN1ubfLm8+xQOX/4BmYzAvcNy9Qkrb99PLPgJlU4LKPAoDLbjdhRdIBemDn6C6F8vC0mR7e/C7l0MqfJBdk+K//SDzWAy8lgffBz7skoVeH1EZKSJlqVdds067atmauzIIHl3auBwCwLcnBMMsxjT2PSx4zhFJlin3anKWso/qNuXrHpP+bY9Iwx6TJO2a8oDr/SF7KFTsmPe2YNNyxhZh6KoSSFTsmvYrlzgTogaPUDhwKi4Ov8LZyraqysn0ZZoZDQOpybjkx3N1YdeIoqxo966w4W2+kKh8AgH9lDE6YxRj83u0Yc6v/XBJxn09e4ti73EOh93c9EUFQbyXalWR1mvhfBmtmOY0c+L2kAADbVsbp7rkYZ57kT5vnwZvXyx187Zep/ZtQPL/z2flwZp0AMFfjjry/kAAAkSX5WGb5/I4g1fThlPNVErwL6+/4R894+ZRC8a7SrTcJVHSzVF0t5RFCAIAtK6Yy3ff/SEV+Wv2iWsVCZPw9FGnn6I7C4la5FiEIqCNYZ2fXpabo/Bxx/+pkywEAYP0jAm9Xd2c7EgKxGlS5tlGGmSEDLZTgBpsngpRgGQ+cMP/VAyfMbw98MQs8yDFeuIbm12m+vZgl//TR/idkM37AyKbtyDQ29CPYviHeU4Isk5u7rZ25pmfnih1x91yFI5xwhN+OQA/+RRb4vBvOx7aIbJ6u90+IZtyA3TBm50ZeBummgf3a+2j9svNjLZRieuNYhSEbFoD89sTJwxU3pzWl49c1x3DGG4Lx/P7gtGZuUhdsYcxbfpaQ4ZuxhhzfjEWE+QljlT1vEkMveoqUb3mTTPetwiTuBSC/TfKBTG5CZnpplZEIGilWXVnzwEy6QcbQWKsKXW1QVonOMRNDatdnGxJyPhHKZLNKH3z6VV9G2fTa858x02V4yy6JTcczGFTnSmT144o0AAAY/Km6GUv/ozrR6dXqP2O93Dgrb2yEsgU1/6y4cWHdaH0ScQvPADqIuFVcL9hmh9s7OmP/d3cMOI0TZqV75MyxX6Szu6vsvGNN0ojIGopZYo7iCotfFw3RJYlZYcTunivZiha9azwzRk2KAwAQy776zFPOXDgWLr4FSx2vGtQ+pqEgerhwc/1Ng16jthTquVumsiHqoQQAQHxJYnZSYtN9/+8LKETX94SRftj7S9MiM2vPDuQ6Dxf6ze9SZarl9RgAgOOSs5YeKnWV7xHurq54lK2Hxyr2m3WQ4SgPvK8zVmIW6JdRdcbJ0/ujVkXNtlpk9T862caScmLJGTSMMYnml78Id9jd7bQU3ZrUEZ3IZenRSDE3J8VabLq3AwCklxTPMMvu6IJxwK5CPjsMAOXs6uAqcRTnMKffOdZ3R8Nfm0qSpKNDBXbGvLyibmlfeZpbDSM54lJqLGtfVipuL+zfXOJuyN78gKMsavsBU4HTChwEoR9RtuVeUXeuj7rr9/vWeLYQrZqIJf1Tx1VGRwvSf0x1hmY+lOWi0g4CIOA2PY0LIwCsj0Y/ngCA30M5jwKAHsybjV8pdR7d+XV2q2z+9IIKrA3taVEKGNZ1A4ddRTd8LXXPktRsc+QQOa/qgdgzKYJ/2rd7bMukCJ7hYjDlD6P8ZN3EW2s/s1BHlav3ncGwDvCte7V9c0hC8PbXAcX8QY1/OxmahUjQMdEkcFOtq+NyGaZ0YNXaycrCFX7+dOGx48f/LnD4W7bYjNsKjWa8GxawhVWMiUI7uMsA2zphmlx56aTwmCuv7MVQhwtd4tIRDC2Txf/Wpz3A81/gu6ZSE7lTdPjJueRKvoOtjhxX+M6vPaVmhvlJwzZwUmr4K1O6io7gR4w6SiBYCyuFOSoyYa3AdVV93DvnvcrJN11TkWpjRl+0KGnYSgTi3cTjJfZwpt2JT0mLTwkMFrzgwKaVmyfDMMJS+6RVliZmL7Gu8W025aaLG9StN37ayO58OGlEfuLsF7586ghV4bGftx7vND5I6WtOPDv5Qf3kc3qfraHFtz9a5Nkx9Imx7U06pFE4tX4izj3HP5HzlG4i4egjtfrghMM/1//oD8D8vDJw6yvlj0yVPH6OnvSDDdTIR8P9lEC1UPiI0j+0A5cSGJkVRdzGxL6JZd89rpJjZrX72P4vIoYfzhtI9YwI+j86JGjiH1IwueHHFMJcXrkt3FMjM1jT+SHLuV3yd1+c2S0QGYRQ6q+oEKb7bnHl1P2vYwOBh5QevMui7nRJizkXkC/U0lPpNLRTvvdMvU8vcdR2H7raISxW94vfZ9zZ050x1lcsr38FLcYjgnaisYwK6WxnTsRanCwI2tJQGttkqXLfUtKMc9M7f5uBUU+bCy/kr7y7TKeU94W4QYyW8XK27Eacdl7WTT4eNcXaAJdgXh+62gEvV2EsY9rXF4dxcWUUTc14/nZk/L7X3cbBTYnPr4YwVZwSuJrTIyb4Kn+zeBpbkdupi2HnpXUQzD5JnA14Pc1wZJEG3fVQU/PHr/149vfJ9ga1sXopa77fqyKum2o6VVV7Plcdxx5ocA4jmNJeW89jf/9Ipa1tgLmvsVCtaA61cdsTdeWYD5rSzyftg0eq33CzieKth1mOGLbRPa6ofJTw+eJP9EYd98CTBmHGQ3dpuA9ux3NJeN3v7owM5tETrP8cjSNqD4hm/fhW+8oj6vbDmMkNGlrfzoWw7HnQ5aZSMz7AyW20lZb133IE0rFG7fljaofuo0m6Pue1aZwVXuYYeAvUjRk/Su/rMt3JY/Gt7CBmwMq108kq9cHI82GRwnXhecy0j54Lr1HrVVE4bTX19HnsVQTKtpcwsv7ga1P2wTVxPOXhFkVXW7EfTcWH8a338tEvBwO7paSOfE4PVtvqgAh/7b0RYfUScSRdBse/B1cUV+00oFpIe0ZHkLlxfPM7ljGBv+z3IoJbbS3ks4bfm/GrmHcm3cIqcB+9pqB+dyS2QW73lm+2jIkPVXvPNP5Vmqe4KVdheDNF4LPMQ+iU5gq1hHWS7hj+wqCzTcz9F9w23THX8s22VWwUSw92U2m0LDM/uznW5oPRzZ1b8yc6Il5OTRo29/BOoLqUDVLuW/s5HAqtCe35q8Wim9CqdQ1/JrxbdkSt3vC41reyZK53+ImLN/2R7OyHyx6sE+fj06qI8qfemzT4ftJJzv+TVbDlE7Opo2HexW8RFX7e8TcsHVAnxH3ZBZnCEmvyH9LbeCkMFKes/Xi29eq1Kc23JRMJUT+qde+VhFyWj9I4c5/hHPJB3oeQgQd8JQRH9x0PWg+LVl1I43h9ikAQVHrif/3Rv9lvvg0GfziATy4gtrUHhFGlnrHw8jNv4G2VubOmJj4So8r7Petorpy/c+W3t7ondKyqwgfXa1ZHbg+hzqOm59shQBRz143tle0OmXS+4aDVwhGZE6rJwNs5lJ2jdbzqyIvHTdz12AE9Fn/ikIK8985oOS+dWPf1voFq2VnEvXv37hncO0g0+Ec1Y4LKQLFSsdEuQ6PAJJaOJcq1RkR8sKVNh6dAzye300n+r6G3HW/21j0d+fEX9l9m5v5/J5DFlGZrIgu5Dl6Tb2LiMMkT5mviLdYbvtromHKmNNfh5Hh288drckrRKEn+21IhttadMXn7NTlTW16in37/ibvGXhdeWnzELjk9rPD6s5HajWrsb0uY+czxdnKeDB4Fpgiz4rwbKZbxQ+EtkcwR8bp0u1zlOFlautwPdKU9rZBSrNyNE7M3f/qdkW0jNSreAEWt52rqt5Zm+HW9vvcRwYtWmp+VBD+pungHHgsSyKxqLipqO6r5ICnVXrG0r9Yq+YXu5XZn7bjJT/HXEpUsPh7uj3HYYk543azQZ8U6MBlCyarpnJKTHylwSVqOJb6PD0mXw+szlEsQYN/+Lcvu3Z2O8o/NN0NDmd6c6KXEpiEtbZS/Oz48/HYy7kUOZkNq+37PD1y2gmHXPu1IL4r9h/FrMn/TlqIbPb2xuk9QOUFxEQZUaD1x/VcFr035tjS1S56WOqYeaMIcYdLJ9BJDteNk/1mZz0WhymMJOt5VQnutxcfWKj0+USp9IHbo2lDB3oxOolbitWpZpUN932QMvZrsx9AX69yyKb7mdbSM547l4nMYzhLi9vTmJB4oeR9058nJn32tg+M6/s+S/g7bxKyPGLwrerMrIbd+R9IN4uC59GPek5d749pLyi3aMmvq4iLs2iv2mecn0V7oyZJT68Ejdj2/eYya7cftVxW4yHFjgSfJPdfTX+/al3Tkcddmfe2xBsZn99SnApJON/IeKjiXKmXrTMvpZ5V6XqruxOlAfct6U7qB/ksCvrvCBnR1WS6qxXG86cqKqVbkO/5mM2Ki11+jWyNOgN+CA6+Kbjjowy8v2br/he3bdW8SuZMHO3rz2m0bSvl3WJjvv1Tq96aHINFgHM0jlcMi7c3fUatl61/8/J48A5KAqBqtjkob/WK/nbub+MHOIbQGMcayT7+CPnP9pOoZn4h1ulZxAspbR7+iIytNA4PjY8ul3VhD1rMH8Jdsu16gfrv08w3F0z/u4q0eMzUcOEDZI9KoaOJ+rHojr9T7Z/3b3zK4YjdSxBUIc1v7tK/BFws3NasbDEi+2+lS9h2r/GVYaysxIWa08NKpvNbRSr7KjodPmo+1E3V0izLshBGsRe+93vkfy7yNzx7eo2xNI1YyWM0/gndsy+Fc339T+JFEGjHCSHBYmXWixX1EapKZOj+nENuallVZnhQ1sAPbmtbyqvrKP1dOGetYecYThh9IHtqa5Xa/AvkN7fmR2C8TE1FhhsvNt9zUc/y7q3nZMO09PURZDSrVp+OOiK8HVYNKsvTtF+3N1gbjVvbVhm056UqZE3hugeb0mkyFu+91EZdyXzrGu1hOOoVsu+HSxnsw/RXmoHba1gZNxNnxAg3mF/u6Kimfp6PKMw6MWEY0KA5oCJXm73LXZx5tDRx5W4j8d90/IcecDj9Sd7jmTTsZ23vABEfIG1/Xqn3U5LR3wEWev/136+MyOVKFw7l98TK1W0WUSzz63wavKwryM5rgf9K6qaGY98VtbJ9L08tmm8/lrh5B1xKs3c8mnfJVfK88mSEkG0i9QyBvpLZ3W3fddY2LqTTI/maT78cuvLBAItYHH35XWn+CrWDjjg8f+G7gB23cPPHOUndfjso7ZikaJOKiIj8UjpcHNZj8VBMInUo7TsN7xYTTl1P6WdrzUdWivpY2mqLmVPbo4j1iU2nMzBTmLJy4ym9aX17LcXtxuZqc0Ht0yys22Yct4Z+c7JHG4qFGe9Q1Gr5oN55WGQ8xXB7d9ROSr7CSUruUmizSv2tlB+gzHrq1+QATXvUkTUcD44aPXal7v+hO5Sef0YvKjMbYjHGwdI4PucccekywuR96sJ82zzTc2OttrHnXqcq6z8KZ5/FD+plauGwdQniQonLnmWcNNae+O4zuD+9QR/N68kXT15y+YXa4XzISG0sU167mCQ/I1GEp6AqTza5sjghnYFVSYCuiadBoR2OJtxqSCq/3veMRGjSoM7mUmM2KCzW5fkh6W51QZGfO0O1dglafeHai3gjnnRhC6MeOFHLez9B0oLEKpDh+31T/KLE8N1B28vWnnk9n31JdKWYbFtiHORtsgy7goN7F0CPbNywducEjR4S18eGI06N0+kvv3dHoE903R2Iai6WPN9Rm94B4tx51Fcby7XZUfq+xMnrWGo9l0N7I8m0q5142h1xhjFT1YHELU3ZNHTkU0CZ9WDOuSQVN63wjafMelSTegqyJnZU+abb9+xgj+23iEOias3sHcvqVo3WZkLV3xvc9m2Kf6/r13w7IYALgMvNyzfWZNx+cHdZH4gge8norXl3rUszPRPXRQTq4CAxSnBHNU93Ik8Gw5Zl5tLP1xSmDmP5EA82i/RHhCc8OhIV7mLZPBBZu98lOInDZEw1p+Ar7U77f/ZrWXXgz/JR8AfMHPsRpvtACf78OvaFN2vtpNvsP1D2stPDv++ppz0fv8CQ3XYAh/nDw7uoX8jwJ8qWnPG90P9n7vW+ScbYiTO9303MAgIfL9vB/VbSK3s2v10A81uWoMwaPJWfIkLgRylykTMustBKtnWoo8vvbqplWRaV2I+Vcc+FxjAgQBABsXvKllRVK5ozxdfX8/Tnxz15d+cjAoGxdcXiMIw7rDtefkqavrVdRiTYz1Kmu2aZdWYlGGqekthPKJLM8RkYZ3YaHPZgJtTWSWZ9S0vSr0PdS5xsmQrRv++UBAFJLiuEhJ8be1RW/hJKKGpT2vAziKIvbYhpMixW1lv8SRlbDESzGbnEN5Vp6ujMaZno0klnEo574RXWM+1+o3w4AQP25jpnfcB0EI/yg99pQpqNmpSJlVZmfCIFG90JFvawYGBgY5ISuCinc8+obw2mE/nuPQTk0euyOkNw4q1FGWHd3VMO7oOsvokyphS4UNAjcebNfYtPxkA1y2mOi0YYJxtIVHFEe68LDXaKjiESddTJE4/DLksy7Sxtf7ZHtTrKLwVy7GHPLOqgRJFOl4DYdD0E7DxeeoJorsrz64pYoAMD4n05i6VVMYjJeSZOdNyRzZH7N7XceruIk+RyzQh5yc4MAnwPzPOJKzbtWyUPu2aekVVeiq0X1tGcnYV2FSFmVkfHL+6nte0q72oXtO4I5Pt+m33anvTOW/6/5z+RUY520v+ag/pKPh5OcDheM42o+T/IvhjX9Q4qMf57DwzJZcPsq9XqnP1Su/Q9U0otQkTwpGxXfdlIq8t3+WaolTow1FByU81qhcal1gH7u7xIDf/1cIsFFCgRNNzHDgG7NA5HkfOYxyOegZhfmz90Ka8DCVBRcADR7tAEmwGtu8FKpKFI8aFSIA4bHQwGWzi8tVRczrK7HpFBkyiPf4Z7F6N29hhKQSxrBq4Emgrhg1ahChi+SNCIFg0aBGGBgibNg8EjRUoZQwQz5/htgFT7IUAFy+SG4dGjOB+6DO2T4IvkhUjBowAfuw6tZMHhQaOU+iFED0lQQnBv6bY0Jxo2lBuRTQaQQ0DgOHKJkFoI09rNABSRhA4dQpAHkUzykENA4CwsM4v4sBJlgzspRuGjBopmblT+PwN8oK5qXjLB5mf976MI4DbwEaPIFPjWn4Ajk4jSkYNCUCxzMmg4sF51ZeW1PoWCwmAxcDjTFApdDQQ+Wi8mQgkHzKnAwVxIwMjGYlddWBwWDBV7gcqCZFE6YHA4GsEzghRQLGj6BYx0nwVqYaVmqsrWwyj5CsUjDKyTnBiRYAj9VhRjBCsIrpHjQ4AgcL3ohHplwysqLpGYCi4dP4KKgUQ9umCidBSBkwiekcNBwBhzuy0K4hWmRpTYlOtimFMwMyOU7Fj8o2GBqXswOJ5vvIMWBZirgONvWgiUCHKQ40KAEK/zyAMMhyWOQwkBjD/Dr2XoWsHTGghQKGktgh0FdIIUiSU0s9ajoYY9KkRWQDTWs9HLjzwrIhRrghUAjBvDLfB5kOJlQw1I6GGA6drAB8vkEkj0R0rGDW+oLA1iYTyBFgnbK4Ae7By9YtN+38oNdgw+Q9tjgAqAtMHgpAXxgyR7bUq6ywlzthSItbKDBBUH7V3wwQbL8YMUNtAWvT5COFA8MtZAcKrmOxoJ9ENJcgkNqbAEra2yRQkJbOXDIKnKQ5PoUK38qBAGwVEcIrgzatYErGyMDsyL/oA0aOCRGEKys+UMKCe21wCHLyEH+qX+cMP+MtoJlWzZwedCeCj9M3o3FsMi1bEhhof0TOCydEFh5e2blB7MHBBaysVDT/PoDZaAM/NkAIPx6XwH/FwAA//+hv1N+QzcAAA=="); err != nil { panic("add binary content to resource manager failed: " + err.Error()) } } diff --git a/os/gres/testdata/testdata.go b/os/gres/testdata/testdata.go index 1b3381164..ebf318e6c 100644 --- a/os/gres/testdata/testdata.go +++ b/os/gres/testdata/testdata.go @@ -3,7 +3,7 @@ package testdata import "github.com/gogf/gf/os/gres" func init() { - if err := gres.Add("H4sIAAAAAAAC/7SaCTTU6xvH3+xZQpYUIWKQMQyRZCnKMpaxRimNNWKEka0FpSxJKRRtF0W61ogkXZV935KUohDZR0Tof8rF/MaMJt1/59TUyfk83/f7Pu9veeaLRlHTcAIGAMAVt5toQPCLGawG9o7Odh4IG1esvaODqQktWNX6JuUgGkXPQPiD5BEcRAi4jacHztXllyQGMGLnCCEJkCb9+y8pnKuL809q93b7gxKVunCElmQ5OsMU3VBbVqvXjEAgWhDa1fAybUltuFlnqJSOXrnkhS4jNBqtVS5ZYwbuyckhW2TrZOtks5Gy8rlNMoZUKBhM1CHR+pK8KRUNAN+//9BqoiHfZgYAsF9W6wYyWl18pByxjv+pTONFmXsXZUrTd535tUweIpn/Ny/RiyJNFkVef3bR9tciiTvo/2Gi0aI+s0V9XwPUlYj1Le1wxgV9to7uMis4ImsgAISHp/UKTgffEsiP33CcnQdORgrnjVu0S0qvAa4Nl5Ix0aqp3aJdvWXVwnoLH1zevg4AwL5sJTZopZ8VFumkycHUqrMcAAAWyp1E/qmTyP/CSSTESSRpJ42WrNcvgp/6N51E/nQSCXVyKVlPNav0d5x0lNmGpcBJYmlrIAC4raP7gjDKt4N9CQRhh/2jHSHgIA7bOTu7Qi9VnRVa1eXlezNM0aNA5gpYNW+a+2cnG86fhv9+GS9Xd2dbojIStYgKbcMMUzQjPWGZG+yeMOIyFLnihPlvXHHC/OuKD2aJK7lGaN832ybdGj+Wht8XUJA6/ibv4Gq2eekF9hIWggCA9SsoOecQpORPgzKN0L5ZNq/x95R1/MF8oZs7rZy5AQCsv+uRu+cKPOIlxfnXI8J7yELf5Et/t34etHGd/IFXvpnai/44H98kthEAwL2CcnP+QG5Z8/2Tn9Hz7EIYC4E/dp8HHq/7ZQ9xLi3kexiurv9Hj1VQ1L8uOXm4Yhd0p3T/uIei59zK+rG9XkWNd/yi8pxWLRyCwk1M+b/uJLJV58wiVXXOtKzFprLMWbSNcQA1S1yVUttM9q7ANn4yqH9t8yY4DFmZ6WXVhmIouERNVe0DU2STLNpIqxpVY1Behco1lYBrN+ags3I/ZJXLZZc9+PBjrRnlP0+s35y9LmObdkjxnshgVFtYLpsvd4QBAMBgZRrnTP5DjeI/z7jf3GbIT7Hxx4SrmNMKzkucEtWN0ieSuHRHWKESf/x1BZuxbikFYYdd7t6QV4EPczdSmz7Kpr54yG284GoCAADB3y7mhKH0khtyf8dTMVixzs7e8guOtUnjYquo5stzllSaywAAxJctz02ivLsnJVezudK0kgXE15xnRogZSQAAjMJ3msXCP88sidO65AqBUwvsmtytKH6oaGPjTYMBw84U2oVHGhU04qEUAEBy2fLrSZc32fufn7VgXZ+Thvqhby//lJpZf244z3msyHfxEleuVtGIAQA4Lit4LUTw/68fieo4YcjVIdOLtJvxtqXZPcZ+BE8yhg6Cx3604pbfqezuSa4yhX1osy3/CBwAILZsVS5o1bkWhBReXPH8/emnu/9QKd1VvvU6gYZhvmBve0W4CABg028WNNn7HxZc7und3dUV90fvQT8ACBsPjxVcVHmWQBAeOB9nO6l53I+VNxgl/7w7a1XWbqmH1/ytk2MkLS+RnEHHFJ1oduWTaLft3R4L8c1J3VGJ3BYezVQLFzuJduu+rQAA5LILYYNqcHTBOKzk/rCeBAbh7OrgKnUU67CwFucYn21NR3hLk5BRIULbo+uualjYV53hUcdIj7uUGcnZl5dJ2ov6tZa6ozlaH3CWR27dbyJ0RpEzS+RbpE3Fscg71yfc9Yd8aj3b8ZYt+NKh2ROqExOF6d9me0IyH8px02gHAuB/ezWdCxMAbI8n3p8EQNBDJZ8KgH7M6w2fqXUe3/nxdKm68cMLGrAmpL9d2X9M1w0cchVf/7nMPVtas9ORU+yCmgds14wY7p/BnZObZsRwjJeCqL8ZFiTrJt5a85GVNrJCY/Ashm1YYO3LrRuDE4K2vvIvEQxs/ssJbRosxcBMl8BDs7aB22WM2oFNazsbK3fYhTNFx0+c+KvQ4S+5ElMeSxSK6W6o/yY2CWYq7aBeA7uOaZPkqsunRCdd+eUuhThc7JVEhjO2z5Q8akx7gBO8KBCrWhuxXXzs6fnkKoEDHY6cVwUurDmtbor5Tsc+fEpm7DNzuqqO8HuMBkIoSMtOBnNUbNpKkfuaxpRX7lvVU697ZyPUJw0/aVHTsZcKxbtJxkvt4kq7E5+SFp8SECR80YFdKy9flnGctf5phxxd9B58Q3NbDjXvpfUaVhs+bOBwPpQ0rjB97pNAAW24mujk91tPthsdoPYxw5+beadx6vlq780hJbffm+fbMg5KsO9JOri7aHbddJx7rl8i12ndxKyjj9UbgxIOfV/3bcgf8/3q8K3P1N8yVfMFOfvTDzTRwh+PDVEDtSLRw8p/0w9fTmBiURJzm5T4IpFz94RqrqnlzuP7Pomh310wkOkfF/Z7fFDY2C+4cGb9t1mYmYJKZ5jn7swgTeeHrOd3KNx9cXanUEQgTHmoslKU4av51dP3P08OBxxUfvAmm7bHJS36vH+BSHt/ldPodoWBs43eA/gJm72oGofQGN1Pvh+x5870RFtdtbj+GbQbjQvbiscwKaaznz0ZY36qMHBTU1lMi4XqfQtpUy7eN37WwxOe1hdfKFx9c4VBOf8Tfr0EPdOVHLkNWO387JsCfOpK9f4uQfzeDPXDx1xF7ZjSPr84hI0rp2ppxQl2weP3vuozCmpJfH4tmLnytNC13H4J4ZcFGyXT2IvdTl8KvYDUgbF4J3E14fQ0w+DFuxmuh5iYPXnly7dvUG4gsJPtmIrm2z2qkrqpJrPV9RfyNLAcAQbnMcIpXfWNfPb3D1fZ2Pib+RiJ1Ivn0hp1PtVQiX6niXw+Yx80XvOah10cZzXGehjdyfCksupxwsdL31EbdNwDThmEGo3epeM5sBXHLXXsfl9PRBCfnnDjxygsXntYPPvbl/qXHpG3H0bPrN+t9eV8MOuuB71uqrVTw1w8hpvp2R5VwOCOterPn9A69B1N0vW+oE3nrFiXa+Al1DBp9Dh9sNdkO5/5l/IDmGFL1x4ny9QH48/HxIrWhuWz0D9+LrpKfUBV8Yzl7D/PY67BEDYDWePrDrwy4RhZFcdXEWZefK3D7r2J5Biu414Bqm4koE9G5vDH9CD1zQ6wsFdeG2CWdbDD6bJYwV3Y4rgap2G1IvqzOsIszVMb37BOCh2x3wML6rAxV8gee2sqqGrWk3TLTpHnaKyixt3xmCb5nZu+2DAlPlQbONt8pCxfiTdPcWwjVcCzzIOolNZK9YS10u4YwaLAcy0sQxfdeO+Yafnk2Cg1S6QHuak2W5SbndsYY/3O8Ob2zQXT3eF1szPo1n7+aUSvikHKfStfh4MhtSH9R9rN+7I6tGJxZ8P65MbVG9EntL6UJ3O/wU1fuukH5+A4VP5graSAgFZlpB/tnqSRtzNO8n4fLIMsnprOHg31KmmDVfp6xd+wcECclPThEGYOTawteLja+pjicEnKmvfnOq7Fzmq2lU4nRH6r0b1XGnxFIXL32fuM5+EP8t8FDz8QKM1ydN/2oOOQePXFNM5Xp7OyhJWf+l1//Cjn9ZeRoHf7ccmF+M4u/1Ca1LPmx3zNmvg7ZO+sqo2PwKjxf80+mifv51z1pU33pI5lddjIOs2aiK3BtPm0qwW2CeEl3HVjBuT6gmecbzhotXNG5IZoMvL3jObkap2oPvziSQtPo92wHqsfflRRwWt7lPwxnRj3dT4B6jnZ+D179uwa2TOCN/hbLWOaxkCpSqnZNmN3oXEMA2uka62Y5Eh7pw5foZ53Xo+TwpHRtu7Xexr+Gf92xO4RC8vQo2l4CbXpqogi7gOxCi3MnMb5ogIt/CV6Y9eaHVPOluU5nJrKaX0fK68chZAWvC0TbGPVE52/T5Mrtb0O9c/X79hYjoawspLDtsnpoUXXn43Xb1DnaCtlETDD2cp7MnoUmsBMS/JvpFjEj4a1R7CEx+sy7HCV52Jt73Xf35v2T6WMUtVOrIS92T9fmdg30CLiDRC0eq4mvmvoxl416nsdFr5kqflRWfiDmotXwPFAoczq1uLizqOaD5JS7ZXKBustk1/oXuly1o6b+RAfm6hs/v7QULTDJrOsV62Kg5ZswzPB1Gyazim5BRFCl5HyrPGDAnCGXH7v0bwsIY6tX7Jt39zprnjfejMkhPn1yQFquzS4hbXKV8eHh9pm4l7kYtandu3zfMdtIxwa+2FbenHM30yfkwVbNhXf6B+I0X2KyA2MCzegQelJ6r8sfGUisKmlS/qMzHGNAGOWcOMe5joMzbZTQ+dkPxaHqEwm6HhVi+yxkpxco/zkZBlyf8xo7GjhnowevFZibI2c8sHBL7LoYy32k6hLDW45VJ/zu9un8ibzcLmM57Lidg3kJu4vfRt45+mp74MdI1M6fs+S/grlZdGHjdwVv9mbkNe4LekGfuR8+nGvmSsDcV2lFeadmbUNceG2XZV7zQqS6C/2Z8ur9+NgO57fPE7L/u32y0psxJSR0NPk/uvpr3bsTTr8pHejvvZkE9Ozexqz/klnmvkPFp5PlbFxpufytUy9INNw8kyAvkWjCcPw0GUhnx2hw7q6rJfU4zhf92ZH1ygJnHi9ETY94Le7b3eckKA5J04N1XTAW1BBumPfC5u2ta8TeZJHugfyu2yaygS3mZvtu1zm+7o/S6rJKIpPJpcV6SXYXa9l41fy/J4CIzwLVj1RE5k28cl+K08f/p2tQ0gtbJJ1r37l6sx1M2pnvcPX6lrGCalsnviMiqgyCQiKj6lAurEFr+PwFyzdcr1Q43bZxxtKZ77dxVk+YW7av5+6X6xZydj9eM0Gfpm3z4a2tjG62m2giisU5bHy7lqFKxFtadUwGJZ+s92l/Kudyqcxrc34hOiJosun8zsmqgSquh8+bT3ehdfRLc6wFYWxFb899sbveOZtXM7YLhUrOonSkRrBcZxjZy7XuqGboo+l0vDhhsJjKmzT7e7jMjMstAW5RXYdadlVFUmRw9vsOtLaX9Zc/fvqaSMdS8/4rLEH0gc3Z7vdr4R/QXm+xw/JRodXmmLzCix4+098dTUrH6O/pwcrr0WkenffEfPxoGlSTUbeftHVamUwZWlfg+7MTVfOnMbxCLWm12Yq3n2rC7ucV+cY72Ix4xS85YZLJ/+B9JeYA9ppm5s0YeemCnezvNjbW0X9PB1RkbF/3CK8SWl4t0hZwQ53fZaJjoDxtiL4o7V/Bx93OvRYwyHWi34mZmC/MTYrf2pth/ZR4zNe/pf4/vLbqY/N5EwVDePxwcnWbxZTKfUYagtaWxzoazgt+LSDt6mE/8Vtu0GXlrpW648Vrh6BsQlW7ueSTvsovVWZyRCRC6DdJpQ/Xj+wpa/h+u5LqXTwoVbjr8cvvjCHw9YFHXpT1niSvXDDtnfvBG7gRqzdPHHOMnfrJhQcs5UMErGREe+KpioCm4y/qwuFzKadoOO/aszlw4V8lvZ8Qq14sL2Trrg1lSOqZJfEbBoLC5UZKxe26ovWp1fyPMe4XY1P6j2+dSwm2Zs94e/cnPHmktFme0QsnUCUG1+HrIcENp/h+knpl3bSMjuUW8zTv2rl+OszHby1cT8zTu0UXXcT0/r3val7PunOFiSf1YvMjMJYT3Ky9kyNukcffJJlfT/kwBB9vkmY0bG2GLPe01UNH0UzL+BG9TO1sDk6WWGBSio9Z5811Z7+6jCxL6xbA8XvKRC1uvbMDdNDQ9IRdjF4Se0avjD/TB3Wwt5QuZyq1vAwRjZlRfZiuqbdXSg7/K2mpKLrg2/4REYMGowvJ+awYUOMrx9EbmkQiejJHb29Q9jyA992xGvR/JOjMP2Y8SKu+xmaDnSWAVQn7pvoH8VX5AXIzbz60P/hXBvN1RL2MaG9mHNB1qhCTtodjP1yg2PIiPUeuWJszQ/HnR6nr7781h2FOtl3czy6uQR5oqk+px/Eu/VrqDJVbLWl8X1lJ6tntfuJLMoLXrFF9Xxda/BVpgg1D1a3UBXX1PGD/p3IQ5pxLaooeucbSRt3qSbxF2ZPb6/yTrMZ2ssUMWQdB0PVntsznDukEqXLDK+/M7X32SzHwlx66LZ/BjMAV1goe4Ode//B2tp5Sx3GEbzBStbUu5QIMtO8d0AGFYMRqrPi+Wob+DIYNz0zi3K2ujRrED2UaKBZvC88LOHZ/tAwD5Ou6YCird45SVnc9ng0nUDRUMrXu5/T+opuhp1WKGR5JwA7IxBS6OfbrTfKq72PbqPfcMPDKnO/wc+e9gKrHZ7mpQsxxh8K2lnzQoEvQaHstOeNvqd7vg7OMM2vCzPw1eQ8AOAhhe+5ODuXo84Y3J+9HM5DSBk0KmmIMBMr1zItq0Jpp6LF/v162VSrskq7mXphlvUkWgwIAwA2Lqube2lJZ4yPqydOZgX6RcjCEDauWBzGEWvnDl1LSpq+tl5lFcoUrVNTu0W7qgoFN0pJ7coql872GJ9gchsb82DJqq+Vzv6QkqZfjbqXujirE6FvG1IAAMgsK0mIvCR7V1fcMnoqaxHai2LwE6xu5JSYlChp/XpEtYySw3YYW/JKKrT0dOeUzA0JpbPxRz1xZNVM+V1s3AoAQKxUzdwnVE2WIW7Ea00I81HTMrHy6swPWQGG90LEj1kyMjIyyotcE1G8d2xwErs75NE9RpWQqMk7IvJTbIYZoX19kU1vAq+/iDShFblY2CR05/U+Kd4TwevltSfFo9AJRshKzkiPtWFhLlGReLzOWlm8UdgVaZadZc0vd8n1JdlGY2IvRd+yCmwGyTQpWN4TwSjnsaKTNAtLrai5tCkSADC1shZHrqDFyfqGJNlPRL2zeDr3OY9VcxF9+fhb1Uj1TBa0NxarSSq37vijaqR6IiWtpgpVI66nPd+iDZVi5dWGRnX3U7t2lfV2idp3B3F+vL16y52unhjBI4vBAZrJHvofHaq/7LbxklfjgnFcyZf3ossTf/4hQ8JRz7Ex2WyooVV6Az+/xl/zxwWRZAoS7aC1qk8XcUEAfK1sDQkL0s4X/Ml7FBNvtdSdVVSc1OQzdvPDzoGdPz7JJO7IIzggCHUixNLE3TxpzjvCRJvAAgmAxIBw0iSSKTFiKmEAbQOEqrgKUJKNIwYShsV4IMD3RECK9BFmuzgguCAqsGzebLmdYITshAg1IJ0MIw9YAwHYQwBEeSboegizW3yQ9dxfAiGTDCNGEoa22CBILhpAPgJGuT+uhBjkCvzJgACW9YcwkQX158sSCJm8FzGSMIoF9ceKFpAPdlHuz6MFDFGKaxFAKre1CJiCAIhSXORVsEMgqnRg2RQX1BTCqBXU53OkOKRSXMRIwlgVFDlIEkkisUX5ak3pwbLpLKg0wtgUVNoNUhxS6SxiJGFACopkYwCUpa8oX63PUiQkZwWVRhiC4oVIKyLFIZGzIiYSpp2gROnVgKIo1XJr5YSs9dpSInFmiuiSSpBkgt4YP5JBkcpMEVMJk0pQ6j5GQHEmivJl15GkQjJPUIGE2SJ+iEBeJkBp5okYSpgGgkKfkoMuDSktt2hWyKKtmcEysSKoNMKUxTqItOSllCUxDmIaYYgHSuNiAb/MDRHTCJM53BDaFRI0ohgQMYwwZwN9JGNZAyiJ9hADCXMw6yHA86SBRGGd5XZiLYSnzAqWy9IQgwjjK1DQHSiIOCyzRBFBIgUKEmADy2VfiEGEwQ8uCCgKCiIRZ6GcRccOfpFUofzhw2uBRRRCofThLA8CIAqhQFdEGAaBPtvPLIGQCKEsp4kNoilgLVguT0J0uyOYrULbu4wEZmmehJhHONOE7luZAPjFfJZy228LAvIzUaggwmEldIFdSyC/K4obIsp8E/j1wBMqjnDSKAI9w2Rh5AaeS155CWaHQhD2NiHwO5PLJbc5gjEgFPyMPJjUUIkYTDh0g4LRwuB3ZoqU75nSZvDrCR5UJeF8DaoymCyMIl8JR2lQ8Dh5MCW+Ek7FoGBbEfA7c7nlfOWF+DpKHkw0YoNKJZx+iUKkaoiC3x2xEcMJJ11QeA4FcCR5L0gNy+a9+L5TFAaWjM5o6X78rypQBVs4ALD+8egD/hcAAP//TL8FiKM6AAA="); err != nil { + if err := gres.Add("H4sIAAAAAAAC/7SaCTTU6xvH3+xZQpYUIWKQMQyRZCnKMpaxRimNNWKEka0FpSxJKRRtF0W61ogkXZV935KUohDZR0Tof8rF/MaMJt1/59TUyfk83/f7Pu9veeaLRlHTcAIGAMAVt5toQPCLGawG9o7Odh4IG1esvaODqQktWNX6JuUgGkXPQPiD5BEcRAi4jacHztXllyQGMGLnCCEJkCb9+y8pnKuL809q93b7gxKVunCElmQ5OsMU3VBbVqvXjEAgWhDa1fAybUltuFlnqJSOXrnkhS4jNBqtVS5ZYwbuyckhW2TrZOtks5Gy8rlNMoZUKBhM1CHR+pK8KRUNAN+//9BqoiHfZgYAsF9W6wYyWl18pByxjv+pTONFmXsXZUrTd535tUweIpn/Ny/RiyJNFkVef3bR9tciiTvo/2Gi0aI+s0V9XwPUlYj1Le1wxgV9to7uMis4ImsgAISHp/UKTgffEsiP33CcnQdORgrnjVu0S0qvAa4Nl5Ix0aqp3aJdvWXVwnoLH1zevg4AwL5sJTZopZ8VFumkycHUqrMcAAAWyp1E/qmTyP/CSSTESSRpJ42WrNcvgp/6N51E/nQSCXVyKVlPNav0d5x0lNmGpcBJYmlrIAC4raP7gjDKt4N9CQRhh/2jHSHgIA7bOTu7Qi9VnRVa1eXlezNM0aNA5gpYNW+a+2cnG86fhv9+GS9Xd2dbojIStYgKbcMMUzQjPWGZG+yeMOIyFLnihPlvXHHC/OuKD2aJK7lGaN832ybdGj+Wht8XUJA6/ibv4Gq2eekF9hIWggCA9SsoOecQpORPgzKN0L5ZNq/x95R1/MF8oZs7rZy5AQCsv+uRu+cKPOIlxfnXI8J7yELf5Et/t34etHGd/IFXvpnai/44H98kthEAwL2CcnP+QG5Z8/2Tn9Hz7EIYC4E/dp8HHq/7ZQ9xLi3kexiurv9Hj1VQ1L8uOXm4Yhd0p3T/uIei59zK+rG9XkWNd/yi8pxWLRyCwk1M+b/uJLJV58wiVXXOtKzFprLMWbSNcQA1S1yVUttM9q7ANn4yqH9t8yY4DFmZ6WXVhmIouERNVe0DU2STLNpIqxpVY1Behco1lYBrN+ags3I/ZJXLZZc9+PBjrRnlP0+s35y9LmObdkjxnshgVFtYLpsvd4QBAMBgZRrnTP5DjeI/z7jf3GbIT7Hxx4SrmNMKzkucEtWN0ieSuHRHWKESf/x1BZuxbikFYYdd7t6QV4EPczdSmz7Kpr54yG284GoCAADB3y7mhKH0khtyf8dTMVixzs7e8guOtUnjYquo5stzllSaywAAxJctz02ivLsnJVezudK0kgXE15xnRogZSQAAjMJ3msXCP88sidO65AqBUwvsmtytKH6oaGPjTYMBw84U2oVHGhU04qEUAEBy2fLrSZc32fufn7VgXZ+Thvqhby//lJpZf244z3msyHfxEleuVtGIAQA4Lit4LUTw/68fieo4YcjVIdOLtJvxtqXZPcZ+BE8yhg6Cx3604pbfqezuSa4yhX1osy3/CBwAILZsVS5o1bkWhBReXPH8/emnu/9QKd1VvvU6gYZhvmBve0W4CABg028WNNn7HxZc7und3dUV90fvQT8ACBsPjxVcVHmWQBAeOB9nO6l53I+VNxgl/7w7a1XWbqmH1/ytk2MkLS+RnEHHFJ1oduWTaLft3R4L8c1J3VGJ3BYezVQLFzuJduu+rQAA5LILYYNqcHTBOKzk/rCeBAbh7OrgKnUU67CwFucYn21NR3hLk5BRIULbo+uualjYV53hUcdIj7uUGcnZl5dJ2ov6tZa6ozlaH3CWR27dbyJ0RpEzS+RbpE3Fscg71yfc9Yd8aj3b8ZYt+NKh2ROqExOF6d9me0IyH8px02gHAuB/ezWdCxMAbI8n3p8EQNBDJZ8KgH7M6w2fqXUe3/nxdKm68cMLGrAmpL9d2X9M1w0cchVf/7nMPVtas9ORU+yCmgds14wY7p/BnZObZsRwjJeCqL8ZFiTrJt5a85GVNrJCY/Ashm1YYO3LrRuDE4K2vvIvEQxs/ssJbRosxcBMl8BDs7aB22WM2oFNazsbK3fYhTNFx0+c+KvQ4S+5ElMeSxSK6W6o/yY2CWYq7aBeA7uOaZPkqsunRCdd+eUuhThc7JVEhjO2z5Q8akx7gBO8KBCrWhuxXXzs6fnkKoEDHY6cVwUurDmtbor5Tsc+fEpm7DNzuqqO8HuMBkIoSMtOBnNUbNpKkfuaxpRX7lvVU697ZyPUJw0/aVHTsZcKxbtJxkvt4kq7E5+SFp8SECR80YFdKy9flnGctf5phxxd9B58Q3NbDjXvpfUaVhs+bOBwPpQ0rjB97pNAAW24mujk91tPthsdoPYxw5+beadx6vlq780hJbffm+fbMg5KsO9JOri7aHbddJx7rl8i12ndxKyjj9UbgxIOfV/3bcgf8/3q8K3P1N8yVfMFOfvTDzTRwh+PDVEDtSLRw8p/0w9fTmBiURJzm5T4IpFz94RqrqnlzuP7Pomh310wkOkfF/Z7fFDY2C+4cGb9t1mYmYJKZ5jn7swgTeeHrOd3KNx9cXanUEQgTHmoslKU4av51dP3P08OBxxUfvAmm7bHJS36vH+BSHt/ldPodoWBs43eA/gJm72oGofQGN1Pvh+x5870RFtdtbj+GbQbjQvbiscwKaaznz0ZY36qMHBTU1lMi4XqfQtpUy7eN37WwxOe1hdfKFx9c4VBOf8Tfr0EPdOVHLkNWO387JsCfOpK9f4uQfzeDPXDx1xF7ZjSPr84hI0rp2ppxQl2weP3vuozCmpJfH4tmLnytNC13H4J4ZcFGyXT2IvdTl8KvYDUgbF4J3E14fQ0w+DFuxmuh5iYPXnly7dvUG4gsJPtmIrm2z2qkrqpJrPV9RfyNLAcAQbnMcIpXfWNfPb3D1fZ2Pib+RiJ1Ivn0hp1PtVQiX6niXw+Yx80XvOah10cZzXGehjdyfCksupxwsdL31EbdNwDThmEGo3epeM5sBXHLXXsfl9PRBCfnnDjxygsXntYPPvbl/qXHpG3H0bPrN+t9eV8MOuuB71uqrVTw1w8hpvp2R5VwOCOterPn9A69B1N0vW+oE3nrFiXa+Al1DBp9Dh9sNdkO5/5l/IDmGFL1x4ny9QH48/HxIrWhuWz0D9+LrpKfUBV8Yzl7D/PY67BEDYDWePrDrwy4RhZFcdXEWZefK3D7r2J5Biu414Bqm4koE9G5vDH9CD1zQ6wsFdeG2CWdbDD6bJYwV3Y4rgap2G1IvqzOsIszVMb37BOCh2x3wML6rAxV8gee2sqqGrWk3TLTpHnaKyixt3xmCb5nZu+2DAlPlQbONt8pCxfiTdPcWwjVcCzzIOolNZK9YS10u4YwaLAcy0sQxfdeO+Yafnk2Cg1S6QHuak2W5SbndsYY/3O8Ob2zQXT3eF1szPo1n7+aUSvikHKfStfh4MhtSH9R9rN+7I6tGJxZ8P65MbVG9EntL6UJ3O/wU1fuukH5+A4VP5graSAgFZlpB/tnqSRtzNO8n4fLIMsnprOHg31KmmDVfp6xd+wcECclPThEGYOTawteLja+pjicEnKmvfnOq7Fzmq2lU4nRH6r0b1XGnxFIXL32fuM5+EP8t8FDz8QKM1ydN/2oOOQePXFNM5Xp7OyhJWf+l1//Cjn9ZeRoHf7ccmF+M4u/1Ca1LPmx3zNmvg7ZO+sqo2PwKjxf80+mifv51z1pU33pI5lddjIOs2aiK3BtPm0qwW2CeEl3HVjBuT6gmecbzhotXNG5IZoMvL3jObkap2oPvziSQtPo92wHqsfflRRwWt7lPwxnRj3dT4B6jnZ+D179uwa2TOCN/hbLWOaxkCpSqnZNmN3oXEMA2uka62Y5Eh7pw5foZ53Xo+TwpHRtu7Xexr+Gf92xO4RC8vQo2l4CbXpqogi7gOxCi3MnMb5ogIt/CV6Y9eaHVPOluU5nJrKaX0fK68chZAWvC0TbGPVE52/T5Mrtb0O9c/X79hYjoawspLDtsnpoUXXn43Xb1DnaCtlETDD2cp7MnoUmsBMS/JvpFjEj4a1R7CEx+sy7HCV52Jt73Xf35v2T6WMUtVOrIS92T9fmdg30CLiDRC0eq4mvmvoxl416nsdFr5kqflRWfiDmotXwPFAoczq1uLizqOaD5JS7ZXKBustk1/oXuly1o6b+RAfm6hs/v7QULTDJrOsV62Kg5ZswzPB1Gyazim5BRFCl5HyrPGDAnCGXH7v0bwsIY6tX7Jt39zprnjfejMkhPn1yQFquzS4hbXKV8eHh9pm4l7kYtandu3zfMdtIxwa+2FbenHM30yfkwVbNhXf6B+I0X2KyA2MCzegQelJ6r8sfGUisKmlS/qMzHGNAGOWcOMe5joMzbZTQ+dkPxaHqEwm6HhVi+yxkpxco/zkZBlyf8xo7GjhnowevFZibI2c8sHBL7LoYy32k6hLDW45VJ/zu9un8ibzcLmM57Lidg3kJu4vfRt45+mp74MdI1M6fs+S/grlZdGHjdwVv9mbkNe4LekGfuR8+nGvmSsDcV2lFeadmbUNceG2XZV7zQqS6C/2Z8ur9+NgO57fPE7L/u32y0psxJSR0NPk/uvpr3bsTTr8pHejvvZkE9Ozexqz/klnmvkPFp5PlbFxpufytUy9INNw8kyAvkWjCcPw0GUhnx2hw7q6rJfU4zhf92ZH1ygJnHi9ETY94Le7b3eckKA5J04N1XTAW1BBumPfC5u2ta8TeZJHugfyu2yaygS3mZvtu1zm+7o/S6rJKIpPJpcV6SXYXa9l41fy/J4CIzwLVj1RE5k28cl+K08f/p2tQ0gtbJJ1r37l6sx1M2pnvcPX6lrGCalsnviMiqgyCQiKj6lAurEFr+PwFyzdcr1Q43bZxxtKZ77dxVk+YW7av5+6X6xZydj9eM0Gfpm3z4a2tjG62m2giisU5bHy7lqFKxFtadUwGJZ+s92l/Kudyqcxrc34hOiJosun8zsmqgSquh8+bT3ehdfRLc6wFYWxFb899sbveOZtXM7YLhUrOonSkRrBcZxjZy7XuqGboo+l0vDhhsJjKmzT7e7jMjMstAW5RXYdadlVFUmRw9vsOtLaX9Zc/fvqaSMdS8/4rLEH0gc3Z7vdr4R/QXm+xw/JRodXmmLzCix4+098dTUrH6O/pwcrr0WkenffEfPxoGlSTUbeftHVamUwZWlfg+7MTVfOnMbxCLWm12Yq3n2rC7ucV+cY72Ix4xS85YZLJ/+B9JeYA9ppm5s0YeemCnezvNjbW0X9PB1RkbF/3CK8SWl4t0hZwQ53fZaJjoDxtiL4o7V/Bx93OvRYwyHWi34mZmC/MTYrf2pth/ZR4zNe/pf4/vLbqY/N5EwVDePxwcnWbxZTKfUYagtaWxzoazgt+LSDt6mE/8Vtu0GXlrpW648Vrh6BsQlW7ueSTvsovVWZyRCRC6DdJpQ/Xj+wpa/h+u5LqXTwoVbjr8cvvjCHw9YFHXpT1niSvXDDtnfvBG7gRqzdPHHOMnfrJhQcs5UMErGREe+KpioCm4y/qwuFzKadoOO/aszlw4V8lvZ8Qq14sL2Trrg1lSOqZJfEbBoLC5UZKxe26ovWp1fyPMe4XY1P6j2+dSwm2Zs94e/cnPHmktFme0QsnUCUG1+HrIcENp/h+knpl3bSMjuUW8zTv2rl+OszHby1cT8zTu0UXXcT0/r3val7PunOFiSf1YvMjMJYT3Ky9kyNukcffJJlfT/kwBB9vkmY0bG2GLPe01UNH0UzL+BG9TO1sDk6WWGBSio9Z5811Z7+6jCxL6xbA8XvKRC1uvbMDdNDQ9IRdjF4Se0avjD/TB3Wwt5QuZyq1vAwRjZlRfZiuqbdXSg7/K2mpKLrg2/4REYMGowvJ+awYUOMrx9EbmkQiejJHb29Q9jyA992xGvR/JOjMP2Y8SKu+xmaDnSWAVQn7pvoH8VX5AXIzbz60P/hXBvN1RL2MaG9mHNB1qhCTtodjP1yg2PIiPUeuWJszQ/HnR6nr7781h2FOtl3czy6uQR5oqk+px/Eu/VrqDJVbLWl8X1lJ6tntfuJLMoLXrFF9Xxda/BVpgg1D1a3UBXX1PGD/p3IQ5pxLaooeucbSRt3qSbxF2ZPb6/yTrMZ2ssUMWQdB0PVntsznDukEqXLDK+/M7X32SzHwlx66LZ/BjMAV1goe4Ode//B2tp5Sx3GEbzBStbUu5QIMtO8d0AGFYMRqrPi+Wob+DIYNz0zi3K2ujRrED2UaKBZvC88LOHZ/tAwD5Ou6YCird45SVnc9ng0nUDRUMrXu5/T+opuhp1WKGR5JwA7IxBS6OfbrTfKq72PbqPfcMPDKnO/wc+e9gKrHZ7mpQsxxh8K2lnzQoEvQaHstOeNvqd7vg7OMM2vCzPw1eQ8AOAhhe+5ODuXo84Y3J+9HM5DSBk0KmmIMBMr1zItq0Jpp6LF/v162VSrskq7mXphlvUkWgwIAwA2Lqube2lJZ4yPqydOZgX6RcjCEDauWBzGEWvnDl1LSpq+tl5lFcoUrVNTu0W7qgoFN0pJ7coql872GJ9gchsb82DJqq+Vzv6QkqZfjbqXujirE6FvG1IAAMgsK0mIvCR7V1fcMnoqaxHai2LwE6xu5JSYlChp/XpEtYySw3YYW/JKKrT0dOeUzA0JpbPxRz1xZNVM+V1s3AoAQKxUzdwnVE2WIW7Ea00I81HTMrHy6swPWQGG90LEj1kyMjIyyotcE1G8d2xwErs75NE9RpWQqMk7IvJTbIYZoX19kU1vAq+/iDShFblY2CR05/U+Kd4TwevltSfFo9AJRshKzkiPtWFhLlGReLzOWlm8UdgVaZadZc0vd8n1JdlGY2IvRd+yCmwGyTQpWN4TwSjnsaKTNAtLrai5tCkSADC1shZHrqDFyfqGJNlPRL2zeDr3OY9VcxF9+fhb1Uj1TBa0NxarSSq37vijaqR6IiWtpgpVI66nPd+iDZVi5dWGRnX3U7t2lfV2idp3B3F+vL16y52unhjBI4vBAZrJHvofHaq/7LbxklfjgnFcyZf3ossTf/4hQ8JRz7Ex2WyooVV6Az+/xl/zxwWRZAoS7aC1qk8XcUEA1lhRGRMWpJ0v+JPn8pYes9SdVVSc1OQzdvPDzoGdPz7JJO7IIzggCHUixNLE3TxpzjvCRJvAAgmAxIBw0iSSKTFiKmEAbQOEqrgKUJKNIwYShsV4IMD3RECK9BFmuzgguCAqsGzebLmdYITshAg1IJ0MIw9YAwHYQwBEeSboegizW3yQ9dxfAiGTDCNGEoa22CBILhpAPgJGuT+uhBjkCvzJgACW9YcwkQX158sSCJm8FzGSMIoF9ceKFpAPdlHuz6MFDFGKaxFAKre1CJiCAIhSXORVsEMgqnRg2RQX1BTCqBXU53OkOKRSXMRIwlgVFDlIEkkisUX5ak3pwbLpLKg0wtgUVNoNUhxS6SxiJGFACopkYwCUpa8oX63PUiQkZwWVRhiC4oVIKyLFIZGzIiYSpp2gROnVgKIo1XJr5YSs9dpSInFmiuiSSpBkgt4YP5JBkcpMEVMJk0pQ6j5GQHEmivJl15GkQjJPUIGE2SJ+iEBeJkBp5okYSpgGgkKfkoMuDSktt2hWyKKtmcEysSKoNMKUxTqItOSllCUxDmIaYYgHSuNiAb/MDRHTCJM53BDaFRI0ohgQMYwwZwN9JGNZAyiJ9hADCXMw6yHA86SBRGGd5XZiLYSnzAqWy9IQgwjjK1DQHSiIOCyzRBFBIgUKEmADy2VfiEGEwQ8uCCgKCiIRZ6GcRccOfpFUofzhw2uBRRRCofThLA8CIAqhQFdEGAaBPtvPLIGQCKEsp4kNoilgLVguT0J0uyOYrULbu4wEZmmehJhHONOE7luZAPjFfJZy228LAvIzUaggwmEldIFdSyC/K4obIsp8E/j1wBMqjnDSKAI9w2Rh5AaeS155CWaHQhD2NiHwO5PLJbc5gjEgFPyMPJjUUIkYTDh0g4LRwuB3ZoqU75nSZvDrCR5UJeF8DaoymCyMIl8JR2lQ8Dh5MCW+Ek7FoGBbEfA7c7nlfOWF+DpKHkw0YoNKJZx+iUKkaoiC3x2xEcMJJ11QeA4FcCR5L0gNy+a9+L5TFAaWjM5o6X78rypQBVs4ALD+8egD/hcAAP//X3/+baM6AAA="); err != nil { panic("add binary content to resource manager failed: " + err.Error()) } } diff --git a/os/grpool/grpool.go b/os/grpool/grpool.go index 48af508ef..f9c19c356 100644 --- a/os/grpool/grpool.go +++ b/os/grpool/grpool.go @@ -1,4 +1,4 @@ -// Copyright 2017-2019 gf Author(https://github.com/gogf/gf). All Rights Reserved. +// Copyright GoFrame Author(https://goframe.org). All Rights Reserved. // // This Source Code Form is subject to the terms of the MIT License. // If a copy of the MIT was not distributed with this file, @@ -8,8 +8,8 @@ package grpool import ( - "errors" - "fmt" + "github.com/gogf/gf/errors/gcode" + "github.com/gogf/gf/errors/gerror" "github.com/gogf/gf/container/glist" "github.com/gogf/gf/container/gtype" @@ -70,7 +70,7 @@ func Jobs() int { // The job will be executed asynchronously. func (p *Pool) Add(f func()) error { for p.closed.Val() { - return errors.New("pool closed") + return gerror.NewCode(gcode.CodeInvalidOperation, "pool closed") } p.list.PushFront(f) // Check whether fork new goroutine or not. @@ -97,9 +97,13 @@ func (p *Pool) Add(f func()) error { func (p *Pool) AddWithRecover(userFunc func(), recoverFunc ...func(err error)) error { return p.Add(func() { defer func() { - if err := recover(); err != nil { + if exception := recover(); exception != nil { if len(recoverFunc) > 0 && recoverFunc[0] != nil { - recoverFunc[0](errors.New(fmt.Sprintf(`%v`, err))) + if err, ok := exception.(error); ok { + recoverFunc[0](err) + } else { + recoverFunc[0](gerror.NewCodef(gcode.CodeInternalError, `%v`, exception)) + } } } }() diff --git a/os/grpool/grpool_bench_1_test.go b/os/grpool/grpool_bench_1_test.go index 0f6eb0f72..cdd111101 100644 --- a/os/grpool/grpool_bench_1_test.go +++ b/os/grpool/grpool_bench_1_test.go @@ -1,4 +1,4 @@ -// Copyright 2017 gf Author(https://github.com/gogf/gf). All Rights Reserved. +// Copyright GoFrame Author(https://goframe.org). All Rights Reserved. // // This Source Code Form is subject to the terms of the MIT License. // If a copy of the MIT was not distributed with this file, diff --git a/os/grpool/grpool_bench_2_test.go b/os/grpool/grpool_bench_2_test.go index c7fae031d..0bc61cebc 100644 --- a/os/grpool/grpool_bench_2_test.go +++ b/os/grpool/grpool_bench_2_test.go @@ -1,4 +1,4 @@ -// Copyright 2017 gf Author(https://github.com/gogf/gf). All Rights Reserved. +// Copyright GoFrame Author(https://goframe.org). All Rights Reserved. // // This Source Code Form is subject to the terms of the MIT License. // If a copy of the MIT was not distributed with this file, diff --git a/os/grpool/grpool_unit_test.go b/os/grpool/grpool_unit_test.go index b4250bd46..7aed8604b 100644 --- a/os/grpool/grpool_unit_test.go +++ b/os/grpool/grpool_unit_test.go @@ -1,4 +1,4 @@ -// Copyright 2017 gf Author(https://github.com/gogf/gf). All Rights Reserved. +// Copyright GoFrame Author(https://goframe.org). All Rights Reserved. // // This Source Code Form is subject to the terms of the MIT License. // If a copy of the MIT was not distributed with this file, diff --git a/os/gsession/gsession.go b/os/gsession/gsession.go index 59417c535..ee1d15fc9 100644 --- a/os/gsession/gsession.go +++ b/os/gsession/gsession.go @@ -1,4 +1,4 @@ -// Copyright 2019 gf Author(https://github.com/gogf/gf). All Rights Reserved. +// Copyright GoFrame Author(https://goframe.org). All Rights Reserved. // // This Source Code Form is subject to the terms of the MIT License. // If a copy of the MIT was not distributed with this file, @@ -8,12 +8,16 @@ package gsession import ( - "errors" + "github.com/gogf/gf/errors/gcode" + "github.com/gogf/gf/errors/gerror" "github.com/gogf/gf/util/guid" ) var ( - ErrorDisabled = errors.New("this feature is disabled in this storage") + ErrorDisabled = gerror.NewOption(gerror.Option{ + Text: "this feature is disabled in this storage", + Code: gcode.CodeNotSupported, + }) ) // NewSessionId creates and returns a new and unique session id string, diff --git a/os/gsession/gsession_manager.go b/os/gsession/gsession_manager.go index 79b096585..81b308d52 100644 --- a/os/gsession/gsession_manager.go +++ b/os/gsession/gsession_manager.go @@ -1,4 +1,4 @@ -// Copyright GoFrame Author(https://github.com/gogf/gf). All Rights Reserved. +// Copyright GoFrame Author(https://goframe.org). All Rights Reserved. // // This Source Code Form is subject to the terms of the MIT License. // If a copy of the MIT was not distributed with this file, @@ -7,6 +7,7 @@ package gsession import ( + "context" "github.com/gogf/gf/container/gmap" "time" @@ -15,9 +16,13 @@ import ( // Manager for sessions. type Manager struct { - ttl time.Duration // TTL for sessions. - storage Storage // Storage interface for session storage. - sessionData *gcache.Cache // Session data cache for session TTL. + ttl time.Duration // TTL for sessions. + storage Storage // Storage interface for session storage. + + // sessionData is the memory data cache for session TTL, + // which is available only if the Storage does not stores any session data in synchronizing. + // Please refer to the implements of StorageFile, StorageMemory and StorageRedis. + sessionData *gcache.Cache } // New creates and returns a new session manager. @@ -35,15 +40,16 @@ func New(ttl time.Duration, storage ...Storage) *Manager { } // New creates or fetches the session for given session id. -// The parameter <sessionId> is optional, it creates a new one if not it's passed +// The parameter `sessionId` is optional, it creates a new one if not it's passed // depending on Storage.New. -func (m *Manager) New(sessionId ...string) *Session { +func (m *Manager) New(ctx context.Context, sessionId ...string) *Session { var id string if len(sessionId) > 0 && sessionId[0] != "" { id = sessionId[0] } return &Session{ id: id, + ctx: ctx, manager: m, } } diff --git a/os/gsession/gsession_session.go b/os/gsession/gsession_session.go index dfbff5336..edc70ff9d 100644 --- a/os/gsession/gsession_session.go +++ b/os/gsession/gsession_session.go @@ -1,4 +1,4 @@ -// Copyright 2019 gf Author(https://github.com/gogf/gf). All Rights Reserved. +// Copyright GoFrame Author(https://goframe.org). All Rights Reserved. // // This Source Code Form is subject to the terms of the MIT License. // If a copy of the MIT was not distributed with this file, @@ -7,20 +7,23 @@ package gsession import ( - "errors" + "context" + "github.com/gogf/gf/errors/gcode" + "github.com/gogf/gf/errors/gerror" "github.com/gogf/gf/internal/intlog" "time" "github.com/gogf/gf/container/gmap" "github.com/gogf/gf/container/gvar" "github.com/gogf/gf/os/gtime" - "github.com/gogf/gf/util/gconv" ) -// Session struct for storing single session data, -// which is bound to a single request. +// Session struct for storing single session data, which is bound to a single request. +// The Session struct is the interface with user, but the Storage is the underlying adapter designed interface +// for functionality implements. type Session struct { id string // Session id. + ctx context.Context // Context for current session, note that: one session one context. data *gmap.StrAnyMap // Session data. dirty bool // Used to mark session is modified. start bool // Used to mark session is started. @@ -37,17 +40,18 @@ func (s *Session) init() { if s.start { return } + var err error if s.id != "" { - var err error // Retrieve memory session data from manager. if r, _ := s.manager.sessionData.Get(s.id); r != nil { s.data = r.(*gmap.StrAnyMap) - intlog.Print("session init data:", s.data) + intlog.Print(s.ctx, "session init data:", s.data) } // Retrieve stored session data from storage. if s.manager.storage != nil { - if s.data, err = s.manager.storage.GetSession(s.id, s.manager.ttl, s.data); err != nil { - intlog.Errorf("session restoring failed for id '%s': %v", s.id, err) + if s.data, err = s.manager.storage.GetSession(s.ctx, s.id, s.manager.ttl, s.data); err != nil && err != ErrorDisabled { + intlog.Errorf(s.ctx, "session restoring failed for id '%s': %v", s.id, err) + panic(err) } } } @@ -57,7 +61,11 @@ func (s *Session) init() { } // Use default session id creating function of storage. if s.id == "" { - s.id = s.manager.storage.New(s.manager.ttl) + s.id, err = s.manager.storage.New(s.ctx, s.manager.ttl) + if err != nil && err != ErrorDisabled { + intlog.Errorf(s.ctx, "create session id failed: %v", err) + panic(err) + } } // Use default session id creating function. if s.id == "" { @@ -67,6 +75,7 @@ func (s *Session) init() { s.data = gmap.NewStrAnyMap(true) } s.start = true + } // Close closes current session and updates its ttl in the session manager. @@ -78,11 +87,11 @@ func (s *Session) Close() { size := s.data.Size() if s.manager.storage != nil { if s.dirty { - if err := s.manager.storage.SetSession(s.id, s.data, s.manager.ttl); err != nil { + if err := s.manager.storage.SetSession(s.ctx, s.id, s.data, s.manager.ttl); err != nil { panic(err) } } else if size > 0 { - if err := s.manager.storage.UpdateTTL(s.id, s.manager.ttl); err != nil { + if err := s.manager.storage.UpdateTTL(s.ctx, s.id, s.manager.ttl); err != nil { panic(err) } } @@ -96,7 +105,7 @@ func (s *Session) Close() { // Set sets key-value pair to this session. func (s *Session) Set(key string, value interface{}) error { s.init() - if err := s.manager.storage.Set(s.id, key, value, s.manager.ttl); err != nil { + if err := s.manager.storage.Set(s.ctx, s.id, key, value, s.manager.ttl); err != nil { if err == ErrorDisabled { s.data.Set(key, value) } else { @@ -116,7 +125,7 @@ func (s *Session) Sets(data map[string]interface{}) error { // SetMap batch sets the session using map. func (s *Session) SetMap(data map[string]interface{}) error { s.init() - if err := s.manager.storage.SetMap(s.id, data, s.manager.ttl); err != nil { + if err := s.manager.storage.SetMap(s.ctx, s.id, data, s.manager.ttl); err != nil { if err == ErrorDisabled { s.data.Sets(data) } else { @@ -134,7 +143,7 @@ func (s *Session) Remove(keys ...string) error { } s.init() for _, key := range keys { - if err := s.manager.storage.Remove(s.id, key); err != nil { + if err := s.manager.storage.Remove(s.ctx, s.id, key); err != nil { if err == ErrorDisabled { s.data.Remove(key) } else { @@ -157,7 +166,7 @@ func (s *Session) RemoveAll() error { return nil } s.init() - if err := s.manager.storage.RemoveAll(s.id); err != nil { + if err := s.manager.storage.RemoveAll(s.ctx, s.id); err != nil { if err == ErrorDisabled { s.data.Clear() } else { @@ -169,7 +178,7 @@ func (s *Session) RemoveAll() error { } // Id returns the session id for this session. -// It create and returns a new session id if the session id is not passed in initialization. +// It creates and returns a new session id if the session id is not passed in initialization. func (s *Session) Id() string { s.init() return s.id @@ -179,7 +188,7 @@ func (s *Session) Id() string { // It returns error if it is called after session starts. func (s *Session) SetId(id string) error { if s.start { - return errors.New("session already started") + return gerror.NewCode(gcode.CodeInvalidOperation, "session already started") } s.id = id return nil @@ -189,7 +198,7 @@ func (s *Session) SetId(id string) error { // It returns error if it is called after session starts. func (s *Session) SetIdFunc(f func(ttl time.Duration) string) error { if s.start { - return errors.New("session already started") + return gerror.NewCode(gcode.CodeInvalidOperation, "session already started") } s.idFunc = f return nil @@ -200,7 +209,11 @@ func (s *Session) SetIdFunc(f func(ttl time.Duration) string) error { func (s *Session) Map() map[string]interface{} { if s.id != "" { s.init() - if data := s.manager.storage.GetMap(s.id); data != nil { + data, err := s.manager.storage.GetMap(s.ctx, s.id) + if err != nil && err != ErrorDisabled { + intlog.Error(s.ctx, err) + } + if data != nil { return data } return s.data.Map() @@ -212,7 +225,11 @@ func (s *Session) Map() map[string]interface{} { func (s *Session) Size() int { if s.id != "" { s.init() - if size := s.manager.storage.GetSize(s.id); size >= 0 { + size, err := s.manager.storage.GetSize(s.ctx, s.id) + if err != nil && err != ErrorDisabled { + intlog.Error(s.ctx, err) + } + if size >= 0 { return size } return s.data.Size() @@ -232,14 +249,18 @@ func (s *Session) IsDirty() bool { } // Get retrieves session value with given key. -// It returns <def> if the key does not exist in the session if <def> is given, -// or else it return nil. +// It returns `def` if the key does not exist in the session if `def` is given, +// or else it returns nil. func (s *Session) Get(key string, def ...interface{}) interface{} { if s.id == "" { return nil } s.init() - if v := s.manager.storage.Get(s.id, key); v != nil { + v, err := s.manager.storage.Get(s.ctx, s.id, key) + if err != nil && err != ErrorDisabled { + intlog.Error(s.ctx, err) + } + if v != nil { return v } if v := s.data.Get(key); v != nil { @@ -256,123 +277,113 @@ func (s *Session) GetVar(key string, def ...interface{}) *gvar.Var { } func (s *Session) GetString(key string, def ...interface{}) string { - return gconv.String(s.Get(key, def...)) + return s.GetVar(key, def...).String() } func (s *Session) GetBool(key string, def ...interface{}) bool { - return gconv.Bool(s.Get(key, def...)) + return s.GetVar(key, def...).Bool() } func (s *Session) GetInt(key string, def ...interface{}) int { - return gconv.Int(s.Get(key, def...)) + return s.GetVar(key, def...).Int() } func (s *Session) GetInt8(key string, def ...interface{}) int8 { - return gconv.Int8(s.Get(key, def...)) + return s.GetVar(key, def...).Int8() } func (s *Session) GetInt16(key string, def ...interface{}) int16 { - return gconv.Int16(s.Get(key, def...)) + return s.GetVar(key, def...).Int16() } func (s *Session) GetInt32(key string, def ...interface{}) int32 { - return gconv.Int32(s.Get(key, def...)) + return s.GetVar(key, def...).Int32() } func (s *Session) GetInt64(key string, def ...interface{}) int64 { - return gconv.Int64(s.Get(key, def...)) + return s.GetVar(key, def...).Int64() } func (s *Session) GetUint(key string, def ...interface{}) uint { - return gconv.Uint(s.Get(key, def...)) + return s.GetVar(key, def...).Uint() } func (s *Session) GetUint8(key string, def ...interface{}) uint8 { - return gconv.Uint8(s.Get(key, def...)) + return s.GetVar(key, def...).Uint8() } func (s *Session) GetUint16(key string, def ...interface{}) uint16 { - return gconv.Uint16(s.Get(key, def...)) + return s.GetVar(key, def...).Uint16() } func (s *Session) GetUint32(key string, def ...interface{}) uint32 { - return gconv.Uint32(s.Get(key, def...)) + return s.GetVar(key, def...).Uint32() } func (s *Session) GetUint64(key string, def ...interface{}) uint64 { - return gconv.Uint64(s.Get(key, def...)) + return s.GetVar(key, def...).Uint64() } func (s *Session) GetFloat32(key string, def ...interface{}) float32 { - return gconv.Float32(s.Get(key, def...)) + return s.GetVar(key, def...).Float32() } func (s *Session) GetFloat64(key string, def ...interface{}) float64 { - return gconv.Float64(s.Get(key, def...)) + return s.GetVar(key, def...).Float64() } func (s *Session) GetBytes(key string, def ...interface{}) []byte { - return gconv.Bytes(s.Get(key, def...)) + return s.GetVar(key, def...).Bytes() } func (s *Session) GetInts(key string, def ...interface{}) []int { - return gconv.Ints(s.Get(key, def...)) + return s.GetVar(key, def...).Ints() } func (s *Session) GetFloats(key string, def ...interface{}) []float64 { - return gconv.Floats(s.Get(key, def...)) + return s.GetVar(key, def...).Floats() } func (s *Session) GetStrings(key string, def ...interface{}) []string { - return gconv.Strings(s.Get(key, def...)) + return s.GetVar(key, def...).Strings() } func (s *Session) GetInterfaces(key string, def ...interface{}) []interface{} { - return gconv.Interfaces(s.Get(key, def...)) + return s.GetVar(key, def...).Interfaces() } func (s *Session) GetTime(key string, format ...string) time.Time { - return gconv.Time(s.Get(key), format...) + return s.GetVar(key).Time(format...) } func (s *Session) GetGTime(key string, format ...string) *gtime.Time { - return gconv.GTime(s.Get(key), format...) + return s.GetVar(key).GTime(format...) } func (s *Session) GetDuration(key string, def ...interface{}) time.Duration { - return gconv.Duration(s.Get(key, def...)) + return s.GetVar(key, def...).Duration() } func (s *Session) GetMap(key string, tags ...string) map[string]interface{} { - return gconv.Map(s.Get(key), tags...) + return s.GetVar(key).Map(tags...) } func (s *Session) GetMapDeep(key string, tags ...string) map[string]interface{} { - return gconv.MapDeep(s.Get(key), tags...) + return s.GetVar(key).MapDeep(tags...) } func (s *Session) GetMaps(key string, tags ...string) []map[string]interface{} { - return gconv.Maps(s.Get(key), tags...) + return s.GetVar(key).Maps(tags...) } func (s *Session) GetMapsDeep(key string, tags ...string) []map[string]interface{} { - return gconv.MapsDeep(s.Get(key), tags...) + return s.GetVar(key).MapsDeep(tags...) } func (s *Session) GetStruct(key string, pointer interface{}, mapping ...map[string]string) error { - return gconv.Struct(s.Get(key), pointer, mapping...) -} - -// Deprecated, use GetStruct instead. -func (s *Session) GetStructDeep(key string, pointer interface{}, mapping ...map[string]string) error { - return gconv.StructDeep(s.Get(key), pointer, mapping...) + return s.GetVar(key).Struct(pointer, mapping...) } func (s *Session) GetStructs(key string, pointer interface{}, mapping ...map[string]string) error { - return gconv.Structs(s.Get(key), pointer, mapping...) -} - -// Deprecated, use GetStructs instead. -func (s *Session) GetStructsDeep(key string, pointer interface{}, mapping ...map[string]string) error { - return gconv.StructsDeep(s.Get(key), pointer, mapping...) + return s.GetVar(key).Structs(pointer, mapping...) } diff --git a/os/gsession/gsession_storage.go b/os/gsession/gsession_storage.go index 75d06ddf2..db5f066dc 100644 --- a/os/gsession/gsession_storage.go +++ b/os/gsession/gsession_storage.go @@ -1,4 +1,4 @@ -// Copyright 2019 gf Author(https://github.com/gogf/gf). All Rights Reserved. +// Copyright GoFrame Author(https://goframe.org). All Rights Reserved. // // This Source Code Form is subject to the terms of the MIT License. // If a copy of the MIT was not distributed with this file, @@ -7,6 +7,7 @@ package gsession import ( + "context" "github.com/gogf/gf/container/gmap" "time" ) @@ -15,47 +16,47 @@ import ( type Storage interface { // New creates a custom session id. // This function can be used for custom session creation. - New(ttl time.Duration) (id string) + New(ctx context.Context, ttl time.Duration) (id string, err error) // Get retrieves and returns session value with given key. // It returns nil if the key does not exist in the session. - Get(id string, key string) interface{} + Get(ctx context.Context, id string, key string) (value interface{}, err error) // GetMap retrieves all key-value pairs as map from storage. - GetMap(id string) map[string]interface{} + GetMap(ctx context.Context, id string) (data map[string]interface{}, err error) // GetSize retrieves and returns the size of key-value pairs from storage. - GetSize(id string) int + GetSize(ctx context.Context, id string) (size int, err error) // Set sets one key-value session pair to the storage. - // The parameter <ttl> specifies the TTL for the session id. - Set(id string, key string, value interface{}, ttl time.Duration) error + // The parameter `ttl` specifies the TTL for the session id. + Set(ctx context.Context, id string, key string, value interface{}, ttl time.Duration) error // SetMap batch sets key-value session pairs as map to the storage. - // The parameter <ttl> specifies the TTL for the session id. - SetMap(id string, data map[string]interface{}, ttl time.Duration) error + // The parameter `ttl` specifies the TTL for the session id. + SetMap(ctx context.Context, id string, data map[string]interface{}, ttl time.Duration) error // Remove deletes key with its value from storage. - Remove(id string, key string) error + Remove(ctx context.Context, id string, key string) error // RemoveAll deletes all key-value pairs from storage. - RemoveAll(id string) error + RemoveAll(ctx context.Context, id string) error - // GetSession returns the session data as *gmap.StrAnyMap for given session id from storage. + // GetSession returns the session data as `*gmap.StrAnyMap` for given session id from storage. // - // The parameter <ttl> specifies the TTL for this session. - // The parameter <data> is the current old session data stored in memory, + // The parameter `ttl` specifies the TTL for this session. + // The parameter `data` is the current old session data stored in memory, // and for some storage it might be nil if memory storage is disabled. // // This function is called ever when session starts. It returns nil if the TTL is exceeded. - GetSession(id string, ttl time.Duration, data *gmap.StrAnyMap) (*gmap.StrAnyMap, error) + GetSession(ctx context.Context, id string, ttl time.Duration, data *gmap.StrAnyMap) (*gmap.StrAnyMap, error) // SetSession updates the data for specified session id. // This function is called ever after session, which is changed dirty, is closed. // This copy all session data map from memory to storage. - SetSession(id string, data *gmap.StrAnyMap, ttl time.Duration) error + SetSession(ctx context.Context, id string, data *gmap.StrAnyMap, ttl time.Duration) error // UpdateTTL updates the TTL for specified session id. // This function is called ever after session, which is not dirty, is closed. - UpdateTTL(id string, ttl time.Duration) error + UpdateTTL(ctx context.Context, id string, ttl time.Duration) error } diff --git a/os/gsession/gsession_storage_file.go b/os/gsession/gsession_storage_file.go index 5a4a4a247..12fd4e2af 100644 --- a/os/gsession/gsession_storage_file.go +++ b/os/gsession/gsession_storage_file.go @@ -1,4 +1,4 @@ -// Copyright 2019 gf Author(https://github.com/gogf/gf). All Rights Reserved. +// Copyright GoFrame Author(https://goframe.org). All Rights Reserved. // // This Source Code Form is subject to the terms of the MIT License. // If a copy of the MIT was not distributed with this file, @@ -7,8 +7,10 @@ package gsession import ( - "fmt" + "context" "github.com/gogf/gf/container/gmap" + "github.com/gogf/gf/errors/gcode" + "github.com/gogf/gf/errors/gerror" "github.com/gogf/gf/internal/intlog" "github.com/gogf/gf/internal/json" "os" @@ -47,15 +49,15 @@ func NewStorageFile(path ...string) *StorageFile { if len(path) > 0 && path[0] != "" { storagePath, _ = gfile.Search(path[0]) if storagePath == "" { - panic(fmt.Sprintf("'%s' does not exist", path[0])) + panic(gerror.NewCodef(gcode.CodeInvalidParameter, `"%s" does not exist`, path[0])) } if !gfile.IsWritable(storagePath) { - panic(fmt.Sprintf("'%s' is not writable", path[0])) + panic(gerror.NewCodef(gcode.CodeInvalidParameter, `"%s" is not writable`, path[0])) } } if storagePath != "" { if err := gfile.Mkdir(storagePath); err != nil { - panic(fmt.Sprintf("mkdir '%s' failed: %v", path[0], err)) + panic(gerror.WrapCodef(gcode.CodeInternalError, err, `Mkdir "%s" failed in PWD "%s"`, path, gfile.Pwd())) } } s := &StorageFile{ @@ -64,24 +66,28 @@ func NewStorageFile(path ...string) *StorageFile { cryptoEnabled: DefaultStorageFileCryptoEnabled, updatingIdSet: gset.NewStrSet(true), } - // Batch updates the TTL for session ids timely. - gtimer.AddSingleton(DefaultStorageFileLoopInterval, func() { - //intlog.Print("StorageFile.timer start") - var id string - var err error - for { - if id = s.updatingIdSet.Pop(); id == "" { - break - } - if err = s.doUpdateTTL(id); err != nil { - intlog.Error(err) - } - } - //intlog.Print("StorageFile.timer end") - }) + + gtimer.AddSingleton(DefaultStorageFileLoopInterval, s.updateSessionTimely) return s } +// updateSessionTimely batch updates the TTL for sessions timely. +func (s *StorageFile) updateSessionTimely() { + var ( + id string + err error + ) + // Batch updating sessions. + for { + if id = s.updatingIdSet.Pop(); id == "" { + break + } + if err = s.updateSessionTTl(context.TODO(), id); err != nil { + intlog.Error(context.TODO(), err) + } + } +} + // SetCryptoKey sets the crypto key for session storage. // The crypto key is used when crypto feature is enabled. func (s *StorageFile) SetCryptoKey(key []byte) { @@ -100,60 +106,59 @@ func (s *StorageFile) sessionFilePath(id string) string { // New creates a session id. // This function can be used for custom session creation. -func (s *StorageFile) New(ttl time.Duration) (id string) { - return "" +func (s *StorageFile) New(ctx context.Context, ttl time.Duration) (id string, err error) { + return "", ErrorDisabled } // Get retrieves session value with given key. // It returns nil if the key does not exist in the session. -func (s *StorageFile) Get(id string, key string) interface{} { - return nil +func (s *StorageFile) Get(ctx context.Context, id string, key string) (value interface{}, err error) { + return nil, ErrorDisabled } // GetMap retrieves all key-value pairs as map from storage. -func (s *StorageFile) GetMap(id string) map[string]interface{} { - return nil +func (s *StorageFile) GetMap(ctx context.Context, id string) (data map[string]interface{}, err error) { + return nil, ErrorDisabled } // GetSize retrieves the size of key-value pairs from storage. -func (s *StorageFile) GetSize(id string) int { - return -1 +func (s *StorageFile) GetSize(ctx context.Context, id string) (size int, err error) { + return -1, ErrorDisabled } // Set sets key-value session pair to the storage. -// The parameter <ttl> specifies the TTL for the session id (not for the key-value pair). -func (s *StorageFile) Set(id string, key string, value interface{}, ttl time.Duration) error { +// The parameter `ttl` specifies the TTL for the session id (not for the key-value pair). +func (s *StorageFile) Set(ctx context.Context, id string, key string, value interface{}, ttl time.Duration) error { return ErrorDisabled } // SetMap batch sets key-value session pairs with map to the storage. -// The parameter <ttl> specifies the TTL for the session id(not for the key-value pair). -func (s *StorageFile) SetMap(id string, data map[string]interface{}, ttl time.Duration) error { +// The parameter `ttl` specifies the TTL for the session id(not for the key-value pair). +func (s *StorageFile) SetMap(ctx context.Context, id string, data map[string]interface{}, ttl time.Duration) error { return ErrorDisabled } // Remove deletes key with its value from storage. -func (s *StorageFile) Remove(id string, key string) error { +func (s *StorageFile) Remove(ctx context.Context, id string, key string) error { return ErrorDisabled } // RemoveAll deletes all key-value pairs from storage. -func (s *StorageFile) RemoveAll(id string) error { +func (s *StorageFile) RemoveAll(ctx context.Context, id string) error { return ErrorDisabled } // GetSession returns the session data as *gmap.StrAnyMap for given session id from storage. // -// The parameter <ttl> specifies the TTL for this session, and it returns nil if the TTL is exceeded. -// The parameter <data> is the current old session data stored in memory, +// The parameter `ttl` specifies the TTL for this session, and it returns nil if the TTL is exceeded. +// The parameter `data` is the current old session data stored in memory, // and for some storage it might be nil if memory storage is disabled. // // This function is called ever when session starts. -func (s *StorageFile) GetSession(id string, ttl time.Duration, data *gmap.StrAnyMap) (*gmap.StrAnyMap, error) { +func (s *StorageFile) GetSession(ctx context.Context, id string, ttl time.Duration, data *gmap.StrAnyMap) (*gmap.StrAnyMap, error) { if data != nil { return data, nil } - //intlog.Printf("StorageFile.GetSession: %s, %v", id, ttl) path := s.sessionFilePath(id) content := gfile.GetBytes(path) if len(content) > 8 { @@ -171,7 +176,7 @@ func (s *StorageFile) GetSession(id string, ttl time.Duration, data *gmap.StrAny } } var m map[string]interface{} - if err = json.Unmarshal(content, &m); err != nil { + if err = json.UnmarshalUseNumber(content, &m); err != nil { return nil, err } if m == nil { @@ -185,8 +190,8 @@ func (s *StorageFile) GetSession(id string, ttl time.Duration, data *gmap.StrAny // SetSession updates the data map for specified session id. // This function is called ever after session, which is changed dirty, is closed. // This copy all session data map from memory to storage. -func (s *StorageFile) SetSession(id string, data *gmap.StrAnyMap, ttl time.Duration) error { - intlog.Printf("StorageFile.SetSession: %s, %v, %v", id, data, ttl) +func (s *StorageFile) SetSession(ctx context.Context, id string, data *gmap.StrAnyMap, ttl time.Duration) error { + intlog.Printf(ctx, "StorageFile.SetSession: %s, %v, %v", id, data, ttl) path := s.sessionFilePath(id) content, err := json.Marshal(data) if err != nil { @@ -218,17 +223,17 @@ func (s *StorageFile) SetSession(id string, data *gmap.StrAnyMap, ttl time.Durat // UpdateTTL updates the TTL for specified session id. // This function is called ever after session, which is not dirty, is closed. // It just adds the session id to the async handling queue. -func (s *StorageFile) UpdateTTL(id string, ttl time.Duration) error { - intlog.Printf("StorageFile.UpdateTTL: %s, %v", id, ttl) - if ttl >= DefaultStorageRedisLoopInterval { +func (s *StorageFile) UpdateTTL(ctx context.Context, id string, ttl time.Duration) error { + intlog.Printf(ctx, "StorageFile.UpdateTTL: %s, %v", id, ttl) + if ttl >= DefaultStorageFileLoopInterval { s.updatingIdSet.Add(id) } return nil } -// doUpdateTTL updates the TTL for session id. -func (s *StorageFile) doUpdateTTL(id string) error { - intlog.Printf("StorageFile.doUpdateTTL: %s", id) +// updateSessionTTL updates the TTL for specified session id. +func (s *StorageFile) updateSessionTTl(ctx context.Context, id string) error { + intlog.Printf(ctx, "StorageFile.updateSession: %s", id) path := s.sessionFilePath(id) file, err := gfile.OpenWithFlag(path, os.O_WRONLY) if err != nil { diff --git a/os/gsession/gsession_storage_memory.go b/os/gsession/gsession_storage_memory.go index 24786e745..32175fe48 100644 --- a/os/gsession/gsession_storage_memory.go +++ b/os/gsession/gsession_storage_memory.go @@ -1,4 +1,4 @@ -// Copyright 2019 gf Author(https://github.com/gogf/gf). All Rights Reserved. +// Copyright GoFrame Author(https://goframe.org). All Rights Reserved. // // This Source Code Form is subject to the terms of the MIT License. // If a copy of the MIT was not distributed with this file, @@ -7,6 +7,7 @@ package gsession import ( + "context" "github.com/gogf/gf/container/gmap" "time" ) @@ -21,74 +22,69 @@ func NewStorageMemory() *StorageMemory { // New creates a session id. // This function can be used for custom session creation. -func (s *StorageMemory) New(ttl time.Duration) (id string) { - return "" +func (s *StorageMemory) New(ctx context.Context, ttl time.Duration) (id string, err error) { + return "", ErrorDisabled } // Get retrieves session value with given key. // It returns nil if the key does not exist in the session. -func (s *StorageMemory) Get(id string, key string) interface{} { - return nil +func (s *StorageMemory) Get(ctx context.Context, id string, key string) (value interface{}, err error) { + return nil, ErrorDisabled } // GetMap retrieves all key-value pairs as map from storage. -func (s *StorageMemory) GetMap(id string) map[string]interface{} { - return nil +func (s *StorageMemory) GetMap(ctx context.Context, id string) (data map[string]interface{}, err error) { + return nil, ErrorDisabled } // GetSize retrieves the size of key-value pairs from storage. -func (s *StorageMemory) GetSize(id string) int { - return -1 +func (s *StorageMemory) GetSize(ctx context.Context, id string) (size int, err error) { + return -1, ErrorDisabled } // Set sets key-value session pair to the storage. -// The parameter <ttl> specifies the TTL for the session id (not for the key-value pair). -func (s *StorageMemory) Set(id string, key string, value interface{}, ttl time.Duration) error { +// The parameter `ttl` specifies the TTL for the session id (not for the key-value pair). +func (s *StorageMemory) Set(ctx context.Context, id string, key string, value interface{}, ttl time.Duration) error { return ErrorDisabled } // SetMap batch sets key-value session pairs with map to the storage. -// The parameter <ttl> specifies the TTL for the session id(not for the key-value pair). -func (s *StorageMemory) SetMap(id string, data map[string]interface{}, ttl time.Duration) error { +// The parameter `ttl` specifies the TTL for the session id(not for the key-value pair). +func (s *StorageMemory) SetMap(ctx context.Context, id string, data map[string]interface{}, ttl time.Duration) error { return ErrorDisabled } // Remove deletes key with its value from storage. -func (s *StorageMemory) Remove(id string, key string) error { +func (s *StorageMemory) Remove(ctx context.Context, id string, key string) error { return ErrorDisabled } // RemoveAll deletes all key-value pairs from storage. -func (s *StorageMemory) RemoveAll(id string) error { +func (s *StorageMemory) RemoveAll(ctx context.Context, id string) error { return ErrorDisabled } // GetSession returns the session data as *gmap.StrAnyMap for given session id from storage. // -// The parameter <ttl> specifies the TTL for this session, and it returns nil if the TTL is exceeded. -// The parameter <data> is the current old session data stored in memory, +// The parameter `ttl` specifies the TTL for this session, and it returns nil if the TTL is exceeded. +// The parameter `data` is the current old session data stored in memory, // and for some storage it might be nil if memory storage is disabled. // // This function is called ever when session starts. -func (s *StorageMemory) GetSession(id string, ttl time.Duration, data *gmap.StrAnyMap) (*gmap.StrAnyMap, error) { +func (s *StorageMemory) GetSession(ctx context.Context, id string, ttl time.Duration, data *gmap.StrAnyMap) (*gmap.StrAnyMap, error) { return data, nil } // SetSession updates the data map for specified session id. // This function is called ever after session, which is changed dirty, is closed. // This copy all session data map from memory to storage. -func (s *StorageMemory) SetSession(id string, data *gmap.StrAnyMap, ttl time.Duration) error { +func (s *StorageMemory) SetSession(ctx context.Context, id string, data *gmap.StrAnyMap, ttl time.Duration) error { return nil } // UpdateTTL updates the TTL for specified session id. // This function is called ever after session, which is not dirty, is closed. // It just adds the session id to the async handling queue. -func (s *StorageMemory) UpdateTTL(id string, ttl time.Duration) error { - return nil -} - -// doUpdateTTL updates the TTL for session id. -func (s *StorageMemory) doUpdateTTL(id string) error { +func (s *StorageMemory) UpdateTTL(ctx context.Context, id string, ttl time.Duration) error { return nil } diff --git a/os/gsession/gsession_storage_redis.go b/os/gsession/gsession_storage_redis.go index 567fe2036..0082093b6 100644 --- a/os/gsession/gsession_storage_redis.go +++ b/os/gsession/gsession_storage_redis.go @@ -1,4 +1,4 @@ -// Copyright 2019 gf Author(https://github.com/gogf/gf). All Rights Reserved. +// Copyright GoFrame Author(https://goframe.org). All Rights Reserved. // // This Source Code Form is subject to the terms of the MIT License. // If a copy of the MIT was not distributed with this file, @@ -7,6 +7,7 @@ package gsession import ( + "context" "github.com/gogf/gf/container/gmap" "github.com/gogf/gf/database/gredis" "github.com/gogf/gf/internal/intlog" @@ -26,7 +27,7 @@ type StorageRedis struct { var ( // DefaultStorageRedisLoopInterval is the interval updating TTL for session ids // in last duration. - DefaultStorageRedisLoopInterval = time.Minute + DefaultStorageRedisLoopInterval = 10 * time.Second ) // NewStorageRedis creates and returns a redis storage object for session. @@ -44,78 +45,80 @@ func NewStorageRedis(redis *gredis.Redis, prefix ...string) *StorageRedis { } // Batch updates the TTL for session ids timely. gtimer.AddSingleton(DefaultStorageRedisLoopInterval, func() { - intlog.Print("StorageRedis.timer start") - var id string - var err error - var ttlSeconds int + intlog.Print(context.TODO(), "StorageRedis.timer start") + var ( + id string + err error + ttlSeconds int + ) for { if id, ttlSeconds = s.updatingIdMap.Pop(); id == "" { break } else { - if err = s.doUpdateTTL(id, ttlSeconds); err != nil { - intlog.Error(err) + if err = s.doUpdateTTL(context.TODO(), id, ttlSeconds); err != nil { + intlog.Error(context.TODO(), err) } } } - intlog.Print("StorageRedis.timer end") + intlog.Print(context.TODO(), "StorageRedis.timer end") }) return s } // New creates a session id. // This function can be used for custom session creation. -func (s *StorageRedis) New(ttl time.Duration) (id string) { - return "" +func (s *StorageRedis) New(ctx context.Context, ttl time.Duration) (id string, err error) { + return "", ErrorDisabled } // Get retrieves session value with given key. // It returns nil if the key does not exist in the session. -func (s *StorageRedis) Get(id string, key string) interface{} { - return nil +func (s *StorageRedis) Get(ctx context.Context, id string, key string) (value interface{}, err error) { + return nil, ErrorDisabled } // GetMap retrieves all key-value pairs as map from storage. -func (s *StorageRedis) GetMap(id string) map[string]interface{} { - return nil +func (s *StorageRedis) GetMap(ctx context.Context, id string) (data map[string]interface{}, err error) { + return nil, ErrorDisabled } // GetSize retrieves the size of key-value pairs from storage. -func (s *StorageRedis) GetSize(id string) int { - return -1 +func (s *StorageRedis) GetSize(ctx context.Context, id string) (size int, err error) { + return -1, ErrorDisabled } // Set sets key-value session pair to the storage. -// The parameter <ttl> specifies the TTL for the session id (not for the key-value pair). -func (s *StorageRedis) Set(id string, key string, value interface{}, ttl time.Duration) error { +// The parameter `ttl` specifies the TTL for the session id (not for the key-value pair). +func (s *StorageRedis) Set(ctx context.Context, id string, key string, value interface{}, ttl time.Duration) error { return ErrorDisabled } // SetMap batch sets key-value session pairs with map to the storage. -// The parameter <ttl> specifies the TTL for the session id(not for the key-value pair). -func (s *StorageRedis) SetMap(id string, data map[string]interface{}, ttl time.Duration) error { +// The parameter `ttl` specifies the TTL for the session id(not for the key-value pair). +func (s *StorageRedis) SetMap(ctx context.Context, id string, data map[string]interface{}, ttl time.Duration) error { return ErrorDisabled } // Remove deletes key with its value from storage. -func (s *StorageRedis) Remove(id string, key string) error { +func (s *StorageRedis) Remove(ctx context.Context, id string, key string) error { return ErrorDisabled } // RemoveAll deletes all key-value pairs from storage. -func (s *StorageRedis) RemoveAll(id string) error { +func (s *StorageRedis) RemoveAll(ctx context.Context, id string) error { return ErrorDisabled } // GetSession returns the session data as *gmap.StrAnyMap for given session id from storage. // -// The parameter <ttl> specifies the TTL for this session, and it returns nil if the TTL is exceeded. -// The parameter <data> is the current old session data stored in memory, +// The parameter `ttl` specifies the TTL for this session, and it returns nil if the TTL is exceeded. +// The parameter `data` is the current old session data stored in memory, // and for some storage it might be nil if memory storage is disabled. // // This function is called ever when session starts. -func (s *StorageRedis) GetSession(id string, ttl time.Duration, data *gmap.StrAnyMap) (*gmap.StrAnyMap, error) { - intlog.Printf("StorageRedis.GetSession: %s, %v", id, ttl) - r, err := s.redis.DoVar("GET", s.key(id)) +func (s *StorageRedis) GetSession(ctx context.Context, id string, ttl time.Duration, data *gmap.StrAnyMap) (*gmap.StrAnyMap, error) { + intlog.Printf(ctx, "StorageRedis.GetSession: %s, %v", id, ttl) + r, err := s.redis.Ctx(ctx).DoVar("GET", s.key(id)) if err != nil { return nil, err } @@ -124,7 +127,7 @@ func (s *StorageRedis) GetSession(id string, ttl time.Duration, data *gmap.StrAn return nil, nil } var m map[string]interface{} - if err = json.Unmarshal(content, &m); err != nil { + if err = json.UnmarshalUseNumber(content, &m); err != nil { return nil, err } if m == nil { @@ -141,21 +144,21 @@ func (s *StorageRedis) GetSession(id string, ttl time.Duration, data *gmap.StrAn // SetSession updates the data map for specified session id. // This function is called ever after session, which is changed dirty, is closed. // This copy all session data map from memory to storage. -func (s *StorageRedis) SetSession(id string, data *gmap.StrAnyMap, ttl time.Duration) error { - intlog.Printf("StorageRedis.SetSession: %s, %v, %v", id, data, ttl) +func (s *StorageRedis) SetSession(ctx context.Context, id string, data *gmap.StrAnyMap, ttl time.Duration) error { + intlog.Printf(ctx, "StorageRedis.SetSession: %s, %v, %v", id, data, ttl) content, err := json.Marshal(data) if err != nil { return err } - _, err = s.redis.DoVar("SETEX", s.key(id), int64(ttl.Seconds()), content) + _, err = s.redis.Ctx(ctx).DoVar("SETEX", s.key(id), int64(ttl.Seconds()), content) return err } // UpdateTTL updates the TTL for specified session id. // This function is called ever after session, which is not dirty, is closed. // It just adds the session id to the async handling queue. -func (s *StorageRedis) UpdateTTL(id string, ttl time.Duration) error { - intlog.Printf("StorageRedis.UpdateTTL: %s, %v", id, ttl) +func (s *StorageRedis) UpdateTTL(ctx context.Context, id string, ttl time.Duration) error { + intlog.Printf(ctx, "StorageRedis.UpdateTTL: %s, %v", id, ttl) if ttl >= DefaultStorageRedisLoopInterval { s.updatingIdMap.Set(id, int(ttl.Seconds())) } @@ -163,9 +166,9 @@ func (s *StorageRedis) UpdateTTL(id string, ttl time.Duration) error { } // doUpdateTTL updates the TTL for session id. -func (s *StorageRedis) doUpdateTTL(id string, ttlSeconds int) error { - intlog.Printf("StorageRedis.doUpdateTTL: %s, %d", id, ttlSeconds) - _, err := s.redis.DoVar("EXPIRE", s.key(id), ttlSeconds) +func (s *StorageRedis) doUpdateTTL(ctx context.Context, id string, ttlSeconds int) error { + intlog.Printf(ctx, "StorageRedis.doUpdateTTL: %s, %d", id, ttlSeconds) + _, err := s.redis.Ctx(ctx).DoVar("EXPIRE", s.key(id), ttlSeconds) return err } diff --git a/os/gsession/gsession_storage_redis_hashtable.go b/os/gsession/gsession_storage_redis_hashtable.go index b7eec2970..c2a6ba195 100644 --- a/os/gsession/gsession_storage_redis_hashtable.go +++ b/os/gsession/gsession_storage_redis_hashtable.go @@ -1,4 +1,4 @@ -// Copyright 2019 gf Author(https://github.com/gogf/gf). All Rights Reserved. +// Copyright GoFrame Author(https://goframe.org). All Rights Reserved. // // This Source Code Form is subject to the terms of the MIT License. // If a copy of the MIT was not distributed with this file, @@ -7,6 +7,7 @@ package gsession import ( + "context" "time" "github.com/gogf/gf/container/gmap" @@ -38,54 +39,57 @@ func NewStorageRedisHashTable(redis *gredis.Redis, prefix ...string) *StorageRed // New creates a session id. // This function can be used for custom session creation. -func (s *StorageRedisHashTable) New(ttl time.Duration) (id string) { - return "" +func (s *StorageRedisHashTable) New(ctx context.Context, ttl time.Duration) (id string, err error) { + return "", ErrorDisabled } // Get retrieves session value with given key. // It returns nil if the key does not exist in the session. -func (s *StorageRedisHashTable) Get(id string, key string) interface{} { - r, _ := s.redis.Do("HGET", s.key(id), key) - if r != nil { - return gconv.String(r) +func (s *StorageRedisHashTable) Get(ctx context.Context, id string, key string) (value interface{}, err error) { + value, err = s.redis.Ctx(ctx).Do("HGET", s.key(id), key) + if value != nil { + value = gconv.String(value) } - return r + return } // GetMap retrieves all key-value pairs as map from storage. -func (s *StorageRedisHashTable) GetMap(id string) map[string]interface{} { - r, err := s.redis.DoVar("HGETALL", s.key(id)) +func (s *StorageRedisHashTable) GetMap(ctx context.Context, id string) (data map[string]interface{}, err error) { + r, err := s.redis.Ctx(ctx).DoVar("HGETALL", s.key(id)) if err != nil { - return nil + return nil, err } + data = make(map[string]interface{}) array := r.Interfaces() - m := make(map[string]interface{}) for i := 0; i < len(array); i += 2 { if array[i+1] != nil { - m[gconv.String(array[i])] = gconv.String(array[i+1]) + data[gconv.String(array[i])] = gconv.String(array[i+1]) } else { - m[gconv.String(array[i])] = array[i+1] + data[gconv.String(array[i])] = array[i+1] } } - return m + return data, nil } // GetSize retrieves the size of key-value pairs from storage. -func (s *StorageRedisHashTable) GetSize(id string) int { - r, _ := s.redis.DoVar("HLEN", s.key(id)) - return r.Int() +func (s *StorageRedisHashTable) GetSize(ctx context.Context, id string) (size int, err error) { + r, err := s.redis.Ctx(ctx).DoVar("HLEN", s.key(id)) + if err != nil { + return -1, err + } + return r.Int(), nil } // Set sets key-value session pair to the storage. -// The parameter <ttl> specifies the TTL for the session id (not for the key-value pair). -func (s *StorageRedisHashTable) Set(id string, key string, value interface{}, ttl time.Duration) error { - _, err := s.redis.Do("HSET", s.key(id), key, value) +// The parameter `ttl` specifies the TTL for the session id (not for the key-value pair). +func (s *StorageRedisHashTable) Set(ctx context.Context, id string, key string, value interface{}, ttl time.Duration) error { + _, err := s.redis.Ctx(ctx).Do("HSET", s.key(id), key, value) return err } // SetMap batch sets key-value session pairs with map to the storage. -// The parameter <ttl> specifies the TTL for the session id(not for the key-value pair). -func (s *StorageRedisHashTable) SetMap(id string, data map[string]interface{}, ttl time.Duration) error { +// The parameter `ttl` specifies the TTL for the session id(not for the key-value pair). +func (s *StorageRedisHashTable) SetMap(ctx context.Context, id string, data map[string]interface{}, ttl time.Duration) error { array := make([]interface{}, len(data)*2+1) array[0] = s.key(id) @@ -95,32 +99,32 @@ func (s *StorageRedisHashTable) SetMap(id string, data map[string]interface{}, t array[index+1] = v index += 2 } - _, err := s.redis.Do("HMSET", array...) + _, err := s.redis.Ctx(ctx).Do("HMSET", array...) return err } // Remove deletes key with its value from storage. -func (s *StorageRedisHashTable) Remove(id string, key string) error { - _, err := s.redis.Do("HDEL", s.key(id), key) +func (s *StorageRedisHashTable) Remove(ctx context.Context, id string, key string) error { + _, err := s.redis.Ctx(ctx).Do("HDEL", s.key(id), key) return err } // RemoveAll deletes all key-value pairs from storage. -func (s *StorageRedisHashTable) RemoveAll(id string) error { - _, err := s.redis.Do("DEL", s.key(id)) +func (s *StorageRedisHashTable) RemoveAll(ctx context.Context, id string) error { + _, err := s.redis.Ctx(ctx).Do("DEL", s.key(id)) return err } // GetSession returns the session data as *gmap.StrAnyMap for given session id from storage. // -// The parameter <ttl> specifies the TTL for this session, and it returns nil if the TTL is exceeded. -// The parameter <data> is the current old session data stored in memory, +// The parameter `ttl` specifies the TTL for this session, and it returns nil if the TTL is exceeded. +// The parameter `data` is the current old session data stored in memory, // and for some storage it might be nil if memory storage is disabled. // // This function is called ever when session starts. -func (s *StorageRedisHashTable) GetSession(id string, ttl time.Duration, data *gmap.StrAnyMap) (*gmap.StrAnyMap, error) { - intlog.Printf("StorageRedisHashTable.GetSession: %s, %v", id, ttl) - r, err := s.redis.DoVar("EXISTS", s.key(id)) +func (s *StorageRedisHashTable) GetSession(ctx context.Context, id string, ttl time.Duration, data *gmap.StrAnyMap) (*gmap.StrAnyMap, error) { + intlog.Printf(ctx, "StorageRedisHashTable.GetSession: %s, %v", id, ttl) + r, err := s.redis.Ctx(ctx).DoVar("EXISTS", s.key(id)) if err != nil { return nil, err } @@ -133,17 +137,17 @@ func (s *StorageRedisHashTable) GetSession(id string, ttl time.Duration, data *g // SetSession updates the data map for specified session id. // This function is called ever after session, which is changed dirty, is closed. // This copy all session data map from memory to storage. -func (s *StorageRedisHashTable) SetSession(id string, data *gmap.StrAnyMap, ttl time.Duration) error { - intlog.Printf("StorageRedisHashTable.SetSession: %s, %v", id, ttl) - _, err := s.redis.Do("EXPIRE", s.key(id), int64(ttl.Seconds())) +func (s *StorageRedisHashTable) SetSession(ctx context.Context, id string, data *gmap.StrAnyMap, ttl time.Duration) error { + intlog.Printf(ctx, "StorageRedisHashTable.SetSession: %s, %v", id, ttl) + _, err := s.redis.Ctx(ctx).Do("EXPIRE", s.key(id), int64(ttl.Seconds())) return err } // UpdateTTL updates the TTL for specified session id. // This function is called ever after session, which is not dirty, is closed. // It just adds the session id to the async handling queue. -func (s *StorageRedisHashTable) UpdateTTL(id string, ttl time.Duration) error { - intlog.Printf("StorageRedisHashTable.UpdateTTL: %s, %v", id, ttl) +func (s *StorageRedisHashTable) UpdateTTL(ctx context.Context, id string, ttl time.Duration) error { + intlog.Printf(ctx, "StorageRedisHashTable.UpdateTTL: %s, %v", id, ttl) _, err := s.redis.Do("EXPIRE", s.key(id), int64(ttl.Seconds())) return err } diff --git a/os/gsession/gsession_unit_storage_file_test.go b/os/gsession/gsession_unit_storage_file_test.go index 1efcf3cc4..3a7c7ebde 100644 --- a/os/gsession/gsession_unit_storage_file_test.go +++ b/os/gsession/gsession_unit_storage_file_test.go @@ -1,4 +1,4 @@ -// Copyright 2019 gf Author(https://github.com/gogf/gf). All Rights Reserved. +// Copyright GoFrame Author(https://goframe.org). All Rights Reserved. // // This Source Code Form is subject to the terms of the MIT License. // If a copy of the MIT was not distributed with this file, @@ -7,6 +7,7 @@ package gsession_test import ( + "context" "github.com/gogf/gf/frame/g" "github.com/gogf/gf/os/gsession" "testing" @@ -20,7 +21,7 @@ func Test_StorageFile(t *testing.T) { manager := gsession.New(time.Second, storage) sessionId := "" gtest.C(t, func(t *gtest.T) { - s := manager.New() + s := manager.New(context.TODO()) defer s.Close() s.Set("k1", "v1") s.Set("k2", "v2") @@ -34,7 +35,7 @@ func Test_StorageFile(t *testing.T) { time.Sleep(500 * time.Millisecond) gtest.C(t, func(t *gtest.T) { - s := manager.New(sessionId) + s := manager.New(context.TODO(), sessionId) t.Assert(s.Get("k1"), "v1") t.Assert(s.Get("k2"), "v2") t.Assert(s.Get("k3"), "v3") @@ -66,7 +67,7 @@ func Test_StorageFile(t *testing.T) { time.Sleep(1000 * time.Millisecond) gtest.C(t, func(t *gtest.T) { - s := manager.New(sessionId) + s := manager.New(context.TODO(), sessionId) t.Assert(s.Size(), 0) t.Assert(s.Get("k5"), nil) t.Assert(s.Get("k6"), nil) diff --git a/os/gsession/gsession_unit_storage_memory_test.go b/os/gsession/gsession_unit_storage_memory_test.go index e551855f0..c42413d34 100644 --- a/os/gsession/gsession_unit_storage_memory_test.go +++ b/os/gsession/gsession_unit_storage_memory_test.go @@ -1,4 +1,4 @@ -// Copyright 2019 gf Author(https://github.com/gogf/gf). All Rights Reserved. +// Copyright GoFrame Author(https://goframe.org). All Rights Reserved. // // This Source Code Form is subject to the terms of the MIT License. // If a copy of the MIT was not distributed with this file, @@ -7,6 +7,7 @@ package gsession_test import ( + "context" "github.com/gogf/gf/frame/g" "github.com/gogf/gf/os/gsession" "testing" @@ -20,7 +21,7 @@ func Test_StorageMemory(t *testing.T) { manager := gsession.New(time.Second, storage) sessionId := "" gtest.C(t, func(t *gtest.T) { - s := manager.New() + s := manager.New(context.TODO()) defer s.Close() s.Set("k1", "v1") s.Set("k2", "v2") @@ -34,7 +35,7 @@ func Test_StorageMemory(t *testing.T) { time.Sleep(500 * time.Millisecond) gtest.C(t, func(t *gtest.T) { - s := manager.New(sessionId) + s := manager.New(context.TODO(), sessionId) t.Assert(s.Get("k1"), "v1") t.Assert(s.Get("k2"), "v2") t.Assert(s.Get("k3"), "v3") @@ -66,7 +67,7 @@ func Test_StorageMemory(t *testing.T) { time.Sleep(1000 * time.Millisecond) gtest.C(t, func(t *gtest.T) { - s := manager.New(sessionId) + s := manager.New(context.TODO(), sessionId) t.Assert(s.Size(), 0) t.Assert(s.Get("k5"), nil) t.Assert(s.Get("k6"), nil) diff --git a/os/gsession/gsession_unit_storage_redis_hashtable_test.go b/os/gsession/gsession_unit_storage_redis_hashtable_test.go index f9f6cd3eb..20d7ff082 100644 --- a/os/gsession/gsession_unit_storage_redis_hashtable_test.go +++ b/os/gsession/gsession_unit_storage_redis_hashtable_test.go @@ -1,4 +1,4 @@ -// Copyright 2019 gf Author(https://github.com/gogf/gf). All Rights Reserved. +// Copyright GoFrame Author(https://goframe.org). All Rights Reserved. // // This Source Code Form is subject to the terms of the MIT License. // If a copy of the MIT was not distributed with this file, @@ -7,6 +7,7 @@ package gsession_test import ( + "context" "github.com/gogf/gf/database/gredis" "github.com/gogf/gf/frame/g" "github.com/gogf/gf/os/gsession" @@ -26,7 +27,7 @@ func Test_StorageRedisHashTable(t *testing.T) { manager := gsession.New(time.Second, storage) sessionId := "" gtest.C(t, func(t *gtest.T) { - s := manager.New() + s := manager.New(context.TODO()) defer s.Close() s.Set("k1", "v1") s.Set("k2", "v2") @@ -38,7 +39,7 @@ func Test_StorageRedisHashTable(t *testing.T) { sessionId = s.Id() }) gtest.C(t, func(t *gtest.T) { - s := manager.New(sessionId) + s := manager.New(context.TODO(), sessionId) t.Assert(s.Get("k1"), "v1") t.Assert(s.Get("k2"), "v2") t.Assert(s.Get("k3"), "v3") @@ -71,7 +72,7 @@ func Test_StorageRedisHashTable(t *testing.T) { time.Sleep(1500 * time.Millisecond) gtest.C(t, func(t *gtest.T) { - s := manager.New(sessionId) + s := manager.New(context.TODO(), sessionId) t.Assert(s.Size(), 0) t.Assert(s.Get("k5"), nil) t.Assert(s.Get("k6"), nil) @@ -89,7 +90,7 @@ func Test_StorageRedisHashTablePrefix(t *testing.T) { manager := gsession.New(time.Second, storage) sessionId := "" gtest.C(t, func(t *gtest.T) { - s := manager.New() + s := manager.New(context.TODO()) defer s.Close() s.Set("k1", "v1") s.Set("k2", "v2") @@ -101,7 +102,7 @@ func Test_StorageRedisHashTablePrefix(t *testing.T) { sessionId = s.Id() }) gtest.C(t, func(t *gtest.T) { - s := manager.New(sessionId) + s := manager.New(context.TODO(), sessionId) t.Assert(s.Get("k1"), "v1") t.Assert(s.Get("k2"), "v2") t.Assert(s.Get("k3"), "v3") @@ -134,7 +135,7 @@ func Test_StorageRedisHashTablePrefix(t *testing.T) { time.Sleep(1500 * time.Millisecond) gtest.C(t, func(t *gtest.T) { - s := manager.New(sessionId) + s := manager.New(context.TODO(), sessionId) t.Assert(s.Size(), 0) t.Assert(s.Get("k5"), nil) t.Assert(s.Get("k6"), nil) diff --git a/os/gsession/gsession_unit_storage_redis_test.go b/os/gsession/gsession_unit_storage_redis_test.go index 278c404ea..53a5e754b 100644 --- a/os/gsession/gsession_unit_storage_redis_test.go +++ b/os/gsession/gsession_unit_storage_redis_test.go @@ -1,4 +1,4 @@ -// Copyright 2019 gf Author(https://github.com/gogf/gf). All Rights Reserved. +// Copyright GoFrame Author(https://goframe.org). All Rights Reserved. // // This Source Code Form is subject to the terms of the MIT License. // If a copy of the MIT was not distributed with this file, @@ -7,6 +7,7 @@ package gsession_test import ( + "context" "github.com/gogf/gf/database/gredis" "github.com/gogf/gf/frame/g" "github.com/gogf/gf/os/gsession" @@ -24,7 +25,7 @@ func Test_StorageRedis(t *testing.T) { manager := gsession.New(time.Second, storage) sessionId := "" gtest.C(t, func(t *gtest.T) { - s := manager.New() + s := manager.New(context.TODO()) defer s.Close() s.Set("k1", "v1") s.Set("k2", "v2") @@ -38,7 +39,7 @@ func Test_StorageRedis(t *testing.T) { time.Sleep(500 * time.Millisecond) gtest.C(t, func(t *gtest.T) { - s := manager.New(sessionId) + s := manager.New(context.TODO(), sessionId) t.Assert(s.Get("k1"), "v1") t.Assert(s.Get("k2"), "v2") t.Assert(s.Get("k3"), "v3") @@ -70,7 +71,7 @@ func Test_StorageRedis(t *testing.T) { time.Sleep(1000 * time.Millisecond) gtest.C(t, func(t *gtest.T) { - s := manager.New(sessionId) + s := manager.New(context.TODO(), sessionId) t.Assert(s.Size(), 0) t.Assert(s.Get("k5"), nil) t.Assert(s.Get("k6"), nil) @@ -86,7 +87,7 @@ func Test_StorageRedisPrefix(t *testing.T) { manager := gsession.New(time.Second, storage) sessionId := "" gtest.C(t, func(t *gtest.T) { - s := manager.New() + s := manager.New(context.TODO()) defer s.Close() s.Set("k1", "v1") s.Set("k2", "v2") @@ -100,7 +101,7 @@ func Test_StorageRedisPrefix(t *testing.T) { time.Sleep(500 * time.Millisecond) gtest.C(t, func(t *gtest.T) { - s := manager.New(sessionId) + s := manager.New(context.TODO(), sessionId) t.Assert(s.Get("k1"), "v1") t.Assert(s.Get("k2"), "v2") t.Assert(s.Get("k3"), "v3") @@ -132,7 +133,7 @@ func Test_StorageRedisPrefix(t *testing.T) { time.Sleep(1000 * time.Millisecond) gtest.C(t, func(t *gtest.T) { - s := manager.New(sessionId) + s := manager.New(context.TODO(), sessionId) t.Assert(s.Size(), 0) t.Assert(s.Get("k5"), nil) t.Assert(s.Get("k6"), nil) diff --git a/os/gsession/gsession_unit_test.go b/os/gsession/gsession_unit_test.go index 41ef14a66..98ebaea17 100644 --- a/os/gsession/gsession_unit_test.go +++ b/os/gsession/gsession_unit_test.go @@ -1,4 +1,4 @@ -// Copyright 2019 gf Author(https://github.com/gogf/gf). All Rights Reserved. +// Copyright GoFrame Author(https://goframe.org). All Rights Reserved. // // This Source Code Form is subject to the terms of the MIT License. // If a copy of the MIT was not distributed with this file, diff --git a/os/gspath/gspath.go b/os/gspath/gspath.go index a2aafccb2..fb0a8818d 100644 --- a/os/gspath/gspath.go +++ b/os/gspath/gspath.go @@ -1,4 +1,4 @@ -// Copyright 2018 gf Author(https://github.com/gogf/gf). All Rights Reserved. +// Copyright GoFrame Author(https://goframe.org). All Rights Reserved. // // This Source Code Form is subject to the terms of the MIT License. // If a copy of the MIT was not distributed with this file, @@ -12,8 +12,9 @@ package gspath import ( - "errors" - "fmt" + "context" + "github.com/gogf/gf/errors/gcode" + "github.com/gogf/gf/errors/gerror" "github.com/gogf/gf/internal/intlog" "os" "sort" @@ -59,7 +60,7 @@ func New(path string, cache bool) *SPath { } // Get creates and returns a instance of searching manager for given path. -// The parameter <cache> specifies whether using cache feature for this manager. +// The parameter `cache` specifies whether using cache feature for this manager. // If cache feature is enabled, it asynchronously and recursively scans the path // and updates all sub files/folders to the cache using package gfsnotify. func Get(root string, cache bool) *SPath { @@ -71,24 +72,24 @@ func Get(root string, cache bool) *SPath { }).(*SPath) } -// Search searches file <name> under path <root>. -// The parameter <root> should be a absolute path. It will not automatically -// convert <root> to absolute path for performance reason. -// The optional parameter <indexFiles> specifies the searching index files when the result is a directory. -// For example, if the result <a> is a directory, and <indexFiles> is [index.html, main.html], it will also -// search [index.html, main.html] under <a>. It returns the absolute file path if any of them found, -// or else it returns <a>. +// Search searches file `name` under path `root`. +// The parameter `root` should be a absolute path. It will not automatically +// convert `root` to absolute path for performance reason. +// The optional parameter `indexFiles` specifies the searching index files when the result is a directory. +// For example, if the result `filePath` is a directory, and `indexFiles` is [index.html, main.html], it will also +// search [index.html, main.html] under `filePath`. It returns the absolute file path if any of them found, +// or else it returns `filePath`. func Search(root string, name string, indexFiles ...string) (filePath string, isDir bool) { return Get(root, false).Search(name, indexFiles...) } -// SearchWithCache searches file <name> under path <root> with cache feature enabled. -// The parameter <root> should be a absolute path. It will not automatically -// convert <root> to absolute path for performance reason. -// The optional parameter <indexFiles> specifies the searching index files when the result is a directory. -// For example, if the result <a> is a directory, and <indexFiles> is [index.html, main.html], it will also -// search [index.html, main.html] under <a>. It returns the absolute file path if any of them found, -// or else it returns <a>. +// SearchWithCache searches file `name` under path `root` with cache feature enabled. +// The parameter `root` should be a absolute path. It will not automatically +// convert `root` to absolute path for performance reason. +// The optional parameter `indexFiles` specifies the searching index files when the result is a directory. +// For example, if the result `filePath` is a directory, and `indexFiles` is [index.html, main.html], it will also +// search [index.html, main.html] under `filePath`. It returns the absolute file path if any of them found, +// or else it returns `filePath`. func SearchWithCache(root string, name string, indexFiles ...string) (filePath string, isDir bool) { return Get(root, true).Search(name, indexFiles...) } @@ -103,7 +104,7 @@ func (sp *SPath) Set(path string) (realPath string, err error) { } } if realPath == "" { - return realPath, errors.New(fmt.Sprintf(`path "%s" does not exist`, path)) + return realPath, gerror.NewCodef(gcode.CodeInvalidParameter, `path "%s" does not exist`, path) } // The set path must be a directory. if gfile.IsDir(realPath) { @@ -113,7 +114,7 @@ func (sp *SPath) Set(path string) (realPath string, err error) { sp.removeMonitorByPath(v) } } - intlog.Print("paths clear:", sp.paths) + intlog.Print(context.TODO(), "paths clear:", sp.paths) sp.paths.Clear() if sp.cache != nil { sp.cache.Clear() @@ -123,7 +124,7 @@ func (sp *SPath) Set(path string) (realPath string, err error) { sp.addMonitorByPath(realPath) return realPath, nil } else { - return "", errors.New(path + " should be a folder") + return "", gerror.NewCode(gcode.CodeInvalidParameter, path+" should be a folder") } } @@ -138,7 +139,7 @@ func (sp *SPath) Add(path string) (realPath string, err error) { } } if realPath == "" { - return realPath, errors.New(fmt.Sprintf(`path "%s" does not exist`, path)) + return realPath, gerror.NewCodef(gcode.CodeInvalidParameter, `path "%s" does not exist`, path) } // The added path must be a directory. if gfile.IsDir(realPath) { @@ -152,15 +153,15 @@ func (sp *SPath) Add(path string) (realPath string, err error) { } return realPath, nil } else { - return "", errors.New(path + " should be a folder") + return "", gerror.NewCode(gcode.CodeInvalidParameter, path+" should be a folder") } } -// Search searches file <name> in the manager. -// The optional parameter <indexFiles> specifies the searching index files when the result is a directory. -// For example, if the result <a> is a directory, and <indexFiles> is [index.html, main.html], it will also -// search [index.html, main.html] under <a>. It returns the absolute file path if any of them found, -// or else it returns <a>. +// Search searches file `name` in the manager. +// The optional parameter `indexFiles` specifies the searching index files when the result is a directory. +// For example, if the result `filePath` is a directory, and `indexFiles` is [index.html, main.html], it will also +// search [index.html, main.html] under `filePath`. It returns the absolute file path if any of them found, +// or else it returns `filePath`. func (sp *SPath) Search(name string, indexFiles ...string) (filePath string, isDir bool) { // No cache enabled. if sp.cache == nil { @@ -213,8 +214,8 @@ func (sp *SPath) Search(name string, indexFiles ...string) (filePath string, isD return } -// Remove deletes the <path> from cache files of the manager. -// The parameter <path> can be either a absolute path or just a relative file name. +// Remove deletes the `path` from cache files of the manager. +// The parameter `path` can be either a absolute path or just a relative file name. func (sp *SPath) Remove(path string) { if sp.cache == nil { return diff --git a/os/gspath/gspath_cache.go b/os/gspath/gspath_cache.go index 9e671ce18..324be56c1 100644 --- a/os/gspath/gspath_cache.go +++ b/os/gspath/gspath_cache.go @@ -1,4 +1,4 @@ -// Copyright 2018 gf Author(https://github.com/gogf/gf). All Rights Reserved. +// Copyright GoFrame Author(https://goframe.org). All Rights Reserved. // // This Source Code Form is subject to the terms of the MIT License. // If a copy of the MIT was not distributed with this file, diff --git a/os/gspath/gspath_unit_test.go b/os/gspath/gspath_unit_test.go index 74b24b2e1..94b741337 100644 --- a/os/gspath/gspath_unit_test.go +++ b/os/gspath/gspath_unit_test.go @@ -1,4 +1,4 @@ -// Copyright 2019 gf Author(https://github.com/gogf/gf). All Rights Reserved. +// Copyright GoFrame Author(https://goframe.org). All Rights Reserved. // // This Source Code Form is subject to the terms of the MIT License. // If a copy of the MIT was not distributed with this file, @@ -90,22 +90,27 @@ func TestSPath_Basic(t *testing.T) { realPath, err = gsp.Add("gf_tmp1") t.Assert(err != nil, false) t.Assert(realPath, gfile.Join(root, "gf_tmp1")) + realPath, err = gsp.Add("gf_tmp3") t.Assert(err != nil, true) t.Assert(realPath, "") + gsp.Remove(gfile.Join(root, "gf_tmp")) gsp.Remove(gfile.Join(root, "gf_tmp1")) gsp.Remove(gfile.Join(root, "gf_tmp3")) t.Assert(gsp.Size(), 3) t.Assert(len(gsp.Paths()), 3) + gsp.AllPaths() gsp.Set(root) fp, isDir = gsp.Search("gf_tmp") t.Assert(fp, gfile.Join(root, "gf_tmp")) t.Assert(isDir, true) + fp, isDir = gsp.Search("gf_tmp", "gf.txt") t.Assert(fp, gfile.Join(root, "gf_tmp", "gf.txt")) t.Assert(isDir, false) + fp, isDir = gsp.Search("/", "gf.txt") t.Assert(fp, pwd) t.Assert(isDir, true) diff --git a/os/gtime/gtime.go b/os/gtime/gtime.go index 500b39761..318704c83 100644 --- a/os/gtime/gtime.go +++ b/os/gtime/gtime.go @@ -1,4 +1,4 @@ -// Copyright 2017 gf Author(https://github.com/gogf/gf). All Rights Reserved. +// Copyright GoFrame Author(https://goframe.org). All Rights Reserved. // // This Source Code Form is subject to the terms of the MIT License. // If a copy of the MIT was not distributed with this file, @@ -10,10 +10,11 @@ package gtime import ( - "errors" "fmt" + "github.com/gogf/gf/errors/gcode" "github.com/gogf/gf/errors/gerror" "github.com/gogf/gf/internal/utils" + "os" "regexp" "strconv" "strings" @@ -24,6 +25,7 @@ import ( const ( // Short writes for common usage durations. + D = 24 * time.Hour H = time.Hour M = time.Minute @@ -45,7 +47,7 @@ const ( // "2018/10/31 - 16:38:46" // "2018-02-09", // "2018.02.09", - TIME_REAGEX_PATTERN1 = `(\d{4}[-/\.]\d{2}[-/\.]\d{2})[:\sT-]*(\d{0,2}:{0,1}\d{0,2}:{0,1}\d{0,2}){0,1}\.{0,1}(\d{0,9})([\sZ]{0,1})([\+-]{0,1})([:\d]*)` + timeRegexPattern1 = `(\d{4}[-/\.]\d{1,2}[-/\.]\d{1,2})[:\sT-]*(\d{0,2}:{0,1}\d{0,2}:{0,1}\d{0,2}){0,1}\.{0,1}(\d{0,9})([\sZ]{0,1})([\+-]{0,1})([:\d]*)` // Regular expression2(datetime separator supports '-', '/', '.'). // Eg: @@ -53,14 +55,21 @@ const ( // 01/Nov/2018 11:50:28 // 01.Nov.2018 11:50:28 // 01.Nov.2018:11:50:28 - TIME_REAGEX_PATTERN2 = `(\d{1,2}[-/\.][A-Za-z]{3,}[-/\.]\d{4})[:\sT-]*(\d{0,2}:{0,1}\d{0,2}:{0,1}\d{0,2}){0,1}\.{0,1}(\d{0,9})([\sZ]{0,1})([\+-]{0,1})([:\d]*)` + timeRegexPattern2 = `(\d{1,2}[-/\.][A-Za-z]{3,}[-/\.]\d{4})[:\sT-]*(\d{0,2}:{0,1}\d{0,2}:{0,1}\d{0,2}){0,1}\.{0,1}(\d{0,9})([\sZ]{0,1})([\+-]{0,1})([:\d]*)` + + // Regular expression3(time). + // Eg: + // 11:50:28 + // 11:50:28.897 + timeRegexPattern3 = `(\d{2}):(\d{2}):(\d{2})\.{0,1}(\d{0,9})` ) var ( // It's more high performance using regular expression // than time.ParseInLocation to parse the datetime string. - timeRegex1, _ = regexp.Compile(TIME_REAGEX_PATTERN1) - timeRegex2, _ = regexp.Compile(TIME_REAGEX_PATTERN2) + timeRegex1, _ = regexp.Compile(timeRegexPattern1) + timeRegex2, _ = regexp.Compile(timeRegexPattern2) + timeRegex3, _ = regexp.Compile(timeRegexPattern3) // Month words to arabic numerals mapping. monthMap = map[string]int{ @@ -95,18 +104,14 @@ var ( // The parameter <zone> is an area string specifying corresponding time zone, // eg: Asia/Shanghai. // -// Note that the time zone database needed by LoadLocation may not be -// present on all systems, especially non-Unix systems. -// LoadLocation looks in the directory or uncompressed zip file -// named by the ZONEINFO environment variable, if any, then looks in -// known installation locations on Unix systems, -// and finally looks in $GOROOT/lib/time/zoneinfo.zip. +// This should be called before package "time" import. +// Please refer to issue: https://github.com/golang/go/issues/34814 func SetTimeZone(zone string) error { location, err := time.LoadLocation(zone) - if err == nil { - time.Local = location + if err != nil { + return err } - return err + return os.Setenv("TZ", location.String()) } // Timestamp retrieves and returns the timestamp in seconds. @@ -192,7 +197,7 @@ func ISO8601() string { return time.Now().Format("2006-01-02T15:04:05-07:00") } -// ISO8601 returns current datetime in RFC822 format like "Mon, 02 Jan 06 15:04 MST". +// RFC822 returns current datetime in RFC822 format like "Mon, 02 Jan 06 15:04 MST". func RFC822() string { return time.Now().Format("Mon, 02 Jan 06 15:04 MST") } @@ -233,6 +238,9 @@ func parseDateStr(s string) (year, month, day int) { // If <format> is not given, it converts string as a "standard" datetime string. // Note that, it fails and returns error if there's no date string in <str>. func StrToTime(str string, format ...string) (*Time, error) { + if str == "" { + return &Time{wrapper{time.Time{}}}, nil + } if len(format) > 0 { return StrToTimeFormat(str, format[0]) } @@ -247,17 +255,33 @@ func StrToTime(str string, format ...string) (*Time, error) { local = time.Local ) if match = timeRegex1.FindStringSubmatch(str); len(match) > 0 && match[1] != "" { - for k, v := range match { - match[k] = strings.TrimSpace(v) - } + //for k, v := range match { + // match[k] = strings.TrimSpace(v) + //} year, month, day = parseDateStr(match[1]) } else if match = timeRegex2.FindStringSubmatch(str); len(match) > 0 && match[1] != "" { - for k, v := range match { - match[k] = strings.TrimSpace(v) - } + //for k, v := range match { + // match[k] = strings.TrimSpace(v) + //} year, month, day = parseDateStr(match[1]) + } else if match = timeRegex3.FindStringSubmatch(str); len(match) > 0 && match[1] != "" { + //for k, v := range match { + // match[k] = strings.TrimSpace(v) + //} + s := strings.Replace(match[2], ":", "", -1) + if len(s) < 6 { + s += strings.Repeat("0", 6-len(s)) + } + hour, _ = strconv.Atoi(match[1]) + min, _ = strconv.Atoi(match[2]) + sec, _ = strconv.Atoi(match[3]) + nsec, _ = strconv.Atoi(match[4]) + for i := 0; i < 9-len(match[4]); i++ { + nsec *= 10 + } + return NewFromTime(time.Date(0, time.Month(1), 1, hour, min, sec, nsec, local)), nil } else { - return nil, errors.New("unsupported time format") + return nil, gerror.NewCode(gcode.CodeInvalidParameter, "unsupported time format") } // Time @@ -292,7 +316,7 @@ func StrToTime(str string, format ...string) (*Time, error) { m, _ := strconv.Atoi(zone[2:4]) s, _ := strconv.Atoi(zone[4:6]) if h > 24 || m > 59 || s > 59 { - return nil, gerror.Newf("invalid zone string: %s", match[6]) + return nil, gerror.NewCodef(gcode.CodeInvalidParameter, "invalid zone string: %s", match[6]) } // Comparing the given time zone whether equals to current time zone, // it converts it to UTC if they does not equal. @@ -330,8 +354,8 @@ func StrToTime(str string, format ...string) (*Time, error) { } } } - if year <= 0 { - return nil, errors.New("invalid time string:" + str) + if month <= 0 || day <= 0 { + return nil, gerror.NewCodef(gcode.CodeInvalidParameter, "invalid time string:%s", str) } return NewFromTime(time.Date(year, time.Month(month), day, hour, min, sec, nsec, local)), nil } @@ -347,7 +371,7 @@ func ConvertZone(strTime string, toZone string, fromZone ...string) (*Time, erro if l, err := time.LoadLocation(fromZone[0]); err != nil { return nil, err } else { - t.Time = time.Date(t.Year(), t.Month(), t.Day(), t.Hour(), t.Minute(), t.Time.Second(), t.Time.Nanosecond(), l) + t.Time = time.Date(t.Year(), time.Month(t.Month()), t.Day(), t.Hour(), t.Minute(), t.Time.Second(), t.Time.Nanosecond(), l) } } if l, err := time.LoadLocation(toZone); err != nil { @@ -387,6 +411,8 @@ func ParseTimeFromContent(content string, format ...string) *Time { return NewFromStr(strings.Trim(match[0], "./_- \n\r")) } else if match := timeRegex2.FindStringSubmatch(content); len(match) >= 1 { return NewFromStr(strings.Trim(match[0], "./_- \n\r")) + } else if match := timeRegex3.FindStringSubmatch(content); len(match) >= 1 { + return NewFromStr(strings.Trim(match[0], "./_- \n\r")) } } return nil diff --git a/os/gtime/gtime_format.go b/os/gtime/gtime_format.go index ada2efd4d..a0c563332 100644 --- a/os/gtime/gtime_format.go +++ b/os/gtime/gtime_format.go @@ -1,4 +1,4 @@ -// Copyright 2018 gf Author(https://github.com/gogf/gf). All Rights Reserved. +// Copyright GoFrame Author(https://goframe.org). All Rights Reserved. // // This Source Code Form is subject to the terms of the MIT License. // If a copy of the MIT was not distributed with this file, @@ -259,17 +259,18 @@ func formatToRegexPattern(format string) string { s := gregex.Quote(formatToStdLayout(format)) s, _ = gregex.ReplaceString(`[0-9]`, `[0-9]`, s) s, _ = gregex.ReplaceString(`[A-Za-z]`, `[A-Za-z]`, s) + s, _ = gregex.ReplaceString(`\s+`, `\s+`, s) return s } // formatMonthDaySuffixMap returns the short english word for current day. func formatMonthDaySuffixMap(day string) string { switch day { - case "01": + case "01", "21", "31": return "st" - case "02": + case "02", "22": return "nd" - case "03": + case "03", "23": return "rd" default: return "th" diff --git a/os/gtime/gtime_sql.go b/os/gtime/gtime_sql.go new file mode 100644 index 000000000..e10822c43 --- /dev/null +++ b/os/gtime/gtime_sql.go @@ -0,0 +1,28 @@ +package gtime + +import ( + "database/sql/driver" +) + +// Scanner is an interface used by Scan in package database/sql for Scanning value +// from database to local golang variable. +func (t *Time) Scan(value interface{}) error { + if t == nil { + return nil + } + newTime := New(value) + *t = *newTime + return nil +} + +// Value is the interface providing the Value method for package database/sql/driver +// for retrieving value from golang variable to database. +func (t *Time) Value() (driver.Value, error) { + if t == nil { + return nil, nil + } + if t.IsZero() { + return nil, nil + } + return t.Time, nil +} diff --git a/os/gtime/gtime_time.go b/os/gtime/gtime_time.go index 66d9d0797..33e0de5e4 100644 --- a/os/gtime/gtime_time.go +++ b/os/gtime/gtime_time.go @@ -1,4 +1,4 @@ -// Copyright 2018 gf Author(https://github.com/gogf/gf). All Rights Reserved. +// Copyright GoFrame Author(https://goframe.org). All Rights Reserved. // // This Source Code Form is subject to the terms of the MIT License. // If a copy of the MIT was not distributed with this file, @@ -8,6 +8,8 @@ package gtime import ( "bytes" + "github.com/gogf/gf/errors/gcode" + "github.com/gogf/gf/errors/gerror" "strconv" "time" ) @@ -31,18 +33,41 @@ func New(param ...interface{}) *Time { return NewFromTime(r) case *time.Time: return NewFromTime(*r) + case Time: return &r + case *Time: return r + case string: + if len(param) > 1 { + switch t := param[1].(type) { + case string: + return NewFromStrFormat(r, t) + case []byte: + return NewFromStrFormat(r, string(t)) + } + } return NewFromStr(r) + case []byte: + if len(param) > 1 { + switch t := param[1].(type) { + case string: + return NewFromStrFormat(string(r), t) + case []byte: + return NewFromStrFormat(string(r), string(t)) + } + } return NewFromStr(string(r)) + case int: return NewFromTimeStamp(int64(r)) + case int64: return NewFromTimeStamp(r) + default: if v, ok := r.(apiUnixNano); ok { return NewFromTimeStamp(v.UnixNano()) @@ -163,6 +188,11 @@ func (t *Time) TimestampNanoStr() string { return strconv.FormatInt(t.TimestampNano(), 10) } +// Month returns the month of the year specified by t. +func (t *Time) Month() int { + return int(t.Time.Month()) +} + // Second returns the second offset within the minute specified by t, // in the range [0, 59]. func (t *Time) Second() int { @@ -198,6 +228,15 @@ func (t *Time) String() string { return t.Format("Y-m-d H:i:s") } +// IsZero reports whether t represents the zero time instant, +// January 1, year 1, 00:00:00 UTC. +func (t *Time) IsZero() bool { + if t == nil { + return true + } + return t.Time.IsZero() +} + // Clone returns a new Time object which is a clone of current time object. func (t *Time) Clone() *Time { return New(t.Time) @@ -219,22 +258,6 @@ func (t *Time) AddStr(duration string) (*Time, error) { } } -// ToLocation converts current time to specified location. -func (t *Time) ToLocation(location *time.Location) *Time { - 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 { - return t.ToLocation(l), nil - } else { - return nil, err - } -} - // UTC converts current time to UTC timezone. func (t *Time) UTC() *Time { newTime := t.Clone() @@ -252,13 +275,6 @@ func (t *Time) RFC822() string { return t.Layout("Mon, 02 Jan 06 15:04 MST") } -// Local converts the time to local timezone. -func (t *Time) Local() *Time { - 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 { newTime := t.Clone() @@ -320,6 +336,113 @@ func (t *Time) Sub(u *Time) time.Duration { return t.Time.Sub(u.Time) } +// StartOfMinute clones and returns a new time of which the seconds is set to 0. +func (t *Time) StartOfMinute() *Time { + newTime := t.Clone() + newTime.Time = newTime.Time.Truncate(time.Minute) + return newTime +} + +// StartOfHour clones and returns a new time of which the hour, minutes and seconds are set to 0. +func (t *Time) StartOfHour() *Time { + y, m, d := t.Date() + newTime := t.Clone() + newTime.Time = time.Date(y, m, d, newTime.Time.Hour(), 0, 0, 0, newTime.Time.Location()) + return newTime +} + +// StartOfDay clones and returns a new time which is the start of day, its time is set to 00:00:00. +func (t *Time) StartOfDay() *Time { + y, m, d := t.Date() + newTime := t.Clone() + newTime.Time = time.Date(y, m, d, 0, 0, 0, 0, newTime.Time.Location()) + return newTime +} + +// StartOfWeek clones and returns a new time which is the first day of week and its time is set to +// 00:00:00. +func (t *Time) StartOfWeek() *Time { + weekday := int(t.Weekday()) + return t.StartOfDay().AddDate(0, 0, -weekday) +} + +// StartOfMonth clones and returns a new time which is the first day of the month and its is set to +// 00:00:00 +func (t *Time) StartOfMonth() *Time { + y, m, _ := t.Date() + newTime := t.Clone() + newTime.Time = time.Date(y, m, 1, 0, 0, 0, 0, newTime.Time.Location()) + return newTime +} + +// StartOfQuarter clones and returns a new time which is the first day of the quarter and its time is set +// to 00:00:00. +func (t *Time) StartOfQuarter() *Time { + month := t.StartOfMonth() + offset := (int(month.Month()) - 1) % 3 + return month.AddDate(0, -offset, 0) +} + +// StartOfHalf clones and returns a new time which is the first day of the half year and its time is set +// to 00:00:00. +func (t *Time) StartOfHalf() *Time { + month := t.StartOfMonth() + offset := (int(month.Month()) - 1) % 6 + return month.AddDate(0, -offset, 0) +} + +// StartOfYear clones and returns a new time which is the first day of the year and its time is set to +// 00:00:00. +func (t *Time) StartOfYear() *Time { + y, _, _ := t.Date() + newTime := t.Clone() + newTime.Time = time.Date(y, time.January, 1, 0, 0, 0, 0, newTime.Time.Location()) + return newTime +} + +// EndOfMinute clones and returns a new time of which the seconds is set to 59. +func (t *Time) EndOfMinute() *Time { + return t.StartOfMinute().Add(time.Minute - time.Nanosecond) +} + +// EndOfHour clones and returns a new time of which the minutes and seconds are both set to 59. +func (t *Time) EndOfHour() *Time { + return t.StartOfHour().Add(time.Hour - time.Nanosecond) +} + +// EndOfDay clones and returns a new time which is the end of day the and its time is set to 23:59:59. +func (t *Time) EndOfDay() *Time { + y, m, d := t.Date() + newTime := t.Clone() + newTime.Time = time.Date(y, m, d, 23, 59, 59, int(time.Second-time.Nanosecond), newTime.Time.Location()) + return newTime +} + +// EndOfWeek clones and returns a new time which is the end of week and its time is set to 23:59:59. +func (t *Time) EndOfWeek() *Time { + return t.StartOfWeek().AddDate(0, 0, 7).Add(-time.Nanosecond) +} + +// EndOfMonth clones and returns a new time which is the end of the month and its time is set to 23:59:59. +func (t *Time) EndOfMonth() *Time { + return t.StartOfMonth().AddDate(0, 1, 0).Add(-time.Nanosecond) +} + +// EndOfQuarter clones and returns a new time which is end of the quarter and its time is set to 23:59:59. +func (t *Time) EndOfQuarter() *Time { + return t.StartOfQuarter().AddDate(0, 3, 0).Add(-time.Nanosecond) +} + +// EndOfHalf clones and returns a new time which is the end of the half year and its time is set to 23:59:59. +func (t *Time) EndOfHalf() *Time { + return t.StartOfHalf().AddDate(0, 6, 0).Add(-time.Nanosecond) +} + +// EndOfYear clones and returns a new time which is the end of the year and its time is set to 23:59:59. +func (t *Time) EndOfYear() *Time { + return t.StartOfYear().AddDate(1, 0, 0).Add(-time.Nanosecond) +} + // MarshalJSON implements the interface MarshalJSON for json.Marshal. func (t *Time) MarshalJSON() ([]byte, error) { return []byte(`"` + t.String() + `"`), nil @@ -338,3 +461,19 @@ func (t *Time) UnmarshalJSON(b []byte) error { t.Time = newTime.Time return nil } + +// UnmarshalText implements the encoding.TextUnmarshaler interface. +// Note that it overwrites the same implementer of `time.Time`. +func (t *Time) UnmarshalText(data []byte) error { + vTime := New(data) + if vTime != nil { + *t = *vTime + return nil + } + return gerror.NewCodef(gcode.CodeInvalidParameter, `invalid time value: %s`, data) +} + +// NoValidation marks this struct object will not be validated by package gvalid. +func (t *Time) NoValidation() { + +} diff --git a/os/gtime/gtime_time_wrapper.go b/os/gtime/gtime_time_wrapper.go index 66c075b73..96c7855e0 100644 --- a/os/gtime/gtime_time_wrapper.go +++ b/os/gtime/gtime_time_wrapper.go @@ -1,4 +1,4 @@ -// Copyright 2019 gf Author(https://github.com/gogf/gf). All Rights Reserved. +// Copyright GoFrame Author(https://goframe.org). All Rights Reserved. // // This Source Code Form is subject to the terms of the MIT License. // If a copy of the MIT was not distributed with this file, diff --git a/os/gtime/gtime_time_zone.go b/os/gtime/gtime_time_zone.go new file mode 100644 index 000000000..8b828bee8 --- /dev/null +++ b/os/gtime/gtime_time_zone.go @@ -0,0 +1,59 @@ +// Copyright GoFrame Author(https://goframe.org). All Rights Reserved. +// +// This Source Code Form is subject to the terms of the MIT License. +// If a copy of the MIT was not distributed with this file, +// You can obtain one at https://github.com/gogf/gf. + +package gtime + +import ( + "sync" + "time" +) + +var ( + // locationMap is time zone name to its location object. + // Time zone name is like: Asia/Shanghai. + locationMap = make(map[string]*time.Location) + + // locationMu is used for concurrent safety for `locationMap`. + locationMu = sync.RWMutex{} +) + +// ToLocation converts current time to specified location. +func (t *Time) ToLocation(location *time.Location) *Time { + 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 location, err := t.getLocationByZoneName(zone); err == nil { + return t.ToLocation(location), nil + } else { + return nil, err + } +} + +func (t *Time) getLocationByZoneName(name string) (location *time.Location, err error) { + locationMu.RLock() + location = locationMap[name] + locationMu.RUnlock() + if location == nil { + location, err = time.LoadLocation(name) + if err == nil && location != nil { + locationMu.Lock() + locationMap[name] = location + locationMu.Unlock() + } + } + return +} + +// Local converts the time to local timezone. +func (t *Time) Local() *Time { + newTime := t.Clone() + newTime.Time = newTime.Time.Local() + return newTime +} diff --git a/os/gtime/gtime_z_bench_test.go b/os/gtime/gtime_z_bench_test.go index 85408cf10..b6be5aead 100644 --- a/os/gtime/gtime_z_bench_test.go +++ b/os/gtime/gtime_z_bench_test.go @@ -1,4 +1,4 @@ -// Copyright 2017 gf Author(https://github.com/gogf/gf). All Rights Reserved. +// Copyright GoFrame Author(https://goframe.org). All Rights Reserved. // // This Source Code Form is subject to the terms of the MIT License. // If a copy of the MIT was not distributed with this file, @@ -8,6 +8,7 @@ package gtime_test import ( "testing" + "time" "github.com/gogf/gf/os/gtime" ) @@ -42,6 +43,18 @@ func Benchmark_StrToTime(b *testing.B) { } } +func Benchmark_StrToTime_Format(b *testing.B) { + for i := 0; i < b.N; i++ { + gtime.StrToTime("2018-02-09 20:46:17.897", "Y-m-d H:i:su") + } +} + +func Benchmark_StrToTime_Layout(b *testing.B) { + for i := 0; i < b.N; i++ { + gtime.StrToTimeLayout("2018-02-09T20:46:17.897Z", time.RFC3339) + } +} + func Benchmark_ParseTimeFromContent(b *testing.B) { for i := 0; i < b.N; i++ { gtime.ParseTimeFromContent("2018-02-09T20:46:17.897Z") diff --git a/os/gtime/gtime_z_unit_basic_test.go b/os/gtime/gtime_z_unit_basic_test.go index d4e0f8fc1..34ef93642 100644 --- a/os/gtime/gtime_z_unit_basic_test.go +++ b/os/gtime/gtime_z_unit_basic_test.go @@ -1,4 +1,4 @@ -// Copyright 2019 gf Author(https://github.com/gogf/gf). All Rights Reserved. +// Copyright GoFrame Author(https://goframe.org). All Rights Reserved. // // This Source Code Form is subject to the terms of the MIT License. // If a copy of the MIT was not distributed with this file, @@ -7,6 +7,7 @@ package gtime_test import ( + "github.com/gogf/gf/frame/g" "testing" "time" @@ -16,8 +17,8 @@ import ( func Test_SetTimeZone(t *testing.T) { gtest.C(t, func(t *gtest.T) { - gtime.SetTimeZone("Asia/Shanghai") - t.Assert(time.Local.String(), "Asia/Shanghai") + t.Assert(gtime.SetTimeZone("Asia/Shanghai"), nil) + //t.Assert(time.Local.String(), "Asia/Shanghai") }) } @@ -86,6 +87,7 @@ func Test_RFC822(t *testing.T) { func Test_StrToTime(t *testing.T) { gtest.C(t, func(t *gtest.T) { + // Correct datetime string. var testDateTimes = []string{ "2006-01-02 15:04:05", "2006/01/02 15:04:05", @@ -103,13 +105,11 @@ func Test_StrToTime(t *testing.T) { for _, item := range testDateTimes { timeTemp, err := gtime.StrToTime(item) - if err != nil { - t.Error("test fail") - } + t.Assert(err, nil) t.Assert(timeTemp.Time.Format("2006-01-02 15:04:05"), "2006-01-02 15:04:05") } - //正常日期列表,时间00:00:00 + // Correct date string,. var testDates = []string{ "2006.01.02", "2006.01.02 00:00", @@ -118,13 +118,25 @@ func Test_StrToTime(t *testing.T) { for _, item := range testDates { timeTemp, err := gtime.StrToTime(item) - if err != nil { - t.Error("test fail") - } + t.Assert(err, nil) t.Assert(timeTemp.Time.Format("2006-01-02 15:04:05"), "2006-01-02 00:00:00") } - //测试格式化formatToStdLayout + // Correct time string. + var testTimes = g.MapStrStr{ + "16:12:01": "15:04:05", + "16:12:01.789": "15:04:05.000", + } + + for k, v := range testTimes { + time1, err := gtime.StrToTime(k) + t.Assert(err, nil) + time2, err := time.ParseInLocation(v, k, time.Local) + t.Assert(err, nil) + t.Assert(time1.Time, time2) + } + + // formatToStdLayout var testDateFormats = []string{ "Y-m-d H:i:s", "\\T\\i\\m\\e Y-m-d H:i:s", @@ -149,7 +161,7 @@ func Test_StrToTime(t *testing.T) { t.Assert(timeTemp.Time.Format("2006-01-02 15:04:05.000"), "2007-01-02 15:04:05.000") } - //异常日期列表 + // 异常日期列表 var testDatesFail = []string{ "2006.01", "06..02", @@ -263,6 +275,11 @@ func Test_ParseTimeFromContent(t *testing.T) { t.Error("test fail") } }) + + gtest.C(t, func(t *gtest.T) { + timeStr := "2021-1-27 9:10:24" + t.Assert(gtime.ParseTimeFromContent(timeStr, "Y-n-d g:i:s").String(), "2021-01-27 09:10:24") + }) } func Test_FuncCost(t *testing.T) { diff --git a/os/gtime/gtime_z_unit_format_test.go b/os/gtime/gtime_z_unit_format_test.go index 80beae8a5..232a72a03 100644 --- a/os/gtime/gtime_z_unit_format_test.go +++ b/os/gtime/gtime_z_unit_format_test.go @@ -1,4 +1,4 @@ -// Copyright 2019 gf Author(https://github.com/gogf/gf). All Rights Reserved. +// Copyright GoFrame Author(https://goframe.org). All Rights Reserved. // // This Source Code Form is subject to the terms of the MIT License. // If a copy of the MIT was not distributed with this file, diff --git a/os/gtime/gtime_z_unit_json_test.go b/os/gtime/gtime_z_unit_json_test.go index 80654db71..781da86d6 100644 --- a/os/gtime/gtime_z_unit_json_test.go +++ b/os/gtime/gtime_z_unit_json_test.go @@ -1,4 +1,4 @@ -// Copyright 2019 gf Author(https://github.com/gogf/gf). All Rights Reserved. +// Copyright GoFrame Author(https://goframe.org). All Rights Reserved. // // This Source Code Form is subject to the terms of the MIT License. // If a copy of the MIT was not distributed with this file, @@ -50,7 +50,7 @@ func Test_Json_Pointer(t *testing.T) { gtest.C(t, func(t *gtest.T) { var t1 gtime.Time s := []byte(`"2006-01-02 15:04:05"`) - err := json.Unmarshal(s, &t1) + err := json.UnmarshalUseNumber(s, &t1) t.Assert(err, nil) t.Assert(t1.String(), "2006-01-02 15:04:05") }) diff --git a/os/gtime/gtime_z_unit_sql_test.go b/os/gtime/gtime_z_unit_sql_test.go new file mode 100644 index 000000000..fa40e71fd --- /dev/null +++ b/os/gtime/gtime_z_unit_sql_test.go @@ -0,0 +1,41 @@ +package gtime_test + +import ( + "github.com/gogf/gf/os/gtime" + "github.com/gogf/gf/test/gtest" + "testing" +) + +func TestTime_Scan(t1 *testing.T) { + gtest.C(t1, func(t *gtest.T) { + tt := gtime.Time{} + //test string + s := gtime.Now().String() + t.Assert(tt.Scan(s), nil) + t.Assert(tt.String(), s) + //test nano + n := gtime.TimestampNano() + t.Assert(tt.Scan(n), nil) + t.Assert(tt.TimestampNano(), n) + //test nil + none := (*gtime.Time)(nil) + t.Assert(none.Scan(nil), nil) + t.Assert(none, nil) + }) + +} + +func TestTime_Value(t1 *testing.T) { + gtest.C(t1, func(t *gtest.T) { + tt := gtime.Now() + s, err := tt.Value() + t.Assert(err, nil) + t.Assert(s, tt.Time) + //test nil + none := (*gtime.Time)(nil) + s, err = none.Value() + t.Assert(err, nil) + t.Assert(s, nil) + + }) +} diff --git a/os/gtime/gtime_z_unit_time_test.go b/os/gtime/gtime_z_unit_time_test.go index edfa6530e..9dbd6f97a 100644 --- a/os/gtime/gtime_z_unit_time_test.go +++ b/os/gtime/gtime_z_unit_time_test.go @@ -1,4 +1,4 @@ -// Copyright 2019 gf Author(https://github.com/gogf/gf). All Rights Reserved. +// Copyright GoFrame Author(https://goframe.org). All Rights Reserved. // // This Source Code Form is subject to the terms of the MIT License. // If a copy of the MIT was not distributed with this file, @@ -42,6 +42,12 @@ func Test_New(t *testing.T) { timeTemp := gtime.New(timeNow.TimestampMicro()) t.Assert(timeTemp.Time.Format("2006-01-02 15:04:05"), timeNow.Time.Format("2006-01-02 15:04:05")) }) + // short datetime. + gtest.C(t, func(t *gtest.T) { + timeTemp := gtime.New("2021-2-9 08:01:21") + t.Assert(timeTemp.Format("Y-m-d H:i:s"), "2021-02-09 08:01:21") + t.Assert(timeTemp.Time.Format("2006-01-02 15:04:05"), "2021-02-09 08:01:21") + }) } func Test_Nil(t *testing.T) { @@ -256,3 +262,131 @@ func Test_Truncate(t *testing.T) { t.Assert(timeTemp.UnixNano(), timeTemp1.Truncate(time.Hour).UnixNano()) }) } + +func Test_StartOfMinute(t *testing.T) { + gtest.C(t, func(t *gtest.T) { + timeTemp := gtime.NewFromStr("2020-12-12 18:24:06") + timeTemp1 := timeTemp.StartOfMinute() + t.Assert(timeTemp1.Format("Y-m-d H:i:s"), "2020-12-12 18:24:00") + }) +} + +func Test_EndOfMinute(t *testing.T) { + gtest.C(t, func(t *gtest.T) { + timeTemp := gtime.NewFromStr("2020-12-12 18:24:06") + timeTemp1 := timeTemp.EndOfMinute() + t.Assert(timeTemp1.Format("Y-m-d H:i:s"), "2020-12-12 18:24:59") + }) +} + +func Test_StartOfHour(t *testing.T) { + gtest.C(t, func(t *gtest.T) { + timeTemp := gtime.NewFromStr("2020-12-12 18:24:06") + timeTemp1 := timeTemp.StartOfHour() + t.Assert(timeTemp1.Format("Y-m-d H:i:s"), "2020-12-12 18:00:00") + }) +} + +func Test_EndOfHour(t *testing.T) { + gtest.C(t, func(t *gtest.T) { + timeTemp := gtime.NewFromStr("2020-12-12 18:24:06") + timeTemp1 := timeTemp.EndOfHour() + t.Assert(timeTemp1.Format("Y-m-d H:i:s"), "2020-12-12 18:59:59") + }) +} + +func Test_StartOfDay(t *testing.T) { + gtest.C(t, func(t *gtest.T) { + timeTemp := gtime.NewFromStr("2020-12-12 18:24:06") + timeTemp1 := timeTemp.StartOfDay() + t.Assert(timeTemp1.Format("Y-m-d H:i:s"), "2020-12-12 00:00:00") + }) +} + +func Test_EndOfDay(t *testing.T) { + gtest.C(t, func(t *gtest.T) { + timeTemp := gtime.NewFromStr("2020-12-12 18:24:06") + timeTemp1 := timeTemp.EndOfDay() + t.Assert(timeTemp1.Format("Y-m-d H:i:s"), "2020-12-12 23:59:59") + }) +} + +func Test_StartOfWeek(t *testing.T) { + gtest.C(t, func(t *gtest.T) { + timeTemp := gtime.NewFromStr("2020-12-12 18:24:06") + timeTemp1 := timeTemp.StartOfWeek() + t.Assert(timeTemp1.Format("Y-m-d H:i:s"), "2020-12-06 00:00:00") + }) +} + +func Test_EndOfWeek(t *testing.T) { + gtest.C(t, func(t *gtest.T) { + timeTemp := gtime.NewFromStr("2020-12-12 18:24:06") + timeTemp1 := timeTemp.EndOfWeek() + t.Assert(timeTemp1.Format("Y-m-d H:i:s"), "2020-12-12 23:59:59") + }) +} + +func Test_StartOfMonth(t *testing.T) { + gtest.C(t, func(t *gtest.T) { + timeTemp := gtime.NewFromStr("2020-12-12 18:24:06") + timeTemp1 := timeTemp.StartOfMonth() + t.Assert(timeTemp1.Format("Y-m-d H:i:s"), "2020-12-01 00:00:00") + }) +} + +func Test_EndOfMonth(t *testing.T) { + gtest.C(t, func(t *gtest.T) { + timeTemp := gtime.NewFromStr("2020-12-12 18:24:06") + timeTemp1 := timeTemp.EndOfMonth() + t.Assert(timeTemp1.Format("Y-m-d H:i:s"), "2020-12-31 23:59:59") + }) +} + +func Test_StartOfQuarter(t *testing.T) { + gtest.C(t, func(t *gtest.T) { + timeTemp := gtime.NewFromStr("2020-12-06 18:24:06") + timeTemp1 := timeTemp.StartOfQuarter() + t.Assert(timeTemp1.Format("Y-m-d H:i:s"), "2020-10-01 00:00:00") + }) +} + +func Test_EndOfQuarter(t *testing.T) { + gtest.C(t, func(t *gtest.T) { + timeTemp := gtime.NewFromStr("2020-12-06 18:24:06") + timeTemp1 := timeTemp.EndOfQuarter() + t.Assert(timeTemp1.Format("Y-m-d H:i:s"), "2020-12-31 23:59:59") + }) +} + +func Test_StartOfHalf(t *testing.T) { + gtest.C(t, func(t *gtest.T) { + timeTemp := gtime.NewFromStr("2020-12-06 18:24:06") + timeTemp1 := timeTemp.StartOfHalf() + t.Assert(timeTemp1.Format("Y-m-d H:i:s"), "2020-07-01 00:00:00") + }) +} + +func Test_EndOfHalf(t *testing.T) { + gtest.C(t, func(t *gtest.T) { + timeTemp := gtime.NewFromStr("2020-12-06 18:24:06") + timeTemp1 := timeTemp.EndOfHalf() + t.Assert(timeTemp1.Format("Y-m-d H:i:s"), "2020-12-31 23:59:59") + }) +} + +func Test_StartOfYear(t *testing.T) { + gtest.C(t, func(t *gtest.T) { + timeTemp := gtime.NewFromStr("2020-12-06 18:24:06") + timeTemp1 := timeTemp.StartOfYear() + t.Assert(timeTemp1.Format("Y-m-d H:i:s"), "2020-01-01 00:00:00") + }) +} + +func Test_EndOfYear(t *testing.T) { + gtest.C(t, func(t *gtest.T) { + timeTemp := gtime.NewFromStr("2020-12-06 18:24:06") + timeTemp1 := timeTemp.EndOfYear() + t.Assert(timeTemp1.Format("Y-m-d H:i:s"), "2020-12-31 23:59:59") + }) +} diff --git a/os/gtimer/gtimer.go b/os/gtimer/gtimer.go index 97ff3782b..3491f6567 100644 --- a/os/gtimer/gtimer.go +++ b/os/gtimer/gtimer.go @@ -1,11 +1,10 @@ -// Copyright GoFrame Author(https://github.com/gogf/gf). All Rights Reserved. +// Copyright GoFrame Author(https://goframe.org). All Rights Reserved. // // This Source Code Form is subject to the terms of the MIT License. // If a copy of the MIT was not distributed with this file, // You can obtain one at https://github.com/gogf/gf. -// Package gtimer implements Hierarchical Timing Wheel for interval/delayed jobs -// running and management. +// Package gtimer implements timer for interval/delayed jobs running and management. // // This package is designed for management for millions of timing jobs. The differences // between gtimer and gcron are as follows: @@ -20,34 +19,49 @@ package gtimer import ( - "fmt" - "math" + "github.com/gogf/gf/container/gtype" + "sync" "time" "github.com/gogf/gf/os/gcmd" ) +// Timer is the timer manager, which uses ticks to calculate the timing interval. +type Timer struct { + mu sync.RWMutex + queue *priorityQueue // queue is a priority queue based on heap structure. + status *gtype.Int // status is the current timer status. + ticks *gtype.Int64 // ticks is the proceeded interval number by the timer. + options TimerOptions // timer options is used for timer configuration. +} + +// TimerOptions is the configuration object for Timer. +type TimerOptions struct { + Interval time.Duration // Interval is the interval escaped of the timer. +} + const ( - StatusReady = 0 // Job is ready for running. - StatusRunning = 1 // Job is already running. - StatusStopped = 2 // Job is stopped. - StatusReset = 3 // Job is reset. - StatusClosed = -1 // Job is closed and waiting to be deleted. - panicExit = "exit" // Internal usage for custom job exit function with panic. - defaultTimes = math.MaxInt32 // Default limit running times, a big number. - defaultSlotNumber = 10 // Default slot number. - defaultWheelInterval = 50 // Default wheel interval. - defaultWheelLevel = 5 // Default wheel level. - cmdEnvKey = "gf.gtimer" // Configuration key for command argument or environment. + StatusReady = 0 // Job or Timer is ready for running. + StatusRunning = 1 // Job or Timer is already running. + StatusStopped = 2 // Job or Timer is stopped. + StatusClosed = -1 // Job or Timer is closed and waiting to be deleted. + panicExit = "exit" // panicExit is used for custom job exit with panic. + defaultTimerInterval = 100 // defaultTimerInterval is the default timer interval in milliseconds. + commandEnvKeyForInterval = "gf.gtimer.interval" // commandEnvKeyForInterval is the key for command argument or environment configuring default interval duration for timer. ) var ( - defaultSlots = gcmd.GetWithEnv(fmt.Sprintf("%s.slots", cmdEnvKey), defaultSlotNumber).Int() - defaultLevel = gcmd.GetWithEnv(fmt.Sprintf("%s.level", cmdEnvKey), defaultWheelLevel).Int() - defaultInterval = gcmd.GetWithEnv(fmt.Sprintf("%s.interval", cmdEnvKey), defaultWheelInterval).Duration() * time.Millisecond - defaultTimer = New(defaultSlots, defaultInterval, defaultLevel) + defaultTimer = New() + defaultInterval = gcmd.GetOptWithEnv(commandEnvKeyForInterval, defaultTimerInterval).Duration() * time.Millisecond ) +// DefaultOptions creates and returns a default options object for Timer creation. +func DefaultOptions() TimerOptions { + return TimerOptions{ + Interval: defaultInterval, + } +} + // SetTimeout runs the job once after duration of <delay>. // It is like the one in javascript. func SetTimeout(delay time.Duration, job JobFunc) { diff --git a/os/gtimer/gtimer_entry.go b/os/gtimer/gtimer_entry.go index 0f228ebbf..3a49e3819 100644 --- a/os/gtimer/gtimer_entry.go +++ b/os/gtimer/gtimer_entry.go @@ -1,4 +1,4 @@ -// Copyright GoFrame Author(https://github.com/gogf/gf). All Rights Reserved. +// Copyright GoFrame Author(https://goframe.org). All Rights Reserved. // // This Source Code Form is subject to the terms of the MIT License. // If a copy of the MIT was not distributed with this file, @@ -7,90 +7,85 @@ package gtimer import ( - "time" - "github.com/gogf/gf/container/gtype" ) -// Entry is the timing job entry to wheel. +// Entry is the timing job. type Entry struct { - wheel *wheel // Belonged wheel. - job JobFunc // The job function. - singleton *gtype.Bool // Singleton mode. - status *gtype.Int // Job status. - times *gtype.Int // Limit running times. - create int64 // Timer ticks when the job installed. - interval int64 // The interval ticks of the job. - createMs int64 // The timestamp in milliseconds when job installed. - intervalMs int64 // The interval milliseconds of the job. - rawIntervalMs int64 // Raw input interval in milliseconds. + job JobFunc // The job function. + timer *Timer // Belonged timer. + ticks int64 // The job runs every tick. + times *gtype.Int // Limit running times. + status *gtype.Int // Job status. + singleton *gtype.Bool // Singleton mode. + nextTicks *gtype.Int64 // Next run ticks of the job. + infinite *gtype.Bool // No times limit. } // JobFunc is the job function. type JobFunc = func() -// addEntry adds a timing job to the wheel. -func (w *wheel) addEntry(interval time.Duration, job JobFunc, singleton bool, times int, status int) *Entry { - if times <= 0 { - times = defaultTimes - } - var ( - ms = interval.Nanoseconds() / 1e6 - num = ms / w.intervalMs - ) - if num == 0 { - // If the given interval is lesser than the one of the wheel, - // then sets it to one tick, which means it will be run in one interval. - num = 1 - } - nowMs := time.Now().UnixNano() / 1e6 - ticks := w.ticks.Val() - entry := &Entry{ - wheel: w, - job: job, - times: gtype.NewInt(times), - status: gtype.NewInt(status), - create: ticks, - interval: num, - singleton: gtype.NewBool(singleton), - createMs: nowMs, - intervalMs: ms, - rawIntervalMs: ms, - } - // Install the job to the list of the slot. - w.slots[(ticks+num)%w.number].PushBack(entry) - return entry -} - -// addEntryByParent adds a timing job with parent entry. -func (w *wheel) addEntryByParent(interval int64, parent *Entry) *Entry { - num := interval / w.intervalMs - if num == 0 { - num = 1 - } - nowMs := time.Now().UnixNano() / 1e6 - ticks := w.ticks.Val() - entry := &Entry{ - wheel: w, - job: parent.job, - times: parent.times, - status: parent.status, - create: ticks, - interval: num, - singleton: parent.singleton, - createMs: nowMs, - intervalMs: interval, - rawIntervalMs: parent.rawIntervalMs, - } - w.slots[(ticks+num)%w.number].PushBack(entry) - return entry -} - // Status returns the status of the job. func (entry *Entry) Status() int { return entry.status.Val() } +// Run runs the timer job asynchronously. +func (entry *Entry) Run() { + if !entry.infinite.Val() { + leftRunningTimes := entry.times.Add(-1) + // It checks its running times exceeding. + if leftRunningTimes < 0 { + entry.status.Set(StatusClosed) + return + } + } + go func() { + defer func() { + if err := recover(); err != nil { + if err != panicExit { + panic(err) + } else { + entry.Close() + return + } + } + if entry.Status() == StatusRunning { + entry.SetStatus(StatusReady) + } + }() + entry.job() + }() +} + +// doCheckAndRunByTicks checks the if job can run in given timer ticks, +// it runs asynchronously if the given `currentTimerTicks` meets or else +// it increments its ticks and waits for next running check. +func (entry *Entry) doCheckAndRunByTicks(currentTimerTicks int64) { + // Ticks check. + if currentTimerTicks < entry.nextTicks.Val() { + return + } + entry.nextTicks.Set(currentTimerTicks + entry.ticks) + // Perform job checking. + switch entry.status.Val() { + case StatusRunning: + if entry.IsSingleton() { + return + } + case StatusReady: + if !entry.status.Cas(StatusReady, StatusRunning) { + return + } + case StatusStopped: + return + case StatusClosed: + return + } + // Perform job running. + entry.Run() +} + // SetStatus custom sets the status for the job. func (entry *Entry) SetStatus(status int) int { return entry.status.Set(status) @@ -106,16 +101,16 @@ func (entry *Entry) Stop() { entry.status.Set(StatusStopped) } -//Reset reset the job. -func (entry *Entry) Reset() { - entry.status.Set(StatusReset) -} - // Close closes the job, and then it will be removed from the timer. func (entry *Entry) Close() { entry.status.Set(StatusClosed) } +// Reset resets the job, which resets its ticks for next running. +func (entry *Entry) Reset() { + entry.nextTicks.Set(entry.timer.ticks.Val() + entry.ticks) +} + // IsSingleton checks and returns whether the job in singleton mode. func (entry *Entry) IsSingleton() bool { return entry.singleton.Val() @@ -126,69 +121,13 @@ func (entry *Entry) SetSingleton(enabled bool) { entry.singleton.Set(enabled) } +// Job returns the job function of this job. +func (entry *Entry) Job() JobFunc { + return entry.job +} + // SetTimes sets the limit running times for the job. func (entry *Entry) SetTimes(times int) { entry.times.Set(times) -} - -// Run runs the job. -func (entry *Entry) Run() { - entry.job() -} - -// check checks if the job should be run in given ticks and timestamp milliseconds. -func (entry *Entry) check(nowTicks int64, nowMs int64) (runnable, addable bool) { - switch entry.status.Val() { - case StatusStopped: - return false, true - case StatusClosed: - return false, false - case StatusReset: - return false, true - } - // Firstly checks using the ticks, this may be low precision as one tick is a little bit long. - if diff := nowTicks - entry.create; diff > 0 && diff%entry.interval == 0 { - // If not the lowest level wheel. - if entry.wheel.level > 0 { - diffMs := nowMs - entry.createMs - switch { - // Add it to the next slot, which means it will run on next interval. - case diffMs < entry.wheel.timer.intervalMs: - entry.wheel.slots[(nowTicks+entry.interval)%entry.wheel.number].PushBack(entry) - return false, false - - // Normal rolls on the job. - case diffMs >= entry.wheel.timer.intervalMs: - // Calculate the leftover milliseconds, - // if it is greater than the minimum interval, then re-install it. - if leftMs := entry.intervalMs - diffMs; leftMs > entry.wheel.timer.intervalMs { - // Re-calculate and re-installs the job proper slot. - entry.wheel.timer.doAddEntryByParent(leftMs, entry) - return false, false - } - } - } - // Singleton mode check. - if entry.IsSingleton() { - // Note that it is atomic operation to ensure concurrent safety. - if entry.status.Set(StatusRunning) == StatusRunning { - return false, true - } - } - // Limit running times. - times := entry.times.Add(-1) - if times <= 0 { - // Note that it is atomic operation to ensure concurrent safety. - if entry.status.Set(StatusClosed) == StatusClosed || times < 0 { - return false, false - } - } - // This means it does not limit the running times. - // I know it's ugly, but it is surely high performance for running times limit. - if times < 2000000000 && times > 1000000000 { - entry.times.Set(defaultTimes) - } - return true, true - } - return false, true + entry.infinite.Set(false) } diff --git a/os/gtimer/gtimer_loop.go b/os/gtimer/gtimer_loop.go deleted file mode 100644 index c38c21c50..000000000 --- a/os/gtimer/gtimer_loop.go +++ /dev/null @@ -1,88 +0,0 @@ -// Copyright GoFrame 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 gtimer - -import ( - "time" - - "github.com/gogf/gf/container/glist" -) - -// start starts the ticker using a standalone goroutine. -func (w *wheel) start() { - go func() { - ticker := time.NewTicker(time.Duration(w.intervalMs) * time.Millisecond) - for { - select { - case <-ticker.C: - switch w.timer.status.Val() { - case StatusRunning: - w.proceed() - - case StatusStopped: - // Do nothing. - - case StatusClosed: - ticker.Stop() - return - } - - } - } - }() -} - -// proceed checks and rolls on the job. -// If a timing job is time for running, it runs in an asynchronous goroutine, -// or else it removes from current slot and re-installs the job to another wheel and slot -// according to its leftover interval in milliseconds. -func (w *wheel) proceed() { - n := w.ticks.Add(1) - l := w.slots[int(n%w.number)] - length := l.Len() - if length > 0 { - go func(l *glist.List, nowTicks int64) { - entry := (*Entry)(nil) - nowMs := time.Now().UnixNano() / 1e6 - for i := length; i > 0; i-- { - if v := l.PopFront(); v == nil { - break - } else { - entry = v.(*Entry) - } - // Checks whether the time for running. - runnable, addable := entry.check(nowTicks, nowMs) - if runnable { - // Just run it in another goroutine. - go func(entry *Entry) { - defer func() { - if err := recover(); err != nil { - if err != panicExit { - panic(err) - } else { - entry.Close() - } - } - if entry.Status() == StatusRunning { - entry.SetStatus(StatusReady) - } - }() - entry.job() - }(entry) - } - // If rolls on the job. - if addable { - //If StatusReset , reset to runnable state. - if entry.Status() == StatusReset { - entry.SetStatus(StatusReady) - } - entry.wheel.timer.doAddEntryByParent(entry.rawIntervalMs, entry) - } - } - }(l, n) - } -} diff --git a/os/gtimer/gtimer_queue.go b/os/gtimer/gtimer_queue.go new file mode 100644 index 000000000..54443bdf9 --- /dev/null +++ b/os/gtimer/gtimer_queue.go @@ -0,0 +1,83 @@ +// Copyright GoFrame Author(https://goframe.org). All Rights Reserved. +// +// This Source Code Form is subject to the terms of the MIT License. +// If a copy of the MIT was not distributed with this file, +// You can obtain one at https://github.com/gogf/gf. + +package gtimer + +import ( + "container/heap" + "github.com/gogf/gf/container/gtype" + "math" + "sync" +) + +// priorityQueue is an abstract data type similar to a regular queue or stack data structure in which +// each element additionally has a "priority" associated with it. In a priority queue, an element with +// high priority is served before an element with low priority. +// priorityQueue is based on heap structure. +type priorityQueue struct { + mu sync.Mutex + heap *priorityQueueHeap // the underlying queue items manager using heap. + nextPriority *gtype.Int64 // nextPriority stores the next priority value of the heap, which is used to check if necessary to call the Pop of heap by Timer. +} + +// priorityQueueHeap is a heap manager, of which the underlying `array` is a array implementing a heap structure. +type priorityQueueHeap struct { + array []priorityQueueItem +} + +// priorityQueueItem stores the queue item which has a `priority` attribute to sort itself in heap. +type priorityQueueItem struct { + value interface{} + priority int64 +} + +// newPriorityQueue creates and returns a priority queue. +func newPriorityQueue() *priorityQueue { + queue := &priorityQueue{ + heap: &priorityQueueHeap{array: make([]priorityQueueItem, 0)}, + nextPriority: gtype.NewInt64(math.MaxInt64), + } + heap.Init(queue.heap) + return queue +} + +// NextPriority retrieves and returns the minimum and the most priority value of the queue. +func (q *priorityQueue) NextPriority() int64 { + return q.nextPriority.Val() +} + +// Push pushes a value to the queue. +// The `priority` specifies the priority of the value. +// The lesser the `priority` value the higher priority of the `value`. +func (q *priorityQueue) Push(value interface{}, priority int64) { + q.mu.Lock() + defer q.mu.Unlock() + heap.Push(q.heap, priorityQueueItem{ + value: value, + priority: priority, + }) + // Update the minimum priority using atomic operation. + nextPriority := q.nextPriority.Val() + if priority >= nextPriority { + return + } + q.nextPriority.Set(priority) +} + +// Pop retrieves, removes and returns the most high priority value from the queue. +func (q *priorityQueue) Pop() interface{} { + q.mu.Lock() + defer q.mu.Unlock() + if v := heap.Pop(q.heap); v != nil { + var nextPriority int64 = math.MaxInt64 + if len(q.heap.array) > 0 { + nextPriority = q.heap.array[0].priority + } + q.nextPriority.Set(nextPriority) + return v.(priorityQueueItem).value + } + return nil +} diff --git a/os/gtimer/gtimer_queue_heap.go b/os/gtimer/gtimer_queue_heap.go new file mode 100644 index 000000000..c4b2f5dbe --- /dev/null +++ b/os/gtimer/gtimer_queue_heap.go @@ -0,0 +1,42 @@ +// Copyright GoFrame Author(https://goframe.org). All Rights Reserved. +// +// This Source Code Form is subject to the terms of the MIT License. +// If a copy of the MIT was not distributed with this file, +// You can obtain one at https://github.com/gogf/gf. + +package gtimer + +// Len is used to implement the interface of sort.Interface. +func (h *priorityQueueHeap) Len() int { + return len(h.array) +} + +// Less is used to implement the interface of sort.Interface. +// The least one is placed to the top of the heap. +func (h *priorityQueueHeap) Less(i, j int) bool { + return h.array[i].priority < h.array[j].priority +} + +// Swap is used to implement the interface of sort.Interface. +func (h *priorityQueueHeap) Swap(i, j int) { + if len(h.array) == 0 { + return + } + h.array[i], h.array[j] = h.array[j], h.array[i] +} + +// Push pushes an item to the heap. +func (h *priorityQueueHeap) Push(x interface{}) { + h.array = append(h.array, x.(priorityQueueItem)) +} + +// Pop retrieves, removes and returns the most high priority item from the heap. +func (h *priorityQueueHeap) Pop() interface{} { + length := len(h.array) + if length == 0 { + return nil + } + item := h.array[length-1] + h.array = h.array[0 : length-1] + return item +} diff --git a/os/gtimer/gtimer_timer.go b/os/gtimer/gtimer_timer.go index 115097c4c..2c43ba8fd 100644 --- a/os/gtimer/gtimer_timer.go +++ b/os/gtimer/gtimer_timer.go @@ -1,4 +1,4 @@ -// Copyright GoFrame Author(https://github.com/gogf/gf). All Rights Reserved. +// Copyright GoFrame Author(https://goframe.org). All Rights Reserved. // // This Source Code Form is subject to the terms of the MIT License. // If a copy of the MIT was not distributed with this file, @@ -7,91 +7,28 @@ package gtimer import ( - "fmt" - "time" - - "github.com/gogf/gf/container/glist" "github.com/gogf/gf/container/gtype" + "time" ) -// Timer is a Hierarchical Timing Wheel manager for timing jobs. -type Timer struct { - status *gtype.Int // Timer status. - wheels []*wheel // The underlying wheels. - length int // Max level of the wheels. - number int // Slot Number of each wheel. - intervalMs int64 // Interval of the slot in milliseconds. -} - -// Wheel is a slot wrapper for timing job install and uninstall. -type wheel struct { - timer *Timer // Belonged timer. - level int // The level in the timer. - slots []*glist.List // Slot array. - number int64 // Slot Number=len(slots). - ticks *gtype.Int64 // Ticked count of the wheel, one tick is one of its interval passed. - totalMs int64 // Total duration in milliseconds=number*interval. - createMs int64 // Created timestamp in milliseconds. - intervalMs int64 // Interval in milliseconds, which is the duration of one slot. -} - -// New creates and returns a Hierarchical Timing Wheel designed timer. -// The parameter <interval> specifies the interval of the timer. -// The optional parameter <level> specifies the wheels count of the timer, -// which is defaultWheelLevel in default. -func New(slot int, interval time.Duration, level ...int) *Timer { - if slot <= 0 { - panic(fmt.Sprintf("invalid slot number: %d", slot)) - } - length := defaultWheelLevel - if len(level) > 0 { - length = level[0] - } +func New(options ...TimerOptions) *Timer { t := &Timer{ - status: gtype.NewInt(StatusRunning), - wheels: make([]*wheel, length), - length: length, - number: slot, - intervalMs: interval.Nanoseconds() / 1e6, + queue: newPriorityQueue(), + status: gtype.NewInt(StatusRunning), + ticks: gtype.NewInt64(), } - for i := 0; i < length; i++ { - if i > 0 { - n := time.Duration(t.wheels[i-1].totalMs) * time.Millisecond - if n <= 0 { - panic(fmt.Sprintf(`inteval is too large with level: %dms x %d`, interval, length)) - } - w := t.newWheel(i, slot, n) - t.wheels[i] = w - t.wheels[i-1].addEntry(n, w.proceed, false, defaultTimes, StatusReady) - } else { - t.wheels[i] = t.newWheel(i, slot, interval) - } + if len(options) > 0 { + t.options = options[0] + } else { + t.options = DefaultOptions() } - t.wheels[0].start() + go t.loop() return t } -// newWheel creates and returns a single wheel. -func (t *Timer) newWheel(level int, slot int, interval time.Duration) *wheel { - w := &wheel{ - timer: t, - level: level, - slots: make([]*glist.List, slot), - number: int64(slot), - ticks: gtype.NewInt64(), - totalMs: int64(slot) * interval.Nanoseconds() / 1e6, - createMs: time.Now().UnixNano() / 1e6, - intervalMs: interval.Nanoseconds() / 1e6, - } - for i := int64(0); i < w.number; i++ { - w.slots[i] = glist.New(true) - } - return w -} - // Add adds a timing job to the timer, which runs in interval of <interval>. func (t *Timer) Add(interval time.Duration, job JobFunc) *Entry { - return t.doAddEntry(interval, job, false, defaultTimes, StatusReady) + return t.createEntry(interval, job, false, -1, StatusReady) } // AddEntry adds a timing job to the timer with detailed parameters. @@ -106,22 +43,22 @@ func (t *Timer) Add(interval time.Duration, job JobFunc) *Entry { // // The parameter <status> specifies the job status when it's firstly added to the timer. func (t *Timer) AddEntry(interval time.Duration, job JobFunc, singleton bool, times int, status int) *Entry { - return t.doAddEntry(interval, job, singleton, times, status) + return t.createEntry(interval, job, singleton, times, status) } // AddSingleton is a convenience function for add singleton mode job. func (t *Timer) AddSingleton(interval time.Duration, job JobFunc) *Entry { - return t.doAddEntry(interval, job, true, defaultTimes, StatusReady) + return t.createEntry(interval, job, true, -1, StatusReady) } // AddOnce is a convenience function for adding a job which only runs once and then exits. func (t *Timer) AddOnce(interval time.Duration, job JobFunc) *Entry { - return t.doAddEntry(interval, job, true, 1, StatusReady) + return t.createEntry(interval, job, true, 1, StatusReady) } // AddTimes is a convenience function for adding a job which is limited running times. func (t *Timer) AddTimes(interval time.Duration, times int, job JobFunc) *Entry { - return t.doAddEntry(interval, job, true, times, StatusReady) + return t.createEntry(interval, job, true, times, StatusReady) } // DelayAdd adds a timing job after delay of <interval> duration. @@ -179,77 +116,35 @@ func (t *Timer) Close() { t.status.Set(StatusClosed) } -// doAddEntry adds a timing job to timer for internal usage. -func (t *Timer) doAddEntry(interval time.Duration, job JobFunc, singleton bool, times int, status int) *Entry { - return t.wheels[t.getLevelByIntervalMs(interval.Nanoseconds()/1e6)].addEntry(interval, job, singleton, times, status) -} - -// doAddEntryByParent adds a timing job to timer with parent entry for internal usage. -func (t *Timer) doAddEntryByParent(interval int64, parent *Entry) *Entry { - return t.wheels[t.getLevelByIntervalMs(interval)].addEntryByParent(interval, parent) -} - -// getLevelByIntervalMs calculates and returns the level of timer wheel with given milliseconds. -func (t *Timer) getLevelByIntervalMs(intervalMs int64) int { - pos, cmp := t.binSearchIndex(intervalMs) - switch cmp { - // If equals to the last comparison value, do not add it directly to this wheel, - // but loop and continue comparison from the index to the first level, - // and add it to the proper level wheel. - case 0: - fallthrough - // If lesser than the last comparison value, - // loop and continue comparison from the index to the first level, - // and add it to the proper level wheel. - case -1: - i := pos - for ; i > 0; i-- { - if intervalMs > t.wheels[i].intervalMs && intervalMs <= t.wheels[i].totalMs { - return i - } - } - return i - - // If greater than the last comparison value, - // loop and continue comparison from the index to the last level, - // and add it to the proper level wheel. - case 1: - i := pos - for ; i < t.length-1; i++ { - if intervalMs > t.wheels[i].intervalMs && intervalMs <= t.wheels[i].totalMs { - return i - } - } - return i +// createEntry creates and adds a timing job to the timer. +func (t *Timer) createEntry(interval time.Duration, job JobFunc, singleton bool, times int, status int) *Entry { + var ( + infinite = false + ) + if times <= 0 { + infinite = true } - return 0 -} - -// binSearchIndex uses binary search algorithm for finding the possible level of the wheel -// for the interval value. -func (t *Timer) binSearchIndex(n int64) (index int, result int) { - min := 0 - max := t.length - 1 - mid := 0 - cmp := -2 - for min <= max { - mid = min + int((max-min)/2) - switch { - case t.wheels[mid].intervalMs == n: - cmp = 0 - case t.wheels[mid].intervalMs > n: - cmp = -1 - case t.wheels[mid].intervalMs < n: - cmp = 1 - } - switch cmp { - case -1: - max = mid - 1 - case 1: - min = mid + 1 - case 0: - return mid, cmp - } + var ( + intervalTicksOfJob = int64(interval / t.options.Interval) + ) + if intervalTicksOfJob == 0 { + // If the given interval is lesser than the one of the wheel, + // then sets it to one tick, which means it will be run in one interval. + intervalTicksOfJob = 1 } - return mid, cmp + var ( + nextTicks = t.ticks.Val() + intervalTicksOfJob + entry = &Entry{ + job: job, + timer: t, + ticks: intervalTicksOfJob, + times: gtype.NewInt(times), + status: gtype.NewInt(status), + singleton: gtype.NewBool(singleton), + nextTicks: gtype.NewInt64(nextTicks), + infinite: gtype.NewBool(infinite), + } + ) + t.queue.Push(entry, nextTicks) + return entry } diff --git a/os/gtimer/gtimer_timer_loop.go b/os/gtimer/gtimer_timer_loop.go new file mode 100644 index 000000000..ae94bd311 --- /dev/null +++ b/os/gtimer/gtimer_timer_loop.go @@ -0,0 +1,67 @@ +// Copyright GoFrame Author(https://goframe.org). All Rights Reserved. +// +// This Source Code Form is subject to the terms of the MIT License. +// If a copy of the MIT was not distributed with this file, +// You can obtain one at https://github.com/gogf/gf. + +package gtimer + +import "time" + +// loop starts the ticker using a standalone goroutine. +func (t *Timer) loop() { + go func() { + var ( + currentTimerTicks int64 + timerIntervalTicker = time.NewTicker(t.options.Interval) + ) + defer timerIntervalTicker.Stop() + for { + select { + case <-timerIntervalTicker.C: + // Check the timer status. + switch t.status.Val() { + case StatusRunning: + // Timer proceeding. + if currentTimerTicks = t.ticks.Add(1); currentTimerTicks >= t.queue.NextPriority() { + t.proceed(currentTimerTicks) + } + + case StatusStopped: + // Do nothing. + + case StatusClosed: + // Timer exits. + return + } + } + } + }() +} + +// proceed function proceeds the timer job checking and running logic. +func (t *Timer) proceed(currentTimerTicks int64) { + var ( + value interface{} + ) + for { + value = t.queue.Pop() + if value == nil { + break + } + entry := value.(*Entry) + // It checks if it meets the ticks' requirement. + if jobNextTicks := entry.nextTicks.Val(); currentTimerTicks < jobNextTicks { + // It pushes the job back if current ticks does not meet its running ticks requirement. + t.queue.Push(entry, entry.nextTicks.Val()) + break + } + // It checks the job running requirements and then does asynchronous running. + entry.doCheckAndRunByTicks(currentTimerTicks) + // Status check: push back or ignore it. + if entry.Status() != StatusClosed { + // It pushes the job back to queue for next running. + t.queue.Push(entry, entry.nextTicks.Val()) + } + } +} diff --git a/os/gtimer/gtimer_z_bench_test.go b/os/gtimer/gtimer_z_bench_test.go index b114ad55a..139bf1c4a 100644 --- a/os/gtimer/gtimer_z_bench_test.go +++ b/os/gtimer/gtimer_z_bench_test.go @@ -1,20 +1,18 @@ -// Copyright 2019 gf Author(https://github.com/gogf/gf). All Rights Reserved. +// Copyright GoFrame Author(https://goframe.org). All Rights Reserved. // // This Source Code Form is subject to the terms of the MIT License. // If a copy of the MIT was not distributed with this file, // You can obtain one at https://github.com/gogf/gf. -package gtimer_test +package gtimer import ( "testing" "time" - - "github.com/gogf/gf/os/gtimer" ) var ( - timer = gtimer.New(5, 30*time.Millisecond) + timer = New() ) func Benchmark_Add(b *testing.B) { @@ -25,6 +23,12 @@ func Benchmark_Add(b *testing.B) { } } +func Benchmark_PriorityQueue_Pop(b *testing.B) { + for i := 0; i < b.N; i++ { + timer.queue.Pop() + } +} + func Benchmark_StartStop(b *testing.B) { for i := 0; i < b.N; i++ { timer.Start() diff --git a/os/gtimer/gtimer_z_example_test.go b/os/gtimer/gtimer_z_example_test.go index 6db68f311..e8cbb543b 100644 --- a/os/gtimer/gtimer_z_example_test.go +++ b/os/gtimer/gtimer_z_example_test.go @@ -1,4 +1,4 @@ -// Copyright 2019 gf Author(https://github.com/gogf/gf). All Rights Reserved. +// Copyright GoFrame Author(https://goframe.org). All Rights Reserved. // // This Source Code Form is subject to the terms of the MIT License. // If a copy of the MIT was not distributed with this file, diff --git a/os/gtimer/gtimer_z_unit_0_test.go b/os/gtimer/gtimer_z_unit_api_test.go similarity index 86% rename from os/gtimer/gtimer_z_unit_0_test.go rename to os/gtimer/gtimer_z_unit_api_test.go index f69f1d8c9..debc25d9c 100644 --- a/os/gtimer/gtimer_z_unit_0_test.go +++ b/os/gtimer/gtimer_z_unit_api_test.go @@ -1,4 +1,4 @@ -// Copyright GoFrame Author(https://github.com/gogf/gf). All Rights Reserved. +// Copyright GoFrame Author(https://goframe.org). All Rights Reserved. // // This Source Code Form is subject to the terms of the MIT License. // If a copy of the MIT was not distributed with this file, @@ -31,11 +31,11 @@ func TestSetTimeout(t *testing.T) { func TestSetInterval(t *testing.T) { gtest.C(t, func(t *gtest.T) { array := garray.New(true) - gtimer.SetInterval(200*time.Millisecond, func() { + gtimer.SetInterval(300*time.Millisecond, func() { array.Append(1) }) - time.Sleep(1100 * time.Millisecond) - t.Assert(array.Len(), 5) + time.Sleep(1000 * time.Millisecond) + t.Assert(array.Len(), 3) }) } @@ -76,12 +76,12 @@ func TestAddTimes(t *testing.T) { func TestDelayAdd(t *testing.T) { gtest.C(t, func(t *gtest.T) { array := garray.New(true) - gtimer.DelayAdd(200*time.Millisecond, 200*time.Millisecond, func() { + gtimer.DelayAdd(500*time.Millisecond, 500*time.Millisecond, func() { array.Append(1) }) - time.Sleep(300 * time.Millisecond) + time.Sleep(600 * time.Millisecond) t.Assert(array.Len(), 0) - time.Sleep(200 * time.Millisecond) + time.Sleep(600 * time.Millisecond) t.Assert(array.Len(), 1) }) } @@ -102,7 +102,7 @@ func TestDelayAddEntry(t *testing.T) { func TestDelayAddSingleton(t *testing.T) { gtest.C(t, func(t *gtest.T) { array := garray.New(true) - gtimer.DelayAddSingleton(200*time.Millisecond, 200*time.Millisecond, func() { + gtimer.DelayAddSingleton(500*time.Millisecond, 500*time.Millisecond, func() { array.Append(1) time.Sleep(10000 * time.Millisecond) }) @@ -129,12 +129,12 @@ func TestDelayAddOnce(t *testing.T) { func TestDelayAddTimes(t *testing.T) { gtest.C(t, func(t *gtest.T) { array := garray.New(true) - gtimer.DelayAddTimes(200*time.Millisecond, 200*time.Millisecond, 2, func() { + gtimer.DelayAddTimes(500*time.Millisecond, 500*time.Millisecond, 2, func() { array.Append(1) }) time.Sleep(300 * time.Millisecond) t.Assert(array.Len(), 0) - time.Sleep(1000 * time.Millisecond) + time.Sleep(1500 * time.Millisecond) t.Assert(array.Len(), 2) }) } diff --git a/os/gtimer/gtimer_z_unit_2_test.go b/os/gtimer/gtimer_z_unit_entry_test.go similarity index 64% rename from os/gtimer/gtimer_z_unit_2_test.go rename to os/gtimer/gtimer_z_unit_entry_test.go index 6006dd125..a542111b7 100644 --- a/os/gtimer/gtimer_z_unit_2_test.go +++ b/os/gtimer/gtimer_z_unit_entry_test.go @@ -1,10 +1,10 @@ -// Copyright GoFrame Author(https://github.com/gogf/gf). All Rights Reserved. +// Copyright GoFrame Author(https://goframe.org). All Rights Reserved. // // This Source Code Form is subject to the terms of the MIT License. // If a copy of the MIT was not distributed with this file, // You can obtain one at https://github.com/gogf/gf. -// Entry Operations +// Job Operations package gtimer_test @@ -17,40 +17,40 @@ import ( "github.com/gogf/gf/test/gtest" ) -func TestEntry_Start_Stop_Close(t *testing.T) { +func TestJob_Start_Stop_Close(t *testing.T) { gtest.C(t, func(t *gtest.T) { timer := New() array := garray.New(true) - entry := timer.Add(200*time.Millisecond, func() { + job := timer.Add(200*time.Millisecond, func() { array.Append(1) }) time.Sleep(250 * time.Millisecond) t.Assert(array.Len(), 1) - entry.Stop() + job.Stop() time.Sleep(250 * time.Millisecond) t.Assert(array.Len(), 1) - entry.Start() + job.Start() time.Sleep(250 * time.Millisecond) t.Assert(array.Len(), 2) - entry.Close() + job.Close() time.Sleep(250 * time.Millisecond) t.Assert(array.Len(), 2) - t.Assert(entry.Status(), gtimer.StatusClosed) + t.Assert(job.Status(), gtimer.StatusClosed) }) } -func TestEntry_Singleton(t *testing.T) { +func TestJob_Singleton(t *testing.T) { gtest.C(t, func(t *gtest.T) { timer := New() array := garray.New(true) - entry := timer.Add(200*time.Millisecond, func() { + job := timer.Add(200*time.Millisecond, func() { array.Append(1) time.Sleep(10 * time.Second) }) - t.Assert(entry.IsSingleton(), false) - entry.SetSingleton(true) - t.Assert(entry.IsSingleton(), true) + t.Assert(job.IsSingleton(), false) + job.SetSingleton(true) + t.Assert(job.IsSingleton(), true) time.Sleep(250 * time.Millisecond) t.Assert(array.Len(), 1) @@ -59,27 +59,28 @@ func TestEntry_Singleton(t *testing.T) { }) } -func TestEntry_SetTimes(t *testing.T) { +func TestJob_SetTimes(t *testing.T) { gtest.C(t, func(t *gtest.T) { timer := New() array := garray.New(true) - entry := timer.Add(200*time.Millisecond, func() { + job := timer.Add(200*time.Millisecond, func() { array.Append(1) }) - entry.SetTimes(2) + job.SetTimes(2) + //job.IsSingleton() time.Sleep(1200 * time.Millisecond) t.Assert(array.Len(), 2) }) } -func TestEntry_Run(t *testing.T) { +func TestJob_Run(t *testing.T) { gtest.C(t, func(t *gtest.T) { timer := New() array := garray.New(true) - entry := timer.Add(1000*time.Millisecond, func() { + job := timer.Add(1000*time.Millisecond, func() { array.Append(1) }) - entry.Run() + job.Job()() t.Assert(array.Len(), 1) }) } diff --git a/os/gtimer/gtimer_z_unit_timer_internal_test.go b/os/gtimer/gtimer_z_unit_timer_internal_test.go new file mode 100644 index 000000000..785fd9823 --- /dev/null +++ b/os/gtimer/gtimer_z_unit_timer_internal_test.go @@ -0,0 +1,83 @@ +// Copyright GoFrame Author(https://goframe.org). All Rights Reserved. +// +// This Source Code Form is subject to the terms of the MIT License. +// If a copy of the MIT was not distributed with this file, +// You can obtain one at https://github.com/gogf/gf. + +package gtimer + +import ( + "github.com/gogf/gf/container/garray" + "github.com/gogf/gf/test/gtest" + "testing" + "time" +) + +func TestTimer_Proceed(t *testing.T) { + gtest.C(t, func(t *gtest.T) { + array := garray.New(true) + timer := New(TimerOptions{ + Interval: time.Hour, + }) + timer.Add(10000*time.Hour, func() { + array.Append(1) + }) + timer.proceed(10001) + time.Sleep(10 * time.Millisecond) + t.Assert(array.Len(), 1) + timer.proceed(20001) + time.Sleep(10 * time.Millisecond) + t.Assert(array.Len(), 2) + }) + gtest.C(t, func(t *gtest.T) { + array := garray.New(true) + timer := New(TimerOptions{ + Interval: time.Millisecond * 100, + }) + timer.Add(10000*time.Hour, func() { + array.Append(1) + }) + ticks := int64((10000 * time.Hour) / (time.Millisecond * 100)) + timer.proceed(ticks + 1) + time.Sleep(10 * time.Millisecond) + t.Assert(array.Len(), 1) + timer.proceed(2*ticks + 1) + time.Sleep(10 * time.Millisecond) + t.Assert(array.Len(), 2) + }) +} + +func TestTimer_PriorityQueue(t *testing.T) { + gtest.C(t, func(t *gtest.T) { + queue := newPriorityQueue() + queue.Push(1, 1) + queue.Push(4, 4) + queue.Push(5, 5) + queue.Push(2, 2) + queue.Push(3, 3) + t.Assert(queue.Pop(), 1) + t.Assert(queue.Pop(), 2) + t.Assert(queue.Pop(), 3) + t.Assert(queue.Pop(), 4) + t.Assert(queue.Pop(), 5) + }) +} + +func TestTimer_PriorityQueue_FirstOneInArrayIsTheLeast(t *testing.T) { + gtest.C(t, func(t *gtest.T) { + var ( + size = 1000000 + array = garray.NewIntArrayRange(0, size, 1) + ) + array.Shuffle() + queue := newPriorityQueue() + array.Iterator(func(k int, v int) bool { + queue.Push(v, int64(v)) + return true + }) + for i := 0; i < size; i++ { + t.Assert(queue.Pop(), i) + t.Assert(queue.heap.array[0].priority, i+1) + } + }) +} diff --git a/os/gtimer/gtimer_z_unit_1_test.go b/os/gtimer/gtimer_z_unit_timer_test.go similarity index 85% rename from os/gtimer/gtimer_z_unit_1_test.go rename to os/gtimer/gtimer_z_unit_timer_test.go index be75a9cb7..3faf7e908 100644 --- a/os/gtimer/gtimer_z_unit_1_test.go +++ b/os/gtimer/gtimer_z_unit_timer_test.go @@ -1,4 +1,4 @@ -// Copyright GoFrame Author(https://github.com/gogf/gf). All Rights Reserved. +// Copyright GoFrame Author(https://goframe.org). All Rights Reserved. // // This Source Code Form is subject to the terms of the MIT License. // If a copy of the MIT was not distributed with this file, @@ -9,7 +9,6 @@ package gtimer_test import ( - "github.com/gogf/gf/os/glog" "testing" "time" @@ -19,7 +18,7 @@ import ( ) func New() *gtimer.Timer { - return gtimer.New(10, 10*time.Millisecond) + return gtimer.New() } func TestTimer_Add_Close(t *testing.T) { @@ -28,15 +27,15 @@ func TestTimer_Add_Close(t *testing.T) { array := garray.New(true) //fmt.Println("start", time.Now()) timer.Add(200*time.Millisecond, func() { - //fmt.Println("entry1", time.Now()) + //fmt.Println("job1", time.Now()) array.Append(1) }) timer.Add(200*time.Millisecond, func() { - //fmt.Println("entry2", time.Now()) + //fmt.Println("job2", time.Now()) array.Append(1) }) timer.Add(400*time.Millisecond, func() { - //fmt.Println("entry3", time.Now()) + //fmt.Println("job3", time.Now()) array.Append(1) }) time.Sleep(250 * time.Millisecond) @@ -55,40 +54,39 @@ func TestTimer_Start_Stop_Close(t *testing.T) { gtest.C(t, func(t *gtest.T) { timer := New() array := garray.New(true) - timer.Add(200*time.Millisecond, func() { - //glog.Println("add...") + timer.Add(1000*time.Millisecond, func() { array.Append(1) }) t.Assert(array.Len(), 0) - time.Sleep(300 * time.Millisecond) + time.Sleep(1200 * time.Millisecond) t.Assert(array.Len(), 1) timer.Stop() - time.Sleep(1000 * time.Millisecond) + time.Sleep(1200 * time.Millisecond) t.Assert(array.Len(), 1) timer.Start() - time.Sleep(200 * time.Millisecond) + time.Sleep(1200 * time.Millisecond) t.Assert(array.Len(), 2) timer.Close() - time.Sleep(1000 * time.Millisecond) + time.Sleep(1200 * time.Millisecond) t.Assert(array.Len(), 2) }) } -func TestTimer_Reset(t *testing.T) { +func TestJob_Reset(t *testing.T) { gtest.C(t, func(t *gtest.T) { timer := New() array := garray.New(true) - glog.Printf("start time:%d", time.Now().Unix()) - singleton := timer.AddSingleton(2*time.Second, func() { - timestamp := time.Now().Unix() - glog.Println(timestamp) - array.Append(timestamp) + job := timer.AddSingleton(500*time.Millisecond, func() { + array.Append(1) }) - time.Sleep(5 * time.Second) - glog.Printf("reset time:%d", time.Now().Unix()) - singleton.Reset() - time.Sleep(10 * time.Second) - t.Assert(array.Len(), 6) + time.Sleep(300 * time.Millisecond) + job.Reset() + time.Sleep(300 * time.Millisecond) + job.Reset() + time.Sleep(300 * time.Millisecond) + job.Reset() + time.Sleep(600 * time.Millisecond) + t.Assert(array.Len(), 1) }) } @@ -156,7 +154,7 @@ func TestTimer_DelayAdd(t *testing.T) { }) } -func TestTimer_DelayAddEntry(t *testing.T) { +func TestTimer_DelayAddJob(t *testing.T) { gtest.C(t, func(t *gtest.T) { timer := New() array := garray.New(true) @@ -227,7 +225,9 @@ func TestTimer_DelayAddTimes(t *testing.T) { func TestTimer_AddLessThanInterval(t *testing.T) { gtest.C(t, func(t *gtest.T) { - timer := gtimer.New(10, 100*time.Millisecond) + timer := gtimer.New(gtimer.TimerOptions{ + Interval: 100 * time.Millisecond, + }) array := garray.New(true) timer.Add(20*time.Millisecond, func() { array.Append(1) @@ -243,7 +243,7 @@ func TestTimer_AddLessThanInterval(t *testing.T) { }) } -func TestTimer_AddLeveledEntry1(t *testing.T) { +func TestTimer_AddLeveledJob1(t *testing.T) { gtest.C(t, func(t *gtest.T) { timer := New() array := garray.New(true) diff --git a/os/gview/gview.go b/os/gview/gview.go index f421a570b..e6823d072 100644 --- a/os/gview/gview.go +++ b/os/gview/gview.go @@ -1,4 +1,4 @@ -// Copyright 2017 gf Author(https://github.com/gogf/gf). All Rights Reserved. +// Copyright GoFrame Author(https://goframe.org). All Rights Reserved. // // This Source Code Form is subject to the terms of the MIT License. // If a copy of the MIT was not distributed with this file, @@ -11,6 +11,7 @@ package gview import ( + "context" "github.com/gogf/gf/container/gmap" "github.com/gogf/gf/internal/intlog" @@ -35,6 +36,10 @@ type ( FuncMap = map[string]interface{} // FuncMap is type for custom template functions. ) +const ( + commandEnvKeyForPath = "gf.gview.path" +) + var ( // Default view object. defaultViewObj *View @@ -50,13 +55,13 @@ func checkAndInitDefaultView() { // ParseContent parses the template content directly using the default view object // and returns the parsed content. -func ParseContent(content string, params ...Params) (string, error) { +func ParseContent(ctx context.Context, content string, params ...Params) (string, error) { checkAndInitDefaultView() - return defaultViewObj.ParseContent(content, params...) + return defaultViewObj.ParseContent(ctx, content, params...) } // New returns a new view object. -// The parameter <path> specifies the template directory path to load template files. +// The parameter `path` specifies the template directory path to load template files. func New(path ...string) *View { view := &View{ paths: garray.NewStrArray(), @@ -67,14 +72,14 @@ func New(path ...string) *View { } if len(path) > 0 && len(path[0]) > 0 { if err := view.SetPath(path[0]); err != nil { - intlog.Error(err) + intlog.Error(context.TODO(), err) } } else { // Customized dir path from env/cmd. - if envPath := gcmd.GetWithEnv("gf.gview.path").String(); envPath != "" { + if envPath := gcmd.GetOptWithEnv(commandEnvKeyForPath).String(); envPath != "" { if gfile.Exists(envPath) { if err := view.SetPath(envPath); err != nil { - intlog.Error(err) + intlog.Error(context.TODO(), err) } } else { if errorPrint() { @@ -84,18 +89,18 @@ func New(path ...string) *View { } else { // Dir path of working dir. if err := view.SetPath(gfile.Pwd()); err != nil { - intlog.Error(err) + intlog.Error(context.TODO(), err) } // Dir path of binary. if selfPath := gfile.SelfDir(); selfPath != "" && gfile.Exists(selfPath) { if err := view.AddPath(selfPath); err != nil { - intlog.Error(err) + intlog.Error(context.TODO(), err) } } // Dir path of main package. if mainPath := gfile.MainPkgPath(); mainPath != "" && gfile.Exists(mainPath) { if err := view.AddPath(mainPath); err != nil { - intlog.Error(err) + intlog.Error(context.TODO(), err) } } } @@ -138,6 +143,10 @@ func New(path ...string) *View { "map": view.buildInFuncMap, "maps": view.buildInFuncMaps, "json": view.buildInFuncJson, + "plus": view.buildInFuncPlus, + "minus": view.buildInFuncMinus, + "times": view.buildInFuncTimes, + "divide": view.buildInFuncDivide, }) return view diff --git a/os/gview/gview_buildin.go b/os/gview/gview_buildin.go index fc4f8f697..da22f78fa 100644 --- a/os/gview/gview_buildin.go +++ b/os/gview/gview_buildin.go @@ -1,4 +1,4 @@ -// Copyright 2017 gf Author(https://github.com/gogf/gf). All Rights Reserved. +// Copyright GoFrame Author(https://goframe.org). All Rights Reserved. // // This Source Code Form is subject to the terms of the MIT License. // If a copy of the MIT was not distributed with this file, @@ -7,6 +7,7 @@ package gview import ( + "context" "fmt" "github.com/gogf/gf/internal/json" "github.com/gogf/gf/util/gutil" @@ -51,11 +52,11 @@ func (view *View) buildInFuncMaps(value ...interface{}) []map[string]interface{} func (view *View) buildInFuncEq(value interface{}, others ...interface{}) bool { s := gconv.String(value) for _, v := range others { - if strings.Compare(s, gconv.String(v)) != 0 { - return false + if strings.Compare(s, gconv.String(v)) == 0 { + return true } } - return true + return false } // buildInFuncNe implements build-in template function: ne @@ -115,7 +116,7 @@ func (view *View) buildInFuncInclude(file interface{}, data ...map[string]interf return "" } // It will search the file internally. - content, err := view.Parse(path, m) + content, err := view.Parse(context.TODO(), path, m) if err != nil { return htmltpl.HTML(err.Error()) } @@ -218,8 +219,53 @@ func (view *View) buildInFuncNl2Br(str interface{}) string { } // buildInFuncJson implements build-in template function: json , -// which encodes and returns <value> as JSON string. +// which encodes and returns `value` as JSON string. func (view *View) buildInFuncJson(value interface{}) (string, error) { b, err := json.Marshal(value) return gconv.UnsafeBytesToStr(b), err } + +// buildInFuncPlus implements build-in template function: plus , +// which returns the result that pluses all `deltas` to `value`. +func (view *View) buildInFuncPlus(value interface{}, deltas ...interface{}) string { + result := gconv.Float64(value) + for _, v := range deltas { + result += gconv.Float64(v) + } + return gconv.String(result) +} + +// buildInFuncMinus implements build-in template function: minus , +// which returns the result that subtracts all `deltas` from `value`. +func (view *View) buildInFuncMinus(value interface{}, deltas ...interface{}) string { + result := gconv.Float64(value) + for _, v := range deltas { + result -= gconv.Float64(v) + } + return gconv.String(result) +} + +// buildInFuncTimes implements build-in template function: times , +// which returns the result that multiplies `value` by all of `values`. +func (view *View) buildInFuncTimes(value interface{}, values ...interface{}) string { + result := gconv.Float64(value) + for _, v := range values { + result *= gconv.Float64(v) + } + return gconv.String(result) +} + +// buildInFuncDivide implements build-in template function: divide , +// which returns the result that divides `value` by all of `values`. +func (view *View) buildInFuncDivide(value interface{}, values ...interface{}) string { + result := gconv.Float64(value) + for _, v := range values { + value2Float64 := gconv.Float64(v) + if value2Float64 == 0 { + // Invalid `value2`. + return "0" + } + result /= value2Float64 + } + return gconv.String(result) +} diff --git a/os/gview/gview_config.go b/os/gview/gview_config.go index 2404c2f56..cc62d1e88 100644 --- a/os/gview/gview_config.go +++ b/os/gview/gview_config.go @@ -1,4 +1,4 @@ -// Copyright 2017 gf Author(https://github.com/gogf/gf). All Rights Reserved. +// Copyright GoFrame Author(https://goframe.org). All Rights Reserved. // // This Source Code Form is subject to the terms of the MIT License. // If a copy of the MIT was not distributed with this file, @@ -7,8 +7,9 @@ package gview import ( - "errors" - "fmt" + "context" + "github.com/gogf/gf/errors/gcode" + "github.com/gogf/gf/errors/gerror" "github.com/gogf/gf/i18n/gi18n" "github.com/gogf/gf/internal/intlog" "github.com/gogf/gf/os/gfile" @@ -21,12 +22,12 @@ import ( // Config is the configuration object for template engine. type Config struct { - Paths []string // Searching array for path, NOT concurrent-safe for performance purpose. - Data map[string]interface{} // Global template variables including configuration. - DefaultFile string // Default template file for parsing. - Delimiters []string // Custom template delimiters. - AutoEncode bool // Automatically encodes and provides safe html output, which is good for avoiding XSS. - I18nManager *gi18n.Manager // I18n manager for the view. + Paths []string `json:"paths"` // Searching array for path, NOT concurrent-safe for performance purpose. + Data map[string]interface{} `json:"data"` // Global template variables including configuration. + DefaultFile string `json:"defaultFile"` // Default template file for parsing. + Delimiters []string `json:"delimiters"` // Custom template delimiters. + AutoEncode bool `json:"autoEncode"` // Automatically encodes and provides safe html output, which is good for avoiding XSS. + I18nManager *gi18n.Manager `json:"-"` // I18n manager for the view. } const ( @@ -67,14 +68,14 @@ func (view *View) SetConfig(config Config) error { // It's just cache, do not hesitate clearing it. templates.Clear() - intlog.Printf("SetConfig: %+v", view.config) + intlog.Printf(context.TODO(), "SetConfig: %+v", view.config) return nil } // SetConfigWithMap set configurations with map for the view. func (view *View) SetConfigWithMap(m map[string]interface{}) error { if m == nil || len(m) == 0 { - return errors.New("configuration cannot be empty") + return gerror.NewCode(gcode.CodeInvalidParameter, "configuration cannot be empty") } // The m now is a shallow copy of m. // Any changes to m does not affect the original one. @@ -94,7 +95,7 @@ func (view *View) SetConfigWithMap(m map[string]interface{}) error { } // SetPath sets the template directory path for template file search. -// The parameter <path> can be absolute or relative path, but absolute path is suggested. +// The parameter `path` can be absolute or relative path, but absolute path is suggested. func (view *View) SetPath(path string) error { var ( isDir = false @@ -123,7 +124,7 @@ func (view *View) SetPath(path string) error { } // Path not exist. if realPath == "" { - err := errors.New(fmt.Sprintf(`[gview] SetPath failed: path "%s" does not exist`, path)) + err := gerror.NewCodef(gcode.CodeInvalidParameter, `[gview] SetPath failed: path "%s" does not exist`, path) if errorPrint() { glog.Error(err) } @@ -131,7 +132,7 @@ func (view *View) SetPath(path string) error { } // Should be a directory. if !isDir { - err := errors.New(fmt.Sprintf(`[gview] SetPath failed: path "%s" should be directory type`, path)) + err := gerror.NewCodef(gcode.CodeInvalidParameter, `[gview] SetPath failed: path "%s" should be directory type`, path) if errorPrint() { glog.Error(err) } @@ -177,7 +178,7 @@ func (view *View) AddPath(path string) error { } // Path not exist. if realPath == "" { - err := errors.New(fmt.Sprintf(`[gview] AddPath failed: path "%s" does not exist`, path)) + err := gerror.NewCodef(gcode.CodeInvalidParameter, `[gview] AddPath failed: path "%s" does not exist`, path) if errorPrint() { glog.Error(err) } @@ -185,7 +186,7 @@ func (view *View) AddPath(path string) error { } // realPath should be type of folder. if !isDir { - err := errors.New(fmt.Sprintf(`[gview] AddPath failed: path "%s" should be directory type`, path)) + err := gerror.NewCodef(gcode.CodeInvalidParameter, `[gview] AddPath failed: path "%s" should be directory type`, path) if errorPrint() { glog.Error(err) } @@ -238,9 +239,9 @@ func (view *View) SetAutoEncode(enable bool) { view.config.AutoEncode = enable } -// BindFunc registers customized global template function named <name> -// with given function <function> to current view object. -// The <name> is the function name which can be called in template content. +// BindFunc registers customized global template function named `name` +// with given function `function` to current view object. +// The `name` is the function name which can be called in template content. func (view *View) BindFunc(name string, function interface{}) { view.funcMap[name] = function // Clear global template object cache. diff --git a/os/gview/gview_error.go b/os/gview/gview_error.go index 423fc6d55..268321bf7 100644 --- a/os/gview/gview_error.go +++ b/os/gview/gview_error.go @@ -1,4 +1,4 @@ -// Copyright 2019 gf Author(https://github.com/gogf/gf). All Rights Reserved. +// Copyright GoFrame Author(https://goframe.org). All Rights Reserved. // // This Source Code Form is subject to the terms of the MIT License. // If a copy of the MIT was not distributed with this file, @@ -11,12 +11,12 @@ import ( ) const ( - // gERROR_PRINT_KEY is used to specify the key controlling error printing to stdout. + // commandEnvKeyForErrorPrint is used to specify the key controlling error printing to stdout. // This error is designed not to be returned by functions. - gERROR_PRINT_KEY = "gf.gview.errorprint" + commandEnvKeyForErrorPrint = "gf.gview.errorprint" ) // errorPrint checks whether printing error to stdout. func errorPrint() bool { - return gcmd.GetWithEnv(gERROR_PRINT_KEY, true).Bool() + return gcmd.GetOptWithEnv(commandEnvKeyForErrorPrint, true).Bool() } diff --git a/os/gview/gview_i18n.go b/os/gview/gview_i18n.go index 0e5fa1116..554f2f567 100644 --- a/os/gview/gview_i18n.go +++ b/os/gview/gview_i18n.go @@ -1,4 +1,4 @@ -// Copyright 2019 gf Author(https://github.com/gogf/gf). All Rights Reserved. +// Copyright GoFrame Author(https://goframe.org). All Rights Reserved. // // This Source Code Form is subject to the terms of the MIT License. // If a copy of the MIT was not distributed with this file, @@ -6,18 +6,33 @@ package gview -import "github.com/gogf/gf/util/gconv" +import ( + "context" + "github.com/gogf/gf/i18n/gi18n" + "github.com/gogf/gf/util/gconv" +) + +const ( + i18nLanguageVariableName = "I18nLanguage" +) // i18nTranslate translate the content with i18n feature. -func (view *View) i18nTranslate(content string, params Params) string { +func (view *View) i18nTranslate(ctx context.Context, content string, variables Params) string { if view.config.I18nManager != nil { - if v, ok := params["I18nLanguage"]; ok { - language := gconv.String(v) - if language != "" { - return view.config.I18nManager.T(content, language) - } + // Compatible with old version. + if language, ok := variables[i18nLanguageVariableName]; ok { + ctx = gi18n.WithLanguage(ctx, gconv.String(language)) } - return view.config.I18nManager.T(content) + return view.config.I18nManager.T(ctx, content) } return content } + +// setI18nLanguageFromCtx retrieves language name from context and sets it to template variables map. +func (view *View) setI18nLanguageFromCtx(ctx context.Context, variables map[string]interface{}) { + if language, ok := variables[i18nLanguageVariableName]; !ok { + if language = gi18n.LanguageFromCtx(ctx); language != "" { + variables[i18nLanguageVariableName] = language + } + } +} diff --git a/os/gview/gview_instance.go b/os/gview/gview_instance.go index 0e976b911..8845e5d21 100644 --- a/os/gview/gview_instance.go +++ b/os/gview/gview_instance.go @@ -1,4 +1,4 @@ -// Copyright 2017 gf Author(https://github.com/gogf/gf). All Rights Reserved. +// Copyright GoFrame Author(https://goframe.org). All Rights Reserved. // // This Source Code Form is subject to the terms of the MIT License. // If a copy of the MIT was not distributed with this file, @@ -19,7 +19,7 @@ var ( ) // Instance returns an instance of View with default settings. -// The parameter <name> is the name for the instance. +// The parameter `name` is the name for the instance. func Instance(name ...string) *View { key := DefaultName if len(name) > 0 && name[0] != "" { diff --git a/os/gview/gview_parse.go b/os/gview/gview_parse.go index cd0a632af..c8a4811ee 100644 --- a/os/gview/gview_parse.go +++ b/os/gview/gview_parse.go @@ -1,4 +1,4 @@ -// Copyright GoFrame Author(https://github.com/gogf/gf). All Rights Reserved. +// Copyright GoFrame Author(https://goframe.org). All Rights Reserved. // // This Source Code Form is subject to the terms of the MIT License. // If a copy of the MIT was not distributed with this file, @@ -8,9 +8,10 @@ package gview import ( "bytes" - "errors" + "context" "fmt" "github.com/gogf/gf/encoding/ghash" + "github.com/gogf/gf/errors/gcode" "github.com/gogf/gf/errors/gerror" "github.com/gogf/gf/internal/intlog" "github.com/gogf/gf/os/gfsnotify" @@ -52,15 +53,19 @@ var ( resourceTryFolders = []string{"template/", "template", "/template", "/template/"} ) -// Parse parses given template file <file> with given template variables <params> +// Parse parses given template file `file` with given template variables `params` // and returns the parsed template content. -func (view *View) Parse(file string, params ...Params) (result string, err error) { +func (view *View) Parse(ctx context.Context, file string, params ...Params) (result string, err error) { var tpl interface{} // It caches the file, folder and its content to enhance performance. r := view.fileCacheMap.GetOrSetFuncLock(file, func() interface{} { - var path, folder, content string - var resource *gres.File - // Searching the absolute file path for <file>. + var ( + path string + folder string + content string + resource *gres.File + ) + // Searching the absolute file path for `file`. path, folder, resource, err = view.searchFile(file) if err != nil { return nil @@ -78,7 +83,7 @@ func (view *View) Parse(file string, params ...Params) (result string, err error templates.Clear() gfsnotify.Exit() }); err != nil { - intlog.Error(err) + intlog.Error(ctx, err) } } return &fileCacheItem{ @@ -95,7 +100,7 @@ func (view *View) Parse(file string, params ...Params) (result string, err error if item.content == "" { return "", nil } - // Get the template object instance for <folder>. + // Get the template object instance for `folder`. tpl, err = view.getTemplate(item.path, item.folder, fmt.Sprintf(`*%s`, gfile.Ext(item.path))) if err != nil { return "", err @@ -108,19 +113,21 @@ func (view *View) Parse(file string, params ...Params) (result string, err error tpl, err = tpl.(*texttpl.Template).Parse(item.content) } if err != nil && item.path != "" { - err = gerror.Wrap(err, item.path) + err = gerror.WrapCode(gcode.CodeInternalError, err, item.path) } }) if err != nil { return "", err } // Note that the template variable assignment cannot change the value - // of the existing <params> or view.data because both variables are pointers. + // of the existing `params` or view.data because both variables are pointers. // It needs to merge the values of the two maps into a new map. variables := gutil.MapMergeCopy(params...) if len(view.data) > 0 { gutil.MapMerge(variables, view.data) } + view.setI18nLanguageFromCtx(ctx, variables) + buffer := bytes.NewBuffer(nil) if view.config.AutoEncode { newTpl, err := tpl.(*htmltpl.Template).Clone() @@ -138,18 +145,18 @@ func (view *View) Parse(file string, params ...Params) (result string, err error // TODO any graceful plan to replace "<no value>"? result = gstr.Replace(buffer.String(), "<no value>", "") - result = view.i18nTranslate(result, variables) + result = view.i18nTranslate(ctx, result, variables) return result, nil } // ParseDefault parses the default template file with params. -func (view *View) ParseDefault(params ...Params) (result string, err error) { - return view.Parse(view.config.DefaultFile, params...) +func (view *View) ParseDefault(ctx context.Context, params ...Params) (result string, err error) { + return view.Parse(ctx, view.config.DefaultFile, params...) } -// ParseContent parses given template content <content> with template variables <params> +// ParseContent parses given template content `content` with template variables `params` // and returns the parsed content in []byte. -func (view *View) ParseContent(content string, params ...Params) (string, error) { +func (view *View) ParseContent(ctx context.Context, content string, params ...Params) (string, error) { // It's not necessary continuing parsing if template content is empty. if content == "" { return "", nil @@ -181,12 +188,14 @@ func (view *View) ParseContent(content string, params ...Params) (string, error) return "", err } // Note that the template variable assignment cannot change the value - // of the existing <params> or view.data because both variables are pointers. + // of the existing `params` or view.data because both variables are pointers. // It needs to merge the values of the two maps into a new map. variables := gutil.MapMergeCopy(params...) if len(view.data) > 0 { gutil.MapMerge(variables, view.data) } + view.setI18nLanguageFromCtx(ctx, variables) + buffer := bytes.NewBuffer(nil) if view.config.AutoEncode { newTpl, err := tpl.(*htmltpl.Template).Clone() @@ -203,27 +212,26 @@ func (view *View) ParseContent(content string, params ...Params) (string, error) } // TODO any graceful plan to replace "<no value>"? result := gstr.Replace(buffer.String(), "<no value>", "") - result = view.i18nTranslate(result, variables) + result = view.i18nTranslate(ctx, result, variables) return result, nil } -// getTemplate returns the template object associated with given template file <path>. +// getTemplate returns the template object associated with given template file `path`. // It uses template cache to enhance performance, that is, it will return the same template object -// with the same given <path>. It will also automatically refresh the template cache -// if the template files under <path> changes (recursively). +// with the same given `path`. It will also automatically refresh the template cache +// if the template files under `path` changes (recursively). func (view *View) getTemplate(filePath, folderPath, pattern string) (tpl interface{}, err error) { // Key for template cache. key := fmt.Sprintf("%s_%v", filePath, view.config.Delimiters) result := templates.GetOrSetFuncLock(key, func() interface{} { - // Do not use <key> but the <filePath> as the parameter <name> for function New, - // because when error occurs the <name> will be printed out for error locating. + tplName := filePath if view.config.AutoEncode { - tpl = htmltpl.New(filePath).Delims( + tpl = htmltpl.New(tplName).Delims( view.config.Delimiters[0], view.config.Delimiters[1], ).Funcs(view.funcMap) } else { - tpl = texttpl.New(filePath).Delims( + tpl = texttpl.New(tplName).Delims( view.config.Delimiters[0], view.config.Delimiters[1], ).Funcs(view.funcMap) @@ -232,16 +240,22 @@ func (view *View) getTemplate(filePath, folderPath, pattern string) (tpl interfa if !gres.IsEmpty() { if files := gres.ScanDirFile(folderPath, pattern, true); len(files) > 0 { var err error - for _, v := range files { - if view.config.AutoEncode { - _, err = tpl.(*htmltpl.Template).New(v.FileInfo().Name()).Parse(string(v.Content())) + if view.config.AutoEncode { + t := tpl.(*htmltpl.Template) + for _, v := range files { + _, err = t.New(v.FileInfo().Name()).Parse(string(v.Content())) if err != nil { - intlog.Error(err) + err = view.formatTemplateObjectCreatingError(v.Name(), tplName, err) + return nil } - } else { - _, err = tpl.(*texttpl.Template).New(v.FileInfo().Name()).Parse(string(v.Content())) + } + } else { + t := tpl.(*texttpl.Template) + for _, v := range files { + _, err = t.New(v.FileInfo().Name()).Parse(string(v.Content())) if err != nil { - intlog.Error(err) + err = view.formatTemplateObjectCreatingError(v.Name(), tplName, err) + return nil } } } @@ -260,16 +274,16 @@ func (view *View) getTemplate(filePath, folderPath, pattern string) (tpl interfa if view.config.AutoEncode { t := tpl.(*htmltpl.Template) for _, file := range files { - _, err = t.Parse(gfile.GetContents(file)) - if err != nil { + if _, err = t.Parse(gfile.GetContents(file)); err != nil { + err = view.formatTemplateObjectCreatingError(file, tplName, err) return nil } } } else { t := tpl.(*texttpl.Template) for _, file := range files { - _, err = t.Parse(gfile.GetContents(file)) - if err != nil { + if _, err = t.Parse(gfile.GetContents(file)); err != nil { + err = view.formatTemplateObjectCreatingError(file, tplName, err) return nil } } @@ -282,9 +296,17 @@ func (view *View) getTemplate(filePath, folderPath, pattern string) (tpl interfa return } -// searchFile returns the found absolute path for <file> and its template folder path. -// Note that, the returned <folder> is the template folder path, but not the folder of -// the returned template file <path>. +// formatTemplateObjectCreatingError formats the error that creted from creating template object. +func (view *View) formatTemplateObjectCreatingError(filePath, tplName string, err error) error { + if err != nil { + return gerror.NewCodeSkip(gcode.CodeInternalError, 1, gstr.Replace(err.Error(), tplName, filePath)) + } + return nil +} + +// searchFile returns the found absolute path for `file` and its template folder path. +// Note that, the returned `folder` is the template folder path, but not the folder of +// the returned template file `path`. func (view *View) searchFile(file string) (path string, folder string, resource *gres.File, err error) { // Firstly checking the resource manager. if !gres.IsEmpty() { @@ -355,7 +377,7 @@ func (view *View) searchFile(file string) (path string, folder string, resource if errorPrint() { glog.Error(buffer.String()) } - err = errors.New(fmt.Sprintf(`template file "%s" not found`, file)) + err = gerror.NewCodef(gcode.CodeInvalidParameter, `template file "%s" not found`, file) } return } diff --git a/os/gview/gview_unit_basic_test.go b/os/gview/gview_unit_basic_test.go index afca128ed..7bbdcd390 100644 --- a/os/gview/gview_unit_basic_test.go +++ b/os/gview/gview_unit_basic_test.go @@ -1,4 +1,4 @@ -// Copyright 2017 gf Author(https://github.com/gogf/gf). All Rights Reserved. +// Copyright GoFrame Author(https://goframe.org). All Rights Reserved. // // This Source Code Form is subject to the terms of the MIT License. // If a copy of the MIT was not distributed with this file, @@ -7,7 +7,9 @@ package gview_test import ( + "context" "github.com/gogf/gf/encoding/ghtml" + "github.com/gogf/gf/os/gctx" "github.com/gogf/gf/os/gtime" "github.com/gogf/gf/util/gconv" "io/ioutil" @@ -39,18 +41,18 @@ func Test_Basic(t *testing.T) { view.Assigns(g.Map{"version": "1.7.0"}) view.BindFunc("GetName", func() string { return "gf" }) view.BindFuncMap(gview.FuncMap{"GetVersion": func() string { return "1.7.0" }}) - result, err := view.ParseContent(str, g.Map{"other": "that's all"}) + result, err := view.ParseContent(context.TODO(), str, g.Map{"other": "that's all"}) t.Assert(err != nil, false) t.Assert(result, "hello gf,version:1.7.0;hello gf,version:1.7.0;that's all") //测试api方法 str = `hello {{.name}}` - result, err = gview.ParseContent(str, g.Map{"name": "gf"}) + result, err = gview.ParseContent(context.TODO(), str, g.Map{"name": "gf"}) t.Assert(err != nil, false) t.Assert(result, "hello gf") //测试instance方法 - result, err = gview.Instance().ParseContent(str, g.Map{"name": "gf"}) + result, err = gview.Instance().ParseContent(context.TODO(), str, g.Map{"name": "gf"}) t.Assert(err != nil, false) t.Assert(result, "hello gf") }) @@ -59,128 +61,141 @@ func Test_Basic(t *testing.T) { func Test_Func(t *testing.T) { gtest.C(t, func(t *gtest.T) { str := `{{eq 1 1}};{{eq 1 2}};{{eq "A" "B"}}` - result, err := gview.ParseContent(str, nil) + result, err := gview.ParseContent(context.TODO(), str, nil) t.Assert(err != nil, false) t.Assert(result, `true;false;false`) str = `{{ne 1 2}};{{ne 1 1}};{{ne "A" "B"}}` - result, err = gview.ParseContent(str, nil) + result, err = gview.ParseContent(context.TODO(), str, nil) t.Assert(err != nil, false) t.Assert(result, `true;false;true`) str = `{{lt 1 2}};{{lt 1 1}};{{lt 1 0}};{{lt "A" "B"}}` - result, err = gview.ParseContent(str, nil) + result, err = gview.ParseContent(context.TODO(), str, nil) t.Assert(err != nil, false) t.Assert(result, `true;false;false;true`) str = `{{le 1 2}};{{le 1 1}};{{le 1 0}};{{le "A" "B"}}` - result, err = gview.ParseContent(str, nil) + result, err = gview.ParseContent(context.TODO(), str, nil) t.Assert(err != nil, false) t.Assert(result, `true;true;false;true`) str = `{{gt 1 2}};{{gt 1 1}};{{gt 1 0}};{{gt "A" "B"}}` - result, err = gview.ParseContent(str, nil) + result, err = gview.ParseContent(context.TODO(), str, nil) t.Assert(err != nil, false) t.Assert(result, `false;false;true;false`) str = `{{ge 1 2}};{{ge 1 1}};{{ge 1 0}};{{ge "A" "B"}}` - result, err = gview.ParseContent(str, nil) + result, err = gview.ParseContent(context.TODO(), str, nil) t.Assert(err != nil, false) t.Assert(result, `false;true;true;false`) str = `{{"<div>测试</div>"|text}}` - result, err = gview.ParseContent(str, nil) + result, err = gview.ParseContent(context.TODO(), str, nil) t.Assert(err != nil, false) t.Assert(result, `测试`) str = `{{"<div>测试</div>"|html}}` - result, err = gview.ParseContent(str, nil) + result, err = gview.ParseContent(context.TODO(), str, nil) t.Assert(err != nil, false) t.Assert(result, `&lt;div&gt;测试&lt;/div&gt;`) str = `{{"<div>测试</div>"|htmlencode}}` - result, err = gview.ParseContent(str, nil) + result, err = gview.ParseContent(context.TODO(), str, nil) t.Assert(err != nil, false) t.Assert(result, `&lt;div&gt;测试&lt;/div&gt;`) str = `{{"&lt;div&gt;测试&lt;/div&gt;"|htmldecode}}` - result, err = gview.ParseContent(str, nil) + result, err = gview.ParseContent(context.TODO(), str, nil) t.Assert(err != nil, false) t.Assert(result, `<div>测试</div>`) str = `{{"https://goframe.org"|url}}` - result, err = gview.ParseContent(str, nil) + result, err = gview.ParseContent(context.TODO(), str, nil) t.Assert(err != nil, false) t.Assert(result, `https%3A%2F%2Fgoframe.org`) str = `{{"https://goframe.org"|urlencode}}` - result, err = gview.ParseContent(str, nil) + result, err = gview.ParseContent(context.TODO(), str, nil) t.Assert(err != nil, false) t.Assert(result, `https%3A%2F%2Fgoframe.org`) str = `{{"https%3A%2F%2Fgoframe.org"|urldecode}}` - result, err = gview.ParseContent(str, nil) + result, err = gview.ParseContent(context.TODO(), str, nil) t.Assert(err != nil, false) t.Assert(result, `https://goframe.org`) str = `{{"https%3NA%2F%2Fgoframe.org"|urldecode}}` - result, err = gview.ParseContent(str, nil) + result, err = gview.ParseContent(context.TODO(), str, nil) t.Assert(err != nil, false) t.Assert(gstr.Contains(result, "invalid URL escape"), true) str = `{{1540822968 | date "Y-m-d"}}` - result, err = gview.ParseContent(str, nil) + result, err = gview.ParseContent(context.TODO(), str, nil) t.Assert(err != nil, false) t.Assert(result, `2018-10-29`) str = `{{date "Y-m-d"}}` - result, err = gview.ParseContent(str, nil) + result, err = gview.ParseContent(context.TODO(), str, nil) t.Assert(err != nil, false) str = `{{"我是中国人" | substr 2 -1}};{{"我是中国人" | substr 2 2}}` - result, err = gview.ParseContent(str, nil) + result, err = gview.ParseContent(context.TODO(), str, nil) t.Assert(err != nil, false) t.Assert(result, `中国人;中国`) str = `{{"我是中国人" | strlimit 2 "..."}}` - result, err = gview.ParseContent(str, nil) + result, err = gview.ParseContent(context.TODO(), str, nil) t.Assert(err != nil, false) t.Assert(result, `我是...`) str = `{{"I'm中国人" | replace "I'm" "我是"}}` - result, err = gview.ParseContent(str, nil) + result, err = gview.ParseContent(context.TODO(), str, nil) t.Assert(err != nil, false) t.Assert(result, `我是中国人`) str = `{{compare "A" "B"}};{{compare "1" "2"}};{{compare 2 1}};{{compare 1 1}}` - result, err = gview.ParseContent(str, nil) + result, err = gview.ParseContent(context.TODO(), str, nil) t.Assert(err != nil, false) t.Assert(result, `-1;-1;1;0`) str = `{{"热爱GF热爱生活" | hidestr 20 "*"}};{{"热爱GF热爱生活" | hidestr 50 "*"}}` - result, err = gview.ParseContent(str, nil) + result, err = gview.ParseContent(context.TODO(), str, nil) t.Assert(err != nil, false) t.Assert(result, `热爱GF*爱生活;热爱****生活`) str = `{{"热爱GF热爱生活" | highlight "GF" "red"}}` - result, err = gview.ParseContent(str, nil) + result, err = gview.ParseContent(context.TODO(), str, nil) t.Assert(err != nil, false) t.Assert(result, `热爱<span style="color:red;">GF</span>热爱生活`) str = `{{"gf" | toupper}};{{"GF" | tolower}}` - result, err = gview.ParseContent(str, nil) + result, err = gview.ParseContent(context.TODO(), str, nil) t.Assert(err != nil, false) t.Assert(result, `GF;gf`) str = `{{concat "I" "Love" "GoFrame"}}` - result, err = gview.ParseContent(str, nil) + result, err = gview.ParseContent(context.TODO(), str, nil) t.Assert(err, nil) t.Assert(result, `ILoveGoFrame`) }) + // eq: multiple values. + gtest.C(t, func(t *gtest.T) { + str := `{{eq 1 2 1 3 4 5}}` + result, err := gview.ParseContent(context.TODO(), str, nil) + t.Assert(err != nil, false) + t.Assert(result, `true`) + }) + gtest.C(t, func(t *gtest.T) { + str := `{{eq 6 2 1 3 4 5}}` + result, err := gview.ParseContent(context.TODO(), str, nil) + t.Assert(err != nil, false) + t.Assert(result, `false`) + }) } func Test_FuncNl2Br(t *testing.T) { gtest.C(t, func(t *gtest.T) { str := `{{"Go\nFrame" | nl2br}}` - result, err := gview.ParseContent(str, nil) + result, err := gview.ParseContent(context.TODO(), str, nil) t.Assert(err, nil) t.Assert(result, `Go<br>Frame`) }) @@ -190,7 +205,7 @@ func Test_FuncNl2Br(t *testing.T) { s += "Go\nFrame\n中文" } str := `{{.content | nl2br}}` - result, err := gview.ParseContent(str, g.Map{ + result, err := gview.ParseContent(context.TODO(), str, g.Map{ "content": s, }) t.Assert(err, nil) @@ -218,10 +233,10 @@ func Test_FuncInclude(t *testing.T) { ioutil.WriteFile(templatePath+gfile.Separator+"footer.html", []byte(footer), 0644) ioutil.WriteFile(templatePath+gfile.Separator+"layout.html", []byte(layout), 0644) view := gview.New(templatePath) - result, err := view.Parse("notfound.html") + result, err := view.Parse(context.TODO(), "notfound.html") t.Assert(err != nil, true) t.Assert(result, ``) - result, err = view.Parse("layout.html") + result, err = view.Parse(context.TODO(), "layout.html") t.Assert(err != nil, false) t.Assert(result, `<h1>HEADER</h1> <h1>hello gf</h1> @@ -230,7 +245,7 @@ func Test_FuncInclude(t *testing.T) { gfile.Mkdir(templatePath + gfile.Separator + "template") gfile.Create(notfoundPath) ioutil.WriteFile(notfoundPath, []byte("notfound"), 0644) - result, err = view.Parse("notfound.html") + result, err = view.Parse(context.TODO(), "notfound.html") t.Assert(err != nil, true) t.Assert(result, ``) }) @@ -270,7 +285,7 @@ func Test_ParseContent(t *testing.T) { gtest.C(t, func(t *gtest.T) { str := `{{.name}}` view := gview.New() - result, err := view.ParseContent(str, g.Map{"name": func() {}}) + result, err := view.ParseContent(context.TODO(), str, g.Map{"name": func() {}}) t.Assert(err != nil, true) t.Assert(result, ``) }) @@ -293,7 +308,7 @@ func Test_HotReload(t *testing.T) { view := gview.New(dirPath) time.Sleep(100 * time.Millisecond) - result, err := view.Parse("test.html", g.Map{ + result, err := view.Parse(context.TODO(), "test.html", g.Map{ "var": "1", }) t.Assert(err, nil) @@ -304,7 +319,7 @@ func Test_HotReload(t *testing.T) { t.Assert(err, nil) time.Sleep(100 * time.Millisecond) - result, err = view.Parse("test.html", g.Map{ + result, err = view.Parse(context.TODO(), "test.html", g.Map{ "var": "2", }) t.Assert(err, nil) @@ -316,7 +331,7 @@ func Test_XSS(t *testing.T) { gtest.C(t, func(t *gtest.T) { v := gview.New() s := "<br>" - r, err := v.ParseContent("{{.v}}", g.Map{ + r, err := v.ParseContent(context.TODO(), "{{.v}}", g.Map{ "v": s, }) t.Assert(err, nil) @@ -326,7 +341,7 @@ func Test_XSS(t *testing.T) { v := gview.New() v.SetAutoEncode(true) s := "<br>" - r, err := v.ParseContent("{{.v}}", g.Map{ + r, err := v.ParseContent(context.TODO(), "{{.v}}", g.Map{ "v": s, }) t.Assert(err, nil) @@ -337,7 +352,7 @@ func Test_XSS(t *testing.T) { v := gview.New() v.SetAutoEncode(true) s := "<br>" - r, err := v.ParseContent("{{if eq 1 1}}{{.v}}{{end}}", g.Map{ + r, err := v.ParseContent(context.TODO(), "{{if eq 1 1}}{{.v}}{{end}}", g.Map{ "v": s, }) t.Assert(err, nil) @@ -358,7 +373,7 @@ func Test_BuildInFuncMap(t *testing.T) { gtest.C(t, func(t *gtest.T) { v := gview.New() v.Assign("v", new(TypeForBuildInFuncMap)) - r, err := v.ParseContent("{{range $k, $v := map .v.Test}} {{$k}}:{{$v}} {{end}}") + r, err := v.ParseContent(context.TODO(), "{{range $k, $v := map .v.Test}} {{$k}}:{{$v}} {{end}}") t.Assert(err, nil) t.Assert(gstr.Contains(r, "Name:john"), true) t.Assert(gstr.Contains(r, "Score:99.9"), true) @@ -381,7 +396,7 @@ func Test_BuildInFuncMaps(t *testing.T) { gtest.C(t, func(t *gtest.T) { v := gview.New() v.Assign("v", new(TypeForBuildInFuncMaps)) - r, err := v.ParseContent("{{range $k, $v := maps .v.Test}} {{$k}}:{{$v.Name}} {{$v.Score}} {{end}}") + r, err := v.ParseContent(context.TODO(), "{{range $k, $v := maps .v.Test}} {{$k}}:{{$v.Name}} {{$v.Score}} {{end}}") t.Assert(err, nil) t.Assert(r, ` 0:john 99.9 1:smith 100 `) }) @@ -394,7 +409,7 @@ func Test_BuildInFuncDump(t *testing.T) { "name": "john", "score": 100, }) - r, err := v.ParseContent("{{dump .}}") + r, err := v.ParseContent(context.TODO(), "{{dump .}}") t.Assert(err, nil) t.Assert(gstr.Contains(r, `"name": "john"`), true) t.Assert(gstr.Contains(r, `"score": 100`), true) @@ -407,8 +422,44 @@ func Test_BuildInFuncJson(t *testing.T) { v.Assign("v", g.Map{ "name": "john", }) - r, err := v.ParseContent("{{json .v}}") + r, err := v.ParseContent(context.TODO(), "{{json .v}}") t.Assert(err, nil) t.Assert(r, `{"name":"john"}`) }) } + +func Test_BuildInFuncPlus(t *testing.T) { + gtest.C(t, func(t *gtest.T) { + v := gview.New() + r, err := v.ParseContent(gctx.New(), "{{plus 1 2 3}}") + t.Assert(err, nil) + t.Assert(r, `6`) + }) +} + +func Test_BuildInFuncMinus(t *testing.T) { + gtest.C(t, func(t *gtest.T) { + v := gview.New() + r, err := v.ParseContent(gctx.New(), "{{minus 1 2 3}}") + t.Assert(err, nil) + t.Assert(r, `-4`) + }) +} + +func Test_BuildInFuncTimes(t *testing.T) { + gtest.C(t, func(t *gtest.T) { + v := gview.New() + r, err := v.ParseContent(gctx.New(), "{{times 1 2 3 4}}") + t.Assert(err, nil) + t.Assert(r, `24`) + }) +} + +func Test_BuildInFuncDivide(t *testing.T) { + gtest.C(t, func(t *gtest.T) { + v := gview.New() + r, err := v.ParseContent(gctx.New(), "{{divide 8 2 2}}") + t.Assert(err, nil) + t.Assert(r, `2`) + }) +} diff --git a/os/gview/gview_unit_config_test.go b/os/gview/gview_unit_config_test.go index a19d60701..899245157 100644 --- a/os/gview/gview_unit_config_test.go +++ b/os/gview/gview_unit_config_test.go @@ -1,4 +1,4 @@ -// Copyright 2017 gf Author(https://github.com/gogf/gf). All Rights Reserved. +// Copyright GoFrame Author(https://goframe.org). All Rights Reserved. // // This Source Code Form is subject to the terms of the MIT License. // If a copy of the MIT was not distributed with this file, @@ -7,6 +7,7 @@ package gview_test import ( + "context" "github.com/gogf/gf/debug/gdebug" "github.com/gogf/gf/frame/g" "github.com/gogf/gf/os/gview" @@ -30,11 +31,11 @@ func Test_Config(t *testing.T) { str := `hello ${.name},version:${.version}` view.Assigns(g.Map{"version": "1.7.0"}) - result, err := view.ParseContent(str, nil) + result, err := view.ParseContent(context.TODO(), str, nil) t.Assert(err, nil) t.Assert(result, "hello gf,version:1.7.0") - result, err = view.ParseDefault() + result, err = view.ParseDefault(context.TODO()) t.Assert(err, nil) t.Assert(result, "name:gf") }) @@ -55,11 +56,11 @@ func Test_ConfigWithMap(t *testing.T) { str := `hello ${.name},version:${.version}` view.Assigns(g.Map{"version": "1.7.0"}) - result, err := view.ParseContent(str, nil) + result, err := view.ParseContent(context.TODO(), str, nil) t.Assert(err, nil) t.Assert(result, "hello gf,version:1.7.0") - result, err = view.ParseDefault() + result, err = view.ParseDefault(context.TODO()) t.Assert(err, nil) t.Assert(result, "name:gf") }) diff --git a/os/gview/gview_unit_encode_test.go b/os/gview/gview_unit_encode_test.go index d3d9efea6..892be705b 100644 --- a/os/gview/gview_unit_encode_test.go +++ b/os/gview/gview_unit_encode_test.go @@ -1,4 +1,4 @@ -// Copyright 2020 gf Author(https://github.com/gogf/gf). All Rights Reserved. +// Copyright GoFrame Author(https://goframe.org). All Rights Reserved. // // This Source Code Form is subject to the terms of the MIT License. // If a copy of the MIT was not distributed with this file, @@ -7,6 +7,7 @@ package gview_test import ( + "context" "github.com/gogf/gf/debug/gdebug" "github.com/gogf/gf/frame/g" "github.com/gogf/gf/os/gfile" @@ -20,7 +21,7 @@ func Test_Encode_Parse(t *testing.T) { v := gview.New() v.SetPath(gdebug.TestDataPath("tpl")) v.SetAutoEncode(true) - result, err := v.Parse("encode.tpl", g.Map{ + result, err := v.Parse(context.TODO(), "encode.tpl", g.Map{ "title": "<b>my title</b>", }) t.Assert(err, nil) @@ -33,7 +34,7 @@ func Test_Encode_ParseContent(t *testing.T) { v := gview.New() tplContent := gfile.GetContents(gdebug.TestDataPath("tpl", "encode.tpl")) v.SetAutoEncode(true) - result, err := v.ParseContent(tplContent, g.Map{ + result, err := v.ParseContent(context.TODO(), tplContent, g.Map{ "title": "<b>my title</b>", }) t.Assert(err, nil) diff --git a/os/gview/gview_unit_i18n_test.go b/os/gview/gview_unit_i18n_test.go index 216e1309a..23550e66c 100644 --- a/os/gview/gview_unit_i18n_test.go +++ b/os/gview/gview_unit_i18n_test.go @@ -1,4 +1,4 @@ -// Copyright 2019 gf Author(https://github.com/gogf/gf). All Rights Reserved. +// Copyright GoFrame Author(https://goframe.org). All Rights Reserved. // // This Source Code Form is subject to the terms of the MIT License. // If a copy of the MIT was not distributed with this file, @@ -7,6 +7,7 @@ package gview_test import ( + "context" "testing" "github.com/gogf/gf/debug/gdebug" @@ -26,21 +27,21 @@ func Test_I18n(t *testing.T) { g.I18n().SetPath(gdebug.TestDataPath("i18n")) g.I18n().SetLanguage("zh-CN") - result1, err := g.View().ParseContent(content, g.Map{ + result1, err := g.View().ParseContent(context.TODO(), content, g.Map{ "name": "john", }) t.Assert(err, nil) t.Assert(result1, expect1) g.I18n().SetLanguage("ja") - result2, err := g.View().ParseContent(content, g.Map{ + result2, err := g.View().ParseContent(context.TODO(), content, g.Map{ "name": "john", }) t.Assert(err, nil) t.Assert(result2, expect2) g.I18n().SetLanguage("none") - result3, err := g.View().ParseContent(content, g.Map{ + result3, err := g.View().ParseContent(context.TODO(), content, g.Map{ "name": "john", }) t.Assert(err, nil) @@ -54,21 +55,21 @@ func Test_I18n(t *testing.T) { g.I18n().SetPath(gdebug.CallerDirectory() + gfile.Separator + "testdata" + gfile.Separator + "i18n") - result1, err := g.View().ParseContent(content, g.Map{ + result1, err := g.View().ParseContent(context.TODO(), content, g.Map{ "name": "john", "I18nLanguage": "zh-CN", }) t.Assert(err, nil) t.Assert(result1, expect1) - result2, err := g.View().ParseContent(content, g.Map{ + result2, err := g.View().ParseContent(context.TODO(), content, g.Map{ "name": "john", "I18nLanguage": "ja", }) t.Assert(err, nil) t.Assert(result2, expect2) - result3, err := g.View().ParseContent(content, g.Map{ + result3, err := g.View().ParseContent(context.TODO(), content, g.Map{ "name": "john", "I18nLanguage": "none", }) diff --git a/test/gtest/gtest.go b/test/gtest/gtest.go index e5464b3b0..b8c0273bb 100644 --- a/test/gtest/gtest.go +++ b/test/gtest/gtest.go @@ -1,4 +1,4 @@ -// Copyright 2018 gf Author(https://github.com/gogf/gf). All Rights Reserved. +// Copyright GoFrame Author(https://goframe.org). All Rights Reserved. // // This Source Code Form is subject to the terms of the MIT License. // If a copy of the MIT was not distributed with this file, diff --git a/test/gtest/gtest_t.go b/test/gtest/gtest_t.go index 06c677227..4ce3dae8e 100644 --- a/test/gtest/gtest_t.go +++ b/test/gtest/gtest_t.go @@ -1,4 +1,4 @@ -// Copyright 2018 gf Author(https://github.com/gogf/gf). All Rights Reserved. +// Copyright GoFrame Author(https://goframe.org). All Rights Reserved. // // This Source Code Form is subject to the terms of the MIT License. // If a copy of the MIT was not distributed with this file, @@ -15,74 +15,79 @@ type T struct { *testing.T } -// Assert checks <value> and <expect> EQUAL. +// Assert checks `value` and `expect` EQUAL. func (t *T) Assert(value, expect interface{}) { Assert(value, expect) } -// AssertEQ checks <value> and <expect> EQUAL, including their TYPES. +// AssertEQ checks `value` and `expect` EQUAL, including their TYPES. func (t *T) AssertEQ(value, expect interface{}) { AssertEQ(value, expect) } -// AssertNE checks <value> and <expect> NOT EQUAL. +// AssertNE checks `value` and `expect` NOT EQUAL. func (t *T) AssertNE(value, expect interface{}) { AssertNE(value, expect) } -// AssertNQ checks <value> and <expect> NOT EQUAL, including their TYPES. +// AssertNQ checks `value` and `expect` NOT EQUAL, including their TYPES. func (t *T) AssertNQ(value, expect interface{}) { AssertNQ(value, expect) } -// AssertGT checks <value> is GREATER THAN <expect>. +// AssertGT checks `value` is GREATER THAN `expect`. // Notice that, only string, integer and float types can be compared by AssertGT, // others are invalid. func (t *T) AssertGT(value, expect interface{}) { AssertGT(value, expect) } -// AssertGE checks <value> is GREATER OR EQUAL THAN <expect>. +// AssertGE checks `value` is GREATER OR EQUAL THAN `expect`. // Notice that, only string, integer and float types can be compared by AssertGTE, // others are invalid. func (t *T) AssertGE(value, expect interface{}) { AssertGE(value, expect) } -// AssertLT checks <value> is LESS EQUAL THAN <expect>. +// AssertLT checks `value` is LESS EQUAL THAN `expect`. // Notice that, only string, integer and float types can be compared by AssertLT, // others are invalid. func (t *T) AssertLT(value, expect interface{}) { AssertLT(value, expect) } -// AssertLE checks <value> is LESS OR EQUAL THAN <expect>. +// AssertLE checks `value` is LESS OR EQUAL THAN `expect`. // Notice that, only string, integer and float types can be compared by AssertLTE, // others are invalid. func (t *T) AssertLE(value, expect interface{}) { AssertLE(value, expect) } -// AssertIN checks <value> is IN <expect>. -// The <expect> should be a slice, -// but the <value> can be a slice or a basic type variable. +// AssertIN checks `value` is IN `expect`. +// The `expect` should be a slice, +// but the `value` can be a slice or a basic type variable. func (t *T) AssertIN(value, expect interface{}) { AssertIN(value, expect) } -// AssertNI checks <value> is NOT IN <expect>. -// The <expect> should be a slice, -// but the <value> can be a slice or a basic type variable. +// AssertNI checks `value` is NOT IN `expect`. +// The `expect` should be a slice, +// but the `value` can be a slice or a basic type variable. func (t *T) AssertNI(value, expect interface{}) { AssertNI(value, expect) } -// Error panics with given <message>. +// AssertNil asserts `value` is nil. +func (t *T) AssertNil(value interface{}) { + AssertNil(value) +} + +// Error panics with given `message`. func (t *T) Error(message ...interface{}) { Error(message...) } -// Fatal prints <message> to stderr and exit the process. +// Fatal prints `message` to stderr and exit the process. func (t *T) Fatal(message ...interface{}) { Fatal(message...) } diff --git a/test/gtest/gtest_util.go b/test/gtest/gtest_util.go index 701d8250c..a1644afa2 100644 --- a/test/gtest/gtest_util.go +++ b/test/gtest/gtest_util.go @@ -1,4 +1,4 @@ -// Copyright 2018 gf Author(https://github.com/gogf/gf). All Rights Reserved. +// Copyright GoFrame Author(https://goframe.org). All Rights Reserved. // // This Source Code Form is subject to the terms of the MIT License. // If a copy of the MIT was not distributed with this file, @@ -8,6 +8,7 @@ package gtest import ( "fmt" + "github.com/gogf/gf/internal/empty" "os" "reflect" "testing" @@ -18,16 +19,16 @@ import ( ) const ( - gPATH_FILTER_KEY = "/test/gtest/gtest" + pathFilterKey = "/test/gtest/gtest" ) // C creates an unit testing case. -// The parameter <t> is the pointer to testing.T of stdlib (*testing.T). -// The parameter <f> is the closure function for unit testing case. +// The parameter `t` is the pointer to testing.T of stdlib (*testing.T). +// The parameter `f` is the closure function for unit testing case. func C(t *testing.T, f func(t *T)) { defer func() { if err := recover(); err != nil { - fmt.Fprintf(os.Stderr, "%v\n%s", err, gdebug.StackWithFilter(gPATH_FILTER_KEY)) + fmt.Fprintf(os.Stderr, "%v\n%s", err, gdebug.StackWithFilter(pathFilterKey)) t.Fail() } }() @@ -35,23 +36,23 @@ func C(t *testing.T, f func(t *T)) { } // Case creates an unit testing case. -// The parameter <t> is the pointer to testing.T of stdlib (*testing.T). -// The parameter <f> is the closure function for unit testing case. +// The parameter `t` is the pointer to testing.T of stdlib (*testing.T). +// The parameter `f` is the closure function for unit testing case. // Deprecated. func Case(t *testing.T, f func()) { defer func() { if err := recover(); err != nil { - fmt.Fprintf(os.Stderr, "%v\n%s", err, gdebug.StackWithFilter(gPATH_FILTER_KEY)) + fmt.Fprintf(os.Stderr, "%v\n%s", err, gdebug.StackWithFilter(pathFilterKey)) t.Fail() } }() f() } -// Assert checks <value> and <expect> EQUAL. +// Assert checks `value` and `expect` EQUAL. func Assert(value, expect interface{}) { rvExpect := reflect.ValueOf(expect) - if isNil(value) { + if empty.IsNil(value) { value = nil } if rvExpect.Kind() == reflect.Map { @@ -69,11 +70,11 @@ func Assert(value, expect interface{}) { } } -// AssertEQ checks <value> and <expect> EQUAL, including their TYPES. +// AssertEQ checks `value` and `expect` EQUAL, including their TYPES. func AssertEQ(value, expect interface{}) { // Value assert. rvExpect := reflect.ValueOf(expect) - if isNil(value) { + if empty.IsNil(value) { value = nil } if rvExpect.Kind() == reflect.Map { @@ -95,10 +96,10 @@ func AssertEQ(value, expect interface{}) { } } -// AssertNE checks <value> and <expect> NOT EQUAL. +// AssertNE checks `value` and `expect` NOT EQUAL. func AssertNE(value, expect interface{}) { rvExpect := reflect.ValueOf(expect) - if isNil(value) { + if empty.IsNil(value) { value = nil } if rvExpect.Kind() == reflect.Map { @@ -116,7 +117,7 @@ func AssertNE(value, expect interface{}) { } } -// AssertNQ checks <value> and <expect> NOT EQUAL, including their TYPES. +// AssertNQ checks `value` and `expect` NOT EQUAL, including their TYPES. func AssertNQ(value, expect interface{}) { // Type assert. t1 := reflect.TypeOf(value) @@ -133,7 +134,7 @@ func AssertNQ(value, expect interface{}) { AssertNE(value, expect) } -// AssertGT checks <value> is GREATER THAN <expect>. +// AssertGT checks `value` is GREATER THAN `expect`. // Notice that, only string, integer and float types can be compared by AssertGT, // others are invalid. func AssertGT(value, expect interface{}) { @@ -156,7 +157,7 @@ func AssertGT(value, expect interface{}) { } } -// AssertGE checks <value> is GREATER OR EQUAL THAN <expect>. +// AssertGE checks `value` is GREATER OR EQUAL THAN `expect`. // Notice that, only string, integer and float types can be compared by AssertGTE, // others are invalid. func AssertGE(value, expect interface{}) { @@ -183,7 +184,7 @@ func AssertGE(value, expect interface{}) { } } -// AssertLT checks <value> is LESS EQUAL THAN <expect>. +// AssertLT checks `value` is LESS EQUAL THAN `expect`. // Notice that, only string, integer and float types can be compared by AssertLT, // others are invalid. func AssertLT(value, expect interface{}) { @@ -206,7 +207,7 @@ func AssertLT(value, expect interface{}) { } } -// AssertLE checks <value> is LESS OR EQUAL THAN <expect>. +// AssertLE checks `value` is LESS OR EQUAL THAN `expect`. // Notice that, only string, integer and float types can be compared by AssertLTE, // others are invalid. func AssertLE(value, expect interface{}) { @@ -229,13 +230,15 @@ func AssertLE(value, expect interface{}) { } } -// AssertIN checks <value> is IN <expect>. -// The <expect> should be a slice, -// but the <value> can be a slice or a basic type variable. +// AssertIN checks `value` is IN `expect`. +// The `expect` should be a slice, +// but the `value` can be a slice or a basic type variable. // TODO map support. func AssertIN(value, expect interface{}) { - passed := true - expectKind := reflect.ValueOf(expect).Kind() + var ( + passed = true + expectKind = reflect.ValueOf(expect).Kind() + ) switch expectKind { case reflect.Slice, reflect.Array: expectSlice := gconv.Strings(expect) @@ -260,13 +263,15 @@ func AssertIN(value, expect interface{}) { } } -// AssertNI checks <value> is NOT IN <expect>. -// The <expect> should be a slice, -// but the <value> can be a slice or a basic type variable. +// AssertNI checks `value` is NOT IN `expect`. +// The `expect` should be a slice, +// but the `value` can be a slice or a basic type variable. // TODO map support. func AssertNI(value, expect interface{}) { - passed := true - expectKind := reflect.ValueOf(expect).Kind() + var ( + passed = true + expectKind = reflect.ValueOf(expect).Kind() + ) switch expectKind { case reflect.Slice, reflect.Array: for _, v1 := range gconv.Strings(value) { @@ -290,22 +295,24 @@ func AssertNI(value, expect interface{}) { } } -// Error panics with given <message>. +// Error panics with given `message`. func Error(message ...interface{}) { panic(fmt.Sprintf("[ERROR] %s", fmt.Sprint(message...))) } -// Fatal prints <message> to stderr and exit the process. +// Fatal prints `message` to stderr and exit the process. func Fatal(message ...interface{}) { - fmt.Fprintf(os.Stderr, "[FATAL] %s\n%s", fmt.Sprint(message...), gdebug.StackWithFilter(gPATH_FILTER_KEY)) + fmt.Fprintf(os.Stderr, "[FATAL] %s\n%s", fmt.Sprint(message...), gdebug.StackWithFilter(pathFilterKey)) os.Exit(1) } // compareMap compares two maps, returns nil if they are equal, or else returns error. func compareMap(value, expect interface{}) error { - rvValue := reflect.ValueOf(value) - rvExpect := reflect.ValueOf(expect) - if isNil(value) { + var ( + rvValue = reflect.ValueOf(value) + rvExpect = reflect.ValueOf(expect) + ) + if empty.IsNil(value) { value = nil } if rvExpect.Kind() == reflect.Map { @@ -334,19 +341,19 @@ func compareMap(value, expect interface{}) error { return fmt.Errorf(`[ASSERT] EXPECT MAP LENGTH %d == %d`, rvValue.Len(), rvExpect.Len()) } } else { - return fmt.Errorf(`[ASSERT] EXPECT VALUE TO BE A MAP`) + return fmt.Errorf(`[ASSERT] EXPECT VALUE TO BE A MAP, BUT GIVEN "%s"`, rvValue.Kind()) } } return nil } -// isNil checks whether <value> is nil. -func isNil(value interface{}) bool { - rv := reflect.ValueOf(value) - switch rv.Kind() { - case reflect.Slice, reflect.Array, reflect.Map, reflect.Ptr, reflect.Func: - return rv.IsNil() - default: - return value == nil +// AssertNil asserts `value` is nil. +func AssertNil(value interface{}) { + if empty.IsNil(value) { + return } + if err, ok := value.(error); ok { + panic(fmt.Sprintf(`%+v`, err)) + } + AssertNE(value, nil) } diff --git a/test/gtest/gtest_z_unit_test.go b/test/gtest/gtest_z_unit_test.go index 0f5b756cd..db74e97f0 100644 --- a/test/gtest/gtest_z_unit_test.go +++ b/test/gtest/gtest_z_unit_test.go @@ -1,4 +1,4 @@ -// Copyright 2018 gf Author(https://github.com/gogf/gf). All Rights Reserved. +// Copyright GoFrame Author(https://goframe.org). All Rights Reserved. // // This Source Code Form is subject to the terms of the MIT License. // If a copy of the MIT was not distributed with this file, diff --git a/text/gregex/gregex.go b/text/gregex/gregex.go index 8ad0292ee..4f77bf356 100644 --- a/text/gregex/gregex.go +++ b/text/gregex/gregex.go @@ -1,4 +1,4 @@ -// Copyright 2017-2018 gf Author(https://github.com/gogf/gf). All Rights Reserved. +// Copyright GoFrame Author(https://goframe.org). All Rights Reserved. // // This Source Code Form is subject to the terms of the MIT License. // If a copy of the MIT was not distributed with this file, diff --git a/text/gregex/gregex_cache.go b/text/gregex/gregex_cache.go index 1b4913782..ebce7e8e4 100644 --- a/text/gregex/gregex_cache.go +++ b/text/gregex/gregex_cache.go @@ -1,4 +1,4 @@ -// Copyright 2019 gf Author(https://github.com/gogf/gf). All Rights Reserved. +// Copyright GoFrame Author(https://goframe.org). All Rights Reserved. // // This Source Code Form is subject to the terms of the MIT License. // If a copy of the MIT was not distributed with this file, diff --git a/text/gregex/gregex_z_bench_test.go b/text/gregex/gregex_z_bench_test.go index e4c9f9339..3e51ffc6f 100644 --- a/text/gregex/gregex_z_bench_test.go +++ b/text/gregex/gregex_z_bench_test.go @@ -1,4 +1,4 @@ -// Copyright 2017-2018 gf Author(https://github.com/gogf/gf). All Rights Reserved. +// Copyright GoFrame Author(https://goframe.org). All Rights Reserved. // // This Source Code Form is subject to the terms of the MIT License. // If a copy of the MIT was not distributed with this file, diff --git a/text/gregex/gregex_z_unit_test.go b/text/gregex/gregex_z_unit_test.go index a9b132b45..81ef93706 100644 --- a/text/gregex/gregex_z_unit_test.go +++ b/text/gregex/gregex_z_unit_test.go @@ -1,4 +1,4 @@ -// Copyright 2019 gf Author(https://github.com/gogf/gf). All Rights Reserved. +// Copyright GoFrame Author(https://goframe.org). All Rights Reserved. // // This Source Code Form is subject to the terms of the MIT License. // If a copy of the MIT was not distributed with this file, diff --git a/text/gstr/gstr.go b/text/gstr/gstr.go index 4f8607e17..4a81426d4 100644 --- a/text/gstr/gstr.go +++ b/text/gstr/gstr.go @@ -1,4 +1,4 @@ -// Copyright 2018 gf Author(https://github.com/gogf/gf). All Rights Reserved. +// Copyright GoFrame Author(https://goframe.org). All Rights Reserved. // // This Source Code Form is subject to the terms of the MIT License. // If a copy of the MIT was not distributed with this file, @@ -23,93 +23,27 @@ import ( "github.com/gogf/gf/util/grand" ) -// Replace returns a copy of the string <origin> -// in which string <search> replaced by <replace> case-sensitively. -func Replace(origin, search, replace string, count ...int) string { - n := -1 - if len(count) > 0 { - n = count[0] - } - return strings.Replace(origin, search, replace, n) -} +const ( + // NotFoundIndex is the position index for string not found in searching functions. + NotFoundIndex = -1 +) -// Replace returns a copy of the string <origin> -// in which string <search> replaced by <replace> case-insensitively. -func ReplaceI(origin, search, replace string, count ...int) string { - n := -1 - if len(count) > 0 { - n = count[0] - } - if n == 0 { - return origin - } - length := len(search) - searchLower := strings.ToLower(search) - for { - originLower := strings.ToLower(origin) - if pos := strings.Index(originLower, searchLower); pos != -1 { - origin = origin[:pos] + replace + origin[pos+length:] - if n--; n == 0 { - break - } - } else { - break - } - } - return origin -} +const ( + defaultSuffixForStrLimit = "..." +) -// Count counts the number of <substr> appears in <s>. -// It returns 0 if no <substr> found in <s>. +// Count counts the number of `substr` appears in `s`. +// It returns 0 if no `substr` found in `s`. func Count(s, substr string) int { return strings.Count(s, substr) } -// CountI counts the number of <substr> appears in <s>, case-insensitively. -// It returns 0 if no <substr> found in <s>. +// CountI counts the number of `substr` appears in `s`, case-insensitively. +// It returns 0 if no `substr` found in `s`. func CountI(s, substr string) int { return strings.Count(ToLower(s), ToLower(substr)) } -// ReplaceByArray returns a copy of <origin>, -// which is replaced by a slice in order, case-sensitively. -func ReplaceByArray(origin string, array []string) string { - for i := 0; i < len(array); i += 2 { - if i+1 >= len(array) { - break - } - origin = Replace(origin, array[i], array[i+1]) - } - return origin -} - -// ReplaceIByArray returns a copy of <origin>, -// which is replaced by a slice in order, case-insensitively. -func ReplaceIByArray(origin string, array []string) string { - for i := 0; i < len(array); i += 2 { - if i+1 >= len(array) { - break - } - origin = ReplaceI(origin, array[i], array[i+1]) - } - return origin -} - -// ReplaceByMap returns a copy of <origin>, -// which is replaced by a map in unordered way, case-sensitively. -func ReplaceByMap(origin string, replaces map[string]string) string { - return utils.ReplaceByMap(origin, replaces) -} - -// ReplaceIByMap returns a copy of <origin>, -// which is replaced by a map in unordered way, case-insensitively. -func ReplaceIByMap(origin string, replaces map[string]string) string { - for k, v := range replaces { - origin = ReplaceI(origin, k, v) - } - return origin -} - // ToLower returns a copy of the string s with all Unicode letters mapped to their lower case. func ToLower(s string) string { return strings.ToLower(s) @@ -156,86 +90,7 @@ func IsNumeric(s string) bool { return utils.IsNumeric(s) } -// SubStr returns a portion of string <str> specified by the <start> and <length> parameters. -func SubStr(str string, start int, length ...int) (substr string) { - lth := len(str) - - // Simple border checks. - if start < 0 { - start = 0 - } - if start >= lth { - start = lth - } - end := lth - if len(length) > 0 { - end = start + length[0] - if end < start { - end = lth - } - } - if end > lth { - end = lth - } - return str[start:end] -} - -// SubStrRune returns a portion of string <str> specified by the <start> and <length> parameters. -// SubStrRune considers parameter <str> as unicode string. -func SubStrRune(str string, start int, length ...int) (substr string) { - // Converting to []rune to support unicode. - rs := []rune(str) - lth := len(rs) - - // Simple border checks. - if start < 0 { - start = 0 - } - if start >= lth { - start = lth - } - end := lth - if len(length) > 0 { - end = start + length[0] - if end < start { - end = lth - } - } - if end > lth { - end = lth - } - return string(rs[start:end]) -} - -// StrLimit returns a portion of string <str> specified by <length> parameters, if the length -// of <str> is greater than <length>, then the <suffix> will be appended to the result string. -func StrLimit(str string, length int, suffix ...string) string { - if len(str) < length { - return str - } - addStr := "..." - if len(suffix) > 0 { - addStr = suffix[0] - } - return str[0:length] + addStr -} - -// StrLimitRune returns a portion of string <str> specified by <length> parameters, if the length -// of <str> is greater than <length>, then the <suffix> will be appended to the result string. -// StrLimitRune considers parameter <str> as unicode string. -func StrLimitRune(str string, length int, suffix ...string) string { - rs := []rune(str) - if len(rs) < length { - return str - } - addStr := "..." - if len(suffix) > 0 { - addStr = suffix[0] - } - return string(rs[0:length]) + addStr -} - -// Reverse returns a string which is the reverse of <str>. +// Reverse returns a string which is the reverse of `str`. func Reverse(str string) string { runes := []rune(str) for i, j := 0, len(runes)-1; i < j; i, j = i+1, j-1 { @@ -245,9 +100,9 @@ func Reverse(str string) string { } // NumberFormat formats a number with grouped thousands. -// <decimals>: Sets the number of decimal points. -// <decPoint>: Sets the separator for the decimal point. -// <thousandsSep>: Sets the thousands separator. +// `decimals`: Sets the number of decimal points. +// `decPoint`: Sets the separator for the decimal point. +// `thousandsSep`: Sets the thousands' separator. // See http://php.net/manual/en/function.number-format.php. func NumberFormat(number float64, decimals int, decPoint, thousandsSep string) string { neg := false @@ -294,7 +149,7 @@ func NumberFormat(number float64, decimals int, decPoint, thousandsSep string) s // Can be used to split a string into smaller chunks which is useful for // e.g. converting BASE64 string output to match RFC 2045 semantics. // It inserts end every chunkLen characters. -// It considers parameter <body> and <end> as unicode string. +// It considers parameter `body` and `end` as unicode string. func ChunkSplit(body string, chunkLen int, end string) string { if end == "" { end = "\r\n" @@ -322,7 +177,7 @@ func Compare(a, b string) int { return strings.Compare(a, b) } -// Equal reports whether <a> and <b>, interpreted as UTF-8 strings, +// Equal reports whether `a` and `b`, interpreted as UTF-8 strings, // are equal under Unicode case-folding, case-insensitively. func Equal(a, b string) bool { return strings.EqualFold(a, b) @@ -344,7 +199,7 @@ func HasSuffix(s, suffix string) bool { } // CountWords returns information about words' count used in a string. -// It considers parameter <str> as unicode string. +// It considers parameter `str` as unicode string. func CountWords(str string) map[string]int { m := make(map[string]int) buffer := bytes.NewBuffer(nil) @@ -365,7 +220,7 @@ func CountWords(str string) map[string]int { } // CountChars returns information about chars' count used in a string. -// It considers parameter <str> as unicode string. +// It considers parameter `str` as unicode string. func CountChars(str string, noSpace ...bool) map[string]int { m := make(map[string]int) countSpace := true @@ -458,22 +313,8 @@ func Repeat(input string, multiplier int) string { return strings.Repeat(input, multiplier) } -// Str returns part of <haystack> string starting from and including -// the first occurrence of <needle> to the end of <haystack>. -// See http://php.net/manual/en/function.strstr.php. -func Str(haystack string, needle string) string { - if needle == "" { - return "" - } - idx := strings.Index(haystack, needle) - if idx == -1 { - return "" - } - return haystack[idx+len([]byte(needle))-1:] -} - // Shuffle randomly shuffles a string. -// It considers parameter <str> as unicode string. +// It considers parameter `str` as unicode string. func Shuffle(str string) string { runes := []rune(str) s := make([]rune, len(runes)) @@ -483,12 +324,12 @@ func Shuffle(str string) string { return string(s) } -// Split splits string <str> by a string <delimiter>, to an array. +// Split splits string `str` by a string `delimiter`, to an array. func Split(str, delimiter string) []string { return strings.Split(str, delimiter) } -// SplitAndTrim splits string <str> by a string <delimiter> to an array, +// SplitAndTrim splits string `str` by a string `delimiter` to an array, // and calls Trim to every element of this array. It ignores the elements // which are empty after Trim. func SplitAndTrim(str, delimiter string, characterMask ...string) []string { @@ -502,9 +343,9 @@ func SplitAndTrim(str, delimiter string, characterMask ...string) []string { return array } -// SplitAndTrimSpace splits string <str> by a string <delimiter> to an array, +// SplitAndTrimSpace splits string `str` by a string `delimiter` to an array, // and calls TrimSpace to every element of this array. -// Deprecated. +// Deprecated, use SplitAndTrim instead. func SplitAndTrimSpace(str, delimiter string) []string { array := make([]string, 0) for _, v := range strings.Split(str, delimiter) { @@ -516,27 +357,27 @@ func SplitAndTrimSpace(str, delimiter string) []string { return array } -// Join concatenates the elements of <array> to create a single string. The separator string -// <sep> is placed between elements in the resulting string. +// Join concatenates the elements of `array` to create a single string. The separator string +// `sep` is placed between elements in the resulting string. func Join(array []string, sep string) string { return strings.Join(array, sep) } -// JoinAny concatenates the elements of <array> to create a single string. The separator string -// <sep> is placed between elements in the resulting string. +// JoinAny concatenates the elements of `array` to create a single string. The separator string +// `sep` is placed between elements in the resulting string. // -// The parameter <array> can be any type of slice, which be converted to string array. +// The parameter `array` can be any type of slice, which be converted to string array. func JoinAny(array interface{}, sep string) string { return strings.Join(gconv.Strings(array), sep) } -// Explode splits string <str> by a string <delimiter>, to an array. +// Explode splits string `str` by a string `delimiter`, to an array. // See http://php.net/manual/en/function.explode.php. func Explode(delimiter, str string) []string { return Split(str, delimiter) } -// Implode joins array elements <pieces> with a string <glue>. +// Implode joins array elements `pieces` with a string `glue`. // http://php.net/manual/en/function.implode.php func Implode(glue string, pieces []string) string { return strings.Join(pieces, glue) @@ -552,8 +393,8 @@ func Ord(char string) int { return int(char[0]) } -// HideStr replaces part of the the string <str> to <hide> by <percentage> from the <middle>. -// It considers parameter <str> as unicode string. +// HideStr replaces part of the string `str` to `hide` by `percentage` from the `middle`. +// It considers parameter `str` as unicode string. func HideStr(str string, percent int, hide string) string { array := strings.Split(str, "@") if len(array) > 1 { @@ -583,7 +424,7 @@ func HideStr(str string, percent int, hide string) string { // Nl2Br inserts HTML line breaks(<br>|<br />) before all newlines in a string: // \n\r, \r\n, \r, \n. -// It considers parameter <str> as unicode string. +// It considers parameter `str` as unicode string. func Nl2Br(str string, isXhtml ...bool) string { r, n, runes := '\r', '\n', []rune(str) var br []byte @@ -669,19 +510,19 @@ func QuoteMeta(str string, chars ...string) string { return buf.String() } -// SearchArray searches string <s> in string slice <a> case-sensitively, -// returns its index in <a>. -// If <s> is not found in <a>, it returns -1. +// SearchArray searches string `s` in string slice `a` case-sensitively, +// returns its index in `a`. +// If `s` is not found in `a`, it returns -1. func SearchArray(a []string, s string) int { for i, v := range a { if s == v { return i } } - return -1 + return NotFoundIndex } -// InArray checks whether string <s> in slice <a>. +// InArray checks whether string `s` in slice `a`. func InArray(a []string, s string) bool { - return SearchArray(a, s) != -1 + return SearchArray(a, s) != NotFoundIndex } diff --git a/text/gstr/gstr_case.go b/text/gstr/gstr_case.go index e472f9055..a720beea0 100644 --- a/text/gstr/gstr_case.go +++ b/text/gstr/gstr_case.go @@ -1,4 +1,4 @@ -// Copyright 2019 gf Author(https://github.com/gogf/gf). All Rights Reserved. +// Copyright GoFrame Author(https://goframe.org). All Rights Reserved. // // This Source Code Form is subject to the terms of the MIT License. // If a copy of the MIT was not distributed with this file, @@ -6,15 +6,15 @@ // // | Function | Result | // |-----------------------------------|--------------------| -// | SnakeCase(s) | any_kind_of_string | -// | SnakeScreamingCase(s) | ANY_KIND_OF_STRING | -// | KebabCase(s) | any-kind-of-string | -// | KebabScreamingCase(s) | ANY-KIND-OF-STRING | -// | DelimitedCase(s, '.') | any.kind.of.string | -// | DelimitedScreamingCase(s, '.') | ANY.KIND.OF.STRING | -// | CamelCase(s) | AnyKindOfString | -// | CamelLowerCase(s) | anyKindOfString | -// | SnakeFirstUpperCase(RGBCodeMd5) | rgb_code_md5 | +// | CaseSnake(s) | any_kind_of_string | +// | CaseSnakeScreaming(s) | ANY_KIND_OF_STRING | +// | CaseSnakeFirstUpper("RGBCodeMd5") | rgb_code_md5 | +// | CaseKebab(s) | any-kind-of-string | +// | CaseKebabScreaming(s) | ANY-KIND-OF-STRING | +// | CaseDelimited(s, '.') | any.kind.of.string | +// | CaseDelimitedScreaming(s, '.') | ANY.KIND.OF.STRING | +// | CaseCamel(s) | AnyKindOfString | +// | CaseCamelLower(s) | anyKindOfString | package gstr @@ -30,12 +30,24 @@ var ( ) // CamelCase converts a string to CamelCase. +// Deprecated, use CaseCamel instead. func CamelCase(s string) string { + return CaseCamel(s) +} + +// CaseCamel converts a string to CamelCase. +func CaseCamel(s string) string { return toCamelInitCase(s, true) } // CamelLowerCase converts a string to lowerCamelCase. +// Deprecated, use CaseCamelLower instead. func CamelLowerCase(s string) string { + return CaseCamelLower(s) +} + +// CaseCamelLower converts a string to lowerCamelCase. +func CaseCamelLower(s string) string { if s == "" { return s } @@ -46,19 +58,37 @@ func CamelLowerCase(s string) string { } // SnakeCase converts a string to snake_case. +// Deprecated, use CaseSnake instead. func SnakeCase(s string) string { + return CaseSnake(s) +} + +// CaseSnake converts a string to snake_case. +func CaseSnake(s string) string { return DelimitedCase(s, '_') } // SnakeScreamingCase converts a string to SNAKE_CASE_SCREAMING. +// Deprecated, use CaseSnakeScreaming instead. func SnakeScreamingCase(s string) string { - return DelimitedScreamingCase(s, '_', true) + return CaseSnakeScreaming(s) +} + +// CaseSnakeScreaming converts a string to SNAKE_CASE_SCREAMING. +func CaseSnakeScreaming(s string) string { + return CaseDelimitedScreaming(s, '_', true) } // SnakeFirstUpperCase converts a string from RGBCodeMd5 to rgb_code_md5. // The length of word should not be too long -// TODO for efficiency should change regexp to traversing string in future +// Deprecated, use CaseSnakeFirstUpper instead. func SnakeFirstUpperCase(word string, underscore ...string) string { + return CaseSnakeFirstUpper(word, underscore...) +} + +// CaseSnakeFirstUpper converts a string like "RGBCodeMd5" to "rgb_code_md5". +// TODO for efficiency should change regexp to traversing string in future. +func CaseSnakeFirstUpper(word string, underscore ...string) string { replace := "_" if len(underscore) > 0 { replace = underscore[0] @@ -73,7 +103,7 @@ func SnakeFirstUpperCase(word string, underscore ...string) string { m := firstCamelCaseStart.FindAllStringSubmatch(word, 1) if len(m) > 0 && m[0][1] != "" { w := strings.ToLower(m[0][1]) - w = string(w[:len(w)-1]) + replace + string(w[len(w)-1]) + w = w[:len(w)-1] + replace + string(w[len(w)-1]) word = strings.Replace(word, m[0][1], w, 1) } else { @@ -84,23 +114,47 @@ func SnakeFirstUpperCase(word string, underscore ...string) string { return TrimLeft(word, replace) } -// KebabCase converts a string to kebab-case +// KebabCase converts a string to kebab-case. +// Deprecated, use CaseKebab instead. func KebabCase(s string) string { - return DelimitedCase(s, '-') + return CaseKebab(s) +} + +// CaseKebab converts a string to kebab-case +func CaseKebab(s string) string { + return CaseDelimited(s, '-') } // KebabScreamingCase converts a string to KEBAB-CASE-SCREAMING. +// Deprecated, use CaseKebabScreaming instead. func KebabScreamingCase(s string) string { - return DelimitedScreamingCase(s, '-', true) + return CaseKebabScreaming(s) +} + +// CaseKebabScreaming converts a string to KEBAB-CASE-SCREAMING. +func CaseKebabScreaming(s string) string { + return CaseDelimitedScreaming(s, '-', true) } // DelimitedCase converts a string to snake.case.delimited. +// Deprecated, use CaseDelimited instead. func DelimitedCase(s string, del uint8) string { - return DelimitedScreamingCase(s, del, false) + return CaseDelimited(s, del) +} + +// CaseDelimited converts a string to snake.case.delimited. +func CaseDelimited(s string, del uint8) string { + return CaseDelimitedScreaming(s, del, false) } // DelimitedScreamingCase converts a string to DELIMITED.SCREAMING.CASE or delimited.screaming.case. +// Deprecated, use CaseDelimitedScreaming instead. func DelimitedScreamingCase(s string, del uint8, screaming bool) string { + return CaseDelimitedScreaming(s, del, screaming) +} + +// CaseDelimitedScreaming converts a string to DELIMITED.SCREAMING.CASE or delimited.screaming.case. +func CaseDelimitedScreaming(s string, del uint8, screaming bool) string { s = addWordBoundariesToNumbers(s) s = strings.Trim(s, " ") n := "" diff --git a/text/gstr/gstr_contain.go b/text/gstr/gstr_contain.go index 73ded148d..0302c9696 100644 --- a/text/gstr/gstr_contain.go +++ b/text/gstr/gstr_contain.go @@ -1,4 +1,4 @@ -// Copyright 2019 gf Author(https://github.com/gogf/gf). All Rights Reserved. +// Copyright GoFrame Author(https://goframe.org). All Rights Reserved. // // This Source Code Form is subject to the terms of the MIT License. // If a copy of the MIT was not distributed with this file, diff --git a/text/gstr/gstr_convert.go b/text/gstr/gstr_convert.go index 357035b65..7efce8c2b 100644 --- a/text/gstr/gstr_convert.go +++ b/text/gstr/gstr_convert.go @@ -1,4 +1,4 @@ -// Copyright 2019 gf Author(https://github.com/gogf/gf). All Rights Reserved. +// Copyright GoFrame Author(https://goframe.org). All Rights Reserved. // // This Source Code Form is subject to the terms of the MIT License. // If a copy of the MIT was not distributed with this file, diff --git a/text/gstr/gstr_domain.go b/text/gstr/gstr_domain.go index 020ed5705..30b57d0dd 100644 --- a/text/gstr/gstr_domain.go +++ b/text/gstr/gstr_domain.go @@ -1,4 +1,4 @@ -// Copyright 2019 gf Author(https://github.com/gogf/gf). All Rights Reserved. +// Copyright GoFrame Author(https://goframe.org). All Rights Reserved. // // This Source Code Form is subject to the terms of the MIT License. // If a copy of the MIT was not distributed with this file, diff --git a/text/gstr/gstr_levenshtein.go b/text/gstr/gstr_levenshtein.go index 4656281a2..489c6eb3b 100644 --- a/text/gstr/gstr_levenshtein.go +++ b/text/gstr/gstr_levenshtein.go @@ -1,4 +1,4 @@ -// Copyright 2018 gf Author(https://github.com/gogf/gf). All Rights Reserved. +// Copyright GoFrame Author(https://goframe.org). All Rights Reserved. // // This Source Code Form is subject to the terms of the MIT License. // If a copy of the MIT was not distributed with this file, diff --git a/text/gstr/gstr_parse.go b/text/gstr/gstr_parse.go index c44c2cbe5..a41267403 100644 --- a/text/gstr/gstr_parse.go +++ b/text/gstr/gstr_parse.go @@ -1,4 +1,4 @@ -// Copyright 2018 gf Author(https://github.com/gogf/gf). All Rights Reserved. +// Copyright GoFrame Author(https://goframe.org). All Rights Reserved. // // This Source Code Form is subject to the terms of the MIT License. // If a copy of the MIT was not distributed with this file, diff --git a/text/gstr/gstr_pos.go b/text/gstr/gstr_pos.go index bf76e756a..e279793e6 100644 --- a/text/gstr/gstr_pos.go +++ b/text/gstr/gstr_pos.go @@ -1,4 +1,4 @@ -// Copyright 2018 gf Author(https://github.com/gogf/gf). All Rights Reserved. +// Copyright GoFrame Author(https://goframe.org). All Rights Reserved. // // This Source Code Form is subject to the terms of the MIT License. // If a copy of the MIT was not distributed with this file, @@ -8,8 +8,8 @@ package gstr import "strings" -// Pos returns the position of the first occurrence of <needle> -// in <haystack> from <startOffset>, case-sensitively. +// Pos returns the position of the first occurrence of `needle` +// in `haystack` from <startOffset>, case-sensitively. // It returns -1, if not found. func Pos(haystack, needle string, startOffset ...int) int { length := len(haystack) @@ -20,13 +20,12 @@ func Pos(haystack, needle string, startOffset ...int) int { if length == 0 || offset > length || -offset > length { return -1 } - if offset < 0 { offset += length } pos := strings.Index(haystack[offset:], needle) - if pos == -1 { - return -1 + if pos == NotFoundIndex { + return NotFoundIndex } return pos + offset } diff --git a/text/gstr/gstr_replace.go b/text/gstr/gstr_replace.go new file mode 100644 index 000000000..98ffa1957 --- /dev/null +++ b/text/gstr/gstr_replace.go @@ -0,0 +1,89 @@ +// Copyright GoFrame Author(https://goframe.org). All Rights Reserved. +// +// This Source Code Form is subject to the terms of the MIT License. +// If a copy of the MIT was not distributed with this file, +// You can obtain one at https://github.com/gogf/gf. + +package gstr + +import ( + "github.com/gogf/gf/internal/utils" + "strings" +) + +// Replace returns a copy of the string `origin` +// in which string `search` replaced by `replace` case-sensitively. +func Replace(origin, search, replace string, count ...int) string { + n := -1 + if len(count) > 0 { + n = count[0] + } + return strings.Replace(origin, search, replace, n) +} + +// ReplaceI returns a copy of the string `origin` +// in which string `search` replaced by `replace` case-insensitively. +func ReplaceI(origin, search, replace string, count ...int) string { + n := -1 + if len(count) > 0 { + n = count[0] + } + if n == 0 { + return origin + } + var ( + length = len(search) + searchLower = strings.ToLower(search) + ) + for { + originLower := strings.ToLower(origin) + if pos := strings.Index(originLower, searchLower); pos != -1 { + origin = origin[:pos] + replace + origin[pos+length:] + if n--; n == 0 { + break + } + } else { + break + } + } + return origin +} + +// ReplaceByArray returns a copy of `origin`, +// which is replaced by a slice in order, case-sensitively. +func ReplaceByArray(origin string, array []string) string { + for i := 0; i < len(array); i += 2 { + if i+1 >= len(array) { + break + } + origin = Replace(origin, array[i], array[i+1]) + } + return origin +} + +// ReplaceIByArray returns a copy of `origin`, +// which is replaced by a slice in order, case-insensitively. +func ReplaceIByArray(origin string, array []string) string { + for i := 0; i < len(array); i += 2 { + if i+1 >= len(array) { + break + } + origin = ReplaceI(origin, array[i], array[i+1]) + } + return origin +} + +// ReplaceByMap returns a copy of `origin`, +// which is replaced by a map in unordered way, case-sensitively. +func ReplaceByMap(origin string, replaces map[string]string) string { + return utils.ReplaceByMap(origin, replaces) +} + +// ReplaceIByMap returns a copy of `origin`, +// which is replaced by a map in unordered way, case-insensitively. +func ReplaceIByMap(origin string, replaces map[string]string) string { + for k, v := range replaces { + origin = ReplaceI(origin, k, v) + } + return origin +} diff --git a/text/gstr/gstr_similartext.go b/text/gstr/gstr_similartext.go index 3bd816ef1..efb253739 100644 --- a/text/gstr/gstr_similartext.go +++ b/text/gstr/gstr_similartext.go @@ -1,4 +1,4 @@ -// Copyright 2018 gf Author(https://github.com/gogf/gf). All Rights Reserved. +// Copyright GoFrame Author(https://goframe.org). All Rights Reserved. // // This Source Code Form is subject to the terms of the MIT License. // If a copy of the MIT was not distributed with this file, diff --git a/text/gstr/gstr_soundex.go b/text/gstr/gstr_soundex.go index 117c2dd39..bdba8933e 100644 --- a/text/gstr/gstr_soundex.go +++ b/text/gstr/gstr_soundex.go @@ -1,4 +1,4 @@ -// Copyright 2018 gf Author(https://github.com/gogf/gf). All Rights Reserved. +// Copyright GoFrame Author(https://goframe.org). All Rights Reserved. // // This Source Code Form is subject to the terms of the MIT License. // If a copy of the MIT was not distributed with this file, diff --git a/text/gstr/gstr_str.go b/text/gstr/gstr_str.go new file mode 100644 index 000000000..8b316efd1 --- /dev/null +++ b/text/gstr/gstr_str.go @@ -0,0 +1,52 @@ +// Copyright GoFrame Author(https://goframe.org). All Rights Reserved. +// +// This Source Code Form is subject to the terms of the MIT License. +// If a copy of the MIT was not distributed with this file, +// You can obtain one at https://github.com/gogf/gf. + +package gstr + +import "strings" + +// Str returns part of `haystack` string starting from and including +// the first occurrence of `needle` to the end of `haystack`. +// See http://php.net/manual/en/function.strstr.php. +func Str(haystack string, needle string) string { + if needle == "" { + return "" + } + pos := strings.Index(haystack, needle) + if pos == NotFoundIndex { + return "" + } + return haystack[pos+len([]byte(needle))-1:] +} + +// StrEx returns part of `haystack` string starting from and excluding +// the first occurrence of `needle` to the end of `haystack`. +func StrEx(haystack string, needle string) string { + if s := Str(haystack, needle); s != "" { + return s[1:] + } + return "" +} + +// StrTill returns part of `haystack` string ending to and including +// the first occurrence of `needle` from the start of `haystack`. +func StrTill(haystack string, needle string) string { + pos := strings.Index(haystack, needle) + if pos == NotFoundIndex || pos == 0 { + return "" + } + return haystack[:pos+1] +} + +// StrTillEx returns part of `haystack` string ending to and excluding +// the first occurrence of `needle` from the start of `haystack`. +func StrTillEx(haystack string, needle string) string { + pos := strings.Index(haystack, needle) + if pos == NotFoundIndex || pos == 0 { + return "" + } + return haystack[:pos] +} diff --git a/text/gstr/gstr_substr.go b/text/gstr/gstr_substr.go new file mode 100644 index 000000000..d4d422c16 --- /dev/null +++ b/text/gstr/gstr_substr.go @@ -0,0 +1,89 @@ +// Copyright GoFrame Author(https://goframe.org). All Rights Reserved. +// +// This Source Code Form is subject to the terms of the MIT License. +// If a copy of the MIT was not distributed with this file, +// You can obtain one at https://github.com/gogf/gf. + +package gstr + +// SubStr returns a portion of string `str` specified by the `start` and `length` parameters. +// The parameter `length` is optional, it uses the length of `str` in default. +func SubStr(str string, start int, length ...int) (substr string) { + strLength := len(str) + // Simple border checks. + if start < 0 { + start = 0 + } + if start >= strLength { + start = strLength + } + end := strLength + if len(length) > 0 { + end = start + length[0] + if end < start { + end = strLength + } + } + if end > strLength { + end = strLength + } + return str[start:end] +} + +// SubStrRune returns a portion of string `str` specified by the `start` and `length` parameters. +// SubStrRune considers parameter `str` as unicode string. +// The parameter `length` is optional, it uses the length of `str` in default. +func SubStrRune(str string, start int, length ...int) (substr string) { + // Converting to []rune to support unicode. + var ( + runes = []rune(str) + runesLength = len(runes) + ) + + // Simple border checks. + if start < 0 { + start = 0 + } + if start >= runesLength { + start = runesLength + } + end := runesLength + if len(length) > 0 { + end = start + length[0] + if end < start { + end = runesLength + } + } + if end > runesLength { + end = runesLength + } + return string(runes[start:end]) +} + +// StrLimit returns a portion of string `str` specified by `length` parameters, if the length +// of `str` is greater than `length`, then the `suffix` will be appended to the result string. +func StrLimit(str string, length int, suffix ...string) string { + if len(str) < length { + return str + } + suffixStr := defaultSuffixForStrLimit + if len(suffix) > 0 { + suffixStr = suffix[0] + } + return str[0:length] + suffixStr +} + +// StrLimitRune returns a portion of string `str` specified by `length` parameters, if the length +// of `str` is greater than `length`, then the `suffix` will be appended to the result string. +// StrLimitRune considers parameter `str` as unicode string. +func StrLimitRune(str string, length int, suffix ...string) string { + runes := []rune(str) + if len(runes) < length { + return str + } + suffixStr := defaultSuffixForStrLimit + if len(suffix) > 0 { + suffixStr = suffix[0] + } + return string(runes[0:length]) + suffixStr +} diff --git a/text/gstr/gstr_trim.go b/text/gstr/gstr_trim.go index 2029357ac..a566bf935 100644 --- a/text/gstr/gstr_trim.go +++ b/text/gstr/gstr_trim.go @@ -1,4 +1,4 @@ -// Copyright 2019 gf Author(https://github.com/gogf/gf). All Rights Reserved. +// Copyright GoFrame Author(https://goframe.org). All Rights Reserved. // // This Source Code Form is subject to the terms of the MIT License. // If a copy of the MIT was not distributed with this file, @@ -7,51 +7,33 @@ package gstr import ( + "github.com/gogf/gf/internal/utils" "strings" ) -var ( - // defaultTrimChars are the characters which are stripped by Trim* functions in default. - defaultTrimChars = string([]byte{ - '\t', // Tab. - '\v', // Vertical tab. - '\n', // New line (line feed). - '\r', // Carriage return. - '\f', // New page. - ' ', // Ordinary space. - 0x00, // NUL-byte. - 0x85, // Delete. - 0xA0, // Non-breaking space. - }) -) - // Trim strips whitespace (or other characters) from the beginning and end of a string. // The optional parameter <characterMask> specifies the additional stripped characters. func Trim(str string, characterMask ...string) string { - if len(characterMask) == 0 { - return strings.Trim(str, defaultTrimChars) - } else { - return strings.Trim(str, defaultTrimChars+characterMask[0]) - } + return utils.Trim(str, characterMask...) } -// TrimStr strips all of the given <cut> string from the beginning and end of a string. -// Note that it does not strips the whitespaces of its beginning or end. +// TrimStr strips all the given <cut> string from the beginning and end of a string. +// Note that it does not strip the whitespaces of its beginning or end. func TrimStr(str string, cut string, count ...int) string { return TrimLeftStr(TrimRightStr(str, cut, count...), cut, count...) } // TrimLeft strips whitespace (or other characters) from the beginning of a string. func TrimLeft(str string, characterMask ...string) string { - if len(characterMask) == 0 { - return strings.TrimLeft(str, defaultTrimChars) - } else { - return strings.TrimLeft(str, defaultTrimChars+characterMask[0]) + trimChars := utils.DefaultTrimChars + if len(characterMask) > 0 { + trimChars += characterMask[0] } + return strings.TrimLeft(str, trimChars) } -// TrimLeftStr strips all of the given <cut> string from the beginning of a string. -// Note that it does not strips the whitespaces of its beginning. +// TrimLeftStr strips all the given <cut> string from the beginning of a string. +// Note that it does not strip the whitespaces of its beginning. func TrimLeftStr(str string, cut string, count ...int) string { var ( lenCut = len(cut) @@ -69,15 +51,15 @@ func TrimLeftStr(str string, cut string, count ...int) string { // TrimRight strips whitespace (or other characters) from the end of a string. func TrimRight(str string, characterMask ...string) string { - if len(characterMask) == 0 { - return strings.TrimRight(str, defaultTrimChars) - } else { - return strings.TrimRight(str, defaultTrimChars+characterMask[0]) + trimChars := utils.DefaultTrimChars + if len(characterMask) > 0 { + trimChars += characterMask[0] } + return strings.TrimRight(str, trimChars) } -// TrimRightStr strips all of the given <cut> string from the end of a string. -// Note that it does not strips the whitespaces of its end. +// TrimRightStr strips all the given <cut> string from the end of a string. +// Note that it does not strip the whitespaces of its end. func TrimRightStr(str string, cut string, count ...int) string { var ( lenStr = len(str) @@ -94,3 +76,28 @@ func TrimRightStr(str string, cut string, count ...int) string { } return str } + +// TrimAll trims all characters in string `str`. +func TrimAll(str string, characterMask ...string) string { + trimChars := utils.DefaultTrimChars + if len(characterMask) > 0 { + trimChars += characterMask[0] + } + var ( + filtered bool + slice = make([]rune, 0, len(str)) + ) + for _, char := range str { + filtered = false + for _, trimChar := range trimChars { + if char == trimChar { + filtered = true + break + } + } + if !filtered { + slice = append(slice, char) + } + } + return string(slice) +} diff --git a/text/gstr/gstr_version.go b/text/gstr/gstr_version.go index 16dbe2797..ac74f6423 100644 --- a/text/gstr/gstr_version.go +++ b/text/gstr/gstr_version.go @@ -1,4 +1,4 @@ -// Copyright 2020 gf Author(https://github.com/gogf/gf). All Rights Reserved. +// Copyright GoFrame Author(https://goframe.org). All Rights Reserved. // // This Source Code Form is subject to the terms of the MIT License. // If a copy of the MIT was not distributed with this file, @@ -24,10 +24,10 @@ import ( // 10.2.0 // etc. func CompareVersion(a, b string) int { - if a[0] == 'v' { + if a != "" && a[0] == 'v' { a = a[1:] } - if b[0] == 'v' { + if b != "" && b[0] == 'v' { b = b[1:] } var ( @@ -71,10 +71,10 @@ func CompareVersion(a, b string) int { // v4.20.0+incompatible // etc. func CompareVersionGo(a, b string) int { - if a[0] == 'v' { + if a != "" && a[0] == 'v' { a = a[1:] } - if b[0] == 'v' { + if b != "" && b[0] == 'v' { b = b[1:] } if Count(a, "-") > 1 { diff --git a/text/gstr/gstr_z_unit_basic_test.go b/text/gstr/gstr_z_unit_basic_test.go index 12c37b253..6e3934dc3 100644 --- a/text/gstr/gstr_z_unit_basic_test.go +++ b/text/gstr/gstr_z_unit_basic_test.go @@ -1,4 +1,4 @@ -// Copyright 2019 gf Author(https://github.com/gogf/gf). All Rights Reserved. +// Copyright GoFrame Author(https://goframe.org). All Rights Reserved. // // This Source Code Form is subject to the terms of the MIT License. // If a copy of the MIT was not distributed with this file, @@ -295,6 +295,30 @@ func Test_Str(t *testing.T) { }) } +func Test_StrEx(t *testing.T) { + gtest.C(t, func(t *gtest.T) { + t.Assert(gstr.StrEx("name@example.com", "@"), "example.com") + t.Assert(gstr.StrEx("name@example.com", ""), "") + t.Assert(gstr.StrEx("name@example.com", "z"), "") + }) +} + +func Test_StrTill(t *testing.T) { + gtest.C(t, func(t *gtest.T) { + t.Assert(gstr.StrTill("name@example.com", "@"), "name@") + t.Assert(gstr.StrTill("name@example.com", ""), "") + t.Assert(gstr.StrTill("name@example.com", "z"), "") + }) +} + +func Test_StrTillEx(t *testing.T) { + gtest.C(t, func(t *gtest.T) { + t.Assert(gstr.StrTillEx("name@example.com", "@"), "name") + t.Assert(gstr.StrTillEx("name@example.com", ""), "") + t.Assert(gstr.StrTillEx("name@example.com", "z"), "") + }) +} + func Test_Shuffle(t *testing.T) { gtest.C(t, func(t *gtest.T) { t.Assert(len(gstr.Shuffle("123456")), 6) diff --git a/text/gstr/gstr_z_unit_case_test.go b/text/gstr/gstr_z_unit_case_test.go index cde0ed961..a87de35e4 100644 --- a/text/gstr/gstr_z_unit_case_test.go +++ b/text/gstr/gstr_z_unit_case_test.go @@ -1,4 +1,4 @@ -// Copyright 2019 gf Author(https://github.com/gogf/gf). All Rights Reserved. +// Copyright GoFrame Author(https://goframe.org). All Rights Reserved. // // This Source Code Form is subject to the terms of the MIT License. // If a copy of the MIT was not distributed with this file, @@ -7,12 +7,13 @@ package gstr_test import ( + "github.com/gogf/gf/test/gtest" "testing" "github.com/gogf/gf/text/gstr" ) -func Test_CamelCase(t *testing.T) { +func Test_CaseCamel(t *testing.T) { cases := [][]string{ {"test_case", "TestCase"}, {"test", "Test"}, @@ -28,14 +29,14 @@ func Test_CamelCase(t *testing.T) { for _, i := range cases { in := i[0] out := i[1] - result := gstr.CamelCase(in) + result := gstr.CaseCamel(in) if result != out { t.Error("'" + result + "' != '" + out + "'") } } } -func Test_CamelLowerCase(t *testing.T) { +func Test_CaseCamelLower(t *testing.T) { cases := [][]string{ {"foo-bar", "fooBar"}, {"TestCase", "testCase"}, @@ -45,14 +46,14 @@ func Test_CamelLowerCase(t *testing.T) { for _, i := range cases { in := i[0] out := i[1] - result := gstr.CamelLowerCase(in) + result := gstr.CaseCamelLower(in) if result != out { t.Error("'" + result + "' != '" + out + "'") } } } -func Test_SnakeCase(t *testing.T) { +func Test_CaseSnake(t *testing.T) { cases := [][]string{ {"testCase", "test_case"}, {"TestCase", "test_case"}, @@ -75,14 +76,14 @@ func Test_SnakeCase(t *testing.T) { for _, i := range cases { in := i[0] out := i[1] - result := gstr.SnakeCase(in) + result := gstr.CaseSnake(in) if result != out { t.Error("'" + in + "'('" + result + "' != '" + out + "')") } } } -func Test_DelimitedCase(t *testing.T) { +func Test_CaseDelimited(t *testing.T) { cases := [][]string{ {"testCase", "test@case"}, {"TestCase", "test@case"}, @@ -106,28 +107,28 @@ func Test_DelimitedCase(t *testing.T) { for _, i := range cases { in := i[0] out := i[1] - result := gstr.DelimitedCase(in, '@') + result := gstr.CaseDelimited(in, '@') if result != out { t.Error("'" + in + "' ('" + result + "' != '" + out + "')") } } } -func Test_SnakeScreamingCase(t *testing.T) { +func Test_CaseSnakeScreaming(t *testing.T) { cases := [][]string{ {"testCase", "TEST_CASE"}, } for _, i := range cases { in := i[0] out := i[1] - result := gstr.SnakeScreamingCase(in) + result := gstr.CaseSnakeScreaming(in) if result != out { t.Error("'" + result + "' != '" + out + "'") } } } -func Test_KebabCase(t *testing.T) { +func Test_CaseKebab(t *testing.T) { cases := [][]string{ {"testCase", "test-case"}, {"optimization1.0.0", "optimization-1-0-0"}, @@ -135,42 +136,42 @@ func Test_KebabCase(t *testing.T) { for _, i := range cases { in := i[0] out := i[1] - result := gstr.KebabCase(in) + result := gstr.CaseKebab(in) if result != out { t.Error("'" + result + "' != '" + out + "'") } } } -func Test_KebabScreamingCase(t *testing.T) { +func Test_CaseKebabScreaming(t *testing.T) { cases := [][]string{ {"testCase", "TEST-CASE"}, } for _, i := range cases { in := i[0] out := i[1] - result := gstr.KebabScreamingCase(in) + result := gstr.CaseKebabScreaming(in) if result != out { t.Error("'" + result + "' != '" + out + "'") } } } -func Test_DelimitedScreamingCase(t *testing.T) { +func Test_CaseDelimitedScreaming(t *testing.T) { cases := [][]string{ {"testCase", "TEST.CASE"}, } for _, i := range cases { in := i[0] out := i[1] - result := gstr.DelimitedScreamingCase(in, '.', true) + result := gstr.CaseDelimitedScreaming(in, '.', true) if result != out { t.Error("'" + result + "' != '" + out + "'") } } } -func TestSnakeFirstUpperCase(t *testing.T) { +func Test_CaseSnakeFirstUpper(t *testing.T) { cases := [][]string{ {"RGBCodeMd5", "rgb_code_md5"}, {"testCase", "test_case"}, @@ -182,13 +183,12 @@ func TestSnakeFirstUpperCase(t *testing.T) { {"User_ID", "user_id"}, {"user_id", "user_id"}, {"md5", "md5"}, + {"Numbers2And55With000", "numbers2_and55_with000"}, } - for _, i := range cases { - in := i[0] - out := i[1] - result := gstr.SnakeFirstUpperCase(in) - if result != out { - t.Error("'" + result + "' != '" + out + "'") + gtest.C(t, func(t *gtest.T) { + for _, item := range cases { + t.Assert(gstr.CaseSnakeFirstUpper(item[0]), item[1]) } - } + }) + } diff --git a/text/gstr/gstr_z_unit_convert_test.go b/text/gstr/gstr_z_unit_convert_test.go index 94f71cea0..354c42697 100644 --- a/text/gstr/gstr_z_unit_convert_test.go +++ b/text/gstr/gstr_z_unit_convert_test.go @@ -1,4 +1,4 @@ -// Copyright 2019 gf Author(https://github.com/gogf/gf). All Rights Reserved. +// Copyright GoFrame Author(https://goframe.org). All Rights Reserved. // // This Source Code Form is subject to the terms of the MIT License. // If a copy of the MIT was not distributed with this file, diff --git a/text/gstr/gstr_z_unit_domain_test.go b/text/gstr/gstr_z_unit_domain_test.go index 93ffb6a79..7672bc22a 100644 --- a/text/gstr/gstr_z_unit_domain_test.go +++ b/text/gstr/gstr_z_unit_domain_test.go @@ -1,4 +1,4 @@ -// Copyright 2019 gf Author(https://github.com/gogf/gf). All Rights Reserved. +// Copyright GoFrame Author(https://goframe.org). All Rights Reserved. // // This Source Code Form is subject to the terms of the MIT License. // If a copy of the MIT was not distributed with this file, diff --git a/text/gstr/gstr_z_unit_parse_test.go b/text/gstr/gstr_z_unit_parse_test.go index d2e414ca7..32261c2ae 100644 --- a/text/gstr/gstr_z_unit_parse_test.go +++ b/text/gstr/gstr_z_unit_parse_test.go @@ -1,4 +1,4 @@ -// Copyright 2019 gf Author(https://github.com/gogf/gf). All Rights Reserved. +// Copyright GoFrame Author(https://goframe.org). All Rights Reserved. // // This Source Code Form is subject to the terms of the MIT License. // If a copy of the MIT was not distributed with this file, diff --git a/text/gstr/gstr_z_unit_pos_test.go b/text/gstr/gstr_z_unit_pos_test.go index 68288f3cf..7f524acbd 100644 --- a/text/gstr/gstr_z_unit_pos_test.go +++ b/text/gstr/gstr_z_unit_pos_test.go @@ -1,4 +1,4 @@ -// Copyright 2019 gf Author(https://github.com/gogf/gf). All Rights Reserved. +// Copyright GoFrame Author(https://goframe.org). All Rights Reserved. // // This Source Code Form is subject to the terms of the MIT License. // If a copy of the MIT was not distributed with this file, diff --git a/text/gstr/gstr_z_unit_trim_test.go b/text/gstr/gstr_z_unit_trim_test.go index 7e9bc8a06..8415ae731 100644 --- a/text/gstr/gstr_z_unit_trim_test.go +++ b/text/gstr/gstr_z_unit_trim_test.go @@ -1,4 +1,4 @@ -// Copyright 2019 gf Author(https://github.com/gogf/gf). All Rights Reserved. +// Copyright GoFrame Author(https://goframe.org). All Rights Reserved. // // This Source Code Form is subject to the terms of the MIT License. // If a copy of the MIT was not distributed with this file, @@ -81,3 +81,17 @@ func Test_TrimLeftStr(t *testing.T) { t.Assert(gstr.TrimLeftStr("我爱中国人", "我爱中国"), "人") }) } + +func Test_TrimAll(t *testing.T) { + gtest.C(t, func(t *gtest.T) { + t.Assert(gstr.TrimAll("gogo我go\n爱gogo\n", "go"), "我爱") + }) + gtest.C(t, func(t *gtest.T) { + t.Assert(gstr.TrimAll("gogo\n我go爱gogo", "go"), "我爱") + t.Assert(gstr.TrimAll("gogo\n我go爱gogo\n", "go"), "我爱") + t.Assert(gstr.TrimAll("gogo\n我go\n爱gogo", "go"), "我爱") + }) + gtest.C(t, func(t *gtest.T) { + t.Assert(gstr.TrimAll("啊我爱\n啊中国\n人啊", "啊"), "我爱中国人") + }) +} diff --git a/text/gstr/gstr_z_unit_version_test.go b/text/gstr/gstr_z_unit_version_test.go index c79eeee39..2d4a81210 100644 --- a/text/gstr/gstr_z_unit_version_test.go +++ b/text/gstr/gstr_z_unit_version_test.go @@ -1,4 +1,4 @@ -// Copyright 2019 gf Author(https://github.com/gogf/gf). All Rights Reserved. +// Copyright GoFrame Author(https://goframe.org). All Rights Reserved. // // This Source Code Form is subject to the terms of the MIT License. // If a copy of the MIT was not distributed with this file, @@ -17,6 +17,9 @@ import ( func Test_CompareVersion(t *testing.T) { gtest.C(t, func(t *gtest.T) { + t.AssertEQ(gstr.CompareVersion("1", ""), 1) + t.AssertEQ(gstr.CompareVersion("", ""), 0) + t.AssertEQ(gstr.CompareVersion("", "v0.1"), -1) t.AssertEQ(gstr.CompareVersion("1", "v0.99"), 1) t.AssertEQ(gstr.CompareVersion("v1.0", "v0.99"), 1) t.AssertEQ(gstr.CompareVersion("v1.0.1", "v1.1.0"), -1) @@ -28,6 +31,9 @@ func Test_CompareVersion(t *testing.T) { func Test_CompareVersionGo(t *testing.T) { gtest.C(t, func(t *gtest.T) { + t.AssertEQ(gstr.CompareVersionGo("1", ""), 1) + t.AssertEQ(gstr.CompareVersionGo("", ""), 0) + t.AssertEQ(gstr.CompareVersionGo("", "v0.1"), -1) t.AssertEQ(gstr.CompareVersionGo("v1.0.1", "v1.1.0"), -1) t.AssertEQ(gstr.CompareVersionGo("1.0.1", "v1.1.0"), -1) t.AssertEQ(gstr.CompareVersionGo("1.0.0", "v0.1.0"), 1) diff --git a/util/gconv/gconv.go b/util/gconv/gconv.go index fc0b5fb82..0a72171f8 100644 --- a/util/gconv/gconv.go +++ b/util/gconv/gconv.go @@ -1,4 +1,4 @@ -// Copyright 2017 gf Author(https://github.com/gogf/gf). All Rights Reserved. +// Copyright GoFrame Author(https://goframe.org). All Rights Reserved. // // This Source Code Form is subject to the terms of the MIT License. // If a copy of the MIT was not distributed with this file, @@ -13,6 +13,7 @@ import ( "fmt" "github.com/gogf/gf/internal/json" "github.com/gogf/gf/os/gtime" + "math" "reflect" "strconv" "strings" @@ -21,6 +22,14 @@ import ( "github.com/gogf/gf/encoding/gbinary" ) +type ( + // errorStack is the interface for Stack feature. + errorStack interface { + Error() string + Stack() string + } +) + var ( // Empty strings. emptyStringMap = map[string]struct{}{ @@ -31,290 +40,338 @@ var ( "false": {}, } - // Priority tags for Map*/Struct* functions. + // StructTagPriority defines the default priority tags for Map*/Struct* functions. // Note, the "gconv", "param", "params" tags are used by old version of package. // It is strongly recommended using short tag "c" or "p" instead in the future. StructTagPriority = []string{"gconv", "param", "params", "c", "p", "json"} ) -// Convert converts the variable <i> to the type <t>, the type <t> is specified by string. -// The optional parameter <params> is used for additional necessary parameter for this conversion. -// It supports common types conversion as its conversion based on type name string. -func Convert(i interface{}, t string, params ...interface{}) interface{} { - switch t { +type doConvertInput struct { + FromValue interface{} // Value that is converted from. + ToTypeName string // Target value type name in string. + ReferValue interface{} // Referred value, a value in type `ToTypeName`. + Extra []interface{} // Extra values for implementing the converting. +} + +// doConvert does commonly used types converting. +func doConvert(input doConvertInput) interface{} { + switch input.ToTypeName { case "int": - return Int(i) + return Int(input.FromValue) case "*int": - if _, ok := i.(*int); ok { - return i + if _, ok := input.FromValue.(*int); ok { + return input.FromValue } - v := Int(i) + v := Int(input.FromValue) return &v case "int8": - return Int8(i) + return Int8(input.FromValue) case "*int8": - if _, ok := i.(*int8); ok { - return i + if _, ok := input.FromValue.(*int8); ok { + return input.FromValue } - v := Int8(i) + v := Int8(input.FromValue) return &v case "int16": - return Int16(i) + return Int16(input.FromValue) case "*int16": - if _, ok := i.(*int16); ok { - return i + if _, ok := input.FromValue.(*int16); ok { + return input.FromValue } - v := Int16(i) + v := Int16(input.FromValue) return &v case "int32": - return Int32(i) + return Int32(input.FromValue) case "*int32": - if _, ok := i.(*int32); ok { - return i + if _, ok := input.FromValue.(*int32); ok { + return input.FromValue } - v := Int32(i) + v := Int32(input.FromValue) return &v case "int64": - return Int64(i) + return Int64(input.FromValue) case "*int64": - if _, ok := i.(*int64); ok { - return i + if _, ok := input.FromValue.(*int64); ok { + return input.FromValue } - v := Int64(i) + v := Int64(input.FromValue) return &v case "uint": - return Uint(i) + return Uint(input.FromValue) case "*uint": - if _, ok := i.(*uint); ok { - return i + if _, ok := input.FromValue.(*uint); ok { + return input.FromValue } - v := Uint(i) + v := Uint(input.FromValue) return &v case "uint8": - return Uint8(i) + return Uint8(input.FromValue) case "*uint8": - if _, ok := i.(*uint8); ok { - return i + if _, ok := input.FromValue.(*uint8); ok { + return input.FromValue } - v := Uint8(i) + v := Uint8(input.FromValue) return &v case "uint16": - return Uint16(i) + return Uint16(input.FromValue) case "*uint16": - if _, ok := i.(*uint16); ok { - return i + if _, ok := input.FromValue.(*uint16); ok { + return input.FromValue } - v := Uint16(i) + v := Uint16(input.FromValue) return &v case "uint32": - return Uint32(i) + return Uint32(input.FromValue) case "*uint32": - if _, ok := i.(*uint32); ok { - return i + if _, ok := input.FromValue.(*uint32); ok { + return input.FromValue } - v := Uint32(i) + v := Uint32(input.FromValue) return &v case "uint64": - return Uint64(i) + return Uint64(input.FromValue) case "*uint64": - if _, ok := i.(*uint64); ok { - return i + if _, ok := input.FromValue.(*uint64); ok { + return input.FromValue } - v := Uint64(i) + v := Uint64(input.FromValue) return &v case "float32": - return Float32(i) + return Float32(input.FromValue) case "*float32": - if _, ok := i.(*float32); ok { - return i + if _, ok := input.FromValue.(*float32); ok { + return input.FromValue } - v := Float32(i) + v := Float32(input.FromValue) return &v case "float64": - return Float64(i) + return Float64(input.FromValue) case "*float64": - if _, ok := i.(*float64); ok { - return i + if _, ok := input.FromValue.(*float64); ok { + return input.FromValue } - v := Float64(i) + v := Float64(input.FromValue) return &v case "bool": - return Bool(i) + return Bool(input.FromValue) case "*bool": - if _, ok := i.(*bool); ok { - return i + if _, ok := input.FromValue.(*bool); ok { + return input.FromValue } - v := Bool(i) + v := Bool(input.FromValue) return &v case "string": - return String(i) + return String(input.FromValue) case "*string": - if _, ok := i.(*string); ok { - return i + if _, ok := input.FromValue.(*string); ok { + return input.FromValue } - v := String(i) + v := String(input.FromValue) return &v case "[]byte": - return Bytes(i) + return Bytes(input.FromValue) case "[]int": - return Ints(i) + return Ints(input.FromValue) case "[]int32": - return Int32s(i) + return Int32s(input.FromValue) case "[]int64": - return Int64s(i) + return Int64s(input.FromValue) case "[]uint": - return Uints(i) + return Uints(input.FromValue) + case "[]uint8": + return Bytes(input.FromValue) case "[]uint32": - return Uint32s(i) + return Uint32s(input.FromValue) case "[]uint64": - return Uint64s(i) + return Uint64s(input.FromValue) case "[]float32": - return Float32s(i) + return Float32s(input.FromValue) case "[]float64": - return Float64s(i) + return Float64s(input.FromValue) case "[]string": - return Strings(i) + return Strings(input.FromValue) case "Time", "time.Time": - if len(params) > 0 { - return Time(i, String(params[0])) + if len(input.Extra) > 0 { + return Time(input.FromValue, String(input.Extra[0])) } - return Time(i) + return Time(input.FromValue) case "*time.Time": var v interface{} - if len(params) > 0 { - v = Time(i, String(params[0])) + if len(input.Extra) > 0 { + v = Time(input.FromValue, String(input.Extra[0])) } else { - if _, ok := i.(*time.Time); ok { - return i + if _, ok := input.FromValue.(*time.Time); ok { + return input.FromValue } - v = Time(i) + v = Time(input.FromValue) } return &v case "GTime", "gtime.Time": - if len(params) > 0 { - if v := GTime(i, String(params[0])); v != nil { + if len(input.Extra) > 0 { + if v := GTime(input.FromValue, String(input.Extra[0])); v != nil { return *v } else { return *gtime.New() } } - if v := GTime(i); v != nil { + if v := GTime(input.FromValue); v != nil { return *v } else { return *gtime.New() } case "*gtime.Time": - if len(params) > 0 { - if v := GTime(i, String(params[0])); v != nil { + if len(input.Extra) > 0 { + if v := GTime(input.FromValue, String(input.Extra[0])); v != nil { return v } else { return gtime.New() } } - if v := GTime(i); v != nil { + if v := GTime(input.FromValue); v != nil { return v } else { return gtime.New() } case "Duration", "time.Duration": - return Duration(i) + return Duration(input.FromValue) case "*time.Duration": - if _, ok := i.(*time.Duration); ok { - return i + if _, ok := input.FromValue.(*time.Duration); ok { + return input.FromValue } - v := Duration(i) + v := Duration(input.FromValue) return &v case "map[string]string": - return MapStrStr(i) + return MapStrStr(input.FromValue) case "map[string]interface{}": - return Map(i) + return Map(input.FromValue) case "[]map[string]interface{}": - return Maps(i) - - //case "gvar.Var": - // // TODO remove reflect usage to create gvar.Var, considering using unsafe pointer - // rv := reflect.New(intstore.ReflectTypeVarImp) - // ri := rv.Interface() - // if v, ok := ri.(apiSet); ok { - // v.Set(i) - // } else if v, ok := ri.(apiUnmarshalValue); ok { - // v.UnmarshalValue(i) - // } else { - // rv.Set(reflect.ValueOf(i)) - // } - // return ri + return Maps(input.FromValue) default: - return i + if input.ReferValue != nil { + var ( + referReflectValue reflect.Value + ) + if v, ok := input.ReferValue.(reflect.Value); ok { + referReflectValue = v + } else { + referReflectValue = reflect.ValueOf(input.ReferValue) + } + input.ToTypeName = referReflectValue.Kind().String() + input.ReferValue = nil + return reflect.ValueOf(doConvert(input)).Convert(referReflectValue.Type()).Interface() + } + return input.FromValue } } -// Byte converts <i> to byte. -func Byte(i interface{}) byte { - if v, ok := i.(byte); ok { +// Convert converts the variable `fromValue` to the type `toTypeName`, the type `toTypeName` is specified by string. +// The optional parameter `extraParams` is used for additional necessary parameter for this conversion. +// It supports common types conversion as its conversion based on type name string. +func Convert(fromValue interface{}, toTypeName string, extraParams ...interface{}) interface{} { + return doConvert(doConvertInput{ + FromValue: fromValue, + ToTypeName: toTypeName, + ReferValue: nil, + Extra: extraParams, + }) +} + +// Byte converts `any` to byte. +func Byte(any interface{}) byte { + if v, ok := any.(byte); ok { return v } - return Uint8(i) + return Uint8(any) } -// Bytes converts <i> to []byte. -func Bytes(i interface{}) []byte { - if i == nil { +// Bytes converts `any` to []byte. +func Bytes(any interface{}) []byte { + if any == nil { return nil } - switch value := i.(type) { + switch value := any.(type) { case string: return []byte(value) case []byte: return value default: - return gbinary.Encode(i) + if f, ok := value.(apiBytes); ok { + return f.Bytes() + } + var ( + reflectValue = reflect.ValueOf(any) + reflectKind = reflectValue.Kind() + ) + for reflectKind == reflect.Ptr { + reflectValue = reflectValue.Elem() + reflectKind = reflectValue.Kind() + } + switch reflectKind { + case reflect.Array, reflect.Slice: + var ( + ok = true + bytes = make([]byte, reflectValue.Len()) + ) + for i, _ := range bytes { + int32Value := Int32(reflectValue.Index(i).Interface()) + if int32Value < 0 || int32Value > math.MaxUint8 { + ok = false + break + } + bytes[i] = byte(int32Value) + } + if ok { + return bytes + } + } + return gbinary.Encode(any) } } -// Rune converts <i> to rune. -func Rune(i interface{}) rune { - if v, ok := i.(rune); ok { +// Rune converts `any` to rune. +func Rune(any interface{}) rune { + if v, ok := any.(rune); ok { return v } - return rune(Int32(i)) + return Int32(any) } -// Runes converts <i> to []rune. -func Runes(i interface{}) []rune { - if v, ok := i.([]rune); ok { +// Runes converts `any` to []rune. +func Runes(any interface{}) []rune { + if v, ok := any.([]rune); ok { return v } - return []rune(String(i)) + return []rune(String(any)) } -// String converts <i> to string. -// It's most common used converting function. -func String(i interface{}) string { - if i == nil { +// String converts `any` to string. +// It's most commonly used converting function. +func String(any interface{}) string { + if any == nil { return "" } - switch value := i.(type) { + switch value := any.(type) { case int: return strconv.Itoa(value) case int8: @@ -402,7 +459,7 @@ func String(i interface{}) string { if kind == reflect.Ptr { return String(rv.Elem().Interface()) } - // Finally we use json.Marshal to convert. + // Finally, we use json.Marshal to convert. if jsonContent, err := json.Marshal(value); err != nil { return fmt.Sprint(value) } else { @@ -411,13 +468,13 @@ func String(i interface{}) string { } } -// Bool converts <i> to bool. -// It returns false if <i> is: false, "", 0, "false", "off", "no", empty slice/map. -func Bool(i interface{}) bool { - if i == nil { +// Bool converts `any` to bool. +// It returns false if `any` is: false, "", 0, "false", "off", "no", empty slice/map. +func Bool(any interface{}) bool { + if any == nil { return false } - switch value := i.(type) { + switch value := any.(type) { case bool: return value case []byte: @@ -431,7 +488,10 @@ func Bool(i interface{}) bool { } return true default: - rv := reflect.ValueOf(i) + if f, ok := value.(apiBool); ok { + return f.Bool() + } + rv := reflect.ValueOf(any) switch rv.Kind() { case reflect.Ptr: return !rv.IsNil() @@ -444,7 +504,7 @@ func Bool(i interface{}) bool { case reflect.Struct: return true default: - s := strings.ToLower(String(i)) + s := strings.ToLower(String(any)) if _, ok := emptyStringMap[s]; ok { return false } @@ -453,56 +513,56 @@ func Bool(i interface{}) bool { } } -// Int converts <i> to int. -func Int(i interface{}) int { - if i == nil { +// Int converts `any` to int. +func Int(any interface{}) int { + if any == nil { return 0 } - if v, ok := i.(int); ok { + if v, ok := any.(int); ok { return v } - return int(Int64(i)) + return int(Int64(any)) } -// Int8 converts <i> to int8. -func Int8(i interface{}) int8 { - if i == nil { +// Int8 converts `any` to int8. +func Int8(any interface{}) int8 { + if any == nil { return 0 } - if v, ok := i.(int8); ok { + if v, ok := any.(int8); ok { return v } - return int8(Int64(i)) + return int8(Int64(any)) } -// Int16 converts <i> to int16. -func Int16(i interface{}) int16 { - if i == nil { +// Int16 converts `any` to int16. +func Int16(any interface{}) int16 { + if any == nil { return 0 } - if v, ok := i.(int16); ok { + if v, ok := any.(int16); ok { return v } - return int16(Int64(i)) + return int16(Int64(any)) } -// Int32 converts <i> to int32. -func Int32(i interface{}) int32 { - if i == nil { +// Int32 converts `any` to int32. +func Int32(any interface{}) int32 { + if any == nil { return 0 } - if v, ok := i.(int32); ok { + if v, ok := any.(int32); ok { return v } - return int32(Int64(i)) + return int32(Int64(any)) } -// Int64 converts <i> to int64. -func Int64(i interface{}) int64 { - if i == nil { +// Int64 converts `any` to int64. +func Int64(any interface{}) int64 { + if any == nil { return 0 } - switch value := i.(type) { + switch value := any.(type) { case int: return int64(value) case int8: @@ -535,6 +595,9 @@ func Int64(i interface{}) int64 { case []byte: return gbinary.DecodeToInt64(value) default: + if f, ok := value.(apiInt64); ok { + return f.Int64() + } s := String(value) isMinus := false if len(s) > 0 { @@ -575,56 +638,56 @@ func Int64(i interface{}) int64 { } } -// Uint converts <i> to uint. -func Uint(i interface{}) uint { - if i == nil { +// Uint converts `any` to uint. +func Uint(any interface{}) uint { + if any == nil { return 0 } - if v, ok := i.(uint); ok { + if v, ok := any.(uint); ok { return v } - return uint(Uint64(i)) + return uint(Uint64(any)) } -// Uint8 converts <i> to uint8. -func Uint8(i interface{}) uint8 { - if i == nil { +// Uint8 converts `any` to uint8. +func Uint8(any interface{}) uint8 { + if any == nil { return 0 } - if v, ok := i.(uint8); ok { + if v, ok := any.(uint8); ok { return v } - return uint8(Uint64(i)) + return uint8(Uint64(any)) } -// Uint16 converts <i> to uint16. -func Uint16(i interface{}) uint16 { - if i == nil { +// Uint16 converts `any` to uint16. +func Uint16(any interface{}) uint16 { + if any == nil { return 0 } - if v, ok := i.(uint16); ok { + if v, ok := any.(uint16); ok { return v } - return uint16(Uint64(i)) + return uint16(Uint64(any)) } -// Uint32 converts <i> to uint32. -func Uint32(i interface{}) uint32 { - if i == nil { +// Uint32 converts `any` to uint32. +func Uint32(any interface{}) uint32 { + if any == nil { return 0 } - if v, ok := i.(uint32); ok { + if v, ok := any.(uint32); ok { return v } - return uint32(Uint64(i)) + return uint32(Uint64(any)) } -// Uint64 converts <i> to uint64. -func Uint64(i interface{}) uint64 { - if i == nil { +// Uint64 converts `any` to uint64. +func Uint64(any interface{}) uint64 { + if any == nil { return 0 } - switch value := i.(type) { + switch value := any.(type) { case int: return uint64(value) case int8: @@ -657,6 +720,9 @@ func Uint64(i interface{}) uint64 { case []byte: return gbinary.DecodeToUint64(value) default: + if f, ok := value.(apiUint64); ok { + return f.Uint64() + } s := String(value) // Hexadecimal if len(s) > 2 && s[0] == '0' && (s[1] == 'x' || s[1] == 'X') { @@ -679,12 +745,12 @@ func Uint64(i interface{}) uint64 { } } -// Float32 converts <i> to float32. -func Float32(i interface{}) float32 { - if i == nil { +// Float32 converts `any` to float32. +func Float32(any interface{}) float32 { + if any == nil { return 0 } - switch value := i.(type) { + switch value := any.(type) { case float32: return value case float64: @@ -692,17 +758,20 @@ func Float32(i interface{}) float32 { case []byte: return gbinary.DecodeToFloat32(value) default: - v, _ := strconv.ParseFloat(String(i), 64) + if f, ok := value.(apiFloat32); ok { + return f.Float32() + } + v, _ := strconv.ParseFloat(String(any), 64) return float32(v) } } -// Float64 converts <i> to float64. -func Float64(i interface{}) float64 { - if i == nil { +// Float64 converts `any` to float64. +func Float64(any interface{}) float64 { + if any == nil { return 0 } - switch value := i.(type) { + switch value := any.(type) { case float32: return float64(value) case float64: @@ -710,7 +779,10 @@ func Float64(i interface{}) float64 { case []byte: return gbinary.DecodeToFloat64(value) default: - v, _ := strconv.ParseFloat(String(i), 64) + if f, ok := value.(apiFloat64); ok { + return f.Float64() + } + v, _ := strconv.ParseFloat(String(any), 64) return v } } diff --git a/util/gconv/gconv_interface.go b/util/gconv/gconv_interface.go index 069c97b24..9c23f9425 100644 --- a/util/gconv/gconv_interface.go +++ b/util/gconv/gconv_interface.go @@ -1,4 +1,4 @@ -// Copyright 2017 gf Author(https://github.com/gogf/gf). All Rights Reserved. +// Copyright GoFrame Author(https://goframe.org). All Rights Reserved. // // This Source Code Form is subject to the terms of the MIT License. // If a copy of the MIT was not distributed with this file, @@ -6,16 +6,48 @@ package gconv +import "github.com/gogf/gf/os/gtime" + // apiString is used for type assert api for String(). type apiString interface { String() string } +// apiBool is used for type assert api for Bool(). +type apiBool interface { + Bool() bool +} + +// apiInt64 is used for type assert api for Int64(). +type apiInt64 interface { + Int64() int64 +} + +// apiUint64 is used for type assert api for Uint64(). +type apiUint64 interface { + Uint64() uint64 +} + +// apiFloat32 is used for type assert api for Float32(). +type apiFloat32 interface { + Float32() float32 +} + +// apiFloat64 is used for type assert api for Float64(). +type apiFloat64 interface { + Float64() float64 +} + // apiError is used for type assert api for Error(). type apiError interface { Error() string } +// apiBytes is used for type assert api for Bytes(). +type apiBytes interface { + Bytes() []byte +} + // apiInterfaces is used for type assert api for Interfaces(). type apiInterfaces interface { Interfaces() []interface{} @@ -58,7 +90,18 @@ type apiUnmarshalText interface { UnmarshalText(text []byte) error } +// apiUnmarshalText is the interface for custom defined types customizing value assignment. +// Note that only pointer can implement interface apiUnmarshalJSON. +type apiUnmarshalJSON interface { + UnmarshalJSON(b []byte) error +} + // apiSet is the interface for custom value assignment. type apiSet interface { Set(value interface{}) (old interface{}) } + +// apiGTime is the interface for gtime.Time converting. +type apiGTime interface { + GTime(format ...string) *gtime.Time +} diff --git a/util/gconv/gconv_map.go b/util/gconv/gconv_map.go index 8688507b9..0650f0c5c 100644 --- a/util/gconv/gconv_map.go +++ b/util/gconv/gconv_map.go @@ -1,4 +1,4 @@ -// Copyright 2018 gf Author(https://github.com/gogf/gf). All Rights Reserved. +// Copyright GoFrame Author(https://goframe.org). All Rights Reserved. // // This Source Code Form is subject to the terms of the MIT License. // If a copy of the MIT was not distributed with this file, @@ -7,7 +7,6 @@ package gconv import ( - "github.com/gogf/gf/errors/gerror" "github.com/gogf/gf/internal/json" "reflect" "strings" @@ -16,17 +15,17 @@ import ( "github.com/gogf/gf/internal/utils" ) -// Map converts any variable <value> to map[string]interface{}. If the parameter <value> is not a +// Map converts any variable `value` to map[string]interface{}. If the parameter `value` is not a // map/struct/*struct type, then the conversion will fail and returns nil. // -// If <value> is a struct/*struct object, the second parameter <tags> specifies the most priority +// If `value` is a struct/*struct object, the second parameter `tags` specifies the most priority // tags that will be detected, otherwise it detects the tags in order of: // gconv, json, field name. func Map(value interface{}, tags ...string) map[string]interface{} { return doMapConvert(value, false, tags...) } -// MapDeep does Map function recursively, which means if the attribute of <value> +// MapDeep does Map function recursively, which means if the attribute of `value` // is also a struct/*struct, calls Map function on this attribute converting it to // a map[string]interface{} type variable. // Also see Map. @@ -35,7 +34,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. +// It automatically checks and converts json string to map if `value` is string/[]byte. // // TODO completely implement the recursive converting for all types, especially the map. func doMapConvert(value interface{}, recursive bool, tags ...string) map[string]interface{} { @@ -45,7 +44,7 @@ func doMapConvert(value interface{}, recursive bool, tags ...string) map[string] newTags := StructTagPriority switch len(tags) { case 0: - // No need handle. + // No need handling. case 1: newTags = append(strings.Split(tags[0], ","), StructTagPriority...) default: @@ -57,7 +56,7 @@ func doMapConvert(value interface{}, recursive bool, tags ...string) map[string] case string: // If it is a JSON string, automatically unmarshal it! if len(r) > 0 && r[0] == '{' && r[len(r)-1] == '}' { - if err := json.Unmarshal([]byte(r), &dataMap); err != nil { + if err := json.UnmarshalUseNumber([]byte(r), &dataMap); err != nil { return nil } } else { @@ -66,7 +65,7 @@ func doMapConvert(value interface{}, recursive bool, tags ...string) map[string] case []byte: // If it is a JSON string, automatically unmarshal it! if len(r) > 0 && r[0] == '{' && r[len(r)-1] == '}' { - if err := json.Unmarshal(r, &dataMap); err != nil { + if err := json.UnmarshalUseNumber(r, &dataMap); err != nil { return nil } } else { @@ -154,7 +153,7 @@ func doMapConvert(value interface{}, recursive bool, tags ...string) map[string] reflectKind = reflectValue.Kind() } switch reflectKind { - // If <value> is type of array, it converts the value of even number index as its key and + // If `value` is type of array, it converts the value of even number index as its key and // the value of odd number index as its corresponding value, for example: // []string{"k1","v1","k2","v2"} => map[string]interface{}{"k1":"v1", "k2":"v2"} // []string{"k1","v1","k2"} => map[string]interface{}{"k1":"v1", "k2":nil} @@ -167,7 +166,7 @@ func doMapConvert(value interface{}, recursive bool, tags ...string) map[string] dataMap[String(reflectValue.Index(i).Interface())] = nil } } - case reflect.Map, reflect.Struct: + case reflect.Map, reflect.Struct, reflect.Interface: convertedValue := doMapConvertForMapOrStructValue(true, value, recursive, newTags...) if m, ok := convertedValue.(map[string]interface{}); ok { return m @@ -232,7 +231,7 @@ func doMapConvertForMapOrStructValue(isRoot bool, value interface{}, recursive b rvField reflect.Value dataMap = make(map[string]interface{}) // result map. reflectType = reflectValue.Type() // attribute value type. - name = "" // name may be the tag name or the struct attribute name. + mapKey = "" // mapKey may be the tag name or the struct attribute name. ) for i := 0; i < reflectValue.NumField(); i++ { rtField = reflectType.Field(i) @@ -242,32 +241,32 @@ func doMapConvertForMapOrStructValue(isRoot bool, value interface{}, recursive b if !utils.IsLetterUpper(fieldName[0]) { continue } - name = "" + mapKey = "" fieldTag := rtField.Tag for _, tag := range tags { - if name = fieldTag.Get(tag); name != "" { + if mapKey = fieldTag.Get(tag); mapKey != "" { break } } - if name == "" { - name = fieldName + if mapKey == "" { + mapKey = fieldName } else { // Support json tag feature: -, omitempty - name = strings.TrimSpace(name) - if name == "-" { + mapKey = strings.TrimSpace(mapKey) + if mapKey == "-" { continue } - array := strings.Split(name, ",") + array := strings.Split(mapKey, ",") if len(array) > 1 { switch strings.TrimSpace(array[1]) { case "omitempty": if empty.IsEmpty(rvField.Interface()) { continue } else { - name = strings.TrimSpace(array[0]) + mapKey = strings.TrimSpace(array[0]) } default: - name = strings.TrimSpace(array[0]) + mapKey = strings.TrimSpace(array[0]) } } } @@ -284,7 +283,7 @@ func doMapConvertForMapOrStructValue(isRoot bool, value interface{}, recursive b switch rvAttrKind { case reflect.Struct: var ( - hasNoTag = name == fieldName + hasNoTag = mapKey == fieldName rvAttrInterface = rvAttrField.Interface() ) if hasNoTag && rtField.Anonymous { @@ -296,41 +295,41 @@ func doMapConvertForMapOrStructValue(isRoot bool, value interface{}, recursive b dataMap[k] = v } } else { - dataMap[name] = rvAttrInterface + dataMap[mapKey] = rvAttrInterface } } else if !hasNoTag && rtField.Anonymous { // It means this attribute field has desired tag. - dataMap[name] = doMapConvertForMapOrStructValue(false, rvAttrInterface, true, tags...) + dataMap[mapKey] = doMapConvertForMapOrStructValue(false, rvAttrInterface, true, tags...) } else { - dataMap[name] = doMapConvertForMapOrStructValue(false, rvAttrInterface, false, tags...) + dataMap[mapKey] = doMapConvertForMapOrStructValue(false, rvAttrInterface, recursive, tags...) } // The struct attribute is type of slice. case reflect.Array, reflect.Slice: length := rvField.Len() if length == 0 { - dataMap[name] = rvField.Interface() + dataMap[mapKey] = rvField.Interface() break } array := make([]interface{}, length) for i := 0; i < length; i++ { array[i] = doMapConvertForMapOrStructValue(false, rvField.Index(i), recursive, tags...) } - dataMap[name] = array + dataMap[mapKey] = array default: if rvField.IsValid() { - dataMap[name] = reflectValue.Field(i).Interface() + dataMap[mapKey] = reflectValue.Field(i).Interface() } else { - dataMap[name] = nil + dataMap[mapKey] = nil } } } else { // No recursive map value converting if rvField.IsValid() { - dataMap[name] = reflectValue.Field(i).Interface() + dataMap[mapKey] = reflectValue.Field(i).Interface() } else { - dataMap[name] = nil + dataMap[mapKey] = nil } } } @@ -354,7 +353,7 @@ func doMapConvertForMapOrStructValue(isRoot bool, value interface{}, recursive b return value } -// MapStrStr converts <value> to map[string]string. +// MapStrStr converts `value` to map[string]string. // Note that there might be data copy for this map type converting. func MapStrStr(value interface{}, tags ...string) map[string]string { if r, ok := value.(map[string]string); ok { @@ -371,7 +370,7 @@ func MapStrStr(value interface{}, tags ...string) map[string]string { return nil } -// MapStrStrDeep converts <value> to map[string]string recursively. +// MapStrStrDeep converts `value` to map[string]string recursively. // Note that there might be data copy for this map type converting. func MapStrStrDeep(value interface{}, tags ...string) map[string]string { if r, ok := value.(map[string]string); ok { @@ -387,188 +386,3 @@ func MapStrStrDeep(value interface{}, tags ...string) map[string]string { } return nil } - -// MapToMap converts any map type variable <params> to another map type variable <pointer> -// using reflect. -// See doMapToMap. -func MapToMap(params interface{}, pointer interface{}, mapping ...map[string]string) error { - return doMapToMap(params, pointer, mapping...) -} - -// MapToMapDeep converts any map type variable <params> to another map type variable <pointer> -// using reflect recursively. -// Deprecated, use MapToMap instead. -func MapToMapDeep(params interface{}, pointer interface{}, mapping ...map[string]string) error { - return doMapToMap(params, pointer, mapping...) -} - -// doMapToMap converts any map type variable <params> to another map type variable <pointer>. -// -// The parameter <params> can be any type of map, like: -// map[string]string, map[string]struct, , map[string]*struct, etc. -// -// The parameter <pointer> should be type of *map, like: -// map[int]string, map[string]struct, , map[string]*struct, etc. -// -// The optional parameter <mapping> is used for struct attribute to map key mapping, which makes -// sense only if the items of original map <params> is type struct. -func doMapToMap(params interface{}, pointer interface{}, mapping ...map[string]string) (err error) { - var ( - paramsRv = reflect.ValueOf(params) - paramsKind = paramsRv.Kind() - ) - if paramsKind == reflect.Ptr { - paramsRv = paramsRv.Elem() - paramsKind = paramsRv.Kind() - } - if paramsKind != reflect.Map { - return gerror.New("params should be type of map") - } - // Empty params map, no need continue. - if paramsRv.Len() == 0 { - return nil - } - var pointerRv reflect.Value - if v, ok := pointer.(reflect.Value); ok { - pointerRv = v - } else { - pointerRv = reflect.ValueOf(pointer) - } - pointerKind := pointerRv.Kind() - for pointerKind == reflect.Ptr { - pointerRv = pointerRv.Elem() - pointerKind = pointerRv.Kind() - } - if pointerKind != reflect.Map { - return gerror.New("pointer should be type of *map") - } - defer func() { - // Catch the panic, especially the reflect operation panics. - if e := recover(); e != nil { - err = gerror.NewSkipf(1, "%v", e) - } - }() - var ( - paramsKeys = paramsRv.MapKeys() - pointerKeyType = pointerRv.Type().Key() - pointerValueType = pointerRv.Type().Elem() - pointerValueKind = pointerValueType.Kind() - dataMap = reflect.MakeMapWithSize(pointerRv.Type(), len(paramsKeys)) - ) - // Retrieve the true element type of target map. - if pointerValueKind == reflect.Ptr { - pointerValueKind = pointerValueType.Elem().Kind() - } - for _, key := range paramsKeys { - e := reflect.New(pointerValueType).Elem() - switch pointerValueKind { - case reflect.Map, reflect.Struct: - if err = Struct(paramsRv.MapIndex(key).Interface(), e, mapping...); err != nil { - return err - } - default: - e.Set( - reflect.ValueOf( - Convert( - paramsRv.MapIndex(key).Interface(), - pointerValueType.String(), - ), - ), - ) - } - dataMap.SetMapIndex( - reflect.ValueOf( - Convert( - key.Interface(), - pointerKeyType.Name(), - ), - ), - e, - ) - } - pointerRv.Set(dataMap) - return nil -} - -// MapToMaps converts any map type variable <params> to another map type variable <pointer>. -// See doMapToMaps. -func MapToMaps(params interface{}, pointer interface{}, mapping ...map[string]string) error { - return doMapToMaps(params, pointer, mapping...) -} - -// MapToMapsDeep converts any map type variable <params> to another map type variable -// <pointer> recursively. -// Deprecated, use MapToMaps instead. -func MapToMapsDeep(params interface{}, pointer interface{}, mapping ...map[string]string) error { - return doMapToMaps(params, pointer, mapping...) -} - -// doMapToMaps converts any map type variable <params> to another map type variable <pointer>. -// -// The parameter <params> can be any type of map, of which the item type is slice map, like: -// map[int][]map, map[string][]map. -// -// The parameter <pointer> should be type of *map, of which the item type is slice map, like: -// map[string][]struct, map[string][]*struct. -// -// The optional parameter <mapping> is used for struct attribute to map key mapping, which makes -// sense only if the items of original map is type struct. -// -// TODO it's supposed supporting target type <pointer> like: map[int][]map, map[string][]map. -func doMapToMaps(params interface{}, pointer interface{}, mapping ...map[string]string) (err error) { - var ( - paramsRv = reflect.ValueOf(params) - paramsKind = paramsRv.Kind() - ) - if paramsKind == reflect.Ptr { - paramsRv = paramsRv.Elem() - paramsKind = paramsRv.Kind() - } - if paramsKind != reflect.Map { - return gerror.New("params should be type of map") - } - // Empty params map, no need continue. - if paramsRv.Len() == 0 { - return nil - } - var ( - pointerRv = reflect.ValueOf(pointer) - pointerKind = pointerRv.Kind() - ) - for pointerKind == reflect.Ptr { - pointerRv = pointerRv.Elem() - pointerKind = pointerRv.Kind() - } - if pointerKind != reflect.Map { - return gerror.New("pointer should be type of *map/**map") - } - defer func() { - // Catch the panic, especially the reflect operation panics. - if e := recover(); e != nil { - err = gerror.NewSkipf(1, "%v", e) - } - }() - var ( - paramsKeys = paramsRv.MapKeys() - pointerKeyType = pointerRv.Type().Key() - pointerValueType = pointerRv.Type().Elem() - dataMap = reflect.MakeMapWithSize(pointerRv.Type(), len(paramsKeys)) - ) - for _, key := range paramsKeys { - e := reflect.New(pointerValueType).Elem() - if err = Structs(paramsRv.MapIndex(key).Interface(), e.Addr(), mapping...); err != nil { - return err - } - dataMap.SetMapIndex( - reflect.ValueOf( - Convert( - key.Interface(), - pointerKeyType.Name(), - ), - ), - e, - ) - } - pointerRv.Set(dataMap) - return nil -} diff --git a/util/gconv/gconv_maps.go b/util/gconv/gconv_maps.go new file mode 100644 index 000000000..cc0dd33fb --- /dev/null +++ b/util/gconv/gconv_maps.go @@ -0,0 +1,119 @@ +// Copyright GoFrame Author(https://goframe.org). All Rights Reserved. +// +// This Source Code Form is subject to the terms of the MIT License. +// If a copy of the MIT was not distributed with this file, +// You can obtain one at https://github.com/gogf/gf. + +package gconv + +import "github.com/gogf/gf/internal/json" + +// SliceMap is alias of Maps. +func SliceMap(any interface{}) []map[string]interface{} { + return Maps(any) +} + +// SliceMapDeep is alias of MapsDeep. +func SliceMapDeep(any interface{}) []map[string]interface{} { + return MapsDeep(any) +} + +// SliceStruct is alias of Structs. +func SliceStruct(params interface{}, pointer interface{}, mapping ...map[string]string) (err error) { + return Structs(params, pointer, mapping...) +} + +// Maps converts `value` 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 + } + switch r := value.(type) { + case string: + list := make([]map[string]interface{}, 0) + if len(r) > 0 && r[0] == '[' && r[len(r)-1] == ']' { + if err := json.UnmarshalUseNumber([]byte(r), &list); err != nil { + return nil + } + return list + } else { + return nil + } + + case []byte: + list := make([]map[string]interface{}, 0) + if len(r) > 0 && r[0] == '[' && r[len(r)-1] == ']' { + if err := json.UnmarshalUseNumber(r, &list); err != nil { + return nil + } + return list + } else { + return nil + } + + case []map[string]interface{}: + return r + + default: + array := Interfaces(value) + if len(array) == 0 { + return nil + } + list := make([]map[string]interface{}, len(array)) + for k, v := range array { + list[k] = Map(v, tags...) + } + return list + } +} + +// MapsDeep converts `value` to []map[string]interface{} recursively. +// +// TODO completely implement the recursive converting for all types. +func MapsDeep(value interface{}, tags ...string) []map[string]interface{} { + if value == nil { + return nil + } + switch r := value.(type) { + case string: + list := make([]map[string]interface{}, 0) + if len(r) > 0 && r[0] == '[' && r[len(r)-1] == ']' { + if err := json.UnmarshalUseNumber([]byte(r), &list); err != nil { + return nil + } + return list + } else { + return nil + } + + case []byte: + list := make([]map[string]interface{}, 0) + if len(r) > 0 && r[0] == '[' && r[len(r)-1] == ']' { + if err := json.UnmarshalUseNumber(r, &list); err != nil { + return nil + } + return list + } else { + return nil + } + + case []map[string]interface{}: + list := make([]map[string]interface{}, len(r)) + for k, v := range r { + list[k] = MapDeep(v, tags...) + } + return list + + default: + array := Interfaces(value) + if len(array) == 0 { + return nil + } + list := make([]map[string]interface{}, len(array)) + for k, v := range array { + list[k] = MapDeep(v, tags...) + } + return list + } +} diff --git a/util/gconv/gconv_maptomap.go b/util/gconv/gconv_maptomap.go new file mode 100644 index 000000000..973f12e52 --- /dev/null +++ b/util/gconv/gconv_maptomap.go @@ -0,0 +1,146 @@ +// Copyright GoFrame Author(https://goframe.org). All Rights Reserved. +// +// This Source Code Form is subject to the terms of the MIT License. +// If a copy of the MIT was not distributed with this file, +// You can obtain one at https://github.com/gogf/gf. + +package gconv + +import ( + "github.com/gogf/gf/errors/gcode" + "github.com/gogf/gf/errors/gerror" + "github.com/gogf/gf/internal/json" + "reflect" +) + +// MapToMap converts any map type variable `params` to another map type variable `pointer` +// using reflect. +// See doMapToMap. +func MapToMap(params interface{}, pointer interface{}, mapping ...map[string]string) error { + return doMapToMap(params, pointer, mapping...) +} + +// doMapToMap converts any map type variable `params` to another map type variable `pointer`. +// +// The parameter `params` can be any type of map, like: +// map[string]string, map[string]struct, map[string]*struct, etc. +// +// The parameter `pointer` should be type of *map, like: +// map[int]string, map[string]struct, map[string]*struct, etc. +// +// The optional parameter `mapping` is used for struct attribute to map key mapping, which makes +// sense only if the items of original map `params` is type struct. +func doMapToMap(params interface{}, pointer interface{}, mapping ...map[string]string) (err error) { + // If given `params` is JSON, it then uses json.Unmarshal doing the converting. + switch r := params.(type) { + case []byte: + if json.Valid(r) { + if rv, ok := pointer.(reflect.Value); ok { + if rv.Kind() == reflect.Ptr { + return json.UnmarshalUseNumber(r, rv.Interface()) + } + } else { + return json.UnmarshalUseNumber(r, pointer) + } + } + case string: + if paramsBytes := []byte(r); json.Valid(paramsBytes) { + if rv, ok := pointer.(reflect.Value); ok { + if rv.Kind() == reflect.Ptr { + return json.UnmarshalUseNumber(paramsBytes, rv.Interface()) + } + } else { + return json.UnmarshalUseNumber(paramsBytes, pointer) + } + } + } + var ( + paramsRv reflect.Value + paramsKind reflect.Kind + keyToAttributeNameMapping map[string]string + ) + if len(mapping) > 0 { + keyToAttributeNameMapping = mapping[0] + } + if v, ok := params.(reflect.Value); ok { + paramsRv = v + } else { + paramsRv = reflect.ValueOf(params) + } + paramsKind = paramsRv.Kind() + if paramsKind == reflect.Ptr { + paramsRv = paramsRv.Elem() + paramsKind = paramsRv.Kind() + } + if paramsKind != reflect.Map { + return doMapToMap(Map(params), pointer, mapping...) + } + // Empty params map, no need continue. + if paramsRv.Len() == 0 { + return nil + } + var pointerRv reflect.Value + if v, ok := pointer.(reflect.Value); ok { + pointerRv = v + } else { + pointerRv = reflect.ValueOf(pointer) + } + pointerKind := pointerRv.Kind() + for pointerKind == reflect.Ptr { + pointerRv = pointerRv.Elem() + pointerKind = pointerRv.Kind() + } + if pointerKind != reflect.Map { + return gerror.NewCodef(gcode.CodeInvalidParameter, "pointer should be type of *map, but got:%s", pointerKind) + } + defer func() { + // Catch the panic, especially the reflect operation panics. + if exception := recover(); exception != nil { + if e, ok := exception.(errorStack); ok { + err = e + } else { + err = gerror.NewCodeSkipf(gcode.CodeInternalError, 1, "%v", exception) + } + } + }() + var ( + paramsKeys = paramsRv.MapKeys() + pointerKeyType = pointerRv.Type().Key() + pointerValueType = pointerRv.Type().Elem() + pointerValueKind = pointerValueType.Kind() + dataMap = reflect.MakeMapWithSize(pointerRv.Type(), len(paramsKeys)) + ) + // Retrieve the true element type of target map. + if pointerValueKind == reflect.Ptr { + pointerValueKind = pointerValueType.Elem().Kind() + } + for _, key := range paramsKeys { + e := reflect.New(pointerValueType).Elem() + switch pointerValueKind { + case reflect.Map, reflect.Struct: + if err = doStruct(paramsRv.MapIndex(key).Interface(), e, keyToAttributeNameMapping, ""); err != nil { + return err + } + default: + e.Set( + reflect.ValueOf( + Convert( + paramsRv.MapIndex(key).Interface(), + pointerValueType.String(), + ), + ), + ) + } + dataMap.SetMapIndex( + reflect.ValueOf( + Convert( + key.Interface(), + pointerKeyType.Name(), + ), + ), + e, + ) + } + pointerRv.Set(dataMap) + return nil +} diff --git a/util/gconv/gconv_maptomaps.go b/util/gconv/gconv_maptomaps.go new file mode 100644 index 000000000..a3c0928b5 --- /dev/null +++ b/util/gconv/gconv_maptomaps.go @@ -0,0 +1,147 @@ +// Copyright GoFrame Author(https://goframe.org). All Rights Reserved. +// +// This Source Code Form is subject to the terms of the MIT License. +// If a copy of the MIT was not distributed with this file, +// You can obtain one at https://github.com/gogf/gf. + +package gconv + +import ( + "github.com/gogf/gf/errors/gcode" + "github.com/gogf/gf/errors/gerror" + "github.com/gogf/gf/internal/json" + "reflect" +) + +// MapToMaps converts any slice type variable `params` to another map slice type variable `pointer`. +// See doMapToMaps. +func MapToMaps(params interface{}, pointer interface{}, mapping ...map[string]string) error { + return doMapToMaps(params, pointer, mapping...) +} + +// MapToMapsDeep converts any slice type variable `params` to another map slice type variable +// `pointer` recursively. +// Deprecated, use MapToMaps instead. +func MapToMapsDeep(params interface{}, pointer interface{}, mapping ...map[string]string) error { + return doMapToMaps(params, pointer, mapping...) +} + +// doMapToMaps converts any map type variable `params` to another map slice variable `pointer`. +// +// The parameter `params` can be type of []map, []*map, []struct, []*struct. +// +// The parameter `pointer` should be type of []map, []*map. +// +// The optional parameter `mapping` is used for struct attribute to map key mapping, which makes +// sense only if the item of `params` is type struct. +func doMapToMaps(params interface{}, pointer interface{}, mapping ...map[string]string) (err error) { + // If given `params` is JSON, it then uses json.Unmarshal doing the converting. + switch r := params.(type) { + case []byte: + if json.Valid(r) { + if rv, ok := pointer.(reflect.Value); ok { + if rv.Kind() == reflect.Ptr { + return json.UnmarshalUseNumber(r, rv.Interface()) + } + } else { + return json.UnmarshalUseNumber(r, pointer) + } + } + case string: + if paramsBytes := []byte(r); json.Valid(paramsBytes) { + if rv, ok := pointer.(reflect.Value); ok { + if rv.Kind() == reflect.Ptr { + return json.UnmarshalUseNumber(paramsBytes, rv.Interface()) + } + } else { + return json.UnmarshalUseNumber(paramsBytes, pointer) + } + } + } + // Params and its element type check. + var ( + paramsRv reflect.Value + paramsKind reflect.Kind + ) + if v, ok := params.(reflect.Value); ok { + paramsRv = v + } else { + paramsRv = reflect.ValueOf(params) + } + paramsKind = paramsRv.Kind() + if paramsKind == reflect.Ptr { + paramsRv = paramsRv.Elem() + paramsKind = paramsRv.Kind() + } + if paramsKind != reflect.Array && paramsKind != reflect.Slice { + return gerror.NewCode(gcode.CodeInvalidParameter, "params should be type of slice, eg: []map/[]*map/[]struct/[]*struct") + } + var ( + paramsElem = paramsRv.Type().Elem() + paramsElemKind = paramsElem.Kind() + ) + if paramsElemKind == reflect.Ptr { + paramsElem = paramsElem.Elem() + paramsElemKind = paramsElem.Kind() + } + if paramsElemKind != reflect.Map && paramsElemKind != reflect.Struct && paramsElemKind != reflect.Interface { + return gerror.NewCodef(gcode.CodeInvalidParameter, "params element should be type of map/*map/struct/*struct, but got: %s", paramsElemKind) + } + // Empty slice, no need continue. + if paramsRv.Len() == 0 { + return nil + } + // Pointer and its element type check. + var ( + pointerRv = reflect.ValueOf(pointer) + pointerKind = pointerRv.Kind() + ) + for pointerKind == reflect.Ptr { + pointerRv = pointerRv.Elem() + pointerKind = pointerRv.Kind() + } + if pointerKind != reflect.Array && pointerKind != reflect.Slice { + return gerror.NewCode(gcode.CodeInvalidParameter, "pointer should be type of *[]map/*[]*map") + } + var ( + pointerElemType = pointerRv.Type().Elem() + pointerElemKind = pointerElemType.Kind() + ) + if pointerElemKind == reflect.Ptr { + pointerElemKind = pointerElemType.Elem().Kind() + } + if pointerElemKind != reflect.Map { + return gerror.NewCode(gcode.CodeInvalidParameter, "pointer element should be type of map/*map") + } + defer func() { + // Catch the panic, especially the reflect operation panics. + if exception := recover(); exception != nil { + if e, ok := exception.(errorStack); ok { + err = e + } else { + err = gerror.NewCodeSkipf(gcode.CodeInternalError, 1, "%v", exception) + } + } + }() + var ( + pointerSlice = reflect.MakeSlice(pointerRv.Type(), paramsRv.Len(), paramsRv.Len()) + ) + for i := 0; i < paramsRv.Len(); i++ { + var item reflect.Value + if pointerElemType.Kind() == reflect.Ptr { + item = reflect.New(pointerElemType.Elem()) + if err = MapToMap(paramsRv.Index(i).Interface(), item, mapping...); err != nil { + return err + } + pointerSlice.Index(i).Set(item) + } else { + item = reflect.New(pointerElemType) + if err = MapToMap(paramsRv.Index(i).Interface(), item, mapping...); err != nil { + return err + } + pointerSlice.Index(i).Set(item.Elem()) + } + } + pointerRv.Set(pointerSlice) + return +} diff --git a/util/gconv/gconv_scan.go b/util/gconv/gconv_scan.go index 85e941e3e..9f32740b0 100644 --- a/util/gconv/gconv_scan.go +++ b/util/gconv/gconv_scan.go @@ -1,4 +1,4 @@ -// Copyright 2017 gf Author(https://github.com/gogf/gf). All Rights Reserved. +// Copyright GoFrame Author(https://goframe.org). All Rights Reserved. // // This Source Code Form is subject to the terms of the MIT License. // If a copy of the MIT was not distributed with this file, @@ -7,38 +7,78 @@ package gconv import ( + "github.com/gogf/gf/errors/gcode" "github.com/gogf/gf/errors/gerror" "reflect" ) -// Scan automatically calls Struct or Structs function according to the type of parameter -// <pointer> to implement the converting. -// It calls function Struct if <pointer> is type of *struct/**struct to do the converting. -// It calls function Structs if <pointer> is type of *[]struct/*[]*struct to do the converting. +// Scan automatically checks the type of `pointer` and converts `params` to `pointer`. It supports `pointer` +// with type of `*map/*[]map/*[]*map/*struct/**struct/*[]struct/*[]*struct` for converting. +// +// It calls function `doMapToMap` internally if `pointer` is type of *map for converting. +// It calls function `doMapToMaps` internally if `pointer` is type of *[]map/*[]*map for converting. +// It calls function `doStruct` internally if `pointer` is type of *struct/**struct for converting. +// It calls function `doStructs` internally if `pointer` is type of *[]struct/*[]*struct for converting. func Scan(params interface{}, pointer interface{}, mapping ...map[string]string) (err error) { - t := reflect.TypeOf(pointer) - k := t.Kind() - if k != reflect.Ptr { - return gerror.Newf("params should be type of pointer, but got: %v", k) + var ( + pointerType reflect.Type + pointerKind reflect.Kind + ) + if v, ok := pointer.(reflect.Value); ok { + pointerType = v.Type() + } else { + pointerType = reflect.TypeOf(pointer) } - switch t.Elem().Kind() { + if pointerType == nil { + return gerror.NewCode(gcode.CodeInvalidParameter, "parameter pointer should not be nil") + } + pointerKind = pointerType.Kind() + if pointerKind != reflect.Ptr { + return gerror.NewCodef(gcode.CodeInvalidParameter, "params should be type of pointer, but got type: %v", pointerKind) + } + var ( + pointerElem = pointerType.Elem() + pointerElemKind = pointerElem.Kind() + keyToAttributeNameMapping map[string]string + ) + if len(mapping) > 0 { + keyToAttributeNameMapping = mapping[0] + } + switch pointerElemKind { + case reflect.Map: + return doMapToMap(params, pointer, mapping...) + case reflect.Array, reflect.Slice: - return Structs(params, pointer, mapping...) + var ( + sliceElem = pointerElem.Elem() + sliceElemKind = sliceElem.Kind() + ) + for sliceElemKind == reflect.Ptr { + sliceElem = sliceElem.Elem() + sliceElemKind = sliceElem.Kind() + } + if sliceElemKind == reflect.Map { + return doMapToMaps(params, pointer, mapping...) + } + return doStructs(params, pointer, keyToAttributeNameMapping, "") + default: - return Struct(params, pointer, mapping...) + + return doStruct(params, pointer, keyToAttributeNameMapping, "") } } // ScanDeep automatically calls StructDeep or StructsDeep function according to the type of -// parameter <pointer> to implement the converting.. -// It calls function StructDeep if <pointer> is type of *struct/**struct to do the converting. -// It calls function StructsDeep if <pointer> is type of *[]struct/*[]*struct to do the converting. +// parameter `pointer` to implement the converting. +// +// It calls function StructDeep if `pointer` is type of *struct/**struct to do the converting. +// It calls function StructsDeep if `pointer` is type of *[]struct/*[]*struct to do the converting. // Deprecated, use Scan instead. func ScanDeep(params interface{}, pointer interface{}, mapping ...map[string]string) (err error) { t := reflect.TypeOf(pointer) k := t.Kind() if k != reflect.Ptr { - return gerror.Newf("params should be type of pointer, but got: %v", k) + return gerror.NewCodef(gcode.CodeInvalidParameter, "params should be type of pointer, but got: %v", k) } switch t.Elem().Kind() { case reflect.Array, reflect.Slice: diff --git a/util/gconv/gconv_slice.go b/util/gconv/gconv_slice.go index ffbc87772..a5c4126a5 100644 --- a/util/gconv/gconv_slice.go +++ b/util/gconv/gconv_slice.go @@ -1,127 +1,7 @@ -// Copyright 2017 gf Author(https://github.com/gogf/gf). All Rights Reserved. +// Copyright GoFrame Author(https://goframe.org). All Rights Reserved. // // This Source Code Form is subject to the terms of the MIT License. // If a copy of the MIT was not distributed with this file, // You can obtain one at https://github.com/gogf/gf. package gconv - -import ( - "github.com/gogf/gf/internal/json" -) - -// SliceMap is alias of Maps. -func SliceMap(i interface{}) []map[string]interface{} { - return Maps(i) -} - -// SliceMapDeep is alias of MapsDeep. -func SliceMapDeep(i interface{}) []map[string]interface{} { - return MapsDeep(i) -} - -// SliceStruct is alias of Structs. -func SliceStruct(params interface{}, pointer interface{}, mapping ...map[string]string) (err error) { - return Structs(params, pointer, mapping...) -} - -// SliceStructDeep is alias of StructsDeep. -// Deprecated, use SliceStruct instead. -func SliceStructDeep(params interface{}, pointer interface{}, mapping ...map[string]string) (err error) { - return StructsDeep(params, pointer, mapping...) -} - -// 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 - } - switch r := value.(type) { - case string: - list := make([]map[string]interface{}, 0) - if len(r) > 0 && r[0] == '[' && r[len(r)-1] == ']' { - if err := json.Unmarshal([]byte(r), &list); err != nil { - return nil - } - return list - } else { - return nil - } - - case []byte: - list := make([]map[string]interface{}, 0) - if len(r) > 0 && r[0] == '[' && r[len(r)-1] == ']' { - if err := json.Unmarshal(r, &list); err != nil { - return nil - } - return list - } else { - return nil - } - - case []map[string]interface{}: - return r - - default: - array := Interfaces(value) - if len(array) == 0 { - return nil - } - list := make([]map[string]interface{}, len(array)) - for k, v := range array { - list[k] = Map(v, tags...) - } - return list - } -} - -// MapsDeep converts <i> to []map[string]interface{} recursively. -// -// TODO completely implement the recursive converting for all types. -func MapsDeep(value interface{}, tags ...string) []map[string]interface{} { - if value == nil { - return nil - } - switch r := value.(type) { - case string: - list := make([]map[string]interface{}, 0) - if len(r) > 0 && r[0] == '[' && r[len(r)-1] == ']' { - if err := json.Unmarshal([]byte(r), &list); err != nil { - return nil - } - return list - } else { - return nil - } - - case []byte: - list := make([]map[string]interface{}, 0) - if len(r) > 0 && r[0] == '[' && r[len(r)-1] == ']' { - if err := json.Unmarshal(r, &list); err != nil { - return nil - } - return list - } else { - return nil - } - - case []map[string]interface{}: - list := make([]map[string]interface{}, len(r)) - for k, v := range r { - list[k] = MapDeep(v, tags...) - } - return list - - default: - array := Interfaces(value) - if len(array) == 0 { - return nil - } - list := make([]map[string]interface{}, len(array)) - for k, v := range array { - list[k] = MapDeep(v, tags...) - } - return list - } -} diff --git a/util/gconv/gconv_slice_any.go b/util/gconv/gconv_slice_any.go index 02b3d0ef2..90b5b372b 100644 --- a/util/gconv/gconv_slice_any.go +++ b/util/gconv/gconv_slice_any.go @@ -1,4 +1,4 @@ -// Copyright 2019 gf Author(https://github.com/gogf/gf). All Rights Reserved. +// Copyright GoFrame Author(https://goframe.org). All Rights Reserved. // // This Source Code Form is subject to the terms of the MIT License. // If a copy of the MIT was not distributed with this file, @@ -11,22 +11,22 @@ import ( ) // SliceAny is alias of Interfaces. -func SliceAny(i interface{}) []interface{} { - return Interfaces(i) +func SliceAny(any interface{}) []interface{} { + return Interfaces(any) } -// Interfaces converts <i> to []interface{}. -func Interfaces(i interface{}) []interface{} { - if i == nil { +// Interfaces converts `any` to []interface{}. +func Interfaces(any interface{}) []interface{} { + if any == nil { return nil } - if r, ok := i.([]interface{}); ok { + if r, ok := any.([]interface{}); ok { return r - } else if r, ok := i.(apiInterfaces); ok { + } else if r, ok := any.(apiInterfaces); ok { return r.Interfaces() } else { var array []interface{} - switch value := i.(type) { + switch value := any.(type) { case []string: array = make([]interface{}, len(value)) for k, v := range value { @@ -99,7 +99,7 @@ func Interfaces(i interface{}) []interface{} { default: // Finally we use reflection. var ( - reflectValue = reflect.ValueOf(i) + reflectValue = reflect.ValueOf(any) reflectKind = reflectValue.Kind() ) for reflectKind == reflect.Ptr { @@ -130,7 +130,7 @@ func Interfaces(i interface{}) []interface{} { // array = append(array, v) // } default: - return []interface{}{i} + return []interface{}{any} } } return array diff --git a/util/gconv/gconv_slice_float.go b/util/gconv/gconv_slice_float.go index 0ade14af5..86dd7ebe9 100644 --- a/util/gconv/gconv_slice_float.go +++ b/util/gconv/gconv_slice_float.go @@ -1,4 +1,4 @@ -// Copyright 2019 gf Author(https://github.com/gogf/gf). All Rights Reserved. +// Copyright GoFrame Author(https://goframe.org). All Rights Reserved. // // This Source Code Form is subject to the terms of the MIT License. // If a copy of the MIT was not distributed with this file, @@ -9,32 +9,32 @@ package gconv import "reflect" // SliceFloat is alias of Floats. -func SliceFloat(i interface{}) []float64 { - return Floats(i) +func SliceFloat(any interface{}) []float64 { + return Floats(any) } // SliceFloat32 is alias of Float32s. -func SliceFloat32(i interface{}) []float32 { - return Float32s(i) +func SliceFloat32(any interface{}) []float32 { + return Float32s(any) } // SliceFloat64 is alias of Float64s. -func SliceFloat64(i interface{}) []float64 { - return Floats(i) +func SliceFloat64(any interface{}) []float64 { + return Floats(any) } -// Floats converts <i> to []float64. -func Floats(i interface{}) []float64 { - return Float64s(i) +// Floats converts `any` to []float64. +func Floats(any interface{}) []float64 { + return Float64s(any) } -// Float32s converts <i> to []float32. -func Float32s(i interface{}) []float32 { - if i == nil { +// Float32s converts `any` to []float32. +func Float32s(any interface{}) []float32 { + if any == nil { return nil } var array []float32 - switch value := i.(type) { + switch value := any.(type) { case string: if value == "" { return []float32{} @@ -112,35 +112,49 @@ func Float32s(i interface{}) []float32 { array[k] = Float32(v) } default: - if v, ok := i.(apiFloats); ok { + if v, ok := any.(apiFloats); ok { return Float32s(v.Floats()) } - if v, ok := i.(apiInterfaces); ok { + if v, ok := any.(apiInterfaces); ok { return Float32s(v.Interfaces()) } - // Use reflect feature at last. - rv := reflect.ValueOf(i) - switch rv.Kind() { + // Not a common type, it then uses reflection for conversion. + var reflectValue reflect.Value + if v, ok := value.(reflect.Value); ok { + reflectValue = v + } else { + reflectValue = reflect.ValueOf(value) + } + reflectKind := reflectValue.Kind() + for reflectKind == reflect.Ptr { + reflectValue = reflectValue.Elem() + reflectKind = reflectValue.Kind() + } + switch reflectKind { case reflect.Slice, reflect.Array: - length := rv.Len() - array = make([]float32, length) - for n := 0; n < length; n++ { - array[n] = Float32(rv.Index(n).Interface()) + var ( + length = reflectValue.Len() + slice = make([]float32, length) + ) + for i := 0; i < length; i++ { + slice[i] = Float32(reflectValue.Index(i).Interface()) } + return slice + default: - return []float32{Float32(i)} + return []float32{Float32(any)} } } return array } -// Float64s converts <i> to []float64. -func Float64s(i interface{}) []float64 { - if i == nil { +// Float64s converts `any` to []float64. +func Float64s(any interface{}) []float64 { + if any == nil { return nil } var array []float64 - switch value := i.(type) { + switch value := any.(type) { case string: if value == "" { return []float64{} @@ -218,23 +232,37 @@ func Float64s(i interface{}) []float64 { array[k] = Float64(v) } default: - if v, ok := i.(apiFloats); ok { + if v, ok := any.(apiFloats); ok { return v.Floats() } - if v, ok := i.(apiInterfaces); ok { + if v, ok := any.(apiInterfaces); ok { return Floats(v.Interfaces()) } - // Use reflect feature at last. - rv := reflect.ValueOf(i) - switch rv.Kind() { + // Not a common type, it then uses reflection for conversion. + var reflectValue reflect.Value + if v, ok := value.(reflect.Value); ok { + reflectValue = v + } else { + reflectValue = reflect.ValueOf(value) + } + reflectKind := reflectValue.Kind() + for reflectKind == reflect.Ptr { + reflectValue = reflectValue.Elem() + reflectKind = reflectValue.Kind() + } + switch reflectKind { case reflect.Slice, reflect.Array: - length := rv.Len() - array = make([]float64, length) - for n := 0; n < length; n++ { - array[n] = Float64(rv.Index(n).Interface()) + var ( + length = reflectValue.Len() + slice = make([]float64, length) + ) + for i := 0; i < length; i++ { + slice[i] = Float64(reflectValue.Index(i).Interface()) } + return slice + default: - return []float64{Float64(i)} + return []float64{Float64(any)} } } return array diff --git a/util/gconv/gconv_slice_int.go b/util/gconv/gconv_slice_int.go index 7bb461bd5..1195d2053 100644 --- a/util/gconv/gconv_slice_int.go +++ b/util/gconv/gconv_slice_int.go @@ -1,4 +1,4 @@ -// Copyright 2019 gf Author(https://github.com/gogf/gf). All Rights Reserved. +// Copyright GoFrame Author(https://goframe.org). All Rights Reserved. // // This Source Code Form is subject to the terms of the MIT License. // If a copy of the MIT was not distributed with this file, @@ -9,27 +9,27 @@ package gconv import "reflect" // SliceInt is alias of Ints. -func SliceInt(i interface{}) []int { - return Ints(i) +func SliceInt(any interface{}) []int { + return Ints(any) } // SliceInt32 is alias of Int32s. -func SliceInt32(i interface{}) []int32 { - return Int32s(i) +func SliceInt32(any interface{}) []int32 { + return Int32s(any) } // SliceInt is alias of Int64s. -func SliceInt64(i interface{}) []int64 { - return Int64s(i) +func SliceInt64(any interface{}) []int64 { + return Int64s(any) } -// Ints converts <i> to []int. -func Ints(i interface{}) []int { - if i == nil { +// Ints converts `any` to []int. +func Ints(any interface{}) []int { + if any == nil { return nil } var array []int - switch value := i.(type) { + switch value := any.(type) { case string: if value == "" { return []int{} @@ -117,35 +117,49 @@ func Ints(i interface{}) []int { array[k] = Int(v) } default: - if v, ok := i.(apiInts); ok { + if v, ok := any.(apiInts); ok { return v.Ints() } - if v, ok := i.(apiInterfaces); ok { + if v, ok := any.(apiInterfaces); ok { return Ints(v.Interfaces()) } - // Use reflect feature at last. - rv := reflect.ValueOf(i) - switch rv.Kind() { + // Not a common type, it then uses reflection for conversion. + var reflectValue reflect.Value + if v, ok := value.(reflect.Value); ok { + reflectValue = v + } else { + reflectValue = reflect.ValueOf(value) + } + reflectKind := reflectValue.Kind() + for reflectKind == reflect.Ptr { + reflectValue = reflectValue.Elem() + reflectKind = reflectValue.Kind() + } + switch reflectKind { case reflect.Slice, reflect.Array: - length := rv.Len() - array = make([]int, length) - for n := 0; n < length; n++ { - array[n] = Int(rv.Index(n).Interface()) + var ( + length = reflectValue.Len() + slice = make([]int, length) + ) + for i := 0; i < length; i++ { + slice[i] = Int(reflectValue.Index(i).Interface()) } + return slice + default: - return []int{Int(i)} + return []int{Int(any)} } } return array } -// Int32s converts <i> to []int32. -func Int32s(i interface{}) []int32 { - if i == nil { +// Int32s converts `any` to []int32. +func Int32s(any interface{}) []int32 { + if any == nil { return nil } var array []int32 - switch value := i.(type) { + switch value := any.(type) { case string: if value == "" { return []int32{} @@ -233,24 +247,49 @@ func Int32s(i interface{}) []int32 { array[k] = Int32(v) } default: - if v, ok := i.(apiInts); ok { + if v, ok := any.(apiInts); ok { return Int32s(v.Ints()) } - if v, ok := i.(apiInterfaces); ok { + if v, ok := any.(apiInterfaces); ok { return Int32s(v.Interfaces()) } - return []int32{Int32(i)} + // Not a common type, it then uses reflection for conversion. + var reflectValue reflect.Value + if v, ok := value.(reflect.Value); ok { + reflectValue = v + } else { + reflectValue = reflect.ValueOf(value) + } + reflectKind := reflectValue.Kind() + for reflectKind == reflect.Ptr { + reflectValue = reflectValue.Elem() + reflectKind = reflectValue.Kind() + } + switch reflectKind { + case reflect.Slice, reflect.Array: + var ( + length = reflectValue.Len() + slice = make([]int32, length) + ) + for i := 0; i < length; i++ { + slice[i] = Int32(reflectValue.Index(i).Interface()) + } + return slice + + default: + return []int32{Int32(any)} + } } return array } -// Int64s converts <i> to []int64. -func Int64s(i interface{}) []int64 { - if i == nil { +// Int64s converts `any` to []int64. +func Int64s(any interface{}) []int64 { + if any == nil { return nil } var array []int64 - switch value := i.(type) { + switch value := any.(type) { case string: if value == "" { return []int64{} @@ -338,13 +377,38 @@ func Int64s(i interface{}) []int64 { array[k] = Int64(v) } default: - if v, ok := i.(apiInts); ok { + if v, ok := any.(apiInts); ok { return Int64s(v.Ints()) } - if v, ok := i.(apiInterfaces); ok { + if v, ok := any.(apiInterfaces); ok { return Int64s(v.Interfaces()) } - return []int64{Int64(i)} + // Not a common type, it then uses reflection for conversion. + var reflectValue reflect.Value + if v, ok := value.(reflect.Value); ok { + reflectValue = v + } else { + reflectValue = reflect.ValueOf(value) + } + reflectKind := reflectValue.Kind() + for reflectKind == reflect.Ptr { + reflectValue = reflectValue.Elem() + reflectKind = reflectValue.Kind() + } + switch reflectKind { + case reflect.Slice, reflect.Array: + var ( + length = reflectValue.Len() + slice = make([]int64, length) + ) + for i := 0; i < length; i++ { + slice[i] = Int64(reflectValue.Index(i).Interface()) + } + return slice + + default: + return []int64{Int64(any)} + } } return array } diff --git a/util/gconv/gconv_slice_str.go b/util/gconv/gconv_slice_str.go index 63f9fc9cd..0d235d529 100644 --- a/util/gconv/gconv_slice_str.go +++ b/util/gconv/gconv_slice_str.go @@ -1,4 +1,4 @@ -// Copyright 2019 gf Author(https://github.com/gogf/gf). All Rights Reserved. +// Copyright GoFrame Author(https://goframe.org). All Rights Reserved. // // This Source Code Form is subject to the terms of the MIT License. // If a copy of the MIT was not distributed with this file, @@ -9,17 +9,17 @@ package gconv import "reflect" // SliceStr is alias of Strings. -func SliceStr(i interface{}) []string { - return Strings(i) +func SliceStr(any interface{}) []string { + return Strings(any) } -// Strings converts <i> to []string. -func Strings(i interface{}) []string { - if i == nil { +// Strings converts `any` to []string. +func Strings(any interface{}) []string { + if any == nil { return nil } var array []string - switch value := i.(type) { + switch value := any.(type) { case []int: array = make([]string, len(value)) for k, v := range value { @@ -98,23 +98,37 @@ func Strings(i interface{}) []string { array[k] = String(v) } default: - if v, ok := i.(apiStrings); ok { + if v, ok := any.(apiStrings); ok { return v.Strings() } - if v, ok := i.(apiInterfaces); ok { + if v, ok := any.(apiInterfaces); ok { return Strings(v.Interfaces()) } - // Use reflect feature at last. - rv := reflect.ValueOf(i) - switch rv.Kind() { + // Not a common type, it then uses reflection for conversion. + var reflectValue reflect.Value + if v, ok := value.(reflect.Value); ok { + reflectValue = v + } else { + reflectValue = reflect.ValueOf(value) + } + reflectKind := reflectValue.Kind() + for reflectKind == reflect.Ptr { + reflectValue = reflectValue.Elem() + reflectKind = reflectValue.Kind() + } + switch reflectKind { case reflect.Slice, reflect.Array: - length := rv.Len() - array = make([]string, length) - for n := 0; n < length; n++ { - array[n] = String(rv.Index(n).Interface()) + var ( + length = reflectValue.Len() + slice = make([]string, length) + ) + for i := 0; i < length; i++ { + slice[i] = String(reflectValue.Index(i).Interface()) } + return slice + default: - return []string{String(i)} + return []string{String(any)} } } return array diff --git a/util/gconv/gconv_slice_uint.go b/util/gconv/gconv_slice_uint.go index 60b102dea..2cb3321d9 100644 --- a/util/gconv/gconv_slice_uint.go +++ b/util/gconv/gconv_slice_uint.go @@ -1,4 +1,4 @@ -// Copyright 2019 gf Author(https://github.com/gogf/gf). All Rights Reserved. +// Copyright GoFrame Author(https://goframe.org). All Rights Reserved. // // This Source Code Form is subject to the terms of the MIT License. // If a copy of the MIT was not distributed with this file, @@ -9,28 +9,28 @@ package gconv import "reflect" // SliceUint is alias of Uints. -func SliceUint(i interface{}) []uint { - return Uints(i) +func SliceUint(any interface{}) []uint { + return Uints(any) } // SliceUint32 is alias of Uint32s. -func SliceUint32(i interface{}) []uint32 { - return Uint32s(i) +func SliceUint32(any interface{}) []uint32 { + return Uint32s(any) } // SliceUint64 is alias of Uint64s. -func SliceUint64(i interface{}) []uint64 { - return Uint64s(i) +func SliceUint64(any interface{}) []uint64 { + return Uint64s(any) } -// Uints converts <i> to []uint. -func Uints(i interface{}) []uint { - if i == nil { +// Uints converts `any` to []uint. +func Uints(any interface{}) []uint { + if any == nil { return nil } var array []uint - switch value := i.(type) { + switch value := any.(type) { case string: if value == "" { return []uint{} @@ -113,35 +113,49 @@ func Uints(i interface{}) []uint { array[k] = Uint(v) } default: - if v, ok := i.(apiUints); ok { + if v, ok := any.(apiUints); ok { return v.Uints() } - if v, ok := i.(apiInterfaces); ok { + if v, ok := any.(apiInterfaces); ok { return Uints(v.Interfaces()) } - // Use reflect feature at last. - rv := reflect.ValueOf(i) - switch rv.Kind() { + // Not a common type, it then uses reflection for conversion. + var reflectValue reflect.Value + if v, ok := value.(reflect.Value); ok { + reflectValue = v + } else { + reflectValue = reflect.ValueOf(value) + } + reflectKind := reflectValue.Kind() + for reflectKind == reflect.Ptr { + reflectValue = reflectValue.Elem() + reflectKind = reflectValue.Kind() + } + switch reflectKind { case reflect.Slice, reflect.Array: - length := rv.Len() - array = make([]uint, length) - for n := 0; n < length; n++ { - array[n] = Uint(rv.Index(n).Interface()) + var ( + length = reflectValue.Len() + slice = make([]uint, length) + ) + for i := 0; i < length; i++ { + slice[i] = Uint(reflectValue.Index(i).Interface()) } + return slice + default: - return []uint{Uint(i)} + return []uint{Uint(any)} } } return array } -// Uint32s converts <i> to []uint32. -func Uint32s(i interface{}) []uint32 { - if i == nil { +// Uint32s converts `any` to []uint32. +func Uint32s(any interface{}) []uint32 { + if any == nil { return nil } var array []uint32 - switch value := i.(type) { + switch value := any.(type) { case string: if value == "" { return []uint32{} @@ -224,35 +238,49 @@ func Uint32s(i interface{}) []uint32 { array[k] = Uint32(v) } default: - if v, ok := i.(apiUints); ok { + if v, ok := any.(apiUints); ok { return Uint32s(v.Uints()) } - if v, ok := i.(apiInterfaces); ok { + if v, ok := any.(apiInterfaces); ok { return Uint32s(v.Interfaces()) } - // Use reflect feature at last. - rv := reflect.ValueOf(i) - switch rv.Kind() { + // Not a common type, it then uses reflection for conversion. + var reflectValue reflect.Value + if v, ok := value.(reflect.Value); ok { + reflectValue = v + } else { + reflectValue = reflect.ValueOf(value) + } + reflectKind := reflectValue.Kind() + for reflectKind == reflect.Ptr { + reflectValue = reflectValue.Elem() + reflectKind = reflectValue.Kind() + } + switch reflectKind { case reflect.Slice, reflect.Array: - length := rv.Len() - array = make([]uint32, length) - for n := 0; n < length; n++ { - array[n] = Uint32(rv.Index(n).Interface()) + var ( + length = reflectValue.Len() + slice = make([]uint32, length) + ) + for i := 0; i < length; i++ { + slice[i] = Uint32(reflectValue.Index(i).Interface()) } + return slice + default: - return []uint32{Uint32(i)} + return []uint32{Uint32(any)} } } return array } -// Uint64s converts <i> to []uint64. -func Uint64s(i interface{}) []uint64 { - if i == nil { +// Uint64s converts `any` to []uint64. +func Uint64s(any interface{}) []uint64 { + if any == nil { return nil } var array []uint64 - switch value := i.(type) { + switch value := any.(type) { case string: if value == "" { return []uint64{} @@ -335,23 +363,37 @@ func Uint64s(i interface{}) []uint64 { array[k] = Uint64(v) } default: - if v, ok := i.(apiUints); ok { + if v, ok := any.(apiUints); ok { return Uint64s(v.Uints()) } - if v, ok := i.(apiInterfaces); ok { + if v, ok := any.(apiInterfaces); ok { return Uint64s(v.Interfaces()) } - // Use reflect feature at last. - rv := reflect.ValueOf(i) - switch rv.Kind() { + // Not a common type, it then uses reflection for conversion. + var reflectValue reflect.Value + if v, ok := value.(reflect.Value); ok { + reflectValue = v + } else { + reflectValue = reflect.ValueOf(value) + } + reflectKind := reflectValue.Kind() + for reflectKind == reflect.Ptr { + reflectValue = reflectValue.Elem() + reflectKind = reflectValue.Kind() + } + switch reflectKind { case reflect.Slice, reflect.Array: - length := rv.Len() - array = make([]uint64, length) - for n := 0; n < length; n++ { - array[n] = Uint64(rv.Index(n).Interface()) + var ( + length = reflectValue.Len() + slice = make([]uint64, length) + ) + for i := 0; i < length; i++ { + slice[i] = Uint64(reflectValue.Index(i).Interface()) } + return slice + default: - return []uint64{Uint64(i)} + return []uint64{Uint64(any)} } } return array diff --git a/util/gconv/gconv_struct.go b/util/gconv/gconv_struct.go index a84226e33..bb959daa1 100644 --- a/util/gconv/gconv_struct.go +++ b/util/gconv/gconv_struct.go @@ -1,4 +1,4 @@ -// Copyright 2017 gf Author(https://github.com/gogf/gf). All Rights Reserved. +// Copyright GoFrame Author(https://goframe.org). All Rights Reserved. // // This Source Code Form is subject to the terms of the MIT License. // If a copy of the MIT was not distributed with this file, @@ -7,7 +7,7 @@ package gconv import ( - "fmt" + "github.com/gogf/gf/errors/gcode" "github.com/gogf/gf/errors/gerror" "github.com/gogf/gf/internal/empty" "github.com/gogf/gf/internal/json" @@ -19,71 +19,91 @@ import ( ) // Struct maps the params key-value pairs to the corresponding struct object's attributes. -// The third parameter <mapping> is unnecessary, indicating the mapping rules between the +// The third parameter `mapping` is unnecessary, indicating the mapping rules between the // custom key name and the attribute name(case sensitive). // // Note: -// 1. The <params> can be any type of map/struct, usually a map. -// 2. The <pointer> should be type of *struct/**struct, which is a pointer to struct object +// 1. The `params` can be any type of map/struct, usually a map. +// 2. The `pointer` should be type of *struct/**struct, which is a pointer to struct object // or struct pointer. // 3. Only the public attributes of struct object can be mapped. -// 4. If <params> is a map, the key of the map <params> can be lowercase. +// 4. If `params` is a map, the key of the map `params` can be lowercase. // It will automatically convert the first letter of the key to uppercase // in mapping procedure to do the matching. // It ignores the map key, if it does not match. func Struct(params interface{}, pointer interface{}, mapping ...map[string]string) (err error) { - return doStruct(params, pointer, mapping...) + return Scan(params, pointer, mapping...) +} + +// StructTag acts as Struct but also with support for priority tag feature, which retrieves the +// specified tags for `params` key-value items to struct attribute names mapping. +// The parameter `priorityTag` supports multiple tags that can be joined with char ','. +func StructTag(params interface{}, pointer interface{}, priorityTag string) (err error) { + return doStruct(params, pointer, nil, priorityTag) } // StructDeep do Struct function recursively. // Deprecated, use Struct instead. func StructDeep(params interface{}, pointer interface{}, mapping ...map[string]string) error { - return doStruct(params, pointer, mapping...) + var keyToAttributeNameMapping map[string]string + if len(mapping) > 0 { + keyToAttributeNameMapping = mapping[0] + } + return doStruct(params, pointer, keyToAttributeNameMapping, "") } // doStruct is the core internal converting function for any data to struct. -func doStruct(params interface{}, pointer interface{}, mapping ...map[string]string) (err error) { +func doStruct(params interface{}, pointer interface{}, mapping map[string]string, priorityTag string) (err error) { if params == nil { - // If <params> is nil, no conversion. + // If `params` is nil, no conversion. return nil } if pointer == nil { - return gerror.New("object pointer cannot be nil") + return gerror.NewCode(gcode.CodeInvalidParameter, "object pointer cannot be nil") } defer func() { // Catch the panic, especially the reflect operation panics. - if e := recover(); e != nil { - err = gerror.NewSkipf(1, "%v", e) + if exception := recover(); exception != nil { + if e, ok := exception.(errorStack); ok { + err = e + } else { + err = gerror.NewCodeSkipf(gcode.CodeInternalError, 1, "%v", exception) + } } }() - // If given <params> is JSON, it then uses json.Unmarshal doing the converting. + // If given `params` is JSON, it then uses json.Unmarshal doing the converting. switch r := params.(type) { case []byte: if json.Valid(r) { if rv, ok := pointer.(reflect.Value); ok { if rv.Kind() == reflect.Ptr { - return json.Unmarshal(r, rv.Interface()) + return json.UnmarshalUseNumber(r, rv.Interface()) + } else if rv.CanAddr() { + return json.UnmarshalUseNumber(r, rv.Addr().Interface()) } } else { - return json.Unmarshal(r, pointer) + return json.UnmarshalUseNumber(r, pointer) } } case string: if paramsBytes := []byte(r); json.Valid(paramsBytes) { if rv, ok := pointer.(reflect.Value); ok { if rv.Kind() == reflect.Ptr { - return json.Unmarshal(paramsBytes, rv.Interface()) + return json.UnmarshalUseNumber(paramsBytes, rv.Interface()) + } else if rv.CanAddr() { + return json.UnmarshalUseNumber(paramsBytes, rv.Addr().Interface()) } } else { - return json.Unmarshal(paramsBytes, pointer) + return json.UnmarshalUseNumber(paramsBytes, pointer) } } } var ( paramsReflectValue reflect.Value + paramsInterface interface{} // DO NOT use `params` directly as it might be type of `reflect.Value` pointerReflectValue reflect.Value pointerReflectKind reflect.Kind pointerElemReflectValue reflect.Value // The pointed element. @@ -93,6 +113,7 @@ func doStruct(params interface{}, pointer interface{}, mapping ...map[string]str } else { paramsReflectValue = reflect.ValueOf(params) } + paramsInterface = paramsReflectValue.Interface() if v, ok := pointer.(reflect.Value); ok { pointerReflectValue = v pointerElemReflectValue = v @@ -100,11 +121,11 @@ func doStruct(params interface{}, pointer interface{}, mapping ...map[string]str pointerReflectValue = reflect.ValueOf(pointer) pointerReflectKind = pointerReflectValue.Kind() if pointerReflectKind != reflect.Ptr { - return gerror.Newf("object pointer should be type of '*struct', but got '%v'", pointerReflectKind) + return gerror.NewCodef(gcode.CodeInvalidParameter, "object pointer should be type of '*struct', but got '%v'", pointerReflectKind) } // Using IsNil on reflect.Ptr variable is OK. if !pointerReflectValue.IsValid() || pointerReflectValue.IsNil() { - return gerror.New("object pointer cannot be nil") + return gerror.NewCode(gcode.CodeInvalidParameter, "object pointer cannot be nil") } pointerElemReflectValue = pointerReflectValue.Elem() } @@ -115,22 +136,24 @@ func doStruct(params interface{}, pointer interface{}, mapping ...map[string]str return nil } - // UnmarshalValue. - // Assign value with interface UnmarshalValue. - // Note that only pointer can implement interface UnmarshalValue. - if v, ok := pointerReflectValue.Interface().(apiUnmarshalValue); ok { - return v.UnmarshalValue(params) + // Normal unmarshalling interfaces checks. + if err, ok := bindVarToReflectValueWithInterfaceCheck(pointerReflectValue, paramsInterface); ok { + return err } // It automatically creates struct object if necessary. - // For example, if <pointer> is **User, then <elem> is *User, which is a pointer to User. + // For example, if `pointer` is **User, then `elem` is *User, which is a pointer to User. if pointerElemReflectValue.Kind() == reflect.Ptr { if !pointerElemReflectValue.IsValid() || pointerElemReflectValue.IsNil() { e := reflect.New(pointerElemReflectValue.Type().Elem()).Elem() pointerElemReflectValue.Set(e.Addr()) } - if v, ok := pointerElemReflectValue.Interface().(apiUnmarshalValue); ok { - return v.UnmarshalValue(params) + //if v, ok := pointerElemReflectValue.Interface().(apiUnmarshalValue); ok { + // return v.UnmarshalValue(params) + //} + // Note that it's `pointerElemReflectValue` here not `pointerReflectValue`. + if err, ok := bindVarToReflectValueWithInterfaceCheck(pointerElemReflectValue, paramsInterface); ok { + return err } // Retrieve its element, may be struct at last. pointerElemReflectValue = pointerElemReflectValue.Elem() @@ -138,9 +161,9 @@ func doStruct(params interface{}, pointer interface{}, mapping ...map[string]str // paramsMap is the map[string]interface{} type variable for params. // DO NOT use MapDeep here. - paramsMap := Map(params) + paramsMap := Map(paramsInterface) if paramsMap == nil { - return gerror.Newf("convert params to map failed: %v", params) + return gerror.NewCodef(gcode.CodeInvalidParameter, "convert params to map failed: %v", params) } // It only performs one converting to the same attribute. @@ -173,7 +196,7 @@ func doStruct(params interface{}, pointer interface{}, mapping ...map[string]str continue } } - if err = doStruct(paramsMap, elemFieldValue, mapping...); err != nil { + if err = doStruct(paramsMap, elemFieldValue, mapping, priorityTag); err != nil { return err } } else { @@ -187,13 +210,26 @@ func doStruct(params interface{}, pointer interface{}, mapping ...map[string]str // The key of the tagMap is the attribute name of the struct, // and the value is its replaced tag name for later comparison to improve performance. - tagMap := make(map[string]string) - tagToNameMap, err := structs.TagMapName(pointerElemReflectValue, StructTagPriority) + var ( + tagMap = make(map[string]string) + priorityTagArray []string + ) + if priorityTag != "" { + priorityTagArray = append(utils.SplitAndTrim(priorityTag, ","), StructTagPriority...) + } else { + priorityTagArray = StructTagPriority + } + tagToNameMap, err := structs.TagMapName(pointerElemReflectValue, priorityTagArray) if err != nil { return err } - for k, v := range tagToNameMap { - tagMap[v] = utils.RemoveSymbols(k) + for tagName, attributeName := range tagToNameMap { + // If there's something else in the tag string, + // it uses the first part which is split using char ','. + // Eg: + // orm:"id, priority" + // orm:"name, with:uid=id" + tagMap[attributeName] = utils.RemoveSymbols(strings.Split(tagName, ",")[0]) } var ( @@ -203,8 +239,8 @@ func doStruct(params interface{}, pointer interface{}, mapping ...map[string]str for mapK, mapV := range paramsMap { attrName = "" // It firstly checks the passed mapping rules. - if len(mapping) > 0 && len(mapping[0]) > 0 { - if passedAttrKey, ok := mapping[0][mapK]; ok { + if len(mapping) > 0 { + if passedAttrKey, ok := mapping[mapK]; ok { attrName = passedAttrKey } } @@ -215,7 +251,7 @@ func doStruct(params interface{}, pointer interface{}, mapping ...map[string]str // string cases and chars like '-'/'_'/'.'/' '. // Matching the parameters to struct tag names. - // The <tagV> is the attribute name of the struct. + // The `tagV` is the attribute name of the struct. for attrKey, cmpKey := range tagMap { if strings.EqualFold(checkName, cmpKey) { attrName = attrKey @@ -248,7 +284,7 @@ func doStruct(params interface{}, pointer interface{}, mapping ...map[string]str } // Mark it done. doneMap[attrName] = struct{}{} - if err := bindVarToStructAttr(pointerElemReflectValue, attrName, mapV, mapping...); err != nil { + if err := bindVarToStructAttr(pointerElemReflectValue, attrName, mapV, mapping, priorityTag); err != nil { return err } } @@ -256,7 +292,7 @@ func doStruct(params interface{}, pointer interface{}, mapping ...map[string]str } // bindVarToStructAttr sets value to struct object attribute by name. -func bindVarToStructAttr(elem reflect.Value, name string, value interface{}, mapping ...map[string]string) (err error) { +func bindVarToStructAttr(elem reflect.Value, name string, value interface{}, mapping map[string]string, priorityTag string) (err error) { structFieldValue := elem.FieldByName(name) if !structFieldValue.IsValid() { return nil @@ -266,9 +302,9 @@ func bindVarToStructAttr(elem reflect.Value, name string, value interface{}, map return nil } defer func() { - if e := recover(); e != nil { - if err = bindVarToReflectValue(structFieldValue, value, mapping...); err != nil { - err = gerror.Wrapf(err, `error binding value to attribute "%s"`, name) + if exception := recover(); exception != nil { + if err = bindVarToReflectValue(structFieldValue, value, mapping, priorityTag); err != nil { + err = gerror.WrapCodef(gcode.CodeInternalError, err, `error binding value to attribute "%s"`, name) } } }() @@ -276,36 +312,79 @@ func bindVarToStructAttr(elem reflect.Value, name string, value interface{}, map if empty.IsNil(value) { structFieldValue.Set(reflect.Zero(structFieldValue.Type())) } else { - structFieldValue.Set(reflect.ValueOf(Convert(value, structFieldValue.Type().String()))) + structFieldValue.Set(reflect.ValueOf(doConvert( + doConvertInput{ + FromValue: value, + ToTypeName: structFieldValue.Type().String(), + ReferValue: structFieldValue, + }, + ))) } return nil } // bindVarToReflectValueWithInterfaceCheck does binding using common interfaces checks. -func bindVarToReflectValueWithInterfaceCheck(structFieldValue reflect.Value, value interface{}) (err error, ok bool) { - if structFieldValue.CanAddr() { - pointer := structFieldValue.Addr().Interface() - if v, ok := pointer.(apiUnmarshalValue); ok { - return v.UnmarshalValue(value), ok +func bindVarToReflectValueWithInterfaceCheck(reflectValue reflect.Value, value interface{}) (err error, ok bool) { + var pointer interface{} + if reflectValue.Kind() != reflect.Ptr && reflectValue.CanAddr() { + reflectValueAddr := reflectValue.Addr() + if reflectValueAddr.IsNil() || !reflectValueAddr.IsValid() { + return nil, false } - if v, ok := pointer.(apiUnmarshalText); ok { - if s, ok := value.(string); ok { - return v.UnmarshalText([]byte(s)), ok - } - if b, ok := value.([]byte); ok { - return v.UnmarshalText(b), ok + // Not a pointer, but can token address, that makes it can be unmarshalled. + pointer = reflectValue.Addr().Interface() + } else { + if reflectValue.IsNil() || !reflectValue.IsValid() { + return nil, false + } + pointer = reflectValue.Interface() + } + // UnmarshalValue. + if v, ok := pointer.(apiUnmarshalValue); ok { + return v.UnmarshalValue(value), ok + } + // UnmarshalText. + if v, ok := pointer.(apiUnmarshalText); ok { + var valueBytes []byte + if b, ok := value.([]byte); ok { + valueBytes = b + } else if s, ok := value.(string); ok { + valueBytes = []byte(s) + } + if len(valueBytes) > 0 { + return v.UnmarshalText(valueBytes), ok + } + } + // UnmarshalJSON. + if v, ok := pointer.(apiUnmarshalJSON); ok { + var valueBytes []byte + if b, ok := value.([]byte); ok { + valueBytes = b + } else if s, ok := value.(string); ok { + valueBytes = []byte(s) + } + + if len(valueBytes) > 0 { + // If it is not a valid JSON string, it then adds char `"` on its both sides to make it is. + if !json.Valid(valueBytes) { + newValueBytes := make([]byte, len(valueBytes)+2) + newValueBytes[0] = '"' + newValueBytes[len(newValueBytes)-1] = '"' + copy(newValueBytes[1:], valueBytes) + valueBytes = newValueBytes } + return v.UnmarshalJSON(valueBytes), ok } - if v, ok := pointer.(apiSet); ok { - v.Set(value) - return nil, ok - } + } + if v, ok := pointer.(apiSet); ok { + v.Set(value) + return nil, ok } return nil, false } -// bindVarToReflectValue sets <value> to reflect value object <structFieldValue>. -func bindVarToReflectValue(structFieldValue reflect.Value, value interface{}, mapping ...map[string]string) (err error) { +// bindVarToReflectValue sets `value` to reflect value object `structFieldValue`. +func bindVarToReflectValue(structFieldValue reflect.Value, value interface{}, mapping map[string]string, priorityTag string) (err error) { if err, ok := bindVarToReflectValueWithInterfaceCheck(structFieldValue, value); ok { return err } @@ -323,9 +402,12 @@ func bindVarToReflectValue(structFieldValue reflect.Value, value interface{}, ma // Converting by kind. switch kind { + case reflect.Map: + return doMapToMap(value, structFieldValue, mapping) + case reflect.Struct: // Recursively converting for struct attribute. - if err := doStruct(value, structFieldValue); err != nil { + if err := doStruct(value, structFieldValue, nil, ""); err != nil { // Note there's reflect conversion mechanism here. structFieldValue.Set(reflect.ValueOf(value).Convert(structFieldValue.Type())) } @@ -342,14 +424,14 @@ func bindVarToReflectValue(structFieldValue reflect.Value, value interface{}, ma for i := 0; i < v.Len(); i++ { if t.Kind() == reflect.Ptr { e := reflect.New(t.Elem()).Elem() - if err := doStruct(v.Index(i).Interface(), e); err != nil { + if err := doStruct(v.Index(i).Interface(), e, nil, ""); err != nil { // Note there's reflect conversion mechanism here. e.Set(reflect.ValueOf(v.Index(i).Interface()).Convert(t)) } a.Index(i).Set(e.Addr()) } else { e := reflect.New(t).Elem() - if err := doStruct(v.Index(i).Interface(), e); err != nil { + if err := doStruct(v.Index(i).Interface(), e, nil, ""); err != nil { // Note there's reflect conversion mechanism here. e.Set(reflect.ValueOf(v.Index(i).Interface()).Convert(t)) } @@ -362,14 +444,14 @@ func bindVarToReflectValue(structFieldValue reflect.Value, value interface{}, ma t := a.Index(0).Type() if t.Kind() == reflect.Ptr { e := reflect.New(t.Elem()).Elem() - if err := doStruct(value, e); err != nil { + if err := doStruct(value, e, nil, ""); err != nil { // Note there's reflect conversion mechanism here. e.Set(reflect.ValueOf(value).Convert(t)) } a.Index(0).Set(e.Addr()) } else { e := reflect.New(t).Elem() - if err := doStruct(value, e); err != nil { + if err := doStruct(value, e, nil, ""); err != nil { // Note there's reflect conversion mechanism here. e.Set(reflect.ValueOf(value).Convert(t)) } @@ -385,7 +467,7 @@ func bindVarToReflectValue(structFieldValue reflect.Value, value interface{}, ma return err } elem := item.Elem() - if err = bindVarToReflectValue(elem, value, mapping...); err == nil { + if err = bindVarToReflectValue(elem, value, mapping, priorityTag); err == nil { structFieldValue.Set(elem.Addr()) } @@ -401,16 +483,17 @@ func bindVarToReflectValue(structFieldValue reflect.Value, value interface{}, ma default: defer func() { - if e := recover(); e != nil { - err = gerror.New( - fmt.Sprintf(`cannot convert value "%+v" to type "%s"`, - value, - structFieldValue.Type().String(), - ), + if exception := recover(); exception != nil { + err = gerror.NewCodef( + gcode.CodeInternalError, + `cannot convert value "%+v" to type "%s":%+v`, + value, + structFieldValue.Type().String(), + exception, ) } }() - // It here uses reflect converting <value> to type of the attribute and assigns + // It here uses reflect converting `value` to type of the attribute and assigns // the result value to the attribute. It might fail and panic if the usual Go // conversion rules do not allow conversion. structFieldValue.Set(reflect.ValueOf(value).Convert(structFieldValue.Type())) diff --git a/util/gconv/gconv_structs.go b/util/gconv/gconv_structs.go index 0372bb86f..2bb370af8 100644 --- a/util/gconv/gconv_structs.go +++ b/util/gconv/gconv_structs.go @@ -1,4 +1,4 @@ -// Copyright 2020 gf Author(https://github.com/gogf/gf). All Rights Reserved. +// Copyright GoFrame Author(https://goframe.org). All Rights Reserved. // // This Source Code Form is subject to the terms of the MIT License. // If a copy of the MIT was not distributed with this file, @@ -7,36 +7,49 @@ package gconv import ( + "github.com/gogf/gf/errors/gcode" "github.com/gogf/gf/errors/gerror" "github.com/gogf/gf/internal/json" "reflect" ) // Structs converts any slice to given struct slice. +// Also see Scan, Struct. func Structs(params interface{}, pointer interface{}, mapping ...map[string]string) (err error) { - return doStructs(params, pointer, mapping...) + return Scan(params, pointer, mapping...) +} + +// StructsTag acts as Structs but also with support for priority tag feature, which retrieves the +// specified tags for `params` key-value items to struct attribute names mapping. +// The parameter `priorityTag` supports multiple tags that can be joined with char ','. +func StructsTag(params interface{}, pointer interface{}, priorityTag string) (err error) { + return doStructs(params, pointer, nil, priorityTag) } // StructsDeep converts any slice to given struct slice recursively. // Deprecated, use Structs instead. func StructsDeep(params interface{}, pointer interface{}, mapping ...map[string]string) (err error) { - return doStructs(params, pointer, mapping...) + var keyToAttributeNameMapping map[string]string + if len(mapping) > 0 { + keyToAttributeNameMapping = mapping[0] + } + return doStructs(params, pointer, keyToAttributeNameMapping, "") } // doStructs converts any slice to given struct slice. // -// It automatically checks and converts json string to []map if <params> is string/[]byte. +// 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, +// 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{}, mapping ...map[string]string) (err error) { +func doStructs(params interface{}, pointer interface{}, mapping map[string]string, priorityTag string) (err error) { if params == nil { - // If <params> is nil, no conversion. + // If `params` is nil, no conversion. return nil } if pointer == nil { - return gerror.New("object pointer cannot be nil") + return gerror.NewCode(gcode.CodeInvalidParameter, "object pointer cannot be nil") } if doStructsByDirectReflectSet(params, pointer) { @@ -45,30 +58,34 @@ func doStructs(params interface{}, pointer interface{}, mapping ...map[string]st defer func() { // Catch the panic, especially the reflect operation panics. - if e := recover(); e != nil { - err = gerror.NewSkipf(1, "%v", e) + if exception := recover(); exception != nil { + if e, ok := exception.(errorStack); ok { + err = e + } else { + err = gerror.NewCodeSkipf(gcode.CodeInternalError, 1, "%v", exception) + } } }() - // If given <params> is JSON, it then uses json.Unmarshal doing the converting. + // If given `params` is JSON, it then uses json.Unmarshal doing the converting. switch r := params.(type) { case []byte: if json.Valid(r) { if rv, ok := pointer.(reflect.Value); ok { if rv.Kind() == reflect.Ptr { - return json.Unmarshal(r, rv.Interface()) + return json.UnmarshalUseNumber(r, rv.Interface()) } } else { - return json.Unmarshal(r, pointer) + return json.UnmarshalUseNumber(r, pointer) } } case string: if paramsBytes := []byte(r); json.Valid(paramsBytes) { if rv, ok := pointer.(reflect.Value); ok { if rv.Kind() == reflect.Ptr { - return json.Unmarshal(paramsBytes, rv.Interface()) + return json.UnmarshalUseNumber(paramsBytes, rv.Interface()) } } else { - return json.Unmarshal(paramsBytes, pointer) + return json.UnmarshalUseNumber(paramsBytes, pointer) } } } @@ -77,37 +94,75 @@ func doStructs(params interface{}, pointer interface{}, mapping ...map[string]st if !ok { pointerRv = reflect.ValueOf(pointer) if kind := pointerRv.Kind(); kind != reflect.Ptr { - return gerror.Newf("pointer should be type of pointer, but got: %v", kind) + return gerror.NewCodef(gcode.CodeInvalidParameter, "pointer should be type of pointer, but got: %v", kind) } } - // Converting <params> to map slice. - paramsMaps := Maps(params) - // If <params> is an empty slice, no conversion. - if len(paramsMaps) == 0 { + // Converting `params` to map slice. + var ( + paramsList []interface{} + paramsRv = reflect.ValueOf(params) + paramsKind = paramsRv.Kind() + ) + for paramsKind == reflect.Ptr { + paramsRv = paramsRv.Elem() + paramsKind = paramsRv.Kind() + } + switch paramsKind { + case reflect.Slice, reflect.Array: + paramsList = make([]interface{}, paramsRv.Len()) + for i := 0; i < paramsRv.Len(); i++ { + paramsList[i] = paramsRv.Index(i) + } + default: + var paramsMaps = Maps(params) + paramsList = make([]interface{}, len(paramsMaps)) + for i := 0; i < len(paramsMaps); i++ { + paramsList[i] = paramsMaps[i] + } + } + // If `params` is an empty slice, no conversion. + if len(paramsList) == 0 { return nil } var ( - array = reflect.MakeSlice(pointerRv.Type().Elem(), len(paramsMaps), len(paramsMaps)) - itemType = array.Index(0).Type() + reflectElemArray = reflect.MakeSlice(pointerRv.Type().Elem(), len(paramsList), len(paramsList)) + itemType = reflectElemArray.Index(0).Type() + itemTypeKind = itemType.Kind() + pointerRvElem = pointerRv.Elem() + pointerRvLength = pointerRvElem.Len() ) - for i := 0; i < len(paramsMaps); i++ { - if itemType.Kind() == reflect.Ptr { - // Slice element is type pointer. - e := reflect.New(itemType.Elem()).Elem() - if err = Struct(paramsMaps[i], e, mapping...); err != nil { + if itemTypeKind == reflect.Ptr { + // Pointer element. + for i := 0; i < len(paramsList); i++ { + var tempReflectValue reflect.Value + if i < pointerRvLength { + // Might be nil. + tempReflectValue = pointerRvElem.Index(i).Elem() + } + if !tempReflectValue.IsValid() { + tempReflectValue = reflect.New(itemType.Elem()).Elem() + } + if err = doStruct(paramsList[i], tempReflectValue, mapping, priorityTag); err != nil { return err } - array.Index(i).Set(e.Addr()) - } else { - // Slice element is not type of pointer. - e := reflect.New(itemType).Elem() - if err = Struct(paramsMaps[i], e, mapping...); err != nil { + reflectElemArray.Index(i).Set(tempReflectValue.Addr()) + } + } else { + // Struct element. + for i := 0; i < len(paramsList); i++ { + var tempReflectValue reflect.Value + if i < pointerRvLength { + tempReflectValue = pointerRvElem.Index(i) + } else { + tempReflectValue = reflect.New(itemType).Elem() + } + if err = doStruct(paramsList[i], tempReflectValue, mapping, priorityTag); err != nil { return err } - array.Index(i).Set(e) + reflectElemArray.Index(i).Set(tempReflectValue) } } - pointerRv.Elem().Set(array) + pointerRv.Elem().Set(reflectElemArray) return nil } diff --git a/util/gconv/gconv_time.go b/util/gconv/gconv_time.go index d7f6e5c47..20d9b77b7 100644 --- a/util/gconv/gconv_time.go +++ b/util/gconv/gconv_time.go @@ -1,4 +1,4 @@ -// Copyright 2017 gf Author(https://github.com/gogf/gf). All Rights Reserved. +// Copyright GoFrame Author(https://goframe.org). All Rights Reserved. // // This Source Code Form is subject to the terms of the MIT License. // If a copy of the MIT was not distributed with this file, @@ -13,51 +13,60 @@ import ( "github.com/gogf/gf/os/gtime" ) -// Time converts <i> to time.Time. -func Time(i interface{}, format ...string) time.Time { +// Time converts `any` to time.Time. +func Time(any interface{}, format ...string) time.Time { // It's already this type. if len(format) == 0 { - if v, ok := i.(time.Time); ok { + if v, ok := any.(time.Time); ok { return v } } - if t := GTime(i, format...); t != nil { + if t := GTime(any, format...); t != nil { return t.Time } return time.Time{} } -// Duration converts <i> to time.Duration. -// If <i> is string, then it uses time.ParseDuration to convert it. -// If <i> is numeric, then it converts <i> as nanoseconds. -func Duration(i interface{}) time.Duration { +// Duration converts `any` to time.Duration. +// If `any` is string, then it uses time.ParseDuration to convert it. +// If `any` is numeric, then it converts `any` as nanoseconds. +func Duration(any interface{}) time.Duration { // It's already this type. - if v, ok := i.(time.Duration); ok { + if v, ok := any.(time.Duration); ok { return v } - s := String(i) + s := String(any) if !utils.IsNumeric(s) { d, _ := gtime.ParseDuration(s) return d } - return time.Duration(Int64(i)) + return time.Duration(Int64(any)) } -// GTime converts <i> to *gtime.Time. -// The parameter <format> can be used to specify the format of <i>. -// If no <format> given, it converts <i> using gtime.NewFromTimeStamp if <i> is numeric, -// or using gtime.StrToTime if <i> is string. -func GTime(i interface{}, format ...string) *gtime.Time { - if i == nil { +// GTime converts `any` to *gtime.Time. +// The parameter `format` can be used to specify the format of `any`. +// If no `format` given, it converts `any` using gtime.NewFromTimeStamp if `any` is numeric, +// or using gtime.StrToTime if `any` is string. +func GTime(any interface{}, format ...string) *gtime.Time { + if any == nil { return nil } + if v, ok := any.(apiGTime); ok { + return v.GTime(format...) + } // It's already this type. if len(format) == 0 { - if v, ok := i.(*gtime.Time); ok { + if v, ok := any.(*gtime.Time); ok { return v } + if t, ok := any.(time.Time); ok { + return gtime.New(t) + } + if t, ok := any.(*time.Time); ok { + return gtime.New(t) + } } - s := String(i) + s := String(any) if len(s) == 0 { return gtime.New() } diff --git a/util/gconv/gconv_unsafe.go b/util/gconv/gconv_unsafe.go index ce2f824f3..e4b24fdfc 100644 --- a/util/gconv/gconv_unsafe.go +++ b/util/gconv/gconv_unsafe.go @@ -1,4 +1,4 @@ -// Copyright 2017 gf Author(https://github.com/gogf/gf). All Rights Reserved. +// Copyright GoFrame Author(https://goframe.org). All Rights Reserved. // // This Source Code Form is subject to the terms of the MIT License. // If a copy of the MIT was not distributed with this file, @@ -9,14 +9,14 @@ package gconv import "unsafe" // UnsafeStrToBytes converts string to []byte without memory copy. -// Note that, if you completely sure you will never use <s> variable in the feature, +// Note that, if you completely sure you will never use `s` variable in the feature, // you can use this unsafe function to implement type conversion in high performance. func UnsafeStrToBytes(s string) []byte { return *(*[]byte)(unsafe.Pointer(&s)) } // UnsafeBytesToStr converts []byte to string without memory copy. -// Note that, if you completely sure you will never use <b> variable in the feature, +// Note that, if you completely sure you will never use `b` variable in the feature, // you can use this unsafe function to implement type conversion in high performance. func UnsafeBytesToStr(b []byte) string { return *(*string)(unsafe.Pointer(&b)) diff --git a/util/gconv/gconv_z_bench_bytes_test.go b/util/gconv/gconv_z_bench_bytes_test.go index 3109b976a..901292439 100644 --- a/util/gconv/gconv_z_bench_bytes_test.go +++ b/util/gconv/gconv_z_bench_bytes_test.go @@ -1,4 +1,4 @@ -// Copyright 2017-2018 gf Author(https://github.com/gogf/gf). All Rights Reserved. +// Copyright GoFrame Author(https://goframe.org). All Rights Reserved. // // This Source Code Form is subject to the terms of the MIT License. // If a copy of the MIT was not distributed with this file, diff --git a/util/gconv/gconv_z_bench_float_test.go b/util/gconv/gconv_z_bench_float_test.go index 1c7f500b9..04606a1f7 100644 --- a/util/gconv/gconv_z_bench_float_test.go +++ b/util/gconv/gconv_z_bench_float_test.go @@ -1,4 +1,4 @@ -// Copyright 2017-2018 gf Author(https://github.com/gogf/gf). All Rights Reserved. +// Copyright GoFrame Author(https://goframe.org). All Rights Reserved. // // This Source Code Form is subject to the terms of the MIT License. // If a copy of the MIT was not distributed with this file, diff --git a/util/gconv/gconv_z_bench_int_test.go b/util/gconv/gconv_z_bench_int_test.go index 7d814d751..0f627a397 100644 --- a/util/gconv/gconv_z_bench_int_test.go +++ b/util/gconv/gconv_z_bench_int_test.go @@ -1,4 +1,4 @@ -// Copyright 2017-2018 gf Author(https://github.com/gogf/gf). All Rights Reserved. +// Copyright GoFrame Author(https://goframe.org). All Rights Reserved. // // This Source Code Form is subject to the terms of the MIT License. // If a copy of the MIT was not distributed with this file, diff --git a/util/gconv/gconv_z_bench_reflect_test.go b/util/gconv/gconv_z_bench_reflect_test.go index 162779cb5..cdc2890bb 100644 --- a/util/gconv/gconv_z_bench_reflect_test.go +++ b/util/gconv/gconv_z_bench_reflect_test.go @@ -1,4 +1,4 @@ -// Copyright 2017-2018 gf Author(https://github.com/gogf/gf). All Rights Reserved. +// Copyright GoFrame Author(https://goframe.org). All Rights Reserved. // // This Source Code Form is subject to the terms of the MIT License. // If a copy of the MIT was not distributed with this file, diff --git a/util/gconv/gconv_z_bench_str_test.go b/util/gconv/gconv_z_bench_str_test.go index f04fea07d..10790f990 100644 --- a/util/gconv/gconv_z_bench_str_test.go +++ b/util/gconv/gconv_z_bench_str_test.go @@ -1,4 +1,4 @@ -// Copyright 2017-2018 gf Author(https://github.com/gogf/gf). All Rights Reserved. +// Copyright GoFrame Author(https://goframe.org). All Rights Reserved. // // This Source Code Form is subject to the terms of the MIT License. // If a copy of the MIT was not distributed with this file, diff --git a/util/gconv/gconv_z_bench_struct_test.go b/util/gconv/gconv_z_bench_struct_test.go index 4373bdf9f..31f9ac57b 100644 --- a/util/gconv/gconv_z_bench_struct_test.go +++ b/util/gconv/gconv_z_bench_struct_test.go @@ -1,4 +1,4 @@ -// Copyright 2017-2018 gf Author(https://github.com/gogf/gf). All Rights Reserved. +// Copyright GoFrame Author(https://goframe.org). All Rights Reserved. // // This Source Code Form is subject to the terms of the MIT License. // If a copy of the MIT was not distributed with this file, diff --git a/util/gconv/gconv_z_unit_all_test.go b/util/gconv/gconv_z_unit_all_test.go index fd027adbd..a358c2362 100644 --- a/util/gconv/gconv_z_unit_all_test.go +++ b/util/gconv/gconv_z_unit_all_test.go @@ -1,4 +1,4 @@ -// Copyright 2019 gf Author(https://github.com/gogf/gf). All Rights Reserved. +// Copyright GoFrame Author(https://goframe.org). All Rights Reserved. // // This Source Code Form is subject to the terms of the MIT License. // If a copy of the MIT was not distributed with this file, @@ -38,8 +38,8 @@ func (s1 S1) Error() string { func Test_Bool_All(t *testing.T) { gtest.C(t, func(t *gtest.T) { - var i interface{} = nil - t.AssertEQ(gconv.Bool(i), false) + var any interface{} = nil + t.AssertEQ(gconv.Bool(any), false) t.AssertEQ(gconv.Bool(false), false) t.AssertEQ(gconv.Bool(nil), false) t.AssertEQ(gconv.Bool(0), false) @@ -72,8 +72,8 @@ func Test_Bool_All(t *testing.T) { func Test_Int_All(t *testing.T) { gtest.C(t, func(t *gtest.T) { - var i interface{} = nil - t.AssertEQ(gconv.Int(i), 0) + var any interface{} = nil + t.AssertEQ(gconv.Int(any), 0) t.AssertEQ(gconv.Int(false), 0) t.AssertEQ(gconv.Int(nil), 0) t.Assert(gconv.Int(nil), 0) @@ -107,8 +107,8 @@ func Test_Int_All(t *testing.T) { func Test_Int8_All(t *testing.T) { gtest.C(t, func(t *gtest.T) { - var i interface{} = nil - t.Assert(gconv.Int8(i), int8(0)) + var any interface{} = nil + t.Assert(gconv.Int8(any), int8(0)) t.AssertEQ(gconv.Int8(false), int8(0)) t.AssertEQ(gconv.Int8(nil), int8(0)) t.AssertEQ(gconv.Int8(0), int8(0)) @@ -141,8 +141,8 @@ func Test_Int8_All(t *testing.T) { func Test_Int16_All(t *testing.T) { gtest.C(t, func(t *gtest.T) { - var i interface{} = nil - t.Assert(gconv.Int16(i), int16(0)) + var any interface{} = nil + t.Assert(gconv.Int16(any), int16(0)) t.AssertEQ(gconv.Int16(false), int16(0)) t.AssertEQ(gconv.Int16(nil), int16(0)) t.AssertEQ(gconv.Int16(0), int16(0)) @@ -175,8 +175,8 @@ func Test_Int16_All(t *testing.T) { func Test_Int32_All(t *testing.T) { gtest.C(t, func(t *gtest.T) { - var i interface{} = nil - t.Assert(gconv.Int32(i), int32(0)) + var any interface{} = nil + t.Assert(gconv.Int32(any), int32(0)) t.AssertEQ(gconv.Int32(false), int32(0)) t.AssertEQ(gconv.Int32(nil), int32(0)) t.AssertEQ(gconv.Int32(0), int32(0)) @@ -209,11 +209,11 @@ func Test_Int32_All(t *testing.T) { func Test_Int64_All(t *testing.T) { gtest.C(t, func(t *gtest.T) { - var i interface{} = nil + var any interface{} = nil t.AssertEQ(gconv.Int64("0x00e"), int64(14)) t.Assert(gconv.Int64("022"), int64(18)) - t.Assert(gconv.Int64(i), int64(0)) + t.Assert(gconv.Int64(any), int64(0)) t.Assert(gconv.Int64(true), 1) t.Assert(gconv.Int64("1"), int64(1)) t.Assert(gconv.Int64("0"), int64(0)) @@ -263,8 +263,8 @@ func Test_Int64_All(t *testing.T) { func Test_Uint_All(t *testing.T) { gtest.C(t, func(t *gtest.T) { - var i interface{} = nil - t.AssertEQ(gconv.Uint(i), uint(0)) + var any interface{} = nil + t.AssertEQ(gconv.Uint(any), uint(0)) t.AssertEQ(gconv.Uint(false), uint(0)) t.AssertEQ(gconv.Uint(nil), uint(0)) t.Assert(gconv.Uint(nil), uint(0)) @@ -298,8 +298,8 @@ func Test_Uint_All(t *testing.T) { func Test_Uint8_All(t *testing.T) { gtest.C(t, func(t *gtest.T) { - var i interface{} = nil - t.Assert(gconv.Uint8(i), uint8(0)) + var any interface{} = nil + t.Assert(gconv.Uint8(any), uint8(0)) t.AssertEQ(gconv.Uint8(uint8(1)), uint8(1)) t.AssertEQ(gconv.Uint8(false), uint8(0)) t.AssertEQ(gconv.Uint8(nil), uint8(0)) @@ -333,8 +333,8 @@ func Test_Uint8_All(t *testing.T) { func Test_Uint16_All(t *testing.T) { gtest.C(t, func(t *gtest.T) { - var i interface{} = nil - t.Assert(gconv.Uint16(i), uint16(0)) + var any interface{} = nil + t.Assert(gconv.Uint16(any), uint16(0)) t.AssertEQ(gconv.Uint16(uint16(1)), uint16(1)) t.AssertEQ(gconv.Uint16(false), uint16(0)) t.AssertEQ(gconv.Uint16(nil), uint16(0)) @@ -368,8 +368,8 @@ func Test_Uint16_All(t *testing.T) { func Test_Uint32_All(t *testing.T) { gtest.C(t, func(t *gtest.T) { - var i interface{} = nil - t.Assert(gconv.Uint32(i), uint32(0)) + var any interface{} = nil + t.Assert(gconv.Uint32(any), uint32(0)) t.AssertEQ(gconv.Uint32(uint32(1)), uint32(1)) t.AssertEQ(gconv.Uint32(false), uint32(0)) t.AssertEQ(gconv.Uint32(nil), uint32(0)) @@ -403,11 +403,11 @@ func Test_Uint32_All(t *testing.T) { func Test_Uint64_All(t *testing.T) { gtest.C(t, func(t *gtest.T) { - var i interface{} = nil + var any interface{} = nil t.AssertEQ(gconv.Uint64("0x00e"), uint64(14)) t.Assert(gconv.Uint64("022"), uint64(18)) - t.AssertEQ(gconv.Uint64(i), uint64(0)) + t.AssertEQ(gconv.Uint64(any), uint64(0)) t.AssertEQ(gconv.Uint64(true), uint64(1)) t.Assert(gconv.Uint64("1"), int64(1)) t.Assert(gconv.Uint64("0"), uint64(0)) @@ -457,8 +457,8 @@ func Test_Uint64_All(t *testing.T) { func Test_Float32_All(t *testing.T) { gtest.C(t, func(t *gtest.T) { - var i interface{} = nil - t.Assert(gconv.Float32(i), float32(0)) + var any interface{} = nil + t.Assert(gconv.Float32(any), float32(0)) t.AssertEQ(gconv.Float32(false), float32(0)) t.AssertEQ(gconv.Float32(nil), float32(0)) t.AssertEQ(gconv.Float32(0), float32(0)) @@ -491,8 +491,8 @@ func Test_Float32_All(t *testing.T) { func Test_Float64_All(t *testing.T) { gtest.C(t, func(t *gtest.T) { - var i interface{} = nil - t.Assert(gconv.Float64(i), float64(0)) + var any interface{} = nil + t.Assert(gconv.Float64(any), float64(0)) t.AssertEQ(gconv.Float64(false), float64(0)) t.AssertEQ(gconv.Float64(nil), float64(0)) t.AssertEQ(gconv.Float64(0), float64(0)) @@ -527,8 +527,8 @@ func Test_String_All(t *testing.T) { gtest.C(t, func(t *gtest.T) { var s []rune t.AssertEQ(gconv.String(s), "") - var i interface{} = nil - t.AssertEQ(gconv.String(i), "") + var any interface{} = nil + t.AssertEQ(gconv.String(any), "") t.AssertEQ(gconv.String("1"), "1") t.AssertEQ(gconv.String("0"), string("0")) t.Assert(gconv.String("X"), string("X")) @@ -615,8 +615,8 @@ func Test_Byte_All(t *testing.T) { func Test_Convert_All(t *testing.T) { gtest.C(t, func(t *gtest.T) { - var i interface{} = nil - t.AssertEQ(gconv.Convert(i, "string"), "") + var any interface{} = nil + t.AssertEQ(gconv.Convert(any, "string"), "") t.AssertEQ(gconv.Convert("1", "string"), "1") t.Assert(gconv.Convert(int64(1), "int64"), int64(1)) t.Assert(gconv.Convert(int(0), "int"), int(0)) @@ -647,13 +647,6 @@ func Test_Convert_All(t *testing.T) { }) } -func Test_Time_All(t *testing.T) { - gtest.C(t, func(t *gtest.T) { - t.AssertEQ(gconv.Duration(""), time.Duration(int64(0))) - t.AssertEQ(gconv.GTime(""), gtime.New()) - }) -} - func Test_Slice_All(t *testing.T) { gtest.C(t, func(t *gtest.T) { value := 123.456 diff --git a/util/gconv/gconv_z_unit_basic_test.go b/util/gconv/gconv_z_unit_basic_test.go index 33c5e2db2..7a82eae3b 100644 --- a/util/gconv/gconv_z_unit_basic_test.go +++ b/util/gconv/gconv_z_unit_basic_test.go @@ -1,4 +1,4 @@ -// Copyright 2018 gf Author(https://github.com/gogf/gf). All Rights Reserved. +// Copyright GoFrame Author(https://goframe.org). All Rights Reserved. // // This Source Code Form is subject to the terms of the MIT License. // If a copy of the MIT was not distributed with this file, diff --git a/util/gconv/gconv_z_unit_bool_test.go b/util/gconv/gconv_z_unit_bool_test.go index 85eee9fcb..cfe2b143e 100644 --- a/util/gconv/gconv_z_unit_bool_test.go +++ b/util/gconv/gconv_z_unit_bool_test.go @@ -1,4 +1,4 @@ -// Copyright 2018 gf Author(https://github.com/gogf/gf). All Rights Reserved. +// Copyright GoFrame Author(https://goframe.org). All Rights Reserved. // // This Source Code Form is subject to the terms of the MIT License. // If a copy of the MIT was not distributed with this file, @@ -18,8 +18,8 @@ type boolStruct struct { func Test_Bool(t *testing.T) { gtest.C(t, func(t *gtest.T) { - var i interface{} = nil - t.AssertEQ(gconv.Bool(i), false) + var any interface{} = nil + t.AssertEQ(gconv.Bool(any), false) t.AssertEQ(gconv.Bool(false), false) t.AssertEQ(gconv.Bool(nil), false) t.AssertEQ(gconv.Bool(0), false) diff --git a/util/gconv/gconv_z_unit_custom_type_test.go b/util/gconv/gconv_z_unit_custom_type_test.go index 8fa33d1f2..4183546db 100644 --- a/util/gconv/gconv_z_unit_custom_type_test.go +++ b/util/gconv/gconv_z_unit_custom_type_test.go @@ -1,4 +1,4 @@ -// Copyright 2018 gf Author(https://github.com/gogf/gf). All Rights Reserved. +// Copyright GoFrame Author(https://goframe.org). All Rights Reserved. // // This Source Code Form is subject to the terms of the MIT License. // If a copy of the MIT was not distributed with this file, diff --git a/util/gconv/gconv_z_unit_map_test.go b/util/gconv/gconv_z_unit_map_test.go index 64d9d5822..f1676048c 100644 --- a/util/gconv/gconv_z_unit_map_test.go +++ b/util/gconv/gconv_z_unit_map_test.go @@ -1,4 +1,4 @@ -// Copyright 2018 gf Author(https://github.com/gogf/gf). All Rights Reserved. +// Copyright GoFrame Author(https://goframe.org). All Rights Reserved. // // This Source Code Form is subject to the terms of the MIT License. // If a copy of the MIT was not distributed with this file, @@ -317,6 +317,60 @@ func Test_MapDeep2(t *testing.T) { }) } +func Test_MapDeep3(t *testing.T) { + type Base struct { + Id int `c:"id"` + Date string `c:"date"` + } + type User struct { + UserBase Base `c:"base"` + Passport string `c:"passport"` + Password string `c:"password"` + Nickname string `c:"nickname"` + } + + gtest.C(t, func(t *gtest.T) { + user := &User{ + UserBase: Base{ + Id: 1, + Date: "2019-10-01", + }, + Passport: "john", + Password: "123456", + Nickname: "JohnGuo", + } + m := gconv.MapDeep(user) + t.Assert(m, g.Map{ + "base": g.Map{ + "id": user.UserBase.Id, + "date": user.UserBase.Date, + }, + "passport": user.Passport, + "password": user.Password, + "nickname": user.Nickname, + }) + }) + + gtest.C(t, func(t *gtest.T) { + user := &User{ + UserBase: Base{ + Id: 1, + Date: "2019-10-01", + }, + Passport: "john", + Password: "123456", + Nickname: "JohnGuo", + } + m := gconv.Map(user) + t.Assert(m, g.Map{ + "base": user.UserBase, + "passport": user.Passport, + "password": user.Password, + "nickname": user.Nickname, + }) + }) +} + func Test_MapDeepWithAttributeTag(t *testing.T) { type Ids struct { Id int `c:"id"` diff --git a/util/gconv/gconv_z_unit_maptomap_test.go b/util/gconv/gconv_z_unit_maptomap_test.go index 0bf458e78..838d96cd2 100644 --- a/util/gconv/gconv_z_unit_maptomap_test.go +++ b/util/gconv/gconv_z_unit_maptomap_test.go @@ -1,4 +1,4 @@ -// Copyright 2018 gf Author(https://github.com/gogf/gf). All Rights Reserved. +// Copyright GoFrame Author(https://goframe.org). All Rights Reserved. // // This Source Code Form is subject to the terms of the MIT License. // If a copy of the MIT was not distributed with this file, @@ -83,7 +83,7 @@ func Test_MapToMap2(t *testing.T) { gtest.C(t, func(t *gtest.T) { m := make(map[string]User) err := gconv.MapToMap(params, &m) - t.Assert(err, nil) + t.AssertNil(err) t.Assert(len(m), 1) t.Assert(m["key"].Id, 1) t.Assert(m["key"].Name, "john") @@ -143,184 +143,46 @@ func Test_MapToMapDeep(t *testing.T) { }) } -func Test_MapToMaps1(t *testing.T) { +func Test_MapToMaps(t *testing.T) { + params := g.Slice{ + g.Map{"id": 1, "name": "john"}, + g.Map{"id": 2, "name": "smith"}, + } + gtest.C(t, func(t *gtest.T) { + var s []g.Map + err := gconv.MapToMaps(params, &s) + t.AssertNil(err) + t.Assert(len(s), 2) + t.Assert(s, params) + }) + gtest.C(t, func(t *gtest.T) { + var s []*g.Map + err := gconv.MapToMaps(params, &s) + t.AssertNil(err) + t.Assert(len(s), 2) + t.Assert(s, params) + }) +} + +func Test_MapToMaps_StructParams(t *testing.T) { type User struct { Id int - Name int - } - params := g.Map{ - "key1": g.Slice{ - g.Map{"id": 1, "name": "john"}, - g.Map{"id": 2, "name": "smith"}, - }, - "key2": g.Slice{ - g.Map{"id": 3, "name": "green"}, - g.Map{"id": 4, "name": "jim"}, - }, - } - gtest.C(t, func(t *gtest.T) { - m := make(map[string][]User) - err := gconv.MapToMaps(params, &m) - t.Assert(err, nil) - t.Assert(len(m), 2) - t.Assert(m["key1"][0].Id, 1) - t.Assert(m["key1"][1].Id, 2) - t.Assert(m["key2"][0].Id, 3) - t.Assert(m["key2"][1].Id, 4) - }) - gtest.C(t, func(t *gtest.T) { - m := (map[string][]User)(nil) - err := gconv.MapToMaps(params, &m) - t.Assert(err, nil) - t.Assert(len(m), 2) - t.Assert(m["key1"][0].Id, 1) - t.Assert(m["key1"][1].Id, 2) - t.Assert(m["key2"][0].Id, 3) - t.Assert(m["key2"][1].Id, 4) - }) - gtest.C(t, func(t *gtest.T) { - m := make(map[string][]*User) - err := gconv.MapToMaps(params, &m) - t.Assert(err, nil) - t.Assert(len(m), 2) - t.Assert(m["key1"][0].Id, 1) - t.Assert(m["key1"][1].Id, 2) - t.Assert(m["key2"][0].Id, 3) - t.Assert(m["key2"][1].Id, 4) - }) - gtest.C(t, func(t *gtest.T) { - m := (map[string][]*User)(nil) - err := gconv.MapToMaps(params, &m) - t.Assert(err, nil) - t.Assert(len(m), 2) - t.Assert(m["key1"][0].Id, 1) - t.Assert(m["key1"][1].Id, 2) - t.Assert(m["key2"][0].Id, 3) - t.Assert(m["key2"][1].Id, 4) - }) -} - -func Test_MapToMaps2(t *testing.T) { - type User struct { - Id int - Name int - } - params := g.MapIntAny{ - 100: g.Slice{ - g.Map{"id": 1, "name": "john"}, - g.Map{"id": 2, "name": "smith"}, - }, - 200: g.Slice{ - g.Map{"id": 3, "name": "green"}, - g.Map{"id": 4, "name": "jim"}, - }, - } - gtest.C(t, func(t *gtest.T) { - m := make(map[int][]User) - err := gconv.MapToMaps(params, &m) - t.Assert(err, nil) - t.Assert(len(m), 2) - t.Assert(m[100][0].Id, 1) - t.Assert(m[100][1].Id, 2) - t.Assert(m[200][0].Id, 3) - t.Assert(m[200][1].Id, 4) - }) - gtest.C(t, func(t *gtest.T) { - m := make(map[int][]*User) - err := gconv.MapToMaps(params, &m) - t.Assert(err, nil) - t.Assert(len(m), 2) - t.Assert(m[100][0].Id, 1) - t.Assert(m[100][1].Id, 2) - t.Assert(m[200][0].Id, 3) - t.Assert(m[200][1].Id, 4) - }) - gtest.C(t, func(t *gtest.T) { - m := make(map[string][]*User) - err := gconv.MapToMaps(params, &m) - t.Assert(err, nil) - t.Assert(len(m), 2) - t.Assert(m["100"][0].Id, 1) - t.Assert(m["100"][1].Id, 2) - t.Assert(m["200"][0].Id, 3) - t.Assert(m["200"][1].Id, 4) - }) -} - -func Test_MapToMaps3(t *testing.T) { - type Ids struct { - Id int - Uid int - } - type Base struct { - Ids - Time string - } - type User struct { - Base Name string } - params := g.MapIntAny{ - 100: g.Slice{ - g.Map{"id": 1, "name": "john"}, - g.Map{"id": 2, "name": "smith"}, - }, - 200: g.Slice{ - g.Map{"id": 3, "name": "green"}, - g.Map{"id": 4, "name": "jim"}, - }, + params := g.Slice{ + User{1, "name1"}, + User{2, "name2"}, } gtest.C(t, func(t *gtest.T) { - m := make(map[string][]*User) - err := gconv.MapToMaps(params, &m) - t.Assert(err, nil) - t.Assert(len(m), 2) - t.Assert(m["100"][0].Id, 1) - t.Assert(m["100"][1].Id, 2) - t.Assert(m["100"][0].Name, "john") - t.Assert(m["100"][1].Name, "smith") - t.Assert(m["200"][0].Id, 3) - t.Assert(m["200"][1].Id, 4) - t.Assert(m["200"][0].Name, "green") - t.Assert(m["200"][1].Name, "jim") + var s []g.Map + err := gconv.MapToMaps(params, &s) + t.AssertNil(err) + t.Assert(len(s), 2) }) -} - -func Test_MapToMapsWithTag(t *testing.T) { - type Ids struct { - Id int - Uid int - } - type Base struct { - Ids `json:"ids"` - Time string - } - type User struct { - Base `json:"base"` - Name string - } - params := g.MapIntAny{ - 100: g.Slice{ - g.Map{"id": 1, "name": "john"}, - g.Map{"id": 2, "name": "smith"}, - }, - 200: g.Slice{ - g.Map{"id": 3, "name": "green"}, - g.Map{"id": 4, "name": "jim"}, - }, - } gtest.C(t, func(t *gtest.T) { - m := make(map[string][]*User) - err := gconv.MapToMaps(params, &m) - t.Assert(err, nil) - t.Assert(len(m), 2) - t.Assert(m["100"][0].Id, 1) - t.Assert(m["100"][1].Id, 2) - t.Assert(m["100"][0].Name, "john") - t.Assert(m["100"][1].Name, "smith") - t.Assert(m["200"][0].Id, 3) - t.Assert(m["200"][1].Id, 4) - t.Assert(m["200"][0].Name, "green") - t.Assert(m["200"][1].Name, "jim") + var s []*g.Map + err := gconv.MapToMaps(params, &s) + t.AssertNil(err) + t.Assert(len(s), 2) }) } diff --git a/util/gconv/gconv_z_unit_scan_test.go b/util/gconv/gconv_z_unit_scan_test.go index aaa41ff83..541baf7dc 100644 --- a/util/gconv/gconv_z_unit_scan_test.go +++ b/util/gconv/gconv_z_unit_scan_test.go @@ -1,4 +1,4 @@ -// Copyright 2020 gf Author(https://github.com/gogf/gf). All Rights Reserved. +// Copyright GoFrame Author(https://goframe.org). All Rights Reserved. // // This Source Code Form is subject to the terms of the MIT License. // If a copy of the MIT was not distributed with this file, @@ -13,7 +13,7 @@ import ( "testing" ) -func Test_Scan(t *testing.T) { +func Test_Scan_StructStructs(t *testing.T) { type User struct { Uid int Name string @@ -58,7 +58,7 @@ func Test_Scan(t *testing.T) { } ) err := gconv.Scan(params, &users) - t.Assert(err, nil) + t.AssertNil(err) t.Assert(users, g.Slice{ &User{ Uid: 1, @@ -76,7 +76,7 @@ func Test_Scan(t *testing.T) { }) } -func Test_ScanStr(t *testing.T) { +func Test_Scan_StructStr(t *testing.T) { type User struct { Uid int Name string @@ -123,3 +123,73 @@ func Test_ScanStr(t *testing.T) { }) }) } + +func Test_Scan_Map(t *testing.T) { + gtest.C(t, func(t *gtest.T) { + var m map[string]string + data := g.Map{ + "k1": "v1", + "k2": "v2", + } + err := gconv.Scan(data, &m) + t.AssertNil(err) + t.Assert(data, m) + }) + gtest.C(t, func(t *gtest.T) { + var m map[int]int + data := g.Map{ + "1": "11", + "2": "22", + } + err := gconv.Scan(data, &m) + t.AssertNil(err) + t.Assert(data, m) + }) + // json string parameter. + gtest.C(t, func(t *gtest.T) { + var m map[string]string + data := `{"k1":"v1","k2":"v2"}` + err := gconv.Scan(data, &m) + t.AssertNil(err) + t.Assert(m, g.Map{ + "k1": "v1", + "k2": "v2", + }) + }) +} + +func Test_Scan_Maps(t *testing.T) { + gtest.C(t, func(t *gtest.T) { + var maps []map[string]string + data := g.Slice{ + g.Map{ + "k1": "v1", + "k2": "v2", + }, + g.Map{ + "k3": "v3", + "k4": "v4", + }, + } + err := gconv.Scan(data, &maps) + t.AssertNil(err) + t.Assert(data, maps) + }) + // json string parameter. + gtest.C(t, func(t *gtest.T) { + var maps []map[string]string + data := `[{"k1":"v1","k2":"v2"},{"k3":"v3","k4":"v4"}]` + err := gconv.Scan(data, &maps) + t.AssertNil(err) + t.Assert(maps, g.Slice{ + g.Map{ + "k1": "v1", + "k2": "v2", + }, + g.Map{ + "k3": "v3", + "k4": "v4", + }, + }) + }) +} diff --git a/util/gconv/gconv_z_unit_slice_test.go b/util/gconv/gconv_z_unit_slice_test.go index 4b4769618..c67f4bf3f 100644 --- a/util/gconv/gconv_z_unit_slice_test.go +++ b/util/gconv/gconv_z_unit_slice_test.go @@ -1,4 +1,4 @@ -// Copyright 2018 gf Author(https://github.com/gogf/gf). All Rights Reserved. +// Copyright GoFrame Author(https://goframe.org). All Rights Reserved. // // This Source Code Form is subject to the terms of the MIT License. // If a copy of the MIT was not distributed with this file, @@ -7,6 +7,7 @@ package gconv_test import ( + "github.com/gogf/gf/container/gvar" "testing" "github.com/gogf/gf/frame/g" @@ -18,11 +19,20 @@ func Test_Slice(t *testing.T) { gtest.C(t, func(t *gtest.T) { value := 123.456 t.AssertEQ(gconv.Bytes("123"), []byte("123")) + t.AssertEQ(gconv.Bytes([]interface{}{1}), []byte{1}) + t.AssertEQ(gconv.Bytes([]interface{}{300}), []byte("[300]")) t.AssertEQ(gconv.Strings(value), []string{"123.456"}) t.AssertEQ(gconv.Ints(value), []int{123}) t.AssertEQ(gconv.Floats(value), []float64{123.456}) t.AssertEQ(gconv.Interfaces(value), []interface{}{123.456}) }) + gtest.C(t, func(t *gtest.T) { + s := []*gvar.Var{ + gvar.New(1), + gvar.New(2), + } + t.AssertEQ(gconv.SliceInt64(s), []int64{1, 2}) + }) } func Test_Slice_Empty(t *testing.T) { diff --git a/util/gconv/gconv_z_unit_string_test.go b/util/gconv/gconv_z_unit_string_test.go index 454f9b241..1317df9e3 100644 --- a/util/gconv/gconv_z_unit_string_test.go +++ b/util/gconv/gconv_z_unit_string_test.go @@ -1,4 +1,4 @@ -// Copyright 2018 gf Author(https://github.com/gogf/gf). All Rights Reserved. +// Copyright GoFrame Author(https://goframe.org). All Rights Reserved. // // This Source Code Form is subject to the terms of the MIT License. // If a copy of the MIT was not distributed with this file, diff --git a/util/gconv/gconv_z_unit_struct_interface_test.go b/util/gconv/gconv_z_unit_struct_marshal_unmarshal_test.go similarity index 84% rename from util/gconv/gconv_z_unit_struct_interface_test.go rename to util/gconv/gconv_z_unit_struct_marshal_unmarshal_test.go index 2794ef031..118022f82 100644 --- a/util/gconv/gconv_z_unit_struct_interface_test.go +++ b/util/gconv/gconv_z_unit_struct_marshal_unmarshal_test.go @@ -1,4 +1,4 @@ -// Copyright 2020 gf Author(https://github.com/gogf/gf). All Rights Reserved. +// Copyright GoFrame Author(https://goframe.org). All Rights Reserved. // // This Source Code Form is subject to the terms of the MIT License. // If a copy of the MIT was not distributed with this file, @@ -7,9 +7,9 @@ package gconv_test import ( - "errors" "github.com/gogf/gf/crypto/gcrc32" "github.com/gogf/gf/encoding/gbinary" + "github.com/gogf/gf/errors/gerror" "github.com/gogf/gf/frame/g" "github.com/gogf/gf/os/gtime" "github.com/gogf/gf/test/gtest" @@ -46,6 +46,12 @@ func Test_Struct_UnmarshalValue1(t *testing.T) { gtest.C(t, func(t *gtest.T) { st := &MyTimeSt{} err := gconv.Struct(g.Map{"ServiceDate": nil}, st) + t.AssertNil(err) + t.Assert(st.ServiceDate.Time.IsZero(), true) + }) + gtest.C(t, func(t *gtest.T) { + st := &MyTimeSt{} + err := gconv.Struct(g.Map{"ServiceDate": "error"}, st) t.AssertNE(err, nil) }) } @@ -78,16 +84,16 @@ func (p *Pkg) Marshal() []byte { func (p *Pkg) UnmarshalValue(v interface{}) error { b := gconv.Bytes(v) if len(b) < 6 { - return errors.New("invalid package length") + return gerror.New("invalid package length") } p.Length = gbinary.DecodeToUint16(b[:2]) if len(b) < int(p.Length) { - return errors.New("invalid data length") + return gerror.New("invalid data length") } p.Crc32 = gbinary.DecodeToUint32(b[2:6]) p.Data = b[6:] if gcrc32.Encrypt(p.Data) != p.Crc32 { - return errors.New("crc32 validation failed") + return gerror.New("crc32 validation failed") } return nil } diff --git a/util/gconv/gconv_z_unit_struct_tag_test.go b/util/gconv/gconv_z_unit_struct_tag_test.go new file mode 100644 index 000000000..daa06d477 --- /dev/null +++ b/util/gconv/gconv_z_unit_struct_tag_test.go @@ -0,0 +1,77 @@ +// Copyright GoFrame Author(https://goframe.org). All Rights Reserved. +// +// This Source Code Form is subject to the terms of the MIT License. +// If a copy of the MIT was not distributed with this file, +// You can obtain one at https://github.com/gogf/gf. + +package gconv_test + +import ( + "github.com/gogf/gf/frame/g" + "github.com/gogf/gf/test/gtest" + "github.com/gogf/gf/util/gconv" + "testing" +) + +func Test_StructTag(t *testing.T) { + type User struct { + Uid int + Name string + Pass1 string `orm:"password1"` + Pass2 string `orm:"password2"` + } + gtest.C(t, func(t *gtest.T) { + user := new(User) + params1 := g.Map{ + "uid": 1, + "Name": "john", + "password1": "123", + "password2": "456", + } + if err := gconv.Struct(params1, user); err != nil { + t.Error(err) + } + t.Assert(user, &User{ + Uid: 1, + Name: "john", + Pass1: "", + Pass2: "", + }) + }) + gtest.C(t, func(t *gtest.T) { + user := new(User) + params1 := g.Map{ + "uid": 1, + "Name": "john", + "password1": "123", + "password2": "456", + } + if err := gconv.StructTag(params1, user, "orm"); err != nil { + t.Error(err) + } + t.Assert(user, &User{ + Uid: 1, + Name: "john", + Pass1: "123", + Pass2: "456", + }) + }) + gtest.C(t, func(t *gtest.T) { + user := new(User) + params2 := g.Map{ + "uid": 2, + "name": "smith", + "password1": "111", + "password2": "222", + } + if err := gconv.StructTag(params2, user, "orm"); err != nil { + t.Error(err) + } + t.Assert(user, &User{ + Uid: 2, + Name: "smith", + Pass1: "111", + Pass2: "222", + }) + }) +} diff --git a/util/gconv/gconv_z_unit_struct_test.go b/util/gconv/gconv_z_unit_struct_test.go index 1932b13ce..9c7ff0713 100644 --- a/util/gconv/gconv_z_unit_struct_test.go +++ b/util/gconv/gconv_z_unit_struct_test.go @@ -1,4 +1,4 @@ -// Copyright 2018 gf Author(https://github.com/gogf/gf). All Rights Reserved. +// Copyright GoFrame Author(https://goframe.org). All Rights Reserved. // // This Source Code Form is subject to the terms of the MIT License. // If a copy of the MIT was not distributed with this file, @@ -7,14 +7,14 @@ package gconv_test import ( - "github.com/gogf/gf/internal/json" - "testing" - "time" - + "github.com/gogf/gf/container/gvar" "github.com/gogf/gf/frame/g" + "github.com/gogf/gf/internal/json" "github.com/gogf/gf/os/gtime" "github.com/gogf/gf/test/gtest" "github.com/gogf/gf/util/gconv" + "testing" + "time" ) func Test_Struct_Basic1(t *testing.T) { @@ -360,6 +360,45 @@ func Test_Struct_Attr_CustomType2(t *testing.T) { }) } +// From: k8s.io/apimachinery@v0.22.0/pkg/apis/meta/v1/duration.go +type MyDuration struct { + time.Duration +} + +// UnmarshalJSON implements the json.Unmarshaller interface. +func (d *MyDuration) UnmarshalJSON(b []byte) error { + var str string + err := json.Unmarshal(b, &str) + if err != nil { + return err + } + + pd, err := time.ParseDuration(str) + if err != nil { + return err + } + d.Duration = pd + return nil +} + +func Test_Struct_Attr_CustomType3(t *testing.T) { + type Config struct { + D MyDuration + } + gtest.C(t, func(t *gtest.T) { + config := new(Config) + err := gconv.Struct(g.Map{"d": "15s"}, config) + t.AssertNil(err) + t.Assert(config.D, "15s") + }) + gtest.C(t, func(t *gtest.T) { + config := new(Config) + err := gconv.Struct(g.Map{"d": `"15s"`}, config) + t.AssertNil(err) + t.Assert(config.D, "15s") + }) +} + func Test_Struct_PrivateAttribute(t *testing.T) { type User struct { Id int @@ -600,6 +639,22 @@ func Test_Struct_Time(t *testing.T) { }) } +func Test_Struct_GTime(t *testing.T) { + // https://github.com/gogf/gf/issues/1387 + gtest.C(t, func(t *gtest.T) { + type User struct { + Name string + CreateTime *gtime.Time + } + var user *User + err := gconv.Struct(`{"Name":"John","CreateTime":""}`, &user) + t.AssertNil(err) + t.AssertNE(user, nil) + t.Assert(user.Name, `John`) + t.Assert(user.CreateTime, nil) + }) +} + // Auto create struct when given pointer. func Test_Struct_Create(t *testing.T) { gtest.C(t, func(t *gtest.T) { @@ -817,7 +872,7 @@ func Test_Struct_Complex(t *testing.T) { "errorMsg": null }` m := make(g.Map) - err := json.Unmarshal([]byte(data), &m) + err := json.UnmarshalUseNumber([]byte(data), &m) t.Assert(err, nil) model := new(XinYanModel) @@ -904,6 +959,89 @@ func Test_Struct_Embedded(t *testing.T) { }) } +func Test_Struct_Slice(t *testing.T) { + gtest.C(t, func(t *gtest.T) { + type User struct { + Scores []int + } + user := new(User) + array := g.Slice{1, 2, 3} + err := gconv.Struct(g.Map{"scores": array}, user) + t.Assert(err, nil) + t.Assert(user.Scores, array) + }) + gtest.C(t, func(t *gtest.T) { + type User struct { + Scores []int32 + } + user := new(User) + array := g.Slice{1, 2, 3} + err := gconv.Struct(g.Map{"scores": array}, user) + t.Assert(err, nil) + t.Assert(user.Scores, array) + }) + gtest.C(t, func(t *gtest.T) { + type User struct { + Scores []int64 + } + user := new(User) + array := g.Slice{1, 2, 3} + err := gconv.Struct(g.Map{"scores": array}, user) + t.Assert(err, nil) + t.Assert(user.Scores, array) + }) + gtest.C(t, func(t *gtest.T) { + type User struct { + Scores []uint + } + user := new(User) + array := g.Slice{1, 2, 3} + err := gconv.Struct(g.Map{"scores": array}, user) + t.Assert(err, nil) + t.Assert(user.Scores, array) + }) + gtest.C(t, func(t *gtest.T) { + type User struct { + Scores []uint32 + } + user := new(User) + array := g.Slice{1, 2, 3} + err := gconv.Struct(g.Map{"scores": array}, user) + t.Assert(err, nil) + t.Assert(user.Scores, array) + }) + gtest.C(t, func(t *gtest.T) { + type User struct { + Scores []uint64 + } + user := new(User) + array := g.Slice{1, 2, 3} + err := gconv.Struct(g.Map{"scores": array}, user) + t.Assert(err, nil) + t.Assert(user.Scores, array) + }) + gtest.C(t, func(t *gtest.T) { + type User struct { + Scores []float32 + } + user := new(User) + array := g.Slice{1, 2, 3} + err := gconv.Struct(g.Map{"scores": array}, user) + t.Assert(err, nil) + t.Assert(user.Scores, array) + }) + gtest.C(t, func(t *gtest.T) { + type User struct { + Scores []float64 + } + user := new(User) + array := g.Slice{1, 2, 3} + err := gconv.Struct(g.Map{"scores": array}, user) + t.Assert(err, nil) + t.Assert(user.Scores, array) + }) +} + func Test_Struct_To_Struct(t *testing.T) { var TestA struct { Id int `p:"id"` @@ -1057,3 +1195,52 @@ func Test_Struct_JsonParam(t *testing.T) { t.Assert(a.Name, "john") }) } + +func Test_Struct_GVarAttribute(t *testing.T) { + type A struct { + Id int `json:"id"` + Name string `json:"name"` + Status bool `json:"status"` + } + gtest.C(t, func(t *gtest.T) { + var ( + a = A{} + data = g.Map{ + "id": 100, + "name": "john", + "status": gvar.New(false), + } + ) + err := gconv.Struct(data, &a) + t.Assert(err, nil) + t.Assert(a.Id, data["id"]) + t.Assert(a.Name, data["name"]) + t.Assert(a.Status, data["status"]) + }) + +} + +func Test_Struct_MapAttribute(t *testing.T) { + type NodeStatus struct { + ID int + } + type Nodes map[string]NodeStatus + type Output struct { + Nodes Nodes + } + + gtest.C(t, func(t *gtest.T) { + var ( + out = Output{} + data = g.Map{ + "nodes": g.Map{ + "name": g.Map{ + "id": 10000, + }, + }, + } + ) + err := gconv.Struct(data, &out) + t.AssertNil(err) + }) +} diff --git a/util/gconv/gconv_z_unit_struct_slice_test.go b/util/gconv/gconv_z_unit_structs_test.go similarity index 56% rename from util/gconv/gconv_z_unit_struct_slice_test.go rename to util/gconv/gconv_z_unit_structs_test.go index d10482acf..6279b002d 100644 --- a/util/gconv/gconv_z_unit_struct_slice_test.go +++ b/util/gconv/gconv_z_unit_structs_test.go @@ -1,4 +1,4 @@ -// Copyright 2019 gf Author(https://github.com/gogf/gf). All Rights Reserved. +// Copyright GoFrame Author(https://goframe.org). All Rights Reserved. // // This Source Code Form is subject to the terms of the MIT License. // If a copy of the MIT was not distributed with this file, @@ -14,90 +14,7 @@ import ( "github.com/gogf/gf/util/gconv" ) -func Test_Struct_Slice(t *testing.T) { - gtest.C(t, func(t *gtest.T) { - type User struct { - Scores []int - } - user := new(User) - array := g.Slice{1, 2, 3} - err := gconv.Struct(g.Map{"scores": array}, user) - t.Assert(err, nil) - t.Assert(user.Scores, array) - }) - gtest.C(t, func(t *gtest.T) { - type User struct { - Scores []int32 - } - user := new(User) - array := g.Slice{1, 2, 3} - err := gconv.Struct(g.Map{"scores": array}, user) - t.Assert(err, nil) - t.Assert(user.Scores, array) - }) - gtest.C(t, func(t *gtest.T) { - type User struct { - Scores []int64 - } - user := new(User) - array := g.Slice{1, 2, 3} - err := gconv.Struct(g.Map{"scores": array}, user) - t.Assert(err, nil) - t.Assert(user.Scores, array) - }) - gtest.C(t, func(t *gtest.T) { - type User struct { - Scores []uint - } - user := new(User) - array := g.Slice{1, 2, 3} - err := gconv.Struct(g.Map{"scores": array}, user) - t.Assert(err, nil) - t.Assert(user.Scores, array) - }) - gtest.C(t, func(t *gtest.T) { - type User struct { - Scores []uint32 - } - user := new(User) - array := g.Slice{1, 2, 3} - err := gconv.Struct(g.Map{"scores": array}, user) - t.Assert(err, nil) - t.Assert(user.Scores, array) - }) - gtest.C(t, func(t *gtest.T) { - type User struct { - Scores []uint64 - } - user := new(User) - array := g.Slice{1, 2, 3} - err := gconv.Struct(g.Map{"scores": array}, user) - t.Assert(err, nil) - t.Assert(user.Scores, array) - }) - gtest.C(t, func(t *gtest.T) { - type User struct { - Scores []float32 - } - user := new(User) - array := g.Slice{1, 2, 3} - err := gconv.Struct(g.Map{"scores": array}, user) - t.Assert(err, nil) - t.Assert(user.Scores, array) - }) - gtest.C(t, func(t *gtest.T) { - type User struct { - Scores []float64 - } - user := new(User) - array := g.Slice{1, 2, 3} - err := gconv.Struct(g.Map{"scores": array}, user) - t.Assert(err, nil) - t.Assert(user.Scores, array) - }) -} - -func Test_Struct_SliceWithTag(t *testing.T) { +func Test_Structs_WithTag(t *testing.T) { type User struct { Uid int `json:"id"` NickName string `json:"name"` @@ -144,6 +61,97 @@ func Test_Struct_SliceWithTag(t *testing.T) { }) } +func Test_Structs_WithoutTag(t *testing.T) { + type User struct { + Uid int + NickName string + } + gtest.C(t, func(t *gtest.T) { + var users []User + params := g.Slice{ + g.Map{ + "uid": 1, + "nick-name": "name1", + }, + g.Map{ + "uid": 2, + "nick-name": "name2", + }, + } + err := gconv.Structs(params, &users) + t.Assert(err, nil) + t.Assert(len(users), 2) + t.Assert(users[0].Uid, 1) + t.Assert(users[0].NickName, "name1") + t.Assert(users[1].Uid, 2) + t.Assert(users[1].NickName, "name2") + }) + gtest.C(t, func(t *gtest.T) { + var users []*User + params := g.Slice{ + g.Map{ + "uid": 1, + "nick-name": "name1", + }, + g.Map{ + "uid": 2, + "nick-name": "name2", + }, + } + err := gconv.Structs(params, &users) + t.Assert(err, nil) + t.Assert(len(users), 2) + t.Assert(users[0].Uid, 1) + t.Assert(users[0].NickName, "name1") + t.Assert(users[1].Uid, 2) + t.Assert(users[1].NickName, "name2") + }) +} + +func Test_Structs_SliceParameter(t *testing.T) { + gtest.C(t, func(t *gtest.T) { + type User struct { + Uid int + NickName string + } + var users []User + params := g.Slice{ + g.Map{ + "uid": 1, + "nick-name": "name1", + }, + g.Map{ + "uid": 2, + "nick-name": "name2", + }, + } + err := gconv.Structs(params, users) + t.AssertNE(err, nil) + }) + gtest.C(t, func(t *gtest.T) { + type User struct { + Uid int + NickName string + } + type A struct { + Users []User + } + var a A + params := g.Slice{ + g.Map{ + "uid": 1, + "nick-name": "name1", + }, + g.Map{ + "uid": 2, + "nick-name": "name2", + }, + } + err := gconv.Structs(params, a.Users) + t.AssertNE(err, nil) + }) +} + func Test_Structs_DirectReflectSet(t *testing.T) { type A struct { Id int @@ -175,7 +183,7 @@ func Test_Structs_DirectReflectSet(t *testing.T) { }) } -func Test_Structs_SliceIntAttribute(t *testing.T) { +func Test_Structs_IntSliceAttribute(t *testing.T) { type A struct { Id []int } diff --git a/util/gconv/gconv_z_unit_time_test.go b/util/gconv/gconv_z_unit_time_test.go index 7f27375a6..f44acc035 100644 --- a/util/gconv/gconv_z_unit_time_test.go +++ b/util/gconv/gconv_z_unit_time_test.go @@ -1,4 +1,4 @@ -// Copyright 2018 gf Author(https://github.com/gogf/gf). All Rights Reserved. +// Copyright GoFrame Author(https://goframe.org). All Rights Reserved. // // This Source Code Form is subject to the terms of the MIT License. // If a copy of the MIT was not distributed with this file, @@ -7,6 +7,8 @@ package gconv_test import ( + "github.com/gogf/gf/container/gvar" + "github.com/gogf/gf/frame/g" "testing" "time" @@ -17,9 +19,60 @@ import ( func Test_Time(t *testing.T) { gtest.C(t, func(t *gtest.T) { - t1 := "2011-10-10 01:02:03.456" - t.AssertEQ(gconv.GTime(t1), gtime.NewFromStr(t1)) - t.AssertEQ(gconv.Time(t1), gtime.NewFromStr(t1).Time) + t.AssertEQ(gconv.Duration(""), time.Duration(int64(0))) + t.AssertEQ(gconv.GTime(""), gtime.New()) + }) + + gtest.C(t, func(t *gtest.T) { + s := "2011-10-10 01:02:03.456" + t.AssertEQ(gconv.GTime(s), gtime.NewFromStr(s)) + t.AssertEQ(gconv.Time(s), gtime.NewFromStr(s).Time) t.AssertEQ(gconv.Duration(100), 100*time.Nanosecond) }) + gtest.C(t, func(t *gtest.T) { + s := "01:02:03.456" + t.AssertEQ(gconv.GTime(s).Hour(), 1) + t.AssertEQ(gconv.GTime(s).Minute(), 2) + t.AssertEQ(gconv.GTime(s).Second(), 3) + t.AssertEQ(gconv.GTime(s), gtime.NewFromStr(s)) + t.AssertEQ(gconv.Time(s), gtime.NewFromStr(s).Time) + }) + gtest.C(t, func(t *gtest.T) { + s := "0000-01-01 01:02:03" + t.AssertEQ(gconv.GTime(s).Year(), 0) + t.AssertEQ(gconv.GTime(s).Month(), 1) + t.AssertEQ(gconv.GTime(s).Day(), 1) + t.AssertEQ(gconv.GTime(s).Hour(), 1) + t.AssertEQ(gconv.GTime(s).Minute(), 2) + t.AssertEQ(gconv.GTime(s).Second(), 3) + t.AssertEQ(gconv.GTime(s), gtime.NewFromStr(s)) + t.AssertEQ(gconv.Time(s), gtime.NewFromStr(s).Time) + }) + gtest.C(t, func(t *gtest.T) { + t1 := gtime.NewFromStr("2021-05-21 05:04:51.206547+00") + t2 := gconv.GTime(gvar.New(t1)) + t3 := gvar.New(t1).GTime() + t.AssertEQ(t1, t2) + t.AssertEQ(t1.Local(), t2.Local()) + t.AssertEQ(t1, t3) + t.AssertEQ(t1.Local(), t3.Local()) + }) +} + +func Test_Time_Slice_Attribute(t *testing.T) { + type SelectReq struct { + Arr []*gtime.Time + One *gtime.Time + } + gtest.C(t, func(t *gtest.T) { + var s *SelectReq + err := gconv.Struct(g.Map{ + "arr": g.Slice{"2021-01-12 12:34:56", "2021-01-12 12:34:57"}, + "one": "2021-01-12 12:34:58", + }, &s) + t.AssertNil(err) + t.Assert(s.One, "2021-01-12 12:34:58") + t.Assert(s.Arr[0], "2021-01-12 12:34:56") + t.Assert(s.Arr[1], "2021-01-12 12:34:57") + }) } diff --git a/util/gconv/gconv_z_unit_unsafe_test.go b/util/gconv/gconv_z_unit_unsafe_test.go index 326d9a942..7a73cf8c7 100644 --- a/util/gconv/gconv_z_unit_unsafe_test.go +++ b/util/gconv/gconv_z_unit_unsafe_test.go @@ -1,4 +1,4 @@ -// Copyright 2018 gf Author(https://github.com/gogf/gf). All Rights Reserved. +// Copyright GoFrame Author(https://goframe.org). All Rights Reserved. // // This Source Code Form is subject to the terms of the MIT License. // If a copy of the MIT was not distributed with this file, diff --git a/util/gmeta/gmeta.go b/util/gmeta/gmeta.go new file mode 100644 index 000000000..c522fcd7b --- /dev/null +++ b/util/gmeta/gmeta.go @@ -0,0 +1,46 @@ +// Copyright GoFrame Author(https://goframe.org). All Rights Reserved. +// +// This Source Code Form is subject to the terms of the MIT License. +// If a copy of the MIT was not distributed with this file, +// You can obtain one at https://github.com/gogf/gf. + +// Package gmeta provides embedded meta data feature for struct. +package gmeta + +import ( + "github.com/gogf/gf/container/gvar" + "github.com/gogf/gf/internal/structs" +) + +// Meta is used as an embedded attribute for struct to enabled meta data feature. +type Meta struct{} + +const ( + // metaAttributeName is the attribute name of meta data in struct. + metaAttributeName = "Meta" +) + +// Data retrieves and returns all metadata from `object`. +// It automatically parses and caches the tag string from "Mata" attribute as its meta data. +func Data(object interface{}) map[string]interface{} { + reflectType, err := structs.StructType(object) + if err != nil { + panic(err) + } + if field, ok := reflectType.FieldByName(metaAttributeName); ok { + var ( + tags = structs.ParseTag(string(field.Tag)) + data = make(map[string]interface{}, len(tags)) + ) + for k, v := range tags { + data[k] = v + } + return data + } + return map[string]interface{}{} +} + +// Get retrieves and returns specified metadata by `key` from `object`. +func Get(object interface{}, key string) *gvar.Var { + return gvar.New(Data(object)[key]) +} diff --git a/util/gmeta/gmeta_test.go b/util/gmeta/gmeta_test.go new file mode 100644 index 000000000..2186d5414 --- /dev/null +++ b/util/gmeta/gmeta_test.go @@ -0,0 +1,78 @@ +// Copyright GoFrame Author(https://goframe.org). All Rights Reserved. +// +// This Source Code Form is subject to the terms of the MIT License. +// If a copy of the MIT was not distributed with this file, +// You can obtain one at https://github.com/gogf/gf. + +package gmeta_test + +import ( + "github.com/gogf/gf/internal/json" + "github.com/gogf/gf/util/gmeta" + "testing" + + "github.com/gogf/gf/test/gtest" +) + +type A struct { + gmeta.Meta `tag:"123" orm:"456"` + Id int + Name string +} + +var ( + a1 A + a2 *A +) + +func Benchmark_Data_Struct(b *testing.B) { + for i := 0; i < b.N; i++ { + gmeta.Data(a1) + } +} + +func Benchmark_Data_Pointer1(b *testing.B) { + for i := 0; i < b.N; i++ { + gmeta.Data(a2) + } +} + +func Benchmark_Data_Pointer2(b *testing.B) { + for i := 0; i < b.N; i++ { + gmeta.Data(&a2) + } +} + +func Benchmark_Data_Get_Struct(b *testing.B) { + for i := 0; i < b.N; i++ { + gmeta.Get(a1, "tag") + } +} + +func Benchmark_Data_Get_Pointer1(b *testing.B) { + for i := 0; i < b.N; i++ { + gmeta.Get(a2, "tag") + } +} + +func Benchmark_Data_Get_Pointer2(b *testing.B) { + for i := 0; i < b.N; i++ { + gmeta.Get(&a2, "tag") + } +} + +func TestMeta_Basic(t *testing.T) { + gtest.C(t, func(t *gtest.T) { + a := &A{ + Id: 100, + Name: "john", + } + t.Assert(len(gmeta.Data(a)), 2) + t.Assert(gmeta.Get(a, "tag").String(), "123") + t.Assert(gmeta.Get(a, "orm").String(), "456") + + b, err := json.Marshal(a) + t.AssertNil(err) + t.Assert(b, `{"Id":100,"Name":"john"}`) + }) +} diff --git a/util/gmode/gmode.go b/util/gmode/gmode.go index d94ef86b8..199e89a0d 100644 --- a/util/gmode/gmode.go +++ b/util/gmode/gmode.go @@ -1,4 +1,4 @@ -// Copyright 2019 gf Author(https://github.com/gogf/gf). All Rights Reserved. +// Copyright GoFrame Author(https://goframe.org). All Rights Reserved. // // This Source Code Form is subject to the terms of the MIT License. // If a copy of the MIT was not distributed with this file, @@ -16,15 +16,16 @@ import ( ) const ( - NOT_SET = "not-set" - DEVELOP = "develop" - TESTING = "testing" - STAGING = "staging" - PRODUCT = "product" - cmdEnvKey = "gf.gmode" + NOT_SET = "not-set" + DEVELOP = "develop" + TESTING = "testing" + STAGING = "staging" + PRODUCT = "product" + commandEnvKey = "gf.gmode" ) var ( + // Note that `currentMode` is not concurrent safe. currentMode = NOT_SET ) @@ -57,7 +58,7 @@ func SetProduct() { func Mode() string { // If current mode is not set, do this auto check. if currentMode == NOT_SET { - if v := gcmd.GetWithEnv(cmdEnvKey).String(); v != "" { + if v := gcmd.GetOptWithEnv(commandEnvKey).String(); v != "" { // Mode configured from command argument of environment. currentMode = v } else { diff --git a/util/gmode/gmode_test.go b/util/gmode/gmode_test.go index 0d0cf20a8..19549560b 100644 --- a/util/gmode/gmode_test.go +++ b/util/gmode/gmode_test.go @@ -1,4 +1,4 @@ -// Copyright 2020 gf Author(https://github.com/gogf/gf). All Rights Reserved. +// Copyright GoFrame Author(https://goframe.org). All Rights Reserved. // // This Source Code Form is subject to the terms of the MIT License. // If a copy of the MIT was not distributed with this file, diff --git a/util/gpage/gpage.go b/util/gpage/gpage.go index 4b240247d..a0b05ef3c 100644 --- a/util/gpage/gpage.go +++ b/util/gpage/gpage.go @@ -1,4 +1,4 @@ -// Copyright 2018 gf Author(https://github.com/gogf/gf). All Rights Reserved. +// Copyright GoFrame Author(https://goframe.org). All Rights Reserved. // // This Source Code Form is subject to the terms of the MIT License. // If a copy of the MIT was not distributed with this file, @@ -21,9 +21,9 @@ type Page struct { TotalPage int // Total page, which is automatically calculated. CurrentPage int // Current page number >= 1. UrlTemplate string // Custom url template for page url producing. - LinkStyle string // CSS style name for HTML link tag <a>. - SpanStyle string // CSS style name for HTML span tag <span>, which is used for first, current and last page tag. - SelectStyle string // CSS style name for HTML select tag <select>. + LinkStyle string // CSS style name for HTML link tag `a`. + SpanStyle string // CSS style name for HTML span tag `span`, which is used for first, current and last page tag. + SelectStyle string // CSS style name for HTML select tag `select`. NextPageTag string // Tag name for next p. PrevPageTag string // Tag name for prev p. FirstPageTag string // Tag name for first p. @@ -35,14 +35,14 @@ type Page struct { } const ( - PAGE_NAME = "page" // PAGE_NAME defines the default page name. - PAGE_PLACE_HOLDER = "{.page}" // PAGE_PLACE_HOLDER defines the place holder for the url template. + DefaultPageName = "page" // DefaultPageName defines the default page name. + DefaultPagePlaceHolder = "{.page}" // DefaultPagePlaceHolder defines the place holder for the url template. ) // New creates and returns a pagination manager. -// Note that the parameter <urlTemplate> specifies the URL producing template, like: +// Note that the parameter `urlTemplate` specifies the URL producing template, like: // /user/list/{.page}, /user/list/{.page}.html, /user/list?page={.page}&type=1, etc. -// The build-in variable in <urlTemplate> "{.page}" specifies the page number, which will be replaced by certain +// The build-in variable in `urlTemplate` "{.page}" specifies the page number, which will be replaced by certain // page number when producing. func New(totalSize, pageSize, currentPage int, urlTemplate string) *Page { p := &Page{ @@ -206,10 +206,10 @@ func (p *Page) GetContent(mode int) string { // Note that the UrlTemplate attribute can be either an URL or a URI string with "{.page}" // place holder specifying the page number position. func (p *Page) GetUrl(page int) string { - return gstr.Replace(p.UrlTemplate, PAGE_PLACE_HOLDER, gconv.String(page)) + return gstr.Replace(p.UrlTemplate, DefaultPagePlaceHolder, gconv.String(page)) } -// GetLink returns the HTML link tag <a> content for given page number. +// GetLink returns the HTML link tag `a` content for given page number. func (p *Page) GetLink(page int, text, title string) string { if len(p.AjaxActionName) > 0 { return fmt.Sprintf( diff --git a/util/gpage/gpage_unit_test.go b/util/gpage/gpage_unit_test.go index afa49c5f7..d2a12b1f6 100644 --- a/util/gpage/gpage_unit_test.go +++ b/util/gpage/gpage_unit_test.go @@ -1,4 +1,4 @@ -// Copyright 2019 gf Author(https://github.com/gogf/gf). All Rights Reserved. +// Copyright GoFrame Author(https://goframe.org). All Rights Reserved. // // This Source Code Form is subject to the terms of the MIT License. // If a copy of the MIT was not distributed with this file, diff --git a/util/grand/grand.go b/util/grand/grand.go index c39dea46f..63a71618d 100644 --- a/util/grand/grand.go +++ b/util/grand/grand.go @@ -1,4 +1,4 @@ -// Copyright 2017 gf Author(https://github.com/gogf/gf). All Rights Reserved. +// Copyright GoFrame Author(https://goframe.org). All Rights Reserved. // // This Source Code Form is subject to the terms of the MIT License. // If a copy of the MIT was not distributed with this file, @@ -9,6 +9,7 @@ package grand import ( "encoding/binary" + "time" "unsafe" ) @@ -22,8 +23,8 @@ var ( // Intn returns a int number which is between 0 and max: [0, max). // // Note that: -// 1. The <max> can only be greater than 0, or else it returns <max> directly; -// 2. The result is greater than or equal to 0, but less than <max>; +// 1. The `max` can only be greater than 0, or else it returns `max` directly; +// 2. The result is greater than or equal to 0, but less than `max`; // 3. The result number is 32bit and less than math.MaxUint32. func Intn(max int) int { if max <= 0 { @@ -36,7 +37,7 @@ func Intn(max int) int { return n } -// B retrieves and returns random bytes of given length <n>. +// B retrieves and returns random bytes of given length `n`. func B(n int) []byte { if n <= 0 { return nil @@ -54,7 +55,7 @@ func B(n int) []byte { } // N returns a random int between min and max: [min, max]. -// The <min> and <max> also support negative numbers. +// The `min` and `max` also support negative numbers. func N(min, max int) int { if min >= max { return min @@ -76,8 +77,8 @@ func N(min, max int) int { return 0 } -// S returns a random string which contains digits and letters, and its length is <n>. -// The optional parameter <symbols> specifies whether the result could contain symbols, +// S returns a random string which contains digits and letters, and its length is `n`. +// The optional parameter `symbols` specifies whether the result could contain symbols, // which is false in default. func S(n int, symbols ...bool) string { if n <= 0 { @@ -97,7 +98,21 @@ func S(n int, symbols ...bool) string { return *(*string)(unsafe.Pointer(&b)) } -// Str randomly picks and returns <n> count of chars from given string <s>. +// D returns a random time.Duration between min and max: [min, max]. +func D(min, max time.Duration) time.Duration { + multiple := int64(1) + if min != 0 { + for min%10 == 0 { + multiple *= 10 + min /= 10 + max /= 10 + } + } + n := int64(N(int(min), int(max))) + return time.Duration(n * multiple) +} + +// Str randomly picks and returns `n` count of chars from given string `s`. // It also supports unicode string like Chinese/Russian/Japanese, etc. func Str(s string, n int) string { if n <= 0 { @@ -120,7 +135,7 @@ func Str(s string, n int) string { return string(b) } -// Digits returns a random string which contains only digits, and its length is <n>. +// Digits returns a random string which contains only digits, and its length is `n`. func Digits(n int) string { if n <= 0 { return "" @@ -135,7 +150,7 @@ func Digits(n int) string { return *(*string)(unsafe.Pointer(&b)) } -// Letters returns a random string which contains only letters, and its length is <n>. +// Letters returns a random string which contains only letters, and its length is `n`. func Letters(n int) string { if n <= 0 { return "" @@ -150,7 +165,7 @@ func Letters(n int) string { return *(*string)(unsafe.Pointer(&b)) } -// Symbols returns a random string which contains only symbols, and its length is <n>. +// Symbols returns a random string which contains only symbols, and its length is `n`. func Symbols(n int) string { if n <= 0 { return "" @@ -177,7 +192,7 @@ func Perm(n int) []int { return m } -// Meet randomly calculate whether the given probability <num>/<total> is met. +// Meet randomly calculate whether the given probability `num`/`total` is met. func Meet(num, total int) bool { return Intn(total) < num } diff --git a/util/grand/grand_buffer.go b/util/grand/grand_buffer.go index f2b994e46..07861a515 100644 --- a/util/grand/grand_buffer.go +++ b/util/grand/grand_buffer.go @@ -1,4 +1,4 @@ -// Copyright 2018 gf Author(https://github.com/gogf/gf). All Rights Reserved. +// Copyright GoFrame Author(https://goframe.org). All Rights Reserved. // // This Source Code Form is subject to the terms of the MIT License. // If a copy of the MIT was not distributed with this file, @@ -12,13 +12,13 @@ import ( const ( // Buffer size for uint32 random number. - gBUFFER_SIZE = 10000 + bufferChanSize = 10000 ) var ( // bufferChan is the buffer for random bytes, // every item storing 4 bytes. - bufferChan = make(chan []byte, gBUFFER_SIZE) + bufferChan = make(chan []byte, bufferChanSize) ) func init() { diff --git a/util/grand/grand_z_bench_test.go b/util/grand/grand_z_bench_test.go index fc3a165ca..d2a84883e 100644 --- a/util/grand/grand_z_bench_test.go +++ b/util/grand/grand_z_bench_test.go @@ -1,4 +1,4 @@ -// Copyright 2018 gf Author(https://github.com/gogf/gf). All Rights Reserved. +// Copyright GoFrame Author(https://goframe.org). All Rights Reserved. // // This Source Code Form is subject to the terms of the MIT License. // If a copy of the MIT was not distributed with this file, @@ -9,11 +9,12 @@ package grand_test import ( - "crypto/rand" - "encoding/binary" - "testing" + cryptoRand "crypto/rand" + mathRand "math/rand" + "encoding/binary" "github.com/gogf/gf/util/grand" + "testing" ) var ( @@ -23,15 +24,21 @@ var ( strForStr = "我爱GoFrame" ) -func Benchmark_Rand_Buffer4(b *testing.B) { +func Benchmark_Math_Rand_Int(b *testing.B) { for i := 0; i < b.N; i++ { - rand.Read(randBuffer4) + mathRand.Int() } } -func Benchmark_Rand_Buffer1024(b *testing.B) { +func Benchmark_CryptoRand_Buffer4(b *testing.B) { for i := 0; i < b.N; i++ { - rand.Read(randBuffer1024) + cryptoRand.Read(randBuffer4) + } +} + +func Benchmark_CryptoRand_Buffer1024(b *testing.B) { + for i := 0; i < b.N; i++ { + cryptoRand.Read(randBuffer1024) } } @@ -101,9 +108,9 @@ func Benchmark_Uint32Converting(b *testing.B) { } } -func Benchmark_Buffer(b *testing.B) { +func Benchmark_CryptoRand_Buffer(b *testing.B) { for i := 0; i < b.N; i++ { - if _, err := rand.Read(buffer); err == nil { + if _, err := cryptoRand.Read(buffer); err == nil { binary.LittleEndian.Uint64(buffer) } } diff --git a/util/grand/grand_z_unit_test.go b/util/grand/grand_z_unit_test.go index d58a77b06..133df3282 100644 --- a/util/grand/grand_z_unit_test.go +++ b/util/grand/grand_z_unit_test.go @@ -1,4 +1,4 @@ -// Copyright 2018 gf Author(https://github.com/gogf/gf). All Rights Reserved. +// Copyright GoFrame Author(https://goframe.org). All Rights Reserved. // // This Source Code Form is subject to the terms of the MIT License. // If a copy of the MIT was not distributed with this file, @@ -11,6 +11,7 @@ package grand_test import ( "github.com/gogf/gf/text/gstr" "testing" + "time" "github.com/gogf/gf/test/gtest" "github.com/gogf/gf/util/grand" @@ -73,6 +74,23 @@ func Test_N(t *testing.T) { }) } +func Test_D(t *testing.T) { + gtest.C(t, func(t *gtest.T) { + for i := 0; i < 100; i++ { + t.Assert(grand.D(time.Second, time.Second), time.Second) + } + for i := 0; i < 100; i++ { + t.Assert(grand.D(0, 0), time.Duration(0)) + } + for i := 0; i < 100; i++ { + t.AssertIN( + grand.D(1*time.Second, 3*time.Second), + []time.Duration{1 * time.Second, 2 * time.Second, 3 * time.Second}, + ) + } + }) +} + func Test_Rand(t *testing.T) { gtest.C(t, func(t *gtest.T) { for i := 0; i < 100; i++ { diff --git a/util/guid/guid.go b/util/guid/guid.go index 2746c31b9..59cca7b6a 100644 --- a/util/guid/guid.go +++ b/util/guid/guid.go @@ -1,4 +1,4 @@ -// Copyright 2020 gf Author(https://github.com/gogf/gf). All Rights Reserved. +// Copyright GoFrame Author(https://goframe.org). All Rights Reserved. // // This Source Code Form is subject to the terms of the MIT License. // If a copy of the MIT was not distributed with this file, diff --git a/util/guid/guid_string.go b/util/guid/guid_string.go index 2668d666d..666d0ec8d 100644 --- a/util/guid/guid_string.go +++ b/util/guid/guid_string.go @@ -1,4 +1,4 @@ -// Copyright 2020 gf Author(https://github.com/gogf/gf). All Rights Reserved. +// Copyright GoFrame Author(https://goframe.org). All Rights Reserved. // // This Source Code Form is subject to the terms of the MIT License. // If a copy of the MIT was not distributed with this file, @@ -17,12 +17,15 @@ import ( "time" ) -var ( - sequence gtype.Uint32 // Sequence for unique purpose of current process. +const ( sequenceMax = uint32(46655) // Sequence max("zzz"). randomStrBase = "0123456789abcdefghijklmnopqrstuvwxyz" // Random chars string(36 bytes). - macAddrStr = "0000000" // MAC addresses hash result in 7 bytes. - processIdStr = "0000" // Process id in 4 bytes. +) + +var ( + sequence gtype.Uint32 // Sequence for unique purpose of current process. + macAddrStr = "0000000" // MAC addresses hash result in 7 bytes. + processIdStr = "0000" // Process id in 4 bytes. ) // init initializes several fixed local variable. @@ -50,11 +53,11 @@ func init() { // S creates and returns a global unique string in 32 bytes that meets most common // usages without strict UUID algorithm. It returns an unique string using default -// unique algorithm if no <data> is given. +// unique algorithm if no `data` is given. // -// The specified <data> can be no more than 2 parts. No matter how long each of the -// <data> size is, each of them will be hashed into 7 bytes as part of the result. -// If given <data> parts is less than 2, the leftover size of the result bytes will +// The specified `data` can be no more than 2 parts. No matter how long each of the +// `data` size is, each of them will be hashed into 7 bytes as part of the result. +// If given `data` parts is less than 2, the leftover size of the result bytes will // be token by random string. // // The returned string is composed with: @@ -63,7 +66,7 @@ func init() { // // Note that: // 1. The returned length is fixed to 32 bytes for performance purpose. -// 2. The custom parameter <data> composed should have unique attribute in your +// 2. The custom parameter `data` composed should have unique attribute in your // business situation. func S(data ...[]byte) string { var ( @@ -103,7 +106,7 @@ func getSequence() []byte { return b } -// getRandomStr randomly picks and returns <n> count of chars from randomStrBase. +// getRandomStr randomly picks and returns `n` count of chars from randomStrBase. func getRandomStr(n int) []byte { if n <= 0 { return []byte{} diff --git a/util/guid/guid_z_bench_test.go b/util/guid/guid_z_bench_test.go index 07955a648..e874373a6 100644 --- a/util/guid/guid_z_bench_test.go +++ b/util/guid/guid_z_bench_test.go @@ -1,4 +1,4 @@ -// Copyright 2018 gf Author(https://github.com/gogf/gf). All Rights Reserved. +// Copyright GoFrame Author(https://goframe.org). All Rights Reserved. // // This Source Code Form is subject to the terms of the MIT License. // If a copy of the MIT was not distributed with this file, diff --git a/util/guid/guid_z_unit_test.go b/util/guid/guid_z_unit_test.go index 1b35fdb9a..537be5aa5 100644 --- a/util/guid/guid_z_unit_test.go +++ b/util/guid/guid_z_unit_test.go @@ -1,4 +1,4 @@ -// Copyright 2018 gf Author(https://github.com/gogf/gf). All Rights Reserved. +// Copyright GoFrame Author(https://goframe.org). All Rights Reserved. // // This Source Code Form is subject to the terms of the MIT License. // If a copy of the MIT was not distributed with this file, diff --git a/util/gutil/gutil.go b/util/gutil/gutil.go index 81ad45ae8..38716cdf9 100644 --- a/util/gutil/gutil.go +++ b/util/gutil/gutil.go @@ -1,4 +1,4 @@ -// Copyright 2017 gf Author(https://github.com/gogf/gf). All Rights Reserved. +// Copyright GoFrame Author(https://goframe.org). All Rights Reserved. // // This Source Code Form is subject to the terms of the MIT License. // If a copy of the MIT was not distributed with this file, @@ -32,18 +32,22 @@ func Try(try func()) (err error) { } // TryCatch implements try...catch... logistics using internal panic...recover. -// It automatically calls function <catch> if any exception occurs ans passes the exception as an error. +// It automatically calls function `catch` if any exception occurs ans passes the exception as an error. func TryCatch(try func(), catch ...func(exception error)) { defer func() { - if e := recover(); e != nil && len(catch) > 0 { - catch[0](fmt.Errorf(`%v`, e)) + if exception := recover(); exception != nil && len(catch) > 0 { + if err, ok := exception.(error); ok { + catch[0](err) + } else { + catch[0](fmt.Errorf(`%v`, exception)) + } } }() try() } -// IsEmpty checks given <value> empty or not. -// It returns false if <value> is: integer(0), bool(false), slice/map(len=0), nil; +// IsEmpty checks given `value` empty or not. +// It returns false if `value` is: integer(0), bool(false), slice/map(len=0), nil; // or else returns true. func IsEmpty(value interface{}) bool { return empty.IsEmpty(value) diff --git a/util/gutil/gutil_comparator.go b/util/gutil/gutil_comparator.go index 6a3d1f2c1..0dbf4b98d 100644 --- a/util/gutil/gutil_comparator.go +++ b/util/gutil/gutil_comparator.go @@ -1,4 +1,4 @@ -// Copyright 2019 gf Author(https://github.com/gogf/gf). All Rights Reserved. +// Copyright GoFrame Author(https://goframe.org). All Rights Reserved. // // This Source Code Form is subject to the terms of the MIT License. // If a copy of the MIT was not distributed with this file, @@ -77,12 +77,28 @@ func ComparatorUint64(a, b interface{}) int { // ComparatorFloat32 provides a basic comparison on float32. func ComparatorFloat32(a, b interface{}) int { - return int(gconv.Float32(a) - gconv.Float32(b)) + aFloat := gconv.Float32(a) + bFloat := gconv.Float32(b) + if aFloat == bFloat { + return 0 + } + if aFloat > bFloat { + return 1 + } + return -1 } // ComparatorFloat64 provides a basic comparison on float64. func ComparatorFloat64(a, b interface{}) int { - return int(gconv.Float64(a) - gconv.Float64(b)) + aFloat := gconv.Float64(a) + bFloat := gconv.Float64(b) + if aFloat == bFloat { + return 0 + } + if aFloat > bFloat { + return 1 + } + return -1 } // ComparatorByte provides a basic comparison on byte. diff --git a/util/gutil/gutil_dump.go b/util/gutil/gutil_dump.go index 394146fb8..e499b4886 100644 --- a/util/gutil/gutil_dump.go +++ b/util/gutil/gutil_dump.go @@ -1,4 +1,4 @@ -// Copyright 2017 gf Author(https://github.com/gogf/gf). All Rights Reserved. +// Copyright GoFrame Author(https://goframe.org). All Rights Reserved. // // This Source Code Form is subject to the terms of the MIT License. // If a copy of the MIT was not distributed with this file, diff --git a/util/gutil/gutil_list.go b/util/gutil/gutil_list.go index 5be42b682..14fd8e2f6 100644 --- a/util/gutil/gutil_list.go +++ b/util/gutil/gutil_list.go @@ -1,4 +1,4 @@ -// Copyright 2020 gf Author(https://github.com/gogf/gf). All Rights Reserved. +// Copyright GoFrame Author(https://goframe.org). All Rights Reserved. // // This Source Code Form is subject to the terms of the MIT License. // If a copy of the MIT was not distributed with this file, @@ -10,16 +10,16 @@ import ( "reflect" ) -// ListItemValues retrieves and returns the elements of all item struct/map with key <key>. -// Note that the parameter <list> should be type of slice which contains elements of map or struct, +// ListItemValues retrieves and returns the elements of all item struct/map with key `key`. +// Note that the parameter `list` should be type of slice which contains elements of map or struct, // or else it returns an empty slice. // -// The parameter <list> supports types like: +// The parameter `list` supports types like: // []map[string]interface{} // []map[string]sub-map // []struct // []struct:sub-struct -// Note that the sub-map/sub-struct makes sense only if the optional parameter <subKey> is given. +// Note that the sub-map/sub-struct makes sense only if the optional parameter `subKey` is given. func ListItemValues(list interface{}, key interface{}, subKey ...interface{}) (values []interface{}) { var reflectValue reflect.Value if v, ok := list.(reflect.Value); ok { @@ -58,8 +58,8 @@ func ListItemValues(list interface{}, key interface{}, subKey ...interface{}) (v return } -// ItemValue retrieves and returns its value of which name/attribute specified by <key>. -// The parameter <item> can be type of map/*map/struct/*struct. +// ItemValue retrieves and returns its value of which name/attribute specified by `key`. +// The parameter `item` can be type of map/*map/struct/*struct. func ItemValue(item interface{}, key interface{}) (value interface{}, found bool) { var reflectValue reflect.Value if v, ok := item.(reflect.Value); ok { @@ -84,7 +84,7 @@ func ItemValue(item interface{}, key interface{}) (value interface{}, found bool } switch reflectKind { case reflect.Array, reflect.Slice: - // The <key> must be type of string. + // The `key` must be type of string. values := ListItemValues(reflectValue, keyValue.String()) if values == nil { return nil, false @@ -99,7 +99,7 @@ func ItemValue(item interface{}, key interface{}) (value interface{}, found bool } case reflect.Struct: - // The <mapKey> must be type of string. + // The `mapKey` must be type of string. v := reflectValue.FieldByName(keyValue.String()) if v.IsValid() { found = true @@ -109,8 +109,8 @@ func ItemValue(item interface{}, key interface{}) (value interface{}, found bool return } -// ListItemValuesUnique retrieves and returns the unique elements of all struct/map with key <key>. -// Note that the parameter <list> should be type of slice which contains elements of map or struct, +// ListItemValuesUnique retrieves and returns the unique elements of all struct/map with key `key`. +// Note that the parameter `list` should be type of slice which contains elements of map or struct, // or else it returns an empty slice. func ListItemValuesUnique(list interface{}, key string, subKey ...interface{}) []interface{} { values := ListItemValues(list, key, subKey...) diff --git a/util/gutil/gutil_map.go b/util/gutil/gutil_map.go index 89a5ad55c..8516b0035 100644 --- a/util/gutil/gutil_map.go +++ b/util/gutil/gutil_map.go @@ -1,4 +1,4 @@ -// Copyright GoFrame Author(https://github.com/gogf/gf). All Rights Reserved. +// Copyright GoFrame Author(https://goframe.org). All Rights Reserved. // // This Source Code Form is subject to the terms of the MIT License. // If a copy of the MIT was not distributed with this file, @@ -11,7 +11,7 @@ import ( "reflect" ) -// MapCopy does a shallow copy from map <data> to <copy> for most commonly used map type +// MapCopy does a shallow copy from map `data` to `copy` for most commonly used map type // map[string]interface{}. func MapCopy(data map[string]interface{}) (copy map[string]interface{}) { copy = make(map[string]interface{}, len(data)) @@ -21,7 +21,7 @@ func MapCopy(data map[string]interface{}) (copy map[string]interface{}) { return } -// MapContains checks whether map <data> contains <key>. +// MapContains checks whether map `data` contains `key`. func MapContains(data map[string]interface{}, key string) (ok bool) { if len(data) == 0 { return @@ -30,7 +30,7 @@ func MapContains(data map[string]interface{}, key string) (ok bool) { return } -// MapDelete deletes all <keys> from map <data>. +// MapDelete deletes all `keys` from map `data`. func MapDelete(data map[string]interface{}, keys ...string) { if len(data) == 0 { return @@ -40,7 +40,7 @@ func MapDelete(data map[string]interface{}, keys ...string) { } } -// MapMerge merges all map from <src> to map <dst>. +// MapMerge merges all map from `src` to map `dst`. func MapMerge(dst map[string]interface{}, src ...map[string]interface{}) { if dst == nil { return @@ -52,7 +52,7 @@ func MapMerge(dst map[string]interface{}, src ...map[string]interface{}) { } } -// MapMergeCopy creates and returns a new map which merges all map from <src>. +// MapMergeCopy creates and returns a new map which merges all map from `src`. func MapMergeCopy(src ...map[string]interface{}) (copy map[string]interface{}) { copy = make(map[string]interface{}) for _, m := range src { @@ -82,7 +82,7 @@ func MapPossibleItemByKey(data map[string]interface{}, key string) (foundKey str return "", nil } -// MapContainsPossibleKey checks if the given <key> is contained in given map <data>. +// MapContainsPossibleKey checks if the given `key` is contained in given map `data`. // It checks the key ignoring cases and symbols. // // Note that this function might be of low performance. diff --git a/util/gutil/gutil_slice.go b/util/gutil/gutil_slice.go index 81161f6ce..7da40a8db 100644 --- a/util/gutil/gutil_slice.go +++ b/util/gutil/gutil_slice.go @@ -1,4 +1,4 @@ -// Copyright GoFrame Author(https://github.com/gogf/gf). All Rights Reserved. +// Copyright GoFrame Author(https://goframe.org). All Rights Reserved. // // This Source Code Form is subject to the terms of the MIT License. // If a copy of the MIT was not distributed with this file, @@ -11,7 +11,7 @@ import ( "reflect" ) -// SliceCopy does a shallow copy of slice <data> for most commonly used slice type +// SliceCopy does a shallow copy of slice `data` for most commonly used slice type // []interface{}. func SliceCopy(data []interface{}) []interface{} { newData := make([]interface{}, len(data)) @@ -19,8 +19,8 @@ func SliceCopy(data []interface{}) []interface{} { return newData } -// SliceDelete deletes an element at <index> and returns the new slice. -// It does nothing if the given <index> is invalid. +// SliceDelete deletes an element at `index` and returns the new slice. +// It does nothing if the given `index` is invalid. func SliceDelete(data []interface{}, index int) (newSlice []interface{}) { if index < 0 || index >= len(data) { return data @@ -65,3 +65,29 @@ func SliceToMap(slice interface{}) map[string]interface{} { } return nil } + +// SliceToMapWithColumnAsKey converts slice type variable `slice` to `map[interface{}]interface{}` +// The value of specified column use as the key for returned map. +// Eg: +// SliceToMapWithColumnAsKey([{"K1": "v1", "K2": 1}, {"K1": "v2", "K2": 2}], "K1") => {"v1": {"K1": "v1", "K2": 1}, "v2": {"K1": "v2", "K2": 2}} +// SliceToMapWithColumnAsKey([{"K1": "v1", "K2": 1}, {"K1": "v2", "K2": 2}], "K2") => {1: {"K1": "v1", "K2": 1}, 2: {"K1": "v2", "K2": 2}} +func SliceToMapWithColumnAsKey(slice interface{}, key interface{}) map[interface{}]interface{} { + var ( + reflectValue = reflect.ValueOf(slice) + reflectKind = reflectValue.Kind() + ) + for reflectKind == reflect.Ptr { + reflectValue = reflectValue.Elem() + reflectKind = reflectValue.Kind() + } + data := make(map[interface{}]interface{}) + switch reflectKind { + case reflect.Slice, reflect.Array: + for i := 0; i < reflectValue.Len(); i++ { + if k, ok := ItemValue(reflectValue.Index(i), key); ok { + data[k] = reflectValue.Index(i).Interface() + } + } + } + return data +} diff --git a/util/gutil/gutil_struct.go b/util/gutil/gutil_struct.go index f22ab9e57..dc75ff68e 100644 --- a/util/gutil/gutil_struct.go +++ b/util/gutil/gutil_struct.go @@ -1,4 +1,4 @@ -// Copyright GoFrame Author(https://github.com/gogf/gf). All Rights Reserved. +// Copyright GoFrame Author(https://goframe.org). All Rights Reserved. // // This Source Code Form is subject to the terms of the MIT License. // If a copy of the MIT was not distributed with this file, diff --git a/util/gutil/gutil_z_bench_test.go b/util/gutil/gutil_z_bench_test.go index f08e48015..2920143ad 100644 --- a/util/gutil/gutil_z_bench_test.go +++ b/util/gutil/gutil_z_bench_test.go @@ -1,4 +1,4 @@ -// Copyright 2018 gf Author(https://github.com/gogf/gf). All Rights Reserved. +// Copyright GoFrame Author(https://goframe.org). All Rights Reserved. // // This Source Code Form is subject to the terms of the MIT License. // If a copy of the MIT was not distributed with this file, diff --git a/util/gutil/gutil_z_unit_comparator_test.go b/util/gutil/gutil_z_unit_comparator_test.go index 428a7bda4..db6ad9092 100755 --- a/util/gutil/gutil_z_unit_comparator_test.go +++ b/util/gutil/gutil_z_unit_comparator_test.go @@ -1,4 +1,4 @@ -// Copyright 2019 gf Author(https://github.com/gogf/gf). All Rights Reserved. +// Copyright GoFrame Author(https://goframe.org). All Rights Reserved. // // This Source Code Form is subject to the terms of the MIT License. // If a copy of the MIT was not distributed with this file, @@ -160,3 +160,20 @@ func Test_ComparatorTime(t *testing.T) { t.Assert(l, -1) }) } + +func Test_ComparatorFloat32OfFixed(t *testing.T) { + gtest.C(t, func(t *gtest.T) { + t.Assert(gutil.ComparatorFloat32(0.1, 0.1), 0) + t.Assert(gutil.ComparatorFloat32(1.1, 2.1), -1) + t.Assert(gutil.ComparatorFloat32(2.1, 1.1), 1) + }) +} + +func Test_ComparatorFloat64OfFixed(t *testing.T) { + + gtest.C(t, func(t *gtest.T) { + t.Assert(gutil.ComparatorFloat64(0.1, 0.1), 0) + t.Assert(gutil.ComparatorFloat64(1.1, 2.1), -1) + t.Assert(gutil.ComparatorFloat64(2.1, 1.1), 1) + }) +} diff --git a/util/gutil/gutil_z_unit_list_test.go b/util/gutil/gutil_z_unit_list_test.go index 408707fea..82985dce0 100755 --- a/util/gutil/gutil_z_unit_list_test.go +++ b/util/gutil/gutil_z_unit_list_test.go @@ -1,4 +1,4 @@ -// Copyright 2019 gf Author(https://github.com/gogf/gf). All Rights Reserved. +// Copyright GoFrame Author(https://goframe.org). All Rights Reserved. // // This Source Code Form is subject to the terms of the MIT License. // If a copy of the MIT was not distributed with this file, diff --git a/util/gutil/gutil_z_unit_map_test.go b/util/gutil/gutil_z_unit_map_test.go index 07ede8454..e52415bef 100755 --- a/util/gutil/gutil_z_unit_map_test.go +++ b/util/gutil/gutil_z_unit_map_test.go @@ -1,4 +1,4 @@ -// Copyright GoFrame Author(https://github.com/gogf/gf). All Rights Reserved. +// Copyright GoFrame Author(https://goframe.org). All Rights Reserved. // // This Source Code Form is subject to the terms of the MIT License. // If a copy of the MIT was not distributed with this file, diff --git a/util/gutil/gutil_z_unit_slice_test.go b/util/gutil/gutil_z_unit_slice_test.go index 768e504a9..218b1b65b 100755 --- a/util/gutil/gutil_z_unit_slice_test.go +++ b/util/gutil/gutil_z_unit_slice_test.go @@ -1,4 +1,4 @@ -// Copyright GoFrame Author(https://github.com/gogf/gf). All Rights Reserved. +// Copyright GoFrame Author(https://goframe.org). All Rights Reserved. // // This Source Code Form is subject to the terms of the MIT License. // If a copy of the MIT was not distributed with this file, @@ -35,3 +35,23 @@ func Test_SliceToMap(t *testing.T) { t.Assert(m, nil) }) } + +func Test_SliceToMapWithColumnAsKey(t *testing.T) { + m1 := g.Map{"K1": "v1", "K2": 1} + m2 := g.Map{"K1": "v2", "K2": 2} + s := g.Slice{m1, m2} + gtest.C(t, func(t *gtest.T) { + m := gutil.SliceToMapWithColumnAsKey(s, "K1") + t.Assert(m, g.MapAnyAny{ + "v1": m1, + "v2": m2, + }) + }) + gtest.C(t, func(t *gtest.T) { + m := gutil.SliceToMapWithColumnAsKey(s, "K2") + t.Assert(m, g.MapAnyAny{ + 1: m1, + 2: m2, + }) + }) +} diff --git a/util/gutil/gutil_z_unit_struct_test.go b/util/gutil/gutil_z_unit_struct_test.go index a7e66be76..1df3aaeff 100755 --- a/util/gutil/gutil_z_unit_struct_test.go +++ b/util/gutil/gutil_z_unit_struct_test.go @@ -1,4 +1,4 @@ -// Copyright GoFrame Author(https://github.com/gogf/gf). All Rights Reserved. +// Copyright GoFrame Author(https://goframe.org). All Rights Reserved. // // This Source Code Form is subject to the terms of the MIT License. // If a copy of the MIT was not distributed with this file, diff --git a/util/gutil/gutil_z_unit_test.go b/util/gutil/gutil_z_unit_test.go index 934e16489..9bfd3fee7 100755 --- a/util/gutil/gutil_z_unit_test.go +++ b/util/gutil/gutil_z_unit_test.go @@ -1,4 +1,4 @@ -// Copyright 2019 gf Author(https://github.com/gogf/gf). All Rights Reserved. +// Copyright GoFrame Author(https://goframe.org). All Rights Reserved. // // This Source Code Form is subject to the terms of the MIT License. // If a copy of the MIT was not distributed with this file, diff --git a/util/gvalid/gvalid.go b/util/gvalid/gvalid.go index 59a45dec4..a035b9337 100644 --- a/util/gvalid/gvalid.go +++ b/util/gvalid/gvalid.go @@ -1,4 +1,4 @@ -// Copyright 2017-2018 gf Author(https://github.com/gogf/gf). All Rights Reserved. +// Copyright GoFrame Author(https://goframe.org). All Rights Reserved. // // This Source Code Form is subject to the terms of the MIT License. // If a copy of the MIT was not distributed with this file, @@ -8,21 +8,26 @@ package gvalid import ( + "context" + "regexp" "strings" "github.com/gogf/gf/text/gregex" ) -// Refer to Laravel validation: https://laravel.com/docs/5.5/validation#available-validation-rules +// Refer to Laravel validation: +// https://laravel.com/docs/5.5/validation#available-validation-rules +// https://learnku.com/docs/laravel/5.4/validation // // All supported rules: // required format: required brief: Required. // required-if format: required-if:field,value,... brief: Required unless all given field and its value are equal. // required-unless format: required-unless:field,value,... brief: Required unless all given field and its value are not equal. // required-with format: required-with:field1,field2,... brief: Required if any of given fields are not empty. -// required-with-all format: required-with-all:field1,field2,... brief: Required if all of given fields are not empty. +// required-with-all format: required-with-all:field1,field2,... brief: Required if all given fields are not empty. // required-without format: required-without:field1,field2,... brief: Required if any of given fields are empty. -// required-without-all format: required-without-all:field1,field2,...brief: Required if all of given fields are empty. +// required-without-all format: required-without-all:field1,field2,...brief: Required if all given fields are empty. +// bail format: bail brief: Stop validating when this field's validation failed. // date format: date brief: Standard date, like: 2006-01-02, 20060102, 2006.01.02 // date-format format: date-format:format brief: Custom date format. // email format: email brief: Email address. @@ -45,6 +50,7 @@ import ( // length format: length:min,max brief: Length between :min and :max. The length is calculated using unicode string, which means one chinese character or letter both has the length of 1. // min-length format: min-length:min brief: Length is equal or greater than :min. The length is calculated using unicode string, which means one chinese character or letter both has the length of 1. // max-length format: max-length:max brief: Length is equal or lesser than :max. The length is calculated using unicode string, which means one chinese character or letter both has the length of 1. +// size format: size:size brief: Length must be :size. The length is calculated using unicode string, which means one chinese character or letter both has the length of 1. // between format: between:min,max brief: Range between :min and :max. It supports both integer and float. // min format: min:min brief: Equal or greater than :min. It supports both integer and float. // max format: max:max brief: Equal or lesser than :max. It supports both integer and float. @@ -62,6 +68,235 @@ import ( // like: map[field] => string|map[rule]string type CustomMsg = map[string]interface{} +// fieldRule defined the alias name and rule string for specified field. +type fieldRule struct { + Name string // Alias name for the field. + Rule string // Rule string like: "max:6" +} + +// apiNoValidation is an interface that marks current struct not validated by package `gvalid`. +type apiNoValidation interface { + NoValidation() +} + +const ( + singleRulePattern = `^([\w-]+):{0,1}(.*)` // regular expression pattern for single validation rule. + internalRulesErrRuleName = "InvalidRules" // rule name for internal invalid rules validation error. + internalParamsErrRuleName = "InvalidParams" // rule name for internal invalid params validation error. + internalObjectErrRuleName = "InvalidObject" // rule name for internal invalid object validation error. + internalErrorMapKey = "__InternalError__" // error map key for internal errors. + internalDefaultRuleName = "__default__" // default rule name for i18n error message format if no i18n message found for specified error rule. + ruleMessagePrefixForI18n = "gf.gvalid.rule." // prefix string for each rule configuration in i18n content. + noValidationTagName = "nv" // no validation tag name for struct attribute. + bailRuleName = "bail" // the name for rule "bail" +) + +var ( + defaultValidator = New() // defaultValidator is the default validator for package functions. + structTagPriority = []string{"gvalid", "valid", "v"} // structTagPriority specifies the validation tag priority array. + aliasNameTagPriority = []string{"param", "params", "p"} // aliasNameTagPriority specifies the alias tag priority array. + + // all internal error keys. + internalErrKeyMap = map[string]string{ + internalRulesErrRuleName: internalRulesErrRuleName, + internalParamsErrRuleName: internalParamsErrRuleName, + internalObjectErrRuleName: internalObjectErrRuleName, + } + // regular expression object for single rule + // which is compiled just once and of repeatable usage. + ruleRegex, _ = regexp.Compile(singleRulePattern) + + // mustCheckRulesEvenValueEmpty specifies some rules that must be validated + // even the value is empty (nil or empty). + mustCheckRulesEvenValueEmpty = map[string]struct{}{ + "required": {}, + "required-if": {}, + "required-unless": {}, + "required-with": {}, + "required-with-all": {}, + "required-without": {}, + "required-without-all": {}, + //"same": {}, + //"different": {}, + //"in": {}, + //"not-in": {}, + //"regex": {}, + } + // allSupportedRules defines all supported rules that is used for quick checks. + allSupportedRules = map[string]struct{}{ + "required": {}, + "required-if": {}, + "required-unless": {}, + "required-with": {}, + "required-with-all": {}, + "required-without": {}, + "required-without-all": {}, + "bail": {}, + "date": {}, + "date-format": {}, + "email": {}, + "phone": {}, + "phone-loose": {}, + "telephone": {}, + "passport": {}, + "password": {}, + "password2": {}, + "password3": {}, + "postcode": {}, + "resident-id": {}, + "bank-card": {}, + "qq": {}, + "ip": {}, + "ipv4": {}, + "ipv6": {}, + "mac": {}, + "url": {}, + "domain": {}, + "length": {}, + "min-length": {}, + "max-length": {}, + "size": {}, + "between": {}, + "min": {}, + "max": {}, + "json": {}, + "integer": {}, + "float": {}, + "boolean": {}, + "same": {}, + "different": {}, + "in": {}, + "not-in": {}, + "regex": {}, + } + // boolMap defines the boolean values. + boolMap = map[string]struct{}{ + "1": {}, + "true": {}, + "on": {}, + "yes": {}, + "": {}, + "0": {}, + "false": {}, + "off": {}, + "no": {}, + } + // defaultMessages is the default error messages. + // Note that these messages are synchronized from ./i18n/en/validation.toml . + defaultMessages = map[string]string{ + "required": "The :attribute field is required", + "required-if": "The :attribute field is required", + "required-unless": "The :attribute field is required", + "required-with": "The :attribute field is required", + "required-with-all": "The :attribute field is required", + "required-without": "The :attribute field is required", + "required-without-all": "The :attribute field is required", + "date": "The :attribute value is not a valid date", + "date-format": "The :attribute value does not match the format :format", + "email": "The :attribute value must be a valid email address", + "phone": "The :attribute value must be a valid phone number", + "telephone": "The :attribute value must be a valid telephone number", + "passport": "The :attribute value is not a valid passport format", + "password": "The :attribute value is not a valid passport format", + "password2": "The :attribute value is not a valid passport format", + "password3": "The :attribute value is not a valid passport format", + "postcode": "The :attribute value is not a valid passport format", + "resident-id": "The :attribute value is not a valid resident id number", + "bank-card": "The :attribute value must be a valid bank card number", + "qq": "The :attribute value must be a valid QQ number", + "ip": "The :attribute value must be a valid IP address", + "ipv4": "The :attribute value must be a valid IPv4 address", + "ipv6": "The :attribute value must be a valid IPv6 address", + "mac": "The :attribute value must be a valid MAC address", + "url": "The :attribute value must be a valid URL address", + "domain": "The :attribute value must be a valid domain format", + "length": "The :attribute value length must be between :min and :max", + "min-length": "The :attribute value length must be equal or greater than :min", + "max-length": "The :attribute value length must be equal or lesser than :max", + "size": "The :attribute value length must be :size", + "between": "The :attribute value must be between :min and :max", + "min": "The :attribute value must be equal or greater than :min", + "max": "The :attribute value must be equal or lesser than :max", + "json": "The :attribute value must be a valid JSON string", + "xml": "The :attribute value must be a valid XML string", + "array": "The :attribute value must be an array", + "integer": "The :attribute value must be an integer", + "float": "The :attribute value must be a float", + "boolean": "The :attribute value field must be true or false", + "same": "The :attribute value must be the same as field :field", + "different": "The :attribute value must be different from field :field", + "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", + internalDefaultRuleName: "The :attribute value is invalid", + } + // markedRuleMap defines all rules that are just marked rules which have neither functional meaning + // nor error messages. + markedRuleMap = map[string]bool{ + bailRuleName: true, + //"nullable": true, + } +) + +// CheckValue checks single value with specified rules. +// It returns nil if successful validation. +// +// The parameter `value` can be any type of variable, which will be converted to string +// for validation. +// The parameter `rules` can be one or more rules, multiple rules joined using char '|'. +// The parameter `messages` specifies the custom error messages, which can be type of: +// string/map/struct/*struct. +// The optional parameter `params` specifies the extra validation parameters for some rules +// like: required-*、same、different, etc. +func CheckValue(ctx context.Context, value interface{}, rules string, messages interface{}, params ...interface{}) Error { + var data interface{} + if len(params) > 0 { + data = params[0] + } + return defaultValidator.Ctx(ctx).Rules(rules).Data(data).Messages(messages).CheckValue(value) +} + +// CheckMap validates map and returns the error result. It returns nil if with successful validation. +// +// The parameter `rules` can be type of []string/map[string]string. It supports sequence in error result +// if `rules` is type of []string. +// The optional parameter `messages` specifies the custom error messages for specified keys and rules. +func CheckMap(ctx context.Context, params interface{}, rules interface{}, messages ...CustomMsg) Error { + var customErrorMessages CustomMsg + if len(messages) > 0 { + customErrorMessages = messages[0] + } + return defaultValidator.Ctx(ctx).Rules(rules).Messages(customErrorMessages).CheckMap(params) +} + +// CheckStruct validates struct and returns the error result. +// +// The parameter `object` should be type of struct/*struct. +// The parameter `rules` can be type of []string/map[string]string. It supports sequence in error result +// if `rules` is type of []string. +// The optional parameter `messages` specifies the custom error messages for specified keys and rules. +func CheckStruct(ctx context.Context, object interface{}, rules interface{}, messages ...CustomMsg) Error { + var customErrorMessages CustomMsg + if len(messages) > 0 { + customErrorMessages = messages[0] + } + return defaultValidator.Ctx(ctx).Rules(rules).Messages(customErrorMessages).CheckStruct(object) +} + +// CheckStructWithData validates struct with given parameter map and returns the error result. +// +// The parameter `object` should be type of struct/*struct. +// The parameter `rules` can be type of []string/map[string]string. It supports sequence in error result +// if `rules` is type of []string. +// The optional parameter `messages` specifies the custom error messages for specified keys and rules. +func CheckStructWithData(ctx context.Context, object interface{}, data interface{}, rules interface{}, messages ...CustomMsg) Error { + var customErrorMessages CustomMsg + if len(messages) > 0 { + customErrorMessages = messages[0] + } + return defaultValidator.Ctx(ctx).Data(data).Rules(rules).Messages(customErrorMessages).CheckStruct(object) +} + // parseSequenceTag parses one sequence tag to field, rule and error message. // The sequence tag is like: [alias@]rule[...#msg...] func parseSequenceTag(tag string) (field, rule, msg string) { diff --git a/util/gvalid/gvalid_check_map.go b/util/gvalid/gvalid_check_map.go deleted file mode 100644 index ca5a754f8..000000000 --- a/util/gvalid/gvalid_check_map.go +++ /dev/null @@ -1,136 +0,0 @@ -// Copyright 2017-2018 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 gvalid - -import ( - "strings" - - "github.com/gogf/gf/util/gconv" -) - -// CheckMap validates map and returns the error result. It returns nil if with successful validation. -// -// The parameter <rules> can be type of []string/map[string]string. It supports sequence in error result -// if <rules> is type of []string. -// The optional parameter <messages> specifies the custom error messages for specified keys and rules. -func CheckMap(params interface{}, rules interface{}, messages ...CustomMsg) *Error { - // If there's no validation rules, it does nothing and returns quickly. - if params == nil || rules == nil { - return nil - } - var ( - checkRules = make(map[string]string) - customMsgs = make(CustomMsg) - errorRules = make([]string, 0) - errorMaps = make(ErrorMap) - ) - switch v := rules.(type) { - // Sequence tag: []sequence tag - // Sequence has order for error results. - case []string: - for _, tag := range v { - name, rule, msg := parseSequenceTag(tag) - if len(name) == 0 { - continue - } - if len(msg) > 0 { - var ( - msgArray = strings.Split(msg, "|") - ruleArray = strings.Split(rule, "|") - ) - for k, v := range ruleArray { - // If length of custom messages is lesser than length of rules, - // the rest rules use the default error messages. - if len(msgArray) <= k { - continue - } - if len(msgArray[k]) == 0 { - continue - } - array := strings.Split(v, ":") - if _, ok := customMsgs[name]; !ok { - customMsgs[name] = make(map[string]string) - } - customMsgs[name].(map[string]string)[strings.TrimSpace(array[0])] = strings.TrimSpace(msgArray[k]) - } - } - checkRules[name] = rule - errorRules = append(errorRules, name+"@"+rule) - } - - // No sequence rules: map[field]rule - case map[string]string: - checkRules = v - } - // If there's no validation rules, it does nothing and returns quickly. - if len(checkRules) == 0 { - return nil - } - data := gconv.Map(params) - if data == nil { - return newErrorStr( - "invalid_params", - "invalid params type: convert to map failed", - ) - } - if len(messages) > 0 && len(messages[0]) > 0 { - if len(customMsgs) > 0 { - for k, v := range messages[0] { - customMsgs[k] = v - } - } else { - customMsgs = messages[0] - } - } - var value interface{} - for key, rule := range checkRules { - if len(rule) == 0 { - continue - } - value = nil - if v, ok := data[key]; ok { - value = v - } - // It checks each rule and its value in loop. - if e := doCheck(key, value, rule, customMsgs[key], data); e != nil { - _, item := e.FirstItem() - // =========================================================== - // Only in map and struct validations, if value is nil or empty - // string and has no required* rules, it clears the error message. - // =========================================================== - if gconv.String(value) == "" { - required := false - // rule => error - for k := range item { - // Default required rules. - if _, ok := mustCheckRulesEvenValueEmpty[k]; ok { - required = true - break - } - // Custom rules are also required in default. - if _, ok := customRuleFuncMap[k]; ok { - required = true - break - } - } - if !required { - continue - } - } - if _, ok := errorMaps[key]; !ok { - errorMaps[key] = make(map[string]string) - } - for k, v := range item { - errorMaps[key][k] = v - } - } - } - if len(errorMaps) > 0 { - return newError(errorRules, errorMaps) - } - return nil -} diff --git a/util/gvalid/gvalid_check_struct.go b/util/gvalid/gvalid_check_struct.go deleted file mode 100644 index 69fee0430..000000000 --- a/util/gvalid/gvalid_check_struct.go +++ /dev/null @@ -1,206 +0,0 @@ -// Copyright 2017-2018 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 gvalid - -import ( - "strings" - - "github.com/gogf/gf/internal/structs" - "github.com/gogf/gf/util/gconv" -) - -var ( - structTagPriority = []string{"gvalid", "valid", "v"} // structTagPriority specifies the validation tag priority array. - aliasNameTagPriority = []string{"param", "params", "p"} // aliasNameTagPriority specifies the alias tag priority array. -) - -// CheckStruct validates strcut and returns the error result. -// -// The parameter <object> should be type of struct/*struct. -// The parameter <rules> can be type of []string/map[string]string. It supports sequence in error result -// if <rules> is type of []string. -// The optional parameter <messages> specifies the custom error messages for specified keys and rules. -func CheckStruct(object interface{}, rules interface{}, messages ...CustomMsg) *Error { - // It here must use structs.TagFields not structs.MapField to ensure error sequence. - tagField, err := structs.TagFields(object, structTagPriority) - if err != nil { - return newErrorStr("invalid_object", err.Error()) - } - // If there's no struct tag and validation rules, it does nothing and returns quickly. - if len(tagField) == 0 && rules == nil { - return nil - } - var ( - params = make(map[string]interface{}) - checkRules = make(map[string]string) - customMessage = make(CustomMsg) - fieldAliases = make(map[string]string) // Alias names for <messages> overwriting struct tag names. - errorRules = make([]string, 0) // Sequence rules. - errorMaps = make(ErrorMap) // Returned error - ) - switch v := rules.(type) { - // Sequence tag: []sequence tag - // Sequence has order for error results. - case []string: - for _, tag := range v { - name, rule, msg := parseSequenceTag(tag) - if len(name) == 0 { - continue - } - if len(msg) > 0 { - var ( - msgArray = strings.Split(msg, "|") - ruleArray = strings.Split(rule, "|") - ) - for k, v := range ruleArray { - // If length of custom messages is lesser than length of rules, - // the rest rules use the default error messages. - if len(msgArray) <= k { - continue - } - if len(msgArray[k]) == 0 { - continue - } - array := strings.Split(v, ":") - if _, ok := customMessage[name]; !ok { - customMessage[name] = make(map[string]string) - } - customMessage[name].(map[string]string)[strings.TrimSpace(array[0])] = strings.TrimSpace(msgArray[k]) - } - } - checkRules[name] = rule - errorRules = append(errorRules, name+"@"+rule) - } - - // Map type rules does not support sequence. - // Format: map[key]rule - case map[string]string: - checkRules = v - } - // If there's no struct tag and validation rules, it does nothing and returns quickly. - if len(tagField) == 0 && len(checkRules) == 0 { - return nil - } - // Checks and extends the parameters map with struct alias tag. - mapField, err := structs.MapField(object, aliasNameTagPriority) - if err != nil { - return newErrorStr("invalid_object", err.Error()) - } - for nameOrTag, field := range mapField { - params[nameOrTag] = field.Value() - params[field.Name()] = field.Value() - } - for _, field := range tagField { - fieldName := field.Name() - // sequence tag == struct tag - // The name here is alias of field name. - name, rule, msg := parseSequenceTag(field.TagValue) - if len(name) == 0 { - name = fieldName - } else { - fieldAliases[fieldName] = name - } - // It here extends the params map using alias names. - if _, ok := params[name]; !ok { - params[name] = field.Value() - } - if _, ok := checkRules[name]; !ok { - if _, ok := checkRules[fieldName]; ok { - // If there's alias name, - // use alias name as its key and remove the field name key. - checkRules[name] = checkRules[fieldName] - delete(checkRules, fieldName) - } else { - checkRules[name] = rule - } - errorRules = append(errorRules, name+"@"+rule) - } else { - // The passed rules can overwrite the rules in struct tag. - continue - } - if len(msg) > 0 { - var ( - msgArray = strings.Split(msg, "|") - ruleArray = strings.Split(rule, "|") - ) - for k, v := range ruleArray { - // If length of custom messages is lesser than length of rules, - // the rest rules use the default error messages. - if len(msgArray) <= k { - continue - } - if len(msgArray[k]) == 0 { - continue - } - array := strings.Split(v, ":") - if _, ok := customMessage[name]; !ok { - customMessage[name] = make(map[string]string) - } - customMessage[name].(map[string]string)[strings.TrimSpace(array[0])] = strings.TrimSpace(msgArray[k]) - } - } - } - - // Custom error messages, - // which have the most priority than <rules> and struct tag. - if len(messages) > 0 && len(messages[0]) > 0 { - for k, v := range messages[0] { - if a, ok := fieldAliases[k]; ok { - // Overwrite the key of field name. - customMessage[a] = v - } else { - customMessage[k] = v - } - } - } - - // The following logic is the same as some of CheckMap. - var value interface{} - for key, rule := range checkRules { - value = nil - if v, ok := params[key]; ok { - value = v - } - // It checks each rule and its value in loop. - if e := doCheck(key, value, rule, customMessage[key], params); e != nil { - _, item := e.FirstItem() - // =========================================================== - // Only in map and struct validations, if value is nil or empty - // string and has no required* rules, it clears the error message. - // =========================================================== - if value == nil || gconv.String(value) == "" { - required := false - // rule => error - for k := range item { - // Default required rules. - if _, ok := mustCheckRulesEvenValueEmpty[k]; ok { - required = true - break - } - // Custom rules are also required in default. - if _, ok := customRuleFuncMap[k]; ok { - required = true - break - } - } - if !required { - continue - } - } - if _, ok := errorMaps[key]; !ok { - errorMaps[key] = make(map[string]string) - } - for k, v := range item { - errorMaps[key][k] = v - } - } - } - if len(errorMaps) > 0 { - return newError(errorRules, errorMaps) - } - return nil -} diff --git a/util/gvalid/gvalid_custom_rule.go b/util/gvalid/gvalid_custom_rule.go index 543a6dd58..d1e3589bb 100644 --- a/util/gvalid/gvalid_custom_rule.go +++ b/util/gvalid/gvalid_custom_rule.go @@ -1,4 +1,4 @@ -// Copyright 2020 gf Author(https://github.com/gogf/gf). All Rights Reserved. +// Copyright GoFrame Author(https://goframe.org). All Rights Reserved. // // This Source Code Form is subject to the terms of the MIT License. // If a copy of the MIT was not distributed with this file, @@ -6,13 +6,15 @@ package gvalid +import "context" + // 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. 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 +// 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 `data` specifies the `data` which is passed to the Validator. It might be type of map/struct or a nil value. +// You can ignore the parameter `data` if you do not really need it in your custom validation rule. +type RuleFunc func(ctx context.Context, rule string, value interface{}, message string, data interface{}) error var ( // customRuleFuncMap stores the custom rule functions. diff --git a/util/gvalid/gvalid_error.go b/util/gvalid/gvalid_error.go index cdd85acf4..5fe38875c 100644 --- a/util/gvalid/gvalid_error.go +++ b/util/gvalid/gvalid_error.go @@ -1,4 +1,4 @@ -// Copyright 2018 gf Author(https://github.com/gogf/gf). All Rights Reserved. +// Copyright GoFrame Author(https://goframe.org). All Rights Reserved. // // This Source Code Form is subject to the terms of the MIT License. // If a copy of the MIT was not distributed with this file, @@ -7,49 +7,74 @@ package gvalid import ( - "errors" + "github.com/gogf/gf/errors/gcode" + "github.com/gogf/gf/errors/gerror" "github.com/gogf/gf/text/gregex" + "github.com/gogf/gf/text/gstr" "strings" ) // Error is the validation error for validation result. -type Error struct { - rules []string // Rules by sequence, which is used for keeping error sequence. - errors ErrorMap // Error map. - firstKey string // The first error rule key(nil in default). - firstItem map[string]string // The first error rule value(nil in default). +type Error interface { + Code() gcode.Code + Current() error + Error() string + FirstItem() (key string, messages map[string]string) + FirstRule() (rule string, err string) + FirstString() (err string) + Items() (items []map[string]map[string]string) + Map() map[string]string + Maps() map[string]map[string]string + String() string + Strings() (errs []string) } -// ErrorMap is the validation error map: -// map[field]map[rule]message -type ErrorMap map[string]map[string]string +// validationError is the validation error for validation result. +type validationError struct { + code gcode.Code // Error code. + rules []fieldRule // Rules by sequence, which is used for keeping error sequence. + errors map[string]map[string]string // Error map:map[field]map[rule]message + firstKey string // The first error rule key(empty in default). + firstItem map[string]string // The first error rule value(nil in default). +} // newError creates and returns a validation error. -func newError(rules []string, errors map[string]map[string]string) *Error { +func newError(code gcode.Code, rules []fieldRule, errors map[string]map[string]string) *validationError { for field, m := range errors { for k, v := range m { v = strings.Replace(v, ":attribute", field, -1) - m[k], _ = gregex.ReplaceString(`\s{2,}`, ` `, v) + v, _ = gregex.ReplaceString(`\s{2,}`, ` `, v) + v = gstr.Trim(v) + m[k] = v } errors[field] = m } - return &Error{ + return &validationError{ + code: code, rules: rules, errors: errors, } } // newErrorStr creates and returns a validation error by string. -func newErrorStr(key, err string) *Error { - return newError(nil, map[string]map[string]string{ - "__gvalid__": { +func newErrorStr(key, err string) *validationError { + return newError(gcode.CodeInternalError, nil, map[string]map[string]string{ + internalErrorMapKey: { key: err, }, }) } +// Code returns the error code of current validation error. +func (e *validationError) Code() gcode.Code { + if e == nil { + return gcode.CodeNil + } + return e.code +} + // Map returns the first error message as map. -func (e *Error) Map() map[string]string { +func (e *validationError) Map() map[string]string { if e == nil { return map[string]string{} } @@ -58,15 +83,42 @@ func (e *Error) Map() map[string]string { } // Maps returns all error messages as map. -func (e *Error) Maps() ErrorMap { +func (e *validationError) Maps() map[string]map[string]string { if e == nil { return nil } return e.errors } +// Items retrieves and returns error items array in sequence if possible, +// or else it returns error items with no sequence . +func (e *validationError) Items() (items []map[string]map[string]string) { + if e == nil { + return []map[string]map[string]string{} + } + items = make([]map[string]map[string]string, 0) + // By sequence. + if len(e.rules) > 0 { + for _, v := range e.rules { + if errorItemMap, ok := e.errors[v.Name]; ok { + items = append(items, map[string]map[string]string{ + v.Name: errorItemMap, + }) + } + } + return items + } + // No sequence. + for name, errorRuleMap := range e.errors { + items = append(items, map[string]map[string]string{ + name: errorRuleMap, + }) + } + return +} + // FirstItem returns the field name and error messages for the first validation rule error. -func (e *Error) FirstItem() (key string, messages map[string]string) { +func (e *validationError) FirstItem() (key string, messages map[string]string) { if e == nil { return "", map[string]string{} } @@ -76,11 +128,10 @@ func (e *Error) FirstItem() (key string, messages map[string]string) { // By sequence. if len(e.rules) > 0 { for _, v := range e.rules { - name, _, _ := parseSequenceTag(v) - if m, ok := e.errors[name]; ok { - e.firstKey = name - e.firstItem = m - return name, m + if errorItemMap, ok := e.errors[v.Name]; ok { + e.firstKey = v.Name + e.firstItem = errorItemMap + return v.Name, errorItemMap } } } @@ -94,28 +145,27 @@ 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) { +func (e *validationError) FirstRule() (rule string, err string) { if e == nil { return "", "" } // By sequence. if len(e.rules) > 0 { for _, v := range e.rules { - name, rule, _ := parseSequenceTag(v) - if m, ok := e.errors[name]; ok { - for _, rule := range strings.Split(rule, "|") { - array := strings.Split(rule, ":") - rule = strings.TrimSpace(array[0]) - if err, ok := m[rule]; ok { - return rule, err + if errorItemMap, ok := e.errors[v.Name]; ok { + for _, ruleItem := range strings.Split(v.Rule, "|") { + array := strings.Split(ruleItem, ":") + ruleItem = strings.TrimSpace(array[0]) + if err, ok = errorItemMap[ruleItem]; ok { + return ruleItem, err } } } } } // No sequence. - for _, m := range e.errors { - for k, v := range m { + for _, errorItemMap := range e.errors { + for k, v := range errorItemMap { return k, v } } @@ -124,7 +174,7 @@ 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) { +func (e *validationError) FirstString() (err string) { if e == nil { return "" } @@ -133,16 +183,16 @@ func (e *Error) FirstString() (err string) { } // Current is alis of FirstString, which implements interface gerror.ApiCurrent. -func (e *Error) Current() error { +func (e *validationError) Current() error { if e == nil { return nil } _, err := e.FirstRule() - return errors.New(err) + return gerror.NewCode(e.code, err) } // String returns all error messages as string, multiple error messages joined using char ';'. -func (e *Error) String() string { +func (e *validationError) String() string { if e == nil { return "" } @@ -150,7 +200,7 @@ func (e *Error) String() string { } // Error implements interface of error.Error. -func (e *Error) Error() string { +func (e *validationError) Error() string { if e == nil { return "" } @@ -158,7 +208,7 @@ func (e *Error) Error() string { } // Strings returns all error messages as string array. -func (e *Error) Strings() (errs []string) { +func (e *validationError) Strings() (errs []string) { if e == nil { return []string{} } @@ -166,18 +216,17 @@ func (e *Error) Strings() (errs []string) { // By sequence. if len(e.rules) > 0 { for _, v := range e.rules { - name, rule, _ := parseSequenceTag(v) - if m, ok := e.errors[name]; ok { + if errorItemMap, ok := e.errors[v.Name]; ok { // validation error checks. - for _, rule := range strings.Split(rule, "|") { - rule = strings.TrimSpace(strings.Split(rule, ":")[0]) - if err, ok := m[rule]; ok { + for _, ruleItem := range strings.Split(v.Rule, "|") { + ruleItem = strings.TrimSpace(strings.Split(ruleItem, ":")[0]) + if err, ok := errorItemMap[ruleItem]; ok { errs = append(errs, err) } } // internal error checks. for k, _ := range internalErrKeyMap { - if err, ok := m[k]; ok { + if err, ok := errorItemMap[k]; ok { errs = append(errs, err) } } @@ -186,8 +235,8 @@ func (e *Error) Strings() (errs []string) { return errs } // No sequence. - for _, m := range e.errors { - for _, err := range m { + for _, errorItemMap := range e.errors { + for _, err := range errorItemMap { errs = append(errs, err) } } diff --git a/util/gvalid/gvalid_message.go b/util/gvalid/gvalid_message.go deleted file mode 100644 index c3b6e6c2b..000000000 --- a/util/gvalid/gvalid_message.go +++ /dev/null @@ -1,83 +0,0 @@ -// Copyright 2018 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 gvalid - -import ( - "fmt" - "github.com/gogf/gf/i18n/gi18n" -) - -// defaultMessages is the default error messages. -// Note that these messages are synchronized from ./i18n/en/validation.toml . -var defaultMessages = map[string]string{ - "required": "The :attribute field is required", - "required-if": "The :attribute field is required", - "required-unless": "The :attribute field is required", - "required-with": "The :attribute field is required", - "required-with-all": "The :attribute field is required", - "required-without": "The :attribute field is required", - "required-without-all": "The :attribute field is required", - "date": "The :attribute value is not a valid date", - "date-format": "The :attribute value does not match the format :format", - "email": "The :attribute value must be a valid email address", - "phone": "The :attribute value must be a valid phone number", - "telephone": "The :attribute value must be a valid telephone number", - "passport": "The :attribute value is not a valid passport format", - "password": "The :attribute value is not a valid passport format", - "password2": "The :attribute value is not a valid passport format", - "password3": "The :attribute value is not a valid passport format", - "postcode": "The :attribute value is not a valid passport format", - "resident-id": "The :attribute value is not a valid resident id number", - "bank-card": "The :attribute value must be a valid bank card number", - "qq": "The :attribute value must be a valid QQ number", - "ip": "The :attribute value must be a valid IP address", - "ipv4": "The :attribute value must be a valid IPv4 address", - "ipv6": "The :attribute value must be a valid IPv6 address", - "mac": "The :attribute value must be a valid MAC address", - "url": "The :attribute value must be a valid URL address", - "domain": "The :attribute value must be a valid domain format", - "length": "The :attribute value length must be between :min and :max", - "min-length": "The :attribute value length must be equal or greater than :min", - "max-length": "The :attribute value length must be equal or lesser than :max", - "between": "The :attribute value must be between :min and :max", - "min": "The :attribute value must be equal or greater than :min", - "max": "The :attribute value must be equal or lesser than :max", - "json": "The :attribute value must be a valid JSON string", - "xml": "The :attribute value must be a valid XML string", - "array": "The :attribute value must be an array", - "integer": "The :attribute value must be an integer", - "float": "The :attribute value must be a float", - "boolean": "The :attribute value field must be true or false", - "same": "The :attribute value must be the same as field :field", - "different": "The :attribute value must be different from field :field", - "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. -// It firstly retrieves the message from custom message map, and then checks i18n manager, -// it returns the default error message if it's not found in custom message map or i18n manager. -func getErrorMessageByRule(ruleKey string, customMsgMap map[string]string) string { - content := customMsgMap[ruleKey] - if content != "" { - return content - } - content = gi18n.GetContent(fmt.Sprintf(`gf.gvalid.rule.%s`, ruleKey)) - 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 -} diff --git a/util/gvalid/gvalid_rule_required.go b/util/gvalid/gvalid_rule_required.go deleted file mode 100644 index c92456788..000000000 --- a/util/gvalid/gvalid_rule_required.go +++ /dev/null @@ -1,114 +0,0 @@ -// Copyright 2018 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 gvalid - -import ( - "strings" -) - -// checkRequired checks <value> using required rules. -func checkRequired(value, ruleKey, ruleVal string, params map[string]string) bool { - required := false - switch ruleKey { - // Required. - case "required": - required = true - - // Required unless all given field and its value are equal. - // Example: required-if: id,1,age,18 - case "required-if": - required = false - array := strings.Split(ruleVal, ",") - // It supports multiple field and value pairs. - if len(array)%2 == 0 { - for i := 0; i < len(array); { - tk := array[i] - tv := array[i+1] - if v, ok := params[tk]; ok { - if strings.Compare(tv, v) == 0 { - required = true - break - } - } - i += 2 - } - } - - // Required unless all given field and its value are not equal. - // Example: required-unless: id,1,age,18 - case "required-unless": - required = true - array := strings.Split(ruleVal, ",") - // It supports multiple field and value pairs. - if len(array)%2 == 0 { - for i := 0; i < len(array); { - tk := array[i] - tv := array[i+1] - if v, ok := params[tk]; ok { - if strings.Compare(tv, v) == 0 { - required = false - break - } - } - i += 2 - } - } - - // Required if any of given fields are not empty. - // Example: required-with:id,name - case "required-with": - required = false - array := strings.Split(ruleVal, ",") - for i := 0; i < len(array); i++ { - if params[array[i]] != "" { - required = true - break - } - } - - // Required if all of given fields are not empty. - // Example: required-with:id,name - case "required-with-all": - required = true - array := strings.Split(ruleVal, ",") - for i := 0; i < len(array); i++ { - if params[array[i]] == "" { - required = false - break - } - } - - // Required if any of given fields are empty. - // Example: required-with:id,name - case "required-without": - required = false - array := strings.Split(ruleVal, ",") - for i := 0; i < len(array); i++ { - if params[array[i]] == "" { - required = true - break - } - } - - // Required if all of given fields are empty. - // Example: required-with:id,name - case "required-without-all": - required = true - array := strings.Split(ruleVal, ",") - for i := 0; i < len(array); i++ { - if params[array[i]] != "" { - required = false - break - } - } - } - if required { - return !(value == "") - } else { - return true - } -} diff --git a/util/gvalid/gvalid_unit_basic_all_test.go b/util/gvalid/gvalid_unit_basic_all_test.go deleted file mode 100755 index b1aea6849..000000000 --- a/util/gvalid/gvalid_unit_basic_all_test.go +++ /dev/null @@ -1,926 +0,0 @@ -// Copyright 2017 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 gvalid_test - -import ( - "github.com/gogf/gf/errors/gerror" - "testing" - - "github.com/gogf/gf/frame/g" - "github.com/gogf/gf/test/gtest" - "github.com/gogf/gf/util/gvalid" -) - -func Test_Check(t *testing.T) { - gtest.C(t, func(t *gtest.T) { - rule := "abc:6,16" - val1 := 0 - val2 := 7 - val3 := 20 - err1 := gvalid.Check(val1, rule, nil) - err2 := gvalid.Check(val2, rule, nil) - err3 := gvalid.Check(val3, rule, nil) - t.Assert(err1, "invalid_rules: abc:6,16") - t.Assert(err2, "invalid_rules: abc:6,16") - t.Assert(err3, "invalid_rules: abc:6,16") - }) -} - -func Test_Required(t *testing.T) { - if m := gvalid.Check("1", "required", nil); m != nil { - t.Error(m) - } - if m := gvalid.Check("", "required", nil); m == nil { - t.Error(m) - } - if m := gvalid.Check("", "required-if: id,1,age,18", nil, map[string]interface{}{"id": 1, "age": 19}); m == nil { - t.Error("Required校验失败") - } - if m := gvalid.Check("", "required-if: id,1,age,18", nil, map[string]interface{}{"id": 2, "age": 19}); m != nil { - t.Error("Required校验失败") - } -} - -func Test_RequiredIf(t *testing.T) { - gtest.C(t, func(t *gtest.T) { - rule := "required-if:id,1,age,18" - t.AssertNE(gvalid.Check("", rule, nil, g.Map{"id": 1}), nil) - t.Assert(gvalid.Check("", rule, nil, g.Map{"id": 0}), nil) - t.AssertNE(gvalid.Check("", rule, nil, g.Map{"age": 18}), nil) - t.Assert(gvalid.Check("", rule, nil, g.Map{"age": 20}), nil) - }) -} - -func Test_RequiredUnless(t *testing.T) { - gtest.C(t, func(t *gtest.T) { - rule := "required-unless:id,1,age,18" - t.Assert(gvalid.Check("", rule, nil, g.Map{"id": 1}), nil) - t.AssertNE(gvalid.Check("", rule, nil, g.Map{"id": 0}), nil) - t.Assert(gvalid.Check("", rule, nil, g.Map{"age": 18}), nil) - t.AssertNE(gvalid.Check("", rule, nil, g.Map{"age": 20}), nil) - }) -} - -func Test_RequiredWith(t *testing.T) { - gtest.C(t, func(t *gtest.T) { - rule := "required-with:id,name" - val1 := "" - params1 := g.Map{ - "age": 18, - } - params2 := g.Map{ - "id": 100, - } - params3 := g.Map{ - "id": 100, - "name": "john", - } - err1 := gvalid.Check(val1, rule, nil, params1) - err2 := gvalid.Check(val1, rule, nil, params2) - err3 := gvalid.Check(val1, rule, nil, params3) - t.Assert(err1, nil) - t.AssertNE(err2, nil) - t.AssertNE(err3, nil) - }) -} - -func Test_RequiredWithAll(t *testing.T) { - gtest.C(t, func(t *gtest.T) { - rule := "required-with-all:id,name" - val1 := "" - params1 := g.Map{ - "age": 18, - } - params2 := g.Map{ - "id": 100, - } - params3 := g.Map{ - "id": 100, - "name": "john", - } - err1 := gvalid.Check(val1, rule, nil, params1) - err2 := gvalid.Check(val1, rule, nil, params2) - err3 := gvalid.Check(val1, rule, nil, params3) - t.Assert(err1, nil) - t.Assert(err2, nil) - t.AssertNE(err3, nil) - }) -} - -func Test_RequiredWithOut(t *testing.T) { - gtest.C(t, func(t *gtest.T) { - rule := "required-without:id,name" - val1 := "" - params1 := g.Map{ - "age": 18, - } - params2 := g.Map{ - "id": 100, - } - params3 := g.Map{ - "id": 100, - "name": "john", - } - err1 := gvalid.Check(val1, rule, nil, params1) - err2 := gvalid.Check(val1, rule, nil, params2) - err3 := gvalid.Check(val1, rule, nil, params3) - t.AssertNE(err1, nil) - t.AssertNE(err2, nil) - t.Assert(err3, nil) - }) -} - -func Test_RequiredWithOutAll(t *testing.T) { - gtest.C(t, func(t *gtest.T) { - rule := "required-without-all:id,name" - val1 := "" - params1 := g.Map{ - "age": 18, - } - params2 := g.Map{ - "id": 100, - } - params3 := g.Map{ - "id": 100, - "name": "john", - } - err1 := gvalid.Check(val1, rule, nil, params1) - err2 := gvalid.Check(val1, rule, nil, params2) - err3 := gvalid.Check(val1, rule, nil, params3) - t.AssertNE(err1, nil) - t.Assert(err2, nil) - t.Assert(err3, nil) - }) -} - -func Test_Date(t *testing.T) { - gtest.C(t, func(t *gtest.T) { - rule := "date" - val1 := "2010" - val2 := "201011" - val3 := "20101101" - val4 := "2010-11-01" - val5 := "2010.11.01" - val6 := "2010/11/01" - val7 := "2010=11=01" - err1 := gvalid.Check(val1, rule, nil) - err2 := gvalid.Check(val2, rule, nil) - err3 := gvalid.Check(val3, rule, nil) - err4 := gvalid.Check(val4, rule, nil) - err5 := gvalid.Check(val5, rule, nil) - err6 := gvalid.Check(val6, rule, nil) - err7 := gvalid.Check(val7, rule, nil) - t.Assert(err1, nil) - t.Assert(err2, nil) - t.Assert(err3, nil) - t.Assert(err4, nil) - t.Assert(err5, nil) - t.Assert(err6, nil) - t.AssertNE(err7, nil) - }) -} - -func Test_DateFormat(t *testing.T) { - gtest.C(t, func(t *gtest.T) { - val1 := "2010" - val2 := "201011" - val3 := "2010.11" - val4 := "201011-01" - val5 := "2010~11~01" - val6 := "2010-11~01" - err1 := gvalid.Check(val1, "date-format:Y", nil) - err2 := gvalid.Check(val2, "date-format:Ym", nil) - err3 := gvalid.Check(val3, "date-format:Y.m", nil) - err4 := gvalid.Check(val4, "date-format:Ym-d", nil) - err5 := gvalid.Check(val5, "date-format:Y~m~d", nil) - err6 := gvalid.Check(val6, "date-format:Y~m~d", nil) - t.Assert(err1, nil) - t.Assert(err2, nil) - t.Assert(err3, nil) - t.Assert(err4, nil) - t.Assert(err5, nil) - t.AssertNE(err6, nil) - }) -} - -func Test_Email(t *testing.T) { - gtest.C(t, func(t *gtest.T) { - rule := "email" - value1 := "m@johngcn" - value2 := "m@www@johngcn" - value3 := "m-m_m@mail.johng.cn" - value4 := "m.m-m@johng.cn" - err1 := gvalid.Check(value1, rule, nil) - err2 := gvalid.Check(value2, rule, nil) - err3 := gvalid.Check(value3, rule, nil) - err4 := gvalid.Check(value4, rule, nil) - t.AssertNE(err1, nil) - t.AssertNE(err2, nil) - t.Assert(err3, nil) - t.Assert(err4, nil) - }) -} - -func Test_Phone(t *testing.T) { - gtest.C(t, func(t *gtest.T) { - err1 := gvalid.Check("1361990897", "phone", nil) - err2 := gvalid.Check("13619908979", "phone", nil) - err3 := gvalid.Check("16719908979", "phone", nil) - err4 := gvalid.Check("19719908989", "phone", nil) - t.AssertNE(err1.String(), nil) - t.Assert(err2, nil) - t.Assert(err3, nil) - t.Assert(err4, nil) - }) -} - -func Test_PhoneLoose(t *testing.T) { - gtest.C(t, func(t *gtest.T) { - err1 := gvalid.Check("13333333333", "phone-loose", nil) - err2 := gvalid.Check("15555555555", "phone-loose", nil) - err3 := gvalid.Check("16666666666", "phone-loose", nil) - err4 := gvalid.Check("23333333333", "phone-loose", nil) - err5 := gvalid.Check("1333333333", "phone-loose", nil) - err6 := gvalid.Check("10333333333", "phone-loose", nil) - t.Assert(err1, nil) - t.Assert(err2, nil) - t.Assert(err3, nil) - t.AssertNE(err4, nil) - t.AssertNE(err5, nil) - t.AssertNE(err6, nil) - }) -} -func Test_Telephone(t *testing.T) { - gtest.C(t, func(t *gtest.T) { - rule := "telephone" - val1 := "869265" - val2 := "028-869265" - val3 := "86292651" - val4 := "028-8692651" - val5 := "0830-8692651" - err1 := gvalid.Check(val1, rule, nil) - err2 := gvalid.Check(val2, rule, nil) - err3 := gvalid.Check(val3, rule, nil) - err4 := gvalid.Check(val4, rule, nil) - err5 := gvalid.Check(val5, rule, nil) - t.AssertNE(err1, nil) - t.AssertNE(err2, nil) - t.Assert(err3, nil) - t.Assert(err4, nil) - t.Assert(err5, nil) - }) -} - -func Test_Passport(t *testing.T) { - gtest.C(t, func(t *gtest.T) { - rule := "passport" - val1 := "123456" - val2 := "a12345-6" - val3 := "aaaaa" - val4 := "aaaaaa" - val5 := "a123_456" - err1 := gvalid.Check(val1, rule, nil) - err2 := gvalid.Check(val2, rule, nil) - err3 := gvalid.Check(val3, rule, nil) - err4 := gvalid.Check(val4, rule, nil) - err5 := gvalid.Check(val5, rule, nil) - t.AssertNE(err1, nil) - t.AssertNE(err2, nil) - t.AssertNE(err3, nil) - t.Assert(err4, nil) - t.Assert(err5, nil) - }) -} - -func Test_Password(t *testing.T) { - gtest.C(t, func(t *gtest.T) { - rule := "password" - val1 := "12345" - val2 := "aaaaa" - val3 := "a12345-6" - val4 := ">,/;'[09-" - val5 := "a123_456" - err1 := gvalid.Check(val1, rule, nil) - err2 := gvalid.Check(val2, rule, nil) - err3 := gvalid.Check(val3, rule, nil) - err4 := gvalid.Check(val4, rule, nil) - err5 := gvalid.Check(val5, rule, nil) - t.AssertNE(err1, nil) - t.AssertNE(err2, nil) - t.Assert(err3, nil) - t.Assert(err4, nil) - t.Assert(err5, nil) - }) -} - -func Test_Password2(t *testing.T) { - gtest.C(t, func(t *gtest.T) { - rule := "password2" - val1 := "12345" - val2 := "Naaaa" - val3 := "a12345-6" - val4 := ">,/;'[09-" - val5 := "a123_456" - val6 := "Nant1986" - val7 := "Nant1986!" - err1 := gvalid.Check(val1, rule, nil) - err2 := gvalid.Check(val2, rule, nil) - err3 := gvalid.Check(val3, rule, nil) - err4 := gvalid.Check(val4, rule, nil) - err5 := gvalid.Check(val5, rule, nil) - err6 := gvalid.Check(val6, rule, nil) - err7 := gvalid.Check(val7, rule, nil) - t.AssertNE(err1, nil) - t.AssertNE(err2, nil) - t.AssertNE(err3, nil) - t.AssertNE(err4, nil) - t.AssertNE(err5, nil) - t.Assert(err6, nil) - t.Assert(err7, nil) - }) -} - -func Test_Password3(t *testing.T) { - gtest.C(t, func(t *gtest.T) { - rule := "password3" - val1 := "12345" - val2 := "Naaaa" - val3 := "a12345-6" - val4 := ">,/;'[09-" - val5 := "a123_456" - val6 := "Nant1986" - val7 := "Nant1986!" - err1 := gvalid.Check(val1, rule, nil) - err2 := gvalid.Check(val2, rule, nil) - err3 := gvalid.Check(val3, rule, nil) - err4 := gvalid.Check(val4, rule, nil) - err5 := gvalid.Check(val5, rule, nil) - err6 := gvalid.Check(val6, rule, nil) - err7 := gvalid.Check(val7, rule, nil) - t.AssertNE(err1, nil) - t.AssertNE(err2, nil) - t.AssertNE(err3, nil) - t.AssertNE(err4, nil) - t.AssertNE(err5, nil) - t.AssertNE(err6, nil) - t.Assert(err7, nil) - }) -} - -func Test_Postcode(t *testing.T) { - gtest.C(t, func(t *gtest.T) { - rule := "postcode" - val1 := "12345" - val2 := "610036" - err1 := gvalid.Check(val1, rule, nil) - err2 := gvalid.Check(val2, rule, nil) - t.AssertNE(err1, nil) - t.Assert(err2, nil) - }) -} - -func Test_ResidentId(t *testing.T) { - gtest.C(t, func(t *gtest.T) { - rule := "resident-id" - val1 := "11111111111111" - val2 := "1111111111111111" - val3 := "311128500121201" - val4 := "510521198607185367" - val5 := "51052119860718536x" - err1 := gvalid.Check(val1, rule, nil) - err2 := gvalid.Check(val2, rule, nil) - err3 := gvalid.Check(val3, rule, nil) - err4 := gvalid.Check(val4, rule, nil) - err5 := gvalid.Check(val5, rule, nil) - t.AssertNE(err1, nil) - t.AssertNE(err2, nil) - t.AssertNE(err3, nil) - t.AssertNE(err4, nil) - t.Assert(err5, nil) - }) -} - -func Test_BankCard(t *testing.T) { - gtest.C(t, func(t *gtest.T) { - rule := "bank-card" - val1 := "6230514630000424470" - val2 := "6230514630000424473" - err1 := gvalid.Check(val1, rule, nil) - err2 := gvalid.Check(val2, rule, nil) - t.AssertNE(err1, nil) - t.Assert(err2, nil) - }) -} - -func Test_QQ(t *testing.T) { - gtest.C(t, func(t *gtest.T) { - rule := "qq" - val1 := "100" - val2 := "1" - val3 := "10000" - val4 := "38996181" - val5 := "389961817" - err1 := gvalid.Check(val1, rule, nil) - err2 := gvalid.Check(val2, rule, nil) - err3 := gvalid.Check(val3, rule, nil) - err4 := gvalid.Check(val4, rule, nil) - err5 := gvalid.Check(val5, rule, nil) - t.AssertNE(err1, nil) - t.AssertNE(err2, nil) - t.Assert(err3, nil) - t.Assert(err4, nil) - t.Assert(err5, nil) - }) -} - -func Test_Ip(t *testing.T) { - if m := gvalid.Check("10.0.0.1", "ip", nil); m != nil { - t.Error(m) - } - if m := gvalid.Check("10.0.0.1", "ipv4", nil); m != nil { - t.Error(m) - } - if m := gvalid.Check("0.0.0.0", "ipv4", nil); m != nil { - t.Error(m) - } - if m := gvalid.Check("1920.0.0.0", "ipv4", nil); m == nil { - t.Error("ipv4校验失败") - } - if m := gvalid.Check("1920.0.0.0", "ip", nil); m == nil { - t.Error("ipv4校验失败") - } - if m := gvalid.Check("fe80::5484:7aff:fefe:9799", "ipv6", nil); m != nil { - t.Error(m) - } - if m := gvalid.Check("fe80::5484:7aff:fefe:9799123", "ipv6", nil); m == nil { - t.Error(m) - } - if m := gvalid.Check("fe80::5484:7aff:fefe:9799", "ip", nil); m != nil { - t.Error(m) - } - if m := gvalid.Check("fe80::5484:7aff:fefe:9799123", "ip", nil); m == nil { - t.Error(m) - } -} - -func Test_IPv4(t *testing.T) { - gtest.C(t, func(t *gtest.T) { - rule := "ipv4" - val1 := "0.0.0" - val2 := "0.0.0.0" - val3 := "1.1.1.1" - val4 := "255.255.255.0" - val5 := "127.0.0.1" - err1 := gvalid.Check(val1, rule, nil) - err2 := gvalid.Check(val2, rule, nil) - err3 := gvalid.Check(val3, rule, nil) - err4 := gvalid.Check(val4, rule, nil) - err5 := gvalid.Check(val5, rule, nil) - t.AssertNE(err1, nil) - t.Assert(err2, nil) - t.Assert(err3, nil) - t.Assert(err4, nil) - t.Assert(err5, nil) - }) -} - -func Test_IPv6(t *testing.T) { - gtest.C(t, func(t *gtest.T) { - rule := "ipv6" - val1 := "192.168.1.1" - val2 := "CDCD:910A:2222:5498:8475:1111:3900:2020" - val3 := "1030::C9B4:FF12:48AA:1A2B" - val4 := "2000:0:0:0:0:0:0:1" - val5 := "0000:0000:0000:0000:0000:ffff:c0a8:5909" - err1 := gvalid.Check(val1, rule, nil) - err2 := gvalid.Check(val2, rule, nil) - err3 := gvalid.Check(val3, rule, nil) - err4 := gvalid.Check(val4, rule, nil) - err5 := gvalid.Check(val5, rule, nil) - t.AssertNE(err1, nil) - t.Assert(err2, nil) - t.Assert(err3, nil) - t.Assert(err4, nil) - t.Assert(err5, nil) - }) -} - -func Test_MAC(t *testing.T) { - gtest.C(t, func(t *gtest.T) { - rule := "mac" - val1 := "192.168.1.1" - val2 := "44-45-53-54-00-00" - val3 := "01:00:5e:00:00:00" - err1 := gvalid.Check(val1, rule, nil) - err2 := gvalid.Check(val2, rule, nil) - err3 := gvalid.Check(val3, rule, nil) - t.AssertNE(err1, nil) - t.Assert(err2, nil) - t.Assert(err3, nil) - }) -} - -func Test_URL(t *testing.T) { - gtest.C(t, func(t *gtest.T) { - rule := "url" - val1 := "127.0.0.1" - val2 := "https://www.baidu.com" - val3 := "http://127.0.0.1" - val4 := "file:///tmp/test.txt" - err1 := gvalid.Check(val1, rule, nil) - err2 := gvalid.Check(val2, rule, nil) - err3 := gvalid.Check(val3, rule, nil) - err4 := gvalid.Check(val4, rule, nil) - t.AssertNE(err1, nil) - t.Assert(err2, nil) - t.Assert(err3, nil) - t.Assert(err4, nil) - }) -} - -func Test_Domain(t *testing.T) { - gtest.C(t, func(t *gtest.T) { - m := g.MapStrBool{ - "localhost": false, - "baidu.com": true, - "www.baidu.com": true, - "jn.np": true, - "www.jn.np": true, - "w.www.jn.np": true, - "127.0.0.1": false, - "www.360.com": true, - "www.360": false, - "360": false, - "my-gf": false, - "my-gf.com": true, - "my-gf.360.com": true, - } - var err error - for k, v := range m { - err = gvalid.Check(k, "domain", nil) - if v { - //fmt.Println(k) - t.Assert(err, nil) - } else { - //fmt.Println(k) - t.AssertNE(err, nil) - } - } - }) -} - -func Test_Length(t *testing.T) { - rule := "length:6,16" - if m := gvalid.Check("123456", rule, nil); m != nil { - t.Error(m) - } - if m := gvalid.Check("12345", rule, nil); m == nil { - t.Error("长度校验失败") - } -} - -func Test_MinLength(t *testing.T) { - rule := "min-length:6" - msgs := map[string]string{ - "min-length": "地址长度至少为:min位", - } - if m := gvalid.Check("123456", rule, nil); m != nil { - t.Error(m) - } - if m := gvalid.Check("12345", rule, nil); m == nil { - t.Error("长度校验失败") - } - if m := gvalid.Check("12345", rule, msgs); m == nil { - t.Error("长度校验失败") - } - - rule2 := "min-length:abc" - if m := gvalid.Check("123456", rule2, nil); m == nil { - t.Error("长度校验失败") - } -} - -func Test_MaxLength(t *testing.T) { - rule := "max-length:6" - msgs := map[string]string{ - "max-length": "地址长度至大为:max位", - } - if m := gvalid.Check("12345", rule, nil); m != nil { - t.Error(m) - } - if m := gvalid.Check("1234567", rule, nil); m == nil { - t.Error("长度校验失败") - } - if m := gvalid.Check("1234567", rule, msgs); m == nil { - t.Error("长度校验失败") - } - - rule2 := "max-length:abc" - if m := gvalid.Check("123456", rule2, nil); m == nil { - t.Error("长度校验失败") - } -} - -func Test_Between(t *testing.T) { - rule := "between:6.01, 10.01" - if m := gvalid.Check(10, rule, nil); m != nil { - t.Error(m) - } - if m := gvalid.Check(10.02, rule, nil); m == nil { - t.Error("大小范围校验失败") - } - if m := gvalid.Check("a", rule, nil); m == nil { - t.Error("大小范围校验失败") - } -} - -func Test_Min(t *testing.T) { - gtest.C(t, func(t *gtest.T) { - rule := "min:100" - val1 := "1" - val2 := "99" - val3 := "100" - val4 := "1000" - val5 := "a" - err1 := gvalid.Check(val1, rule, nil) - err2 := gvalid.Check(val2, rule, nil) - err3 := gvalid.Check(val3, rule, nil) - err4 := gvalid.Check(val4, rule, nil) - err5 := gvalid.Check(val5, rule, nil) - t.AssertNE(err1, nil) - t.AssertNE(err2, nil) - t.Assert(err3, nil) - t.Assert(err4, nil) - t.AssertNE(err5, nil) - - rule2 := "min:a" - err6 := gvalid.Check(val1, rule2, nil) - t.AssertNE(err6, nil) - }) -} - -func Test_Max(t *testing.T) { - gtest.C(t, func(t *gtest.T) { - rule := "max:100" - val1 := "1" - val2 := "99" - val3 := "100" - val4 := "1000" - val5 := "a" - err1 := gvalid.Check(val1, rule, nil) - err2 := gvalid.Check(val2, rule, nil) - err3 := gvalid.Check(val3, rule, nil) - err4 := gvalid.Check(val4, rule, nil) - err5 := gvalid.Check(val5, rule, nil) - t.Assert(err1, nil) - t.Assert(err2, nil) - t.Assert(err3, nil) - t.AssertNE(err4, nil) - t.AssertNE(err5, nil) - - rule2 := "max:a" - err6 := gvalid.Check(val1, rule2, nil) - t.AssertNE(err6, nil) - }) -} - -func Test_Json(t *testing.T) { - gtest.C(t, func(t *gtest.T) { - rule := "json" - val1 := "" - val2 := "." - val3 := "{}" - val4 := "[]" - val5 := "[1,2,3,4]" - val6 := `{"list":[1,2,3,4]}` - err1 := gvalid.Check(val1, rule, nil) - err2 := gvalid.Check(val2, rule, nil) - err3 := gvalid.Check(val3, rule, nil) - err4 := gvalid.Check(val4, rule, nil) - err5 := gvalid.Check(val5, rule, nil) - err6 := gvalid.Check(val6, rule, nil) - t.AssertNE(err1, nil) - t.AssertNE(err2, nil) - t.Assert(err3, nil) - t.Assert(err4, nil) - t.Assert(err5, nil) - t.Assert(err6, nil) - }) -} - -func Test_Integer(t *testing.T) { - gtest.C(t, func(t *gtest.T) { - rule := "integer" - val1 := "" - val2 := "1.0" - val3 := "001" - val4 := "1" - val5 := "100" - val6 := `999999999` - err1 := gvalid.Check(val1, rule, nil) - err2 := gvalid.Check(val2, rule, nil) - err3 := gvalid.Check(val3, rule, nil) - err4 := gvalid.Check(val4, rule, nil) - err5 := gvalid.Check(val5, rule, nil) - err6 := gvalid.Check(val6, rule, nil) - t.AssertNE(err1, nil) - t.AssertNE(err2, nil) - t.Assert(err3, nil) - t.Assert(err4, nil) - t.Assert(err5, nil) - t.Assert(err6, nil) - }) -} - -func Test_Float(t *testing.T) { - gtest.C(t, func(t *gtest.T) { - rule := "float" - val1 := "" - val2 := "a" - val3 := "1" - val4 := "1.0" - val5 := "1.1" - val6 := `0.1` - err1 := gvalid.Check(val1, rule, nil) - err2 := gvalid.Check(val2, rule, nil) - err3 := gvalid.Check(val3, rule, nil) - err4 := gvalid.Check(val4, rule, nil) - err5 := gvalid.Check(val5, rule, nil) - err6 := gvalid.Check(val6, rule, nil) - t.AssertNE(err1, nil) - t.AssertNE(err2, nil) - t.Assert(err3, nil) - t.Assert(err4, nil) - t.Assert(err5, nil) - t.Assert(err6, nil) - }) -} - -func Test_Boolean(t *testing.T) { - gtest.C(t, func(t *gtest.T) { - rule := "boolean" - val1 := "a" - val2 := "-" - val3 := "" - val4 := "1" - val5 := "true" - val6 := `off` - err1 := gvalid.Check(val1, rule, nil) - err2 := gvalid.Check(val2, rule, nil) - err3 := gvalid.Check(val3, rule, nil) - err4 := gvalid.Check(val4, rule, nil) - err5 := gvalid.Check(val5, rule, nil) - err6 := gvalid.Check(val6, rule, nil) - t.AssertNE(err1, nil) - t.AssertNE(err2, nil) - t.Assert(err3, nil) - t.Assert(err4, nil) - t.Assert(err5, nil) - t.Assert(err6, nil) - }) -} - -func Test_Same(t *testing.T) { - gtest.C(t, func(t *gtest.T) { - rule := "same:id" - val1 := "100" - params1 := g.Map{ - "age": 18, - } - params2 := g.Map{ - "id": 100, - } - params3 := g.Map{ - "id": 100, - "name": "john", - } - err1 := gvalid.Check(val1, rule, nil, params1) - err2 := gvalid.Check(val1, rule, nil, params2) - err3 := gvalid.Check(val1, rule, nil, params3) - t.AssertNE(err1, nil) - t.Assert(err2, nil) - t.Assert(err3, nil) - }) -} - -func Test_Different(t *testing.T) { - gtest.C(t, func(t *gtest.T) { - rule := "different:id" - val1 := "100" - params1 := g.Map{ - "age": 18, - } - params2 := g.Map{ - "id": 100, - } - params3 := g.Map{ - "id": 100, - "name": "john", - } - err1 := gvalid.Check(val1, rule, nil, params1) - err2 := gvalid.Check(val1, rule, nil, params2) - err3 := gvalid.Check(val1, rule, nil, params3) - t.Assert(err1, nil) - t.AssertNE(err2, nil) - t.AssertNE(err3, nil) - }) -} - -func Test_In(t *testing.T) { - gtest.C(t, func(t *gtest.T) { - rule := "in:100,200" - val1 := "" - val2 := "1" - val3 := "100" - val4 := "200" - err1 := gvalid.Check(val1, rule, nil) - err2 := gvalid.Check(val2, rule, nil) - err3 := gvalid.Check(val3, rule, nil) - err4 := gvalid.Check(val4, rule, nil) - t.AssertNE(err1, nil) - t.AssertNE(err2, nil) - t.Assert(err3, nil) - t.Assert(err4, nil) - }) -} - -func Test_NotIn(t *testing.T) { - gtest.C(t, func(t *gtest.T) { - rule := "not-in:100" - val1 := "" - val2 := "1" - val3 := "100" - val4 := "200" - err1 := gvalid.Check(val1, rule, nil) - err2 := gvalid.Check(val2, rule, nil) - err3 := gvalid.Check(val3, rule, nil) - err4 := gvalid.Check(val4, rule, nil) - t.Assert(err1, nil) - t.Assert(err2, nil) - t.AssertNE(err3, nil) - t.Assert(err4, nil) - }) - gtest.C(t, func(t *gtest.T) { - rule := "not-in:100,200" - val1 := "" - val2 := "1" - val3 := "100" - val4 := "200" - err1 := gvalid.Check(val1, rule, nil) - err2 := gvalid.Check(val2, rule, nil) - err3 := gvalid.Check(val3, rule, nil) - err4 := gvalid.Check(val4, rule, nil) - t.Assert(err1, nil) - t.Assert(err2, nil) - t.AssertNE(err3, nil) - t.AssertNE(err4, nil) - }) -} - -func Test_Regex1(t *testing.T) { - rule := `regex:\d{6}|\D{6}|length:6,16` - if m := gvalid.Check("123456", rule, nil); m != nil { - t.Error(m) - } - if m := gvalid.Check("abcde6", rule, nil); m == nil { - t.Error("校验失败") - } -} - -func Test_Regex2(t *testing.T) { - gtest.C(t, func(t *gtest.T) { - rule := `required|min-length:6|regex:^data:image\/(jpeg|png);base64,` - str1 := "" - str2 := "data" - str3 := "data:image/jpeg;base64,/9jrbattq22r" - err1 := gvalid.Check(str1, rule, nil) - err2 := gvalid.Check(str2, rule, nil) - err3 := gvalid.Check(str3, rule, nil) - t.AssertNE(err1, nil) - t.AssertNE(err2, nil) - t.Assert(err3, nil) - - t.AssertNE(err1.Map()["required"], nil) - t.AssertNE(err2.Map()["min-length"], nil) - }) -} - -// issue: https://github.com/gogf/gf/issues/1077 -func Test_InternalError_String(t *testing.T) { - gtest.C(t, func(t *gtest.T) { - type a struct { - Name string `v:"hh"` - } - aa := a{Name: "2"} - err := gvalid.CheckStruct(&aa, nil) - - t.Assert(err.String(), "invalid_rules: hh") - t.Assert(err.Strings(), g.Slice{"invalid_rules: hh"}) - t.Assert(err.FirstString(), "invalid_rules: hh") - t.Assert(gerror.Current(err), "invalid_rules: hh") - }) -} diff --git a/util/gvalid/gvalid_unit_custom_rule_test.go b/util/gvalid/gvalid_unit_custom_rule_test.go deleted file mode 100644 index b6ec82d69..000000000 --- a/util/gvalid/gvalid_unit_custom_rule_test.go +++ /dev/null @@ -1,153 +0,0 @@ -// 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 gvalid_test - -import ( - "errors" - "github.com/gogf/gf/frame/g" - "github.com/gogf/gf/util/gconv" - "testing" - - "github.com/gogf/gf/test/gtest" - "github.com/gogf/gf/util/gvalid" -) - -func Test_CustomRule1(t *testing.T) { - rule := "custom" - 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) - } - if params["data"] != pass { - return errors.New(message) - } - return nil - }) - gtest.Assert(err, nil) - gtest.C(t, func(t *gtest.T) { - err := gvalid.Check("123456", rule, "custom message") - t.Assert(err.String(), "custom message") - err = gvalid.Check("123456", rule, "custom message", g.Map{"data": "123456"}) - t.Assert(err, nil) - }) - // Error with struct validation. - gtest.C(t, func(t *gtest.T) { - type T struct { - Value string `v:"uid@custom#自定义错误"` - Data string `p:"data"` - } - st := &T{ - Value: "123", - 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 string `v:"uid@custom#自定义错误"` - Data string `p:"data"` - } - st := &T{ - Value: "123456", - Data: "123456", - } - err := gvalid.CheckStruct(st, nil) - 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) - }) -} - -func Test_CustomRule_AllowEmpty(t *testing.T) { - rule := "allow-empty-str" - err := gvalid.RegisterRule(rule, func(rule string, value interface{}, message string, params map[string]interface{}) error { - s := gconv.String(value) - if len(s) == 0 || s == "gf" { - return nil - } - return errors.New(message) - }) - gtest.Assert(err, nil) - // Check. - gtest.C(t, func(t *gtest.T) { - errStr := "error" - t.Assert(gvalid.Check("", rule, errStr).String(), "") - t.Assert(gvalid.Check("gf", rule, errStr).String(), "") - t.Assert(gvalid.Check("gf2", rule, errStr).String(), errStr) - }) - // Error with struct validation. - gtest.C(t, func(t *gtest.T) { - type T struct { - Value string `v:"uid@allow-empty-str#自定义错误"` - Data string `p:"data"` - } - st := &T{ - Value: "", - 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 string `v:"uid@allow-empty-str#自定义错误"` - Data string `p:"data"` - } - st := &T{ - Value: "john", - Data: "123456", - } - err := gvalid.CheckStruct(st, nil) - t.Assert(err.String(), "自定义错误") - }) -} diff --git a/util/gvalid/gvalid_validator.go b/util/gvalid/gvalid_validator.go new file mode 100644 index 000000000..33ea9dfd0 --- /dev/null +++ b/util/gvalid/gvalid_validator.go @@ -0,0 +1,114 @@ +// Copyright GoFrame Author(https://goframe.org). All Rights Reserved. +// +// This Source Code Form is subject to the terms of the MIT License. +// If a copy of the MIT was not distributed with this file, +// You can obtain one at https://github.com/gogf/gf. + +package gvalid + +import ( + "context" + "github.com/gogf/gf/i18n/gi18n" +) + +// Validator is the validation manager for chaining operations. +type Validator struct { + ctx context.Context // Context containing custom context variables. + i18nManager *gi18n.Manager // I18n manager for error message translation. + key string // Single validation key. + value interface{} // Single validation value. + data interface{} // Validation data, which is usually a map. + rules interface{} // Custom validation data. + messages interface{} // Custom validation error messages, which can be string or type of CustomMsg. + ruleFuncMap map[string]RuleFunc // ruleFuncMap stores custom rule functions for current Validator. + useDataInsteadOfObjectAttributes bool // Using `data` as its validation source instead of attribute values from `Object`. + bail bool // Stop validation after the first validation error. +} + +// New creates and returns a new Validator. +func New() *Validator { + return &Validator{ + ctx: context.TODO(), // Initialize an empty context. + i18nManager: gi18n.Instance(), // Use default i18n manager. + ruleFuncMap: make(map[string]RuleFunc), // Custom rule function storing map. + } +} + +// Clone creates and returns a new Validator which is a shallow copy of current one. +func (v *Validator) Clone() *Validator { + newValidator := New() + *newValidator = *v + return newValidator +} + +// I18n sets the i18n manager for the validator. +func (v *Validator) I18n(i18nManager *gi18n.Manager) *Validator { + newValidator := v.Clone() + newValidator.i18nManager = i18nManager + return newValidator +} + +// Ctx is a chaining operation function, which sets the context for next validation. +func (v *Validator) Ctx(ctx context.Context) *Validator { + newValidator := v.Clone() + newValidator.ctx = ctx + return newValidator +} + +// Bail sets the mark for stopping validation after the first validation error. +func (v *Validator) Bail() *Validator { + newValidator := v.Clone() + newValidator.bail = true + return newValidator +} + +// Data is a chaining operation function, which sets validation data for current operation. +// The parameter `data` usually be type of map, which specifies the parameter map used in validation. +// Calling this function also sets `useDataInsteadOfObjectAttributes` true no mather the `data` is nil or not. +func (v *Validator) Data(data interface{}) *Validator { + newValidator := v.Clone() + newValidator.data = data + newValidator.useDataInsteadOfObjectAttributes = true + return newValidator +} + +// Rules is a chaining operation function, which sets custom validation rules for current operation. +func (v *Validator) Rules(rules interface{}) *Validator { + newValidator := v.Clone() + newValidator.rules = rules + return newValidator +} + +// Messages is a chaining operation function, which sets custom error messages for current operation. +// The parameter `messages` can be type of string/[]string/map[string]string. It supports sequence in error result +// if `rules` is type of []string. +func (v *Validator) Messages(messages interface{}) *Validator { + newValidator := v.Clone() + newValidator.messages = messages + return newValidator +} + +// RuleFunc registers one custom rule function to current Validator. +func (v *Validator) RuleFunc(rule string, f RuleFunc) *Validator { + newValidator := v.Clone() + newValidator.ruleFuncMap[rule] = f + return newValidator +} + +// RuleFuncMap registers multiple custom rule functions to current Validator. +func (v *Validator) RuleFuncMap(m map[string]RuleFunc) *Validator { + newValidator := v.Clone() + for k, v := range m { + newValidator.ruleFuncMap[k] = v + } + return newValidator +} + +// getRuleFunc retrieves and returns the custom rule function for specified rule. +func (v *Validator) getRuleFunc(rule string) RuleFunc { + ruleFunc := v.ruleFuncMap[rule] + if ruleFunc == nil { + ruleFunc = customRuleFuncMap[rule] + } + return ruleFunc +} diff --git a/util/gvalid/gvalid_validator_check_map.go b/util/gvalid/gvalid_validator_check_map.go new file mode 100644 index 000000000..9db900232 --- /dev/null +++ b/util/gvalid/gvalid_validator_check_map.go @@ -0,0 +1,155 @@ +// Copyright GoFrame Author(https://goframe.org). All Rights Reserved. +// +// This Source Code Form is subject to the terms of the MIT License. +// If a copy of the MIT was not distributed with this file, +// You can obtain one at https://github.com/gogf/gf. + +package gvalid + +import ( + "github.com/gogf/gf/errors/gcode" + "github.com/gogf/gf/util/gconv" + "strings" +) + +// CheckMap validates map and returns the error result. It returns nil if with successful validation. +// The parameter `params` should be type of map. +func (v *Validator) CheckMap(params interface{}) Error { + return v.doCheckMap(params) +} + +func (v *Validator) doCheckMap(params interface{}) Error { + // If there's no validation rules, it does nothing and returns quickly. + if params == nil || v.rules == nil { + return nil + } + var ( + checkRules = make([]fieldRule, 0) + customMessage = make(CustomMsg) // map[RuleKey]ErrorMsg. + errorMaps = make(map[string]map[string]string) + ) + switch assertValue := v.rules.(type) { + // Sequence tag: []sequence tag + // Sequence has order for error results. + case []string: + for _, tag := range assertValue { + name, rule, msg := parseSequenceTag(tag) + if len(name) == 0 { + continue + } + if len(msg) > 0 { + var ( + msgArray = strings.Split(msg, "|") + ruleArray = strings.Split(rule, "|") + ) + for k, ruleItem := range ruleArray { + // If length of custom messages is lesser than length of rules, + // the rest rules use the default error messages. + if len(msgArray) <= k { + continue + } + if len(msgArray[k]) == 0 { + continue + } + array := strings.Split(ruleItem, ":") + if _, ok := customMessage[name]; !ok { + customMessage[name] = make(map[string]string) + } + customMessage[name].(map[string]string)[strings.TrimSpace(array[0])] = strings.TrimSpace(msgArray[k]) + } + } + checkRules = append(checkRules, fieldRule{ + Name: name, + Rule: rule, + }) + } + + // No sequence rules: map[field]rule + case map[string]string: + for name, rule := range assertValue { + checkRules = append(checkRules, fieldRule{ + Name: name, + Rule: rule, + }) + } + } + // If there's no validation rules, it does nothing and returns quickly. + if len(checkRules) == 0 { + return nil + } + data := gconv.Map(params) + if data == nil { + return newErrorStr( + internalParamsErrRuleName, + "invalid params type: convert to map failed", + ) + } + if msg, ok := v.messages.(CustomMsg); ok && len(msg) > 0 { + if len(customMessage) > 0 { + for k, v := range msg { + customMessage[k] = v + } + } else { + customMessage = msg + } + } + var ( + value interface{} + ) + for _, checkRuleItem := range checkRules { + if len(checkRuleItem.Rule) == 0 { + continue + } + value = nil + if valueItem, ok := data[checkRuleItem.Name]; ok { + value = valueItem + } + // It checks each rule and its value in loop. + if validatedError := v.doCheckValue(doCheckValueInput{ + Name: checkRuleItem.Name, + Value: value, + Rule: checkRuleItem.Rule, + Messages: customMessage[checkRuleItem.Name], + DataRaw: params, + DataMap: data, + }); validatedError != nil { + _, errorItem := validatedError.FirstItem() + // =========================================================== + // Only in map and struct validations, if value is nil or empty + // string and has no required* rules, it clears the error message. + // =========================================================== + if gconv.String(value) == "" { + required := false + // rule => error + for ruleKey := range errorItem { + // Default required rules. + if _, ok := mustCheckRulesEvenValueEmpty[ruleKey]; ok { + required = true + break + } + // Custom rules are also required in default. + if f := v.getRuleFunc(ruleKey); f != nil { + required = true + break + } + } + if !required { + continue + } + } + if _, ok := errorMaps[checkRuleItem.Name]; !ok { + errorMaps[checkRuleItem.Name] = make(map[string]string) + } + for ruleKey, errorItemMsgMap := range errorItem { + errorMaps[checkRuleItem.Name][ruleKey] = errorItemMsgMap + } + if v.bail { + break + } + } + } + if len(errorMaps) > 0 { + return newError(gcode.CodeValidationFailed, checkRules, errorMaps) + } + return nil +} diff --git a/util/gvalid/gvalid_validator_check_struct.go b/util/gvalid/gvalid_validator_check_struct.go new file mode 100644 index 000000000..78c4f9847 --- /dev/null +++ b/util/gvalid/gvalid_validator_check_struct.go @@ -0,0 +1,294 @@ +// Copyright GoFrame Author(https://goframe.org). All Rights Reserved. +// +// This Source Code Form is subject to the terms of the MIT License. +// If a copy of the MIT was not distributed with this file, +// You can obtain one at https://github.com/gogf/gf. + +package gvalid + +import ( + "github.com/gogf/gf/errors/gcode" + "github.com/gogf/gf/internal/structs" + "github.com/gogf/gf/util/gconv" + "github.com/gogf/gf/util/gutil" + "strings" +) + +// CheckStruct validates struct and returns the error result. +// The parameter `object` should be type of struct/*struct. +func (v *Validator) CheckStruct(object interface{}) Error { + return v.doCheckStruct(object) +} + +func (v *Validator) doCheckStruct(object interface{}) Error { + var ( + errorMaps = make(map[string]map[string]string) // Returning error. + fieldToAliasNameMap = make(map[string]string) // Field name to alias name map. + ) + fieldMap, err := structs.FieldMap(structs.FieldMapInput{ + Pointer: object, + PriorityTagArray: aliasNameTagPriority, + RecursiveOption: structs.RecursiveOptionEmbedded, + }) + if err != nil { + return newErrorStr(internalObjectErrRuleName, err.Error()) + } + // It checks the struct recursively the its attribute is an embedded struct. + for _, field := range fieldMap { + if field.IsEmbedded() { + // No validation interface implements check. + if _, ok := field.Value.Interface().(apiNoValidation); ok { + continue + } + if _, ok := field.TagLookup(noValidationTagName); ok { + continue + } + if err := v.doCheckStruct(field.Value); err != nil { + // It merges the errors into single error map. + for k, m := range err.(*validationError).errors { + errorMaps[k] = m + } + } + } else { + if field.TagValue != "" { + fieldToAliasNameMap[field.Name()] = field.TagValue + } + } + } + // It here must use structs.TagFields not structs.FieldMap to ensure error sequence. + tagField, err := structs.TagFields(object, structTagPriority) + if err != nil { + return newErrorStr(internalObjectErrRuleName, err.Error()) + } + // If there's no struct tag and validation rules, it does nothing and returns quickly. + if len(tagField) == 0 && v.messages == nil { + return nil + } + + var ( + inputParamMap map[string]interface{} + checkRules = make([]fieldRule, 0) + nameToRuleMap = make(map[string]string) // just for internally searching index purpose. + customMessage = make(CustomMsg) // Custom rule error message map. + checkValueData = v.data // Ready to be validated data, which can be type of . + ) + if checkValueData == nil { + checkValueData = object + } + switch assertValue := v.rules.(type) { + // Sequence tag: []sequence tag + // Sequence has order for error results. + case []string: + for _, tag := range assertValue { + name, rule, msg := parseSequenceTag(tag) + if len(name) == 0 { + continue + } + if len(msg) > 0 { + var ( + msgArray = strings.Split(msg, "|") + ruleArray = strings.Split(rule, "|") + ) + for k, v := range ruleArray { + // If length of custom messages is lesser than length of rules, + // the rest rules use the default error messages. + if len(msgArray) <= k { + continue + } + if len(msgArray[k]) == 0 { + continue + } + array := strings.Split(v, ":") + if _, ok := customMessage[name]; !ok { + customMessage[name] = make(map[string]string) + } + customMessage[name].(map[string]string)[strings.TrimSpace(array[0])] = strings.TrimSpace(msgArray[k]) + } + } + nameToRuleMap[name] = rule + checkRules = append(checkRules, fieldRule{ + Name: name, + Rule: rule, + }) + } + + // Map type rules does not support sequence. + // Format: map[key]rule + case map[string]string: + nameToRuleMap = assertValue + for name, rule := range assertValue { + checkRules = append(checkRules, fieldRule{ + Name: name, + Rule: rule, + }) + } + } + // If there's no struct tag and validation rules, it does nothing and returns quickly. + if len(tagField) == 0 && len(checkRules) == 0 { + return nil + } + // Input parameter map handling. + if v.data == nil || !v.useDataInsteadOfObjectAttributes { + inputParamMap = make(map[string]interface{}) + } else { + inputParamMap = gconv.Map(v.data) + } + // Checks and extends the parameters map with struct alias tag. + if !v.useDataInsteadOfObjectAttributes { + for nameOrTag, field := range fieldMap { + inputParamMap[nameOrTag] = field.Value.Interface() + if nameOrTag != field.Name() { + inputParamMap[field.Name()] = field.Value.Interface() + } + } + } + + // Merge the custom validation rules with rules in struct tag. + // The custom rules has the most high priority that can overwrite the struct tag rules. + for _, field := range tagField { + var ( + fieldName = field.Name() // Attribute name. + name, rule, msg = parseSequenceTag(field.TagValue) // The `name` is different from `attribute alias`, which is used for validation only. + ) + if len(name) == 0 { + if v, ok := fieldToAliasNameMap[fieldName]; ok { + // It uses alias name of the attribute if its alias name tag exists. + name = v + } else { + // It or else uses the attribute name directly. + name = fieldName + } + } else { + // It uses the alias name from validation rule. + fieldToAliasNameMap[fieldName] = name + } + // It here extends the params map using alias names. + // Note that the variable `name` might be alias name or attribute name. + if _, ok := inputParamMap[name]; !ok { + if !v.useDataInsteadOfObjectAttributes { + inputParamMap[name] = field.Value.Interface() + } else { + if name != fieldName { + if foundKey, foundValue := gutil.MapPossibleItemByKey(inputParamMap, fieldName); foundKey != "" { + inputParamMap[name] = foundValue + } + } + } + } + + if _, ok := nameToRuleMap[name]; !ok { + if _, ok := nameToRuleMap[fieldName]; ok { + // If there's alias name, + // use alias name as its key and remove the field name key. + nameToRuleMap[name] = nameToRuleMap[fieldName] + delete(nameToRuleMap, fieldName) + for index, checkRuleItem := range checkRules { + if fieldName == checkRuleItem.Name { + checkRuleItem.Name = name + checkRules[index] = checkRuleItem + break + } + } + } else { + nameToRuleMap[name] = rule + checkRules = append(checkRules, fieldRule{ + Name: name, + Rule: rule, + }) + } + } else { + // The input rules can overwrite the rules in struct tag. + continue + } + + if len(msg) > 0 { + var ( + msgArray = strings.Split(msg, "|") + ruleArray = strings.Split(rule, "|") + ) + for k, v := range ruleArray { + // If length of custom messages is lesser than length of rules, + // the rest rules use the default error messages. + if len(msgArray) <= k { + continue + } + if len(msgArray[k]) == 0 { + continue + } + array := strings.Split(v, ":") + if _, ok := customMessage[name]; !ok { + customMessage[name] = make(map[string]string) + } + customMessage[name].(map[string]string)[strings.TrimSpace(array[0])] = strings.TrimSpace(msgArray[k]) + } + } + } + + // Custom error messages, + // which have the most priority than `rules` and struct tag. + if msg, ok := v.messages.(CustomMsg); ok && len(msg) > 0 { + for k, v := range msg { + if a, ok := fieldToAliasNameMap[k]; ok { + // Overwrite the key of field name. + customMessage[a] = v + } else { + customMessage[k] = v + } + } + } + + // The following logic is the same as some of CheckMap but with sequence support. + var ( + value interface{} + ) + for _, checkRuleItem := range checkRules { + _, value = gutil.MapPossibleItemByKey(inputParamMap, checkRuleItem.Name) + // It checks each rule and its value in loop. + if validatedError := v.doCheckValue(doCheckValueInput{ + Name: checkRuleItem.Name, + Value: value, + Rule: checkRuleItem.Rule, + Messages: customMessage[checkRuleItem.Name], + DataRaw: checkValueData, + DataMap: inputParamMap, + }); validatedError != nil { + _, errorItem := validatedError.FirstItem() + // =================================================================== + // Only in map and struct validations, if value is nil or empty string + // and has no required* rules, it clears the error message. + // =================================================================== + if value == nil || gconv.String(value) == "" { + required := false + // rule => error + for ruleKey := range errorItem { + // Default required rules. + if _, ok := mustCheckRulesEvenValueEmpty[ruleKey]; ok { + required = true + break + } + // Custom rules are also required in default. + if f := v.getRuleFunc(ruleKey); f != nil { + required = true + break + } + } + if !required { + continue + } + } + if _, ok := errorMaps[checkRuleItem.Name]; !ok { + errorMaps[checkRuleItem.Name] = make(map[string]string) + } + for ruleKey, errorItemMsgMap := range errorItem { + errorMaps[checkRuleItem.Name][ruleKey] = errorItemMsgMap + } + if v.bail { + break + } + } + } + if len(errorMaps) > 0 { + return newError(gcode.CodeValidationFailed, checkRules, errorMaps) + } + return nil +} diff --git a/util/gvalid/gvalid_check.go b/util/gvalid/gvalid_validator_check_value.go similarity index 54% rename from util/gvalid/gvalid_check.go rename to util/gvalid/gvalid_validator_check_value.go index 99e5c327f..f02fff8a2 100644 --- a/util/gvalid/gvalid_check.go +++ b/util/gvalid/gvalid_validator_check_value.go @@ -1,4 +1,4 @@ -// Copyright 2017-2018 gf Author(https://github.com/gogf/gf). All Rights Reserved. +// Copyright GoFrame Author(https://goframe.org). All Rights Reserved. // // This Source Code Form is subject to the terms of the MIT License. // If a copy of the MIT was not distributed with this file, @@ -7,170 +7,86 @@ package gvalid import ( - "errors" + "github.com/gogf/gf/errors/gcode" + "github.com/gogf/gf/errors/gerror" + "strconv" + "strings" + "time" + "github.com/gogf/gf/internal/json" "github.com/gogf/gf/net/gipv4" "github.com/gogf/gf/net/gipv6" "github.com/gogf/gf/os/gtime" "github.com/gogf/gf/text/gregex" "github.com/gogf/gf/util/gconv" - "regexp" - "strconv" - "strings" + "github.com/gogf/gf/util/gutil" ) -const ( - // regular expression pattern for single validation rule. - singleRulePattern = `^([\w-]+):{0,1}(.*)` - invalidRulesErrKey = "invalid_rules" - invalidParamsErrKey = "invalid_params" - invalidObjectErrKey = "invalid_object" -) - -var ( - // all internal error keys. - internalErrKeyMap = map[string]string{ - invalidRulesErrKey: invalidRulesErrKey, - invalidParamsErrKey: invalidParamsErrKey, - invalidObjectErrKey: invalidObjectErrKey, - } - // regular expression object for single rule - // which is compiled just once and of repeatable usage. - ruleRegex, _ = regexp.Compile(singleRulePattern) - - // mustCheckRulesEvenValueEmpty specifies some rules that must be validated - // even the value is empty (nil or empty). - mustCheckRulesEvenValueEmpty = map[string]struct{}{ - "required": {}, - "required-if": {}, - "required-unless": {}, - "required-with": {}, - "required-with-all": {}, - "required-without": {}, - "required-without-all": {}, - //"same": {}, - //"different": {}, - //"in": {}, - //"not-in": {}, - //"regex": {}, - } - // allSupportedRules defines all supported rules that is used for quick checks. - allSupportedRules = map[string]struct{}{ - "required": {}, - "required-if": {}, - "required-unless": {}, - "required-with": {}, - "required-with-all": {}, - "required-without": {}, - "required-without-all": {}, - "date": {}, - "date-format": {}, - "email": {}, - "phone": {}, - "phone-loose": {}, - "telephone": {}, - "passport": {}, - "password": {}, - "password2": {}, - "password3": {}, - "postcode": {}, - "resident-id": {}, - "bank-card": {}, - "qq": {}, - "ip": {}, - "ipv4": {}, - "ipv6": {}, - "mac": {}, - "url": {}, - "domain": {}, - "length": {}, - "min-length": {}, - "max-length": {}, - "between": {}, - "min": {}, - "max": {}, - "json": {}, - "integer": {}, - "float": {}, - "boolean": {}, - "same": {}, - "different": {}, - "in": {}, - "not-in": {}, - "regex": {}, - } - // boolMap defines the boolean values. - boolMap = map[string]struct{}{ - "1": {}, - "true": {}, - "on": {}, - "yes": {}, - "": {}, - "0": {}, - "false": {}, - "off": {}, - "no": {}, - } -) - -// Check checks single value with specified rules. -// It returns nil if successful validation. -// -// The parameter <value> can be any type of variable, which will be converted to string -// for validation. -// The parameter <rules> can be one or more rules, multiple rules joined using char '|'. -// The parameter <messages> specifies the custom error messages, which can be type of: -// string/map/struct/*struct. -// The optional parameter <params> specifies the extra validation parameters for some rules -// like: required-*、same、different, etc. -func Check(value interface{}, rules string, messages interface{}, params ...interface{}) *Error { - return doCheck("", value, rules, messages, params...) +type apiTime interface { + Date() (year int, month time.Month, day int) + IsZero() bool } -// doCheck does the really rules validation for single key-value. -func doCheck(key string, value interface{}, rules string, messages interface{}, params ...interface{}) *Error { +// CheckValue checks single value with specified rules. +// It returns nil if successful validation. +func (v *Validator) CheckValue(value interface{}) Error { + return v.doCheckValue(doCheckValueInput{ + Name: "", + Value: value, + Rule: gconv.String(v.rules), + Messages: v.messages, + DataRaw: v.data, + DataMap: gconv.Map(v.data), + }) +} + +type doCheckValueInput struct { + Name string // Name specifies the name of parameter `value`. + Value interface{} // Value specifies the value for this rules to be validated. + Rule string // Rule specifies the validation rules string, like "required", "required|between:1,100", etc. + Messages interface{} // Messages specifies the custom error messages for this rule, which is usually type of map/slice. + DataRaw interface{} // DataRaw specifies the `raw data` which is passed to the Validator. It might be type of map/struct or a nil value. + DataMap map[string]interface{} // DataMap specifies the map that is converted from `dataRaw`. It is usually used internally +} + +// doCheckSingleValue does the really rules validation for single key-value. +func (v *Validator) doCheckValue(input doCheckValueInput) Error { // If there's no validation rules, it does nothing and returns quickly. - if rules == "" { + if input.Rule == "" { return nil } // It converts value to string and then does the validation. var ( // Do not trim it as the space is also part of the value. - data = make(map[string]string) errorMsgArray = make(map[string]string) ) - if len(params) > 0 { - for k, v := range gconv.Map(params[0]) { - data[k] = gconv.String(v) - } - } // Custom error messages handling. var ( msgArray = make([]string, 0) customMsgMap = make(map[string]string) ) - switch v := messages.(type) { + switch v := input.Messages.(type) { case string: msgArray = strings.Split(v, "|") default: - for k, v := range gconv.Map(messages) { + for k, v := range gconv.Map(input.Messages) { customMsgMap[k] = gconv.String(v) } } // Handle the char '|' in the rule, // which makes this rule separated into multiple rules. - ruleItems := strings.Split(strings.TrimSpace(rules), "|") + ruleItems := strings.Split(strings.TrimSpace(input.Rule), "|") for i := 0; ; { array := strings.Split(ruleItems[i], ":") _, ok := allSupportedRules[array[0]] - if !ok && customRuleFuncMap[array[0]] == nil { + if !ok && v.getRuleFunc(array[0]) == nil { if i > 0 && ruleItems[i-1][:5] == "regex" { ruleItems[i-1] += "|" + ruleItems[i] ruleItems = append(ruleItems[:i], ruleItems[i+1:]...) } else { return newErrorStr( - invalidRulesErrKey, - invalidRulesErrKey+": "+rules, + internalRulesErrRuleName, + internalRulesErrRuleName+": "+input.Rule, ) } } else { @@ -180,28 +96,42 @@ func doCheck(key string, value interface{}, rules string, messages interface{}, break } } + var ( + hasBailRule = false + ) for index := 0; index < len(ruleItems); { var ( - err error - match = false - results = ruleRegex.FindStringSubmatch(ruleItems[index]) - ruleKey = strings.TrimSpace(results[1]) - rulePattern = strings.TrimSpace(results[2]) + err error + match = false // whether this rule is matched(has no error) + results = ruleRegex.FindStringSubmatch(ruleItems[index]) // split single rule. + ruleKey = strings.TrimSpace(results[1]) // rule name like "max" in rule "max: 6" + rulePattern = strings.TrimSpace(results[2]) // rule value if any like "6" in rule:"max:6" + customRuleFunc RuleFunc ) + + if !hasBailRule && ruleKey == bailRuleName { + hasBailRule = true + } + + // Ignore logic executing for marked rules. + if markedRuleMap[ruleKey] { + index++ + continue + } + if len(msgArray) > index { customMsgMap[ruleKey] = strings.TrimSpace(msgArray[index]) } - if f, ok := customRuleFuncMap[ruleKey]; ok { + // Custom rule handling. + // 1. It firstly checks and uses the custom registered rules functions in the current Validator. + // 2. It secondly checks and uses the globally registered rules functions. + // 3. It finally checks and uses the build-in rules functions. + customRuleFunc = v.getRuleFunc(ruleKey) + if customRuleFunc != nil { // 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 { + message := v.getErrorMessageByRule(ruleKey, customMsgMap) + if err := customRuleFunc(v.ctx, ruleItems[index], input.Value, message, input.DataRaw); err != nil { match = false errorMsgArray[ruleKey] = err.Error() } else { @@ -209,7 +139,15 @@ func doCheck(key string, value interface{}, rules string, messages interface{}, } } else { // It checks build-in validation rules if there's no custom rule. - match, err = doCheckBuildInRules(index, value, ruleKey, rulePattern, ruleItems, data, customMsgMap) + match, err = v.doCheckBuildInRules(doCheckBuildInRulesInput{ + Index: index, + Value: input.Value, + RuleKey: ruleKey, + RulePattern: rulePattern, + RuleItems: ruleItems, + DataMap: input.DataMap, + CustomMsgMap: customMsgMap, + }) if !match && err != nil { errorMsgArray[ruleKey] = err.Error() } @@ -220,30 +158,37 @@ func doCheck(key string, value interface{}, rules string, messages interface{}, // It does nothing if the error message for this rule // is already set in previous validation. if _, ok := errorMsgArray[ruleKey]; !ok { - errorMsgArray[ruleKey] = getErrorMessageByRule(ruleKey, customMsgMap) + errorMsgArray[ruleKey] = v.getErrorMessageByRule(ruleKey, customMsgMap) + } + // If it is with error and there's bail rule, + // it then does not continue validating for left rules. + if hasBailRule { + break } } index++ } if len(errorMsgArray) > 0 { - return newError([]string{rules}, ErrorMap{ - key: errorMsgArray, + return newError(gcode.CodeValidationFailed, []fieldRule{{Name: input.Name, Rule: input.Rule}}, map[string]map[string]string{ + input.Name: 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 { +type doCheckBuildInRulesInput struct { + Index int + Value interface{} + RuleKey string + RulePattern string + RuleItems []string + DataMap map[string]interface{} + CustomMsgMap map[string]string +} + +func (v *Validator) doCheckBuildInRules(input doCheckBuildInRulesInput) (match bool, err error) { + valueStr := gconv.String(input.Value) + switch input.RuleKey { // Required rules. case "required", @@ -253,16 +198,20 @@ func doCheckBuildInRules( "required-with-all", "required-without", "required-without-all": - match = checkRequired(valueStr, ruleKey, rulePattern, dataMap) + match = v.checkRequired(input.Value, input.RuleKey, input.RulePattern, input.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) + "max-length", + "size": + if msg := v.checkLength(valueStr, input.RuleKey, input.RulePattern, input.CustomMsgMap); msg != "" { + return match, gerror.NewOption(gerror.Option{ + Text: msg, + Code: gcode.CodeValidationFailed, + }) } else { match = true } @@ -272,8 +221,11 @@ func doCheckBuildInRules( "min", "max", "between": - if msg := checkRange(valueStr, ruleKey, rulePattern, customMsgMap); msg != "" { - return match, errors.New(msg) + if msg := v.checkRange(valueStr, input.RuleKey, input.RulePattern, input.CustomMsgMap); msg != "" { + return match, gerror.NewOption(gerror.Option{ + Text: msg, + Code: gcode.CodeValidationFailed, + }) } else { match = true } @@ -281,70 +233,80 @@ func doCheckBuildInRules( // 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(singleRulePattern, ruleItems[i]) { - rulePattern += "|" + ruleItems[i] - index++ + for i := input.Index + 1; i < len(input.RuleItems); i++ { + if !gregex.IsMatchString(singleRulePattern, input.RuleItems[i]) { + input.RulePattern += "|" + input.RuleItems[i] + input.Index++ } } - match = gregex.IsMatchString(rulePattern, valueStr) + match = gregex.IsMatchString(input.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 + // support for time value, eg: gtime.Time/*gtime.Time, time.Time/*time.Time. + if v, ok := input.Value.(apiTime); ok { + return !v.IsZero(), nil } + match = gregex.IsMatchString(`\d{4}[\.\-\_/]{0,1}\d{2}[\.\-\_/]{0,1}\d{2}`, valueStr) // Date rule with specified format. case "date-format": - if _, err := gtime.StrToTimeFormat(valueStr, rulePattern); err == nil { + // support for time value, eg: gtime.Time/*gtime.Time, time.Time/*time.Time. + if v, ok := input.Value.(apiTime); ok { + return !v.IsZero(), nil + } + if _, err := gtime.StrToTimeFormat(valueStr, input.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) + msg = v.getErrorMessageByRule(input.RuleKey, input.CustomMsgMap) + msg = strings.Replace(msg, ":format", input.RulePattern, -1) + return match, gerror.NewOption(gerror.Option{ + Text: msg, + Code: gcode.CodeValidationFailed, + }) } // Values of two fields should be equal as string. case "same": - if v, ok := dataMap[rulePattern]; ok { - if strings.Compare(valueStr, v) == 0 { + _, foundValue := gutil.MapPossibleItemByKey(input.DataMap, input.RulePattern) + if foundValue != nil { + if strings.Compare(valueStr, gconv.String(foundValue)) == 0 { match = true } } if !match { var msg string - msg = getErrorMessageByRule(ruleKey, customMsgMap) - msg = strings.Replace(msg, ":field", rulePattern, -1) - return match, errors.New(msg) + msg = v.getErrorMessageByRule(input.RuleKey, input.CustomMsgMap) + msg = strings.Replace(msg, ":field", input.RulePattern, -1) + return match, gerror.NewOption(gerror.Option{ + Text: msg, + Code: gcode.CodeValidationFailed, + }) } // 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 { + _, foundValue := gutil.MapPossibleItemByKey(input.DataMap, input.RulePattern) + if foundValue != nil { + if strings.Compare(valueStr, gconv.String(foundValue)) == 0 { match = false } } if !match { var msg string - msg = getErrorMessageByRule(ruleKey, customMsgMap) - msg = strings.Replace(msg, ":field", rulePattern, -1) - return match, errors.New(msg) + msg = v.getErrorMessageByRule(input.RuleKey, input.CustomMsgMap) + msg = strings.Replace(msg, ":field", input.RulePattern, -1) + return match, gerror.NewOption(gerror.Option{ + Text: msg, + Code: gcode.CodeValidationFailed, + }) } // Field value should be in range of. case "in": - array := strings.Split(rulePattern, ",") + array := strings.Split(input.RulePattern, ",") for _, v := range array { if strings.Compare(valueStr, strings.TrimSpace(v)) == 0 { match = true @@ -355,7 +317,7 @@ func doCheckBuildInRules( // Field value should not be in range of. case "not-in": match = true - array := strings.Split(rulePattern, ",") + array := strings.Split(input.RulePattern, ",") for _, v := range array { if strings.Compare(valueStr, strings.TrimSpace(v)) == 0 { match = false @@ -385,6 +347,7 @@ func doCheckBuildInRules( // 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,2,3,5,6,7,8]{1}\d{8}$|^18[\d]{9}$|^19[\d]{9}$`, valueStr) + // Loose mobile phone number verification(宽松的手机号验证) // As long as the 11 digit numbers beginning with // 13, 14, 15, 16, 17, 18, 19 can pass the verification (只要满足 13、14、15、16、17、18、19开头的11位数字都可以通过验证) @@ -430,11 +393,11 @@ func doCheckBuildInRules( // 总: // (^[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) + match = v.checkResidentId(valueStr) // Bank card number using LUHN algorithm. case "bank-card": - match = checkLuHn(valueStr) + match = v.checkLuHn(valueStr) // Universal passport format rule: // Starting with letter, containing only numbers or underscores, length between 6 and 18. @@ -521,7 +484,10 @@ func doCheckBuildInRules( 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, gerror.NewOption(gerror.Option{ + Text: "Invalid rule name: " + input.RuleKey, + Code: gcode.CodeInvalidParameter, + }) } return match, nil } diff --git a/util/gvalid/gvalid_validator_message.go b/util/gvalid/gvalid_validator_message.go new file mode 100644 index 000000000..7701e5f96 --- /dev/null +++ b/util/gvalid/gvalid_validator_message.go @@ -0,0 +1,35 @@ +// Copyright GoFrame Author(https://goframe.org). All Rights Reserved. +// +// This Source Code Form is subject to the terms of the MIT License. +// If a copy of the MIT was not distributed with this file, +// You can obtain one at https://github.com/gogf/gf. + +package gvalid + +// getErrorMessageByRule retrieves and returns the error message for specified rule. +// It firstly retrieves the message from custom message map, and then checks i18n manager, +// it returns the default error message if it's not found in custom message map or i18n manager. +func (v *Validator) getErrorMessageByRule(ruleKey string, customMsgMap map[string]string) string { + content := customMsgMap[ruleKey] + if content != "" { + // I18n translation. + i18nContent := v.i18nManager.GetContent(v.ctx, content) + if i18nContent != "" { + return i18nContent + } + return content + } + // Retrieve default message according to certain rule. + content = v.i18nManager.GetContent(v.ctx, ruleMessagePrefixForI18n+ruleKey) + if content == "" { + content = defaultMessages[ruleKey] + } + // If there's no configured rule message, it uses default one. + if content == "" { + content = v.i18nManager.GetContent(v.ctx, ruleMessagePrefixForI18n+internalDefaultRuleName) + if content == "" { + content = defaultMessages[internalDefaultRuleName] + } + } + return content +} diff --git a/util/gvalid/gvalid_rule_length.go b/util/gvalid/gvalid_validator_rule_length.go similarity index 68% rename from util/gvalid/gvalid_rule_length.go rename to util/gvalid/gvalid_validator_rule_length.go index 7d67284f8..7bd5b188e 100644 --- a/util/gvalid/gvalid_rule_length.go +++ b/util/gvalid/gvalid_validator_rule_length.go @@ -1,4 +1,4 @@ -// Copyright 2018 gf Author(https://github.com/gogf/gf). All Rights Reserved. +// Copyright GoFrame Author(https://goframe.org). All Rights Reserved. // // This Source Code Form is subject to the terms of the MIT License. // If a copy of the MIT was not distributed with this file, @@ -12,10 +12,10 @@ import ( "strings" ) -// checkLength checks <value> using length rules. +// checkLength checks `value` using length rules. // The length is calculated using unicode string, which means one chinese character or letter // both has the length of 1. -func checkLength(value, ruleKey, ruleVal string, customMsgMap map[string]string) string { +func (v *Validator) checkLength(value, ruleKey, ruleVal string, customMsgMap map[string]string) string { var ( msg = "" runeArray = gconv.Runes(value) @@ -39,7 +39,7 @@ func checkLength(value, ruleKey, ruleVal string, customMsgMap map[string]string) } } if valueLen < min || valueLen > max { - msg = getErrorMessageByRule(ruleKey, customMsgMap) + msg = v.getErrorMessageByRule(ruleKey, customMsgMap) msg = strings.Replace(msg, ":min", strconv.Itoa(min), -1) msg = strings.Replace(msg, ":max", strconv.Itoa(max), -1) return msg @@ -48,16 +48,23 @@ func checkLength(value, ruleKey, ruleVal string, customMsgMap map[string]string) case "min-length": min, err := strconv.Atoi(ruleVal) if valueLen < min || err != nil { - msg = getErrorMessageByRule(ruleKey, customMsgMap) + msg = v.getErrorMessageByRule(ruleKey, customMsgMap) msg = strings.Replace(msg, ":min", strconv.Itoa(min), -1) } case "max-length": max, err := strconv.Atoi(ruleVal) if valueLen > max || err != nil { - msg = getErrorMessageByRule(ruleKey, customMsgMap) + msg = v.getErrorMessageByRule(ruleKey, customMsgMap) msg = strings.Replace(msg, ":max", strconv.Itoa(max), -1) } + + case "size": + size, err := strconv.Atoi(ruleVal) + if valueLen != size || err != nil { + msg = v.getErrorMessageByRule(ruleKey, customMsgMap) + msg = strings.Replace(msg, ":size", strconv.Itoa(size), -1) + } } return msg } diff --git a/util/gvalid/gvalid_rule_luhn.go b/util/gvalid/gvalid_validator_rule_luhn.go similarity index 74% rename from util/gvalid/gvalid_rule_luhn.go rename to util/gvalid/gvalid_validator_rule_luhn.go index ed8983dae..9d743535d 100644 --- a/util/gvalid/gvalid_rule_luhn.go +++ b/util/gvalid/gvalid_validator_rule_luhn.go @@ -1,4 +1,4 @@ -// Copyright 2018 gf Author(https://github.com/gogf/gf). All Rights Reserved. +// Copyright GoFrame Author(https://goframe.org). All Rights Reserved. // // This Source Code Form is subject to the terms of the MIT License. // If a copy of the MIT was not distributed with this file, @@ -6,9 +6,9 @@ package gvalid -// checkLuHn checks <value> with LUHN algorithm. +// checkLuHn checks `value` with LUHN algorithm. // It's usually used for bank card number validation. -func checkLuHn(value string) bool { +func (v *Validator) checkLuHn(value string) bool { var ( sum = 0 nDigits = len(value) diff --git a/util/gvalid/gvalid_rule_range.go b/util/gvalid/gvalid_validator_rule_range.go similarity index 73% rename from util/gvalid/gvalid_rule_range.go rename to util/gvalid/gvalid_validator_rule_range.go index 1993f2ac8..71627eb53 100644 --- a/util/gvalid/gvalid_rule_range.go +++ b/util/gvalid/gvalid_validator_rule_range.go @@ -1,4 +1,4 @@ -// Copyright 2018 gf Author(https://github.com/gogf/gf). All Rights Reserved. +// Copyright GoFrame Author(https://goframe.org). All Rights Reserved. // // This Source Code Form is subject to the terms of the MIT License. // If a copy of the MIT was not distributed with this file, @@ -11,8 +11,8 @@ import ( "strings" ) -// checkRange checks <value> using range rules. -func checkRange(value, ruleKey, ruleVal string, customMsgMap map[string]string) string { +// checkRange checks `value` using range rules. +func (v *Validator) checkRange(value, ruleKey, ruleVal string, customMsgMap map[string]string) string { msg := "" switch ruleKey { // Value range. @@ -30,9 +30,9 @@ func checkRange(value, ruleKey, ruleVal string, customMsgMap map[string]string) max = v } } - v, err := strconv.ParseFloat(value, 10) - if v < min || v > max || err != nil { - msg = getErrorMessageByRule(ruleKey, customMsgMap) + valueF, err := strconv.ParseFloat(value, 10) + if valueF < min || valueF > max || err != nil { + msg = v.getErrorMessageByRule(ruleKey, customMsgMap) msg = strings.Replace(msg, ":min", strconv.FormatFloat(min, 'f', -1, 64), -1) msg = strings.Replace(msg, ":max", strconv.FormatFloat(max, 'f', -1, 64), -1) } @@ -44,7 +44,7 @@ func checkRange(value, ruleKey, ruleVal string, customMsgMap map[string]string) valueN, err2 = strconv.ParseFloat(value, 10) ) if valueN < min || err1 != nil || err2 != nil { - msg = getErrorMessageByRule(ruleKey, customMsgMap) + msg = v.getErrorMessageByRule(ruleKey, customMsgMap) msg = strings.Replace(msg, ":min", strconv.FormatFloat(min, 'f', -1, 64), -1) } @@ -55,7 +55,7 @@ func checkRange(value, ruleKey, ruleVal string, customMsgMap map[string]string) valueN, err2 = strconv.ParseFloat(value, 10) ) if valueN > max || err1 != nil || err2 != nil { - msg = getErrorMessageByRule(ruleKey, customMsgMap) + msg = v.getErrorMessageByRule(ruleKey, customMsgMap) msg = strings.Replace(msg, ":max", strconv.FormatFloat(max, 'f', -1, 64), -1) } diff --git a/util/gvalid/gvalid_validator_rule_required.go b/util/gvalid/gvalid_validator_rule_required.go new file mode 100644 index 000000000..9e30b704d --- /dev/null +++ b/util/gvalid/gvalid_validator_rule_required.go @@ -0,0 +1,148 @@ +// Copyright GoFrame Author(https://goframe.org). All Rights Reserved. +// +// This Source Code Form is subject to the terms of the MIT License. +// If a copy of the MIT was not distributed with this file, +// You can obtain one at https://github.com/gogf/gf. + +package gvalid + +import ( + "github.com/gogf/gf/internal/empty" + "github.com/gogf/gf/util/gconv" + "github.com/gogf/gf/util/gutil" + "reflect" + "strings" +) + +// checkRequired checks `value` using required rules. +// It also supports require checks for `value` of type: slice, map. +func (v *Validator) checkRequired(value interface{}, ruleKey, rulePattern string, dataMap map[string]interface{}) bool { + required := false + switch ruleKey { + // Required. + case "required": + required = true + + // Required unless all given field and its value are equal. + // Example: required-if: id,1,age,18 + case "required-if": + required = false + var ( + array = strings.Split(rulePattern, ",") + foundValue interface{} + ) + // It supports multiple field and value pairs. + if len(array)%2 == 0 { + for i := 0; i < len(array); { + tk := array[i] + tv := array[i+1] + _, foundValue = gutil.MapPossibleItemByKey(dataMap, tk) + if strings.Compare(tv, gconv.String(foundValue)) == 0 { + required = true + break + } + i += 2 + } + } + + // Required unless all given field and its value are not equal. + // Example: required-unless: id,1,age,18 + case "required-unless": + required = true + var ( + array = strings.Split(rulePattern, ",") + foundValue interface{} + ) + // It supports multiple field and value pairs. + if len(array)%2 == 0 { + for i := 0; i < len(array); { + tk := array[i] + tv := array[i+1] + _, foundValue = gutil.MapPossibleItemByKey(dataMap, tk) + if strings.Compare(tv, gconv.String(foundValue)) == 0 { + required = false + break + } + + i += 2 + } + } + + // Required if any of given fields are not empty. + // Example: required-with:id,name + case "required-with": + required = false + var ( + array = strings.Split(rulePattern, ",") + foundValue interface{} + ) + for i := 0; i < len(array); i++ { + _, foundValue = gutil.MapPossibleItemByKey(dataMap, array[i]) + if !empty.IsEmpty(foundValue) { + required = true + break + } + } + + // Required if all of given fields are not empty. + // Example: required-with:id,name + case "required-with-all": + required = true + var ( + array = strings.Split(rulePattern, ",") + foundValue interface{} + ) + for i := 0; i < len(array); i++ { + _, foundValue = gutil.MapPossibleItemByKey(dataMap, array[i]) + if empty.IsEmpty(foundValue) { + required = false + break + } + } + + // Required if any of given fields are empty. + // Example: required-with:id,name + case "required-without": + required = false + var ( + array = strings.Split(rulePattern, ",") + foundValue interface{} + ) + for i := 0; i < len(array); i++ { + _, foundValue = gutil.MapPossibleItemByKey(dataMap, array[i]) + if empty.IsEmpty(foundValue) { + required = true + break + } + } + + // Required if all of given fields are empty. + // Example: required-with:id,name + case "required-without-all": + required = true + var ( + array = strings.Split(rulePattern, ",") + foundValue interface{} + ) + for i := 0; i < len(array); i++ { + _, foundValue = gutil.MapPossibleItemByKey(dataMap, array[i]) + if !empty.IsEmpty(foundValue) { + required = false + break + } + } + } + if required { + reflectValue := reflect.ValueOf(value) + for reflectValue.Kind() == reflect.Ptr { + reflectValue = reflectValue.Elem() + } + switch reflectValue.Kind() { + case reflect.String, reflect.Map, reflect.Array, reflect.Slice: + return reflectValue.Len() != 0 + } + return gconv.String(value) != "" + } else { + return true + } +} diff --git a/util/gvalid/gvalid_rule_resident_id.go b/util/gvalid/gvalid_validator_rule_resident_id.go similarity index 93% rename from util/gvalid/gvalid_rule_resident_id.go rename to util/gvalid/gvalid_validator_rule_resident_id.go index b43cc952d..8a83e949b 100644 --- a/util/gvalid/gvalid_rule_resident_id.go +++ b/util/gvalid/gvalid_validator_rule_resident_id.go @@ -1,4 +1,4 @@ -// Copyright 2018 gf Author(https://github.com/gogf/gf). All Rights Reserved. +// Copyright GoFrame Author(https://goframe.org). All Rights Reserved. // // This Source Code Form is subject to the terms of the MIT License. // If a copy of the MIT was not distributed with this file, @@ -32,7 +32,7 @@ import ( // // 总: // (^[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}$) -func checkResidentId(id string) bool { +func (v *Validator) checkResidentId(id string) bool { id = strings.ToUpper(strings.TrimSpace(id)) if len(id) != 18 { return false diff --git a/util/gvalid/gvalid_z_example_test.go b/util/gvalid/gvalid_z_example_test.go index fda12dad4..d11fb4e9f 100644 --- a/util/gvalid/gvalid_z_example_test.go +++ b/util/gvalid/gvalid_z_example_test.go @@ -1,4 +1,4 @@ -// Copyright 2020 gf Author(https://github.com/gogf/gf). All Rights Reserved. +// Copyright GoFrame Author(https://goframe.org). All Rights Reserved. // // This Source Code Form is subject to the terms of the MIT License. // If a copy of the MIT was not distributed with this file, @@ -7,14 +7,16 @@ package gvalid_test import ( + "context" "errors" "fmt" + "math" + "reflect" + "github.com/gogf/gf/container/gvar" "github.com/gogf/gf/frame/g" "github.com/gogf/gf/util/gconv" "github.com/gogf/gf/util/gvalid" - "math" - "reflect" ) func ExampleCheckMap() { @@ -28,7 +30,7 @@ func ExampleCheckMap() { "password@required|length:6,16|same:password2#密码不能为空|密码长度应当在:min到:max之间|两次密码输入不相等", "password2@required|length:6,16#", } - if e := gvalid.CheckMap(params, rules); e != nil { + if e := gvalid.CheckMap(context.TODO(), params, rules); e != nil { fmt.Println(e.Map()) fmt.Println(e.FirstItem()) fmt.Println(e.FirstString()) @@ -50,7 +52,7 @@ func ExampleCheckMap2() { "password@required|length:6,16|same:password2#密码不能为空|密码长度应当在:min到:max之间|两次密码输入不相等", "password2@required|length:6,16#", } - if e := gvalid.CheckMap(params, rules); e != nil { + if e := gvalid.CheckMap(context.TODO(), params, rules); e != nil { fmt.Println(e.Map()) fmt.Println(e.FirstItem()) fmt.Println(e.FirstString()) @@ -72,7 +74,7 @@ func ExampleCheckStruct() { Page: 1, Size: 10, } - err := gvalid.CheckStruct(obj, nil) + err := gvalid.CheckStruct(context.TODO(), obj, nil) fmt.Println(err == nil) // Output: // true @@ -89,7 +91,7 @@ func ExampleCheckStruct2() { Page: 1, Size: 10, } - err := gvalid.CheckStruct(obj, nil) + err := gvalid.CheckStruct(context.TODO(), obj, nil) fmt.Println(err == nil) // Output: // true @@ -106,28 +108,13 @@ func ExampleCheckStruct3() { Page: 1, Size: 10, } - err := gvalid.CheckStruct(obj, nil) + err := gvalid.CheckStruct(context.TODO(), obj, nil) fmt.Println(err) // Output: // project id must between 1, 10000 } func ExampleRegisterRule() { - rule := "unique-name" - gvalid.RegisterRule(rule, func(rule string, value interface{}, message string, params map[string]interface{}) error { - var ( - id = gconv.Int(params["Id"]) - name = gconv.String(value) - ) - n, err := g.Table("user").Where("id != ? and name = ?", id, name).Count() - if err != nil { - return err - } - if n > 0 { - return errors.New(message) - } - return nil - }) type User struct { Id int Name string `v:"required|unique-name # 请输入用户名称|用户名称已被占用"` @@ -138,7 +125,23 @@ func ExampleRegisterRule() { Name: "john", Pass: "123456", } - err := gvalid.CheckStruct(user, nil) + + rule := "unique-name" + gvalid.RegisterRule(rule, func(ctx context.Context, rule string, value interface{}, message string, data interface{}) error { + var ( + id = data.(*User).Id + name = gconv.String(value) + ) + n, err := g.Model("user").Where("id != ? and name = ?", id, name).Count() + if err != nil { + return err + } + if n > 0 { + return errors.New(message) + } + return nil + }) + err := gvalid.CheckStruct(context.TODO(), user, nil) fmt.Println(err.Error()) // May Output: // 用户名称已被占用 @@ -146,7 +149,7 @@ func ExampleRegisterRule() { func ExampleRegisterRule_OverwriteRequired() { rule := "required" - gvalid.RegisterRule(rule, func(rule string, value interface{}, message string, params map[string]interface{}) error { + gvalid.RegisterRule(rule, func(ctx context.Context, rule string, value interface{}, message string, data interface{}) error { reflectValue := reflect.ValueOf(value) if reflectValue.Kind() == reflect.Ptr { reflectValue = reflectValue.Elem() @@ -172,18 +175,95 @@ func ExampleRegisterRule_OverwriteRequired() { } return nil }) - fmt.Println(gvalid.Check("", "required", "It's required")) - fmt.Println(gvalid.Check([]string{}, "required", "It's required")) - fmt.Println(gvalid.Check(map[string]int{}, "required", "It's required")) + fmt.Println(gvalid.CheckValue(context.TODO(), "", "required", "It's required")) + fmt.Println(gvalid.CheckValue(context.TODO(), 0, "required", "It's required")) + fmt.Println(gvalid.CheckValue(context.TODO(), false, "required", "It's required")) gvalid.DeleteRule(rule) fmt.Println("rule deleted") - fmt.Println(gvalid.Check("", "required", "It's required")) - fmt.Println(gvalid.Check([]string{}, "required", "It's required")) - fmt.Println(gvalid.Check(map[string]int{}, "required", "It's required")) + fmt.Println(gvalid.CheckValue(context.TODO(), "", "required", "It's required")) + fmt.Println(gvalid.CheckValue(context.TODO(), 0, "required", "It's required")) + fmt.Println(gvalid.CheckValue(context.TODO(), false, "required", "It's required")) // Output: // It's required // It's required // It's required // rule deleted // It's required + // <nil> + // <nil> +} + +func ExampleValidator_Rules() { + data := g.Map{ + "password": "123", + } + err := g.Validator().Data(data).Rules("required-with:password").Messages("请输入确认密码").CheckValue("") + fmt.Println(err.String()) + + // Output: + // 请输入确认密码 +} + +func ExampleValidator_CheckValue() { + err := g.Validator().Rules("min:18").Messages("未成年人不允许注册哟").CheckValue(16) + fmt.Println(err.String()) + + // Output: + // 未成年人不允许注册哟 +} + +func ExampleValidator_CheckMap() { + params := map[string]interface{}{ + "passport": "", + "password": "123456", + "password2": "1234567", + } + rules := map[string]string{ + "passport": "required|length:6,16", + "password": "required|length:6,16|same:password2", + "password2": "required|length:6,16", + } + messages := map[string]interface{}{ + "passport": "账号不能为空|账号长度应当在:min到:max之间", + "password": map[string]string{ + "required": "密码不能为空", + "same": "两次密码输入不相等", + }, + } + err := g.Validator().Messages(messages).Rules(rules).CheckMap(params) + if err != nil { + g.Dump(err.Maps()) + } + + // May Output: + //{ + // "passport": { + // "length": "账号长度应当在6到16之间", + // "required": "账号不能为空" + //}, + // "password": { + // "same": "两次密码输入不相等" + //} + //} +} + +func ExampleValidator_CheckStruct() { + type User struct { + Name string `v:"required#请输入用户姓名"` + Type int `v:"required#请选择用户类型"` + } + data := g.Map{ + "name": "john", + } + user := User{} + if err := gconv.Scan(data, &user); err != nil { + panic(err) + } + err := g.Validator().Data(data).CheckStruct(user) + if err != nil { + fmt.Println(err.Items()) + } + + // Output: + // [map[Type:map[required:请选择用户类型]]] } diff --git a/util/gvalid/gvalid_z_unit_basic_all_test.go b/util/gvalid/gvalid_z_unit_basic_all_test.go new file mode 100755 index 000000000..906d768fa --- /dev/null +++ b/util/gvalid/gvalid_z_unit_basic_all_test.go @@ -0,0 +1,1082 @@ +// Copyright GoFrame Author(https://goframe.org). All Rights Reserved. +// +// This Source Code Form is subject to the terms of the MIT License. +// If a copy of the MIT was not distributed with this file, +// You can obtain one at https://github.com/gogf/gf. + +package gvalid_test + +import ( + "context" + "github.com/gogf/gf/errors/gcode" + "github.com/gogf/gf/errors/gerror" + "github.com/gogf/gf/os/gtime" + "testing" + "time" + + "github.com/gogf/gf/frame/g" + "github.com/gogf/gf/test/gtest" + "github.com/gogf/gf/util/gvalid" +) + +func Test_Check(t *testing.T) { + gtest.C(t, func(t *gtest.T) { + rule := "abc:6,16" + val1 := 0 + val2 := 7 + val3 := 20 + err1 := gvalid.CheckValue(context.TODO(), val1, rule, nil) + err2 := gvalid.CheckValue(context.TODO(), val2, rule, nil) + err3 := gvalid.CheckValue(context.TODO(), val3, rule, nil) + t.Assert(err1, "InvalidRules: abc:6,16") + t.Assert(err2, "InvalidRules: abc:6,16") + t.Assert(err3, "InvalidRules: abc:6,16") + }) +} + +func Test_Required(t *testing.T) { + if m := gvalid.CheckValue(context.TODO(), "1", "required", nil); m != nil { + t.Error(m) + } + if m := gvalid.CheckValue(context.TODO(), "", "required", nil); m == nil { + t.Error(m) + } + if m := gvalid.CheckValue(context.TODO(), "", "required-if: id,1,age,18", nil, map[string]interface{}{"id": 1, "age": 19}); m == nil { + t.Error("Required校验失败") + } + if m := gvalid.CheckValue(context.TODO(), "", "required-if: id,1,age,18", nil, map[string]interface{}{"id": 2, "age": 19}); m != nil { + t.Error("Required校验失败") + } +} + +func Test_RequiredIf(t *testing.T) { + gtest.C(t, func(t *gtest.T) { + rule := "required-if:id,1,age,18" + t.AssertNE(gvalid.CheckValue(context.TODO(), "", rule, nil, g.Map{"id": 1}), nil) + t.Assert(gvalid.CheckValue(context.TODO(), "", rule, nil, g.Map{"id": 0}), nil) + t.AssertNE(gvalid.CheckValue(context.TODO(), "", rule, nil, g.Map{"age": 18}), nil) + t.Assert(gvalid.CheckValue(context.TODO(), "", rule, nil, g.Map{"age": 20}), nil) + }) +} + +func Test_RequiredUnless(t *testing.T) { + gtest.C(t, func(t *gtest.T) { + rule := "required-unless:id,1,age,18" + t.Assert(gvalid.CheckValue(context.TODO(), "", rule, nil, g.Map{"id": 1}), nil) + t.AssertNE(gvalid.CheckValue(context.TODO(), "", rule, nil, g.Map{"id": 0}), nil) + t.Assert(gvalid.CheckValue(context.TODO(), "", rule, nil, g.Map{"age": 18}), nil) + t.AssertNE(gvalid.CheckValue(context.TODO(), "", rule, nil, g.Map{"age": 20}), nil) + }) +} + +func Test_RequiredWith(t *testing.T) { + gtest.C(t, func(t *gtest.T) { + rule := "required-with:id,name" + val1 := "" + params1 := g.Map{ + "age": 18, + } + params2 := g.Map{ + "id": 100, + } + params3 := g.Map{ + "id": 100, + "name": "john", + } + err1 := gvalid.CheckValue(context.TODO(), val1, rule, nil, params1) + err2 := gvalid.CheckValue(context.TODO(), val1, rule, nil, params2) + err3 := gvalid.CheckValue(context.TODO(), val1, rule, nil, params3) + t.Assert(err1, nil) + t.AssertNE(err2, nil) + t.AssertNE(err3, nil) + }) + // time.Time + gtest.C(t, func(t *gtest.T) { + rule := "required-with:id,time" + val1 := "" + params1 := g.Map{ + "age": 18, + } + params2 := g.Map{ + "id": 100, + } + params3 := g.Map{ + "time": time.Time{}, + } + err1 := gvalid.CheckValue(context.TODO(), val1, rule, nil, params1) + err2 := gvalid.CheckValue(context.TODO(), val1, rule, nil, params2) + err3 := gvalid.CheckValue(context.TODO(), val1, rule, nil, params3) + t.Assert(err1, nil) + t.AssertNE(err2, nil) + t.Assert(err3, nil) + }) + gtest.C(t, func(t *gtest.T) { + rule := "required-with:id,time" + val1 := "" + params1 := g.Map{ + "age": 18, + } + params2 := g.Map{ + "id": 100, + } + params3 := g.Map{ + "time": time.Now(), + } + err1 := gvalid.CheckValue(context.TODO(), val1, rule, nil, params1) + err2 := gvalid.CheckValue(context.TODO(), val1, rule, nil, params2) + err3 := gvalid.CheckValue(context.TODO(), val1, rule, nil, params3) + t.Assert(err1, nil) + t.AssertNE(err2, nil) + t.AssertNE(err3, nil) + }) + // gtime.Time + gtest.C(t, func(t *gtest.T) { + type UserApiSearch struct { + Uid int64 `json:"uid"` + Nickname string `json:"nickname" v:"required-with:Uid"` + StartTime *gtime.Time `json:"start_time" v:"required-with:EndTime"` + EndTime *gtime.Time `json:"end_time" v:"required-with:StartTime"` + } + data := UserApiSearch{ + StartTime: nil, + EndTime: nil, + } + t.Assert(gvalid.CheckStruct(context.TODO(), data, nil), nil) + }) + gtest.C(t, func(t *gtest.T) { + type UserApiSearch struct { + Uid int64 `json:"uid"` + Nickname string `json:"nickname" v:"required-with:Uid"` + StartTime *gtime.Time `json:"start_time" v:"required-with:EndTime"` + EndTime *gtime.Time `json:"end_time" v:"required-with:StartTime"` + } + data := UserApiSearch{ + StartTime: nil, + EndTime: gtime.Now(), + } + t.AssertNE(gvalid.CheckStruct(context.TODO(), data, nil), nil) + }) +} + +func Test_RequiredWithAll(t *testing.T) { + gtest.C(t, func(t *gtest.T) { + rule := "required-with-all:id,name" + val1 := "" + params1 := g.Map{ + "age": 18, + } + params2 := g.Map{ + "id": 100, + } + params3 := g.Map{ + "id": 100, + "name": "john", + } + err1 := gvalid.CheckValue(context.TODO(), val1, rule, nil, params1) + err2 := gvalid.CheckValue(context.TODO(), val1, rule, nil, params2) + err3 := gvalid.CheckValue(context.TODO(), val1, rule, nil, params3) + t.Assert(err1, nil) + t.Assert(err2, nil) + t.AssertNE(err3, nil) + }) +} + +func Test_RequiredWithOut(t *testing.T) { + gtest.C(t, func(t *gtest.T) { + rule := "required-without:id,name" + val1 := "" + params1 := g.Map{ + "age": 18, + } + params2 := g.Map{ + "id": 100, + } + params3 := g.Map{ + "id": 100, + "name": "john", + } + err1 := gvalid.CheckValue(context.TODO(), val1, rule, nil, params1) + err2 := gvalid.CheckValue(context.TODO(), val1, rule, nil, params2) + err3 := gvalid.CheckValue(context.TODO(), val1, rule, nil, params3) + t.AssertNE(err1, nil) + t.AssertNE(err2, nil) + t.Assert(err3, nil) + }) +} + +func Test_RequiredWithOutAll(t *testing.T) { + gtest.C(t, func(t *gtest.T) { + rule := "required-without-all:id,name" + val1 := "" + params1 := g.Map{ + "age": 18, + } + params2 := g.Map{ + "id": 100, + } + params3 := g.Map{ + "id": 100, + "name": "john", + } + err1 := gvalid.CheckValue(context.TODO(), val1, rule, nil, params1) + err2 := gvalid.CheckValue(context.TODO(), val1, rule, nil, params2) + err3 := gvalid.CheckValue(context.TODO(), val1, rule, nil, params3) + t.AssertNE(err1, nil) + t.Assert(err2, nil) + t.Assert(err3, nil) + }) +} + +func Test_Date(t *testing.T) { + gtest.C(t, func(t *gtest.T) { + rule := "date" + val1 := "2010" + val2 := "201011" + val3 := "20101101" + val4 := "2010-11-01" + val5 := "2010.11.01" + val6 := "2010/11/01" + val7 := "2010=11=01" + val8 := "123" + err1 := gvalid.CheckValue(context.TODO(), val1, rule, nil) + err2 := gvalid.CheckValue(context.TODO(), val2, rule, nil) + err3 := gvalid.CheckValue(context.TODO(), val3, rule, nil) + err4 := gvalid.CheckValue(context.TODO(), val4, rule, nil) + err5 := gvalid.CheckValue(context.TODO(), val5, rule, nil) + err6 := gvalid.CheckValue(context.TODO(), val6, rule, nil) + err7 := gvalid.CheckValue(context.TODO(), val7, rule, nil) + err8 := gvalid.CheckValue(context.TODO(), val8, rule, nil) + t.AssertNE(err1, nil) + t.AssertNE(err2, nil) + t.Assert(err3, nil) + t.Assert(err4, nil) + t.Assert(err5, nil) + t.Assert(err6, nil) + t.AssertNE(err7, nil) + t.AssertNE(err8, nil) + }) +} + +func Test_DateFormat(t *testing.T) { + gtest.C(t, func(t *gtest.T) { + val1 := "2010" + val2 := "201011" + val3 := "2010.11" + val4 := "201011-01" + val5 := "2010~11~01" + val6 := "2010-11~01" + err1 := gvalid.CheckValue(context.TODO(), val1, "date-format:Y", nil) + err2 := gvalid.CheckValue(context.TODO(), val2, "date-format:Ym", nil) + err3 := gvalid.CheckValue(context.TODO(), val3, "date-format:Y.m", nil) + err4 := gvalid.CheckValue(context.TODO(), val4, "date-format:Ym-d", nil) + err5 := gvalid.CheckValue(context.TODO(), val5, "date-format:Y~m~d", nil) + err6 := gvalid.CheckValue(context.TODO(), val6, "date-format:Y~m~d", nil) + t.Assert(err1, nil) + t.Assert(err2, nil) + t.Assert(err3, nil) + t.Assert(err4, nil) + t.Assert(err5, nil) + t.AssertNE(err6, nil) + }) + gtest.C(t, func(t *gtest.T) { + t1 := gtime.Now() + t2 := time.Time{} + err1 := gvalid.CheckValue(context.TODO(), t1, "date-format:Y", nil) + err2 := gvalid.CheckValue(context.TODO(), t2, "date-format:Y", nil) + t.Assert(err1, nil) + t.AssertNE(err2, nil) + }) +} + +func Test_Email(t *testing.T) { + gtest.C(t, func(t *gtest.T) { + rule := "email" + value1 := "m@johngcn" + value2 := "m@www@johngcn" + value3 := "m-m_m@mail.johng.cn" + value4 := "m.m-m@johng.cn" + err1 := gvalid.CheckValue(context.TODO(), value1, rule, nil) + err2 := gvalid.CheckValue(context.TODO(), value2, rule, nil) + err3 := gvalid.CheckValue(context.TODO(), value3, rule, nil) + err4 := gvalid.CheckValue(context.TODO(), value4, rule, nil) + t.AssertNE(err1, nil) + t.AssertNE(err2, nil) + t.Assert(err3, nil) + t.Assert(err4, nil) + }) +} + +func Test_Phone(t *testing.T) { + gtest.C(t, func(t *gtest.T) { + err1 := gvalid.CheckValue(context.TODO(), "1361990897", "phone", nil) + err2 := gvalid.CheckValue(context.TODO(), "13619908979", "phone", nil) + err3 := gvalid.CheckValue(context.TODO(), "16719908979", "phone", nil) + err4 := gvalid.CheckValue(context.TODO(), "19719908989", "phone", nil) + t.AssertNE(err1.String(), nil) + t.Assert(err2, nil) + t.Assert(err3, nil) + t.Assert(err4, nil) + }) +} + +func Test_PhoneLoose(t *testing.T) { + gtest.C(t, func(t *gtest.T) { + err1 := gvalid.CheckValue(context.TODO(), "13333333333", "phone-loose", nil) + err2 := gvalid.CheckValue(context.TODO(), "15555555555", "phone-loose", nil) + err3 := gvalid.CheckValue(context.TODO(), "16666666666", "phone-loose", nil) + err4 := gvalid.CheckValue(context.TODO(), "23333333333", "phone-loose", nil) + err5 := gvalid.CheckValue(context.TODO(), "1333333333", "phone-loose", nil) + err6 := gvalid.CheckValue(context.TODO(), "10333333333", "phone-loose", nil) + t.Assert(err1, nil) + t.Assert(err2, nil) + t.Assert(err3, nil) + t.AssertNE(err4, nil) + t.AssertNE(err5, nil) + t.AssertNE(err6, nil) + }) +} +func Test_Telephone(t *testing.T) { + gtest.C(t, func(t *gtest.T) { + rule := "telephone" + val1 := "869265" + val2 := "028-869265" + val3 := "86292651" + val4 := "028-8692651" + val5 := "0830-8692651" + err1 := gvalid.CheckValue(context.TODO(), val1, rule, nil) + err2 := gvalid.CheckValue(context.TODO(), val2, rule, nil) + err3 := gvalid.CheckValue(context.TODO(), val3, rule, nil) + err4 := gvalid.CheckValue(context.TODO(), val4, rule, nil) + err5 := gvalid.CheckValue(context.TODO(), val5, rule, nil) + t.AssertNE(err1, nil) + t.AssertNE(err2, nil) + t.Assert(err3, nil) + t.Assert(err4, nil) + t.Assert(err5, nil) + }) +} + +func Test_Passport(t *testing.T) { + gtest.C(t, func(t *gtest.T) { + rule := "passport" + val1 := "123456" + val2 := "a12345-6" + val3 := "aaaaa" + val4 := "aaaaaa" + val5 := "a123_456" + err1 := gvalid.CheckValue(context.TODO(), val1, rule, nil) + err2 := gvalid.CheckValue(context.TODO(), val2, rule, nil) + err3 := gvalid.CheckValue(context.TODO(), val3, rule, nil) + err4 := gvalid.CheckValue(context.TODO(), val4, rule, nil) + err5 := gvalid.CheckValue(context.TODO(), val5, rule, nil) + t.AssertNE(err1, nil) + t.AssertNE(err2, nil) + t.AssertNE(err3, nil) + t.Assert(err4, nil) + t.Assert(err5, nil) + }) +} + +func Test_Password(t *testing.T) { + gtest.C(t, func(t *gtest.T) { + rule := "password" + val1 := "12345" + val2 := "aaaaa" + val3 := "a12345-6" + val4 := ">,/;'[09-" + val5 := "a123_456" + err1 := gvalid.CheckValue(context.TODO(), val1, rule, nil) + err2 := gvalid.CheckValue(context.TODO(), val2, rule, nil) + err3 := gvalid.CheckValue(context.TODO(), val3, rule, nil) + err4 := gvalid.CheckValue(context.TODO(), val4, rule, nil) + err5 := gvalid.CheckValue(context.TODO(), val5, rule, nil) + t.AssertNE(err1, nil) + t.AssertNE(err2, nil) + t.Assert(err3, nil) + t.Assert(err4, nil) + t.Assert(err5, nil) + }) +} + +func Test_Password2(t *testing.T) { + gtest.C(t, func(t *gtest.T) { + rule := "password2" + val1 := "12345" + val2 := "Naaaa" + val3 := "a12345-6" + val4 := ">,/;'[09-" + val5 := "a123_456" + val6 := "Nant1986" + val7 := "Nant1986!" + err1 := gvalid.CheckValue(context.TODO(), val1, rule, nil) + err2 := gvalid.CheckValue(context.TODO(), val2, rule, nil) + err3 := gvalid.CheckValue(context.TODO(), val3, rule, nil) + err4 := gvalid.CheckValue(context.TODO(), val4, rule, nil) + err5 := gvalid.CheckValue(context.TODO(), val5, rule, nil) + err6 := gvalid.CheckValue(context.TODO(), val6, rule, nil) + err7 := gvalid.CheckValue(context.TODO(), val7, rule, nil) + t.AssertNE(err1, nil) + t.AssertNE(err2, nil) + t.AssertNE(err3, nil) + t.AssertNE(err4, nil) + t.AssertNE(err5, nil) + t.Assert(err6, nil) + t.Assert(err7, nil) + }) +} + +func Test_Password3(t *testing.T) { + gtest.C(t, func(t *gtest.T) { + rule := "password3" + val1 := "12345" + val2 := "Naaaa" + val3 := "a12345-6" + val4 := ">,/;'[09-" + val5 := "a123_456" + val6 := "Nant1986" + val7 := "Nant1986!" + err1 := gvalid.CheckValue(context.TODO(), val1, rule, nil) + err2 := gvalid.CheckValue(context.TODO(), val2, rule, nil) + err3 := gvalid.CheckValue(context.TODO(), val3, rule, nil) + err4 := gvalid.CheckValue(context.TODO(), val4, rule, nil) + err5 := gvalid.CheckValue(context.TODO(), val5, rule, nil) + err6 := gvalid.CheckValue(context.TODO(), val6, rule, nil) + err7 := gvalid.CheckValue(context.TODO(), val7, rule, nil) + t.AssertNE(err1, nil) + t.AssertNE(err2, nil) + t.AssertNE(err3, nil) + t.AssertNE(err4, nil) + t.AssertNE(err5, nil) + t.AssertNE(err6, nil) + t.Assert(err7, nil) + }) +} + +func Test_Postcode(t *testing.T) { + gtest.C(t, func(t *gtest.T) { + rule := "postcode" + val1 := "12345" + val2 := "610036" + err1 := gvalid.CheckValue(context.TODO(), val1, rule, nil) + err2 := gvalid.CheckValue(context.TODO(), val2, rule, nil) + t.AssertNE(err1, nil) + t.Assert(err2, nil) + }) +} + +func Test_ResidentId(t *testing.T) { + gtest.C(t, func(t *gtest.T) { + rule := "resident-id" + val1 := "11111111111111" + val2 := "1111111111111111" + val3 := "311128500121201" + val4 := "510521198607185367" + val5 := "51052119860718536x" + err1 := gvalid.CheckValue(context.TODO(), val1, rule, nil) + err2 := gvalid.CheckValue(context.TODO(), val2, rule, nil) + err3 := gvalid.CheckValue(context.TODO(), val3, rule, nil) + err4 := gvalid.CheckValue(context.TODO(), val4, rule, nil) + err5 := gvalid.CheckValue(context.TODO(), val5, rule, nil) + t.AssertNE(err1, nil) + t.AssertNE(err2, nil) + t.AssertNE(err3, nil) + t.AssertNE(err4, nil) + t.Assert(err5, nil) + }) +} + +func Test_BankCard(t *testing.T) { + gtest.C(t, func(t *gtest.T) { + rule := "bank-card" + val1 := "6230514630000424470" + val2 := "6230514630000424473" + err1 := gvalid.CheckValue(context.TODO(), val1, rule, nil) + err2 := gvalid.CheckValue(context.TODO(), val2, rule, nil) + t.AssertNE(err1, nil) + t.Assert(err2, nil) + }) +} + +func Test_QQ(t *testing.T) { + gtest.C(t, func(t *gtest.T) { + rule := "qq" + val1 := "100" + val2 := "1" + val3 := "10000" + val4 := "38996181" + val5 := "389961817" + err1 := gvalid.CheckValue(context.TODO(), val1, rule, nil) + err2 := gvalid.CheckValue(context.TODO(), val2, rule, nil) + err3 := gvalid.CheckValue(context.TODO(), val3, rule, nil) + err4 := gvalid.CheckValue(context.TODO(), val4, rule, nil) + err5 := gvalid.CheckValue(context.TODO(), val5, rule, nil) + t.AssertNE(err1, nil) + t.AssertNE(err2, nil) + t.Assert(err3, nil) + t.Assert(err4, nil) + t.Assert(err5, nil) + }) +} + +func Test_Ip(t *testing.T) { + if m := gvalid.CheckValue(context.TODO(), "10.0.0.1", "ip", nil); m != nil { + t.Error(m) + } + if m := gvalid.CheckValue(context.TODO(), "10.0.0.1", "ipv4", nil); m != nil { + t.Error(m) + } + if m := gvalid.CheckValue(context.TODO(), "0.0.0.0", "ipv4", nil); m != nil { + t.Error(m) + } + if m := gvalid.CheckValue(context.TODO(), "1920.0.0.0", "ipv4", nil); m == nil { + t.Error("ipv4校验失败") + } + if m := gvalid.CheckValue(context.TODO(), "1920.0.0.0", "ip", nil); m == nil { + t.Error("ipv4校验失败") + } + if m := gvalid.CheckValue(context.TODO(), "fe80::5484:7aff:fefe:9799", "ipv6", nil); m != nil { + t.Error(m) + } + if m := gvalid.CheckValue(context.TODO(), "fe80::5484:7aff:fefe:9799123", "ipv6", nil); m == nil { + t.Error(m) + } + if m := gvalid.CheckValue(context.TODO(), "fe80::5484:7aff:fefe:9799", "ip", nil); m != nil { + t.Error(m) + } + if m := gvalid.CheckValue(context.TODO(), "fe80::5484:7aff:fefe:9799123", "ip", nil); m == nil { + t.Error(m) + } +} + +func Test_IPv4(t *testing.T) { + gtest.C(t, func(t *gtest.T) { + rule := "ipv4" + val1 := "0.0.0" + val2 := "0.0.0.0" + val3 := "1.1.1.1" + val4 := "255.255.255.0" + val5 := "127.0.0.1" + err1 := gvalid.CheckValue(context.TODO(), val1, rule, nil) + err2 := gvalid.CheckValue(context.TODO(), val2, rule, nil) + err3 := gvalid.CheckValue(context.TODO(), val3, rule, nil) + err4 := gvalid.CheckValue(context.TODO(), val4, rule, nil) + err5 := gvalid.CheckValue(context.TODO(), val5, rule, nil) + t.AssertNE(err1, nil) + t.Assert(err2, nil) + t.Assert(err3, nil) + t.Assert(err4, nil) + t.Assert(err5, nil) + }) +} + +func Test_IPv6(t *testing.T) { + gtest.C(t, func(t *gtest.T) { + rule := "ipv6" + val1 := "192.168.1.1" + val2 := "CDCD:910A:2222:5498:8475:1111:3900:2020" + val3 := "1030::C9B4:FF12:48AA:1A2B" + val4 := "2000:0:0:0:0:0:0:1" + val5 := "0000:0000:0000:0000:0000:ffff:c0a8:5909" + err1 := gvalid.CheckValue(context.TODO(), val1, rule, nil) + err2 := gvalid.CheckValue(context.TODO(), val2, rule, nil) + err3 := gvalid.CheckValue(context.TODO(), val3, rule, nil) + err4 := gvalid.CheckValue(context.TODO(), val4, rule, nil) + err5 := gvalid.CheckValue(context.TODO(), val5, rule, nil) + t.AssertNE(err1, nil) + t.Assert(err2, nil) + t.Assert(err3, nil) + t.Assert(err4, nil) + t.Assert(err5, nil) + }) +} + +func Test_MAC(t *testing.T) { + gtest.C(t, func(t *gtest.T) { + rule := "mac" + val1 := "192.168.1.1" + val2 := "44-45-53-54-00-00" + val3 := "01:00:5e:00:00:00" + err1 := gvalid.CheckValue(context.TODO(), val1, rule, nil) + err2 := gvalid.CheckValue(context.TODO(), val2, rule, nil) + err3 := gvalid.CheckValue(context.TODO(), val3, rule, nil) + t.AssertNE(err1, nil) + t.Assert(err2, nil) + t.Assert(err3, nil) + }) +} + +func Test_URL(t *testing.T) { + gtest.C(t, func(t *gtest.T) { + rule := "url" + val1 := "127.0.0.1" + val2 := "https://www.baidu.com" + val3 := "http://127.0.0.1" + val4 := "file:///tmp/test.txt" + err1 := gvalid.CheckValue(context.TODO(), val1, rule, nil) + err2 := gvalid.CheckValue(context.TODO(), val2, rule, nil) + err3 := gvalid.CheckValue(context.TODO(), val3, rule, nil) + err4 := gvalid.CheckValue(context.TODO(), val4, rule, nil) + t.AssertNE(err1, nil) + t.Assert(err2, nil) + t.Assert(err3, nil) + t.Assert(err4, nil) + }) +} + +func Test_Domain(t *testing.T) { + gtest.C(t, func(t *gtest.T) { + m := g.MapStrBool{ + "localhost": false, + "baidu.com": true, + "www.baidu.com": true, + "jn.np": true, + "www.jn.np": true, + "w.www.jn.np": true, + "127.0.0.1": false, + "www.360.com": true, + "www.360": false, + "360": false, + "my-gf": false, + "my-gf.com": true, + "my-gf.360.com": true, + } + var err error + for k, v := range m { + err = gvalid.CheckValue(context.TODO(), k, "domain", nil) + if v { + //fmt.Println(k) + t.Assert(err, nil) + } else { + //fmt.Println(k) + t.AssertNE(err, nil) + } + } + }) +} + +func Test_Length(t *testing.T) { + rule := "length:6,16" + if m := gvalid.CheckValue(context.TODO(), "123456", rule, nil); m != nil { + t.Error(m) + } + if m := gvalid.CheckValue(context.TODO(), "12345", rule, nil); m == nil { + t.Error("长度校验失败") + } +} + +func Test_MinLength(t *testing.T) { + rule := "min-length:6" + msgs := map[string]string{ + "min-length": "地址长度至少为:min位", + } + if m := gvalid.CheckValue(context.TODO(), "123456", rule, nil); m != nil { + t.Error(m) + } + if m := gvalid.CheckValue(context.TODO(), "12345", rule, nil); m == nil { + t.Error("长度校验失败") + } + if m := gvalid.CheckValue(context.TODO(), "12345", rule, msgs); m == nil { + t.Error("长度校验失败") + } + + rule2 := "min-length:abc" + if m := gvalid.CheckValue(context.TODO(), "123456", rule2, nil); m == nil { + t.Error("长度校验失败") + } +} + +func Test_MaxLength(t *testing.T) { + rule := "max-length:6" + msgs := map[string]string{ + "max-length": "地址长度至大为:max位", + } + if m := gvalid.CheckValue(context.TODO(), "12345", rule, nil); m != nil { + t.Error(m) + } + if m := gvalid.CheckValue(context.TODO(), "1234567", rule, nil); m == nil { + t.Error("长度校验失败") + } + if m := gvalid.CheckValue(context.TODO(), "1234567", rule, msgs); m == nil { + t.Error("长度校验失败") + } + + rule2 := "max-length:abc" + if m := gvalid.CheckValue(context.TODO(), "123456", rule2, nil); m == nil { + t.Error("长度校验失败") + } +} + +func Test_Size(t *testing.T) { + rule := "size:5" + if m := gvalid.CheckValue(context.TODO(), "12345", rule, nil); m != nil { + t.Error(m) + } + if m := gvalid.CheckValue(context.TODO(), "123456", rule, nil); m == nil { + t.Error("长度校验失败") + } +} + +func Test_Between(t *testing.T) { + rule := "between:6.01, 10.01" + if m := gvalid.CheckValue(context.TODO(), 10, rule, nil); m != nil { + t.Error(m) + } + if m := gvalid.CheckValue(context.TODO(), 10.02, rule, nil); m == nil { + t.Error("大小范围校验失败") + } + if m := gvalid.CheckValue(context.TODO(), "a", rule, nil); m == nil { + t.Error("大小范围校验失败") + } +} + +func Test_Min(t *testing.T) { + gtest.C(t, func(t *gtest.T) { + rule := "min:100" + val1 := "1" + val2 := "99" + val3 := "100" + val4 := "1000" + val5 := "a" + err1 := gvalid.CheckValue(context.TODO(), val1, rule, nil) + err2 := gvalid.CheckValue(context.TODO(), val2, rule, nil) + err3 := gvalid.CheckValue(context.TODO(), val3, rule, nil) + err4 := gvalid.CheckValue(context.TODO(), val4, rule, nil) + err5 := gvalid.CheckValue(context.TODO(), val5, rule, nil) + t.AssertNE(err1, nil) + t.AssertNE(err2, nil) + t.Assert(err3, nil) + t.Assert(err4, nil) + t.AssertNE(err5, nil) + + rule2 := "min:a" + err6 := gvalid.CheckValue(context.TODO(), val1, rule2, nil) + t.AssertNE(err6, nil) + }) +} + +func Test_Max(t *testing.T) { + gtest.C(t, func(t *gtest.T) { + rule := "max:100" + val1 := "1" + val2 := "99" + val3 := "100" + val4 := "1000" + val5 := "a" + err1 := gvalid.CheckValue(context.TODO(), val1, rule, nil) + err2 := gvalid.CheckValue(context.TODO(), val2, rule, nil) + err3 := gvalid.CheckValue(context.TODO(), val3, rule, nil) + err4 := gvalid.CheckValue(context.TODO(), val4, rule, nil) + err5 := gvalid.CheckValue(context.TODO(), val5, rule, nil) + t.Assert(err1, nil) + t.Assert(err2, nil) + t.Assert(err3, nil) + t.AssertNE(err4, nil) + t.AssertNE(err5, nil) + + rule2 := "max:a" + err6 := gvalid.CheckValue(context.TODO(), val1, rule2, nil) + t.AssertNE(err6, nil) + }) +} + +func Test_Json(t *testing.T) { + gtest.C(t, func(t *gtest.T) { + rule := "json" + val1 := "" + val2 := "." + val3 := "{}" + val4 := "[]" + val5 := "[1,2,3,4]" + val6 := `{"list":[1,2,3,4]}` + err1 := gvalid.CheckValue(context.TODO(), val1, rule, nil) + err2 := gvalid.CheckValue(context.TODO(), val2, rule, nil) + err3 := gvalid.CheckValue(context.TODO(), val3, rule, nil) + err4 := gvalid.CheckValue(context.TODO(), val4, rule, nil) + err5 := gvalid.CheckValue(context.TODO(), val5, rule, nil) + err6 := gvalid.CheckValue(context.TODO(), val6, rule, nil) + t.AssertNE(err1, nil) + t.AssertNE(err2, nil) + t.Assert(err3, nil) + t.Assert(err4, nil) + t.Assert(err5, nil) + t.Assert(err6, nil) + }) +} + +func Test_Integer(t *testing.T) { + gtest.C(t, func(t *gtest.T) { + rule := "integer" + val1 := "" + val2 := "1.0" + val3 := "001" + val4 := "1" + val5 := "100" + val6 := `999999999` + err1 := gvalid.CheckValue(context.TODO(), val1, rule, nil) + err2 := gvalid.CheckValue(context.TODO(), val2, rule, nil) + err3 := gvalid.CheckValue(context.TODO(), val3, rule, nil) + err4 := gvalid.CheckValue(context.TODO(), val4, rule, nil) + err5 := gvalid.CheckValue(context.TODO(), val5, rule, nil) + err6 := gvalid.CheckValue(context.TODO(), val6, rule, nil) + t.AssertNE(err1, nil) + t.AssertNE(err2, nil) + t.Assert(err3, nil) + t.Assert(err4, nil) + t.Assert(err5, nil) + t.Assert(err6, nil) + }) +} + +func Test_Float(t *testing.T) { + gtest.C(t, func(t *gtest.T) { + rule := "float" + val1 := "" + val2 := "a" + val3 := "1" + val4 := "1.0" + val5 := "1.1" + val6 := `0.1` + err1 := gvalid.CheckValue(context.TODO(), val1, rule, nil) + err2 := gvalid.CheckValue(context.TODO(), val2, rule, nil) + err3 := gvalid.CheckValue(context.TODO(), val3, rule, nil) + err4 := gvalid.CheckValue(context.TODO(), val4, rule, nil) + err5 := gvalid.CheckValue(context.TODO(), val5, rule, nil) + err6 := gvalid.CheckValue(context.TODO(), val6, rule, nil) + t.AssertNE(err1, nil) + t.AssertNE(err2, nil) + t.Assert(err3, nil) + t.Assert(err4, nil) + t.Assert(err5, nil) + t.Assert(err6, nil) + }) +} + +func Test_Boolean(t *testing.T) { + gtest.C(t, func(t *gtest.T) { + rule := "boolean" + val1 := "a" + val2 := "-" + val3 := "" + val4 := "1" + val5 := "true" + val6 := `off` + err1 := gvalid.CheckValue(context.TODO(), val1, rule, nil) + err2 := gvalid.CheckValue(context.TODO(), val2, rule, nil) + err3 := gvalid.CheckValue(context.TODO(), val3, rule, nil) + err4 := gvalid.CheckValue(context.TODO(), val4, rule, nil) + err5 := gvalid.CheckValue(context.TODO(), val5, rule, nil) + err6 := gvalid.CheckValue(context.TODO(), val6, rule, nil) + t.AssertNE(err1, nil) + t.AssertNE(err2, nil) + t.Assert(err3, nil) + t.Assert(err4, nil) + t.Assert(err5, nil) + t.Assert(err6, nil) + }) +} + +func Test_Same(t *testing.T) { + gtest.C(t, func(t *gtest.T) { + rule := "same:id" + val1 := "100" + params1 := g.Map{ + "age": 18, + } + params2 := g.Map{ + "id": 100, + } + params3 := g.Map{ + "id": 100, + "name": "john", + } + err1 := gvalid.CheckValue(context.TODO(), val1, rule, nil, params1) + err2 := gvalid.CheckValue(context.TODO(), val1, rule, nil, params2) + err3 := gvalid.CheckValue(context.TODO(), val1, rule, nil, params3) + t.AssertNE(err1, nil) + t.Assert(err2, nil) + t.Assert(err3, nil) + }) +} + +func Test_Different(t *testing.T) { + gtest.C(t, func(t *gtest.T) { + rule := "different:id" + val1 := "100" + params1 := g.Map{ + "age": 18, + } + params2 := g.Map{ + "id": 100, + } + params3 := g.Map{ + "id": 100, + "name": "john", + } + err1 := gvalid.CheckValue(context.TODO(), val1, rule, nil, params1) + err2 := gvalid.CheckValue(context.TODO(), val1, rule, nil, params2) + err3 := gvalid.CheckValue(context.TODO(), val1, rule, nil, params3) + t.Assert(err1, nil) + t.AssertNE(err2, nil) + t.AssertNE(err3, nil) + }) +} + +func Test_In(t *testing.T) { + gtest.C(t, func(t *gtest.T) { + rule := "in:100,200" + val1 := "" + val2 := "1" + val3 := "100" + val4 := "200" + err1 := gvalid.CheckValue(context.TODO(), val1, rule, nil) + err2 := gvalid.CheckValue(context.TODO(), val2, rule, nil) + err3 := gvalid.CheckValue(context.TODO(), val3, rule, nil) + err4 := gvalid.CheckValue(context.TODO(), val4, rule, nil) + t.AssertNE(err1, nil) + t.AssertNE(err2, nil) + t.Assert(err3, nil) + t.Assert(err4, nil) + }) +} + +func Test_NotIn(t *testing.T) { + gtest.C(t, func(t *gtest.T) { + rule := "not-in:100" + val1 := "" + val2 := "1" + val3 := "100" + val4 := "200" + err1 := gvalid.CheckValue(context.TODO(), val1, rule, nil) + err2 := gvalid.CheckValue(context.TODO(), val2, rule, nil) + err3 := gvalid.CheckValue(context.TODO(), val3, rule, nil) + err4 := gvalid.CheckValue(context.TODO(), val4, rule, nil) + t.Assert(err1, nil) + t.Assert(err2, nil) + t.AssertNE(err3, nil) + t.Assert(err4, nil) + }) + gtest.C(t, func(t *gtest.T) { + rule := "not-in:100,200" + val1 := "" + val2 := "1" + val3 := "100" + val4 := "200" + err1 := gvalid.CheckValue(context.TODO(), val1, rule, nil) + err2 := gvalid.CheckValue(context.TODO(), val2, rule, nil) + err3 := gvalid.CheckValue(context.TODO(), val3, rule, nil) + err4 := gvalid.CheckValue(context.TODO(), val4, rule, nil) + t.Assert(err1, nil) + t.Assert(err2, nil) + t.AssertNE(err3, nil) + t.AssertNE(err4, nil) + }) +} + +func Test_Regex1(t *testing.T) { + rule := `regex:\d{6}|\D{6}|length:6,16` + if m := gvalid.CheckValue(context.TODO(), "123456", rule, nil); m != nil { + t.Error(m) + } + if m := gvalid.CheckValue(context.TODO(), "abcde6", rule, nil); m == nil { + t.Error("校验失败") + } +} + +func Test_Regex2(t *testing.T) { + gtest.C(t, func(t *gtest.T) { + rule := `required|min-length:6|regex:^data:image\/(jpeg|png);base64,` + str1 := "" + str2 := "data" + str3 := "data:image/jpeg;base64,/9jrbattq22r" + err1 := gvalid.CheckValue(context.TODO(), str1, rule, nil) + err2 := gvalid.CheckValue(context.TODO(), str2, rule, nil) + err3 := gvalid.CheckValue(context.TODO(), str3, rule, nil) + t.AssertNE(err1, nil) + t.AssertNE(err2, nil) + t.Assert(err3, nil) + + t.AssertNE(err1.Map()["required"], nil) + t.AssertNE(err2.Map()["min-length"], nil) + }) +} + +// issue: https://github.com/gogf/gf/issues/1077 +func Test_InternalError_String(t *testing.T) { + gtest.C(t, func(t *gtest.T) { + type a struct { + Name string `v:"hh"` + } + aa := a{Name: "2"} + err := gvalid.CheckStruct(context.TODO(), &aa, nil) + + t.Assert(err.String(), "InvalidRules: hh") + t.Assert(err.Strings(), g.Slice{"InvalidRules: hh"}) + t.Assert(err.FirstString(), "InvalidRules: hh") + t.Assert(gerror.Current(err), "InvalidRules: hh") + }) +} + +func Test_Code(t *testing.T) { + gtest.C(t, func(t *gtest.T) { + err := g.Validator().Rules("required").CheckValue("") + t.AssertNE(err, nil) + t.Assert(gerror.Code(err), gcode.CodeValidationFailed) + }) + + gtest.C(t, func(t *gtest.T) { + err := g.Validator().Rules("none-exist-rule").CheckValue("") + t.AssertNE(err, nil) + t.Assert(gerror.Code(err), gcode.CodeInternalError) + }) +} + +func Test_Bail(t *testing.T) { + // check value with no bail + gtest.C(t, func(t *gtest.T) { + err := g.Validator(). + Rules("required|min:1|between:1,100"). + Messages("|min number is 1|size is between 1 and 100"). + CheckValue(-1) + t.AssertNE(err, nil) + t.Assert(err.Error(), "min number is 1; size is between 1 and 100") + }) + + // check value with bail + gtest.C(t, func(t *gtest.T) { + err := g.Validator(). + Rules("bail|required|min:1|between:1,100"). + Messages("||min number is 1|size is between 1 and 100"). + CheckValue(-1) + t.AssertNE(err, nil) + t.Assert(err.Error(), "min number is 1") + }) + + // struct with no bail + gtest.C(t, func(t *gtest.T) { + type Params struct { + Page int `v:"required|min:1"` + Size int `v:"required|min:1|between:1,100 # |min number is 1|size is between 1 and 100"` + } + obj := &Params{ + Page: 1, + Size: -1, + } + err := g.Validator().CheckStruct(obj) + t.AssertNE(err, nil) + t.Assert(err.Error(), "min number is 1; size is between 1 and 100") + }) + // struct with bail + gtest.C(t, func(t *gtest.T) { + type Params struct { + Page int `v:"required|min:1"` + Size int `v:"bail|required|min:1|between:1,100 # ||min number is 1|size is between 1 and 100"` + } + obj := &Params{ + Page: 1, + Size: -1, + } + err := g.Validator().CheckStruct(obj) + t.AssertNE(err, nil) + t.Assert(err.Error(), "min number is 1") + }) +} diff --git a/util/gvalid/gvalid_unit_checkmap_test.go b/util/gvalid/gvalid_z_unit_checkmap_test.go similarity index 66% rename from util/gvalid/gvalid_unit_checkmap_test.go rename to util/gvalid/gvalid_z_unit_checkmap_test.go index e29d9104a..7750e923b 100755 --- a/util/gvalid/gvalid_unit_checkmap_test.go +++ b/util/gvalid/gvalid_z_unit_checkmap_test.go @@ -1,4 +1,4 @@ -// Copyright GoFrame Author(https://github.com/gogf/gf). All Rights Reserved. +// Copyright GoFrame Author(https://goframe.org). All Rights Reserved. // // This Source Code Form is subject to the terms of the MIT License. // If a copy of the MIT was not distributed with this file, @@ -7,7 +7,9 @@ package gvalid_test import ( + "context" "github.com/gogf/gf/errors/gerror" + "github.com/gogf/gf/frame/g" "testing" "github.com/gogf/gf/test/gtest" @@ -24,7 +26,7 @@ func Test_CheckMap1(t *testing.T) { "id": "required|between:1,100", "name": "required|length:6,16", } - if m := gvalid.CheckMap(data, rules); m == nil { + if m := gvalid.CheckMap(context.TODO(), data, rules); m == nil { t.Error("CheckMap校验失败") } else { t.Assert(len(m.Maps()), 2) @@ -37,7 +39,7 @@ func Test_CheckMap1(t *testing.T) { func Test_CheckMap2(t *testing.T) { var params interface{} gtest.C(t, func(t *gtest.T) { - if err := gvalid.CheckMap(params, nil, nil); err == nil { + if err := gvalid.CheckMap(context.TODO(), params, nil, nil); err == nil { t.Assert(err, nil) } }) @@ -57,7 +59,7 @@ func Test_CheckMap2(t *testing.T) { "length": "名称长度为:min到:max个字符", }, } - if m := gvalid.CheckMap(kvmap, rules, msgs); m == nil { + if m := gvalid.CheckMap(context.TODO(), kvmap, rules, msgs); m == nil { t.Error("CheckMap校验失败") } @@ -76,7 +78,7 @@ func Test_CheckMap2(t *testing.T) { "length": "名称长度为:min到:max个字符", }, } - if m := gvalid.CheckMap(kvmap, rules, msgs); m != nil { + if m := gvalid.CheckMap(context.TODO(), kvmap, rules, msgs); m != nil { t.Error(m) } @@ -95,7 +97,7 @@ func Test_CheckMap2(t *testing.T) { "length": "名称长度为:min到:max个字符", }, } - if m := gvalid.CheckMap(kvmap, rules, msgs); m != nil { + if m := gvalid.CheckMap(context.TODO(), kvmap, rules, msgs); m != nil { t.Error(m) } @@ -114,7 +116,7 @@ func Test_CheckMap2(t *testing.T) { "length": "名称长度为:min到:max个字符", }, } - if m := gvalid.CheckMap(kvmap, rules2, msgs); m != nil { + if m := gvalid.CheckMap(context.TODO(), kvmap, rules2, msgs); m != nil { t.Error(m) } @@ -133,7 +135,7 @@ func Test_CheckMap2(t *testing.T) { "length": "名称长度为:min到:max个字符", }, } - if m := gvalid.CheckMap(kvmap, rules2, msgs); m != nil { + if m := gvalid.CheckMap(context.TODO(), kvmap, rules2, msgs); m != nil { t.Error(m) } @@ -152,7 +154,7 @@ func Test_CheckMap2(t *testing.T) { "length": "名称长度为:min到:max个字符", }, } - if m := gvalid.CheckMap(kvmap, rules2, msgs); m != nil { + if m := gvalid.CheckMap(context.TODO(), kvmap, rules2, msgs); m != nil { t.Error(m) } } @@ -166,7 +168,7 @@ func Test_CheckMapWithNilAndNotRequiredField(t *testing.T) { "id": "required", "name": "length:4,16", } - if m := gvalid.CheckMap(data, rules); m != nil { + if m := gvalid.CheckMap(context.TODO(), data, rules); m != nil { t.Error(m) } } @@ -183,13 +185,18 @@ func Test_Sequence(t *testing.T) { "password@required|length:6,16|same:password2#密码不能为空|密码长度应当在:min到:max之间|两次密码输入不相等", "password2@required|length:6,16#", } - err := gvalid.CheckMap(params, rules) + err := gvalid.CheckMap(context.TODO(), params, rules) t.AssertNE(err, nil) t.Assert(len(err.Map()), 2) t.Assert(err.Map()["required"], "账号不能为空") t.Assert(err.Map()["length"], "账号长度应当在6到16之间") t.Assert(len(err.Maps()), 2) + t.Assert(len(err.Items()), 2) + t.Assert(err.Items()[0]["passport"]["length"], "账号长度应当在6到16之间") + t.Assert(err.Items()[0]["passport"]["required"], "账号不能为空") + t.Assert(err.Items()[1]["password"]["same"], "两次密码输入不相等") + t.Assert(err.String(), "账号不能为空; 账号长度应当在6到16之间; 两次密码输入不相等") t.Assert(err.Strings(), []string{"账号不能为空", "账号长度应当在6到16之间", "两次密码输入不相等"}) @@ -204,3 +211,38 @@ func Test_Sequence(t *testing.T) { t.Assert(gerror.Current(err), "账号不能为空") }) } + +func Test_Map_Bail(t *testing.T) { + // global bail + gtest.C(t, func(t *gtest.T) { + params := map[string]interface{}{ + "passport": "", + "password": "123456", + "password2": "1234567", + } + rules := []string{ + "passport@required|length:6,16#账号不能为空|账号长度应当在:min到:max之间", + "password@required|length:6,16|same:password2#密码不能为空|密码长度应当在:min到:max之间|两次密码输入不相等", + "password2@required|length:6,16#", + } + err := g.Validator().Bail().Rules(rules).CheckMap(params) + t.AssertNE(err, nil) + t.Assert(err.String(), "账号不能为空; 账号长度应当在6到16之间") + }) + // global bail with rule bail + gtest.C(t, func(t *gtest.T) { + params := map[string]interface{}{ + "passport": "", + "password": "123456", + "password2": "1234567", + } + rules := []string{ + "passport@bail|required|length:6,16#|账号不能为空|账号长度应当在:min到:max之间", + "password@required|length:6,16|same:password2#密码不能为空|密码长度应当在:min到:max之间|两次密码输入不相等", + "password2@required|length:6,16#", + } + err := g.Validator().Bail().Rules(rules).CheckMap(params) + t.AssertNE(err, nil) + t.Assert(err.String(), "账号不能为空") + }) +} diff --git a/util/gvalid/gvalid_unit_checkstruct_test.go b/util/gvalid/gvalid_z_unit_checkstruct_test.go similarity index 61% rename from util/gvalid/gvalid_unit_checkstruct_test.go rename to util/gvalid/gvalid_z_unit_checkstruct_test.go index 3e9f3aae5..14a80fb3a 100755 --- a/util/gvalid/gvalid_unit_checkstruct_test.go +++ b/util/gvalid/gvalid_z_unit_checkstruct_test.go @@ -1,4 +1,4 @@ -// Copyright GoFrame Author(https://github.com/gogf/gf). All Rights Reserved. +// Copyright GoFrame Author(https://goframe.org). All Rights Reserved. // // This Source Code Form is subject to the terms of the MIT License. // If a copy of the MIT was not distributed with this file, @@ -7,7 +7,9 @@ package gvalid_test import ( + "context" "github.com/gogf/gf/container/gvar" + "github.com/gogf/gf/os/gtime" "testing" "github.com/gogf/gf/frame/g" @@ -34,7 +36,7 @@ func Test_CheckStruct(t *testing.T) { "Age": "年龄为18到30周岁", } obj := &Object{"john", 16} - err := gvalid.CheckStruct(obj, rules, msgs) + err := gvalid.CheckStruct(context.TODO(), obj, rules, msgs) t.Assert(err, nil) }) @@ -55,7 +57,7 @@ func Test_CheckStruct(t *testing.T) { "Age": "年龄为18到30周岁", } obj := &Object{"john", 16} - err := gvalid.CheckStruct(obj, rules, msgs) + err := gvalid.CheckStruct(context.TODO(), obj, rules, msgs) t.AssertNE(err, nil) t.Assert(len(err.Maps()), 2) t.Assert(err.Maps()["Name"]["required"], "") @@ -80,7 +82,7 @@ func Test_CheckStruct(t *testing.T) { "Age": "年龄为18到30周岁", } obj := &Object{"john", 16} - err := gvalid.CheckStruct(obj, rules, msgs) + err := gvalid.CheckStruct(context.TODO(), obj, rules, msgs) t.AssertNE(err, nil) t.Assert(len(err.Maps()), 2) t.Assert(err.Maps()["Name"]["required"], "") @@ -105,7 +107,7 @@ func Test_CheckStruct(t *testing.T) { "Age": "年龄为18到30周岁", } obj := &Object{"john", 16} - err := gvalid.CheckStruct(obj, rules, msgs) + err := gvalid.CheckStruct(context.TODO(), obj, rules, msgs) t.AssertNE(err, nil) t.Assert(len(err.Maps()), 2) t.Assert(err.Maps()["Name"]["required"], "") @@ -119,7 +121,7 @@ func Test_CheckStruct(t *testing.T) { Password string `json:"password" gvalid:"password@required#登录密码不能为空"` } var login LoginRequest - err := gvalid.CheckStruct(login, nil) + err := gvalid.CheckStruct(context.TODO(), login, nil) t.AssertNE(err, nil) t.Assert(len(err.Maps()), 2) t.Assert(err.Maps()["username"]["required"], "用户名不能为空") @@ -132,7 +134,7 @@ func Test_CheckStruct(t *testing.T) { Password string `json:"password" gvalid:"@required#登录密码不能为空"` } var login LoginRequest - err := gvalid.CheckStruct(login, nil) + err := gvalid.CheckStruct(context.TODO(), login, nil) t.Assert(err, nil) }) @@ -142,7 +144,7 @@ func Test_CheckStruct(t *testing.T) { Password string `json:"password" gvalid:"password@required#登录密码不能为空"` } var login LoginRequest - err := gvalid.CheckStruct(login, nil) + err := gvalid.CheckStruct(context.TODO(), login, nil) t.AssertNE(err, nil) t.Assert(err.Maps()["password"]["required"], "登录密码不能为空") }) @@ -160,7 +162,7 @@ func Test_CheckStruct(t *testing.T) { Username: "john", Password: "123456", } - err := gvalid.CheckStruct(user, nil) + err := gvalid.CheckStruct(context.TODO(), user, nil) t.AssertNE(err, nil) t.Assert(len(err.Maps()), 1) t.Assert(err.Maps()["uid"]["min"], "ID不能为空") @@ -183,7 +185,7 @@ func Test_CheckStruct(t *testing.T) { "username@required#用户名不能为空", } - err := gvalid.CheckStruct(user, rules) + err := gvalid.CheckStruct(context.TODO(), user, rules) t.AssertNE(err, nil) t.Assert(len(err.Maps()), 1) t.Assert(err.Maps()["uid"]["min"], "ID不能为空") @@ -201,7 +203,7 @@ func Test_CheckStruct(t *testing.T) { Username: "john", Password: "123456", } - err := gvalid.CheckStruct(user, nil) + err := gvalid.CheckStruct(context.TODO(), user, nil) t.AssertNE(err, nil) t.Assert(len(err.Maps()), 1) }) @@ -219,14 +221,65 @@ func Test_CheckStruct(t *testing.T) { Username: "john", Password: "123456", } - err := gvalid.CheckStruct(user, nil) + err := gvalid.CheckStruct(context.TODO(), user, nil) t.AssertNE(err, nil) t.Assert(len(err.Maps()), 1) t.Assert(err.Maps()["uid"]["min"], "ID不能为空") }) } -func Test_CheckStruct_With_EmbedObject(t *testing.T) { +func Test_CheckStruct_EmbeddedObject_Attribute(t *testing.T) { + gtest.C(t, func(t *gtest.T) { + type Base struct { + Time *gtime.Time + } + type Object struct { + Base + Name string + Type int + } + rules := map[string]string{ + "Name": "required", + "Type": "required", + } + ruleMsg := map[string]interface{}{ + "Name": "名称必填", + "Type": "类型必填", + } + obj := &Object{} + obj.Type = 1 + obj.Name = "john" + obj.Time = gtime.Now() + err := gvalid.CheckStruct(context.TODO(), obj, rules, ruleMsg) + t.Assert(err, nil) + }) + gtest.C(t, func(t *gtest.T) { + type Base struct { + Name string + Type int + } + type Object struct { + Base Base + Name string + Type int + } + rules := map[string]string{ + "Name": "required", + "Type": "required", + } + ruleMsg := map[string]interface{}{ + "Name": "名称必填", + "Type": "类型必填", + } + obj := &Object{} + obj.Type = 1 + obj.Name = "john" + err := gvalid.CheckStruct(context.TODO(), obj, rules, ruleMsg) + t.Assert(err, nil) + }) +} + +func Test_CheckStruct_With_EmbeddedObject(t *testing.T) { gtest.C(t, func(t *gtest.T) { type Pass struct { Pass1 string `valid:"password1@required|same:password2#请输入您的密码|您两次输入的密码不一致"` @@ -244,7 +297,33 @@ func Test_CheckStruct_With_EmbedObject(t *testing.T) { Pass2: "2", }, } - err := gvalid.CheckStruct(user, nil) + err := gvalid.CheckStruct(context.TODO(), user, nil) + t.AssertNE(err, nil) + t.Assert(err.Maps()["name"], g.Map{"required": "请输入您的姓名"}) + t.Assert(err.Maps()["password1"], g.Map{"same": "您两次输入的密码不一致"}) + t.Assert(err.Maps()["password2"], g.Map{"same": "您两次输入的密码不一致"}) + }) +} + +func Test_CheckStruct_With_StructAttribute(t *testing.T) { + gtest.C(t, func(t *gtest.T) { + type Pass struct { + Pass1 string `valid:"password1@required|same:password2#请输入您的密码|您两次输入的密码不一致"` + Pass2 string `valid:"password2@required|same:password1#请再次输入您的密码|您两次输入的密码不一致"` + } + type User struct { + Pass + Id int + Name string `valid:"name@required#请输入您的姓名"` + } + user := &User{ + Name: "", + Pass: Pass{ + Pass1: "1", + Pass2: "2", + }, + } + err := gvalid.CheckStruct(context.TODO(), user, nil) t.AssertNE(err, nil) t.Assert(err.Maps()["name"], g.Map{"required": "请输入您的姓名"}) t.Assert(err.Maps()["password1"], g.Map{"same": "您两次输入的密码不一致"}) @@ -263,7 +342,7 @@ func Test_CheckStruct_Optional(t *testing.T) { Page: 1, Size: 10, } - err := gvalid.CheckStruct(obj, nil) + err := gvalid.CheckStruct(context.TODO(), obj, nil) t.Assert(err, nil) }) gtest.C(t, func(t *gtest.T) { @@ -276,7 +355,7 @@ func Test_CheckStruct_Optional(t *testing.T) { Page: 1, Size: 10, } - err := gvalid.CheckStruct(obj, nil) + err := gvalid.CheckStruct(context.TODO(), obj, nil) t.Assert(err, nil) }) gtest.C(t, func(t *gtest.T) { @@ -289,7 +368,7 @@ func Test_CheckStruct_Optional(t *testing.T) { Page: 1, Size: 10, } - err := gvalid.CheckStruct(obj, nil) + err := gvalid.CheckStruct(context.TODO(), obj, nil) t.Assert(err.String(), "project id must between 1, 10000") }) } @@ -305,7 +384,83 @@ func Test_CheckStruct_NoTag(t *testing.T) { Page: 1, Size: 10, } - err := gvalid.CheckStruct(obj, nil) + err := gvalid.CheckStruct(context.TODO(), obj, nil) t.Assert(err, nil) }) } + +func Test_CheckStruct_InvalidRule(t *testing.T) { + gtest.C(t, func(t *gtest.T) { + type Params struct { + Name string + Age uint + Phone string `v:"mobile"` + } + obj := &Params{ + Name: "john", + Age: 18, + Phone: "123", + } + err := gvalid.CheckStruct(context.TODO(), obj, nil) + t.AssertNE(err, nil) + }) +} + +func TestValidator_CheckStructWithData(t *testing.T) { + gtest.C(t, func(t *gtest.T) { + type UserApiSearch struct { + Uid int64 `v:"required"` + Nickname string `v:"required-with:uid"` + } + data := UserApiSearch{ + Uid: 1, + Nickname: "john", + } + t.Assert(gvalid.CheckStructWithData(context.TODO(), data, g.Map{"uid": 1, "nickname": "john"}, nil), nil) + }) + gtest.C(t, func(t *gtest.T) { + type UserApiSearch struct { + Uid int64 `v:"required"` + Nickname string `v:"required-with:uid"` + } + data := UserApiSearch{} + t.AssertNE(gvalid.CheckStructWithData(context.TODO(), data, g.Map{}, nil), nil) + }) + gtest.C(t, func(t *gtest.T) { + type UserApiSearch struct { + Uid int64 `json:"uid" v:"required"` + Nickname string `json:"nickname" v:"required-with:Uid"` + } + data := UserApiSearch{ + Uid: 1, + } + t.AssertNE(gvalid.CheckStructWithData(context.TODO(), data, g.Map{}, nil), nil) + }) + + gtest.C(t, func(t *gtest.T) { + type UserApiSearch struct { + Uid int64 `json:"uid"` + Nickname string `json:"nickname" v:"required-with:Uid"` + StartTime *gtime.Time `json:"start_time" v:"required-with:EndTime"` + EndTime *gtime.Time `json:"end_time" v:"required-with:StartTime"` + } + data := UserApiSearch{ + StartTime: nil, + EndTime: nil, + } + t.Assert(gvalid.CheckStructWithData(context.TODO(), data, g.Map{}, nil), nil) + }) + gtest.C(t, func(t *gtest.T) { + type UserApiSearch struct { + Uid int64 `json:"uid"` + Nickname string `json:"nickname" v:"required-with:Uid"` + StartTime *gtime.Time `json:"start_time" v:"required-with:EndTime"` + EndTime *gtime.Time `json:"end_time" v:"required-with:StartTime"` + } + data := UserApiSearch{ + StartTime: gtime.Now(), + EndTime: nil, + } + t.AssertNE(gvalid.CheckStructWithData(context.TODO(), data, g.Map{"start_time": gtime.Now()}, nil), nil) + }) +} diff --git a/util/gvalid/gvalid_z_unit_custom_rule_test.go b/util/gvalid/gvalid_z_unit_custom_rule_test.go new file mode 100644 index 000000000..0a4e694b4 --- /dev/null +++ b/util/gvalid/gvalid_z_unit_custom_rule_test.go @@ -0,0 +1,274 @@ +// Copyright GoFrame Author(https://goframe.org). All Rights Reserved. +// +// This Source Code Form is subject to the terms of the MIT License. +// If a copy of the MIT was not distributed with this file, +// You can obtain one at https://github.com/gogf/gf. + +package gvalid_test + +import ( + "context" + "errors" + "testing" + + "github.com/gogf/gf/frame/g" + "github.com/gogf/gf/util/gconv" + + "github.com/gogf/gf/test/gtest" + "github.com/gogf/gf/util/gvalid" +) + +func Test_CustomRule1(t *testing.T) { + rule := "custom" + err := gvalid.RegisterRule( + rule, + func(ctx context.Context, rule string, value interface{}, message string, data interface{}) error { + pass := gconv.String(value) + if len(pass) != 6 { + return errors.New(message) + } + m := gconv.Map(data) + if m["data"] != pass { + return errors.New(message) + } + return nil + }, + ) + gtest.Assert(err, nil) + gtest.C(t, func(t *gtest.T) { + err := gvalid.CheckValue(context.TODO(), "123456", rule, "custom message") + t.Assert(err.String(), "custom message") + err = gvalid.CheckValue(context.TODO(), "123456", rule, "custom message", g.Map{"data": "123456"}) + t.Assert(err, nil) + }) + // Error with struct validation. + gtest.C(t, func(t *gtest.T) { + type T struct { + Value string `v:"uid@custom#自定义错误"` + Data string `p:"data"` + } + st := &T{ + Value: "123", + Data: "123456", + } + err := gvalid.CheckStruct(context.TODO(), st, nil) + t.Assert(err.String(), "自定义错误") + }) + // No error with struct validation. + gtest.C(t, func(t *gtest.T) { + type T struct { + Value string `v:"uid@custom#自定义错误"` + Data string `p:"data"` + } + st := &T{ + Value: "123456", + Data: "123456", + } + err := gvalid.CheckStruct(context.TODO(), st, nil) + t.Assert(err, nil) + }) +} + +func Test_CustomRule2(t *testing.T) { + rule := "required-map" + err := gvalid.RegisterRule(rule, func(ctx context.Context, rule string, value interface{}, message string, data 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.CheckValue(context.TODO(), g.Map{}, rule, errStr).String(), errStr) + t.Assert(gvalid.CheckValue(context.TODO(), g.Map{"k": "v"}, rule, errStr), 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(context.TODO(), 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(context.TODO(), st, nil) + t.Assert(err, nil) + }) +} + +func Test_CustomRule_AllowEmpty(t *testing.T) { + rule := "allow-empty-str" + err := gvalid.RegisterRule(rule, func(ctx context.Context, rule string, value interface{}, message string, data interface{}) error { + s := gconv.String(value) + if len(s) == 0 || s == "gf" { + return nil + } + return errors.New(message) + }) + gtest.Assert(err, nil) + // Check. + gtest.C(t, func(t *gtest.T) { + errStr := "error" + t.Assert(gvalid.CheckValue(context.TODO(), "", rule, errStr), nil) + t.Assert(gvalid.CheckValue(context.TODO(), "gf", rule, errStr), nil) + t.Assert(gvalid.CheckValue(context.TODO(), "gf2", rule, errStr).String(), errStr) + }) + // Error with struct validation. + gtest.C(t, func(t *gtest.T) { + type T struct { + Value string `v:"uid@allow-empty-str#自定义错误"` + Data string `p:"data"` + } + st := &T{ + Value: "", + Data: "123456", + } + err := gvalid.CheckStruct(context.TODO(), st, nil) + t.Assert(err, nil) + }) + // No error with struct validation. + gtest.C(t, func(t *gtest.T) { + type T struct { + Value string `v:"uid@allow-empty-str#自定义错误"` + Data string `p:"data"` + } + st := &T{ + Value: "john", + Data: "123456", + } + err := gvalid.CheckStruct(context.TODO(), st, nil) + t.Assert(err.String(), "自定义错误") + }) +} + +func TestValidator_RuleFunc(t *testing.T) { + ruleName := "custom_1" + ruleFunc := func(ctx context.Context, rule string, value interface{}, message string, data interface{}) error { + pass := gconv.String(value) + if len(pass) != 6 { + return errors.New(message) + } + if m := gconv.Map(data); m["data"] != pass { + return errors.New(message) + } + return nil + } + gtest.C(t, func(t *gtest.T) { + err := g.Validator().Rules(ruleName).Messages("custom message").RuleFunc(ruleName, ruleFunc).CheckValue("123456") + t.Assert(err.String(), "custom message") + err = g.Validator(). + Rules(ruleName). + Messages("custom message"). + Data(g.Map{"data": "123456"}). + RuleFunc(ruleName, ruleFunc). + CheckValue("123456") + t.AssertNil(err) + }) + // Error with struct validation. + gtest.C(t, func(t *gtest.T) { + type T struct { + Value string `v:"uid@custom_1#自定义错误"` + Data string `p:"data"` + } + st := &T{ + Value: "123", + Data: "123456", + } + err := g.Validator().RuleFunc(ruleName, ruleFunc).CheckStruct(st) + t.Assert(err.String(), "自定义错误") + }) + // No error with struct validation. + gtest.C(t, func(t *gtest.T) { + type T struct { + Value string `v:"uid@custom_1#自定义错误"` + Data string `p:"data"` + } + st := &T{ + Value: "123456", + Data: "123456", + } + err := g.Validator().RuleFunc(ruleName, ruleFunc).CheckStruct(st) + t.AssertNil(err) + }) +} + +func TestValidator_RuleFuncMap(t *testing.T) { + ruleName := "custom_1" + ruleFunc := func(ctx context.Context, rule string, value interface{}, message string, data interface{}) error { + pass := gconv.String(value) + if len(pass) != 6 { + return errors.New(message) + } + if m := gconv.Map(data); m["data"] != pass { + return errors.New(message) + } + return nil + } + gtest.C(t, func(t *gtest.T) { + err := g.Validator(). + Rules(ruleName). + Messages("custom message"). + RuleFuncMap(map[string]gvalid.RuleFunc{ + ruleName: ruleFunc, + }).CheckValue("123456") + t.Assert(err.String(), "custom message") + err = g.Validator(). + Rules(ruleName). + Messages("custom message"). + Data(g.Map{"data": "123456"}). + RuleFuncMap(map[string]gvalid.RuleFunc{ + ruleName: ruleFunc, + }). + CheckValue("123456") + t.AssertNil(err) + }) + // Error with struct validation. + gtest.C(t, func(t *gtest.T) { + type T struct { + Value string `v:"uid@custom_1#自定义错误"` + Data string `p:"data"` + } + st := &T{ + Value: "123", + Data: "123456", + } + err := g.Validator(). + RuleFuncMap(map[string]gvalid.RuleFunc{ + ruleName: ruleFunc, + }).CheckStruct(st) + t.Assert(err.String(), "自定义错误") + }) + // No error with struct validation. + gtest.C(t, func(t *gtest.T) { + type T struct { + Value string `v:"uid@custom_1#自定义错误"` + Data string `p:"data"` + } + st := &T{ + Value: "123456", + Data: "123456", + } + err := g.Validator(). + RuleFuncMap(map[string]gvalid.RuleFunc{ + ruleName: ruleFunc, + }).CheckStruct(st) + t.AssertNil(err) + }) +} diff --git a/util/gvalid/gvalid_unit_customerror_test.go b/util/gvalid/gvalid_z_unit_customerror_test.go similarity index 84% rename from util/gvalid/gvalid_unit_customerror_test.go rename to util/gvalid/gvalid_z_unit_customerror_test.go index ef76bb6bc..75bc8f21a 100755 --- a/util/gvalid/gvalid_unit_customerror_test.go +++ b/util/gvalid/gvalid_z_unit_customerror_test.go @@ -1,4 +1,4 @@ -// Copyright 2019 gf Author(https://github.com/gogf/gf). All Rights Reserved. +// Copyright GoFrame Author(https://goframe.org). All Rights Reserved. // // This Source Code Form is subject to the terms of the MIT License. // If a copy of the MIT was not distributed with this file, @@ -7,6 +7,7 @@ package gvalid_test import ( + "context" "strings" "testing" @@ -19,7 +20,7 @@ func Test_Map(t *testing.T) { var ( rule = "ipv4" val = "0.0.0" - err = gvalid.Check(val, rule, nil) + err = gvalid.CheckValue(context.TODO(), val, rule, nil) msg = map[string]string{ "ipv4": "The value must be a valid IPv4 address", } @@ -33,7 +34,7 @@ func Test_FirstString(t *testing.T) { var ( rule = "ipv4" val = "0.0.0" - err = gvalid.Check(val, rule, nil) + err = gvalid.CheckValue(context.TODO(), val, rule, nil) n = err.FirstString() ) t.Assert(n, "The value must be a valid IPv4 address") @@ -46,7 +47,7 @@ func Test_CustomError1(t *testing.T) { "integer": "请输入一个整数", "length": "参数长度不对啊老铁", } - e := gvalid.Check("6.66", rule, msgs) + e := gvalid.CheckValue(context.TODO(), "6.66", rule, msgs) if e == nil || len(e.Map()) != 2 { t.Error("规则校验失败") } else { @@ -66,7 +67,7 @@ func Test_CustomError1(t *testing.T) { func Test_CustomError2(t *testing.T) { rule := "integer|length:6,16" msgs := "请输入一个整数|参数长度不对啊老铁" - e := gvalid.Check("6.66", rule, msgs) + e := gvalid.CheckValue(context.TODO(), "6.66", rule, msgs) if e == nil || len(e.Map()) != 2 { t.Error("规则校验失败") } else { diff --git a/util/gvalid/gvalid_z_unit_i18n_test.go b/util/gvalid/gvalid_z_unit_i18n_test.go new file mode 100644 index 000000000..5294c7e3c --- /dev/null +++ b/util/gvalid/gvalid_z_unit_i18n_test.go @@ -0,0 +1,50 @@ +// Copyright GoFrame Author(https://goframe.org). All Rights Reserved. +// +// This Source Code Form is subject to the terms of the MIT License. +// If a copy of the MIT was not distributed with this file, +// You can obtain one at https://github.com/gogf/gf. + +package gvalid_test + +import ( + "context" + "github.com/gogf/gf/debug/gdebug" + "github.com/gogf/gf/i18n/gi18n" + "github.com/gogf/gf/util/gvalid" + "testing" + + "github.com/gogf/gf/test/gtest" +) + +func TestValidator_I18n(t *testing.T) { + var ( + err gvalid.Error + i18nManager = gi18n.New(gi18n.Options{Path: gdebug.TestDataPath("i18n")}) + ctxCn = gi18n.WithLanguage(context.TODO(), "cn") + validator = gvalid.New().I18n(i18nManager) + ) + gtest.C(t, func(t *gtest.T) { + err = validator.Rules("required").CheckValue("") + t.Assert(err.String(), "The field is required") + + err = validator.Ctx(ctxCn).Rules("required").CheckValue("") + t.Assert(err.String(), "字段不能为空") + }) + gtest.C(t, func(t *gtest.T) { + err = validator.Ctx(ctxCn).Rules("required").Messages("CustomMessage").CheckValue("") + t.Assert(err.String(), "自定义错误") + }) + gtest.C(t, func(t *gtest.T) { + type Params struct { + Page int `v:"required|min:1 # page is required"` + Size int `v:"required|between:1,100 # size is required"` + ProjectId int `v:"between:1,10000 # project id must between :min, :max"` + } + obj := &Params{ + Page: 1, + Size: 10, + } + err := validator.Ctx(ctxCn).CheckStruct(obj) + t.Assert(err.String(), "项目ID必须大于等于1并且要小于等于10000") + }) +} diff --git a/util/gvalid/gvalid_unit_internal_test.go b/util/gvalid/gvalid_z_unit_internal_test.go similarity index 94% rename from util/gvalid/gvalid_unit_internal_test.go rename to util/gvalid/gvalid_z_unit_internal_test.go index 6a68cd6f2..89d38dcd6 100644 --- a/util/gvalid/gvalid_unit_internal_test.go +++ b/util/gvalid/gvalid_z_unit_internal_test.go @@ -1,4 +1,4 @@ -// Copyright 2020 gf Author(https://github.com/gogf/gf). All Rights Reserved. +// Copyright GoFrame Author(https://goframe.org). All Rights Reserved. // // This Source Code Form is subject to the terms of the MIT License. // If a copy of the MIT was not distributed with this file, diff --git a/util/gvalid/i18n/cn/validation.toml b/util/gvalid/i18n/cn/validation.toml index fab6dfba6..fb6c7c787 100644 --- a/util/gvalid/i18n/cn/validation.toml +++ b/util/gvalid/i18n/cn/validation.toml @@ -28,6 +28,7 @@ "gf.gvalid.rule.length" = ":attribute 字段长度为:min到:max个字符" "gf.gvalid.rule.min-length" = ":attribute 字段最小长度为:min" "gf.gvalid.rule.max-length" = ":attribute 字段最大长度为:max" +"gf.gvalid.rule.size" = ":attribute 字段长度必须为:size" "gf.gvalid.rule.between" = ":attribute 字段大小为:min到:max" "gf.gvalid.rule.min" = ":attribute 字段最小值为:min" "gf.gvalid.rule.max" = ":attribute 字段最大值为:max" diff --git a/util/gvalid/i18n/en/validation.toml b/util/gvalid/i18n/en/validation.toml index 7dc56cab0..4de5880d2 100644 --- a/util/gvalid/i18n/en/validation.toml +++ b/util/gvalid/i18n/en/validation.toml @@ -28,6 +28,7 @@ "gf.gvalid.rule.length" = "The :attribute value length must be between :min and :max" "gf.gvalid.rule.min-length" = "The :attribute value length must be equal or greater than :min" "gf.gvalid.rule.max-length" = "The :attribute value length must be equal or lesser than :max" +"gf.gvalid.rule.size" = "The :attribute value length must be :size" "gf.gvalid.rule.between" = "The :attribute value must be between :min and :max" "gf.gvalid.rule.min" = "The :attribute value must be equal or greater than :min" "gf.gvalid.rule.max" = "The :attribute value must be equal or lesser than :max" diff --git a/util/gvalid/testdata/i18n/cn/validation.toml b/util/gvalid/testdata/i18n/cn/validation.toml new file mode 100644 index 000000000..531aaa2ca --- /dev/null +++ b/util/gvalid/testdata/i18n/cn/validation.toml @@ -0,0 +1,49 @@ +"gf.gvalid.rule.required" = ":attribute 字段不能为空" +"gf.gvalid.rule.required-if" = ":attribute 字段不能为空" +"gf.gvalid.rule.required-unless" = ":attribute 字段不能为空" +"gf.gvalid.rule.required-with" = ":attribute 字段不能为空" +"gf.gvalid.rule.required-with-all" = ":attribute 字段不能为空" +"gf.gvalid.rule.required-without" = ":attribute 字段不能为空" +"gf.gvalid.rule.required-without-all" = ":attribute 字段不能为空" +"gf.gvalid.rule.date" = ":attribute 日期格式不正确" +"gf.gvalid.rule.date-format" = ":attribute 日期格式不满足:format" +"gf.gvalid.rule.email" = ":attribute 邮箱地址格式不正确" +"gf.gvalid.rule.phone" = ":attribute 手机号码格式不正确" +"gf.gvalid.rule.phone-loose" = ":attribute 手机号码格式不正确" +"gf.gvalid.rule.telephone" = ":attribute 电话号码格式不正确" +"gf.gvalid.rule.passport" = ":attribute 账号格式不合法,必需以字母开头,只能包含字母、数字和下划线,长度在6~18之间" +"gf.gvalid.rule.password" = ":attribute 密码格式不合法,密码格式为任意6-18位的可见字符" +"gf.gvalid.rule.password2" = ":attribute 密码格式不合法,密码格式为任意6-18位的可见字符,必须包含大小写字母和数字" +"gf.gvalid.rule.password3" = ":attribute 密码格式不合法,密码格式为任意6-18位的可见字符,必须包含大小写字母、数字和特殊字符" +"gf.gvalid.rule.postcode" = ":attribute 邮政编码不正确" +"gf.gvalid.rule.resident-id" = ":attribute 身份证号码格式不正确" +"gf.gvalid.rule.bank-card" = ":attribute 银行卡号格式不正确" +"gf.gvalid.rule.qq" = ":attribute QQ号码格式不正确" +"gf.gvalid.rule.ip" = ":attribute IP地址格式不正确" +"gf.gvalid.rule.ipv4" = ":attribute IPv4地址格式不正确" +"gf.gvalid.rule.ipv6" = ":attribute IPv6地址格式不正确" +"gf.gvalid.rule.mac" = ":attribute MAC地址格式不正确" +"gf.gvalid.rule.url" = ":attribute URL地址格式不正确" +"gf.gvalid.rule.domain" = ":attribute 域名格式不正确" +"gf.gvalid.rule.length" = ":attribute 字段长度为:min到:max个字符" +"gf.gvalid.rule.min-length" = ":attribute 字段最小长度为:min" +"gf.gvalid.rule.max-length" = ":attribute 字段最大长度为:max" +"gf.gvalid.rule.size" = ":attribute 字段长度为必须为:size" +"gf.gvalid.rule.between" = ":attribute 字段大小为:min到:max" +"gf.gvalid.rule.min" = ":attribute 字段最小值为:min" +"gf.gvalid.rule.max" = ":attribute 字段最大值为:max" +"gf.gvalid.rule.json" = ":attribute 字段应当为JSON格式" +"gf.gvalid.rule.xml" = ":attribute 字段应当为XML格式" +"gf.gvalid.rule.array" = ":attribute 字段应当为数组" +"gf.gvalid.rule.integer" = ":attribute 字段应当为整数" +"gf.gvalid.rule.float" = ":attribute 字段应当为浮点数" +"gf.gvalid.rule.boolean" = ":attribute 字段应当为布尔值" +"gf.gvalid.rule.same" = ":attribute 字段值必须和:field相同" +"gf.gvalid.rule.different" = ":attribute 字段值不能与:field相同" +"gf.gvalid.rule.in" = ":attribute 字段值不合法" +"gf.gvalid.rule.not-in" = ":attribute 字段值不合法" +"gf.gvalid.rule.regex" = ":attribute 字段值不合法" +"gf.gvalid.rule.__default__" = ":attribute 字段值不合法" + +"CustomMessage" = "自定义错误" +"project id must between :min, :max" = "项目ID必须大于等于:min并且要小于等于:max" \ No newline at end of file diff --git a/util/gvalid/testdata/i18n/en/validation.toml b/util/gvalid/testdata/i18n/en/validation.toml new file mode 100644 index 000000000..4de5880d2 --- /dev/null +++ b/util/gvalid/testdata/i18n/en/validation.toml @@ -0,0 +1,46 @@ +"gf.gvalid.rule.required" = "The :attribute field is required" +"gf.gvalid.rule.required-if" = "The :attribute field is required" +"gf.gvalid.rule.required-unless" = "The :attribute field is required" +"gf.gvalid.rule.required-with" = "The :attribute field is required" +"gf.gvalid.rule.required-with-all" = "The :attribute field is required" +"gf.gvalid.rule.required-without" = "The :attribute field is required" +"gf.gvalid.rule.required-without-all" = "The :attribute field is required" +"gf.gvalid.rule.date" = "The :attribute value is not a valid date" +"gf.gvalid.rule.date-format" = "The :attribute value does not match the format :format" +"gf.gvalid.rule.email" = "The :attribute value must be a valid email address" +"gf.gvalid.rule.phone" = "The :attribute value must be a valid phone number" +"gf.gvalid.rule.phone-loose" = "The :attribute value must be a valid phone number" +"gf.gvalid.rule.telephone" = "The :attribute value must be a valid telephone number" +"gf.gvalid.rule.passport" = "The :attribute value is not a valid passport format" +"gf.gvalid.rule.password" = "The :attribute value is not a valid passport format" +"gf.gvalid.rule.password2" = "The :attribute value is not a valid passport format" +"gf.gvalid.rule.password3" = "The :attribute value is not a valid passport format" +"gf.gvalid.rule.postcode" = "The :attribute value is not a valid passport format" +"gf.gvalid.rule.resident-id" = "The :attribute value is not a valid resident id number" +"gf.gvalid.rule.bank-card" = "The :attribute value must be a valid bank card number" +"gf.gvalid.rule.qq" = "The :attribute value must be a valid QQ number" +"gf.gvalid.rule.ip" = "The :attribute value must be a valid IP address" +"gf.gvalid.rule.ipv4" = "The :attribute value must be a valid IPv4 address" +"gf.gvalid.rule.ipv6" = "The :attribute value must be a valid IPv6 address" +"gf.gvalid.rule.mac" = "The :attribute value must be a valid MAC address" +"gf.gvalid.rule.url" = "The :attribute value must be a valid URL address" +"gf.gvalid.rule.domain" = "The :attribute value must be a valid domain format" +"gf.gvalid.rule.length" = "The :attribute value length must be between :min and :max" +"gf.gvalid.rule.min-length" = "The :attribute value length must be equal or greater than :min" +"gf.gvalid.rule.max-length" = "The :attribute value length must be equal or lesser than :max" +"gf.gvalid.rule.size" = "The :attribute value length must be :size" +"gf.gvalid.rule.between" = "The :attribute value must be between :min and :max" +"gf.gvalid.rule.min" = "The :attribute value must be equal or greater than :min" +"gf.gvalid.rule.max" = "The :attribute value must be equal or lesser than :max" +"gf.gvalid.rule.json" = "The :attribute value must be a valid JSON string" +"gf.gvalid.rule.xml" = "The :attribute value must be a valid XML string" +"gf.gvalid.rule.array" = "The :attribute value must be an array" +"gf.gvalid.rule.integer" = "The :attribute value must be an integer" +"gf.gvalid.rule.float" = "The :attribute value must be a float" +"gf.gvalid.rule.boolean" = "The :attribute value field must be true or false" +"gf.gvalid.rule.same" = "The :attribute value must be the same as field :field" +"gf.gvalid.rule.different" = "The :attribute value must be different from field :field" +"gf.gvalid.rule.in" = "The :attribute value is not in acceptable range" +"gf.gvalid.rule.not-in" = "The :attribute value is not in acceptable range" +"gf.gvalid.rule.regex" = "The :attribute value is invalid" +"gf.gvalid.rule.__default__" = "The :attribute value is invalid" \ No newline at end of file diff --git a/version.go b/version.go index 2a3fc1a63..e2d7525e5 100644 --- a/version.go +++ b/version.go @@ -1,4 +1,4 @@ package gf -const VERSION = "v1.14.6" +const VERSION = "v1.16.6" const AUTHORS = "john<john@goframe.org>"