mirror of
https://gitee.com/johng/gf
synced 2026-06-09 19:13:58 +08:00
Compare commits
230 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| fd92fd2409 | |||
| a2823a501e | |||
| cc60bc9dab | |||
| 425ca8aa3e | |||
| 73d2b7ed06 | |||
| 0048e2c8c4 | |||
| 549adf6487 | |||
| b5502c5580 | |||
| 46bf669cdd | |||
| fa7a3e987d | |||
| b316b9c073 | |||
| 5b57c35522 | |||
| e0a3233ea3 | |||
| 1b588b14d8 | |||
| 2c839db941 | |||
| f9eaa8f930 | |||
| 59397fd8a5 | |||
| 6be582355c | |||
| 257897763d | |||
| 29d50994ae | |||
| b129ee3f04 | |||
| a47699bf6e | |||
| 66b8b591df | |||
| 7b47fe96dd | |||
| abc8e62d58 | |||
| ef36cf8446 | |||
| c5345239fc | |||
| a229960218 | |||
| 43180ef239 | |||
| e10240bd7c | |||
| a0ef6fce81 | |||
| 314bee2b4e | |||
| 050c93ee8d | |||
| feefcc98ef | |||
| 8f4ce91361 | |||
| f03f56ba4e | |||
| 457d552fa0 | |||
| be2fcf7f57 | |||
| ad6c4f5245 | |||
| db621e38d9 | |||
| 170af1ab00 | |||
| 31ef4e7b5a | |||
| f56f689970 | |||
| 17b73e7cb3 | |||
| 903f29a824 | |||
| 12f2193963 | |||
| 13ecbc263e | |||
| 7b9888c004 | |||
| 91cd4f96f0 | |||
| 95a44122dd | |||
| 2bd76dfdde | |||
| 26895294f9 | |||
| 4cd4559784 | |||
| 3fc96f2bd0 | |||
| 91dd9e2bf9 | |||
| 9c5642f141 | |||
| 00f0a743fc | |||
| 232751e3db | |||
| 1c600d5b20 | |||
| 9bc3b44a61 | |||
| 83729f18ad | |||
| fdb6e70322 | |||
| efaf3d591c | |||
| ef50eb6d6b | |||
| fb5a1f2306 | |||
| 9d4382d12e | |||
| 9ee76ecc93 | |||
| 0d2ca48d16 | |||
| f1857df5e2 | |||
| fc1dfb7ff9 | |||
| dc407bf293 | |||
| 4eb057227c | |||
| 9cd944fd77 | |||
| cd3593182a | |||
| 0f6820df9e | |||
| 0e158903c2 | |||
| 214d0513e5 | |||
| 75ca866991 | |||
| f22aa1b5d6 | |||
| 00d7ee93b2 | |||
| 537cbf983e | |||
| 4c54b1cfbc | |||
| e9ea58df64 | |||
| 65fff6feae | |||
| dbded5e753 | |||
| 3043645605 | |||
| 685bf56a30 | |||
| a4497ed547 | |||
| 28cb0bef25 | |||
| 114cdb2351 | |||
| a2bb8ad2f2 | |||
| 5a4de52900 | |||
| ff70e54e3e | |||
| fddc21670a | |||
| f02372cf58 | |||
| ab5f809074 | |||
| 121c1a0125 | |||
| 7678540270 | |||
| 248e6ff134 | |||
| 839ebd5b51 | |||
| fa64df6f91 | |||
| 0acd118c03 | |||
| 2b5244a54b | |||
| 2472dd5fac | |||
| 5899f676f7 | |||
| bb57dc1ae7 | |||
| 5a6c2c27df | |||
| 9f096fc63d | |||
| 4267aadd78 | |||
| ef77a54c7e | |||
| afb0af4afd | |||
| 9be92cc3d4 | |||
| 15aabfb4e7 | |||
| c83e899f1f | |||
| ebe90dcaa8 | |||
| 1c3ae11eba | |||
| b718aa88a2 | |||
| 6240c3d90b | |||
| c78f9d19f5 | |||
| 0ddacdd7e2 | |||
| f8486474aa | |||
| 906c54ce61 | |||
| f72d991c36 | |||
| 528f0e5434 | |||
| 88009ee278 | |||
| b192b7dd60 | |||
| 03d51bd18c | |||
| 5c6c932a75 | |||
| 92c3c136f9 | |||
| 5069436fd2 | |||
| 141ba2e951 | |||
| fae4dea37a | |||
| 860b22aba4 | |||
| 30dbccf99e | |||
| 9b2497bc57 | |||
| 0140808460 | |||
| 84aa30d9c2 | |||
| bc724deb5e | |||
| fbfc23211c | |||
| 3d4d3a763a | |||
| 046749566d | |||
| 3b1b8a8306 | |||
| 5e92747737 | |||
| 82ad7e2acc | |||
| 2d319d0856 | |||
| c060904f3f | |||
| a63c4b6441 | |||
| 9df860a202 | |||
| 2a1634fd6f | |||
| 2970864158 | |||
| 8e76d7a8ed | |||
| 50e5dd5bd0 | |||
| 7e2605188d | |||
| e5ae1cb85c | |||
| 1e78734f2c | |||
| d5fad88c56 | |||
| 35a81b868f | |||
| 84355c1ddd | |||
| 012121ea77 | |||
| 1e628b9edb | |||
| bfdeb6c4f5 | |||
| c42a9d6e50 | |||
| 083e32fd9e | |||
| 2a350fd3ab | |||
| 03928f1977 | |||
| a2771c7558 | |||
| 968e1db94d | |||
| bb0a3e09d6 | |||
| d109706ad3 | |||
| b958689264 | |||
| 859ea150ed | |||
| 91ca79b300 | |||
| 8210f40469 | |||
| 50ffaef33f | |||
| c25f88293b | |||
| 025cdd66c5 | |||
| 237f172ae5 | |||
| 4cd7e4e5a0 | |||
| e6688b9e86 | |||
| 65131c6f22 | |||
| 7667aca4c2 | |||
| 816791b9c1 | |||
| d6ea2220f7 | |||
| 69dd5db774 | |||
| adca9222ab | |||
| 7144aa6999 | |||
| fbad5f60eb | |||
| 266f592739 | |||
| 7c8bbcb3af | |||
| ba18e2bf6b | |||
| 5fefe97b87 | |||
| 5fba250a14 | |||
| 2606ad83ac | |||
| d450de8e0d | |||
| e4b0de0d4f | |||
| 2af4fd86cc | |||
| ddd171bc18 | |||
| 5ef4ef61f0 | |||
| 2679f92aa8 | |||
| cca438d77f | |||
| f2bc29e5c1 | |||
| fe7209e76d | |||
| 7c4a0453b7 | |||
| 97879834bc | |||
| 332535901f | |||
| e68e7a3224 | |||
| b935a8c652 | |||
| 65befd5ac4 | |||
| 78bdb5ef71 | |||
| 6eb7261dfd | |||
| 4f82be5bc0 | |||
| 3ac5772059 | |||
| eb723e47c2 | |||
| 8aa7f08350 | |||
| a54559d016 | |||
| 8e1f6abac5 | |||
| 7f3a2207a3 | |||
| 742c7913ea | |||
| 2d8ab726e2 | |||
| 702a296258 | |||
| c3c5414ce2 | |||
| ee3d375532 | |||
| e3f5c9175c | |||
| 7c24449a24 | |||
| 1c09846d3e | |||
| db94346863 | |||
| 3e6b9864d5 | |||
| 7b32791006 | |||
| aa04948319 | |||
| f3983cd6b7 |
@ -1,12 +1,7 @@
|
||||
// Copyright GoFrame Author(https://goframe.org). All Rights Reserved.
|
||||
//
|
||||
// This Source Code Form is subject to the terms of the MIT License.
|
||||
// If a copy of the MIT was not distributed with this file,
|
||||
// You can obtain one at https://github.com/gogf/gf.
|
||||
|
||||
package driver
|
||||
|
||||
import (
|
||||
"context"
|
||||
"database/sql"
|
||||
"github.com/gogf/gf/database/gdb"
|
||||
"github.com/gogf/gf/os/gtime"
|
||||
@ -47,9 +42,9 @@ func (d *MyDriver) New(core *gdb.Core, node *gdb.ConfigNode) (gdb.DB, error) {
|
||||
|
||||
// DoQuery commits the sql string and its arguments to underlying driver
|
||||
// through given link object and returns the execution result.
|
||||
func (d *MyDriver) DoQuery(link gdb.Link, sql string, args ...interface{}) (rows *sql.Rows, err error) {
|
||||
func (d *MyDriver) DoQuery(ctx context.Context, link gdb.Link, sql string, args ...interface{}) (rows *sql.Rows, err error) {
|
||||
tsMilli := gtime.TimestampMilli()
|
||||
rows, err = d.DriverMysql.DoQuery(link, sql, args...)
|
||||
rows, err = d.DriverMysql.DoQuery(ctx, link, sql, args...)
|
||||
link.Exec(
|
||||
"INSERT INTO `monitor`(`sql`,`cost`,`time`,`error`) VALUES(?,?,?,?)",
|
||||
gdb.FormatSqlWithArgs(sql, args),
|
||||
@ -62,9 +57,9 @@ func (d *MyDriver) DoQuery(link gdb.Link, sql string, args ...interface{}) (rows
|
||||
|
||||
// DoExec commits the query string and its arguments to underlying driver
|
||||
// through given link object and returns the execution result.
|
||||
func (d *MyDriver) DoExec(link gdb.Link, sql string, args ...interface{}) (result sql.Result, err error) {
|
||||
func (d *MyDriver) DoExec(ctx context.Context, link gdb.Link, sql string, args ...interface{}) (result sql.Result, err error) {
|
||||
tsMilli := gtime.TimestampMilli()
|
||||
result, err = d.DriverMysql.DoExec(link, sql, args...)
|
||||
result, err = d.DriverMysql.DoExec(ctx, link, sql, args...)
|
||||
link.Exec(
|
||||
"INSERT INTO `monitor`(`sql`,`cost`,`time`,`error`) VALUES(?,?,?,?)",
|
||||
gdb.FormatSqlWithArgs(sql, args),
|
||||
|
||||
@ -10,9 +10,9 @@ func main() {
|
||||
//db := g.DB()
|
||||
|
||||
gdb.AddDefaultConfigNode(gdb.ConfigNode{
|
||||
LinkInfo: "root:12345678@tcp(127.0.0.1:3306)/test?parseTime=true&loc=Local",
|
||||
Type: "mysql",
|
||||
Charset: "utf8",
|
||||
Link: "root:12345678@tcp(127.0.0.1:3306)/test?parseTime=true&loc=Local",
|
||||
Type: "mysql",
|
||||
Charset: "utf8",
|
||||
})
|
||||
db, _ := gdb.New()
|
||||
|
||||
|
||||
18
.example/net/ghttp/server/redirect/redirect_to.go
Normal file
18
.example/net/ghttp/server/redirect/redirect_to.go
Normal file
@ -0,0 +1,18 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"github.com/gogf/gf/frame/g"
|
||||
"github.com/gogf/gf/net/ghttp"
|
||||
)
|
||||
|
||||
func main() {
|
||||
s := g.Server()
|
||||
s.BindHandler("/", func(r *ghttp.Request) {
|
||||
r.Response.RedirectTo("/login")
|
||||
})
|
||||
s.BindHandler("/login", func(r *ghttp.Request) {
|
||||
r.Response.Writeln("Login First")
|
||||
})
|
||||
s.SetPort(8199)
|
||||
s.Run()
|
||||
}
|
||||
15
.example/net/ghttp/server/servefile/servefile.go
Normal file
15
.example/net/ghttp/server/servefile/servefile.go
Normal file
@ -0,0 +1,15 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"github.com/gogf/gf/frame/g"
|
||||
"github.com/gogf/gf/net/ghttp"
|
||||
)
|
||||
|
||||
func main() {
|
||||
s := g.Server()
|
||||
s.BindHandler("/", func(r *ghttp.Request) {
|
||||
r.Response.ServeFile("test.txt")
|
||||
})
|
||||
s.SetPort(8999)
|
||||
s.Run()
|
||||
}
|
||||
15
.example/net/ghttp/server/servefile/servefiledownload.go
Normal file
15
.example/net/ghttp/server/servefile/servefiledownload.go
Normal file
@ -0,0 +1,15 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"github.com/gogf/gf/frame/g"
|
||||
"github.com/gogf/gf/net/ghttp"
|
||||
)
|
||||
|
||||
func main() {
|
||||
s := g.Server()
|
||||
s.BindHandler("/", func(r *ghttp.Request) {
|
||||
r.Response.ServeFileDownload("test.txt")
|
||||
})
|
||||
s.SetPort(8999)
|
||||
s.Run()
|
||||
}
|
||||
1
.example/net/ghttp/server/servefile/test.txt
Normal file
1
.example/net/ghttp/server/servefile/test.txt
Normal file
@ -0,0 +1 @@
|
||||
test
|
||||
@ -16,7 +16,7 @@ func main() {
|
||||
}
|
||||
defer conn.Close()
|
||||
|
||||
if err := conn.Send([]byte("GET / HTTP/1.1\n\n")); err != nil {
|
||||
if err := conn.Send([]byte("GET / HTTP/1.1\r\n\r\n")); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
@ -30,13 +30,14 @@ func main() {
|
||||
array := bytes.Split(data, []byte(": "))
|
||||
// 获得页面内容长度
|
||||
if contentLength == 0 && len(array) == 2 && bytes.EqualFold([]byte("Content-Length"), array[0]) {
|
||||
contentLength = gconv.Int(array[1])
|
||||
// http 以\r\n换行,需要把\r也去掉
|
||||
contentLength = gconv.Int(string(array[1][:len(array[1])-1]))
|
||||
}
|
||||
header = append(header, data...)
|
||||
header = append(header, '\n')
|
||||
}
|
||||
// header读取完毕,读取文本内容
|
||||
if contentLength > 0 && len(data) == 0 {
|
||||
// header读取完毕,读取文本内容, 1为\r
|
||||
if contentLength > 0 && len(data) == 1 {
|
||||
content, _ = conn.Recv(contentLength)
|
||||
break
|
||||
}
|
||||
|
||||
28
.example/os/gcache/getorset_func_lock.go
Normal file
28
.example/os/gcache/getorset_func_lock.go
Normal file
@ -0,0 +1,28 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/gogf/gf/os/gcache"
|
||||
"github.com/gogf/gf/os/gctx"
|
||||
"time"
|
||||
)
|
||||
|
||||
func main() {
|
||||
var (
|
||||
ch = make(chan struct{}, 0)
|
||||
ctx = gctx.New()
|
||||
key = `key`
|
||||
value = `value`
|
||||
)
|
||||
for i := 0; i < 10; i++ {
|
||||
go func(index int) {
|
||||
<-ch
|
||||
_, _ = gcache.Ctx(ctx).GetOrSetFuncLock(key, func() (interface{}, error) {
|
||||
fmt.Println(index, "entered")
|
||||
return value, nil
|
||||
}, 0)
|
||||
}(i)
|
||||
}
|
||||
close(ch)
|
||||
time.Sleep(time.Second)
|
||||
}
|
||||
19
.example/os/gcache/note_interface_key.go
Normal file
19
.example/os/gcache/note_interface_key.go
Normal file
@ -0,0 +1,19 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/gogf/gf/os/gcache"
|
||||
"github.com/gogf/gf/os/gctx"
|
||||
)
|
||||
|
||||
func main() {
|
||||
var (
|
||||
ctx = gctx.New()
|
||||
key1 int32 = 1
|
||||
key2 float64 = 1
|
||||
value = `value`
|
||||
)
|
||||
_ = gcache.Ctx(ctx).Set(key1, value, 0)
|
||||
fmt.Println(gcache.Ctx(ctx).Get(key1))
|
||||
fmt.Println(gcache.Ctx(ctx).Get(key2))
|
||||
}
|
||||
29
.example/os/gcache/note_interface_value.go
Normal file
29
.example/os/gcache/note_interface_value.go
Normal file
@ -0,0 +1,29 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/gogf/gf/os/gcache"
|
||||
"github.com/gogf/gf/os/gctx"
|
||||
)
|
||||
|
||||
func main() {
|
||||
type User struct {
|
||||
Id int
|
||||
Name string
|
||||
Site string
|
||||
}
|
||||
var (
|
||||
ctx = gctx.New()
|
||||
user *User
|
||||
key = `UserKey`
|
||||
value = &User{
|
||||
Id: 1,
|
||||
Name: "GoFrame",
|
||||
Site: "https://goframe.org",
|
||||
}
|
||||
)
|
||||
_ = gcache.Ctx(ctx).Set(key, value, 0)
|
||||
v, _ := gcache.Ctx(ctx).GetVar(key)
|
||||
_ = v.Scan(&user)
|
||||
fmt.Printf(`%#v`, user)
|
||||
}
|
||||
14
.example/os/glog/glog_CtxKeys.go
Normal file
14
.example/os/glog/glog_CtxKeys.go
Normal file
@ -0,0 +1,14 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"github.com/gogf/gf/frame/g"
|
||||
)
|
||||
|
||||
func main() {
|
||||
g.Log().SetCtxKeys("TraceId", "SpanId", "Test")
|
||||
ctx := context.WithValue(context.Background(), "TraceId", "1234567890")
|
||||
ctx = context.WithValue(ctx, "SpanId", "abcdefg")
|
||||
|
||||
g.Log().Ctx(ctx).Print(1, 2, 3)
|
||||
}
|
||||
@ -6,7 +6,7 @@ import (
|
||||
)
|
||||
|
||||
func main() {
|
||||
err := glog.SetConfigWithMap(g.Map{
|
||||
err := g.Log().SetConfigWithMap(g.Map{
|
||||
"prefix": "[TEST]",
|
||||
})
|
||||
if err != nil {
|
||||
@ -1,14 +1,13 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"github.com/gogf/gf/frame/g"
|
||||
"time"
|
||||
|
||||
"github.com/gogf/gf/os/glog"
|
||||
)
|
||||
|
||||
func main() {
|
||||
for i := 0; i < 10; i++ {
|
||||
glog.Async().Print("async log", i)
|
||||
g.Log().Async().Print("async log", i)
|
||||
}
|
||||
time.Sleep(time.Second)
|
||||
}
|
||||
@ -1,15 +1,14 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"github.com/gogf/gf/frame/g"
|
||||
"time"
|
||||
|
||||
"github.com/gogf/gf/os/glog"
|
||||
)
|
||||
|
||||
func main() {
|
||||
glog.SetAsync(true)
|
||||
g.Log().SetAsync(true)
|
||||
for i := 0; i < 10; i++ {
|
||||
glog.Async().Print("async log", i)
|
||||
g.Log().Print("async log", i)
|
||||
}
|
||||
time.Sleep(time.Second)
|
||||
}
|
||||
@ -3,13 +3,12 @@ package main
|
||||
import (
|
||||
"github.com/gogf/gf/frame/g"
|
||||
"github.com/gogf/gf/os/gfile"
|
||||
"github.com/gogf/gf/os/glog"
|
||||
)
|
||||
|
||||
func main() {
|
||||
path := "/tmp/glog-cat"
|
||||
glog.SetPath(path)
|
||||
glog.Stdout(false).Cat("cat1").Cat("cat2").Println("test")
|
||||
g.Log().SetPath(path)
|
||||
g.Log().Stdout(false).Cat("cat1").Cat("cat2").Println("test")
|
||||
list, err := gfile.ScanDir(path, "*", true)
|
||||
g.Dump(err)
|
||||
g.Dump(list)
|
||||
|
||||
15
.example/os/glog/glog_color.go
Normal file
15
.example/os/glog/glog_color.go
Normal file
@ -0,0 +1,15 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"github.com/gogf/gf/frame/g"
|
||||
)
|
||||
|
||||
func main() {
|
||||
g.Log().Print("Print")
|
||||
g.Log().Debug("Debug")
|
||||
g.Log().Info("Info")
|
||||
g.Log().Notice("Notice")
|
||||
g.Log().Warning("Warning")
|
||||
g.Log().Error("Error")
|
||||
g.Log().Critical("Critical")
|
||||
}
|
||||
@ -1,14 +0,0 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"github.com/gogf/gf/os/glog"
|
||||
)
|
||||
|
||||
func main() {
|
||||
glog.SetCtxKeys("Trace-Id", "Span-Id", "Test")
|
||||
ctx := context.WithValue(context.Background(), "Trace-Id", "1234567890")
|
||||
ctx = context.WithValue(ctx, "Span-Id", "abcdefg")
|
||||
|
||||
glog.Ctx(ctx).Print(1, 2, 3)
|
||||
}
|
||||
@ -1,19 +1,19 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"github.com/gogf/gf/frame/g"
|
||||
"time"
|
||||
|
||||
"github.com/gogf/gf/os/glog"
|
||||
"github.com/gogf/gf/os/gtime"
|
||||
"github.com/gogf/gf/os/gtimer"
|
||||
)
|
||||
|
||||
func main() {
|
||||
gtimer.SetTimeout(3*time.Second, func() {
|
||||
glog.SetDebug(false)
|
||||
g.Log().SetDebug(false)
|
||||
})
|
||||
for {
|
||||
glog.Debug(gtime.Datetime())
|
||||
g.Log().Debug(gtime.Datetime())
|
||||
time.Sleep(time.Second)
|
||||
}
|
||||
}
|
||||
|
||||
@ -3,24 +3,25 @@ package main
|
||||
import (
|
||||
"github.com/gogf/gf/frame/g"
|
||||
"github.com/gogf/gf/os/gfile"
|
||||
"github.com/gogf/gf/os/glog"
|
||||
)
|
||||
|
||||
// 设置日志等级
|
||||
func main() {
|
||||
l := glog.New()
|
||||
path := "/tmp/glog"
|
||||
l.SetPath(path)
|
||||
l.SetStdoutPrint(false)
|
||||
g.Log().SetPath(path)
|
||||
g.Log().SetStdoutPrint(false)
|
||||
|
||||
// 使用默认文件名称格式
|
||||
l.Println("标准文件名称格式,使用当前时间时期")
|
||||
g.Log().Println("标准文件名称格式,使用当前时间时期")
|
||||
|
||||
// 通过SetFile设置文件名称格式
|
||||
l.SetFile("stdout.log")
|
||||
l.Println("设置日志输出文件名称格式为同一个文件")
|
||||
g.Log().SetFile("stdout.log")
|
||||
g.Log().Println("设置日志输出文件名称格式为同一个文件")
|
||||
|
||||
// 链式操作设置文件名称格式
|
||||
l.File("stderr.log").Println("支持链式操作")
|
||||
l.File("error-{Ymd}.log").Println("文件名称支持带gtime日期格式")
|
||||
l.File("access-{Ymd}.log").Println("文件名称支持带gtime日期格式")
|
||||
g.Log().File("stderr.log").Println("支持链式操作")
|
||||
g.Log().File("error-{Ymd}.log").Println("文件名称支持带gtime日期格式")
|
||||
g.Log().File("access-{Ymd}.log").Println("文件名称支持带gtime日期格式")
|
||||
|
||||
list, err := gfile.ScanDir(path, "*")
|
||||
g.Dump(err)
|
||||
|
||||
@ -1,15 +1,15 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"github.com/gogf/gf/frame/g"
|
||||
"github.com/gogf/gf/os/glog"
|
||||
)
|
||||
|
||||
func main() {
|
||||
l := glog.New()
|
||||
l.SetFlags(glog.F_TIME_TIME | glog.F_FILE_SHORT)
|
||||
l.Println("time and short line number")
|
||||
l.SetFlags(glog.F_TIME_MILLI | glog.F_FILE_LONG)
|
||||
l.Println("time with millisecond and long line number")
|
||||
l.SetFlags(glog.F_TIME_STD | glog.F_FILE_LONG)
|
||||
l.Println("standard time format and long line number")
|
||||
g.Log().SetFlags(glog.F_TIME_TIME | glog.F_FILE_SHORT)
|
||||
g.Log().Println("time and short line number")
|
||||
g.Log().SetFlags(glog.F_TIME_MILLI | glog.F_FILE_LONG)
|
||||
g.Log().Println("time with millisecond and long line number")
|
||||
g.Log().SetFlags(glog.F_TIME_STD | glog.F_FILE_LONG)
|
||||
g.Log().Println("standard time format and long line number")
|
||||
}
|
||||
|
||||
@ -2,9 +2,9 @@ package main
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"github.com/gogf/gf/frame/g"
|
||||
|
||||
"github.com/gogf/gf/errors/gerror"
|
||||
"github.com/gogf/gf/os/glog"
|
||||
)
|
||||
|
||||
func MakeError() error {
|
||||
@ -18,8 +18,8 @@ func MakeGError() error {
|
||||
func TestGError() {
|
||||
err1 := MakeError()
|
||||
err2 := MakeGError()
|
||||
glog.Error(err1)
|
||||
glog.Error(err2)
|
||||
g.Log().Error(err1)
|
||||
g.Log().Error(err2)
|
||||
}
|
||||
|
||||
func main() {
|
||||
|
||||
@ -2,15 +2,14 @@ package main
|
||||
|
||||
import (
|
||||
"github.com/gogf/gf/frame/g"
|
||||
"github.com/gogf/gf/os/glog"
|
||||
)
|
||||
|
||||
func main() {
|
||||
glog.Debug(g.Map{"uid": 100, "name": "john"})
|
||||
g.Log().Debug(g.Map{"uid": 100, "name": "john"})
|
||||
|
||||
type User struct {
|
||||
Uid int `json:"uid"`
|
||||
Name string `json:"name"`
|
||||
}
|
||||
glog.Debug(User{100, "john"})
|
||||
g.Log().Debug(User{100, "john"})
|
||||
}
|
||||
|
||||
@ -1,13 +1,13 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"github.com/gogf/gf/frame/g"
|
||||
"github.com/gogf/gf/os/glog"
|
||||
)
|
||||
|
||||
// 设置日志等级,过滤掉Info日志信息
|
||||
func main() {
|
||||
l := glog.New()
|
||||
l.Info("info1")
|
||||
l.SetLevel(glog.LEVEL_ALL ^ glog.LEVEL_INFO)
|
||||
l.Info("info2")
|
||||
g.Log().Info("info1")
|
||||
g.Log().SetLevel(glog.LEVEL_ALL ^ glog.LEVEL_INFO)
|
||||
g.Log().Info("info2")
|
||||
}
|
||||
|
||||
@ -1,11 +1,11 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"github.com/gogf/gf/frame/g"
|
||||
"github.com/gogf/gf/os/glog"
|
||||
)
|
||||
|
||||
func main() {
|
||||
l := glog.New()
|
||||
l.SetLevelPrefix(glog.LEVEL_DEBU, "debug")
|
||||
l.Debug("test")
|
||||
g.Log().SetLevelPrefix(glog.LEVEL_DEBU, "debug")
|
||||
g.Log().Debug("test")
|
||||
}
|
||||
|
||||
@ -1,10 +1,10 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"github.com/gogf/gf/os/glog"
|
||||
"github.com/gogf/gf/frame/g"
|
||||
)
|
||||
|
||||
func main() {
|
||||
glog.Line().Debug("this is the short file name with its line number")
|
||||
glog.Line(true).Debug("lone file name with line number")
|
||||
g.Log().Line().Debug("this is the short file name with its line number")
|
||||
g.Log().Line(true).Debug("lone file name with line number")
|
||||
}
|
||||
|
||||
@ -1,12 +1,12 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"github.com/gogf/gf/os/glog"
|
||||
"github.com/gogf/gf/frame/g"
|
||||
)
|
||||
|
||||
func PrintLog(content string) {
|
||||
glog.Skip(0).Line().Println("line number with skip:", content)
|
||||
glog.Line(true).Println("line number without skip:", content)
|
||||
g.Log().Skip(0).Line().Println("line number with skip:", content)
|
||||
g.Log().Line(true).Println("line number without skip:", content)
|
||||
}
|
||||
|
||||
func main() {
|
||||
|
||||
@ -3,14 +3,13 @@ package main
|
||||
import (
|
||||
"github.com/gogf/gf/frame/g"
|
||||
"github.com/gogf/gf/os/gfile"
|
||||
"github.com/gogf/gf/os/glog"
|
||||
)
|
||||
|
||||
// 设置日志输出路径
|
||||
func main() {
|
||||
path := "/tmp/glog"
|
||||
glog.SetPath(path)
|
||||
glog.Println("日志内容")
|
||||
g.Log().SetPath(path)
|
||||
g.Log().Println("日志内容")
|
||||
list, err := gfile.ScanDir(path, "*")
|
||||
g.Dump(err)
|
||||
g.Dump(list)
|
||||
|
||||
@ -1,18 +1,18 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"github.com/gogf/gf/frame/g"
|
||||
"time"
|
||||
|
||||
"github.com/gogf/gf/os/glog"
|
||||
"github.com/gogf/gf/os/gtime"
|
||||
)
|
||||
|
||||
// 测试删除日志文件是否会重建日志文件
|
||||
func main() {
|
||||
path := "/Users/john/Temp/test"
|
||||
glog.SetPath(path)
|
||||
g.Log().SetPath(path)
|
||||
for {
|
||||
glog.Println(gtime.Now().String())
|
||||
g.Log().Println(gtime.Now().String())
|
||||
time.Sleep(time.Second)
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,12 +1,11 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"github.com/gogf/gf/os/glog"
|
||||
"github.com/gogf/gf/frame/g"
|
||||
)
|
||||
|
||||
func main() {
|
||||
l := glog.New()
|
||||
l.SetPrefix("[API]")
|
||||
l.Println("hello world")
|
||||
l.Error("error occurred")
|
||||
g.Log().SetPrefix("[API]")
|
||||
g.Log().Println("hello world")
|
||||
g.Log().Error("error occurred")
|
||||
}
|
||||
|
||||
@ -2,15 +2,11 @@ package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/gogf/gf/os/glog"
|
||||
"github.com/gogf/gf/frame/g"
|
||||
)
|
||||
|
||||
func main() {
|
||||
g.Log().PrintStack()
|
||||
|
||||
glog.PrintStack()
|
||||
glog.New().PrintStack()
|
||||
|
||||
fmt.Println(glog.GetStack())
|
||||
fmt.Println(glog.New().GetStack())
|
||||
fmt.Println(g.Log().GetStack())
|
||||
}
|
||||
|
||||
@ -1,22 +1,23 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"github.com/gogf/gf/frame/g"
|
||||
"sync"
|
||||
|
||||
"github.com/gogf/gf/os/glog"
|
||||
)
|
||||
|
||||
func main() {
|
||||
wg := sync.WaitGroup{}
|
||||
c := make(chan struct{})
|
||||
var (
|
||||
wg = sync.WaitGroup{}
|
||||
ch = make(chan struct{})
|
||||
)
|
||||
wg.Add(3000)
|
||||
for i := 0; i < 3000; i++ {
|
||||
go func() {
|
||||
<-c
|
||||
glog.Println("abcdefghijklmnopqrstuvwxyz1234567890")
|
||||
<-ch
|
||||
g.Log().Println("abcdefghijklmnopqrstuvwxyz1234567890")
|
||||
wg.Done()
|
||||
}()
|
||||
}
|
||||
close(c)
|
||||
close(ch)
|
||||
wg.Wait()
|
||||
}
|
||||
|
||||
@ -2,8 +2,8 @@ package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/gogf/gf/frame/g"
|
||||
|
||||
"github.com/gogf/gf/net/ghttp"
|
||||
"github.com/gogf/gf/os/glog"
|
||||
"github.com/gogf/gf/text/gregex"
|
||||
)
|
||||
@ -16,7 +16,7 @@ func (w *MyWriter) Write(p []byte) (n int, err error) {
|
||||
s := string(p)
|
||||
if gregex.IsMatchString(`\[(PANI|FATA)\]`, s) {
|
||||
fmt.Println("SERIOUS ISSUE OCCURRED!! I'd better tell monitor in first time!")
|
||||
ghttp.PostContent("http://monitor.mydomain.com", s)
|
||||
g.Client().PostContent("http://monitor.mydomain.com", s)
|
||||
}
|
||||
return w.logger.Write(p)
|
||||
}
|
||||
|
||||
31
.example/os/glog/handler/glog_handler_greylog.go
Normal file
31
.example/os/glog/handler/glog_handler_greylog.go
Normal file
@ -0,0 +1,31 @@
|
||||
package main
|
||||
|
||||
//import (
|
||||
// "context"
|
||||
// "github.com/gogf/gf/frame/g"
|
||||
// "github.com/gogf/gf/os/glog"
|
||||
// "github.com/robertkowalski/graylog-golang"
|
||||
//)
|
||||
//
|
||||
//var greyLogClient = gelf.New(gelf.Config{
|
||||
// GraylogPort: 80,
|
||||
// GraylogHostname: "graylog-host.com",
|
||||
// Connection: "wan",
|
||||
// MaxChunkSizeWan: 42,
|
||||
// MaxChunkSizeLan: 1337,
|
||||
//})
|
||||
//
|
||||
//// LoggingGreyLogHandler is an example handler for logging content to remote GreyLog service.
|
||||
//var LoggingGreyLogHandler glog.Handler = func(ctx context.Context, in *glog.HandlerInput) {
|
||||
// in.Next()
|
||||
// greyLogClient.Log(in.Buffer.String())
|
||||
//}
|
||||
//
|
||||
//func main() {
|
||||
// g.Log().SetHandlers(LoggingGreyLogHandler)
|
||||
//
|
||||
// g.Log().Debug("Debugging...")
|
||||
// g.Log().Warning("It is warning info")
|
||||
// g.Log().Error("Error occurs, please have a check")
|
||||
// glog.Println("test log")
|
||||
//}
|
||||
42
.example/os/glog/handler/glog_handler_json.go
Normal file
42
.example/os/glog/handler/glog_handler_json.go
Normal file
@ -0,0 +1,42 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"github.com/gogf/gf/frame/g"
|
||||
"github.com/gogf/gf/internal/json"
|
||||
"github.com/gogf/gf/os/glog"
|
||||
"github.com/gogf/gf/text/gstr"
|
||||
"os"
|
||||
)
|
||||
|
||||
// JsonOutputsForLogger is for JSON marshaling in sequence.
|
||||
type JsonOutputsForLogger struct {
|
||||
Time string `json:"time"`
|
||||
Level string `json:"level"`
|
||||
Content string `json:"content"`
|
||||
}
|
||||
|
||||
// LoggingJsonHandler is an example handler for logging JSON format content.
|
||||
var LoggingJsonHandler glog.Handler = func(ctx context.Context, in *glog.HandlerInput) {
|
||||
jsonForLogger := JsonOutputsForLogger{
|
||||
Time: in.TimeFormat,
|
||||
Level: in.LevelFormat,
|
||||
Content: gstr.Trim(in.String()),
|
||||
}
|
||||
jsonBytes, err := json.Marshal(jsonForLogger)
|
||||
if err != nil {
|
||||
_, _ = os.Stderr.WriteString(err.Error())
|
||||
return
|
||||
}
|
||||
in.Buffer.Write(jsonBytes)
|
||||
in.Buffer.WriteString("\n")
|
||||
in.Next()
|
||||
}
|
||||
|
||||
func main() {
|
||||
g.Log().SetHandlers(LoggingJsonHandler)
|
||||
|
||||
g.Log().Debug("Debugging...")
|
||||
g.Log().Warning("It is warning info")
|
||||
g.Log().Error("Error occurs, please have a check")
|
||||
}
|
||||
@ -10,8 +10,10 @@ import (
|
||||
|
||||
// 内存锁基本使用
|
||||
func main() {
|
||||
key := "lock"
|
||||
wg := sync.WaitGroup{}
|
||||
var (
|
||||
key = "lock"
|
||||
wg = sync.WaitGroup{}
|
||||
)
|
||||
for i := 0; i < 10; i++ {
|
||||
wg.Add(1)
|
||||
go func(i int) {
|
||||
@ -11,12 +11,13 @@ import (
|
||||
|
||||
// 测试Locker是否会产生死锁
|
||||
func main() {
|
||||
l := gmlock.New()
|
||||
wg := sync.WaitGroup{}
|
||||
key := "test"
|
||||
event := make(chan int)
|
||||
number := 100000
|
||||
|
||||
var (
|
||||
l = gmlock.New()
|
||||
wg = sync.WaitGroup{}
|
||||
key = "test"
|
||||
event = make(chan int)
|
||||
number = 100000
|
||||
)
|
||||
for i := 0; i < number; i++ {
|
||||
wg.Add(1)
|
||||
go func() {
|
||||
@ -1,23 +0,0 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"sync"
|
||||
|
||||
"github.com/gogf/gf/os/glog"
|
||||
"github.com/gogf/gf/os/gmlock"
|
||||
)
|
||||
|
||||
// 内存锁 - 给定过期时间
|
||||
func main() {
|
||||
key := "lock"
|
||||
wg := sync.WaitGroup{}
|
||||
for i := 0; i < 10; i++ {
|
||||
wg.Add(1)
|
||||
go func(i int) {
|
||||
gmlock.Lock(key, 1000)
|
||||
glog.Println(i)
|
||||
wg.Done()
|
||||
}(i)
|
||||
}
|
||||
wg.Wait()
|
||||
}
|
||||
@ -1,95 +0,0 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"math/rand"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/gogf/gf/os/gmlock"
|
||||
)
|
||||
|
||||
// 测试是否会产生死锁
|
||||
func main() {
|
||||
mu := gmlock.NewMutex()
|
||||
wg := sync.WaitGroup{}
|
||||
event := make(chan int)
|
||||
number := 100000
|
||||
|
||||
for i := 0; i < number; i++ {
|
||||
wg.Add(1)
|
||||
go func() {
|
||||
<-event
|
||||
mu.Lock()
|
||||
//fmt.Println("get lock")
|
||||
mu.Unlock()
|
||||
wg.Done()
|
||||
}()
|
||||
}
|
||||
|
||||
for i := 0; i < number; i++ {
|
||||
wg.Add(1)
|
||||
go func() {
|
||||
<-event
|
||||
mu.RLock()
|
||||
//fmt.Println("get rlock")
|
||||
mu.RUnlock()
|
||||
wg.Done()
|
||||
}()
|
||||
}
|
||||
|
||||
for i := 0; i < number; i++ {
|
||||
wg.Add(1)
|
||||
go func() {
|
||||
<-event
|
||||
if mu.TryLock() {
|
||||
//fmt.Println("get lock")
|
||||
mu.Unlock()
|
||||
}
|
||||
wg.Done()
|
||||
}()
|
||||
}
|
||||
|
||||
for i := 0; i < number; i++ {
|
||||
wg.Add(1)
|
||||
go func() {
|
||||
<-event
|
||||
if mu.TryRLock() {
|
||||
//fmt.Println("get rlock")
|
||||
mu.RUnlock()
|
||||
}
|
||||
wg.Done()
|
||||
}()
|
||||
}
|
||||
|
||||
for i := 0; i < number; i++ {
|
||||
wg.Add(1)
|
||||
go func() {
|
||||
<-event
|
||||
if mu.TryLock() {
|
||||
// 模拟业务逻辑的随机处理间隔
|
||||
time.Sleep(time.Duration(rand.Intn(100)) * time.Millisecond)
|
||||
mu.Unlock()
|
||||
}
|
||||
wg.Done()
|
||||
}()
|
||||
}
|
||||
|
||||
for i := 0; i < number; i++ {
|
||||
wg.Add(1)
|
||||
go func() {
|
||||
<-event
|
||||
if mu.TryRLock() {
|
||||
// 模拟业务逻辑的随机处理间隔
|
||||
time.Sleep(time.Duration(rand.Intn(100)) * time.Millisecond)
|
||||
mu.RUnlock()
|
||||
}
|
||||
wg.Done()
|
||||
}()
|
||||
}
|
||||
// 使用chan作为事件发送测试指令,让所有的goroutine同时执行
|
||||
close(event)
|
||||
wg.Wait()
|
||||
|
||||
fmt.Println("done!")
|
||||
}
|
||||
35
.example/util/gvalid/gvalid_i18n_http.go
Normal file
35
.example/util/gvalid/gvalid_i18n_http.go
Normal file
@ -0,0 +1,35 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"github.com/gogf/gf/net/ghttp"
|
||||
|
||||
"github.com/gogf/gf/frame/g"
|
||||
"github.com/gogf/gf/i18n/gi18n"
|
||||
)
|
||||
|
||||
func main() {
|
||||
type User struct {
|
||||
Name string `v:"required#ReuiredUserName"`
|
||||
Type int `v:"required#ReuiredUserType"`
|
||||
Project string `v:"size:10#MustSize"`
|
||||
}
|
||||
s := g.Server()
|
||||
s.Group("/", func(group *ghttp.RouterGroup) {
|
||||
group.Middleware(func(r *ghttp.Request) {
|
||||
lang := r.GetString("lang", "zh-CN")
|
||||
r.SetCtx(gi18n.WithLanguage(r.Context(), lang))
|
||||
r.Middleware.Next()
|
||||
})
|
||||
group.GET("/validate", func(r *ghttp.Request) {
|
||||
var (
|
||||
err error
|
||||
user = User{}
|
||||
)
|
||||
if err = r.Parse(&user); err != nil {
|
||||
r.Response.WriteExit(err)
|
||||
}
|
||||
r.Response.WriteExit(user)
|
||||
})
|
||||
})
|
||||
s.SetPort(8199)
|
||||
}
|
||||
@ -1,4 +1,7 @@
|
||||
<!-- 为更高效率地交流并解决问题,请按照以下模板提交issue,感谢! -->
|
||||
<!-- 为高效处理您的疑问,如果觉得是BUG类问题,请您务必提供可复现该问题的最小可运行代码! -->
|
||||
<!-- 为高效处理您的疑问,如果觉得是BUG类问题,请您务必提供可复现该问题的最小可运行代码! -->
|
||||
<!-- 为高效处理您的疑问,如果觉得是BUG类问题,请您务必提供可复现该问题的最小可运行代码! -->
|
||||
<!-- 重要的事情说三遍! -->
|
||||
|
||||
### 1. 您当前使用的`Go`版本,及系统版本、系统架构?
|
||||
|
||||
|
||||
7
.github/ISSUE_TEMPLATE.MD
vendored
7
.github/ISSUE_TEMPLATE.MD
vendored
@ -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?
|
||||
|
||||
|
||||
|
||||
|
||||
71
.github/workflows/go.yml
vendored
Normal file
71
.github/workflows/go.yml
vendored
Normal file
@ -0,0 +1,71 @@
|
||||
name: GoFrame CI
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- master
|
||||
- develop
|
||||
pull_request:
|
||||
branches: [master, develop]
|
||||
env:
|
||||
GF_DEBUG: 1
|
||||
|
||||
jobs:
|
||||
code-test:
|
||||
runs-on: ubuntu-latest
|
||||
# Service containers to run with `code-test`
|
||||
services:
|
||||
redis:
|
||||
image : redis
|
||||
options: >-
|
||||
--health-cmd "redis-cli ping"
|
||||
--health-interval 10s
|
||||
--health-timeout 5s
|
||||
--health-retries 5
|
||||
ports:
|
||||
# Maps tcp port 6379 on service container to the host
|
||||
- 6379:6379
|
||||
mysql:
|
||||
image: mysql:5.7
|
||||
env:
|
||||
MYSQL_DATABASE : test
|
||||
MYSQL_ROOT_PASSWORD: 12345678
|
||||
ports:
|
||||
# Maps tcp port 3306 on service container to the host
|
||||
- 3306:3306
|
||||
|
||||
# strategy set
|
||||
strategy:
|
||||
matrix:
|
||||
go: ["1.14", "1.15", "1.16"]
|
||||
|
||||
steps:
|
||||
- name: Set Up Timezone
|
||||
uses: szenius/set-timezone@v1.0
|
||||
with:
|
||||
timezoneLinux: "Asia/Shanghai"
|
||||
|
||||
- name: Checkout Repositary
|
||||
uses: actions/checkout@v2
|
||||
|
||||
- name: Set Up Go
|
||||
uses: actions/setup-go@v2
|
||||
with:
|
||||
go-version: ${{ matrix.go }}
|
||||
|
||||
- name: Before Script
|
||||
run: |
|
||||
date
|
||||
find . -name "*.go" | xargs gofmt -w
|
||||
git diff --name-only --exit-code || exit 1
|
||||
sudo echo "127.0.0.1 local" | sudo tee -a /etc/hosts
|
||||
|
||||
- name: Run i386 Arch Test
|
||||
run: GOARCH=386 go test -v ./... || exit 1
|
||||
|
||||
- name: Run amd64 Arch Test
|
||||
run: GOARCH=amd64 go test -v ./... -race -coverprofile=coverage.txt -covermode=atomic
|
||||
|
||||
- name: Report Coverage
|
||||
run: bash <(curl -s https://codecov.io/bash)
|
||||
|
||||
2
.gitignore
vendored
2
.gitignore
vendored
@ -15,3 +15,5 @@ cbuild
|
||||
**/.DS_Store
|
||||
.vscode/
|
||||
.example/other/
|
||||
main
|
||||
gf
|
||||
@ -1,7 +1,7 @@
|
||||
# GoFrame
|
||||
|
||||
[](https://godoc.org/github.com/gogf/gf)
|
||||
[](https://travis-ci.org/gogf/gf)
|
||||
[](https://github.com/gogf/gf/actions/workflows/go.yml)
|
||||
[](https://goreportcard.com/report/github.com/gogf/gf)
|
||||
[](https://codecov.io/gh/gogf/gf/branch/master)
|
||||
[](https://github.com/gogf/gf)
|
||||
|
||||
@ -8,8 +8,9 @@ package garray
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"errors"
|
||||
"fmt"
|
||||
"github.com/gogf/gf/errors/gcode"
|
||||
"github.com/gogf/gf/errors/gerror"
|
||||
"github.com/gogf/gf/internal/empty"
|
||||
"github.com/gogf/gf/internal/json"
|
||||
"github.com/gogf/gf/text/gstr"
|
||||
@ -30,19 +31,19 @@ type Array struct {
|
||||
}
|
||||
|
||||
// New creates and returns an empty array.
|
||||
// The parameter <safe> is used to specify whether using array in concurrent-safety,
|
||||
// The parameter `safe` is used to specify whether using array in concurrent-safety,
|
||||
// which is false in default.
|
||||
func New(safe ...bool) *Array {
|
||||
return NewArraySize(0, 0, safe...)
|
||||
}
|
||||
|
||||
// See New.
|
||||
// NewArray is alias of New, please see New.
|
||||
func NewArray(safe ...bool) *Array {
|
||||
return NewArraySize(0, 0, safe...)
|
||||
}
|
||||
|
||||
// NewArraySize create and returns an array with given size and cap.
|
||||
// The parameter <safe> is used to specify whether using array in concurrent-safety,
|
||||
// The parameter `safe` is used to specify whether using array in concurrent-safety,
|
||||
// which is false in default.
|
||||
func NewArraySize(size int, cap int, safe ...bool) *Array {
|
||||
return &Array{
|
||||
@ -51,8 +52,8 @@ func NewArraySize(size int, cap int, safe ...bool) *Array {
|
||||
}
|
||||
}
|
||||
|
||||
// NewArrayRange creates and returns a array by a range from <start> to <end>
|
||||
// with step value <step>.
|
||||
// NewArrayRange creates and returns a array by a range from `start` to `end`
|
||||
// with step value `step`.
|
||||
func NewArrayRange(start, end, step int, safe ...bool) *Array {
|
||||
if step == 0 {
|
||||
panic(fmt.Sprintf(`invalid step value: %d`, step))
|
||||
@ -66,18 +67,20 @@ func NewArrayRange(start, end, step int, safe ...bool) *Array {
|
||||
return NewArrayFrom(slice, safe...)
|
||||
}
|
||||
|
||||
// NewFrom is alias of NewArrayFrom.
|
||||
// See NewArrayFrom.
|
||||
func NewFrom(array []interface{}, safe ...bool) *Array {
|
||||
return NewArrayFrom(array, safe...)
|
||||
}
|
||||
|
||||
// NewFromCopy is alias of NewArrayFromCopy.
|
||||
// See NewArrayFromCopy.
|
||||
func NewFromCopy(array []interface{}, safe ...bool) *Array {
|
||||
return NewArrayFromCopy(array, safe...)
|
||||
}
|
||||
|
||||
// NewArrayFrom creates and returns an array with given slice <array>.
|
||||
// The parameter <safe> is used to specify whether using array in concurrent-safety,
|
||||
// NewArrayFrom creates and returns an array with given slice `array`.
|
||||
// The parameter `safe` is used to specify whether using array in concurrent-safety,
|
||||
// which is false in default.
|
||||
func NewArrayFrom(array []interface{}, safe ...bool) *Array {
|
||||
return &Array{
|
||||
@ -86,8 +89,8 @@ func NewArrayFrom(array []interface{}, safe ...bool) *Array {
|
||||
}
|
||||
}
|
||||
|
||||
// NewArrayFromCopy creates and returns an array from a copy of given slice <array>.
|
||||
// The parameter <safe> is used to specify whether using array in concurrent-safety,
|
||||
// NewArrayFromCopy creates and returns an array from a copy of given slice `array`.
|
||||
// The parameter `safe` is used to specify whether using array in concurrent-safety,
|
||||
// which is false in default.
|
||||
func NewArrayFromCopy(array []interface{}, safe ...bool) *Array {
|
||||
newArray := make([]interface{}, len(array))
|
||||
@ -98,8 +101,15 @@ func NewArrayFromCopy(array []interface{}, safe ...bool) *Array {
|
||||
}
|
||||
}
|
||||
|
||||
// At returns the value by the specified index.
|
||||
// If the given `index` is out of range of the array, it returns `nil`.
|
||||
func (a *Array) At(index int) (value interface{}) {
|
||||
value, _ = a.Get(index)
|
||||
return
|
||||
}
|
||||
|
||||
// Get returns the value by the specified index.
|
||||
// If the given <index> is out of range of the array, the <found> is false.
|
||||
// If the given `index` is out of range of the array, the `found` is false.
|
||||
func (a *Array) Get(index int) (value interface{}, found bool) {
|
||||
a.mu.RLock()
|
||||
defer a.mu.RUnlock()
|
||||
@ -114,13 +124,13 @@ func (a *Array) Set(index int, value interface{}) error {
|
||||
a.mu.Lock()
|
||||
defer a.mu.Unlock()
|
||||
if index < 0 || index >= len(a.array) {
|
||||
return errors.New(fmt.Sprintf("index %d out of array range %d", index, len(a.array)))
|
||||
return gerror.NewCodef(gcode.CodeInvalidParameter, "index %d out of array range %d", index, len(a.array))
|
||||
}
|
||||
a.array[index] = value
|
||||
return nil
|
||||
}
|
||||
|
||||
// SetArray sets the underlying slice array with the given <array>.
|
||||
// SetArray sets the underlying slice array with the given `array`.
|
||||
func (a *Array) SetArray(array []interface{}) *Array {
|
||||
a.mu.Lock()
|
||||
defer a.mu.Unlock()
|
||||
@ -128,7 +138,7 @@ func (a *Array) SetArray(array []interface{}) *Array {
|
||||
return a
|
||||
}
|
||||
|
||||
// Replace replaces the array items by given <array> from the beginning of array.
|
||||
// Replace replaces the array items by given `array` from the beginning of array.
|
||||
func (a *Array) Replace(array []interface{}) *Array {
|
||||
a.mu.Lock()
|
||||
defer a.mu.Unlock()
|
||||
@ -152,7 +162,7 @@ func (a *Array) Sum() (sum int) {
|
||||
return
|
||||
}
|
||||
|
||||
// SortFunc sorts the array by custom function <less>.
|
||||
// SortFunc sorts the array by custom function `less`.
|
||||
func (a *Array) SortFunc(less func(v1, v2 interface{}) bool) *Array {
|
||||
a.mu.Lock()
|
||||
defer a.mu.Unlock()
|
||||
@ -162,12 +172,12 @@ func (a *Array) SortFunc(less func(v1, v2 interface{}) bool) *Array {
|
||||
return a
|
||||
}
|
||||
|
||||
// InsertBefore inserts the <value> to the front of <index>.
|
||||
// InsertBefore inserts the `value` to the front of `index`.
|
||||
func (a *Array) InsertBefore(index int, value interface{}) error {
|
||||
a.mu.Lock()
|
||||
defer a.mu.Unlock()
|
||||
if index < 0 || index >= len(a.array) {
|
||||
return errors.New(fmt.Sprintf("index %d out of array range %d", index, len(a.array)))
|
||||
return gerror.NewCodef(gcode.CodeInvalidParameter, "index %d out of array range %d", index, len(a.array))
|
||||
}
|
||||
rear := append([]interface{}{}, a.array[index:]...)
|
||||
a.array = append(a.array[0:index], value)
|
||||
@ -175,12 +185,12 @@ func (a *Array) InsertBefore(index int, value interface{}) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// InsertAfter inserts the <value> to the back of <index>.
|
||||
// InsertAfter inserts the `value` to the back of `index`.
|
||||
func (a *Array) InsertAfter(index int, value interface{}) error {
|
||||
a.mu.Lock()
|
||||
defer a.mu.Unlock()
|
||||
if index < 0 || index >= len(a.array) {
|
||||
return errors.New(fmt.Sprintf("index %d out of array range %d", index, len(a.array)))
|
||||
return gerror.NewCodef(gcode.CodeInvalidParameter, "index %d out of array range %d", index, len(a.array))
|
||||
}
|
||||
rear := append([]interface{}{}, a.array[index+1:]...)
|
||||
a.array = append(a.array[0:index+1], value)
|
||||
@ -189,7 +199,7 @@ func (a *Array) InsertAfter(index int, value interface{}) error {
|
||||
}
|
||||
|
||||
// Remove removes an item by index.
|
||||
// If the given <index> is out of range of the array, the <found> is false.
|
||||
// If the given `index` is out of range of the array, the `found` is false.
|
||||
func (a *Array) Remove(index int) (value interface{}, found bool) {
|
||||
a.mu.Lock()
|
||||
defer a.mu.Unlock()
|
||||
@ -247,14 +257,14 @@ func (a *Array) PushRight(value ...interface{}) *Array {
|
||||
}
|
||||
|
||||
// PopRand randomly pops and return an item out of array.
|
||||
// Note that if the array is empty, the <found> is false.
|
||||
// Note that if the array is empty, the `found` is false.
|
||||
func (a *Array) PopRand() (value interface{}, found bool) {
|
||||
a.mu.Lock()
|
||||
defer a.mu.Unlock()
|
||||
return a.doRemoveWithoutLock(grand.Intn(len(a.array)))
|
||||
}
|
||||
|
||||
// PopRands randomly pops and returns <size> items out of array.
|
||||
// PopRands randomly pops and returns `size` items out of array.
|
||||
func (a *Array) PopRands(size int) []interface{} {
|
||||
a.mu.Lock()
|
||||
defer a.mu.Unlock()
|
||||
@ -272,7 +282,7 @@ func (a *Array) PopRands(size int) []interface{} {
|
||||
}
|
||||
|
||||
// PopLeft pops and returns an item from the beginning of array.
|
||||
// Note that if the array is empty, the <found> is false.
|
||||
// Note that if the array is empty, the `found` is false.
|
||||
func (a *Array) PopLeft() (value interface{}, found bool) {
|
||||
a.mu.Lock()
|
||||
defer a.mu.Unlock()
|
||||
@ -285,7 +295,7 @@ func (a *Array) PopLeft() (value interface{}, found bool) {
|
||||
}
|
||||
|
||||
// PopRight pops and returns an item from the end of array.
|
||||
// Note that if the array is empty, the <found> is false.
|
||||
// Note that if the array is empty, the `found` is false.
|
||||
func (a *Array) PopRight() (value interface{}, found bool) {
|
||||
a.mu.Lock()
|
||||
defer a.mu.Unlock()
|
||||
@ -298,7 +308,7 @@ func (a *Array) PopRight() (value interface{}, found bool) {
|
||||
return value, true
|
||||
}
|
||||
|
||||
// PopLefts pops and returns <size> items from the beginning of array.
|
||||
// PopLefts pops and returns `size` items from the beginning of array.
|
||||
func (a *Array) PopLefts(size int) []interface{} {
|
||||
a.mu.Lock()
|
||||
defer a.mu.Unlock()
|
||||
@ -315,7 +325,7 @@ func (a *Array) PopLefts(size int) []interface{} {
|
||||
return value
|
||||
}
|
||||
|
||||
// PopRights pops and returns <size> items from the end of array.
|
||||
// PopRights pops and returns `size` items from the end of array.
|
||||
func (a *Array) PopRights(size int) []interface{} {
|
||||
a.mu.Lock()
|
||||
defer a.mu.Unlock()
|
||||
@ -337,8 +347,8 @@ func (a *Array) PopRights(size int) []interface{} {
|
||||
// Notice, if in concurrent-safe usage, it returns a copy of slice;
|
||||
// else a pointer to the underlying data.
|
||||
//
|
||||
// If <end> is negative, then the offset will start from the end of array.
|
||||
// If <end> is omitted, then the sequence will have everything from start up
|
||||
// If `end` is negative, then the offset will start from the end of array.
|
||||
// If `end` is omitted, then the sequence will have everything from start up
|
||||
// until the end of the array.
|
||||
func (a *Array) Range(start int, end ...int) []interface{} {
|
||||
a.mu.RLock()
|
||||
@ -364,7 +374,7 @@ func (a *Array) Range(start int, end ...int) []interface{} {
|
||||
}
|
||||
|
||||
// SubSlice returns a slice of elements from the array as specified
|
||||
// by the <offset> and <size> parameters.
|
||||
// by the `offset` and `size` parameters.
|
||||
// If in concurrent safe usage, it returns a copy of the slice; else a pointer.
|
||||
//
|
||||
// If offset is non-negative, the sequence will start at that offset in the array.
|
||||
@ -413,7 +423,7 @@ func (a *Array) SubSlice(offset int, length ...int) []interface{} {
|
||||
}
|
||||
}
|
||||
|
||||
// See PushRight.
|
||||
// Append is alias of PushRight, please See PushRight.
|
||||
func (a *Array) Append(value ...interface{}) *Array {
|
||||
a.PushRight(value...)
|
||||
return a
|
||||
@ -471,7 +481,7 @@ func (a *Array) Contains(value interface{}) bool {
|
||||
return a.Search(value) != -1
|
||||
}
|
||||
|
||||
// Search searches array by <value>, returns the index of <value>,
|
||||
// Search searches array by `value`, returns the index of `value`,
|
||||
// or returns -1 if not exists.
|
||||
func (a *Array) Search(value interface{}) int {
|
||||
a.mu.RLock()
|
||||
@ -506,7 +516,7 @@ func (a *Array) Unique() *Array {
|
||||
return a
|
||||
}
|
||||
|
||||
// LockFunc locks writing by callback function <f>.
|
||||
// LockFunc locks writing by callback function `f`.
|
||||
func (a *Array) LockFunc(f func(array []interface{})) *Array {
|
||||
a.mu.Lock()
|
||||
defer a.mu.Unlock()
|
||||
@ -514,7 +524,7 @@ func (a *Array) LockFunc(f func(array []interface{})) *Array {
|
||||
return a
|
||||
}
|
||||
|
||||
// RLockFunc locks reading by callback function <f>.
|
||||
// RLockFunc locks reading by callback function `f`.
|
||||
func (a *Array) RLockFunc(f func(array []interface{})) *Array {
|
||||
a.mu.RLock()
|
||||
defer a.mu.RUnlock()
|
||||
@ -522,21 +532,21 @@ func (a *Array) RLockFunc(f func(array []interface{})) *Array {
|
||||
return a
|
||||
}
|
||||
|
||||
// Merge merges <array> into current array.
|
||||
// The parameter <array> can be any garray or slice type.
|
||||
// Merge merges `array` into current array.
|
||||
// The parameter `array` can be any garray or slice type.
|
||||
// The difference between Merge and Append is Append supports only specified slice type,
|
||||
// but Merge supports more parameter types.
|
||||
func (a *Array) Merge(array interface{}) *Array {
|
||||
return a.Append(gconv.Interfaces(array)...)
|
||||
}
|
||||
|
||||
// Fill fills an array with num entries of the value <value>,
|
||||
// keys starting at the <startIndex> parameter.
|
||||
// Fill fills an array with num entries of the value `value`,
|
||||
// keys starting at the `startIndex` parameter.
|
||||
func (a *Array) Fill(startIndex int, num int, value interface{}) error {
|
||||
a.mu.Lock()
|
||||
defer a.mu.Unlock()
|
||||
if startIndex < 0 || startIndex > len(a.array) {
|
||||
return errors.New(fmt.Sprintf("index %d out of array range %d", startIndex, len(a.array)))
|
||||
return gerror.NewCodef(gcode.CodeInvalidParameter, "index %d out of array range %d", startIndex, len(a.array))
|
||||
}
|
||||
for i := startIndex; i < startIndex+num; i++ {
|
||||
if i > len(a.array)-1 {
|
||||
@ -549,7 +559,7 @@ func (a *Array) Fill(startIndex int, num int, value interface{}) error {
|
||||
}
|
||||
|
||||
// Chunk splits an array into multiple arrays,
|
||||
// the size of each array is determined by <size>.
|
||||
// the size of each array is determined by `size`.
|
||||
// The last chunk may contain less than size elements.
|
||||
func (a *Array) Chunk(size int) [][]interface{} {
|
||||
if size < 1 {
|
||||
@ -571,9 +581,9 @@ func (a *Array) Chunk(size int) [][]interface{} {
|
||||
return n
|
||||
}
|
||||
|
||||
// Pad pads array to the specified length with <value>.
|
||||
// Pad pads array to the specified length with `value`.
|
||||
// If size is positive then the array is padded on the right, or negative on the left.
|
||||
// If the absolute value of <size> is less than or equal to the length of the array
|
||||
// If the absolute value of `size` is less than or equal to the length of the array
|
||||
// then no padding takes place.
|
||||
func (a *Array) Pad(size int, val interface{}) *Array {
|
||||
a.mu.Lock()
|
||||
@ -608,7 +618,7 @@ func (a *Array) Rand() (value interface{}, found bool) {
|
||||
return a.array[grand.Intn(len(a.array))], true
|
||||
}
|
||||
|
||||
// Rands randomly returns <size> items from array(no deleting).
|
||||
// Rands randomly returns `size` items from array(no deleting).
|
||||
func (a *Array) Rands(size int) []interface{} {
|
||||
a.mu.RLock()
|
||||
defer a.mu.RUnlock()
|
||||
@ -642,7 +652,7 @@ func (a *Array) Reverse() *Array {
|
||||
return a
|
||||
}
|
||||
|
||||
// Join joins array elements with a string <glue>.
|
||||
// Join joins array elements with a string `glue`.
|
||||
func (a *Array) Join(glue string) string {
|
||||
a.mu.RLock()
|
||||
defer a.mu.RUnlock()
|
||||
@ -675,8 +685,8 @@ func (a *Array) Iterator(f func(k int, v interface{}) bool) {
|
||||
a.IteratorAsc(f)
|
||||
}
|
||||
|
||||
// IteratorAsc iterates the array readonly in ascending order with given callback function <f>.
|
||||
// If <f> returns true, then it continues iterating; or false to stop.
|
||||
// IteratorAsc iterates the array readonly in ascending order with given callback function `f`.
|
||||
// If `f` returns true, then it continues iterating; or false to stop.
|
||||
func (a *Array) IteratorAsc(f func(k int, v interface{}) bool) {
|
||||
a.mu.RLock()
|
||||
defer a.mu.RUnlock()
|
||||
@ -687,8 +697,8 @@ func (a *Array) IteratorAsc(f func(k int, v interface{}) bool) {
|
||||
}
|
||||
}
|
||||
|
||||
// IteratorDesc iterates the array readonly in descending order with given callback function <f>.
|
||||
// If <f> returns true, then it continues iterating; or false to stop.
|
||||
// IteratorDesc iterates the array readonly in descending order with given callback function `f`.
|
||||
// If `f` returns true, then it continues iterating; or false to stop.
|
||||
func (a *Array) IteratorDesc(f func(k int, v interface{}) bool) {
|
||||
a.mu.RLock()
|
||||
defer a.mu.RUnlock()
|
||||
@ -784,7 +794,7 @@ func (a *Array) FilterEmpty() *Array {
|
||||
return a
|
||||
}
|
||||
|
||||
// Walk applies a user supplied function <f> to every item of array.
|
||||
// Walk applies a user supplied function `f` to every item of array.
|
||||
func (a *Array) Walk(f func(value interface{}) interface{}) *Array {
|
||||
a.mu.Lock()
|
||||
defer a.mu.Unlock()
|
||||
|
||||
@ -8,8 +8,9 @@ package garray
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"errors"
|
||||
"fmt"
|
||||
"github.com/gogf/gf/errors/gcode"
|
||||
"github.com/gogf/gf/errors/gerror"
|
||||
"github.com/gogf/gf/internal/json"
|
||||
"math"
|
||||
"sort"
|
||||
@ -28,14 +29,14 @@ type IntArray struct {
|
||||
}
|
||||
|
||||
// NewIntArray creates and returns an empty array.
|
||||
// The parameter <safe> is used to specify whether using array in concurrent-safety,
|
||||
// The parameter `safe` is used to specify whether using array in concurrent-safety,
|
||||
// which is false in default.
|
||||
func NewIntArray(safe ...bool) *IntArray {
|
||||
return NewIntArraySize(0, 0, safe...)
|
||||
}
|
||||
|
||||
// NewIntArraySize create and returns an array with given size and cap.
|
||||
// The parameter <safe> is used to specify whether using array in concurrent-safety,
|
||||
// The parameter `safe` is used to specify whether using array in concurrent-safety,
|
||||
// which is false in default.
|
||||
func NewIntArraySize(size int, cap int, safe ...bool) *IntArray {
|
||||
return &IntArray{
|
||||
@ -44,8 +45,8 @@ func NewIntArraySize(size int, cap int, safe ...bool) *IntArray {
|
||||
}
|
||||
}
|
||||
|
||||
// NewIntArrayRange creates and returns a array by a range from <start> to <end>
|
||||
// with step value <step>.
|
||||
// NewIntArrayRange creates and returns a array by a range from `start` to `end`
|
||||
// with step value `step`.
|
||||
func NewIntArrayRange(start, end, step int, safe ...bool) *IntArray {
|
||||
if step == 0 {
|
||||
panic(fmt.Sprintf(`invalid step value: %d`, step))
|
||||
@ -59,8 +60,8 @@ func NewIntArrayRange(start, end, step int, safe ...bool) *IntArray {
|
||||
return NewIntArrayFrom(slice, safe...)
|
||||
}
|
||||
|
||||
// NewIntArrayFrom creates and returns an array with given slice <array>.
|
||||
// The parameter <safe> is used to specify whether using array in concurrent-safety,
|
||||
// NewIntArrayFrom creates and returns an array with given slice `array`.
|
||||
// The parameter `safe` is used to specify whether using array in concurrent-safety,
|
||||
// which is false in default.
|
||||
func NewIntArrayFrom(array []int, safe ...bool) *IntArray {
|
||||
return &IntArray{
|
||||
@ -69,8 +70,8 @@ func NewIntArrayFrom(array []int, safe ...bool) *IntArray {
|
||||
}
|
||||
}
|
||||
|
||||
// NewIntArrayFromCopy creates and returns an array from a copy of given slice <array>.
|
||||
// The parameter <safe> is used to specify whether using array in concurrent-safety,
|
||||
// NewIntArrayFromCopy creates and returns an array from a copy of given slice `array`.
|
||||
// The parameter `safe` is used to specify whether using array in concurrent-safety,
|
||||
// which is false in default.
|
||||
func NewIntArrayFromCopy(array []int, safe ...bool) *IntArray {
|
||||
newArray := make([]int, len(array))
|
||||
@ -81,8 +82,15 @@ func NewIntArrayFromCopy(array []int, safe ...bool) *IntArray {
|
||||
}
|
||||
}
|
||||
|
||||
// At returns the value by the specified index.
|
||||
// If the given `index` is out of range of the array, it returns `0`.
|
||||
func (a *IntArray) At(index int) (value int) {
|
||||
value, _ = a.Get(index)
|
||||
return
|
||||
}
|
||||
|
||||
// Get returns the value by the specified index.
|
||||
// If the given <index> is out of range of the array, the <found> is false.
|
||||
// If the given `index` is out of range of the array, the `found` is false.
|
||||
func (a *IntArray) Get(index int) (value int, found bool) {
|
||||
a.mu.RLock()
|
||||
defer a.mu.RUnlock()
|
||||
@ -97,13 +105,13 @@ func (a *IntArray) Set(index int, value int) error {
|
||||
a.mu.Lock()
|
||||
defer a.mu.Unlock()
|
||||
if index < 0 || index >= len(a.array) {
|
||||
return errors.New(fmt.Sprintf("index %d out of array range %d", index, len(a.array)))
|
||||
return gerror.NewCodef(gcode.CodeInvalidParameter, "index %d out of array range %d", index, len(a.array))
|
||||
}
|
||||
a.array[index] = value
|
||||
return nil
|
||||
}
|
||||
|
||||
// SetArray sets the underlying slice array with the given <array>.
|
||||
// SetArray sets the underlying slice array with the given `array`.
|
||||
func (a *IntArray) SetArray(array []int) *IntArray {
|
||||
a.mu.Lock()
|
||||
defer a.mu.Unlock()
|
||||
@ -111,7 +119,7 @@ func (a *IntArray) SetArray(array []int) *IntArray {
|
||||
return a
|
||||
}
|
||||
|
||||
// Replace replaces the array items by given <array> from the beginning of array.
|
||||
// Replace replaces the array items by given `array` from the beginning of array.
|
||||
func (a *IntArray) Replace(array []int) *IntArray {
|
||||
a.mu.Lock()
|
||||
defer a.mu.Unlock()
|
||||
@ -136,16 +144,13 @@ func (a *IntArray) Sum() (sum int) {
|
||||
}
|
||||
|
||||
// Sort sorts the array in increasing order.
|
||||
// The parameter <reverse> controls whether sort in increasing order(default) or decreasing order.
|
||||
// The parameter `reverse` controls whether sort in increasing order(default) or decreasing order.
|
||||
func (a *IntArray) Sort(reverse ...bool) *IntArray {
|
||||
a.mu.Lock()
|
||||
defer a.mu.Unlock()
|
||||
if len(reverse) > 0 && reverse[0] {
|
||||
sort.Slice(a.array, func(i, j int) bool {
|
||||
if a.array[i] < a.array[j] {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
return a.array[i] >= a.array[j]
|
||||
})
|
||||
} else {
|
||||
sort.Ints(a.array)
|
||||
@ -153,7 +158,7 @@ func (a *IntArray) Sort(reverse ...bool) *IntArray {
|
||||
return a
|
||||
}
|
||||
|
||||
// SortFunc sorts the array by custom function <less>.
|
||||
// SortFunc sorts the array by custom function `less`.
|
||||
func (a *IntArray) SortFunc(less func(v1, v2 int) bool) *IntArray {
|
||||
a.mu.Lock()
|
||||
defer a.mu.Unlock()
|
||||
@ -163,12 +168,12 @@ func (a *IntArray) SortFunc(less func(v1, v2 int) bool) *IntArray {
|
||||
return a
|
||||
}
|
||||
|
||||
// InsertBefore inserts the <value> to the front of <index>.
|
||||
// InsertBefore inserts the `value` to the front of `index`.
|
||||
func (a *IntArray) InsertBefore(index int, value int) error {
|
||||
a.mu.Lock()
|
||||
defer a.mu.Unlock()
|
||||
if index < 0 || index >= len(a.array) {
|
||||
return errors.New(fmt.Sprintf("index %d out of array range %d", index, len(a.array)))
|
||||
return gerror.NewCodef(gcode.CodeInvalidParameter, "index %d out of array range %d", index, len(a.array))
|
||||
}
|
||||
rear := append([]int{}, a.array[index:]...)
|
||||
a.array = append(a.array[0:index], value)
|
||||
@ -176,12 +181,12 @@ func (a *IntArray) InsertBefore(index int, value int) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// InsertAfter inserts the <value> to the back of <index>.
|
||||
// InsertAfter inserts the `value` to the back of `index`.
|
||||
func (a *IntArray) InsertAfter(index int, value int) error {
|
||||
a.mu.Lock()
|
||||
defer a.mu.Unlock()
|
||||
if index < 0 || index >= len(a.array) {
|
||||
return errors.New(fmt.Sprintf("index %d out of array range %d", index, len(a.array)))
|
||||
return gerror.NewCodef(gcode.CodeInvalidParameter, "index %d out of array range %d", index, len(a.array))
|
||||
}
|
||||
rear := append([]int{}, a.array[index+1:]...)
|
||||
a.array = append(a.array[0:index+1], value)
|
||||
@ -190,7 +195,7 @@ func (a *IntArray) InsertAfter(index int, value int) error {
|
||||
}
|
||||
|
||||
// Remove removes an item by index.
|
||||
// If the given <index> is out of range of the array, the <found> is false.
|
||||
// If the given `index` is out of range of the array, the `found` is false.
|
||||
func (a *IntArray) Remove(index int) (value int, found bool) {
|
||||
a.mu.Lock()
|
||||
defer a.mu.Unlock()
|
||||
@ -248,7 +253,7 @@ func (a *IntArray) PushRight(value ...int) *IntArray {
|
||||
}
|
||||
|
||||
// PopLeft pops and returns an item from the beginning of array.
|
||||
// Note that if the array is empty, the <found> is false.
|
||||
// Note that if the array is empty, the `found` is false.
|
||||
func (a *IntArray) PopLeft() (value int, found bool) {
|
||||
a.mu.Lock()
|
||||
defer a.mu.Unlock()
|
||||
@ -261,7 +266,7 @@ func (a *IntArray) PopLeft() (value int, found bool) {
|
||||
}
|
||||
|
||||
// PopRight pops and returns an item from the end of array.
|
||||
// Note that if the array is empty, the <found> is false.
|
||||
// Note that if the array is empty, the `found` is false.
|
||||
func (a *IntArray) PopRight() (value int, found bool) {
|
||||
a.mu.Lock()
|
||||
defer a.mu.Unlock()
|
||||
@ -275,16 +280,16 @@ func (a *IntArray) PopRight() (value int, found bool) {
|
||||
}
|
||||
|
||||
// PopRand randomly pops and return an item out of array.
|
||||
// Note that if the array is empty, the <found> is false.
|
||||
// Note that if the array is empty, the `found` is false.
|
||||
func (a *IntArray) PopRand() (value int, found bool) {
|
||||
a.mu.Lock()
|
||||
defer a.mu.Unlock()
|
||||
return a.doRemoveWithoutLock(grand.Intn(len(a.array)))
|
||||
}
|
||||
|
||||
// PopRands randomly pops and returns <size> items out of array.
|
||||
// If the given <size> is greater than size of the array, it returns all elements of the array.
|
||||
// Note that if given <size> <= 0 or the array is empty, it returns nil.
|
||||
// PopRands randomly pops and returns `size` items out of array.
|
||||
// If the given `size` is greater than size of the array, it returns all elements of the array.
|
||||
// Note that if given `size` <= 0 or the array is empty, it returns nil.
|
||||
func (a *IntArray) PopRands(size int) []int {
|
||||
a.mu.Lock()
|
||||
defer a.mu.Unlock()
|
||||
@ -301,9 +306,9 @@ func (a *IntArray) PopRands(size int) []int {
|
||||
return array
|
||||
}
|
||||
|
||||
// PopLefts pops and returns <size> items from the beginning of array.
|
||||
// If the given <size> is greater than size of the array, it returns all elements of the array.
|
||||
// Note that if given <size> <= 0 or the array is empty, it returns nil.
|
||||
// PopLefts pops and returns `size` items from the beginning of array.
|
||||
// If the given `size` is greater than size of the array, it returns all elements of the array.
|
||||
// Note that if given `size` <= 0 or the array is empty, it returns nil.
|
||||
func (a *IntArray) PopLefts(size int) []int {
|
||||
a.mu.Lock()
|
||||
defer a.mu.Unlock()
|
||||
@ -320,9 +325,9 @@ func (a *IntArray) PopLefts(size int) []int {
|
||||
return value
|
||||
}
|
||||
|
||||
// PopRights pops and returns <size> items from the end of array.
|
||||
// If the given <size> is greater than size of the array, it returns all elements of the array.
|
||||
// Note that if given <size> <= 0 or the array is empty, it returns nil.
|
||||
// PopRights pops and returns `size` items from the end of array.
|
||||
// If the given `size` is greater than size of the array, it returns all elements of the array.
|
||||
// Note that if given `size` <= 0 or the array is empty, it returns nil.
|
||||
func (a *IntArray) PopRights(size int) []int {
|
||||
a.mu.Lock()
|
||||
defer a.mu.Unlock()
|
||||
@ -344,8 +349,8 @@ func (a *IntArray) PopRights(size int) []int {
|
||||
// Notice, if in concurrent-safe usage, it returns a copy of slice;
|
||||
// else a pointer to the underlying data.
|
||||
//
|
||||
// If <end> is negative, then the offset will start from the end of array.
|
||||
// If <end> is omitted, then the sequence will have everything from start up
|
||||
// If `end` is negative, then the offset will start from the end of array.
|
||||
// If `end` is omitted, then the sequence will have everything from start up
|
||||
// until the end of the array.
|
||||
func (a *IntArray) Range(start int, end ...int) []int {
|
||||
a.mu.RLock()
|
||||
@ -371,7 +376,7 @@ func (a *IntArray) Range(start int, end ...int) []int {
|
||||
}
|
||||
|
||||
// SubSlice returns a slice of elements from the array as specified
|
||||
// by the <offset> and <size> parameters.
|
||||
// by the `offset` and `size` parameters.
|
||||
// If in concurrent safe usage, it returns a copy of the slice; else a pointer.
|
||||
//
|
||||
// If offset is non-negative, the sequence will start at that offset in the array.
|
||||
@ -420,7 +425,7 @@ func (a *IntArray) SubSlice(offset int, length ...int) []int {
|
||||
}
|
||||
}
|
||||
|
||||
// See PushRight.
|
||||
// Append is alias of PushRight,please See PushRight.
|
||||
func (a *IntArray) Append(value ...int) *IntArray {
|
||||
a.mu.Lock()
|
||||
a.array = append(a.array, value...)
|
||||
@ -487,7 +492,7 @@ func (a *IntArray) Contains(value int) bool {
|
||||
return a.Search(value) != -1
|
||||
}
|
||||
|
||||
// Search searches array by <value>, returns the index of <value>,
|
||||
// Search searches array by `value`, returns the index of `value`,
|
||||
// or returns -1 if not exists.
|
||||
func (a *IntArray) Search(value int) int {
|
||||
a.mu.RLock()
|
||||
@ -522,7 +527,7 @@ func (a *IntArray) Unique() *IntArray {
|
||||
return a
|
||||
}
|
||||
|
||||
// LockFunc locks writing by callback function <f>.
|
||||
// LockFunc locks writing by callback function `f`.
|
||||
func (a *IntArray) LockFunc(f func(array []int)) *IntArray {
|
||||
a.mu.Lock()
|
||||
defer a.mu.Unlock()
|
||||
@ -530,7 +535,7 @@ func (a *IntArray) LockFunc(f func(array []int)) *IntArray {
|
||||
return a
|
||||
}
|
||||
|
||||
// RLockFunc locks reading by callback function <f>.
|
||||
// RLockFunc locks reading by callback function `f`.
|
||||
func (a *IntArray) RLockFunc(f func(array []int)) *IntArray {
|
||||
a.mu.RLock()
|
||||
defer a.mu.RUnlock()
|
||||
@ -538,21 +543,21 @@ func (a *IntArray) RLockFunc(f func(array []int)) *IntArray {
|
||||
return a
|
||||
}
|
||||
|
||||
// Merge merges <array> into current array.
|
||||
// The parameter <array> can be any garray or slice type.
|
||||
// Merge merges `array` into current array.
|
||||
// The parameter `array` can be any garray or slice type.
|
||||
// The difference between Merge and Append is Append supports only specified slice type,
|
||||
// but Merge supports more parameter types.
|
||||
func (a *IntArray) Merge(array interface{}) *IntArray {
|
||||
return a.Append(gconv.Ints(array)...)
|
||||
}
|
||||
|
||||
// Fill fills an array with num entries of the value <value>,
|
||||
// keys starting at the <startIndex> parameter.
|
||||
// Fill fills an array with num entries of the value `value`,
|
||||
// keys starting at the `startIndex` parameter.
|
||||
func (a *IntArray) Fill(startIndex int, num int, value int) error {
|
||||
a.mu.Lock()
|
||||
defer a.mu.Unlock()
|
||||
if startIndex < 0 || startIndex > len(a.array) {
|
||||
return errors.New(fmt.Sprintf("index %d out of array range %d", startIndex, len(a.array)))
|
||||
return gerror.NewCodef(gcode.CodeInvalidParameter, "index %d out of array range %d", startIndex, len(a.array))
|
||||
}
|
||||
for i := startIndex; i < startIndex+num; i++ {
|
||||
if i > len(a.array)-1 {
|
||||
@ -565,7 +570,7 @@ func (a *IntArray) Fill(startIndex int, num int, value int) error {
|
||||
}
|
||||
|
||||
// Chunk splits an array into multiple arrays,
|
||||
// the size of each array is determined by <size>.
|
||||
// the size of each array is determined by `size`.
|
||||
// The last chunk may contain less than size elements.
|
||||
func (a *IntArray) Chunk(size int) [][]int {
|
||||
if size < 1 {
|
||||
@ -587,9 +592,9 @@ func (a *IntArray) Chunk(size int) [][]int {
|
||||
return n
|
||||
}
|
||||
|
||||
// Pad pads array to the specified length with <value>.
|
||||
// Pad pads array to the specified length with `value`.
|
||||
// If size is positive then the array is padded on the right, or negative on the left.
|
||||
// If the absolute value of <size> is less than or equal to the length of the array
|
||||
// If the absolute value of `size` is less than or equal to the length of the array
|
||||
// then no padding takes place.
|
||||
func (a *IntArray) Pad(size int, value int) *IntArray {
|
||||
a.mu.Lock()
|
||||
@ -624,7 +629,7 @@ func (a *IntArray) Rand() (value int, found bool) {
|
||||
return a.array[grand.Intn(len(a.array))], true
|
||||
}
|
||||
|
||||
// Rands randomly returns <size> items from array(no deleting).
|
||||
// Rands randomly returns `size` items from array(no deleting).
|
||||
func (a *IntArray) Rands(size int) []int {
|
||||
a.mu.RLock()
|
||||
defer a.mu.RUnlock()
|
||||
@ -658,7 +663,7 @@ func (a *IntArray) Reverse() *IntArray {
|
||||
return a
|
||||
}
|
||||
|
||||
// Join joins array elements with a string <glue>.
|
||||
// Join joins array elements with a string `glue`.
|
||||
func (a *IntArray) Join(glue string) string {
|
||||
a.mu.RLock()
|
||||
defer a.mu.RUnlock()
|
||||
@ -691,8 +696,8 @@ func (a *IntArray) Iterator(f func(k int, v int) bool) {
|
||||
a.IteratorAsc(f)
|
||||
}
|
||||
|
||||
// IteratorAsc iterates the array readonly in ascending order with given callback function <f>.
|
||||
// If <f> returns true, then it continues iterating; or false to stop.
|
||||
// IteratorAsc iterates the array readonly in ascending order with given callback function `f`.
|
||||
// If `f` returns true, then it continues iterating; or false to stop.
|
||||
func (a *IntArray) IteratorAsc(f func(k int, v int) bool) {
|
||||
a.mu.RLock()
|
||||
defer a.mu.RUnlock()
|
||||
@ -703,8 +708,8 @@ func (a *IntArray) IteratorAsc(f func(k int, v int) bool) {
|
||||
}
|
||||
}
|
||||
|
||||
// IteratorDesc iterates the array readonly in descending order with given callback function <f>.
|
||||
// If <f> returns true, then it continues iterating; or false to stop.
|
||||
// IteratorDesc iterates the array readonly in descending order with given callback function `f`.
|
||||
// If `f` returns true, then it continues iterating; or false to stop.
|
||||
func (a *IntArray) IteratorDesc(f func(k int, v int) bool) {
|
||||
a.mu.RLock()
|
||||
defer a.mu.RUnlock()
|
||||
@ -768,7 +773,7 @@ func (a *IntArray) FilterEmpty() *IntArray {
|
||||
return a
|
||||
}
|
||||
|
||||
// Walk applies a user supplied function <f> to every item of array.
|
||||
// Walk applies a user supplied function `f` to every item of array.
|
||||
func (a *IntArray) Walk(f func(value int) int) *IntArray {
|
||||
a.mu.Lock()
|
||||
defer a.mu.Unlock()
|
||||
|
||||
@ -8,8 +8,8 @@ package garray
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"errors"
|
||||
"fmt"
|
||||
"github.com/gogf/gf/errors/gcode"
|
||||
"github.com/gogf/gf/errors/gerror"
|
||||
"github.com/gogf/gf/internal/json"
|
||||
"github.com/gogf/gf/text/gstr"
|
||||
"math"
|
||||
@ -30,14 +30,14 @@ type StrArray struct {
|
||||
}
|
||||
|
||||
// NewStrArray creates and returns an empty array.
|
||||
// The parameter <safe> is used to specify whether using array in concurrent-safety,
|
||||
// The parameter `safe` is used to specify whether using array in concurrent-safety,
|
||||
// which is false in default.
|
||||
func NewStrArray(safe ...bool) *StrArray {
|
||||
return NewStrArraySize(0, 0, safe...)
|
||||
}
|
||||
|
||||
// NewStrArraySize create and returns an array with given size and cap.
|
||||
// The parameter <safe> is used to specify whether using array in concurrent-safety,
|
||||
// The parameter `safe` is used to specify whether using array in concurrent-safety,
|
||||
// which is false in default.
|
||||
func NewStrArraySize(size int, cap int, safe ...bool) *StrArray {
|
||||
return &StrArray{
|
||||
@ -46,8 +46,8 @@ func NewStrArraySize(size int, cap int, safe ...bool) *StrArray {
|
||||
}
|
||||
}
|
||||
|
||||
// NewStrArrayFrom creates and returns an array with given slice <array>.
|
||||
// The parameter <safe> is used to specify whether using array in concurrent-safety,
|
||||
// NewStrArrayFrom creates and returns an array with given slice `array`.
|
||||
// The parameter `safe` is used to specify whether using array in concurrent-safety,
|
||||
// which is false in default.
|
||||
func NewStrArrayFrom(array []string, safe ...bool) *StrArray {
|
||||
return &StrArray{
|
||||
@ -56,8 +56,8 @@ func NewStrArrayFrom(array []string, safe ...bool) *StrArray {
|
||||
}
|
||||
}
|
||||
|
||||
// NewStrArrayFromCopy creates and returns an array from a copy of given slice <array>.
|
||||
// The parameter <safe> is used to specify whether using array in concurrent-safety,
|
||||
// NewStrArrayFromCopy creates and returns an array from a copy of given slice `array`.
|
||||
// The parameter `safe` is used to specify whether using array in concurrent-safety,
|
||||
// which is false in default.
|
||||
func NewStrArrayFromCopy(array []string, safe ...bool) *StrArray {
|
||||
newArray := make([]string, len(array))
|
||||
@ -68,8 +68,15 @@ func NewStrArrayFromCopy(array []string, safe ...bool) *StrArray {
|
||||
}
|
||||
}
|
||||
|
||||
// At returns the value by the specified index.
|
||||
// If the given `index` is out of range of the array, it returns an empty string.
|
||||
func (a *StrArray) At(index int) (value string) {
|
||||
value, _ = a.Get(index)
|
||||
return
|
||||
}
|
||||
|
||||
// Get returns the value by the specified index.
|
||||
// If the given <index> is out of range of the array, the <found> is false.
|
||||
// If the given `index` is out of range of the array, the `found` is false.
|
||||
func (a *StrArray) Get(index int) (value string, found bool) {
|
||||
a.mu.RLock()
|
||||
defer a.mu.RUnlock()
|
||||
@ -84,13 +91,13 @@ func (a *StrArray) Set(index int, value string) error {
|
||||
a.mu.Lock()
|
||||
defer a.mu.Unlock()
|
||||
if index < 0 || index >= len(a.array) {
|
||||
return errors.New(fmt.Sprintf("index %d out of array range %d", index, len(a.array)))
|
||||
return gerror.NewCodef(gcode.CodeInvalidParameter, "index %d out of array range %d", index, len(a.array))
|
||||
}
|
||||
a.array[index] = value
|
||||
return nil
|
||||
}
|
||||
|
||||
// SetArray sets the underlying slice array with the given <array>.
|
||||
// SetArray sets the underlying slice array with the given `array`.
|
||||
func (a *StrArray) SetArray(array []string) *StrArray {
|
||||
a.mu.Lock()
|
||||
defer a.mu.Unlock()
|
||||
@ -98,7 +105,7 @@ func (a *StrArray) SetArray(array []string) *StrArray {
|
||||
return a
|
||||
}
|
||||
|
||||
// Replace replaces the array items by given <array> from the beginning of array.
|
||||
// Replace replaces the array items by given `array` from the beginning of array.
|
||||
func (a *StrArray) Replace(array []string) *StrArray {
|
||||
a.mu.Lock()
|
||||
defer a.mu.Unlock()
|
||||
@ -123,17 +130,14 @@ func (a *StrArray) Sum() (sum int) {
|
||||
}
|
||||
|
||||
// Sort sorts the array in increasing order.
|
||||
// The parameter <reverse> controls whether sort
|
||||
// The parameter `reverse` controls whether sort
|
||||
// in increasing order(default) or decreasing order
|
||||
func (a *StrArray) Sort(reverse ...bool) *StrArray {
|
||||
a.mu.Lock()
|
||||
defer a.mu.Unlock()
|
||||
if len(reverse) > 0 && reverse[0] {
|
||||
sort.Slice(a.array, func(i, j int) bool {
|
||||
if strings.Compare(a.array[i], a.array[j]) < 0 {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
return strings.Compare(a.array[i], a.array[j]) >= 0
|
||||
})
|
||||
} else {
|
||||
sort.Strings(a.array)
|
||||
@ -141,7 +145,7 @@ func (a *StrArray) Sort(reverse ...bool) *StrArray {
|
||||
return a
|
||||
}
|
||||
|
||||
// SortFunc sorts the array by custom function <less>.
|
||||
// SortFunc sorts the array by custom function `less`.
|
||||
func (a *StrArray) SortFunc(less func(v1, v2 string) bool) *StrArray {
|
||||
a.mu.Lock()
|
||||
defer a.mu.Unlock()
|
||||
@ -151,12 +155,12 @@ func (a *StrArray) SortFunc(less func(v1, v2 string) bool) *StrArray {
|
||||
return a
|
||||
}
|
||||
|
||||
// InsertBefore inserts the <value> to the front of <index>.
|
||||
// InsertBefore inserts the `value` to the front of `index`.
|
||||
func (a *StrArray) InsertBefore(index int, value string) error {
|
||||
a.mu.Lock()
|
||||
defer a.mu.Unlock()
|
||||
if index < 0 || index >= len(a.array) {
|
||||
return errors.New(fmt.Sprintf("index %d out of array range %d", index, len(a.array)))
|
||||
return gerror.NewCodef(gcode.CodeInvalidParameter, "index %d out of array range %d", index, len(a.array))
|
||||
}
|
||||
rear := append([]string{}, a.array[index:]...)
|
||||
a.array = append(a.array[0:index], value)
|
||||
@ -164,12 +168,12 @@ func (a *StrArray) InsertBefore(index int, value string) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// InsertAfter inserts the <value> to the back of <index>.
|
||||
// InsertAfter inserts the `value` to the back of `index`.
|
||||
func (a *StrArray) InsertAfter(index int, value string) error {
|
||||
a.mu.Lock()
|
||||
defer a.mu.Unlock()
|
||||
if index < 0 || index >= len(a.array) {
|
||||
return errors.New(fmt.Sprintf("index %d out of array range %d", index, len(a.array)))
|
||||
return gerror.NewCodef(gcode.CodeInvalidParameter, "index %d out of array range %d", index, len(a.array))
|
||||
}
|
||||
rear := append([]string{}, a.array[index+1:]...)
|
||||
a.array = append(a.array[0:index+1], value)
|
||||
@ -178,7 +182,7 @@ func (a *StrArray) InsertAfter(index int, value string) error {
|
||||
}
|
||||
|
||||
// Remove removes an item by index.
|
||||
// If the given <index> is out of range of the array, the <found> is false.
|
||||
// If the given `index` is out of range of the array, the `found` is false.
|
||||
func (a *StrArray) Remove(index int) (value string, found bool) {
|
||||
a.mu.Lock()
|
||||
defer a.mu.Unlock()
|
||||
@ -236,7 +240,7 @@ func (a *StrArray) PushRight(value ...string) *StrArray {
|
||||
}
|
||||
|
||||
// PopLeft pops and returns an item from the beginning of array.
|
||||
// Note that if the array is empty, the <found> is false.
|
||||
// Note that if the array is empty, the `found` is false.
|
||||
func (a *StrArray) PopLeft() (value string, found bool) {
|
||||
a.mu.Lock()
|
||||
defer a.mu.Unlock()
|
||||
@ -249,7 +253,7 @@ func (a *StrArray) PopLeft() (value string, found bool) {
|
||||
}
|
||||
|
||||
// PopRight pops and returns an item from the end of array.
|
||||
// Note that if the array is empty, the <found> is false.
|
||||
// Note that if the array is empty, the `found` is false.
|
||||
func (a *StrArray) PopRight() (value string, found bool) {
|
||||
a.mu.Lock()
|
||||
defer a.mu.Unlock()
|
||||
@ -263,16 +267,16 @@ func (a *StrArray) PopRight() (value string, found bool) {
|
||||
}
|
||||
|
||||
// PopRand randomly pops and return an item out of array.
|
||||
// Note that if the array is empty, the <found> is false.
|
||||
// Note that if the array is empty, the `found` is false.
|
||||
func (a *StrArray) PopRand() (value string, found bool) {
|
||||
a.mu.Lock()
|
||||
defer a.mu.Unlock()
|
||||
return a.doRemoveWithoutLock(grand.Intn(len(a.array)))
|
||||
}
|
||||
|
||||
// PopRands randomly pops and returns <size> items out of array.
|
||||
// If the given <size> is greater than size of the array, it returns all elements of the array.
|
||||
// Note that if given <size> <= 0 or the array is empty, it returns nil.
|
||||
// PopRands randomly pops and returns `size` items out of array.
|
||||
// If the given `size` is greater than size of the array, it returns all elements of the array.
|
||||
// Note that if given `size` <= 0 or the array is empty, it returns nil.
|
||||
func (a *StrArray) PopRands(size int) []string {
|
||||
a.mu.Lock()
|
||||
defer a.mu.Unlock()
|
||||
@ -289,9 +293,9 @@ func (a *StrArray) PopRands(size int) []string {
|
||||
return array
|
||||
}
|
||||
|
||||
// PopLefts pops and returns <size> items from the beginning of array.
|
||||
// If the given <size> is greater than size of the array, it returns all elements of the array.
|
||||
// Note that if given <size> <= 0 or the array is empty, it returns nil.
|
||||
// PopLefts pops and returns `size` items from the beginning of array.
|
||||
// If the given `size` is greater than size of the array, it returns all elements of the array.
|
||||
// Note that if given `size` <= 0 or the array is empty, it returns nil.
|
||||
func (a *StrArray) PopLefts(size int) []string {
|
||||
a.mu.Lock()
|
||||
defer a.mu.Unlock()
|
||||
@ -308,9 +312,9 @@ func (a *StrArray) PopLefts(size int) []string {
|
||||
return value
|
||||
}
|
||||
|
||||
// PopRights pops and returns <size> items from the end of array.
|
||||
// If the given <size> is greater than size of the array, it returns all elements of the array.
|
||||
// Note that if given <size> <= 0 or the array is empty, it returns nil.
|
||||
// PopRights pops and returns `size` items from the end of array.
|
||||
// If the given `size` is greater than size of the array, it returns all elements of the array.
|
||||
// Note that if given `size` <= 0 or the array is empty, it returns nil.
|
||||
func (a *StrArray) PopRights(size int) []string {
|
||||
a.mu.Lock()
|
||||
defer a.mu.Unlock()
|
||||
@ -332,8 +336,8 @@ func (a *StrArray) PopRights(size int) []string {
|
||||
// Notice, if in concurrent-safe usage, it returns a copy of slice;
|
||||
// else a pointer to the underlying data.
|
||||
//
|
||||
// If <end> is negative, then the offset will start from the end of array.
|
||||
// If <end> is omitted, then the sequence will have everything from start up
|
||||
// If `end` is negative, then the offset will start from the end of array.
|
||||
// If `end` is omitted, then the sequence will have everything from start up
|
||||
// until the end of the array.
|
||||
func (a *StrArray) Range(start int, end ...int) []string {
|
||||
a.mu.RLock()
|
||||
@ -359,7 +363,7 @@ func (a *StrArray) Range(start int, end ...int) []string {
|
||||
}
|
||||
|
||||
// SubSlice returns a slice of elements from the array as specified
|
||||
// by the <offset> and <size> parameters.
|
||||
// by the `offset` and `size` parameters.
|
||||
// If in concurrent safe usage, it returns a copy of the slice; else a pointer.
|
||||
//
|
||||
// If offset is non-negative, the sequence will start at that offset in the array.
|
||||
@ -408,7 +412,7 @@ func (a *StrArray) SubSlice(offset int, length ...int) []string {
|
||||
}
|
||||
}
|
||||
|
||||
// See PushRight.
|
||||
// Append is alias of PushRight,please See PushRight.
|
||||
func (a *StrArray) Append(value ...string) *StrArray {
|
||||
a.mu.Lock()
|
||||
a.array = append(a.array, value...)
|
||||
@ -491,7 +495,7 @@ func (a *StrArray) ContainsI(value string) bool {
|
||||
return false
|
||||
}
|
||||
|
||||
// Search searches array by <value>, returns the index of <value>,
|
||||
// Search searches array by `value`, returns the index of `value`,
|
||||
// or returns -1 if not exists.
|
||||
func (a *StrArray) Search(value string) int {
|
||||
a.mu.RLock()
|
||||
@ -526,7 +530,7 @@ func (a *StrArray) Unique() *StrArray {
|
||||
return a
|
||||
}
|
||||
|
||||
// LockFunc locks writing by callback function <f>.
|
||||
// LockFunc locks writing by callback function `f`.
|
||||
func (a *StrArray) LockFunc(f func(array []string)) *StrArray {
|
||||
a.mu.Lock()
|
||||
defer a.mu.Unlock()
|
||||
@ -534,7 +538,7 @@ func (a *StrArray) LockFunc(f func(array []string)) *StrArray {
|
||||
return a
|
||||
}
|
||||
|
||||
// RLockFunc locks reading by callback function <f>.
|
||||
// RLockFunc locks reading by callback function `f`.
|
||||
func (a *StrArray) RLockFunc(f func(array []string)) *StrArray {
|
||||
a.mu.RLock()
|
||||
defer a.mu.RUnlock()
|
||||
@ -542,21 +546,21 @@ func (a *StrArray) RLockFunc(f func(array []string)) *StrArray {
|
||||
return a
|
||||
}
|
||||
|
||||
// Merge merges <array> into current array.
|
||||
// The parameter <array> can be any garray or slice type.
|
||||
// Merge merges `array` into current array.
|
||||
// The parameter `array` can be any garray or slice type.
|
||||
// The difference between Merge and Append is Append supports only specified slice type,
|
||||
// but Merge supports more parameter types.
|
||||
func (a *StrArray) Merge(array interface{}) *StrArray {
|
||||
return a.Append(gconv.Strings(array)...)
|
||||
}
|
||||
|
||||
// Fill fills an array with num entries of the value <value>,
|
||||
// keys starting at the <startIndex> parameter.
|
||||
// Fill fills an array with num entries of the value `value`,
|
||||
// keys starting at the `startIndex` parameter.
|
||||
func (a *StrArray) Fill(startIndex int, num int, value string) error {
|
||||
a.mu.Lock()
|
||||
defer a.mu.Unlock()
|
||||
if startIndex < 0 || startIndex > len(a.array) {
|
||||
return errors.New(fmt.Sprintf("index %d out of array range %d", startIndex, len(a.array)))
|
||||
return gerror.NewCodef(gcode.CodeInvalidParameter, "index %d out of array range %d", startIndex, len(a.array))
|
||||
}
|
||||
for i := startIndex; i < startIndex+num; i++ {
|
||||
if i > len(a.array)-1 {
|
||||
@ -569,7 +573,7 @@ func (a *StrArray) Fill(startIndex int, num int, value string) error {
|
||||
}
|
||||
|
||||
// Chunk splits an array into multiple arrays,
|
||||
// the size of each array is determined by <size>.
|
||||
// the size of each array is determined by `size`.
|
||||
// The last chunk may contain less than size elements.
|
||||
func (a *StrArray) Chunk(size int) [][]string {
|
||||
if size < 1 {
|
||||
@ -591,9 +595,9 @@ func (a *StrArray) Chunk(size int) [][]string {
|
||||
return n
|
||||
}
|
||||
|
||||
// Pad pads array to the specified length with <value>.
|
||||
// Pad pads array to the specified length with `value`.
|
||||
// If size is positive then the array is padded on the right, or negative on the left.
|
||||
// If the absolute value of <size> is less than or equal to the length of the array
|
||||
// If the absolute value of `size` is less than or equal to the length of the array
|
||||
// then no padding takes place.
|
||||
func (a *StrArray) Pad(size int, value string) *StrArray {
|
||||
a.mu.Lock()
|
||||
@ -628,7 +632,7 @@ func (a *StrArray) Rand() (value string, found bool) {
|
||||
return a.array[grand.Intn(len(a.array))], true
|
||||
}
|
||||
|
||||
// Rands randomly returns <size> items from array(no deleting).
|
||||
// Rands randomly returns `size` items from array(no deleting).
|
||||
func (a *StrArray) Rands(size int) []string {
|
||||
a.mu.RLock()
|
||||
defer a.mu.RUnlock()
|
||||
@ -662,7 +666,7 @@ func (a *StrArray) Reverse() *StrArray {
|
||||
return a
|
||||
}
|
||||
|
||||
// Join joins array elements with a string <glue>.
|
||||
// Join joins array elements with a string `glue`.
|
||||
func (a *StrArray) Join(glue string) string {
|
||||
a.mu.RLock()
|
||||
defer a.mu.RUnlock()
|
||||
@ -695,8 +699,8 @@ func (a *StrArray) Iterator(f func(k int, v string) bool) {
|
||||
a.IteratorAsc(f)
|
||||
}
|
||||
|
||||
// IteratorAsc iterates the array readonly in ascending order with given callback function <f>.
|
||||
// If <f> returns true, then it continues iterating; or false to stop.
|
||||
// IteratorAsc iterates the array readonly in ascending order with given callback function `f`.
|
||||
// If `f` returns true, then it continues iterating; or false to stop.
|
||||
func (a *StrArray) IteratorAsc(f func(k int, v string) bool) {
|
||||
a.mu.RLock()
|
||||
defer a.mu.RUnlock()
|
||||
@ -707,8 +711,8 @@ func (a *StrArray) IteratorAsc(f func(k int, v string) bool) {
|
||||
}
|
||||
}
|
||||
|
||||
// IteratorDesc iterates the array readonly in descending order with given callback function <f>.
|
||||
// If <f> returns true, then it continues iterating; or false to stop.
|
||||
// IteratorDesc iterates the array readonly in descending order with given callback function `f`.
|
||||
// If `f` returns true, then it continues iterating; or false to stop.
|
||||
func (a *StrArray) IteratorDesc(f func(k int, v string) bool) {
|
||||
a.mu.RLock()
|
||||
defer a.mu.RUnlock()
|
||||
@ -783,7 +787,7 @@ func (a *StrArray) FilterEmpty() *StrArray {
|
||||
return a
|
||||
}
|
||||
|
||||
// Walk applies a user supplied function <f> to every item of array.
|
||||
// Walk applies a user supplied function `f` to every item of array.
|
||||
func (a *StrArray) Walk(f func(value string) string) *StrArray {
|
||||
a.mu.Lock()
|
||||
defer a.mu.Unlock()
|
||||
|
||||
@ -34,8 +34,8 @@ type SortedArray struct {
|
||||
}
|
||||
|
||||
// NewSortedArray creates and returns an empty sorted array.
|
||||
// The parameter <safe> is used to specify whether using array in concurrent-safety, which is false in default.
|
||||
// The parameter <comparator> used to compare values to sort in array,
|
||||
// The parameter `safe` is used to specify whether using array in concurrent-safety, which is false in default.
|
||||
// The parameter `comparator` used to compare values to sort in array,
|
||||
// if it returns value < 0, means v1 < v2; the v1 will be inserted before v2;
|
||||
// if it returns value = 0, means v1 = v2; the v1 will be replaced by v2;
|
||||
// if it returns value > 0, means v1 > v2; the v1 will be inserted after v2;
|
||||
@ -44,7 +44,7 @@ func NewSortedArray(comparator func(a, b interface{}) int, safe ...bool) *Sorted
|
||||
}
|
||||
|
||||
// NewSortedArraySize create and returns an sorted array with given size and cap.
|
||||
// The parameter <safe> is used to specify whether using array in concurrent-safety,
|
||||
// The parameter `safe` is used to specify whether using array in concurrent-safety,
|
||||
// which is false in default.
|
||||
func NewSortedArraySize(cap int, comparator func(a, b interface{}) int, safe ...bool) *SortedArray {
|
||||
return &SortedArray{
|
||||
@ -54,8 +54,8 @@ func NewSortedArraySize(cap int, comparator func(a, b interface{}) int, safe ...
|
||||
}
|
||||
}
|
||||
|
||||
// NewSortedArrayRange creates and returns a array by a range from <start> to <end>
|
||||
// with step value <step>.
|
||||
// NewSortedArrayRange creates and returns a array by a range from `start` to `end`
|
||||
// with step value `step`.
|
||||
func NewSortedArrayRange(start, end, step int, comparator func(a, b interface{}) int, safe ...bool) *SortedArray {
|
||||
if step == 0 {
|
||||
panic(fmt.Sprintf(`invalid step value: %d`, step))
|
||||
@ -69,8 +69,8 @@ func NewSortedArrayRange(start, end, step int, comparator func(a, b interface{})
|
||||
return NewSortedArrayFrom(slice, comparator, safe...)
|
||||
}
|
||||
|
||||
// NewSortedArrayFrom creates and returns an sorted array with given slice <array>.
|
||||
// The parameter <safe> is used to specify whether using array in concurrent-safety,
|
||||
// NewSortedArrayFrom creates and returns an sorted array with given slice `array`.
|
||||
// The parameter `safe` is used to specify whether using array in concurrent-safety,
|
||||
// which is false in default.
|
||||
func NewSortedArrayFrom(array []interface{}, comparator func(a, b interface{}) int, safe ...bool) *SortedArray {
|
||||
a := NewSortedArraySize(0, comparator, safe...)
|
||||
@ -81,8 +81,8 @@ func NewSortedArrayFrom(array []interface{}, comparator func(a, b interface{}) i
|
||||
return a
|
||||
}
|
||||
|
||||
// NewSortedArrayFromCopy creates and returns an sorted array from a copy of given slice <array>.
|
||||
// The parameter <safe> is used to specify whether using array in concurrent-safety,
|
||||
// NewSortedArrayFromCopy creates and returns an sorted array from a copy of given slice `array`.
|
||||
// The parameter `safe` is used to specify whether using array in concurrent-safety,
|
||||
// which is false in default.
|
||||
func NewSortedArrayFromCopy(array []interface{}, comparator func(a, b interface{}) int, safe ...bool) *SortedArray {
|
||||
newArray := make([]interface{}, len(array))
|
||||
@ -90,7 +90,14 @@ func NewSortedArrayFromCopy(array []interface{}, comparator func(a, b interface{
|
||||
return NewSortedArrayFrom(newArray, comparator, safe...)
|
||||
}
|
||||
|
||||
// SetArray sets the underlying slice array with the given <array>.
|
||||
// At returns the value by the specified index.
|
||||
// If the given `index` is out of range of the array, it returns `nil`.
|
||||
func (a *SortedArray) At(index int) (value interface{}) {
|
||||
value, _ = a.Get(index)
|
||||
return
|
||||
}
|
||||
|
||||
// SetArray sets the underlying slice array with the given `array`.
|
||||
func (a *SortedArray) SetArray(array []interface{}) *SortedArray {
|
||||
a.mu.Lock()
|
||||
defer a.mu.Unlock()
|
||||
@ -113,7 +120,7 @@ func (a *SortedArray) SetComparator(comparator func(a, b interface{}) int) {
|
||||
}
|
||||
|
||||
// Sort sorts the array in increasing order.
|
||||
// The parameter <reverse> controls whether sort
|
||||
// The parameter `reverse` controls whether sort
|
||||
// in increasing order(default) or decreasing order
|
||||
func (a *SortedArray) Sort() *SortedArray {
|
||||
a.mu.Lock()
|
||||
@ -149,15 +156,13 @@ func (a *SortedArray) Append(values ...interface{}) *SortedArray {
|
||||
if cmp > 0 {
|
||||
index++
|
||||
}
|
||||
rear := append([]interface{}{}, a.array[index:]...)
|
||||
a.array = append(a.array[0:index], value)
|
||||
a.array = append(a.array, rear...)
|
||||
a.array = append(a.array[:index], append([]interface{}{value}, a.array[index:]...)...)
|
||||
}
|
||||
return a
|
||||
}
|
||||
|
||||
// Get returns the value by the specified index.
|
||||
// If the given <index> is out of range of the array, the <found> is false.
|
||||
// If the given `index` is out of range of the array, the `found` is false.
|
||||
func (a *SortedArray) Get(index int) (value interface{}, found bool) {
|
||||
a.mu.RLock()
|
||||
defer a.mu.RUnlock()
|
||||
@ -168,7 +173,7 @@ func (a *SortedArray) Get(index int) (value interface{}, found bool) {
|
||||
}
|
||||
|
||||
// Remove removes an item by index.
|
||||
// If the given <index> is out of range of the array, the <found> is false.
|
||||
// If the given `index` is out of range of the array, the `found` is false.
|
||||
func (a *SortedArray) Remove(index int) (value interface{}, found bool) {
|
||||
a.mu.Lock()
|
||||
defer a.mu.Unlock()
|
||||
@ -209,7 +214,7 @@ func (a *SortedArray) RemoveValue(value interface{}) bool {
|
||||
}
|
||||
|
||||
// PopLeft pops and returns an item from the beginning of array.
|
||||
// Note that if the array is empty, the <found> is false.
|
||||
// Note that if the array is empty, the `found` is false.
|
||||
func (a *SortedArray) PopLeft() (value interface{}, found bool) {
|
||||
a.mu.Lock()
|
||||
defer a.mu.Unlock()
|
||||
@ -222,7 +227,7 @@ func (a *SortedArray) PopLeft() (value interface{}, found bool) {
|
||||
}
|
||||
|
||||
// PopRight pops and returns an item from the end of array.
|
||||
// Note that if the array is empty, the <found> is false.
|
||||
// Note that if the array is empty, the `found` is false.
|
||||
func (a *SortedArray) PopRight() (value interface{}, found bool) {
|
||||
a.mu.Lock()
|
||||
defer a.mu.Unlock()
|
||||
@ -236,14 +241,14 @@ func (a *SortedArray) PopRight() (value interface{}, found bool) {
|
||||
}
|
||||
|
||||
// PopRand randomly pops and return an item out of array.
|
||||
// Note that if the array is empty, the <found> is false.
|
||||
// Note that if the array is empty, the `found` is false.
|
||||
func (a *SortedArray) PopRand() (value interface{}, found bool) {
|
||||
a.mu.Lock()
|
||||
defer a.mu.Unlock()
|
||||
return a.doRemoveWithoutLock(grand.Intn(len(a.array)))
|
||||
}
|
||||
|
||||
// PopRands randomly pops and returns <size> items out of array.
|
||||
// PopRands randomly pops and returns `size` items out of array.
|
||||
func (a *SortedArray) PopRands(size int) []interface{} {
|
||||
a.mu.Lock()
|
||||
defer a.mu.Unlock()
|
||||
@ -260,7 +265,7 @@ func (a *SortedArray) PopRands(size int) []interface{} {
|
||||
return array
|
||||
}
|
||||
|
||||
// PopLefts pops and returns <size> items from the beginning of array.
|
||||
// PopLefts pops and returns `size` items from the beginning of array.
|
||||
func (a *SortedArray) PopLefts(size int) []interface{} {
|
||||
a.mu.Lock()
|
||||
defer a.mu.Unlock()
|
||||
@ -277,7 +282,7 @@ func (a *SortedArray) PopLefts(size int) []interface{} {
|
||||
return value
|
||||
}
|
||||
|
||||
// PopRights pops and returns <size> items from the end of array.
|
||||
// PopRights pops and returns `size` items from the end of array.
|
||||
func (a *SortedArray) PopRights(size int) []interface{} {
|
||||
a.mu.Lock()
|
||||
defer a.mu.Unlock()
|
||||
@ -299,8 +304,8 @@ func (a *SortedArray) PopRights(size int) []interface{} {
|
||||
// Notice, if in concurrent-safe usage, it returns a copy of slice;
|
||||
// else a pointer to the underlying data.
|
||||
//
|
||||
// If <end> is negative, then the offset will start from the end of array.
|
||||
// If <end> is omitted, then the sequence will have everything from start up
|
||||
// If `end` is negative, then the offset will start from the end of array.
|
||||
// If `end` is omitted, then the sequence will have everything from start up
|
||||
// until the end of the array.
|
||||
func (a *SortedArray) Range(start int, end ...int) []interface{} {
|
||||
a.mu.RLock()
|
||||
@ -326,7 +331,7 @@ func (a *SortedArray) Range(start int, end ...int) []interface{} {
|
||||
}
|
||||
|
||||
// SubSlice returns a slice of elements from the array as specified
|
||||
// by the <offset> and <size> parameters.
|
||||
// by the `offset` and `size` parameters.
|
||||
// If in concurrent safe usage, it returns a copy of the slice; else a pointer.
|
||||
//
|
||||
// If offset is non-negative, the sequence will start at that offset in the array.
|
||||
@ -419,7 +424,7 @@ func (a *SortedArray) Contains(value interface{}) bool {
|
||||
return a.Search(value) != -1
|
||||
}
|
||||
|
||||
// Search searches array by <value>, returns the index of <value>,
|
||||
// Search searches array by `value`, returns the index of `value`,
|
||||
// or returns -1 if not exists.
|
||||
func (a *SortedArray) Search(value interface{}) (index int) {
|
||||
if i, r := a.binSearch(value, true); r == 0 {
|
||||
@ -430,9 +435,9 @@ func (a *SortedArray) Search(value interface{}) (index int) {
|
||||
|
||||
// Binary search.
|
||||
// It returns the last compared index and the result.
|
||||
// If <result> equals to 0, it means the value at <index> is equals to <value>.
|
||||
// If <result> lesser than 0, it means the value at <index> is lesser than <value>.
|
||||
// If <result> greater than 0, it means the value at <index> is greater than <value>.
|
||||
// If `result` equals to 0, it means the value at `index` is equals to `value`.
|
||||
// If `result` lesser than 0, it means the value at `index` is lesser than `value`.
|
||||
// If `result` greater than 0, it means the value at `index` is greater than `value`.
|
||||
func (a *SortedArray) binSearch(value interface{}, lock bool) (index int, result int) {
|
||||
if lock {
|
||||
a.mu.RLock()
|
||||
@ -446,7 +451,7 @@ func (a *SortedArray) binSearch(value interface{}, lock bool) (index int, result
|
||||
mid := 0
|
||||
cmp := -2
|
||||
for min <= max {
|
||||
mid = min + int((max-min)/2)
|
||||
mid = min + (max-min)/2
|
||||
cmp = a.getComparator()(value, a.array[mid])
|
||||
switch {
|
||||
case cmp < 0:
|
||||
@ -512,7 +517,7 @@ func (a *SortedArray) Clear() *SortedArray {
|
||||
return a
|
||||
}
|
||||
|
||||
// LockFunc locks writing by callback function <f>.
|
||||
// LockFunc locks writing by callback function `f`.
|
||||
func (a *SortedArray) LockFunc(f func(array []interface{})) *SortedArray {
|
||||
a.mu.Lock()
|
||||
defer a.mu.Unlock()
|
||||
@ -526,7 +531,7 @@ func (a *SortedArray) LockFunc(f func(array []interface{})) *SortedArray {
|
||||
return a
|
||||
}
|
||||
|
||||
// RLockFunc locks reading by callback function <f>.
|
||||
// RLockFunc locks reading by callback function `f`.
|
||||
func (a *SortedArray) RLockFunc(f func(array []interface{})) *SortedArray {
|
||||
a.mu.RLock()
|
||||
defer a.mu.RUnlock()
|
||||
@ -534,8 +539,8 @@ func (a *SortedArray) RLockFunc(f func(array []interface{})) *SortedArray {
|
||||
return a
|
||||
}
|
||||
|
||||
// Merge merges <array> into current array.
|
||||
// The parameter <array> can be any garray or slice type.
|
||||
// Merge merges `array` into current array.
|
||||
// The parameter `array` can be any garray or slice type.
|
||||
// The difference between Merge and Append is Append supports only specified slice type,
|
||||
// but Merge supports more parameter types.
|
||||
func (a *SortedArray) Merge(array interface{}) *SortedArray {
|
||||
@ -543,7 +548,7 @@ func (a *SortedArray) Merge(array interface{}) *SortedArray {
|
||||
}
|
||||
|
||||
// Chunk splits an array into multiple arrays,
|
||||
// the size of each array is determined by <size>.
|
||||
// the size of each array is determined by `size`.
|
||||
// The last chunk may contain less than size elements.
|
||||
func (a *SortedArray) Chunk(size int) [][]interface{} {
|
||||
if size < 1 {
|
||||
@ -575,7 +580,7 @@ func (a *SortedArray) Rand() (value interface{}, found bool) {
|
||||
return a.array[grand.Intn(len(a.array))], true
|
||||
}
|
||||
|
||||
// Rands randomly returns <size> items from array(no deleting).
|
||||
// Rands randomly returns `size` items from array(no deleting).
|
||||
func (a *SortedArray) Rands(size int) []interface{} {
|
||||
a.mu.RLock()
|
||||
defer a.mu.RUnlock()
|
||||
@ -589,7 +594,7 @@ func (a *SortedArray) Rands(size int) []interface{} {
|
||||
return array
|
||||
}
|
||||
|
||||
// Join joins array elements with a string <glue>.
|
||||
// Join joins array elements with a string `glue`.
|
||||
func (a *SortedArray) Join(glue string) string {
|
||||
a.mu.RLock()
|
||||
defer a.mu.RUnlock()
|
||||
@ -622,8 +627,8 @@ func (a *SortedArray) Iterator(f func(k int, v interface{}) bool) {
|
||||
a.IteratorAsc(f)
|
||||
}
|
||||
|
||||
// IteratorAsc iterates the array readonly in ascending order with given callback function <f>.
|
||||
// If <f> returns true, then it continues iterating; or false to stop.
|
||||
// IteratorAsc iterates the array readonly in ascending order with given callback function `f`.
|
||||
// If `f` returns true, then it continues iterating; or false to stop.
|
||||
func (a *SortedArray) IteratorAsc(f func(k int, v interface{}) bool) {
|
||||
a.mu.RLock()
|
||||
defer a.mu.RUnlock()
|
||||
@ -634,8 +639,8 @@ func (a *SortedArray) IteratorAsc(f func(k int, v interface{}) bool) {
|
||||
}
|
||||
}
|
||||
|
||||
// IteratorDesc iterates the array readonly in descending order with given callback function <f>.
|
||||
// If <f> returns true, then it continues iterating; or false to stop.
|
||||
// IteratorDesc iterates the array readonly in descending order with given callback function `f`.
|
||||
// If `f` returns true, then it continues iterating; or false to stop.
|
||||
func (a *SortedArray) IteratorDesc(f func(k int, v interface{}) bool) {
|
||||
a.mu.RLock()
|
||||
defer a.mu.RUnlock()
|
||||
@ -761,7 +766,7 @@ func (a *SortedArray) FilterEmpty() *SortedArray {
|
||||
return a
|
||||
}
|
||||
|
||||
// Walk applies a user supplied function <f> to every item of array.
|
||||
// Walk applies a user supplied function `f` to every item of array.
|
||||
func (a *SortedArray) Walk(f func(value interface{}) interface{}) *SortedArray {
|
||||
a.mu.Lock()
|
||||
defer a.mu.Unlock()
|
||||
|
||||
@ -31,14 +31,14 @@ type SortedIntArray struct {
|
||||
}
|
||||
|
||||
// NewSortedIntArray creates and returns an empty sorted array.
|
||||
// The parameter <safe> is used to specify whether using array in concurrent-safety,
|
||||
// The parameter `safe` is used to specify whether using array in concurrent-safety,
|
||||
// which is false in default.
|
||||
func NewSortedIntArray(safe ...bool) *SortedIntArray {
|
||||
return NewSortedIntArraySize(0, safe...)
|
||||
}
|
||||
|
||||
// NewSortedIntArrayComparator creates and returns an empty sorted array with specified comparator.
|
||||
// The parameter <safe> is used to specify whether using array in concurrent-safety which is false in default.
|
||||
// The parameter `safe` is used to specify whether using array in concurrent-safety which is false in default.
|
||||
func NewSortedIntArrayComparator(comparator func(a, b int) int, safe ...bool) *SortedIntArray {
|
||||
array := NewSortedIntArray(safe...)
|
||||
array.comparator = comparator
|
||||
@ -46,7 +46,7 @@ func NewSortedIntArrayComparator(comparator func(a, b int) int, safe ...bool) *S
|
||||
}
|
||||
|
||||
// NewSortedIntArraySize create and returns an sorted array with given size and cap.
|
||||
// The parameter <safe> is used to specify whether using array in concurrent-safety,
|
||||
// The parameter `safe` is used to specify whether using array in concurrent-safety,
|
||||
// which is false in default.
|
||||
func NewSortedIntArraySize(cap int, safe ...bool) *SortedIntArray {
|
||||
return &SortedIntArray{
|
||||
@ -56,8 +56,8 @@ func NewSortedIntArraySize(cap int, safe ...bool) *SortedIntArray {
|
||||
}
|
||||
}
|
||||
|
||||
// NewSortedIntArrayRange creates and returns a array by a range from <start> to <end>
|
||||
// with step value <step>.
|
||||
// NewSortedIntArrayRange creates and returns a array by a range from `start` to `end`
|
||||
// with step value `step`.
|
||||
func NewSortedIntArrayRange(start, end, step int, safe ...bool) *SortedIntArray {
|
||||
if step == 0 {
|
||||
panic(fmt.Sprintf(`invalid step value: %d`, step))
|
||||
@ -71,8 +71,8 @@ func NewSortedIntArrayRange(start, end, step int, safe ...bool) *SortedIntArray
|
||||
return NewSortedIntArrayFrom(slice, safe...)
|
||||
}
|
||||
|
||||
// NewIntArrayFrom creates and returns an sorted array with given slice <array>.
|
||||
// The parameter <safe> is used to specify whether using array in concurrent-safety,
|
||||
// NewSortedIntArrayFrom creates and returns an sorted array with given slice `array`.
|
||||
// The parameter `safe` is used to specify whether using array in concurrent-safety,
|
||||
// which is false in default.
|
||||
func NewSortedIntArrayFrom(array []int, safe ...bool) *SortedIntArray {
|
||||
a := NewSortedIntArraySize(0, safe...)
|
||||
@ -81,8 +81,8 @@ func NewSortedIntArrayFrom(array []int, safe ...bool) *SortedIntArray {
|
||||
return a
|
||||
}
|
||||
|
||||
// NewSortedIntArrayFromCopy creates and returns an sorted array from a copy of given slice <array>.
|
||||
// The parameter <safe> is used to specify whether using array in concurrent-safety,
|
||||
// NewSortedIntArrayFromCopy creates and returns an sorted array from a copy of given slice `array`.
|
||||
// The parameter `safe` is used to specify whether using array in concurrent-safety,
|
||||
// which is false in default.
|
||||
func NewSortedIntArrayFromCopy(array []int, safe ...bool) *SortedIntArray {
|
||||
newArray := make([]int, len(array))
|
||||
@ -90,7 +90,14 @@ func NewSortedIntArrayFromCopy(array []int, safe ...bool) *SortedIntArray {
|
||||
return NewSortedIntArrayFrom(newArray, safe...)
|
||||
}
|
||||
|
||||
// SetArray sets the underlying slice array with the given <array>.
|
||||
// At returns the value by the specified index.
|
||||
// If the given `index` is out of range of the array, it returns `0`.
|
||||
func (a *SortedIntArray) At(index int) (value int) {
|
||||
value, _ = a.Get(index)
|
||||
return
|
||||
}
|
||||
|
||||
// SetArray sets the underlying slice array with the given `array`.
|
||||
func (a *SortedIntArray) SetArray(array []int) *SortedIntArray {
|
||||
a.mu.Lock()
|
||||
defer a.mu.Unlock()
|
||||
@ -100,7 +107,7 @@ func (a *SortedIntArray) SetArray(array []int) *SortedIntArray {
|
||||
}
|
||||
|
||||
// Sort sorts the array in increasing order.
|
||||
// The parameter <reverse> controls whether sort
|
||||
// The parameter `reverse` controls whether sort
|
||||
// in increasing order(default) or decreasing order.
|
||||
func (a *SortedIntArray) Sort() *SortedIntArray {
|
||||
a.mu.Lock()
|
||||
@ -142,7 +149,7 @@ func (a *SortedIntArray) Append(values ...int) *SortedIntArray {
|
||||
}
|
||||
|
||||
// Get returns the value by the specified index.
|
||||
// If the given <index> is out of range of the array, the <found> is false.
|
||||
// If the given `index` is out of range of the array, the `found` is false.
|
||||
func (a *SortedIntArray) Get(index int) (value int, found bool) {
|
||||
a.mu.RLock()
|
||||
defer a.mu.RUnlock()
|
||||
@ -153,7 +160,7 @@ func (a *SortedIntArray) Get(index int) (value int, found bool) {
|
||||
}
|
||||
|
||||
// Remove removes an item by index.
|
||||
// If the given <index> is out of range of the array, the <found> is false.
|
||||
// If the given `index` is out of range of the array, the `found` is false.
|
||||
func (a *SortedIntArray) Remove(index int) (value int, found bool) {
|
||||
a.mu.Lock()
|
||||
defer a.mu.Unlock()
|
||||
@ -194,7 +201,7 @@ func (a *SortedIntArray) RemoveValue(value int) bool {
|
||||
}
|
||||
|
||||
// PopLeft pops and returns an item from the beginning of array.
|
||||
// Note that if the array is empty, the <found> is false.
|
||||
// Note that if the array is empty, the `found` is false.
|
||||
func (a *SortedIntArray) PopLeft() (value int, found bool) {
|
||||
a.mu.Lock()
|
||||
defer a.mu.Unlock()
|
||||
@ -207,7 +214,7 @@ func (a *SortedIntArray) PopLeft() (value int, found bool) {
|
||||
}
|
||||
|
||||
// PopRight pops and returns an item from the end of array.
|
||||
// Note that if the array is empty, the <found> is false.
|
||||
// Note that if the array is empty, the `found` is false.
|
||||
func (a *SortedIntArray) PopRight() (value int, found bool) {
|
||||
a.mu.Lock()
|
||||
defer a.mu.Unlock()
|
||||
@ -221,16 +228,16 @@ func (a *SortedIntArray) PopRight() (value int, found bool) {
|
||||
}
|
||||
|
||||
// PopRand randomly pops and return an item out of array.
|
||||
// Note that if the array is empty, the <found> is false.
|
||||
// Note that if the array is empty, the `found` is false.
|
||||
func (a *SortedIntArray) PopRand() (value int, found bool) {
|
||||
a.mu.Lock()
|
||||
defer a.mu.Unlock()
|
||||
return a.doRemoveWithoutLock(grand.Intn(len(a.array)))
|
||||
}
|
||||
|
||||
// PopRands randomly pops and returns <size> items out of array.
|
||||
// If the given <size> is greater than size of the array, it returns all elements of the array.
|
||||
// Note that if given <size> <= 0 or the array is empty, it returns nil.
|
||||
// PopRands randomly pops and returns `size` items out of array.
|
||||
// If the given `size` is greater than size of the array, it returns all elements of the array.
|
||||
// Note that if given `size` <= 0 or the array is empty, it returns nil.
|
||||
func (a *SortedIntArray) PopRands(size int) []int {
|
||||
a.mu.Lock()
|
||||
defer a.mu.Unlock()
|
||||
@ -247,9 +254,9 @@ func (a *SortedIntArray) PopRands(size int) []int {
|
||||
return array
|
||||
}
|
||||
|
||||
// PopLefts pops and returns <size> items from the beginning of array.
|
||||
// If the given <size> is greater than size of the array, it returns all elements of the array.
|
||||
// Note that if given <size> <= 0 or the array is empty, it returns nil.
|
||||
// PopLefts pops and returns `size` items from the beginning of array.
|
||||
// If the given `size` is greater than size of the array, it returns all elements of the array.
|
||||
// Note that if given `size` <= 0 or the array is empty, it returns nil.
|
||||
func (a *SortedIntArray) PopLefts(size int) []int {
|
||||
a.mu.Lock()
|
||||
defer a.mu.Unlock()
|
||||
@ -266,9 +273,9 @@ func (a *SortedIntArray) PopLefts(size int) []int {
|
||||
return value
|
||||
}
|
||||
|
||||
// PopRights pops and returns <size> items from the end of array.
|
||||
// If the given <size> is greater than size of the array, it returns all elements of the array.
|
||||
// Note that if given <size> <= 0 or the array is empty, it returns nil.
|
||||
// PopRights pops and returns `size` items from the end of array.
|
||||
// If the given `size` is greater than size of the array, it returns all elements of the array.
|
||||
// Note that if given `size` <= 0 or the array is empty, it returns nil.
|
||||
func (a *SortedIntArray) PopRights(size int) []int {
|
||||
a.mu.Lock()
|
||||
defer a.mu.Unlock()
|
||||
@ -290,8 +297,8 @@ func (a *SortedIntArray) PopRights(size int) []int {
|
||||
// Notice, if in concurrent-safe usage, it returns a copy of slice;
|
||||
// else a pointer to the underlying data.
|
||||
//
|
||||
// If <end> is negative, then the offset will start from the end of array.
|
||||
// If <end> is omitted, then the sequence will have everything from start up
|
||||
// If `end` is negative, then the offset will start from the end of array.
|
||||
// If `end` is omitted, then the sequence will have everything from start up
|
||||
// until the end of the array.
|
||||
func (a *SortedIntArray) Range(start int, end ...int) []int {
|
||||
a.mu.RLock()
|
||||
@ -317,7 +324,7 @@ func (a *SortedIntArray) Range(start int, end ...int) []int {
|
||||
}
|
||||
|
||||
// SubSlice returns a slice of elements from the array as specified
|
||||
// by the <offset> and <size> parameters.
|
||||
// by the `offset` and `size` parameters.
|
||||
// If in concurrent safe usage, it returns a copy of the slice; else a pointer.
|
||||
//
|
||||
// If offset is non-negative, the sequence will start at that offset in the array.
|
||||
@ -416,7 +423,7 @@ func (a *SortedIntArray) Contains(value int) bool {
|
||||
return a.Search(value) != -1
|
||||
}
|
||||
|
||||
// Search searches array by <value>, returns the index of <value>,
|
||||
// Search searches array by `value`, returns the index of `value`,
|
||||
// or returns -1 if not exists.
|
||||
func (a *SortedIntArray) Search(value int) (index int) {
|
||||
if i, r := a.binSearch(value, true); r == 0 {
|
||||
@ -427,9 +434,9 @@ func (a *SortedIntArray) Search(value int) (index int) {
|
||||
|
||||
// Binary search.
|
||||
// It returns the last compared index and the result.
|
||||
// If <result> equals to 0, it means the value at <index> is equals to <value>.
|
||||
// If <result> lesser than 0, it means the value at <index> is lesser than <value>.
|
||||
// If <result> greater than 0, it means the value at <index> is greater than <value>.
|
||||
// If `result` equals to 0, it means the value at `index` is equals to `value`.
|
||||
// If `result` lesser than 0, it means the value at `index` is lesser than `value`.
|
||||
// If `result` greater than 0, it means the value at `index` is greater than `value`.
|
||||
func (a *SortedIntArray) binSearch(value int, lock bool) (index int, result int) {
|
||||
if lock {
|
||||
a.mu.RLock()
|
||||
@ -509,7 +516,7 @@ func (a *SortedIntArray) Clear() *SortedIntArray {
|
||||
return a
|
||||
}
|
||||
|
||||
// LockFunc locks writing by callback function <f>.
|
||||
// LockFunc locks writing by callback function `f`.
|
||||
func (a *SortedIntArray) LockFunc(f func(array []int)) *SortedIntArray {
|
||||
a.mu.Lock()
|
||||
defer a.mu.Unlock()
|
||||
@ -517,7 +524,7 @@ func (a *SortedIntArray) LockFunc(f func(array []int)) *SortedIntArray {
|
||||
return a
|
||||
}
|
||||
|
||||
// RLockFunc locks reading by callback function <f>.
|
||||
// RLockFunc locks reading by callback function `f`.
|
||||
func (a *SortedIntArray) RLockFunc(f func(array []int)) *SortedIntArray {
|
||||
a.mu.RLock()
|
||||
defer a.mu.RUnlock()
|
||||
@ -525,8 +532,8 @@ func (a *SortedIntArray) RLockFunc(f func(array []int)) *SortedIntArray {
|
||||
return a
|
||||
}
|
||||
|
||||
// Merge merges <array> into current array.
|
||||
// The parameter <array> can be any garray or slice type.
|
||||
// Merge merges `array` into current array.
|
||||
// The parameter `array` can be any garray or slice type.
|
||||
// The difference between Merge and Append is Append supports only specified slice type,
|
||||
// but Merge supports more parameter types.
|
||||
func (a *SortedIntArray) Merge(array interface{}) *SortedIntArray {
|
||||
@ -534,7 +541,7 @@ func (a *SortedIntArray) Merge(array interface{}) *SortedIntArray {
|
||||
}
|
||||
|
||||
// Chunk splits an array into multiple arrays,
|
||||
// the size of each array is determined by <size>.
|
||||
// the size of each array is determined by `size`.
|
||||
// The last chunk may contain less than size elements.
|
||||
func (a *SortedIntArray) Chunk(size int) [][]int {
|
||||
if size < 1 {
|
||||
@ -566,7 +573,7 @@ func (a *SortedIntArray) Rand() (value int, found bool) {
|
||||
return a.array[grand.Intn(len(a.array))], true
|
||||
}
|
||||
|
||||
// Rands randomly returns <size> items from array(no deleting).
|
||||
// Rands randomly returns `size` items from array(no deleting).
|
||||
func (a *SortedIntArray) Rands(size int) []int {
|
||||
a.mu.RLock()
|
||||
defer a.mu.RUnlock()
|
||||
@ -580,7 +587,7 @@ func (a *SortedIntArray) Rands(size int) []int {
|
||||
return array
|
||||
}
|
||||
|
||||
// Join joins array elements with a string <glue>.
|
||||
// Join joins array elements with a string `glue`.
|
||||
func (a *SortedIntArray) Join(glue string) string {
|
||||
a.mu.RLock()
|
||||
defer a.mu.RUnlock()
|
||||
@ -613,8 +620,8 @@ func (a *SortedIntArray) Iterator(f func(k int, v int) bool) {
|
||||
a.IteratorAsc(f)
|
||||
}
|
||||
|
||||
// IteratorAsc iterates the array readonly in ascending order with given callback function <f>.
|
||||
// If <f> returns true, then it continues iterating; or false to stop.
|
||||
// IteratorAsc iterates the array readonly in ascending order with given callback function `f`.
|
||||
// If `f` returns true, then it continues iterating; or false to stop.
|
||||
func (a *SortedIntArray) IteratorAsc(f func(k int, v int) bool) {
|
||||
a.mu.RLock()
|
||||
defer a.mu.RUnlock()
|
||||
@ -625,8 +632,8 @@ func (a *SortedIntArray) IteratorAsc(f func(k int, v int) bool) {
|
||||
}
|
||||
}
|
||||
|
||||
// IteratorDesc iterates the array readonly in descending order with given callback function <f>.
|
||||
// If <f> returns true, then it continues iterating; or false to stop.
|
||||
// IteratorDesc iterates the array readonly in descending order with given callback function `f`.
|
||||
// If `f` returns true, then it continues iterating; or false to stop.
|
||||
func (a *SortedIntArray) IteratorDesc(f func(k int, v int) bool) {
|
||||
a.mu.RLock()
|
||||
defer a.mu.RUnlock()
|
||||
@ -707,7 +714,7 @@ func (a *SortedIntArray) FilterEmpty() *SortedIntArray {
|
||||
return a
|
||||
}
|
||||
|
||||
// Walk applies a user supplied function <f> to every item of array.
|
||||
// Walk applies a user supplied function `f` to every item of array.
|
||||
func (a *SortedIntArray) Walk(f func(value int) int) *SortedIntArray {
|
||||
a.mu.Lock()
|
||||
defer a.mu.Unlock()
|
||||
|
||||
@ -32,14 +32,14 @@ type SortedStrArray struct {
|
||||
}
|
||||
|
||||
// NewSortedStrArray creates and returns an empty sorted array.
|
||||
// The parameter <safe> is used to specify whether using array in concurrent-safety,
|
||||
// The parameter `safe` is used to specify whether using array in concurrent-safety,
|
||||
// which is false in default.
|
||||
func NewSortedStrArray(safe ...bool) *SortedStrArray {
|
||||
return NewSortedStrArraySize(0, safe...)
|
||||
}
|
||||
|
||||
// NewSortedStrArrayComparator creates and returns an empty sorted array with specified comparator.
|
||||
// The parameter <safe> is used to specify whether using array in concurrent-safety which is false in default.
|
||||
// The parameter `safe` is used to specify whether using array in concurrent-safety which is false in default.
|
||||
func NewSortedStrArrayComparator(comparator func(a, b string) int, safe ...bool) *SortedStrArray {
|
||||
array := NewSortedStrArray(safe...)
|
||||
array.comparator = comparator
|
||||
@ -47,7 +47,7 @@ func NewSortedStrArrayComparator(comparator func(a, b string) int, safe ...bool)
|
||||
}
|
||||
|
||||
// NewSortedStrArraySize create and returns an sorted array with given size and cap.
|
||||
// The parameter <safe> is used to specify whether using array in concurrent-safety,
|
||||
// The parameter `safe` is used to specify whether using array in concurrent-safety,
|
||||
// which is false in default.
|
||||
func NewSortedStrArraySize(cap int, safe ...bool) *SortedStrArray {
|
||||
return &SortedStrArray{
|
||||
@ -57,8 +57,8 @@ func NewSortedStrArraySize(cap int, safe ...bool) *SortedStrArray {
|
||||
}
|
||||
}
|
||||
|
||||
// NewSortedStrArrayFrom creates and returns an sorted array with given slice <array>.
|
||||
// The parameter <safe> is used to specify whether using array in concurrent-safety,
|
||||
// NewSortedStrArrayFrom creates and returns an sorted array with given slice `array`.
|
||||
// The parameter `safe` is used to specify whether using array in concurrent-safety,
|
||||
// which is false in default.
|
||||
func NewSortedStrArrayFrom(array []string, safe ...bool) *SortedStrArray {
|
||||
a := NewSortedStrArraySize(0, safe...)
|
||||
@ -67,8 +67,8 @@ func NewSortedStrArrayFrom(array []string, safe ...bool) *SortedStrArray {
|
||||
return a
|
||||
}
|
||||
|
||||
// NewSortedStrArrayFromCopy creates and returns an sorted array from a copy of given slice <array>.
|
||||
// The parameter <safe> is used to specify whether using array in concurrent-safety,
|
||||
// NewSortedStrArrayFromCopy creates and returns an sorted array from a copy of given slice `array`.
|
||||
// The parameter `safe` is used to specify whether using array in concurrent-safety,
|
||||
// which is false in default.
|
||||
func NewSortedStrArrayFromCopy(array []string, safe ...bool) *SortedStrArray {
|
||||
newArray := make([]string, len(array))
|
||||
@ -76,7 +76,7 @@ func NewSortedStrArrayFromCopy(array []string, safe ...bool) *SortedStrArray {
|
||||
return NewSortedStrArrayFrom(newArray, safe...)
|
||||
}
|
||||
|
||||
// SetArray sets the underlying slice array with the given <array>.
|
||||
// SetArray sets the underlying slice array with the given `array`.
|
||||
func (a *SortedStrArray) SetArray(array []string) *SortedStrArray {
|
||||
a.mu.Lock()
|
||||
defer a.mu.Unlock()
|
||||
@ -85,8 +85,15 @@ func (a *SortedStrArray) SetArray(array []string) *SortedStrArray {
|
||||
return a
|
||||
}
|
||||
|
||||
// At returns the value by the specified index.
|
||||
// If the given `index` is out of range of the array, it returns an empty string.
|
||||
func (a *SortedStrArray) At(index int) (value string) {
|
||||
value, _ = a.Get(index)
|
||||
return
|
||||
}
|
||||
|
||||
// Sort sorts the array in increasing order.
|
||||
// The parameter <reverse> controls whether sort
|
||||
// The parameter `reverse` controls whether sort
|
||||
// in increasing order(default) or decreasing order.
|
||||
func (a *SortedStrArray) Sort() *SortedStrArray {
|
||||
a.mu.Lock()
|
||||
@ -128,7 +135,7 @@ func (a *SortedStrArray) Append(values ...string) *SortedStrArray {
|
||||
}
|
||||
|
||||
// Get returns the value by the specified index.
|
||||
// If the given <index> is out of range of the array, the <found> is false.
|
||||
// If the given `index` is out of range of the array, the `found` is false.
|
||||
func (a *SortedStrArray) Get(index int) (value string, found bool) {
|
||||
a.mu.RLock()
|
||||
defer a.mu.RUnlock()
|
||||
@ -139,7 +146,7 @@ func (a *SortedStrArray) Get(index int) (value string, found bool) {
|
||||
}
|
||||
|
||||
// Remove removes an item by index.
|
||||
// If the given <index> is out of range of the array, the <found> is false.
|
||||
// If the given `index` is out of range of the array, the `found` is false.
|
||||
func (a *SortedStrArray) Remove(index int) (value string, found bool) {
|
||||
a.mu.Lock()
|
||||
defer a.mu.Unlock()
|
||||
@ -180,7 +187,7 @@ func (a *SortedStrArray) RemoveValue(value string) bool {
|
||||
}
|
||||
|
||||
// PopLeft pops and returns an item from the beginning of array.
|
||||
// Note that if the array is empty, the <found> is false.
|
||||
// Note that if the array is empty, the `found` is false.
|
||||
func (a *SortedStrArray) PopLeft() (value string, found bool) {
|
||||
a.mu.Lock()
|
||||
defer a.mu.Unlock()
|
||||
@ -193,7 +200,7 @@ func (a *SortedStrArray) PopLeft() (value string, found bool) {
|
||||
}
|
||||
|
||||
// PopRight pops and returns an item from the end of array.
|
||||
// Note that if the array is empty, the <found> is false.
|
||||
// Note that if the array is empty, the `found` is false.
|
||||
func (a *SortedStrArray) PopRight() (value string, found bool) {
|
||||
a.mu.Lock()
|
||||
defer a.mu.Unlock()
|
||||
@ -207,16 +214,16 @@ func (a *SortedStrArray) PopRight() (value string, found bool) {
|
||||
}
|
||||
|
||||
// PopRand randomly pops and return an item out of array.
|
||||
// Note that if the array is empty, the <found> is false.
|
||||
// Note that if the array is empty, the `found` is false.
|
||||
func (a *SortedStrArray) PopRand() (value string, found bool) {
|
||||
a.mu.Lock()
|
||||
defer a.mu.Unlock()
|
||||
return a.doRemoveWithoutLock(grand.Intn(len(a.array)))
|
||||
}
|
||||
|
||||
// PopRands randomly pops and returns <size> items out of array.
|
||||
// If the given <size> is greater than size of the array, it returns all elements of the array.
|
||||
// Note that if given <size> <= 0 or the array is empty, it returns nil.
|
||||
// PopRands randomly pops and returns `size` items out of array.
|
||||
// If the given `size` is greater than size of the array, it returns all elements of the array.
|
||||
// Note that if given `size` <= 0 or the array is empty, it returns nil.
|
||||
func (a *SortedStrArray) PopRands(size int) []string {
|
||||
a.mu.Lock()
|
||||
defer a.mu.Unlock()
|
||||
@ -233,9 +240,9 @@ func (a *SortedStrArray) PopRands(size int) []string {
|
||||
return array
|
||||
}
|
||||
|
||||
// PopLefts pops and returns <size> items from the beginning of array.
|
||||
// If the given <size> is greater than size of the array, it returns all elements of the array.
|
||||
// Note that if given <size> <= 0 or the array is empty, it returns nil.
|
||||
// PopLefts pops and returns `size` items from the beginning of array.
|
||||
// If the given `size` is greater than size of the array, it returns all elements of the array.
|
||||
// Note that if given `size` <= 0 or the array is empty, it returns nil.
|
||||
func (a *SortedStrArray) PopLefts(size int) []string {
|
||||
a.mu.Lock()
|
||||
defer a.mu.Unlock()
|
||||
@ -252,9 +259,9 @@ func (a *SortedStrArray) PopLefts(size int) []string {
|
||||
return value
|
||||
}
|
||||
|
||||
// PopRights pops and returns <size> items from the end of array.
|
||||
// If the given <size> is greater than size of the array, it returns all elements of the array.
|
||||
// Note that if given <size> <= 0 or the array is empty, it returns nil.
|
||||
// PopRights pops and returns `size` items from the end of array.
|
||||
// If the given `size` is greater than size of the array, it returns all elements of the array.
|
||||
// Note that if given `size` <= 0 or the array is empty, it returns nil.
|
||||
func (a *SortedStrArray) PopRights(size int) []string {
|
||||
a.mu.Lock()
|
||||
defer a.mu.Unlock()
|
||||
@ -276,8 +283,8 @@ func (a *SortedStrArray) PopRights(size int) []string {
|
||||
// Notice, if in concurrent-safe usage, it returns a copy of slice;
|
||||
// else a pointer to the underlying data.
|
||||
//
|
||||
// If <end> is negative, then the offset will start from the end of array.
|
||||
// If <end> is omitted, then the sequence will have everything from start up
|
||||
// If `end` is negative, then the offset will start from the end of array.
|
||||
// If `end` is omitted, then the sequence will have everything from start up
|
||||
// until the end of the array.
|
||||
func (a *SortedStrArray) Range(start int, end ...int) []string {
|
||||
a.mu.RLock()
|
||||
@ -303,7 +310,7 @@ func (a *SortedStrArray) Range(start int, end ...int) []string {
|
||||
}
|
||||
|
||||
// SubSlice returns a slice of elements from the array as specified
|
||||
// by the <offset> and <size> parameters.
|
||||
// by the `offset` and `size` parameters.
|
||||
// If in concurrent safe usage, it returns a copy of the slice; else a pointer.
|
||||
//
|
||||
// If offset is non-negative, the sequence will start at that offset in the array.
|
||||
@ -418,7 +425,7 @@ func (a *SortedStrArray) ContainsI(value string) bool {
|
||||
return false
|
||||
}
|
||||
|
||||
// Search searches array by <value>, returns the index of <value>,
|
||||
// Search searches array by `value`, returns the index of `value`,
|
||||
// or returns -1 if not exists.
|
||||
func (a *SortedStrArray) Search(value string) (index int) {
|
||||
if i, r := a.binSearch(value, true); r == 0 {
|
||||
@ -429,9 +436,9 @@ func (a *SortedStrArray) Search(value string) (index int) {
|
||||
|
||||
// Binary search.
|
||||
// It returns the last compared index and the result.
|
||||
// If <result> equals to 0, it means the value at <index> is equals to <value>.
|
||||
// If <result> lesser than 0, it means the value at <index> is lesser than <value>.
|
||||
// If <result> greater than 0, it means the value at <index> is greater than <value>.
|
||||
// If `result` equals to 0, it means the value at `index` is equals to `value`.
|
||||
// If `result` lesser than 0, it means the value at `index` is lesser than `value`.
|
||||
// If `result` greater than 0, it means the value at `index` is greater than `value`.
|
||||
func (a *SortedStrArray) binSearch(value string, lock bool) (index int, result int) {
|
||||
if lock {
|
||||
a.mu.RLock()
|
||||
@ -511,7 +518,7 @@ func (a *SortedStrArray) Clear() *SortedStrArray {
|
||||
return a
|
||||
}
|
||||
|
||||
// LockFunc locks writing by callback function <f>.
|
||||
// LockFunc locks writing by callback function `f`.
|
||||
func (a *SortedStrArray) LockFunc(f func(array []string)) *SortedStrArray {
|
||||
a.mu.Lock()
|
||||
defer a.mu.Unlock()
|
||||
@ -519,7 +526,7 @@ func (a *SortedStrArray) LockFunc(f func(array []string)) *SortedStrArray {
|
||||
return a
|
||||
}
|
||||
|
||||
// RLockFunc locks reading by callback function <f>.
|
||||
// RLockFunc locks reading by callback function `f`.
|
||||
func (a *SortedStrArray) RLockFunc(f func(array []string)) *SortedStrArray {
|
||||
a.mu.RLock()
|
||||
defer a.mu.RUnlock()
|
||||
@ -527,8 +534,8 @@ func (a *SortedStrArray) RLockFunc(f func(array []string)) *SortedStrArray {
|
||||
return a
|
||||
}
|
||||
|
||||
// Merge merges <array> into current array.
|
||||
// The parameter <array> can be any garray or slice type.
|
||||
// Merge merges `array` into current array.
|
||||
// The parameter `array` can be any garray or slice type.
|
||||
// The difference between Merge and Append is Append supports only specified slice type,
|
||||
// but Merge supports more parameter types.
|
||||
func (a *SortedStrArray) Merge(array interface{}) *SortedStrArray {
|
||||
@ -536,7 +543,7 @@ func (a *SortedStrArray) Merge(array interface{}) *SortedStrArray {
|
||||
}
|
||||
|
||||
// Chunk splits an array into multiple arrays,
|
||||
// the size of each array is determined by <size>.
|
||||
// the size of each array is determined by `size`.
|
||||
// The last chunk may contain less than size elements.
|
||||
func (a *SortedStrArray) Chunk(size int) [][]string {
|
||||
if size < 1 {
|
||||
@ -568,7 +575,7 @@ func (a *SortedStrArray) Rand() (value string, found bool) {
|
||||
return a.array[grand.Intn(len(a.array))], true
|
||||
}
|
||||
|
||||
// Rands randomly returns <size> items from array(no deleting).
|
||||
// Rands randomly returns `size` items from array(no deleting).
|
||||
func (a *SortedStrArray) Rands(size int) []string {
|
||||
a.mu.RLock()
|
||||
defer a.mu.RUnlock()
|
||||
@ -582,7 +589,7 @@ func (a *SortedStrArray) Rands(size int) []string {
|
||||
return array
|
||||
}
|
||||
|
||||
// Join joins array elements with a string <glue>.
|
||||
// Join joins array elements with a string `glue`.
|
||||
func (a *SortedStrArray) Join(glue string) string {
|
||||
a.mu.RLock()
|
||||
defer a.mu.RUnlock()
|
||||
@ -615,8 +622,8 @@ func (a *SortedStrArray) Iterator(f func(k int, v string) bool) {
|
||||
a.IteratorAsc(f)
|
||||
}
|
||||
|
||||
// IteratorAsc iterates the array readonly in ascending order with given callback function <f>.
|
||||
// If <f> returns true, then it continues iterating; or false to stop.
|
||||
// IteratorAsc iterates the array readonly in ascending order with given callback function `f`.
|
||||
// If `f` returns true, then it continues iterating; or false to stop.
|
||||
func (a *SortedStrArray) IteratorAsc(f func(k int, v string) bool) {
|
||||
a.mu.RLock()
|
||||
defer a.mu.RUnlock()
|
||||
@ -627,8 +634,8 @@ func (a *SortedStrArray) IteratorAsc(f func(k int, v string) bool) {
|
||||
}
|
||||
}
|
||||
|
||||
// IteratorDesc iterates the array readonly in descending order with given callback function <f>.
|
||||
// If <f> returns true, then it continues iterating; or false to stop.
|
||||
// IteratorDesc iterates the array readonly in descending order with given callback function `f`.
|
||||
// If `f` returns true, then it continues iterating; or false to stop.
|
||||
func (a *SortedStrArray) IteratorDesc(f func(k int, v string) bool) {
|
||||
a.mu.RLock()
|
||||
defer a.mu.RUnlock()
|
||||
@ -720,7 +727,7 @@ func (a *SortedStrArray) FilterEmpty() *SortedStrArray {
|
||||
return a
|
||||
}
|
||||
|
||||
// Walk applies a user supplied function <f> to every item of array.
|
||||
// Walk applies a user supplied function `f` to every item of array.
|
||||
func (a *SortedStrArray) Walk(f func(value string) string) *SortedStrArray {
|
||||
a.mu.Lock()
|
||||
defer a.mu.Unlock()
|
||||
|
||||
39
container/garray/garray_z_bench_any_test.go
Normal file
39
container/garray/garray_z_bench_any_test.go
Normal file
@ -0,0 +1,39 @@
|
||||
// Copyright GoFrame Author(https://goframe.org). All Rights Reserved.
|
||||
//
|
||||
// This Source Code Form is subject to the terms of the MIT License.
|
||||
// If a copy of the MIT was not distributed with this file,
|
||||
// You can obtain one at https://github.com/gogf/gf.
|
||||
|
||||
package garray_test
|
||||
|
||||
import (
|
||||
"github.com/gogf/gf/container/garray"
|
||||
"testing"
|
||||
)
|
||||
|
||||
type anySortedArrayItem struct {
|
||||
priority int64
|
||||
value interface{}
|
||||
}
|
||||
|
||||
var (
|
||||
anyArray = garray.NewArray()
|
||||
anySortedArray = garray.NewSortedArray(func(a, b interface{}) int {
|
||||
return int(a.(anySortedArrayItem).priority - b.(anySortedArrayItem).priority)
|
||||
})
|
||||
)
|
||||
|
||||
func Benchmark_AnyArray_Add(b *testing.B) {
|
||||
for i := 0; i < b.N; i++ {
|
||||
anyArray.Append(i)
|
||||
}
|
||||
}
|
||||
|
||||
func Benchmark_AnySortedArray_Add(b *testing.B) {
|
||||
for i := 0; i < b.N; i++ {
|
||||
anySortedArray.Add(anySortedArrayItem{
|
||||
priority: int64(i),
|
||||
value: i,
|
||||
})
|
||||
}
|
||||
}
|
||||
@ -75,14 +75,14 @@ func ExampleNew() {
|
||||
func ExampleArray_Iterator() {
|
||||
array := garray.NewArrayFrom(g.Slice{"a", "b", "c"})
|
||||
// Iterator is alias of IteratorAsc, which iterates the array readonly in ascending order
|
||||
// with given callback function <f>.
|
||||
// If <f> returns true, then it continues iterating; or false to stop.
|
||||
// with given callback function `f`.
|
||||
// If `f` returns true, then it continues iterating; or false to stop.
|
||||
array.Iterator(func(k int, v interface{}) bool {
|
||||
fmt.Println(k, v)
|
||||
return true
|
||||
})
|
||||
// IteratorDesc iterates the array readonly in descending order with given callback function <f>.
|
||||
// If <f> returns true, then it continues iterating; or false to stop.
|
||||
// IteratorDesc iterates the array readonly in descending order with given callback function `f`.
|
||||
// If `f` returns true, then it continues iterating; or false to stop.
|
||||
array.IteratorDesc(func(k int, v interface{}) bool {
|
||||
fmt.Println(k, v)
|
||||
return true
|
||||
@ -150,7 +150,7 @@ func ExampleArray_Chunk() {
|
||||
array := garray.NewFrom(g.Slice{1, 2, 3, 4, 5, 6, 7, 8, 9})
|
||||
|
||||
// Chunk splits an array into multiple arrays,
|
||||
// the size of each array is determined by <size>.
|
||||
// the size of each array is determined by `size`.
|
||||
// The last chunk may contain less than size elements.
|
||||
fmt.Println(array.Chunk(2))
|
||||
|
||||
|
||||
@ -8,7 +8,8 @@
|
||||
package gpool
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"github.com/gogf/gf/errors/gcode"
|
||||
"github.com/gogf/gf/errors/gerror"
|
||||
"time"
|
||||
|
||||
"github.com/gogf/gf/container/glist"
|
||||
@ -66,7 +67,7 @@ func New(ttl time.Duration, newFunc NewFunc, expireFunc ...ExpireFunc) *Pool {
|
||||
// Put puts an item to pool.
|
||||
func (p *Pool) Put(value interface{}) error {
|
||||
if p.closed.Val() {
|
||||
return errors.New("pool is closed")
|
||||
return gerror.NewCode(gcode.CodeInvalidOperation, "pool is closed")
|
||||
}
|
||||
item := &poolItem{
|
||||
value: value,
|
||||
@ -117,7 +118,7 @@ func (p *Pool) Get() (interface{}, error) {
|
||||
if p.NewFunc != nil {
|
||||
return p.NewFunc()
|
||||
}
|
||||
return nil, errors.New("pool is empty")
|
||||
return nil, gerror.NewCode(gcode.CodeInvalidOperation, "pool is empty")
|
||||
}
|
||||
|
||||
// Size returns the count of available items of pool.
|
||||
|
||||
@ -21,7 +21,7 @@ type StrSet struct {
|
||||
data map[string]struct{}
|
||||
}
|
||||
|
||||
// New create and returns a new set, which contains un-repeated items.
|
||||
// NewStrSet create and returns a new set, which contains un-repeated items.
|
||||
// The parameter <safe> is used to specify whether using set in concurrent-safety,
|
||||
// which is false in default.
|
||||
func NewStrSet(safe ...bool) *StrSet {
|
||||
|
||||
@ -22,8 +22,8 @@ type Var struct {
|
||||
safe bool // Concurrent safe or not.
|
||||
}
|
||||
|
||||
// New creates and returns a new Var with given <value>.
|
||||
// The optional parameter <safe> specifies whether Var is used in concurrent-safety,
|
||||
// New creates and returns a new Var with given `value`.
|
||||
// The optional parameter `safe` specifies whether Var is used in concurrent-safety,
|
||||
// which is false in default.
|
||||
func New(value interface{}, safe ...bool) *Var {
|
||||
v := Var{}
|
||||
@ -36,8 +36,8 @@ func New(value interface{}, safe ...bool) *Var {
|
||||
return &v
|
||||
}
|
||||
|
||||
// Create creates and returns a new Var with given <value>.
|
||||
// The optional parameter <safe> specifies whether Var is used in concurrent-safety,
|
||||
// Create creates and returns a new Var with given `value`.
|
||||
// The optional parameter `safe` specifies whether Var is used in concurrent-safety,
|
||||
// which is false in default.
|
||||
func Create(value interface{}, safe ...bool) Var {
|
||||
v := Var{}
|
||||
@ -55,7 +55,7 @@ func (v *Var) Clone() *Var {
|
||||
return New(v.Val(), v.safe)
|
||||
}
|
||||
|
||||
// Set sets <value> to <v>, and returns the old value.
|
||||
// Set sets `value` to `v`, and returns the old value.
|
||||
func (v *Var) Set(value interface{}) (old interface{}) {
|
||||
if v.safe {
|
||||
if t, ok := v.value.(*gtype.Interface); ok {
|
||||
@ -68,7 +68,7 @@ func (v *Var) Set(value interface{}) (old interface{}) {
|
||||
return
|
||||
}
|
||||
|
||||
// Val returns the current value of <v>.
|
||||
// Val returns the current value of `v`.
|
||||
func (v *Var) Val() interface{} {
|
||||
if v == nil {
|
||||
return nil
|
||||
@ -86,96 +86,96 @@ func (v *Var) Interface() interface{} {
|
||||
return v.Val()
|
||||
}
|
||||
|
||||
// Bytes converts and returns <v> as []byte.
|
||||
// Bytes converts and returns `v` as []byte.
|
||||
func (v *Var) Bytes() []byte {
|
||||
return gconv.Bytes(v.Val())
|
||||
}
|
||||
|
||||
// String converts and returns <v> as string.
|
||||
// String converts and returns `v` as string.
|
||||
func (v *Var) String() string {
|
||||
return gconv.String(v.Val())
|
||||
}
|
||||
|
||||
// Bool converts and returns <v> as bool.
|
||||
// Bool converts and returns `v` as bool.
|
||||
func (v *Var) Bool() bool {
|
||||
return gconv.Bool(v.Val())
|
||||
}
|
||||
|
||||
// Int converts and returns <v> as int.
|
||||
// Int converts and returns `v` as int.
|
||||
func (v *Var) Int() int {
|
||||
return gconv.Int(v.Val())
|
||||
}
|
||||
|
||||
// Int8 converts and returns <v> as int8.
|
||||
// Int8 converts and returns `v` as int8.
|
||||
func (v *Var) Int8() int8 {
|
||||
return gconv.Int8(v.Val())
|
||||
}
|
||||
|
||||
// Int16 converts and returns <v> as int16.
|
||||
// Int16 converts and returns `v` as int16.
|
||||
func (v *Var) Int16() int16 {
|
||||
return gconv.Int16(v.Val())
|
||||
}
|
||||
|
||||
// Int32 converts and returns <v> as int32.
|
||||
// Int32 converts and returns `v` as int32.
|
||||
func (v *Var) Int32() int32 {
|
||||
return gconv.Int32(v.Val())
|
||||
}
|
||||
|
||||
// Int64 converts and returns <v> as int64.
|
||||
// Int64 converts and returns `v` as int64.
|
||||
func (v *Var) Int64() int64 {
|
||||
return gconv.Int64(v.Val())
|
||||
}
|
||||
|
||||
// Uint converts and returns <v> as uint.
|
||||
// Uint converts and returns `v` as uint.
|
||||
func (v *Var) Uint() uint {
|
||||
return gconv.Uint(v.Val())
|
||||
}
|
||||
|
||||
// Uint8 converts and returns <v> as uint8.
|
||||
// Uint8 converts and returns `v` as uint8.
|
||||
func (v *Var) Uint8() uint8 {
|
||||
return gconv.Uint8(v.Val())
|
||||
}
|
||||
|
||||
// Uint16 converts and returns <v> as uint16.
|
||||
// Uint16 converts and returns `v` as uint16.
|
||||
func (v *Var) Uint16() uint16 {
|
||||
return gconv.Uint16(v.Val())
|
||||
}
|
||||
|
||||
// Uint32 converts and returns <v> as uint32.
|
||||
// Uint32 converts and returns `v` as uint32.
|
||||
func (v *Var) Uint32() uint32 {
|
||||
return gconv.Uint32(v.Val())
|
||||
}
|
||||
|
||||
// Uint64 converts and returns <v> as uint64.
|
||||
// Uint64 converts and returns `v` as uint64.
|
||||
func (v *Var) Uint64() uint64 {
|
||||
return gconv.Uint64(v.Val())
|
||||
}
|
||||
|
||||
// Float32 converts and returns <v> as float32.
|
||||
// Float32 converts and returns `v` as float32.
|
||||
func (v *Var) Float32() float32 {
|
||||
return gconv.Float32(v.Val())
|
||||
}
|
||||
|
||||
// Float64 converts and returns <v> as float64.
|
||||
// Float64 converts and returns `v` as float64.
|
||||
func (v *Var) Float64() float64 {
|
||||
return gconv.Float64(v.Val())
|
||||
}
|
||||
|
||||
// Time converts and returns <v> as time.Time.
|
||||
// The parameter <format> specifies the format of the time string using gtime,
|
||||
// Time converts and returns `v` as time.Time.
|
||||
// The parameter `format` specifies the format of the time string using gtime,
|
||||
// eg: Y-m-d H:i:s.
|
||||
func (v *Var) Time(format ...string) time.Time {
|
||||
return gconv.Time(v.Val(), format...)
|
||||
}
|
||||
|
||||
// Duration converts and returns <v> as time.Duration.
|
||||
// If value of <v> is string, then it uses time.ParseDuration for conversion.
|
||||
// Duration converts and returns `v` as time.Duration.
|
||||
// If value of `v` is string, then it uses time.ParseDuration for conversion.
|
||||
func (v *Var) Duration() time.Duration {
|
||||
return gconv.Duration(v.Val())
|
||||
}
|
||||
|
||||
// GTime converts and returns <v> as *gtime.Time.
|
||||
// The parameter <format> specifies the format of the time string using gtime,
|
||||
// GTime converts and returns `v` as *gtime.Time.
|
||||
// The parameter `format` specifies the format of the time string using gtime,
|
||||
// eg: Y-m-d H:i:s.
|
||||
func (v *Var) GTime(format ...string) *gtime.Time {
|
||||
return gconv.GTime(v.Val(), format...)
|
||||
|
||||
@ -7,92 +7,45 @@
|
||||
package gvar
|
||||
|
||||
import (
|
||||
"github.com/gogf/gf/internal/empty"
|
||||
"reflect"
|
||||
"github.com/gogf/gf/internal/utils"
|
||||
)
|
||||
|
||||
// IsNil checks whether <v> is nil.
|
||||
// IsNil checks whether `v` is nil.
|
||||
func (v *Var) IsNil() bool {
|
||||
return v.Val() == nil
|
||||
return utils.IsNil(v.Val())
|
||||
}
|
||||
|
||||
// IsEmpty checks whether <v> is empty.
|
||||
// IsEmpty checks whether `v` is empty.
|
||||
func (v *Var) IsEmpty() bool {
|
||||
return empty.IsEmpty(v.Val())
|
||||
return utils.IsEmpty(v.Val())
|
||||
}
|
||||
|
||||
// IsInt checks whether <v> is type of int.
|
||||
// IsInt checks whether `v` is type of int.
|
||||
func (v *Var) IsInt() bool {
|
||||
switch v.Val().(type) {
|
||||
case int, *int, int8, *int8, int16, *int16, int32, *int32, int64, *int64:
|
||||
return true
|
||||
}
|
||||
return false
|
||||
return utils.IsInt(v.Val())
|
||||
}
|
||||
|
||||
// IsUint checks whether <v> is type of uint.
|
||||
// IsUint checks whether `v` is type of uint.
|
||||
func (v *Var) IsUint() bool {
|
||||
switch v.Val().(type) {
|
||||
case uint, *uint, uint8, *uint8, uint16, *uint16, uint32, *uint32, uint64, *uint64:
|
||||
return true
|
||||
}
|
||||
return false
|
||||
return utils.IsUint(v.Val())
|
||||
}
|
||||
|
||||
// IsFloat checks whether <v> is type of float.
|
||||
// IsFloat checks whether `v` is type of float.
|
||||
func (v *Var) IsFloat() bool {
|
||||
switch v.Val().(type) {
|
||||
case float32, *float32, float64, *float64:
|
||||
return true
|
||||
}
|
||||
return false
|
||||
return utils.IsFloat(v.Val())
|
||||
}
|
||||
|
||||
// IsSlice checks whether <v> is type of slice.
|
||||
// IsSlice checks whether `v` is type of slice.
|
||||
func (v *Var) IsSlice() bool {
|
||||
var (
|
||||
reflectValue = reflect.ValueOf(v.Val())
|
||||
reflectKind = reflectValue.Kind()
|
||||
)
|
||||
for reflectKind == reflect.Ptr {
|
||||
reflectValue = reflectValue.Elem()
|
||||
}
|
||||
switch reflectKind {
|
||||
case reflect.Slice, reflect.Array:
|
||||
return true
|
||||
}
|
||||
return false
|
||||
return utils.IsSlice(v.Val())
|
||||
}
|
||||
|
||||
// IsMap checks whether <v> is type of map.
|
||||
// IsMap checks whether `v` is type of map.
|
||||
func (v *Var) IsMap() bool {
|
||||
var (
|
||||
reflectValue = reflect.ValueOf(v.Val())
|
||||
reflectKind = reflectValue.Kind()
|
||||
)
|
||||
for reflectKind == reflect.Ptr {
|
||||
reflectValue = reflectValue.Elem()
|
||||
}
|
||||
switch reflectKind {
|
||||
case reflect.Map:
|
||||
return true
|
||||
}
|
||||
return false
|
||||
return utils.IsMap(v.Val())
|
||||
}
|
||||
|
||||
// IsStruct checks whether <v> is type of struct.
|
||||
// IsStruct checks whether `v` is type of struct.
|
||||
func (v *Var) IsStruct() bool {
|
||||
var (
|
||||
reflectValue = reflect.ValueOf(v.Val())
|
||||
reflectKind = reflectValue.Kind()
|
||||
)
|
||||
for reflectKind == reflect.Ptr {
|
||||
reflectValue = reflectValue.Elem()
|
||||
reflectKind = reflectValue.Kind()
|
||||
}
|
||||
switch reflectKind {
|
||||
case reflect.Struct:
|
||||
return true
|
||||
}
|
||||
return false
|
||||
return utils.IsStruct(v.Val())
|
||||
}
|
||||
|
||||
@ -10,15 +10,15 @@ import (
|
||||
"github.com/gogf/gf/util/gutil"
|
||||
)
|
||||
|
||||
// ListItemValues retrieves and returns the elements of all item struct/map with key <key>.
|
||||
// Note that the parameter <list> should be type of slice which contains elements of map or struct,
|
||||
// ListItemValues retrieves and returns the elements of all item struct/map with key `key`.
|
||||
// Note that the parameter `list` should be type of slice which contains elements of map or struct,
|
||||
// or else it returns an empty slice.
|
||||
func (v *Var) ListItemValues(key interface{}) (values []interface{}) {
|
||||
return gutil.ListItemValues(v.Val(), key)
|
||||
}
|
||||
|
||||
// ListItemValuesUnique retrieves and returns the unique elements of all struct/map with key <key>.
|
||||
// Note that the parameter <list> should be type of slice which contains elements of map or struct,
|
||||
// ListItemValuesUnique retrieves and returns the unique elements of all struct/map with key `key`.
|
||||
// Note that the parameter `list` should be type of slice which contains elements of map or struct,
|
||||
// or else it returns an empty slice.
|
||||
func (v *Var) ListItemValuesUnique(key string) []interface{} {
|
||||
return gutil.ListItemValuesUnique(v.Val(), key)
|
||||
|
||||
@ -8,7 +8,7 @@ package gvar
|
||||
|
||||
import "github.com/gogf/gf/util/gconv"
|
||||
|
||||
// Map converts and returns <v> as map[string]interface{}.
|
||||
// Map converts and returns `v` as map[string]interface{}.
|
||||
func (v *Var) Map(tags ...string) map[string]interface{} {
|
||||
return gconv.Map(v.Val(), tags...)
|
||||
}
|
||||
@ -18,12 +18,12 @@ func (v *Var) MapStrAny() map[string]interface{} {
|
||||
return v.Map()
|
||||
}
|
||||
|
||||
// MapStrStr converts and returns <v> as map[string]string.
|
||||
// MapStrStr converts and returns `v` as map[string]string.
|
||||
func (v *Var) MapStrStr(tags ...string) map[string]string {
|
||||
return gconv.MapStrStr(v.Val(), tags...)
|
||||
}
|
||||
|
||||
// MapStrVar converts and returns <v> as map[string]Var.
|
||||
// MapStrVar converts and returns `v` as map[string]Var.
|
||||
func (v *Var) MapStrVar(tags ...string) map[string]*Var {
|
||||
m := v.Map(tags...)
|
||||
if len(m) > 0 {
|
||||
@ -36,17 +36,17 @@ func (v *Var) MapStrVar(tags ...string) map[string]*Var {
|
||||
return nil
|
||||
}
|
||||
|
||||
// MapDeep converts and returns <v> as map[string]interface{} recursively.
|
||||
// MapDeep converts and returns `v` as map[string]interface{} recursively.
|
||||
func (v *Var) MapDeep(tags ...string) map[string]interface{} {
|
||||
return gconv.MapDeep(v.Val(), tags...)
|
||||
}
|
||||
|
||||
// MapDeep converts and returns <v> as map[string]string recursively.
|
||||
// MapStrStrDeep converts and returns `v` as map[string]string recursively.
|
||||
func (v *Var) MapStrStrDeep(tags ...string) map[string]string {
|
||||
return gconv.MapStrStrDeep(v.Val(), tags...)
|
||||
}
|
||||
|
||||
// MapStrVarDeep converts and returns <v> as map[string]*Var recursively.
|
||||
// MapStrVarDeep converts and returns `v` as map[string]*Var recursively.
|
||||
func (v *Var) MapStrVarDeep(tags ...string) map[string]*Var {
|
||||
m := v.MapDeep(tags...)
|
||||
if len(m) > 0 {
|
||||
@ -59,27 +59,33 @@ func (v *Var) MapStrVarDeep(tags ...string) map[string]*Var {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Maps converts and returns <v> as map[string]string.
|
||||
// Maps converts and returns `v` as map[string]string.
|
||||
// See gconv.Maps.
|
||||
func (v *Var) Maps(tags ...string) []map[string]interface{} {
|
||||
return gconv.Maps(v.Val(), tags...)
|
||||
}
|
||||
|
||||
// MapToMap converts any map type variable <params> to another map type variable <pointer>.
|
||||
// MapsDeep converts `value` to []map[string]interface{} recursively.
|
||||
// See gconv.MapsDeep.
|
||||
func (v *Var) MapsDeep(tags ...string) []map[string]interface{} {
|
||||
return gconv.MapsDeep(v.Val(), tags...)
|
||||
}
|
||||
|
||||
// MapToMap converts any map type variable `params` to another map type variable `pointer`.
|
||||
// See gconv.MapToMap.
|
||||
func (v *Var) MapToMap(pointer interface{}, mapping ...map[string]string) (err error) {
|
||||
return gconv.MapToMap(v.Val(), pointer, mapping...)
|
||||
}
|
||||
|
||||
// MapToMaps converts any map type variable <params> to another map type variable <pointer>.
|
||||
// MapToMaps converts any map type variable `params` to another map type variable `pointer`.
|
||||
// See gconv.MapToMaps.
|
||||
func (v *Var) MapToMaps(pointer interface{}, mapping ...map[string]string) (err error) {
|
||||
return gconv.MapToMaps(v.Val(), pointer, mapping...)
|
||||
}
|
||||
|
||||
// MapToMapsDeep converts any map type variable <params> to another map type variable
|
||||
// <pointer> recursively.
|
||||
// MapToMapsDeep converts any map type variable `params` to another map type variable
|
||||
// `pointer` recursively.
|
||||
// See gconv.MapToMapsDeep.
|
||||
func (v *Var) MapToMapsDeep(pointer interface{}, mapping ...map[string]string) (err error) {
|
||||
return gconv.MapToMapsDeep(v.Val(), pointer, mapping...)
|
||||
return gconv.MapToMaps(v.Val(), pointer, mapping...)
|
||||
}
|
||||
|
||||
@ -8,22 +8,22 @@ package gvar
|
||||
|
||||
import "github.com/gogf/gf/util/gconv"
|
||||
|
||||
// Ints converts and returns <v> as []int.
|
||||
// Ints converts and returns `v` as []int.
|
||||
func (v *Var) Ints() []int {
|
||||
return gconv.Ints(v.Val())
|
||||
}
|
||||
|
||||
// Int64s converts and returns <v> as []int64.
|
||||
// Int64s converts and returns `v` as []int64.
|
||||
func (v *Var) Int64s() []int64 {
|
||||
return gconv.Int64s(v.Val())
|
||||
}
|
||||
|
||||
// Uints converts and returns <v> as []uint.
|
||||
// Uints converts and returns `v` as []uint.
|
||||
func (v *Var) Uints() []uint {
|
||||
return gconv.Uints(v.Val())
|
||||
}
|
||||
|
||||
// Uint64s converts and returns <v> as []uint64.
|
||||
// Uint64s converts and returns `v` as []uint64.
|
||||
func (v *Var) Uint64s() []uint64 {
|
||||
return gconv.Uint64s(v.Val())
|
||||
}
|
||||
@ -33,22 +33,22 @@ func (v *Var) Floats() []float64 {
|
||||
return gconv.Floats(v.Val())
|
||||
}
|
||||
|
||||
// Float32s converts and returns <v> as []float32.
|
||||
// Float32s converts and returns `v` as []float32.
|
||||
func (v *Var) Float32s() []float32 {
|
||||
return gconv.Float32s(v.Val())
|
||||
}
|
||||
|
||||
// Float64s converts and returns <v> as []float64.
|
||||
// Float64s converts and returns `v` as []float64.
|
||||
func (v *Var) Float64s() []float64 {
|
||||
return gconv.Float64s(v.Val())
|
||||
}
|
||||
|
||||
// Strings converts and returns <v> as []string.
|
||||
// Strings converts and returns `v` as []string.
|
||||
func (v *Var) Strings() []string {
|
||||
return gconv.Strings(v.Val())
|
||||
}
|
||||
|
||||
// Interfaces converts and returns <v> as []interfaces{}.
|
||||
// Interfaces converts and returns `v` as []interfaces{}.
|
||||
func (v *Var) Interfaces() []interface{} {
|
||||
return gconv.Interfaces(v.Val())
|
||||
}
|
||||
@ -63,7 +63,7 @@ func (v *Var) Array() []interface{} {
|
||||
return v.Interfaces()
|
||||
}
|
||||
|
||||
// Vars converts and returns <v> as []Var.
|
||||
// Vars converts and returns `v` as []Var.
|
||||
func (v *Var) Vars() []*Var {
|
||||
array := gconv.Interfaces(v.Val())
|
||||
if len(array) == 0 {
|
||||
|
||||
@ -10,44 +10,48 @@ import (
|
||||
"github.com/gogf/gf/util/gconv"
|
||||
)
|
||||
|
||||
// Struct maps value of <v> to <pointer>.
|
||||
// The parameter <pointer> should be a pointer to a struct instance.
|
||||
// The parameter <mapping> is used to specify the key-to-attribute mapping rules.
|
||||
// Struct maps value of `v` to `pointer`.
|
||||
// The parameter `pointer` should be a pointer to a struct instance.
|
||||
// The parameter `mapping` is used to specify the key-to-attribute mapping rules.
|
||||
func (v *Var) Struct(pointer interface{}, mapping ...map[string]string) error {
|
||||
return gconv.Struct(v.Val(), pointer, mapping...)
|
||||
}
|
||||
|
||||
// Struct maps value of <v> to <pointer> recursively.
|
||||
// The parameter <pointer> should be a pointer to a struct instance.
|
||||
// The parameter <mapping> is used to specify the key-to-attribute mapping rules.
|
||||
// StructDeep maps value of `v` to `pointer` recursively.
|
||||
// The parameter `pointer` should be a pointer to a struct instance.
|
||||
// The parameter `mapping` is used to specify the key-to-attribute mapping rules.
|
||||
// Deprecated, use Struct instead.
|
||||
func (v *Var) StructDeep(pointer interface{}, mapping ...map[string]string) error {
|
||||
return gconv.StructDeep(v.Val(), pointer, mapping...)
|
||||
}
|
||||
|
||||
// Structs converts and returns <v> as given struct slice.
|
||||
// Structs converts and returns `v` as given struct slice.
|
||||
func (v *Var) Structs(pointer interface{}, mapping ...map[string]string) error {
|
||||
return gconv.Structs(v.Val(), pointer, mapping...)
|
||||
}
|
||||
|
||||
// StructsDeep converts and returns <v> as given struct slice recursively.
|
||||
// StructsDeep converts and returns `v` as given struct slice recursively.
|
||||
// Deprecated, use Struct instead.
|
||||
func (v *Var) StructsDeep(pointer interface{}, mapping ...map[string]string) error {
|
||||
return gconv.StructsDeep(v.Val(), pointer, mapping...)
|
||||
}
|
||||
|
||||
// Scan automatically calls Struct or Structs function according to the type of parameter
|
||||
// <pointer> to implement the converting.
|
||||
// It calls function Struct if <pointer> is type of *struct/**struct to do the converting.
|
||||
// It calls function Structs if <pointer> is type of *[]struct/*[]*struct to do the converting.
|
||||
// `pointer` to implement the converting.
|
||||
//
|
||||
// It calls function Struct if `pointer` is type of *struct/**struct to do the converting.
|
||||
// It calls function Structs if `pointer` is type of *[]struct/*[]*struct to do the converting.
|
||||
func (v *Var) Scan(pointer interface{}, mapping ...map[string]string) error {
|
||||
return gconv.Scan(v.Val(), pointer, mapping...)
|
||||
}
|
||||
|
||||
// ScanDeep automatically calls StructDeep or StructsDeep function according to the type of
|
||||
// parameter <pointer> to implement the converting.
|
||||
// It calls function StructDeep if <pointer> is type of *struct/**struct to do the converting.
|
||||
// It calls function StructsDeep if <pointer> is type of *[]struct/*[]*struct to do the converting.
|
||||
// parameter `pointer` to implement the converting.
|
||||
//
|
||||
// It calls function StructDeep if `pointer` is type of *struct/**struct to do the converting.
|
||||
// It calls function StructsDeep if `pointer` is type of *[]struct/*[]*struct to do the converting.
|
||||
//
|
||||
// Deprecated, use Scan instead.
|
||||
func (v *Var) ScanDeep(pointer interface{}, mapping ...map[string]string) error {
|
||||
return gconv.ScanDeep(v.Val(), pointer, mapping...)
|
||||
}
|
||||
|
||||
@ -11,7 +11,8 @@ import (
|
||||
"bytes"
|
||||
"crypto/aes"
|
||||
"crypto/cipher"
|
||||
"errors"
|
||||
"github.com/gogf/gf/errors/gcode"
|
||||
"github.com/gogf/gf/errors/gerror"
|
||||
)
|
||||
|
||||
var (
|
||||
@ -63,7 +64,7 @@ func DecryptCBC(cipherText []byte, key []byte, iv ...[]byte) ([]byte, error) {
|
||||
}
|
||||
blockSize := block.BlockSize()
|
||||
if len(cipherText) < blockSize {
|
||||
return nil, errors.New("cipherText too short")
|
||||
return nil, gerror.NewCode(gcode.CodeInvalidParameter, "cipherText too short")
|
||||
}
|
||||
ivValue := ([]byte)(nil)
|
||||
if len(iv) > 0 {
|
||||
@ -72,7 +73,7 @@ func DecryptCBC(cipherText []byte, key []byte, iv ...[]byte) ([]byte, error) {
|
||||
ivValue = []byte(IVDefaultValue)
|
||||
}
|
||||
if len(cipherText)%blockSize != 0 {
|
||||
return nil, errors.New("cipherText is not a multiple of the block size")
|
||||
return nil, gerror.NewCode(gcode.CodeInvalidParameter, "cipherText is not a multiple of the block size")
|
||||
}
|
||||
blockModel := cipher.NewCBCDecrypter(block, ivValue)
|
||||
plainText := make([]byte, len(cipherText))
|
||||
@ -93,22 +94,22 @@ func PKCS5Padding(src []byte, blockSize int) []byte {
|
||||
func PKCS5UnPadding(src []byte, blockSize int) ([]byte, error) {
|
||||
length := len(src)
|
||||
if blockSize <= 0 {
|
||||
return nil, errors.New("invalid blocklen")
|
||||
return nil, gerror.NewCode(gcode.CodeInvalidParameter, "invalid blocklen")
|
||||
}
|
||||
|
||||
if length%blockSize != 0 || length == 0 {
|
||||
return nil, errors.New("invalid data len")
|
||||
return nil, gerror.NewCode(gcode.CodeInvalidParameter, "invalid data len")
|
||||
}
|
||||
|
||||
unpadding := int(src[length-1])
|
||||
if unpadding > blockSize || unpadding == 0 {
|
||||
return nil, errors.New("invalid padding")
|
||||
return nil, gerror.NewCode(gcode.CodeInvalidParameter, "invalid padding")
|
||||
}
|
||||
|
||||
padding := src[length-unpadding:]
|
||||
for i := 0; i < unpadding; i++ {
|
||||
if padding[i] != byte(unpadding) {
|
||||
return nil, errors.New("invalid padding")
|
||||
return nil, gerror.NewCode(gcode.CodeInvalidParameter, "invalid padding")
|
||||
}
|
||||
}
|
||||
|
||||
@ -146,7 +147,7 @@ func DecryptCFB(cipherText []byte, key []byte, unPadding int, iv ...[]byte) ([]b
|
||||
return nil, err
|
||||
}
|
||||
if len(cipherText) < aes.BlockSize {
|
||||
return nil, errors.New("cipherText too short")
|
||||
return nil, gerror.NewCode(gcode.CodeInvalidParameter, "cipherText too short")
|
||||
}
|
||||
ivValue := ([]byte)(nil)
|
||||
if len(iv) > 0 {
|
||||
|
||||
@ -11,7 +11,8 @@ import (
|
||||
"bytes"
|
||||
"crypto/cipher"
|
||||
"crypto/des"
|
||||
"errors"
|
||||
"github.com/gogf/gf/errors/gcode"
|
||||
"github.com/gogf/gf/errors/gerror"
|
||||
)
|
||||
|
||||
const (
|
||||
@ -66,7 +67,7 @@ func DecryptECB(cipherText []byte, key []byte, padding int) ([]byte, error) {
|
||||
// The length of the <key> should be either 16 or 24 bytes.
|
||||
func EncryptECBTriple(plainText []byte, key []byte, padding int) ([]byte, error) {
|
||||
if len(key) != 16 && len(key) != 24 {
|
||||
return nil, errors.New("key length error")
|
||||
return nil, gerror.NewCode(gcode.CodeInvalidParameter, "key length error")
|
||||
}
|
||||
|
||||
text, err := Padding(plainText, padding)
|
||||
@ -100,7 +101,7 @@ func EncryptECBTriple(plainText []byte, key []byte, padding int) ([]byte, error)
|
||||
// The length of the <key> should be either 16 or 24 bytes.
|
||||
func DecryptECBTriple(cipherText []byte, key []byte, padding int) ([]byte, error) {
|
||||
if len(key) != 16 && len(key) != 24 {
|
||||
return nil, errors.New("key length error")
|
||||
return nil, gerror.NewCode(gcode.CodeInvalidParameter, "key length error")
|
||||
}
|
||||
|
||||
var newKey []byte
|
||||
@ -138,7 +139,7 @@ func EncryptCBC(plainText []byte, key []byte, iv []byte, padding int) ([]byte, e
|
||||
}
|
||||
|
||||
if len(iv) != block.BlockSize() {
|
||||
return nil, errors.New("iv length invalid")
|
||||
return nil, gerror.NewCode(gcode.CodeInvalidParameter, "iv length invalid")
|
||||
}
|
||||
|
||||
text, err := Padding(plainText, padding)
|
||||
@ -161,7 +162,7 @@ func DecryptCBC(cipherText []byte, key []byte, iv []byte, padding int) ([]byte,
|
||||
}
|
||||
|
||||
if len(iv) != block.BlockSize() {
|
||||
return nil, errors.New("iv length invalid")
|
||||
return nil, gerror.NewCode(gcode.CodeInvalidParameter, "iv length invalid")
|
||||
}
|
||||
|
||||
text := make([]byte, len(cipherText))
|
||||
@ -179,7 +180,7 @@ func DecryptCBC(cipherText []byte, key []byte, iv []byte, padding int) ([]byte,
|
||||
// EncryptCBCTriple encrypts <plainText> using TripleDES and CBC mode.
|
||||
func EncryptCBCTriple(plainText []byte, key []byte, iv []byte, padding int) ([]byte, error) {
|
||||
if len(key) != 16 && len(key) != 24 {
|
||||
return nil, errors.New("key length invalid")
|
||||
return nil, gerror.NewCode(gcode.CodeInvalidParameter, "key length invalid")
|
||||
}
|
||||
|
||||
var newKey []byte
|
||||
@ -196,7 +197,7 @@ func EncryptCBCTriple(plainText []byte, key []byte, iv []byte, padding int) ([]b
|
||||
}
|
||||
|
||||
if len(iv) != block.BlockSize() {
|
||||
return nil, errors.New("iv length invalid")
|
||||
return nil, gerror.NewCode(gcode.CodeInvalidParameter, "iv length invalid")
|
||||
}
|
||||
|
||||
text, err := Padding(plainText, padding)
|
||||
@ -214,7 +215,7 @@ func EncryptCBCTriple(plainText []byte, key []byte, iv []byte, padding int) ([]b
|
||||
// DecryptCBCTriple decrypts <cipherText> using TripleDES and CBC mode.
|
||||
func DecryptCBCTriple(cipherText []byte, key []byte, iv []byte, padding int) ([]byte, error) {
|
||||
if len(key) != 16 && len(key) != 24 {
|
||||
return nil, errors.New("key length invalid")
|
||||
return nil, gerror.NewCode(gcode.CodeInvalidParameter, "key length invalid")
|
||||
}
|
||||
|
||||
var newKey []byte
|
||||
@ -231,7 +232,7 @@ func DecryptCBCTriple(cipherText []byte, key []byte, iv []byte, padding int) ([]
|
||||
}
|
||||
|
||||
if len(iv) != block.BlockSize() {
|
||||
return nil, errors.New("iv length invalid")
|
||||
return nil, gerror.NewCode(gcode.CodeInvalidParameter, "iv length invalid")
|
||||
}
|
||||
|
||||
text := make([]byte, len(cipherText))
|
||||
@ -262,12 +263,12 @@ func Padding(text []byte, padding int) ([]byte, error) {
|
||||
switch padding {
|
||||
case NOPADDING:
|
||||
if len(text)%8 != 0 {
|
||||
return nil, errors.New("text length invalid")
|
||||
return nil, gerror.NewCode(gcode.CodeInvalidParameter, "text length invalid")
|
||||
}
|
||||
case PKCS5PADDING:
|
||||
return PaddingPKCS5(text, 8), nil
|
||||
default:
|
||||
return nil, errors.New("padding type error")
|
||||
return nil, gerror.NewCode(gcode.CodeInvalidParameter, "padding type error")
|
||||
}
|
||||
|
||||
return text, nil
|
||||
@ -277,12 +278,12 @@ func UnPadding(text []byte, padding int) ([]byte, error) {
|
||||
switch padding {
|
||||
case NOPADDING:
|
||||
if len(text)%8 != 0 {
|
||||
return nil, errors.New("text length invalid")
|
||||
return nil, gerror.NewCode(gcode.CodeInvalidParameter, "text length invalid")
|
||||
}
|
||||
case PKCS5PADDING:
|
||||
return UnPaddingPKCS5(text), nil
|
||||
default:
|
||||
return nil, errors.New("padding type error")
|
||||
return nil, gerror.NewCode(gcode.CodeInvalidParameter, "padding type error")
|
||||
}
|
||||
return text, nil
|
||||
}
|
||||
|
||||
@ -10,7 +10,7 @@ package gdb
|
||||
import (
|
||||
"context"
|
||||
"database/sql"
|
||||
"fmt"
|
||||
"github.com/gogf/gf/errors/gcode"
|
||||
"time"
|
||||
|
||||
"github.com/gogf/gf/errors/gerror"
|
||||
@ -38,6 +38,7 @@ type DB interface {
|
||||
// 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.
|
||||
Table(tableNameOrStruct ...interface{}) *Model
|
||||
|
||||
// Model creates and returns a new ORM model from given schema.
|
||||
@ -51,6 +52,9 @@ type DB interface {
|
||||
// Also see Core.Model.
|
||||
Model(tableNameOrStruct ...interface{}) *Model
|
||||
|
||||
// Raw creates and returns a model based on a raw sql not a table.
|
||||
Raw(rawSql string, args ...interface{}) *Model
|
||||
|
||||
// Schema creates and returns a schema.
|
||||
// Also see Core.Schema.
|
||||
Schema(schema string) *Schema
|
||||
@ -66,11 +70,17 @@ type DB interface {
|
||||
|
||||
// Ctx is a chaining function, which creates and returns a new DB that is a shallow copy
|
||||
// of current DB object and with given context in it.
|
||||
// Note that this returned DB object can be used only once, so do not assign it to
|
||||
// a global or package variable for long using.
|
||||
// Also see Core.Ctx.
|
||||
Ctx(ctx context.Context) DB
|
||||
|
||||
// Close closes the database and prevents new queries from starting.
|
||||
// Close then waits for all queries that have started processing on the server
|
||||
// to finish.
|
||||
//
|
||||
// It is rare to Close a DB, as the DB handle is meant to be
|
||||
// long-lived and shared between many goroutines.
|
||||
Close(ctx context.Context) error
|
||||
|
||||
// ===========================================================================
|
||||
// Query APIs.
|
||||
// ===========================================================================
|
||||
@ -83,31 +93,39 @@ type DB interface {
|
||||
// Common APIs for CURD.
|
||||
// ===========================================================================
|
||||
|
||||
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.
|
||||
|
||||
BatchInsert(table string, list interface{}, batch ...int) (sql.Result, error) // See Core.BatchInsert.
|
||||
BatchReplace(table string, list interface{}, batch ...int) (sql.Result, error) // See Core.BatchReplace.
|
||||
BatchSave(table string, list interface{}, batch ...int) (sql.Result, error) // See Core.BatchSave.
|
||||
|
||||
Insert(table string, data interface{}, batch ...int) (sql.Result, error) // See Core.Insert.
|
||||
InsertIgnore(table string, data interface{}, batch ...int) (sql.Result, error) // See Core.InsertIgnore.
|
||||
InsertAndGetId(table string, data interface{}, batch ...int) (int64, error) // See Core.InsertAndGetId.
|
||||
Replace(table string, data interface{}, batch ...int) (sql.Result, error) // See Core.Replace.
|
||||
Save(table string, data interface{}, batch ...int) (sql.Result, error) // See Core.Save.
|
||||
Update(table string, data interface{}, condition interface{}, args ...interface{}) (sql.Result, error) // See Core.Update.
|
||||
Delete(table string, condition interface{}, args ...interface{}) (sql.Result, error) // See Core.Delete.
|
||||
|
||||
// ===========================================================================
|
||||
// Internal APIs for CURD, which can be overwritten by custom CURD implements.
|
||||
// ===========================================================================
|
||||
|
||||
DoGetAll(ctx context.Context, link Link, sql string, args ...interface{}) (result Result, err error) // See Core.DoGetAll.
|
||||
DoInsert(ctx context.Context, link Link, table string, data List, option DoInsertOption) (result sql.Result, err error) // See Core.DoInsert.
|
||||
DoUpdate(ctx context.Context, link Link, table string, data interface{}, condition string, args ...interface{}) (result sql.Result, err error) // See Core.DoUpdate.
|
||||
DoDelete(ctx context.Context, link Link, table string, condition string, args ...interface{}) (result sql.Result, err error) // See Core.DoDelete.
|
||||
DoQuery(ctx context.Context, link Link, sql string, args ...interface{}) (rows *sql.Rows, err error) // See Core.DoQuery.
|
||||
DoExec(ctx context.Context, link Link, sql string, args ...interface{}) (result sql.Result, err error) // See Core.DoExec.
|
||||
DoCommit(ctx context.Context, link Link, sql string, args []interface{}) (newSql string, newArgs []interface{}, err error) // See Core.DoCommit.
|
||||
DoPrepare(ctx context.Context, link Link, sql string) (*Stmt, error) // See Core.DoPrepare.
|
||||
|
||||
// ===========================================================================
|
||||
// Query APIs for convenience purpose.
|
||||
// ===========================================================================
|
||||
|
||||
GetAll(sql string, args ...interface{}) (Result, error) // See Core.GetAll.
|
||||
GetOne(sql string, args ...interface{}) (Record, error) // See Core.GetOne.
|
||||
GetValue(sql string, args ...interface{}) (Value, error) // See Core.GetValue.
|
||||
GetArray(sql string, args ...interface{}) ([]Value, error) // See Core.GetArray.
|
||||
GetCount(sql string, args ...interface{}) (int, error) // See Core.GetCount.
|
||||
GetStruct(objPointer interface{}, sql string, args ...interface{}) error // See Core.GetStruct.
|
||||
GetStructs(objPointerSlice interface{}, sql string, args ...interface{}) error // See Core.GetStructs.
|
||||
GetScan(objPointer interface{}, sql string, args ...interface{}) error // See Core.GetScan.
|
||||
GetAll(sql string, args ...interface{}) (Result, error) // See Core.GetAll.
|
||||
GetOne(sql string, args ...interface{}) (Record, error) // See Core.GetOne.
|
||||
GetValue(sql string, args ...interface{}) (Value, error) // See Core.GetValue.
|
||||
GetArray(sql string, args ...interface{}) ([]Value, error) // See Core.GetArray.
|
||||
GetCount(sql string, args ...interface{}) (int, error) // See Core.GetCount.
|
||||
GetScan(objPointer interface{}, sql string, args ...interface{}) error // See Core.GetScan.
|
||||
Union(unions ...*Model) *Model // See Core.Union.
|
||||
UnionAll(unions ...*Model) *Model // See Core.UnionAll.
|
||||
|
||||
// ===========================================================================
|
||||
// Master/Slave specification support.
|
||||
@ -159,14 +177,7 @@ type DB interface {
|
||||
GetChars() (charLeft string, charRight string) // See Core.GetChars.
|
||||
Tables(ctx context.Context, schema ...string) (tables []string, err error) // See Core.Tables.
|
||||
TableFields(ctx context.Context, table string, schema ...string) (map[string]*TableField, error) // See Core.TableFields.
|
||||
FilteredLinkInfo() string // See Core.FilteredLinkInfo.
|
||||
|
||||
// HandleSqlBeforeCommit is a hook function, which deals with the sql string before
|
||||
// it's committed to underlying driver. The parameter `link` specifies the current
|
||||
// database connection operation object. You can modify the sql string `sql` and its
|
||||
// arguments `args` as you wish before they're committed to driver.
|
||||
// Also see Core.HandleSqlBeforeCommit.
|
||||
HandleSqlBeforeCommit(ctx context.Context, link Link, sql string, args []interface{}) (string, []interface{})
|
||||
FilteredLink() string // FilteredLink is used for filtering sensitive information in `Link` configuration before output it to tracing server.
|
||||
}
|
||||
|
||||
// Core is the base struct for database management.
|
||||
@ -176,8 +187,9 @@ type Core struct {
|
||||
group string // Configuration group name.
|
||||
debug *gtype.Bool // Enable debug mode for the database, which can be changed in runtime.
|
||||
cache *gcache.Cache // Cache manager, SQL result cache only.
|
||||
links *gmap.StrAnyMap // links caches all created links by node.
|
||||
schema *gtype.String // Custom schema for this object.
|
||||
logger *glog.Logger // Logger.
|
||||
logger *glog.Logger // Logger for logging functionality.
|
||||
config *ConfigNode // Current config node.
|
||||
}
|
||||
|
||||
@ -198,6 +210,12 @@ type Link interface {
|
||||
IsTransaction() bool
|
||||
}
|
||||
|
||||
// Logger is the logging interface for DB.
|
||||
type Logger interface {
|
||||
Error(ctx context.Context, s string)
|
||||
Debug(ctx context.Context, s string)
|
||||
}
|
||||
|
||||
// Sql is the sql recording struct.
|
||||
type Sql struct {
|
||||
Sql string // SQL string(may contain reserved char '?').
|
||||
@ -211,6 +229,14 @@ type Sql struct {
|
||||
IsTransaction bool // IsTransaction marks whether this sql is executed in transaction.
|
||||
}
|
||||
|
||||
// DoInsertOption is the input struct for function DoInsert.
|
||||
type DoInsertOption struct {
|
||||
OnDuplicateStr string
|
||||
OnDuplicateMap map[string]interface{}
|
||||
InsertOption int // Insert operation.
|
||||
BatchCount int // Batch count for batch inserting.
|
||||
}
|
||||
|
||||
// TableField is the struct for table field.
|
||||
type TableField struct {
|
||||
Index int // For ordering purpose as map is unordered.
|
||||
@ -239,6 +265,10 @@ type (
|
||||
)
|
||||
|
||||
const (
|
||||
queryTypeNormal = 0
|
||||
queryTypeCount = 1
|
||||
unionTypeNormal = 0
|
||||
unionTypeAll = 1
|
||||
insertOptionDefault = 0
|
||||
insertOptionReplace = 1
|
||||
insertOptionSave = 2
|
||||
@ -250,12 +280,12 @@ const (
|
||||
ctxTimeoutTypeExec = iota
|
||||
ctxTimeoutTypeQuery
|
||||
ctxTimeoutTypePrepare
|
||||
commandEnvKeyForDryRun = "gf.gdb.dryrun"
|
||||
ctxStrictKeyName = "gf.gdb.CtxStrictEnabled"
|
||||
ctxStrictErrorStr = "context is required for database operation, did you missing call function Ctx"
|
||||
)
|
||||
|
||||
var (
|
||||
// ErrNoRows is alias of sql.ErrNoRows.
|
||||
ErrNoRows = sql.ErrNoRows
|
||||
|
||||
// instances is the management map for instances.
|
||||
instances = gmap.NewStrAnyMap(true)
|
||||
|
||||
@ -281,9 +311,6 @@ var (
|
||||
// in the field name as it conflicts with "db.table.field" pattern in SOME situations.
|
||||
regularFieldNameWithoutDotRegPattern = `^[\w\-]+$`
|
||||
|
||||
// internalCache is the memory cache for internal usage.
|
||||
internalCache = gcache.New()
|
||||
|
||||
// tableFieldsMap caches the table information retrived from database.
|
||||
tableFieldsMap = gmap.New(true)
|
||||
|
||||
@ -294,7 +321,7 @@ var (
|
||||
|
||||
func init() {
|
||||
// allDryRun is initialized from environment or command options.
|
||||
allDryRun = gcmd.GetOptWithEnv("gf.gdb.dryrun", false).Bool()
|
||||
allDryRun = gcmd.GetOptWithEnv(commandEnvKeyForDryRun, false).Bool()
|
||||
}
|
||||
|
||||
// Register registers custom database driver to gdb.
|
||||
@ -315,7 +342,10 @@ func New(group ...string) (db DB, err error) {
|
||||
defer configs.RUnlock()
|
||||
|
||||
if len(configs.config) < 1 {
|
||||
return nil, gerror.New("database configuration is empty, please set the database configuration before using")
|
||||
return nil, gerror.NewCode(
|
||||
gcode.CodeInvalidConfiguration,
|
||||
"database configuration is empty, please set the database configuration before using",
|
||||
)
|
||||
}
|
||||
if _, ok := configs.config[groupName]; ok {
|
||||
if node, err := getConfigNodeByGroup(groupName, true); err == nil {
|
||||
@ -323,6 +353,7 @@ func New(group ...string) (db DB, err error) {
|
||||
group: groupName,
|
||||
debug: gtype.NewBool(),
|
||||
cache: gcache.New(),
|
||||
links: gmap.NewStrAnyMap(true),
|
||||
schema: gtype.NewString(),
|
||||
logger: glog.New(),
|
||||
config: node,
|
||||
@ -334,7 +365,8 @@ func New(group ...string) (db DB, err error) {
|
||||
}
|
||||
return c.db, nil
|
||||
} else {
|
||||
return nil, gerror.Newf(
|
||||
return nil, gerror.NewCodef(
|
||||
gcode.CodeInvalidConfiguration,
|
||||
`cannot find database driver for specified database type "%s", did you misspell type name "%s" or forget importing the database driver?`,
|
||||
node.Type, node.Type,
|
||||
)
|
||||
@ -343,7 +375,8 @@ func New(group ...string) (db DB, err error) {
|
||||
return nil, err
|
||||
}
|
||||
} else {
|
||||
return nil, gerror.Newf(
|
||||
return nil, gerror.NewCodef(
|
||||
gcode.CodeInvalidConfiguration,
|
||||
`database configuration node "%s" is not found, did you misspell group name "%s" or miss the database configuration?`,
|
||||
groupName, groupName,
|
||||
)
|
||||
@ -386,7 +419,7 @@ func getConfigNodeByGroup(group string, master bool) (*ConfigNode, error) {
|
||||
}
|
||||
}
|
||||
if len(masterList) < 1 {
|
||||
return nil, gerror.New("at least one master node configuration's need to make sense")
|
||||
return nil, gerror.NewCode(gcode.CodeInvalidConfiguration, "at least one master node configuration's need to make sense")
|
||||
}
|
||||
if len(slaveList) < 1 {
|
||||
slaveList = masterList
|
||||
@ -397,7 +430,7 @@ func getConfigNodeByGroup(group string, master bool) (*ConfigNode, error) {
|
||||
return getConfigNodeByWeight(slaveList), nil
|
||||
}
|
||||
} else {
|
||||
return nil, gerror.New(fmt.Sprintf("empty database configuration for item name '%s'", group))
|
||||
return nil, gerror.NewCodef(gcode.CodeInvalidConfiguration, "empty database configuration for item name '%s'", group)
|
||||
}
|
||||
}
|
||||
|
||||
@ -415,7 +448,7 @@ func getConfigNodeByWeight(cg ConfigGroup) *ConfigNode {
|
||||
for i := 0; i < len(cg); i++ {
|
||||
total += cg[i].Weight * 100
|
||||
}
|
||||
// If total is 0 means all of the nodes have no weight attribute configured.
|
||||
// If total is 0 means all the nodes have no weight attribute configured.
|
||||
// It then defaults each node's weight attribute to 1.
|
||||
if total == 0 {
|
||||
for i := 0; i < len(cg); i++ {
|
||||
@ -464,16 +497,18 @@ func (c *Core) getSqlDb(master bool, schema ...string) (sqlDb *sql.DB, err error
|
||||
node = &n
|
||||
}
|
||||
// Cache the underlying connection pool object by node.
|
||||
v, _ := internalCache.GetOrSetFuncLock(node.String(), func() (interface{}, error) {
|
||||
v := c.links.GetOrSetFuncLock(node.String(), func() interface{} {
|
||||
intlog.Printf(
|
||||
c.db.GetCtx(),
|
||||
`open new connection, master:%#v, config:%#v, node:%#v`,
|
||||
master, c.config, node,
|
||||
)
|
||||
defer func() {
|
||||
if err != nil {
|
||||
intlog.Printf(`open new connection failed: %v, %#v`, err, node)
|
||||
intlog.Printf(c.db.GetCtx(), `open new connection failed: %v, %#v`, err, node)
|
||||
} else {
|
||||
intlog.Printf(
|
||||
c.db.GetCtx(),
|
||||
`open new connection success, master:%#v, config:%#v, node:%#v`,
|
||||
master, c.config, node,
|
||||
)
|
||||
@ -482,7 +517,7 @@ func (c *Core) getSqlDb(master bool, schema ...string) (sqlDb *sql.DB, err error
|
||||
|
||||
sqlDb, err = c.db.Open(node)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return nil
|
||||
}
|
||||
|
||||
if c.config.MaxIdleConnCount > 0 {
|
||||
@ -506,8 +541,8 @@ func (c *Core) getSqlDb(master bool, schema ...string) (sqlDb *sql.DB, err error
|
||||
} else {
|
||||
sqlDb.SetConnMaxLifetime(defaultMaxConnLifeTime)
|
||||
}
|
||||
return sqlDb, nil
|
||||
}, 0)
|
||||
return sqlDb
|
||||
})
|
||||
if v != nil && sqlDb == nil {
|
||||
sqlDb = v.(*sql.DB)
|
||||
}
|
||||
|
||||
@ -11,6 +11,8 @@ import (
|
||||
"context"
|
||||
"database/sql"
|
||||
"fmt"
|
||||
"github.com/gogf/gf/errors/gcode"
|
||||
"github.com/gogf/gf/internal/intlog"
|
||||
"reflect"
|
||||
"strings"
|
||||
|
||||
@ -36,10 +38,7 @@ 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
|
||||
}
|
||||
ctx = context.WithValue(ctx, ctxStrictKeyName, 1)
|
||||
// It makes a shallow copy of current db and changes its context for next chaining operation.
|
||||
var (
|
||||
err error
|
||||
@ -88,11 +87,33 @@ func (c *Core) GetCtxTimeout(timeoutType int, ctx context.Context) (context.Cont
|
||||
return context.WithTimeout(ctx, c.db.GetConfig().PrepareTimeout)
|
||||
}
|
||||
default:
|
||||
panic(gerror.Newf("invalid context timeout type: %d", timeoutType))
|
||||
panic(gerror.NewCodef(gcode.CodeInvalidParameter, "invalid context timeout type: %d", timeoutType))
|
||||
}
|
||||
return ctx, func() {}
|
||||
}
|
||||
|
||||
// Close closes the database and prevents new queries from starting.
|
||||
// Close then waits for all queries that have started processing on the server
|
||||
// to finish.
|
||||
//
|
||||
// It is rare to Close a DB, as the DB handle is meant to be
|
||||
// long-lived and shared between many goroutines.
|
||||
func (c *Core) Close(ctx context.Context) (err error) {
|
||||
c.links.LockFunc(func(m map[string]interface{}) {
|
||||
for k, v := range m {
|
||||
if db, ok := v.(*sql.DB); ok {
|
||||
err = db.Close()
|
||||
intlog.Printf(ctx, `close link: %s, err: %v`, k, err)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
delete(m, k)
|
||||
}
|
||||
}
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
// Master creates and returns a connection from master node if master-slave configured.
|
||||
// It returns the default connection if master-slave not configured.
|
||||
func (c *Core) Master(schema ...string) (*sql.DB, error) {
|
||||
@ -119,12 +140,12 @@ func (c *Core) Slave(schema ...string) (*sql.DB, error) {
|
||||
|
||||
// GetAll queries and returns data records from database.
|
||||
func (c *Core) GetAll(sql string, args ...interface{}) (Result, error) {
|
||||
return c.DoGetAll(c.GetCtx(), nil, sql, args...)
|
||||
return c.db.DoGetAll(c.GetCtx(), nil, sql, args...)
|
||||
}
|
||||
|
||||
// DoGetAll queries and returns data records from database.
|
||||
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...)
|
||||
rows, err := c.db.DoQuery(ctx, link, sql, args...)
|
||||
if err != nil || rows == nil {
|
||||
return nil, err
|
||||
}
|
||||
@ -147,7 +168,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.DoGetAll(c.GetCtx(), nil, sql, args...)
|
||||
all, err := c.db.DoGetAll(c.GetCtx(), nil, sql, args...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@ -189,9 +210,9 @@ func (c *Core) GetScan(pointer interface{}, sql string, args ...interface{}) err
|
||||
k = t.Elem().Kind()
|
||||
switch k {
|
||||
case reflect.Array, reflect.Slice:
|
||||
return c.db.GetStructs(pointer, sql, args...)
|
||||
return c.db.GetCore().GetStructs(pointer, sql, args...)
|
||||
case reflect.Struct:
|
||||
return c.db.GetStruct(pointer, sql, args...)
|
||||
return c.db.GetCore().GetStruct(pointer, sql, args...)
|
||||
}
|
||||
return fmt.Errorf("element type should be type of struct/slice, unsupported: %v", k)
|
||||
}
|
||||
@ -224,6 +245,39 @@ func (c *Core) GetCount(sql string, args ...interface{}) (int, error) {
|
||||
return value.Int(), nil
|
||||
}
|
||||
|
||||
// Union does "(SELECT xxx FROM xxx) UNION (SELECT xxx FROM xxx) ..." statement.
|
||||
func (c *Core) Union(unions ...*Model) *Model {
|
||||
return c.doUnion(unionTypeNormal, unions...)
|
||||
}
|
||||
|
||||
// UnionAll does "(SELECT xxx FROM xxx) UNION ALL (SELECT xxx FROM xxx) ..." statement.
|
||||
func (c *Core) UnionAll(unions ...*Model) *Model {
|
||||
return c.doUnion(unionTypeAll, unions...)
|
||||
}
|
||||
|
||||
func (c *Core) doUnion(unionType int, unions ...*Model) *Model {
|
||||
var (
|
||||
unionTypeStr string
|
||||
composedSqlStr string
|
||||
composedArgs = make([]interface{}, 0)
|
||||
)
|
||||
if unionType == unionTypeAll {
|
||||
unionTypeStr = "UNION ALL"
|
||||
} else {
|
||||
unionTypeStr = "UNION"
|
||||
}
|
||||
for _, v := range unions {
|
||||
sqlWithHolder, holderArgs := v.getFormattedSqlAndArgs(queryTypeNormal, false)
|
||||
if composedSqlStr == "" {
|
||||
composedSqlStr += fmt.Sprintf(`(%s)`, sqlWithHolder)
|
||||
} else {
|
||||
composedSqlStr += fmt.Sprintf(` %s (%s)`, unionTypeStr, sqlWithHolder)
|
||||
}
|
||||
composedArgs = append(composedArgs, holderArgs...)
|
||||
}
|
||||
return c.db.Raw(composedSqlStr, composedArgs...)
|
||||
}
|
||||
|
||||
// PingMaster pings the master node to check authentication or keeps the connection alive.
|
||||
func (c *Core) PingMaster() error {
|
||||
if master, err := c.db.Master(); err != nil {
|
||||
@ -319,7 +373,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 forF given table.
|
||||
// This function is usually used for custom interface definition, you do not need call it manually.
|
||||
// The parameter `data` can be type of map/gmap/struct/*struct/[]map/[]struct, etc.
|
||||
// Eg:
|
||||
@ -331,181 +385,15 @@ 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(ctx context.Context, link Link, table string, data interface{}, option int, batch ...int) (result sql.Result, err error) {
|
||||
table = c.QuotePrefixTableName(table)
|
||||
func (c *Core) DoInsert(ctx context.Context, link Link, table string, list List, option DoInsertOption) (result sql.Result, err error) {
|
||||
var (
|
||||
fields []string
|
||||
values []string
|
||||
params []interface{}
|
||||
dataMap Map
|
||||
reflectValue = reflect.ValueOf(data)
|
||||
reflectKind = reflectValue.Kind()
|
||||
keys []string // Field names.
|
||||
values []string // Value holder string array, like: (?,?,?)
|
||||
params []interface{} // Values that will be committed to underlying database driver.
|
||||
onDuplicateStr string // onDuplicateStr is used in "ON DUPLICATE KEY UPDATE" statement.
|
||||
)
|
||||
if reflectKind == reflect.Ptr {
|
||||
reflectValue = reflectValue.Elem()
|
||||
reflectKind = reflectValue.Kind()
|
||||
}
|
||||
switch reflectKind {
|
||||
case reflect.Slice, reflect.Array:
|
||||
return c.DoBatchInsert(ctx, link, table, data, option, batch...)
|
||||
case reflect.Struct:
|
||||
if _, ok := data.(apiInterfaces); ok {
|
||||
return c.DoBatchInsert(ctx, link, table, data, option, batch...)
|
||||
} else {
|
||||
dataMap = ConvertDataForTableRecord(data)
|
||||
}
|
||||
case reflect.Map:
|
||||
dataMap = ConvertDataForTableRecord(data)
|
||||
default:
|
||||
return result, gerror.New(fmt.Sprint("unsupported data type:", reflectKind))
|
||||
}
|
||||
if len(dataMap) == 0 {
|
||||
return nil, gerror.New("data cannot be empty")
|
||||
}
|
||||
var (
|
||||
charL, charR = c.db.GetChars()
|
||||
operation = GetInsertOperationByOption(option)
|
||||
updateStr = ""
|
||||
)
|
||||
for k, v := range dataMap {
|
||||
fields = append(fields, charL+k+charR)
|
||||
if s, ok := v.(Raw); ok {
|
||||
values = append(values, gconv.String(s))
|
||||
} else {
|
||||
values = append(values, "?")
|
||||
params = append(params, v)
|
||||
}
|
||||
}
|
||||
if option == insertOptionSave {
|
||||
for k, _ := range dataMap {
|
||||
// If it's SAVE operation,
|
||||
// do not automatically update the creating time.
|
||||
if c.isSoftCreatedFiledName(k) {
|
||||
continue
|
||||
}
|
||||
if len(updateStr) > 0 {
|
||||
updateStr += ","
|
||||
}
|
||||
updateStr += fmt.Sprintf(
|
||||
"%s%s%s=VALUES(%s%s%s)",
|
||||
charL, k, charR,
|
||||
charL, k, charR,
|
||||
)
|
||||
}
|
||||
updateStr = fmt.Sprintf("ON DUPLICATE KEY UPDATE %s", updateStr)
|
||||
}
|
||||
if link == nil {
|
||||
if link, err = c.MasterLink(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
return c.DoExec(ctx, link, fmt.Sprintf(
|
||||
"%s INTO %s(%s) VALUES(%s) %s",
|
||||
operation, table, strings.Join(fields, ","),
|
||||
strings.Join(values, ","), updateStr,
|
||||
), params...)
|
||||
}
|
||||
|
||||
// BatchInsert batch inserts data.
|
||||
// The parameter `list` must be type of slice of map or struct.
|
||||
func (c *Core) BatchInsert(table string, list interface{}, batch ...int) (sql.Result, error) {
|
||||
if len(batch) > 0 {
|
||||
return c.Model(table).Data(list).Batch(batch[0]).Insert()
|
||||
}
|
||||
return c.Model(table).Data(list).Insert()
|
||||
}
|
||||
|
||||
// BatchInsertIgnore batch inserts data with ignore option.
|
||||
// The parameter `list` must be type of slice of map or struct.
|
||||
func (c *Core) BatchInsertIgnore(table string, list interface{}, batch ...int) (sql.Result, error) {
|
||||
if len(batch) > 0 {
|
||||
return c.Model(table).Data(list).Batch(batch[0]).InsertIgnore()
|
||||
}
|
||||
return c.Model(table).Data(list).InsertIgnore()
|
||||
}
|
||||
|
||||
// BatchReplace batch replaces data.
|
||||
// The parameter `list` must be type of slice of map or struct.
|
||||
func (c *Core) BatchReplace(table string, list interface{}, batch ...int) (sql.Result, error) {
|
||||
if len(batch) > 0 {
|
||||
return c.Model(table).Data(list).Batch(batch[0]).Replace()
|
||||
}
|
||||
return c.Model(table).Data(list).Replace()
|
||||
}
|
||||
|
||||
// BatchSave batch replaces data.
|
||||
// The parameter `list` must be type of slice of map or struct.
|
||||
func (c *Core) BatchSave(table string, list interface{}, batch ...int) (sql.Result, error) {
|
||||
if len(batch) > 0 {
|
||||
return c.Model(table).Data(list).Batch(batch[0]).Save()
|
||||
}
|
||||
return c.Model(table).Data(list).Save()
|
||||
}
|
||||
|
||||
// DoBatchInsert batch inserts/replaces/saves data.
|
||||
// This function is usually used for custom interface definition, you do not need call it manually.
|
||||
func (c *Core) DoBatchInsert(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: (?,?,?)
|
||||
params []interface{} // Values that will be committed to underlying database driver.
|
||||
listMap List // The data list that passed from caller.
|
||||
)
|
||||
switch value := list.(type) {
|
||||
case Result:
|
||||
listMap = value.List()
|
||||
case Record:
|
||||
listMap = List{value.Map()}
|
||||
case List:
|
||||
listMap = value
|
||||
case Map:
|
||||
listMap = List{value}
|
||||
default:
|
||||
var (
|
||||
rv = reflect.ValueOf(list)
|
||||
kind = rv.Kind()
|
||||
)
|
||||
if kind == reflect.Ptr {
|
||||
rv = rv.Elem()
|
||||
kind = rv.Kind()
|
||||
}
|
||||
switch kind {
|
||||
// If it's slice type, it then converts it to List type.
|
||||
case reflect.Slice, reflect.Array:
|
||||
listMap = make(List, rv.Len())
|
||||
for i := 0; i < rv.Len(); i++ {
|
||||
listMap[i] = ConvertDataForTableRecord(rv.Index(i).Interface())
|
||||
}
|
||||
case reflect.Map:
|
||||
listMap = List{ConvertDataForTableRecord(value)}
|
||||
case reflect.Struct:
|
||||
if v, ok := value.(apiInterfaces); ok {
|
||||
var (
|
||||
array = v.Interfaces()
|
||||
list = make(List, len(array))
|
||||
)
|
||||
for i := 0; i < len(array); i++ {
|
||||
list[i] = ConvertDataForTableRecord(array[i])
|
||||
}
|
||||
listMap = list
|
||||
} else {
|
||||
listMap = List{ConvertDataForTableRecord(value)}
|
||||
}
|
||||
default:
|
||||
return result, gerror.New(fmt.Sprint("unsupported list type:", kind))
|
||||
}
|
||||
}
|
||||
if len(listMap) < 1 {
|
||||
return result, gerror.New("data list cannot be empty")
|
||||
}
|
||||
if link == nil {
|
||||
if link, err = c.MasterLink(); err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
// Handle the field names and place holders.
|
||||
for k, _ := range listMap[0] {
|
||||
for k, _ := range list[0] {
|
||||
keys = append(keys, k)
|
||||
}
|
||||
// Prepare the batch result pointer.
|
||||
@ -513,54 +401,35 @@ func (c *Core) DoBatchInsert(ctx context.Context, link Link, table string, list
|
||||
charL, charR = c.db.GetChars()
|
||||
batchResult = new(SqlResult)
|
||||
keysStr = charL + strings.Join(keys, charR+","+charL) + charR
|
||||
operation = GetInsertOperationByOption(option)
|
||||
updateStr = ""
|
||||
operation = GetInsertOperationByOption(option.InsertOption)
|
||||
)
|
||||
if option == insertOptionSave {
|
||||
for _, k := range keys {
|
||||
// If it's SAVE operation,
|
||||
// do not automatically update the creating time.
|
||||
if c.isSoftCreatedFiledName(k) {
|
||||
continue
|
||||
}
|
||||
if len(updateStr) > 0 {
|
||||
updateStr += ","
|
||||
}
|
||||
updateStr += fmt.Sprintf(
|
||||
"%s%s%s=VALUES(%s%s%s)",
|
||||
charL, k, charR,
|
||||
charL, k, charR,
|
||||
)
|
||||
}
|
||||
updateStr = fmt.Sprintf("ON DUPLICATE KEY UPDATE %s", updateStr)
|
||||
}
|
||||
batchNum := defaultBatchNumber
|
||||
if len(batch) > 0 && batch[0] > 0 {
|
||||
batchNum = batch[0]
|
||||
if option.InsertOption == insertOptionSave {
|
||||
onDuplicateStr = c.formatOnDuplicate(keys, option)
|
||||
}
|
||||
var (
|
||||
listMapLen = len(listMap)
|
||||
listLength = len(list)
|
||||
valueHolder = make([]string, 0)
|
||||
)
|
||||
for i := 0; i < listMapLen; i++ {
|
||||
for i := 0; i < listLength; i++ {
|
||||
values = values[:0]
|
||||
// Note that the map type is unordered,
|
||||
// so it should use slice+key to retrieve the value.
|
||||
for _, k := range keys {
|
||||
if s, ok := listMap[i][k].(Raw); ok {
|
||||
if s, ok := list[i][k].(Raw); ok {
|
||||
values = append(values, gconv.String(s))
|
||||
} else {
|
||||
values = append(values, "?")
|
||||
params = append(params, listMap[i][k])
|
||||
params = append(params, list[i][k])
|
||||
}
|
||||
}
|
||||
valueHolder = append(valueHolder, "("+gstr.Join(values, ",")+")")
|
||||
if len(valueHolder) == batchNum || (i == listMapLen-1 && len(valueHolder) > 0) {
|
||||
r, err := c.DoExec(ctx, link, fmt.Sprintf(
|
||||
// Batch package checks: It meets the batch number or it is the last element.
|
||||
if len(valueHolder) == option.BatchCount || (i == listLength-1 && len(valueHolder) > 0) {
|
||||
r, err := c.db.DoExec(ctx, link, fmt.Sprintf(
|
||||
"%s INTO %s(%s) VALUES%s %s",
|
||||
operation, table, keysStr,
|
||||
operation, c.QuotePrefixTableName(table), keysStr,
|
||||
gstr.Join(valueHolder, ","),
|
||||
updateStr,
|
||||
onDuplicateStr,
|
||||
), params...)
|
||||
if err != nil {
|
||||
return r, err
|
||||
@ -578,6 +447,51 @@ func (c *Core) DoBatchInsert(ctx context.Context, link Link, table string, list
|
||||
return batchResult, nil
|
||||
}
|
||||
|
||||
func (c *Core) formatOnDuplicate(columns []string, option DoInsertOption) string {
|
||||
var (
|
||||
onDuplicateStr string
|
||||
)
|
||||
if option.OnDuplicateStr != "" {
|
||||
onDuplicateStr = option.OnDuplicateStr
|
||||
} else if len(option.OnDuplicateMap) > 0 {
|
||||
for k, v := range option.OnDuplicateMap {
|
||||
if len(onDuplicateStr) > 0 {
|
||||
onDuplicateStr += ","
|
||||
}
|
||||
switch v.(type) {
|
||||
case Raw, *Raw:
|
||||
onDuplicateStr += fmt.Sprintf(
|
||||
"%s=%s",
|
||||
c.QuoteWord(k),
|
||||
v,
|
||||
)
|
||||
default:
|
||||
onDuplicateStr += fmt.Sprintf(
|
||||
"%s=VALUES(%s)",
|
||||
c.QuoteWord(k),
|
||||
c.QuoteWord(gconv.String(v)),
|
||||
)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
for _, column := range columns {
|
||||
// If it's SAVE operation, do not automatically update the creating time.
|
||||
if c.isSoftCreatedFieldName(column) {
|
||||
continue
|
||||
}
|
||||
if len(onDuplicateStr) > 0 {
|
||||
onDuplicateStr += ","
|
||||
}
|
||||
onDuplicateStr += fmt.Sprintf(
|
||||
"%s=VALUES(%s)",
|
||||
c.QuoteWord(column),
|
||||
c.QuoteWord(column),
|
||||
)
|
||||
}
|
||||
}
|
||||
return fmt.Sprintf("ON DUPLICATE KEY UPDATE %s", onDuplicateStr)
|
||||
}
|
||||
|
||||
// Update does "UPDATE ... " statement for the table.
|
||||
//
|
||||
// The parameter `data` can be type of string/map/gmap/struct/*struct, etc.
|
||||
@ -615,29 +529,34 @@ func (c *Core) DoUpdate(ctx context.Context, link Link, table string, data inter
|
||||
switch kind {
|
||||
case reflect.Map, reflect.Struct:
|
||||
var (
|
||||
fields []string
|
||||
dataMap = ConvertDataForTableRecord(data)
|
||||
fields []string
|
||||
dataMap = ConvertDataForTableRecord(data)
|
||||
counterHandler = func(column string, counter Counter) {
|
||||
if counter.Value != 0 {
|
||||
var (
|
||||
column = c.QuoteWord(column)
|
||||
columnRef = c.QuoteWord(counter.Field)
|
||||
columnVal = counter.Value
|
||||
operator = "+"
|
||||
)
|
||||
if columnVal < 0 {
|
||||
operator = "-"
|
||||
columnVal = -columnVal
|
||||
}
|
||||
fields = append(fields, fmt.Sprintf("%s=%s%s?", column, columnRef, operator))
|
||||
params = append(params, columnVal)
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
for k, v := range dataMap {
|
||||
switch value := v.(type) {
|
||||
case *Counter:
|
||||
if value.Value != 0 {
|
||||
column := k
|
||||
if value.Field != "" {
|
||||
column = c.QuoteWord(value.Field)
|
||||
}
|
||||
fields = append(fields, fmt.Sprintf("%s=%s+?", column, column))
|
||||
params = append(params, value.Value)
|
||||
}
|
||||
counterHandler(k, *value)
|
||||
|
||||
case Counter:
|
||||
if value.Value != 0 {
|
||||
column := k
|
||||
if value.Field != "" {
|
||||
column = c.QuoteWord(value.Field)
|
||||
}
|
||||
fields = append(fields, fmt.Sprintf("%s=%s+?", column, column))
|
||||
params = append(params, value.Value)
|
||||
}
|
||||
counterHandler(k, value)
|
||||
|
||||
default:
|
||||
if s, ok := v.(Raw); ok {
|
||||
fields = append(fields, c.QuoteWord(k)+"="+gconv.String(s))
|
||||
@ -648,11 +567,12 @@ func (c *Core) DoUpdate(ctx context.Context, link Link, table string, data inter
|
||||
}
|
||||
}
|
||||
updates = strings.Join(fields, ",")
|
||||
|
||||
default:
|
||||
updates = gconv.String(data)
|
||||
}
|
||||
if len(updates) == 0 {
|
||||
return nil, gerror.New("data cannot be empty")
|
||||
return nil, gerror.NewCode(gcode.CodeMissingParameter, "data cannot be empty")
|
||||
}
|
||||
if len(params) > 0 {
|
||||
args = append(params, args...)
|
||||
@ -663,7 +583,7 @@ func (c *Core) DoUpdate(ctx context.Context, link Link, table string, data inter
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
return c.DoExec(ctx, link, fmt.Sprintf("UPDATE %s SET %s%s", table, updates, condition), args...)
|
||||
return c.db.DoExec(ctx, link, fmt.Sprintf("UPDATE %s SET %s%s", table, updates, condition), args...)
|
||||
}
|
||||
|
||||
// Delete does "DELETE FROM ... " statement for the table.
|
||||
@ -690,7 +610,7 @@ func (c *Core) DoDelete(ctx context.Context, link Link, table string, condition
|
||||
}
|
||||
}
|
||||
table = c.QuotePrefixTableName(table)
|
||||
return c.DoExec(ctx, link, fmt.Sprintf("DELETE FROM %s%s", table, condition), args...)
|
||||
return c.db.DoExec(ctx, link, fmt.Sprintf("DELETE FROM %s%s", table, condition), args...)
|
||||
}
|
||||
|
||||
// convertRowsToResult converts underlying data record type sql.Rows to Result type.
|
||||
@ -711,7 +631,7 @@ func (c *Core) convertRowsToResult(rows *sql.Rows) (Result, error) {
|
||||
}
|
||||
var (
|
||||
values = make([]interface{}, len(columnNames))
|
||||
records = make(Result, 0)
|
||||
result = make(Result, 0)
|
||||
scanArgs = make([]interface{}, len(values))
|
||||
)
|
||||
for i := range values {
|
||||
@ -719,22 +639,22 @@ func (c *Core) convertRowsToResult(rows *sql.Rows) (Result, error) {
|
||||
}
|
||||
for {
|
||||
if err := rows.Scan(scanArgs...); err != nil {
|
||||
return records, err
|
||||
return result, err
|
||||
}
|
||||
row := make(Record)
|
||||
record := Record{}
|
||||
for i, value := range values {
|
||||
if value == nil {
|
||||
row[columnNames[i]] = gvar.New(nil)
|
||||
record[columnNames[i]] = gvar.New(nil)
|
||||
} else {
|
||||
row[columnNames[i]] = gvar.New(c.convertFieldValueToLocalValue(value, columnTypes[i]))
|
||||
record[columnNames[i]] = gvar.New(c.convertFieldValueToLocalValue(value, columnTypes[i]))
|
||||
}
|
||||
}
|
||||
records = append(records, row)
|
||||
result = append(result, record)
|
||||
if !rows.Next() {
|
||||
break
|
||||
}
|
||||
}
|
||||
return records, nil
|
||||
return result, nil
|
||||
}
|
||||
|
||||
// MarshalJSON implements the interface MarshalJSON for json.Marshal.
|
||||
@ -778,8 +698,8 @@ func (c *Core) HasTable(name string) (bool, error) {
|
||||
return false, nil
|
||||
}
|
||||
|
||||
// isSoftCreatedFiledName checks and returns whether given filed name is an automatic-filled created time.
|
||||
func (c *Core) isSoftCreatedFiledName(fieldName string) bool {
|
||||
// isSoftCreatedFieldName checks and returns whether given filed name is an automatic-filled created time.
|
||||
func (c *Core) isSoftCreatedFieldName(fieldName string) bool {
|
||||
if fieldName == "" {
|
||||
return false
|
||||
}
|
||||
|
||||
@ -8,12 +8,11 @@ package gdb
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/gogf/gf/os/glog"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/gogf/gf/os/gcache"
|
||||
|
||||
"github.com/gogf/gf/os/glog"
|
||||
)
|
||||
|
||||
// Config is the configuration management object.
|
||||
@ -30,13 +29,14 @@ type ConfigNode struct {
|
||||
Pass string `json:"pass"` // Authentication password.
|
||||
Name string `json:"name"` // Default used database name.
|
||||
Type string `json:"type"` // Database type: mysql, sqlite, mssql, pgsql, oracle.
|
||||
Link string `json:"link"` // (Optional) Custom link information, when it is used, configuration Host/Port/User/Pass/Name are ignored.
|
||||
Role string `json:"role"` // (Optional, "master" in default) Node role, used for master-slave mode: master, slave.
|
||||
Debug bool `json:"debug"` // (Optional) Debug mode enables debug information logging and output.
|
||||
Prefix string `json:"prefix"` // (Optional) Table prefix.
|
||||
DryRun bool `json:"dryRun"` // (Optional) Dry run, which does SELECT but no INSERT/UPDATE/DELETE statements.
|
||||
Weight int `json:"weight"` // (Optional) Weight for load balance calculating, it's useless if there's just one node.
|
||||
Charset string `json:"charset"` // (Optional, "utf8mb4" in default) Custom charset when operating on database.
|
||||
LinkInfo string `json:"link"` // (Optional) Custom link information, when it is used, configuration Host/Port/User/Pass/Name are ignored.
|
||||
Timezone string `json:"timezone"` // (Optional) Sets the time zone for displaying and interpreting time stamps.
|
||||
MaxIdleConnCount int `json:"maxIdle"` // (Optional) Max idle connection configuration for underlying connection pool.
|
||||
MaxOpenConnCount int `json:"maxOpen"` // (Optional) Max open connection configuration for underlying connection pool.
|
||||
MaxConnLifeTime time.Duration `json:"maxLifeTime"` // (Optional) Max amount of time a connection may be idle before being closed.
|
||||
@ -48,6 +48,7 @@ type ConfigNode struct {
|
||||
UpdatedAt string `json:"updatedAt"` // (Optional) The filed name of table for automatic-filled updated datetime.
|
||||
DeletedAt string `json:"deletedAt"` // (Optional) The filed name of table for automatic-filled updated datetime.
|
||||
TimeMaintainDisabled bool `json:"timeMaintainDisabled"` // (Optional) Disable the automatic time maintaining feature.
|
||||
CtxStrict bool `json:"ctxStrict"` // (Optional) Strictly require context input for all database operations.
|
||||
}
|
||||
|
||||
const (
|
||||
@ -137,7 +138,7 @@ func (c *Core) SetLogger(logger *glog.Logger) {
|
||||
c.logger = logger
|
||||
}
|
||||
|
||||
// GetLogger returns the logger of the orm.
|
||||
// GetLogger returns the (logger) of the orm.
|
||||
func (c *Core) GetLogger() *glog.Logger {
|
||||
return c.logger
|
||||
}
|
||||
@ -186,7 +187,7 @@ func (node *ConfigNode) String() string {
|
||||
node.MaxIdleConnCount,
|
||||
node.MaxOpenConnCount,
|
||||
node.MaxConnLifeTime,
|
||||
node.LinkInfo,
|
||||
node.Link,
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@ -12,7 +12,6 @@ import (
|
||||
"fmt"
|
||||
"github.com/gogf/gf"
|
||||
"github.com/gogf/gf/net/gtrace"
|
||||
"github.com/gogf/gf/os/gcmd"
|
||||
"go.opentelemetry.io/otel"
|
||||
"go.opentelemetry.io/otel/attribute"
|
||||
"go.opentelemetry.io/otel/codes"
|
||||
@ -34,19 +33,9 @@ const (
|
||||
tracingEventDbExecutionType = "db.execution.type"
|
||||
)
|
||||
|
||||
var (
|
||||
// tracingInternal enables tracing for internal type spans.
|
||||
// It's true in default.
|
||||
tracingInternal = true
|
||||
)
|
||||
|
||||
func init() {
|
||||
tracingInternal = gcmd.GetOptWithEnv("gf.tracing.internal", true).Bool()
|
||||
}
|
||||
|
||||
// addSqlToTracing adds sql information to tracer if it's enabled.
|
||||
func (c *Core) addSqlToTracing(ctx context.Context, sql *Sql) {
|
||||
if !tracingInternal || !gtrace.IsActivated(ctx) {
|
||||
if !gtrace.IsTracingInternal() || !gtrace.IsActivated(ctx) {
|
||||
return
|
||||
}
|
||||
tr := otel.GetTracerProvider().Tracer(
|
||||
@ -76,8 +65,8 @@ func (c *Core) addSqlToTracing(ctx context.Context, sql *Sql) {
|
||||
if c.db.GetConfig().User != "" {
|
||||
labels = append(labels, attribute.String(tracingAttrDbUser, c.db.GetConfig().User))
|
||||
}
|
||||
if filteredLinkInfo := c.db.FilteredLinkInfo(); filteredLinkInfo != "" {
|
||||
labels = append(labels, attribute.String(tracingAttrDbLink, c.db.FilteredLinkInfo()))
|
||||
if filteredLink := c.db.FilteredLink(); filteredLink != "" {
|
||||
labels = append(labels, attribute.String(tracingAttrDbLink, c.db.FilteredLink()))
|
||||
}
|
||||
if group := c.db.GetGroup(); group != "" {
|
||||
labels = append(labels, attribute.String(tracingAttrDbGroup, group))
|
||||
|
||||
@ -28,6 +28,7 @@ type TX struct {
|
||||
master *sql.DB // master is the raw and underlying database manager.
|
||||
transactionId string // transactionId is an unique id generated by this object for this transaction.
|
||||
transactionCount int // transactionCount marks the times that Begins.
|
||||
isClosed bool // isClosed marks this transaction has already been committed or rolled back.
|
||||
}
|
||||
|
||||
const (
|
||||
@ -162,6 +163,9 @@ func TXFromCtx(ctx context.Context, group string) *TX {
|
||||
v := ctx.Value(transactionKeyForContext(group))
|
||||
if v != nil {
|
||||
tx := v.(*TX)
|
||||
if tx.IsClosed() {
|
||||
return nil
|
||||
}
|
||||
tx.ctx = ctx
|
||||
return tx
|
||||
}
|
||||
@ -210,6 +214,7 @@ func (tx *TX) Commit() error {
|
||||
IsTransaction: true,
|
||||
}
|
||||
)
|
||||
tx.isClosed = true
|
||||
tx.db.GetCore().addSqlToTracing(tx.ctx, sqlObj)
|
||||
if tx.db.GetDebug() {
|
||||
tx.db.GetCore().writeSqlToLogger(tx.ctx, sqlObj)
|
||||
@ -243,6 +248,7 @@ func (tx *TX) Rollback() error {
|
||||
IsTransaction: true,
|
||||
}
|
||||
)
|
||||
tx.isClosed = true
|
||||
tx.db.GetCore().addSqlToTracing(tx.ctx, sqlObj)
|
||||
if tx.db.GetDebug() {
|
||||
tx.db.GetCore().writeSqlToLogger(tx.ctx, sqlObj)
|
||||
@ -250,6 +256,11 @@ func (tx *TX) Rollback() error {
|
||||
return err
|
||||
}
|
||||
|
||||
// IsClosed checks and returns this transaction has already been committed or rolled back.
|
||||
func (tx *TX) IsClosed() bool {
|
||||
return tx.isClosed
|
||||
}
|
||||
|
||||
// Begin starts a nested transaction procedure.
|
||||
func (tx *TX) Begin() error {
|
||||
_, err := tx.Exec("SAVEPOINT " + tx.transactionKeyForNestedPoint())
|
||||
@ -317,13 +328,13 @@ func (tx *TX) Transaction(ctx context.Context, f func(ctx context.Context, tx *T
|
||||
// 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...)
|
||||
return tx.db.DoQuery(tx.ctx, &txLink{tx.tx}, sql, args...)
|
||||
}
|
||||
|
||||
// Exec does none query operation on transaction.
|
||||
// See Core.Exec.
|
||||
func (tx *TX) Exec(sql string, args ...interface{}) (sql.Result, error) {
|
||||
return tx.db.GetCore().DoExec(tx.ctx, &txLink{tx.tx}, sql, args...)
|
||||
return tx.db.DoExec(tx.ctx, &txLink{tx.tx}, sql, args...)
|
||||
}
|
||||
|
||||
// Prepare creates a prepared statement for later queries or executions.
|
||||
@ -332,7 +343,7 @@ func (tx *TX) Exec(sql string, args ...interface{}) (sql.Result, error) {
|
||||
// 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)
|
||||
return tx.db.DoPrepare(tx.ctx, &txLink{tx.tx}, sql)
|
||||
}
|
||||
|
||||
// GetAll queries and returns data records from database.
|
||||
@ -503,42 +514,6 @@ func (tx *TX) Save(table string, data interface{}, batch ...int) (sql.Result, er
|
||||
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.
|
||||
|
||||
@ -10,6 +10,8 @@ package gdb
|
||||
import (
|
||||
"context"
|
||||
"database/sql"
|
||||
"github.com/gogf/gf/errors/gcode"
|
||||
"github.com/gogf/gf/errors/gerror"
|
||||
|
||||
"github.com/gogf/gf/os/gtime"
|
||||
)
|
||||
@ -17,7 +19,7 @@ import (
|
||||
// Query commits one query SQL to underlying driver and returns the execution result.
|
||||
// It is most commonly used for data querying.
|
||||
func (c *Core) Query(sql string, args ...interface{}) (rows *sql.Rows, err error) {
|
||||
return c.DoQuery(c.GetCtx(), nil, sql, args...)
|
||||
return c.db.DoQuery(c.GetCtx(), nil, sql, args...)
|
||||
}
|
||||
|
||||
// DoQuery commits the sql string and its arguments to underlying driver
|
||||
@ -25,20 +27,30 @@ func (c *Core) Query(sql string, args ...interface{}) (rows *sql.Rows, err error
|
||||
func (c *Core) DoQuery(ctx context.Context, link Link, sql string, args ...interface{}) (rows *sql.Rows, err error) {
|
||||
// Transaction checks.
|
||||
if link == nil {
|
||||
if link, err = c.SlaveLink(); err != nil {
|
||||
if tx := TXFromCtx(ctx, c.db.GetGroup()); tx != nil {
|
||||
// Firstly, check and retrieve transaction link from context.
|
||||
link = &txLink{tx.tx}
|
||||
} else if link, err = c.SlaveLink(); err != nil {
|
||||
// Or else it creates one from master node.
|
||||
return nil, err
|
||||
}
|
||||
} else if !link.IsTransaction() {
|
||||
// If current link is not transaction link, it checks and retrieves transaction from context.
|
||||
if tx := TXFromCtx(ctx, c.db.GetGroup()); tx != nil {
|
||||
link = &txLink{tx.tx}
|
||||
}
|
||||
}
|
||||
// Link execution.
|
||||
sql, args = formatSql(sql, args)
|
||||
sql, args = c.db.HandleSqlBeforeCommit(ctx, link, sql, args)
|
||||
|
||||
if c.GetConfig().QueryTimeout > 0 {
|
||||
ctx, _ = context.WithTimeout(ctx, c.GetConfig().QueryTimeout)
|
||||
}
|
||||
|
||||
// Link execution.
|
||||
sql, args = formatSql(sql, args)
|
||||
sql, args, err = c.db.DoCommit(ctx, link, sql, args)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
mTime1 := gtime.TimestampMilli()
|
||||
rows, err = link.QueryContext(ctx, sql, args...)
|
||||
mTime2 := gtime.TimestampMilli()
|
||||
@ -69,7 +81,7 @@ func (c *Core) DoQuery(ctx context.Context, link Link, sql string, args ...inter
|
||||
// Exec commits one query SQL to underlying driver and returns the execution result.
|
||||
// It is most commonly used for data inserting and updating.
|
||||
func (c *Core) Exec(sql string, args ...interface{}) (result sql.Result, err error) {
|
||||
return c.DoExec(c.GetCtx(), nil, sql, args...)
|
||||
return c.db.DoExec(c.GetCtx(), nil, sql, args...)
|
||||
}
|
||||
|
||||
// DoExec commits the sql string and its arguments to underlying driver
|
||||
@ -77,23 +89,32 @@ func (c *Core) Exec(sql string, args ...interface{}) (result sql.Result, err err
|
||||
func (c *Core) DoExec(ctx context.Context, link Link, sql string, args ...interface{}) (result sql.Result, err error) {
|
||||
// Transaction checks.
|
||||
if link == nil {
|
||||
if link, err = c.MasterLink(); err != nil {
|
||||
if tx := TXFromCtx(ctx, c.db.GetGroup()); tx != nil {
|
||||
// Firstly, check and retrieve transaction link from context.
|
||||
link = &txLink{tx.tx}
|
||||
} else if link, err = c.MasterLink(); err != nil {
|
||||
// Or else it creates one from master node.
|
||||
return nil, err
|
||||
}
|
||||
} else if !link.IsTransaction() {
|
||||
// If current link is not transaction link, it checks and retrieves transaction from context.
|
||||
if tx := TXFromCtx(ctx, c.db.GetGroup()); tx != nil {
|
||||
link = &txLink{tx.tx}
|
||||
}
|
||||
}
|
||||
// Link execution.
|
||||
sql, args = formatSql(sql, args)
|
||||
sql, args = c.db.HandleSqlBeforeCommit(ctx, link, sql, args)
|
||||
|
||||
if c.GetConfig().ExecTimeout > 0 {
|
||||
var cancelFunc context.CancelFunc
|
||||
ctx, cancelFunc = context.WithTimeout(ctx, c.GetConfig().ExecTimeout)
|
||||
defer cancelFunc()
|
||||
}
|
||||
|
||||
// Link execution.
|
||||
sql, args = formatSql(sql, args)
|
||||
sql, args, err = c.db.DoCommit(ctx, link, sql, args)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
mTime1 := gtime.TimestampMilli()
|
||||
if !c.db.GetDryRun() {
|
||||
result, err = link.ExecContext(ctx, sql, args...)
|
||||
@ -120,6 +141,18 @@ func (c *Core) DoExec(ctx context.Context, link Link, sql string, args ...interf
|
||||
return result, formatError(err, sql, args...)
|
||||
}
|
||||
|
||||
// DoCommit is a hook function, which deals with the sql string before it's committed to underlying driver.
|
||||
// The parameter `link` specifies the current database connection operation object. You can modify the sql
|
||||
// string `sql` and its arguments `args` as you wish before they're committed to driver.
|
||||
func (c *Core) DoCommit(ctx context.Context, link Link, sql string, args []interface{}) (newSql string, newArgs []interface{}, err error) {
|
||||
if c.db.GetConfig().CtxStrict {
|
||||
if v := ctx.Value(ctxStrictKeyName); v == nil {
|
||||
return sql, args, gerror.NewCode(gcode.CodeMissingParameter, ctxStrictErrorStr)
|
||||
}
|
||||
}
|
||||
return sql, args, nil
|
||||
}
|
||||
|
||||
// Prepare creates a prepared statement for later queries or executions.
|
||||
// Multiple queries or executions may be run concurrently from the
|
||||
// returned statement.
|
||||
@ -142,20 +175,41 @@ func (c *Core) Prepare(sql string, execOnMaster ...bool) (*Stmt, error) {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
return c.DoPrepare(c.GetCtx(), link, sql)
|
||||
return c.db.DoPrepare(c.GetCtx(), link, sql)
|
||||
}
|
||||
|
||||
// DoPrepare calls prepare function on given link object and returns the statement object.
|
||||
func (c *Core) DoPrepare(ctx context.Context, link Link, sql string) (*Stmt, error) {
|
||||
if link != nil && !link.IsTransaction() {
|
||||
// Transaction checks.
|
||||
if link == nil {
|
||||
if tx := TXFromCtx(ctx, c.db.GetGroup()); tx != nil {
|
||||
// Firstly, check and retrieve transaction link from context.
|
||||
link = &txLink{tx.tx}
|
||||
} else {
|
||||
// Or else it creates one from master node.
|
||||
var err error
|
||||
if link, err = c.MasterLink(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
} else if !link.IsTransaction() {
|
||||
// If current link is not transaction link, it checks and retrieves transaction from context.
|
||||
if tx := TXFromCtx(ctx, c.db.GetGroup()); tx != nil {
|
||||
link = &txLink{tx.tx}
|
||||
}
|
||||
}
|
||||
|
||||
if c.GetConfig().PrepareTimeout > 0 {
|
||||
// DO NOT USE cancel function in prepare statement.
|
||||
ctx, _ = context.WithTimeout(ctx, c.GetConfig().PrepareTimeout)
|
||||
}
|
||||
|
||||
if c.db.GetConfig().CtxStrict {
|
||||
if v := ctx.Value(ctxStrictKeyName); v == nil {
|
||||
return nil, gerror.NewCode(gcode.CodeMissingParameter, ctxStrictErrorStr)
|
||||
}
|
||||
}
|
||||
|
||||
var (
|
||||
mTime1 = gtime.TimestampMilli()
|
||||
stmt, err = link.PrepareContext(ctx, sql)
|
||||
|
||||
@ -31,6 +31,7 @@ func (c *Core) SlaveLink(schema ...string) (Link, error) {
|
||||
|
||||
// QuoteWord checks given string `s` a word, if true quotes it with security chars of the database
|
||||
// and returns the quoted string; or else return `s` without any change.
|
||||
// The meaning of a `word` can be considered as a column name.
|
||||
func (c *Core) QuoteWord(s string) string {
|
||||
charLeft, charRight := c.db.GetChars()
|
||||
return doQuoteWord(s, charLeft, charRight)
|
||||
@ -38,6 +39,7 @@ func (c *Core) QuoteWord(s string) string {
|
||||
|
||||
// QuoteString quotes string with quote chars. Strings like:
|
||||
// "user", "user u", "user,user_detail", "user u, user_detail ut", "u.id asc".
|
||||
// The meaning of a `string` can be considered as part of a statement string including columns.
|
||||
func (c *Core) QuoteString(s string) string {
|
||||
charLeft, charRight := c.db.GetChars()
|
||||
return doQuoteString(s, charLeft, charRight)
|
||||
@ -63,12 +65,6 @@ func (c *Core) GetChars() (charLeft string, charRight string) {
|
||||
return "", ""
|
||||
}
|
||||
|
||||
// HandleSqlBeforeCommit handles the sql before posts it to database.
|
||||
// It does nothing in default.
|
||||
func (c *Core) HandleSqlBeforeCommit(sql string) string {
|
||||
return sql
|
||||
}
|
||||
|
||||
// Tables retrieves and returns the tables of current schema.
|
||||
// It's mainly used in cli tool chain for automatically generating the models.
|
||||
//
|
||||
|
||||
@ -15,6 +15,7 @@ import (
|
||||
"context"
|
||||
"database/sql"
|
||||
"fmt"
|
||||
"github.com/gogf/gf/errors/gcode"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
@ -42,15 +43,15 @@ func (d *DriverMssql) New(core *Core, node *ConfigNode) (DB, error) {
|
||||
// Open creates and returns a underlying sql.DB object for mssql.
|
||||
func (d *DriverMssql) Open(config *ConfigNode) (*sql.DB, error) {
|
||||
source := ""
|
||||
if config.LinkInfo != "" {
|
||||
source = config.LinkInfo
|
||||
if config.Link != "" {
|
||||
source = config.Link
|
||||
} else {
|
||||
source = fmt.Sprintf(
|
||||
"user id=%s;password=%s;server=%s;port=%s;database=%s;encrypt=disable",
|
||||
config.User, config.Pass, config.Host, config.Port, config.Name,
|
||||
)
|
||||
}
|
||||
intlog.Printf("Open: %s", source)
|
||||
intlog.Printf(d.GetCtx(), "Open: %s", source)
|
||||
if db, err := sql.Open("sqlserver", source); err == nil {
|
||||
return db, nil
|
||||
} else {
|
||||
@ -58,17 +59,17 @@ func (d *DriverMssql) Open(config *ConfigNode) (*sql.DB, error) {
|
||||
}
|
||||
}
|
||||
|
||||
// FilteredLinkInfo retrieves and returns filtered `linkInfo` that can be using for
|
||||
// FilteredLink retrieves and returns filtered `linkInfo` that can be using for
|
||||
// logging or tracing purpose.
|
||||
func (d *DriverMssql) FilteredLinkInfo() string {
|
||||
linkInfo := d.GetConfig().LinkInfo
|
||||
func (d *DriverMssql) FilteredLink() string {
|
||||
linkInfo := d.GetConfig().Link
|
||||
if linkInfo == "" {
|
||||
return ""
|
||||
}
|
||||
s, _ := gregex.ReplaceString(
|
||||
`(.+);\s*password=(.+);\s*server=(.+)`,
|
||||
`$1;password=xxx;server=$3`,
|
||||
d.GetConfig().LinkInfo,
|
||||
d.GetConfig().Link,
|
||||
)
|
||||
return s
|
||||
}
|
||||
@ -78,8 +79,11 @@ func (d *DriverMssql) GetChars() (charLeft string, charRight string) {
|
||||
return "\"", "\""
|
||||
}
|
||||
|
||||
// HandleSqlBeforeCommit deals with the sql string before commits it to underlying sql driver.
|
||||
func (d *DriverMssql) HandleSqlBeforeCommit(ctx context.Context, link Link, sql string, args []interface{}) (string, []interface{}) {
|
||||
// DoCommit deals with the sql string before commits it to underlying sql driver.
|
||||
func (d *DriverMssql) DoCommit(ctx context.Context, link Link, sql string, args []interface{}) (newSql string, newArgs []interface{}, err error) {
|
||||
defer func() {
|
||||
newSql, newArgs, err = d.Core.DoCommit(ctx, link, newSql, newArgs)
|
||||
}()
|
||||
var index int
|
||||
// Convert place holder char '?' to string "@px".
|
||||
str, _ := gregex.ReplaceStringFunc("\\?", sql, func(s string) string {
|
||||
@ -87,7 +91,7 @@ func (d *DriverMssql) HandleSqlBeforeCommit(ctx context.Context, link Link, sql
|
||||
return fmt.Sprintf("@p%d", index)
|
||||
})
|
||||
str, _ = gregex.ReplaceString("\"", "", str)
|
||||
return d.parseSql(str), args
|
||||
return d.parseSql(str), args, nil
|
||||
}
|
||||
|
||||
// parseSql does some replacement of the sql before commits it to underlying driver,
|
||||
@ -211,7 +215,7 @@ func (d *DriverMssql) TableFields(ctx context.Context, table string, schema ...s
|
||||
charL, charR := d.GetChars()
|
||||
table = gstr.Trim(table, charL+charR)
|
||||
if gstr.Contains(table, " ") {
|
||||
return nil, gerror.New("function TableFields supports only single table operations")
|
||||
return nil, gerror.NewCode(gcode.CodeInvalidParameter, "function TableFields supports only single table operations")
|
||||
}
|
||||
useSchema := d.db.GetSchema()
|
||||
if len(schema) > 0 && schema[0] != "" {
|
||||
@ -284,3 +288,17 @@ ORDER BY a.id,a.colorder`,
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// DoInsert is not supported in mssql.
|
||||
func (d *DriverMssql) DoInsert(ctx context.Context, link Link, table string, list List, option DoInsertOption) (result sql.Result, err error) {
|
||||
switch option.InsertOption {
|
||||
case insertOptionSave:
|
||||
return nil, gerror.NewCode(gcode.CodeNotSupported, `Save operation is not supported by mssql driver`)
|
||||
|
||||
case insertOptionReplace:
|
||||
return nil, gerror.NewCode(gcode.CodeNotSupported, `Replace operation is not supported by mssql driver`)
|
||||
|
||||
default:
|
||||
return d.Core.DoInsert(ctx, link, table, list, option)
|
||||
}
|
||||
}
|
||||
|
||||
@ -10,11 +10,12 @@ import (
|
||||
"context"
|
||||
"database/sql"
|
||||
"fmt"
|
||||
|
||||
"github.com/gogf/gf/errors/gcode"
|
||||
"github.com/gogf/gf/errors/gerror"
|
||||
"github.com/gogf/gf/internal/intlog"
|
||||
"github.com/gogf/gf/text/gregex"
|
||||
"github.com/gogf/gf/text/gstr"
|
||||
"net/url"
|
||||
|
||||
_ "github.com/go-sql-driver/mysql"
|
||||
)
|
||||
@ -32,12 +33,12 @@ func (d *DriverMysql) New(core *Core, node *ConfigNode) (DB, error) {
|
||||
}, nil
|
||||
}
|
||||
|
||||
// Open creates and returns a underlying sql.DB object for mysql.
|
||||
// Open creates and returns an underlying sql.DB object for mysql.
|
||||
// Note that it converts time.Time argument to local timezone in default.
|
||||
func (d *DriverMysql) Open(config *ConfigNode) (*sql.DB, error) {
|
||||
var source string
|
||||
if config.LinkInfo != "" {
|
||||
source = config.LinkInfo
|
||||
if config.Link != "" {
|
||||
source = config.Link
|
||||
// Custom changing the schema in runtime.
|
||||
if config.Name != "" {
|
||||
source, _ = gregex.ReplaceString(`/([\w\.\-]+)+`, "/"+config.Name, source)
|
||||
@ -47,8 +48,11 @@ func (d *DriverMysql) Open(config *ConfigNode) (*sql.DB, error) {
|
||||
"%s:%s@tcp(%s:%s)/%s?charset=%s",
|
||||
config.User, config.Pass, config.Host, config.Port, config.Name, config.Charset,
|
||||
)
|
||||
if config.Timezone != "" {
|
||||
source = fmt.Sprintf("%s&loc=%s", source, url.QueryEscape(config.Timezone))
|
||||
}
|
||||
}
|
||||
intlog.Printf("Open: %s", source)
|
||||
intlog.Printf(d.GetCtx(), "Open: %s", source)
|
||||
if db, err := sql.Open("mysql", source); err == nil {
|
||||
return db, nil
|
||||
} else {
|
||||
@ -56,10 +60,10 @@ func (d *DriverMysql) Open(config *ConfigNode) (*sql.DB, error) {
|
||||
}
|
||||
}
|
||||
|
||||
// FilteredLinkInfo retrieves and returns filtered `linkInfo` that can be using for
|
||||
// FilteredLink retrieves and returns filtered `linkInfo` that can be using for
|
||||
// logging or tracing purpose.
|
||||
func (d *DriverMysql) FilteredLinkInfo() string {
|
||||
linkInfo := d.GetConfig().LinkInfo
|
||||
func (d *DriverMysql) FilteredLink() string {
|
||||
linkInfo := d.GetConfig().Link
|
||||
if linkInfo == "" {
|
||||
return ""
|
||||
}
|
||||
@ -76,9 +80,9 @@ func (d *DriverMysql) GetChars() (charLeft string, charRight string) {
|
||||
return "`", "`"
|
||||
}
|
||||
|
||||
// HandleSqlBeforeCommit handles the sql before posts it to database.
|
||||
func (d *DriverMysql) HandleSqlBeforeCommit(ctx context.Context, link Link, sql string, args []interface{}) (string, []interface{}) {
|
||||
return sql, args
|
||||
// DoCommit handles the sql before posts it to database.
|
||||
func (d *DriverMysql) DoCommit(ctx context.Context, link Link, sql string, args []interface{}) (newSql string, newArgs []interface{}, err error) {
|
||||
return d.Core.DoCommit(ctx, link, sql, args)
|
||||
}
|
||||
|
||||
// Tables retrieves and returns the tables of current schema.
|
||||
@ -117,7 +121,7 @@ func (d *DriverMysql) TableFields(ctx context.Context, table string, schema ...s
|
||||
charL, charR := d.GetChars()
|
||||
table = gstr.Trim(table, charL+charR)
|
||||
if gstr.Contains(table, " ") {
|
||||
return nil, gerror.New("function TableFields supports only single table operations")
|
||||
return nil, gerror.NewCode(gcode.CodeInvalidParameter, "function TableFields supports only single table operations")
|
||||
}
|
||||
useSchema := d.schema.Val()
|
||||
if len(schema) > 0 && schema[0] != "" {
|
||||
|
||||
@ -15,6 +15,7 @@ import (
|
||||
"context"
|
||||
"database/sql"
|
||||
"fmt"
|
||||
"github.com/gogf/gf/errors/gcode"
|
||||
"reflect"
|
||||
"strconv"
|
||||
"strings"
|
||||
@ -32,11 +33,6 @@ type DriverOracle struct {
|
||||
*Core
|
||||
}
|
||||
|
||||
const (
|
||||
tableAlias1 = "GFORM1"
|
||||
tableAlias2 = "GFORM2"
|
||||
)
|
||||
|
||||
// New creates and returns a database object for oracle.
|
||||
// It implements the interface of gdb.Driver for extra database driver installation.
|
||||
func (d *DriverOracle) New(core *Core, node *ConfigNode) (DB, error) {
|
||||
@ -48,15 +44,15 @@ func (d *DriverOracle) New(core *Core, node *ConfigNode) (DB, error) {
|
||||
// Open creates and returns a underlying sql.DB object for oracle.
|
||||
func (d *DriverOracle) Open(config *ConfigNode) (*sql.DB, error) {
|
||||
var source string
|
||||
if config.LinkInfo != "" {
|
||||
source = config.LinkInfo
|
||||
if config.Link != "" {
|
||||
source = config.Link
|
||||
} else {
|
||||
source = fmt.Sprintf(
|
||||
"%s/%s@%s:%s/%s",
|
||||
config.User, config.Pass, config.Host, config.Port, config.Name,
|
||||
)
|
||||
}
|
||||
intlog.Printf("Open: %s", source)
|
||||
intlog.Printf(d.GetCtx(), "Open: %s", source)
|
||||
if db, err := sql.Open("oci8", source); err == nil {
|
||||
return db, nil
|
||||
} else {
|
||||
@ -64,10 +60,10 @@ func (d *DriverOracle) Open(config *ConfigNode) (*sql.DB, error) {
|
||||
}
|
||||
}
|
||||
|
||||
// FilteredLinkInfo retrieves and returns filtered `linkInfo` that can be using for
|
||||
// FilteredLink retrieves and returns filtered `linkInfo` that can be using for
|
||||
// logging or tracing purpose.
|
||||
func (d *DriverOracle) FilteredLinkInfo() string {
|
||||
linkInfo := d.GetConfig().LinkInfo
|
||||
func (d *DriverOracle) FilteredLink() string {
|
||||
linkInfo := d.GetConfig().Link
|
||||
if linkInfo == "" {
|
||||
return ""
|
||||
}
|
||||
@ -84,8 +80,12 @@ func (d *DriverOracle) GetChars() (charLeft string, charRight string) {
|
||||
return "\"", "\""
|
||||
}
|
||||
|
||||
// HandleSqlBeforeCommit deals with the sql string before commits it to underlying sql driver.
|
||||
func (d *DriverOracle) HandleSqlBeforeCommit(ctx context.Context, link Link, sql string, args []interface{}) (newSql string, newArgs []interface{}) {
|
||||
// DoCommit deals with the sql string before commits it to underlying sql driver.
|
||||
func (d *DriverOracle) DoCommit(ctx context.Context, link Link, sql string, args []interface{}) (newSql string, newArgs []interface{}, err error) {
|
||||
defer func() {
|
||||
newSql, newArgs, err = d.Core.DoCommit(ctx, link, newSql, newArgs)
|
||||
}()
|
||||
|
||||
var index int
|
||||
// Convert place holder char '?' to string ":vx".
|
||||
newSql, _ = gregex.ReplaceStringFunc("\\?", sql, func(s string) string {
|
||||
@ -187,7 +187,7 @@ func (d *DriverOracle) TableFields(ctx context.Context, table string, schema ...
|
||||
charL, charR := d.GetChars()
|
||||
table = gstr.Trim(table, charL+charR)
|
||||
if gstr.Contains(table, " ") {
|
||||
return nil, gerror.New("function TableFields supports only single table operations")
|
||||
return nil, gerror.NewCode(gcode.CodeInvalidParameter, "function TableFields supports only single table operations")
|
||||
}
|
||||
useSchema := d.db.GetSchema()
|
||||
if len(schema) > 0 && schema[0] != "" {
|
||||
@ -236,231 +236,60 @@ FROM USER_TAB_COLUMNS WHERE TABLE_NAME = '%s' ORDER BY COLUMN_ID`,
|
||||
return
|
||||
}
|
||||
|
||||
func (d *DriverOracle) getTableUniqueIndex(table string) (fields map[string]map[string]string, err error) {
|
||||
table = strings.ToUpper(table)
|
||||
v, _ := internalCache.GetOrSetFunc(
|
||||
"table_unique_index_"+table,
|
||||
func() (interface{}, error) {
|
||||
res := (Result)(nil)
|
||||
res, err = d.db.GetAll(fmt.Sprintf(`
|
||||
SELECT INDEX_NAME,COLUMN_NAME,CHAR_LENGTH FROM USER_IND_COLUMNS
|
||||
WHERE TABLE_NAME = '%s'
|
||||
AND INDEX_NAME IN(SELECT INDEX_NAME FROM USER_INDEXES WHERE TABLE_NAME='%s' AND UNIQUENESS='UNIQUE')
|
||||
ORDER BY INDEX_NAME,COLUMN_POSITION`, table, table))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
fields := make(map[string]map[string]string)
|
||||
for _, v := range res {
|
||||
mm := make(map[string]string)
|
||||
mm[v["COLUMN_NAME"].String()] = v["CHAR_LENGTH"].String()
|
||||
fields[v["INDEX_NAME"].String()] = mm
|
||||
}
|
||||
return fields, nil
|
||||
}, 0)
|
||||
if err == nil {
|
||||
fields = v.(map[string]map[string]string)
|
||||
}
|
||||
return
|
||||
}
|
||||
// DoInsert inserts or updates data for given table.
|
||||
// This function is usually used for custom interface definition, you do not need call it manually.
|
||||
// The parameter `data` can be type of map/gmap/struct/*struct/[]map/[]struct, etc.
|
||||
// Eg:
|
||||
// Data(g.Map{"uid": 10000, "name":"john"})
|
||||
// Data(g.Slice{g.Map{"uid": 10000, "name":"john"}, g.Map{"uid": 20000, "name":"smith"})
|
||||
//
|
||||
// The parameter `option` values are as follows:
|
||||
// 0: insert: just insert, if there's unique/primary key in the data, it returns error;
|
||||
// 1: replace: if there's unique/primary key in the data, it deletes it from table and inserts a new one;
|
||||
// 2: save: if there's unique/primary key in the data, it updates it or else inserts a new one;
|
||||
// 3: ignore: if there's unique/primary key in the data, it ignores the inserting;
|
||||
func (d *DriverOracle) DoInsert(ctx context.Context, link Link, table string, list List, option DoInsertOption) (result sql.Result, err error) {
|
||||
switch option.InsertOption {
|
||||
case insertOptionSave:
|
||||
return nil, gerror.NewCode(gcode.CodeNotSupported, `Save operation is not supported by mssql driver`)
|
||||
|
||||
func (d *DriverOracle) DoInsert(ctx context.Context, link Link, table string, data interface{}, option int, batch ...int) (result sql.Result, err error) {
|
||||
var (
|
||||
fields []string
|
||||
values []string
|
||||
params []interface{}
|
||||
dataMap Map
|
||||
rv = reflect.ValueOf(data)
|
||||
kind = rv.Kind()
|
||||
)
|
||||
if kind == reflect.Ptr {
|
||||
rv = rv.Elem()
|
||||
kind = rv.Kind()
|
||||
}
|
||||
switch kind {
|
||||
case reflect.Slice, reflect.Array:
|
||||
return d.DoBatchInsert(ctx, link, table, data, option, batch...)
|
||||
case reflect.Map:
|
||||
fallthrough
|
||||
case reflect.Struct:
|
||||
dataMap = ConvertDataForTableRecord(data)
|
||||
default:
|
||||
return result, gerror.New(fmt.Sprint("unsupported data type:", kind))
|
||||
}
|
||||
var (
|
||||
indexes = make([]string, 0)
|
||||
indexMap = make(map[string]string)
|
||||
indexExists = false
|
||||
)
|
||||
if option != insertOptionDefault {
|
||||
index, err := d.getTableUniqueIndex(table)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if len(index) > 0 {
|
||||
for _, v := range index {
|
||||
for k, _ := range v {
|
||||
indexes = append(indexes, k)
|
||||
}
|
||||
indexMap = v
|
||||
indexExists = true
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
var (
|
||||
subSqlStr = make([]string, 0)
|
||||
onStr = make([]string, 0)
|
||||
updateStr = make([]string, 0)
|
||||
)
|
||||
charL, charR := d.db.GetChars()
|
||||
for k, v := range dataMap {
|
||||
k = strings.ToUpper(k)
|
||||
|
||||
// 操作类型为REPLACE/SAVE时且存在唯一索引才使用merge,否则使用insert
|
||||
if (option == insertOptionReplace || option == insertOptionSave) && indexExists {
|
||||
fields = append(fields, tableAlias1+"."+charL+k+charR)
|
||||
values = append(values, tableAlias2+"."+charL+k+charR)
|
||||
params = append(params, v)
|
||||
subSqlStr = append(subSqlStr, fmt.Sprintf("%s?%s %s", charL, charR, k))
|
||||
//m erge中的on子句中由唯一索引组成, update子句中不含唯一索引
|
||||
if _, ok := indexMap[k]; ok {
|
||||
onStr = append(onStr, fmt.Sprintf("%s.%s = %s.%s ", tableAlias1, k, tableAlias2, k))
|
||||
} else {
|
||||
updateStr = append(updateStr, fmt.Sprintf("%s.%s = %s.%s ", tableAlias1, k, tableAlias2, k))
|
||||
}
|
||||
} else {
|
||||
fields = append(fields, charL+k+charR)
|
||||
values = append(values, "?")
|
||||
params = append(params, v)
|
||||
}
|
||||
case insertOptionReplace:
|
||||
return nil, gerror.NewCode(gcode.CodeNotSupported, `Replace operation is not supported by mssql driver`)
|
||||
}
|
||||
|
||||
if link == nil {
|
||||
if link, err = d.MasterLink(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
if indexExists && option != insertOptionDefault {
|
||||
switch option {
|
||||
case
|
||||
insertOptionReplace,
|
||||
insertOptionSave:
|
||||
tmp := fmt.Sprintf(
|
||||
"MERGE INTO %s %s USING(SELECT %s FROM DUAL) %s ON(%s) WHEN MATCHED THEN UPDATE SET %s WHEN NOT MATCHED THEN INSERT (%s) VALUES(%s)",
|
||||
table, tableAlias1, strings.Join(subSqlStr, ","), tableAlias2,
|
||||
strings.Join(onStr, "AND"), strings.Join(updateStr, ","), strings.Join(fields, ","), strings.Join(values, ","),
|
||||
)
|
||||
return d.DoExec(ctx, link, tmp, params...)
|
||||
|
||||
case insertOptionIgnore:
|
||||
return d.DoExec(ctx, link, fmt.Sprintf(
|
||||
"INSERT /*+ IGNORE_ROW_ON_DUPKEY_INDEX(%s(%s)) */ INTO %s(%s) VALUES(%s)",
|
||||
table, strings.Join(indexes, ","), table, strings.Join(fields, ","), strings.Join(values, ","),
|
||||
), params...)
|
||||
}
|
||||
}
|
||||
|
||||
return d.DoExec(ctx, link,
|
||||
fmt.Sprintf(
|
||||
"INSERT INTO %s(%s) VALUES(%s)",
|
||||
table, strings.Join(fields, ","), strings.Join(values, ","),
|
||||
),
|
||||
params...)
|
||||
}
|
||||
|
||||
func (d *DriverOracle) DoBatchInsert(ctx context.Context, link Link, table string, list interface{}, option int, batch ...int) (result sql.Result, err error) {
|
||||
var (
|
||||
keys []string
|
||||
values []string
|
||||
params []interface{}
|
||||
)
|
||||
listMap := (List)(nil)
|
||||
switch v := list.(type) {
|
||||
case Result:
|
||||
listMap = v.List()
|
||||
case Record:
|
||||
listMap = List{v.Map()}
|
||||
case List:
|
||||
listMap = v
|
||||
case Map:
|
||||
listMap = List{v}
|
||||
default:
|
||||
var (
|
||||
rv = reflect.ValueOf(list)
|
||||
kind = rv.Kind()
|
||||
)
|
||||
if kind == reflect.Ptr {
|
||||
rv = rv.Elem()
|
||||
kind = rv.Kind()
|
||||
}
|
||||
switch kind {
|
||||
case reflect.Slice, reflect.Array:
|
||||
listMap = make(List, rv.Len())
|
||||
for i := 0; i < rv.Len(); i++ {
|
||||
listMap[i] = ConvertDataForTableRecord(rv.Index(i).Interface())
|
||||
}
|
||||
case reflect.Map:
|
||||
fallthrough
|
||||
case reflect.Struct:
|
||||
listMap = List{ConvertDataForTableRecord(list)}
|
||||
default:
|
||||
return result, gerror.New(fmt.Sprint("unsupported list type:", kind))
|
||||
}
|
||||
}
|
||||
if len(listMap) < 1 {
|
||||
return result, gerror.New("empty data list")
|
||||
}
|
||||
if link == nil {
|
||||
if link, err = d.MasterLink(); err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
// Retrieve the table fields and length.
|
||||
holders := []string(nil)
|
||||
for k, _ := range listMap[0] {
|
||||
var (
|
||||
listLength = len(list)
|
||||
valueHolder = make([]string, 0)
|
||||
)
|
||||
for k, _ := range list[0] {
|
||||
keys = append(keys, k)
|
||||
holders = append(holders, "?")
|
||||
valueHolder = append(valueHolder, "?")
|
||||
}
|
||||
var (
|
||||
batchResult = new(SqlResult)
|
||||
charL, charR = d.db.GetChars()
|
||||
keyStr = charL + strings.Join(keys, charL+","+charR) + charR
|
||||
valueHolderStr = strings.Join(holders, ",")
|
||||
valueHolderStr = strings.Join(valueHolder, ",")
|
||||
)
|
||||
if option != insertOptionDefault {
|
||||
for _, v := range listMap {
|
||||
r, err := d.DoInsert(ctx, link, table, v, option, 1)
|
||||
if err != nil {
|
||||
return r, err
|
||||
}
|
||||
|
||||
if n, err := r.RowsAffected(); err != nil {
|
||||
return r, err
|
||||
} else {
|
||||
batchResult.result = r
|
||||
batchResult.affected += n
|
||||
}
|
||||
}
|
||||
return batchResult, nil
|
||||
}
|
||||
|
||||
batchNum := defaultBatchNumber
|
||||
if len(batch) > 0 {
|
||||
batchNum = batch[0]
|
||||
}
|
||||
// Format "INSERT...INTO..." statement.
|
||||
intoStr := make([]string, 0)
|
||||
for i := 0; i < len(listMap); i++ {
|
||||
for i := 0; i < len(list); i++ {
|
||||
for _, k := range keys {
|
||||
params = append(params, listMap[i][k])
|
||||
params = append(params, list[i][k])
|
||||
}
|
||||
values = append(values, valueHolderStr)
|
||||
intoStr = append(intoStr, fmt.Sprintf(" INTO %s(%s) VALUES(%s) ", table, keyStr, valueHolderStr))
|
||||
if len(intoStr) == batchNum {
|
||||
r, err := d.DoExec(ctx, link, fmt.Sprintf("INSERT ALL %s SELECT * FROM DUAL", strings.Join(intoStr, " ")), params...)
|
||||
intoStr = append(intoStr, fmt.Sprintf("INTO %s(%s) VALUES(%s)", table, keyStr, valueHolderStr))
|
||||
if len(intoStr) == option.BatchCount || (i == listLength-1 && len(valueHolder) > 0) {
|
||||
r, err := d.DoExec(ctx, link, fmt.Sprintf(
|
||||
"INSERT ALL %s SELECT * FROM DUAL",
|
||||
strings.Join(intoStr, " "),
|
||||
), params...)
|
||||
if err != nil {
|
||||
return r, err
|
||||
}
|
||||
@ -474,18 +303,5 @@ func (d *DriverOracle) DoBatchInsert(ctx context.Context, link Link, table strin
|
||||
intoStr = intoStr[:0]
|
||||
}
|
||||
}
|
||||
// The leftover data.
|
||||
if len(intoStr) > 0 {
|
||||
r, err := d.DoExec(ctx, link, fmt.Sprintf("INSERT ALL %s SELECT * FROM DUAL", strings.Join(intoStr, " ")), params...)
|
||||
if err != nil {
|
||||
return r, err
|
||||
}
|
||||
if n, err := r.RowsAffected(); err != nil {
|
||||
return r, err
|
||||
} else {
|
||||
batchResult.result = r
|
||||
batchResult.affected += n
|
||||
}
|
||||
}
|
||||
return batchResult, nil
|
||||
}
|
||||
|
||||
@ -15,6 +15,7 @@ import (
|
||||
"context"
|
||||
"database/sql"
|
||||
"fmt"
|
||||
"github.com/gogf/gf/errors/gcode"
|
||||
"strings"
|
||||
|
||||
"github.com/gogf/gf/errors/gerror"
|
||||
@ -40,15 +41,18 @@ func (d *DriverPgsql) New(core *Core, node *ConfigNode) (DB, error) {
|
||||
// Open creates and returns a underlying sql.DB object for pgsql.
|
||||
func (d *DriverPgsql) Open(config *ConfigNode) (*sql.DB, error) {
|
||||
var source string
|
||||
if config.LinkInfo != "" {
|
||||
source = config.LinkInfo
|
||||
if config.Link != "" {
|
||||
source = config.Link
|
||||
} else {
|
||||
source = fmt.Sprintf(
|
||||
"user=%s password=%s host=%s port=%s dbname=%s sslmode=disable",
|
||||
config.User, config.Pass, config.Host, config.Port, config.Name,
|
||||
)
|
||||
if config.Timezone != "" {
|
||||
source = fmt.Sprintf("%s timezone=%s", source, config.Timezone)
|
||||
}
|
||||
}
|
||||
intlog.Printf("Open: %s", source)
|
||||
intlog.Printf(d.GetCtx(), "Open: %s", source)
|
||||
if db, err := sql.Open("postgres", source); err == nil {
|
||||
return db, nil
|
||||
} else {
|
||||
@ -56,10 +60,10 @@ func (d *DriverPgsql) Open(config *ConfigNode) (*sql.DB, error) {
|
||||
}
|
||||
}
|
||||
|
||||
// FilteredLinkInfo retrieves and returns filtered `linkInfo` that can be using for
|
||||
// FilteredLink retrieves and returns filtered `linkInfo` that can be using for
|
||||
// logging or tracing purpose.
|
||||
func (d *DriverPgsql) FilteredLinkInfo() string {
|
||||
linkInfo := d.GetConfig().LinkInfo
|
||||
func (d *DriverPgsql) FilteredLink() string {
|
||||
linkInfo := d.GetConfig().Link
|
||||
if linkInfo == "" {
|
||||
return ""
|
||||
}
|
||||
@ -76,16 +80,20 @@ func (d *DriverPgsql) GetChars() (charLeft string, charRight string) {
|
||||
return "\"", "\""
|
||||
}
|
||||
|
||||
// HandleSqlBeforeCommit deals with the sql string before commits it to underlying sql driver.
|
||||
func (d *DriverPgsql) HandleSqlBeforeCommit(ctx context.Context, link Link, sql string, args []interface{}) (string, []interface{}) {
|
||||
// DoCommit deals with the sql string before commits it to underlying sql driver.
|
||||
func (d *DriverPgsql) DoCommit(ctx context.Context, link Link, sql string, args []interface{}) (newSql string, newArgs []interface{}, err error) {
|
||||
defer func() {
|
||||
newSql, newArgs, err = d.Core.DoCommit(ctx, link, newSql, newArgs)
|
||||
}()
|
||||
|
||||
var index int
|
||||
// Convert place holder char '?' to string "$x".
|
||||
sql, _ = gregex.ReplaceStringFunc("\\?", sql, func(s string) string {
|
||||
index++
|
||||
return fmt.Sprintf("$%d", index)
|
||||
})
|
||||
sql, _ = gregex.ReplaceString(` LIMIT (\d+),\s*(\d+)`, ` LIMIT $2 OFFSET $1`, sql)
|
||||
return sql, args
|
||||
newSql, _ = gregex.ReplaceString(` LIMIT (\d+),\s*(\d+)`, ` LIMIT $2 OFFSET $1`, sql)
|
||||
return newSql, args, nil
|
||||
}
|
||||
|
||||
// Tables retrieves and returns the tables of current schema.
|
||||
@ -119,7 +127,7 @@ func (d *DriverPgsql) TableFields(ctx context.Context, table string, schema ...s
|
||||
charL, charR := d.GetChars()
|
||||
table = gstr.Trim(table, charL+charR)
|
||||
if gstr.Contains(table, " ") {
|
||||
return nil, gerror.New("function TableFields supports only single table operations")
|
||||
return nil, gerror.NewCode(gcode.CodeInvalidParameter, "function TableFields supports only single table operations")
|
||||
}
|
||||
table, _ = gregex.ReplaceString("\"", "", table)
|
||||
useSchema := d.db.GetSchema()
|
||||
@ -135,9 +143,18 @@ func (d *DriverPgsql) TableFields(ctx context.Context, table string, schema ...s
|
||||
result Result
|
||||
link, err = d.SlaveLink(useSchema)
|
||||
structureSql = fmt.Sprintf(`
|
||||
SELECT a.attname AS field, t.typname AS type FROM pg_class c, pg_attribute a
|
||||
LEFT OUTER JOIN pg_description b ON a.attrelid=b.objoid AND a.attnum = b.objsubid,pg_type t
|
||||
WHERE c.relname = '%s' and a.attnum > 0 and a.attrelid = c.oid and a.atttypid = t.oid
|
||||
SELECT a.attname AS field, t.typname AS type,a.attnotnull as null,
|
||||
(case when d.contype is not null then 'pri' else '' end) as key
|
||||
,ic.column_default as default_value,b.description as comment
|
||||
,coalesce(character_maximum_length, numeric_precision, -1) as length
|
||||
,numeric_scale as scale
|
||||
FROM pg_attribute a
|
||||
left join pg_class c on a.attrelid = c.oid
|
||||
left join pg_constraint d on d.conrelid = c.oid and a.attnum = d.conkey[1]
|
||||
left join pg_description b ON a.attrelid=b.objoid AND a.attnum = b.objsubid
|
||||
left join pg_type t ON a.atttypid = t.oid
|
||||
left join information_schema.columns ic on ic.column_name = a.attname and ic.table_name = c.relname
|
||||
WHERE c.relname = '%s' and a.attnum > 0
|
||||
ORDER BY a.attnum`,
|
||||
strings.ToLower(table),
|
||||
)
|
||||
@ -153,9 +170,13 @@ ORDER BY a.attnum`,
|
||||
fields = make(map[string]*TableField)
|
||||
for i, m := range result {
|
||||
fields[m["field"].String()] = &TableField{
|
||||
Index: i,
|
||||
Name: m["field"].String(),
|
||||
Type: m["type"].String(),
|
||||
Index: i,
|
||||
Name: m["field"].String(),
|
||||
Type: m["type"].String(),
|
||||
Null: m["null"].Bool(),
|
||||
Key: m["key"].String(),
|
||||
Default: m["default_value"].Val(),
|
||||
Comment: m["comment"].String(),
|
||||
}
|
||||
}
|
||||
return fields
|
||||
@ -165,3 +186,17 @@ ORDER BY a.attnum`,
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// DoInsert is not supported in pgsql.
|
||||
func (d *DriverPgsql) DoInsert(ctx context.Context, link Link, table string, list List, option DoInsertOption) (result sql.Result, err error) {
|
||||
switch option.InsertOption {
|
||||
case insertOptionSave:
|
||||
return nil, gerror.NewCode(gcode.CodeNotSupported, `Save operation is not supported by pgsql driver`)
|
||||
|
||||
case insertOptionReplace:
|
||||
return nil, gerror.NewCode(gcode.CodeNotSupported, `Replace operation is not supported by pgsql driver`)
|
||||
|
||||
default:
|
||||
return d.Core.DoInsert(ctx, link, table, list, option)
|
||||
}
|
||||
}
|
||||
|
||||
@ -14,6 +14,7 @@ import (
|
||||
"context"
|
||||
"database/sql"
|
||||
"fmt"
|
||||
"github.com/gogf/gf/errors/gcode"
|
||||
"strings"
|
||||
|
||||
"github.com/gogf/gf/errors/gerror"
|
||||
@ -38,8 +39,8 @@ func (d *DriverSqlite) New(core *Core, node *ConfigNode) (DB, error) {
|
||||
// Open creates and returns a underlying sql.DB object for sqlite.
|
||||
func (d *DriverSqlite) Open(config *ConfigNode) (*sql.DB, error) {
|
||||
var source string
|
||||
if config.LinkInfo != "" {
|
||||
source = config.LinkInfo
|
||||
if config.Link != "" {
|
||||
source = config.Link
|
||||
} else {
|
||||
source = config.Name
|
||||
}
|
||||
@ -47,7 +48,7 @@ func (d *DriverSqlite) Open(config *ConfigNode) (*sql.DB, error) {
|
||||
if absolutePath, _ := gfile.Search(source); absolutePath != "" {
|
||||
source = absolutePath
|
||||
}
|
||||
intlog.Printf("Open: %s", source)
|
||||
intlog.Printf(d.GetCtx(), "Open: %s", source)
|
||||
if db, err := sql.Open("sqlite3", source); err == nil {
|
||||
return db, nil
|
||||
} else {
|
||||
@ -55,10 +56,10 @@ func (d *DriverSqlite) Open(config *ConfigNode) (*sql.DB, error) {
|
||||
}
|
||||
}
|
||||
|
||||
// FilteredLinkInfo retrieves and returns filtered `linkInfo` that can be using for
|
||||
// FilteredLink retrieves and returns filtered `linkInfo` that can be using for
|
||||
// logging or tracing purpose.
|
||||
func (d *DriverSqlite) FilteredLinkInfo() string {
|
||||
return d.GetConfig().LinkInfo
|
||||
func (d *DriverSqlite) FilteredLink() string {
|
||||
return d.GetConfig().Link
|
||||
}
|
||||
|
||||
// GetChars returns the security char for this type of database.
|
||||
@ -66,11 +67,9 @@ func (d *DriverSqlite) GetChars() (charLeft string, charRight string) {
|
||||
return "`", "`"
|
||||
}
|
||||
|
||||
// HandleSqlBeforeCommit deals with the sql string before commits it to underlying sql driver.
|
||||
// TODO 需要增加对Save方法的支持,可使用正则来实现替换,
|
||||
// TODO 将ON DUPLICATE KEY UPDATE触发器修改为两条SQL语句(INSERT OR IGNORE & UPDATE)
|
||||
func (d *DriverSqlite) HandleSqlBeforeCommit(ctx context.Context, link Link, sql string, args []interface{}) (string, []interface{}) {
|
||||
return sql, args
|
||||
// DoCommit deals with the sql string before commits it to underlying sql driver.
|
||||
func (d *DriverSqlite) DoCommit(ctx context.Context, link Link, sql string, args []interface{}) (newSql string, newArgs []interface{}, err error) {
|
||||
return d.Core.DoCommit(ctx, link, sql, args)
|
||||
}
|
||||
|
||||
// Tables retrieves and returns the tables of current schema.
|
||||
@ -101,7 +100,7 @@ func (d *DriverSqlite) TableFields(ctx context.Context, table string, schema ...
|
||||
charL, charR := d.GetChars()
|
||||
table = gstr.Trim(table, charL+charR)
|
||||
if gstr.Contains(table, " ") {
|
||||
return nil, gerror.New("function TableFields supports only single table operations")
|
||||
return nil, gerror.NewCode(gcode.CodeInvalidParameter, "function TableFields supports only single table operations")
|
||||
}
|
||||
useSchema := d.db.GetSchema()
|
||||
if len(schema) > 0 && schema[0] != "" {
|
||||
@ -138,3 +137,17 @@ func (d *DriverSqlite) TableFields(ctx context.Context, table string, schema ...
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// DoInsert is not supported in sqlite.
|
||||
func (d *DriverSqlite) DoInsert(ctx context.Context, link Link, table string, list List, option DoInsertOption) (result sql.Result, err error) {
|
||||
switch option.InsertOption {
|
||||
case insertOptionSave:
|
||||
return nil, gerror.NewCode(gcode.CodeNotSupported, `Save operation is not supported by sqlite driver`)
|
||||
|
||||
case insertOptionReplace:
|
||||
return nil, gerror.NewCode(gcode.CodeNotSupported, `Replace operation is not supported by sqlite driver`)
|
||||
|
||||
default:
|
||||
return d.Core.DoInsert(ctx, link, table, list, option)
|
||||
}
|
||||
}
|
||||
|
||||
@ -8,7 +8,9 @@ package gdb
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"database/sql"
|
||||
"fmt"
|
||||
"github.com/gogf/gf/errors/gcode"
|
||||
"reflect"
|
||||
"regexp"
|
||||
"strings"
|
||||
@ -55,11 +57,13 @@ type apiTableName interface {
|
||||
}
|
||||
|
||||
const (
|
||||
OrmTagForStruct = "orm"
|
||||
OrmTagForUnique = "unique"
|
||||
OrmTagForPrimary = "primary"
|
||||
OrmTagForTable = "table"
|
||||
OrmTagForWith = "with"
|
||||
OrmTagForStruct = "orm"
|
||||
OrmTagForUnique = "unique"
|
||||
OrmTagForPrimary = "primary"
|
||||
OrmTagForTable = "table"
|
||||
OrmTagForWith = "with"
|
||||
OrmTagForWithWhere = "where"
|
||||
OrmTagForWithOrder = "order"
|
||||
)
|
||||
|
||||
var (
|
||||
@ -70,6 +74,32 @@ var (
|
||||
structTagPriority = append([]string{OrmTagForStruct}, gconv.StructTagPriority...)
|
||||
)
|
||||
|
||||
// guessPrimaryTableName parses and returns the primary table name.
|
||||
func (m *Model) guessPrimaryTableName(tableStr string) string {
|
||||
if tableStr == "" {
|
||||
return ""
|
||||
}
|
||||
var (
|
||||
guessedTableName = ""
|
||||
array1 = gstr.SplitAndTrim(tableStr, ",")
|
||||
array2 = gstr.SplitAndTrim(array1[0], " ")
|
||||
array3 = gstr.SplitAndTrim(array2[0], ".")
|
||||
)
|
||||
if len(array3) >= 2 {
|
||||
guessedTableName = array3[1]
|
||||
} else {
|
||||
guessedTableName = array3[0]
|
||||
}
|
||||
charL, charR := m.db.GetChars()
|
||||
if charL != "" || charR != "" {
|
||||
guessedTableName = gstr.Trim(guessedTableName, charL+charR)
|
||||
}
|
||||
if !gregex.IsMatchString(regularFieldNameRegPattern, guessedTableName) {
|
||||
return ""
|
||||
}
|
||||
return guessedTableName
|
||||
}
|
||||
|
||||
// getTableNameFromOrmTag retrieves and returns the table name from struct object.
|
||||
func getTableNameFromOrmTag(object interface{}) string {
|
||||
var tableName string
|
||||
@ -142,8 +172,8 @@ func GetInsertOperationByOption(option int) string {
|
||||
// ConvertDataForTableRecord is a very important function, which does converting for any data that
|
||||
// will be inserted into table as a record.
|
||||
//
|
||||
// The parameter `obj` should be type of *map/map/*struct/struct.
|
||||
// It supports inherit struct definition for struct.
|
||||
// The parameter `value` should be type of *map/map/*struct/struct.
|
||||
// It supports embedded struct definition for struct.
|
||||
func ConvertDataForTableRecord(value interface{}) map[string]interface{} {
|
||||
var (
|
||||
rvValue reflect.Value
|
||||
@ -164,12 +194,32 @@ func ConvertDataForTableRecord(value interface{}) map[string]interface{} {
|
||||
// Convert the value to JSON.
|
||||
data[k], _ = json.Marshal(v)
|
||||
}
|
||||
|
||||
case reflect.Struct:
|
||||
switch v.(type) {
|
||||
case time.Time, *time.Time, gtime.Time, *gtime.Time:
|
||||
switch r := v.(type) {
|
||||
// If the time is zero, it then updates it to nil,
|
||||
// which will insert/update the value to database as "null".
|
||||
case time.Time:
|
||||
if r.IsZero() {
|
||||
data[k] = nil
|
||||
}
|
||||
|
||||
case gtime.Time:
|
||||
if r.IsZero() {
|
||||
data[k] = nil
|
||||
}
|
||||
|
||||
case *gtime.Time:
|
||||
if r.IsZero() {
|
||||
data[k] = nil
|
||||
}
|
||||
|
||||
case *time.Time:
|
||||
continue
|
||||
|
||||
case Counter, *Counter:
|
||||
continue
|
||||
|
||||
default:
|
||||
// Use string conversion in default.
|
||||
if s, ok := v.(apiString); ok {
|
||||
@ -184,102 +234,26 @@ func ConvertDataForTableRecord(value interface{}) map[string]interface{} {
|
||||
return data
|
||||
}
|
||||
|
||||
// DataToMapDeep converts `value` to map type recursively.
|
||||
// DataToMapDeep converts `value` to map type recursively(if attribute struct is embedded).
|
||||
// The parameter `value` should be type of *map/map/*struct/struct.
|
||||
// It supports inherit struct definition for struct.
|
||||
// It supports embedded struct definition for struct.
|
||||
func DataToMapDeep(value interface{}) map[string]interface{} {
|
||||
if v, ok := value.(apiMapStrAny); ok {
|
||||
return v.MapStrAny()
|
||||
}
|
||||
var (
|
||||
rvValue reflect.Value
|
||||
rvField reflect.Value
|
||||
rvKind reflect.Kind
|
||||
rtField reflect.StructField
|
||||
)
|
||||
if v, ok := value.(reflect.Value); ok {
|
||||
rvValue = v
|
||||
} else {
|
||||
rvValue = reflect.ValueOf(value)
|
||||
}
|
||||
rvKind = rvValue.Kind()
|
||||
if rvKind == reflect.Ptr {
|
||||
rvValue = rvValue.Elem()
|
||||
rvKind = rvValue.Kind()
|
||||
}
|
||||
// If given `value` is not a struct, it uses gconv.Map for converting.
|
||||
if rvKind != reflect.Struct {
|
||||
return gconv.Map(value, structTagPriority...)
|
||||
}
|
||||
// Struct handling.
|
||||
var (
|
||||
fieldTag reflect.StructTag
|
||||
rvType = rvValue.Type()
|
||||
name = ""
|
||||
data = make(map[string]interface{})
|
||||
)
|
||||
for i := 0; i < rvValue.NumField(); i++ {
|
||||
rtField = rvType.Field(i)
|
||||
rvField = rvValue.Field(i)
|
||||
fieldName := rtField.Name
|
||||
if !utils.IsLetterUpper(fieldName[0]) {
|
||||
continue
|
||||
}
|
||||
// Struct attribute inherit
|
||||
if rtField.Anonymous {
|
||||
for k, v := range DataToMapDeep(rvField) {
|
||||
data[k] = v
|
||||
}
|
||||
continue
|
||||
}
|
||||
// Other attributes.
|
||||
name = ""
|
||||
fieldTag = rtField.Tag
|
||||
for _, tag := range structTagPriority {
|
||||
if s := fieldTag.Get(tag); s != "" && gregex.IsMatchString(regularFieldNameWithoutDotRegPattern, s) {
|
||||
name = s
|
||||
break
|
||||
}
|
||||
}
|
||||
if name == "" {
|
||||
name = fieldName
|
||||
} else {
|
||||
// The "orm" tag supports json tag feature: -, omitempty
|
||||
// The "orm" tag would be like: "id,priority", so it should use splitting handling.
|
||||
name = gstr.Trim(name)
|
||||
if name == "-" {
|
||||
continue
|
||||
}
|
||||
array := gstr.SplitAndTrim(name, ",")
|
||||
if len(array) > 1 {
|
||||
switch array[1] {
|
||||
case "omitempty":
|
||||
if empty.IsEmpty(rvField.Interface()) {
|
||||
continue
|
||||
} else {
|
||||
name = array[0]
|
||||
}
|
||||
default:
|
||||
name = array[0]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// The underlying driver supports time.Time/*time.Time types.
|
||||
fieldValue := rvField.Interface()
|
||||
switch fieldValue.(type) {
|
||||
m := gconv.Map(value, structTagPriority...)
|
||||
for k, v := range m {
|
||||
switch v.(type) {
|
||||
case time.Time, *time.Time, gtime.Time, *gtime.Time:
|
||||
data[name] = fieldValue
|
||||
m[k] = v
|
||||
|
||||
default:
|
||||
// Use string conversion in default.
|
||||
if s, ok := fieldValue.(apiString); ok {
|
||||
data[name] = s.String()
|
||||
if s, ok := v.(apiString); ok {
|
||||
m[k] = s.String()
|
||||
} else {
|
||||
data[name] = fieldValue
|
||||
m[k] = v
|
||||
}
|
||||
}
|
||||
}
|
||||
return data
|
||||
return m
|
||||
}
|
||||
|
||||
// doHandleTableName adds prefix string and quote chars for the table. It handles table string like:
|
||||
@ -325,14 +299,15 @@ func doQuoteWord(s, charLeft, charRight string) string {
|
||||
return s
|
||||
}
|
||||
|
||||
// doQuoteString quotes string with quote chars. It handles strings like:
|
||||
// "user",
|
||||
// "user u",
|
||||
// "user,user_detail",
|
||||
// "user u, user_detail ut",
|
||||
// "user.user u, user.user_detail ut",
|
||||
// "u.id, u.name, u.age",
|
||||
// "u.id asc".
|
||||
// doQuoteString quotes string with quote chars.
|
||||
// For example, if quote char is '`':
|
||||
// "user" => "`user`"
|
||||
// "user u" => "`user` u"
|
||||
// "user,user_detail" => "`user`,`user_detail`"
|
||||
// "user u, user_detail ut" => "`user` u,`user_detail` ut"
|
||||
// "user.user u, user.user_detail ut" => "`user`.`user` u,`user`.`user_detail` ut"
|
||||
// "u.id, u.name, u.age" => "`u`.`id`,`u`.`name`,`u`.`age`"
|
||||
// "u.id asc" => "`u`.`id` asc"
|
||||
func doQuoteString(s, charLeft, charRight string) string {
|
||||
array1 := gstr.SplitAndTrim(s, ",")
|
||||
for k1, v1 := range array1 {
|
||||
@ -444,64 +419,100 @@ func formatSql(sql string, args []interface{}) (newSql string, newArgs []interfa
|
||||
return handleArguments(sql, args)
|
||||
}
|
||||
|
||||
type formatWhereInput struct {
|
||||
Where interface{}
|
||||
Args []interface{}
|
||||
OmitNil bool
|
||||
OmitEmpty bool
|
||||
Schema string
|
||||
Table string
|
||||
}
|
||||
|
||||
// formatWhere formats where statement and its arguments for `Where` and `Having` statements.
|
||||
func formatWhere(db DB, where interface{}, args []interface{}, omitEmpty bool) (newWhere string, newArgs []interface{}) {
|
||||
func formatWhere(db DB, in formatWhereInput) (newWhere string, newArgs []interface{}) {
|
||||
var (
|
||||
buffer = bytes.NewBuffer(nil)
|
||||
rv = reflect.ValueOf(where)
|
||||
kind = rv.Kind()
|
||||
buffer = bytes.NewBuffer(nil)
|
||||
reflectValue = reflect.ValueOf(in.Where)
|
||||
reflectKind = reflectValue.Kind()
|
||||
)
|
||||
for kind == reflect.Ptr {
|
||||
rv = rv.Elem()
|
||||
kind = rv.Kind()
|
||||
for reflectKind == reflect.Ptr {
|
||||
reflectValue = reflectValue.Elem()
|
||||
reflectKind = reflectValue.Kind()
|
||||
}
|
||||
switch kind {
|
||||
switch reflectKind {
|
||||
case reflect.Array, reflect.Slice:
|
||||
newArgs = formatWhereInterfaces(db, gconv.Interfaces(where), buffer, newArgs)
|
||||
newArgs = formatWhereInterfaces(db, gconv.Interfaces(in.Where), buffer, newArgs)
|
||||
|
||||
case reflect.Map:
|
||||
for key, value := range DataToMapDeep(where) {
|
||||
if gregex.IsMatchString(regularFieldNameRegPattern, key) && omitEmpty && empty.IsEmpty(value) {
|
||||
continue
|
||||
for key, value := range DataToMapDeep(in.Where) {
|
||||
if gregex.IsMatchString(regularFieldNameRegPattern, key) {
|
||||
if in.OmitNil && empty.IsNil(value) {
|
||||
continue
|
||||
}
|
||||
if in.OmitEmpty && empty.IsEmpty(value) {
|
||||
continue
|
||||
}
|
||||
}
|
||||
newArgs = formatWhereKeyValue(db, buffer, newArgs, key, value)
|
||||
}
|
||||
|
||||
case reflect.Struct:
|
||||
// If `where` struct implements apiIterator interface,
|
||||
// it then uses its Iterate function to iterates its key-value pairs.
|
||||
// it then uses its Iterate function to iterate its key-value pairs.
|
||||
// For example, ListMap and TreeMap are ordered map,
|
||||
// which implement apiIterator interface and are index-friendly for where conditions.
|
||||
if iterator, ok := where.(apiIterator); ok {
|
||||
if iterator, ok := in.Where.(apiIterator); ok {
|
||||
iterator.Iterator(func(key, value interface{}) bool {
|
||||
ketStr := gconv.String(key)
|
||||
if gregex.IsMatchString(regularFieldNameRegPattern, ketStr) && omitEmpty && empty.IsEmpty(value) {
|
||||
return true
|
||||
if gregex.IsMatchString(regularFieldNameRegPattern, ketStr) {
|
||||
if in.OmitNil && empty.IsNil(value) {
|
||||
return true
|
||||
}
|
||||
if in.OmitEmpty && empty.IsEmpty(value) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
newArgs = formatWhereKeyValue(db, buffer, newArgs, ketStr, value)
|
||||
return true
|
||||
})
|
||||
break
|
||||
}
|
||||
for key, value := range DataToMapDeep(where) {
|
||||
if omitEmpty && empty.IsEmpty(value) {
|
||||
continue
|
||||
// Automatically mapping and filtering the struct attribute.
|
||||
var (
|
||||
reflectType = reflectValue.Type()
|
||||
structField reflect.StructField
|
||||
)
|
||||
data := DataToMapDeep(in.Where)
|
||||
if in.Table != "" {
|
||||
data, _ = db.GetCore().mappingAndFilterData(in.Schema, in.Table, data, true)
|
||||
}
|
||||
// Put the struct attributes in sequence in Where statement.
|
||||
for i := 0; i < reflectType.NumField(); i++ {
|
||||
structField = reflectType.Field(i)
|
||||
foundKey, foundValue := gutil.MapPossibleItemByKey(data, structField.Name)
|
||||
if foundKey != "" {
|
||||
if in.OmitNil && empty.IsNil(foundValue) {
|
||||
continue
|
||||
}
|
||||
if in.OmitEmpty && empty.IsEmpty(foundValue) {
|
||||
continue
|
||||
}
|
||||
newArgs = formatWhereKeyValue(db, buffer, newArgs, foundKey, foundValue)
|
||||
}
|
||||
newArgs = formatWhereKeyValue(db, buffer, newArgs, key, value)
|
||||
}
|
||||
|
||||
default:
|
||||
// Usually a string.
|
||||
var (
|
||||
i = 0
|
||||
whereStr = gconv.String(where)
|
||||
whereStr = gconv.String(in.Where)
|
||||
)
|
||||
for {
|
||||
if i >= len(args) {
|
||||
if i >= len(in.Args) {
|
||||
break
|
||||
}
|
||||
// Sub query, which is always used along with a string condition.
|
||||
if model, ok := args[i].(*Model); ok {
|
||||
if model, ok := in.Args[i].(*Model); ok {
|
||||
var (
|
||||
index = -1
|
||||
)
|
||||
@ -515,7 +526,7 @@ func formatWhere(db DB, where interface{}, args []interface{}, omitEmpty bool) (
|
||||
}
|
||||
return s
|
||||
})
|
||||
args = gutil.SliceDelete(args, i)
|
||||
in.Args = gutil.SliceDelete(in.Args, i)
|
||||
continue
|
||||
}
|
||||
i++
|
||||
@ -524,9 +535,9 @@ func formatWhere(db DB, where interface{}, args []interface{}, omitEmpty bool) (
|
||||
}
|
||||
|
||||
if buffer.Len() == 0 {
|
||||
return "", args
|
||||
return "", in.Args
|
||||
}
|
||||
newArgs = append(newArgs, args...)
|
||||
newArgs = append(newArgs, in.Args...)
|
||||
newWhere = buffer.String()
|
||||
if len(newArgs) > 0 {
|
||||
if gstr.Pos(newWhere, "?") == -1 {
|
||||
@ -769,9 +780,9 @@ func handleArguments(sql string, args []interface{}) (newSql string, newArgs []i
|
||||
}
|
||||
|
||||
// formatError customizes and returns the SQL error.
|
||||
func formatError(err error, sql string, args ...interface{}) error {
|
||||
if err != nil && err != ErrNoRows {
|
||||
return gerror.New(fmt.Sprintf("%s, %s\n", err.Error(), FormatSqlWithArgs(sql, args)))
|
||||
func formatError(err error, s string, args ...interface{}) error {
|
||||
if err != nil && err != sql.ErrNoRows {
|
||||
return gerror.NewCodef(gcode.CodeDbOperationError, "%s, %s\n", err.Error(), FormatSqlWithArgs(s, args))
|
||||
}
|
||||
return err
|
||||
}
|
||||
@ -781,7 +792,9 @@ func formatError(err error, sql string, args ...interface{}) error {
|
||||
func FormatSqlWithArgs(sql string, args []interface{}) string {
|
||||
index := -1
|
||||
newQuery, _ := gregex.ReplaceStringFunc(
|
||||
`(\?|:v\d+|\$\d+|@p\d+)`, sql, func(s string) string {
|
||||
`(\?|:v\d+|\$\d+|@p\d+)`,
|
||||
sql,
|
||||
func(s string) string {
|
||||
index++
|
||||
if len(args) > index {
|
||||
if args[index] == nil {
|
||||
@ -801,6 +814,7 @@ func FormatSqlWithArgs(sql string, args []interface{}) string {
|
||||
switch kind {
|
||||
case reflect.String, reflect.Map, reflect.Slice, reflect.Array:
|
||||
return `'` + gstr.QuoteMeta(gconv.String(args[index]), `'`) + `'`
|
||||
|
||||
case reflect.Struct:
|
||||
if t, ok := args[index].(time.Time); ok {
|
||||
return `'` + t.Format(`2006-01-02 15:04:05`) + `'`
|
||||
|
||||
@ -17,54 +17,63 @@ import (
|
||||
"github.com/gogf/gf/text/gstr"
|
||||
)
|
||||
|
||||
// Model is the DAO for ORM.
|
||||
// Model is core struct implementing the DAO for ORM.
|
||||
type Model struct {
|
||||
db DB // Underlying DB interface.
|
||||
tx *TX // Underlying TX interface.
|
||||
schema string // Custom database schema.
|
||||
linkType int // Mark for operation on master or slave.
|
||||
tablesInit string // Table names when model initialization.
|
||||
tables string // Operation table names, which can be more than one table names and aliases, like: "user", "user u", "user u, user_detail ud".
|
||||
fields string // Operation fields, multiple fields joined using char ','.
|
||||
fieldsEx string // Excluded operation fields, multiple fields joined using char ','.
|
||||
withArray []interface{} // Arguments for With feature.
|
||||
withAll bool // Enable model association operations on all objects that have "with" tag in the struct.
|
||||
extraArgs []interface{} // Extra custom arguments for sql, which are prepended to the arguments before sql committed to underlying driver.
|
||||
whereHolder []*whereHolder // Condition strings for where operation.
|
||||
groupBy string // Used for "group by" statement.
|
||||
orderBy string // Used for "order by" statement.
|
||||
having []interface{} // Used for "having..." statement.
|
||||
start int // Used for "select ... start, limit ..." statement.
|
||||
limit int // Used for "select ... start, limit ..." statement.
|
||||
option int // Option for extra operation features.
|
||||
offset int // Offset statement for some databases grammar.
|
||||
data interface{} // Data for operation, which can be type of map/[]map/struct/*struct/string, etc.
|
||||
batch int // Batch number for batch Insert/Replace/Save operations.
|
||||
filter bool // Filter data and where key-value pairs according to the fields of the table.
|
||||
distinct string // Force the query to only return distinct results.
|
||||
lockInfo string // Lock for update or in shared lock.
|
||||
cacheEnabled bool // Enable sql result cache feature.
|
||||
cacheDuration time.Duration // Cache TTL duration.
|
||||
cacheName string // Cache name for custom operation.
|
||||
unscoped bool // Disables soft deleting features when select/delete operations.
|
||||
safe bool // If true, it clones and returns a new model object whenever operation done; or else it changes the attribute of current model.
|
||||
db DB // Underlying DB interface.
|
||||
tx *TX // Underlying TX interface.
|
||||
rawSql string // rawSql is the raw SQL string which marks a raw SQL based Model not a table based Model.
|
||||
schema string // Custom database schema.
|
||||
linkType int // Mark for operation on master or slave.
|
||||
tablesInit string // Table names when model initialization.
|
||||
tables string // Operation table names, which can be more than one table names and aliases, like: "user", "user u", "user u, user_detail ud".
|
||||
fields string // Operation fields, multiple fields joined using char ','.
|
||||
fieldsEx string // Excluded operation fields, multiple fields joined using char ','.
|
||||
withArray []interface{} // Arguments for With feature.
|
||||
withAll bool // Enable model association operations on all objects that have "with" tag in the struct.
|
||||
extraArgs []interface{} // Extra custom arguments for sql, which are prepended to the arguments before sql committed to underlying driver.
|
||||
whereHolder []ModelWhereHolder // Condition strings for where operation.
|
||||
groupBy string // Used for "group by" statement.
|
||||
orderBy string // Used for "order by" statement.
|
||||
having []interface{} // Used for "having..." statement.
|
||||
start int // Used for "select ... start, limit ..." statement.
|
||||
limit int // Used for "select ... start, limit ..." statement.
|
||||
option int // Option for extra operation features.
|
||||
offset int // Offset statement for some databases grammar.
|
||||
data interface{} // Data for operation, which can be type of map/[]map/struct/*struct/string, etc.
|
||||
batch int // Batch number for batch Insert/Replace/Save operations.
|
||||
filter bool // Filter data and where key-value pairs according to the fields of the table.
|
||||
distinct string // Force the query to only return distinct results.
|
||||
lockInfo string // Lock for update or in shared lock.
|
||||
cacheEnabled bool // Enable sql result cache feature.
|
||||
cacheDuration time.Duration // Cache TTL duration (< 1 for removing cache, >= 0 for saving cache).
|
||||
cacheName string // Cache name for custom operation.
|
||||
unscoped bool // Disables soft deleting features when select/delete operations.
|
||||
safe bool // If true, it clones and returns a new model object whenever operation done; or else it changes the attribute of current model.
|
||||
onDuplicate interface{} // onDuplicate is used for ON "DUPLICATE KEY UPDATE" statement.
|
||||
onDuplicateEx interface{} // onDuplicateEx is used for excluding some columns ON "DUPLICATE KEY UPDATE" statement.
|
||||
}
|
||||
|
||||
// whereHolder is the holder for where condition preparing.
|
||||
type whereHolder struct {
|
||||
operator int // Operator for this holder.
|
||||
where interface{} // Where parameter.
|
||||
args []interface{} // Arguments for where parameter.
|
||||
// ModelHandler is a function that handles given Model and returns a new Model that is custom modified.
|
||||
type ModelHandler func(m *Model) *Model
|
||||
|
||||
// ChunkHandler is a function that is used in function Chunk, which handles given Result and error.
|
||||
// It returns true if it wants continue chunking, or else it returns false to stop chunking.
|
||||
type ChunkHandler func(result Result, err error) bool
|
||||
|
||||
// ModelWhereHolder is the holder for where condition preparing.
|
||||
type ModelWhereHolder struct {
|
||||
Operator int // Operator for this holder.
|
||||
Where interface{} // Where parameter, which can commonly be type of string/map/struct.
|
||||
Args []interface{} // Arguments for where parameter.
|
||||
}
|
||||
|
||||
const (
|
||||
OptionOmitEmpty = 1
|
||||
OptionAllowEmpty = 2
|
||||
linkTypeMaster = 1
|
||||
linkTypeSlave = 2
|
||||
whereHolderWhere = 1
|
||||
whereHolderAnd = 2
|
||||
whereHolderOr = 3
|
||||
linkTypeMaster = 1
|
||||
linkTypeSlave = 2
|
||||
whereHolderOperatorWhere = 1
|
||||
whereHolderOperatorAnd = 2
|
||||
whereHolderOperatorOr = 3
|
||||
defaultFields = "*"
|
||||
)
|
||||
|
||||
// Table is alias of Core.Model.
|
||||
@ -77,29 +86,37 @@ func (c *Core) Table(tableNameQueryOrStruct ...interface{}) *Model {
|
||||
// Model creates and returns a new ORM model from given schema.
|
||||
// The parameter `tableNameQueryOrStruct` can be more than one table names, and also alias name, like:
|
||||
// 1. Model names:
|
||||
// Model("user")
|
||||
// Model("user u")
|
||||
// Model("user, user_detail")
|
||||
// Model("user u, user_detail ud")
|
||||
// 2. Model name with alias: Model("user", "u")
|
||||
// db.Model("user")
|
||||
// db.Model("user u")
|
||||
// db.Model("user, user_detail")
|
||||
// db.Model("user u, user_detail ud")
|
||||
// 2. Model name with alias:
|
||||
// db.Model("user", "u")
|
||||
// 3. Model name with sub-query:
|
||||
// db.Model("? AS a, ? AS b", subQuery1, subQuery2)
|
||||
func (c *Core) Model(tableNameQueryOrStruct ...interface{}) *Model {
|
||||
var (
|
||||
tableStr string
|
||||
tableName string
|
||||
extraArgs []interface{}
|
||||
tableNames = make([]string, len(tableNameQueryOrStruct))
|
||||
tableStr string
|
||||
tableName string
|
||||
extraArgs []interface{}
|
||||
)
|
||||
// Model creation with sub-query.
|
||||
if len(tableNameQueryOrStruct) > 1 {
|
||||
conditionStr := gconv.String(tableNameQueryOrStruct[0])
|
||||
if gstr.Contains(conditionStr, "?") {
|
||||
tableStr, extraArgs = formatWhere(
|
||||
c.db, conditionStr, tableNameQueryOrStruct[1:], false,
|
||||
)
|
||||
tableStr, extraArgs = formatWhere(c.db, formatWhereInput{
|
||||
Where: conditionStr,
|
||||
Args: tableNameQueryOrStruct[1:],
|
||||
OmitNil: false,
|
||||
OmitEmpty: false,
|
||||
Schema: "",
|
||||
Table: "",
|
||||
})
|
||||
}
|
||||
}
|
||||
// Normal model creation.
|
||||
if tableStr == "" {
|
||||
tableNames := make([]string, len(tableNameQueryOrStruct))
|
||||
for k, v := range tableNameQueryOrStruct {
|
||||
if s, ok := v.(string); ok {
|
||||
tableNames[k] = s
|
||||
@ -107,7 +124,6 @@ func (c *Core) Model(tableNameQueryOrStruct ...interface{}) *Model {
|
||||
tableNames[k] = tableName
|
||||
}
|
||||
}
|
||||
|
||||
if len(tableNames) > 1 {
|
||||
tableStr = fmt.Sprintf(
|
||||
`%s AS %s`, c.QuotePrefixTableName(tableNames[0]), c.QuoteWord(tableNames[1]),
|
||||
@ -120,26 +136,44 @@ func (c *Core) Model(tableNameQueryOrStruct ...interface{}) *Model {
|
||||
db: c.db,
|
||||
tablesInit: tableStr,
|
||||
tables: tableStr,
|
||||
fields: "*",
|
||||
fields: defaultFields,
|
||||
start: -1,
|
||||
offset: -1,
|
||||
option: OptionAllowEmpty,
|
||||
filter: true,
|
||||
extraArgs: extraArgs,
|
||||
}
|
||||
}
|
||||
|
||||
// Raw creates and returns a model based on a raw sql not a table.
|
||||
// Example:
|
||||
// db.Raw("SELECT * FROM `user` WHERE `name` = ?", "john").Scan(&result)
|
||||
func (c *Core) Raw(rawSql string, args ...interface{}) *Model {
|
||||
model := c.Model()
|
||||
model.rawSql = rawSql
|
||||
model.extraArgs = args
|
||||
return model
|
||||
}
|
||||
|
||||
// Raw creates and returns a model based on a raw sql not a table.
|
||||
// Example:
|
||||
// db.Raw("SELECT * FROM `user` WHERE `name` = ?", "john").Scan(&result)
|
||||
// See Core.Raw.
|
||||
func (m *Model) Raw(rawSql string, args ...interface{}) *Model {
|
||||
model := m.db.Raw(rawSql, args...)
|
||||
model.db = m.db
|
||||
model.tx = m.tx
|
||||
return model
|
||||
}
|
||||
|
||||
func (tx *TX) Raw(rawSql string, args ...interface{}) *Model {
|
||||
return tx.Model().Raw(rawSql, args...)
|
||||
}
|
||||
|
||||
// With creates and returns an ORM model based on meta data of given object.
|
||||
func (c *Core) With(objects ...interface{}) *Model {
|
||||
return c.db.Model().With(objects...)
|
||||
}
|
||||
|
||||
// Table is alias of tx.Model.
|
||||
// Deprecated, use Model instead.
|
||||
func (tx *TX) Table(tableNameQueryOrStruct ...interface{}) *Model {
|
||||
return tx.Model(tableNameQueryOrStruct...)
|
||||
}
|
||||
|
||||
// Model acts like Core.Model except it operates on transaction.
|
||||
// See Core.Model.
|
||||
func (tx *TX) Model(tableNameQueryOrStruct ...interface{}) *Model {
|
||||
@ -182,7 +216,7 @@ func (m *Model) As(as string) *Model {
|
||||
if m.tables != "" {
|
||||
model := m.getModel()
|
||||
split := " JOIN "
|
||||
if gstr.Contains(model.tables, split) {
|
||||
if gstr.ContainsI(model.tables, split) {
|
||||
// For join table.
|
||||
array := gstr.Split(model.tables, split)
|
||||
array[len(array)-1], _ = gregex.ReplaceString(`(.+) ON`, fmt.Sprintf(`$1 AS %s ON`, as), array[len(array)-1])
|
||||
@ -234,7 +268,7 @@ func (m *Model) Clone() *Model {
|
||||
copy(newModel.extraArgs, m.extraArgs)
|
||||
}
|
||||
if n := len(m.whereHolder); n > 0 {
|
||||
newModel.whereHolder = make([]*whereHolder, n)
|
||||
newModel.whereHolder = make([]ModelWhereHolder, n)
|
||||
copy(newModel.whereHolder, m.whereHolder)
|
||||
}
|
||||
if n := len(m.withArray); n > 0 {
|
||||
@ -276,3 +310,13 @@ func (m *Model) Args(args ...interface{}) *Model {
|
||||
model.extraArgs = append(model.extraArgs, args)
|
||||
return model
|
||||
}
|
||||
|
||||
// Handler calls each of `handlers` on current Model and returns a new Model.
|
||||
// ModelHandler is a function that handles given Model and returns a new Model that is custom modified.
|
||||
func (m *Model) Handler(handlers ...ModelHandler) *Model {
|
||||
model := m.getModel()
|
||||
for _, handler := range handlers {
|
||||
model = handler(model)
|
||||
}
|
||||
return model
|
||||
}
|
||||
|
||||
@ -8,6 +8,7 @@ package gdb
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/gogf/gf/text/gstr"
|
||||
"github.com/gogf/gf/util/gconv"
|
||||
"strings"
|
||||
)
|
||||
@ -26,12 +27,12 @@ import (
|
||||
func (m *Model) Where(where interface{}, args ...interface{}) *Model {
|
||||
model := m.getModel()
|
||||
if model.whereHolder == nil {
|
||||
model.whereHolder = make([]*whereHolder, 0)
|
||||
model.whereHolder = make([]ModelWhereHolder, 0)
|
||||
}
|
||||
model.whereHolder = append(model.whereHolder, &whereHolder{
|
||||
operator: whereHolderWhere,
|
||||
where: where,
|
||||
args: args,
|
||||
model.whereHolder = append(model.whereHolder, ModelWhereHolder{
|
||||
Operator: whereHolderOperatorWhere,
|
||||
Where: where,
|
||||
Args: args,
|
||||
})
|
||||
return model
|
||||
}
|
||||
@ -60,55 +61,86 @@ func (m *Model) WherePri(where interface{}, args ...interface{}) *Model {
|
||||
return m.Where(newWhere[0], newWhere[1:]...)
|
||||
}
|
||||
|
||||
// WhereBetween builds `xxx BETWEEN x AND y` statement.
|
||||
// Wheref builds condition string using fmt.Sprintf and arguments.
|
||||
// Note that if the number of `args` is more than the place holder in `format`,
|
||||
// the extra `args` will be used as the where condition arguments of the Model.
|
||||
func (m *Model) Wheref(format string, args ...interface{}) *Model {
|
||||
var (
|
||||
placeHolderCount = gstr.Count(format, "?")
|
||||
conditionStr = fmt.Sprintf(format, args[:len(args)-placeHolderCount]...)
|
||||
)
|
||||
return m.Where(conditionStr, args[len(args)-placeHolderCount:]...)
|
||||
}
|
||||
|
||||
// WhereLT builds `column < value` statement.
|
||||
func (m *Model) WhereLT(column string, value interface{}) *Model {
|
||||
return m.Wheref(`%s < ?`, column, value)
|
||||
}
|
||||
|
||||
// WhereLTE builds `column <= value` statement.
|
||||
func (m *Model) WhereLTE(column string, value interface{}) *Model {
|
||||
return m.Wheref(`%s <= ?`, column, value)
|
||||
}
|
||||
|
||||
// WhereGT builds `column > value` statement.
|
||||
func (m *Model) WhereGT(column string, value interface{}) *Model {
|
||||
return m.Wheref(`%s > ?`, column, value)
|
||||
}
|
||||
|
||||
// WhereGTE builds `column >= value` statement.
|
||||
func (m *Model) WhereGTE(column string, value interface{}) *Model {
|
||||
return m.Wheref(`%s >= ?`, column, value)
|
||||
}
|
||||
|
||||
// WhereBetween builds `column BETWEEN min AND max` statement.
|
||||
func (m *Model) WhereBetween(column string, min, max interface{}) *Model {
|
||||
return m.Where(fmt.Sprintf(`%s BETWEEN ? AND ?`, m.db.GetCore().QuoteWord(column)), min, max)
|
||||
return m.Wheref(`%s BETWEEN ? AND ?`, m.db.GetCore().QuoteWord(column), min, max)
|
||||
}
|
||||
|
||||
// WhereLike builds `xxx LIKE x` statement.
|
||||
// WhereLike builds `column LIKE like` statement.
|
||||
func (m *Model) WhereLike(column string, like interface{}) *Model {
|
||||
return m.Where(fmt.Sprintf(`%s LIKE ?`, m.db.GetCore().QuoteWord(column)), like)
|
||||
return m.Wheref(`%s LIKE ?`, m.db.GetCore().QuoteWord(column), like)
|
||||
}
|
||||
|
||||
// WhereIn builds `xxx IN (x)` statement.
|
||||
// WhereIn builds `column IN (in)` statement.
|
||||
func (m *Model) WhereIn(column string, in interface{}) *Model {
|
||||
return m.Where(fmt.Sprintf(`%s IN (?)`, m.db.GetCore().QuoteWord(column)), in)
|
||||
return m.Wheref(`%s IN (?)`, m.db.GetCore().QuoteWord(column), in)
|
||||
}
|
||||
|
||||
// WhereNull builds `xxx IS NULL` statement.
|
||||
// WhereNull builds `columns[0] IS NULL AND columns[1] IS NULL ...` statement.
|
||||
func (m *Model) WhereNull(columns ...string) *Model {
|
||||
model := m
|
||||
for _, column := range columns {
|
||||
model = m.Where(fmt.Sprintf(`%s IS NULL`, m.db.GetCore().QuoteWord(column)))
|
||||
model = m.Wheref(`%s IS NULL`, m.db.GetCore().QuoteWord(column))
|
||||
}
|
||||
return model
|
||||
}
|
||||
|
||||
// WhereNotBetween builds `xxx NOT BETWEEN x AND y` statement.
|
||||
// WhereNotBetween builds `column NOT BETWEEN min AND max` statement.
|
||||
func (m *Model) WhereNotBetween(column string, min, max interface{}) *Model {
|
||||
return m.Where(fmt.Sprintf(`%s NOT BETWEEN ? AND ?`, m.db.GetCore().QuoteWord(column)), min, max)
|
||||
return m.Wheref(`%s NOT BETWEEN ? AND ?`, m.db.GetCore().QuoteWord(column), min, max)
|
||||
}
|
||||
|
||||
// WhereNotLike builds `xxx NOT LIKE x` statement.
|
||||
// WhereNotLike builds `column NOT LIKE like` statement.
|
||||
func (m *Model) WhereNotLike(column string, like interface{}) *Model {
|
||||
return m.Where(fmt.Sprintf(`%s NOT LIKE ?`, m.db.GetCore().QuoteWord(column)), like)
|
||||
return m.Wheref(`%s NOT LIKE ?`, m.db.GetCore().QuoteWord(column), like)
|
||||
}
|
||||
|
||||
// WhereNot builds `xxx != x` statement.
|
||||
// WhereNot builds `column != value` statement.
|
||||
func (m *Model) WhereNot(column string, value interface{}) *Model {
|
||||
return m.Where(fmt.Sprintf(`%s != ?`, m.db.GetCore().QuoteWord(column)), value)
|
||||
return m.Wheref(`%s != ?`, m.db.GetCore().QuoteWord(column), value)
|
||||
}
|
||||
|
||||
// WhereNotIn builds `xxx NOT IN (x)` statement.
|
||||
// WhereNotIn builds `column NOT IN (in)` statement.
|
||||
func (m *Model) WhereNotIn(column string, in interface{}) *Model {
|
||||
return m.Where(fmt.Sprintf(`%s NOT IN (?)`, m.db.GetCore().QuoteWord(column)), in)
|
||||
return m.Wheref(`%s NOT IN (?)`, m.db.GetCore().QuoteWord(column), in)
|
||||
}
|
||||
|
||||
// WhereNotNull builds `xxx IS NOT NULL` statement.
|
||||
// WhereNotNull builds `columns[0] IS NOT NULL AND columns[1] IS NOT NULL ...` statement.
|
||||
func (m *Model) WhereNotNull(columns ...string) *Model {
|
||||
model := m
|
||||
for _, column := range columns {
|
||||
model = m.Where(fmt.Sprintf(`%s IS NOT NULL`, m.db.GetCore().QuoteWord(column)))
|
||||
model = m.Wheref(`%s IS NOT NULL`, m.db.GetCore().QuoteWord(column))
|
||||
}
|
||||
return model
|
||||
}
|
||||
@ -117,69 +149,101 @@ func (m *Model) WhereNotNull(columns ...string) *Model {
|
||||
func (m *Model) WhereOr(where interface{}, args ...interface{}) *Model {
|
||||
model := m.getModel()
|
||||
if model.whereHolder == nil {
|
||||
model.whereHolder = make([]*whereHolder, 0)
|
||||
model.whereHolder = make([]ModelWhereHolder, 0)
|
||||
}
|
||||
model.whereHolder = append(model.whereHolder, &whereHolder{
|
||||
operator: whereHolderOr,
|
||||
where: where,
|
||||
args: args,
|
||||
model.whereHolder = append(model.whereHolder, ModelWhereHolder{
|
||||
Operator: whereHolderOperatorOr,
|
||||
Where: where,
|
||||
Args: args,
|
||||
})
|
||||
return model
|
||||
}
|
||||
|
||||
// WhereOrBetween builds `xxx BETWEEN x AND y` statement in `OR` conditions.
|
||||
// WhereOrf builds `OR` condition string using fmt.Sprintf and arguments.
|
||||
func (m *Model) WhereOrf(format string, args ...interface{}) *Model {
|
||||
var (
|
||||
placeHolderCount = gstr.Count(format, "?")
|
||||
conditionStr = fmt.Sprintf(format, args[:len(args)-placeHolderCount]...)
|
||||
)
|
||||
return m.WhereOr(conditionStr, args[len(args)-placeHolderCount:]...)
|
||||
}
|
||||
|
||||
// WhereOrLT builds `column < value` statement in `OR` conditions..
|
||||
func (m *Model) WhereOrLT(column string, value interface{}) *Model {
|
||||
return m.WhereOrf(`%s < ?`, column, value)
|
||||
}
|
||||
|
||||
// WhereOrLTE builds `column <= value` statement in `OR` conditions..
|
||||
func (m *Model) WhereOrLTE(column string, value interface{}) *Model {
|
||||
return m.WhereOrf(`%s <= ?`, column, value)
|
||||
}
|
||||
|
||||
// WhereOrGT builds `column > value` statement in `OR` conditions..
|
||||
func (m *Model) WhereOrGT(column string, value interface{}) *Model {
|
||||
return m.WhereOrf(`%s > ?`, column, value)
|
||||
}
|
||||
|
||||
// WhereOrGTE builds `column >= value` statement in `OR` conditions..
|
||||
func (m *Model) WhereOrGTE(column string, value interface{}) *Model {
|
||||
return m.WhereOrf(`%s >= ?`, column, value)
|
||||
}
|
||||
|
||||
// WhereOrBetween builds `column BETWEEN min AND max` statement in `OR` conditions.
|
||||
func (m *Model) WhereOrBetween(column string, min, max interface{}) *Model {
|
||||
return m.WhereOr(fmt.Sprintf(`%s BETWEEN ? AND ?`, m.db.GetCore().QuoteWord(column)), min, max)
|
||||
return m.WhereOrf(`%s BETWEEN ? AND ?`, m.db.GetCore().QuoteWord(column), min, max)
|
||||
}
|
||||
|
||||
// WhereOrLike builds `xxx LIKE x` statement in `OR` conditions.
|
||||
// WhereOrLike builds `column LIKE like` statement in `OR` conditions.
|
||||
func (m *Model) WhereOrLike(column string, like interface{}) *Model {
|
||||
return m.WhereOr(fmt.Sprintf(`%s LIKE ?`, m.db.GetCore().QuoteWord(column)), like)
|
||||
return m.WhereOrf(`%s LIKE ?`, m.db.GetCore().QuoteWord(column), like)
|
||||
}
|
||||
|
||||
// WhereOrIn builds `xxx IN (x)` statement in `OR` conditions.
|
||||
// WhereOrIn builds `column IN (in)` statement in `OR` conditions.
|
||||
func (m *Model) WhereOrIn(column string, in interface{}) *Model {
|
||||
return m.WhereOr(fmt.Sprintf(`%s IN (?)`, m.db.GetCore().QuoteWord(column)), in)
|
||||
return m.WhereOrf(`%s IN (?)`, m.db.GetCore().QuoteWord(column), in)
|
||||
}
|
||||
|
||||
// WhereOrNull builds `xxx IS NULL` statement in `OR` conditions.
|
||||
// WhereOrNull builds `columns[0] IS NULL OR columns[1] IS NULL ...` statement in `OR` conditions.
|
||||
func (m *Model) WhereOrNull(columns ...string) *Model {
|
||||
model := m
|
||||
for _, column := range columns {
|
||||
model = m.WhereOr(fmt.Sprintf(`%s IS NULL`, m.db.GetCore().QuoteWord(column)))
|
||||
model = m.WhereOrf(`%s IS NULL`, m.db.GetCore().QuoteWord(column))
|
||||
}
|
||||
return model
|
||||
}
|
||||
|
||||
// WhereOrNotBetween builds `xxx NOT BETWEEN x AND y` statement in `OR` conditions.
|
||||
// WhereOrNotBetween builds `column NOT BETWEEN min AND max` statement in `OR` conditions.
|
||||
func (m *Model) WhereOrNotBetween(column string, min, max interface{}) *Model {
|
||||
return m.WhereOr(fmt.Sprintf(`%s NOT BETWEEN ? AND ?`, m.db.GetCore().QuoteWord(column)), min, max)
|
||||
return m.WhereOrf(`%s NOT BETWEEN ? AND ?`, m.db.GetCore().QuoteWord(column), min, max)
|
||||
}
|
||||
|
||||
// WhereOrNotLike builds `xxx NOT LIKE x` statement in `OR` conditions.
|
||||
// WhereOrNotLike builds `column NOT LIKE like` statement in `OR` conditions.
|
||||
func (m *Model) WhereOrNotLike(column string, like interface{}) *Model {
|
||||
return m.WhereOr(fmt.Sprintf(`%s NOT LIKE ?`, m.db.GetCore().QuoteWord(column)), like)
|
||||
return m.WhereOrf(`%s NOT LIKE ?`, m.db.GetCore().QuoteWord(column), like)
|
||||
}
|
||||
|
||||
// WhereOrNotIn builds `xxx NOT IN (x)` statement.
|
||||
// WhereOrNotIn builds `column NOT IN (in)` statement.
|
||||
func (m *Model) WhereOrNotIn(column string, in interface{}) *Model {
|
||||
return m.WhereOr(fmt.Sprintf(`%s NOT IN (?)`, m.db.GetCore().QuoteWord(column)), in)
|
||||
return m.WhereOrf(`%s NOT IN (?)`, m.db.GetCore().QuoteWord(column), in)
|
||||
}
|
||||
|
||||
// WhereOrNotNull builds `xxx IS NOT NULL` statement in `OR` conditions.
|
||||
// WhereOrNotNull builds `columns[0] IS NOT NULL OR columns[1] IS NOT NULL ...` statement in `OR` conditions.
|
||||
func (m *Model) WhereOrNotNull(columns ...string) *Model {
|
||||
model := m
|
||||
for _, column := range columns {
|
||||
model = m.WhereOr(fmt.Sprintf(`%s IS NOT NULL`, m.db.GetCore().QuoteWord(column)))
|
||||
model = m.WhereOrf(`%s IS NOT NULL`, m.db.GetCore().QuoteWord(column))
|
||||
}
|
||||
return model
|
||||
}
|
||||
|
||||
// Group sets the "GROUP BY" statement for the model.
|
||||
func (m *Model) Group(groupBy string) *Model {
|
||||
model := m.getModel()
|
||||
model.groupBy = m.db.GetCore().QuoteString(groupBy)
|
||||
return model
|
||||
func (m *Model) Group(groupBy ...string) *Model {
|
||||
if len(groupBy) > 0 {
|
||||
model := m.getModel()
|
||||
model.groupBy = m.db.GetCore().QuoteString(gstr.Join(groupBy, ","))
|
||||
return model
|
||||
}
|
||||
return m
|
||||
}
|
||||
|
||||
// And adds "AND" condition to the where statement.
|
||||
@ -187,12 +251,12 @@ func (m *Model) Group(groupBy string) *Model {
|
||||
func (m *Model) And(where interface{}, args ...interface{}) *Model {
|
||||
model := m.getModel()
|
||||
if model.whereHolder == nil {
|
||||
model.whereHolder = make([]*whereHolder, 0)
|
||||
model.whereHolder = make([]ModelWhereHolder, 0)
|
||||
}
|
||||
model.whereHolder = append(model.whereHolder, &whereHolder{
|
||||
operator: whereHolderAnd,
|
||||
where: where,
|
||||
args: args,
|
||||
model.whereHolder = append(model.whereHolder, ModelWhereHolder{
|
||||
Operator: whereHolderOperatorAnd,
|
||||
Where: where,
|
||||
Args: args,
|
||||
})
|
||||
return model
|
||||
}
|
||||
@ -216,6 +280,9 @@ func (m *Model) Order(orderBy ...string) *Model {
|
||||
return m
|
||||
}
|
||||
model := m.getModel()
|
||||
if model.orderBy != "" {
|
||||
model.orderBy += ","
|
||||
}
|
||||
model.orderBy = m.db.GetCore().QuoteString(strings.Join(orderBy, " "))
|
||||
return model
|
||||
}
|
||||
@ -226,6 +293,9 @@ func (m *Model) OrderAsc(column string) *Model {
|
||||
return m
|
||||
}
|
||||
model := m.getModel()
|
||||
if model.orderBy != "" {
|
||||
model.orderBy += ","
|
||||
}
|
||||
model.orderBy = m.db.GetCore().QuoteWord(column) + " ASC"
|
||||
return model
|
||||
}
|
||||
@ -236,6 +306,9 @@ func (m *Model) OrderDesc(column string) *Model {
|
||||
return m
|
||||
}
|
||||
model := m.getModel()
|
||||
if model.orderBy != "" {
|
||||
model.orderBy += ","
|
||||
}
|
||||
model.orderBy = m.db.GetCore().QuoteWord(column) + " DESC"
|
||||
return model
|
||||
}
|
||||
@ -312,12 +385,17 @@ func (m *Model) ForPage(page, limit int) *Model {
|
||||
func (m *Model) formatCondition(limit1 bool, isCountStatement bool) (conditionWhere string, conditionExtra string, conditionArgs []interface{}) {
|
||||
if len(m.whereHolder) > 0 {
|
||||
for _, v := range m.whereHolder {
|
||||
switch v.operator {
|
||||
case whereHolderWhere:
|
||||
switch v.Operator {
|
||||
case whereHolderOperatorWhere:
|
||||
if conditionWhere == "" {
|
||||
newWhere, newArgs := formatWhere(
|
||||
m.db, v.where, v.args, m.option&OptionOmitEmpty > 0,
|
||||
)
|
||||
newWhere, newArgs := formatWhere(m.db, formatWhereInput{
|
||||
Where: v.Where,
|
||||
Args: v.Args,
|
||||
OmitNil: m.option&optionOmitNilWhere > 0,
|
||||
OmitEmpty: m.option&optionOmitEmptyWhere > 0,
|
||||
Schema: m.schema,
|
||||
Table: m.tables,
|
||||
})
|
||||
if len(newWhere) > 0 {
|
||||
conditionWhere = newWhere
|
||||
conditionArgs = newArgs
|
||||
@ -326,10 +404,15 @@ func (m *Model) formatCondition(limit1 bool, isCountStatement bool) (conditionWh
|
||||
}
|
||||
fallthrough
|
||||
|
||||
case whereHolderAnd:
|
||||
newWhere, newArgs := formatWhere(
|
||||
m.db, v.where, v.args, m.option&OptionOmitEmpty > 0,
|
||||
)
|
||||
case whereHolderOperatorAnd:
|
||||
newWhere, newArgs := formatWhere(m.db, formatWhereInput{
|
||||
Where: v.Where,
|
||||
Args: v.Args,
|
||||
OmitNil: m.option&optionOmitNilWhere > 0,
|
||||
OmitEmpty: m.option&optionOmitEmptyWhere > 0,
|
||||
Schema: m.schema,
|
||||
Table: m.tables,
|
||||
})
|
||||
if len(newWhere) > 0 {
|
||||
if len(conditionWhere) == 0 {
|
||||
conditionWhere = newWhere
|
||||
@ -341,10 +424,15 @@ func (m *Model) formatCondition(limit1 bool, isCountStatement bool) (conditionWh
|
||||
conditionArgs = append(conditionArgs, newArgs...)
|
||||
}
|
||||
|
||||
case whereHolderOr:
|
||||
newWhere, newArgs := formatWhere(
|
||||
m.db, v.where, v.args, m.option&OptionOmitEmpty > 0,
|
||||
)
|
||||
case whereHolderOperatorOr:
|
||||
newWhere, newArgs := formatWhere(m.db, formatWhereInput{
|
||||
Where: v.Where,
|
||||
Args: v.Args,
|
||||
OmitNil: m.option&optionOmitNilWhere > 0,
|
||||
OmitEmpty: m.option&optionOmitEmptyWhere > 0,
|
||||
Schema: m.schema,
|
||||
Table: m.tables,
|
||||
})
|
||||
if len(newWhere) > 0 {
|
||||
if len(conditionWhere) == 0 {
|
||||
conditionWhere = newWhere
|
||||
@ -360,7 +448,13 @@ func (m *Model) formatCondition(limit1 bool, isCountStatement bool) (conditionWh
|
||||
}
|
||||
// Soft deletion.
|
||||
softDeletingCondition := m.getConditionForSoftDeleting()
|
||||
if !m.unscoped && softDeletingCondition != "" {
|
||||
if m.rawSql != "" && conditionWhere != "" {
|
||||
if gstr.ContainsI(m.rawSql, " WHERE ") {
|
||||
conditionWhere = " AND " + conditionWhere
|
||||
} else {
|
||||
conditionWhere = " WHERE " + conditionWhere
|
||||
}
|
||||
} else if !m.unscoped && softDeletingCondition != "" {
|
||||
if conditionWhere == "" {
|
||||
conditionWhere = fmt.Sprintf(` WHERE %s`, softDeletingCondition)
|
||||
} else {
|
||||
@ -371,15 +465,21 @@ func (m *Model) formatCondition(limit1 bool, isCountStatement bool) (conditionWh
|
||||
conditionWhere = " WHERE " + conditionWhere
|
||||
}
|
||||
}
|
||||
|
||||
// GROUP BY.
|
||||
if m.groupBy != "" {
|
||||
conditionExtra += " GROUP BY " + m.groupBy
|
||||
}
|
||||
// HAVING.
|
||||
if len(m.having) > 0 {
|
||||
havingStr, havingArgs := formatWhere(
|
||||
m.db, m.having[0], gconv.Interfaces(m.having[1]), m.option&OptionOmitEmpty > 0,
|
||||
)
|
||||
havingStr, havingArgs := formatWhere(m.db, formatWhereInput{
|
||||
Where: m.having[0],
|
||||
Args: gconv.Interfaces(m.having[1]),
|
||||
OmitNil: m.option&optionOmitNilWhere > 0,
|
||||
OmitEmpty: m.option&optionOmitEmptyWhere > 0,
|
||||
Schema: m.schema,
|
||||
Table: m.tables,
|
||||
})
|
||||
if len(havingStr) > 0 {
|
||||
conditionExtra += " HAVING " + havingStr
|
||||
conditionArgs = append(conditionArgs, havingArgs...)
|
||||
|
||||
@ -9,6 +9,7 @@ package gdb
|
||||
import (
|
||||
"database/sql"
|
||||
"fmt"
|
||||
"github.com/gogf/gf/errors/gcode"
|
||||
|
||||
"github.com/gogf/gf/errors/gerror"
|
||||
"github.com/gogf/gf/os/gtime"
|
||||
@ -33,7 +34,7 @@ func (m *Model) Delete(where ...interface{}) (result sql.Result, err error) {
|
||||
)
|
||||
// Soft deleting.
|
||||
if !m.unscoped && fieldNameDelete != "" {
|
||||
return m.db.GetCore().DoUpdate(
|
||||
return m.db.DoUpdate(
|
||||
m.GetCtx(),
|
||||
m.getLink(true),
|
||||
m.tables,
|
||||
@ -44,7 +45,7 @@ func (m *Model) Delete(where ...interface{}) (result sql.Result, err error) {
|
||||
}
|
||||
conditionStr := conditionWhere + conditionExtra
|
||||
if !gstr.ContainsI(conditionStr, " WHERE ") {
|
||||
return nil, gerror.New("there should be WHERE condition statement for DELETE operation")
|
||||
return nil, gerror.NewCode(gcode.CodeMissingParameter, "there should be WHERE condition statement for DELETE operation")
|
||||
}
|
||||
return m.db.GetCore().DoDelete(m.GetCtx(), m.getLink(true), m.tables, conditionStr, conditionArgs...)
|
||||
return m.db.DoDelete(m.GetCtx(), m.getLink(true), m.tables, conditionStr, conditionArgs...)
|
||||
}
|
||||
|
||||
@ -14,7 +14,7 @@ import (
|
||||
"github.com/gogf/gf/util/gutil"
|
||||
)
|
||||
|
||||
// Fields sets the operation fields of the model, multiple fields joined using char ','.
|
||||
// Fields appends `fieldNamesOrMapStruct` to the operation fields of the model, multiple fields joined using char ','.
|
||||
// The parameter `fieldNamesOrMapStruct` can be type of string/map/*map/struct/*struct.
|
||||
func (m *Model) Fields(fieldNamesOrMapStruct ...interface{}) *Model {
|
||||
length := len(fieldNamesOrMapStruct)
|
||||
@ -24,26 +24,32 @@ func (m *Model) Fields(fieldNamesOrMapStruct ...interface{}) *Model {
|
||||
switch {
|
||||
// String slice.
|
||||
case length >= 2:
|
||||
model := m.getModel()
|
||||
model.fields = gstr.Join(m.mappingAndFilterToTableFields(gconv.Strings(fieldNamesOrMapStruct), true), ",")
|
||||
return model
|
||||
// It need type asserting.
|
||||
return m.appendFieldsByStr(gstr.Join(
|
||||
m.mappingAndFilterToTableFields(gconv.Strings(fieldNamesOrMapStruct), true),
|
||||
",",
|
||||
))
|
||||
// It needs type asserting.
|
||||
case length == 1:
|
||||
model := m.getModel()
|
||||
switch r := fieldNamesOrMapStruct[0].(type) {
|
||||
case string:
|
||||
model.fields = gstr.Join(m.mappingAndFilterToTableFields([]string{r}, false), ",")
|
||||
return m.appendFieldsByStr(gstr.Join(
|
||||
m.mappingAndFilterToTableFields([]string{r}, false), ",",
|
||||
))
|
||||
case []string:
|
||||
model.fields = gstr.Join(m.mappingAndFilterToTableFields(r, true), ",")
|
||||
return m.appendFieldsByStr(gstr.Join(
|
||||
m.mappingAndFilterToTableFields(r, true), ",",
|
||||
))
|
||||
default:
|
||||
model.fields = gstr.Join(m.mappingAndFilterToTableFields(gutil.Keys(r), true), ",")
|
||||
return m.appendFieldsByStr(gstr.Join(
|
||||
m.mappingAndFilterToTableFields(gutil.Keys(r), true), ",",
|
||||
))
|
||||
}
|
||||
return model
|
||||
}
|
||||
return m
|
||||
}
|
||||
|
||||
// FieldsEx sets the excluded operation fields of the model, multiple fields joined using char ','.
|
||||
// FieldsEx appends `fieldNamesOrMapStruct` to the excluded operation fields of the model,
|
||||
// multiple fields joined using char ','.
|
||||
// Note that this function supports only single table operations.
|
||||
// The parameter `fieldNamesOrMapStruct` can be type of string/map/*map/struct/*struct.
|
||||
func (m *Model) FieldsEx(fieldNamesOrMapStruct ...interface{}) *Model {
|
||||
@ -70,6 +76,78 @@ func (m *Model) FieldsEx(fieldNamesOrMapStruct ...interface{}) *Model {
|
||||
return m
|
||||
}
|
||||
|
||||
// FieldCount formats and appends commonly used field `COUNT(column)` to the select fields of model.
|
||||
func (m *Model) FieldCount(column string, as ...string) *Model {
|
||||
asStr := ""
|
||||
if len(as) > 0 && as[0] != "" {
|
||||
asStr = fmt.Sprintf(` AS %s`, m.db.GetCore().QuoteWord(as[0]))
|
||||
}
|
||||
return m.appendFieldsByStr(fmt.Sprintf(`COUNT(%s)%s`, m.db.GetCore().QuoteWord(column), asStr))
|
||||
}
|
||||
|
||||
// FieldSum formats and appends commonly used field `SUM(column)` to the select fields of model.
|
||||
func (m *Model) FieldSum(column string, as ...string) *Model {
|
||||
asStr := ""
|
||||
if len(as) > 0 && as[0] != "" {
|
||||
asStr = fmt.Sprintf(` AS %s`, m.db.GetCore().QuoteWord(as[0]))
|
||||
}
|
||||
return m.appendFieldsByStr(fmt.Sprintf(`SUM(%s)%s`, m.db.GetCore().QuoteWord(column), asStr))
|
||||
}
|
||||
|
||||
// FieldMin formats and appends commonly used field `MIN(column)` to the select fields of model.
|
||||
func (m *Model) FieldMin(column string, as ...string) *Model {
|
||||
asStr := ""
|
||||
if len(as) > 0 && as[0] != "" {
|
||||
asStr = fmt.Sprintf(` AS %s`, m.db.GetCore().QuoteWord(as[0]))
|
||||
}
|
||||
return m.appendFieldsByStr(fmt.Sprintf(`MIN(%s)%s`, m.db.GetCore().QuoteWord(column), asStr))
|
||||
}
|
||||
|
||||
// FieldMax formats and appends commonly used field `MAX(column)` to the select fields of model.
|
||||
func (m *Model) FieldMax(column string, as ...string) *Model {
|
||||
asStr := ""
|
||||
if len(as) > 0 && as[0] != "" {
|
||||
asStr = fmt.Sprintf(` AS %s`, m.db.GetCore().QuoteWord(as[0]))
|
||||
}
|
||||
return m.appendFieldsByStr(fmt.Sprintf(`MAX(%s)%s`, m.db.GetCore().QuoteWord(column), asStr))
|
||||
}
|
||||
|
||||
// FieldAvg formats and appends commonly used field `AVG(column)` to the select fields of model.
|
||||
func (m *Model) FieldAvg(column string, as ...string) *Model {
|
||||
asStr := ""
|
||||
if len(as) > 0 && as[0] != "" {
|
||||
asStr = fmt.Sprintf(` AS %s`, m.db.GetCore().QuoteWord(as[0]))
|
||||
}
|
||||
return m.appendFieldsByStr(fmt.Sprintf(`AVG(%s)%s`, m.db.GetCore().QuoteWord(column), asStr))
|
||||
}
|
||||
|
||||
func (m *Model) appendFieldsByStr(fields string) *Model {
|
||||
if fields != "" {
|
||||
model := m.getModel()
|
||||
if model.fields == defaultFields {
|
||||
model.fields = ""
|
||||
}
|
||||
if model.fields != "" {
|
||||
model.fields += ","
|
||||
}
|
||||
model.fields += fields
|
||||
return model
|
||||
}
|
||||
return m
|
||||
}
|
||||
|
||||
func (m *Model) appendFieldsExByStr(fieldsEx string) *Model {
|
||||
if fieldsEx != "" {
|
||||
model := m.getModel()
|
||||
if model.fieldsEx != "" {
|
||||
model.fieldsEx += ","
|
||||
}
|
||||
model.fieldsEx += fieldsEx
|
||||
return model
|
||||
}
|
||||
return m
|
||||
}
|
||||
|
||||
// Filter marks filtering the fields which does not exist in the fields of the operated table.
|
||||
// Note that this function supports only single table operations.
|
||||
// Deprecated, filter feature is automatically enabled from GoFrame v1.16.0, it is so no longer used.
|
||||
@ -90,13 +168,13 @@ func (m *Model) FieldsStr(prefix ...string) string {
|
||||
}
|
||||
|
||||
// GetFieldsStr retrieves and returns all fields from the table, joined with char ','.
|
||||
// The optional parameter `prefix` specifies the prefix for each field, eg: FieldsStr("u.").
|
||||
// The optional parameter `prefix` specifies the prefix for each field, eg: GetFieldsStr("u.").
|
||||
func (m *Model) GetFieldsStr(prefix ...string) string {
|
||||
prefixStr := ""
|
||||
if len(prefix) > 0 {
|
||||
prefixStr = prefix[0]
|
||||
}
|
||||
tableFields, err := m.TableFields(m.tables)
|
||||
tableFields, err := m.TableFields(m.tablesInit)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
@ -136,7 +214,7 @@ func (m *Model) GetFieldsExStr(fields string, prefix ...string) string {
|
||||
if len(prefix) > 0 {
|
||||
prefixStr = prefix[0]
|
||||
}
|
||||
tableFields, err := m.TableFields(m.tables)
|
||||
tableFields, err := m.TableFields(m.tablesInit)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
@ -164,7 +242,7 @@ func (m *Model) GetFieldsExStr(fields string, prefix ...string) string {
|
||||
|
||||
// HasField determine whether the field exists in the table.
|
||||
func (m *Model) HasField(field string) (bool, error) {
|
||||
tableFields, err := m.TableFields(m.tables)
|
||||
tableFields, err := m.TableFields(m.tablesInit)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
@ -8,6 +8,8 @@ package gdb
|
||||
|
||||
import (
|
||||
"database/sql"
|
||||
"github.com/gogf/gf/container/gset"
|
||||
"github.com/gogf/gf/errors/gcode"
|
||||
"reflect"
|
||||
|
||||
"github.com/gogf/gf/errors/gerror"
|
||||
@ -51,16 +53,20 @@ func (m *Model) Data(data ...interface{}) *Model {
|
||||
switch params := data[0].(type) {
|
||||
case Result:
|
||||
model.data = params.List()
|
||||
|
||||
case Record:
|
||||
model.data = params.Map()
|
||||
|
||||
case List:
|
||||
list := make(List, len(params))
|
||||
for k, v := range params {
|
||||
list[k] = gutil.MapCopy(v)
|
||||
}
|
||||
model.data = list
|
||||
|
||||
case Map:
|
||||
model.data = gutil.MapCopy(params)
|
||||
|
||||
default:
|
||||
var (
|
||||
rv = reflect.ValueOf(params)
|
||||
@ -77,8 +83,10 @@ func (m *Model) Data(data ...interface{}) *Model {
|
||||
list[i] = ConvertDataForTableRecord(rv.Index(i).Interface())
|
||||
}
|
||||
model.data = list
|
||||
|
||||
case reflect.Map:
|
||||
model.data = ConvertDataForTableRecord(data[0])
|
||||
|
||||
case reflect.Struct:
|
||||
if v, ok := data[0].(apiInterfaces); ok {
|
||||
var (
|
||||
@ -92,6 +100,7 @@ func (m *Model) Data(data ...interface{}) *Model {
|
||||
} else {
|
||||
model.data = ConvertDataForTableRecord(data[0])
|
||||
}
|
||||
|
||||
default:
|
||||
model.data = data[0]
|
||||
}
|
||||
@ -100,6 +109,48 @@ func (m *Model) Data(data ...interface{}) *Model {
|
||||
return model
|
||||
}
|
||||
|
||||
// OnDuplicate sets the operations when columns conflicts occurs.
|
||||
// In MySQL, this is used for "ON DUPLICATE KEY UPDATE" statement.
|
||||
// The parameter `onDuplicate` can be type of string/Raw/*Raw/map/slice.
|
||||
// Example:
|
||||
// OnDuplicate("nickname, age")
|
||||
// OnDuplicate("nickname", "age")
|
||||
// OnDuplicate(g.Map{
|
||||
// "nickname": gdb.Raw("CONCAT('name_', VALUES(`nickname`))"),
|
||||
// })
|
||||
// OnDuplicate(g.Map{
|
||||
// "nickname": "passport",
|
||||
// })
|
||||
func (m *Model) OnDuplicate(onDuplicate ...interface{}) *Model {
|
||||
model := m.getModel()
|
||||
if len(onDuplicate) > 1 {
|
||||
model.onDuplicate = onDuplicate
|
||||
} else {
|
||||
model.onDuplicate = onDuplicate[0]
|
||||
}
|
||||
return model
|
||||
}
|
||||
|
||||
// OnDuplicateEx sets the excluding columns for operations when columns conflicts occurs.
|
||||
// In MySQL, this is used for "ON DUPLICATE KEY UPDATE" statement.
|
||||
// The parameter `onDuplicateEx` can be type of string/map/slice.
|
||||
// Example:
|
||||
// OnDuplicateEx("passport, password")
|
||||
// OnDuplicateEx("passport", "password")
|
||||
// OnDuplicateEx(g.Map{
|
||||
// "passport": "",
|
||||
// "password": "",
|
||||
// })
|
||||
func (m *Model) OnDuplicateEx(onDuplicateEx ...interface{}) *Model {
|
||||
model := m.getModel()
|
||||
if len(onDuplicateEx) > 1 {
|
||||
model.onDuplicateEx = onDuplicateEx
|
||||
} else {
|
||||
model.onDuplicateEx = onDuplicateEx[0]
|
||||
}
|
||||
return model
|
||||
}
|
||||
|
||||
// Insert does "INSERT INTO ..." statement for the model.
|
||||
// The optional parameter `data` is the same as the parameter of Model.Data function,
|
||||
// see Model.Data.
|
||||
@ -156,78 +207,222 @@ func (m *Model) Save(data ...interface{}) (result sql.Result, err error) {
|
||||
}
|
||||
|
||||
// doInsertWithOption inserts data with option parameter.
|
||||
func (m *Model) doInsertWithOption(option int) (result sql.Result, err error) {
|
||||
func (m *Model) doInsertWithOption(insertOption int) (result sql.Result, err error) {
|
||||
defer func() {
|
||||
if err == nil {
|
||||
m.checkAndRemoveCache()
|
||||
}
|
||||
}()
|
||||
if m.data == nil {
|
||||
return nil, gerror.New("inserting into table with empty data")
|
||||
return nil, gerror.NewCode(gcode.CodeMissingParameter, "inserting into table with empty data")
|
||||
}
|
||||
var (
|
||||
list List
|
||||
nowString = gtime.Now().String()
|
||||
fieldNameCreate = m.getSoftFieldNameCreated()
|
||||
fieldNameUpdate = m.getSoftFieldNameUpdated()
|
||||
fieldNameDelete = m.getSoftFieldNameDeleted()
|
||||
)
|
||||
// Batch operation.
|
||||
if list, ok := m.data.(List); ok {
|
||||
batch := defaultBatchNumber
|
||||
if m.batch > 0 {
|
||||
batch = m.batch
|
||||
}
|
||||
newData, err := m.filterDataForInsertOrUpdate(list)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
list = newData.(List)
|
||||
// Automatic handling for creating/updating time.
|
||||
if !m.unscoped && (fieldNameCreate != "" || fieldNameUpdate != "") {
|
||||
for k, v := range list {
|
||||
gutil.MapDelete(v, fieldNameCreate, fieldNameUpdate, fieldNameDelete)
|
||||
if fieldNameCreate != "" {
|
||||
v[fieldNameCreate] = nowString
|
||||
}
|
||||
if fieldNameUpdate != "" {
|
||||
v[fieldNameUpdate] = nowString
|
||||
}
|
||||
list[k] = v
|
||||
}
|
||||
}
|
||||
return m.db.GetCore().DoBatchInsert(
|
||||
m.GetCtx(),
|
||||
m.getLink(true),
|
||||
m.tables,
|
||||
newData,
|
||||
option,
|
||||
batch,
|
||||
)
|
||||
newData, err := m.filterDataForInsertOrUpdate(m.data)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// Single operation.
|
||||
if data, ok := m.data.(Map); ok {
|
||||
newData, err := m.filterDataForInsertOrUpdate(data)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
|
||||
// It converts any data to List type for inserting.
|
||||
switch value := newData.(type) {
|
||||
case Result:
|
||||
list = value.List()
|
||||
|
||||
case Record:
|
||||
list = List{value.Map()}
|
||||
|
||||
case List:
|
||||
list = value
|
||||
for i, v := range list {
|
||||
list[i] = ConvertDataForTableRecord(v)
|
||||
}
|
||||
data = newData.(Map)
|
||||
// Automatic handling for creating/updating time.
|
||||
if !m.unscoped && (fieldNameCreate != "" || fieldNameUpdate != "") {
|
||||
gutil.MapDelete(data, fieldNameCreate, fieldNameUpdate, fieldNameDelete)
|
||||
|
||||
case Map:
|
||||
list = List{ConvertDataForTableRecord(value)}
|
||||
|
||||
default:
|
||||
var (
|
||||
rv = reflect.ValueOf(newData)
|
||||
kind = rv.Kind()
|
||||
)
|
||||
if kind == reflect.Ptr {
|
||||
rv = rv.Elem()
|
||||
kind = rv.Kind()
|
||||
}
|
||||
switch kind {
|
||||
// If it's slice type, it then converts it to List type.
|
||||
case reflect.Slice, reflect.Array:
|
||||
list = make(List, rv.Len())
|
||||
for i := 0; i < rv.Len(); i++ {
|
||||
list[i] = ConvertDataForTableRecord(rv.Index(i).Interface())
|
||||
}
|
||||
|
||||
case reflect.Map:
|
||||
list = List{ConvertDataForTableRecord(value)}
|
||||
|
||||
case reflect.Struct:
|
||||
if v, ok := value.(apiInterfaces); ok {
|
||||
var (
|
||||
array = v.Interfaces()
|
||||
)
|
||||
list = make(List, len(array))
|
||||
for i := 0; i < len(array); i++ {
|
||||
list[i] = ConvertDataForTableRecord(array[i])
|
||||
}
|
||||
} else {
|
||||
list = List{ConvertDataForTableRecord(value)}
|
||||
}
|
||||
|
||||
default:
|
||||
return result, gerror.NewCodef(gcode.CodeInvalidParameter, "unsupported list type:%v", kind)
|
||||
}
|
||||
}
|
||||
|
||||
if len(list) < 1 {
|
||||
return result, gerror.NewCode(gcode.CodeMissingParameter, "data list cannot be empty")
|
||||
}
|
||||
|
||||
// Automatic handling for creating/updating time.
|
||||
if !m.unscoped && (fieldNameCreate != "" || fieldNameUpdate != "") {
|
||||
for k, v := range list {
|
||||
if fieldNameCreate != "" {
|
||||
data[fieldNameCreate] = nowString
|
||||
v[fieldNameCreate] = nowString
|
||||
}
|
||||
if fieldNameUpdate != "" {
|
||||
data[fieldNameUpdate] = nowString
|
||||
v[fieldNameUpdate] = nowString
|
||||
}
|
||||
list[k] = v
|
||||
}
|
||||
}
|
||||
// Format DoInsertOption, especially for "ON DUPLICATE KEY UPDATE" statement.
|
||||
columnNames := make([]string, 0, len(list[0]))
|
||||
for k, _ := range list[0] {
|
||||
columnNames = append(columnNames, k)
|
||||
}
|
||||
doInsertOption, err := m.formatDoInsertOption(insertOption, columnNames)
|
||||
if err != nil {
|
||||
return result, err
|
||||
}
|
||||
|
||||
return m.db.DoInsert(m.GetCtx(), m.getLink(true), m.tables, list, doInsertOption)
|
||||
}
|
||||
|
||||
func (m *Model) formatDoInsertOption(insertOption int, columnNames []string) (option DoInsertOption, err error) {
|
||||
option = DoInsertOption{
|
||||
InsertOption: insertOption,
|
||||
BatchCount: m.getBatch(),
|
||||
}
|
||||
if insertOption == insertOptionSave {
|
||||
onDuplicateExKeys, err := m.formatOnDuplicateExKeys(m.onDuplicateEx)
|
||||
if err != nil {
|
||||
return option, err
|
||||
}
|
||||
var (
|
||||
onDuplicateExKeySet = gset.NewStrSetFrom(onDuplicateExKeys)
|
||||
)
|
||||
if m.onDuplicate != nil {
|
||||
switch m.onDuplicate.(type) {
|
||||
case Raw, *Raw:
|
||||
option.OnDuplicateStr = gconv.String(m.onDuplicate)
|
||||
|
||||
default:
|
||||
var (
|
||||
reflectValue = reflect.ValueOf(m.onDuplicate)
|
||||
reflectKind = reflectValue.Kind()
|
||||
)
|
||||
for reflectKind == reflect.Ptr {
|
||||
reflectValue = reflectValue.Elem()
|
||||
reflectKind = reflectValue.Kind()
|
||||
}
|
||||
switch reflectKind {
|
||||
case reflect.String:
|
||||
option.OnDuplicateMap = make(map[string]interface{})
|
||||
for _, v := range gstr.SplitAndTrim(reflectValue.String(), ",") {
|
||||
if onDuplicateExKeySet.Contains(v) {
|
||||
continue
|
||||
}
|
||||
option.OnDuplicateMap[v] = v
|
||||
}
|
||||
|
||||
case reflect.Map:
|
||||
option.OnDuplicateMap = make(map[string]interface{})
|
||||
for k, v := range gconv.Map(m.onDuplicate) {
|
||||
if onDuplicateExKeySet.Contains(k) {
|
||||
continue
|
||||
}
|
||||
option.OnDuplicateMap[k] = v
|
||||
}
|
||||
|
||||
case reflect.Slice, reflect.Array:
|
||||
option.OnDuplicateMap = make(map[string]interface{})
|
||||
for _, v := range gconv.Strings(m.onDuplicate) {
|
||||
if onDuplicateExKeySet.Contains(v) {
|
||||
continue
|
||||
}
|
||||
option.OnDuplicateMap[v] = v
|
||||
}
|
||||
|
||||
default:
|
||||
return option, gerror.NewCodef(
|
||||
gcode.CodeInvalidParameter,
|
||||
`unsupported OnDuplicate parameter type "%s"`,
|
||||
reflect.TypeOf(m.onDuplicate),
|
||||
)
|
||||
}
|
||||
}
|
||||
} else if onDuplicateExKeySet.Size() > 0 {
|
||||
option.OnDuplicateMap = make(map[string]interface{})
|
||||
for _, v := range columnNames {
|
||||
if onDuplicateExKeySet.Contains(v) {
|
||||
continue
|
||||
}
|
||||
option.OnDuplicateMap[v] = v
|
||||
}
|
||||
}
|
||||
return m.db.GetCore().DoInsert(
|
||||
m.GetCtx(),
|
||||
m.getLink(true),
|
||||
m.tables,
|
||||
newData,
|
||||
option,
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (m *Model) formatOnDuplicateExKeys(onDuplicateEx interface{}) ([]string, error) {
|
||||
if onDuplicateEx == nil {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
var (
|
||||
reflectValue = reflect.ValueOf(onDuplicateEx)
|
||||
reflectKind = reflectValue.Kind()
|
||||
)
|
||||
for reflectKind == reflect.Ptr {
|
||||
reflectValue = reflectValue.Elem()
|
||||
reflectKind = reflectValue.Kind()
|
||||
}
|
||||
switch reflectKind {
|
||||
case reflect.String:
|
||||
return gstr.SplitAndTrim(reflectValue.String(), ","), nil
|
||||
|
||||
case reflect.Map:
|
||||
return gutil.Keys(onDuplicateEx), nil
|
||||
|
||||
case reflect.Slice, reflect.Array:
|
||||
return gconv.Strings(onDuplicateEx), nil
|
||||
|
||||
default:
|
||||
return nil, gerror.NewCodef(
|
||||
gcode.CodeInvalidParameter,
|
||||
`unsupported OnDuplicateEx parameter type "%s"`,
|
||||
reflect.TypeOf(onDuplicateEx),
|
||||
)
|
||||
}
|
||||
return nil, gerror.New("inserting into table with invalid data type")
|
||||
}
|
||||
|
||||
func (m *Model) getBatch() int {
|
||||
batch := defaultBatchNumber
|
||||
if m.batch > 0 {
|
||||
batch = m.batch
|
||||
}
|
||||
return batch
|
||||
}
|
||||
|
||||
@ -56,9 +56,9 @@ func (m *Model) InnerJoin(table ...string) *Model {
|
||||
// doJoin does "LEFT/RIGHT/INNER JOIN ... ON ..." statement on the model.
|
||||
// The parameter `table` can be joined table and its joined condition,
|
||||
// and also with its alias name, like:
|
||||
// Table("user").InnerJoin("user_detail", "user_detail.uid=user.uid")
|
||||
// Table("user", "u").InnerJoin("user_detail", "ud", "ud.uid=u.uid")
|
||||
// Table("user", "u").InnerJoin("SELECT xxx FROM xxx AS a", "a.uid=u.uid")
|
||||
// Model("user").InnerJoin("user_detail", "user_detail.uid=user.uid")
|
||||
// Model("user", "u").InnerJoin("user_detail", "ud", "ud.uid=u.uid")
|
||||
// Model("user", "u").InnerJoin("SELECT xxx FROM xxx AS a", "a.uid=u.uid")
|
||||
// Related issues:
|
||||
// https://github.com/gogf/gf/issues/1024
|
||||
func (m *Model) doJoin(operator string, table ...string) *Model {
|
||||
|
||||
@ -6,22 +6,67 @@
|
||||
|
||||
package gdb
|
||||
|
||||
const (
|
||||
optionOmitNil = optionOmitNilWhere | optionOmitNilData
|
||||
optionOmitEmpty = optionOmitEmptyWhere | optionOmitEmptyData
|
||||
optionOmitEmptyWhere = 1 << iota // 8
|
||||
optionOmitEmptyData // 16
|
||||
optionOmitNilWhere // 32
|
||||
optionOmitNilData // 64
|
||||
)
|
||||
|
||||
// Option adds extra operation option for the model.
|
||||
// Deprecated, use separate operations instead.
|
||||
func (m *Model) Option(option int) *Model {
|
||||
model := m.getModel()
|
||||
model.option = model.option | option
|
||||
return model
|
||||
}
|
||||
|
||||
// OptionOmitEmpty sets OptionOmitEmpty option for the model, which automatically filers
|
||||
// the data and where attributes for empty values.
|
||||
// Deprecated, use OmitEmpty instead.
|
||||
func (m *Model) OptionOmitEmpty() *Model {
|
||||
return m.Option(OptionOmitEmpty)
|
||||
// OmitEmpty sets optionOmitEmpty option for the model, which automatically filers
|
||||
// the data and where parameters for `empty` values.
|
||||
func (m *Model) OmitEmpty() *Model {
|
||||
model := m.getModel()
|
||||
model.option = model.option | optionOmitEmpty
|
||||
return model
|
||||
}
|
||||
|
||||
// OmitEmpty sets OptionOmitEmpty option for the model, which automatically filers
|
||||
// the data and where attributes for empty values.
|
||||
func (m *Model) OmitEmpty() *Model {
|
||||
return m.Option(OptionOmitEmpty)
|
||||
// OmitEmptyWhere sets optionOmitEmptyWhere option for the model, which automatically filers
|
||||
// the Where/Having parameters for `empty` values.
|
||||
func (m *Model) OmitEmptyWhere() *Model {
|
||||
model := m.getModel()
|
||||
model.option = model.option | optionOmitEmptyWhere
|
||||
return model
|
||||
}
|
||||
|
||||
// OmitEmptyData sets optionOmitEmptyData option for the model, which automatically filers
|
||||
// the Data parameters for `empty` values.
|
||||
func (m *Model) OmitEmptyData() *Model {
|
||||
model := m.getModel()
|
||||
model.option = model.option | optionOmitEmptyData
|
||||
return model
|
||||
}
|
||||
|
||||
// OmitNil sets optionOmitNil option for the model, which automatically filers
|
||||
// the data and where parameters for `nil` values.
|
||||
func (m *Model) OmitNil() *Model {
|
||||
model := m.getModel()
|
||||
model.option = model.option | optionOmitNil
|
||||
return model
|
||||
}
|
||||
|
||||
// OmitNilWhere sets optionOmitNilWhere option for the model, which automatically filers
|
||||
// the Where/Having parameters for `nil` values.
|
||||
func (m *Model) OmitNilWhere() *Model {
|
||||
model := m.getModel()
|
||||
model.option = model.option | optionOmitNilWhere
|
||||
return model
|
||||
}
|
||||
|
||||
// OmitNilData sets optionOmitNilData option for the model, which automatically filers
|
||||
// the Data parameters for `nil` values.
|
||||
func (m *Model) OmitNilData() *Model {
|
||||
model := m.getModel()
|
||||
model.option = model.option | optionOmitNilData
|
||||
return model
|
||||
}
|
||||
|
||||
@ -8,6 +8,8 @@ package gdb
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/gogf/gf/errors/gcode"
|
||||
"github.com/gogf/gf/errors/gerror"
|
||||
"reflect"
|
||||
|
||||
"github.com/gogf/gf/container/gset"
|
||||
@ -18,11 +20,6 @@ import (
|
||||
"github.com/gogf/gf/util/gconv"
|
||||
)
|
||||
|
||||
const (
|
||||
queryTypeNormal = "NormalQuery"
|
||||
queryTypeCount = "CountQuery"
|
||||
)
|
||||
|
||||
// Select is alias of Model.All.
|
||||
// See Model.All.
|
||||
// Deprecated, use All instead.
|
||||
@ -80,7 +77,7 @@ func (m *Model) getFieldsFiltered() string {
|
||||
panic("function FieldsEx supports only single table operations")
|
||||
}
|
||||
// Filter table fields with fieldEx.
|
||||
tableFields, err := m.TableFields(m.tables)
|
||||
tableFields, err := m.TableFields(m.tablesInit)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
@ -105,27 +102,27 @@ func (m *Model) getFieldsFiltered() string {
|
||||
return newFields
|
||||
}
|
||||
|
||||
// Chunk iterates the query result with given size and callback function.
|
||||
func (m *Model) Chunk(limit int, callback func(result Result, err error) bool) {
|
||||
// Chunk iterates the query result with given `size` and `handler` function.
|
||||
func (m *Model) Chunk(size int, handler ChunkHandler) {
|
||||
page := m.start
|
||||
if page <= 0 {
|
||||
page = 1
|
||||
}
|
||||
model := m
|
||||
for {
|
||||
model = model.Page(page, limit)
|
||||
model = model.Page(page, size)
|
||||
data, err := model.All()
|
||||
if err != nil {
|
||||
callback(nil, err)
|
||||
handler(nil, err)
|
||||
break
|
||||
}
|
||||
if len(data) == 0 {
|
||||
break
|
||||
}
|
||||
if callback(data, err) == false {
|
||||
if handler(data, err) == false {
|
||||
break
|
||||
}
|
||||
if len(data) < limit {
|
||||
if len(data) < size {
|
||||
break
|
||||
}
|
||||
page++
|
||||
@ -200,6 +197,15 @@ func (m *Model) Array(fieldsAndWhere ...interface{}) ([]Value, error) {
|
||||
return all.Array(), nil
|
||||
}
|
||||
|
||||
// Struct retrieves one record from table and converts it into given struct.
|
||||
// The parameter `pointer` should be type of *struct/**struct. If type **struct is given,
|
||||
// it can create the struct internally during converting.
|
||||
//
|
||||
// Deprecated, use Scan instead.
|
||||
func (m *Model) Struct(pointer interface{}, where ...interface{}) error {
|
||||
return m.doStruct(pointer, where...)
|
||||
}
|
||||
|
||||
// Struct retrieves one record from table and converts it into given struct.
|
||||
// The parameter `pointer` should be type of *struct/**struct. If type **struct is given,
|
||||
// it can create the struct internally during converting.
|
||||
@ -207,24 +213,38 @@ func (m *Model) Array(fieldsAndWhere ...interface{}) ([]Value, error) {
|
||||
// The optional parameter `where` is the same as the parameter of Model.Where function,
|
||||
// see Model.Where.
|
||||
//
|
||||
// Note that it returns sql.ErrNoRows if there's no record retrieved with the given conditions
|
||||
// from table and `pointer` is not nil.
|
||||
// Note that it returns sql.ErrNoRows if the given parameter `pointer` pointed to a variable that has
|
||||
// default value and there's no record retrieved with the given conditions from table.
|
||||
//
|
||||
// Eg:
|
||||
// Example:
|
||||
// user := new(User)
|
||||
// err := db.Model("user").Where("id", 1).Struct(user)
|
||||
// err := db.Model("user").Where("id", 1).Scan(user)
|
||||
//
|
||||
// user := (*User)(nil)
|
||||
// err := db.Model("user").Where("id", 1).Struct(&user)
|
||||
func (m *Model) Struct(pointer interface{}, where ...interface{}) error {
|
||||
one, err := m.One(where...)
|
||||
// err := db.Model("user").Where("id", 1).Scan(&user)
|
||||
func (m *Model) doStruct(pointer interface{}, where ...interface{}) error {
|
||||
model := m
|
||||
// Auto selecting fields by struct attributes.
|
||||
if model.fieldsEx == "" && (model.fields == "" || model.fields == "*") {
|
||||
model = m.Fields(pointer)
|
||||
}
|
||||
one, err := model.One(where...)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err = one.Struct(pointer); err != nil {
|
||||
return err
|
||||
}
|
||||
return m.doWithScanStruct(pointer)
|
||||
return model.doWithScanStruct(pointer)
|
||||
}
|
||||
|
||||
// Structs retrieves records from table and converts them into given struct slice.
|
||||
// The parameter `pointer` should be type of *[]struct/*[]*struct. It can create and fill the struct
|
||||
// slice internally during converting.
|
||||
//
|
||||
// Deprecated, use Scan instead.
|
||||
func (m *Model) Structs(pointer interface{}, where ...interface{}) error {
|
||||
return m.doStructs(pointer, where...)
|
||||
}
|
||||
|
||||
// Structs retrieves records from table and converts them into given struct slice.
|
||||
@ -234,37 +254,45 @@ func (m *Model) Struct(pointer interface{}, where ...interface{}) error {
|
||||
// The optional parameter `where` is the same as the parameter of Model.Where function,
|
||||
// see Model.Where.
|
||||
//
|
||||
// Note that it returns sql.ErrNoRows if there's no record retrieved with the given conditions
|
||||
// from table and `pointer` is not empty.
|
||||
// Note that it returns sql.ErrNoRows if the given parameter `pointer` pointed to a variable that has
|
||||
// default value and there's no record retrieved with the given conditions from table.
|
||||
//
|
||||
// Eg:
|
||||
// Example:
|
||||
// users := ([]User)(nil)
|
||||
// err := db.Model("user").Structs(&users)
|
||||
// err := db.Model("user").Scan(&users)
|
||||
//
|
||||
// users := ([]*User)(nil)
|
||||
// err := db.Model("user").Structs(&users)
|
||||
func (m *Model) Structs(pointer interface{}, where ...interface{}) error {
|
||||
all, err := m.All(where...)
|
||||
// err := db.Model("user").Scan(&users)
|
||||
func (m *Model) doStructs(pointer interface{}, where ...interface{}) error {
|
||||
model := m
|
||||
// Auto selecting fields by struct attributes.
|
||||
if model.fieldsEx == "" && (model.fields == "" || model.fields == "*") {
|
||||
model = m.Fields(
|
||||
reflect.New(
|
||||
reflect.ValueOf(pointer).Elem().Type().Elem(),
|
||||
).Interface(),
|
||||
)
|
||||
}
|
||||
all, err := model.All(where...)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err = all.Structs(pointer); err != nil {
|
||||
return err
|
||||
}
|
||||
return m.doWithScanStructs(pointer)
|
||||
return model.doWithScanStructs(pointer)
|
||||
}
|
||||
|
||||
// Scan automatically calls Struct or Structs function according to the type of parameter `pointer`.
|
||||
// It calls function Struct if `pointer` is type of *struct/**struct.
|
||||
// It calls function Structs if `pointer` is type of *[]struct/*[]*struct.
|
||||
// It calls function doStruct if `pointer` is type of *struct/**struct.
|
||||
// It calls function doStructs if `pointer` is type of *[]struct/*[]*struct.
|
||||
//
|
||||
// The optional parameter `where` is the same as the parameter of Model.Where function,
|
||||
// see Model.Where.
|
||||
// The optional parameter `where` is the same as the parameter of Model.Where function, see Model.Where.
|
||||
//
|
||||
// Note that it returns sql.ErrNoRows if there's no record retrieved with the given conditions
|
||||
// from table.
|
||||
// Note that it returns sql.ErrNoRows if the given parameter `pointer` pointed to a variable that has
|
||||
// default value and there's no record retrieved with the given conditions from table.
|
||||
//
|
||||
// Eg:
|
||||
// Example:
|
||||
// user := new(User)
|
||||
// err := db.Model("user").Where("id", 1).Scan(user)
|
||||
//
|
||||
@ -277,16 +305,38 @@ func (m *Model) Structs(pointer interface{}, where ...interface{}) error {
|
||||
// users := ([]*User)(nil)
|
||||
// err := db.Model("user").Scan(&users)
|
||||
func (m *Model) Scan(pointer interface{}, where ...interface{}) error {
|
||||
var reflectType reflect.Type
|
||||
var (
|
||||
reflectValue reflect.Value
|
||||
reflectKind reflect.Kind
|
||||
)
|
||||
if v, ok := pointer.(reflect.Value); ok {
|
||||
reflectType = v.Type()
|
||||
reflectValue = v
|
||||
} else {
|
||||
reflectType = reflect.TypeOf(pointer)
|
||||
reflectValue = reflect.ValueOf(pointer)
|
||||
}
|
||||
if gstr.Contains(reflectType.String(), "[]") {
|
||||
return m.Structs(pointer, where...)
|
||||
|
||||
reflectKind = reflectValue.Kind()
|
||||
if reflectKind != reflect.Ptr {
|
||||
return gerror.NewCode(gcode.CodeInvalidParameter, `the parameter "pointer" for function Scan should type of pointer`)
|
||||
}
|
||||
for reflectKind == reflect.Ptr {
|
||||
reflectValue = reflectValue.Elem()
|
||||
reflectKind = reflectValue.Kind()
|
||||
}
|
||||
|
||||
switch reflectKind {
|
||||
case reflect.Slice, reflect.Array:
|
||||
return m.doStructs(pointer, where...)
|
||||
|
||||
case reflect.Struct, reflect.Invalid:
|
||||
return m.doStruct(pointer, where...)
|
||||
|
||||
default:
|
||||
return gerror.NewCode(
|
||||
gcode.CodeInvalidParameter,
|
||||
`element of parameter "pointer" for function Scan should type of struct/*struct/[]struct/[]*struct`,
|
||||
)
|
||||
}
|
||||
return m.Struct(pointer, where...)
|
||||
}
|
||||
|
||||
// ScanList converts `r` to struct slice which contains other complex struct attributes.
|
||||
@ -312,11 +362,11 @@ func (m *Model) Scan(pointer interface{}, where ...interface{}) error {
|
||||
// parameter.
|
||||
// See the example or unit testing cases for clear understanding for this function.
|
||||
func (m *Model) ScanList(listPointer interface{}, attributeName string, relation ...string) (err error) {
|
||||
all, err := m.All()
|
||||
result, err := m.All()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return all.ScanList(listPointer, attributeName, relation...)
|
||||
return doScanList(m, result, listPointer, attributeName, relation...)
|
||||
}
|
||||
|
||||
// Count does "SELECT COUNT(x) FROM ..." statement for the model.
|
||||
@ -458,6 +508,16 @@ func (m *Model) FindScan(pointer interface{}, where ...interface{}) error {
|
||||
return m.Scan(pointer)
|
||||
}
|
||||
|
||||
// Union does "(SELECT xxx FROM xxx) UNION (SELECT xxx FROM xxx) ..." statement for the model.
|
||||
func (m *Model) Union(unions ...*Model) *Model {
|
||||
return m.db.Union(unions...)
|
||||
}
|
||||
|
||||
// UnionAll does "(SELECT xxx FROM xxx) UNION ALL (SELECT xxx FROM xxx) ..." statement for the model.
|
||||
func (m *Model) UnionAll(unions ...*Model) *Model {
|
||||
return m.db.UnionAll(unions...)
|
||||
}
|
||||
|
||||
// doGetAllBySql does the select statement on the database.
|
||||
func (m *Model) doGetAllBySql(sql string, args ...interface{}) (result Result, err error) {
|
||||
cacheKey := ""
|
||||
@ -483,25 +543,29 @@ func (m *Model) doGetAllBySql(sql string, args ...interface{}) (result Result, e
|
||||
}
|
||||
}
|
||||
}
|
||||
result, err = m.db.GetCore().DoGetAll(
|
||||
result, err = m.db.DoGetAll(
|
||||
m.GetCtx(), m.getLink(false), sql, m.mergeArguments(args)...,
|
||||
)
|
||||
// Cache the result.
|
||||
if cacheKey != "" && err == nil {
|
||||
if m.cacheDuration < 0 {
|
||||
if _, err := cacheObj.Remove(cacheKey); err != nil {
|
||||
intlog.Error(err)
|
||||
intlog.Error(m.GetCtx(), err)
|
||||
}
|
||||
} else {
|
||||
// In case of Cache Penetration.
|
||||
if result == nil {
|
||||
result = Result{}
|
||||
}
|
||||
if err := cacheObj.Set(cacheKey, result, m.cacheDuration); err != nil {
|
||||
intlog.Error(err)
|
||||
intlog.Error(m.GetCtx(), err)
|
||||
}
|
||||
}
|
||||
}
|
||||
return result, err
|
||||
}
|
||||
|
||||
func (m *Model) getFormattedSqlAndArgs(queryType string, limit1 bool) (sqlWithHolder string, holderArgs []interface{}) {
|
||||
func (m *Model) getFormattedSqlAndArgs(queryType int, limit1 bool) (sqlWithHolder string, holderArgs []interface{}) {
|
||||
switch queryType {
|
||||
case queryTypeCount:
|
||||
countFields := "COUNT(1)"
|
||||
@ -510,6 +574,11 @@ func (m *Model) getFormattedSqlAndArgs(queryType string, limit1 bool) (sqlWithHo
|
||||
// DISTINCT t.user_id uid
|
||||
countFields = fmt.Sprintf(`COUNT(%s%s)`, m.distinct, m.fields)
|
||||
}
|
||||
// Raw SQL Model.
|
||||
if m.rawSql != "" {
|
||||
sqlWithHolder = fmt.Sprintf("SELECT %s FROM (%s) AS T", countFields, m.rawSql)
|
||||
return sqlWithHolder, nil
|
||||
}
|
||||
conditionWhere, conditionExtra, conditionArgs := m.formatCondition(false, true)
|
||||
sqlWithHolder = fmt.Sprintf("SELECT %s FROM %s%s", countFields, m.tables, conditionWhere+conditionExtra)
|
||||
if len(m.groupBy) > 0 {
|
||||
@ -519,6 +588,15 @@ func (m *Model) getFormattedSqlAndArgs(queryType string, limit1 bool) (sqlWithHo
|
||||
|
||||
default:
|
||||
conditionWhere, conditionExtra, conditionArgs := m.formatCondition(limit1, false)
|
||||
// Raw SQL Model, especially for UNION/UNION ALL featured SQL.
|
||||
if m.rawSql != "" {
|
||||
sqlWithHolder = fmt.Sprintf(
|
||||
"%s%s",
|
||||
m.rawSql,
|
||||
conditionWhere+conditionExtra,
|
||||
)
|
||||
return sqlWithHolder, conditionArgs
|
||||
}
|
||||
// DO NOT quote the m.fields where, in case of fields like:
|
||||
// DISTINCT t.user_id uid
|
||||
sqlWithHolder = fmt.Sprintf(
|
||||
|
||||
@ -40,7 +40,7 @@ func (m *Model) getSoftFieldNameCreated(table ...string) string {
|
||||
if len(table) > 0 {
|
||||
tableName = table[0]
|
||||
} else {
|
||||
tableName = m.getPrimaryTableName()
|
||||
tableName = m.tablesInit
|
||||
}
|
||||
config := m.db.GetConfig()
|
||||
if config.CreatedAt != "" {
|
||||
@ -61,7 +61,7 @@ func (m *Model) getSoftFieldNameUpdated(table ...string) (field string) {
|
||||
if len(table) > 0 {
|
||||
tableName = table[0]
|
||||
} else {
|
||||
tableName = m.getPrimaryTableName()
|
||||
tableName = m.tablesInit
|
||||
}
|
||||
config := m.db.GetConfig()
|
||||
if config.UpdatedAt != "" {
|
||||
@ -82,7 +82,7 @@ func (m *Model) getSoftFieldNameDeleted(table ...string) (field string) {
|
||||
if len(table) > 0 {
|
||||
tableName = table[0]
|
||||
} else {
|
||||
tableName = m.getPrimaryTableName()
|
||||
tableName = m.tablesInit
|
||||
}
|
||||
config := m.db.GetConfig()
|
||||
if config.UpdatedAt != "" {
|
||||
@ -170,14 +170,3 @@ func (m *Model) getConditionOfTableStringForSoftDeleting(s string) string {
|
||||
}
|
||||
return fmt.Sprintf(`%s.%s IS NULL`, m.db.GetCore().QuoteWord(table), m.db.GetCore().QuoteWord(field))
|
||||
}
|
||||
|
||||
// getPrimaryTableName parses and returns the primary table name.
|
||||
func (m *Model) getPrimaryTableName() string {
|
||||
array1 := gstr.SplitAndTrim(m.tables, ",")
|
||||
array2 := gstr.SplitAndTrim(array1[0], " ")
|
||||
array3 := gstr.SplitAndTrim(array2[0], ".")
|
||||
if len(array3) >= 2 {
|
||||
return array3[1]
|
||||
}
|
||||
return array3[0]
|
||||
}
|
||||
|
||||
@ -9,13 +9,13 @@ package gdb
|
||||
import (
|
||||
"database/sql"
|
||||
"fmt"
|
||||
"github.com/gogf/gf/errors/gcode"
|
||||
"reflect"
|
||||
|
||||
"github.com/gogf/gf/errors/gerror"
|
||||
"github.com/gogf/gf/os/gtime"
|
||||
"github.com/gogf/gf/text/gstr"
|
||||
"github.com/gogf/gf/util/gconv"
|
||||
"github.com/gogf/gf/util/gutil"
|
||||
)
|
||||
|
||||
// Update does "UPDATE ... " statement for the model.
|
||||
@ -39,13 +39,11 @@ func (m *Model) Update(dataAndWhere ...interface{}) (result sql.Result, err erro
|
||||
}
|
||||
}()
|
||||
if m.data == nil {
|
||||
return nil, gerror.New("updating table with empty data")
|
||||
return nil, gerror.NewCode(gcode.CodeMissingParameter, "updating table with empty data")
|
||||
}
|
||||
var (
|
||||
updateData = m.data
|
||||
fieldNameCreate = m.getSoftFieldNameCreated()
|
||||
fieldNameUpdate = m.getSoftFieldNameUpdated()
|
||||
fieldNameDelete = m.getSoftFieldNameDeleted()
|
||||
conditionWhere, conditionExtra, conditionArgs = m.formatCondition(false, false)
|
||||
)
|
||||
// Automatically update the record updating time.
|
||||
@ -61,7 +59,6 @@ func (m *Model) Update(dataAndWhere ...interface{}) (result sql.Result, err erro
|
||||
switch refKind {
|
||||
case reflect.Map, reflect.Struct:
|
||||
dataMap := ConvertDataForTableRecord(m.data)
|
||||
gutil.MapDelete(dataMap, fieldNameCreate, fieldNameUpdate, fieldNameDelete)
|
||||
if fieldNameUpdate != "" {
|
||||
dataMap[fieldNameUpdate] = gtime.Now().String()
|
||||
}
|
||||
@ -80,9 +77,9 @@ func (m *Model) Update(dataAndWhere ...interface{}) (result sql.Result, err erro
|
||||
}
|
||||
conditionStr := conditionWhere + conditionExtra
|
||||
if !gstr.ContainsI(conditionStr, " WHERE ") {
|
||||
return nil, gerror.New("there should be WHERE condition statement for UPDATE operation")
|
||||
return nil, gerror.NewCode(gcode.CodeMissingParameter, "there should be WHERE condition statement for UPDATE operation")
|
||||
}
|
||||
return m.db.GetCore().DoUpdate(
|
||||
return m.db.DoUpdate(
|
||||
m.GetCtx(),
|
||||
m.getLink(true),
|
||||
m.tables,
|
||||
@ -93,17 +90,19 @@ func (m *Model) Update(dataAndWhere ...interface{}) (result sql.Result, err erro
|
||||
}
|
||||
|
||||
// Increment increments a column's value by a given amount.
|
||||
func (m *Model) Increment(column string, amount float64) (sql.Result, error) {
|
||||
// The parameter `amount` can be type of float or integer.
|
||||
func (m *Model) Increment(column string, amount interface{}) (sql.Result, error) {
|
||||
return m.getModel().Data(column, &Counter{
|
||||
Field: column,
|
||||
Value: amount,
|
||||
Value: gconv.Float64(amount),
|
||||
}).Update()
|
||||
}
|
||||
|
||||
// Decrement decrements a column's value by a given amount.
|
||||
func (m *Model) Decrement(column string, amount float64) (sql.Result, error) {
|
||||
// The parameter `amount` can be type of float or integer.
|
||||
func (m *Model) Decrement(column string, amount interface{}) (sql.Result, error) {
|
||||
return m.getModel().Data(column, &Counter{
|
||||
Field: column,
|
||||
Value: -amount,
|
||||
Value: -gconv.Float64(amount),
|
||||
}).Update()
|
||||
}
|
||||
|
||||
@ -21,15 +21,12 @@ import (
|
||||
// schema.
|
||||
//
|
||||
// Also see DriverMysql.TableFields.
|
||||
func (m *Model) TableFields(table string, schema ...string) (fields map[string]*TableField, err error) {
|
||||
charL, charR := m.db.GetChars()
|
||||
if charL != "" || charR != "" {
|
||||
table = gstr.Trim(table, charL+charR)
|
||||
func (m *Model) TableFields(tableStr string, schema ...string) (fields map[string]*TableField, err error) {
|
||||
useSchema := m.schema
|
||||
if len(schema) > 0 && schema[0] != "" {
|
||||
useSchema = schema[0]
|
||||
}
|
||||
if !gregex.IsMatchString(regularFieldNameRegPattern, table) {
|
||||
return nil, nil
|
||||
}
|
||||
return m.db.TableFields(m.GetCtx(), table, schema...)
|
||||
return m.db.TableFields(m.GetCtx(), m.guessPrimaryTableName(tableStr), useSchema)
|
||||
}
|
||||
|
||||
// getModel creates and returns a cloned model of current model if `safe` is true, or else it returns
|
||||
@ -47,7 +44,7 @@ func (m *Model) getModel() *Model {
|
||||
// ID -> id
|
||||
// NICK_Name -> nickname
|
||||
func (m *Model) mappingAndFilterToTableFields(fields []string, filter bool) []string {
|
||||
fieldsMap, err := m.TableFields(m.tables)
|
||||
fieldsMap, err := m.TableFields(m.tablesInit)
|
||||
if err != nil || len(fieldsMap) == 0 {
|
||||
return fields
|
||||
}
|
||||
@ -106,12 +103,26 @@ func (m *Model) filterDataForInsertOrUpdate(data interface{}) (interface{}, erro
|
||||
// Note that, it does not filter list item, which is also type of map, for "omit empty" feature.
|
||||
func (m *Model) doMappingAndFilterForInsertOrUpdateDataMap(data Map, allowOmitEmpty bool) (Map, error) {
|
||||
var err error
|
||||
data, err = m.db.GetCore().mappingAndFilterData(m.schema, m.tables, data, m.filter)
|
||||
data, err = m.db.GetCore().mappingAndFilterData(
|
||||
m.schema, m.guessPrimaryTableName(m.tablesInit), data, m.filter,
|
||||
)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// Remove key-value pairs of which the value is nil.
|
||||
if allowOmitEmpty && m.option&optionOmitNilData > 0 {
|
||||
tempMap := make(Map, len(data))
|
||||
for k, v := range data {
|
||||
if empty.IsNil(v) {
|
||||
continue
|
||||
}
|
||||
tempMap[k] = v
|
||||
}
|
||||
data = tempMap
|
||||
}
|
||||
|
||||
// Remove key-value pairs of which the value is empty.
|
||||
if allowOmitEmpty && m.option&OptionOmitEmpty > 0 {
|
||||
if allowOmitEmpty && m.option&optionOmitEmptyData > 0 {
|
||||
tempMap := make(Map, len(data))
|
||||
for k, v := range data {
|
||||
if empty.IsEmpty(v) {
|
||||
@ -201,7 +212,7 @@ func (m *Model) getLink(master bool) Link {
|
||||
// It parses m.tables to retrieve the primary table name, supporting m.tables like:
|
||||
// "user", "user u", "user as u, user_detail as ud".
|
||||
func (m *Model) getPrimaryKey() string {
|
||||
table := gstr.SplitAndTrim(m.tables, " ")[0]
|
||||
table := gstr.SplitAndTrim(m.tablesInit, " ")[0]
|
||||
tableFields, err := m.TableFields(table)
|
||||
if err != nil {
|
||||
return ""
|
||||
|
||||
@ -8,6 +8,7 @@ package gdb
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/gogf/gf/errors/gcode"
|
||||
"reflect"
|
||||
|
||||
"github.com/gogf/gf/errors/gerror"
|
||||
@ -17,7 +18,7 @@ import (
|
||||
"github.com/gogf/gf/text/gstr"
|
||||
)
|
||||
|
||||
// With creates and returns an ORM model based on meta data of given object.
|
||||
// With creates and returns an ORM model based on metadata of given object.
|
||||
// It also enables model association operations feature on given `object`.
|
||||
// It can be called multiple times to add one or more objects to model and enable
|
||||
// their mode association operations feature.
|
||||
@ -39,7 +40,10 @@ func (m *Model) With(objects ...interface{}) *Model {
|
||||
model := m.getModel()
|
||||
for _, object := range objects {
|
||||
if m.tables == "" {
|
||||
m.tables = m.db.GetCore().QuotePrefixTableName(getTableNameFromOrmTag(object))
|
||||
m.tablesInit = m.db.GetCore().QuotePrefixTableName(
|
||||
getTableNameFromOrmTag(object),
|
||||
)
|
||||
m.tables = m.tablesInit
|
||||
return model
|
||||
}
|
||||
model.withArray = append(model.withArray, object)
|
||||
@ -60,13 +64,17 @@ func (m *Model) doWithScanStruct(pointer interface{}) error {
|
||||
err error
|
||||
allowedTypeStrArray = make([]string, 0)
|
||||
)
|
||||
fieldMap, err := structs.FieldMap(pointer, nil, false)
|
||||
currentStructFieldMap, err := structs.FieldMap(structs.FieldMapInput{
|
||||
Pointer: pointer,
|
||||
PriorityTagArray: nil,
|
||||
RecursiveOption: structs.RecursiveOptionEmbeddedNoTag,
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
// It checks the with array and automatically calls the ScanList to complete association querying.
|
||||
if !m.withAll {
|
||||
for _, field := range fieldMap {
|
||||
for _, field := range currentStructFieldMap {
|
||||
for _, withItem := range m.withArray {
|
||||
withItemReflectValueType, err := structs.StructType(withItem)
|
||||
if err != nil {
|
||||
@ -76,64 +84,55 @@ func (m *Model) doWithScanStruct(pointer interface{}) error {
|
||||
fieldTypeStr = gstr.TrimAll(field.Type().String(), "*[]")
|
||||
withItemReflectValueTypeStr = gstr.TrimAll(withItemReflectValueType.String(), "*[]")
|
||||
)
|
||||
// It does select operation if the field type is in the specified with type array.
|
||||
// It does select operation if the field type is in the specified "with" type array.
|
||||
if gstr.Compare(fieldTypeStr, withItemReflectValueTypeStr) == 0 {
|
||||
allowedTypeStrArray = append(allowedTypeStrArray, fieldTypeStr)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
for _, field := range fieldMap {
|
||||
for _, field := range currentStructFieldMap {
|
||||
var (
|
||||
withTag string
|
||||
ormTag = field.Tag(OrmTagForStruct)
|
||||
fieldTypeStr = gstr.TrimAll(field.Type().String(), "*[]")
|
||||
match, _ = gregex.MatchString(
|
||||
fmt.Sprintf(`%s\s*:\s*([^,]+)`, OrmTagForWith),
|
||||
ormTag,
|
||||
)
|
||||
fieldTypeStr = gstr.TrimAll(field.Type().String(), "*[]")
|
||||
parsedTagOutput = m.parseWithTagInFieldStruct(field)
|
||||
)
|
||||
if len(match) > 1 {
|
||||
withTag = match[1]
|
||||
}
|
||||
if withTag == "" {
|
||||
if parsedTagOutput.With == "" {
|
||||
continue
|
||||
}
|
||||
// It just handlers "with" type attribute struct, so it ignores other struct types.
|
||||
if !m.withAll && !gstr.InArray(allowedTypeStrArray, fieldTypeStr) {
|
||||
continue
|
||||
}
|
||||
array := gstr.SplitAndTrim(withTag, "=")
|
||||
array := gstr.SplitAndTrim(parsedTagOutput.With, "=")
|
||||
if len(array) == 1 {
|
||||
// It supports using only one column name
|
||||
// It also supports using only one column name
|
||||
// if both tables associates using the same column name.
|
||||
array = append(array, withTag)
|
||||
array = append(array, parsedTagOutput.With)
|
||||
}
|
||||
var (
|
||||
model *Model
|
||||
fieldKeys []string
|
||||
relatedFieldName = array[0]
|
||||
relatedAttrName = array[1]
|
||||
relatedFieldValue interface{}
|
||||
model *Model
|
||||
fieldKeys []string
|
||||
relatedSourceName = array[0]
|
||||
relatedTargetName = array[1]
|
||||
relatedTargetValue interface{}
|
||||
)
|
||||
// Find the value of related attribute from `pointer`.
|
||||
for attributeName, attributeValue := range fieldMap {
|
||||
if utils.EqualFoldWithoutChars(attributeName, relatedAttrName) {
|
||||
relatedFieldValue = attributeValue.Value.Interface()
|
||||
for attributeName, attributeValue := range currentStructFieldMap {
|
||||
if utils.EqualFoldWithoutChars(attributeName, relatedTargetName) {
|
||||
relatedTargetValue = attributeValue.Value.Interface()
|
||||
break
|
||||
}
|
||||
}
|
||||
if relatedFieldValue == nil {
|
||||
return gerror.Newf(
|
||||
`cannot find the related value for attribute name "%s" of with tag "%s"`,
|
||||
relatedAttrName, withTag,
|
||||
if relatedTargetValue == nil {
|
||||
return gerror.NewCodef(
|
||||
gcode.CodeInvalidParameter,
|
||||
`cannot find the target related value of name "%s" in with tag "%s" for attribute "%s.%s"`,
|
||||
relatedTargetName, parsedTagOutput.With, reflect.TypeOf(pointer).Elem(), field.Name(),
|
||||
)
|
||||
}
|
||||
bindToReflectValue := field.Value
|
||||
switch bindToReflectValue.Kind() {
|
||||
case reflect.Array, reflect.Slice:
|
||||
if bindToReflectValue.CanAddr() {
|
||||
bindToReflectValue = bindToReflectValue.Addr()
|
||||
}
|
||||
if bindToReflectValue.Kind() != reflect.Ptr && bindToReflectValue.CanAddr() {
|
||||
bindToReflectValue = bindToReflectValue.Addr()
|
||||
}
|
||||
|
||||
// It automatically retrieves struct field names from current attribute struct/slice.
|
||||
@ -150,8 +149,14 @@ func (m *Model) doWithScanStruct(pointer interface{}) error {
|
||||
} else {
|
||||
model = model.With(m.withArray...)
|
||||
}
|
||||
if parsedTagOutput.Where != "" {
|
||||
model = model.Where(parsedTagOutput.Where)
|
||||
}
|
||||
if parsedTagOutput.Order != "" {
|
||||
model = model.Order(parsedTagOutput.Order)
|
||||
}
|
||||
|
||||
err = model.Fields(fieldKeys).Where(relatedFieldName, relatedFieldValue).Scan(bindToReflectValue)
|
||||
err = model.Fields(fieldKeys).Where(relatedSourceName, relatedTargetValue).Scan(bindToReflectValue)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@ -163,17 +168,25 @@ func (m *Model) doWithScanStruct(pointer interface{}) error {
|
||||
// doWithScanStructs handles model association operations feature for struct slice.
|
||||
// Also see doWithScanStruct.
|
||||
func (m *Model) doWithScanStructs(pointer interface{}) error {
|
||||
if v, ok := pointer.(reflect.Value); ok {
|
||||
pointer = v.Interface()
|
||||
}
|
||||
|
||||
var (
|
||||
err error
|
||||
allowedTypeStrArray = make([]string, 0)
|
||||
)
|
||||
fieldMap, err := structs.FieldMap(pointer, nil, false)
|
||||
currentStructFieldMap, err := structs.FieldMap(structs.FieldMapInput{
|
||||
Pointer: pointer,
|
||||
PriorityTagArray: nil,
|
||||
RecursiveOption: structs.RecursiveOptionEmbeddedNoTag,
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
// It checks the with array and automatically calls the ScanList to complete association querying.
|
||||
if !m.withAll {
|
||||
for _, field := range fieldMap {
|
||||
for _, field := range currentStructFieldMap {
|
||||
for _, withItem := range m.withArray {
|
||||
withItemReflectValueType, err := structs.StructType(withItem)
|
||||
if err != nil {
|
||||
@ -191,49 +204,42 @@ func (m *Model) doWithScanStructs(pointer interface{}) error {
|
||||
}
|
||||
}
|
||||
|
||||
for fieldName, field := range fieldMap {
|
||||
for fieldName, field := range currentStructFieldMap {
|
||||
var (
|
||||
withTag string
|
||||
ormTag = field.Tag(OrmTagForStruct)
|
||||
fieldTypeStr = gstr.TrimAll(field.Type().String(), "*[]")
|
||||
match, _ = gregex.MatchString(
|
||||
fmt.Sprintf(`%s\s*:\s*([^,]+)`, OrmTagForWith),
|
||||
ormTag,
|
||||
)
|
||||
fieldTypeStr = gstr.TrimAll(field.Type().String(), "*[]")
|
||||
parsedTagOutput = m.parseWithTagInFieldStruct(field)
|
||||
)
|
||||
if len(match) > 1 {
|
||||
withTag = match[1]
|
||||
}
|
||||
if withTag == "" {
|
||||
if parsedTagOutput.With == "" {
|
||||
continue
|
||||
}
|
||||
if !m.withAll && !gstr.InArray(allowedTypeStrArray, fieldTypeStr) {
|
||||
continue
|
||||
}
|
||||
array := gstr.SplitAndTrim(withTag, "=")
|
||||
array := gstr.SplitAndTrim(parsedTagOutput.With, "=")
|
||||
if len(array) == 1 {
|
||||
// It supports using only one column name
|
||||
// if both tables associates using the same column name.
|
||||
array = append(array, withTag)
|
||||
array = append(array, parsedTagOutput.With)
|
||||
}
|
||||
var (
|
||||
model *Model
|
||||
fieldKeys []string
|
||||
relatedFieldName = array[0]
|
||||
relatedAttrName = array[1]
|
||||
relatedFieldValue interface{}
|
||||
model *Model
|
||||
fieldKeys []string
|
||||
relatedSourceName = array[0]
|
||||
relatedTargetName = array[1]
|
||||
relatedTargetValue interface{}
|
||||
)
|
||||
// Find the value slice of related attribute from `pointer`.
|
||||
for attributeName, _ := range fieldMap {
|
||||
if utils.EqualFoldWithoutChars(attributeName, relatedAttrName) {
|
||||
relatedFieldValue = ListItemValuesUnique(pointer, attributeName)
|
||||
for attributeName, _ := range currentStructFieldMap {
|
||||
if utils.EqualFoldWithoutChars(attributeName, relatedTargetName) {
|
||||
relatedTargetValue = ListItemValuesUnique(pointer, attributeName)
|
||||
break
|
||||
}
|
||||
}
|
||||
if relatedFieldValue == nil {
|
||||
return gerror.Newf(
|
||||
if relatedTargetValue == nil {
|
||||
return gerror.NewCodef(
|
||||
gcode.CodeInvalidParameter,
|
||||
`cannot find the related value for attribute name "%s" of with tag "%s"`,
|
||||
relatedAttrName, withTag,
|
||||
relatedTargetName, parsedTagOutput.With,
|
||||
)
|
||||
}
|
||||
|
||||
@ -251,11 +257,60 @@ func (m *Model) doWithScanStructs(pointer interface{}) error {
|
||||
} else {
|
||||
model = model.With(m.withArray...)
|
||||
}
|
||||
if parsedTagOutput.Where != "" {
|
||||
model = model.Where(parsedTagOutput.Where)
|
||||
}
|
||||
if parsedTagOutput.Order != "" {
|
||||
model = model.Order(parsedTagOutput.Order)
|
||||
}
|
||||
|
||||
err = model.Fields(fieldKeys).Where(relatedFieldName, relatedFieldValue).ScanList(pointer, fieldName, withTag)
|
||||
err = model.Fields(fieldKeys).
|
||||
Where(relatedSourceName, relatedTargetValue).
|
||||
ScanList(pointer, fieldName, parsedTagOutput.With)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
type parseWithTagInFieldStructOutput struct {
|
||||
With string
|
||||
Where string
|
||||
Order string
|
||||
}
|
||||
|
||||
func (m *Model) parseWithTagInFieldStruct(field *structs.Field) (output parseWithTagInFieldStructOutput) {
|
||||
var (
|
||||
match []string
|
||||
ormTag = field.Tag(OrmTagForStruct)
|
||||
)
|
||||
// with tag.
|
||||
match, _ = gregex.MatchString(
|
||||
fmt.Sprintf(`%s\s*:\s*([^,]+),{0,1}`, OrmTagForWith),
|
||||
ormTag,
|
||||
)
|
||||
if len(match) > 1 {
|
||||
output.With = match[1]
|
||||
}
|
||||
if len(match) > 2 {
|
||||
output.Where = gstr.Trim(match[2])
|
||||
}
|
||||
// where string.
|
||||
match, _ = gregex.MatchString(
|
||||
fmt.Sprintf(`%s\s*:.+,\s*%s:\s*([^,]+),{0,1}`, OrmTagForWith, OrmTagForWithWhere),
|
||||
ormTag,
|
||||
)
|
||||
if len(match) > 1 {
|
||||
output.Where = gstr.Trim(match[1])
|
||||
}
|
||||
// order string.
|
||||
match, _ = gregex.MatchString(
|
||||
fmt.Sprintf(`%s\s*:.+,\s*%s:\s*([^,]+),{0,1}`, OrmTagForWith, OrmTagForWithOrder),
|
||||
ormTag,
|
||||
)
|
||||
if len(match) > 1 {
|
||||
output.Order = gstr.Trim(match[1])
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
@ -33,7 +33,10 @@ func (r *SqlResult) MustGetInsertId() int64 {
|
||||
return id
|
||||
}
|
||||
|
||||
// see sql.Result.RowsAffected
|
||||
// RowsAffected returns the number of rows affected by an
|
||||
// update, insert, or delete. Not every database or database
|
||||
// driver may support this.
|
||||
// Also See sql.Result.
|
||||
func (r *SqlResult) RowsAffected() (int64, error) {
|
||||
if r.affected > 0 {
|
||||
return r.affected, nil
|
||||
@ -44,7 +47,12 @@ func (r *SqlResult) RowsAffected() (int64, error) {
|
||||
return r.result.RowsAffected()
|
||||
}
|
||||
|
||||
// see sql.Result.LastInsertId
|
||||
// LastInsertId returns the integer generated by the database
|
||||
// in response to a command. Typically this will be from an
|
||||
// "auto increment" column when inserting a new row. Not all
|
||||
// databases support this feature, and the syntax of such
|
||||
// statements varies.
|
||||
// Also See sql.Result.
|
||||
func (r *SqlResult) LastInsertId() (int64, error) {
|
||||
if r.result == nil {
|
||||
return 0, nil
|
||||
|
||||
@ -9,6 +9,7 @@ package gdb
|
||||
import (
|
||||
"context"
|
||||
"database/sql"
|
||||
"github.com/gogf/gf/errors/gcode"
|
||||
|
||||
"github.com/gogf/gf/errors/gerror"
|
||||
"github.com/gogf/gf/os/gtime"
|
||||
@ -37,7 +38,7 @@ const (
|
||||
)
|
||||
|
||||
// doStmtCommit commits statement according to given `stmtType`.
|
||||
func (s *Stmt) doStmtCommit(stmtType string, ctx context.Context, args ...interface{}) (result interface{}, err error) {
|
||||
func (s *Stmt) doStmtCommit(ctx context.Context, stmtType string, args ...interface{}) (result interface{}, err error) {
|
||||
var (
|
||||
cancelFuncForTimeout context.CancelFunc
|
||||
timestampMilli1 = gtime.TimestampMilli()
|
||||
@ -59,7 +60,7 @@ func (s *Stmt) doStmtCommit(stmtType string, ctx context.Context, args ...interf
|
||||
result = s.Stmt.QueryRowContext(ctx, args...)
|
||||
|
||||
default:
|
||||
panic(gerror.Newf(`invalid stmtType: %s`, stmtType))
|
||||
panic(gerror.NewCodef(gcode.CodeInvalidParameter, `invalid stmtType: %s`, stmtType))
|
||||
}
|
||||
var (
|
||||
timestampMilli2 = gtime.TimestampMilli()
|
||||
@ -86,7 +87,7 @@ func (s *Stmt) doStmtCommit(stmtType string, ctx context.Context, args ...interf
|
||||
// ExecContext executes a prepared statement with the given arguments and
|
||||
// returns a Result summarizing the effect of the statement.
|
||||
func (s *Stmt) ExecContext(ctx context.Context, args ...interface{}) (sql.Result, error) {
|
||||
result, err := s.doStmtCommit(stmtTypeExecContext, ctx, args...)
|
||||
result, err := s.doStmtCommit(ctx, stmtTypeExecContext, args...)
|
||||
if result != nil {
|
||||
return result.(sql.Result), err
|
||||
}
|
||||
@ -96,7 +97,7 @@ func (s *Stmt) ExecContext(ctx context.Context, args ...interface{}) (sql.Result
|
||||
// QueryContext executes a prepared query statement with the given arguments
|
||||
// and returns the query results as a *Rows.
|
||||
func (s *Stmt) QueryContext(ctx context.Context, args ...interface{}) (*sql.Rows, error) {
|
||||
result, err := s.doStmtCommit(stmtTypeQueryContext, ctx, args...)
|
||||
result, err := s.doStmtCommit(ctx, stmtTypeQueryContext, args...)
|
||||
if result != nil {
|
||||
return result.(*sql.Rows), err
|
||||
}
|
||||
@ -110,7 +111,7 @@ func (s *Stmt) QueryContext(ctx context.Context, args ...interface{}) (*sql.Rows
|
||||
// Otherwise, the *Row's Scan scans the first selected row and discards
|
||||
// the rest.
|
||||
func (s *Stmt) QueryRowContext(ctx context.Context, args ...interface{}) *sql.Row {
|
||||
result, _ := s.doStmtCommit(stmtTypeQueryRowContext, ctx, args...)
|
||||
result, _ := s.doStmtCommit(ctx, stmtTypeQueryRowContext, args...)
|
||||
if result != nil {
|
||||
return result.(*sql.Row)
|
||||
}
|
||||
|
||||
@ -14,6 +14,11 @@ import (
|
||||
"github.com/gogf/gf/util/gconv"
|
||||
)
|
||||
|
||||
// Interface converts and returns `r` as type of interface{}.
|
||||
func (r Record) Interface() interface{} {
|
||||
return r
|
||||
}
|
||||
|
||||
// Json converts `r` to JSON format content.
|
||||
func (r Record) Json() string {
|
||||
content, _ := gparser.VarToJson(r.Map())
|
||||
@ -52,7 +57,7 @@ func (r Record) Struct(pointer interface{}) error {
|
||||
}
|
||||
return nil
|
||||
}
|
||||
return gconv.StructTag(r.Map(), pointer, OrmTagForStruct)
|
||||
return gconv.StructTag(r, pointer, OrmTagForStruct)
|
||||
}
|
||||
|
||||
// IsEmpty checks and returns whether `r` is empty.
|
||||
|
||||
@ -1,27 +0,0 @@
|
||||
// Copyright GoFrame Author(https://goframe.org). All Rights Reserved.
|
||||
//
|
||||
// This Source Code Form is subject to the terms of the MIT License.
|
||||
// If a copy of the MIT was not distributed with this file,
|
||||
// You can obtain one at https://github.com/gogf/gf.
|
||||
|
||||
package gdb
|
||||
|
||||
// Deprecated, use Json instead.
|
||||
func (r Record) ToJson() string {
|
||||
return r.Json()
|
||||
}
|
||||
|
||||
// Deprecated, use Xml instead.
|
||||
func (r Record) ToXml(rootTag ...string) string {
|
||||
return r.Xml(rootTag...)
|
||||
}
|
||||
|
||||
// Deprecated, use Map instead.
|
||||
func (r Record) ToMap() Map {
|
||||
return r.Map()
|
||||
}
|
||||
|
||||
// Deprecated, use Struct instead.
|
||||
func (r Record) ToStruct(pointer interface{}) error {
|
||||
return r.Struct(pointer)
|
||||
}
|
||||
@ -13,6 +13,11 @@ import (
|
||||
"math"
|
||||
)
|
||||
|
||||
// Interface converts and returns `r` as type of interface{}.
|
||||
func (r Result) Interface() interface{} {
|
||||
return r
|
||||
}
|
||||
|
||||
// IsEmpty checks and returns whether `r` is empty.
|
||||
func (r Result) IsEmpty() bool {
|
||||
return r.Len() == 0
|
||||
@ -72,6 +77,7 @@ func (r Result) List() List {
|
||||
|
||||
// Array retrieves and returns specified column values as slice.
|
||||
// The parameter `field` is optional is the column field is only one.
|
||||
// The default `field` is the first field name of the first item in `Result` if parameter `field` is not given.
|
||||
func (r Result) Array(field ...string) []Value {
|
||||
array := make([]Value, len(r))
|
||||
if len(r) == 0 {
|
||||
@ -153,7 +159,7 @@ func (r Result) MapKeyUint(key string) map[uint]Map {
|
||||
return m
|
||||
}
|
||||
|
||||
// RecordKeyInt converts `r` to a map[int]Record of which key is specified by `key`.
|
||||
// RecordKeyStr converts `r` to a map[string]Record of which key is specified by `key`.
|
||||
func (r Result) RecordKeyStr(key string) map[string]Record {
|
||||
m := make(map[string]Record)
|
||||
for _, item := range r {
|
||||
@ -189,5 +195,5 @@ func (r Result) RecordKeyUint(key string) map[uint]Record {
|
||||
// Structs converts `r` to struct slice.
|
||||
// Note that the parameter `pointer` should be type of *[]struct/*[]*struct.
|
||||
func (r Result) Structs(pointer interface{}) (err error) {
|
||||
return gconv.StructsTag(r.List(), pointer, OrmTagForStruct)
|
||||
return gconv.StructsTag(r, pointer, OrmTagForStruct)
|
||||
}
|
||||
|
||||
@ -1,57 +0,0 @@
|
||||
// Copyright GoFrame Author(https://goframe.org). All Rights Reserved.
|
||||
//
|
||||
// This Source Code Form is subject to the terms of the MIT License.
|
||||
// If a copy of the MIT was not distributed with this file,
|
||||
// You can obtain one at https://github.com/gogf/gf.
|
||||
|
||||
package gdb
|
||||
|
||||
// Deprecated, use Json instead.
|
||||
func (r Result) ToJson() string {
|
||||
return r.Json()
|
||||
}
|
||||
|
||||
// Deprecated, use Xml instead.
|
||||
func (r Result) ToXml(rootTag ...string) string {
|
||||
return r.Xml(rootTag...)
|
||||
}
|
||||
|
||||
// Deprecated, use List instead.
|
||||
func (r Result) ToList() List {
|
||||
return r.List()
|
||||
}
|
||||
|
||||
// Deprecated, use MapKeyStr instead.
|
||||
func (r Result) ToStringMap(key string) map[string]Map {
|
||||
return r.MapKeyStr(key)
|
||||
}
|
||||
|
||||
// Deprecated, use MapKetInt instead.
|
||||
func (r Result) ToIntMap(key string) map[int]Map {
|
||||
return r.MapKeyInt(key)
|
||||
}
|
||||
|
||||
// Deprecated, use MapKeyUint instead.
|
||||
func (r Result) ToUintMap(key string) map[uint]Map {
|
||||
return r.MapKeyUint(key)
|
||||
}
|
||||
|
||||
// Deprecated, use RecordKeyStr instead.
|
||||
func (r Result) ToStringRecord(key string) map[string]Record {
|
||||
return r.RecordKeyStr(key)
|
||||
}
|
||||
|
||||
// Deprecated, use RecordKetInt instead.
|
||||
func (r Result) ToIntRecord(key string) map[int]Record {
|
||||
return r.RecordKeyInt(key)
|
||||
}
|
||||
|
||||
// Deprecated, use RecordKetUint instead.
|
||||
func (r Result) ToUintRecord(key string) map[uint]Record {
|
||||
return r.RecordKeyUint(key)
|
||||
}
|
||||
|
||||
// Deprecated, use Structs instead.
|
||||
func (r Result) ToStructs(pointer interface{}) (err error) {
|
||||
return r.Structs(pointer)
|
||||
}
|
||||
@ -8,6 +8,7 @@ package gdb
|
||||
|
||||
import (
|
||||
"database/sql"
|
||||
"github.com/gogf/gf/errors/gcode"
|
||||
"github.com/gogf/gf/errors/gerror"
|
||||
"github.com/gogf/gf/text/gstr"
|
||||
"github.com/gogf/gf/util/gconv"
|
||||
@ -42,21 +43,21 @@ import (
|
||||
//
|
||||
// See the example or unit testing cases for clear understanding for this function.
|
||||
func (r Result) ScanList(listPointer interface{}, bindToAttrName string, relationKV ...string) (err error) {
|
||||
if r.IsEmpty() {
|
||||
return doScanList(nil, r, listPointer, bindToAttrName, relationKV...)
|
||||
}
|
||||
|
||||
// doScanList converts `result` to struct slice which contains other complex struct attributes recursively.
|
||||
// The parameter `model` is used for recursively scanning purpose, which means, it can scans the attribute struct/structs recursively but
|
||||
// it needs the Model for database accessing.
|
||||
// Note that the parameter `listPointer` should be type of *[]struct/*[]*struct.
|
||||
func doScanList(model *Model, result Result, listPointer interface{}, bindToAttrName string, relationKV ...string) (err error) {
|
||||
if result.IsEmpty() {
|
||||
return nil
|
||||
}
|
||||
// Necessary checks for parameters.
|
||||
if bindToAttrName == "" {
|
||||
return gerror.New(`bindToAttrName should not be empty`)
|
||||
return gerror.NewCode(gcode.CodeInvalidParameter, `bindToAttrName should not be empty`)
|
||||
}
|
||||
//if len(relation) > 0 {
|
||||
// if len(relation) < 2 {
|
||||
// return gerror.New(`relation name and key should are both necessary`)
|
||||
// }
|
||||
// if relation[0] == "" || relation[1] == "" {
|
||||
// return gerror.New(`relation name and key should not be empty`)
|
||||
// }
|
||||
//}
|
||||
|
||||
var (
|
||||
reflectValue = reflect.ValueOf(listPointer)
|
||||
@ -67,14 +68,14 @@ func (r Result) ScanList(listPointer interface{}, bindToAttrName string, relatio
|
||||
reflectKind = reflectValue.Kind()
|
||||
}
|
||||
if reflectKind != reflect.Ptr {
|
||||
return gerror.Newf("parameter should be type of *[]struct/*[]*struct, but got: %v", reflectKind)
|
||||
return gerror.NewCodef(gcode.CodeInvalidParameter, "listPointer should be type of *[]struct/*[]*struct, but got: %v", reflectKind)
|
||||
}
|
||||
reflectValue = reflectValue.Elem()
|
||||
reflectKind = reflectValue.Kind()
|
||||
if reflectKind != reflect.Slice && reflectKind != reflect.Array {
|
||||
return gerror.Newf("parameter should be type of *[]struct/*[]*struct, but got: %v", reflectKind)
|
||||
return gerror.NewCodef(gcode.CodeInvalidParameter, "listPointer should be type of *[]struct/*[]*struct, but got: %v", reflectKind)
|
||||
}
|
||||
length := len(r)
|
||||
length := len(result)
|
||||
if length == 0 {
|
||||
// The pointed slice is not empty.
|
||||
if reflectValue.Len() > 0 {
|
||||
@ -134,8 +135,9 @@ func (r Result) ScanList(listPointer interface{}, bindToAttrName string, relatio
|
||||
// uid:UserId
|
||||
relationResultFieldName = array[0]
|
||||
relationBindToSubAttrName = array[1]
|
||||
if key, _ := gutil.MapPossibleItemByKey(r[0].Map(), relationResultFieldName); key == "" {
|
||||
return gerror.Newf(
|
||||
if key, _ := gutil.MapPossibleItemByKey(result[0].Map(), relationResultFieldName); key == "" {
|
||||
return gerror.NewCodef(
|
||||
gcode.CodeInvalidParameter,
|
||||
`cannot find possible related table field name "%s" from given relation key "%s"`,
|
||||
relationResultFieldName,
|
||||
relationKVStr,
|
||||
@ -144,13 +146,14 @@ func (r Result) ScanList(listPointer interface{}, bindToAttrName string, relatio
|
||||
relationResultFieldName = key
|
||||
}
|
||||
} else {
|
||||
return gerror.New(`parameter relationKV should be format of "ResultFieldName:BindToAttrName"`)
|
||||
return gerror.NewCode(gcode.CodeInvalidParameter, `parameter relationKV should be format of "ResultFieldName:BindToAttrName"`)
|
||||
}
|
||||
if relationResultFieldName != "" {
|
||||
relationDataMap = r.MapKeyValue(relationResultFieldName)
|
||||
// Note that the value might be type of slice.
|
||||
relationDataMap = result.MapKeyValue(relationResultFieldName)
|
||||
}
|
||||
if len(relationDataMap) == 0 {
|
||||
return gerror.Newf(`cannot find the relation data map, maybe invalid relation given "%v"`, relationKV)
|
||||
return gerror.NewCodef(gcode.CodeInvalidParameter, `cannot find the relation data map, maybe invalid relation given "%v"`, relationKV)
|
||||
}
|
||||
}
|
||||
// Bind to target attribute.
|
||||
@ -163,11 +166,19 @@ func (r Result) ScanList(listPointer interface{}, bindToAttrName string, relatio
|
||||
)
|
||||
if arrayItemType.Kind() == reflect.Ptr {
|
||||
if bindToAttrField, ok = arrayItemType.Elem().FieldByName(bindToAttrName); !ok {
|
||||
return gerror.Newf(`invalid parameter bindToAttrName: cannot find attribute with name "%s" from slice element`, bindToAttrName)
|
||||
return gerror.NewCodef(
|
||||
gcode.CodeInvalidParameter,
|
||||
`invalid parameter bindToAttrName: cannot find attribute with name "%s" from slice element`,
|
||||
bindToAttrName,
|
||||
)
|
||||
}
|
||||
} else {
|
||||
if bindToAttrField, ok = arrayItemType.FieldByName(bindToAttrName); !ok {
|
||||
return gerror.Newf(`invalid parameter bindToAttrName: cannot find attribute with name "%s" from slice element`, bindToAttrName)
|
||||
return gerror.NewCodef(
|
||||
gcode.CodeInvalidParameter,
|
||||
`invalid parameter bindToAttrName: cannot find attribute with name "%s" from slice element`,
|
||||
bindToAttrName,
|
||||
)
|
||||
}
|
||||
}
|
||||
bindToAttrType = bindToAttrField.Type
|
||||
@ -209,7 +220,7 @@ func (r Result) ScanList(listPointer interface{}, bindToAttrName string, relatio
|
||||
relationFromAttrValue = arrayElemValue
|
||||
}
|
||||
if len(relationDataMap) > 0 && !relationFromAttrValue.IsValid() {
|
||||
return gerror.Newf(`invalid relation specified: "%v"`, relationKV)
|
||||
return gerror.NewCodef(gcode.CodeInvalidParameter, `invalid relation specified: "%v"`, relationKV)
|
||||
}
|
||||
// Check and find possible bind to attribute name.
|
||||
if relationKVStr != "" && !relationBindToSubAttrNameChecked {
|
||||
@ -223,7 +234,8 @@ func (r Result) ScanList(listPointer interface{}, bindToAttrName string, relatio
|
||||
filedMap[relationFromAttrType.Field(i).Name] = struct{}{}
|
||||
}
|
||||
if key, _ := gutil.MapPossibleItemByKey(filedMap, relationBindToSubAttrName); key == "" {
|
||||
return gerror.Newf(
|
||||
return gerror.NewCodef(
|
||||
gcode.CodeInvalidParameter,
|
||||
`cannot find possible related attribute name "%s" from given relation key "%s"`,
|
||||
relationBindToSubAttrName,
|
||||
relationKVStr,
|
||||
@ -239,18 +251,29 @@ func (r Result) ScanList(listPointer interface{}, bindToAttrName string, relatio
|
||||
if len(relationDataMap) > 0 {
|
||||
relationFromAttrField = relationFromAttrValue.FieldByName(relationBindToSubAttrName)
|
||||
if relationFromAttrField.IsValid() {
|
||||
if err = gconv.Structs(
|
||||
relationDataMap[gconv.String(relationFromAttrField.Interface())],
|
||||
bindToAttrValue.Addr(),
|
||||
); err != nil {
|
||||
results := make(Result, 0)
|
||||
for _, v := range relationDataMap[gconv.String(relationFromAttrField.Interface())].Slice() {
|
||||
results = append(results, v.(Record))
|
||||
}
|
||||
if err = results.Structs(bindToAttrValue.Addr()); err != nil {
|
||||
return err
|
||||
}
|
||||
// Recursively Scan.
|
||||
if model != nil {
|
||||
if err = model.doWithScanStructs(bindToAttrValue.Addr()); err != nil {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// May be the attribute does not exist yet.
|
||||
return gerror.Newf(`invalid relation specified: "%v"`, relationKV)
|
||||
return gerror.NewCodef(gcode.CodeInvalidParameter, `invalid relation specified: "%v"`, relationKV)
|
||||
}
|
||||
} else {
|
||||
return gerror.Newf(`relationKey should not be empty as field "%s" is slice`, bindToAttrName)
|
||||
return gerror.NewCodef(
|
||||
gcode.CodeInvalidParameter,
|
||||
`relationKey should not be empty as field "%s" is slice`,
|
||||
bindToAttrName,
|
||||
)
|
||||
}
|
||||
|
||||
case reflect.Ptr:
|
||||
@ -268,24 +291,36 @@ func (r Result) ScanList(listPointer interface{}, bindToAttrName string, relatio
|
||||
// There's no relational data.
|
||||
continue
|
||||
}
|
||||
if err = gconv.Struct(v, element); err != nil {
|
||||
return err
|
||||
if v.IsSlice() {
|
||||
if err = v.Slice()[0].(Record).Struct(element); err != nil {
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
if err = v.Val().(Record).Struct(element); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// May be the attribute does not exist yet.
|
||||
return gerror.Newf(`invalid relation specified: "%v"`, relationKV)
|
||||
return gerror.NewCodef(gcode.CodeInvalidParameter, `invalid relation specified: "%v"`, relationKV)
|
||||
}
|
||||
} else {
|
||||
if i >= len(r) {
|
||||
if i >= len(result) {
|
||||
// There's no relational data.
|
||||
continue
|
||||
}
|
||||
v := r[i]
|
||||
v := result[i]
|
||||
if v == nil {
|
||||
// There's no relational data.
|
||||
continue
|
||||
}
|
||||
if err = gconv.Struct(v, element); err != nil {
|
||||
if err = v.Struct(element); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
// Recursively Scan.
|
||||
if model != nil {
|
||||
if err = model.doWithScanStruct(element); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
@ -300,30 +335,42 @@ func (r Result) ScanList(listPointer interface{}, bindToAttrName string, relatio
|
||||
// There's no relational data.
|
||||
continue
|
||||
}
|
||||
if err = gconv.Struct(relationDataItem, bindToAttrValue); err != nil {
|
||||
return err
|
||||
if relationDataItem.IsSlice() {
|
||||
if err = relationDataItem.Slice()[0].(Record).Struct(bindToAttrValue); err != nil {
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
if err = relationDataItem.Val().(Record).Struct(bindToAttrValue); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// May be the attribute does not exist yet.
|
||||
return gerror.Newf(`invalid relation specified: "%v"`, relationKV)
|
||||
return gerror.NewCodef(gcode.CodeInvalidParameter, `invalid relation specified: "%v"`, relationKV)
|
||||
}
|
||||
} else {
|
||||
if i >= len(r) {
|
||||
if i >= len(result) {
|
||||
// There's no relational data.
|
||||
continue
|
||||
}
|
||||
relationDataItem := r[i]
|
||||
relationDataItem := result[i]
|
||||
if relationDataItem == nil {
|
||||
// There's no relational data.
|
||||
continue
|
||||
}
|
||||
if err = gconv.Struct(relationDataItem, bindToAttrValue); err != nil {
|
||||
if err = relationDataItem.Struct(bindToAttrValue); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
// Recursively Scan.
|
||||
if model != nil {
|
||||
if err = model.doWithScanStruct(bindToAttrValue); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
default:
|
||||
return gerror.Newf(`unsupported attribute type: %s`, bindToAttrKind.String())
|
||||
return gerror.NewCodef(gcode.CodeInvalidParameter, `unsupported attribute type: %s`, bindToAttrKind.String())
|
||||
}
|
||||
}
|
||||
reflect.ValueOf(listPointer).Elem().Set(arrayValue)
|
||||
|
||||
@ -18,9 +18,9 @@ import (
|
||||
|
||||
// MyDriver is a custom database driver, which is used for testing only.
|
||||
// For simplifying the unit testing case purpose, MyDriver struct inherits the mysql driver
|
||||
// gdb.DriverMysql and overwrites its function HandleSqlBeforeCommit.
|
||||
// So if there's any sql execution, it goes through MyDriver.HandleSqlBeforeCommit firstly and
|
||||
// then gdb.DriverMysql.HandleSqlBeforeCommit.
|
||||
// gdb.DriverMysql and overwrites its function DoCommit.
|
||||
// So if there's any sql execution, it goes through MyDriver.DoCommit firstly and
|
||||
// then gdb.DriverMysql.DoCommit.
|
||||
// You can call it sql "HOOK" or "HiJack" as your will.
|
||||
type MyDriver struct {
|
||||
*gdb.DriverMysql
|
||||
@ -41,11 +41,11 @@ func (d *MyDriver) New(core *gdb.Core, node *gdb.ConfigNode) (gdb.DB, error) {
|
||||
}, nil
|
||||
}
|
||||
|
||||
// HandleSqlBeforeCommit handles the sql before posts it to database.
|
||||
// DoCommit handles the sql before posts it to database.
|
||||
// It here overwrites the same method of gdb.DriverMysql and makes some custom changes.
|
||||
func (d *MyDriver) HandleSqlBeforeCommit(ctx context.Context, link gdb.Link, sql string, args []interface{}) (string, []interface{}) {
|
||||
func (d *MyDriver) DoCommit(ctx context.Context, link gdb.Link, sql string, args []interface{}) (newSql string, newArgs []interface{}, err error) {
|
||||
latestSqlString.Set(sql)
|
||||
return d.DriverMysql.HandleSqlBeforeCommit(ctx, link, sql, args)
|
||||
return d.DriverMysql.DoCommit(ctx, link, sql, args)
|
||||
}
|
||||
|
||||
func init() {
|
||||
|
||||
@ -7,6 +7,7 @@
|
||||
package gdb_test
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"github.com/gogf/gf/container/garray"
|
||||
"github.com/gogf/gf/frame/g"
|
||||
@ -29,9 +30,10 @@ const (
|
||||
)
|
||||
|
||||
var (
|
||||
db gdb.DB
|
||||
dbPrefix gdb.DB
|
||||
configNode gdb.ConfigNode
|
||||
db gdb.DB
|
||||
dbPrefix gdb.DB
|
||||
dbCtxStrict gdb.DB
|
||||
configNode gdb.ConfigNode
|
||||
)
|
||||
|
||||
func init() {
|
||||
@ -56,9 +58,15 @@ func init() {
|
||||
}
|
||||
nodePrefix := configNode
|
||||
nodePrefix.Prefix = TableNamePrefix1
|
||||
|
||||
nodeCtxStrict := configNode
|
||||
nodeCtxStrict.CtxStrict = true
|
||||
|
||||
gdb.AddConfigNode("test", configNode)
|
||||
gdb.AddConfigNode("prefix", nodePrefix)
|
||||
gdb.AddConfigNode("ctxstrict", nodeCtxStrict)
|
||||
gdb.AddConfigNode(gdb.DefaultGroupName, configNode)
|
||||
|
||||
// Default db.
|
||||
if r, err := gdb.New(); err != nil {
|
||||
gtest.Error(err)
|
||||
@ -87,6 +95,20 @@ func init() {
|
||||
gtest.Error(err)
|
||||
}
|
||||
dbPrefix.SetSchema(TestSchema1)
|
||||
|
||||
// CtxStrict db.
|
||||
if r, err := gdb.New("ctxstrict"); err != nil {
|
||||
gtest.Error(err)
|
||||
} else {
|
||||
dbCtxStrict = r
|
||||
}
|
||||
if _, err := dbCtxStrict.Ctx(context.TODO()).Exec(fmt.Sprintf(schemaTemplate, TestSchema1)); err != nil {
|
||||
gtest.Error(err)
|
||||
}
|
||||
if _, err := dbCtxStrict.Ctx(context.TODO()).Exec(fmt.Sprintf(schemaTemplate, TestSchema2)); err != nil {
|
||||
gtest.Error(err)
|
||||
}
|
||||
dbCtxStrict.SetSchema(TestSchema1)
|
||||
}
|
||||
|
||||
func createTable(table ...string) string {
|
||||
@ -111,7 +133,7 @@ func createTableWithDb(db gdb.DB, table ...string) (name string) {
|
||||
|
||||
switch configNode.Type {
|
||||
case "sqlite":
|
||||
if _, err := db.Exec(fmt.Sprintf(`
|
||||
if _, err := db.Ctx(context.TODO()).Exec(fmt.Sprintf(`
|
||||
CREATE TABLE %s (
|
||||
id bigint unsigned NOT NULL AUTO_INCREMENT,
|
||||
passport varchar(45),
|
||||
@ -124,7 +146,7 @@ func createTableWithDb(db gdb.DB, table ...string) (name string) {
|
||||
gtest.Fatal(err)
|
||||
}
|
||||
case "pgsql":
|
||||
if _, err := db.Exec(fmt.Sprintf(`
|
||||
if _, err := db.Ctx(context.TODO()).Exec(fmt.Sprintf(`
|
||||
CREATE TABLE %s (
|
||||
id bigint NOT NULL,
|
||||
passport varchar(45),
|
||||
@ -137,7 +159,7 @@ func createTableWithDb(db gdb.DB, table ...string) (name string) {
|
||||
gtest.Fatal(err)
|
||||
}
|
||||
case "mssql":
|
||||
if _, err := db.Exec(fmt.Sprintf(`
|
||||
if _, err := db.Ctx(context.TODO()).Exec(fmt.Sprintf(`
|
||||
IF NOT EXISTS (SELECT * FROM sysobjects WHERE name='%s' and xtype='U')
|
||||
CREATE TABLE %s (
|
||||
ID numeric(10,0) NOT NULL,
|
||||
@ -151,7 +173,7 @@ func createTableWithDb(db gdb.DB, table ...string) (name string) {
|
||||
gtest.Fatal(err)
|
||||
}
|
||||
case "oracle":
|
||||
if _, err := db.Exec(fmt.Sprintf(`
|
||||
if _, err := db.Ctx(context.TODO()).Exec(fmt.Sprintf(`
|
||||
CREATE TABLE %s (
|
||||
ID NUMBER(10) NOT NULL,
|
||||
PASSPORT VARCHAR(45) NOT NULL,
|
||||
@ -164,7 +186,7 @@ func createTableWithDb(db gdb.DB, table ...string) (name string) {
|
||||
gtest.Fatal(err)
|
||||
}
|
||||
case "mysql":
|
||||
if _, err := db.Exec(fmt.Sprintf(`
|
||||
if _, err := db.Ctx(context.TODO()).Exec(fmt.Sprintf(`
|
||||
CREATE TABLE %s (
|
||||
id int(10) unsigned NOT NULL AUTO_INCREMENT,
|
||||
passport varchar(45) NULL,
|
||||
@ -195,7 +217,7 @@ func createInitTableWithDb(db gdb.DB, table ...string) (name string) {
|
||||
})
|
||||
}
|
||||
|
||||
result, err := db.BatchInsert(name, array.Slice())
|
||||
result, err := db.Ctx(context.TODO()).Insert(name, array.Slice())
|
||||
gtest.AssertNil(err)
|
||||
|
||||
n, e := result.RowsAffected()
|
||||
@ -205,7 +227,7 @@ func createInitTableWithDb(db gdb.DB, table ...string) (name string) {
|
||||
}
|
||||
|
||||
func dropTableWithDb(db gdb.DB, table string) {
|
||||
if _, err := db.Exec(fmt.Sprintf("DROP TABLE IF EXISTS `%s`", table)); err != nil {
|
||||
if _, err := db.Ctx(context.TODO()).Exec(fmt.Sprintf("DROP TABLE IF EXISTS `%s`", table)); err != nil {
|
||||
gtest.Error(err)
|
||||
}
|
||||
}
|
||||
|
||||
@ -8,12 +8,83 @@ package gdb_test
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/gogf/gf/debug/gdebug"
|
||||
"github.com/gogf/gf/frame/g"
|
||||
"github.com/gogf/gf/os/gfile"
|
||||
"github.com/gogf/gf/test/gtest"
|
||||
"github.com/gogf/gf/text/gstr"
|
||||
"github.com/gogf/gf/util/gmeta"
|
||||
"testing"
|
||||
)
|
||||
|
||||
/*
|
||||
mysql> show tables;
|
||||
+----------------+
|
||||
| Tables_in_test |
|
||||
+----------------+
|
||||
| user |
|
||||
| user_detail |
|
||||
| user_score |
|
||||
+----------------+
|
||||
3 rows in set (0.01 sec)
|
||||
|
||||
mysql> select * from `user`;
|
||||
+----+--------+
|
||||
| id | name |
|
||||
+----+--------+
|
||||
| 1 | name_1 |
|
||||
| 2 | name_2 |
|
||||
| 3 | name_3 |
|
||||
| 4 | name_4 |
|
||||
| 5 | name_5 |
|
||||
+----+--------+
|
||||
5 rows in set (0.01 sec)
|
||||
|
||||
mysql> select * from `user_detail`;
|
||||
+-----+-----------+
|
||||
| uid | address |
|
||||
+-----+-----------+
|
||||
| 1 | address_1 |
|
||||
| 2 | address_2 |
|
||||
| 3 | address_3 |
|
||||
| 4 | address_4 |
|
||||
| 5 | address_5 |
|
||||
+-----+-----------+
|
||||
5 rows in set (0.00 sec)
|
||||
|
||||
mysql> select * from `user_score`;
|
||||
+----+-----+-------+
|
||||
| id | uid | score |
|
||||
+----+-----+-------+
|
||||
| 1 | 1 | 1 |
|
||||
| 2 | 1 | 2 |
|
||||
| 3 | 1 | 3 |
|
||||
| 4 | 1 | 4 |
|
||||
| 5 | 1 | 5 |
|
||||
| 6 | 2 | 1 |
|
||||
| 7 | 2 | 2 |
|
||||
| 8 | 2 | 3 |
|
||||
| 9 | 2 | 4 |
|
||||
| 10 | 2 | 5 |
|
||||
| 11 | 3 | 1 |
|
||||
| 12 | 3 | 2 |
|
||||
| 13 | 3 | 3 |
|
||||
| 14 | 3 | 4 |
|
||||
| 15 | 3 | 5 |
|
||||
| 16 | 4 | 1 |
|
||||
| 17 | 4 | 2 |
|
||||
| 18 | 4 | 3 |
|
||||
| 19 | 4 | 4 |
|
||||
| 20 | 4 | 5 |
|
||||
| 21 | 5 | 1 |
|
||||
| 22 | 5 | 2 |
|
||||
| 23 | 5 | 3 |
|
||||
| 24 | 5 | 4 |
|
||||
| 25 | 5 | 5 |
|
||||
+----+-----+-------+
|
||||
25 rows in set (0.00 sec)
|
||||
*/
|
||||
|
||||
func Test_Table_Relation_With_Scan(t *testing.T) {
|
||||
var (
|
||||
tableUser = "user"
|
||||
@ -297,6 +368,7 @@ PRIMARY KEY (id)
|
||||
gtest.Assert(err, nil)
|
||||
}
|
||||
}
|
||||
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
var users []*User
|
||||
err := db.With(User{}).
|
||||
@ -667,7 +739,143 @@ PRIMARY KEY (id)
|
||||
})
|
||||
}
|
||||
|
||||
func Test_Table_Relation_WithAll_Embedded(t *testing.T) {
|
||||
func Test_Table_Relation_WithAllCondition_List(t *testing.T) {
|
||||
var (
|
||||
tableUser = "user"
|
||||
tableUserDetail = "user_detail"
|
||||
tableUserScores = "user_scores"
|
||||
)
|
||||
if _, err := db.Exec(fmt.Sprintf(`
|
||||
CREATE TABLE IF NOT EXISTS %s (
|
||||
id int(10) unsigned NOT NULL AUTO_INCREMENT,
|
||||
name varchar(45) NOT NULL,
|
||||
PRIMARY KEY (id)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
|
||||
`, tableUser)); err != nil {
|
||||
gtest.Error(err)
|
||||
}
|
||||
defer dropTable(tableUser)
|
||||
|
||||
if _, err := db.Exec(fmt.Sprintf(`
|
||||
CREATE TABLE IF NOT EXISTS %s (
|
||||
uid int(10) unsigned NOT NULL AUTO_INCREMENT,
|
||||
address varchar(45) NOT NULL,
|
||||
PRIMARY KEY (uid)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
|
||||
`, tableUserDetail)); err != nil {
|
||||
gtest.Error(err)
|
||||
}
|
||||
defer dropTable(tableUserDetail)
|
||||
|
||||
if _, err := db.Exec(fmt.Sprintf(`
|
||||
CREATE TABLE IF NOT EXISTS %s (
|
||||
id int(10) unsigned NOT NULL AUTO_INCREMENT,
|
||||
uid int(10) unsigned NOT NULL,
|
||||
score int(10) unsigned NOT NULL,
|
||||
PRIMARY KEY (id)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
|
||||
`, tableUserScores)); err != nil {
|
||||
gtest.Error(err)
|
||||
}
|
||||
defer dropTable(tableUserScores)
|
||||
|
||||
type UserDetail struct {
|
||||
gmeta.Meta `orm:"table:user_detail"`
|
||||
Uid int `json:"uid"`
|
||||
Address string `json:"address"`
|
||||
}
|
||||
|
||||
type UserScores struct {
|
||||
gmeta.Meta `orm:"table:user_scores"`
|
||||
Id int `json:"id"`
|
||||
Uid int `json:"uid"`
|
||||
Score int `json:"score"`
|
||||
}
|
||||
|
||||
type User struct {
|
||||
gmeta.Meta `orm:"table:user"`
|
||||
Id int `json:"id"`
|
||||
Name string `json:"name"`
|
||||
UserDetail *UserDetail `orm:"with:uid=id, where:uid > 3"`
|
||||
UserScores []*UserScores `orm:"with:uid=id, where:score>1 and score<5, order:score desc"`
|
||||
}
|
||||
|
||||
// Initialize the data.
|
||||
var err error
|
||||
for i := 1; i <= 5; i++ {
|
||||
// User.
|
||||
_, err = db.Insert(tableUser, g.Map{
|
||||
"id": i,
|
||||
"name": fmt.Sprintf(`name_%d`, i),
|
||||
})
|
||||
gtest.Assert(err, nil)
|
||||
// Detail.
|
||||
_, err = db.Insert(tableUserDetail, g.Map{
|
||||
"uid": i,
|
||||
"address": fmt.Sprintf(`address_%d`, i),
|
||||
})
|
||||
gtest.Assert(err, nil)
|
||||
// Scores.
|
||||
for j := 1; j <= 5; j++ {
|
||||
_, err = db.Insert(tableUserScores, g.Map{
|
||||
"uid": i,
|
||||
"score": j,
|
||||
})
|
||||
gtest.Assert(err, nil)
|
||||
}
|
||||
}
|
||||
|
||||
db.SetDebug(true)
|
||||
defer db.SetDebug(false)
|
||||
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
var users []*User
|
||||
err := db.Model(tableUser).WithAll().Where("id", []int{3, 4}).Scan(&users)
|
||||
t.AssertNil(err)
|
||||
t.Assert(len(users), 2)
|
||||
t.Assert(users[0].Id, 3)
|
||||
t.Assert(users[0].Name, "name_3")
|
||||
t.Assert(users[0].UserDetail, nil)
|
||||
t.Assert(users[1].Id, 4)
|
||||
t.Assert(users[1].Name, "name_4")
|
||||
t.AssertNE(users[1].UserDetail, nil)
|
||||
t.Assert(users[1].UserDetail.Uid, 4)
|
||||
t.Assert(users[1].UserDetail.Address, "address_4")
|
||||
t.Assert(len(users[1].UserScores), 3)
|
||||
t.Assert(users[1].UserScores[0].Uid, 4)
|
||||
t.Assert(users[1].UserScores[0].Score, 4)
|
||||
t.Assert(users[1].UserScores[2].Uid, 4)
|
||||
t.Assert(users[1].UserScores[2].Score, 2)
|
||||
})
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
var users []User
|
||||
err := db.Model(tableUser).WithAll().Where("id", []int{3, 4}).Scan(&users)
|
||||
t.AssertNil(err)
|
||||
t.Assert(len(users), 2)
|
||||
t.Assert(users[0].Id, 3)
|
||||
t.Assert(users[0].Name, "name_3")
|
||||
t.Assert(users[0].UserDetail, nil)
|
||||
|
||||
t.Assert(len(users[0].UserScores), 3)
|
||||
t.Assert(users[0].UserScores[0].Uid, 3)
|
||||
t.Assert(users[0].UserScores[0].Score, 4)
|
||||
t.Assert(users[0].UserScores[2].Uid, 3)
|
||||
t.Assert(users[0].UserScores[2].Score, 2)
|
||||
|
||||
t.Assert(users[1].Id, 4)
|
||||
t.Assert(users[1].Name, "name_4")
|
||||
t.AssertNE(users[1].UserDetail, nil)
|
||||
t.Assert(users[1].UserDetail.Uid, 4)
|
||||
t.Assert(users[1].UserDetail.Address, "address_4")
|
||||
t.Assert(len(users[1].UserScores), 3)
|
||||
t.Assert(users[1].UserScores[0].Uid, 4)
|
||||
t.Assert(users[1].UserScores[0].Score, 4)
|
||||
t.Assert(users[1].UserScores[2].Uid, 4)
|
||||
t.Assert(users[1].UserScores[2].Score, 2)
|
||||
})
|
||||
}
|
||||
|
||||
func Test_Table_Relation_WithAll_Embedded_With_SelfMaintained_Attributes(t *testing.T) {
|
||||
var (
|
||||
tableUser = "user"
|
||||
tableUserDetail = "user_detail"
|
||||
@ -782,6 +990,245 @@ PRIMARY KEY (id)
|
||||
})
|
||||
}
|
||||
|
||||
func Test_Table_Relation_WithAll_Embedded_Without_SelfMaintained_Attributes(t *testing.T) {
|
||||
var (
|
||||
tableUser = "user"
|
||||
tableUserDetail = "user_detail"
|
||||
tableUserScores = "user_scores"
|
||||
)
|
||||
if _, err := db.Exec(fmt.Sprintf(`
|
||||
CREATE TABLE IF NOT EXISTS %s (
|
||||
id int(10) unsigned NOT NULL AUTO_INCREMENT,
|
||||
name varchar(45) NOT NULL,
|
||||
PRIMARY KEY (id)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
|
||||
`, tableUser)); err != nil {
|
||||
gtest.Error(err)
|
||||
}
|
||||
defer dropTable(tableUser)
|
||||
|
||||
if _, err := db.Exec(fmt.Sprintf(`
|
||||
CREATE TABLE IF NOT EXISTS %s (
|
||||
uid int(10) unsigned NOT NULL AUTO_INCREMENT,
|
||||
address varchar(45) NOT NULL,
|
||||
PRIMARY KEY (uid)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
|
||||
`, tableUserDetail)); err != nil {
|
||||
gtest.Error(err)
|
||||
}
|
||||
defer dropTable(tableUserDetail)
|
||||
|
||||
if _, err := db.Exec(fmt.Sprintf(`
|
||||
CREATE TABLE IF NOT EXISTS %s (
|
||||
id int(10) unsigned NOT NULL AUTO_INCREMENT,
|
||||
uid int(10) unsigned NOT NULL,
|
||||
score int(10) unsigned NOT NULL,
|
||||
PRIMARY KEY (id)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
|
||||
`, tableUserScores)); err != nil {
|
||||
gtest.Error(err)
|
||||
}
|
||||
defer dropTable(tableUserScores)
|
||||
|
||||
type UserDetail struct {
|
||||
gmeta.Meta `orm:"table:user_detail"`
|
||||
Uid int `json:"uid"`
|
||||
Address string `json:"address"`
|
||||
}
|
||||
|
||||
type UserScores struct {
|
||||
gmeta.Meta `orm:"table:user_scores"`
|
||||
Id int `json:"id"`
|
||||
Uid int `json:"uid"`
|
||||
Score int `json:"score"`
|
||||
}
|
||||
|
||||
// For Test Only
|
||||
type UserEmbedded struct {
|
||||
Id int `json:"id"`
|
||||
Name string `json:"name"`
|
||||
}
|
||||
|
||||
type User struct {
|
||||
gmeta.Meta `orm:"table:user"`
|
||||
*UserDetail `orm:"with:uid=id"`
|
||||
UserEmbedded
|
||||
UserScores []*UserScores `orm:"with:uid=id"`
|
||||
}
|
||||
|
||||
// Initialize the data.
|
||||
var err error
|
||||
for i := 1; i <= 5; i++ {
|
||||
// User.
|
||||
_, err = db.Insert(tableUser, g.Map{
|
||||
"id": i,
|
||||
"name": fmt.Sprintf(`name_%d`, i),
|
||||
})
|
||||
gtest.Assert(err, nil)
|
||||
// Detail.
|
||||
_, err = db.Insert(tableUserDetail, g.Map{
|
||||
"uid": i,
|
||||
"address": fmt.Sprintf(`address_%d`, i),
|
||||
})
|
||||
gtest.Assert(err, nil)
|
||||
// Scores.
|
||||
for j := 1; j <= 5; j++ {
|
||||
_, err = db.Insert(tableUserScores, g.Map{
|
||||
"uid": i,
|
||||
"score": j,
|
||||
})
|
||||
gtest.Assert(err, nil)
|
||||
}
|
||||
}
|
||||
db.SetDebug(true)
|
||||
defer db.SetDebug(false)
|
||||
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
var user *User
|
||||
err := db.Model(tableUser).WithAll().Where("id", 3).Scan(&user)
|
||||
t.AssertNil(err)
|
||||
t.Assert(user.Id, 3)
|
||||
t.AssertNE(user.UserDetail, nil)
|
||||
t.Assert(user.UserDetail.Uid, 3)
|
||||
t.Assert(user.UserDetail.Address, `address_3`)
|
||||
t.Assert(len(user.UserScores), 5)
|
||||
t.Assert(user.UserScores[0].Uid, 3)
|
||||
t.Assert(user.UserScores[0].Score, 1)
|
||||
t.Assert(user.UserScores[4].Uid, 3)
|
||||
t.Assert(user.UserScores[4].Score, 5)
|
||||
})
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
var user User
|
||||
err := db.Model(tableUser).WithAll().Where("id", 4).Scan(&user)
|
||||
t.AssertNil(err)
|
||||
t.Assert(user.Id, 4)
|
||||
t.AssertNE(user.UserDetail, nil)
|
||||
t.Assert(user.UserDetail.Uid, 4)
|
||||
t.Assert(user.UserDetail.Address, `address_4`)
|
||||
t.Assert(len(user.UserScores), 5)
|
||||
t.Assert(user.UserScores[0].Uid, 4)
|
||||
t.Assert(user.UserScores[0].Score, 1)
|
||||
t.Assert(user.UserScores[4].Uid, 4)
|
||||
t.Assert(user.UserScores[4].Score, 5)
|
||||
})
|
||||
}
|
||||
|
||||
func Test_Table_Relation_WithAll_Embedded_WithoutMeta(t *testing.T) {
|
||||
var (
|
||||
tableUser = "user"
|
||||
tableUserDetail = "user_detail"
|
||||
tableUserScores = "user_scores"
|
||||
)
|
||||
if _, err := db.Exec(fmt.Sprintf(`
|
||||
CREATE TABLE IF NOT EXISTS %s (
|
||||
id int(10) unsigned NOT NULL AUTO_INCREMENT,
|
||||
name varchar(45) NOT NULL,
|
||||
PRIMARY KEY (id)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
|
||||
`, tableUser)); err != nil {
|
||||
gtest.Error(err)
|
||||
}
|
||||
defer dropTable(tableUser)
|
||||
|
||||
if _, err := db.Exec(fmt.Sprintf(`
|
||||
CREATE TABLE IF NOT EXISTS %s (
|
||||
uid int(10) unsigned NOT NULL AUTO_INCREMENT,
|
||||
address varchar(45) NOT NULL,
|
||||
PRIMARY KEY (uid)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
|
||||
`, tableUserDetail)); err != nil {
|
||||
gtest.Error(err)
|
||||
}
|
||||
defer dropTable(tableUserDetail)
|
||||
|
||||
if _, err := db.Exec(fmt.Sprintf(`
|
||||
CREATE TABLE IF NOT EXISTS %s (
|
||||
id int(10) unsigned NOT NULL AUTO_INCREMENT,
|
||||
uid int(10) unsigned NOT NULL,
|
||||
score int(10) unsigned NOT NULL,
|
||||
PRIMARY KEY (id)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
|
||||
`, tableUserScores)); err != nil {
|
||||
gtest.Error(err)
|
||||
}
|
||||
defer dropTable(tableUserScores)
|
||||
|
||||
type UserDetailBase struct {
|
||||
Uid int `json:"uid"`
|
||||
Address string `json:"address"`
|
||||
}
|
||||
|
||||
type UserDetail struct {
|
||||
UserDetailBase
|
||||
}
|
||||
|
||||
type UserScores struct {
|
||||
Id int `json:"id"`
|
||||
Uid int `json:"uid"`
|
||||
Score int `json:"score"`
|
||||
}
|
||||
|
||||
type User struct {
|
||||
*UserDetail `orm:"with:uid=id"`
|
||||
Id int `json:"id"`
|
||||
Name string `json:"name"`
|
||||
UserScores []*UserScores `orm:"with:uid=id"`
|
||||
}
|
||||
|
||||
// Initialize the data.
|
||||
var err error
|
||||
for i := 1; i <= 5; i++ {
|
||||
// User.
|
||||
_, err = db.Insert(tableUser, g.Map{
|
||||
"id": i,
|
||||
"name": fmt.Sprintf(`name_%d`, i),
|
||||
})
|
||||
gtest.Assert(err, nil)
|
||||
// Detail.
|
||||
_, err = db.Insert(tableUserDetail, g.Map{
|
||||
"uid": i,
|
||||
"address": fmt.Sprintf(`address_%d`, i),
|
||||
})
|
||||
gtest.Assert(err, nil)
|
||||
// Scores.
|
||||
for j := 1; j <= 5; j++ {
|
||||
_, err = db.Insert(tableUserScores, g.Map{
|
||||
"uid": i,
|
||||
"score": j,
|
||||
})
|
||||
gtest.Assert(err, nil)
|
||||
}
|
||||
}
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
var user *User
|
||||
err := db.Model(tableUser).WithAll().Where("id", 3).Scan(&user)
|
||||
t.AssertNil(err)
|
||||
t.Assert(user.Id, 3)
|
||||
t.AssertNE(user.UserDetail, nil)
|
||||
t.Assert(user.UserDetail.Uid, 3)
|
||||
t.Assert(user.UserDetail.Address, `address_3`)
|
||||
t.Assert(len(user.UserScores), 5)
|
||||
t.Assert(user.UserScores[0].Uid, 3)
|
||||
t.Assert(user.UserScores[0].Score, 1)
|
||||
t.Assert(user.UserScores[4].Uid, 3)
|
||||
t.Assert(user.UserScores[4].Score, 5)
|
||||
})
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
var user User
|
||||
err := db.Model(tableUser).WithAll().Where("id", 4).Scan(&user)
|
||||
t.AssertNil(err)
|
||||
t.Assert(user.Id, 4)
|
||||
t.AssertNE(user.UserDetail, nil)
|
||||
t.Assert(user.UserDetail.Uid, 4)
|
||||
t.Assert(user.UserDetail.Address, `address_4`)
|
||||
t.Assert(len(user.UserScores), 5)
|
||||
t.Assert(user.UserScores[0].Uid, 4)
|
||||
t.Assert(user.UserScores[0].Score, 1)
|
||||
t.Assert(user.UserScores[4].Uid, 4)
|
||||
t.Assert(user.UserScores[4].Score, 5)
|
||||
})
|
||||
}
|
||||
|
||||
func Test_Table_Relation_WithAll_AttributeStructAlsoHasWithTag(t *testing.T) {
|
||||
var (
|
||||
tableUser = "user"
|
||||
@ -1189,3 +1636,358 @@ PRIMARY KEY (id)
|
||||
t.Assert(user.UserDetail.UserScores[4].Score, 5)
|
||||
})
|
||||
}
|
||||
|
||||
func Test_Table_Relation_With_MultipleDepends1(t *testing.T) {
|
||||
defer func() {
|
||||
dropTable("table_a")
|
||||
dropTable("table_b")
|
||||
dropTable("table_c")
|
||||
}()
|
||||
for _, v := range gstr.SplitAndTrim(gfile.GetContents(gdebug.TestDataPath("with_multiple_depends.sql")), ";") {
|
||||
if _, err := db.Exec(v); err != nil {
|
||||
gtest.Error(err)
|
||||
}
|
||||
}
|
||||
|
||||
type TableC struct {
|
||||
gmeta.Meta `orm:"table_c"`
|
||||
Id int `orm:"id,primary" json:"id"`
|
||||
TableBId int `orm:"table_b_id" json:"table_b_id"`
|
||||
}
|
||||
|
||||
type TableB struct {
|
||||
gmeta.Meta `orm:"table_b"`
|
||||
Id int `orm:"id,primary" json:"id"`
|
||||
TableAId int `orm:"table_a_id" json:"table_a_id"`
|
||||
TableC *TableC `orm:"with:table_b_id=id" json:"table_c"`
|
||||
}
|
||||
|
||||
type TableA struct {
|
||||
gmeta.Meta `orm:"table_a"`
|
||||
Id int `orm:"id,primary" json:"id"`
|
||||
TableB *TableB `orm:"with:table_a_id=id" json:"table_b"`
|
||||
}
|
||||
|
||||
db.SetDebug(true)
|
||||
defer db.SetDebug(false)
|
||||
|
||||
// Struct.
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
var tableA *TableA
|
||||
err := db.Model("table_a").WithAll().Scan(&tableA)
|
||||
//g.Dump(tableA)
|
||||
t.AssertNil(err)
|
||||
t.AssertNE(tableA, nil)
|
||||
t.Assert(tableA.Id, 1)
|
||||
|
||||
t.AssertNE(tableA.TableB, nil)
|
||||
t.AssertNE(tableA.TableB.TableC, nil)
|
||||
t.Assert(tableA.TableB.TableAId, 1)
|
||||
t.Assert(tableA.TableB.TableC.Id, 100)
|
||||
t.Assert(tableA.TableB.TableC.TableBId, 10)
|
||||
})
|
||||
|
||||
// Structs
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
var tableA []*TableA
|
||||
err := db.Model("table_a").WithAll().OrderAsc("id").Scan(&tableA)
|
||||
//g.Dump(tableA)
|
||||
t.AssertNil(err)
|
||||
t.Assert(len(tableA), 2)
|
||||
t.AssertNE(tableA[0].TableB, nil)
|
||||
t.AssertNE(tableA[1].TableB, nil)
|
||||
t.AssertNE(tableA[0].TableB.TableC, nil)
|
||||
t.AssertNE(tableA[1].TableB.TableC, nil)
|
||||
|
||||
t.Assert(tableA[0].Id, 1)
|
||||
t.Assert(tableA[0].TableB.Id, 10)
|
||||
t.Assert(tableA[0].TableB.TableC.Id, 100)
|
||||
|
||||
t.Assert(tableA[1].Id, 2)
|
||||
t.Assert(tableA[1].TableB.Id, 20)
|
||||
t.Assert(tableA[1].TableB.TableC.Id, 300)
|
||||
})
|
||||
}
|
||||
|
||||
func Test_Table_Relation_With_MultipleDepends2(t *testing.T) {
|
||||
defer func() {
|
||||
dropTable("table_a")
|
||||
dropTable("table_b")
|
||||
dropTable("table_c")
|
||||
}()
|
||||
for _, v := range gstr.SplitAndTrim(gfile.GetContents(gdebug.TestDataPath("with_multiple_depends.sql")), ";") {
|
||||
if _, err := db.Exec(v); err != nil {
|
||||
gtest.Error(err)
|
||||
}
|
||||
}
|
||||
|
||||
type TableC struct {
|
||||
gmeta.Meta `orm:"table_c"`
|
||||
Id int `orm:"id,primary" json:"id"`
|
||||
TableBId int `orm:"table_b_id" json:"table_b_id"`
|
||||
}
|
||||
|
||||
type TableB struct {
|
||||
gmeta.Meta `orm:"table_b"`
|
||||
Id int `orm:"id,primary" json:"id"`
|
||||
TableAId int `orm:"table_a_id" json:"table_a_id"`
|
||||
TableC []*TableC `orm:"with:table_b_id=id" json:"table_c"`
|
||||
}
|
||||
|
||||
type TableA struct {
|
||||
gmeta.Meta `orm:"table_a"`
|
||||
Id int `orm:"id,primary" json:"id"`
|
||||
TableB []*TableB `orm:"with:table_a_id=id" json:"table_b"`
|
||||
}
|
||||
|
||||
db.SetDebug(true)
|
||||
defer db.SetDebug(false)
|
||||
|
||||
// Struct.
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
var tableA *TableA
|
||||
err := db.Model("table_a").WithAll().Scan(&tableA)
|
||||
//g.Dump(tableA)
|
||||
t.AssertNil(err)
|
||||
t.AssertNE(tableA, nil)
|
||||
t.Assert(tableA.Id, 1)
|
||||
|
||||
t.Assert(len(tableA.TableB), 2)
|
||||
t.Assert(tableA.TableB[0].Id, 10)
|
||||
t.Assert(tableA.TableB[1].Id, 30)
|
||||
|
||||
t.Assert(len(tableA.TableB[0].TableC), 2)
|
||||
t.Assert(len(tableA.TableB[1].TableC), 1)
|
||||
t.Assert(tableA.TableB[0].TableC[0].Id, 100)
|
||||
t.Assert(tableA.TableB[0].TableC[0].TableBId, 10)
|
||||
t.Assert(tableA.TableB[0].TableC[1].Id, 200)
|
||||
t.Assert(tableA.TableB[0].TableC[1].TableBId, 10)
|
||||
t.Assert(tableA.TableB[1].TableC[0].Id, 400)
|
||||
t.Assert(tableA.TableB[1].TableC[0].TableBId, 30)
|
||||
})
|
||||
|
||||
// Structs
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
var tableA []*TableA
|
||||
err := db.Model("table_a").WithAll().OrderAsc("id").Scan(&tableA)
|
||||
//g.Dump(tableA)
|
||||
t.AssertNil(err)
|
||||
t.Assert(len(tableA), 2)
|
||||
|
||||
t.Assert(len(tableA[0].TableB), 2)
|
||||
t.Assert(tableA[0].TableB[0].Id, 10)
|
||||
t.Assert(tableA[0].TableB[1].Id, 30)
|
||||
|
||||
t.Assert(len(tableA[0].TableB[0].TableC), 2)
|
||||
t.Assert(len(tableA[0].TableB[1].TableC), 1)
|
||||
t.Assert(tableA[0].TableB[0].TableC[0].Id, 100)
|
||||
t.Assert(tableA[0].TableB[0].TableC[0].TableBId, 10)
|
||||
t.Assert(tableA[0].TableB[0].TableC[1].Id, 200)
|
||||
t.Assert(tableA[0].TableB[0].TableC[1].TableBId, 10)
|
||||
t.Assert(tableA[0].TableB[1].TableC[0].Id, 400)
|
||||
t.Assert(tableA[0].TableB[1].TableC[0].TableBId, 30)
|
||||
|
||||
t.Assert(tableA[1].TableB[0].TableC[0].Id, 300)
|
||||
t.Assert(tableA[1].TableB[0].TableC[0].TableBId, 20)
|
||||
|
||||
t.Assert(tableA[1].TableB[1].Id, 40)
|
||||
t.Assert(tableA[1].TableB[1].TableAId, 2)
|
||||
t.Assert(tableA[1].TableB[1].TableC, nil)
|
||||
})
|
||||
}
|
||||
|
||||
func Test_Table_Relation_With_MultipleDepends_Embedded(t *testing.T) {
|
||||
defer func() {
|
||||
dropTable("table_a")
|
||||
dropTable("table_b")
|
||||
dropTable("table_c")
|
||||
}()
|
||||
for _, v := range gstr.SplitAndTrim(gfile.GetContents(gdebug.TestDataPath("with_multiple_depends.sql")), ";") {
|
||||
if _, err := db.Exec(v); err != nil {
|
||||
gtest.Error(err)
|
||||
}
|
||||
}
|
||||
|
||||
type TableC struct {
|
||||
gmeta.Meta `orm:"table_c"`
|
||||
Id int `orm:"id,primary" json:"id"`
|
||||
TableBId int `orm:"table_b_id" json:"table_b_id"`
|
||||
}
|
||||
|
||||
type TableB struct {
|
||||
gmeta.Meta `orm:"table_b"`
|
||||
Id int `orm:"id,primary" json:"id"`
|
||||
TableAId int `orm:"table_a_id" json:"table_a_id"`
|
||||
*TableC `orm:"with:table_b_id=id" json:"table_c"`
|
||||
}
|
||||
|
||||
type TableA struct {
|
||||
gmeta.Meta `orm:"table_a"`
|
||||
Id int `orm:"id,primary" json:"id"`
|
||||
*TableB `orm:"with:table_a_id=id" json:"table_b"`
|
||||
}
|
||||
|
||||
db.SetDebug(true)
|
||||
defer db.SetDebug(false)
|
||||
|
||||
// Struct.
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
var tableA *TableA
|
||||
err := db.Model("table_a").WithAll().Scan(&tableA)
|
||||
//g.Dump(tableA)
|
||||
t.AssertNil(err)
|
||||
t.AssertNE(tableA, nil)
|
||||
t.Assert(tableA.Id, 1)
|
||||
|
||||
t.AssertNE(tableA.TableB, nil)
|
||||
t.AssertNE(tableA.TableB.TableC, nil)
|
||||
t.Assert(tableA.TableB.TableAId, 1)
|
||||
t.Assert(tableA.TableB.TableC.Id, 100)
|
||||
t.Assert(tableA.TableB.TableC.TableBId, 10)
|
||||
})
|
||||
|
||||
// Structs
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
var tableA []*TableA
|
||||
err := db.Model("table_a").WithAll().OrderAsc("id").Scan(&tableA)
|
||||
//g.Dump(tableA)
|
||||
t.AssertNil(err)
|
||||
t.Assert(len(tableA), 2)
|
||||
t.AssertNE(tableA[0].TableB, nil)
|
||||
t.AssertNE(tableA[1].TableB, nil)
|
||||
t.AssertNE(tableA[0].TableB.TableC, nil)
|
||||
t.AssertNE(tableA[1].TableB.TableC, nil)
|
||||
|
||||
t.Assert(tableA[0].Id, 1)
|
||||
t.Assert(tableA[0].TableB.Id, 10)
|
||||
t.Assert(tableA[0].TableB.TableC.Id, 100)
|
||||
|
||||
t.Assert(tableA[1].Id, 2)
|
||||
t.Assert(tableA[1].TableB.Id, 20)
|
||||
t.Assert(tableA[1].TableB.TableC.Id, 300)
|
||||
})
|
||||
}
|
||||
|
||||
func Test_Table_Relation_WithAll_Embedded_Meta_NameMatchingRule(t *testing.T) {
|
||||
var (
|
||||
tableUser = "user1"
|
||||
tableUserDetail = "user_detail1"
|
||||
tableUserScores = "user_scores1"
|
||||
)
|
||||
if _, err := db.Exec(fmt.Sprintf(`
|
||||
CREATE TABLE IF NOT EXISTS %s (
|
||||
id int(10) unsigned NOT NULL AUTO_INCREMENT,
|
||||
name varchar(45) NOT NULL,
|
||||
PRIMARY KEY (id)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
|
||||
`, tableUser)); err != nil {
|
||||
gtest.Error(err)
|
||||
}
|
||||
defer dropTable(tableUser)
|
||||
|
||||
if _, err := db.Exec(fmt.Sprintf(`
|
||||
CREATE TABLE IF NOT EXISTS %s (
|
||||
user_id int(10) unsigned NOT NULL,
|
||||
address varchar(45) NOT NULL,
|
||||
PRIMARY KEY (user_id)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
|
||||
`, tableUserDetail)); err != nil {
|
||||
gtest.Error(err)
|
||||
}
|
||||
defer dropTable(tableUserDetail)
|
||||
|
||||
if _, err := db.Exec(fmt.Sprintf(`
|
||||
CREATE TABLE IF NOT EXISTS %s (
|
||||
id int(10) unsigned NOT NULL AUTO_INCREMENT,
|
||||
user_id int(10) unsigned NOT NULL,
|
||||
score int(10) unsigned NOT NULL,
|
||||
PRIMARY KEY (id)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
|
||||
`, tableUserScores)); err != nil {
|
||||
gtest.Error(err)
|
||||
}
|
||||
defer dropTable(tableUserScores)
|
||||
|
||||
type UserDetail struct {
|
||||
gmeta.Meta `orm:"table:user_detail1"`
|
||||
UserID int `json:"user_id"`
|
||||
Address string `json:"address"`
|
||||
}
|
||||
|
||||
type UserScores struct {
|
||||
gmeta.Meta `orm:"table:user_scores1"`
|
||||
ID int `json:"id"`
|
||||
UserID int `json:"user_id"`
|
||||
Score int `json:"score"`
|
||||
}
|
||||
|
||||
// For Test Only
|
||||
type UserEmbedded struct {
|
||||
ID int `json:"id"`
|
||||
Name string `json:"name"`
|
||||
}
|
||||
|
||||
type User struct {
|
||||
gmeta.Meta `orm:"table:user1"`
|
||||
UserEmbedded
|
||||
UserDetail UserDetail `orm:"with:user_id=id"`
|
||||
UserScores []*UserScores `orm:"with:user_id=id"`
|
||||
}
|
||||
|
||||
// Initialize the data.
|
||||
var err error
|
||||
for i := 1; i <= 5; i++ {
|
||||
// User.
|
||||
_, err = db.Insert(tableUser, g.Map{
|
||||
"id": i,
|
||||
"name": fmt.Sprintf(`name_%d`, i),
|
||||
})
|
||||
gtest.AssertNil(err)
|
||||
// Detail.
|
||||
_, err = db.Insert(tableUserDetail, g.Map{
|
||||
"user_id": i,
|
||||
"address": fmt.Sprintf(`address_%d`, i),
|
||||
})
|
||||
gtest.AssertNil(err)
|
||||
// Scores.
|
||||
for j := 1; j <= 5; j++ {
|
||||
_, err = db.Insert(tableUserScores, g.Map{
|
||||
"user_id": i,
|
||||
"score": j,
|
||||
})
|
||||
gtest.AssertNil(err)
|
||||
}
|
||||
}
|
||||
|
||||
db.SetDebug(true)
|
||||
defer db.SetDebug(false)
|
||||
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
var user *User
|
||||
err := db.Model(tableUser).WithAll().Where("id", 3).Scan(&user)
|
||||
t.AssertNil(err)
|
||||
t.Assert(user.ID, 3)
|
||||
t.AssertNE(user.UserDetail, nil)
|
||||
t.Assert(user.UserDetail.UserID, 3)
|
||||
t.Assert(user.UserDetail.Address, `address_3`)
|
||||
t.Assert(len(user.UserScores), 5)
|
||||
t.Assert(user.UserScores[0].UserID, 3)
|
||||
t.Assert(user.UserScores[0].Score, 1)
|
||||
t.Assert(user.UserScores[4].UserID, 3)
|
||||
t.Assert(user.UserScores[4].Score, 5)
|
||||
})
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
var user User
|
||||
err := db.Model(tableUser).WithAll().Where("id", 4).Scan(&user)
|
||||
t.AssertNil(err)
|
||||
t.Assert(user.ID, 4)
|
||||
t.AssertNE(user.UserDetail, nil)
|
||||
t.Assert(user.UserDetail.UserID, 4)
|
||||
t.Assert(user.UserDetail.Address, `address_4`)
|
||||
t.Assert(len(user.UserScores), 5)
|
||||
t.Assert(user.UserScores[0].UserID, 4)
|
||||
t.Assert(user.UserScores[0].Score, 1)
|
||||
t.Assert(user.UserScores[4].UserID, 4)
|
||||
t.Assert(user.UserScores[4].Score, 5)
|
||||
})
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user