Merge branch 'master' of github.com:gogf/gf

This commit is contained in:
fangjw 2021-06-02 09:08:45 +08:00
commit 7c24449a24
250 changed files with 7263 additions and 3591 deletions

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -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}!`))
}

View File

@ -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}!`))
}

View File

@ -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,
))
}

View File

@ -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)

View File

@ -1 +1,3 @@
OrderPaid = "您已成功完成订单号 #%d 支付,支付金额¥%.2f。"
hello = "你好"
world = "世界"

View File

@ -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)
}

View File

@ -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()
}

View File

@ -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(),

View File

@ -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,

View File

@ -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(),

View File

@ -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()
}

View File

@ -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
}
}

View File

@ -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()
}

View File

@ -0,0 +1,14 @@
# MySQL.
[database]
[database.default]
link = "mysql:root:12345678@tcp(127.0.0.1:3306)/test"
debug = true

View File

@ -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:两次密码输入不相等]]

View File

@ -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())
}
}

View File

@ -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())
}

View File

@ -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())

View File

@ -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())
}
}

View File

@ -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())
}

View File

@ -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())

View File

@ -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())
}

View File

@ -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))
}

View File

@ -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())
}

View File

@ -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)
}

View File

@ -1,14 +0,0 @@
"gf.gvalid.required" = "字段不能为空"

View File

@ -0,0 +1,7 @@
"gf.gvalid.required" = "字段不能为空"
"ReuiredUserName" = "Please input user name"
"ReuiredUserType" = "Please select user type"
"MustSize" = "Size of :attribute must be :size"

View File

@ -0,0 +1,16 @@
"gf.gvalid.required" = "字段不能为空"
"ReuiredUserName" = "请输入用户名称"
"ReuiredUserType" = "请选择用户类型"
"MustSize" = ":attribute长度必须为:size"

View File

@ -1,4 +1,7 @@
<!-- 为更高效率地交流并解决问题请按照以下模板提交issue感谢 -->
<!-- 为高效处理您的疑问如果觉得是BUG类问题请您务必提供可复现该问题的最小可运行代码 -->
<!-- 为高效处理您的疑问如果觉得是BUG类问题请您务必提供可复现该问题的最小可运行代码 -->
<!-- 为高效处理您的疑问如果觉得是BUG类问题请您务必提供可复现该问题的最小可运行代码 -->
<!-- 重要的事情说三遍! -->
### 1. 您当前使用的`Go`版本,及系统版本、系统架构?

View File

@ -1,5 +1,10 @@
<!-- Please answer these questions before submitting your issue. Thanks! -->
<!-- 为高效处理您的疑问如果觉得是BUG类问题请您务必提供可复现该问题的最小可运行代码 -->
<!-- 为高效处理您的疑问如果觉得是BUG类问题请您务必提供可复现该问题的最小可运行代码 -->
<!-- 为高效处理您的疑问如果觉得是BUG类问题请您务必提供可复现该问题的最小可运行代码 -->
<!-- 重要的事情说三遍! -->
### 1. What version of `Go` and system type/arch are you using?
<!--
@ -13,7 +18,7 @@ What expect to see is like: `go 1.12, linux/amd64`
<!-- You can find the GF version from your `go.mod`, or from the `version.go` in `GF` -->
### 3. Can this issue be reproduced with the latest release?
### 3. Can this issue be re-produced with the latest release?

View File

@ -736,7 +736,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 +748,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)
}

View File

@ -735,7 +735,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 +747,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)
}

View File

@ -750,7 +750,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 +762,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)
}

View File

@ -685,7 +685,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 +706,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)
}

View File

@ -658,7 +658,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 +676,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)
}

View File

@ -671,7 +671,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 +689,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)
}

View 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"])

View 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"])

View 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"])

View 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)

View 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})

View 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+"})

View 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)
}

View 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)
})

View 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 {

View 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

View 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)

View 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)

View 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

View 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)

View 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

View 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 {

View 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"])

View 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])

View 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])

View 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])

View 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"])

View 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"])

View 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"])

View 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"])

View 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"])

View File

@ -129,7 +129,7 @@ func (q *Queue) Close() {
// 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()

View 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)
}

View 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)
}

View File

@ -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)
}

View 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)

View 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)

View 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)

View 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 {

View 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
}

View 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())
})

View 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")
})

View 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)
})

View 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)
})

View 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)
})

View 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)
})

View 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)
})

View 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)
})

View 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)
})

View 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)
})

View 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)
})

View 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)
})

View 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)
})

View File

@ -189,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
}

View File

@ -41,7 +41,7 @@ func TestVar_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 TestVar_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)
})

View File

@ -11,9 +11,10 @@ import (
"context"
"database/sql"
"fmt"
"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,12 +33,12 @@ type DB interface {
// Model creation.
// ===========================================================================
// Table function is deprecated, use Model instead.
// The DB interface is designed not only for
// relational databases but also for NoSQL databases in the future. The name
// "Table" is not proper for that purpose any more.
// Also see Core.Table.
// Deprecated, use Model instead.
Table(table ...string) *Model
Table(tableNameOrStruct ...interface{}) *Model
// Model creates and returns a new ORM model from given schema.
// The parameter `table` can be more than one table names, and also alias name, like:
@ -48,7 +49,7 @@ type DB interface {
// Model("user u, user_detail ud")
// 2. Model name with alias: Model("user", "u")
// Also see Core.Model.
Model(table ...string) *Model
Model(tableNameOrStruct ...interface{}) *Model
// Schema creates and returns a schema.
// Also see Core.Schema.
@ -56,7 +57,7 @@ type DB interface {
// With creates and returns an ORM model based on meta data of given object.
// Also see Core.With.
With(object interface{}) *Model
With(objects ...interface{}) *Model
// Open creates a raw connection object for database with given node configuration.
// Note that it is not recommended using the this function manually.
@ -84,6 +85,7 @@ type DB interface {
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.
@ -94,19 +96,6 @@ type DB interface {
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.
// ===========================================================================
DoQuery(link Link, sql string, args ...interface{}) (rows *sql.Rows, err error) // See Core.DoQuery.
DoGetAll(link Link, sql string, args ...interface{}) (result Result, err error) // See Core.DoGetAll.
DoExec(link Link, sql string, args ...interface{}) (result sql.Result, err error) // See Core.DoExec.
DoPrepare(link Link, sql string) (*Stmt, error) // See Core.DoPrepare.
DoInsert(link Link, table string, data interface{}, option int, batch ...int) (result sql.Result, err error) // See Core.DoInsert.
DoBatchInsert(link Link, table string, list interface{}, option int, batch ...int) (result sql.Result, err error) // See Core.DoBatchInsert.
DoUpdate(link Link, table string, data interface{}, condition string, args ...interface{}) (result sql.Result, err error) // See Core.DoUpdate.
DoDelete(link Link, table string, condition string, args ...interface{}) (result sql.Result, err error) // See Core.DoDelete.
// ===========================================================================
// Query APIs for convenience purpose.
// ===========================================================================
@ -124,8 +113,8 @@ type DB interface {
// Master/Slave specification support.
// ===========================================================================
Master() (*sql.DB, error) // See Core.Master.
Slave() (*sql.DB, error) // See Core.Slave.
Master(schema ...string) (*sql.DB, error) // See Core.Master.
Slave(schema ...string) (*sql.DB, error) // See Core.Slave.
// ===========================================================================
// Ping-Pong.
@ -139,7 +128,7 @@ type DB interface {
// ===========================================================================
Begin() (*TX, error) // See Core.Begin.
Transaction(f func(tx *TX) error) (err error) // See Core.Transaction.
Transaction(ctx context.Context, f func(ctx context.Context, tx *TX) error) error // See Core.Transaction.
// ===========================================================================
// Configuration methods.
@ -166,15 +155,10 @@ type DB interface {
// ===========================================================================
GetCtx() context.Context // See Core.GetCtx.
GetCore() *Core // See Core.GetCore
GetChars() (charLeft string, charRight string) // See Core.GetChars.
GetMaster(schema ...string) (*sql.DB, error) // See Core.GetMaster.
GetSlave(schema ...string) (*sql.DB, error) // See Core.GetSlave.
QuoteWord(s string) string // See Core.QuoteWord.
QuoteString(s string) string // See Core.QuoteString.
QuotePrefixTableName(table string) string // See Core.QuotePrefixTableName.
Tables(schema ...string) (tables []string, err error) // See Core.Tables.
TableFields(link Link, table string, schema ...string) (map[string]*TableField, error) // See Core.TableFields.
HasTable(name string) (bool, error) // See Core.HasTable.
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.
FilteredLinkInfo() string // See Core.FilteredLinkInfo.
// HandleSqlBeforeCommit is a hook function, which deals with the sql string before
@ -182,21 +166,13 @@ type DB interface {
// database connection operation object. You can modify the sql string `sql` and its
// arguments `args` as you wish before they're committed to driver.
// Also see Core.HandleSqlBeforeCommit.
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) // See Core.mappingAndFilterData.
convertFieldValueToLocalValue(fieldValue interface{}, fieldType string) interface{} // See Core.convertFieldValueToLocalValue.
convertRowsToResult(rows *sql.Rows) (Result, error) // See Core.convertRowsToResult.
HandleSqlBeforeCommit(ctx context.Context, link Link, sql string, args []interface{}) (string, []interface{})
}
// Core is the base struct for database management.
type Core struct {
db DB // DB interface object.
ctx context.Context // Context for chaining operation only.
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.
@ -211,6 +187,17 @@ 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
}
// Sql is the sql recording struct.
type Sql struct {
Sql string // SQL string(may contain reserved char '?').
@ -221,6 +208,7 @@ type Sql struct {
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.
}
// TableField is the struct for table field.
@ -235,16 +223,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)
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)
}
// Counter is the type for update count.
type Counter struct {
Field string
@ -296,12 +274,12 @@ var (
// regularFieldNameRegPattern is the regular expression pattern for a string
// which is a regular field name of table.
regularFieldNameRegPattern = `^[\w\.\-\_]+$`
regularFieldNameRegPattern = `^[\w\.\-]+$`
// 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\-\_]+$`
regularFieldNameWithoutDotRegPattern = `^[\w\-]+$`
// internalCache is the memory cache for internal usage.
internalCache = gcache.New()
@ -337,7 +315,7 @@ 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.New("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 {
@ -356,13 +334,19 @@ func New(group ...string) (db DB, err error) {
}
return c.db, nil
} else {
return nil, gerror.New(fmt.Sprintf(`unsupported database type "%s"`, node.Type))
return nil, gerror.Newf(
`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.Newf(
`database configuration node "%s" is not found, did you misspell group name "%s" or miss the database configuration?`,
groupName, groupName,
)
}
}

View File

@ -11,19 +11,23 @@ import (
"context"
"database/sql"
"fmt"
"github.com/gogf/gf/errors/gerror"
"github.com/gogf/gf/text/gstr"
"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
@ -32,6 +36,11 @@ func (c *Core) Ctx(ctx context.Context) DB {
if ctx == nil {
return c.db
}
// It is already set context in previous chaining operation.
if c.ctx != nil {
return c.db
}
// It makes a shallow copy of current db and changes its context for next chaining operation.
var (
err error
newCore = &Core{}
@ -39,10 +48,12 @@ func (c *Core) Ctx(ctx context.Context) DB {
)
*newCore = *c
newCore.ctx = ctx
// It creates a new DB object, which is commonly a wrapper for object `Core`.
newCore.db, err = driverMap[configNode.Type].New(newCore, configNode)
// Seldom error, just log it.
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
}
@ -53,13 +64,13 @@ func (c *Core) GetCtx() context.Context {
if c.ctx != nil {
return c.ctx
}
return context.Background()
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.db.GetCtx()
ctx = c.GetCtx()
} else {
ctx = context.WithValue(ctx, "WrappedByGetCtxTimeout", nil)
}
@ -84,183 +95,41 @@ func (c *Core) GetCtxTimeout(timeoutType int, ctx context.Context) (context.Cont
// 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)
ctx := c.db.GetCtx()
if c.GetConfig().QueryTimeout > 0 {
ctx, _ = context.WithTimeout(ctx, c.GetConfig().QueryTimeout)
}
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(),
}
c.addSqlToTracing(ctx, sqlObj)
if c.db.GetDebug() {
c.writeSqlToLogger(sqlObj)
}
if err == nil {
return rows, nil
func (c *Core) Slave(schema ...string) (*sql.DB, error) {
useSchema := ""
if len(schema) > 0 && schema[0] != "" {
useSchema = schema[0]
} else {
err = formatError(err, sql, args...)
useSchema = c.schema.Val()
}
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)
ctx := c.db.GetCtx()
if c.GetConfig().ExecTimeout > 0 {
var cancelFunc context.CancelFunc
ctx, cancelFunc = context.WithTimeout(ctx, c.GetConfig().ExecTimeout)
defer cancelFunc()
}
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(),
}
c.addSqlToTracing(ctx, sqlObj)
if c.db.GetDebug() {
c.writeSqlToLogger(sqlObj)
}
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) (*Stmt, error) {
var (
err error
link Link
)
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) (*Stmt, error) {
ctx := c.db.GetCtx()
if c.GetConfig().PrepareTimeout > 0 {
// DO NOT USE cancel function in prepare statement.
ctx, _ = context.WithTimeout(ctx, c.GetConfig().PrepareTimeout)
}
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(),
}
)
c.addSqlToTracing(ctx, sqlObj)
if c.db.GetDebug() {
c.writeSqlToLogger(sqlObj)
}
return &Stmt{
Stmt: stmt,
core: c,
sql: sql,
}, err
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.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.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.
@ -278,7 +147,7 @@ func (c *Core) GetOne(sql string, args ...interface{}) (Record, error) {
// GetArray queries and returns data values as slice from database.
// 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.DoGetAll(c.GetCtx(), nil, sql, args...)
if err != nil {
return nil, err
}
@ -373,65 +242,6 @@ func (c *Core) PingSlave() error {
}
}
// 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 {
//ctx := c.db.GetCtx()
//if c.GetConfig().TranTimeout > 0 {
// var cancelFunc context.CancelFunc
// ctx, cancelFunc = context.WithTimeout(ctx, c.GetConfig().TranTimeout)
// defer cancelFunc()
//}
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.
//
@ -464,6 +274,14 @@ 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.
@ -501,7 +319,7 @@ 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 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:
@ -513,8 +331,8 @@ func (c *Core) Save(table string, data interface{}, batch ...int) (sql.Result, e
// 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, data interface{}, option int, batch ...int) (result sql.Result, err error) {
table = c.QuotePrefixTableName(table)
var (
fields []string
values []string
@ -529,10 +347,10 @@ func (c *Core) DoInsert(link Link, table string, data interface{}, option int, b
}
switch reflectKind {
case reflect.Slice, reflect.Array:
return c.db.DoBatchInsert(link, table, data, option, batch...)
return c.DoBatchInsert(ctx, link, table, data, option, batch...)
case reflect.Struct:
if _, ok := data.(apiInterfaces); ok {
return c.db.DoBatchInsert(link, table, data, option, batch...)
return c.DoBatchInsert(ctx, link, table, data, option, batch...)
} else {
dataMap = ConvertDataForTableRecord(data)
}
@ -577,19 +395,15 @@ func (c *Core) DoInsert(link Link, table string, data interface{}, option int, b
updateStr = fmt.Sprintf("ON DUPLICATE KEY UPDATE %s", updateStr)
}
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(
return c.DoExec(ctx, link, fmt.Sprintf(
"%s INTO %s(%s) VALUES(%s) %s",
operation, table, strings.Join(fields, ","),
strings.Join(values, ","), updateStr,
),
params...,
)
), params...)
}
// BatchInsert batch inserts data.
@ -630,8 +444,8 @@ func (c *Core) BatchSave(table string, list interface{}, batch ...int) (sql.Resu
// 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)
func (c *Core) DoBatchInsert(ctx context.Context, link Link, table string, list interface{}, option int, batch ...int) (result sql.Result, err error) {
table = c.QuotePrefixTableName(table)
var (
keys []string // Field names.
values []string // Value holder string array, like: (?,?,?)
@ -686,7 +500,7 @@ func (c *Core) DoBatchInsert(link Link, table string, list interface{}, option i
return result, gerror.New("data list cannot be empty")
}
if link == nil {
if link, err = c.db.Master(); err != nil {
if link, err = c.MasterLink(); err != nil {
return
}
}
@ -742,16 +556,12 @@ func (c *Core) DoBatchInsert(link Link, table string, list interface{}, option i
}
valueHolder = append(valueHolder, "("+gstr.Join(values, ",")+")")
if len(valueHolder) == batchNum || (i == listMapLen-1 && len(valueHolder) > 0) {
r, err := c.db.DoExec(
link,
fmt.Sprintf(
r, err := c.DoExec(ctx, link, fmt.Sprintf(
"%s INTO %s(%s) VALUES%s %s",
operation, table, keysStr,
gstr.Join(valueHolder, ","),
updateStr,
),
params...,
)
), params...)
if err != nil {
return r, err
}
@ -786,10 +596,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()
@ -812,24 +622,29 @@ func (c *Core) DoUpdate(link Link, table string, data interface{}, condition str
switch value := v.(type) {
case *Counter:
if value.Value != 0 {
column := c.db.QuoteWord(value.Field)
column := k
if value.Field != "" {
column = c.QuoteWord(value.Field)
}
fields = append(fields, fmt.Sprintf("%s=%s+?", column, column))
params = append(params, value.Value)
}
case Counter:
if value.Value != 0 {
column := c.db.QuoteWord(value.Field)
column := k
if value.Field != "" {
column = c.QuoteWord(value.Field)
}
fields = append(fields, fmt.Sprintf("%s=%s+?", column, column))
params = append(params, value.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, ",")
@ -844,15 +659,11 @@ func (c *Core) DoUpdate(link Link, table string, data interface{}, condition str
}
// 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.DoExec(ctx, link, fmt.Sprintf("UPDATE %s SET %s%s", table, updates, condition), args...)
}
// Delete does "DELETE FROM ... " statement for the table.
@ -872,14 +683,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.DoExec(ctx, link, fmt.Sprintf("DELETE FROM %s%s", table, condition), args...)
}
// convertRowsToResult converts underlying data record type sql.Rows to Result type.
@ -915,7 +726,7 @@ func (c *Core) convertRowsToResult(rows *sql.Rows) (Result, error) {
if value == nil {
row[columnNames[i]] = gvar.New(nil)
} else {
row[columnNames[i]] = gvar.New(c.db.convertFieldValueToLocalValue(value, columnTypes[i]))
row[columnNames[i]] = gvar.New(c.convertFieldValueToLocalValue(value, columnTypes[i]))
}
}
records = append(records, row)
@ -937,19 +748,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
}

View File

@ -8,15 +8,12 @@ package gdb
import (
"fmt"
"github.com/gogf/gf/os/gcache"
"sync"
"time"
"github.com/gogf/gf/os/glog"
)
"github.com/gogf/gf/os/gcache"
const (
DefaultGroupName = "default" // Default group name.
"github.com/gogf/gf/os/glog"
)
// Config is the configuration management object.
@ -53,6 +50,10 @@ type ConfigNode struct {
TimeMaintainDisabled bool `json:"timeMaintainDisabled"` // (Optional) Disable the automatic time maintaining feature.
}
const (
DefaultGroupName = "default" // Default group name.
)
// configs is internal used configuration object.
var configs struct {
sync.RWMutex

View File

@ -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
}

View File

@ -7,10 +7,11 @@
package gdb
import (
"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"
@ -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(nil, 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

View File

@ -0,0 +1,573 @@
// Copyright GoFrame Author(https://goframe.org). All Rights Reserved.
//
// This 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.
}
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)
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.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.db.GetCore().addSqlToTracing(tx.ctx, sqlObj)
if tx.db.GetDebug() {
tx.db.GetCore().writeSqlToLogger(tx.ctx, sqlObj)
}
return err
}
// 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.GetCore().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.GetCore().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.GetCore().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()
}
// 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).Ctx(tx.ctx).Data(list).Batch(batch[0]).Insert()
}
return tx.Model(table).Ctx(tx.ctx).Data(list).Insert()
}
// BatchInsertIgnore batch inserts data with ignore option.
// The parameter `list` must be type of slice of map or struct.
func (tx *TX) BatchInsertIgnore(table string, list interface{}, batch ...int) (sql.Result, error) {
if len(batch) > 0 {
return tx.Model(table).Ctx(tx.ctx).Data(list).Batch(batch[0]).InsertIgnore()
}
return tx.Model(table).Ctx(tx.ctx).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).Ctx(tx.ctx).Data(list).Batch(batch[0]).Replace()
}
return tx.Model(table).Ctx(tx.ctx).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).Ctx(tx.ctx).Data(list).Batch(batch[0]).Save()
}
return tx.Model(table).Ctx(tx.ctx).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).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()
}

Some files were not shown because too many files have changed in this diff Show More